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 0000000..12b0bcc Binary files /dev/null and b/zen/cesium-messaging/lib/__pycache__/cesium.cpython-36.pyc differ diff --git a/zen/cesium-messaging/lib/__pycache__/likes.cpython-36.pyc b/zen/cesium-messaging/lib/__pycache__/likes.cpython-36.pyc new file mode 100644 index 0000000..570996c Binary files /dev/null and b/zen/cesium-messaging/lib/__pycache__/likes.cpython-36.pyc differ diff --git a/zen/cesium-messaging/lib/cesium.py b/zen/cesium-messaging/lib/cesium.py new file mode 100644 index 0000000..0d346e3 --- /dev/null +++ b/zen/cesium-messaging/lib/cesium.py @@ -0,0 +1,463 @@ +#!/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}" + +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")) + + + 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