new jaklis

This commit is contained in:
qo-op 2020-12-12 21:00:01 +01:00
parent 6748274034
commit b11b7dc207
24 changed files with 1249 additions and 705 deletions

View File

@ -1,4 +1,5 @@
DUNIKEY="/.zen/secret.dunikey" # Chemin du fichier de trousseau Ḡ1 de l'émetteur, au format PubSec 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://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.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"

View File

@ -1,4 +1,6 @@
DUNIKEY="" # Chemin de la clé privé Ḡ1 de l'émetteur, au format PubSec 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.duniter.fr" # Adresse du pod Cesium ou Gchange à utiliser
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://data.gchange.fr" # Adresse du pod ḠChange à utiliser #POD="https://data.gchange.fr" # Adresse du pod ḠChange à utiliser
NODE="https://g1.librelois.fr/gva"

View File

@ -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: Client CLI pour Cesium+ et Ḡchange
{read,send,delete,get,set,erase,like,unlike}
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 read Lecture des messages
send Envoi d'un message send Envoi d'un message
delete Supression d'un message delete Supression d'un message
@ -32,12 +40,10 @@ positional arguments:
erase Effacer son profile Cesium+ erase Effacer son profile Cesium+
like Voir les likes d'un profile / Liker un profile (option -s NOTE) like Voir les likes d'un profile / Liker un profile (option -s NOTE)
unlike Supprimer un like 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``CMD` est la commande souhaité pour obtenir l'aide détaillé de cette commande. Utilisez `./jaklis CMD -h``CMD` est la commande souhaité pour obtenir l'aide détaillé de cette commande.

View File

@ -1,26 +1,26 @@
#!/usr/bin/env python3 #!/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 os.path import join, dirname
from shutil import copyfile from shutil import copyfile
from dotenv import load_dotenv from dotenv import load_dotenv
from duniterpy.key import SigningKey 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 # Get variables environment
if not os.path.isfile('.env'): if not os.path.isfile(MY_PATH + '.env'):
copyfile(".env.template", ".env") copyfile(MY_PATH + ".env.template",MY_PATH + ".env")
dotenv_path = join(dirname(__file__), '.env') dotenv_path = join(dirname(__file__),MY_PATH + '.env')
load_dotenv(dotenv_path) load_dotenv(dotenv_path)
# Parse arguments # Parse arguments
parser = argparse.ArgumentParser(description="Client CLI pour Cesium+ et Ḡchange") 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('-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('-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") subparsers = parser.add_subparsers(title="Commandes de jaklis", dest="cmd")
read_cmd = subparsers.add_parser('read', help="Lecture des messages") 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+") 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)") 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") 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 # Messages management
read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") 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") like_cmd.add_argument('-s', '--stars', type=int, help="Nombre d'étoile")
unlike_cmd.add_argument('-p', '--profile', help="Profile à déliker") 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() args = parser.parse_args()
cmd = args.cmd cmd = args.cmd
if args.version:
print(__version__)
sys.exit(0)
if not cmd: if not cmd:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
if args.version:
print(VERSION)
sys.exit(0)
def createTmpDunikey(): def createTmpDunikey():
# Generate pseudo-random nonce # Generate pseudo-random nonce
nonce=[] nonce=[]
@ -87,79 +105,121 @@ def createTmpDunikey():
return keyPath return keyPath
if args.node: # Check if we need dunikey
pod = args.node try:
else: pubkey = args.pubkey
pod = os.getenv('POD') except:
if not pod: pubkey = False
pod="https://g1.data.le-sou.org" try:
profile = args.profile
except:
profile = False
if args.key: if cmd in ('history','balance','get') and (pubkey or profile):
dunikey = args.key noNeedDunikey = True
keyPath = False keyPath = False
try:
dunikey = args.pubkey
except:
dunikey = args.profile
else: else:
dunikey = os.getenv('DUNIKEY') noNeedDunikey = False
if not dunikey: if args.key:
keyPath = createTmpDunikey() dunikey = args.key
dunikey = keyPath
else:
keyPath = False 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: else:
titre = input("Indiquez le titre du message: ") dunikey = os.getenv('DUNIKEY')
msg = input("Indiquez le contenu du message: ") 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": # Construct CesiumPlus object
messages = DeleteFromCesium(dunikey, pod, args.outbox) if cmd in ("read","send","delete","set","get","erase","like","unlike"):
messages.delete(args.id[0]) from lib.cesium import CesiumPlus
# Build cesium+ profiles class if args.node:
elif cmd in ('set','get','erase'): pod = args.node
cesium = Profiles(dunikey, pod) else:
if cmd == "set": 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) cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site, args.avatar)
elif cmd == "get": elif cmd == "get":
cesium.get(args.profile, args.avatar) cesium.get(args.profile, args.avatar)
elif cmd == "erase": elif cmd == "erase":
cesium.erase() cesium.erase()
# Build cesium+ likes class # Likes
elif cmd == "like": elif cmd == "like":
if args.stars or args.stars == 0: if args.stars or args.stars == 0:
gchange = SendLikes(dunikey, pod) cesium.like(args.stars, args.profile)
gchange.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: else:
gchange = ReadLikes(dunikey, pod) node = os.getenv('NODE')
gchange.readLikes(args.profile) if not node:
elif cmd == "unlike": node="https://g1.librelois.fr/gva"
gchange = UnLikes(dunikey, pod)
gchange.unLike(args.profile) 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: if keyPath:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 class CesiumPlus(CesiumCommon):
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
PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" #################### Messaging ####################
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
def read(self, nbrMsg, outbox, isJSON): def read(self, nbrMsg, outbox, isJSON):
jsonMsg = self.sendDocument(nbrMsg, outbox) readCesium = ReadFromCesium(self.dunikey, self.pod)
jsonMsg = readCesium.sendDocument(nbrMsg, outbox)
if isJSON: if isJSON:
jsonFormat = self.jsonMessages(jsonMsg, nbrMsg, outbox) jsonFormat = readCesium.jsonMessages(jsonMsg, nbrMsg, outbox)
print(jsonFormat) print(jsonFormat)
else: else:
self.readMessages(jsonMsg, nbrMsg, outbox) readCesium.readMessages(jsonMsg, nbrMsg, outbox)
def send(self, title, msg, recipient, outbox):
sendCesium = SendToCesium(self.dunikey, self.pod)
sendCesium.recipient = recipient
#################### 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
# Generate pseudo-random nonce # Generate pseudo-random nonce
nonce=[] nonce=[]
for _ in range(32): for _ in range(32):
nonce.append(random.choice(string.ascii_letters + string.digits)) 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: finalDoc = sendCesium.configDoc(sendCesium.encryptMsg(title), sendCesium.encryptMsg(msg)) # Configure JSON document to send
sys.stderr.write("La clé publique n'est pas au bon format.\n") sendCesium.sendDocument(finalDoc, outbox) # Send final signed document
sys.exit(1)
def delete(self, idsMsgList, outbox):
def encryptMsg(self, msg): deleteCesium = DeleteFromCesium(self.dunikey, self.pod)
return fmt["64"](box_encrypt(msg.encode(), get_privkey(self.dunikey, "pubsec"), self.recipient, self.nonce)).decode() # deleteCesium.issuer = recipient
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):
for idMsg in idsMsgList: for idMsg in idsMsgList:
finalDoc = self.configDoc(idMsg) finalDoc = deleteCesium.configDoc(idMsg, outbox)
self.sendDocument(finalDoc, idMsg) deleteCesium.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)
#################### Profiles ####################
def set(self, name=None, description=None, ville=None, adresse=None, position=None, site=None, avatar=None): 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) setProfile = Profiles(self.dunikey, self.pod)
result = self.sendDocument(document,'set') document = setProfile.configDocSet(name, description, ville, adresse, position, site, avatar)
result = setProfile.sendDocument(document,'set')
print(result) print(result)
return result return result
def get(self, profile=None, avatar=None): def get(self, profile=None, avatar=None):
getProfile = Profiles(self.dunikey, self.pod, self.noNeedDunikey)
if not profile: if not profile:
profile = self.pubkey profile = self.pubkey
if not re.match(PUBKEY_REGEX, profile) or len(profile) > 45: if not re.match(PUBKEY_REGEX, profile) or len(profile) > 45:
@ -542,16 +56,38 @@ class Profiles:
else: else:
scope = '_id' scope = '_id'
document = self.configDocGet(profile, scope, avatar) document = getProfile.configDocGet(profile, scope, avatar)
resultJSON = self.sendDocument(document, 'get') resultJSON = getProfile.sendDocument(document, 'get')
result = self.parseJSON(resultJSON) result = getProfile.parseJSON(resultJSON)
print(result) print(result)
return result
def erase(self): def erase(self):
document = self.configDocErase() eraseProfile = Profiles(self.dunikey, self.pod)
result = self.sendDocument(document,'erase') document = eraseProfile.configDocErase()
result = eraseProfile.sendDocument(document,'erase')
print(result) print(result)
return 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)

View File

@ -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)

31
zen/jaklis/lib/crypt.py Executable file
View File

@ -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)

59
zen/jaklis/lib/gva.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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

172
zen/jaklis/lib/gvaPay.py Normal file
View File

@ -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

View File

@ -1,40 +1,15 @@
#!/usr/bin/env python3
import os, sys, ast, requests, json, base58, base64, time, string, random, re 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 lib.natools import fmt, sign, get_privkey, box_decrypt, box_encrypt
from time import sleep from time import sleep
from hashlib import sha256 from hashlib import sha256
from datetime import datetime from datetime import datetime
from termcolor import colored from termcolor import colored
from lib.cesiumCommon import CesiumCommon, PUBKEY_REGEX
PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" class ReadLikes(CesiumCommon):
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)
# Configure JSON document to send # Configure JSON document to send
def configDoc(self, profile): def configDoc(self, profile):
if not profile: profile = self.issuer if not profile: profile = self.pubkey
# 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']
data = {} data = {}
data['query'] = {} data['query'] = {}
@ -98,7 +73,7 @@ class ReadLikes:
payTo = '' payTo = ''
id = i['_id'] id = i['_id']
level = i['_source']['level'] level = i['_source']['level']
if issuer == self.issuer: if issuer == self.pubkey:
finalPrint['yours'] = { 'id' : id, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level } finalPrint['yours'] = { 'id' : id, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level }
else: else:
finalPrint['likes'].append({ 'issuer' : issuer, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level }) finalPrint['likes'].append({ 'issuer' : issuer, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level })
@ -124,47 +99,18 @@ class ReadLikes:
data = json.dumps(data) data = json.dumps(data)
result = requests.post('{0}/user/profile/_search'.format(self.pod), headers=headers, data=data) result = requests.post('{0}/user/profile/_search'.format(self.pod), headers=headers, data=data)
result = json.loads(result.text)['hits']['hits'][0]['_source'] result = json.loads(result.text)['hits']['hits']
for i in result:
return result return i['_source']
def readLikes(self, profile=False):
document = self.configDoc(profile)
result = self.sendDocument(document)
result = self.parseResult(result)
print(result)
return result
#################### Like class #################### #################### Like class ####################
class SendLikes(CesiumCommon):
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)
# Configure JSON document to send # Configure JSON document to send
def configDoc(self, profile, likes): def configDoc(self, profile, likes):
if not profile: profile = self.issuer if not profile: profile = self.pubkey
if likes not in range(0, 6): if likes not in range(0, 6):
sys.stderr.write(colored('Votre like doit être compris entre 0 et 5.\n', 'red')) sys.stderr.write(colored('Votre like doit être compris entre 0 et 5.\n', 'red'))
return False return False
@ -180,7 +126,7 @@ class SendLikes:
data['kind'] = "STAR" data['kind'] = "STAR"
data['level'] = likes data['level'] = likes
data['time'] = timeSent data['time'] = timeSent
data['issuer'] = self.issuer data['issuer'] = self.pubkey
document = json.dumps(data) document = json.dumps(data)
@ -216,7 +162,10 @@ class SendLikes:
resultJson = json.loads(result.text) resultJson = json.loads(result.text)
if 'DuplicatedDocumentException' in resultJson['error']: if 'DuplicatedDocumentException' in resultJson['error']:
rmLike = UnLikes(self.dunikey, self.pod) 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) sleep(0.5)
self.sendDocument(document, pubkey) self.sendDocument(document, pubkey)
return resultJson['error'] 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') 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 #################### #################### Unlike class ####################
class UnLikes(CesiumCommon):
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)
# Check if you liked this profile # Check if you liked this profile
def checkLike(self, pubkey): def checkLike(self, pubkey):
readProfileLikes = ReadLikes(self.dunikey, self.pod) readProfileLikes = ReadLikes(self.dunikey, self.pod)
document = readProfileLikes.configDoc(pubkey) document = readProfileLikes.configDoc(pubkey)
result = readProfileLikes.sendDocument(document) result = readProfileLikes.sendDocument(document)
@ -285,7 +204,7 @@ class UnLikes:
data['index'] = "like" data['index'] = "like"
data['type'] = "record" data['type'] = "record"
data['id'] = idLike data['id'] = idLike
data['issuer'] = self.issuer data['issuer'] = self.pubkey
data['time'] = timeSent data['time'] = timeSent
document = json.dumps(data) document = json.dumps(data)
@ -321,11 +240,3 @@ class UnLikes:
return result.text return result.text
else: else:
sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') 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)

236
zen/jaklis/lib/messaging.py Normal file
View File

@ -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.")

125
zen/jaklis/lib/profiles.py Normal file
View File

@ -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'

94
zen/jaklis/paiements.py Executable file
View File

@ -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()

View File

@ -4,3 +4,4 @@ pybase64
duniterpy duniterpy
termcolor termcolor
python-dotenv python-dotenv
gql