From 13bffaea9643a7ecad55fafc7e6b4b702e6b1dd9 Mon Sep 17 00:00:00 2001 From: Believethehype Date: Mon, 20 Nov 2023 22:09:38 +0100 Subject: [PATCH] rebuild so each dvm runs on its own. --- .env_example | 1 + backends/nova_server.py | 2 +- dvm.py | 823 +++++++++++++++++---------------- interfaces/dvmtaskinterface.py | 12 +- main.py | 62 ++- tasks/imagegenerationsdxl.py | 38 +- tasks/textextractionpdf.py | 22 +- tasks/translation.py | 19 +- utils/admin_utils.py | 14 +- utils/backend_utils.py | 27 +- utils/definitions.py | 5 +- utils/env.py | 1 + utils/zap_utils.py | 3 +- 13 files changed, 547 insertions(+), 482 deletions(-) diff --git a/.env_example b/.env_example index a5718bf..31ca32a 100644 --- a/.env_example +++ b/.env_example @@ -9,6 +9,7 @@ LNBITS_HOST = "https://lnbits.com" TASK_TEXTEXTRACTION_NIP89_DTAG = "asdd" TASK_TRANSLATION_NIP89_DTAG = "abcded" TASK_IMAGEGENERATION_NIP89_DTAG = "fgdfgdf" +TASK_IMAGEGENERATION_NIP89_DTAG2 = "fgdfgdf" #Backend Specific Options for tasks that require them NOVA_SERVER = "127.0.0.1:37318" \ No newline at end of file diff --git a/backends/nova_server.py b/backends/nova_server.py index c631241..46ce83d 100644 --- a/backends/nova_server.py +++ b/backends/nova_server.py @@ -29,7 +29,7 @@ in the module that is calling the server def send_request_to_nova_server(request_form, address): print("Sending job to NOVA-Server") - url = ('http://' + address + '/' + str(request_form["mode"]).lower()) + url = ('http://' + address + '/process') headers = {'Content-type': 'application/x-www-form-urlencoded'} response = requests.post(url, headers=headers, data=request_form) return response.content diff --git a/dvm.py b/dvm.py index 535be35..48d3f17 100644 --- a/dvm.py +++ b/dvm.py @@ -19,438 +19,473 @@ if use_logger: job_list = [] jobs_on_hold_list = [] -dvm_config = DVMConfig() -def DVM(config): - dvm_config = config - keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY) - pk = keys.public_key() +class DVM: + dvm_config: DVMConfig + keys: Keys + client: Client - print(f"Nostr DVM public key: {pk.to_bech32()}, Hex: {pk.to_hex()} ") - print('Supported DVM tasks: ' + ', '.join(p.NAME + ":" + p.TASK for p in dvm_config.SUPPORTED_TASKS)) + def __init__(self, config): + self.dvm_config = config + self.keys = Keys.from_sk_str(config.PRIVATE_KEY) + self.client = Client(self.keys) + pk = self.keys.public_key() - client = Client(keys) - for relay in dvm_config.RELAY_LIST: - client.add_relay(relay) - client.connect() + print("Nostr DVM public key: " + str(pk.to_bech32()) + "Hex: " + str(pk.to_hex()) + " Supported DVM tasks: " + + ', '.join(p.NAME + ":" + p.TASK for p in self.dvm_config.SUPPORTED_TASKS) + "\n") - dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) + for relay in self.dvm_config.RELAY_LIST: + self.client.add_relay(relay) + self.client.connect() - kinds = [EventDefinitions.KIND_NIP90_GENERIC] - for dvm in dvm_config.SUPPORTED_TASKS: - if dvm.KIND not in kinds: - kinds.append(dvm.KIND) - dvm_filter = (Filter().kinds(kinds).since(Timestamp.now())) - client.subscribe([dm_zap_filter, dvm_filter]) + dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) - create_sql_table() - admin_make_database_updates(config=dvm_config, client=client) + kinds = [EventDefinitions.KIND_NIP90_GENERIC] + for dvm in self.dvm_config.SUPPORTED_TASKS: + if dvm.KIND not in kinds: + kinds.append(dvm.KIND) + dvm_filter = (Filter().kinds(kinds).since(Timestamp.now())) + self.client.subscribe([dm_zap_filter, dvm_filter]) - class NotificationHandler(HandleNotification): - def handle(self, relay_url, nostr_event): - if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC: + create_sql_table() + admin_make_database_updates(config=self.dvm_config, client=self.client) + + class NotificationHandler(HandleNotification): + client = self.client + dvm_config = self.dvm_config + keys = self.keys + + def handle(self, relay_url, nostr_event): print(f"[Nostr] Received new NIP90 Job Request from {relay_url}: {nostr_event.as_json()}") - handle_nip90_job_event(nostr_event, dvm_config) - elif nostr_event.kind() == EventDefinitions.KIND_ZAP: - handle_zap(nostr_event, dvm_config) + if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC: + self.handle_nip90_job_event(nostr_event) + elif nostr_event.kind() == EventDefinitions.KIND_ZAP: + self.handle_zap(nostr_event) - def handle_msg(self, relay_url, msg): - return - - def handle_nip90_job_event(event, dvm_config): - user = get_or_add_user(event.pubkey().to_hex()) - task_supported, task, duration = check_task_is_supported(event, client=client, - get_duration=(not user.iswhitelisted), - config=dvm_config) - print(task) - - if user.isblacklisted: - send_job_status_reaction(event, "error", client=client, config=dvm_config) - print("[Nostr] Request by blacklisted user, skipped") - - elif task_supported: - print("Received new Task: " + task) - print(duration) - amount = get_amount_per_task(task, dvm_config, duration) - if amount is None: + def handle_msg(self, relay_url, msg): return - task_is_free = False - for dvm in dvm_config.SUPPORTED_TASKS: - if dvm.TASK == task and dvm.COST == 0: - task_is_free = True + def handle_nip90_job_event(self, nip90_event): + user = get_or_add_user(nip90_event.pubkey().to_hex()) + task_supported, task, duration = check_task_is_supported(nip90_event, client=self.client, + get_duration=(not user.iswhitelisted), + config=self.dvm_config) + print(task) - if user.iswhitelisted or task_is_free: - print("[Nostr] Free or Whitelisted for task " + task + ". Starting processing..") - send_job_status_reaction(event, "processing", True, 0, client=client, config=dvm_config) - do_work(event, is_from_bot=False) - # otherwise send payment request - else: - bid = 0 - for tag in event.tags(): - if tag.as_vec()[0] == 'bid': - bid = int(tag.as_vec()[1]) + if user.isblacklisted: + send_job_status_reaction(nip90_event, "error", client=self.client, config=self.dvm_config) + print("[Nostr] Request by blacklisted user, skipped") - print("[Nostr][Payment required] New Nostr " + task + " Job event: " + event.as_json()) - if bid > 0: - bid_offer = int(bid / 1000) - if bid_offer >= amount: - send_job_status_reaction(event, "payment-required", False, - amount, # bid_offer - client=client, config=dvm_config) + elif task_supported: + print("Received new Task: " + task) + amount = get_amount_per_task(task, self.dvm_config, duration) + if amount is None: + return - else: # If there is no bid, just request server rate from user - print("[Nostr] Requesting payment for Event: " + event.id().to_hex()) - send_job_status_reaction(event, "payment-required", - False, amount, client=client, config=dvm_config) - else: - print("Task not supported on this DVM, skipping..") + task_is_free = False + for dvm in self.dvm_config.SUPPORTED_TASKS: + if dvm.TASK == task and dvm.COST == 0: + task_is_free = True - def handle_zap(event, dvm_config): - zapped_event = None - invoice_amount = 0 - anon = False - sender = event.pubkey() + if user.iswhitelisted or task_is_free: + print("[Nostr] Free or Whitelisted for task " + task + ". Starting processing..") + send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client, + config=self.dvm_config) + do_work(nip90_event, is_from_bot=False) + # otherwise send payment request + else: + bid = 0 + for tag in nip90_event.tags(): + if tag.as_vec()[0] == 'bid': + bid = int(tag.as_vec()[1]) - try: - for tag in event.tags(): - if tag.as_vec()[0] == 'bolt11': - invoice_amount = parse_bolt11_invoice(tag.as_vec()[1]) - elif tag.as_vec()[0] == 'e': - zapped_event = get_event_by_id(tag.as_vec()[1], config=dvm_config) - elif tag.as_vec()[0] == 'description': - zap_request_event = Event.from_json(tag.as_vec()[1]) - sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(), - zap_request_event.content()) - for ztag in zap_request_event.tags(): - if ztag.as_vec()[0] == 'anon': - if len(ztag.as_vec()) > 1: - print("Private Zap received.") - decrypted_content = decrypt_private_zap_message(ztag.as_vec()[1], - keys.secret_key(), - zap_request_event.pubkey()) - decrypted_private_event = Event.from_json(decrypted_content) - if decrypted_private_event.kind() == 9733: - sender = decrypted_private_event.pubkey().to_hex() - message = decrypted_private_event.content() - if message != "": - print("Zap Message: " + message) - else: - anon = True - print("Anonymous Zap received. Unlucky, I don't know from whom, and never will") - user = get_or_add_user(sender) - print(str(user)) + print("[Nostr][Payment required] New Nostr " + task + " Job event: " + nip90_event.as_json()) + if bid > 0: + bid_offer = int(bid / 1000) + if bid_offer >= amount: + send_job_status_reaction(nip90_event, "payment-required", False, + amount, # bid_offer + client=self.client, config=self.dvm_config) - if zapped_event is not None: - if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped - if not dvm_config.IS_BOT: - print("Zap received for NIP90 task: " + str(invoice_amount) + " Sats from " + str( - user.name)) - amount = 0 - job_event = None - for tag in zapped_event.tags(): - if tag.as_vec()[0] == 'amount': - amount = int(float(tag.as_vec()[1]) / 1000) - elif tag.as_vec()[0] == 'e': - job_event = get_event_by_id(tag.as_vec()[1], config=dvm_config) - - task_supported, task, duration = check_task_is_supported(job_event, client=client, - get_duration=False, config=dvm_config) - if job_event is not None and task_supported: - if amount <= invoice_amount: - print("[Nostr] Payment-request fulfilled...") - send_job_status_reaction(job_event, "processing", client=client, - config=dvm_config) - indices = [i for i, x in enumerate(job_list) if - x.event_id == job_event.id().to_hex()] - index = -1 - if len(indices) > 0: - index = indices[0] - if index > -1: - if job_list[index].is_processed: # If payment-required appears a processing - job_list[index].is_paid = True - check_and_return_event(job_list[index].result, str(job_event.as_json()), - dvm_key=dvm_config.PRIVATE_KEY) - elif not (job_list[index]).is_processed: - # If payment-required appears before processing - job_list.pop(index) - print("Starting work...") - do_work(job_event, is_from_bot=False) - else: - print("Job not in List, but starting work...") - do_work(job_event, is_from_bot=False) - - else: - send_job_status_reaction(job_event, "payment-rejected", - False, invoice_amount, client=client, config=dvm_config) - print("[Nostr] Invoice was not paid sufficiently") - - elif zapped_event.kind() in EventDefinitions.ANY_RESULT: - print("Someone zapped the result of an exisiting Task. Nice") - elif not anon and not dvm_config.PASSIVE_MODE: - print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str( - user.name)) - update_user_balance(sender, invoice_amount, config=dvm_config) - - # a regular note - elif not anon and not dvm_config.PASSIVE_MODE: - print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str( - user.name)) - update_user_balance(sender, invoice_amount, config=dvm_config) - - except Exception as e: - print(f"Error during content decryption: {e}") - - def do_work(job_event, is_from_bot=False): - if ((EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= job_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC) - or job_event.kind() == EventDefinitions.KIND_DM): - - task = get_task(job_event, client=client, dvmconfig=dvm_config) - result = "" - for dvm in dvm_config.SUPPORTED_TASKS: - try: - if task == dvm.TASK: - request_form = dvm.create_request_form_from_nostr_event(job_event, client, dvm_config) - result = dvm.process(request_form) - check_and_return_event(result, str(job_event.as_json()), dvm_key=dvm_config.PRIVATE_KEY) - - except Exception as e: - print(e) - respond_to_error(e, job_event.as_json(), is_from_bot, dvm_config.PRIVATE_KEY) - return - - - - def check_event_has_not_unfinished_job_input(nevent, append, client, dvmconfig): - task_supported, task, duration = check_task_is_supported(nevent, client, False, config=dvmconfig) - if not task_supported: - return False - - for tag in nevent.tags(): - if tag.as_vec()[0] == 'i': - if len(tag.as_vec()) < 3: - print("Job Event missing/malformed i tag, skipping..") - return False + else: # If there is no bid, just request server rate from user + print("[Nostr] Requesting payment for Event: " + nip90_event.id().to_hex()) + send_job_status_reaction(nip90_event, "payment-required", + False, amount, client=self.client, config=self.dvm_config) else: - input = tag.as_vec()[1] - input_type = tag.as_vec()[2] - if input_type == "job": - evt = get_referenced_event_by_id(input, EventDefinitions.ANY_RESULT, client, config=dvmconfig) - if evt is None: - if append: - job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs()) - jobs_on_hold_list.append(job) - send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client, - config=dvmconfig) + print("Task not supported on this DVM, skipping..") - return False - else: - return True + def handle_zap(self, event): + zapped_event = None + invoice_amount = 0 + anon = False + sender = event.pubkey() - def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None, content=None, config=None, - key=None): - dvmconfig = config - alt_description = "This is a reaction to a NIP90 DVM AI task. " - task = get_task(original_event, client=client, dvmconfig=dvmconfig) - if status == "processing": - alt_description = "NIP90 DVM AI task " + task + " started processing. " - reaction = alt_description + emoji.emojize(":thumbs_up:") - elif status == "success": - alt_description = "NIP90 DVM AI task " + task + " finished successfully. " - reaction = alt_description + emoji.emojize(":call_me_hand:") - elif status == "chain-scheduled": - alt_description = "NIP90 DVM AI task " + task + " Chain Task scheduled" - reaction = alt_description + emoji.emojize(":thumbs_up:") - elif status == "error": - alt_description = "NIP90 DVM AI task " + task + " had an error. " - if content is None: - reaction = alt_description + emoji.emojize(":thumbs_down:") - else: - reaction = alt_description + emoji.emojize(":thumbs_down:") + content - - elif status == "payment-required": - - alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(amount) + " Sats. " - reaction = alt_description + emoji.emojize(":orange_heart:") - - elif status == "payment-rejected": - alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str(amount) + " Sats. " - reaction = alt_description + emoji.emojize(":thumbs_down:") - elif status == "user-blocked-from-service": - alt_description = "NIP90 DVM AI task " + task + " can't be performed. User has been blocked from Service. " - reaction = alt_description + emoji.emojize(":thumbs_down:") - else: - reaction = emoji.emojize(":thumbs_down:") - - e_tag = Tag.parse(["e", original_event.id().to_hex()]) - p_tag = Tag.parse(["p", original_event.pubkey().to_hex()]) - alt_tag = Tag.parse(["alt", alt_description]) - status_tag = Tag.parse(["status", status]) - tags = [e_tag, p_tag, alt_tag, status_tag] - - if status == "success" or status == "error": # - for x in job_list: - if x.event_id == original_event.id(): - is_paid = x.is_paid - amount = x.amount - break - - bolt11 = "" - payment_hash = "" - expires = original_event.created_at().as_secs() + (60 * 60 * 24) - if status == "payment-required" or (status == "processing" and not is_paid): - if dvmconfig.LNBITS_INVOICE_KEY != "": try: - bolt11, payment_hash = create_bolt11_ln_bits(amount, dvmconfig) + for tag in event.tags(): + if tag.as_vec()[0] == 'bolt11': + invoice_amount = parse_bolt11_invoice(tag.as_vec()[1]) + elif tag.as_vec()[0] == 'e': + zapped_event = get_event_by_id(tag.as_vec()[1], config=self.dvm_config) + elif tag.as_vec()[0] == 'description': + zap_request_event = Event.from_json(tag.as_vec()[1]) + sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(), + zap_request_event.content()) + for ztag in zap_request_event.tags(): + if ztag.as_vec()[0] == 'anon': + if len(ztag.as_vec()) > 1: + print("Private Zap received.") + decrypted_content = decrypt_private_zap_message(ztag.as_vec()[1], + self.keys.secret_key(), + zap_request_event.pubkey()) + decrypted_private_event = Event.from_json(decrypted_content) + if decrypted_private_event.kind() == 9733: + sender = decrypted_private_event.pubkey().to_hex() + message = decrypted_private_event.content() + if message != "": + print("Zap Message: " + message) + else: + anon = True + print("Anonymous Zap received. Unlucky, I don't know from whom, and never will") + user = get_or_add_user(sender) + print(str(user)) + + if zapped_event is not None: + if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped + if not self.dvm_config.IS_BOT: + print("Zap received for NIP90 task: " + str(invoice_amount) + " Sats from " + str( + user.name)) + amount = 0 + job_event = None + for tag in zapped_event.tags(): + if tag.as_vec()[0] == 'amount': + amount = int(float(tag.as_vec()[1]) / 1000) + elif tag.as_vec()[0] == 'e': + job_event = get_event_by_id(tag.as_vec()[1], config=self.dvm_config) + + task_supported, task, duration = check_task_is_supported(job_event, client=self.client, + get_duration=False, + config=self.dvm_config) + if job_event is not None and task_supported: + if amount <= invoice_amount: + print("[Nostr] Payment-request fulfilled...") + send_job_status_reaction(job_event, "processing", client=self.client, + config=self.dvm_config) + indices = [i for i, x in enumerate(job_list) if + x.event_id == job_event.id().to_hex()] + index = -1 + if len(indices) > 0: + index = indices[0] + if index > -1: + if job_list[index].is_processed: # If payment-required appears a processing + job_list[index].is_paid = True + check_and_return_event(job_list[index].result, + str(job_event.as_json()), + dvm_key=self.dvm_config.PRIVATE_KEY) + elif not (job_list[index]).is_processed: + # If payment-required appears before processing + job_list.pop(index) + print("Starting work...") + do_work(job_event, is_from_bot=False) + else: + print("Job not in List, but starting work...") + do_work(job_event, is_from_bot=False) + + else: + send_job_status_reaction(job_event, "payment-rejected", + False, invoice_amount, client=self.client, + config=self.dvm_config) + print("[Nostr] Invoice was not paid sufficiently") + + elif zapped_event.kind() in EventDefinitions.ANY_RESULT: + print("Someone zapped the result of an exisiting Task. Nice") + elif not anon and not self.dvm_config.PASSIVE_MODE: + print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str( + user.name)) + update_user_balance(sender, invoice_amount, config=self.dvm_config) + + # a regular note + elif not anon and not self.dvm_config.PASSIVE_MODE: + print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str( + user.name)) + update_user_balance(sender, invoice_amount, config=self.dvm_config) + except Exception as e: - print(e) + print(f"Error during content decryption: {e}") - if not any(x.event_id == original_event.id().to_hex() for x in job_list): - job_list.append( - JobToWatch(event_id=original_event.id().to_hex(), timestamp=original_event.created_at().as_secs(), - amount=amount, - is_paid=is_paid, - status=status, result="", is_processed=False, bolt11=bolt11, payment_hash=payment_hash, - expires=expires, from_bot=False)) - print(str(job_list)) - if status == "payment-required" or status == "payment-rejected" or (status == "processing" and not is_paid) or ( - status == "success" and not is_paid): - if dvmconfig.LNBITS_INVOICE_KEY != "": - amount_tag = Tag.parse(["amount", str(amount * 1000), bolt11]) + + def check_event_has_not_unfinished_job_input(nevent, append, client, dvmconfig): + task_supported, task, duration = check_task_is_supported(nevent, client, False, config=dvmconfig) + if not task_supported: + return False + + for tag in nevent.tags(): + if tag.as_vec()[0] == 'i': + if len(tag.as_vec()) < 3: + print("Job Event missing/malformed i tag, skipping..") + return False + else: + input = tag.as_vec()[1] + input_type = tag.as_vec()[2] + if input_type == "job": + evt = get_referenced_event_by_id(input, EventDefinitions.ANY_RESULT, client, + config=dvmconfig) + if evt is None: + if append: + job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs()) + jobs_on_hold_list.append(job) + send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client, + config=dvmconfig) + + return False else: - amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats - tags.append(amount_tag) - if key is not None: - keys = Keys.from_sk_str(key) - else: - keys = Keys.from_sk_str(dvmconfig.PRIVATE_KEY) - event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys) - - send_event(event, key=keys) - print( - "[Nostr] Sent Kind " + str(EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json()) - return event.as_json() - - def check_and_return_event(data, original_event_str: str, dvm_key=""): - original_event = Event.from_json(original_event_str) - keys = Keys.from_sk_str(dvm_key) - - for x in job_list: - if x.event_id == original_event.id().to_hex(): - is_paid = x.is_paid - amount = x.amount - x.result = data - x.is_processed = True - if dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: - send_nostr_reply_event(data, original_event_str, key=keys) - send_job_status_reaction(original_event, "success", amount, - config=dvm_config) # or payment-required, or both? - elif not dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: - send_job_status_reaction(original_event, "success", amount, - config=dvm_config) # or payment-required, or both? - - if dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: - job_list.remove(x) - elif not dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: - job_list.remove(x) - send_nostr_reply_event(data, original_event_str, key=keys) - break + return True - try: - post_processed_content = post_process_result(data, original_event) - send_nostr_reply_event(post_processed_content, original_event_str, key=keys) - except Exception as e: - respond_to_error(e, original_event_str, False, dvm_config.PRIVATE_KEY) - - def send_nostr_reply_event(content, original_event_as_str, key=None): - originalevent = Event.from_json(original_event_as_str) - requesttag = Tag.parse(["request", original_event_as_str.replace("\\", "")]) - etag = Tag.parse(["e", originalevent.id().to_hex()]) - ptag = Tag.parse(["p", originalevent.pubkey().to_hex()]) - alttag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str( - originalevent.kind()) + ". The task was: " + originalevent.content()]) - statustag = Tag.parse(["status", "success"]) - replytags = [requesttag, etag, ptag, alttag, statustag] - for tag in originalevent.tags(): - if tag.as_vec()[0] == "i": - icontent = tag.as_vec()[1] - ikind = tag.as_vec()[2] - itag = Tag.parse(["i", icontent, ikind]) - replytags.append(itag) - - if key is None: - key = Keys.from_sk_str(dvm_config.PRIVATE_KEY) - - response_kind = originalevent.kind() + 1000 - event = EventBuilder(response_kind, str(content), replytags).to_event(key) - send_event(event, key=key) - print("[Nostr] " + str(response_kind) + " Job Response event sent: " + event.as_json()) - return event.as_json() - - client.handle_notifications(NotificationHandler()) - - def respond_to_error(content, originaleventstr, is_from_bot=False, dvm_key=None): - print("ERROR") - if dvm_key is None: - keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY) - else: + def check_and_return_event(data, original_event_str: str, dvm_key=""): + original_event = Event.from_json(original_event_str) keys = Keys.from_sk_str(dvm_key) - original_event = Event.from_json(originaleventstr) - sender = "" - task = "" - if not is_from_bot: - send_job_status_reaction(original_event, "error", content=str(content), key=dvm_key) - # TODO Send Zap back - else: - for tag in original_event.tags(): - if tag.as_vec()[0] == "p": - sender = tag.as_vec()[1] - elif tag.as_vec()[0] == "i": - task = tag.as_vec()[1] + for x in job_list: + if x.event_id == original_event.id().to_hex(): + is_paid = x.is_paid + amount = x.amount + x.result = data + x.is_processed = True + if self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: + send_nostr_reply_event(data, original_event_str, key=keys) + send_job_status_reaction(original_event, "success", amount, + config=self.dvm_config) # or payment-required, or both? + elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: + send_job_status_reaction(original_event, "success", amount, + config=self.dvm_config) # or payment-required, or both? - user = get_from_sql_table(sender) - if not user.iswhitelisted: - amount = int(user.balance) + get_amount_per_task(task, dvm_config) - update_sql_table(sender, amount, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16, - user.name, - Timestamp.now().as_secs()) - message = "There was the following error : " + content + ". Credits have been reimbursed" + if self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: + job_list.remove(x) + elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: + job_list.remove(x) + send_nostr_reply_event(data, original_event_str, key=keys) + break + + try: + post_processed_content = post_process_result(data, original_event) + send_nostr_reply_event(post_processed_content, original_event_str, key=keys) + except Exception as e: + respond_to_error(e, original_event_str, False, self.dvm_config.PRIVATE_KEY) + + def send_nostr_reply_event(content, original_event_as_str, key=None): + originalevent = Event.from_json(original_event_as_str) + requesttag = Tag.parse(["request", original_event_as_str.replace("\\", "")]) + etag = Tag.parse(["e", originalevent.id().to_hex()]) + ptag = Tag.parse(["p", originalevent.pubkey().to_hex()]) + alttag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str( + originalevent.kind()) + ". The task was: " + originalevent.content()]) + statustag = Tag.parse(["status", "success"]) + replytags = [requesttag, etag, ptag, alttag, statustag] + for tag in originalevent.tags(): + if tag.as_vec()[0] == "i": + icontent = tag.as_vec()[1] + ikind = tag.as_vec()[2] + itag = Tag.parse(["i", icontent, ikind]) + replytags.append(itag) + + if key is None: + key = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY) + + response_kind = originalevent.kind() + 1000 + event = EventBuilder(response_kind, str(content), replytags).to_event(key) + send_event(event, key=key) + print("[Nostr] " + str(response_kind) + " Job Response event sent: " + event.as_json()) + return event.as_json() + + def respond_to_error(content, originaleventstr, is_from_bot=False, dvm_key=None): + print("ERROR") + if dvm_key is None: + keys = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY) else: - # User didn't pay, so no reimbursement - message = "There was the following error : " + content + keys = Keys.from_sk_str(dvm_key) - evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message, None).to_event(keys) - send_event(evt, key=keys) + original_event = Event.from_json(originaleventstr) + sender = "" + task = "" + if not is_from_bot: + send_job_status_reaction(original_event, "error", content=str(content), key=dvm_key) + # TODO Send Zap back + else: + for tag in original_event.tags(): + if tag.as_vec()[0] == "p": + sender = tag.as_vec()[1] + elif tag.as_vec()[0] == "i": + task = tag.as_vec()[1] - while True: - for job in job_list: - if job.bolt11 != "" and job.payment_hash != "" and not job.is_paid: - if str(check_bolt11_ln_bits_is_paid(job.payment_hash, dvm_config)) == "True": - job.is_paid = True - event = get_event_by_id(job.event_id, config=dvm_config) - if event != None: - send_job_status_reaction(event, "processing", True, 0, client=client, config=dvm_config) - print("do work from joblist") + user = get_from_sql_table(sender) + if not user.iswhitelisted: + amount = int(user.balance) + get_amount_per_task(task, self.dvm_config) + update_sql_table(sender, amount, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16, + user.name, + Timestamp.now().as_secs()) + message = "There was the following error : " + content + ". Credits have been reimbursed" + else: + # User didn't pay, so no reimbursement + message = "There was the following error : " + content - do_work(event, is_from_bot=False) - elif check_bolt11_ln_bits_is_paid(job.payment_hash, dvm_config) is None: # invoice expired - job_list.remove(job) + evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message, + None).to_event(keys) + send_event(evt, key=keys) - if Timestamp.now().as_secs() > job.expires: - job_list.remove(job) + def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None, + content=None, + config=None, + key=None): + dvmconfig = config + alt_description = "This is a reaction to a NIP90 DVM AI task. " + task = get_task(original_event, client=client, dvmconfig=dvmconfig) + if status == "processing": + alt_description = "NIP90 DVM AI task " + task + " started processing. " + reaction = alt_description + emoji.emojize(":thumbs_up:") + elif status == "success": + alt_description = "NIP90 DVM AI task " + task + " finished successfully. " + reaction = alt_description + emoji.emojize(":call_me_hand:") + elif status == "chain-scheduled": + alt_description = "NIP90 DVM AI task " + task + " Chain Task scheduled" + reaction = alt_description + emoji.emojize(":thumbs_up:") + elif status == "error": + alt_description = "NIP90 DVM AI task " + task + " had an error. " + if content is None: + reaction = alt_description + emoji.emojize(":thumbs_down:") + else: + reaction = alt_description + emoji.emojize(":thumbs_down:") + content - for job in jobs_on_hold_list: - if check_event_has_not_unfinished_job_input(job.event, False, client=client, dvmconfig=dvm_config): - handle_nip90_job_event(job.event) - jobs_on_hold_list.remove(job) + elif status == "payment-required": - if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes.. - jobs_on_hold_list.remove(job) + alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str( + amount) + " Sats. " + reaction = alt_description + emoji.emojize(":orange_heart:") - time.sleep(1.0) + elif status == "payment-rejected": + alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str( + amount) + " Sats. " + reaction = alt_description + emoji.emojize(":thumbs_down:") + elif status == "user-blocked-from-service": + alt_description = "NIP90 DVM AI task " + task + " can't be performed. User has been blocked from Service. " + reaction = alt_description + emoji.emojize(":thumbs_down:") + else: + reaction = emoji.emojize(":thumbs_down:") + + e_tag = Tag.parse(["e", original_event.id().to_hex()]) + p_tag = Tag.parse(["p", original_event.pubkey().to_hex()]) + alt_tag = Tag.parse(["alt", alt_description]) + status_tag = Tag.parse(["status", status]) + tags = [e_tag, p_tag, alt_tag, status_tag] + + if status == "success" or status == "error": # + for x in job_list: + if x.event_id == original_event.id(): + is_paid = x.is_paid + amount = x.amount + break + + bolt11 = "" + payment_hash = "" + expires = original_event.created_at().as_secs() + (60 * 60 * 24) + if status == "payment-required" or (status == "processing" and not is_paid): + if dvmconfig.LNBITS_INVOICE_KEY != "": + try: + bolt11, payment_hash = create_bolt11_ln_bits(amount, dvmconfig) + except Exception as e: + print(e) + + if not any(x.event_id == original_event.id().to_hex() for x in job_list): + job_list.append( + JobToWatch(event_id=original_event.id().to_hex(), + timestamp=original_event.created_at().as_secs(), + amount=amount, + is_paid=is_paid, + status=status, result="", is_processed=False, bolt11=bolt11, + payment_hash=payment_hash, + expires=expires, from_bot=False)) + print(str(job_list)) + if status == "payment-required" or status == "payment-rejected" or ( + status == "processing" and not is_paid) or ( + status == "success" and not is_paid): + + if dvmconfig.LNBITS_INVOICE_KEY != "": + amount_tag = Tag.parse(["amount", str(amount * 1000), bolt11]) + else: + amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats + tags.append(amount_tag) + if key is not None: + keys = Keys.from_sk_str(key) + else: + keys = Keys.from_sk_str(dvmconfig.PRIVATE_KEY) + event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys) + + send_event(event, key=keys) + print( + "[Nostr] Sent Kind " + str( + EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json()) + return event.as_json() + def do_work(job_event, is_from_bot=False): + if (( + EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= job_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC) + or job_event.kind() == EventDefinitions.KIND_DM): + + task = get_task(job_event, client=self.client, dvmconfig=self.dvm_config) + result = "" + for dvm in self.dvm_config.SUPPORTED_TASKS: + try: + if task == dvm.TASK: + request_form = dvm.create_request_form_from_nostr_event(job_event, self.client, + self.dvm_config) + result = dvm.process(request_form) + check_and_return_event(result, str(job_event.as_json()), + dvm_key=self.dvm_config.PRIVATE_KEY) + + except Exception as e: + print(e) + respond_to_error(e, job_event.as_json(), is_from_bot, self.dvm_config.PRIVATE_KEY) + return + + self.client.handle_notifications(NotificationHandler()) + while True: + for job in job_list: + if job.bolt11 != "" and job.payment_hash != "" and not job.is_paid: + if str(check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config)) == "True": + job.is_paid = True + event = get_event_by_id(job.event_id, config=self.dvm_config) + if event is not None: + send_job_status_reaction(event, "processing", True, 0, + client=self.client, + config=self.dvm_config) + print("do work from joblist") + + do_work(event, is_from_bot=False) + elif check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config) is None: # invoice expired + try: + job_list.remove(job) + except: + continue + + if Timestamp.now().as_secs() > job.expires: + try: + job_list.remove(job) + except: + continue + + for job in jobs_on_hold_list: + if check_event_has_not_unfinished_job_input(job.event, False, client=self.client, + dvmconfig=self.dvm_config): + # handle_nip90_job_event(event=job.event) + try: + jobs_on_hold_list.remove(job) + except: + continue + + + if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes.. + jobs_on_hold_list.remove(job) + + time.sleep(1.0) diff --git a/interfaces/dvmtaskinterface.py b/interfaces/dvmtaskinterface.py index d08c79e..e48df54 100644 --- a/interfaces/dvmtaskinterface.py +++ b/interfaces/dvmtaskinterface.py @@ -1,11 +1,19 @@ +from utils.nip89_utils import NIP89Announcement + + class DVMTaskInterface: KIND: int TASK: str COST: int + PK: str def NIP89_announcement(self, d_tag, content): - """Define the NIP89 Announcement""" - pass + nip89 = NIP89Announcement() + nip89.kind = self.KIND + nip89.pk = self.PK + nip89.dtag = d_tag + nip89.content = content + return nip89 def is_input_supported(self, input_type, input_content) -> bool: """Check if input is supported for current Task.""" diff --git a/main.py b/main.py index afcab5e..d110401 100644 --- a/main.py +++ b/main.py @@ -8,19 +8,22 @@ from tasks.imagegenerationsdxl import ImageGenerationSDXL from tasks.textextractionpdf import TextExtractionPDF from tasks.translation import Translation + def run_nostr_dvm_with_local_config(): from dvm import DVM, DVMConfig - dvm_config = DVMConfig() - dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY) - # Spawn the DVMs - # Add NIP89 events for each DVM (set rebroad_cast = True for the next start in admin_utils) + # Add NIP89 events for each DVM (set rebroadcast = True for the next start in admin_utils) # Add the dtag here or in your .env file, so you can update your dvm later and change the content as needed. # Get a dtag and the content at vendata.io # Spawn DVM1 Kind 5000 Text Ectractor from PDFs - pdfextactor = TextExtractionPDF("PDF Extractor", os.getenv(env.NOSTR_PRIVATE_KEY)) + dvm_config = DVMConfig() + dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY) + dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY) + dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST) + + pdfextactor = TextExtractionPDF("PDF Extractor", dvm_config) d_tag = os.getenv(env.TASK_TEXTEXTRACTION_NIP89_DTAG) content = "{\"name\":\"" + pdfextactor.NAME + ("\",\"image\":\"https://image.nostr.build" "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669" @@ -29,7 +32,13 @@ def run_nostr_dvm_with_local_config(): dvm_config.NIP89s.append(pdfextactor.NIP89_announcement(d_tag, content)) # Spawn DVM2 Kind 5002 Text Translation - translator = Translation("Translator", os.getenv(env.NOSTR_PRIVATE_KEY)) + dvm_config = DVMConfig() + dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY) + dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY) + dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST) + + translator = Translation("Translator", dvm_config) + d_tag = os.getenv(env.TASK_TRANSLATION_NIP89_DTAG) content = "{\"name\":\"" + translator.NAME + ("\",\"image\":\"https://image.nostr.build" "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669" @@ -48,31 +57,36 @@ def run_nostr_dvm_with_local_config(): "\"sm\",\"sn\",\"so\",\"sq\",\"sr\",\"st\",\"su\",\"sv\",\"sw\"," "\"ta\",\"te\",\"tg\",\"th\",\"tl\",\"tr\",\"ug\",\"uk\",\"ur\"," "\"uz\",\"vi\",\"xh\",\"yi\",\"yo\",\"zh\",\"zu\"]}}}") + dvm_config.NIP89s.append(translator.NIP89_announcement(d_tag, content)) # Spawn DVM3 Kind 5100 Image Generation This one uses a specific backend called nova-server. If you want to use # it see the instructions in backends/nova_server - artist = ImageGenerationSDXL("Unstable Diffusion", os.getenv(env.NOSTR_PRIVATE_KEY)) - d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG) - content = "{\"name\":\"" + artist.NAME + ("\",\"image\":\"https://image.nostr.build" - "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669.jpg" - "\",\"about\":\"I draw images based on a prompt with Stable Diffusion " - "XL 1.0.\",\"nip90Params\":{}}") - dvm_config.NIP89s.append(artist.NIP89_announcement(d_tag, content)) - - - - # Add the DVMS you want to use to the config - dvm_config.SUPPORTED_TASKS = [pdfextactor, translator, artist] - - # SET Lnbits Invoice Key and Server if DVM should provide invoices directly, else make sure you have a lnaddress - # on the profile + dvm_config = DVMConfig() + dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY) dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY) dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST) - # Start the Server - nostr_dvm_thread = Thread(target=DVM, args=[dvm_config]) - nostr_dvm_thread.start() + unstableartist = ImageGenerationSDXL("Unstable Diffusion", dvm_config, "unstable") + d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG) + content = "{\"name\":\"" + unstableartist.NAME + ("\",\"image\":\"https://image.nostr.build" + "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669.jpg" + "\",\"about\":\"I draw images based on a prompt with a Model called unstable diffusion.\",\"nip90Params\":{}}") + dvm_config.NIP89s.append(unstableartist.NIP89_announcement(d_tag, content)) + + dvm_config = DVMConfig() + dvm_config.PRIVATE_KEY = "73b262d31edc6ea1316dffcc7daa772651d661e6475761b7b78291482c1bf5cb" + dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY) + dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST) + + # Spawn another Instance of text-to-image but use a different model and lora this time. + + sketcher = ImageGenerationSDXL("Sketcher", dvm_config, "mohawk", "timburton") + d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG2) + content = "{\"name\":\"" + sketcher.NAME + ("\",\"image\":\"https://image.nostr.build" + "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669.jpg" + "\",\"about\":\"I draw images based on a prompt in kind of Tim Burton style\",\"nip90Params\":{}}") + dvm_config.NIP89s.append(sketcher.NIP89_announcement(d_tag, content)) if __name__ == '__main__': diff --git a/tasks/imagegenerationsdxl.py b/tasks/imagegenerationsdxl.py index 58ecb40..0db36f9 100644 --- a/tasks/imagegenerationsdxl.py +++ b/tasks/imagegenerationsdxl.py @@ -1,6 +1,9 @@ import os from multiprocessing.pool import ThreadPool +from threading import Thread + from backends.nova_server import check_nova_server_status, send_request_to_nova_server +from dvm import DVM from interfaces.dvmtaskinterface import DVMTaskInterface from utils.definitions import EventDefinitions from utils.nip89_utils import NIP89Announcement @@ -15,21 +18,23 @@ Outputs: An url to an Image class ImageGenerationSDXL(DVMTaskInterface): + NAME: str KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE TASK: str = "text-to-image" - COST: int = 0 + COST: int = 50 + PK: str - def __init__(self, name, pk): + def __init__(self, name, dvm_config, default_model=None, default_lora=None): self.NAME = name - self.PK = pk + dvm_config.SUPPORTED_TASKS = [self] + self.PK = dvm_config.PRIVATE_KEY + self.default_model = default_model + self.default_lora = default_lora + + dvm = DVM + nostr_dvm_thread = Thread(target=dvm, args=[dvm_config]) + nostr_dvm_thread.start() - def NIP89_announcement(self, d_tag, content): - nip89 = NIP89Announcement() - nip89.kind = self.KIND - nip89.pk = self.PK - nip89.dtag = d_tag - nip89.content = content - return nip89 def is_input_supported(self, input_type, input_content): if input_type != "text": @@ -38,19 +43,24 @@ class ImageGenerationSDXL(DVMTaskInterface): def create_request_form_from_nostr_event(self, event, client=None, dvm_config=None): request_form = {"jobID": event.id().to_hex() + "_"+ self.NAME.replace(" ", "")} - request_form["mode"] = "PROCESS" request_form["trainerFilePath"] = 'modules\\stablediffusionxl\\stablediffusionxl.trainer' prompt = "" negative_prompt = "" - #model = "stabilityai/stable-diffusion-xl-base-1.0" - model = "unstable" + if self.default_model is None: + model = "stabilityai/stable-diffusion-xl-base-1.0" + else: + model = self.default_model + # models: juggernautXL, dynavisionXL, colossusProjectXL, newrealityXL, unstable ratio_width = "1" ratio_height = "1" width = "" height = "" - lora = "" + if self.default_lora == None: + lora = "" + else: + lora = self.default_lora lora_weight = "" strength = "" guidance_scale = "" diff --git a/tasks/textextractionpdf.py b/tasks/textextractionpdf.py index 26f8c45..598fcbd 100644 --- a/tasks/textextractionpdf.py +++ b/tasks/textextractionpdf.py @@ -1,6 +1,8 @@ import os import re +from threading import Thread +from dvm import DVM from interfaces.dvmtaskinterface import DVMTaskInterface from utils.definitions import EventDefinitions from utils.nip89_utils import NIP89Announcement @@ -13,21 +15,23 @@ Accepted Inputs: Url to pdf file, Event containing an URL to a PDF file Outputs: Text containing the extracted contents of the PDF file """ class TextExtractionPDF(DVMTaskInterface): + NAME: str KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT TASK: str = "pdf-to-text" COST: int = 20 + PK: str - def __init__(self, name, pk): + + def __init__(self, name, dvm_config): self.NAME = name - self.PK = pk + dvm_config.SUPPORTED_TASKS = [self] + self.PK = dvm_config.PRIVATE_KEY + + dvm = DVM + nostr_dvm_thread = Thread(target=dvm, args=[dvm_config]) + nostr_dvm_thread.start() + - def NIP89_announcement(self, d_tag, content): - nip89 = NIP89Announcement() - nip89.kind = self.KIND - nip89.pk = self.PK - nip89.dtag = d_tag - nip89.content = content - return nip89 def is_input_supported(self, input_type, input_content): if input_type != "url" and input_type != "event": diff --git a/tasks/translation.py b/tasks/translation.py index 362aee8..912bd0b 100644 --- a/tasks/translation.py +++ b/tasks/translation.py @@ -1,5 +1,7 @@ import os +from threading import Thread +from dvm import DVM from interfaces.dvmtaskinterface import DVMTaskInterface from utils.definitions import EventDefinitions from utils.nip89_utils import NIP89Announcement @@ -15,21 +17,20 @@ Outputs: Text containing the Translation in the desired language. class Translation(DVMTaskInterface): + NAME: str KIND: int = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT TASK: str = "translation" COST: int = 0 + PK: str - def __init__(self, name, pk): + def __init__(self, name, dvm_config): self.NAME = name - self.PK = pk + dvm_config.SUPPORTED_TASKS = [self] + self.PK = dvm_config.PRIVATE_KEY - def NIP89_announcement(self, d_tag, content): - nip89 = NIP89Announcement() - nip89.kind = self.KIND - nip89.pk = self.PK - nip89.dtag = d_tag - nip89.content = content - return nip89 + dvm = DVM + nostr_dvm_thread = Thread(target=dvm, args=[dvm_config]) + nostr_dvm_thread.start() def is_input_supported(self, input_type, input_content): if input_type != "event" and input_type != "job" and input_type != "text": diff --git a/utils/admin_utils.py b/utils/admin_utils.py index 743b4bc..f7429c1 100644 --- a/utils/admin_utils.py +++ b/utils/admin_utils.py @@ -20,8 +20,7 @@ def admin_make_database_updates(config=None, client=None): whitelistuser = False unwhitelistuser = False blacklistuser = False - addbalance = False - additional_balance = 50 + # publickey = PublicKey.from_bech32("npub1...").to_hex() # use this if you have the npub @@ -42,17 +41,6 @@ def admin_make_database_updates(config=None, client=None): user = get_from_sql_table(publickey) update_sql_table(user.npub, user.balance, False, True, user.nip05, user.lud16, user.name, user.lastactive) - if addbalance: - user = get_from_sql_table(publickey) - update_sql_table(user[0], (int(user.balance) + additional_balance), user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16, user.name, user.lastactive) - time.sleep(1.0) - message = str(additional_balance) + " Sats have been added to your balance. Your new balance is " + str( - (int(user.balance) + additional_balance)) + " Sats." - keys = Keys.from_sk_str(config.PRIVATE_KEY) - evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(publickey), message, - None).to_event(keys) - send_event(evt, key=keys) - if deleteuser: delete_from_sql_table(publickey) diff --git a/utils/backend_utils.py b/utils/backend_utils.py index adcc9e1..b56fdde 100644 --- a/utils/backend_utils.py +++ b/utils/backend_utils.py @@ -2,10 +2,6 @@ import requests from utils.definitions import EventDefinitions from utils.nostr_utils import get_event_by_id -from tasks.textextractionpdf import TextExtractionPDF -from tasks.translation import Translation -from tasks.imagegenerationsdxl import ImageGenerationSDXL - def get_task(event, client, dvmconfig): if event.kind() == EventDefinitions.KIND_NIP90_GENERIC: # use this for events that have no id yet @@ -28,7 +24,7 @@ def get_task(event, client, dvmconfig): if tag.as_vec()[2] == "url": file_type = check_url_is_readable(tag.as_vec()[1]) if file_type == "pdf": - return TextExtractionPDF.TASK + return "pdf-to-text" else: return "unknown job" elif tag.as_vec()[2] == "event": @@ -46,9 +42,9 @@ def get_task(event, client, dvmconfig): return "unknown type" elif event.kind() == EventDefinitions.KIND_NIP90_TRANSLATE_TEXT: - return Translation.TASK + return "translation" elif event.kind() == EventDefinitions.KIND_NIP90_GENERATE_IMAGE: - return ImageGenerationSDXL.TASK + return "text-to-image" else: return "unknown type" @@ -60,6 +56,8 @@ def check_task_is_supported(event, client, get_duration=False, config=None): input_type = "" duration = 1 + + for tag in event.tags(): if tag.as_vec()[0] == 'i': if len(tag.as_vec()) < 3: @@ -82,6 +80,10 @@ def check_task_is_supported(event, client, get_duration=False, config=None): return False, "", 0 task = get_task(event, client=client, dvmconfig=dvm_config) + for dvm in dvm_config.SUPPORTED_TASKS: + if dvm.TASK == task: + if not dvm.is_input_supported(input_type, event.content()): + return False, task, duration if input_type == 'url' and check_url_is_readable(input_value) is None: print("url not readable") @@ -90,10 +92,7 @@ def check_task_is_supported(event, client, get_duration=False, config=None): if task not in (x.TASK for x in dvm_config.SUPPORTED_TASKS): return False, task, duration - for dvm in dvm_config.SUPPORTED_TASKS: - if dvm.TASK == task: - if not dvm.is_input_supported(input_type, event.content()): - return False, task, duration + return True, task, duration @@ -107,9 +106,9 @@ def check_url_is_readable(url): if content_type == 'audio/x-wav' or str(url).endswith(".wav") or content_type == 'audio/mpeg' or str(url).endswith( ".mp3") or content_type == 'audio/ogg' or str(url).endswith(".ogg"): return "audio" - elif content_type == 'image/png' or str(url).endswith(".png") or content_type == 'image/jpg' or str(url).endswith( - ".jpg") or content_type == 'image/jpeg' or str(url).endswith(".jpeg") or content_type == 'image/png' or str( - url).endswith(".png"): + elif (content_type == 'image/png' or str(url).endswith(".png") or content_type == 'image/jpg' or str(url).endswith( + ".jpg") or content_type == 'image/jpeg' or str(url).endswith(".jpeg") or content_type == 'image/png' or + str(url).endswith(".png")): return "image" elif content_type == 'video/mp4' or str(url).endswith(".mp4") or content_type == 'video/avi' or str(url).endswith( ".avi") or content_type == 'video/mov' or str(url).endswith(".mov"): diff --git a/utils/definitions.py b/utils/definitions.py index 04f5c8b..0344493 100644 --- a/utils/definitions.py +++ b/utils/definitions.py @@ -1,7 +1,10 @@ +import os from dataclasses import dataclass from nostr_sdk import Event +from utils import env + NEW_USER_BALANCE: int = 250 # Free credits for new users RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine", @@ -42,7 +45,7 @@ class EventDefinitions: class DVMConfig: SUPPORTED_TASKS = [] - PRIVATE_KEY: str + PRIVATE_KEY: str = os.getenv(env.NOSTR_PRIVATE_KEY) RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine", "wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", diff --git a/utils/env.py b/utils/env.py index 3d0bd1a..6945dfd 100644 --- a/utils/env.py +++ b/utils/env.py @@ -9,5 +9,6 @@ LNBITS_HOST = "LNBITS_HOST" TASK_TRANSLATION_NIP89_DTAG = "TASK_TRANSLATION_NIP89_DTAG" TASK_TEXTEXTRACTION_NIP89_DTAG = "TASK_TEXTEXTRACTION_NIP89_DTAG" TASK_IMAGEGENERATION_NIP89_DTAG = "TASK_IMAGEGENERATION_NIP89_DTAG" +TASK_IMAGEGENERATION_NIP89_DTAG2 = "TASK_IMAGEGENERATION_NIP89_DTAG2" diff --git a/utils/zap_utils.py b/utils/zap_utils.py index b81a627..8e7fa31 100644 --- a/utils/zap_utils.py +++ b/utils/zap_utils.py @@ -33,6 +33,7 @@ def parse_bolt11_invoice(invoice): return int(number) + def create_bolt11_ln_bits(sats, config): url = config.LNBITS_URL + "/api/v1/payments" data = {'out': False, 'amount': sats, 'memo': "Nostr-DVM"} @@ -45,6 +46,7 @@ def create_bolt11_ln_bits(sats, config): print(e) return None + def check_bolt11_ln_bits_is_paid(payment_hash, config): url = config.LNBITS_URL + "/api/v1/payments/" + payment_hash headers = {'X-API-Key': config.LNBITS_INVOICE_KEY, 'Content-Type': 'application/json', 'charset': 'UTF-8'} @@ -92,4 +94,3 @@ def decrypt_private_zap_message(msg, privkey, pubkey): return decoded except Exception as ex: return str(ex) -