import asyncio import json import os import signal import subprocess import sys import time from subprocess import run from sys import platform from threading import Thread from venv import create from nostr_sdk import Keys, Kind, LogLevel from nostr_dvm.dvm import DVM from nostr_dvm.utils.admin_utils import AdminConfig from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config from nostr_dvm.utils.nip88_utils import NIP88Config from nostr_dvm.utils.nip89_utils import NIP89Config, delete_nip_89 from nostr_dvm.utils.output_utils import post_process_result class DVMTaskInterface: NAME: str KIND: Kind TASK: str = "" FIX_COST: float = 0 PER_UNIT_COST: float = 0 PRIVATE_KEY: str PUBLIC_KEY: str DVM = DVM SUPPORTS_ENCRYPTION = True # DVMs build with this framework support encryption, but others might not. ACCEPTS_CASHU = False # Cashu requires manual activation dvm_config: DVMConfig admin_config: AdminConfig dependencies = [] def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None, admin_config: AdminConfig = None, options=None, task=None): self.stop_thread = False self.nostr_dvm_thread = None if options is None: self.options = {} else: self.options = options self.init(name, dvm_config, admin_config, nip88config, nip89config, task) self.install_dependencies(dvm_config) def init(self, name, dvm_config, admin_config=None, nip88config=None, nip89config=None, task=None, options=None): self.NAME = name self.PRIVATE_KEY = dvm_config.PRIVATE_KEY if dvm_config.PUBLIC_KEY == "" or dvm_config.PUBLIC_KEY is None: dvm_config.PUBLIC_KEY = Keys.parse(dvm_config.PRIVATE_KEY).public_key().to_hex() self.PUBLIC_KEY = dvm_config.PUBLIC_KEY if dvm_config.FIX_COST is not None: self.FIX_COST = dvm_config.FIX_COST if dvm_config.PER_UNIT_COST is not None: self.PER_UNIT_COST = dvm_config.PER_UNIT_COST if task is not None: self.TASK = task dvm_config.SUPPORTED_DVMS = [self] dvm_config.DB = "db/" + self.NAME + ".db" if nip89config.KIND is not None: self.KIND = nip89config.KIND dvm_config.NIP89 = self.NIP89_announcement(nip89config) if nip88config is None: dvm_config.NIP88 = None else: dvm_config.NIP88 = nip88config self.dvm_config = dvm_config self.admin_config = admin_config asyncio.run(self.init_dvm(name, dvm_config, nip89config, nip88config, admin_config, options)) async def init_dvm(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None, admin_config: AdminConfig = None, options=None): pass def install_dependencies(self, dvm_config): if dvm_config.SCRIPT != "": if self.dvm_config.USE_OWN_VENV: dir = r'cache/venvs/' + os.path.basename(dvm_config.SCRIPT).split(".py")[0] pip_location = 'bin/pip' if platform == "win32" or platform == "nt": pip_location = dir + '/Scripts/pip' if not os.path.isdir(dir): print("Creating Venv: " + dir) create(dir, with_pip=True, upgrade_deps=False) self.dependencies.append(("nostr-dvm", "nostr-dvm")) for (module, package) in self.dependencies: print("Installing Venv Module: " + module) run([pip_location, "install", "--upgrade", package], cwd=dir) else: for module, package in self.dependencies: if module != "nostr-dvm": try: __import__(module) except ImportError: print("Installing global Module: " + module) subprocess.check_call([sys.executable, "-m", "pip", "install", package]) async def run_dvm(self, dvm_config, admin_config): print("Implement the run dvm function") pass def run(self): self.stop_thread = False try: self.nostr_dvm_thread = Thread(target=self.DVM, args=[self.dvm_config, self.admin_config, lambda: self.stop_thread], daemon=False) self.nostr_dvm_thread.start() except BaseException as e: print("gone") def join(self): self.stop_thread = True self.nostr_dvm_thread.join(1) dvm_shutdown(self.dvm_config) async def schedule(self, dvm_config): """schedule something, e.g. define some time to update or to post, does nothing by default""" pass def NIP89_announcement(self, nip89config: NIP89Config): nip89 = NIP89Config() nip89.NAME = self.NAME nip89.KIND = self.KIND nip89.PK = self.PRIVATE_KEY nip89.DTAG = nip89config.DTAG nip89.CONTENT = nip89config.CONTENT nip89.EXTRA_TAGS = nip89config.EXTRA_TAGS return nip89 async def is_input_supported(self, tags, client=None, dvm_config=None) -> bool: """Check if input is supported for current Task.""" pass async def create_request_from_nostr_event(self, event, client=None, dvm_config=None) -> dict: """Parse input into a request form that will be given to the process method""" pass async def process(self, request_form): "Process the data and return the result" pass async def post_process(self, result, event): """Post-process the data and return the result Use default function, if not overwritten""" return post_process_result(result, event) def set_options(self, request_form): if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print("[" + self.dvm_config.NIP89.NAME + "] " + "Setting options...") opts = [] if request_form.get("options"): opts = json.loads(request_form["options"]) if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print("[" + self.dvm_config.NIP89.NAME + "] " + str(opts)) return dict(opts) @staticmethod def process_args(): import argparse parser = argparse.ArgumentParser() parser.add_argument('--request', dest='request') parser.add_argument('--identifier', dest='identifier') parser.add_argument('--output', dest='output') args = parser.parse_args() return args @staticmethod def write_output(result, output): with open(os.path.abspath(output), 'w', encoding="utf8") as f: f.write(result) # f.close() def process_venv(identifier): args = DVMTaskInterface.process_args() dvm_config = build_default_config(args.identifier) dvm = identifier(name="", dvm_config=dvm_config, nip89config=NIP89Config(), admin_config=None) try: result = asyncio.run(dvm.process(json.loads(args.request))) DVMTaskInterface.write_output(result, args.output) except Exception as e: DVMTaskInterface.write_output("Error: " + str(e), args.output) def dvm_shutdown(dvm_config): if dvm_config.DELETE_ANNOUNCEMENT_ON_SHUTDOWN: print(dvm_config.NIP89.NAME) asyncio.run(delete_nip_89(dvm_config, dvm_config.DELETE_ANNOUNCEMENT_ON_SHUTDOWN_POW))