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(): 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); 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();