jaklis/lib/gvaHistory.py

287 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
import sys, re, os.path, json, ast, time, hashlib
from datetime import datetime
from duniterpy.key import base58
from termcolor import colored
from lib.natools import fmt, sign, get_privkey
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}"
class History:
def __init__(self, dunikey, node, pubkey):
self.dunikey = dunikey
self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey
self.node = node
if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45:
sys.stderr.write("La clé publique n'est pas au bon format.\n")
sys.exit(1)
# Define Duniter GVA node
transport = AIOHTTPTransport(url=node)
self.client = Client(transport=transport, fetch_schema_from_transport=True)
def sendDoc(self, number):
# Build history generation document
queryBuild = gql(
"""
query ($pubkey: PubKeyGva!, $script: PkOrScriptGva!, $number: Int!){
txsHistoryBc(
script: $script
pagination: { pageSize: $number, ord: DESC }
) {
both {
pageInfo {
hasPreviousPage
hasNextPage
}
edges {
direction
node {
currency
issuers
blockstamp
outputs
comment
writtenTime
hash
}
}
}
}
txsHistoryMp(pubkey: $pubkey) {
receiving {
currency
issuers
comment
outputs
receivedTime
blockstamp
hash
}
sending {
currency
issuers
comment
outputs
receivedTime
blockstamp
hash
}
}
balance(script: $script) {
amount
base
}
node {
peer {
currency
}
}
currentUd {
amount
base
}
}
"""
)
paramsBuild = {
"pubkey": self.pubkey,
"number": number,
"script": f"SIG({self.pubkey})",
}
# Send history document
try:
self.historyDoc = self.client.execute(queryBuild, variable_values=paramsBuild)
except Exception as e:
message = ast.literal_eval(str(e))["message"]
sys.stderr.write("Echec de récupération de l'historique:\n" + message + "\n")
sys.exit(1)
def parseHistory(self):
trans = []
i = 0
currentBase = int(self.historyDoc['currentUd']['base'])
self.UD = self.historyDoc['currentUd']['amount']/100
# Parse transactions in blockchain
resBc = []
resBc = self.historyDoc['txsHistoryBc']['both']['edges']
for j, transaction in enumerate(resBc):
# print(transaction)
direction = resBc[j]['direction']
transaction = resBc[j]['node']
output = transaction['outputs'][0]
outPubkey = output.split("SIG(")[1].replace(')','')
# if direction == 'RECEIVED' or self.pubkey != outPubkey:
trans.append(i)
trans[i] = []
trans[i].append(direction)
trans[i].append(transaction['writtenTime'])
if direction == 'SENT':
trans[i].append(outPubkey)
amount = int('-' + output.split(':')[0])
else:
trans[i].append(transaction['issuers'][0])
amount = int(output.split(':')[0])
base = int(output.split(':')[1])
applyBase = base-currentBase
amount = round(amount*pow(10,applyBase)/100, 2)
# if referential == 'DU': amount = round(amount/UD, 2)
trans[i].append(amount)
trans[i].append(round(amount/self.UD, 2))
trans[i].append(transaction['comment'])
trans[i].append(base)
trans[i].append(transaction['blockstamp'])
trans[i].append(transaction['hash'])
i += 1
# Parse transactions in mempool
for direction, resBc in self.historyDoc['txsHistoryMp'].items():
for j, transaction in enumerate(resBc):
# print(transaction)
transaction = resBc[j]
output = transaction['outputs'][0]
outPubkey = output.split("SIG(")[1].replace(')','')
# if direction == 'RECEIVING' or self.pubkey != outPubkey:
trans.append(i)
trans[i] = []
trans[i].append(direction.upper())
trans[i].append(transaction['receivedTime'])
# trans[i].append(int(time.time()))
if direction.upper() == 'SENDING':
trans[i].append(outPubkey)
amount = int('-' + output.split(':')[0])
else:
trans[i].append(transaction['issuers'][0])
amount = int(output.split(':')[0])
base = int(output.split(':')[1])
applyBase = base-currentBase
amount = round(amount*pow(10,applyBase)/100, 2)
# if referential == 'DU': amount = round(amount/UD, 2)
trans[i].append(amount)
trans[i].append(round(amount/self.UD, 2))
trans[i].append(transaction['comment'])
trans[i].append(base)
trans[i].append(transaction['blockstamp'])
trans[i].append(transaction['hash'])
i += 1
# Order transactions by date
trans.sort(key=lambda x: x[1])
# Keep only base if there is base change
lastBase = 0
for i in trans:
if i[6] == lastBase: i[6] = None
else: lastBase = i[6]
return trans
def printHistory(self, trans, noColors):
# Get balance
if (self.historyDoc['balance'] == None):
balance = balanceUD = 'null'
else:
balance = self.historyDoc['balance']['amount']/100
balanceUD = round(balance/self.UD, 2)
# Get currency
currency = self.historyDoc['node']['peer']['currency']
if currency == 'g1': currency = 'Ḡ1'
elif currency == 'g1-test': currency = 'GT'
# if referential == 'DU': currency = 'DU/' + currency.lower()
# Get terminal size
rows = int(os.popen('stty size', 'r').read().split()[1])
# Display history
print('+', end='')
print('-'.center(rows-1, '-'))
if noColors: isBold = isBoldEnd = ''
else:
isBold = '\033[1m'
isBoldEnd = '\033[0m'
print(isBold + "|{: <19} | {: <12} | {: <7} | {: <7} | {: <30}".format(" Date"," De / À"," {0}".format(currency)," DU/{0}".format(currency.lower()),"Commentaire") + isBoldEnd)
print('|', end='')
for t in trans:
if t[0] == "RECEIVED": color = "green"
elif t[0] == "SENT": color = "blue"
elif t[0] == "receiving": color = "yellow"
elif t[0] == "sending": color = "red"
else: color = None
if noColors:
color = None
if t[0] in ('RECEIVING','SENDING'):
comment = '(EN ATTENTE) ' + t[5]
else:
comment = t[5]
else:
comment = t[5]
date = datetime.fromtimestamp(t[1]).strftime("%d/%m/%Y à %H:%M")
print('-'.center(rows-1, '-'))
if t[6]:
print('|', end='')
print(' Changement de base : {0} '.format(t[6]).center(rows-1, '#'))
print('|', end='')
print('-'.center(rows-1, '-'))
print('|', end='')
checksum = self.gen_checksum(t[2])
shortPubkey = t[2][0:4] + '\u2026' + t[2][-4:] + ':' + checksum
if noColors:
print(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, shortPubkey, t[3], t[4], comment))
else:
print(colored(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, shortPubkey, t[3], t[4], comment), color))
print('|', end='')
print('-'.center(rows-1, '-'))
print('|', end='')
print(isBold + 'Solde du compte: {0} {1} ({2} DU/{3})'.format(balance, currency, balanceUD, currency.lower()).center(rows-1, ' ') + isBoldEnd)
print('+', end='')
print(''.center(rows-1, '-'))
if not noColors:
print(colored('Reçus', 'green'), '-', colored('En cours de réception', 'yellow'), '-', colored('Envoyé', 'blue'), '-', colored("En cours d'envoi", 'red'))
return trans
def gen_checksum(self, pubkey):
"""
Returns the checksum of the input pubkey (encoded in b58)
thx Matograine
"""
pubkey_byte = base58.Base58Encoder.decode(str.encode(pubkey))
hash = hashlib.sha256(hashlib.sha256(pubkey_byte).digest()).digest()
return base58.Base58Encoder.encode(hash)[:3]
def jsonHistory(self, transList):
dailyJSON = []
for i, trans in enumerate(transList):
dailyJSON.append(i)
dailyJSON[i] = {}
dailyJSON[i]['status'] = trans[0].upper()
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[i]['blockstamp'] = trans[7]
dailyJSON[i]['hash'] = trans[8]
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