From 07ef6ce4289ee9583ea210baf706a5949d74baf7 Mon Sep 17 00:00:00 2001 From: qo-op Date: Wed, 2 Dec 2020 22:45:10 +0100 Subject: [PATCH] cesium messaging jaklis --- zen/cesium-messaging/.env | 3 +- zen/cesium-messaging/jaklis.py | 147 ++++++ .../lib/__pycache__/cesium.cpython-36.pyc | Bin 0 -> 12012 bytes .../lib/__pycache__/likes.cpython-36.pyc | Bin 0 -> 7885 bytes zen/cesium-messaging/lib/cesium.py | 463 ++++++++++++++++++ zen/cesium-messaging/lib/likes.py | 324 ++++++++++++ zen/cesium-messaging/userEnv.py | 7 +- 7 files changed, 940 insertions(+), 4 deletions(-) create mode 100755 zen/cesium-messaging/jaklis.py create mode 100644 zen/cesium-messaging/lib/__pycache__/cesium.cpython-36.pyc create mode 100644 zen/cesium-messaging/lib/__pycache__/likes.cpython-36.pyc create mode 100644 zen/cesium-messaging/lib/cesium.py create mode 100644 zen/cesium-messaging/lib/likes.py diff --git a/zen/cesium-messaging/.env b/zen/cesium-messaging/.env index 9d0f4d9..81d66f0 100644 --- a/zen/cesium-messaging/.env +++ b/zen/cesium-messaging/.env @@ -1,3 +1,4 @@ -DUNIKEY="/home/pi/.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://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://data.gchange.fr" # Noeud Gchange utilisé pour l'envoi du message diff --git a/zen/cesium-messaging/jaklis.py b/zen/cesium-messaging/jaklis.py new file mode 100755 index 0000000..d331632 --- /dev/null +++ b/zen/cesium-messaging/jaklis.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +import argparse, sys, os, random, string, getpass +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" + +# Get variables environment +if not os.path.isfile('.env'): + copyfile(".env.template", ".env") +dotenv_path = join(dirname(__file__), '.env') +load_dotenv(dotenv_path) + +# Parse arguments +parser = argparse.ArgumentParser() +parser.add_argument('-v', '--version', action='store_true', help="Affiche la version actuelle du programme") + +subparsers = parser.add_subparsers() +read_cmd = subparsers.add_parser('read', help="Lecture des messages") +send_cmd = subparsers.add_parser('send', help="Envoi d'un message") +delete_cmd = subparsers.add_parser('delete', help="Supression d'un message") +getProfile_cmd = subparsers.add_parser('get', help="Voir un profile Cesium+") +setProfile_cmd = subparsers.add_parser('set', help="Configurer son profile Cesium+") +eraseProfile_cmd = subparsers.add_parser('erase', help="Effacer son profile Cesium+") +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") + +if len(sys.argv) <= 1 or not sys.argv[1] in ('read','send','delete','set','get','erase','like','unlike','-v','--version'): + sys.stderr.write("Veuillez indiquer une commande valide:\n\n") + parser.print_help() + sys.exit(1) + +# Messages management +read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") +read_cmd.add_argument('-o', '--outbox', action='store_true', help="Lit les messages envoyés") + +send_cmd.add_argument('-d', '--destinataire', required=True, help="Destinataire du message") +send_cmd.add_argument('-t', '--titre', help="Titre du message à envoyer") +send_cmd.add_argument('-m', '--message', help="Message à envoyer") +send_cmd.add_argument('-f', '--fichier', help="Envoyer le message contenu dans le fichier 'FICHIER'") +send_cmd.add_argument('-o', '--outbox', action='store_true', help="Envoi le message sur la boite d'envoi") + +delete_cmd.add_argument('-i', '--id', action='append', nargs='+', required=True, help="ID(s) du/des message(s) à supprimer") +delete_cmd.add_argument('-o', '--outbox', action='store_true', help="Suppression d'un message envoyé") + +# Profiles management +setProfile_cmd.add_argument('-n', '--name', help="Nom du profile") +setProfile_cmd.add_argument('-d', '--description', help="Description du profile") +setProfile_cmd.add_argument('-v', '--ville', help="Ville du profile") +setProfile_cmd.add_argument('-a', '--adresse', help="Adresse du profile") +setProfile_cmd.add_argument('-pos', '--position', nargs=2, help="Points géographiques (lat + lon)") +setProfile_cmd.add_argument('-s', '--site', help="Site web du profile") + +getProfile_cmd.add_argument('-p', '--profile', help="Nom du profile") + +# Likes management +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") + +args = parser.parse_args() + +if args.version: + print(VERSION) + sys.exit(0) + +def createTmpDunikey(): + # Generate pseudo-random nonce + nonce=[] + for i in range(32): + nonce.append(random.choice(string.ascii_letters + string.digits)) + nonce = ''.join(nonce) + keyPath = "/tmp/secret.dunikey-" + nonce + + key = SigningKey.from_credentials(getpass.getpass("Identifiant: "), getpass.getpass("Mot de passe: "), None) + key.save_pubsec_file(keyPath) + + return keyPath + +pod = os.getenv('POD') +if not pod: + pod="https://g1.data.le-sou.org" + +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 + os.getenv('DUNIKEY') + + +# Build cesiumMessaging class +if sys.argv[1] == "read": + messages = ReadFromCesium(dunikey, pod) + messages.read(args.number, args.outbox) +elif sys.argv[1] == "send": + if args.fichier: + with open(args.fichier, 'r') as f: + titre = f.readline() + msg = ''.join(f.read().splitlines(True)[0:]) + 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: ") + + messages = SendToCesium(dunikey, pod, args.destinataire, args.outbox) + messages.send(titre, msg) + +elif sys.argv[1] == "delete": + messages = DeleteFromCesium(dunikey, pod, args.outbox) + messages.delete(args.id[0]) + +# Build cesium+ profiles class +elif sys.argv[1] in ('set','get','erase'): + cesium = Profiles(dunikey, pod) + if sys.argv[1] == "set": + cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site) + elif sys.argv[1] == "get": + cesium.get(args.profile) + elif sys.argv[1] == "erase": + cesium.erase() + +# Build cesium+ likes class +elif sys.argv[1] == "like": + if args.stars or args.stars == 0: + gchange = SendLikes(dunikey, pod) + gchange.like(args.stars, args.profile) + else: + gchange = ReadLikes(dunikey, pod) + gchange.readLikes(args.profile) +elif sys.argv[1] == "unlike": + gchange = UnLikes(dunikey, pod) + gchange.unLike(args.profile) + + +if keyPath: + os.remove(keyPath) diff --git a/zen/cesium-messaging/lib/__pycache__/cesium.cpython-36.pyc b/zen/cesium-messaging/lib/__pycache__/cesium.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12b0bccae3d82c0270893fe691bd4f24aeca3b59 GIT binary patch literal 12012 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 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")) + + + def read(self, nbrMsg, outbox): + jsonMsg = self.sendDocument(nbrMsg, outbox) + self.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 + + # Generate pseudo-random nonce + nonce=[] + for i in range(32): + nonce.append(random.choice(string.ascii_letters + string.digits)) + self.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) + + + 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): + 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): + 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 + 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'): + + data = { + "query": { + "bool": { + "should":[ + { + "match":{ + scope:{ + "query": profile,"boost":2 + } + } + },{ + "prefix": {scope: profile} + } + ] + } + },"highlight": { + "fields": { + "title":{}, + "tags":{} + } + },"from":0, + "size":100, + "_source":["title","avatar._content_type","description","city","address","socials.url","creationTime","membersCount","type"], + "indices_boost":{"user":100,"page":1,"group":0.01 + } + } + + document = json.dumps(data) + + return 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) + + 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) + + + def set(self, name=None, description=None, ville=None, adresse=None, position=None, site=None): + document = self.configDocSet(name, description, ville, adresse, position, site) + result = self.sendDocument(document,'set') + + print(result) + return result + + def get(self, profile=None): + if not profile: + profile = self.pubkey + if not re.match(PUBKEY_REGEX, profile) or len(profile) > 45: + scope = 'title' + else: + scope = '_id' + + document = self.configDocGet(profile, scope) + resultJSON = self.sendDocument(document, 'get') + result = self.parseJSON(resultJSON) + + print(result) + return result + + def erase(self): + document = self.configDocSet(None, None, None, None, None, None) + result = self.sendDocument(document,'set') + + print(result) + return result \ No newline at end of file diff --git a/zen/cesium-messaging/lib/likes.py b/zen/cesium-messaging/lib/likes.py new file mode 100644 index 0000000..cccb994 --- /dev/null +++ b/zen/cesium-messaging/lib/likes.py @@ -0,0 +1,324 @@ +#!/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 + +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) + + # 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'] + + data = {} + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = [ + {'term': {'index': 'user'}}, + {'term': {'type': 'profile'}}, + {'term': {'id': profile}}, + {'term': {'kind': 'STAR'}} + ] + # data['query']['bool']['should'] = {'term':{'issuer': self.issuer}} + data['size'] = 5000 + data['_source'] = ['issuer','level'] + data['aggs'] = { + 'level_sum': { + 'sum': { + 'field': 'level' + } + } + } + + return json.dumps(data) + + def sendDocument(self, document): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/like/record/_search'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + # print(result.text) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') + + def parseResult(self, result): + result = json.loads(result) + totalLikes = result['hits']['total'] + totalValue = result['aggregations']['level_sum']['value'] + if totalLikes: + score = totalValue/totalLikes + else: + score = 0 + raw = result['hits']['hits'] + finalPrint = {} + finalPrint['likes'] = [] + for i in raw: + issuer = i['_source']['issuer'] + gProfile = self.getProfile(issuer) + pseudo = gProfile['title'] + payTo = gProfile['pubkey'] + id = i['_id'] + level = i['_source']['level'] + if issuer == self.issuer: + finalPrint['yours'] = { 'id' : id, 'pseudo' : pseudo, 'level' : level } + else: + finalPrint['likes'].append({ 'issuer' : issuer, 'pseudo' : pseudo, 'payTo' : payTo, 'level' : level }) + finalPrint['score'] = score + + return json.dumps(finalPrint) + + def getProfile(self, profile): + headers = { + 'Content-type': 'application/json', + } + + data = {} + data['query'] = {} + data['query']['bool'] = {} + data['query']['bool']['filter'] = [ + {'term': {'_index': 'user'}}, + {'term': {'_type': 'profile'}}, + {'term': {'_id': profile}} + ] + data['_source'] = ['title','pubkey'] + + data = json.dumps(data) + + result = requests.post('{0}/user/profile/_search'.format(self.pod), headers=headers, data=data) + result = json.loads(result.text)['hits']['hits'][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 + + + + +#################### 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) + + # Configure JSON document to send + def configDoc(self, profile, likes): + if not profile: profile = self.issuer + if likes not in range(0, 6): + sys.stderr.write(colored('Votre like doit être compris entre 0 et 5.\n', 'red')) + return False + + + timeSent = int(time.time()) + + data = {} + data['version'] = 2 + data['index'] = "user" + data['type'] = "profile" + data['id'] = profile + data['kind'] = "STAR" + data['level'] = likes + data['time'] = timeSent + data['issuer'] = self.issuer + + document = json.dumps(data) + + # Generate hash of document + hashDoc = sha256(document.encode()).hexdigest().upper() + + # Generate signature of document + signature = fmt["64"](sign(hashDoc.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(hashDoc.encode())]).decode() + + # Build final document + data = {} + data['hash'] = hashDoc + data['signature'] = signature + signJSON = json.dumps(data) + finalJSON = {**json.loads(signJSON), **json.loads(document)} + finalDoc = json.dumps(finalJSON) + + return finalDoc + + def sendDocument(self, document, pubkey): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/user/profile/:id/_like'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + print(colored("Profile liké avec succès !", 'green')) + return result.text + elif result.status_code == 400: + resultJson = json.loads(result.text) + if 'DuplicatedDocumentException' in resultJson['error']: + rmLike = UnLikes(self.dunikey, self.pod) + rmLike.unLike(pubkey, True) + sleep(0.5) + self.sendDocument(document, pubkey) + return resultJson['error'] + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') + else: + resultJson = json.loads(result.text) + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + resultJson['error'] + '\n') + + + + + 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) + + # Check if you liked this profile + def checkLike(self, pubkey): + + readProfileLikes = ReadLikes(self.dunikey, self.pod) + document = readProfileLikes.configDoc(pubkey) + result = readProfileLikes.sendDocument(document) + result = readProfileLikes.parseResult(result) + result = json.loads(result) + + if 'yours' in result: + myLike = result['yours']['id'] + return myLike + else: + sys.stderr.write("Vous n'avez pas liké ce profile\n") + return False + + # Configure JSON document to send + def configDoc(self, idLike): + timeSent = int(time.time()) + + data = {} + data['version'] = 2 + data['index'] = "like" + data['type'] = "record" + data['id'] = idLike + data['issuer'] = self.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, silent): + + headers = { + 'Content-type': 'application/json', + } + + # Send JSON document and get JSON result + result = requests.post('{0}/history/delete'.format(self.pod), headers=headers, data=document) + + if result.status_code == 200: + if not silent: + print(colored("Like supprimé avec succès !", 'green')) + return result.text + else: + sys.stderr.write("Echec de l'envoi du document de lecture des messages...\n" + result.text + '\n') + + + def unLike(self, pubkey, silent=False): + idLike = self.checkLike(pubkey) + if idLike: + document = self.configDoc(idLike) + self.sendDocument(document, silent) + diff --git a/zen/cesium-messaging/userEnv.py b/zen/cesium-messaging/userEnv.py index c302765..81d66f0 100644 --- a/zen/cesium-messaging/userEnv.py +++ b/zen/cesium-messaging/userEnv.py @@ -1,3 +1,4 @@ -DUNIKEY="/home/pi/.zen/secret.dunikey" # Chemin du fichier de trousseau Ḡ1 de l'émetteur, au format PubSec -POD="https://g1.data.duniter.fr" # Noeud Cecium+ utilisé pour l'envoi du message -#pod="https://data.gchange.fr" # Noeud Gchange utilisé pour l'envoi du message +DUNIKEY="/.zen/secret.dunikey" # Chemin du fichier de trousseau Ḡ1 de l'émetteur, au format PubSec +#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://data.gchange.fr" # Noeud Gchange utilisé pour l'envoi du message