From 55dbf45b3241eaff2ee9293206717c0c5adb06e8 Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 23 Aug 2022 13:54:16 +0200 Subject: [PATCH] jaklis --- installnot.sh | 5 +- tools/jaklis/.env | 10 + tools/jaklis/.env.template | 10 + tools/jaklis/README.md | 82 +++++ tools/jaklis/jaklis.py | 262 +++++++++++++++ .../lib/__pycache__/cesium.cpython-36.pyc | Bin 0 -> 4098 bytes .../lib/__pycache__/cesium.cpython-38.pyc | Bin 0 -> 4153 bytes .../__pycache__/cesiumCommon.cpython-36.pyc | Bin 0 -> 1720 bytes .../__pycache__/cesiumCommon.cpython-38.pyc | Bin 0 -> 1742 bytes .../lib/__pycache__/currentUd.cpython-36.pyc | Bin 0 -> 1400 bytes .../lib/__pycache__/currentUd.cpython-38.pyc | Bin 0 -> 1422 bytes .../jaklis/lib/__pycache__/gva.cpython-36.pyc | Bin 0 -> 2548 bytes .../jaklis/lib/__pycache__/gva.cpython-38.pyc | Bin 0 -> 2570 bytes .../lib/__pycache__/gvaBalance.cpython-36.pyc | Bin 0 -> 1810 bytes .../lib/__pycache__/gvaBalance.cpython-38.pyc | Bin 0 -> 1834 bytes .../lib/__pycache__/gvaHistory.cpython-36.pyc | Bin 0 -> 7637 bytes .../lib/__pycache__/gvaHistory.cpython-38.pyc | Bin 0 -> 7636 bytes .../lib/__pycache__/gvaID.cpython-36.pyc | Bin 0 -> 2205 bytes .../lib/__pycache__/gvaID.cpython-38.pyc | Bin 0 -> 2231 bytes .../lib/__pycache__/gvaPay.cpython-36.pyc | Bin 0 -> 5853 bytes .../lib/__pycache__/gvaPay.cpython-38.pyc | Bin 0 -> 5890 bytes .../lib/__pycache__/messaging.cpython-36.pyc | Bin 0 -> 6758 bytes .../lib/__pycache__/messaging.cpython-38.pyc | Bin 0 -> 6703 bytes .../lib/__pycache__/natools.cpython-36.pyc | Bin 0 -> 9291 bytes .../lib/__pycache__/natools.cpython-38.pyc | Bin 0 -> 9408 bytes .../lib/__pycache__/offers.cpython-36.pyc | Bin 0 -> 3552 bytes .../lib/__pycache__/offers.cpython-38.pyc | Bin 0 -> 3527 bytes .../lib/__pycache__/profiles.cpython-36.pyc | Bin 0 -> 3046 bytes .../lib/__pycache__/profiles.cpython-38.pyc | Bin 0 -> 3106 bytes .../lib/__pycache__/stars.cpython-36.pyc | Bin 0 -> 6044 bytes .../lib/__pycache__/stars.cpython-38.pyc | Bin 0 -> 5931 bytes tools/jaklis/lib/cesium.py | 120 +++++++ tools/jaklis/lib/cesiumCommon.py | 51 +++ tools/jaklis/lib/crypt.py | 31 ++ tools/jaklis/lib/currentUd.py | 40 +++ tools/jaklis/lib/gva.py | 76 +++++ tools/jaklis/lib/gvaBalance.py | 53 ++++ tools/jaklis/lib/gvaHistory.py | 272 ++++++++++++++++ tools/jaklis/lib/gvaID.py | 81 +++++ tools/jaklis/lib/gvaPay.py | 172 ++++++++++ tools/jaklis/lib/messaging.py | 236 ++++++++++++++ tools/jaklis/lib/natools.py | 297 ++++++++++++++++++ tools/jaklis/lib/offers.py | 138 ++++++++ tools/jaklis/lib/profiles.py | 125 ++++++++ tools/jaklis/lib/qrcode-reader.py | 86 +++++ tools/jaklis/lib/stars.py | 242 ++++++++++++++ tools/jaklis/paiements.py | 94 ++++++ tools/jaklis/requirements.txt | 9 + tools/jaklis/setup.sh | 12 + 49 files changed, 2503 insertions(+), 1 deletion(-) create mode 100644 tools/jaklis/.env create mode 100755 tools/jaklis/.env.template create mode 100755 tools/jaklis/README.md create mode 100755 tools/jaklis/jaklis.py create mode 100644 tools/jaklis/lib/__pycache__/cesium.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/cesium.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/cesiumCommon.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/currentUd.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/currentUd.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/gva.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/gva.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaBalance.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaBalance.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaHistory.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaHistory.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaID.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaID.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/gvaPay.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/messaging.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/messaging.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/natools.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/natools.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/offers.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/offers.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/profiles.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/profiles.cpython-38.pyc create mode 100644 tools/jaklis/lib/__pycache__/stars.cpython-36.pyc create mode 100644 tools/jaklis/lib/__pycache__/stars.cpython-38.pyc create mode 100755 tools/jaklis/lib/cesium.py create mode 100755 tools/jaklis/lib/cesiumCommon.py create mode 100755 tools/jaklis/lib/crypt.py create mode 100644 tools/jaklis/lib/currentUd.py create mode 100755 tools/jaklis/lib/gva.py create mode 100755 tools/jaklis/lib/gvaBalance.py create mode 100755 tools/jaklis/lib/gvaHistory.py create mode 100644 tools/jaklis/lib/gvaID.py create mode 100755 tools/jaklis/lib/gvaPay.py create mode 100755 tools/jaklis/lib/messaging.py create mode 100755 tools/jaklis/lib/natools.py create mode 100644 tools/jaklis/lib/offers.py create mode 100755 tools/jaklis/lib/profiles.py create mode 100755 tools/jaklis/lib/qrcode-reader.py create mode 100755 tools/jaklis/lib/stars.py create mode 100755 tools/jaklis/paiements.py create mode 100755 tools/jaklis/requirements.txt create mode 100755 tools/jaklis/setup.sh diff --git a/installnot.sh b/installnot.sh index b6616798..8f547a6b 100644 --- a/installnot.sh +++ b/installnot.sh @@ -10,6 +10,7 @@ ME="${0##*/}" [ $(id -u) -eq 0 ] && echo "LANCEMENT root INTERDIT. Utilisez un simple utilisateur du groupe \"sudo\" SVP" && exit 1 +echo "Just for reference. PLEASE ADAPT" && exit ######################################################################## [[ ! $(which ipfs) ]] && echo "=== Installez IPFS !!" && echo "https://docs.ipfs.io/install/command-line/#official-distributions" && exit 1 @@ -80,6 +81,8 @@ if [[ "$USER" == "pi" ]]; then ## PROPOSE QR_CODE PRINTER SUR RPI sudo cupsctl --remote-admin sudo usermod -aG lpadmin pi sudo usermod -a -G gammu pi + sudo usermod -a -G tty pi + fi fi @@ -107,7 +110,7 @@ cp -Rf ~/.zen/astrXbian/.install/.kodi ~/ ######################################################################## echo "=== Configuration jaklis: Centre de communication CESIUM+ GCHANGE+" -cd ~/.zen/astrXbian/zen/jaklis +cd $MY_PATH/toos/jaklis ./setup.sh ######################################################################## diff --git a/tools/jaklis/.env b/tools/jaklis/.env new file mode 100644 index 00000000..4376e7c0 --- /dev/null +++ b/tools/jaklis/.env @@ -0,0 +1,10 @@ +# Chemin de la clé privé Ḡ1 de l'émetteur, au format PubSec +DUNIKEY=/.zen/secret.dunikey + +# Noeud Duniter +NODE=https://g1.librelois.fr/gva + +# Adresse du pod Cesium ou Gchange à utiliser +#POD=https://g1.data.le-sou.org +#POD=https://g1.data.duniter.fr +POD=https://data.gchange.fr diff --git a/tools/jaklis/.env.template b/tools/jaklis/.env.template new file mode 100755 index 00000000..462b319e --- /dev/null +++ b/tools/jaklis/.env.template @@ -0,0 +1,10 @@ +# Chemin de la clé privé Ḡ1 de l'émetteur, au format PubSec +DUNIKEY= + +# Noeud Duniter +NODE=https://g1.librelois.fr/gva + +# Adresse du pod Cesium ou Gchange à utiliser +POD=https://g1.data.le-sou.org +#POD=https://g1.data.duniter.fr +#POD=https://data.gchange.fr diff --git a/tools/jaklis/README.md b/tools/jaklis/README.md new file mode 100755 index 00000000..9d7e9504 --- /dev/null +++ b/tools/jaklis/README.md @@ -0,0 +1,82 @@ +# Client CLI for Cesium+/Ḡchange pod +## Installation + +Linux: +``` +bash setup.sh +``` + +Autre: +``` +Débrouillez-vous. +``` + +## Utilisation + +*Python 3.9 minimum* + +Renseignez optionnellement le fichier **.env** (Généré lors de la première tentative d'execution, ou à copier depuis .env.template). + +``` +./jaklis.py -h +``` + +``` +usage: jaklis.py [-h] [-v] [-k KEY] [-n NODE] {read,send,delete,get,set,erase,stars,unstars,getoffer,setoffer,deleteoffer,pay,history,balance,id,idBalance} ... + +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,stars,unstars,getoffer,setoffer,deleteoffer,pay,history,balance,id,idBalance} + read Lecture des messages + send Envoi d'un message + delete Supression d'un message + get Voir un profile Cesium+ + set Configurer son profile Cesium+ + erase Effacer son profile Cesium+ + stars Voir les étoiles d'un profile / Noter un profile (option -s NOTE) + unstars Supprimer un star + getoffer Obtenir les informations d'une annonce gchange + setoffer Créer une annonce gchange + deleteoffer Supprimer une annonce gchange + pay Payer en Ḡ1 + history Voir l'historique des transactions d'un compte Ḡ1 + balance Voir le solde d'un compte Ḡ1 + id Voir l'identité d'une clé publique/username + idBalance Voir l'identité d'une clé publique/username et son solde +``` + +Utilisez `./jaklis CMD -h` où `CMD` est la commande souhaité pour obtenir l'aide détaillé de cette commande. + +### Exemples: + +Lire les 10 derniers messages de mon compte indiqué dans le fichier `.env` (par defaut 3 messages): +``` +./jaklis read -n10 +``` + +Envoyer un message à la clé publique `Do99s6wQR2JLfhirPdpAERSjNbmjjECzGxHNJMiNKT3P` avec un fichier de trousseau particulier: +``` +./jaklis.py -k /home/saucisse/mon_fichier_de_trousseau.dunikey send -d Do99s6wQR2JLfhirPdpAERSjNbmjjECzGxHNJMiNKT3P -t "Objet du message" -m "Corps de mon message" +``` + +Noter 4 étoiles le profile `S9EJbjbaGPnp26VuV6fKjR7raE1YkNhUGDgoydHvAJ1` sur gchange: +``` +./jaklis.py -n https://data.gchange.fr like -p S9EJbjbaGPnp26VuV6fKjR7raE1YkNhUGDgoydHvAJ1 -s 4 +``` + +Paramétrer mon profile Cesium+: +``` +./jaklis.py set -n "Sylvain Durif" -v "Bugarach" -a "42 route de Vénus" -d "Christ cosmique" -pos 48.539927 2.6608169 -s https://www.creationmonetaire.info -A mon_avatar.png +``` + +Effacer mon profile Gchange: +``` +./jaklis.py -n https://data.gchange.fr erase +``` diff --git a/tools/jaklis/jaklis.py b/tools/jaklis/jaklis.py new file mode 100755 index 00000000..0a13eb26 --- /dev/null +++ b/tools/jaklis/jaklis.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 + +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 + +__version__ = "0.0.4" + +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) + +# 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+, Gchange ou Duniter à utiliser") + +subparsers = parser.add_subparsers(title="Commandes de jaklis", dest="cmd") +read_cmd = subparsers.add_parser('read', help="Lecture des messages") +send_cmd = subparsers.add_parser('send', help="Envoi d'un message") +delete_cmd = subparsers.add_parser('delete', help="Supression d'un message") +getProfile_cmd = subparsers.add_parser('get', help="Voir un profile Cesium+") +setProfile_cmd = subparsers.add_parser('set', help="Configurer son profile Cesium+") +eraseProfile_cmd = subparsers.add_parser('erase', help="Effacer son profile Cesium+") +stars_cmd = subparsers.add_parser('stars', help="Voir les étoiles d'un profile / Noter un profile (option -s NOTE)") +unstars_cmd = subparsers.add_parser('unstars', help="Supprimer un star") +getoffer_cmd = subparsers.add_parser('getoffer', help="Obtenir les informations d'une annonce gchange") +setoffer_cmd = subparsers.add_parser('setoffer', help="Créer une annonce gchange") +deleteoffer_cmd = subparsers.add_parser('deleteoffer', help="Supprimer une annonce gchange") +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") +id_cmd = subparsers.add_parser('id', help="Voir l'identité d'une clé publique/username") +id_balance_cmd = subparsers.add_parser('idBalance', help="Voir l'identité d'une clé publique/username et son solde") +currentUd = subparsers.add_parser('currentUd', help="Affiche la montant actuel du dividende Universel") + +# Messages management +read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") +read_cmd.add_argument('-j', '--json', action='store_true', help="Sort au format JSON") +read_cmd.add_argument('-o', '--outbox', action='store_true', help="Lit les messages envoyés") + +send_cmd.add_argument('-d', '--destinataire', required=True, help="Destinataire du message") +send_cmd.add_argument('-t', '--titre', help="Titre du message à envoyer") +send_cmd.add_argument('-m', '--message', help="Message à envoyer") +send_cmd.add_argument('-f', '--fichier', help="Envoyer le message contenu dans le fichier 'FICHIER'") +send_cmd.add_argument('-o', '--outbox', action='store_true', help="Envoi le message sur la boite d'envoi") + +delete_cmd.add_argument('-i', '--id', action='append', nargs='+', required=True, help="ID(s) du/des message(s) à supprimer") +delete_cmd.add_argument('-o', '--outbox', action='store_true', help="Suppression d'un message envoyé") + +# Profiles management +setProfile_cmd.add_argument('-n', '--name', help="Nom du profile") +setProfile_cmd.add_argument('-d', '--description', help="Description du profile") +setProfile_cmd.add_argument('-v', '--ville', help="Ville du profile") +setProfile_cmd.add_argument('-a', '--adresse', help="Adresse du profile") +setProfile_cmd.add_argument('-pos', '--position', nargs=2, help="Points géographiques (lat + lon)") +setProfile_cmd.add_argument('-s', '--site', help="Site web du profile") +setProfile_cmd.add_argument('-A', '--avatar', help="Chemin vers mon avatar en PNG") + +getProfile_cmd.add_argument('-p', '--profile', help="Nom du profile") +getProfile_cmd.add_argument('-a', '--avatar', action='store_true', help="Récupérer également l'avatar au format raw base64") + +# Likes management +stars_cmd.add_argument('-p', '--profile', help="Profile cible") +stars_cmd.add_argument('-n', '--number', type=int, help="Nombre d'étoile") +unstars_cmd.add_argument('-p', '--profile', help="Profile à dénoter") + +# Offers management +getoffer_cmd.add_argument('-i', '--id', help="Annonce cible à récupérer") +setoffer_cmd.add_argument('-t', '--title', help="Titre de l'annonce à créer") +setoffer_cmd.add_argument('-d', '--description', help="Description de l'annonce à créer") +setoffer_cmd.add_argument('-c', '--category', help="Categorie de l'annonce à créer") +setoffer_cmd.add_argument('-l', '--localisation', nargs=2, help="Localisation de l'annonce à créer (lat + lon)") +setoffer_cmd.add_argument('-p', '--picture', help="Image de l'annonce à créer") +setoffer_cmd.add_argument('-ci', '--city', help="Ville de l'annonce à créer") +setoffer_cmd.add_argument('-pr', '--price', help="Prix de l'annonce à créer") +deleteoffer_cmd.add_argument('-i', '--id', help="Annonce cible à supprimer") + +# 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('-n', '--number',type=int, default=10, help="Affiche les NUMBER dernières transactions") +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") +id_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +id_cmd.add_argument('-u', '--username', help="Username du compte visé") +id_balance_cmd.add_argument('-p', '--pubkey', help="Pubkey du compte visé") +currentUd.add_argument('-p', '--pubkey', help="Pubkey du compte visé") + + +args = parser.parse_args() +cmd = args.cmd + +if args.version: + print(__version__) + sys.exit(0) + +if not cmd: + parser.print_help() + sys.exit(1) + +def createTmpDunikey(): + # Generate pseudo-random nonce + nonce=[] + for _ in range(32): + nonce.append(random.choice(string.ascii_letters + string.digits)) + nonce = ''.join(nonce) + keyPath = "/tmp/secret.dunikey-" + nonce + + key = SigningKey.from_credentials(getpass.getpass("Identifiant: "), getpass.getpass("Mot de passe: "), None) + key.save_pubsec_file(keyPath) + + return keyPath + +# Check if we need dunikey +try: + pubkey = args.pubkey +except: + pubkey = False +try: + profile = args.profile +except: + profile = False + +if cmd in ('history','balance','get','id','idBalance') and (pubkey or profile): + noNeedDunikey = True + keyPath = False + try: + dunikey = args.pubkey + except: + dunikey = args.profile +else: + noNeedDunikey = False + if args.key: + dunikey = args.key + keyPath = False + else: + 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) + + +# Construct CesiumPlus object +if cmd in ("read","send","delete","set","get","erase","stars","unstars","getoffer","setoffer","deleteoffer"): + from lib.cesium import CesiumPlus + + 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() + + # Stars + elif cmd == "stars": + if args.number or args.number == 0: + cesium.like(args.number, args.profile) + else: + cesium.readLikes(args.profile) + elif cmd == "unstars": + cesium.unLike(args.profile) + + # Offers + elif cmd == "getoffer": + cesium.getOffer(args.id) + elif cmd == "setoffer": + cesium.setOffer(args.title, args.description, args.city, args.localisation, args.category, args.price, args.picture) + elif cmd == "deleteoffer": + cesium.deleteOffer(args.id) + +# Construct GVA object +elif cmd in ("pay","history","balance","id","idBalance","currentUd"): + from lib.gva import GvaApi + + if args.node: + node = args.node + else: + 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) + elif cmd == "history": + gva.history(args.json, args.nocolors, args.number) + elif cmd == "balance": + gva.balance(args.mempool) + elif cmd == "id": + gva.id(args.pubkey, args.username) + elif cmd == "idBalance": + gva.idBalance(args.pubkey) + elif cmd == "currentUd": + gva.currentUd() + + +if keyPath: + os.remove(keyPath) diff --git a/tools/jaklis/lib/__pycache__/cesium.cpython-36.pyc b/tools/jaklis/lib/__pycache__/cesium.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f1b3e10ecdbbff62919583233208992118bb62c GIT binary patch literal 4098 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 b-BfMuecGDt_mr^Jx6loJTi*qlq8a}M-Oi>I literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc b/tools/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25ca01374745614c36b36e24c05440509ac441b5 GIT binary patch literal 1720 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 Cz^Nku literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/currentUd.cpython-36.pyc b/tools/jaklis/lib/__pycache__/currentUd.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a89b0283d914428e4cfafd5507a1079b99ae29d7 GIT binary patch literal 1400 zcmZux&2Aev5GJ|YU1_zB?I2AXG(~l+F4Cb$4nTeME&5(T#OlCoCj?#d+B zu~exS`|5L_!S}vF-h$|@r@TT>9j;=<21x*p9$Ip+V;jyp8q-*u{CRYAwYeaKX5=j+)}Aa=jyI zvzabVkL7$oTyS)vT=Y~Gva+DF^W}Hnf4jf`!@lN~nbq3n-rDsbuz3R0JOm<FS0E^?RD|F~oIaZ$dGDuBqP8arCmLh1xW zS4iK1<=;o6NnOg(7(f~g7qS|0WA(vd!K;x!P5H4Z%t#f7qfIcw+1z!qtf&f`Wj!!r zd_;&OcyagLrRxhh_Cgrmp&giQ=ohfDP#x!Zg$uMI^LtA)#Z$6uLAWHV;3cYlq{s%x ziPjjcC`OCNmQF((E$KPviQoZRg*Kjcrd>go;rmFyJjWOTFAKMnEOBwA#p?~CHNlR!FPc^Bq>?!ZqOdS)z}+^zUV4m$}Dq9mX)<=0J_YjS$5KJ zwU%gaS%2h-FYg<~zu$u80c$I^Lpov08SLs0fa!>qF0RdR#_hzB+BiBjb)~(ZtP7Bp z?Qf!eFS{PJ#IWMFu9eZgtF>nAvE@kz;ljd@oT literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/currentUd.cpython-38.pyc b/tools/jaklis/lib/__pycache__/currentUd.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72ea66497c996930237c012a84252d4a520792d8 GIT binary patch literal 1422 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 WbgwmBe2S;21L)FC_yxfNFZ&zoOfB^Q literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/gva.cpython-38.pyc b/tools/jaklis/lib/__pycache__/gva.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcd3ae3247d43d9d2e00a293e6cb4b96aa2017e7 GIT binary patch literal 2570 zcmb7GNpsvp6z*>An#Crh3J6qcSc*g}o_i|AlGx5dFsUry=rYl$JGN{sla^%4F8Pwl zf8c6Yad8z#{sDKmtGIE>U*N=hEqOeiZ~&?F`t|Ep>wRB;{bnBbdM<(M*Ps4;`iDcv zU)Y(yH0XQ$}P~ z<4)4`yUMrXUefpbl-wfR=FUsPohkE|ID1Gu_Z57Tc(iuHQc95)kNF1lI$NR`O=PsU zEA~BBcOJ?ZV~Yzo0*m^b#d_7oDB%QIG4#u$M2$ z6Oyt`GPi~SYy7O%FxoYK`EP4*0vI5;vsQVv*MF_d#5WH7PS$Yx#a&n|Z;7l(q4rj0)iywVsRkne4(F zvWKRRrUT}L$hV>SdpO+A5;1%x1s|>)h;$g_h1?uRK{~|VPOuwC`7n;g!>zqwWwKw} zqfwMb#b|_QF-Q1>n(Ef6%lb^GHnSO{@6j$qyU;uH50)B%t(>lLPG~4H8y+RJYv2^5 z5!DWe41|MX(<*&x9U7H!q#uJYpf%doY+LIXHuZ#Vc(&AGjXCT~(DltNk*;Q;?}pnV z+i&ddT16_Wlmu0n<9El_^a8g?c8;R&n0-1}p4Edd% zJWJb7Z9oxA3z&h;X`!+dNxm87cOQPY;oE6;GmA3`I!w!C3_={h*L3U?AJD+kZF(zg`qa3 z$ji7`%$`O3OIWQ<`~oKKv>Ood*SMdFb}?s=Eb^{#3gXlt=Na=J+jt~#t6FxO!Zbhx zSW*`AaU$OSBCf({o7OTY<6wD~Ry!f2Fyr(>c2Oxe;mI$9X&9Z-Lt4=z228R-17^?Z zL6`VSQ!md*)EW{8IP@jzXa=^w!SVufSI0)#T55qG^*XvD9Lyk zqu(8ko|i$~jL1v)#rM%5z48j0PtkmaMx~0Ce1S3ve~<`1>M+^Vy=+@<$2Ra*W8Sjg zgR;Id%dJ`JA}+NI#_!;08Wd3Ma`k)UyBOKp3+@N|O>IT7`u#TA76nydX2Et-)Cu#Q kcIKVcMyr_KT-pkSDxdcChKnz7FBO0;U54KheBfsP0H;YZRR910 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/gvaBalance.cpython-36.pyc b/tools/jaklis/lib/__pycache__/gvaBalance.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb498ac002809be502c7843cb16c53f81d1c89c5 GIT binary patch literal 1810 zcmZWpPj4JG6t_M9c4udkZD}bb6-*HlSwwbKD3z!Xv?*z+fVOHwn@&{?&e%z2Gk>BsETT!d# z*;$YWQINNzc71lTxqLpFhrJthZXxEe#tXz6`y@KW@B`Fs9>V+RP8Vx0DYH^=*0r>? zkt=P>bhFT{O|IghNO!jRUYBTV^I4|d8(GQ=1v;JUx9@(tx_WO_&_WJNp+*afUtfH3 z_jdNQ`{dHn7uS~}x-@$F{PL%tT>gBLn5T}$aRA-}4oBwG5drrIvtD5My3L#y2)^!u zlMU@H(~K4gznM5Q0}xPT!@L1g3Ls-VMll*&Vs2~|zQSY7@IDz3;F7To)Kd1qQSKNG z$UX^>yr#T*j~Usv#Y5E?GzUJj_Ay4VZ!x>x1!KaTmw4i+_Sj)A^IjT@a|{{3?r3*d ztxKL9E`bNt$3VWPVUoQ%2!Wra&ng}kA91O|A(bJm!u7HUH%gIH)$6Z-_FFk1xCP+GACbLPCTIM(qL|v|ma71J8$LnQ0=pcHos{ zO9%IUT>ft5aeRN}*2nz-dP0 z=q#PR;ht{9aayD*j@w{Gnr9Gp@!K%p!vXd!ACEdm;zy#137r|nRijd);1Ad+D5ZTo zft*5aaf&Bo?~KXvzyf>#wqBBn^#~RBZKUjNU+iKuu`wE5QO>{xJV4I(F|*Gj$oCWv z&e%Bz#d8iF9|eV{>IU;X`9n zTL2^-WB^h@vzUWB0AMA=IivcHA?FQw7f9C=7hutT9n|G2&6sWtsi3*64^eyO%zZAa zOli!8`Hc={?1$#7HJqp9eRJtaUa)V<#JnQSD~J%!wS1f4Gq{6K1HAzK#3dbTG`~De zPH(0IjrC^NF&Hms&f{47ah#W|g0RLqh~sA!&89aZ1jFKzA*P&85aWbs0%^gNrsp+? zAJm-g>1h<+0;cv#*&9-|rAaBZJ&a$qHyn|qOmjjok7@@CY`{m9Q- zQLC2OS&#=&khi0D%{$ptJ{?WN-i>B%A?C5hbHo~ZBs#_Lebi|l!ux2cgSD5GSt&T{ zSlU|4m9}NNUg*|3S8-pY+gp6ML$tO2G}G>lEaioQJ2TgB-}!EN`R=lyh3uC?4QA)Q zzWDgg?d(bC@um4Mug^zxe(>bk<o)B zeBT8}8`@i-87&fiGjUEBf#4z=<_(y#4`hgkC`Lm|ObxBVS9pjS-Xog?q-1CVwUoW@ zD0hf9$x9-xDbMWoFe6*GSXPbA=DyFYmlz|}*l#hr-UUO#oELcHsP@odF7sX(ds7S< z&z7{?uT~{b4(H+h>Qf*;&@jnf9fTmy(x(*li$$z7>ksTdbRJHLojac%RRREY){6kcsL8K>F> zIPh#u%ow3vlyK*iQ95nN6p#ZXOcMY7qPJ1zytf8G>U9Ua=uxS}%4$lBp5Zp>R+h?M zmag{Jx9KFI-Ttm_#Bo}rDvsM=MVe<2cJaF~FW>m_i!2=FX~uM`PX*0o z%|z{qGxxZxGNmyW<~KT+h<`F)tsy-oADTl?@`8O^Cgv3x0zrge^4j(ZvR~T{!DsLc zJ`MB&36M)>tiklcI7QvbO=z??JB~qnL319*+K=PBWEBKA)>)b9x+$cR;AUQg-`PZD>+TZEwo55T-Rjhv6cxo4p{0Z$UM7 z3#y=FNMTyCFgEM1GEPd}3C=?2?;f|GE=|h~rTXJman|S?k4|`5J6%Vu@zwZ;*Nkmx OkT3@&Z9)|goBRg>^vv7< literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/gvaHistory.cpython-36.pyc b/tools/jaklis/lib/__pycache__/gvaHistory.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c50235f73c1ce5f8f99e700a24118de067f9e26a GIT binary patch literal 7637 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 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/gvaHistory.cpython-38.pyc b/tools/jaklis/lib/__pycache__/gvaHistory.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a06ef2aeda943d40f8cfba42334f3fa8cfe519c4 GIT binary patch literal 7636 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 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/gvaID.cpython-36.pyc b/tools/jaklis/lib/__pycache__/gvaID.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec2a402b0f84e532bc3d9e497892e80b727e3fa4 GIT binary patch literal 2205 zcmd5-OLH7G5SBF0o!zw^$9Z868!A+JsARVS;Xo=Ch-1f?RP0o2D$b-*!|X`5XFZRN zMq}d5EEoIgGY8;K?i{)07r;@)jg$WYl@qPmb%4aVqbjvp-BPR7YJL4lv+4Zu{U6_4 zauE6x{i|ufe*rxo0HKKD7ZmlCYEPmwN{Jmq~_HU$8(ap zSFdDx+(;T;BWZfgYHq|6$)q<4yy;CXA!8E4eh zq_&v|sprvFD(hQJ_e3#eHYHR$z;N{%HK+-k zPAzcLkz|=l6dL30CX71t+tBlyKuTO9AC(%PD7DlPxWp9ilO6#nDRrQl&`UzeAs!mS zC=IGn{ZMI5iom|HBF%o$%~=Sy(h450!)0sY_*!yylZkNK&%CLOsnW<5OzUDMgwUOqvgXM*GAI|mtD2;^gD?2%49zw*# zXR&EIcu-%a?PL9&kSHaz8uV%CRXqO!ro?Y_)VYDunX#r0+U6`8NkKL- zUi~|r99nd0Xs74L(o+}E&>Hj8z}v#<)nVUhI&(-M6V9vzUtxrt)*J0tK^n5TW6#G|c$XVNk`<{q(bC)Y|0@~%_SUv{$MXLgvNx`z zuPraly~3_bs$VP3Q5XR`CU*lK1>KnWyFpyAy!Ze<(;~n|*rKe(U%m*7{>v9U5K)%4 zXwk~En6m4R#Lf|99g^1gVWc?<`4$MPc0^(Dbd@}wc~9*x?o?T^qrJ!4@Zn5->63t3T=EP^V~%VCj0 zi=>~sHGWz-;XqB8ALWm9s<)gkqL|8hKj1-mV2qHQrbz390sq1)JCwK-=;q&-?qJyS^C&&zGU>%!48)V=B zw2e)yz{@D$tB8y5xQ0SSg~h%v9p6tfTEH9mvf=wriy$6p_&K$uqsTw5Rh;sVE0C;1 z&sF8IiPNkk?lk`p=8|wGtt@Z%1FJ$x0ZRB75$ux!HL<= zPCmG~m^}0zoIUgD)?!Rxe=C(H2YhS+p`EI!nV~mFH>jO6XbA zqGc9Cq`302wgiN<%YyMNOc}R<&8=D%jbdm*oLREUrxa7qa?1F$QaYf(1c2v=ABOGa z+0J^Nvd$`GsM8*>tP>W3uXN)u>nN%hZX|KhN#bs2Z8KcF+U~=>K@exL2m)oNsOyoK z_y{&l2M?Nyw7n<477@_cTMa;zavjq@0jV&b6i}kuct|oEFru&u^)S^G{0>q52aOuv zlc6@40S@VD6{$38?vSB=4`o+%B=il3KgDQhU^Mth7(MfuMJ-`h#x_0*1$h+xh%pkz zvj%M_>>fG~B|bF1F;>uhl#vxw=|dAd`K8jSa~xzFg#SdF)QxdvQs*MdX2y~xNGDF9 z5f^xqW9{GQ)X<{SLpwV*<{mhXhSnHA2)HerUK7q8qBA=Ls^Rcb_$7v5wBA^cO2)TZ zv!|d^Aioz{x61BywzaSso;=+;RYW}Q3t(>Cvwy(l)Uv4){!YpF5d!CP<@GdvPG%qu;ucf!`|CclP^{r#?j^+O~ zWN$o4zq7b7`x1K|ssCJQj=~82V`?+xao9~*uo)&LE6NYxXPSrB5w$35@fXjdvj5^a z4@I13En2pUJfZA@BeAm!RfnWCPK-21AzuS#y>=;VzOIVLH*cu(<+ZwFNIM?a$FBOL z!3Q(WsOr@&SFLi?TMhyyE%um2rC_o#|2SfO<%86UtpfZP(1f@&zUDEAHE1(ZXOCl; z2)zQ73|glB!fWtD$_WSN!`!HTq*Lc|u8b2ZoBfc7X;Bk?vliTj2qZ#c=G)59952PF z&9#(gs>zMO5x}HXu#8^KqxXO)&H+F~#{`qV%}E7ySI6#Orb+M&p2o-U`$Px1izmqp zaj}j~fG!z0(B#I3mLNZ>tJT26*E~b@N7a>sKsrH?=Cp)N1=0bp4(ox`F z2Ww1u$yKXt!dIvnWD{qi=6DDBhd@ihnY8kv-4DgOBzeI^Rl)mY@pVm(GiiK~akVVJ({SM#PF#`(Gs{d%fM_txFH>+|=6+w%+aE43T@ Yc`WUVqlaEnmlQ65SqPg7q6;DX2Oek=$p8QV literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc b/tools/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc2076721a6acf9b5b0baab93791948dd7d88293 GIT binary patch literal 5853 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 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/messaging.cpython-38.pyc b/tools/jaklis/lib/__pycache__/messaging.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11ab222a23dee82e09a8e9eaa1cac1004f5ce004 GIT binary patch literal 6703 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 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/natools.cpython-36.pyc b/tools/jaklis/lib/__pycache__/natools.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b1efdefe60c689980c708689241f9d8fe3e7f2d GIT binary patch literal 9291 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 XL?!j~;@?TjPTAHVYI)p~vgZE-_l%&s literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/natools.cpython-38.pyc b/tools/jaklis/lib/__pycache__/natools.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b903497e47353ff90f4df7bca4f181f055e79e91 GIT binary patch literal 9408 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 z zigIgwF{v{gzCGy|Y1C(y+x`TR{EtTj?SE zRAfVCt~D~}t~>Rba(eQz?gBc??$!l#6*VxwA?kG;_=a1j4i1=tpLmQulALY5_rQ^K zDx6|ZC3%_AwnVZquqK98je=?*%nkV> zck#?ejdAp9Wnza^X9RSYRn%^!Kii)AY+Kz)5sI>6%Z?AcpKZewil;o@2JTwvL@SiIJh3xu> z>#Paz_&y;iH~WNYrwM#aKpmo9N3GZ!t@HR0T+G)fuyx=1aKI|<8BLusqKm?Jh{}%% z%n&#Sfa?Y93Up^2AE+fY=5BA6g^J#xF60~`pCz!DwD0+Sb{b2nSr^w$VEF$IkUE+& zCJkoUxvZ5nv!#TwgMK}0(76XC{w^5(`Y@j8%|=P1u07E*ig)~h{z>C_(nn)0oSx1Yr_mFY V)RM>FalM!F0jzd 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 C<^H1p literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/stars.cpython-36.pyc b/tools/jaklis/lib/__pycache__/stars.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77f0337f1afe993ec4e838031ef4e9623db18faa GIT binary patch literal 6044 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 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/__pycache__/stars.cpython-38.pyc b/tools/jaklis/lib/__pycache__/stars.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..388a01d2841ac3aaf21128fbada73e8db9180b4d GIT binary patch literal 5931 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 literal 0 HcmV?d00001 diff --git a/tools/jaklis/lib/cesium.py b/tools/jaklis/lib/cesium.py new file mode 100755 index 00000000..e8225296 --- /dev/null +++ b/tools/jaklis/lib/cesium.py @@ -0,0 +1,120 @@ +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.stars import ReadLikes, SendLikes, UnLikes +from lib.offers import Offers + +class CesiumPlus(CesiumCommon): + + #################### Messaging #################### + + def read(self, nbrMsg, outbox, isJSON): + readCesium = ReadFromCesium(self.dunikey, self.pod) + jsonMsg = readCesium.sendDocument(nbrMsg, outbox) + if isJSON: + jsonFormat = readCesium.jsonMessages(jsonMsg, nbrMsg, outbox) + print(jsonFormat) + else: + readCesium.readMessages(jsonMsg, nbrMsg, 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)) + sendCesium.nonce = base64.b64decode(''.join(nonce)) + + finalDoc = sendCesium.configDoc(sendCesium.encryptMsg(title), sendCesium.encryptMsg(msg)) # Configure JSON document to send + sendCesium.sendDocument(finalDoc, outbox) # Send final signed document + + def delete(self, idsMsgList, outbox): + deleteCesium = DeleteFromCesium(self.dunikey, self.pod) + # deleteCesium.issuer = recipient + for idMsg in idsMsgList: + 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): + 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: + scope = 'title' + else: + scope = '_id' + + document = getProfile.configDocGet(profile, scope, avatar) + resultJSON = getProfile.sendDocument(document, 'get') + result = getProfile.parseJSON(resultJSON) + + print(result) + + def erase(self): + eraseProfile = Profiles(self.dunikey, self.pod) + document = eraseProfile.configDocErase() + result = eraseProfile.sendDocument(document,'erase') + + print(result) + + #################### 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) + + #################### Offer #################### + + def setOffer(self, title=None, description=None, city=None, localisation=None, category=None, price=None, picture=None): + setOffer = Offers(self.dunikey, self.pod) + document = setOffer.configDocSet(title, description, city, localisation, category, price, picture) + result = setOffer.sendDocumentSet(document,'set') + + # print(result) + return result + + def getOffer(self, id, avatar=None): + getOffer = Offers(self.dunikey, self.pod, self.noNeedDunikey) + + resultJSON = getOffer.sendDocumentGet(id, 'get') + # print(resultJSON) + result = getOffer.parseJSON(resultJSON) + + print(result) + + def deleteOffer(self, id): + eraseOffer = Offers(self.dunikey, self.pod) + document = eraseOffer.configDocErase(id) + result = eraseOffer.sendDocumentSet(document,'delete', id) + + print(result) diff --git a/tools/jaklis/lib/cesiumCommon.py b/tools/jaklis/lib/cesiumCommon.py new file mode 100755 index 00000000..b68337d2 --- /dev/null +++ b/tools/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/tools/jaklis/lib/crypt.py b/tools/jaklis/lib/crypt.py new file mode 100755 index 00000000..ee4cfb2a --- /dev/null +++ b/tools/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/tools/jaklis/lib/currentUd.py b/tools/jaklis/lib/currentUd.py new file mode 100644 index 00000000..f038ac5e --- /dev/null +++ b/tools/jaklis/lib/currentUd.py @@ -0,0 +1,40 @@ +#!/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 + +class currentUd: + + def __init__(self, node): + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build UD generation document + queryBuild = gql( + """ + query { + currentUd { + amount + } + } + """ + ) + paramsBuild = { + } + + # Send UD document + try: + udValue = 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 DU:\n" + message + "\n") + sys.exit(1) + + udValueFinal = udValue['currentUd']['amount'] + + return udValueFinal diff --git a/tools/jaklis/lib/gva.py b/tools/jaklis/lib/gva.py new file mode 100755 index 00000000..c4173c9a --- /dev/null +++ b/tools/jaklis/lib/gva.py @@ -0,0 +1,76 @@ +from lib.currentUd import currentUd +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 +from lib.gvaID import Id + +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, number=10): + gva = History(self.dunikey, self.node, self.destPubkey) + gva.sendDoc(number) + 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) + + def id(self, pubkey, username): + gva = Id(self.dunikey, self.node, pubkey, username) + result = gva.sendDoc() + print(result) + + def idBalance(self, pubkey): + gva = Id(self.dunikey, self.node, pubkey) + result = gva.sendDoc(True) + print(result) + + def currentUd(self): + gva = currentUd(self.node) + result = gva.sendDoc() + print(result) \ No newline at end of file diff --git a/tools/jaklis/lib/gvaBalance.py b/tools/jaklis/lib/gvaBalance.py new file mode 100755 index 00000000..1a20ea2f --- /dev/null +++ b/tools/jaklis/lib/gvaBalance.py @@ -0,0 +1,53 @@ +#!/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: PkOrScriptGva!){ + 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) + + if (balanceResult['balance'] == None): balanceValue = 'null' + else: + balanceValue = balanceResult['balance']['amount']/100 + + # print(balanceValue) + return balanceValue diff --git a/tools/jaklis/lib/gvaHistory.py b/tools/jaklis/lib/gvaHistory.py new file mode 100755 index 00000000..39563a7e --- /dev/null +++ b/tools/jaklis/lib/gvaHistory.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast, time, hashlib +from datetime import datetime +from duniterpy.key import base58 +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, number): + # Build history generation document + queryBuild = gql( + """ + query ($pubkey: PubKeyGva!, $script: PkOrScriptGva!, $number: Int!){ + txsHistoryBc( + script: $script + pagination: { pageSize: $number, ord: DESC } + ) { + both { + pageInfo { + hasPreviousPage + hasNextPage + } + edges { + direction + node { + currency + issuers + outputs + comment + writtenTime + } + } + } + } + txsHistoryMp(pubkey: $pubkey) { + receiving { + currency + issuers + comment + outputs + receivedTime + } + receiving { + currency + issuers + comment + outputs + receivedTime + } + } + balance(script: $script) { + amount + base + } + node { + peer { + currency + } + } + currentUd { + amount + base + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey, + "number": number, + "script": f"SIG({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 + + + # Parse transactions in blockchain + resBc = [] + resBc = self.historyDoc['txsHistoryBc']['both']['edges'] + for j, transaction in enumerate(resBc): + # print(transaction) + direction = resBc[j]['direction'] + transaction = resBc[j]['node'] + output = transaction['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + # if direction == 'RECEIVED' or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(direction) + trans[i].append(transaction['writtenTime']) + if direction == 'SENT': + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(transaction['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(transaction['comment']) + trans[i].append(base) + i += 1 + + # Parse transactions in mempool + for direction in self.historyDoc['txsHistoryMp']: + resBc = [] + resBc = self.historyDoc['txsHistoryMp'][direction] + for j, transaction in enumerate(resBc): + # print(transaction) + transaction = resBc[j] + output = transaction['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + # if direction == 'RECEIVING' or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(direction) + trans[i].append(int(time.time())) + if direction == 'SENDING': + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(transaction['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(transaction['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 + if (self.historyDoc['balance'] == None): + balance = balanceUD = 'null' + else: + + 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] == "SENT": color = "blue" + elif t[0] == "receiving": color = "yellow" + elif t[0] == "sending": color = "red" + else: color = None + 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='') + checksum = self.gen_checksum(t[2]) + shortPubkey = t[2][0:4] + '\u2026' + t[2][-4:] + ':' + checksum + if noColors: + print(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, shortPubkey, t[3], t[4], comment)) + else: + print(colored(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, shortPubkey, 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 gen_checksum(self, pubkey): + """ + Returns the checksum of the input pubkey (encoded in b58) + thx Matograine + """ + pubkey_byte = base58.Base58Encoder.decode(str.encode(pubkey)) + hash = hashlib.sha256(hashlib.sha256(pubkey_byte).digest()).digest() + return base58.Base58Encoder.encode(hash)[:3] + + 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/tools/jaklis/lib/gvaID.py b/tools/jaklis/lib/gvaID.py new file mode 100644 index 00000000..023a1442 --- /dev/null +++ b/tools/jaklis/lib/gvaID.py @@ -0,0 +1,81 @@ +#!/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 Id: + + def __init__(self, dunikey, node, pubkey='', username=''): + + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.username = username + # 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, getBalance=False): + # Build balance generation document + if (getBalance): + queryBuild = gql( + """ + query ($pubkey: PubKeyGva!, $script: PkOrScriptGva!){ + idty (pubkey: $pubkey) { + isMember + username + } + balance(script: $script) { + amount + } + } + """ + ) + else: + queryBuild = gql( + """ + query ($pubkey: PubKeyGva!){ + idty (pubkey: $pubkey) { + isMember + username + } + } + """ + ) + + paramsBuild = { + "pubkey": self.pubkey, + "script": f"SIG({self.pubkey})" + } + + # Send balance document + try: + queryResult = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + sys.stderr.write("Echec de récupération du solde:\n" + str(e) + "\n") + sys.exit(1) + + jsonBrut = queryResult + + if (getBalance): + if (queryResult['balance'] == None): + jsonBrut['balance'] = {"amount": 0.0} + else: + jsonBrut['balance'] = queryResult['balance']['amount']/100 + + if (queryResult['idty'] == None): + username = 'Matiou' + isMember = False + else: + username = queryResult['idty']['username'] + isMember = queryResult['idty']['isMember'] + + return json.dumps(jsonBrut, indent=2) diff --git a/tools/jaklis/lib/gvaPay.py b/tools/jaklis/lib/gvaPay.py new file mode 100755 index 00000000..caa084f3 --- /dev/null +++ b/tools/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])[0-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: PkOrScriptGva!, $issuer: PubKeyGva!, $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/tools/jaklis/lib/messaging.py b/tools/jaklis/lib/messaging.py new file mode 100755 index 00000000..2165182d --- /dev/null +++ b/tools/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/tools/jaklis/lib/natools.py b/tools/jaklis/lib/natools.py new file mode 100755 index 00000000..18f06d12 --- /dev/null +++ b/tools/jaklis/lib/natools.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 + +""" + CopyLeft 2020 Pascal Engélibert + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +""" + +__version__ = "1.3.1" + +import os, sys, duniterpy.key, libnacl, base58, base64, getpass + +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 + +def read_data(data_path, b=True): + if data_path == "-": + if b: + return sys.stdin.buffer.read() + else: + return sys.stdin.read() + else: + return open(os.path.expanduser(data_path), "rb" if b else "r").read() + +def write_data(data, result_path): + if result_path == "-": + os.fdopen(sys.stdout.fileno(), 'wb').write(data) + else: + open(os.path.expanduser(result_path), "wb").write(data) + +def encrypt(data, pubkey): + return duniterpy.key.PublicKey(pubkey).encrypt_seal(data) + +def decrypt(data, privkey): + return privkey.decrypt_seal(data) + +def box_encrypt(data, privkey, pubkey, nonce=None, attach_nonce=False): + signer = libnacl.sign.Signer(privkey.seed) + sk = libnacl.public.SecretKey(libnacl.crypto_sign_ed25519_sk_to_curve25519(signer.sk)) + verifier = libnacl.sign.Verifier(base58.b58decode(pubkey).hex()) + pk = libnacl.public.PublicKey(libnacl.crypto_sign_ed25519_pk_to_curve25519(verifier.vk)) + box = libnacl.public.Box(sk.sk, pk.pk) + data = box.encrypt(data, nonce) if nonce else box.encrypt(data) + return data if attach_nonce else data[24:] + +def box_decrypt(data, privkey, pubkey, nonce=None): + signer = libnacl.sign.Signer(privkey.seed) + sk = libnacl.public.SecretKey(libnacl.crypto_sign_ed25519_sk_to_curve25519(signer.sk)) + verifier = libnacl.sign.Verifier(base58.b58decode(pubkey).hex()) + pk = libnacl.public.PublicKey(libnacl.crypto_sign_ed25519_pk_to_curve25519(verifier.vk)) + box = libnacl.public.Box(sk.sk, pk.pk) + return box.decrypt(data, nonce) if nonce else box.decrypt(data) + +def sign(data, privkey): + return privkey.sign(data) + +def verify(data, pubkey): + try: + ret = libnacl.sign.Verifier(duniterpy.key.PublicKey(pubkey).hex_pk()).verify(data) + sys.stderr.write("Signature OK!\n") + return ret + except ValueError: + sys.stderr.write("Bad signature!\n") + exit(1) + +def get_privkey(privkey_path, privkey_format): + if privkey_format == "pubsec": + if privkey_path == "*": + privkey_path = "privkey.pubsec" + return duniterpy.key.SigningKey.from_pubsec_file(privkey_path) + + elif privkey_format == "cred": + if privkey_path == "*": + privkey_path = "-" + if privkey_path == "-": + return duniterpy.key.SigningKey.from_credentials(getpass.getpass("Password: "), getpass.getpass("Salt: ")) + else: + return duniterpy.key.SigningKey.from_credentials_file(privkey_path) + + elif privkey_format == "seedh": + if privkey_path == "*": + privkey_path = "authfile.seedhex" + return duniterpy.key.SigningKey.from_seedhex(read_data(privkey_path, False)) + + elif privkey_format == "wif": + if privkey_path == "*": + privkey_path = "authfile.wif" + return duniterpy.key.SigningKey.from_wif_or_ewif_file(privkey_path) + + elif privkey_format == "wifh": + if privkey_path == "*": + privkey_path = "authfile.wif" + return duniterpy.key.SigningKey.from_wif_or_ewif_hex(privkey_path) + + elif privkey_format == "ssb": + if privkey_path == "*": + privkey_path = "secret" + return duniterpy.key.SigningKey.from_ssb_file(privkey_path) + + elif privkey_format == "key": + if privkey_path == "*": + privkey_path = "authfile.key" + return duniterpy.key.SigningKey.from_private_key(privkey_path) + + print("Error: unknown privkey format") + +def fill_pubkey(pubkey, length=32): + while pubkey[0] == 0: + pubkey = pubkey[1:] + return b"\x00"*(length-len(pubkey)) + pubkey + +def pubkey_checksum(pubkey, length=32, clength=3): + return base58.b58encode(libnacl.crypto_hash_sha256(libnacl.crypto_hash_sha256(fill_pubkey(base58.b58decode(pubkey), length)))).decode()[:clength] + +# returns (pubkey:bytes|None, deprecated_length:bool) +def check_pubkey(pubkey): + if ":" in pubkey: + parts = pubkey.split(":") + if len(parts[1]) < 3 or len(parts[1]) > 32: + return (None, False) + for i in range(32, 0, -1): + if pubkey_checksum(parts[0], i, len(parts[1])) == parts[1]: + return (parts[0], i < 32) + return (None, False) + return (pubkey, False) + +fmt = { + "raw": lambda data: data, + "16": lambda data: data.hex().encode(), + "32": lambda data: base64.b32encode(data), + "58": lambda data: base58.b58encode(data), + "64": lambda data: base64.b64encode(data), + "64u": lambda data: base64.urlsafe_b64encode(data), + "85": lambda data: base64.b85encode(data), +} + +defmt = { + "raw": lambda data: data, + "16": lambda data: bytes.fromhex(data), + "32": lambda data: base64.b32decode(data), + "58": lambda data: base58.b58decode(data), + "64": lambda data: base64.b64decode(data), + "85": lambda data: base64.b85decode(data), +} + +def show_help(): + print("""Usage: +python3 natools.py [options] + +Commands: + encrypt Encrypt data + decrypt Decrypt data + box-encrypt Encrypt data (NaCl box) + box-decrypt Decrypt data (NaCl box) + sign Sign data + verify Verify data + pubkey Display pubkey + pk Display b58 pubkey shorthand + +Options: + -c Display pubkey checksum + -f Private key format (default: cred) + key cred pubsec seedh ssb wif wifh + -i Input file path (default: -) + -I Input format: raw 16 32 58 64 85 (default: raw) + -k Privkey file path (* for auto) (default: *) + -n Nonce (b64, 24 bytes) (for NaCl box) + -N Attach nonce to output (for NaCl box encryption) + --noinc Do not include msg after signature + -o Output file path (default: -) + -O Output format: raw 16 32 58 64 64u 85 (default: raw) + -p Pubkey (base58) + + --help Show help + --version Show version + --debug Debug mode (display full errors) + +Note: "-" means stdin or stdout. +""") + +if __name__ == "__main__": + + if "--help" in sys.argv: + show_help() + exit() + + if "--version" in sys.argv: + print(__version__) + exit() + + privkey_format = getargv("-f", "cred") + data_path = getargv("-i", "-") + privkey_path = getargv("-k", "*") + pubkey = getargv("-p") + result_path = getargv("-o", "-") + output_format = getargv("-O", "raw") + input_format = getargv("-I", "raw") + + if pubkey: + pubkey, len_deprecated = check_pubkey(pubkey) + if not pubkey: + print("Invalid pubkey checksum! Please check spelling.") + exit(1) + if len(base58.b58decode(pubkey)) > 32: + print("Invalid pubkey: too long!") + exit(1) + if len_deprecated: + print("Warning: valid pubkey checksum, but deprecated format (truncating zeros)") + + try: + if sys.argv[1] == "encrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + write_data(fmt[output_format](encrypt(defmt[input_format](read_data(data_path)), pubkey)), result_path) + + elif sys.argv[1] == "decrypt": + write_data(fmt[output_format](decrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format))), result_path) + + elif sys.argv[1] == "box-encrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + nonce = getargv("-n", None) + if nonce: + nonce = base64.b64decode(nonce) + attach_nonce = "-N" in sys.argv + write_data(fmt[output_format](box_encrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format), pubkey, nonce, attach_nonce)), result_path) + + elif sys.argv[1] == "box-decrypt": + if not pubkey: + print("Please provide pubkey!") + exit(1) + nonce = getargv("-n", None) + if nonce: + nonce = base64.b64decode(nonce) + write_data(fmt[output_format](box_decrypt(defmt[input_format](read_data(data_path)), get_privkey(privkey_path, privkey_format), pubkey, nonce)), result_path) + + elif sys.argv[1] == "sign": + data = defmt[input_format](read_data(data_path)) + signed = sign(data, get_privkey(privkey_path, privkey_format)) + + if "--noinc" in sys.argv: + signed = signed[:len(signed)-len(data)] + + write_data(fmt[output_format](signed), result_path) + + elif sys.argv[1] == "verify": + if not pubkey: + print("Please provide pubkey!") + exit(1) + write_data(fmt[output_format](verify(defmt[input_format](read_data(data_path)), pubkey)), result_path) + + elif sys.argv[1] == "pubkey": + if pubkey: + if "-c" in sys.argv and output_format == "58": + write_data("{}:{}".format(pubkey, pubkey_checksum(pubkey)).encode(), result_path) + else: + write_data(fmt[output_format](base58.b58decode(pubkey)), result_path) + else: + pubkey = get_privkey(privkey_path, privkey_format).pubkey + if "-c" in sys.argv and output_format == "58": + write_data("{}:{}".format(pubkey, pubkey_checksum(pubkey)).encode(), result_path) + else: + write_data(fmt[output_format](base58.b58decode(pubkey)), result_path) + + elif sys.argv[1] == "pk": + if not pubkey: + pubkey = get_privkey(privkey_path, privkey_format).pubkey + if "-c" in sys.argv: + print("{}:{}".format(pubkey, pubkey_checksum(pubkey))) + else: + print(pubkey) + + else: + show_help() + + except Exception as e: + if "--debug" in sys.argv: + 0/0 # DEBUG MODE (raise error when handling error to display backtrace) + sys.stderr.write("Error: {}\n".format(e)) + show_help() + exit(1) diff --git a/tools/jaklis/lib/offers.py b/tools/jaklis/lib/offers.py new file mode 100644 index 00000000..ce406df6 --- /dev/null +++ b/tools/jaklis/lib/offers.py @@ -0,0 +1,138 @@ +import sys, re, json, requests, base64 +from time import time +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX + +class Offers(CesiumCommon): + # Configure JSON document SET to send + def configDocSet(self, title, description, city, localisation, category, price: float, picture): + timeSent = int(time()) + +# {"parent":"cat90","localizedNames":{"en":"Fruits & Vegetables","es-ES":"Frutas y Vegetales","fr-FR":"Fruits & Légumes"},"name":"Fruits & Légumes","id":"cat92"} + + data = {} + if title: data['title'] = title + if description: data['description'] = description + if city: data['city'] = city + if localisation: + geoPoint = {} + geoPoint['lat'] = localisation[0] + geoPoint['lon'] = localisation[1] + data['geoPoint'] = geoPoint + if picture: + picture = open(picture, 'rb').read() + picture = base64.b64encode(picture).decode() + data['thumbnail'] = {} + data['thumbnail']['_content'] = picture + data['thumbnail']['_content_type'] = "image/png" + # if category: data['category'] = category + # else: + data['category'] = {"parent":"cat24","localizedNames":{"en":"DVD / Films","es-ES":"DVDs / Cine","fr-FR":"DVD / Films"},"name":"DVD / Films","id":"cat25"} + if price: data['price'] = float(price) * 100 + data['type'] = 'offer' + data['time'] = timeSent + data['creationTime'] = timeSent + data['issuer'] = self.pubkey + data['pubkey'] = self.pubkey + data['version'] = 2 + data['currency'] = 'g1' + data['unit'] = None + data['fees'] = None + data['feesCurrency'] = None + if picture: data['picturesCount'] = 1 + else: data['picturesCount'] = 0 + data['stock'] = 1 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + # Configure JSON document SET to send + def configDocErase(self, id): + timeSent = int(time()) + +# "currency":"g1","unit":null,"fees":null,"feesCurrency":null,"picturesCount":0,"stock":0,"tags":[],"id":"AXehXeyZaml2THvBAeS5","creationTime":1613320117} +#AXehXeyZaml2THvBAeS5 + + + offerToDeleteBrut = self.sendDocumentGet(id, 'get') + offerToDelete = json.loads(self.parseJSON(offerToDeleteBrut)) + + title = offerToDelete['title'] + creationTime = offerToDelete['time'] + issuer = offerToDelete['issuer'] + pubkey = offerToDelete['pubkey'] + + data = {} + data['title'] = title + data['time'] = timeSent + data['creationTime'] = creationTime + data['id'] = id + data['issuer'] = issuer + data['pubkey'] = pubkey + data['version'] = 2 + data['type'] = "offer" + data['currency'] = "g1" + data['unit'] = None + data['fees'] = None + data['feesCurrency'] = None + data['picturesCount'] = 0 + data['stock'] = 0 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocumentGet(self, id, type): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/market/record'.format(self.pod) + elif type == 'get': + reqQuery = '{0}/market/record/{1}?_source=category,title,description,issuer,time,creationTime,location,address,city,price,unit,currency,thumbnail._content_type,thumbnail._content,picturesCount,type,stock,fees,feesCurrency,geoPoint,pubkey,freePrice'.format(self.pod, id) + elif type == 'erase': + reqQuery = '{0}/market/delete'.format(self.pod) + + + result = requests.get(reqQuery, headers=headers) + # print(result) + 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 sendDocumentSet(self, document, type, id=None): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/market/record'.format(self.pod) + if type == 'delete': + reqQuery = '{0}/market/record/{1}/_update'.format(self.pod, id) + + 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)['_source'] + if doc: + # pubkey = { "pubkey": doc['issuer'] } + # rest = { "description": doc['description'] } + # final = {**pubkey, **rest} + return json.dumps(doc, indent=2) + else: + return 'Profile vide' diff --git a/tools/jaklis/lib/profiles.py b/tools/jaklis/lib/profiles.py new file mode 100755 index 00000000..cfe25920 --- /dev/null +++ b/tools/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","geoPoint"], + "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/tools/jaklis/lib/qrcode-reader.py b/tools/jaklis/lib/qrcode-reader.py new file mode 100755 index 00000000..f92bfcd1 --- /dev/null +++ b/tools/jaklis/lib/qrcode-reader.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +from io import BytesIO +import base64, base58, varint, os, json +# from lib.cesium import CesiumPlus as cs + +## BytesIO adds a stream interface to bytes +## Exemple: +qr = BytesIO(bytes.fromhex("8316140212c28e52e034ecaf684fa3e5d755db519074f27ad086bddffd26b386e55f3b623ca01f0177c0f8ce5f6a69764c7bc10263ec")) + +## Read from a file: +# qr = open("qrcode-AXfA-M5faml2THvBAmPs.bin","rb") +# qr = BytesIO(qr.read()) + +## Check magic number +assert qr.read(3) == b"\x83\x16\x14" + +## Read data type +data_type = varint.decode_stream(qr) + +## Read price type +raw_price_type = varint.decode_stream(qr) +price_type = raw_price_type >> 4 +amount_len = raw_price_type & 0b1111 + +## Read pubkey +pubkey = qr.read(32) +pubkey_b58 = base58.b58encode(pubkey) +# print("Pubkey: {}".format(pubkey_b58.decode("utf-8"))) + +## Read amount + +if price_type == 0: # Free price, ignore amount + qr.read(amount_len) + print("Free price") + +elif price_type == 1: # Units + amount = varint.decode_stream(qr) + # print("Price: {} Ğ1".format(amount/100)) + +elif price_type == 2: # UD + amount_n = varint.decode_stream(qr) + amount_e = varint.decode_stream(qr) + amount = amount_n * 10 ** -amount_e + # print("Price: {} UD_Ğ1".format(amount.decode("utf-8"))) + +else: + qr.read(amount_len) + print("Error: unknown price type, ignoring price") + +## Read data + +if data_type == 0: # No data + data = None + print("There is no data") + +elif data_type == 1: # Plain text + data = qr.read() + print("Data:") + print(data) + +elif data_type == 2: # Ğchange ad + data = base64.urlsafe_b64encode(qr.read(16)) + # print("Ğchange ad ID: {}".format(data.decode("utf-8"))) + + +## Get gchange-pod datas + +item = os.popen("./../jaklis/jaklis.py getoffer -i {0}".format(data.decode("utf-8"))) +# item = cs.getOffer(id) + + +jsonR = json.load(item) +item_time = jsonR['creationTime'] +item_name = jsonR['title'] +item_description = jsonR['description'] +item_image = jsonR['thumbnail'] +isImage = '_content' in item_image +if (isImage): + print(item_image['_content']) + +# print(jsonR) +print(item_time) +print(item_name) +print(item_description) + diff --git a/tools/jaklis/lib/stars.py b/tools/jaklis/lib/stars.py new file mode 100755 index 00000000..5eee3392 --- /dev/null +++ b/tools/jaklis/lib/stars.py @@ -0,0 +1,242 @@ +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 + +class ReadLikes(CesiumCommon): + # Configure JSON document to send + def configDoc(self, profile): + if not profile: profile = self.pubkey + + data = {} + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = [ + {'term': {'index': 'user'}}, + {'term': {'type': 'profile'}}, + {'term': {'id': profile}}, + {'term': {'kind': 'STAR'}} + ] + # data['query']['bool']['should'] = {'term':{'issuer': self.issuer}} + data['size'] = 5000 + data['_source'] = ['issuer','level'] + data['aggs'] = { + 'level_sum': { + 'sum': { + 'field': 'level' + } + } + } + + return json.dumps(data) + + def sendDocument(self, document): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/like/record/_search'.format(self.pod), 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 de lecture des messages...\n" + result.text + '\n') + + def parseResult(self, result): + result = json.loads(result) + totalLikes = result['hits']['total'] + totalValue = result['aggregations']['level_sum']['value'] + if totalLikes: + score = totalValue/totalLikes + else: + score = 0 + raw = result['hits']['hits'] + finalPrint = {} + finalPrint['likes'] = [] + for i in raw: + issuer = i['_source']['issuer'] + # print(issuer) + gProfile = self.getProfile(issuer) + try: + pseudo = gProfile['title'] + except: + pseudo = '' + try: + payTo = gProfile['pubkey'] + except: + payTo = '' + id = i['_id'] + level = i['_source']['level'] + 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 }) + finalPrint['score'] = score + + return json.dumps(finalPrint) + + def getProfile(self, profile): + headers = { + 'Content-type': 'application/json', + } + + data = {} + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = [ + {'term': {'_index': 'user'}}, + {'term': {'_type': 'profile'}}, + {'term': {'_id': profile}} + ] + data['_source'] = ['title','pubkey'] + + 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'] + for i in result: + return i['_source'] + + +#################### Like class #################### + + +class SendLikes(CesiumCommon): + # Configure JSON document to send + def configDoc(self, profile, likes): + 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 + + + timeSent = int(time.time()) + + data = {} + data['version'] = 2 + data['index'] = "user" + data['type'] = "profile" + data['id'] = profile + data['kind'] = "STAR" + data['level'] = likes + data['time'] = timeSent + data['issuer'] = self.pubkey + + 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, pubkey): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/user/profile/:id/_like'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + print(colored("Profile liké avec succès !", 'green')) + return result.text + elif result.status_code == 400: + resultJson = json.loads(result.text) + if 'DuplicatedDocumentException' in resultJson['error']: + rmLike = UnLikes(self.dunikey, self.pod) + idLike = rmLike.checkLike(pubkey) + if idLike: + document = rmLike.configDoc(idLike) + rmLike.sendDocument(document, True) + sleep(0.5) + self.sendDocument(document, pubkey) + return resultJson['error'] + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') + else: + resultJson = json.loads(result.text) + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') + + +#################### Unlike class #################### + + +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) + result = readProfileLikes.parseResult(result) + result = json.loads(result) + + if 'yours' in result: + myLike = result['yours']['id'] + return myLike + else: + sys.stderr.write("Vous n'avez pas liké ce profile\n") + return False + + # Configure JSON document to send + def configDoc(self, idLike): + timeSent = int(time.time()) + + data = {} + data['version'] = 2 + data['index'] = "like" + data['type'] = "record" + data['id'] = idLike + data['issuer'] = self.pubkey + 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, silent): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + if not silent: + print(colored("Like supprimé avec succès !", 'green')) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') diff --git a/tools/jaklis/paiements.py b/tools/jaklis/paiements.py new file mode 100755 index 00000000..dde58da6 --- /dev/null +++ b/tools/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/tools/jaklis/requirements.txt b/tools/jaklis/requirements.txt new file mode 100755 index 00000000..67be2607 --- /dev/null +++ b/tools/jaklis/requirements.txt @@ -0,0 +1,9 @@ +wheel +base58 +pybase64 +duniterpy==0.62.0 +termcolor +python-dotenv +gql==3.0.0a5 +#gql==2.0 +requests diff --git a/tools/jaklis/setup.sh b/tools/jaklis/setup.sh new file mode 100755 index 00000000..222e4ba9 --- /dev/null +++ b/tools/jaklis/setup.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +for i in gcc python3-pip python3-setuptools libpq-dev python3-dev python3-wheel; do + if [ $(dpkg-query -W -f='${Status}' $i 2>/dev/null | grep -c "ok installed") -eq 0 ]; then + [[ ! $j ]] && sudo apt update + sudo apt install -y $i + j=1 + fi +done + +pip3 install -r requirements.txt +chmod u+x jaklis.py