diff --git a/nostr_dvm/dvm.py b/nostr_dvm/dvm.py index fcab097..52eb0d9 100644 --- a/nostr_dvm/dvm.py +++ b/nostr_dvm/dvm.py @@ -17,7 +17,8 @@ from nostr_dvm.utils.database_utils import create_sql_table, get_or_add_user, up update_user_subscription from nostr_dvm.utils.mediasource_utils import input_data_file_duration from nostr_dvm.utils.nip88_utils import nip88_has_active_subscription -from nostr_dvm.utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event, check_and_decrypt_tags +from nostr_dvm.utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event, check_and_decrypt_tags, \ + send_event_outbox from nostr_dvm.utils.output_utils import build_status_reaction from nostr_dvm.utils.zap_utils import check_bolt11_ln_bits_is_paid, create_bolt11_ln_bits, parse_zap_event_tags, \ parse_amount_from_bolt11_invoice, zaprequest, pay_bolt11_ln_bits, create_bolt11_lud16 @@ -40,12 +41,11 @@ class DVM: wait_for_send = False skip_disconnected_relays = True relaylimits = RelayLimits.disable() - opts = (Options().wait_for_send(wait_for_send). send_timeout(timedelta(seconds=self.dvm_config.RELAY_TIMEOUT)) + opts = (Options().wait_for_send(wait_for_send).send_timeout(timedelta(seconds=self.dvm_config.RELAY_TIMEOUT)) .skip_disconnected_relays(skip_disconnected_relays).relay_limits(relaylimits)) signer = NostrSigner.keys(self.keys) self.client = Client.with_opts(signer, opts) - self.job_list = [] self.jobs_on_hold_list = [] pk = self.keys.public_key() @@ -121,7 +121,8 @@ class DVM: print("[" + self.dvm_config.NIP89.NAME + "] Request by blacklisted user, skipped") return - print(bcolors.MAGENTA + "[" + self.dvm_config.NIP89.NAME + "] Received new Request: " + task + " from " + user.name + bcolors.ENDC) + print( + bcolors.MAGENTA + "[" + self.dvm_config.NIP89.NAME + "] Received new Request: " + task + " from " + user.name + bcolors.ENDC) duration = input_data_file_duration(nip90_event, dvm_config=self.dvm_config, client=self.client) amount = get_amount_per_task(task, self.dvm_config, duration) if amount is None: @@ -456,6 +457,16 @@ class DVM: original_event.kind().as_u64()) + ". The task was: " + original_event.content()]) status_tag = Tag.parse(["status", "success"]) reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag] + + relay_tag = None + for tag in original_event.tags(): + if tag.as_vec()[0] == "relays": + relay_tag = tag + break + if relay_tag is not None: + reply_tags.append(relay_tag) + + encrypted = False for tag in original_event.tags(): if tag.as_vec()[0] == "encrypted": @@ -477,7 +488,8 @@ class DVM: reply_event = EventBuilder(Kind(original_event.kind().as_u64() + 1000), str(content), reply_tags).to_event( self.keys) - send_event(reply_event, client=self.client, dvm_config=self.dvm_config) + # send_event(reply_event, client=self.client, dvm_config=self.dvm_config) + send_event_outbox(reply_event, client=self.client, dvm_config=self.dvm_config) print(bcolors.GREEN + "[" + self.dvm_config.NIP89.NAME + "] " + str( original_event.kind().as_u64() + 1000) + " Job Response event sent: " + reply_event.as_json() + bcolors.ENDC) @@ -493,7 +505,17 @@ class DVM: p_tag = Tag.parse(["p", original_event.author().to_hex()]) alt_tag = Tag.parse(["alt", alt_description]) status_tag = Tag.parse(["status", status]) + reply_tags = [e_tag, alt_tag, status_tag] + + relay_tag = None + for tag in original_event.tags(): + if tag.as_vec()[0] == "relays": + relay_tag = tag + break + if relay_tag is not None: + reply_tags.append(relay_tag) + encryption_tags = [] encrypted = False @@ -577,7 +599,8 @@ class DVM: keys = Keys.parse(dvm_config.PRIVATE_KEY) reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys) - send_event(reaction_event, client=self.client, dvm_config=self.dvm_config) + # send_event(reaction_event, client=self.client, dvm_config=self.dvm_config) + send_event_outbox(reaction_event, client=self.client, dvm_config=self.dvm_config) print(bcolors.YELLOW + "[" + self.dvm_config.NIP89.NAME + "]" + " Sent Kind " + str( EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC) @@ -625,7 +648,8 @@ class DVM: post_processed = dvm.post_process(result, job_event) send_nostr_reply_event(post_processed, job_event.as_json()) except Exception as e: - print(bcolors.RED + "[" + self.dvm_config.NIP89.NAME + "] Error: " + str(e) + bcolors.ENDC) + print(bcolors.RED + "[" + self.dvm_config.NIP89.NAME + "] Error: " + str( + e) + bcolors.ENDC) send_job_status_reaction(job_event, "error", content=str(e), dvm_config=self.dvm_config) except Exception as e: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_by_top_zaps.py b/nostr_dvm/tasks/content_discovery_currently_popular_by_top_zaps.py index 86408d9..bf4eb62 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_by_top_zaps.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_by_top_zaps.py @@ -155,14 +155,16 @@ class DicoverContentCurrentlyPopularZaps(DVMTaskInterface): ns.finallist[event.id().to_hex()] = invoice_amount result_list = [] - print("[" + self.dvm_config.IDENTIFIER + "] Filtered " + str( - len(result_list)) + " fitting events.") + finallist_sorted = sorted(ns.finallist.items(), key=lambda x: x[1], reverse=True)[:int(options["max_results"])] for entry in finallist_sorted: # print(EventId.parse(entry[0]).to_bech32() + "/" + EventId.parse(entry[0]).to_hex() + ": " + str(entry[1])) e_tag = Tag.parse(["e", entry[0]]) result_list.append(e_tag.as_vec()) + print("[" + self.dvm_config.IDENTIFIER + "] Filtered " + str( + len(result_list)) + " fitting events.") + cli.disconnect() cli.shutdown() diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_topic.py b/nostr_dvm/tasks/content_discovery_currently_popular_topic.py index 8c49b75..7d65da6 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_topic.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_topic.py @@ -176,7 +176,6 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface): # print(EventId.parse(entry[0]).to_bech32() + "/" + EventId.parse(entry[0]).to_hex() + ": " + str(entry[1])) e_tag = Tag.parse(["e", entry[0]]) result_list.append(e_tag.as_vec()) - print(len(result_list)) print("[" + self.dvm_config.IDENTIFIER + "] Filtered " + str( len(result_list)) + " fitting events.") diff --git a/nostr_dvm/utils/nostr_utils.py b/nostr_dvm/utils/nostr_utils.py index 2d233d2..c29aec0 100644 --- a/nostr_dvm/utils/nostr_utils.py +++ b/nostr_dvm/utils/nostr_utils.py @@ -6,7 +6,9 @@ from typing import List import dotenv from nostr_sdk import Filter, Client, Alphabet, EventId, Event, PublicKey, Tag, Keys, nip04_decrypt, Metadata, Options, \ - Nip19Event, SingleLetterTag + Nip19Event, SingleLetterTag, RelayOptions, RelayLimits, SecretKey, NostrSigner + +from nostr_dvm.utils.definitions import EventDefinitions def get_event_by_id(event_id: str, client: Client, config=None) -> Event | None: @@ -104,10 +106,80 @@ def get_referenced_event_by_id(event_id, client, dvm_config, kinds) -> Event | N return None +def get_inbox_relays(event_to_send: Event, client: Client, dvm_config): + ptags = [] + for tag in event_to_send.tags(): + if tag.as_vec()[0] == 'p': + ptag = PublicKey.parse(tag.as_vec()[1]) + ptags.append(ptag) + + filter = Filter().kinds([EventDefinitions.KIND_RELAY_ANNOUNCEMENT]).authors(ptags) + events = client.get_events_of([filter], timedelta(dvm_config.RELAY_TIMEOUT)) + if len(events) == 0: + return [] + else: + nip65event = events[0] + relays = [] + for tag in nip65event.tags(): + if tag.as_vec()[0] == 'r' and len(tag.as_vec()) == 2: + rtag = tag.as_vec()[1] + relays.append(rtag) + elif tag.as_vec()[0] == 'r' and len(tag.as_vec()) == 3: + if tag.as_vec()[2] == "read": + rtag = tag.as_vec()[1] + relays.append(rtag) + return relays + + +def send_event_outbox(event: Event, client, dvm_config) -> EventId: + + # 1. OK, Let's overcomplicate things. + # 2. If our event has a relays tag, we just send the event to these relay in the classical way. + relays = [] + for tag in event.tags(): + if tag.as_vec()[0] == 'relays': + for index, param in enumerate(tag.as_vec()): + if index != 0: + relays.append(tag.as_vec()[index]) + break + + + # 3. If we couldn't find relays, we look in the receivers inbox + if len(relays) == 0: + relays = get_inbox_relays(event, client, dvm_config) + + # 4. If we don't find inbox relays (e.g. because the user didn't announce them, we just send to our default relays + if len(relays) == 0: + print("[" + dvm_config.NIP89.NAME + "] No Inbox found, replying to generic relays") + eventid = send_event(event, client, dvm_config) + return eventid + + # 5. Otherwise, we create a new Outbox client with the inbox relays and send the event there + relaylimits = RelayLimits.disable() + opts = ( + Options().wait_for_send(False).send_timeout(timedelta(seconds=dvm_config.RELAY_TIMEOUT)).relay_limits( + relaylimits)) + sk = SecretKey.from_hex(dvm_config.PRIVATE_KEY) + keys = Keys.parse(sk.to_hex()) + signer = NostrSigner.keys(keys) + outboxclient = Client.with_opts(signer, opts) + + print("[" + dvm_config.NIP89.NAME + "] Receiver Inbox relays: " + str(relays)) + + for relay in relays: + opts = RelayOptions().ping(False) + outboxclient.add_relay_with_opts(relay, opts) + + outboxclient.connect() + event_id = outboxclient.send_event(event) + outboxclient.shutdown() + + return event_id + + def send_event(event: Event, client: Client, dvm_config, blastr=False) -> EventId: try: relays = [] - for tag in event.tags(): if tag.as_vec()[0] == 'relays': for index, param in enumerate(tag.as_vec()): @@ -118,16 +190,16 @@ def send_event(event: Event, client: Client, dvm_config, blastr=False) -> EventI if relay not in dvm_config.RELAY_LIST: client.add_relay(relay) - if blastr: - client.add_relay("wss://nostr.mutinywallet.com") + #if blastr: + # client.add_relay("wss://nostr.mutinywallet.com") event_id = client.send_event(event) for relay in relays: if relay not in dvm_config.RELAY_LIST: client.remove_relay(relay) - if blastr: - client.remove_relay("wss://nostr.mutinywallet.com") + #if blastr: + # client.remove_relay("wss://nostr.mutinywallet.com") return event_id except Exception as e: print(e) diff --git a/tests/discovery.py b/tests/discovery.py index 783ed16..35fe93f 100644 --- a/tests/discovery.py +++ b/tests/discovery.py @@ -19,7 +19,9 @@ from nostr_dvm.utils.nostr_utils import check_and_set_private_key from nostr_dvm.utils.zap_utils import check_and_set_ln_bits_keys rebbroadcast_NIP89 = False # Announce NIP89 on startup -rebbroadcast_NIP65_Relay_List = True +rebbroadcast_NIP65_Relay_List = False +update_profile = False + global_update_rate = 120 # set this high on first sync so db can fully sync before another process trys to. use_logger = True @@ -238,7 +240,7 @@ def playground(): admin_config_trending_nostr_band = AdminConfig() admin_config_trending_nostr_band.REBROADCAST_NIP89 = rebbroadcast_NIP89 admin_config_trending_nostr_band.REBROADCAST_NIP65_RELAY_LIST = rebbroadcast_NIP65_Relay_List - admin_config_trending_nostr_band.UPDATE_PROFILE = False + admin_config_trending_nostr_band.UPDATE_PROFILE = update_profile #admin_config_trending_nostr_band.DELETE_NIP89 = True #admin_config_trending_nostr_band.PRIVKEY = "" #admin_config_trending_nostr_band.EVENTID = "e7a7aaa7113f17af94ccbfe86c06e04c27ffce3d2f654d613ce249b68414bdae" @@ -259,7 +261,7 @@ def playground(): admin_config_animals = AdminConfig() admin_config_animals.REBROADCAST_NIP89 = rebbroadcast_NIP89 admin_config_animals.REBROADCAST_NIP65_RELAY_LIST = rebbroadcast_NIP65_Relay_List - admin_config_animals.UPDATE_PROFILE = False + admin_config_animals.UPDATE_PROFILE = update_profile #admin_config_animals.DELETE_NIP89 = True #admin_config_animals.PRIVKEY = "" #admin_config_animals.EVENTID = "79c613b5f0e71718628bd0c782a5b6b495dac491f36c326ccf416ada80fd8fdc" @@ -317,7 +319,7 @@ def playground(): admin_config_plants = AdminConfig() admin_config_plants.REBROADCAST_NIP89 = rebbroadcast_NIP89 admin_config_plants.REBROADCAST_NIP65_RELAY_LIST = rebbroadcast_NIP65_Relay_List - admin_config_plants.UPDATE_PROFILE = False + admin_config_plants.UPDATE_PROFILE = update_profile #admin_config_plants.DELETE_NIP89 = True #admin_config_plants.PRIVKEY = "" #admin_config_plants.EVENTID = "ff28be59708ee597c7010fd43a7e649e1ab51da491266ca82a84177e0007e4d6" @@ -363,7 +365,7 @@ def playground(): admin_config_top_zaps = AdminConfig() admin_config_top_zaps.REBROADCAST_NIP89 = rebbroadcast_NIP89 admin_config_top_zaps.REBROADCAST_NIP65_RELAY_LIST = rebbroadcast_NIP65_Relay_List - admin_config_top_zaps.UPDATE_PROFILE = False + admin_config_top_zaps.UPDATE_PROFILE = update_profile #admin_config_top_zaps.DELETE_NIP89 = True #admin_config_top_zaps.PRIVKEY = "" #admin_config_top_zaps.EVENTID = "05a6de88e15aa6c8b4c8ec54481f885f397a30761ff2799958e5c5ee9ad6917b" @@ -394,7 +396,7 @@ def playground(): admin_config_followers = AdminConfig() admin_config_followers.REBROADCAST_NIP89 = rebbroadcast_NIP89 admin_config_followers.REBROADCAST_NIP65_RELAY_LIST = rebbroadcast_NIP65_Relay_List - admin_config_followers.UPDATE_PROFILE = False + admin_config_followers.UPDATE_PROFILE = update_profile #admin_config_followers.DELETE_NIP89 = True #admin_config_followers.PRIVKEY = "" #admin_config_followers.EVENTID = "590cd7b2902224f740acbd6845023a5ab4a959386184f3360c2859019cfd48fa" @@ -426,7 +428,7 @@ def playground(): admin_config_global_popular = AdminConfig() admin_config_global_popular.REBROADCAST_NIP89 = rebbroadcast_NIP89 admin_config_global_popular.REBROADCAST_NIP65_RELAY_LIST = rebbroadcast_NIP65_Relay_List - admin_config_global_popular.UPDATE_PROFILE = False + admin_config_global_popular.UPDATE_PROFILE = update_profile #admin_config_global_popular.DELETE_NIP89 = True #admin_config_global_popular.PRIVKEY = "" #admin_config_global_popular.EVENTID = "2fea4ee2ccf0fa11db171113ffd7a676f800f34121478b7c9a4e73c2f1990028"