Closes WikiDeck/wikideck#1 #2
237
cli.py
Normal file
237
cli.py
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from typing import Optional
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "wikideck")));
|
||||||
|
|
||||||
|
from Mine.Block import Block
|
||||||
|
from Mine.Transaction import Transaction
|
||||||
|
|
||||||
|
WIKIDECK_URL = os.getenv('WIKIDECK_URL', 'http://localhost:8080/');
|
||||||
|
|
||||||
|
|
||||||
|
rsa_private_key: Optional[rsa.RSAPrivateKey] = None;
|
||||||
|
msg_array: list[str] = [""];
|
||||||
|
private_key_file: str = "private_key.pem";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
if (msg_array[0]):
|
||||||
|
print(f"Last Action: {msg_array[0]}");
|
||||||
|
|
||||||
|
print("\n--- Wikideck CLI ---");
|
||||||
|
print("1. Create RSA Keypair");
|
||||||
|
print("2. Save RSA Keypair");
|
||||||
|
print("3. Load RSA Keypair");
|
||||||
|
print("4. Mine a Block");
|
||||||
|
print("5. Generate a Transaction");
|
||||||
|
print("6. View Deck");
|
||||||
|
print("0. Exit");
|
||||||
|
print("\n-------------------");
|
||||||
|
|
||||||
|
try:
|
||||||
|
choice: int = int(input("Select an option: ").strip());
|
||||||
|
except ValueError:
|
||||||
|
msg_array[0] = "Invalid Input. Please Choose a Number Between 0-6.";
|
||||||
|
clear_console();
|
||||||
|
continue;
|
||||||
|
|
||||||
|
|
||||||
|
if choice == 0:
|
||||||
|
|||||||
|
break;
|
||||||
|
elif choice == 1:
|
||||||
|
opt_generate_private_key(65537, 2048);
|
||||||
|
elif choice == 2:
|
||||||
|
opt_save_private_key();
|
||||||
|
elif choice == 3:
|
||||||
|
opt_load_private_key();
|
||||||
|
elif choice == 4:
|
||||||
|
opt_mine_block();
|
||||||
|
elif choice == 5:
|
||||||
|
opt_generate_transaction();
|
||||||
|
elif choice == 6:
|
||||||
|
opt_view_deck();
|
||||||
|
else:
|
||||||
|
msg_array[0] = "Invalid Input. Please Choose a Number Between 0-6.";
|
||||||
|
|
||||||
|
clear_console();
|
||||||
|
|
||||||
|
def opt_generate_private_key(exponent, size):
|
||||||
|
global rsa_private_key;
|
||||||
|
rsa_private_key = rsa.generate_private_key(
|
||||||
|
public_exponent = exponent,
|
||||||
|
key_size = size,
|
||||||
|
)
|
||||||
|
msg_array[0] = "RSA Keypair Generated";
|
||||||
|
|
||||||
|
def opt_save_private_key():
|
||||||
eric
commented
It would be useful to save both the public and private key to a file for easy access. It would be useful to save both the public and private key to a file for easy access.
|
|||||||
|
if rsa_private_key is None:
|
||||||
|
msg_array[0] = "No RSA Keypair to Save. Please Generate one first.";
|
||||||
|
return None;
|
||||||
|
else:
|
||||||
|
with open(private_key_file, "wb") as file:
|
||||||
|
pem = rsa_private_key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.NoEncryption()
|
||||||
|
)
|
||||||
|
file.write(pem);
|
||||||
|
msg_array[0] = f"RSA Private Key Saved to {private_key_file}";
|
||||||
|
|
||||||
|
def opt_load_private_key():
|
||||||
|
global rsa_private_key;
|
||||||
|
if not os.path.exists(private_key_file):
|
||||||
|
msg_array[0] = f"No saved key found ({private_key_file} missing)";
|
||||||
|
return None;
|
||||||
|
|
||||||
|
with open(private_key_file, "rb") as file:
|
||||||
|
rsa_private_key = serialization.load_pem_private_key(
|
||||||
|
file.read(),
|
||||||
|
password=None,
|
||||||
|
)
|
||||||
|
msg_array[0] = f"RSA Private Key Loaded From {private_key_file}";
|
||||||
|
|
||||||
|
def opt_mine_block():
|
||||||
|
|
||||||
|
global rsa_private_key;
|
||||||
|
|
||||||
|
|
||||||
|
if rsa_private_key is None:
|
||||||
|
msg_array[0] = "You must load or generate an RSA keypair first.";
|
||||||
|
return;
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/blocks");
|
||||||
|
if response.status_code != 200:
|
||||||
|
msg_array[0] = f"Failed to fetch block to mine: {response.status_code}";
|
||||||
|
return;
|
||||||
|
|
||||||
|
block_to_mine = Block(data = response.json());
|
||||||
|
block_to_mine.mine(rsa_private_key);
|
||||||
|
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/blocks", json=block_to_mine.as_dict());
|
||||||
|
if response.status_code == 200:
|
||||||
|
msg_array[0] = "Block successfully mined and submitted";
|
||||||
|
else:
|
||||||
|
msg_array[0] = f"Failed to submit mined block: {response.status_code}";
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
msg_array[0] = f"Error during mining: {str(e)}";
|
||||||
|
|
||||||
|
def opt_generate_transaction():
|
||||||
|
global rsa_private_key;
|
||||||
|
|
||||||
|
if rsa_private_key is None:
|
||||||
|
msg_array[0] = "You must load or generate an RSA keypair first.";
|
||||||
|
return;
|
||||||
|
|
||||||
|
print("\nPaste the recipient's RSA Public Key (PEM format). End with an Empty Line (Press enter twice): ");
|
||||||
|
lines = [];
|
||||||
|
|
||||||
|
while True:
|
||||||
|
line = input();
|
||||||
|
if not line.strip():
|
||||||
|
break;
|
||||||
|
lines.append(line);
|
||||||
|
|
||||||
|
receiver_pem = "\n".join(lines);
|
||||||
|
|
||||||
|
try:
|
||||||
|
receiver_key = serialization.load_pem_public_key(receiver_pem.encode("utf-8"));
|
||||||
|
except Exception:
|
||||||
|
msg_array[0] = "Invalid public key format";
|
||||||
|
return;
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
cards = fetch_cards_json(rsa_private_key);
|
||||||
|
|
||||||
|
if not cards:
|
||||||
|
msg_array[0] = "You have no cards to send";
|
||||||
|
return;
|
||||||
|
|
||||||
|
print("\nYour cards:");
|
||||||
|
|
||||||
|
for i, card in enumerate(cards):
|
||||||
|
print(f"{i+1}. Card ID: {card['cardId']}, Page ID: {card['pageId']}");
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
index: int = int(input("Select a card number to send: ").strip()) - 1;
|
||||||
|
|
||||||
|
if not (0 <= index < len(cards)):
|
||||||
|
raise ValueError;
|
||||||
|
except ValueError:
|
||||||
|
msg_array[0] = "Invalid card selection";
|
||||||
|
return;
|
||||||
|
|
||||||
|
selected_card = cards[index];
|
||||||
|
|
||||||
|
tx = Transaction(
|
||||||
|
cardId = selected_card['cardId'],
|
||||||
|
sender = rsa_private_key.public_key(),
|
||||||
|
receiver=receiver_key,
|
||||||
|
authorPrivateKey=rsa_private_key
|
||||||
|
);
|
||||||
|
|
||||||
|
response = requests.post(f"{WIKIDECK_URL}/transactions", json=tx.as_dict());
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
msg_array[0] = f"Transaction failed: {response.status_code}";
|
||||||
|
else:
|
||||||
|
msg_array[0] = "Transaction submitted successfully";
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
msg_array[0] = f"Error creating transaction: {str(e)}";
|
||||||
|
|
||||||
|
def opt_view_deck():
|
||||||
|
|
||||||
|
global rsa_private_key;
|
||||||
|
if rsa_private_key is None:
|
||||||
|
msg_array[0] = "You must load or generate an RSA keypair first.";
|
||||||
|
return;
|
||||||
|
|
||||||
|
try:
|
||||||
|
cards = fetch_cards_json(rsa);
|
||||||
eric
commented
As you already pointed out to me, this should be As you already pointed out to me, this should be `rsa_private_key`.
|
|||||||
|
|
||||||
|
|
||||||
|
if len(cards) <= 0:
|
||||||
|
msg_array[0] = "You have no cards to view";
|
||||||
|
return;
|
||||||
|
|
||||||
|
for i, card in enumerate(cards):
|
||||||
|
print(f"{i+1}. Card ID: {card['cardId']}, Page ID: {card['pageId']}");
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
msg_array[0] = f"Error fetching cards: {str(e)}";
|
||||||
|
|
||||||
|
def fetch_cards_json(rsa):
|
||||||
|
pem = rsa.public_key().public_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
||||||
|
).decode("utf-8");
|
||||||
|
|
||||||
|
response = requests.get(f"{WIKIDECK_URL}/cards", params={"publicKey": pem});
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
msg_array[0] = f"Failed to fetch cards: {response.status_code}";
|
||||||
|
return;
|
||||||
|
|
||||||
|
cards = response.json();
|
||||||
|
return cards;
|
||||||
|
|
||||||
|
|
||||||
|
def clear_console():
|
||||||
|
os.system('cls' if os.name == 'nt' else 'clear');
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main();
|
Loading…
Reference in New Issue
Block a user
In a future iteration, we might want to change this to a CLI interface instead of an interactive one.