491 lines
14 KiB
Python
Executable File
491 lines
14 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import sys
|
|
import os
|
|
import string
|
|
import random
|
|
from dotenv import load_dotenv
|
|
from duniterpy.key import SigningKey
|
|
from pathlib import Path
|
|
from lib.gva import GvaApi
|
|
from lib.cesium import CesiumPlus
|
|
|
|
__version__ = "0.1.1"
|
|
|
|
MY_PATH = Path(__file__).resolve().parent
|
|
|
|
# Set file paths
|
|
dotenv_file = MY_PATH / ".env"
|
|
dotenv_template = MY_PATH / ".env.template"
|
|
|
|
# Check and create dotenv file
|
|
if not dotenv_file.is_file():
|
|
dotenv_file.write_text(dotenv_template.read_text())
|
|
|
|
# Load environment variables
|
|
load_dotenv(dotenv_file)
|
|
|
|
# Set global values (default parameters) regarding environment variables
|
|
node = os.getenv("DUNITER") + "/gva" or "https://g1v1.p2p.legal/gva"
|
|
pod = os.getenv("ESNODE") or "https://g1.data.e-is.pro"
|
|
destPubkey = False
|
|
|
|
# define parser
|
|
parser = argparse.ArgumentParser(
|
|
description="CLI Client for Cesium+ and Ḡchange",
|
|
epilog="current node: '" + node + "', current pod: '" + pod + "'.",
|
|
)
|
|
|
|
# load global arguments
|
|
parser.add_argument(
|
|
"-v",
|
|
"--version",
|
|
action="store_true",
|
|
help="Display the current program version",
|
|
)
|
|
parser.add_argument("-k", "--key", help="Path to the keyfile (PubSec)")
|
|
parser.add_argument(
|
|
"-n", "--node", help="Address of the Cesium+, Gchange, or Duniter node to use"
|
|
)
|
|
|
|
# Define commands with arguments
|
|
commands = {
|
|
"read": {
|
|
"help": "Read messages",
|
|
"arguments": {
|
|
("n", "number"): {
|
|
"type": int,
|
|
"default": 3,
|
|
"help": "Display the last NUMBER messages",
|
|
},
|
|
("j", "json"): {"action": "store_true", "help": "Output in JSON format"},
|
|
("o", "outbox"): {"action": "store_true", "help": "Read sent messages"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"send": {
|
|
"help": "Send a message",
|
|
"arguments": {
|
|
("d", "destinataire"): {
|
|
"required": True,
|
|
"help": "Recipient of the message",
|
|
},
|
|
("t", "titre"): {"help": "Title of the message to send"},
|
|
("m", "message"): {"help": "Message to send"},
|
|
("f", "fichier"): {"help": "Send the message from the 'FILE'"},
|
|
("o", "outbox"): {
|
|
"action": "store_true",
|
|
"help": "Send the message to the outbox",
|
|
},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"delete": {
|
|
"help": "Delete a message",
|
|
"arguments": {
|
|
("i", "id"): {
|
|
"action": "append",
|
|
"nargs": "+",
|
|
"required": True,
|
|
"help": "ID(s) of the message(s) to delete",
|
|
},
|
|
("o", "outbox"): {
|
|
"action": "store_true",
|
|
"help": "Delete a sent message",
|
|
},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"get": {
|
|
"help": "View a Cesium+ profile",
|
|
"arguments": {
|
|
("p", "profile"): {"help": "Profile name"},
|
|
("a", "avatar"): {
|
|
"action": "store_true",
|
|
"help": "Also retrieve the avatar in raw base64 format",
|
|
},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"page": {
|
|
"help": "View a Cesium+ page",
|
|
"arguments": {
|
|
("p", "page"): {"help": "Page name"},
|
|
("a", "avatar"): {
|
|
"action": "store_true",
|
|
"help": "Also retrieve the page's avatar in raw base64 format",
|
|
},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"set": {
|
|
"help": "Configure your Cesium+ profile",
|
|
"arguments": {
|
|
("n", "name"): {"help": "Profile name"},
|
|
("d", "description"): {"help": "Profile description"},
|
|
("v", "ville"): {"help": "Profile city"},
|
|
("a", "adresse"): {"help": "Profile address"},
|
|
("pos", "position"): {
|
|
"nargs": 2,
|
|
"help": "Geographical coordinates (lat + lon)",
|
|
},
|
|
("s", "site"): {"help": "Profile website"},
|
|
("A", "avatar"): {"help": "Path to profile avatar in PNG"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"erase": {
|
|
"help": "Erase your Cesium+ profile",
|
|
"arguments": {},
|
|
"type": "cesium",
|
|
},
|
|
"stars": {
|
|
"help": "View a profile's stars / Rate a profile (option -s RATING)",
|
|
"arguments": {
|
|
("p", "profile"): {"help": "Target profile"},
|
|
("n", "number"): {"type": int, "help": "Number of stars"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"unstars": {
|
|
"help": "Remove a star",
|
|
"arguments": {
|
|
("p", "profile"): {"help": "Profile to unstar"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"getoffer": {
|
|
"help": "Get information about a Ḡchange listing",
|
|
"arguments": {
|
|
("i", "id"): {"help": "Target listing to retrieve"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"setoffer": {
|
|
"help": "Create a Ḡchange listing",
|
|
"arguments": {
|
|
("t", "title"): {"help": "Title of the listing to create"},
|
|
("d", "description"): {"help": "Description of the listing to create"},
|
|
("c", "category"): {"help": "Category of the listing to create"},
|
|
("l", "location"): {
|
|
"nargs": 2,
|
|
"help": "Location of the listing to create (lat + lon)",
|
|
},
|
|
("p", "picture"): {"help": "Image of the listing to create"},
|
|
("ci", "city"): {"help": "City of the listing to create"},
|
|
("pr", "price"): {"help": "Price of the listing to create"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"deleteoffer": {
|
|
"help": "Delete a Ḡchange listing",
|
|
"arguments": {
|
|
("i", "id"): {"help": "Target listing to delete"},
|
|
},
|
|
"type": "cesium",
|
|
},
|
|
"geolocProfiles": {
|
|
"help": "Get JSON of all geolocated accounts",
|
|
"arguments": {},
|
|
"type": "cesium",
|
|
},
|
|
"pay": {
|
|
"help": "Pay in Ḡ1",
|
|
"arguments": {
|
|
("p", "pubkey"): {"help": "Payment recipient"},
|
|
("a", "amount"): {"type": float, "help": "Transaction amount"},
|
|
("c", "comment"): {
|
|
"default": "",
|
|
"help": "Transaction comment",
|
|
"nargs": "*",
|
|
},
|
|
("m", "mempool"): {
|
|
"action": "store_true",
|
|
"help": "Use mempool sources",
|
|
},
|
|
("v", "verbose"): {
|
|
"action": "store_true",
|
|
"help": "Display the JSON result of the transaction",
|
|
},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
"history": {
|
|
"help": "View Ḡ1 account transaction history",
|
|
"arguments": {
|
|
("p", "pubkey"): {"help": "Public key of the target account"},
|
|
("n", "number"): {
|
|
"type": int,
|
|
"default": 10,
|
|
"help": "Display the last NUMBER transactions",
|
|
},
|
|
("j", "json"): {
|
|
"action": "store_true",
|
|
"help": "Display the result in JSON format",
|
|
},
|
|
("nocolors"): {
|
|
"action": "store_true",
|
|
"help": "Display the result in black and white",
|
|
},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
"balance": {
|
|
"help": "View Ḡ1 account balance",
|
|
"arguments": {
|
|
("p", "pubkey"): {"help": "Public key of the target account"},
|
|
("m", "mempool"): {
|
|
"action": "store_true",
|
|
"help": "Use mempool sources",
|
|
},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
"id": {
|
|
"help": "View public key/username identity",
|
|
"arguments": {
|
|
("p", "pubkey"): {"help": "Public key of the target account"},
|
|
("u", "username"): {"help": "Username of the target account"},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
"idBalance": {
|
|
"help": "View public key/username identity and balance",
|
|
"arguments": {
|
|
("p", "pubkey"): {"help": "Public key of the target account"},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
"currentUd": {
|
|
"help": "Display the current Universal Dividend amount",
|
|
"arguments": {
|
|
("p", "pubkey"): {"help": "Public key of the target account"},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
"listWallets": {
|
|
"help": "List all G1 wallets",
|
|
"arguments": {
|
|
("m", "mbr"): {
|
|
"action": "store_true",
|
|
"help": "Display raw list of member pubkeys",
|
|
},
|
|
("nm", "non_mbr"): {
|
|
"action": "store_true",
|
|
"help": "Display raw list of nonmember identity pubkeys",
|
|
},
|
|
("l", "larf"): {
|
|
"action": "store_true",
|
|
"help": "Display raw list of nonmember pubkeys",
|
|
},
|
|
("b", "brut"): {
|
|
"action": "store_true",
|
|
"help": "Display raw list of all pubkeys",
|
|
},
|
|
},
|
|
"type": "gva",
|
|
},
|
|
}
|
|
|
|
# Process commands and arguments
|
|
subparsers = parser.add_subparsers(title="jaklis Commands", dest="cmd")
|
|
for cmd, cmd_info in commands.items():
|
|
cmd_parser = subparsers.add_parser(cmd, help=cmd_info["help"])
|
|
for args, kwargs in cmd_info["arguments"].items():
|
|
if isinstance(args, str):
|
|
cmd_parser.add_argument("--" + args, **kwargs)
|
|
else:
|
|
short_arg, long_arg = args
|
|
cmd_parser.add_argument("-" + short_arg, "--" + long_arg, **kwargs)
|
|
|
|
args = parser.parse_args()
|
|
args_dict = vars(args)
|
|
cmd = args.cmd
|
|
if args.version:
|
|
print(__version__)
|
|
sys.exit(0)
|
|
|
|
if not cmd:
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
|
|
def createTmpDunikey():
|
|
# Generate a pseudo-random nonce
|
|
nonce = "".join(
|
|
random.choice(string.ascii_letters + string.digits) for _ in range(32)
|
|
)
|
|
keyPath = "/tmp/secret.dunikey-" + nonce
|
|
|
|
# Create a dummy key (replace with actual key creation logic)
|
|
key = SigningKey.from_credentials(
|
|
"sgse547yhd54xv6541srdh", "sfdgwdrhpkxdawsbszqpof1sdg65xc", None
|
|
)
|
|
key.save_pubsec_file(keyPath)
|
|
|
|
return keyPath
|
|
|
|
|
|
def get_arg_value(args, arg):
|
|
try:
|
|
return getattr(args, arg)
|
|
except AttributeError:
|
|
return False
|
|
|
|
|
|
def get_dunikey(args):
|
|
if args.key:
|
|
return args.key
|
|
dunikey = os.getenv("DUNIKEY")
|
|
if not dunikey:
|
|
keyPath = createTmpDunikey()
|
|
dunikey = keyPath
|
|
if not os.path.isfile(dunikey):
|
|
HOME = os.getenv("HOME")
|
|
dunikey = HOME + dunikey
|
|
if not os.path.isfile(dunikey):
|
|
sys.stderr.write("The keyfile {0} is not found.\n".format(dunikey))
|
|
sys.exit(1)
|
|
return dunikey
|
|
|
|
|
|
pubkey = get_arg_value(args, "pubkey")
|
|
profile = get_arg_value(args, "profile")
|
|
|
|
noNeedDunikey = cmd in (
|
|
"history",
|
|
"balance",
|
|
"page",
|
|
"id",
|
|
"idBalance",
|
|
"listWallets",
|
|
"geolocProfiles",
|
|
) and (pubkey or profile)
|
|
|
|
if noNeedDunikey:
|
|
dunikey = pubkey if pubkey else profile
|
|
else:
|
|
dunikey = get_dunikey(args)
|
|
|
|
keyPath = False if dunikey else createTmpDunikey()
|
|
|
|
|
|
def handle_cesium_commands(args, cmd, cesium):
|
|
# Get args of the command
|
|
cmd_args = (
|
|
list(zip(*list(commands[cmd]["arguments"].keys())))[1]
|
|
if len(commands[cmd]["arguments"].keys()) > 0
|
|
else []
|
|
)
|
|
cmd_args_dict = {arg: args_dict[arg] for arg in cmd_args if arg in args_dict}
|
|
cmd_args_values = list(cmd_args_dict.values())
|
|
|
|
# Messaging
|
|
if cmd == "read":
|
|
cesium.read(*cmd_args_values)
|
|
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("Enter the message title: ")
|
|
msg = input("Enter the message content: ")
|
|
|
|
cesium.send(titre, msg, args.destinataire, args.outbox)
|
|
|
|
elif cmd == "delete":
|
|
cesium.delete(args.id[0], args.outbox)
|
|
|
|
# Profiles
|
|
elif cmd == "set":
|
|
cesium.set(**cmd_args_dict)
|
|
elif cmd == "get":
|
|
cesium.get(**cmd_args_dict)
|
|
elif cmd == "page":
|
|
cesium.getPage(**cmd_args_dict)
|
|
elif cmd == "erase":
|
|
cesium.erase()
|
|
elif cmd == "geolocProfiles":
|
|
cesium.geolocProfiles(node)
|
|
|
|
# Stars
|
|
elif cmd == "stars":
|
|
if args.number or args.number == 0:
|
|
cesium.like(args.number, args.profile)
|
|
else:
|
|
cesium.readLikes(args.profile)
|
|
elif cmd == "unstars":
|
|
cesium.unLike(args.profile)
|
|
|
|
# Offers
|
|
elif cmd == "getoffer":
|
|
cesium.getOffer(args.id)
|
|
elif cmd == "setoffer":
|
|
cesium.setOffer(**cmd_args_dict)
|
|
elif cmd == "deleteoffer":
|
|
cesium.deleteOffer(**cmd_args_dict)
|
|
else:
|
|
raise ValueError(f"Unknown command: {cmd}")
|
|
|
|
|
|
def handle_gva_commands(args, cmd, gva):
|
|
# Get args of the command
|
|
cmd_args = (
|
|
list(zip(*list(commands[cmd]["arguments"].keys())))[1]
|
|
if len(commands[cmd]["arguments"].keys()) > 0
|
|
else []
|
|
)
|
|
cmd_args_dict = {arg: args_dict[arg] for arg in cmd_args if arg in args_dict}
|
|
# cmd_args_values = list(cmd_args_dict.values())
|
|
|
|
if cmd == "pay":
|
|
gva.pay(args.amount, args.comment, args.mempool, args.verbose)
|
|
elif cmd == "history":
|
|
gva.history(args.json, args.nocolors, args.number)
|
|
elif cmd == "balance":
|
|
gva.balance(args.mempool)
|
|
elif cmd == "id":
|
|
gva.id(**cmd_args_dict)
|
|
elif cmd == "idBalance":
|
|
gva.idBalance(**cmd_args_dict)
|
|
elif cmd == "currentUd":
|
|
gva.currentUd()
|
|
elif cmd == "listWallets":
|
|
gva.listWallets(args.brut, args.mbr, args.non_mbr, args.larf)
|
|
else:
|
|
raise ValueError(f"Unknown command: {cmd}")
|
|
|
|
|
|
# Construct the CesiumPlus object
|
|
if commands[cmd]["type"] == "cesium":
|
|
if args.node:
|
|
pod = args.node
|
|
|
|
cesium = CesiumPlus(dunikey, pod, noNeedDunikey)
|
|
handle_cesium_commands(args, cmd, cesium)
|
|
|
|
# Construct the GvaApi object
|
|
elif commands[cmd]["type"] == "gva":
|
|
if args.node:
|
|
node = args.node
|
|
|
|
if hasattr(args, "pubkey"):
|
|
destPubkey = args.pubkey
|
|
|
|
gva = GvaApi(dunikey, node, destPubkey, noNeedDunikey)
|
|
handle_gva_commands(args, cmd, gva)
|
|
else:
|
|
raise ValueError(f"Unknown command: {cmd}")
|
|
|
|
if keyPath:
|
|
os.remove(keyPath)
|