// This file is part of Friendiqa // https://git.friendi.ca/lubuwest/Friendiqa // Copyright (C) 2020 Marco R. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // In addition, as a special exception, the copyright holders give // permission to link the code of portions of this program with the // OpenSSL library under certain conditions as described in each // individual source file, and distribute linked combinations including // the two. // // You must obey the GNU General Public License in all respects for all // of the code used other than OpenSSL. If you modify file(s) with this // exception, you may extend this exception to your version of the // file(s), but you are not obligated to do so. If you do not wish to do // so, delete this exception statement from your version. If you delete // this exception statement from all source files in the program, then // also delete it here. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . import QtQuick 6.3 import QtCore 6.3 import QtQuick.Dialogs 6.3 import QtQuick.Controls 6.3 import QtQuick.Layouts 1.12 import QtQml.Models 6.3 import "qrc:/js/service.js" as Service import "qrc:/js/helper.js" as Helperjs import "qrc:/qml/configqml" import "qrc:/qml/genericqml" Page{ id:accountPage width: root.width height: root.height property var users:[] property var userdata: ({}) property string imagestoredir: "" property var appdata: ({}) function setServericon(server){ if ((server!=null) && (server!="")){ xhr.setUrl(server); xhr.setApi("/api/statusnet/config"); xhr.clearParams(); xhr.get(); } } function verify(userconfig){ Helperjs.friendicaRequest(userconfig,"/api/v1/accounts/verify_credentials",root,function(obj){ accountBusy.running=false; try{var credentials=JSON.parse(obj); if (credentials.hasOwnProperty('error')){print("error "+credentials.error); Helperjs.showMessage(qsTr("Error"),qsTr("Wrong password or 2FA enabled!"),root) } else{ if (users.length==0){Service.setDefaultOptions(db);} if (userconfig.hasOwnProperty("APIVersion")){userconfig.password=""} if (imagestoredir==""){ imagestoredir=filesystem.homePath+"/"+credentials.username+"/"; userconfig.imagestore=imagestoredir } if(userconfig.imagestore == filesystem.homePath+"/"+credentials.username+"/") {filesystem.makePath(filesystem.homePath+"/"+credentials.username);} filesystem.Directory=imagestoredir; filesystem.makeDir("contacts"); filesystem.makeDir("albums"); userconfig.accountId=credentials.id; userconfig.username=credentials.username; Service.storeConfig(db,userconfig); Service.readConfig(db,function(userconfig){ Helperjs.readData(db,"config","",function(storedUsers){ storedUsers.sort(function(obj1, obj2) { return obj1.isActive - obj2.isActive; }); accountPage.users=storedUsers}); //reset values login=userconfig; news=[]; contactlist=[]; rootstack.currentIndex=0; newstypeSignal("refresh"); },"isActive",0); Helperjs.showMessage(qsTr("Success"),qsTr("Name")+": "+credentials.display_name+"\nScreen Name: "+credentials.username,root) rootstackView.pop() } }catch(e){Helperjs.showMessage(qsTr("Error"),qsTr("Wrong password or 2FA enabled!"+e),root)}; }) } BusyIndicator{ id: accountBusy anchors.centerIn: parent width: 5*root.fontFactor*osSettings.bigFontSize height: 5*root.fontFactor*osSettings.bigFontSize running: false } ColumnLayout{ x: root.fontFactor*osSettings.bigFontSize width: root.width - 2*mm y: root.fontFactor*osSettings.bigFontSize spacing: root.fontFactor*osSettings.bigFontSize Row{ spacing:0.5*mm height: userButton.height width: parent.width MButton{ id:userButton text:qsTr("User") font.pointSize: osSettings.bigFontSize visible: users.length>0 onClicked:{ var useritems=""; for (var i=0;i0 text: "-" font.pointSize: osSettings.bigFontSize onClicked:{ var userconfig={server: servername.text, username: username.text, password: Qt.btoa(password.text)}; Service.readConfig(db,function(user){ if(userdata.token!=""){xhr.setUrl(servername.text); xhr.setApi("/oauth/revoke"); xhr.clearParams(); xhr.setParam("client_id",user.client.client_id); xhr.setParam("client_secret",user.client.client_secret); xhr.setParam("token",user.token); xhr.post(); } },"username",username.text); Service.deleteConfig(db,userconfig,function(){ filesystem.Directory=imagestore.text+"contacts"; filesystem.rmDir(); filesystem.Directory=imagestore.text+"albums"; filesystem.rmDir(); servername.text="https://"; servericon.visible=false; servericon.source=""; username.text=""; password.text=""; imagestore.text=""; userButton.text=qsTr("User"); Helperjs.readData(db,"config","",function(storedUsers){ storedUsers.sort(function(obj1, obj2) { return obj1.isActive - obj2.isActive; }) accountPage.users=storedUsers;}) accountPage.state="new_oauth" }) }} MButton { visible: users.length>0 text: "+" font.pointSize: osSettings.bigFontSize onClicked:{ servername.text="https://" servericon.visible=false; servericon.source=""; username.text="" password.text="" imagestore.text="" userButton.text=qsTr("User") accountPage.state="new_oauth" } } MButton { text: "?" font.pointSize: osSettings.bigFontSize onClicked:{ rootstackView.push("qrc:/qml/configqml/InfoBox.qml"); } } MButton { text: "\uf150" font.family: fontAwesome.name font.pointSize: osSettings.bigFontSize Menu { id:authMethodMenu width: 10*root.fontFactor*osSettings.systemFontSize MenuItem { text: qsTr("OAuth") font.pointSize: osSettings.systemFontSize font.bold:accountPage.state=="oauth" onTriggered: {accountPage.state="oauth"} } MenuItem { text: qsTr("Password") font.pointSize: osSettings.systemFontSize font.bold:accountPage.state=="password" onTriggered: {accountPage.state="password"} } } onClicked: {authMethodMenu.popup()} } MButton{ id:closeButton visible: users.length>0 text: "\uf057" font.pointSize: osSettings.bigFontSize onClicked:{rootstackView.pop()} } } Row{ spacing:0.5*mm height: 3*root.fontFactor*osSettings.bigFontSize width: parent.width Image{ id:servericon width:2.5*root.fontFactor*osSettings.bigFontSize; height: 2.5*root.fontFactor*osSettings.bigFontSize visible: false source:"" property var serverconfig:({}) MouseArea{ anchors.fill:parent onClicked:{ let serverConfigString="import QtQuick 2.0; import QtQuick.Dialogs 6.3; MessageDialog{ visible: true; title:'Server';buttons: MessageDialog.Ok;text: 'Name: "+ servericon.serverconfig.site.name+"\nLanguage: "+servericon.serverconfig.site.language+ "\nEmail: "+servericon.serverconfig.site.email+"\nTimezone: "+servericon.serverconfig.site.timezone+"\nClosed: "+servericon.serverconfig.site.closed+ "\nText limit: "+servericon.serverconfig.site.textlimit+"\nShort Url length: "+servericon.serverconfig.site.shorturllength+ "\nFriendica version: "+servericon.serverconfig.site.friendica.FRIENDICA_VERSION+ "\nDB Update version: "+servericon.serverconfig.site.friendica.DB_UPDATE_VERSION+"'}"; var serverconfigObject=Qt.createQmlObject(serverConfigString,accountPage,"serverconfigOutput"); } } } FontLoader{id: fontAwesome; source: "qrc:/images/fontawesome-webfont.ttf"} MButton{ id:serverSearchButton width: 3*root.fontFactor*osSettings.bigFontSize; height: 2.5*root.fontFactor*osSettings.bigFontSize //text:"\uf002" icon.name: "search" font.pointSize: osSettings.bigFontSize visible: servericon.visible?false:true onClicked:{Qt.openUrlExternally(Qt.resolvedUrl("https://dir.friendica.social/servers"))} } // ComboBox{ // id: servername // x: 4*root.fontFactor*osSettings.bigFontSize // y: 3.5*root.fontFactor*osSettings.bigFontSize // width: root.width-5*root.fontFactor*osSettings.bigFontSize // height: 2.5*root.fontFactor*osSettings.bigFontSize//5*mm; // font.pointSize: osSettings.systemFontSize // editable:true // model: serverModel // onAccepted: { // let cleanText =currentText;if(currentText==""){cleanText=editText} // if((cleanText).substring(0,8) !=="https://"){ // cleanText="https://"+cleanText // } // if (find(cleanText) === -1) { // serverModel.append({text: cleanText}) // currentIndex = find(cleanText) // displayText=cleanText // } // if (cleanText!=""){accountPage.setServericon(cleanText)} // } // onFocusChanged: { // if(focus==false){ // onAccepted() // } // } // } TextField { id: servername width: root.width-5*root.fontFactor*osSettings.bigFontSize height: 2.5*root.fontFactor*osSettings.bigFontSize font.pointSize: osSettings.systemFontSize text:"https://" onFocusChanged:{ if (focus){servermenu.open()} else{ if((servername.text).substring(0,11) =="https://http"){ servername.text= (servername.text).substring(8) } if (servername.text!="https://"){ accountPage.setServericon(servername.text)} } } } Menu { id:servermenu width: 13*root.fontFactor*osSettings.bigFontSize Instantiator{ model:serverModel MenuItem{ text: modelData onTriggered: {servername.text=modelData} } onObjectAdded:{servermenu.insertItem(index,object)} onObjectRemoved:{servermenu.removeItem(object)} } } ListModel{id:serverModel ListElement{text:"https://anonsys.net"} ListElement{text:"https://asaps-sm.lafayettegroup.com"} ListElement{text:"https://f.freinetz.ch"} ListElement{text:"https://friendica.chilemasto.casa"} ListElement{text:"https://friendica.eskimo.com"} ListElement{text:"https://friendica.me"} ListElement{text:"https://friendica.opensocial.space"} ListElement{text:"https://friendica.utzer.de"} ListElement{text:"https://friendica.vrije-mens.org"} ListElement{text:"https://libranet.de"} ListElement{text:"https://loma.ml"} ListElement{text:"https://nerdica.net"} ListElement{text:"https://nsfw.wnymathguy.com"} ListElement{text:"https://opensocial.at"} ListElement{text:"https://poliverso.org"} ListElement{text:"https://social.isurf.ca"} ListElement{text:"https://social.trom.tf"} ListElement{text:"https://squeet.me"} ListElement{text:"https://venera.social"} } } MButton { id: ruleButton width: parent.width visible: (osSettings.osType=="Android") && (userButton.text== qsTr("User")) height: 2*root.fontFactor*osSettings.bigFontSize; text: qsTr("Instance rules") font.pointSize: osSettings.bigFontSize onClicked:{ xhr.setUrl(servername.text); xhr.setApi("/api/v1/instance/rules"); xhr.clearParams(); xhr.get(); } } TextField { id: username width: root.width-5*root.fontFactor*osSettings.bigFontSize height: 2.5*root.fontFactor*osSettings.bigFontSize; Layout.leftMargin: 3*root.fontFactor*osSettings.bigFontSize; font.pointSize: osSettings.systemFontSize visible: (osSettings.osType=="Android")?(text!= ""):true placeholderText: qsTr("Nickname") selectByMouse: true onEditingFinished: { if (username.text.indexOf('@')>-1){ Helperjs.showMessage(qsTr("Error"),qsTr("Nicknames containing @ symbol currently not supported"),accountPage) } imagestoredir=filesystem.homePath+"/"+username.text+"/" } } TextField { id: password width: root.width-9*mm; height: 2.5*root.fontFactor*osSettings.bigFontSize; Layout.leftMargin: 3*root.fontFactor*osSettings.bigFontSize; font.pointSize: osSettings.systemFontSize visible: (osSettings.osType=="Android")?(userButton.text!= qsTr("User")):true selectByMouse: true echoMode: TextInput.Password placeholderText: qsTr("Password") inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhSensitiveData } Row{ spacing:0.5*mm height: 3*root.fontFactor*osSettings.bigFontSize width: parent.width Label { id: imagedirlabel visible: imagestore.text!="" text: qsTr("Image dir.") font.pointSize: osSettings.systemFontSize } TextField { id: imagestore width: root.width-17*mm; height: 2.5*root.fontFactor*osSettings.bigFontSize; visible:imagestore.text!="" font.pointSize: osSettings.systemFontSize selectByMouse: true text: "" wrapMode: TextEdit.NoWrap onTextChanged: imagestoredir=imagestore.text } MButton { visible:imagestore.text!="" text: "..." font.pointSize: osSettings.bigFontSize onClicked:{imagestoreDialog.open()} } FolderDialog { id: imagestoreDialog title: "Please choose a directory" currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] //selectFolder: true onAccepted: { var imagestoreString=imagestoreDialog.selectedFolder.toString(); imagestoreString=imagestoreString.replace(/^(file:\/{2})/,"")+"/" imagestore.text=imagestoreString } } } MButton { id:confirmationOAuth width: parent.width text: qsTr("Connect") font.pointSize: osSettings.bigFontSize visible: (osSettings.osType=="Android")?userButton.text!= qsTr("User"):true onClicked:{ if (servername.text==""){Helperjs.showMessage(qsTr("Error"), qsTr("No server given!"),root)} else{ xhr.setUrl(servername.text); xhr.setApi("/api/v1/apps"); xhr.clearParams(); if (osSettings.osType=="Android"){ xhr.setParam("client_name","Friendiqa-Android"); } else { xhr.setParam("client_name","Friendiqa-"+filesystem.hostname); } xhr.setParam("redirect_uris","http://127.0.0.1:1337/"); xhr.setParam("scopes","read write follow push"); xhr.setParam("website","https://friendiqa.ma-nic.de"); xhr.post(); } } } Connections{ target: xhr function onSuccess(text,api){ if(api=="/api/v1/instance/rules"){ let rulestext=""; let rulesarray=JSON.parse(text) for (let rule in rulesarray){ rulestext=rulestext+rulesarray[rule].text+"\n" } var component = Qt.createComponent("qrc:/qml/configqml/AcceptRules.qml"); var rulesdialog = component.createObject(accountPage,{"rules": rulestext}); rulesdialog.open(); } else if(api=="/api/statusnet/config"){ try{let serverdata = JSON.parse(text); servericon.visible=true; servericon.source=serverdata.site.logo; servericon.serverconfig=serverdata; } catch(e){print(e)} } else if (api=="/api/v1/apps"){ let app=JSON.parse(text); accountPage.appdata=app; oauth2.setClientId(app.client_id); oauth2.setClientSecret(app.client_secret); oauth2.setServer(servername.text); oauth2.grant(); } } function onError(text,api){ print(api + " Error "+ text) } } Connections{ target: oauth2 function onSuccess(text){ var userconfig={server: servername.displayText, username:"", password:"", imagestore: imagestoredir,interval:"",token: text,client:Qt.btoa(JSON.stringify(appdata))} verify(userconfig) } function onError(text){ Helperjs.showMessage(qsTr("Error"), qsTr("Couldn't connect to server"),root) print ("oauth2 onerror "+text) } } MButton { id:confirmation width: 10*root.fontFactor*osSettings.bigFontSize; text: qsTr("Confirm") font.pointSize: osSettings.bigFontSize visible: false// (osSettings.osType=="Android")?userButton.text!= qsTr("User"):true onClicked:{ accountBusy.running=true; var userconfig={server: servername.displayText, username: username.text, password:Qt.btoa(password.text), imagestore:imagestoredir,interval:""}; var errormessage=""; if (servername.text==""){errormessage=qsTr("No server given! ")} else if (username.text==""){errormessage+=qsTr("No nickname given! ")} else if (password.text=="") {errormessage+=qsTr("No password given! ")} else if (imagestoredir=="") {errormessage+=qsTr("No image directory given!")} else {errormessage=""} if (errormessage=="") {verify(userconfig)} else {Helperjs.showMessage(qsTr("Error"), errormessage,root)} }} MButton { id: setDefault width: 10*root.fontFactor*osSettings.bigFontSize; text: qsTr("Set as default") font.pointSize: osSettings.bigFontSize visible: false onClicked:{ accountBusy.running=true; let users=updatenews.getAccounts("username",username.text) Service.storeConfig(db,users[0]); Service.readConfig(db,function(userconfig){ //reset values login=userconfig; news=[]; contactlist=[]; rootstack.currentIndex=0; newstypeSignal("refresh"); },"isActive",0); Helperjs.showMessage(qsTr("Success"),"Screen Name: "+users[0].username,root) rootstackView.pop() } } } states: [ State { name: "new_oauth" PropertyChanges {target: username; visible: false } PropertyChanges {target: password; visible: false} PropertyChanges {target: ruleButton; visible: true} }, State { name:"oauth" PropertyChanges {target: username; visible: false} PropertyChanges {target: password; visible: false} PropertyChanges {target: confirmationOAuth; visible: true} PropertyChanges {target: setDefault; visible: true} PropertyChanges {target: confirmation; visible: false} }, State{ name:"password" PropertyChanges {target: username; visible: true } PropertyChanges {target: password; visible: true} PropertyChanges {target: confirmation; visible: true} PropertyChanges {target: confirmationOAuth; visible: false} } ] Component.onCompleted: { //print("filesystem.osType " +filesystem.osType) try{Helperjs.readData(db,"config","",function(storedUsers){ storedUsers.sort(function(obj1, obj2) { return obj1.isActive - obj2.isActive; }) accountPage.users=storedUsers; Service.readConfig(db,function(obj){ if (obj==null){ accountPage.state="new_oauth" } else{ userButton.text=obj.username; servername.text=obj.server; serverModel.insert(0,{text:obj.server}) accountPage.setServericon(obj.server); username.text= obj.username; password.text=Qt.atob(obj.password); imagestore.text=obj.imagestore; imagestoredir=obj.imagestore; if( obj.isActive==0){userButton.font.bold='true'} else {userButton.font.bold='false'} if(obj.password!=""){accountPage.state="password"} else if (obj.token!=""){accountPage.state="oauth"} else {accountPage.state="new_oauth"} } },"isActive",0) })} catch (e){//print("onCompleted" +users.count +e) } } }