From b11b7dc20711bc22995575a19d08869562bfb714 Mon Sep 17 00:00:00 2001 From: qo-op Date: Sat, 12 Dec 2020 21:00:01 +0100 Subject: [PATCH] new jaklis --- zen/jaklis/.env | 3 +- zen/jaklis/.env.template | 4 +- zen/jaklis/README.md | 22 +- zen/jaklis/jaklis.py | 200 ++++--- zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc | Bin 13945 -> 3273 bytes .../lib/__pycache__/cesiumCommon.cpython-36.pyc | Bin 0 -> 1711 bytes zen/jaklis/lib/__pycache__/gva.cpython-36.pyc | Bin 0 -> 1890 bytes zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc | Bin 0 -> 5834 bytes zen/jaklis/lib/__pycache__/likes.cpython-36.pyc | Bin 7954 -> 6035 bytes .../lib/__pycache__/messaging.cpython-36.pyc | Bin 0 -> 6749 bytes zen/jaklis/lib/__pycache__/natools.cpython-36.pyc | Bin 9296 -> 9282 bytes zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc | Bin 0 -> 3030 bytes zen/jaklis/lib/cesium.py | 576 ++------------------- zen/jaklis/lib/cesiumCommon.py | 51 ++ zen/jaklis/lib/crypt.py | 31 ++ zen/jaklis/lib/gva.py | 59 +++ zen/jaklis/lib/gvaBalance.py | 50 ++ zen/jaklis/lib/gvaHistory.py | 209 ++++++++ zen/jaklis/lib/gvaPay.py | 172 ++++++ zen/jaklis/lib/likes.py | 121 +---- zen/jaklis/lib/messaging.py | 236 +++++++++ zen/jaklis/lib/profiles.py | 125 +++++ zen/jaklis/paiements.py | 94 ++++ zen/jaklis/requirements.txt | 1 + 24 files changed, 1249 insertions(+), 705 deletions(-) create mode 100644 zen/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc create mode 100644 zen/jaklis/lib/__pycache__/gva.cpython-36.pyc create mode 100644 zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc create mode 100644 zen/jaklis/lib/__pycache__/messaging.cpython-36.pyc create mode 100644 zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc create mode 100644 zen/jaklis/lib/cesiumCommon.py create mode 100755 zen/jaklis/lib/crypt.py create mode 100644 zen/jaklis/lib/gva.py create mode 100644 zen/jaklis/lib/gvaBalance.py create mode 100644 zen/jaklis/lib/gvaHistory.py create mode 100644 zen/jaklis/lib/gvaPay.py create mode 100644 zen/jaklis/lib/messaging.py create mode 100644 zen/jaklis/lib/profiles.py create mode 100755 zen/jaklis/paiements.py diff --git a/zen/jaklis/.env b/zen/jaklis/.env index 93e5abf..da3302a 100644 --- a/zen/jaklis/.env +++ b/zen/jaklis/.env @@ -1,4 +1,5 @@ DUNIKEY="/.zen/secret.dunikey" # Chemin du fichier de trousseau Ḡ1 de l'émetteur, au format PubSec #POD="https://data.gchange.fr" # Noeud Gchange utilisé pour l'envoi du message #POD="https://g1.data.duniter.fr" # Noeud Cecium+ utilisé pour l'envoi du message -POD="https://g1.data.le-sou.org" # Adresse du pod Cesium de secours +#POD="https://g1.data.le-sou.org" # Adresse du pod Cesium de secours +POD="https://g1.data.e-is.pro" diff --git a/zen/jaklis/.env.template b/zen/jaklis/.env.template index 5063635..dd81446 100644 --- a/zen/jaklis/.env.template +++ b/zen/jaklis/.env.template @@ -1,4 +1,6 @@ DUNIKEY="" # Chemin de la clé privé Ḡ1 de l'émetteur, au format PubSec #POD="https://g1.data.duniter.fr" # Adresse du pod Cesium ou Gchange à utiliser POD="https://g1.data.le-sou.org" # Adresse du pod Cesium de secours -#POD="https://data.gchange.fr" # Adresse du pod ḠChange à utiliser \ No newline at end of file +#POD="https://data.gchange.fr" # Adresse du pod ḠChange à utiliser + +NODE="https://g1.librelois.fr/gva" diff --git a/zen/jaklis/README.md b/zen/jaklis/README.md index 740f446..59f1e61 100644 --- a/zen/jaklis/README.md +++ b/zen/jaklis/README.md @@ -20,10 +20,18 @@ Renseignez optionnellement le fichier **.env** (Généré lors de la première t ``` ``` -usage: jaklis.py [-h] [-v] [-k KEY] [-n NODE] {read,send,delete,get,set,erase,like,unlike} ... +usage: jaklis.py [-h] [-v] [-k KEY] [-n NODE] {read,send,delete,get,set,erase,like,unlike,pay,history,balance} ... -positional arguments: - {read,send,delete,get,set,erase,like,unlike} +Client CLI pour Cesium+ et Ḡchange + +optional arguments: + -h, --help show this help message and exit + -v, --version Affiche la version actuelle du programme + -k KEY, --key KEY Chemin vers mon trousseau de clé (PubSec) + -n NODE, --node NODE Adresse du noeud Cesium+, Gchange ou Duniter à utiliser + +Commandes de jaklis: + {read,send,delete,get,set,erase,like,unlike,pay,history,balance} read Lecture des messages send Envoi d'un message delete Supression d'un message @@ -32,12 +40,10 @@ positional arguments: erase Effacer son profile Cesium+ like Voir les likes d'un profile / Liker un profile (option -s NOTE) unlike Supprimer un like + pay Payer en Ḡ1 + history Voir l'historique des transactions d'un compte Ḡ1 + balance Voir le solde d'un compte Ḡ1 -optional arguments: - -h, --help show this help message and exit - -v, --version Affiche la version actuelle du programme - -k KEY, --key KEY Chemin vers mon trousseau de clé (PubSec) - -n NODE, --node NODE Adresse du noeud Cesium+ ou Gchange à utiliser ``` Utilisez `./jaklis CMD -h` où `CMD` est la commande souhaité pour obtenir l'aide détaillé de cette commande. diff --git a/zen/jaklis/jaklis.py b/zen/jaklis/jaklis.py index 950e631..0f40166 100755 --- a/zen/jaklis/jaklis.py +++ b/zen/jaklis/jaklis.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 -import argparse, sys, os, random, string, getpass, json +import argparse, sys, os, getpass, string, random from os.path import join, dirname from shutil import copyfile from dotenv import load_dotenv from duniterpy.key import SigningKey -from lib.cesium import ReadFromCesium, SendToCesium, DeleteFromCesium, Profiles -from lib.likes import ReadLikes, SendLikes, UnLikes -VERSION = "0.0.1" +__version__ = "0.0.2" + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' # Get variables environment -if not os.path.isfile('.env'): - copyfile(".env.template", ".env") -dotenv_path = join(dirname(__file__), '.env') +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') load_dotenv(dotenv_path) # Parse arguments parser = argparse.ArgumentParser(description="Client CLI pour Cesium+ et Ḡchange") parser.add_argument('-v', '--version', action='store_true', help="Affiche la version actuelle du programme") parser.add_argument('-k', '--key', help="Chemin vers mon trousseau de clé (PubSec)") -parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+ ou Gchange à utiliser") +parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+, Gchange ou Duniter à utiliser") subparsers = parser.add_subparsers(title="Commandes de jaklis", dest="cmd") read_cmd = subparsers.add_parser('read', help="Lecture des messages") @@ -31,6 +31,9 @@ setProfile_cmd = subparsers.add_parser('set', help="Configurer son profile Cesiu eraseProfile_cmd = subparsers.add_parser('erase', help="Effacer son profile Cesium+") like_cmd = subparsers.add_parser('like', help="Voir les likes d'un profile / Liker un profile (option -s NOTE)") unlike_cmd = subparsers.add_parser('unlike', help="Supprimer un like") +pay_cmd = subparsers.add_parser('pay', help="Payer en Ḡ1") +history_cmd = subparsers.add_parser('history', help="Voir l'historique des transactions d'un compte Ḡ1") +balance_cmd = subparsers.add_parser('balance', help="Voir le solde d'un compte Ḡ1") # Messages management read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") @@ -63,17 +66,32 @@ like_cmd.add_argument('-p', '--profile', help="Profile cible") like_cmd.add_argument('-s', '--stars', type=int, help="Nombre d'étoile") unlike_cmd.add_argument('-p', '--profile', help="Profile à déliker") +# GVA usage +pay_cmd.add_argument('-p', '--pubkey', help="Destinataire du paiement") +pay_cmd.add_argument('-a', '--amount', type=float, help="Montant de la transaction") +pay_cmd.add_argument('-c', '--comment', default="", help="Commentaire de la transaction") +pay_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") +pay_cmd.add_argument('-v', '--verbose', action='store_true', help="Affiche le résultat JSON de la transaction") + +history_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +history_cmd.add_argument('-j', '--json', action='store_true', help="Affiche le résultat en format JSON") +history_cmd.add_argument('--nocolors', action='store_true', help="Affiche le résultat en noir et blanc") + +balance_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +balance_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") + + args = parser.parse_args() cmd = args.cmd +if args.version: + print(__version__) + sys.exit(0) + if not cmd: parser.print_help() sys.exit(1) -if args.version: - print(VERSION) - sys.exit(0) - def createTmpDunikey(): # Generate pseudo-random nonce nonce=[] @@ -87,79 +105,121 @@ def createTmpDunikey(): return keyPath -if args.node: - pod = args.node -else: - pod = os.getenv('POD') -if not pod: - pod="https://g1.data.le-sou.org" +# Check if we need dunikey +try: + pubkey = args.pubkey +except: + pubkey = False +try: + profile = args.profile +except: + profile = False -if args.key: - dunikey = args.key +if cmd in ('history','balance','get') and (pubkey or profile): + noNeedDunikey = True keyPath = False + try: + dunikey = args.pubkey + except: + dunikey = args.profile else: - dunikey = os.getenv('DUNIKEY') - if not dunikey: - keyPath = createTmpDunikey() - dunikey = keyPath - else: + noNeedDunikey = False + if args.key: + dunikey = args.key keyPath = False -if not os.path.isfile(dunikey): - HOME = os.getenv("HOME") - dunikey = HOME + dunikey - if not os.path.isfile(dunikey): - sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) - sys.exit(1) - - -# Build cesiumMessaging class -if cmd == "read": - messages = ReadFromCesium(dunikey, pod) - messages.read(args.number, args.outbox, args.json) -elif cmd == "send": - if args.fichier: - with open(args.fichier, 'r') as f: - msgT = f.read() - titre = msgT.splitlines(True)[0].replace('\n', '') - msg = ''.join(msgT.splitlines(True)[1:]) - if args.titre: - titre = args.titre - msg = msgT - elif args.titre and args.message: - titre = args.titre - msg = args.message else: - titre = input("Indiquez le titre du message: ") - msg = input("Indiquez le contenu du message: ") + dunikey = os.getenv('DUNIKEY') + if not dunikey: + keyPath = createTmpDunikey() + dunikey = keyPath + else: + keyPath = False + if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) - messages = SendToCesium(dunikey, pod, args.destinataire, args.outbox) - messages.send(titre, msg) -elif cmd == "delete": - messages = DeleteFromCesium(dunikey, pod, args.outbox) - messages.delete(args.id[0]) +# Construct CesiumPlus object +if cmd in ("read","send","delete","set","get","erase","like","unlike"): + from lib.cesium import CesiumPlus -# Build cesium+ profiles class -elif cmd in ('set','get','erase'): - cesium = Profiles(dunikey, pod) - if cmd == "set": + if args.node: + pod = args.node + else: + pod = os.getenv('POD') + if not pod: + pod="https://g1.data.le-sou.org" + + cesium = CesiumPlus(dunikey, pod, noNeedDunikey) + + # Messaging + if cmd == "read": + cesium.read(args.number, args.outbox, args.json) + elif cmd == "send": + if args.fichier: + with open(args.fichier, 'r') as f: + msgT = f.read() + titre = msgT.splitlines(True)[0].replace('\n', '') + msg = ''.join(msgT.splitlines(True)[1:]) + if args.titre: + titre = args.titre + msg = msgT + elif args.titre and args.message: + titre = args.titre + msg = args.message + else: + titre = input("Indiquez le titre du message: ") + msg = input("Indiquez le contenu du message: ") + + cesium.send(titre, msg, args.destinataire, args.outbox) + + elif cmd == "delete": + cesium.delete(args.id[0], args.outbox) + + # Profiles + elif cmd == "set": cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site, args.avatar) elif cmd == "get": cesium.get(args.profile, args.avatar) elif cmd == "erase": cesium.erase() -# Build cesium+ likes class -elif cmd == "like": - if args.stars or args.stars == 0: - gchange = SendLikes(dunikey, pod) - gchange.like(args.stars, args.profile) + # Likes + elif cmd == "like": + if args.stars or args.stars == 0: + cesium.like(args.stars, args.profile) + else: + cesium.readLikes(args.profile) + elif cmd == "unlike": + cesium.unLike(args.profile) + +# Construct GVA object +elif cmd in ("pay","history","balance"): + from lib.gva import GvaApi + + if args.node: + node = args.node else: - gchange = ReadLikes(dunikey, pod) - gchange.readLikes(args.profile) -elif cmd == "unlike": - gchange = UnLikes(dunikey, pod) - gchange.unLike(args.profile) + node = os.getenv('NODE') + if not node: + node="https://g1.librelois.fr/gva" + + if args.pubkey: + destPubkey = args.pubkey + else: + destPubkey = False + + gva = GvaApi(dunikey, node, destPubkey, noNeedDunikey) + + if cmd == "pay": + gva.pay(args.amount, args.comment, args.mempool, args.verbose) + if cmd == "history": + gva.history(args.json, args.nocolors) + if cmd == "balance": + gva.balance(args.mempool) if keyPath: diff --git a/zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc b/zen/jaklis/lib/__pycache__/cesium.cpython-36.pyc index f7aebdbd14871a84f8d147d8039a6c82790b3c1c..45f50fcf21f847f59690572137d71efc7efae11f 100644 GIT binary patch literal 3273 zcmZ`*S&tmI5pMR~Jw3CBH~W+;&(rG9q;k& zzWvKr_kS2azW?z4_d`by9*YToAnUTT(#aE1@xRxr(f&PAh(>JIhfIf$WPO$wLgDR* z-u^m27fMI;Vy6b*Rz}Ha3s+A7A_n-wl;eD)y{5s#``xX4eBGsB5n^B3BsPxc6s1j1qimM|AGwlDTJAl@RY%p(gt5gO?R z$rj2El0dKZ#?I7Q`p-XTT+S}pJ?qDt%b@jVUgOV#CY(iR@pbDGYs`A~?srzxn~Tldmlq3?`dy@NeyYj`MWSCSl&eVU4-$ntp( z^tC4^)l_JIGM^(T+Q*RBrS`L@b)I2fHHgPl_a`dL^D$W82&uHs^J(6oKb>(V2Ipr-->sSMN&4IdWceNqjivnv;QiM* zFf~pl+?0_OalM=qHov3Sh^<==`kmo#F{?rdxM;^9SmGFV2Kb=wHsw3jh*J0>2sF>=`+X-A1>=u4^>8#mKr9pWdVG5*Ru zSPXkzc-?AJ3f<#EWip>PIclxz0l#jR=HVm(GwSco-`9l9B5WWRfl|4 zF2GQs7De+KMDj!YCN&j95CyKW#17fTU<)C9ZykF=W*z%E1{-ux`J78ZCJ^P!)+K4pquo+l)uZMNq{mC zHPz7@z4{2hE8mAmopH{!GX=o?*8tZ2svZf!@2x7RKVO_uCAFo{9%@_mR1%-HQ;2FS z7!Oy$NTc~gDnVtki%ZzWC75@|mHa*Ze2iaSL7jRktLH*~Z00+#&D)<~;&s$N!yFkZ z%07!7qD1=a2J>y#rhw5L!8lyOG>iEN2#uTfCD5tiwbH^-j?zi}lW_ zO0RcLcjf#^Yy;NztuH%3S#Yi6IH9p_0;C5`pCxB<_%)OxVKykcM>nN;+rgOi(lxx?vRd z!zd2pJtKXyjw=YAukt2|8zg=Mk?!Npbt~Jthf7K&Q_s>qt(E3lhzp3hMt0X23SgSh t>Z*h(Q!&$sraH=-tTF5|0iqqgY3n+?-GTB2RV9P-J8YjFp>iGB{{!~t>3#qJ literal 13945 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 u@ky8NTll(hyl}Pf@_z$@P~YYN diff --git a/zen/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc b/zen/jaklis/lib/__pycache__/cesiumCommon.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..922d2ddf50904d6840e805db84a844da71e8de95 GIT binary patch literal 1711 zcmZux-EJH;6t+E{pPk*50x7gAs)hn;2a&8OX(glz3T;~PqqL$0+NrA1WbI^UGk>)` zv2UhoEe4jzF!bIDc28*s%rvn`|ukNo-gc+T|B#V(Iw4bwt!N>}y)PAKJ-(fjzf%pjmOivl;bTW#rRRJ zc65_X5a&>$0q?)n?cFO_QQmACDOT5H_N3uw|-V0eOOo=M6J* z1-d>1APJTb%I6Cv94L(=xNr|h!i5KC4eaRZk)5tjFt^xn96;~W`U?mh>qtsE2axW* z21z3+>!^;5Bj}v|NWUf3SxWFTeCEzP!7$Ar`=U?yJyNl|V6X0UWQM+i>X;dHRPZ^Q z1voe1Y-mQX8qC><=pW2dFH_;}aP{PwSMNoyeZl;Vz2n!~Du-%CxsGJnnBBb#w+fkQ z8Exf75t*rsn#@ciQ%AdXt0E*?W@LmW=Wn%}x8)=b4=J3}dQH2j<>YV$ChfZbUuMyy z`1NT7`XYbW%BVUgwE=%R%GzkNuA;44m6=%$<73uoYGIeEdP7R_EGFxVMQ}F!CM#OG zu2ijnMMy1egjB$$M=Ceca``wneE<-baxogFq;o?Z#TBndz z%z<9A*ixt9SM@r=Nd%j?vMY{HIu{oJJbF$A27xWY{ZpU!Ncu-5A;2b_dOt8?- z>5%-yPNCfZY@D3zbKn^l&Shj87yL3oR&;z$_g#=ioeNhRN8sEOw(s?;zzLwTyOFR< zYw85VWBD}GQ#-(iB{Qv(@sh&ettYEVEujFO5Rd_i0}w^sU}R zNHkGHTpJ-Q0mQy~8&?BSPudb*hs4J%uhkQE4)t7QCQAYg;PcxzH z|A5^)V5YG^ge^O34yOTpAzfyB%P&;p+3)aLN_Wi8qQeC11;&~;Z{Ccb=l$mQoOfD;wlq=uxsmxXqn& z!kvbNLrx!%z{w;ISmbNa_gLgCinGuXlQYl55Qt` z56(1d?VZ#3>qWYqSiMIauR*^HUHt~4Hfs`*nu4S?1IcPrPHHkoi&2a#v!izx+2ZoIu#tUT80tGbNH2pFo7m0L1-xl4QR=pMX6}pDe|s z_(-T~vWV3rUQT9ZF*z({9#@;oTmNUb7mV9cQSJ%B?=6cIZoGAPub4&2MT;i^vt;15 z^*DTVUmIE`MQgzqlVfS(nvsd3^&Z99Qfx_C%GOXT)!M4!Ldw>9D$_~?R%KFLxw&C=QQ=rrD3tXemU(ju** z$c48QMop-#ACr1)%uMPsmod6QeX#m4d!?UU>v*;K(#HkAY;c1PHNS3yQjv~JbKn;6 z1vzHaX5*}_+M1gUumMJ^AMJj08oWok7q}8L;8M6Fn1=R|DDIU>=q1M@IeB=1x}(w~ zZ5$|3aBT~0`4((%?Km%&Mb$b={9OgdIqnJhw0-3@mU z`LnLH>g4)YB>m#&;6b5(Lf?c)E+Xicp|WoWYd`uZmz-m^3c+HeAOWYHQ%j>$n5K4Tq4pn?+m!X_YP| zp@$Y=?S8!Kas-hL$Gx?`X-%68WB-HcK*Wz;I*dM>roH<`7t)s$c4+Ru0ojk;Xa4}! CnYUa3 literal 0 HcmV?d00001 diff --git a/zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc b/zen/jaklis/lib/__pycache__/gvaPay.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1fdfb5cd918060d5231e85ad7b75f44f5386815 GIT binary patch literal 5834 zcmcgw&2JmW72nxiE|*K{!?G-YCC<8W6S0lyd^KohH}`6lJSL(?dzj zo7wrEx9|7fZ}x?;F|YcMKQ+ESViXUC6yvW_# z26wm3`T@=sjB4pV);H#>EGstSFqVQ>t;|~PsLW1UD^XTn5vtLZt@SmrSv50jU z=fjqW6vj-xe)*j@7Z_3Hc2oqqZC)AitV@A`)?KL5;% zKiF#0Qait)0+vuC3_5h()KvIE9Pdz8)_V(@suf3>ajEGJHc&!7iF6(*`3pp!^^Jznw;-7^ z`!;9O#x~Vi{Q{(|3Vp`SZL{s*sn{p!_KT3Ez6t4Z8d=_aN>$opYP@f4n-wFup(apb z++MR~TTSx9Hv5=eGooY6pualMo@yWD&NgGl$Hsd`WG)$-Gqi_xam|#UVMKA?5$@;+ zHQhIP=?>d+)uF!2Jzl;;JDTAJG*Fqp&vClU>83Xl(Y$p3G@jBb#M^=24Da3b(Ga$7 zroxY&7K!q^LE;Cge?)4&;97)dyT6X-?+GxS)qtB@7*9w#YHJ&nOVtZ zk~xXuLdvXgOSY7dWS+99S8wJhD*=fjT$W_4@+gS|Bo308CUKYq{EHzcAnqFSyT~3? z&#cBBab_8aK65LUYe_d~iZktCEo>!c!q$y5E9=3PV6)cU%-lw!6}42OF@}K&y$gdm z_>T_~l^xS$$5;iW3Ue)!c{^p>W4-ASjMa7~YciS$Khot8X}`k*tlN+?igCi3(FQ)Y zO<=(Wy4V&F*9P9&jsmvYuye=~lEtmh3R~vpvnYG*GSF&r`;N&AM=@Wcf?7kaD(YR~ z&QTzj(K?j{!#32|nkTrNesc$Hq0_ z5l$APKC+fHGBuvO`CfU~4(5VaaZJx2`2k6C)1N&xG_g5zC+GAuj{~T;j1kuUrhyIF)7B^=1EWI<=sl2eKZ8-7H5_?*A_BGhxp0<&L z>>g@QkKG+E#HnnGWX?aeTeqicb@;S3EL97+NzcGG98u|8tVj|q+ZY` zmtxbZpQk>Ln<*iC&dZorJ*e@L6%g3S z1td!5C~{~A8eV!YQ+qe`q+U^Mh-RvQakX(FZf2#68%@#G(Zvc9l~qE5aS%2Hwkv&_ z;UtOgK;WRKkkvhHBj?gq$m`{9AcIb#A2KE+d1j5^T+q2P2Pei_&!C*p1cvDtlg#uS z%Vsunkv!WpXP9FUWGC4an`XVKQBdX+3?b=Z4stRSgpn$fP4R=6;~<>OG}58LDF$sI z7dSbef%YhzYb~JH;lt1aJ)wLAWqVLQisxg484mH|L=vKDoaZrpp2z7t^adB;z(4{{ z%=g^OI*286_X#*X_nlOAQp2>iBZTms#71*ZO=*qJV znvW*8jb3%scnQ{Pi%UR2?UgWY24QlxHXPNf5!D!=+i%8^qOk6#ARIdM=gg11LQ}`* z#sBgg*8b;^Kcwxy4DrZOZ_WGl0RFqGe?DtE{Ztt9Q_ zG0sQBZfKBX_&L2JH!q)nsCs$0DCN=lYH+Jwic|Hv=Bm1QCA|THTK5L}L66J^782Z# z;^x~e#5Jf88+ z4h*Gzy=4-oMliLw**9Bk%icTz6`G3flN2B=gb(WuR7~o?Ep!>&*a0{G4Y+Xu-1se3 zY$K@maZ9B;E4ZtN zF291D*rwP}QIqPxZWu>>MSi0uDa1143fl3S=+gw4_CJRl(I5OesmeLs?5W{f8XcqoYTEl6t*d>{lT@NhygWnVSrX5Y@JS5WY<}k?MThg@U7@l`rzc0ZnYtao z^U<0UiejA9x`A5F%s9#Hb`nP#a`>(#WJiCL)p*Lv_-a$b_cVM0NOmJr9+5PHDLm8< w;R|g|?R*id1+92hsc!yV>2X?<_P5{0GxwETIcF*A>Buw-m`wv#71K8V1+fraY5)KL literal 0 HcmV?d00001 diff --git a/zen/jaklis/lib/__pycache__/likes.cpython-36.pyc b/zen/jaklis/lib/__pycache__/likes.cpython-36.pyc index 523efcd0620e21386c62c888e7191a8a8fc9af43..bd85f7a6875d8135b32b101aaf7e8222eebcae20 100644 GIT binary patch delta 2591 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 K^Dhj)x%Z#aNYBOq diff --git a/zen/jaklis/lib/__pycache__/messaging.cpython-36.pyc b/zen/jaklis/lib/__pycache__/messaging.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbbea04682cb7feb452a2cf64cde13ea81387c7f GIT binary patch literal 6749 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 delta 55 zcmX@)alwPtn3tEUb|b3~r?Q29Mt*Lpep*p#ihg;1QFd`bVsff}VsS}PenEawiGEdT K-sUvUJ5m4wTNAke diff --git a/zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc b/zen/jaklis/lib/__pycache__/profiles.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c142556b72d1c30cf7509cb4cf38b93ff778c3fb GIT binary patch literal 3030 zcmZ`*&2!vH6<15GkI{^sS;vkyTLqXc*zAPhSs(<8GTDG}d5fh?76Y~DGRW$l8BH{! z*=|h|Pc$c|xb2lHs&e4Ug};Lv_dY?v#s31Q{k@j$ct4P;-+TSkUwZv}zwRHbt;N0n z{&Vn`8;T(n1s_P ziI{Wf2rm4Wj__r8<|Qq`j-6g`j?tZ?9y7ekr?Tgo&c4+7V!B^Wr{&Ca9{=v=A0GT+ z@SB6fgO4+6d--@U9--(55YnMFVLeQ^FCDCBP52^s$*`hOMDX~cC1U76v_%Ix6dbd! zo9MABhj}4&7GW5AIw&;uK8n7@9H(+BcIKS1Rg^ocR?XB~HLJ1uJ*RfnJGBejh0WFX zYYy9k?Wu>g2b;t8)e!yS+86$9r;bHHJ`q|wf*qq>3p!AL9XYUL*pRoKiJN0q#IfDf zslVt@%WL&D)I;^J%Bw@Me&$a2nX?Kje-hN;8JmPu%W1qD)Q&X9= zXlFv!CcbV#-`aERdbanRsx`qX5rH<^wCna6?cAG3#;@|KkftpVK9$d_92*v3NS2Swd{&uImsy?`I`N*#6CmJpE{(TPg>lu13DT!&l`0br zvT|0*S+#8C({v>J^Vuj_yL1kylX5YQ339C$Qk~mM_*5!Q3*uEe(!GxHFyTeJbUv3e zVR#8;xRR+b!AYv+gZrj+^5DLlWu=fN5VFzp#mN(SYWPHBIA1KLbB&efquDRY3CZ19yE{KM9mHmskFb(sS<#&O5ro5Hmox4}pD&EF z4qkXs)>#)Xhh9gm@yDmuazkeS8&Q=%b97fgbwVH58jE6%61$7Hq|5T(~PP zJj!?E2NQt^U%5HUnTV)P)~Knsawm60OkXK8#ahk9y0{^_;tjDOZi>xO3*IepOKiXL z#G9{zm0vj%R{KC>PzS)_?IRo9=-cn_!sBLg=1r|5k0Gtwr&pckLBY5(;`XM=JUl8}i?rh{et&|L*SY#)iec7!*({Fa^=K zuvjpWk{}CR;aCHuSEjA2v|4Cz4Gs!k$>(USPqhhjg(;QspQ#*+2pY`T4717q5UWUN zu#bp(On{SeQB+^bK8@oElx~lOKcb5!XgXfRBentbSi*L@Ao<`rRbTIw5sv90{7}wW z3_XTn>64u?!EhBoSutE`J`hCH9z6WE1B6}HaOEJh)!v_hF7$Lk&mi+p-D6}Q3ZbEO zWcWC*bdxod=f)c#>+Zv9sFvYqMz-Mu?_5BB=V>lv&$BsZ{Gv>SZivfoh|9yKG?B;g zjClLQe3lkpj8ki#DlLC`{Oh9y=F;mB4g$n+uF)yC?k+d3QtYbf1+gFXBHSznbZQR< zCLRo?rC1bXcLsw`7HQFZQ9b&LACdS8i4RE}lX%UT)*{+e6l_XbO(Y+=(cAHz=ti_< z{k>Qn!i%eoY^a5HM7>4AURb&~+IPX`S9f1UG@xkjZ2zyC&_sL9vL8~|HBJ34e!c5` E23D@=-v9sr literal 0 HcmV?d00001 diff --git a/zen/jaklis/lib/cesium.py b/zen/jaklis/lib/cesium.py index 62d846b..6a07fd9 100644 --- a/zen/jaklis/lib/cesium.py +++ b/zen/jaklis/lib/cesium.py @@ -1,540 +1,54 @@ -#!/usr/bin/env python3 +import re, string, random, base64 +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX +from lib.messaging import ReadFromCesium, SendToCesium, DeleteFromCesium +from lib.profiles import Profiles +from lib.likes import ReadLikes, SendLikes, UnLikes -import os, sys, ast, requests, json, base58, base64, time, string, random, re -from lib.natools import fmt, sign, get_privkey, box_decrypt, box_encrypt -from time import sleep -from hashlib import sha256 -from datetime import datetime -from termcolor import colored +class CesiumPlus(CesiumCommon): -PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" - -def pp_json(json_thing, sort=True, indents=4): - # Print beautifull JSON - if type(json_thing) is str: - print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) - else: - print(json.dumps(json_thing, sort_keys=sort, indent=indents)) - return None - -class ReadFromCesium: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.recipient = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.recipient) or len(self.recipient) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - - # Configure JSON document to send - def configDoc(self, nbrMsg, outbox): - boxType = "issuer" if outbox else "recipient" - - data = {} - data['sort'] = { "time": "desc" } - data['from'] = 0 - data['size'] = nbrMsg - data['_source'] = ['issuer','recipient','title','content','time','nonce','read_signature'] - data['query'] = {} - data['query']['bool'] = {} - data['query']['bool']['filter'] = {} - data['query']['bool']['filter']['term'] = {} - data['query']['bool']['filter']['term'][boxType] = self.recipient - - document = json.dumps(data) - return document - - def sendDocument(self, nbrMsg, outbox): - boxType = "outbox" if outbox else "inbox" - - document = self.configDoc(nbrMsg, outbox) - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get JSON result - result = requests.post('{0}/message/{1}/_search'.format(self.pod, boxType), headers=headers, data=document) - if result.status_code == 200: - return result.json()["hits"] - else: - sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text) - - # Parse JSON result and display messages - def readMessages(self, msgJSON, nbrMsg, outbox): - def decrypt(msg): - msg64 = base64.b64decode(msg) - return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() - - # Get terminal size - rows = int(os.popen('stty size', 'r').read().split()[1]) - - totalMsg = msgJSON["total"] - if nbrMsg > totalMsg: - nbrMsg = totalMsg - - if totalMsg == 0: - print(colored("Aucun message à afficher.", 'yellow')) - return True - else: - infoTotal = " Nombre de messages: " + str(nbrMsg) + "/" + str(totalMsg) + " " - print(colored(infoTotal.center(rows, '#'), "yellow")) - for hits in msgJSON["hits"]: - self.idMsg = hits["_id"] - msgSrc = hits["_source"] - self.issuer = msgSrc["issuer"] - nonce = msgSrc["nonce"] - nonce = base58.b58decode(nonce) - self.dateS = msgSrc["time"] - date = datetime.fromtimestamp(self.dateS).strftime(", le %d/%m/%Y à %H:%M ") - if outbox: - startHeader = " À " + msgSrc["recipient"] - else: - startHeader = " De " + self.issuer - headerMsg = startHeader + date + "(ID: {})".format(self.idMsg) + " " - - print('-'.center(rows, '-')) - print(colored(headerMsg, "blue").center(rows+9, '-')) - print('-'.center(rows, '-')) - try: - self.title = decrypt(msgSrc["title"]) - self.content = decrypt(msgSrc["content"]) - except Exception as e: - sys.stderr.write(colored(str(e), 'red') + '\n') - pp_json(hits) - continue - print('\033[1m' + self.title + '\033[0m') - print(self.content) - - print(colored(infoTotal.center(rows, '#'), "yellow")) - - # Parse JSON result and display messages - def jsonMessages(self, msgJSON, nbrMsg, outbox): - def decrypt(msg): - msg64 = base64.b64decode(msg) - return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() - - totalMsg = msgJSON["total"] - if nbrMsg > totalMsg: - nbrMsg = totalMsg - - if totalMsg == 0: - print("Aucun message à afficher") - return True - else: - data = [] - # data.append({}) - # data[0]['total'] = totalMsg - for i, hits in enumerate(msgJSON["hits"]): - self.idMsg = hits["_id"] - msgSrc = hits["_source"] - self.issuer = msgSrc["issuer"] - nonce = msgSrc["nonce"] - nonce = base58.b58decode(nonce) - self.date = msgSrc["time"] - - if outbox: - pubkey = msgSrc["recipient"] - else: - pubkey = self.issuer - - try: - self.title = decrypt(msgSrc["title"]) - self.content = decrypt(msgSrc["content"]) - except Exception as e: - sys.stderr.write(colored(str(e), 'red') + '\n') - pp_json(hits) - continue - - data.append(i) - data[i] = {} - data[i]['id'] = self.idMsg - data[i]['date'] = self.date - data[i]['pubkey'] = pubkey - data[i]['title'] = self.title - data[i]['content'] = self.content - - data = json.dumps(data, indent=2) - return data + #################### Messaging #################### def read(self, nbrMsg, outbox, isJSON): - jsonMsg = self.sendDocument(nbrMsg, outbox) + readCesium = ReadFromCesium(self.dunikey, self.pod) + jsonMsg = readCesium.sendDocument(nbrMsg, outbox) if isJSON: - jsonFormat = self.jsonMessages(jsonMsg, nbrMsg, outbox) + jsonFormat = readCesium.jsonMessages(jsonMsg, nbrMsg, outbox) print(jsonFormat) else: - self.readMessages(jsonMsg, nbrMsg, outbox) + readCesium.readMessages(jsonMsg, nbrMsg, outbox) - - - -#################### Sending class #################### - - - - -class SendToCesium: - def __init__(self, dunikey, pod, recipient, outbox): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - self.recipient = recipient - self.outbox = outbox + def send(self, title, msg, recipient, outbox): + sendCesium = SendToCesium(self.dunikey, self.pod) + sendCesium.recipient = recipient # Generate pseudo-random nonce nonce=[] for _ in range(32): nonce.append(random.choice(string.ascii_letters + string.digits)) - self.nonce = base64.b64decode(''.join(nonce)) + sendCesium.nonce = base64.b64decode(''.join(nonce)) - if not re.match(PUBKEY_REGEX, recipient) or len(recipient) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) + finalDoc = sendCesium.configDoc(sendCesium.encryptMsg(title), sendCesium.encryptMsg(msg)) # Configure JSON document to send + sendCesium.sendDocument(finalDoc, outbox) # Send final signed document - - def encryptMsg(self, msg): - return fmt["64"](box_encrypt(msg.encode(), get_privkey(self.dunikey, "pubsec"), self.recipient, self.nonce)).decode() - - def configDoc(self, title, msg): - b58nonce = base58.b58encode(self.nonce).decode() - - # Get current timestamp - timeSent = int(time.time()) - - # Generate custom JSON - data = {} - data['issuer'] = self.issuer - data['recipient'] = self.recipient - data['title'] = title - data['content'] = msg - data['time'] = timeSent - data['nonce'] = b58nonce - data['version'] = 2 - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - finalDoc = '{' + '"hash":"{0}","signature":"{1}",'.format(hashDoc, signature) + document[1:] - - return finalDoc - - - def sendDocument(self, document): - boxType = "outbox" if self.outbox else "inbox" - - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get result - try: - result = requests.post('{0}/message/{1}?pubkey={2}'.format(self.pod, boxType, self.recipient), headers=headers, data=document) - except Exception as e: - sys.stderr.write("Impossible d'envoyer le message:\n" + str(e)) - sys.exit(1) - else: - if result.status_code == 200: - print(colored("Message envoyé avec succès !", "green")) - print("ID: " + result.text) - return result - else: - sys.stderr.write("Erreur inconnue:" + '\n') - print(str(pp_json(result.text)) + '\n') - - def send(self, title, msg): - finalDoc = self.configDoc(self.encryptMsg(title), self.encryptMsg(msg)) # Configure JSON document to send - self.sendDocument(finalDoc) # Send final signed document - - - - -#################### Deleting class #################### - - - - -class DeleteFromCesium: - def __init__(self, dunikey, pod, outbox): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - self.outbox = outbox - - - def configDoc(self, idMsg): - # Get current timestamp - timeSent = int(time.time()) - - boxType = "outbox" if self.outbox else "inbox" - - # Generate document to customize - data = {} - data['version'] = 2 - data['index'] = "message" - data['type'] = boxType - data['id'] = idMsg - data['issuer'] = self.issuer - data['time'] = timeSent - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - data = {} - data['hash'] = hashDoc - data['signature'] = signature - signJSON = json.dumps(data) - finalJSON = {**json.loads(signJSON), **json.loads(document)} - finalDoc = json.dumps(finalJSON) - - return finalDoc - - def sendDocument(self, document, idMsg): - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get result - try: - result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) - if result.status_code == 404: - raise ValueError("Message introuvable") - elif result.status_code == 403: - raise ValueError("Vous n'êtes pas l'auteur de ce message.") - except Exception as e: - sys.stderr.write(colored("Impossible de supprimer le message {0}:\n".format(idMsg), 'red') + str(e) + "\n") - return False - else: - if result.status_code == 200: - print(colored("Message {0} supprimé avec succès !".format(idMsg), "green")) - return result - else: - sys.stderr.write("Erreur inconnue.") - - def delete(self, idsMsgList): + def delete(self, idsMsgList, outbox): + deleteCesium = DeleteFromCesium(self.dunikey, self.pod) + # deleteCesium.issuer = recipient for idMsg in idsMsgList: - finalDoc = self.configDoc(idMsg) - self.sendDocument(finalDoc, idMsg) - - - - - -#################### Profile class #################### - - - - - -class Profiles: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.pubkey = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - - # Configure JSON document SET to send - def configDocSet(self, name, description, city, address, pos, socials, avatar): - timeSent = int(time.time()) - - data = {} - if name: data['title'] = name - if description: data['description'] = description - if address: data['address'] = address - if city: data['city'] = city - if pos: - geoPoint = {} - geoPoint['lat'] = pos[0] - geoPoint['lon'] = pos[1] - data['geoPoint'] = geoPoint - if socials: - data['socials'] = [] - data['socials'].append({}) - data['socials'][0]['type'] = "web" - data['socials'][0]['url'] = socials - if avatar: - avatar = open(avatar, 'rb').read() - avatar = base64.b64encode(avatar).decode() - data['avatar'] = {} - data['avatar']['_content'] = avatar - data['avatar']['_content_type'] = "image/png" - data['time'] = timeSent - data['issuer'] = self.pubkey - data['version'] = 2 - data['tags'] = [] - - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - data = {} - data['hash'] = hashDoc - data['signature'] = signature - signJSON = json.dumps(data) - finalJSON = {**json.loads(signJSON), **json.loads(document)} - finalDoc = json.dumps(finalJSON) - - return finalDoc - - # Configure JSON document GET to send - def configDocGet(self, profile, scope='title', getAvatar=None): - - if getAvatar: - avatar = "avatar" - else: - avatar = "avatar._content_type" - - data = { - "query": { - "bool": { - "should":[ - { - "match":{ - scope:{ - "query": profile,"boost":2 - } - } - },{ - "prefix": {scope: profile} - } - ] - } - },"highlight": { - "fields": { - "title":{}, - "tags":{} - } - },"from":0, - "size":100, - "_source":["title", avatar,"description","city","address","socials.url","creationTime","membersCount","type"], - "indices_boost":{"user":100,"page":1,"group":0.01 - } - } - - document = json.dumps(data) - - return document - - # Configure JSON document SET to send - def configDocErase(self): - timeSent = int(time.time()) - - data = {} - data['time'] = timeSent - data['id'] = self.pubkey - data['issuer'] = self.pubkey - data['version'] = 2 - data['index'] = "user" - data['type'] = "profile" - - document = json.dumps(data) - - # Generate hash of document - hashDoc = sha256(document.encode()).hexdigest().upper() - - # Generate signature of document - signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() - - # Build final document - data = {} - data['hash'] = hashDoc - data['signature'] = signature - signJSON = json.dumps(data) - finalJSON = {**json.loads(signJSON), **json.loads(document)} - finalDoc = json.dumps(finalJSON) - - return finalDoc - - def sendDocument(self, document, type): - - headers = { - 'Content-type': 'application/json', - } - - # Send JSON document and get JSON result - if type == 'set': - reqQuery = '{0}/user/profile?pubkey={1}/_update?pubkey={1}'.format(self.pod, self.pubkey) - elif type == 'get': - reqQuery = '{0}/user,page,group/profile,record/_search'.format(self.pod) - elif type == 'erase': - reqQuery = '{0}/history/delete'.format(self.pod) - - result = requests.post(reqQuery, headers=headers, data=document) - if result.status_code == 200: - # print(result.text) - return result.text - else: - sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') - - def parseJSON(self, doc): - doc = json.loads(doc)['hits']['hits'] - if doc: - pubkey = { "pubkey": doc[0]['_id'] } - rest = doc[0]['_source'] - final = {**pubkey, **rest} - else: - final = 'Profile vide' - - return json.dumps(final, indent=2) + finalDoc = deleteCesium.configDoc(idMsg, outbox) + deleteCesium.sendDocument(finalDoc, idMsg) + #################### Profiles #################### def set(self, name=None, description=None, ville=None, adresse=None, position=None, site=None, avatar=None): - document = self.configDocSet(name, description, ville, adresse, position, site, avatar) - result = self.sendDocument(document,'set') + setProfile = Profiles(self.dunikey, self.pod) + document = setProfile.configDocSet(name, description, ville, adresse, position, site, avatar) + result = setProfile.sendDocument(document,'set') print(result) return result def get(self, profile=None, avatar=None): + getProfile = Profiles(self.dunikey, self.pod, self.noNeedDunikey) if not profile: profile = self.pubkey if not re.match(PUBKEY_REGEX, profile) or len(profile) > 45: @@ -542,16 +56,38 @@ class Profiles: else: scope = '_id' - document = self.configDocGet(profile, scope, avatar) - resultJSON = self.sendDocument(document, 'get') - result = self.parseJSON(resultJSON) + document = getProfile.configDocGet(profile, scope, avatar) + resultJSON = getProfile.sendDocument(document, 'get') + result = getProfile.parseJSON(resultJSON) print(result) - return result def erase(self): - document = self.configDocErase() - result = self.sendDocument(document,'erase') + eraseProfile = Profiles(self.dunikey, self.pod) + document = eraseProfile.configDocErase() + result = eraseProfile.sendDocument(document,'erase') print(result) - return result \ No newline at end of file + + #################### Likes #################### + + def readLikes(self, profile=False): + likes = ReadLikes(self.dunikey, self.pod, self.noNeedDunikey) + document = likes.configDoc(profile) + result = likes.sendDocument(document) + result = likes.parseResult(result) + + print(result) + + def like(self, stars, profile=False): + likes = SendLikes(self.dunikey, self.pod) + document = likes.configDoc(profile, stars) + if document: + likes.sendDocument(document, profile) + + def unLike(self, pubkey, silent=False): + likes = UnLikes(self.dunikey, self.pod) + idLike = likes.checkLike(pubkey) + if idLike: + document = likes.configDoc(idLike) + likes.sendDocument(document, silent) diff --git a/zen/jaklis/lib/cesiumCommon.py b/zen/jaklis/lib/cesiumCommon.py new file mode 100644 index 0000000..b68337d --- /dev/null +++ b/zen/jaklis/lib/cesiumCommon.py @@ -0,0 +1,51 @@ +import sys, re, json +from hashlib import sha256 +from lib.natools import fmt, sign, get_privkey + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +def pp_json(json_thing, sort=True, indents=4): + # Print beautifull JSON + if type(json_thing) is str: + print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) + else: + print(json.dumps(json_thing, sort_keys=sort, indent=indents)) + return None + +class CesiumCommon: + def __init__(self, dunikey, pod, noNeedDunikey=False): + self.pod = pod + self.noNeedDunikey = noNeedDunikey + # Get my pubkey from my private key + try: + self.dunikey = dunikey + if not dunikey: + raise ValueError("Dunikey is empty") + except: + sys.stderr.write("Please fill the path to your private key (PubSec)\n") + sys.exit(1) + + if noNeedDunikey: + self.pubkey = self.dunikey + else: + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + def signDoc(self, document): + # Generate hash of document + hashDoc = sha256(document.encode()).hexdigest().upper() + + # Generate signature of document + signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() + + # Build final document + data = {} + data['hash'] = hashDoc + data['signature'] = signature + signJSON = json.dumps(data) + finalJSON = {**json.loads(signJSON), **json.loads(document)} + + return json.dumps(finalJSON) diff --git a/zen/jaklis/lib/crypt.py b/zen/jaklis/lib/crypt.py new file mode 100755 index 0000000..ee4cfb2 --- /dev/null +++ b/zen/jaklis/lib/crypt.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import base64, base58, sys, string, random +from natools import get_privkey, box_decrypt, box_encrypt, fmt + +def getargv(arg:str, default:str="", n:int=1, args:list=sys.argv) -> str: + if arg in args and len(args) > args.index(arg)+n: + return args[args.index(arg)+n] + else: + return default + +cmd = sys.argv[1] + +dunikey = getargv("-k", "private.dunikey") +msg = getargv("-m", "test") +pubkey = getargv("-p") + +def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(dunikey, "pubsec"), pubkey).decode() + +def encrypt(msg): + return fmt["64"](box_encrypt(msg.encode(), get_privkey(dunikey, "pubsec"), pubkey)).decode() + +if cmd == 'decrypt': + clear = decrypt(msg) + print(clear) +elif cmd == 'encrypt': + clear = encrypt(msg) + print(clear) + diff --git a/zen/jaklis/lib/gva.py b/zen/jaklis/lib/gva.py new file mode 100644 index 0000000..eaf22fd --- /dev/null +++ b/zen/jaklis/lib/gva.py @@ -0,0 +1,59 @@ +import sys, re +from lib.natools import get_privkey +from lib.gvaPay import Transaction, PUBKEY_REGEX +from lib.gvaHistory import History +from lib.gvaBalance import Balance + +class GvaApi(): + def __init__(self, dunikey, node, pubkey, noNeedDunikey=False): + self.noNeedDunikey = noNeedDunikey + self.dunikey = dunikey + self.node = node + if noNeedDunikey: + self.pubkey = self.dunikey + else: + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + + if pubkey: + self.destPubkey = pubkey + else: + self.destPubkey = self.pubkey + + try: + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + try: + if not re.match(PUBKEY_REGEX, self.destPubkey) or len(self.destPubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + #################### Payments #################### + + def pay(self, amount, comment, mempool, verbose): + gva = Transaction(self.dunikey, self.node, self.destPubkey, amount, comment, mempool, verbose) + gva.genDoc() + gva.checkTXDoc() + gva.signDoc() + return gva.sendTXDoc() + + def history(self, isJSON=False, noColors=False): + gva = History(self.dunikey, self.node, self.destPubkey) + gva.sendDoc() + transList = gva.parseHistory() + + if isJSON: + transJson = gva.jsonHistory(transList) + print(transJson) + else: + gva.printHistory(transList, noColors) + + def balance(self, useMempool): + gva = Balance(self.dunikey, self.node, self.destPubkey, useMempool) + balanceValue = gva.sendDoc() + print(balanceValue) diff --git a/zen/jaklis/lib/gvaBalance.py b/zen/jaklis/lib/gvaBalance.py new file mode 100644 index 0000000..b148e7e --- /dev/null +++ b/zen/jaklis/lib/gvaBalance.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Balance: + + def __init__(self, dunikey, node, pubkey, useMempool=False): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build balance generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + balance(script: $pubkey) { + amount + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send balance document + try: + balanceResult = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération du solde:\n" + message + "\n") + sys.exit(1) + + balanceValue = balanceResult['balance']['amount']/100 + # print(balanceValue) + return balanceValue diff --git a/zen/jaklis/lib/gvaHistory.py b/zen/jaklis/lib/gvaHistory.py new file mode 100644 index 0000000..a4f6400 --- /dev/null +++ b/zen/jaklis/lib/gvaHistory.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast, time +from datetime import datetime +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class History: + + def __init__(self, dunikey, node, pubkey): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.node = node + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build history generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + transactionsHistory(pubkey: $pubkey) { + received { + writtenTime + issuers + outputs + comment + } + sent { + writtenTime + issuers + outputs + comment + } + receiving { + issuers + outputs + comment + } + sending { + issuers + outputs + comment + } + } + balance(script: $pubkey) { + amount + base + } + node { + peer { + currency + } + } + currentUd { + amount + base + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send history document + try: + self.historyDoc = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération de l'historique:\n" + message + "\n") + sys.exit(1) + + + def parseHistory(self): + trans = [] + i = 0 + + currentBase = int(self.historyDoc['currentUd']['base']) + self.UD = self.historyDoc['currentUd']['amount']/100 + + for sens in 'received','sent','receiving','sending': + res = self.historyDoc['transactionsHistory'][sens] + for bloc in res: + output = bloc['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + if sens in ('received','receiving') or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(sens) + if sens in ('receiving','sending'): + trans[i].append(int(time.time())) + else: + trans[i].append(bloc['writtenTime']) + if sens in ('sent','sending'): + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(bloc['issuers'][0]) + amount = int(output.split(':')[0]) + base = int(output.split(':')[1]) + applyBase = base-currentBase + amount = round(amount*pow(10,applyBase)/100, 2) + # if referential == 'DU': amount = round(amount/UD, 2) + trans[i].append(amount) + trans[i].append(round(amount/self.UD, 2)) + trans[i].append(bloc['comment']) + trans[i].append(base) + i += 1 + + # Order transactions by date + trans.sort(key=lambda x: x[1]) + + # Keep only base if there is base change + lastBase = 0 + for i in trans: + if i[6] == lastBase: i[6] = None + else: lastBase = i[6] + + return trans + + def printHistory(self, trans, noColors): + # Get balance + balance = self.historyDoc['balance']['amount']/100 + balanceUD = round(balance/self.UD, 2) + + # Get currency + currency = self.historyDoc['node']['peer']['currency'] + if currency == 'g1': currency = 'Ḡ1' + elif currency == 'g1-test': currency = 'GT' + # if referential == 'DU': currency = 'DU/' + currency.lower() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + # Display history + print('+', end='') + print('-'.center(rows-1, '-')) + if noColors: isBold = isBoldEnd = '' + else: + isBold = '\033[1m' + isBoldEnd = '\033[0m' + print(isBold + "|{: <19} | {: <12} | {: <7} | {: <7} | {: <30}".format(" Date"," De / À"," {0}".format(currency)," DU/{0}".format(currency.lower()),"Commentaire") + isBoldEnd) + print('|', end='') + for t in trans: + if t[0] == "received": color = "green" + elif t[0] == "receiving": color = "yellow" + elif t[0] == "sending": color = "red" + else: color = "blue" + if noColors: + color = None + if t[0] in ('receiving','sending'): + comment = '(EN ATTENTE) ' + t[5] + else: + comment = t[5] + else: + comment = t[5] + + date = datetime.fromtimestamp(t[1]).strftime("%d/%m/%Y à %H:%M") + print('-'.center(rows-1, '-')) + if t[6]: + print('|', end='') + print(' Changement de base : {0} '.format(t[6]).center(rows-1, '#')) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + printKey = t[2][0:8] + '\u2026' + t[2][-3:] + if noColors: + print(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment)) + else: + print(colored(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment), color)) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + print(isBold + 'Solde du compte: {0} {1} ({2} DU/{3})'.format(balance, currency, balanceUD, currency.lower()).center(rows-1, ' ') + isBoldEnd) + print('+', end='') + print(''.center(rows-1, '-')) + if not noColors: + print(colored('Reçus', 'green'), '-', colored('En cours de réception', 'yellow'), '-', colored('Envoyé', 'blue'), '-', colored("En cours d'envoi", 'red')) + + return trans + + def jsonHistory(self, transList): + dailyJSON = [] + for i, trans in enumerate(transList): + dailyJSON.append(i) + dailyJSON[i] = {} + dailyJSON[i]['date'] = trans[1] + dailyJSON[i]['pubkey'] = trans[2] + dailyJSON[i]['amount'] = trans[3] + dailyJSON[i]['amountUD'] = trans[4] + dailyJSON[i]['comment'] = trans[5] + + dailyJSON = json.dumps(dailyJSON, indent=2) + # If we want to write JSON to a file + #jsonFile = open("history-{0}.json".format(self.pubkey[0:8]), "w") + #jsonFile.writelines(dailyJSON + '\n') + #jsonFile.close() + return dailyJSON + diff --git a/zen/jaklis/lib/gvaPay.py b/zen/jaklis/lib/gvaPay.py new file mode 100644 index 0000000..e017c88 --- /dev/null +++ b/zen/jaklis/lib/gvaPay.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Transaction: + + def __init__(self, dunikey, node, recipient, amount, comment='', useMempool=False, verbose=False): + self.dunikey = dunikey + self.recipient = recipient + self.amount = int(amount*100) + self.comment = comment + self.issuer = get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + self.verbose = verbose + self.node = node + self._isChange = False + + try: + if not re.match(PUBKEY_REGEX, recipient) or len(recipient) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + + try: + if recipient == self.issuer: + raise ValueError('Le destinataire ne peut pas être vous même.') + except: + sys.stderr.write("Le destinataire ne peut pas être vous même.\n") + raise + + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def genDoc(self): + # Build TX generation document + if self.verbose: print("useMempool:", str(self.useMempool)) + queryBuild = gql( + """ + query ($recipient: String!, $issuer: String!, $amount: Int!, $comment: String!, $useMempool: Boolean!){ genTx( + amount: $amount + comment: $comment + issuer: $issuer + recipient: $recipient + useMempoolSources: $useMempool + ) + } + """ + ) + paramsBuild = { + "recipient": self.recipient, + "issuer": self.issuer, + "amount": int(self.amount), + "comment": self.comment, + "useMempool": self.useMempool + } + + # Send TX document + try: + # self.txDoc = [] + self.txDoc = self.client.execute(queryBuild, variable_values=paramsBuild)['genTx'] + if self.verbose: print(self.txDoc[0]) + return self.txDoc + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la génération du document:\n" + message + "\n") + raise + + + # Check document + def checkTXDoc(self): + issuerRaw=[];outAmount=[];outPubkey=[];commentRaw=[] + for docs in self.txDoc: + docList = docs.splitlines() + for i, line in enumerate(docList): + if re.search("Issuers:", line): + issuerRaw.append(docList[(i + 1) % len(docList)]) + if re.search("Outputs:", line): + outputRaw = docList[(i + 1) % len(docList)].split(":") + outAmount.append(int(outputRaw[0])) + outPubkey.append(outputRaw[2].split("SIG(")[1].replace(')','')) + if re.search("Comment:", line): + commentRaw.append(line.split(': ', 1)[1]) + + # Check if it's only a change transaction + if all(i == self.issuer for i in outPubkey): + print("Le document contient une transaction de change") + self.isChange = True + # Check validity of the document + elif all(i != self.issuer for i in issuerRaw) or sum(outAmount) != self.amount or all(i != self.recipient for i in outPubkey) or all(i != self.comment for i in commentRaw): + sys.stderr.write(colored("Le document généré est corrompu !\nLe noeud " + self.node + "a peut être un dysfonctionnement.\n", 'red')) + sys.stderr.write(colored(issuerRaw[0] + " envoi " + str(outAmount[0]) + " vers " + outPubkey[0] + " with comment: " + commentRaw[0] + "\n", "yellow")) + raise ValueError('Le document généré est corrompu !') + else: + print("Le document généré est conforme.") + self.isChange = False + return self.txDoc + + def signDoc(self): + # Sign TX documents + signature=[] + self.signedDoc=[] + for i, docs in enumerate(self.txDoc): + signature.append(fmt["64"](sign(docs.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(docs.encode())])) + self.signedDoc.append(docs + signature[i].decode()) + return self.signedDoc + + + def sendTXDoc(self): + # Build TX documents + txResult=[] + for docs in self.signedDoc: + querySign = gql( + """ + mutation ($signedDoc: String!){ tx( + rawTx: $signedDoc + ) { + version + issuers + outputs + } + } + """ + ) + paramsSign = { + "signedDoc": docs + } + + # Send TX Signed document + try: + txResult.append(str(self.client.execute(querySign, variable_values=paramsSign))) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la transaction:\n" + message + "\n") + if self.verbose: + sys.stderr.write("Document final:\n" + docs) + raise ValueError(message) + else: + if self.isChange: + self.send() + else: + print(colored("Transaction effectué avec succès !", "green")) + if self.verbose: + print(docs) + break + + return txResult + + def _getIsChange(self): + return self._isChange + def _setIsChange(self, newChange): + if self.verbose: print("_setIsChange: ", str(newChange)) + self._isChange = newChange + if newChange: self.useMempool == True + isChange = property(_getIsChange, _setIsChange) + + def send(self): + result = self.genDoc() + result = self.checkTXDoc() + result = self.signDoc() + result = self.sendTXDoc() + return result + diff --git a/zen/jaklis/lib/likes.py b/zen/jaklis/lib/likes.py index ce4b1f6..5eee339 100644 --- a/zen/jaklis/lib/likes.py +++ b/zen/jaklis/lib/likes.py @@ -1,40 +1,15 @@ -#!/usr/bin/env python3 - import os, sys, ast, requests, json, base58, base64, time, string, random, re from lib.natools import fmt, sign, get_privkey, box_decrypt, box_encrypt from time import sleep from hashlib import sha256 from datetime import datetime from termcolor import colored +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX -PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" - -class ReadLikes: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.issuer) or len(self.issuer) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - +class ReadLikes(CesiumCommon): # Configure JSON document to send def configDoc(self, profile): - if not profile: profile = self.issuer - # elif len(profile) < 42: - # print(len(profile)) - # gProfile = requests.get('{0}/user/profile/{1}'.format(self.pod, issuer)) - # gProfile = json.loads(gProfile.text)['_source'] - # pseudo = gProfile['title'] + if not profile: profile = self.pubkey data = {} data['query'] = {} @@ -98,7 +73,7 @@ class ReadLikes: payTo = '' id = i['_id'] level = i['_source']['level'] - if issuer == self.issuer: + if issuer == self.pubkey: finalPrint['yours'] = { 'id' : id, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level } else: finalPrint['likes'].append({ 'issuer' : issuer, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level }) @@ -124,47 +99,18 @@ class ReadLikes: data = json.dumps(data) result = requests.post('{0}/user/profile/_search'.format(self.pod), headers=headers, data=data) - result = json.loads(result.text)['hits']['hits'][0]['_source'] - - return result - - def readLikes(self, profile=False): - document = self.configDoc(profile) - result = self.sendDocument(document) - result = self.parseResult(result) - - print(result) - return result - - + result = json.loads(result.text)['hits']['hits'] + for i in result: + return i['_source'] #################### Like class #################### - - -class SendLikes: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.issuer) or len(self.issuer) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - +class SendLikes(CesiumCommon): # Configure JSON document to send def configDoc(self, profile, likes): - if not profile: profile = self.issuer + if not profile: profile = self.pubkey if likes not in range(0, 6): sys.stderr.write(colored('Votre like doit être compris entre 0 et 5.\n', 'red')) return False @@ -180,7 +126,7 @@ class SendLikes: data['kind'] = "STAR" data['level'] = likes data['time'] = timeSent - data['issuer'] = self.issuer + data['issuer'] = self.pubkey document = json.dumps(data) @@ -216,7 +162,10 @@ class SendLikes: resultJson = json.loads(result.text) if 'DuplicatedDocumentException' in resultJson['error']: rmLike = UnLikes(self.dunikey, self.pod) - rmLike.unLike(pubkey, True) + idLike = rmLike.checkLike(pubkey) + if idLike: + document = rmLike.configDoc(idLike) + rmLike.sendDocument(document, True) sleep(0.5) self.sendDocument(document, pubkey) return resultJson['error'] @@ -227,42 +176,12 @@ class SendLikes: sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') - - - def like(self, stars, profile=False): - document = self.configDoc(profile, stars) - if document: - self.sendDocument(document, profile) - - - - #################### Unlike class #################### - - -class UnLikes: - def __init__(self, dunikey, pod): - # Get my pubkey from my private key - try: - self.dunikey = dunikey - if not dunikey: - raise ValueError("Dunikey is empty") - except: - sys.stderr.write("Please fill the path to your private key (PubSec)\n") - sys.exit(1) - - self.issuer = get_privkey(dunikey, "pubsec").pubkey - self.pod = pod - - if not re.match(PUBKEY_REGEX, self.issuer) or len(self.issuer) > 45: - sys.stderr.write("La clé publique n'est pas au bon format.\n") - sys.exit(1) - +class UnLikes(CesiumCommon): # Check if you liked this profile def checkLike(self, pubkey): - readProfileLikes = ReadLikes(self.dunikey, self.pod) document = readProfileLikes.configDoc(pubkey) result = readProfileLikes.sendDocument(document) @@ -285,7 +204,7 @@ class UnLikes: data['index'] = "like" data['type'] = "record" data['id'] = idLike - data['issuer'] = self.issuer + data['issuer'] = self.pubkey data['time'] = timeSent document = json.dumps(data) @@ -321,11 +240,3 @@ class UnLikes: return result.text else: sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') - - - def unLike(self, pubkey, silent=False): - idLike = self.checkLike(pubkey) - if idLike: - document = self.configDoc(idLike) - self.sendDocument(document, silent) - diff --git a/zen/jaklis/lib/messaging.py b/zen/jaklis/lib/messaging.py new file mode 100644 index 0000000..2165182 --- /dev/null +++ b/zen/jaklis/lib/messaging.py @@ -0,0 +1,236 @@ +import os, sys, ast, requests, json, base58, base64 +from time import time +from datetime import datetime +from termcolor import colored +from lib.natools import fmt, get_privkey, box_decrypt, box_encrypt +from lib.cesiumCommon import CesiumCommon, pp_json, PUBKEY_REGEX + + +#################### Reading class #################### + + +class ReadFromCesium(CesiumCommon): + # Configure JSON document to send + def configDoc(self, nbrMsg, outbox): + boxType = "issuer" if outbox else "recipient" + + data = {} + data['sort'] = { "time": "desc" } + data['from'] = 0 + data['size'] = nbrMsg + data['_source'] = ['issuer','recipient','title','content','time','nonce','read_signature'] + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = {} + data['query']['bool']['filter']['term'] = {} + data['query']['bool']['filter']['term'][boxType] = self.pubkey + + document = json.dumps(data) + return document + + def sendDocument(self, nbrMsg, outbox): + boxType = "outbox" if outbox else "inbox" + + document = self.configDoc(nbrMsg, outbox) + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/message/{1}/_search'.format(self.pod, boxType), headers=headers, data=document) + if result.status_code == 200: + return result.json()["hits"] + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text) + + # Parse JSON result and display messages + def readMessages(self, msgJSON, nbrMsg, outbox): + def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + totalMsg = msgJSON["total"] + if nbrMsg > totalMsg: + nbrMsg = totalMsg + + if totalMsg == 0: + print(colored("Aucun message à afficher.", 'yellow')) + return True + else: + infoTotal = " Nombre de messages: " + str(nbrMsg) + "/" + str(totalMsg) + " " + print(colored(infoTotal.center(rows, '#'), "yellow")) + for hits in msgJSON["hits"]: + self.idMsg = hits["_id"] + msgSrc = hits["_source"] + self.issuer = msgSrc["issuer"] + nonce = msgSrc["nonce"] + nonce = base58.b58decode(nonce) + self.dateS = msgSrc["time"] + date = datetime.fromtimestamp(self.dateS).strftime(", le %d/%m/%Y à %H:%M ") + if outbox: + startHeader = " À " + msgSrc["recipient"] + else: + startHeader = " De " + self.issuer + headerMsg = startHeader + date + "(ID: {})".format(self.idMsg) + " " + + print('-'.center(rows, '-')) + print(colored(headerMsg, "blue").center(rows+9, '-')) + print('-'.center(rows, '-')) + try: + self.title = decrypt(msgSrc["title"]) + self.content = decrypt(msgSrc["content"]) + except Exception as e: + sys.stderr.write(colored(str(e), 'red') + '\n') + pp_json(hits) + continue + print('\033[1m' + self.title + '\033[0m') + print(self.content) + + print(colored(infoTotal.center(rows, '#'), "yellow")) + + # Parse JSON result and display messages + def jsonMessages(self, msgJSON, nbrMsg, outbox): + def decrypt(msg): + msg64 = base64.b64decode(msg) + return box_decrypt(msg64, get_privkey(self.dunikey, "pubsec"), self.issuer, nonce).decode() + + totalMsg = msgJSON["total"] + if nbrMsg > totalMsg: + nbrMsg = totalMsg + + if totalMsg == 0: + print("Aucun message à afficher") + return True + else: + data = [] + # data.append({}) + # data[0]['total'] = totalMsg + for i, hits in enumerate(msgJSON["hits"]): + self.idMsg = hits["_id"] + msgSrc = hits["_source"] + self.issuer = msgSrc["issuer"] + nonce = msgSrc["nonce"] + nonce = base58.b58decode(nonce) + self.date = msgSrc["time"] + + if outbox: + pubkey = msgSrc["recipient"] + else: + pubkey = self.issuer + + try: + self.title = decrypt(msgSrc["title"]) + self.content = decrypt(msgSrc["content"]) + except Exception as e: + sys.stderr.write(colored(str(e), 'red') + '\n') + pp_json(hits) + continue + + data.append(i) + data[i] = {} + data[i]['id'] = self.idMsg + data[i]['date'] = self.date + data[i]['pubkey'] = pubkey + data[i]['title'] = self.title + data[i]['content'] = self.content + + data = json.dumps(data, indent=2) + return data + + +#################### Sending class #################### + + +class SendToCesium(CesiumCommon): + def encryptMsg(self, msg): + return fmt["64"](box_encrypt(msg.encode(), get_privkey(self.dunikey, "pubsec"), self.recipient, self.nonce)).decode() + + def configDoc(self, title, msg): + b58nonce = base58.b58encode(self.nonce).decode() + + # Get current timestamp + timeSent = int(time()) + + # Generate custom JSON + data = {} + data['issuer'] = self.pubkey + data['recipient'] = self.recipient + data['title'] = title + data['content'] = msg + data['time'] = timeSent + data['nonce'] = b58nonce + data['version'] = 2 + document = json.dumps(data) + + return self.signDoc(document) + + + def sendDocument(self, document, outbox): + boxType = "outbox" if outbox else "inbox" + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get result + try: + result = requests.post('{0}/message/{1}?pubkey={2}'.format(self.pod, boxType, self.recipient), headers=headers, data=document) + except Exception as e: + sys.stderr.write("Impossible d'envoyer le message:\n" + str(e)) + sys.exit(1) + else: + if result.status_code == 200: + print(colored("Message envoyé avec succès !", "green")) + print("ID: " + result.text) + return result + else: + sys.stderr.write("Erreur inconnue:" + '\n') + print(str(pp_json(result.text)) + '\n') + + +#################### Deleting class #################### + + +class DeleteFromCesium(CesiumCommon): + def configDoc(self, idMsg, outbox): + # Get current timestamp + timeSent = int(time()) + + boxType = "outbox" if outbox else "inbox" + + # Generate document to customize + data = {} + data['version'] = 2 + data['index'] = "message" + data['type'] = boxType + data['id'] = idMsg + data['issuer'] = self.pubkey + data['time'] = timeSent + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocument(self, document, idMsg): + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get result + try: + result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) + if result.status_code == 404: + raise ValueError("Message introuvable") + elif result.status_code == 403: + raise ValueError("Vous n'êtes pas l'auteur de ce message.") + except Exception as e: + sys.stderr.write(colored("Impossible de supprimer le message {0}:\n".format(idMsg), 'red') + str(e) + "\n") + return False + else: + if result.status_code == 200: + print(colored("Message {0} supprimé avec succès !".format(idMsg), "green")) + return result + else: + sys.stderr.write("Erreur inconnue.") diff --git a/zen/jaklis/lib/profiles.py b/zen/jaklis/lib/profiles.py new file mode 100644 index 0000000..5386c89 --- /dev/null +++ b/zen/jaklis/lib/profiles.py @@ -0,0 +1,125 @@ +import sys, re, json, requests, base64 +from time import time +from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX + + +class Profiles(CesiumCommon): + # Configure JSON document SET to send + def configDocSet(self, name, description, city, address, pos, socials, avatar): + timeSent = int(time()) + + data = {} + if name: data['title'] = name + if description: data['description'] = description + if address: data['address'] = address + if city: data['city'] = city + if pos: + geoPoint = {} + geoPoint['lat'] = pos[0] + geoPoint['lon'] = pos[1] + data['geoPoint'] = geoPoint + if socials: + data['socials'] = [] + data['socials'].append({}) + data['socials'][0]['type'] = "web" + data['socials'][0]['url'] = socials + if avatar: + avatar = open(avatar, 'rb').read() + avatar = base64.b64encode(avatar).decode() + data['avatar'] = {} + data['avatar']['_content'] = avatar + data['avatar']['_content_type'] = "image/png" + data['time'] = timeSent + data['issuer'] = self.pubkey + data['version'] = 2 + data['tags'] = [] + + document = json.dumps(data) + + return self.signDoc(document) + + # Configure JSON document GET to send + def configDocGet(self, profile, scope='title', getAvatar=None): + + if getAvatar: + avatar = "avatar" + else: + avatar = "avatar._content_type" + + data = { + "query": { + "bool": { + "should":[ + { + "match":{ + scope:{ + "query": profile,"boost":2 + } + } + },{ + "prefix": {scope: profile} + } + ] + } + },"highlight": { + "fields": { + "title":{}, + "tags":{} + } + },"from":0, + "size":100, + "_source":["title", avatar,"description","city","address","socials.url","creationTime","membersCount","type"], + "indices_boost":{"user":100,"page":1,"group":0.01 + } + } + + document = json.dumps(data) + + return document + + # Configure JSON document SET to send + def configDocErase(self): + timeSent = int(time()) + + data = {} + data['time'] = timeSent + data['id'] = self.pubkey + data['issuer'] = self.pubkey + data['version'] = 2 + data['index'] = "user" + data['type'] = "profile" + + document = json.dumps(data) + + return self.signDoc(document) + + def sendDocument(self, document, type): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + if type == 'set': + reqQuery = '{0}/user/profile?pubkey={1}/_update?pubkey={1}'.format(self.pod, self.pubkey) + elif type == 'get': + reqQuery = '{0}/user,page,group/profile,record/_search'.format(self.pod) + elif type == 'erase': + reqQuery = '{0}/history/delete'.format(self.pod) + + result = requests.post(reqQuery, headers=headers, data=document) + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document...\n" + result.text + '\n') + + def parseJSON(self, doc): + doc = json.loads(doc)['hits']['hits'] + if doc: + pubkey = { "pubkey": doc[0]['_id'] } + rest = doc[0]['_source'] + final = {**pubkey, **rest} + return json.dumps(final, indent=2) + else: + return 'Profile vide' diff --git a/zen/jaklis/paiements.py b/zen/jaklis/paiements.py new file mode 100755 index 0000000..dde58da --- /dev/null +++ b/zen/jaklis/paiements.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +""" +ZetCode Tkinter tutorial + +In this example, we use the pack +manager to create a review example. + +Author: Jan Bodnar +Website: www.zetcode.com +""" + +import PySimpleGUI as sg +from lib.gva import GvaApi +import sys, os, threading +from shutil import copyfile +from os.path import join, dirname +from dotenv import load_dotenv +from lib.natools import get_privkey +import requests + +class StdoutRedirector(object): + def __init__(self, text_widget): + self.text_widget = text_widget + + def write(self, s): + self.text_widget.insert('end', s) + self.text_widget.see('end') + + def flush(self): + pass + + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' + +# Get variables environment +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') +load_dotenv(dotenv_path) + +dunikey = os.getenv('DUNIKEY') +if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) +node = os.getenv('NODE') +issuer = get_privkey(dunikey, "pubsec").pubkey + + +def ProceedPaiement(recipient, amount, comment): + if not recipient: + raise ValueError("Veuillez indiquer un destinataire de paiement") + elif not amount: + raise ValueError("Veuillez indiquer le montant de la transaction") + + amount = int(float(amount.replace(',','.'))*100) + print("Paiement en cours vers", recipient) + gva = GvaApi(dunikey, node, recipient) + gva.pay(amount, comment, False, False) + + recipient = amount = comment = None + + +sg.theme('DarkGrey2') +layout = [ [sg.Text('Noeud utilisé: ' + node)], + [sg.Text('Votre clé publique: ' + issuer)], + [sg.Text('')], + [sg.Text('Destinataire: '), sg.InputText(size=(55, None),default_text=issuer)], + [sg.Text('Montant: '), sg.InputText(size=(7, None)), sg.Text('Ḡ1')], + [sg.Text('Commentaire:'), sg.InputText(size=(55, None))], + [sg.Button('Envoyer')] ] + +# Create the Window +window = sg.Window('Paiement Ḡ1 - GVA', layout) +# availablePubkeys = requests.get('https://g1-stats.axiom-team.fr/data/wallets-g1.txt') +while True: + try: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + if event == 'Envoyer': + ProceedPaiement(values[0], values[1], values[2]) + except Exception as e: + loc = window.CurrentLocation() + sg.popup(e, title="ERREUR", button_color=('black','red'), location=(loc)) + else: + loc = window.CurrentLocation() + sg.popup(f'Transaction effectué avec succès !', title="Envoyé", location=(loc)) + + +window.close() diff --git a/zen/jaklis/requirements.txt b/zen/jaklis/requirements.txt index bf25335..e6b5711 100644 --- a/zen/jaklis/requirements.txt +++ b/zen/jaklis/requirements.txt @@ -4,3 +4,4 @@ pybase64 duniterpy termcolor python-dotenv +gql