diff --git a/ISOconfig.sh b/ISOconfig.sh index 728b605..ef8dd24 100755 --- a/ISOconfig.sh +++ b/ISOconfig.sh @@ -166,7 +166,7 @@ done echo 'SEND ipfstryme to oasis' # Add your bootstrap Pub here ./jaklis.py -n "https://g1.data.le-sou.org" send -d 2jQUH4HfHxdTesjCjvMCx1VJgA5AnpuvrWRq1swfRdsS -t "ipfstryme" -f ~/.zen/ipfs/.${IPFSNODEID}/tryme.addr -./jaklis.py -n "https://data.gchange.fr send" -d 2jQUH4HfHxdTesjCjvMCx1VJgA5AnpuvrWRq1swfRdsS -t "ipfstryme" -f ~/.zen/ipfs/.${IPFSNODEID}/tryme.addr +./jaklis.py -n "https://data.gchange.fr" send -d 2jQUH4HfHxdTesjCjvMCx1VJgA5AnpuvrWRq1swfRdsS -t "ipfstryme" -f ~/.zen/ipfs/.${IPFSNODEID}/tryme.addr ######################################################################## echo 'INSTALL Astroport cron_MINUTE' diff --git a/zen/cesium_IPFS_swarm.sh b/zen/cesium_IPFS_swarm.sh index 3e3e074..7baf7b6 100755 --- a/zen/cesium_IPFS_swarm.sh +++ b/zen/cesium_IPFS_swarm.sh @@ -57,12 +57,18 @@ do echo $ip >> ~/.zen/A_allow_ip.txt # Get its ipfsnodeid ipfsnodeid=$(echo "$peer" | awk -F '/' '{print $7}') - - # 3. ADD liking_me friend_of_mine to my swarm & bootstrap - ipfs swarm connect $peer; - ipfs bootstrap add $peer; + echo $ipfsnodeid >> ~/.zen/A_allow_ipfsid.txt + + g1id=$(~/.zen/astrXbian/zen/tools/ipfs_to_g1.py "$ipfsnodeid") + if [[ "$g1id" == "$friend_of_mine" ]]; then + # 3. ADD liking_me friend_of_mine to my swarm & bootstrap + ipfs swarm connect $peer; + ipfs bootstrap add $peer; - ipfsadd=$((ipfsadd+1)) + ipfsadd=$((ipfsadd+1)) + else + echo "$friend_of_mine spoofing is happening" + fi done; #g1id=$(~/.zen/astrXbian/zen/tools/ipfs_to_g1.py "$ipfsnodeid") diff --git a/zen/ipfs_SWARM_refresh.sh b/zen/ipfs_SWARM_refresh.sh index ff63376..3dda3f1 100755 --- a/zen/ipfs_SWARM_refresh.sh +++ b/zen/ipfs_SWARM_refresh.sh @@ -46,9 +46,10 @@ do echo "$nowdate - $id - $ip" foundIp=$(cat ~/.zen/A_allow_ip.txt | grep "$ip") + foundIpfs=$(cat ~/.zen/A_allow_ipfsid.txt | grep "$ipfsnodeid") isLAN=$(echo $ip | cut -f3 -d '/' | grep -E "(^127\.)|(^192\.168\.)|(^fd42\:)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1$)|(^[fF][cCdD])/") - if [[ ! $foundIp && ! $isLAN ]] ; then + if [[ ! $foundIpfs && ! $isLAN ]] ; then echo "${ip} of peer ${id} is not in the authorized ip list." echo "${peer} will be removed from the swarm" @@ -56,10 +57,12 @@ do ipfs bootstrap rm $peer echo "# FAIL2BAN # $USER must activate no password sudo (Rpi & Xbian OK)" - [[ $USER == "pi" || $USER == "xbian" ]] && echo "BAN $ip" \ - && sudo fail2ban-client add recidive \ - && sudo fail2ban-client set recidive banip $ip - + if [[ $USER == "pi" || $USER == "xbian" ]]; then + echo "BAN $ip ($ipfsnodeid)" + sudo fail2ban-client add recidive + sudo fail2ban-client start recidive + sudo fail2ban-client set recidive banip $ip + fi else echo "${peer}" echo "REFRESH /ipns/$ipfsnodeid INTO ~/.zen/ipfs_swarm/" diff --git a/zen/jaklis/.env.template b/zen/jaklis/.env.template index 5063635..dd81446 100644 --- a/zen/jaklis/.env.template +++ b/zen/jaklis/.env.template @@ -1,4 +1,6 @@ DUNIKEY="" # Chemin de la clé privé Ḡ1 de l'émetteur, au format PubSec #POD="https://g1.data.duniter.fr" # Adresse du pod Cesium ou Gchange à utiliser POD="https://g1.data.le-sou.org" # Adresse du pod Cesium de secours -#POD="https://data.gchange.fr" # Adresse du pod ḠChange à utiliser \ No newline at end of file +#POD="https://data.gchange.fr" # Adresse du pod ḠChange à utiliser + +NODE="https://g1.librelois.fr/gva" diff --git a/zen/jaklis/README.md b/zen/jaklis/README.md index 740f446..59f1e61 100644 --- a/zen/jaklis/README.md +++ b/zen/jaklis/README.md @@ -20,10 +20,18 @@ Renseignez optionnellement le fichier **.env** (Généré lors de la première t ``` ``` -usage: jaklis.py [-h] [-v] [-k KEY] [-n NODE] {read,send,delete,get,set,erase,like,unlike} ... +usage: jaklis.py [-h] [-v] [-k KEY] [-n NODE] {read,send,delete,get,set,erase,like,unlike,pay,history,balance} ... -positional arguments: - {read,send,delete,get,set,erase,like,unlike} +Client CLI pour Cesium+ et Ḡchange + +optional arguments: + -h, --help show this help message and exit + -v, --version Affiche la version actuelle du programme + -k KEY, --key KEY Chemin vers mon trousseau de clé (PubSec) + -n NODE, --node NODE Adresse du noeud Cesium+, Gchange ou Duniter à utiliser + +Commandes de jaklis: + {read,send,delete,get,set,erase,like,unlike,pay,history,balance} read Lecture des messages send Envoi d'un message delete Supression d'un message @@ -32,12 +40,10 @@ positional arguments: erase Effacer son profile Cesium+ like Voir les likes d'un profile / Liker un profile (option -s NOTE) unlike Supprimer un like + pay Payer en Ḡ1 + history Voir l'historique des transactions d'un compte Ḡ1 + balance Voir le solde d'un compte Ḡ1 -optional arguments: - -h, --help show this help message and exit - -v, --version Affiche la version actuelle du programme - -k KEY, --key KEY Chemin vers mon trousseau de clé (PubSec) - -n NODE, --node NODE Adresse du noeud Cesium+ ou Gchange à utiliser ``` Utilisez `./jaklis CMD -h` où `CMD` est la commande souhaité pour obtenir l'aide détaillé de cette commande. diff --git a/zen/jaklis/jaklis.py b/zen/jaklis/jaklis.py index 950e631..0f40166 100755 --- a/zen/jaklis/jaklis.py +++ b/zen/jaklis/jaklis.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 -import argparse, sys, os, random, string, getpass, json +import argparse, sys, os, getpass, string, random from os.path import join, dirname from shutil import copyfile from dotenv import load_dotenv from duniterpy.key import SigningKey -from lib.cesium import ReadFromCesium, SendToCesium, DeleteFromCesium, Profiles -from lib.likes import ReadLikes, SendLikes, UnLikes -VERSION = "0.0.1" +__version__ = "0.0.2" + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' # Get variables environment -if not os.path.isfile('.env'): - copyfile(".env.template", ".env") -dotenv_path = join(dirname(__file__), '.env') +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') load_dotenv(dotenv_path) # Parse arguments parser = argparse.ArgumentParser(description="Client CLI pour Cesium+ et Ḡchange") parser.add_argument('-v', '--version', action='store_true', help="Affiche la version actuelle du programme") parser.add_argument('-k', '--key', help="Chemin vers mon trousseau de clé (PubSec)") -parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+ ou Gchange à utiliser") +parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+, Gchange ou Duniter à utiliser") subparsers = parser.add_subparsers(title="Commandes de jaklis", dest="cmd") read_cmd = subparsers.add_parser('read', help="Lecture des messages") @@ -31,6 +31,9 @@ setProfile_cmd = subparsers.add_parser('set', help="Configurer son profile Cesiu eraseProfile_cmd = subparsers.add_parser('erase', help="Effacer son profile Cesium+") like_cmd = subparsers.add_parser('like', help="Voir les likes d'un profile / Liker un profile (option -s NOTE)") unlike_cmd = subparsers.add_parser('unlike', help="Supprimer un like") +pay_cmd = subparsers.add_parser('pay', help="Payer en Ḡ1") +history_cmd = subparsers.add_parser('history', help="Voir l'historique des transactions d'un compte Ḡ1") +balance_cmd = subparsers.add_parser('balance', help="Voir le solde d'un compte Ḡ1") # Messages management read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") @@ -63,17 +66,32 @@ like_cmd.add_argument('-p', '--profile', help="Profile cible") like_cmd.add_argument('-s', '--stars', type=int, help="Nombre d'étoile") unlike_cmd.add_argument('-p', '--profile', help="Profile à déliker") +# GVA usage +pay_cmd.add_argument('-p', '--pubkey', help="Destinataire du paiement") +pay_cmd.add_argument('-a', '--amount', type=float, help="Montant de la transaction") +pay_cmd.add_argument('-c', '--comment', default="", help="Commentaire de la transaction") +pay_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") +pay_cmd.add_argument('-v', '--verbose', action='store_true', help="Affiche le résultat JSON de la transaction") + +history_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +history_cmd.add_argument('-j', '--json', action='store_true', help="Affiche le résultat en format JSON") +history_cmd.add_argument('--nocolors', action='store_true', help="Affiche le résultat en noir et blanc") + +balance_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +balance_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") + + args = parser.parse_args() cmd = args.cmd +if args.version: + print(__version__) + sys.exit(0) + if not cmd: parser.print_help() sys.exit(1) -if args.version: - print(VERSION) - sys.exit(0) - def createTmpDunikey(): # Generate pseudo-random nonce nonce=[] @@ -87,79 +105,121 @@ def createTmpDunikey(): return keyPath -if args.node: - pod = args.node -else: - pod = os.getenv('POD') -if not pod: - pod="https://g1.data.le-sou.org" +# Check if we need dunikey +try: + pubkey = args.pubkey +except: + pubkey = False +try: + profile = args.profile +except: + profile = False -if args.key: - dunikey = args.key +if cmd in ('history','balance','get') and (pubkey or profile): + noNeedDunikey = True keyPath = False + try: + dunikey = args.pubkey + except: + dunikey = args.profile else: - dunikey = os.getenv('DUNIKEY') - if not dunikey: - keyPath = createTmpDunikey() - dunikey = keyPath - else: + noNeedDunikey = False + if args.key: + dunikey = args.key keyPath = False -if not os.path.isfile(dunikey): - HOME = os.getenv("HOME") - dunikey = HOME + dunikey - if not os.path.isfile(dunikey): - sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) - sys.exit(1) - - -# Build cesiumMessaging class -if cmd == "read": - messages = ReadFromCesium(dunikey, pod) - messages.read(args.number, args.outbox, args.json) -elif cmd == "send": - if args.fichier: - with open(args.fichier, 'r') as f: - msgT = f.read() - titre = msgT.splitlines(True)[0].replace('\n', '') - msg = ''.join(msgT.splitlines(True)[1:]) - if args.titre: - titre = args.titre - msg = msgT - elif args.titre and args.message: - titre = args.titre - msg = args.message else: - titre = input("Indiquez le titre du message: ") - msg = input("Indiquez le contenu du message: ") + dunikey = os.getenv('DUNIKEY') + if not dunikey: + keyPath = createTmpDunikey() + dunikey = keyPath + else: + keyPath = False + if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) - messages = SendToCesium(dunikey, pod, args.destinataire, args.outbox) - messages.send(titre, msg) -elif cmd == "delete": - messages = DeleteFromCesium(dunikey, pod, args.outbox) - messages.delete(args.id[0]) +# Construct CesiumPlus object +if cmd in ("read","send","delete","set","get","erase","like","unlike"): + from lib.cesium import CesiumPlus -# Build cesium+ profiles class -elif cmd in ('set','get','erase'): - cesium = Profiles(dunikey, pod) - if cmd == "set": + if args.node: + pod = args.node + else: + pod = os.getenv('POD') + if not pod: + pod="https://g1.data.le-sou.org" + + cesium = CesiumPlus(dunikey, pod, noNeedDunikey) + + # Messaging + if cmd == "read": + cesium.read(args.number, args.outbox, args.json) + elif cmd == "send": + if args.fichier: + with open(args.fichier, 'r') as f: + msgT = f.read() + titre = msgT.splitlines(True)[0].replace('\n', '') + msg = ''.join(msgT.splitlines(True)[1:]) + if args.titre: + titre = args.titre + msg = msgT + elif args.titre and args.message: + titre = args.titre + msg = args.message + else: + titre = input("Indiquez le titre du message: ") + msg = input("Indiquez le contenu du message: ") + + cesium.send(titre, msg, args.destinataire, args.outbox) + + elif cmd == "delete": + cesium.delete(args.id[0], args.outbox) + + # Profiles + elif cmd == "set": cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site, args.avatar) elif cmd == "get": cesium.get(args.profile, args.avatar) elif cmd == "erase": cesium.erase() -# Build cesium+ likes class -elif cmd == "like": - if args.stars or args.stars == 0: - gchange = SendLikes(dunikey, pod) - gchange.like(args.stars, args.profile) + # Likes + elif cmd == "like": + if args.stars or args.stars == 0: + cesium.like(args.stars, args.profile) + else: + cesium.readLikes(args.profile) + elif cmd == "unlike": + cesium.unLike(args.profile) + +# Construct GVA object +elif cmd in ("pay","history","balance"): + from lib.gva import GvaApi + + if args.node: + node = args.node else: - gchange = ReadLikes(dunikey, pod) - gchange.readLikes(args.profile) -elif cmd == "unlike": - gchange = UnLikes(dunikey, pod) - gchange.unLike(args.profile) + node = os.getenv('NODE') + if not node: + node="https://g1.librelois.fr/gva" + + if args.pubkey: + destPubkey = args.pubkey + else: + destPubkey = False + + gva = GvaApi(dunikey, node, destPubkey, noNeedDunikey) + + if cmd == "pay": + gva.pay(args.amount, args.comment, args.mempool, args.verbose) + if cmd == "history": + gva.history(args.json, args.nocolors) + if cmd == "balance": + gva.balance(args.mempool) if keyPath: diff --git a/zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc b/zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc index f7aebdb..45f50fc 100644 Binary files a/zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc and b/zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc b/zen/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc new file mode 100644 index 0000000..922d2dd Binary files /dev/null and b/zen/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/gva.cpython-36.pyc b/zen/jaklis/lib/__pycache__/gva.cpython-36.pyc new file mode 100644 index 0000000..5429345 Binary files /dev/null and b/zen/jaklis/lib/__pycache__/gva.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc b/zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc new file mode 100644 index 0000000..e1fdfb5 Binary files /dev/null and b/zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/likes.cpython-36.pyc b/zen/jaklis/lib/__pycache__/likes.cpython-36.pyc index 523efcd..bd85f7a 100644 Binary files a/zen/jaklis/lib/__pycache__/likes.cpython-36.pyc and b/zen/jaklis/lib/__pycache__/likes.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/messaging.cpython-36.pyc b/zen/jaklis/lib/__pycache__/messaging.cpython-36.pyc new file mode 100644 index 0000000..bbbea04 Binary files /dev/null and b/zen/jaklis/lib/__pycache__/messaging.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/natools.cpython-36.pyc b/zen/jaklis/lib/__pycache__/natools.cpython-36.pyc index 86d4e07..f2963fb 100644 Binary files a/zen/jaklis/lib/__pycache__/natools.cpython-36.pyc and b/zen/jaklis/lib/__pycache__/natools.cpython-36.pyc differ diff --git a/zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc b/zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc new file mode 100644 index 0000000..c142556 Binary files /dev/null and b/zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc differ diff --git a/zen/jaklis/lib/cesium.py b/zen/jaklis/lib/cesium.py index 62d846b..6a07fd9 100644 --- a/zen/jaklis/lib/cesium.py +++ b/zen/jaklis/lib/cesium.py @@ -1,540 +1,54 @@ -#!/usr/bin/env python3 +import re, string, random, base64 +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX +from lib.messaging import ReadFromCesium, SendToCesium, DeleteFromCesium +from lib.profiles import Profiles +from lib.likes import ReadLikes, SendLikes, UnLikes -import os, sys, ast, requests, json, base58, base64, time, string, random, re -from lib.natools import fmt, sign, get_privkey, box_decrypt, box_encrypt -from time import sleep -from hashlib import sha256 -from datetime import datetime -from termcolor import colored +class CesiumPlus(CesiumCommon): -PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" - -def pp_json(json_thing, sort=True, indents=4): - # Print beautifull JSON - if type(json_thing) is str: - print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) - else: - print(json.dumps(json_thing, sort_keys=sort, indent=indents)) - return None - -class ReadFromCesium: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.recipient = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.recipient) or len(self.recipient) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - - # Configure JSON document to send - def configDoc(self, nbrMsg, outbox): - boxType = "issuer" if outbox else "recipient" - - data = {} - data['sort'] = { "time": "desc" } - data['from'] = 0 - data['size'] = nbrMsg - data['_source'] = ['issuer','recipient','title','content','time','nonce','read_signature'] - data['query'] = {} - data['query']['bool'] = {} - data['query']['bool']['filter'] = {} - data['query']['bool']['filter']['term'] = {} - data['query']['bool']['filter']['term'][boxType] = self.recipient - - document = json.dumps(data) - return document - - def sendDocument(self, nbrMsg, outbox): - boxType = "outbox" if outbox else "inbox" - - document = self.configDoc(nbrMsg, outbox) - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get JSON result - result = requests.post('{0}/message/{1}/_search'.format(self.pod, boxType), headers=headers, data=document) - if result.status_code == 200: - return result.json()["hits"] - else: - sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text) - - # Parse JSON result and display messages - def readMessages(self, msgJSON, nbrMsg, outbox): - def decrypt(msg): - msg64 = base64.b64decode(msg) - return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() - - # Get terminal size - rows = int(os.popen('stty size', 'r').read().split()[1]) - - totalMsg = msgJSON["total"] - if nbrMsg > totalMsg: - nbrMsg = totalMsg - - if totalMsg == 0: - print(colored("Aucun message à afficher.", 'yellow')) - return True - else: - infoTotal = " Nombre de messages: " + str(nbrMsg) + "/" + str(totalMsg) + " " - print(colored(infoTotal.center(rows, '#'), "yellow")) - for hits in msgJSON["hits"]: - self.idMsg = hits["_id"] - msgSrc = hits["_source"] - self.issuer = msgSrc["issuer"] - nonce = msgSrc["nonce"] - nonce = base58.b58decode(nonce) - self.dateS = msgSrc["time"] - date = datetime.fromtimestamp(self.dateS).strftime(", le %d/%m/%Y à %H:%M ") - if outbox: - startHeader = " À " + msgSrc["recipient"] - else: - startHeader = " De " + self.issuer - headerMsg = startHeader + date + "(ID: {})".format(self.idMsg) + " " - - print('-'.center(rows, '-')) - print(colored(headerMsg, "blue").center(rows+9, '-')) - print('-'.center(rows, '-')) - try: - self.title = decrypt(msgSrc["title"]) - self.content = decrypt(msgSrc["content"]) - except Exception as e: - sys.stderr.write(colored(str(e), 'red') + '\n') - pp_json(hits) - continue - print('\033[1m' + self.title + '\033[0m') - print(self.content) - - print(colored(infoTotal.center(rows, '#'), "yellow")) - - # Parse JSON result and display messages - def jsonMessages(self, msgJSON, nbrMsg, outbox): - def decrypt(msg): - msg64 = base64.b64decode(msg) - return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() - - totalMsg = msgJSON["total"] - if nbrMsg > totalMsg: - nbrMsg = totalMsg - - if totalMsg == 0: - print("Aucun message à afficher") - return True - else: - data = [] - # data.append({}) - # data[0]['total'] = totalMsg - for i, hits in enumerate(msgJSON["hits"]): - self.idMsg = hits["_id"] - msgSrc = hits["_source"] - self.issuer = msgSrc["issuer"] - nonce = msgSrc["nonce"] - nonce = base58.b58decode(nonce) - self.date = msgSrc["time"] - - if outbox: - pubkey = msgSrc["recipient"] - else: - pubkey = self.issuer - - try: - self.title = decrypt(msgSrc["title"]) - self.content = decrypt(msgSrc["content"]) - except Exception as e: - sys.stderr.write(colored(str(e), 'red') + '\n') - pp_json(hits) - continue - - data.append(i) - data[i] = {} - data[i]['id'] = self.idMsg - data[i]['date'] = self.date - data[i]['pubkey'] = pubkey - data[i]['title'] = self.title - data[i]['content'] = self.content - - data = json.dumps(data, indent=2) - return data + #################### Messaging #################### def read(self, nbrMsg, outbox, isJSON): - jsonMsg = self.sendDocument(nbrMsg, outbox) + readCesium = ReadFromCesium(self.dunikey, self.pod) + jsonMsg = readCesium.sendDocument(nbrMsg, outbox) if isJSON: - jsonFormat = self.jsonMessages(jsonMsg, nbrMsg, outbox) + jsonFormat = readCesium.jsonMessages(jsonMsg, nbrMsg, outbox) print(jsonFormat) else: - self.readMessages(jsonMsg, nbrMsg, outbox) + readCesium.readMessages(jsonMsg, nbrMsg, outbox) - - - -#################### Sending class #################### - - - - -class SendToCesium: - def __init__(self, dunikey, pod, recipient, outbox): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - self.recipient = recipient - self.outbox = outbox + def send(self, title, msg, recipient, outbox): + sendCesium = SendToCesium(self.dunikey, self.pod) + sendCesium.recipient = recipient # Generate pseudo-random nonce nonce=[] for _ in range(32): nonce.append(random.choice(string.ascii_letters + string.digits)) - self.nonce = base64.b64decode(''.join(nonce)) + sendCesium.nonce = base64.b64decode(''.join(nonce)) - if not re.match(PUBKEY_REGEX, recipient) or len(recipient) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) + finalDoc = sendCesium.configDoc(sendCesium.encryptMsg(title), sendCesium.encryptMsg(msg)) # Configure JSON document to send + sendCesium.sendDocument(finalDoc, outbox) # Send final signed document - - def encryptMsg(self, msg): - return fmt["64"](box_encrypt(msg.encode(), get_privkey(self.dunikey, "pubsec"), self.recipient, self.nonce)).decode() - - def configDoc(self, title, msg): - b58nonce = base58.b58encode(self.nonce).decode() - - # Get current timestamp - timeSent = int(time.time()) - - # Generate custom JSON - data = {} - data['issuer'] = self.issuer - data['recipient'] = self.recipient - data['title'] = title - data['content'] = msg - data['time'] = timeSent - data['nonce'] = b58nonce - data['version'] = 2 - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - finalDoc = '{' + '"hash":"{0}","signature":"{1}",'.format(hashDoc, signature) + document[1:] - - return finalDoc - - - def sendDocument(self, document): - boxType = "outbox" if self.outbox else "inbox" - - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get result - try: - result = requests.post('{0}/message/{1}?pubkey={2}'.format(self.pod, boxType, self.recipient), headers=headers, data=document) - except Exception as e: - sys.stderr.write("Impossible d'envoyer le message:\n" + str(e)) - sys.exit(1) - else: - if result.status_code == 200: - print(colored("Message envoyé avec succès !", "green")) - print("ID: " + result.text) - return result - else: - sys.stderr.write("Erreur inconnue:" + '\n') - print(str(pp_json(result.text)) + '\n') - - def send(self, title, msg): - finalDoc = self.configDoc(self.encryptMsg(title), self.encryptMsg(msg)) # Configure JSON document to send - self.sendDocument(finalDoc) # Send final signed document - - - - -#################### Deleting class #################### - - - - -class DeleteFromCesium: - def __init__(self, dunikey, pod, outbox): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - self.outbox = outbox - - - def configDoc(self, idMsg): - # Get current timestamp - timeSent = int(time.time()) - - boxType = "outbox" if self.outbox else "inbox" - - # Generate document to customize - data = {} - data['version'] = 2 - data['index'] = "message" - data['type'] = boxType - data['id'] = idMsg - data['issuer'] = self.issuer - data['time'] = timeSent - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - data = {} - data['hash'] = hashDoc - data['signature'] = signature - signJSON = json.dumps(data) - finalJSON = {**json.loads(signJSON), **json.loads(document)} - finalDoc = json.dumps(finalJSON) - - return finalDoc - - def sendDocument(self, document, idMsg): - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get result - try: - result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) - if result.status_code == 404: - raise ValueError("Message introuvable") - elif result.status_code == 403: - raise ValueError("Vous n'êtes pas l'auteur de ce message.") - except Exception as e: - sys.stderr.write(colored("Impossible de supprimer le message {0}:\n".format(idMsg), 'red') + str(e) + "\n") - return False - else: - if result.status_code == 200: - print(colored("Message {0} supprimé avec succès !".format(idMsg), "green")) - return result - else: - sys.stderr.write("Erreur inconnue.") - - def delete(self, idsMsgList): + def delete(self, idsMsgList, outbox): + deleteCesium = DeleteFromCesium(self.dunikey, self.pod) + # deleteCesium.issuer = recipient for idMsg in idsMsgList: - finalDoc = self.configDoc(idMsg) - self.sendDocument(finalDoc, idMsg) - - - - - -#################### Profile class #################### - - - - - -class Profiles: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.pubkey = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - - # Configure JSON document SET to send - def configDocSet(self, name, description, city, address, pos, socials, avatar): - timeSent = int(time.time()) - - data = {} - if name: data['title'] = name - if description: data['description'] = description - if address: data['address'] = address - if city: data['city'] = city - if pos: - geoPoint = {} - geoPoint['lat'] = pos[0] - geoPoint['lon'] = pos[1] - data['geoPoint'] = geoPoint - if socials: - data['socials'] = [] - data['socials'].append({}) - data['socials'][0]['type'] = "web" - data['socials'][0]['url'] = socials - if avatar: - avatar = open(avatar, 'rb').read() - avatar = base64.b64encode(avatar).decode() - data['avatar'] = {} - data['avatar']['_content'] = avatar - data['avatar']['_content_type'] = "image/png" - data['time'] = timeSent - data['issuer'] = self.pubkey - data['version'] = 2 - data['tags'] = [] - - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - data = {} - data['hash'] = hashDoc - data['signature'] = signature - signJSON = json.dumps(data) - finalJSON = {**json.loads(signJSON), **json.loads(document)} - finalDoc = json.dumps(finalJSON) - - return finalDoc - - # Configure JSON document GET to send - def configDocGet(self, profile, scope='title', getAvatar=None): - - if getAvatar: - avatar = "avatar" - else: - avatar = "avatar._content_type" - - data = { - "query": { - "bool": { - "should":[ - { - "match":{ - scope:{ - "query": profile,"boost":2 - } - } - },{ - "prefix": {scope: profile} - } - ] - } - },"highlight": { - "fields": { - "title":{}, - "tags":{} - } - },"from":0, - "size":100, - "_source":["title", avatar,"description","city","address","socials.url","creationTime","membersCount","type"], - "indices_boost":{"user":100,"page":1,"group":0.01 - } - } - - document = json.dumps(data) - - return document - - # Configure JSON document SET to send - def configDocErase(self): - timeSent = int(time.time()) - - data = {} - data['time'] = timeSent - data['id'] = self.pubkey - data['issuer'] = self.pubkey - data['version'] = 2 - data['index'] = "user" - data['type'] = "profile" - - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - data = {} - data['hash'] = hashDoc - data['signature'] = signature - signJSON = json.dumps(data) - finalJSON = {**json.loads(signJSON), **json.loads(document)} - finalDoc = json.dumps(finalJSON) - - return finalDoc - - def sendDocument(self, document, type): - - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get JSON result - if type == 'set': - reqQuery = '{0}/user/profile?pubkey={1}/_update?pubkey={1}'.format(self.pod, self.pubkey) - elif type == 'get': - reqQuery = '{0}/user,page,group/profile,record/_search'.format(self.pod) - elif type == 'erase': - reqQuery = '{0}/history/delete'.format(self.pod) - - result = requests.post(reqQuery, headers=headers, data=document) - if result.status_code == 200: - # print(result.text) - return result.text - else: - sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') - - def parseJSON(self, doc): - doc = json.loads(doc)['hits']['hits'] - if doc: - pubkey = { "pubkey": doc[0]['_id'] } - rest = doc[0]['_source'] - final = {**pubkey, **rest} - else: - final = 'Profile vide' - - return json.dumps(final, indent=2) + finalDoc = deleteCesium.configDoc(idMsg, outbox) + deleteCesium.sendDocument(finalDoc, idMsg) + #################### Profiles #################### def set(self, name=None, description=None, ville=None, adresse=None, position=None, site=None, avatar=None): - document = self.configDocSet(name, description, ville, adresse, position, site, avatar) - result = self.sendDocument(document,'set') + setProfile = Profiles(self.dunikey, self.pod) + document = setProfile.configDocSet(name, description, ville, adresse, position, site, avatar) + result = setProfile.sendDocument(document,'set') print(result) return result def get(self, profile=None, avatar=None): + getProfile = Profiles(self.dunikey, self.pod, self.noNeedDunikey) if not profile: profile = self.pubkey if not re.match(PUBKEY_REGEX, profile) or len(profile) > 45: @@ -542,16 +56,38 @@ class Profiles: else: scope = '_id' - document = self.configDocGet(profile, scope, avatar) - resultJSON = self.sendDocument(document, 'get') - result = self.parseJSON(resultJSON) + document = getProfile.configDocGet(profile, scope, avatar) + resultJSON = getProfile.sendDocument(document, 'get') + result = getProfile.parseJSON(resultJSON) print(result) - return result def erase(self): - document = self.configDocErase() - result = self.sendDocument(document,'erase') + eraseProfile = Profiles(self.dunikey, self.pod) + document = eraseProfile.configDocErase() + result = eraseProfile.sendDocument(document,'erase') print(result) - return result \ No newline at end of file + + #################### Likes #################### + + def readLikes(self, profile=False): + likes = ReadLikes(self.dunikey, self.pod, self.noNeedDunikey) + document = likes.configDoc(profile) + result = likes.sendDocument(document) + result = likes.parseResult(result) + + print(result) + + def like(self, stars, profile=False): + likes = SendLikes(self.dunikey, self.pod) + document = likes.configDoc(profile, stars) + if document: + likes.sendDocument(document, profile) + + def unLike(self, pubkey, silent=False): + likes = UnLikes(self.dunikey, self.pod) + idLike = likes.checkLike(pubkey) + if idLike: + document = likes.configDoc(idLike) + likes.sendDocument(document, silent) diff --git a/zen/jaklis/lib/cesiumCommon.py b/zen/jaklis/lib/cesiumCommon.py new file mode 100644 index 0000000..b68337d --- /dev/null +++ b/zen/jaklis/lib/cesiumCommon.py @@ -0,0 +1,51 @@ +import sys, re, json +from hashlib import sha256 +from lib.natools import fmt, sign, get_privkey + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +def pp_json(json_thing, sort=True, indents=4): + # Print beautifull JSON + if type(json_thing) is str: + print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) + else: + print(json.dumps(json_thing, sort_keys=sort, indent=indents)) + return None + +class CesiumCommon: + def __init__(self, dunikey, pod, noNeedDunikey=False): + self.pod = pod + self.noNeedDunikey = noNeedDunikey + # Get my pubkey from my private key + try: + self.dunikey = dunikey + if not dunikey: + raise ValueError("Dunikey is empty") + except: + sys.stderr.write("Please fill the path to your private key (PubSec)\n") + sys.exit(1) + + if noNeedDunikey: + self.pubkey = self.dunikey + else: + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + def signDoc(self, document): + # Generate hash of document + hashDoc = sha256(document.encode()).hexdigest().upper() + + # Generate signature of document + signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() + + # Build final document + data = {} + data['hash'] = hashDoc + data['signature'] = signature + signJSON = json.dumps(data) + finalJSON = {**json.loads(signJSON), **json.loads(document)} + + return json.dumps(finalJSON) diff --git a/zen/jaklis/lib/crypt.py b/zen/jaklis/lib/crypt.py new file mode 100755 index 0000000..ee4cfb2 --- /dev/null +++ b/zen/jaklis/lib/crypt.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import base64, base58, sys, string, random +from natools import get_privkey, box_decrypt, box_encrypt, fmt + +def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: + if arg in args and len(args) > args.index(arg)+n: + return args[args.index(arg)+n] + else: + return default + +cmd = sys.argv[1] + +dunikey = getargv("-k", "private.dunikey") +msg = getargv("-m", "test") +pubkey = getargv("-p") + +def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(dunikey, "pubsec"), pubkey).decode() + +def encrypt(msg): + return fmt["64"](box_encrypt(msg.encode(), get_privkey(dunikey, "pubsec"), pubkey)).decode() + +if cmd == 'decrypt': + clear = decrypt(msg) + print(clear) +elif cmd == 'encrypt': + clear = encrypt(msg) + print(clear) + diff --git a/zen/jaklis/lib/gva.py b/zen/jaklis/lib/gva.py new file mode 100644 index 0000000..eaf22fd --- /dev/null +++ b/zen/jaklis/lib/gva.py @@ -0,0 +1,59 @@ +import sys, re +from lib.natools import get_privkey +from lib.gvaPay import Transaction, PUBKEY_REGEX +from lib.gvaHistory import History +from lib.gvaBalance import Balance + +class GvaApi(): + def __init__(self, dunikey, node, pubkey, noNeedDunikey=False): + self.noNeedDunikey = noNeedDunikey + self.dunikey = dunikey + self.node = node + if noNeedDunikey: + self.pubkey = self.dunikey + else: + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + + if pubkey: + self.destPubkey = pubkey + else: + self.destPubkey = self.pubkey + + try: + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + try: + if not re.match(PUBKEY_REGEX, self.destPubkey) or len(self.destPubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + #################### Payments #################### + + def pay(self, amount, comment, mempool, verbose): + gva = Transaction(self.dunikey, self.node, self.destPubkey, amount, comment, mempool, verbose) + gva.genDoc() + gva.checkTXDoc() + gva.signDoc() + return gva.sendTXDoc() + + def history(self, isJSON=False, noColors=False): + gva = History(self.dunikey, self.node, self.destPubkey) + gva.sendDoc() + transList = gva.parseHistory() + + if isJSON: + transJson = gva.jsonHistory(transList) + print(transJson) + else: + gva.printHistory(transList, noColors) + + def balance(self, useMempool): + gva = Balance(self.dunikey, self.node, self.destPubkey, useMempool) + balanceValue = gva.sendDoc() + print(balanceValue) diff --git a/zen/jaklis/lib/gvaBalance.py b/zen/jaklis/lib/gvaBalance.py new file mode 100644 index 0000000..b148e7e --- /dev/null +++ b/zen/jaklis/lib/gvaBalance.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Balance: + + def __init__(self, dunikey, node, pubkey, useMempool=False): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build balance generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + balance(script: $pubkey) { + amount + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send balance document + try: + balanceResult = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération du solde:\n" + message + "\n") + sys.exit(1) + + balanceValue = balanceResult['balance']['amount']/100 + # print(balanceValue) + return balanceValue diff --git a/zen/jaklis/lib/gvaHistory.py b/zen/jaklis/lib/gvaHistory.py new file mode 100644 index 0000000..a4f6400 --- /dev/null +++ b/zen/jaklis/lib/gvaHistory.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast, time +from datetime import datetime +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class History: + + def __init__(self, dunikey, node, pubkey): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.node = node + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build history generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + transactionsHistory(pubkey: $pubkey) { + received { + writtenTime + issuers + outputs + comment + } + sent { + writtenTime + issuers + outputs + comment + } + receiving { + issuers + outputs + comment + } + sending { + issuers + outputs + comment + } + } + balance(script: $pubkey) { + amount + base + } + node { + peer { + currency + } + } + currentUd { + amount + base + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send history document + try: + self.historyDoc = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération de l'historique:\n" + message + "\n") + sys.exit(1) + + + def parseHistory(self): + trans = [] + i = 0 + + currentBase = int(self.historyDoc['currentUd']['base']) + self.UD = self.historyDoc['currentUd']['amount']/100 + + for sens in 'received','sent','receiving','sending': + res = self.historyDoc['transactionsHistory'][sens] + for bloc in res: + output = bloc['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + if sens in ('received','receiving') or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(sens) + if sens in ('receiving','sending'): + trans[i].append(int(time.time())) + else: + trans[i].append(bloc['writtenTime']) + if sens in ('sent','sending'): + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(bloc['issuers'][0]) + amount = int(output.split(':')[0]) + base = int(output.split(':')[1]) + applyBase = base-currentBase + amount = round(amount*pow(10,applyBase)/100, 2) + # if referential == 'DU': amount = round(amount/UD, 2) + trans[i].append(amount) + trans[i].append(round(amount/self.UD, 2)) + trans[i].append(bloc['comment']) + trans[i].append(base) + i += 1 + + # Order transactions by date + trans.sort(key=lambda x: x[1]) + + # Keep only base if there is base change + lastBase = 0 + for i in trans: + if i[6] == lastBase: i[6] = None + else: lastBase = i[6] + + return trans + + def printHistory(self, trans, noColors): + # Get balance + balance = self.historyDoc['balance']['amount']/100 + balanceUD = round(balance/self.UD, 2) + + # Get currency + currency = self.historyDoc['node']['peer']['currency'] + if currency == 'g1': currency = 'Ḡ1' + elif currency == 'g1-test': currency = 'GT' + # if referential == 'DU': currency = 'DU/' + currency.lower() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + # Display history + print('+', end='') + print('-'.center(rows-1, '-')) + if noColors: isBold = isBoldEnd = '' + else: + isBold = '\033[1m' + isBoldEnd = '\033[0m' + print(isBold + "|{: <19} | {: <12} | {: <7} | {: <7} | {: <30}".format(" Date"," De / À"," {0}".format(currency)," DU/{0}".format(currency.lower()),"Commentaire") + isBoldEnd) + print('|', end='') + for t in trans: + if t[0] == "received": color = "green" + elif t[0] == "receiving": color = "yellow" + elif t[0] == "sending": color = "red" + else: color = "blue" + if noColors: + color = None + if t[0] in ('receiving','sending'): + comment = '(EN ATTENTE) ' + t[5] + else: + comment = t[5] + else: + comment = t[5] + + date = datetime.fromtimestamp(t[1]).strftime("%d/%m/%Y à %H:%M") + print('-'.center(rows-1, '-')) + if t[6]: + print('|', end='') + print(' Changement de base : {0} '.format(t[6]).center(rows-1, '#')) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + printKey = t[2][0:8] + '\u2026' + t[2][-3:] + if noColors: + print(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment)) + else: + print(colored(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment), color)) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + print(isBold + 'Solde du compte: {0} {1} ({2} DU/{3})'.format(balance, currency, balanceUD, currency.lower()).center(rows-1, ' ') + isBoldEnd) + print('+', end='') + print(''.center(rows-1, '-')) + if not noColors: + print(colored('Reçus', 'green'), '-', colored('En cours de réception', 'yellow'), '-', colored('Envoyé', 'blue'), '-', colored("En cours d'envoi", 'red')) + + return trans + + def jsonHistory(self, transList): + dailyJSON = [] + for i, trans in enumerate(transList): + dailyJSON.append(i) + dailyJSON[i] = {} + dailyJSON[i]['date'] = trans[1] + dailyJSON[i]['pubkey'] = trans[2] + dailyJSON[i]['amount'] = trans[3] + dailyJSON[i]['amountUD'] = trans[4] + dailyJSON[i]['comment'] = trans[5] + + dailyJSON = json.dumps(dailyJSON, indent=2) + # If we want to write JSON to a file + #jsonFile = open("history-{0}.json".format(self.pubkey[0:8]), "w") + #jsonFile.writelines(dailyJSON + '\n') + #jsonFile.close() + return dailyJSON + diff --git a/zen/jaklis/lib/gvaPay.py b/zen/jaklis/lib/gvaPay.py new file mode 100644 index 0000000..e017c88 --- /dev/null +++ b/zen/jaklis/lib/gvaPay.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Transaction: + + def __init__(self, dunikey, node, recipient, amount, comment='', useMempool=False, verbose=False): + self.dunikey = dunikey + self.recipient = recipient + self.amount = int(amount*100) + self.comment = comment + self.issuer = get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + self.verbose = verbose + self.node = node + self._isChange = False + + try: + if not re.match(PUBKEY_REGEX, recipient) or len(recipient) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + + try: + if recipient == self.issuer: + raise ValueError('Le destinataire ne peut pas être vous même.') + except: + sys.stderr.write("Le destinataire ne peut pas être vous même.\n") + raise + + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def genDoc(self): + # Build TX generation document + if self.verbose: print("useMempool:", str(self.useMempool)) + queryBuild = gql( + """ + query ($recipient: String!, $issuer: String!, $amount: Int!, $comment: String!, $useMempool: Boolean!){ genTx( + amount: $amount + comment: $comment + issuer: $issuer + recipient: $recipient + useMempoolSources: $useMempool + ) + } + """ + ) + paramsBuild = { + "recipient": self.recipient, + "issuer": self.issuer, + "amount": int(self.amount), + "comment": self.comment, + "useMempool": self.useMempool + } + + # Send TX document + try: + # self.txDoc = [] + self.txDoc = self.client.execute(queryBuild, variable_values=paramsBuild)['genTx'] + if self.verbose: print(self.txDoc[0]) + return self.txDoc + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la génération du document:\n" + message + "\n") + raise + + + # Check document + def checkTXDoc(self): + issuerRaw=[];outAmount=[];outPubkey=[];commentRaw=[] + for docs in self.txDoc: + docList = docs.splitlines() + for i, line in enumerate(docList): + if re.search("Issuers:", line): + issuerRaw.append(docList[(i + 1) % len(docList)]) + if re.search("Outputs:", line): + outputRaw = docList[(i + 1) % len(docList)].split(":") + outAmount.append(int(outputRaw[0])) + outPubkey.append(outputRaw[2].split("SIG(")[1].replace(')','')) + if re.search("Comment:", line): + commentRaw.append(line.split(': ', 1)[1]) + + # Check if it's only a change transaction + if all(i == self.issuer for i in outPubkey): + print("Le document contient une transaction de change") + self.isChange = True + # Check validity of the document + elif all(i != self.issuer for i in issuerRaw) or sum(outAmount) != self.amount or all(i != self.recipient for i in outPubkey) or all(i != self.comment for i in commentRaw): + sys.stderr.write(colored("Le document généré est corrompu !\nLe noeud " + self.node + "a peut être un dysfonctionnement.\n", 'red')) + sys.stderr.write(colored(issuerRaw[0] + " envoi " + str(outAmount[0]) + " vers " + outPubkey[0] + " with comment: " + commentRaw[0] + "\n", "yellow")) + raise ValueError('Le document généré est corrompu !') + else: + print("Le document généré est conforme.") + self.isChange = False + return self.txDoc + + def signDoc(self): + # Sign TX documents + signature=[] + self.signedDoc=[] + for i, docs in enumerate(self.txDoc): + signature.append(fmt["64"](sign(docs.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(docs.encode())])) + self.signedDoc.append(docs + signature[i].decode()) + return self.signedDoc + + + def sendTXDoc(self): + # Build TX documents + txResult=[] + for docs in self.signedDoc: + querySign = gql( + """ + mutation ($signedDoc: String!){ tx( + rawTx: $signedDoc + ) { + version + issuers + outputs + } + } + """ + ) + paramsSign = { + "signedDoc": docs + } + + # Send TX Signed document + try: + txResult.append(str(self.client.execute(querySign, variable_values=paramsSign))) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la transaction:\n" + message + "\n") + if self.verbose: + sys.stderr.write("Document final:\n" + docs) + raise ValueError(message) + else: + if self.isChange: + self.send() + else: + print(colored("Transaction effectué avec succès !", "green")) + if self.verbose: + print(docs) + break + + return txResult + + def _getIsChange(self): + return self._isChange + def _setIsChange(self, newChange): + if self.verbose: print("_setIsChange: ", str(newChange)) + self._isChange = newChange + if newChange: self.useMempool == True + isChange = property(_getIsChange, _setIsChange) + + def send(self): + result = self.genDoc() + result = self.checkTXDoc() + result = self.signDoc() + result = self.sendTXDoc() + return result + diff --git a/zen/jaklis/lib/likes.py b/zen/jaklis/lib/likes.py index ce4b1f6..5eee339 100644 --- a/zen/jaklis/lib/likes.py +++ b/zen/jaklis/lib/likes.py @@ -1,40 +1,15 @@ -#!/usr/bin/env python3 - import os, sys, ast, requests, json, base58, base64, time, string, random, re from lib.natools import fmt, sign, get_privkey, box_decrypt, box_encrypt from time import sleep from hashlib import sha256 from datetime import datetime from termcolor import colored +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX -PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" - -class ReadLikes: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.issuer) or len(self.issuer) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - +class ReadLikes(CesiumCommon): # Configure JSON document to send def configDoc(self, profile): - if not profile: profile = self.issuer - # elif len(profile) < 42: - # print(len(profile)) - # gProfile = requests.get('{0}/user/profile/{1}'.format(self.pod, issuer)) - # gProfile = json.loads(gProfile.text)['_source'] - # pseudo = gProfile['title'] + if not profile: profile = self.pubkey data = {} data['query'] = {} @@ -98,7 +73,7 @@ class ReadLikes: payTo = '' id = i['_id'] level = i['_source']['level'] - if issuer == self.issuer: + if issuer == self.pubkey: finalPrint['yours'] = { 'id' : id, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level } else: finalPrint['likes'].append({ 'issuer' : issuer, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level }) @@ -124,47 +99,18 @@ class ReadLikes: data = json.dumps(data) result = requests.post('{0}/user/profile/_search'.format(self.pod), headers=headers, data=data) - result = json.loads(result.text)['hits']['hits'][0]['_source'] - - return result - - def readLikes(self, profile=False): - document = self.configDoc(profile) - result = self.sendDocument(document) - result = self.parseResult(result) - - print(result) - return result - - + result = json.loads(result.text)['hits']['hits'] + for i in result: + return i['_source'] #################### Like class #################### - - -class SendLikes: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.issuer) or len(self.issuer) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - +class SendLikes(CesiumCommon): # Configure JSON document to send def configDoc(self, profile, likes): - if not profile: profile = self.issuer + if not profile: profile = self.pubkey if likes not in range(0, 6): sys.stderr.write(colored('Votre like doit être compris entre 0 et 5.\n', 'red')) return False @@ -180,7 +126,7 @@ class SendLikes: data['kind'] = "STAR" data['level'] = likes data['time'] = timeSent - data['issuer'] = self.issuer + data['issuer'] = self.pubkey document = json.dumps(data) @@ -216,7 +162,10 @@ class SendLikes: resultJson = json.loads(result.text) if 'DuplicatedDocumentException' in resultJson['error']: rmLike = UnLikes(self.dunikey, self.pod) - rmLike.unLike(pubkey, True) + idLike = rmLike.checkLike(pubkey) + if idLike: + document = rmLike.configDoc(idLike) + rmLike.sendDocument(document, True) sleep(0.5) self.sendDocument(document, pubkey) return resultJson['error'] @@ -227,42 +176,12 @@ class SendLikes: sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') - - - def like(self, stars, profile=False): - document = self.configDoc(profile, stars) - if document: - self.sendDocument(document, profile) - - - - #################### Unlike class #################### - - -class UnLikes: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.issuer) or len(self.issuer) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - +class UnLikes(CesiumCommon): # Check if you liked this profile def checkLike(self, pubkey): - readProfileLikes = ReadLikes(self.dunikey, self.pod) document = readProfileLikes.configDoc(pubkey) result = readProfileLikes.sendDocument(document) @@ -285,7 +204,7 @@ class UnLikes: data['index'] = "like" data['type'] = "record" data['id'] = idLike - data['issuer'] = self.issuer + data['issuer'] = self.pubkey data['time'] = timeSent document = json.dumps(data) @@ -321,11 +240,3 @@ class UnLikes: return result.text else: sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') - - - def unLike(self, pubkey, silent=False): - idLike = self.checkLike(pubkey) - if idLike: - document = self.configDoc(idLike) - self.sendDocument(document, silent) - diff --git a/zen/jaklis/lib/messaging.py b/zen/jaklis/lib/messaging.py new file mode 100644 index 0000000..2165182 --- /dev/null +++ b/zen/jaklis/lib/messaging.py @@ -0,0 +1,236 @@ +import os, sys, ast, requests, json, base58, base64 +from time import time +from datetime import datetime +from termcolor import colored +from lib.natools import fmt, get_privkey, box_decrypt, box_encrypt +from lib.cesiumCommon import CesiumCommon, pp_json, PUBKEY_REGEX + + +#################### Reading class #################### + + +class ReadFromCesium(CesiumCommon): + # Configure JSON document to send + def configDoc(self, nbrMsg, outbox): + boxType = "issuer" if outbox else "recipient" + + data = {} + data['sort'] = { "time": "desc" } + data['from'] = 0 + data['size'] = nbrMsg + data['_source'] = ['issuer','recipient','title','content','time','nonce','read_signature'] + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = {} + data['query']['bool']['filter']['term'] = {} + data['query']['bool']['filter']['term'][boxType] = self.pubkey + + document = json.dumps(data) + return document + + def sendDocument(self, nbrMsg, outbox): + boxType = "outbox" if outbox else "inbox" + + document = self.configDoc(nbrMsg, outbox) + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/message/{1}/_search'.format(self.pod, boxType), headers=headers, data=document) + if result.status_code == 200: + return result.json()["hits"] + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text) + + # Parse JSON result and display messages + def readMessages(self, msgJSON, nbrMsg, outbox): + def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + totalMsg = msgJSON["total"] + if nbrMsg > totalMsg: + nbrMsg = totalMsg + + if totalMsg == 0: + print(colored("Aucun message à afficher.", 'yellow')) + return True + else: + infoTotal = " Nombre de messages: " + str(nbrMsg) + "/" + str(totalMsg) + " " + print(colored(infoTotal.center(rows, '#'), "yellow")) + for hits in msgJSON["hits"]: + self.idMsg = hits["_id"] + msgSrc = hits["_source"] + self.issuer = msgSrc["issuer"] + nonce = msgSrc["nonce"] + nonce = base58.b58decode(nonce) + self.dateS = msgSrc["time"] + date = datetime.fromtimestamp(self.dateS).strftime(", le %d/%m/%Y à %H:%M ") + if outbox: + startHeader = " À " + msgSrc["recipient"] + else: + startHeader = " De " + self.issuer + headerMsg = startHeader + date + "(ID: {})".format(self.idMsg) + " " + + print('-'.center(rows, '-')) + print(colored(headerMsg, "blue").center(rows+9, '-')) + print('-'.center(rows, '-')) + try: + self.title = decrypt(msgSrc["title"]) + self.content = decrypt(msgSrc["content"]) + except Exception as e: + sys.stderr.write(colored(str(e), 'red') + '\n') + pp_json(hits) + continue + print('\033[1m' + self.title + '\033[0m') + print(self.content) + + print(colored(infoTotal.center(rows, '#'), "yellow")) + + # Parse JSON result and display messages + def jsonMessages(self, msgJSON, nbrMsg, outbox): + def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() + + totalMsg = msgJSON["total"] + if nbrMsg > totalMsg: + nbrMsg = totalMsg + + if totalMsg == 0: + print("Aucun message à afficher") + return True + else: + data = [] + # data.append({}) + # data[0]['total'] = totalMsg + for i, hits in enumerate(msgJSON["hits"]): + self.idMsg = hits["_id"] + msgSrc = hits["_source"] + self.issuer = msgSrc["issuer"] + nonce = msgSrc["nonce"] + nonce = base58.b58decode(nonce) + self.date = msgSrc["time"] + + if outbox: + pubkey = msgSrc["recipient"] + else: + pubkey = self.issuer + + try: + self.title = decrypt(msgSrc["title"]) + self.content = decrypt(msgSrc["content"]) + except Exception as e: + sys.stderr.write(colored(str(e), 'red') + '\n') + pp_json(hits) + continue + + data.append(i) + data[i] = {} + data[i]['id'] = self.idMsg + data[i]['date'] = self.date + data[i]['pubkey'] = pubkey + data[i]['title'] = self.title + data[i]['content'] = self.content + + data = json.dumps(data, indent=2) + return data + + +#################### Sending class #################### + + +class SendToCesium(CesiumCommon): + def encryptMsg(self, msg): + return fmt["64"](box_encrypt(msg.encode(), get_privkey(self.dunikey, "pubsec"), self.recipient, self.nonce)).decode() + + def configDoc(self, title, msg): + b58nonce = base58.b58encode(self.nonce).decode() + + # Get current timestamp + timeSent = int(time()) + + # Generate custom JSON + data = {} + data['issuer'] = self.pubkey + data['recipient'] = self.recipient + data['title'] = title + data['content'] = msg + data['time'] = timeSent + data['nonce'] = b58nonce + data['version'] = 2 + document = json.dumps(data) + + return self.signDoc(document) + + + def sendDocument(self, document, outbox): + boxType = "outbox" if outbox else "inbox" + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get result + try: + result = requests.post('{0}/message/{1}?pubkey={2}'.format(self.pod, boxType, self.recipient), headers=headers, data=document) + except Exception as e: + sys.stderr.write("Impossible d'envoyer le message:\n" + str(e)) + sys.exit(1) + else: + if result.status_code == 200: + print(colored("Message envoyé avec succès !", "green")) + print("ID: " + result.text) + return result + else: + sys.stderr.write("Erreur inconnue:" + '\n') + print(str(pp_json(result.text)) + '\n') + + +#################### Deleting class #################### + + +class DeleteFromCesium(CesiumCommon): + def configDoc(self, idMsg, outbox): + # Get current timestamp + timeSent = int(time()) + + boxType = "outbox" if outbox else "inbox" + + # Generate document to customize + data = {} + data['version'] = 2 + data['index'] = "message" + data['type'] = boxType + data['id'] = idMsg + data['issuer'] = self.pubkey + data['time'] = timeSent + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocument(self, document, idMsg): + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get result + try: + result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) + if result.status_code == 404: + raise ValueError("Message introuvable") + elif result.status_code == 403: + raise ValueError("Vous n'êtes pas l'auteur de ce message.") + except Exception as e: + sys.stderr.write(colored("Impossible de supprimer le message {0}:\n".format(idMsg), 'red') + str(e) + "\n") + return False + else: + if result.status_code == 200: + print(colored("Message {0} supprimé avec succès !".format(idMsg), "green")) + return result + else: + sys.stderr.write("Erreur inconnue.") diff --git a/zen/jaklis/lib/profiles.py b/zen/jaklis/lib/profiles.py new file mode 100644 index 0000000..5386c89 --- /dev/null +++ b/zen/jaklis/lib/profiles.py @@ -0,0 +1,125 @@ +import sys, re, json, requests, base64 +from time import time +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX + + +class Profiles(CesiumCommon): + # Configure JSON document SET to send + def configDocSet(self, name, description, city, address, pos, socials, avatar): + timeSent = int(time()) + + data = {} + if name: data['title'] = name + if description: data['description'] = description + if address: data['address'] = address + if city: data['city'] = city + if pos: + geoPoint = {} + geoPoint['lat'] = pos[0] + geoPoint['lon'] = pos[1] + data['geoPoint'] = geoPoint + if socials: + data['socials'] = [] + data['socials'].append({}) + data['socials'][0]['type'] = "web" + data['socials'][0]['url'] = socials + if avatar: + avatar = open(avatar, 'rb').read() + avatar = base64.b64encode(avatar).decode() + data['avatar'] = {} + data['avatar']['_content'] = avatar + data['avatar']['_content_type'] = "image/png" + data['time'] = timeSent + data['issuer'] = self.pubkey + data['version'] = 2 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + # Configure JSON document GET to send + def configDocGet(self, profile, scope='title', getAvatar=None): + + if getAvatar: + avatar = "avatar" + else: + avatar = "avatar._content_type" + + data = { + "query": { + "bool": { + "should":[ + { + "match":{ + scope:{ + "query": profile,"boost":2 + } + } + },{ + "prefix": {scope: profile} + } + ] + } + },"highlight": { + "fields": { + "title":{}, + "tags":{} + } + },"from":0, + "size":100, + "_source":["title", avatar,"description","city","address","socials.url","creationTime","membersCount","type"], + "indices_boost":{"user":100,"page":1,"group":0.01 + } + } + + document = json.dumps(data) + + return document + + # Configure JSON document SET to send + def configDocErase(self): + timeSent = int(time()) + + data = {} + data['time'] = timeSent + data['id'] = self.pubkey + data['issuer'] = self.pubkey + data['version'] = 2 + data['index'] = "user" + data['type'] = "profile" + + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocument(self, document, type): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/user/profile?pubkey={1}/_update?pubkey={1}'.format(self.pod, self.pubkey) + elif type == 'get': + reqQuery = '{0}/user,page,group/profile,record/_search'.format(self.pod) + elif type == 'erase': + reqQuery = '{0}/history/delete'.format(self.pod) + + result = requests.post(reqQuery, headers=headers, data=document) + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') + + def parseJSON(self, doc): + doc = json.loads(doc)['hits']['hits'] + if doc: + pubkey = { "pubkey": doc[0]['_id'] } + rest = doc[0]['_source'] + final = {**pubkey, **rest} + return json.dumps(final, indent=2) + else: + return 'Profile vide' diff --git a/zen/jaklis/paiements.py b/zen/jaklis/paiements.py new file mode 100755 index 0000000..dde58da --- /dev/null +++ b/zen/jaklis/paiements.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +""" +ZetCode Tkinter tutorial + +In this example, we use the pack +manager to create a review example. + +Author: Jan Bodnar +Website: www.zetcode.com +""" + +import PySimpleGUI as sg +from lib.gva import GvaApi +import sys, os, threading +from shutil import copyfile +from os.path import join, dirname +from dotenv import load_dotenv +from lib.natools import get_privkey +import requests + +class StdoutRedirector(object): + def __init__(self, text_widget): + self.text_widget = text_widget + + def write(self, s): + self.text_widget.insert('end', s) + self.text_widget.see('end') + + def flush(self): + pass + + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' + +# Get variables environment +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') +load_dotenv(dotenv_path) + +dunikey = os.getenv('DUNIKEY') +if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) +node = os.getenv('NODE') +issuer = get_privkey(dunikey, "pubsec").pubkey + + +def ProceedPaiement(recipient, amount, comment): + if not recipient: + raise ValueError("Veuillez indiquer un destinataire de paiement") + elif not amount: + raise ValueError("Veuillez indiquer le montant de la transaction") + + amount = int(float(amount.replace(',','.'))*100) + print("Paiement en cours vers", recipient) + gva = GvaApi(dunikey, node, recipient) + gva.pay(amount, comment, False, False) + + recipient = amount = comment = None + + +sg.theme('DarkGrey2') +layout = [ [sg.Text('Noeud utilisé: ' + node)], + [sg.Text('Votre clé publique: ' + issuer)], + [sg.Text('')], + [sg.Text('Destinataire: '), sg.InputText(size=(55, None),default_text=issuer)], + [sg.Text('Montant: '), sg.InputText(size=(7, None)), sg.Text('Ḡ1')], + [sg.Text('Commentaire:'), sg.InputText(size=(55, None))], + [sg.Button('Envoyer')] ] + +# Create the Window +window = sg.Window('Paiement Ḡ1 - GVA', layout) +# availablePubkeys = requests.get('https://g1-stats.axiom-team.fr/data/wallets-g1.txt') +while True: + try: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + if event == 'Envoyer': + ProceedPaiement(values[0], values[1], values[2]) + except Exception as e: + loc = window.CurrentLocation() + sg.popup(e, title="ERREUR", button_color=('black','red'), location=(loc)) + else: + loc = window.CurrentLocation() + sg.popup(f'Transaction effectué avec succès !', title="Envoyé", location=(loc)) + + +window.close() diff --git a/zen/jaklis/requirements.txt b/zen/jaklis/requirements.txt index bf25335..e6b5711 100644 --- a/zen/jaklis/requirements.txt +++ b/zen/jaklis/requirements.txt @@ -4,3 +4,4 @@ pybase64 duniterpy termcolor python-dotenv +gql