diff --git a/.env_example b/.env_example index 4393e24..4bc7f34 100644 --- a/.env_example +++ b/.env_example @@ -3,7 +3,7 @@ #Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot LNBITS_ADMIN_KEY = "" # In order to pay invoices, e.g. from the bot to DVMs, or reimburse users. Keep this secret and use responsibly. -LNBITS_ADMIN_ID = "" +LNBITS_WALLET_ID = "" LNBITS_HOST = "https://demo.lnbits.com/" # In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain. # We will use the api to create and manage zapable lightning addresses diff --git a/.gitignore b/.gitignore index 6e4d163..bbce83d 100644 --- a/.gitignore +++ b/.gitignore @@ -191,3 +191,4 @@ tests/db/Cashu/wallet.sqlite3 tests/pagerank/index_map_99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64.json tests/pagerank/network_graph_99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64.json tests/test_data/wallet_mint_api/wallet.sqlite3 +.env_bkp2 diff --git a/examples/ollama_dvm/.env_example b/examples/ollama_dvm/.env_example index eff18ab..868ecfe 100644 --- a/examples/ollama_dvm/.env_example +++ b/examples/ollama_dvm/.env_example @@ -1,6 +1,6 @@ #Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot LNBITS_ADMIN_KEY = "" -LNBITS_ADMIN_ID = "" +LNBITS_WALLET_ID = "" LNBITS_HOST = "https://lnbits.com" #Use your own/a trusted instance ideally. # In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain. # We will use the api to create and manage zapable lightning addresses diff --git a/examples/ollama_dvm/README.md b/examples/ollama_dvm/README.md index 2709125..aff2533 100644 --- a/examples/ollama_dvm/README.md +++ b/examples/ollama_dvm/README.md @@ -9,7 +9,7 @@ Projects in this folder contain ready-to-use DVMs. To tun the DVM following the Create a new venv in this directory by opening the terminal here, or navigate to this directory and type: `"python -m venv venv"` - Place .env file (based on .env_example) in this folder. - Recommended but optional: - - Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_ADMIN_ID`, `LNBITS_HOST`. + - Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `LNBITS_HOST`. - If you are running an own instance of `Nostdress` enter `NOSTDRESS_DOMAIN` or use the default one. - Activate the venv with - MacOS/Linux: source ./venv/bin/activate diff --git a/examples/ollama_dvm/test_client.py b/examples/ollama_dvm/test_client.py index e0a4177..9dea5cb 100644 --- a/examples/ollama_dvm/test_client.py +++ b/examples/ollama_dvm/test_client.py @@ -65,7 +65,7 @@ async def nostr_client(): print(f"Received new event from {relay_url}: {event.as_json()}") if event.kind() == 7000: print("[Nostr Client]: " + event.as_json()) - elif 6000 < event.kind().as_u64() < 6999: + elif 6000 < event.kind().as_u16() < 6999: print("[Nostr Client]: " + event.as_json()) print("[Nostr Client]: " + event.content()) diff --git a/examples/tts_dvm/.env_example b/examples/tts_dvm/.env_example index ef4a5e4..042838b 100644 --- a/examples/tts_dvm/.env_example +++ b/examples/tts_dvm/.env_example @@ -1,6 +1,6 @@ #Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot LNBITS_ADMIN_KEY = "" -LNBITS_ADMIN_ID = "" +LNBITS_WALLET_ID = "" LNBITS_HOST = "https://lnbits.com" #Use your own/a trusted instance ideally. # In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain. # We will use the api to create and manage zapable lightning addresses diff --git a/examples/tts_dvm/README.md b/examples/tts_dvm/README.md index d40bb1b..307a7d8 100644 --- a/examples/tts_dvm/README.md +++ b/examples/tts_dvm/README.md @@ -9,7 +9,7 @@ Projects in this folder contain ready-to-use DVMs. To tun the DVM following the Create a new venv in this directory by opening the terminal here, or navigate to this directory and type: `"python -m venv venv"` - Place .env file (based on .env_example) in this folder. - Recommended but optional: - - Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_ADMIN_ID`, `LNBITS_HOST`. + - Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `LNBITS_HOST`. - If you are running an own instance of `Nostdress` enter `NOSTDRESS_DOMAIN` or use the default one. - Activate the venv with - MacOS/Linux: source ./venv/bin/activate diff --git a/examples/tts_dvm/test_client.py b/examples/tts_dvm/test_client.py index c4951fb..60df0ac 100644 --- a/examples/tts_dvm/test_client.py +++ b/examples/tts_dvm/test_client.py @@ -71,7 +71,7 @@ async def nostr_client(): print(f"Received new event from {relay_url}: {event.as_json()}") if event.kind() == 7000: print("[Nostr Client]: " + event.as_json()) - elif 6000 < event.kind().as_u64() < 6999: + elif 6000 < event.kind().as_u16() < 6999: print("[Nostr Client]: " + event.as_json()) print("[Nostr Client]: " + event.content()) diff --git a/examples/unleashed_dvm/.env_example b/examples/unleashed_dvm/.env_example index 7e177bc..e12a7ea 100644 --- a/examples/unleashed_dvm/.env_example +++ b/examples/unleashed_dvm/.env_example @@ -1,6 +1,6 @@ #Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot LNBITS_ADMIN_KEY = "" -LNBITS_ADMIN_ID = "" +LNBITS_WALLET_ID = "" LNBITS_HOST = "https://lnbits.com" #Use your own/a trusted instance ideally. # In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain. # We will use the api to create and manage zapable lightning addresses diff --git a/examples/unleashed_dvm/README.md b/examples/unleashed_dvm/README.md index d40bb1b..307a7d8 100644 --- a/examples/unleashed_dvm/README.md +++ b/examples/unleashed_dvm/README.md @@ -9,7 +9,7 @@ Projects in this folder contain ready-to-use DVMs. To tun the DVM following the Create a new venv in this directory by opening the terminal here, or navigate to this directory and type: `"python -m venv venv"` - Place .env file (based on .env_example) in this folder. - Recommended but optional: - - Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_ADMIN_ID`, `LNBITS_HOST`. + - Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `LNBITS_HOST`. - If you are running an own instance of `Nostdress` enter `NOSTDRESS_DOMAIN` or use the default one. - Activate the venv with - MacOS/Linux: source ./venv/bin/activate diff --git a/examples/unleashed_dvm/test_client.py b/examples/unleashed_dvm/test_client.py index 7ce4e54..6d96a49 100644 --- a/examples/unleashed_dvm/test_client.py +++ b/examples/unleashed_dvm/test_client.py @@ -70,7 +70,7 @@ async def nostr_client(): print(f"Received new event from {relay_url}: {event.as_json()}") if event.kind() == 7000: print("[Nostr Client]: " + event.as_json()) - elif 6000 < event.kind().as_u64() < 6999: + elif 6000 < event.kind().as_u16() < 6999: print("[Nostr Client " + event.author().to_bech32() + "]: " + event.as_json()) print("[Nostr Client " + event.author().to_bech32() + "]: " + event.content()) diff --git a/nostr_dvm/bot.py b/nostr_dvm/bot.py index 229b125..fb6d2d3 100644 --- a/nostr_dvm/bot.py +++ b/nostr_dvm/bot.py @@ -68,7 +68,7 @@ class Bot: kinds = [EventDefinitions.KIND_NIP90_GENERIC, EventDefinitions.KIND_FEEDBACK] for dvm in self.dvm_config.SUPPORTED_DVMS: if dvm.KIND not in kinds: - kinds.append(Kind(dvm.KIND.as_u64() + 1000)) + kinds.append(Kind(dvm.KIND.as_u16() + 1000)) dvm_filter = (Filter().kinds(kinds).since(Timestamp.now())) await self.client.subscribe([zap_filter, dm_filter, nip17_filter, dvm_filter], None) @@ -82,8 +82,8 @@ class Bot: keys = self.keys async def handle(self, relay_url, subscription_id, nostr_event): - if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() + 1000 <= nostr_event.kind().as_u64() - <= EventDefinitions.KIND_NIP90_GENERIC.as_u64() + 1000): + if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u16() + 1000 <= nostr_event.kind().as_u16() + <= EventDefinitions.KIND_NIP90_GENERIC.as_u16() + 1000): await handle_nip90_response_event(nostr_event) elif nostr_event.kind() == EventDefinitions.KIND_FEEDBACK: await handle_nip90_feedback(nostr_event) @@ -459,7 +459,7 @@ class Bot: return dvms = [x for x in self.dvm_config.SUPPORTED_DVMS if - x.PUBLIC_KEY == nostr_event.author().to_hex() and x.KIND.as_u64() == nostr_event.kind().as_u64() - 1000] + x.PUBLIC_KEY == nostr_event.author().to_hex() and x.KIND.as_u16() == nostr_event.kind().as_u16() - 1000] if len(dvms) > 0: dvm = dvms[0] if dvm.dvm_config.EXTERNAL_POST_PROCESS_TYPE != PostProcessFunctionType.NONE: diff --git a/nostr_dvm/dvm.py b/nostr_dvm/dvm.py index 7ce48a5..3b4f3b0 100644 --- a/nostr_dvm/dvm.py +++ b/nostr_dvm/dvm.py @@ -91,11 +91,11 @@ class DVM: async def handle(self, relay_url, subscription_id, nostr_event: Event): if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print(nostr_event.as_json()) - if EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() <= nostr_event.kind().as_u64() <= EventDefinitions.KIND_NIP90_GENERIC.as_u64(): + if EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u16() <= nostr_event.kind().as_u16() <= EventDefinitions.KIND_NIP90_GENERIC.as_u16(): await handle_nip90_job_event(nostr_event) - elif nostr_event.kind().as_u64() == EventDefinitions.KIND_ZAP.as_u64(): + elif nostr_event.kind().as_u16() == EventDefinitions.KIND_ZAP.as_u16(): await handle_zap(nostr_event) - elif nostr_event.kind().as_u64() == EventDefinitions.KIND_NIP61_NUT_ZAP.as_u64(): + elif nostr_event.kind().as_u16() == EventDefinitions.KIND_NIP61_NUT_ZAP.as_u16(): await handle_nutzap(nostr_event) async def handle_msg(self, relay_url, msg): @@ -139,7 +139,7 @@ class DVM: return if self.dvm_config.LOGLEVEL.value >= LogLevel.INFO.value: print( - bcolors.MAGENTA + "[" + self.dvm_config.NIP89.NAME + "] Received new Request: " + task + " from " + user.name + " (" + user.npub + ")" + bcolors.ENDC) + bcolors.MAGENTA + "[" + self.dvm_config.NIP89.NAME + "] Received new Request: " + task + " from " + user.name + " (" + PublicKey.parse(user.npub).to_bech32() + ")" + bcolors.ENDC) duration = await 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: @@ -575,7 +575,7 @@ class DVM: e_tag = Tag.parse(["e", original_event.id().to_hex()]) p_tag = Tag.parse(["p", original_event.author().to_hex()]) alt_tag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str( - original_event.kind().as_u64()) + ". The task was: " + original_event.content()]) + original_event.kind().as_u16()) + ". The task was: " + original_event.content()]) status_tag = Tag.parse(["status", "success"]) reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag] @@ -607,17 +607,17 @@ class DVM: content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.author().to_hex()), content) - reply_event = EventBuilder(Kind(original_event.kind().as_u64() + 1000), str(content), reply_tags).to_event( + reply_event = EventBuilder(Kind(original_event.kind().as_u16() + 1000), str(content), reply_tags).to_event( self.keys) # send_event(reply_event, client=self.client, dvm_config=self.dvm_config) await send_event_outbox(reply_event, client=self.client, dvm_config=self.dvm_config) if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: 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) + original_event.kind().as_u16() + 1000) + " Job Response event sent: " + reply_event.as_json() + bcolors.ENDC) elif self.dvm_config.LOGLEVEL.value >= LogLevel.INFO.value: print(bcolors.GREEN + "[" + self.dvm_config.NIP89.NAME + "] " + str( - original_event.kind().as_u64() + 1000) + " Job Response event sent: " + reply_event.id().to_hex() + bcolors.ENDC) + original_event.kind().as_u16() + 1000) + " Job Response event sent: " + reply_event.id().to_hex() + bcolors.ENDC) async def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None, content=None, @@ -729,10 +729,10 @@ class DVM: if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print(bcolors.YELLOW + "[" + self.dvm_config.NIP89.NAME + "]" + " Sent Kind " + str( - EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC) + EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC) elif self.dvm_config.LOGLEVEL.value >= LogLevel.INFO.value: print(bcolors.YELLOW + "[" + self.dvm_config.NIP89.NAME + "]" + " Sent Kind " + str( - EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC) + EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC) return reaction_event.as_json() @@ -779,8 +779,8 @@ class DVM: async def do_work(job_event, amount): if (( - EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() <= job_event.kind().as_u64() <= EventDefinitions.KIND_NIP90_GENERIC.as_u64()) - or job_event.kind().as_u64() == EventDefinitions.KIND_DM.as_u64()): + EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u16() <= job_event.kind().as_u16() <= EventDefinitions.KIND_NIP90_GENERIC.as_u16()) + or job_event.kind().as_u16() == EventDefinitions.KIND_DM.as_u16()): task = await get_task(job_event, client=self.client, dvm_config=self.dvm_config) diff --git a/nostr_dvm/subscription.py b/nostr_dvm/subscription.py index 3673da9..1615636 100644 --- a/nostr_dvm/subscription.py +++ b/nostr_dvm/subscription.py @@ -159,7 +159,7 @@ class Subscription: reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys) await send_event(reaction_event, client=self.client, dvm_config=self.dvm_config) print("[" + self.dvm_config.NIP89.NAME + "]" + ": Sent Kind " + str( - EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + "success" + " " + reaction_event.as_json()) + EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + "success" + " " + reaction_event.as_json()) async def pay_zap_split(nwc, overall_amount, zaps, tier, unit="msats"): overallsplit = 0 diff --git a/nostr_dvm/tasks/content_discovery_currently_latest_longform.py b/nostr_dvm/tasks/content_discovery_currently_latest_longform.py index cf125a3..5c81464 100644 --- a/nostr_dvm/tasks/content_discovery_currently_latest_longform.py +++ b/nostr_dvm/tasks/content_discovery_currently_latest_longform.py @@ -112,7 +112,7 @@ class DicoverContentLatestLongForm(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) #print(self.db_name) cli = ClientBuilder().database(database).signer(signer).opts(opts).build() await cli.connect() @@ -178,7 +178,7 @@ class DicoverContentLatestLongForm(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_currently_latest_wiki.py b/nostr_dvm/tasks/content_discovery_currently_latest_wiki.py index 91430a6..c793916 100644 --- a/nostr_dvm/tasks/content_discovery_currently_latest_wiki.py +++ b/nostr_dvm/tasks/content_discovery_currently_latest_wiki.py @@ -112,7 +112,7 @@ class DicoverContentLatestWiki(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) #print(self.db_name) cli = ClientBuilder().database(database).signer(signer).opts(opts).build() await cli.connect() @@ -178,7 +178,7 @@ class DicoverContentLatestWiki(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular.py b/nostr_dvm/tasks/content_discovery_currently_popular.py index 3d05204..554a389 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular.py @@ -109,7 +109,7 @@ class DicoverContentCurrentlyPopular(DVMTaskInterface): ns = SimpleNamespace() options = self.set_options(request_form) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) timestamp_since = Timestamp.now().as_secs() - self.db_since since = Timestamp.from_secs(timestamp_since) @@ -175,7 +175,7 @@ class DicoverContentCurrentlyPopular(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: 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 7d882f8..4ba0e9c 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 @@ -115,7 +115,7 @@ class DicoverContentCurrentlyPopularZaps(DVMTaskInterface): #keys = Keys.parse(sk.to_hex()) #signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) #cli = ClientBuilder().database(database).signer(signer).opts(opts).build() #await cli.connect() @@ -230,7 +230,7 @@ class DicoverContentCurrentlyPopularZaps(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_followers.py b/nostr_dvm/tasks/content_discovery_currently_popular_followers.py index 96c5d53..f629be3 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_followers.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_followers.py @@ -101,7 +101,7 @@ class DicoverContentCurrentlyPopularFollowers(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().database(database).signer(signer).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: await cli.add_relay(relay) @@ -199,7 +199,7 @@ class DicoverContentCurrentlyPopularFollowers(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_gallery.py b/nostr_dvm/tasks/content_discovery_currently_popular_gallery.py index 8f23c97..60061ac 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_gallery.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_gallery.py @@ -112,7 +112,7 @@ class DicoverContentCurrentlyPopularGallery(DVMTaskInterface): ns = SimpleNamespace() options = self.set_options(request_form) - databasegallery = await NostrDatabase.sqlite(self.db_name) + databasegallery = NostrDatabase.lmdb(self.db_name) timestamp_since = Timestamp.now().as_secs() - self.db_since since = Timestamp.from_secs(timestamp_since) @@ -250,7 +250,7 @@ class DicoverContentCurrentlyPopularGallery(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_mostr.py b/nostr_dvm/tasks/content_discovery_currently_popular_mostr.py index fb7cec2..902e7b6 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_mostr.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_mostr.py @@ -106,6 +106,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface): from nostr_sdk import Filter from types import SimpleNamespace + ns = SimpleNamespace() options = self.set_options(request_form) @@ -114,9 +115,17 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + + + database = NostrDatabase.lmdb(self.db_name) + try: + await database.delete(Filter().until(Timestamp.from_secs( + Timestamp.now().as_secs() - self.db_since))) + except Exception as e: + print(e) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() + timestamp_since = Timestamp.now().as_secs() - self.db_since since = Timestamp.from_secs(timestamp_since) @@ -153,6 +162,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface): # await cli.shutdown() return json.dumps(result_list) + async def post_process(self, result, event): """Overwrite the interface function to return a social client readable format, if requested""" for tag in event.tags(): @@ -183,7 +193,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: @@ -221,7 +231,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface): except Exception as e: print(e) # Do not delete profiles - await cli.database().delete(Filter().kinds([EventDefinitions.KIND_NOTE, EventDefinitions.KIND_ZAP, EventDefinitions.KIND_REPOST, EventDefinitions.KIND_REACTION]).until(Timestamp.from_secs( + await cli.database().delete(Filter().until(Timestamp.from_secs( Timestamp.now().as_secs() - self.db_since))) # Clear old events so db doesn't get too full. await cli.shutdown() if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_nonfollowers.py b/nostr_dvm/tasks/content_discovery_currently_popular_nonfollowers.py index 0bbb975..58eaa29 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_nonfollowers.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_nonfollowers.py @@ -146,7 +146,7 @@ class DicoverContentCurrentlyPopularNonFollowers(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) if self.database is None: - self.database = await NostrDatabase.sqlite(self.db_name) + self.database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().database(self.database).signer(signer).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: @@ -230,7 +230,7 @@ class DicoverContentCurrentlyPopularNonFollowers(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_currently_popular_topic.py b/nostr_dvm/tasks/content_discovery_currently_popular_topic.py index 1043a4b..720d739 100644 --- a/nostr_dvm/tasks/content_discovery_currently_popular_topic.py +++ b/nostr_dvm/tasks/content_discovery_currently_popular_topic.py @@ -93,6 +93,7 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface): # default values max_results = 200 + user = event.author().to_hex() for tag in event.tags(): if tag.as_vec()[0] == 'i': @@ -101,6 +102,8 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface): param = tag.as_vec()[1] if param == "max_results": # check for param type max_results = int(tag.as_vec()[2]) + elif param == "user": # check for param type + user = (tag.as_vec()[2]) elif param == "search_list": # check for param type self.search_list = str(tag.as_vec()[2]).split(",") print(self.search_list) @@ -113,6 +116,8 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface): options = { "max_results": max_results, + "request_event_id": event.id().to_hex(), + "request_event_author": event.author().to_hex() } request_form['options'] = json.dumps(options) self.request_form = request_form @@ -144,29 +149,34 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface): options = self.set_options(request_form) if self.database is None: - self.database = await NostrDatabase.sqlite(self.db_name) + self.database = NostrDatabase.lmdb(self.db_name) timestamp_since = Timestamp.now().as_secs() - self.db_since since = Timestamp.from_secs(timestamp_since) - filter1 = Filter().kind(definitions.EventDefinitions.KIND_NOTE).since(since) + filters = [] + for word in self.search_list: + filter = Filter().kind(definitions.EventDefinitions.KIND_NOTE).since(since).search(word) + filters.append(filter) - events = await self.database.query([filter1]) + + + events = await self.database.query(filters) if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print("[" + self.dvm_config.NIP89.NAME + "] Considering " + str(len(events)) + " Events") ns.finallist = {} for event in events: if all(ele in event.content().lower() for ele in self.must_list): - if any(ele in event.content().lower() for ele in self.search_list): - if not any(ele in event.content().lower() for ele in self.avoid_list): - filt = Filter().kinds( - [definitions.EventDefinitions.KIND_ZAP, definitions.EventDefinitions.KIND_REACTION, - definitions.EventDefinitions.KIND_REPOST, - definitions.EventDefinitions.KIND_NOTE]).event(event.id()).since(since) - reactions = await self.database.query([filt]) - if len(reactions) >= self.min_reactions: - ns.finallist[event.id().to_hex()] = len(reactions) + #if any(ele in event.content().lower() for ele in self.search_list): + if not any(ele in event.content().lower() for ele in self.avoid_list): + filt = Filter().kinds( + [definitions.EventDefinitions.KIND_ZAP, definitions.EventDefinitions.KIND_REACTION, + definitions.EventDefinitions.KIND_REPOST, + definitions.EventDefinitions.KIND_NOTE]).event(event.id()).since(since) + reactions = await self.database.query([filt]) + if len(reactions) >= self.min_reactions: + ns.finallist[event.id().to_hex()] = len(reactions) result_list = [] finallist_sorted = sorted(ns.finallist.items(), key=lambda x: x[1], reverse=True)[:int(options["max_results"])] @@ -198,7 +208,7 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: diff --git a/nostr_dvm/tasks/content_discovery_update_db_only.py b/nostr_dvm/tasks/content_discovery_update_db_only.py index 53c9191..aa9e55a 100644 --- a/nostr_dvm/tasks/content_discovery_update_db_only.py +++ b/nostr_dvm/tasks/content_discovery_update_db_only.py @@ -1,7 +1,11 @@ import asyncio import json import os +import time from datetime import timedelta +from itertools import islice + +import networkx as nx from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, NostrDatabase, \ ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel, Event, EventId, Kind, \ RelayLimits @@ -14,7 +18,7 @@ from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config from nostr_dvm.utils.nip88_utils import NIP88Config, check_and_set_d_tag_nip88, check_and_set_tiereventid_nip88 from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag, create_amount_tag from nostr_dvm.utils.output_utils import post_process_list_to_events - +from nostr_dvm.utils.wot_utils import build_wot_network, save_network, print_results """ This File contains a Module to update the database for content discovery dvms @@ -132,24 +136,49 @@ class DicoverContentDBUpdateScheduler(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) if self.database is None: - self.database = await NostrDatabase.sqlite(self.db_name) + self.database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(self.database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: await cli.add_relay(relay) + await cli.connect() + if self.dvm_config.WOT_FILTERING: + print("Calculating WOT for " + str(self.dvm_config.WOT_BASED_ON_NPUBS)) + filtering = cli.filtering() + index_map, G = await build_wot_network(self.dvm_config.WOT_BASED_ON_NPUBS, depth=self.dvm_config.WOT_DEPTH, max_batch=500, max_time_request=10) + + # Do we actually need pagerank here? + #print('computing global pagerank...') + #tic = time.time() + #p_G = nx.pagerank(G, tol=1e-12) + # print("network after pagerank: " + str(len(p_G))) + + wot_keys = [] + for item in islice(G, len(G)): + key = next((PublicKey.parse(pubkey) for pubkey, id in index_map.items() if id == item), + None) + wot_keys.append(key) + + #toc = time.time() + #print(f'finished in {toc - tic} seconds') + await filtering.add_public_keys(wot_keys) + + + + # Mute public key - await cli.mute_public_keys(self.dvm_config.MUTE) + #await cli. (self.dvm_config.MUTE) timestamp_since = Timestamp.now().as_secs() - self.db_since since = Timestamp.from_secs(timestamp_since) filter1 = Filter().kinds([definitions.EventDefinitions.KIND_NOTE, definitions.EventDefinitions.KIND_REACTION, - definitions.EventDefinitions.KIND_ZAP]).since(since) # Notes, reactions, zaps + definitions.EventDefinitions.KIND_ZAP]).since(since) # Notes, reactions, zaps # filter = Filter().author(keys.public_key()) if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: diff --git a/nostr_dvm/tasks/discovery_bot_farms.py b/nostr_dvm/tasks/discovery_bot_farms.py index 9de1e15..479d774 100644 --- a/nostr_dvm/tasks/discovery_bot_farms.py +++ b/nostr_dvm/tasks/discovery_bot_farms.py @@ -77,7 +77,7 @@ class DiscoveryBotFarms(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite("db/nostr_profiles.db") + database = NostrDatabase.lmdb("db/nostr_profiles.db") cli = ClientBuilder().database(database).signer(signer).opts(opts).build() await cli.add_relay("wss://relay.damus.io") @@ -137,7 +137,7 @@ class DiscoveryBotFarms(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite("db/nostr_profiles.db") + database = NostrDatabase.lmdb("db/nostr_profiles.db") cli = ClientBuilder().signer(signer).database(database).opts(opts).build() await cli.add_relay("wss://relay.damus.io") diff --git a/nostr_dvm/tasks/generic_dvm.py b/nostr_dvm/tasks/generic_dvm.py index 6a3f4a6..695e485 100644 --- a/nostr_dvm/tasks/generic_dvm.py +++ b/nostr_dvm/tasks/generic_dvm.py @@ -51,9 +51,8 @@ class GenericDVM(DVMTaskInterface): request_form = {"jobID": event.id().to_hex()} - self.options["user"] = user self.options["request_event_id"] = event.id().to_hex() - self.options["request_event_author"] = event.author().to_hex() + self.options["request_event_author"] = user if prompt != "": self.options["input"] = prompt request_form['options'] = json.dumps(self.options) diff --git a/nostr_dvm/tasks/people_discovery_mywot.py b/nostr_dvm/tasks/people_discovery_mywot.py index a86b5a5..592b94a 100644 --- a/nostr_dvm/tasks/people_discovery_mywot.py +++ b/nostr_dvm/tasks/people_discovery_mywot.py @@ -19,7 +19,7 @@ from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config from nostr_dvm.utils.nip88_utils import NIP88Config, check_and_set_d_tag_nip88, check_and_set_tiereventid_nip88 from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag, create_amount_tag from nostr_dvm.utils.output_utils import post_process_list_to_events, post_process_list_to_users -from nostr_dvm.utils.wot_utils import build_network_from, save_network, load_network, print_results, \ +from nostr_dvm.utils.wot_utils import build_wot_network, save_network, load_network, print_results, \ convert_index_to_hex """ @@ -141,7 +141,7 @@ class DiscoverPeopleMyWOT(DVMTaskInterface): #hop1 user_id = PublicKey.parse(options["user"]).to_hex() - index_map, G = await build_network_from(options["user"], depth=int(options["hops"]), max_batch=500, max_time_request=10) + index_map, G = await build_wot_network(options["user"], depth=int(options["hops"]), max_batch=500, max_time_request=10) if use_files: save_network(index_map, G, options["user"]) @@ -217,7 +217,7 @@ class DiscoverPeopleMyWOT(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: @@ -257,7 +257,7 @@ async def analyse_users(user_ids=None, dunbar=100000000): print(npub) print(e) - database = await NostrDatabase.sqlite("db/nostr_followlists.db") + database = NostrDatabase.lmdb("db/nostr_followlists.db") followers_filter = Filter().authors(user_keys).kind(Kind(3)) followers = await database.query([followers_filter]) allfriends = [] diff --git a/nostr_dvm/tasks/people_discovery_wot.py b/nostr_dvm/tasks/people_discovery_wot.py index 42dc84a..05f8456 100644 --- a/nostr_dvm/tasks/people_discovery_wot.py +++ b/nostr_dvm/tasks/people_discovery_wot.py @@ -215,7 +215,7 @@ class DiscoverPeopleWOT(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST: @@ -255,7 +255,7 @@ async def analyse_users(user_ids=None, dunbar=100000000): print(npub) print(e) - database = await NostrDatabase.sqlite("db/nostr_followlists.db") + database = NostrDatabase.lmdb("db/nostr_followlists.db") followers_filter = Filter().authors(user_keys).kind(Kind(3)) followers = await database.query([followers_filter]) allfriends = [] diff --git a/nostr_dvm/tasks/search_users.py b/nostr_dvm/tasks/search_users.py index 4da1500..fb4de6c 100644 --- a/nostr_dvm/tasks/search_users.py +++ b/nostr_dvm/tasks/search_users.py @@ -83,7 +83,7 @@ class SearchUser(DVMTaskInterface): keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().database(database).signer(signer).opts(opts).build() await cli.add_relay(self.relay) @@ -143,7 +143,7 @@ class SearchUser(DVMTaskInterface): sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY) keys = Keys.parse(sk.to_hex()) signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite(self.db_name) + database = NostrDatabase.lmdb(self.db_name) cli = ClientBuilder().signer(signer).database(database).opts(opts).build() await cli.add_relay(self.relay) diff --git a/nostr_dvm/utils/backend_utils.py b/nostr_dvm/utils/backend_utils.py index ff6472f..9048bcb 100644 --- a/nostr_dvm/utils/backend_utils.py +++ b/nostr_dvm/utils/backend_utils.py @@ -92,7 +92,7 @@ async def get_task(event, client, dvm_config): else: for dvm in dvm_config.SUPPORTED_DVMS: - if dvm.KIND.as_u64() == event.kind().as_u64(): + if dvm.KIND.as_u16() == event.kind().as_u16(): return dvm.TASK except Exception as e: print("Get task: " + str(e)) diff --git a/nostr_dvm/utils/dvmconfig.py b/nostr_dvm/utils/dvmconfig.py index e2c88b2..e924ad8 100644 --- a/nostr_dvm/utils/dvmconfig.py +++ b/nostr_dvm/utils/dvmconfig.py @@ -20,30 +20,16 @@ class DVMConfig: "wss://relay.nostr.net" ] - RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io", "wss://nostr21.com", - "wss://nostr.oxtr.dev", - "wss://relay.nostr.net" , "wss://relay.primal.net"] #, "wss://relay.snort.social"] + RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", + "wss://relay.nostr.net" , "wss://relay.primal.net"] # Straight Censorship (reply guy spam) - MUTE = [PublicKey.parse("npub1x5vhtx7j2prvueeenwf7tmesrzmuzc50zs0aakgd75v5c30ekj3s5zjckj"), - PublicKey.parse("npub1l03urys27uet2u6wq6u90rnzf7kv5c3wfu3cyndqz9lq75g46c5q0wkpsj"), - PublicKey.parse("npub17g7qhlu4caefd88vateedm9wau9ys6xt6jhjcfu2kqyw9xmnucxs5d6crj"), - PublicKey.parse("npub1epwccahqndqhseh6q02seu40cqa2ghk3u9tvu92yh4hd6lmxg33spwzujc"), - PublicKey.parse("npub1v0kgu3hymtd4fw9zrlem6l74c3cwl8jdqentt4qsxrrzan6paxaqkkf6dr"), - PublicKey.parse("npub1y8teqt2jay2ulww87wlmpe97gxhjqvhva60jv3ghp8emgk2da3psc2x7lt"), - PublicKey.parse("npub1drz9ts6esv22vplg9vunajwhts4ecaxvusmpxwy8yy683ejnzfnqvfvtk9"), - PublicKey.parse("npub1af5hgjkjpdqavpu3xqz092xjrvrpr3nzfftp4pgh4nez635hznas7m0vvn"), - PublicKey.parse("npub1rwxfxn33mmt9fe66qwg25lm9pm3nwtj5za5qm5cpqjsxlhk3dtsqkmunfe"), - PublicKey.parse("npub1l6y4l424ggvstc8a40n5c4rf8wwt5hlt5vuhn4dzvx9xf0eff9uqnc2fuw"), - PublicKey.parse("npub1q57y985vcazhx87naj5qhdyxxmtrrqakfq7lmvhxppvduqpfkesq3n46e8"), - PublicKey.parse("npub1dryv50m3rl6cx6ajeakmh3ygz83vjcdf6cga99yllmfx9ugqa04st0nk3w"), - PublicKey.parse("npub1xh3n3q2cp2wf9r66mmzmkyunyj8cf5r8aszsvwtdld6upqjmkxcsxgejsd"), - PublicKey.parse("npub1cek65lcyks0jjwx4y47c0zxmx8y22zn7zmhxktfjr32h2z5wtgeqn70vjc"), - PublicKey.parse("npub1y2v5mqw32m7n8mz4wpypcy7wt0t4hg93g67qw0fuyspjdspk0etqm0xx9y"), - PublicKey.parse("npub1cfgr520quxl8p74f5r3u5snt05dxyshfk88tcm5tuj78ypkdhgyqcpxkx2"), - PublicKey.parse("npub1620alhm0dyn063wlrlp6r8xzxlzdk9pp94xfag99gghj92wentlq23t6rz"), - - ] + WOT_FILTERING = False + WOT_BASED_ON_NPUBS = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", + "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", + "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24" + ] + WOT_DEPTH = 2 AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST @@ -91,7 +77,7 @@ def build_default_config(identifier): dvm_config.PRIVATE_KEY = check_and_set_private_key(identifier) dvm_config.IDENTIFIER = identifier npub = Keys.parse(dvm_config.PRIVATE_KEY).public_key().to_bech32() - invoice_key, admin_key, wallet_id, user_id, lnaddress = check_and_set_ln_bits_keys(identifier, npub) + invoice_key, admin_key, wallet_id, lnaddress = check_and_set_ln_bits_keys(identifier, npub) dvm_config.LNBITS_INVOICE_KEY = invoice_key dvm_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST") diff --git a/nostr_dvm/utils/nip65_utils.py b/nostr_dvm/utils/nip65_utils.py index 85639cc..3ed0785 100644 --- a/nostr_dvm/utils/nip65_utils.py +++ b/nostr_dvm/utils/nip65_utils.py @@ -16,7 +16,7 @@ async def nip65_announce_relays(dvm_config, client): content = "" event = EventBuilder(EventDefinitions.KIND_RELAY_ANNOUNCEMENT, content, tags).to_event(keys) - eventid = await send_event(event, client=client, dvm_config=dvm_config, blastr=True) + eventid = await send_event(event, client=client, dvm_config=dvm_config) if (eventid is not None): print( bcolors.BLUE + "[" + dvm_config.NIP89.NAME + "] Announced NIP 65 for " + dvm_config.NIP89.NAME + " (EventID: " + str( diff --git a/nostr_dvm/utils/nip89_utils.py b/nostr_dvm/utils/nip89_utils.py index 832ff95..8145f29 100644 --- a/nostr_dvm/utils/nip89_utils.py +++ b/nostr_dvm/utils/nip89_utils.py @@ -26,7 +26,7 @@ def nip89_create_d_tag(name, pubkey, image): async def nip89_announce_tasks(dvm_config, client): - k_tag = Tag.parse(["k", str(dvm_config.NIP89.KIND.as_u64())]) + k_tag = Tag.parse(["k", str(dvm_config.NIP89.KIND.as_u16())]) d_tag = Tag.parse(["d", dvm_config.NIP89.DTAG]) keys = Keys.parse(dvm_config.NIP89.PK) content = dvm_config.NIP89.CONTENT @@ -65,7 +65,7 @@ async def fetch_nip89_parameters_for_deletion(keys, eventid, client, dvmconfig, async def nip89_delete_announcement(eid: str, keys: Keys, dtag: str, client: Client, config): e_tag = Tag.parse(["e", eid]) a_tag = Tag.parse( - ["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u64()) + ":" + keys.public_key().to_hex() + ":" + dtag]) + ["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u16()) + ":" + keys.public_key().to_hex() + ":" + dtag]) event = EventBuilder(Kind(5), "", [e_tag, a_tag]).to_event(keys) print(f"POW event: {event.as_json()}") await send_event(event, client, config) @@ -73,8 +73,8 @@ async def nip89_delete_announcement(eid: str, keys: Keys, dtag: str, client: Cli async def nip89_delete_announcement_pow(eid: str, keys: Keys, dtag: str, client: Client, config): e_tag = Tag.parse(["e", eid]) a_tag = Tag.parse( - ["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u64()) + ":" + keys.public_key().to_hex() + ":" + dtag]) - event = EventBuilder(Kind(5), "", [e_tag, a_tag]).to_pow_event(keys, 28) + ["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u16()) + ":" + keys.public_key().to_hex() + ":" + dtag]) + event = EventBuilder(Kind(5), "", [e_tag, a_tag]).pow(28).to_event(keys) print(f"POW event: {event.as_json()}") await send_event(event, client, config) @@ -91,7 +91,7 @@ async def nip89_fetch_all_dvms(client): async def nip89_fetch_events_pubkey(client, pubkey, kind): - ktags = [str(kind.as_u64())] + ktags = [str(kind.as_u16())] nip89filter = (Filter().kind(EventDefinitions.KIND_ANNOUNCEMENT).author(PublicKey.parse(pubkey)). custom_tag(SingleLetterTag.lowercase(Alphabet.K), ktags)) events = await client.get_events_of([nip89filter], relay_timeout) diff --git a/nostr_dvm/utils/nostr_utils.py b/nostr_dvm/utils/nostr_utils.py index 646ae79..c92c36f 100644 --- a/nostr_dvm/utils/nostr_utils.py +++ b/nostr_dvm/utils/nostr_utils.py @@ -234,13 +234,15 @@ async def send_event_outbox(event: Event, client, dvm_config) -> EventId: # 5. Fallback, if we couldn't send the event to any relay, we try to send to generic relays instead. if event_id is None: for relay in relays: - await outboxclient.remove_relay(relay) + try: + await outboxclient.remove_relay(relay) + except: + print("Error removing relay: " + relay) relays = await get_main_relays(event, client, dvm_config) for relay in relays: - opts = RelayOptions().ping(False) - await outboxclient.add_relay_with_opts(relay, opts) + await outboxclient.add_relay(relay) try: await outboxclient.connect() event_id = await outboxclient.send_event(event) @@ -256,7 +258,7 @@ async def send_event_outbox(event: Event, client, dvm_config) -> EventId: -async def send_event(event: Event, client: Client, dvm_config, blastr=False): +async def send_event(event: Event, client: Client, dvm_config): try: relays = [] for tag in event.tags(): @@ -275,11 +277,8 @@ async def send_event(event: Event, client: Client, dvm_config, blastr=False): if relay not in dvm_config.RELAY_LIST: await client.add_relay(relay) - #if blastr: - # client.add_relay("wss://nostr.mutinywallet.com") try: event_id = await client.send_event(event) - #event_id = output.id except Exception as e: print(e) event_id = None @@ -288,8 +287,6 @@ async def send_event(event: Event, client: Client, dvm_config, blastr=False): if relay not in dvm_config.RELAY_LIST: if relay not in dvm_config.RELAY_LIST: await client.remove_relay(relay) - #if blastr: - # client.remove_relay("wss://nostr.mutinywallet.com") return event_id except Exception as e: print(e) @@ -360,6 +357,24 @@ def check_and_decrypt_own_tags(event, dvm_config): return event +async def update_profile_lnaddress(private_key, dvm_config, lud16="",): + keys = Keys.parse(private_key) + opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=5)) + .skip_disconnected_relays(True)) + + signer = NostrSigner.keys(keys) + client = Client.with_opts(signer, opts) + for relay in dvm_config.RELAY_LIST: + await client.add_relay(relay) + await client.connect() + + metadata = Metadata() \ + .set_lud16(lud16) \ + .set_nip05(lud16) + + await client.set_metadata(metadata) + + async def update_profile(dvm_config, client, lud16=""): keys = Keys.parse(dvm_config.PRIVATE_KEY) try: diff --git a/nostr_dvm/utils/nut_wallet_utils.py b/nostr_dvm/utils/nut_wallet_utils.py index b2b8685..a909973 100644 --- a/nostr_dvm/utils/nut_wallet_utils.py +++ b/nostr_dvm/utils/nut_wallet_utils.py @@ -6,7 +6,7 @@ from datetime import timedelta import requests from nostr_dvm.utils.database_utils import fetch_user_metadata -from nostr_dvm.utils.definitions import EventDefinitions +from nostr_dvm.utils.definitions import EventDefinitions, relay_timeout, relay_timeout_long from nostr_dvm.utils.dvmconfig import DVMConfig from nostr_dvm.utils.nostr_utils import check_and_set_private_key from nostr_dvm.utils.zap_utils import pay_bolt11_ln_bits, zaprequest @@ -14,6 +14,7 @@ from nostr_sdk import Tag, Keys, nip44_encrypt, nip44_decrypt, Nip44Version, Eve EventId, nip04_decrypt, nip04_encrypt, Options, NostrSigner, PublicKey, init_logger, LogLevel, Metadata from nostr_dvm.utils.print import bcolors + class NutWallet(object): def __init__(self): self.name: str = "NutWallet" @@ -28,7 +29,7 @@ class NutWallet(object): self.a: str = "" self.legacy_encryption: bool = False # Use Nip04 instead of Nip44, for reasons, turn to False ASAP. self.trust_unknown_mints: bool = False - self.missing_balance_strategy: str = "mint" #swap to use existing tokens from other mints (fees!) or mint to mint from lightning + self.missing_balance_strategy: str = "mint" #none to do nothing until manually minted, mint to mint from lightning or swap to use existing tokens from other mints (fees!) (not working yet!) class NutMint(object): @@ -46,7 +47,6 @@ class NutMint(object): return balance - class NutZapWallet: async def client_connect(self, relay_list): @@ -72,8 +72,8 @@ class NutZapWallet: new_nut_wallet.description = description new_nut_wallet.mints = mint_urls new_nut_wallet.relays = relays - new_nut_wallet.d = "wallet" # sha256(str(new_nut_wallet.name + new_nut_wallet.description).encode('utf-8')).hexdigest()[:16] - new_nut_wallet.a = str(Kind(7375).as_u64()) + ":" + keys.public_key().to_hex() + ":" + new_nut_wallet.d + new_nut_wallet.d = "wallet" + new_nut_wallet.a = str(Kind(7375).as_u16()) + ":" + keys.public_key().to_hex() + ":" + new_nut_wallet.d print("Creating Wallet..") send_response_id = await self.create_or_update_nut_wallet_event(new_nut_wallet, client, keys) @@ -120,7 +120,8 @@ class NutZapWallet: nut_wallet = None wallet_filter = Filter().kind(EventDefinitions.KIND_NUT_WALLET).author(keys.public_key()) - wallets = await client.get_events_of([wallet_filter], timedelta(10)) + #relay_timeout = EventSource.relays(timedelta(seconds=10)) + wallets = await client.get_events_of([wallet_filter], relay_timeout_long) if len(wallets) > 0: @@ -194,7 +195,8 @@ class NutZapWallet: # Now all proof events proof_filter = Filter().kind(Kind(7375)).author(keys.public_key()) - proof_events = await client.get_events_of([proof_filter], timedelta(5)) + #relay_timeout = EventSource.relays(timedelta(seconds=5)) + proof_events = await client.get_events_of([proof_filter], relay_timeout) latest_proof_sec = 0 latest_proof_event_id = EventId @@ -247,7 +249,7 @@ class NutZapWallet: nut_proof.amount = proof['amount'] nut_proof.C = proof['C'] nut_mint.proofs.append(nut_proof) - #print(proof) + # print(proof) mints = [x for x in nut_wallet.nutmints if x.mint_url == mint_url] if len(mints) == 0: @@ -347,8 +349,8 @@ class NutZapWallet: "secret": proof['secret'], "C": proof['C'] } - #print("Mint proofs:") - #print(proof) + # print("Mint proofs:") + # print(proof) new_proofs.append(proofjson) old_event_id = mint.previous_event_id @@ -447,7 +449,8 @@ class NutZapWallet: async def fetch_mint_info_event(self, pubkey, client): mint_info_filter = Filter().kind(Kind(10019)).author(PublicKey.parse(pubkey)) - preferences = await client.get_events_of([mint_info_filter], timedelta(5)) + #relay_timeout = EventSource.relays(timedelta(seconds=5)) + preferences = await client.get_events_of([mint_info_filter], relay_timeout) mints = [] relays = [] pubkey = "" @@ -517,21 +520,23 @@ class NutZapWallet: return await self.update_nut_wallet(nut_wallet, [mint_url], client, keys) - - async def handle_low_balance_on_mint(self, nut_wallet, outgoing_mint_url, mint, amount, client, keys): + async def handle_low_balance_on_mint(self, nut_wallet, mint_to_send, mint, amount, client, keys): required_amount = amount - mint.available_balance() + if nut_wallet.missing_balance_strategy == "mint": - await self.mint_cashu(nut_wallet, outgoing_mint_url, client, keys, required_amount) + await self.mint_cashu(nut_wallet, mint_to_send, client, keys, required_amount) elif nut_wallet.missing_balance_strategy == "swap": for nutmint in nut_wallet.nutmints: estimated_fees = 3 - if nutmint.available_balance() > required_amount+estimated_fees: - await self.swap(required_amount, nutmint.mint_url, outgoing_mint_url) + if nutmint.available_balance() > required_amount + estimated_fees: + print(nutmint.mint_url) + await self.swap(required_amount, mint_to_send, nutmint.mint_url, nut_wallet) break - - + else: + print(bcolors.RED + "[" + nut_wallet.name + "] Not enough Balance on Mint, mint some tokens first. " + str( + amount) + " " + nut_wallet.unit + bcolors.ENDC) async def send_nut_zap(self, amount, comment, nut_wallet: NutWallet, zapped_event, zapped_user, client: Client, keys: Keys): @@ -562,7 +567,6 @@ class NutZapWallet: if mint.available_balance() < amount: await self.handle_low_balance_on_mint(nut_wallet, mint_url, mint, amount, client, keys) - # If that's not the case, iterate over the recipents mints and try to mint there. This might be a bit dangerous as not all mints might give cashu, so loss of ln is possible if mint_url is None: if nut_wallet.trust_unknown_mints: @@ -687,39 +691,39 @@ class NutZapWallet: from cashu.wallet.wallet import Wallet from cashu.core.base import Proof from cashu.core.crypto.keys import PrivateKey - - proofs = [] - mint_url = "" - amount = 0 - unit = "sat" - zapped_user = "" - zapped_event = "" - sender = event.author().to_hex() - message = event.content() - for tag in event.tags(): - if tag.as_vec()[0] == "proof": - proof_json = json.loads(tag.as_vec()[1]) - proof = Proof().from_dict(proof_json) - proofs.append(proof) - elif tag.as_vec()[0] == "u": - mint_url = tag.as_vec()[1] - elif tag.as_vec()[0] == "amount": - amount = int(tag.as_vec()[1]) - elif tag.as_vec()[0] == "unit": - unit = tag.as_vec()[1] - elif tag.as_vec()[0] == "p": - zapped_user = tag.as_vec()[1] - elif tag.as_vec()[0] == "e": - zapped_event = tag.as_vec()[1] - - cashu_wallet = await Wallet.with_db( - url=mint_url, - db="db/Receiver", - name="receiver", - ) - cashu_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True) - await cashu_wallet.load_mint() try: + proofs = [] + mint_url = "" + amount = 0 + unit = "sat" + zapped_user = "" + zapped_event = "" + sender = event.author().to_hex() + message = event.content() + for tag in event.tags(): + if tag.as_vec()[0] == "proof": + proof_json = json.loads(tag.as_vec()[1]) + proof = Proof().from_dict(proof_json) + proofs.append(proof) + elif tag.as_vec()[0] == "u": + mint_url = tag.as_vec()[1] + elif tag.as_vec()[0] == "amount": + amount = int(tag.as_vec()[1]) + elif tag.as_vec()[0] == "unit": + unit = tag.as_vec()[1] + elif tag.as_vec()[0] == "p": + zapped_user = tag.as_vec()[1] + elif tag.as_vec()[0] == "e": + zapped_event = tag.as_vec()[1] + + cashu_wallet = await Wallet.with_db( + url=mint_url, + db="db/Receiver", + name="receiver", + ) + cashu_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True) + await cashu_wallet.load_mint() + new_proofs, _ = await cashu_wallet.redeem(proofs) mint = self.get_mint(nut_wallet, mint_url) print(mint.proofs) @@ -735,7 +739,6 @@ class NutZapWallet: print(bcolors.RED + str(e) + bcolors.ENDC) return None, message, sender - async def melt_cashu(self, nut_wallet, mint_url, total_amount, client, keys, lud16=None, npub=None): from cashu.wallet.wallet import Wallet mint = self.get_mint(nut_wallet, mint_url) @@ -774,35 +777,43 @@ class NutZapWallet: total_amount - estimated_fees) + " (Fees: " + str(estimated_fees) + ") " + nut_wallet.unit + bcolors.ENDC) - async def swap(self, amountinsats, outgoing_mint_url, incoming_mint_url): + async def swap(self, amountinsats, incoming_mint_url, outgoing_mint_url, nut_wallet): + #TODO this doesn't seem to work yet. from cashu.wallet.cli.cli_helpers import print_mint_balances from cashu.wallet.wallet import Wallet - # print("Select the mint to swap from:") - # outgoing_wallet = await get_mint_wallet(ctx, force_select=True) + from cashu.core.crypto.keys import PrivateKey + + + outgoing_mint = self.get_mint(nut_wallet, outgoing_mint_url) outgoing_wallet = await Wallet.with_db( url=outgoing_mint_url, - db="db/Sender", - name="sender", + db="db/Cashu", + name="outgoing", ) + outgoing_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True) + await outgoing_wallet.load_mint() + outgoing_wallet.proofs = outgoing_mint.proofs - print("Select the mint to swap to:") - # incoming_wallet = await get_mint_wallet(ctx, force_select=True) + print(outgoing_wallet.available_balance) + + incoming_mint = self.get_mint(nut_wallet, incoming_mint_url) incoming_wallet = await Wallet.with_db( url=incoming_mint_url, - db="db/Receiver", - name="reeciver", + db="db/Cashu", + name="incoming", ) - + incoming_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True) await incoming_wallet.load_mint() - await outgoing_wallet.load_mint() + incoming_wallet.proofs = incoming_mint.proofs + + if incoming_wallet.url == outgoing_wallet.url: raise Exception("mints for swap have to be different") print("Incoming Mint units: " + incoming_wallet.unit.name) - assert amountinsats > 0, "amount is not positive" # request invoice from incoming mint @@ -816,9 +827,13 @@ class NutZapWallet: send_proofs, fees = await outgoing_wallet.select_to_send( outgoing_wallet.proofs, total_amount, set_reserved=True ) - await outgoing_wallet.melt( - send_proofs, invoice.bolt11, quote.fee_reserve, quote.quote - ) + + try: + await outgoing_wallet.melt( + proofs=send_proofs, invoice=invoice.bolt11, fee_reserve_sat=quote.fee_reserve, quote_id=quote.quote + ) + except: + print("anyways..") # mint token in incoming mint await incoming_wallet.mint(amountinsats, id=invoice.id) diff --git a/nostr_dvm/utils/output_utils.py b/nostr_dvm/utils/output_utils.py index 4255bdd..0723b33 100644 --- a/nostr_dvm/utils/output_utils.py +++ b/nostr_dvm/utils/output_utils.py @@ -318,9 +318,9 @@ async def send_job_status_reaction(original_event_id_hex, original_event_author_ if dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print(bcolors.YELLOW + "[" + dvm_config.NIP89.NAME + "]" + " Sent Kind " + str( - EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC) + EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC) elif dvm_config.LOGLEVEL.value >= LogLevel.INFO.value: print(bcolors.YELLOW + "[" + dvm_config.NIP89.NAME + "]" + " Sent Kind " + str( - EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC) + EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC) return reaction_event.as_json() \ No newline at end of file diff --git a/nostr_dvm/utils/reaction_utils.py b/nostr_dvm/utils/reaction_utils.py new file mode 100644 index 0000000..3902250 --- /dev/null +++ b/nostr_dvm/utils/reaction_utils.py @@ -0,0 +1,68 @@ +import asyncio + +from nostr_sdk import Tag, Keys, EventBuilder, Kind, NostrSigner, Client + +from nostr_dvm.utils.dvmconfig import DVMConfig +from nostr_dvm.utils.nostr_utils import send_event, check_and_set_private_key +from nostr_dvm.utils.print import bcolors + + +async def create_reaction(keys, title, dtag): + d_tag = Tag.parse(["d", dtag]) + title_tag = Tag.parse(["title", title]) + + emoji_tags = [] + + # add more if you want + name = "ThugAmy" + url = "https://image.nostr.build/ccc229cbe11f5a13a1cc7fd24e13ac53fc78f287ecce0d9a674807e2e20f6fd5.png" + emoji_tag1 = Tag.parse(["emoji", name, url]) + emoji_tags.append(emoji_tag1) + + + + keys = Keys.parse(keys) + content = "" + event = EventBuilder(Kind(30030), content, [d_tag, title_tag] + emoji_tags).to_event(keys) + + signer = NostrSigner.keys(keys) + client = Client(signer) + # We add the relays we defined above and told our DVM we would want to receive events to. + for relay in DVMConfig().RELAY_LIST: + await client.add_relay(relay) + # We connect the client + await client.connect() + + eventid = await send_event(event, client=client, dvm_config=DVMConfig()) + + print( + bcolors.BLUE + "[" + "Reaction" + "] Announced (" + eventid.id.to_nostr_uri() + + " Hex: " + eventid.id.to_hex() + ")" + bcolors.ENDC) + + +async def delete_reaction(keys, eid: str, dtag: str): + keys = Keys.parse(keys) + e_tag = Tag.parse(["e", eid]) + a_tag = Tag.parse( + ["a", "30030:" + keys.public_key().to_hex() + ":" + dtag]) + event = EventBuilder(Kind(5), "", [e_tag, a_tag]).to_event(keys) + + signer = NostrSigner.keys(keys) + client = Client(signer) + # We add the relays we defined above and told our DVM we would want to receive events to. + for relay in DVMConfig().RELAY_LIST: + await client.add_relay(relay) + # We connect the client + await client.connect() + + eventid = await send_event(event, client, DVMConfig()) + print( + bcolors.BLUE + "[" + "Reaction" + "] deleted (" + eventid.id.to_nostr_uri() + + " Hex: " + eventid.id.to_hex() + ")" + bcolors.ENDC) + + +keys = check_and_set_private_key("test_client") +eventid = "da05cefc512ad43363f84131343f5d2a80303ea3b9368b9ad7f010e07db37d90" + +asyncio.run(create_reaction(keys=keys, title="ThugAmy", dtag="ThugAmy")) +#asyncio.run(delete_reaction(keys=keys, eid=eventid, dtag="ThugAmy")) \ No newline at end of file diff --git a/nostr_dvm/utils/wot_utils.py b/nostr_dvm/utils/wot_utils.py index 0034c57..69bf4cc 100644 --- a/nostr_dvm/utils/wot_utils.py +++ b/nostr_dvm/utils/wot_utils.py @@ -93,7 +93,7 @@ async def get_following(pks, max_time_request=10, newer_than_time=None): return following -async def build_network_from(seed_pks, depth=2, max_batch=500, max_time_request=10): +async def build_wot_network(seed_pks, depth=2, max_batch=500, max_time_request=10): if not seed_pks: print('Error: seed_pks cannot be empty') return @@ -636,5 +636,5 @@ async def convert_index_to_hex(graph, index_map, show_results_num): def test(): # WARNING, DEPENDING ON DEPTH THIS TAKES LONG user = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d' - index_map, network_graph = asyncio.run(build_network_from(user, depth=2, max_batch=500, max_time_request=10)) + index_map, network_graph = asyncio.run(build_wot_network(user, depth=2, max_batch=500, max_time_request=10)) save_network(index_map, network_graph, user) diff --git a/nostr_dvm/utils/zap_utils.py b/nostr_dvm/utils/zap_utils.py index debe63c..bd57894 100644 --- a/nostr_dvm/utils/zap_utils.py +++ b/nostr_dvm/utils/zap_utils.py @@ -1,6 +1,8 @@ # LIGHTNING/ZAP FUNCTIONS import json import os +import random +import string import urllib.parse from pathlib import Path @@ -12,7 +14,8 @@ from bech32 import bech32_decode, convertbits, bech32_encode from nostr_sdk import PublicKey, SecretKey, Event, EventBuilder, Tag, Keys, generate_shared_key, Kind, \ Timestamp -from nostr_dvm.utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags +from nostr_dvm.utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags, update_profile, \ + update_profile_lnaddress from hashlib import sha256 import dotenv @@ -51,7 +54,7 @@ async def parse_zap_event_tags(zap_event, keys, name, client, config): keys.secret_key(), zap_request_event.author()) decrypted_private_event = Event.from_json(decrypted_content) - if decrypted_private_event.kind().as_u64() == 9733: + if decrypted_private_event.kind().as_u16() == 9733: sender = decrypted_private_event.author().to_hex() message = decrypted_private_event.content() # if message != "": @@ -132,11 +135,11 @@ def create_bolt11_lud16(lud16, amount): def create_lnbits_account(name): - if os.getenv("LNBITS_ADMIN_ID") is None or os.getenv("LNBITS_ADMIN_ID") == "": + if os.getenv("LNBITS_WALLET_ID") is None or os.getenv("LNBITS_WALLET_ID") == "": print("No admin id set, no wallet created.") return "", "", "", "", "failed" data = { - 'admin_id': os.getenv("LNBITS_ADMIN_ID"), + 'admin_id': os.getenv("LNBITS_WALLET_ID"), 'wallet_name': name, 'user_name': name, } @@ -347,20 +350,26 @@ def get_price_per_sat(currency): return price_currency_per_sat +def randomword(length): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + +def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain, newname = " ", currentname=" "): + + if newname == " ": + newname = identifier -def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain): print(os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper())) data = { - 'name': identifier, + 'name': newname, 'domain': nostdressdomain, 'kind': "lnbits", 'host': os.getenv("LNBITS_HOST"), 'key': os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper()), 'pin': pin, 'npub': npub, - 'currentname': " " + 'currentname': currentname } - try: url = "https://" + nostdressdomain + "/api/easy/" res = requests.post(url, data=data) @@ -368,10 +377,22 @@ def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain): obj = json.loads(res.text) if obj.get("ok"): - return identifier + "@" + nostdressdomain, obj["pin"] + return data["name"] + "@" + nostdressdomain, obj["pin"] + except Exception as e: - print(e) - return "", "" + print("Creating random name..") + data["name"] = data["name"] + "_" + randomword(10) + try: + url = "https://" + nostdressdomain + "/api/easy/" + res = requests.post(url, data=data) + print(res.text) + obj = json.loads(res.text) + + if obj.get("ok"): + return data["name"] + "@" + nostdressdomain, obj["pin"] + + except Exception as e: + return "", "" def check_and_set_ln_bits_keys(identifier, npub): @@ -379,26 +400,36 @@ def check_and_set_ln_bits_keys(identifier, npub): invoicekey, adminkey, walletid, userid, success = create_lnbits_account(identifier) add_key_to_env_file("LNBITS_INVOICE_KEY_" + identifier.upper(), invoicekey) add_key_to_env_file("LNBITS_ADMIN_KEY_" + identifier.upper(), adminkey) - add_key_to_env_file("LNBITS_USER_ID_" + identifier.upper(), userid) add_key_to_env_file("LNBITS_WALLET_ID_" + identifier.upper(), userid) lnaddress = "" pin = "" if os.getenv("NOSTDRESS_DOMAIN") and success != "failed": print(os.getenv("NOSTDRESS_DOMAIN")) - lnaddress, pin = make_ln_address_nostdress(identifier, npub, " ", os.getenv("NOSTDRESS_DOMAIN")) + lnaddress, pin = make_ln_address_nostdress(identifier, npub, " ", os.getenv("NOSTDRESS_DOMAIN"), identifier) add_key_to_env_file("LNADDRESS_" + identifier.upper(), lnaddress) add_key_to_env_file("LNADDRESS_PIN_" + identifier.upper(), pin) - return invoicekey, adminkey, userid, walletid, lnaddress + return invoicekey, adminkey, walletid, lnaddress else: return (os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper()), os.getenv("LNBITS_ADMIN_KEY_" + identifier.upper()), - os.getenv("LNBITS_USER_ID_" + identifier.upper()), os.getenv("LNBITS_WALLET_ID_" + identifier.upper()), os.getenv("LNADDRESS_" + identifier.upper())) +async def change_ln_address(identifier, new_identifier, dvm_config, updateprofile=False): + previous_identifier = os.getenv("LNADDRESS_" + identifier.upper()).split("@")[0] + pin = os.getenv("LNADDRESS_PIN_" + identifier.upper()) + npub = Keys.parse(os.getenv("DVM_PRIVATE_KEY_" + identifier.upper())).public_key().to_hex() + lnaddress, pin = make_ln_address_nostdress(identifier, npub, pin, os.getenv("NOSTDRESS_DOMAIN"), new_identifier, currentname=previous_identifier) + add_key_to_env_file("LNADDRESS_" + identifier.upper(), lnaddress) + add_key_to_env_file("LNADDRESS_PIN_" + identifier.upper(), pin) + print("changed lnaddress") + if updateprofile: + private_key = os.getenv("DVM_PRIVATE_KEY_" + identifier.upper()) + await update_profile_lnaddress(private_key, dvm_config, lud16=lnaddress) + def add_key_to_env_file(value, oskey): env_path = Path('.env') if env_path.is_file(): diff --git a/setup.py b/setup.py index a9c411f..095675c 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -VERSION = '0.8.20' +VERSION = '0.8.21' DESCRIPTION = 'A framework to build and run Nostr NIP90 Data Vending Machines' LONG_DESCRIPTION = ('A framework to build and run Nostr NIP90 Data Vending Machines. See the github repository for more information') @@ -14,7 +14,7 @@ setup( long_description=LONG_DESCRIPTION, packages=find_packages(include=['nostr_dvm', 'nostr_dvm.*']), - install_requires=["nostr-sdk==0.34.0", + install_requires=["nostr-sdk==0.35.0", "bech32==1.2.0", "pycryptodome==3.20.0", "yt-dlp==2024.5.27", diff --git a/tests/db.py b/tests/db.py index 58947ca..24e3dd0 100644 --- a/tests/db.py +++ b/tests/db.py @@ -8,7 +8,7 @@ print(keys.public_key().to_bech32()) async def reconcile_db(): # Create/open SQLite database - database = await NostrDatabase.sqlite("nostr.db") + database = NostrDatabase.lmdb("nostr.db") # NOT AVAILABLE ON WINDOWS AT THE MOMENT! # Create/open nostrdb database @@ -28,7 +28,7 @@ async def reconcile_db(): await do_some_work() async def do_some_work(): - database = await NostrDatabase.sqlite("nostr.db") + database = NostrDatabase.lmdb("nostr.db") f = Filter().author(keys.public_key()).limit(10) events = await database.query([f]) diff --git a/tests/discovery.py b/tests/discovery.py index fc6c595..8147d25 100644 --- a/tests/discovery.py +++ b/tests/discovery.py @@ -37,12 +37,12 @@ rebroadcast_NIP65_Relay_List = True update_profile = False global_update_rate = 180 # set this high on first sync so db can fully sync before another process trys to. -use_logger = False +use_logger = True log_level = LogLevel.INFO +RECONCILE_DB_RELAY_LIST = [ "wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.oxtr.dev"] -RECONCILE_DB_RELAY_LIST = [ "wss://relay.nostr.net", "wss://relay.damus.io", "wss://nostr.oxtr.dev"] RELAY_LIST = ["wss://relay.primal.net", "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.net" @@ -63,6 +63,7 @@ def build_db_scheduler(name, identifier, admin_config, options, image, descripti dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.DATABASE = database + dvm_config.WOT_FILTERING = True # Activate these to use a subscription based model instead # dvm_config.SUBSCRIPTION_REQUIRED = True @@ -147,11 +148,9 @@ def build_example_nostrband(name, identifier, admin_config, image, about, custom dvm_config.USE_OWN_VENV = False dvm_config.CUSTOM_PROCESSING_MESSAGE = custom_processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.LOGLEVEL = LogLevel.INFO - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -288,11 +287,9 @@ def build_example_topic(name, identifier, admin_config, options, image, descript dvm_config.LOGLEVEL = LogLevel.INFO dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.DATABASE = database - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -326,21 +323,15 @@ def build_example_topic(name, identifier, admin_config, options, image, descript def build_example_popular(name, identifier, admin_config, options, image, cost=0, update_rate=180, processing_msg=None, update_db=True, database=None): dvm_config = build_default_config(identifier) - dvm_config.USE_OWN_VENV = False dvm_config.LOGLEVEL = LogLevel.INFO - # dvm_config.SHOWLOG = True dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 10 minutes dvm_config.UPDATE_DATABASE = update_db dvm_config.FIX_COST = cost - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - #"wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.DATABASE = database - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -381,10 +372,8 @@ def build_example_popular_followers(name, identifier, admin_config, options, ima dvm_config.FIX_COST = cost dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -427,11 +416,12 @@ def build_example_popular_non_followers(name, identifier, admin_config, options, dvm_config.UPDATE_DATABASE = update_db dvm_config.DATABASE = database # Activate these to use a subscription based model instead - dvm_config.FIX_COST = cost + dvm_config.FIX_COST = 10 dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST - dvm_config.SUBSCRIPTION_REQUIRED = True + dvm_config.SUBSCRIPTION_REQUIRED = False admin_config.LUD16 = dvm_config.LN_ADDRESS admin_config.REBROADCAST_NIP88 = False #admin_config.REBROADCAST_NIP89 = True @@ -446,8 +436,8 @@ def build_example_popular_non_followers(name, identifier, admin_config, options, "lud16": dvm_config.LN_ADDRESS, "encryptionSupported": True, "cashuAccepted": True, - "subscription": True, - "personalized": False, + "subscription": False, + "personalized": True, "nip90Params": { "max_results": { "required": False, @@ -479,7 +469,7 @@ def build_example_popular_non_followers(name, identifier, admin_config, options, admin_config.PRIVKEY = dvm_config.PRIVATE_KEY return DicoverContentCurrentlyPopularNonFollowers(name=name, dvm_config=dvm_config, nip89config=nip89config, - nip88config=nip88config, + #nip88config=nip88config, admin_config=admin_config, options=options) @@ -497,11 +487,9 @@ def build_example_top_zapped(name, identifier, admin_config, options, image, cos dvm_config.FIX_COST = cost dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.DATABASE = database - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -544,17 +532,13 @@ def build_example_mostr(name, identifier, admin_config, options, image, cost=0, # dvm_config.SHOWLOG = True dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 10 minutes dvm_config.UPDATE_DATABASE = update_db + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.RECONCILE_DB_RELAY_LIST = ["wss://nfrelay.app/?user=activitypub"] dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - # "wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -594,12 +578,9 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c dvm_config.UPDATE_DATABASE = False dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost - dvm_config.RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.primal.net"] - # "wss://relay.nostr.net"] + dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST + dvm_config.RELAY_LIST = RELAY_LIST dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -630,7 +611,7 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c async def init_db(database): - return await NostrDatabase.sqlite(database) + return NostrDatabase.lmdb(database) def playground(): @@ -640,7 +621,7 @@ def playground(): admin_config_db_scheduler= AdminConfig() options_animal = { "db_name": main_db, - "db_since": 12 * 60 * 60, # 48h since gmt, + "db_since": 48 * 60 * 60, # 48h since gmt, "personalized": False, "logger": False} image = "" @@ -831,6 +812,34 @@ def playground(): update_db=True) discovery_mostr.run() + # Popular Garden&Plants + admin_config_asknostr = AdminConfig() + admin_config_asknostr.REBROADCAST_NIP89 =rebroadcast_NIP89 + admin_config_asknostr.REBROADCAST_NIP65_RELAY_LIST = rebroadcast_NIP65_Relay_List + admin_config_asknostr.UPDATE_PROFILE = update_profile + options_plants = { + "search_list": ["#asknostr"], + "avoid_list": [], + "db_name": "db/nostr_recent_notes.db", + "db_since": 24 * 60 * 60, # 12h since gmt + "personalized": False, + "logger": False} + + image = "https://i.nostr.build/vIixmuRacIhULsrP.png" + description = "I show popular questions #asknostr" + custom_processing_msg = ["Finding the best notes for you.. #asknostr"] + update_db = False + cost = 0 + discovery_asknostr = build_example_topic("Popular on #asknostr", "discovery_content_asknostr", + admin_config_asknostr, options_plants, + image=image, + description=description, + update_rate=global_update_rate, + cost=cost, + processing_msg=custom_processing_msg, + update_db=update_db, + database=DATABASE) + discovery_asknostr.run() # Popular Animals (Fluffy frens) admin_config_animals = AdminConfig() @@ -1049,7 +1058,7 @@ def playground(): options_global_popular = { "db_name": "db/nostr_recent_notes.db", - "db_since": 60 * 60, # 1h since gmt, + "db_since": 60 * 60 * 1, # 1h since gmt, } cost = 0 #image = "https://image.nostr.build/b29b6ec4bf9b6184f69d33cb44862db0d90a2dd9a506532e7ba5698af7d36210.jpg" @@ -1119,7 +1128,9 @@ def playground(): subscription_config = DVMConfig() subscription_config.PRIVATE_KEY = check_and_set_private_key("dvm_subscription") npub = Keys.parse(subscription_config.PRIVATE_KEY).public_key().to_bech32() - invoice_key, admin_key, wallet_id, user_id, lnaddress = check_and_set_ln_bits_keys("dvm_subscription", npub) + invoice_key, admin_key, wallet_id, lnaddress = check_and_set_ln_bits_keys("dvm_subscription", npub) + subscription_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST + subscription_config.RELAY_LIST = RELAY_LIST subscription_config.LNBITS_INVOICE_KEY = invoice_key subscription_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back subscription_config.LNBITS_URL = os.getenv("LNBITS_HOST") diff --git a/tests/discovery_gallery.py b/tests/discovery_gallery.py index af11f72..11f9d76 100644 --- a/tests/discovery_gallery.py +++ b/tests/discovery_gallery.py @@ -33,12 +33,7 @@ def build_example_gallery(name, identifier, admin_config, options, image, cost=0 dvm_config.UPDATE_DATABASE = update_db dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - # "wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 diff --git a/tests/discovery_mostr.py b/tests/discovery_mostr.py index 4a7092d..446d570 100644 --- a/tests/discovery_mostr.py +++ b/tests/discovery_mostr.py @@ -37,12 +37,7 @@ def build_example_mostr(name, identifier, admin_config, options, image, cost=0, dvm_config.RECONCILE_DB_RELAY_LIST = ["wss://nfrelay.app/?user=activitypub"] dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - # "wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 diff --git a/tests/discovery_one_per_follow.py b/tests/discovery_one_per_follow.py index 63840fd..1dce146 100644 --- a/tests/discovery_one_per_follow.py +++ b/tests/discovery_one_per_follow.py @@ -38,11 +38,7 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost dvm_config.RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.primal.net"] - # "wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 diff --git a/tests/discovery_people.py b/tests/discovery_people.py index 14b69fb..0d9f014 100644 --- a/tests/discovery_people.py +++ b/tests/discovery_people.py @@ -42,12 +42,7 @@ def build_example_wot(name, identifier, admin_config, options, image, cost=0, up #dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - #"wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 diff --git a/tests/discovery_test.py b/tests/discovery_test.py index e3d4884..c852d83 100644 --- a/tests/discovery_test.py +++ b/tests/discovery_test.py @@ -149,9 +149,6 @@ def build_example_nostrband(name, identifier, admin_config, image, about, custom dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST dvm_config.LOGLEVEL = LogLevel.INFO - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -289,9 +286,6 @@ def build_example_topic(name, identifier, admin_config, options, image, descript dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -331,14 +325,9 @@ def build_example_popular(name, identifier, admin_config, options, image, cost=0 dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 10 minutes dvm_config.UPDATE_DATABASE = update_db dvm_config.FIX_COST = cost - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - #"wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -380,9 +369,6 @@ def build_example_popular_followers(name, identifier, admin_config, options, ima dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -495,9 +481,6 @@ def build_example_top_zapped(name, identifier, admin_config, options, image, cos dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST dvm_config.RELAY_LIST = RELAY_LIST - #dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -545,12 +528,7 @@ def build_example_mostr(name, identifier, admin_config, options, image, cost=0, dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", - # "wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 @@ -591,11 +569,7 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c dvm_config.LOGLEVEL = LogLevel.DEBUG dvm_config.FIX_COST = cost dvm_config.RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.primal.net"] - # "wss://relay.nostr.net"] dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg - # dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", - # "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg" - # ] admin_config.LUD16 = dvm_config.LN_ADDRESS # Add NIP89 diff --git a/tests/generic_dvm_autotopic_feed.py b/tests/generic_dvm_autotopic_feed.py index 231ba4f..8a15665 100644 --- a/tests/generic_dvm_autotopic_feed.py +++ b/tests/generic_dvm_autotopic_feed.py @@ -8,6 +8,7 @@ from duck_chat import ModelType from nostr_sdk import Kind, Filter, PublicKey, SecretKey, Keys, NostrSigner, RelayLimits, Options, Client, Tag, \ LogLevel, Timestamp, NostrDatabase + from nostr_dvm.tasks.generic_dvm import GenericDVM from nostr_dvm.utils import definitions from nostr_dvm.utils.admin_utils import AdminConfig @@ -50,13 +51,13 @@ def playground(announce=False): dvm_config = build_default_config(identifier) dvm_config.KIND = Kind(kind) # Manually set the Kind Number (see data-vending-machines.org) dvm_config.CUSTOM_PROCESSING_MESSAGE = "Creating a personalized feed based on the topics you write about. This might take a moment." - dvm_config.FIX_COST = 10 + dvm_config.FIX_COST = 0 - admin_config.DELETE_NIP89 = True - admin_config.POW = True - admin_config.EVENTID = "5322b731230cf8961f8403d025722a381af9b012b5d5f6dcc09f88e160f4e4ff" - admin_config.PRIVKEY = dvm_config.PRIVATE_KEY + #admin_config.DELETE_NIP89 = True + #admin_config.POW = True + #admin_config.EVENTID = "5322b731230cf8961f8403d025722a381af9b012b5d5f6dcc09f88e160f4e4ff" + #admin_config.PRIVKEY = dvm_config.PRIVATE_KEY # Add NIP89 @@ -83,12 +84,10 @@ def playground(announce=False): admin_config=admin_config, options=options) - async def process_request(request_form, prompt): + async def process_request(options, prompt): result = "" try: from duck_chat import DuckChat - options = dvm.set_options(request_form) - result = "" async with DuckChat(model=ModelType.GPT4o) as chat: query = prompt result = await chat.ask_question(query) @@ -119,7 +118,8 @@ def playground(announce=False): await cli.connect() #pip install -U https://github.com/mrgick/duckduckgo-chat-ai/archive/master.zip - author = PublicKey.parse(options["user"]) + author = PublicKey.parse(options["request_event_author"]) + print(options["request_event_author"]) filterauth = Filter().kind(definitions.EventDefinitions.KIND_NOTE).author(author).limit(100) evts = await cli.get_events_of([filterauth], relay_timeout) @@ -128,12 +128,14 @@ def playground(announce=False): for event in evts: text = text + event.content() + ";" + text = text[:6000] prompt = "Only reply with the result. Here is a list of notes, seperated by ;. Find the 20 most important keywords and return them by a comma seperated list: " + text #loop = asyncio.get_running_loop() - result = asyncio.run(process_request(request_form, prompt)) + result = await process_request(options, prompt) + print(result) content = "I identified these as your topics:\n\n"+result.replace(",", ", ") + "\n\nProcessing, just a few more seconds..." await send_job_status_reaction(original_event_id_hex=dvm.options["request_event_id"], original_event_author_hex=dvm.options["request_event_author"], client=cli, dvm_config=dvm_config, content=content) @@ -144,11 +146,11 @@ def playground(announce=False): # result = await chat.ask_question(query) # result = result.replace(", ", ",") # print(result) - result = "" + from types import SimpleNamespace ns = SimpleNamespace() - database = await NostrDatabase.sqlite("db/nostr_recent_notes.db") + database = NostrDatabase.lmdb("db/nostr_recent_notes.db") timestamp_since = Timestamp.now().as_secs() - since since = Timestamp.from_secs(timestamp_since) @@ -159,7 +161,7 @@ def playground(announce=False): if dvm.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value: print("[" + dvm.dvm_config.NIP89.NAME + "] Considering " + str(len(events)) + " Events") ns.finallist = {} - search_list = result.split('') + #search_list = result.split(',') for event in events: #if all(ele in event.content().lower() for ele in []): diff --git a/tests/pagerank/subrank_demo_notebook.ipynb b/tests/pagerank/subrank_demo_notebook.ipynb index 2e83484..16b774e 100644 --- a/tests/pagerank/subrank_demo_notebook.ipynb +++ b/tests/pagerank/subrank_demo_notebook.ipynb @@ -27,7 +27,7 @@ "outputs": [], "source": [ "# Imports\n", - "from nostr_dvm.utils.wot_utils import build_network_from, save_network, load_network, get_mc_pagerank, get_subrank, get_metadata, print_results\n", + "from nostr_dvm.utils.wot_utils import build_wot_network, save_network, load_network, get_mc_pagerank, get_subrank, get_metadata, print_results\n", "import time\n", "import networkx as nx\n", "import random\n", @@ -85,7 +85,7 @@ } ], "source": [ - "index_map, G = await build_network_from(user, depth=2, max_batch=500, max_time_request=10)\n", + "index_map, G = await build_wot_network(user, depth=2, max_batch=500, max_time_request=10)\n", "if use_files:\n", " save_network(index_map, G, user)" ], diff --git a/tests/test_dvm_client.py b/tests/test_dvm_client.py index 50a728a..12cace4 100644 --- a/tests/test_dvm_client.py +++ b/tests/test_dvm_client.py @@ -224,7 +224,7 @@ async def nostr_client_custom_discovery(user, ptag): pTag = Tag.parse(["p", ptag]) - tags = [relaysTag, alttag, paramTag, pTag, paramTagSearch, paramTagMust, paramTagAvoid] + tags = [relaysTag, alttag, paramTag, pTag]# paramTagSearch, paramTagMust, paramTagAvoid] event = EventBuilder(EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY, str("Give me content"), tags).to_event(keys) @@ -234,7 +234,7 @@ async def nostr_client_custom_discovery(user, ptag): for relay in relay_list: await client.add_relay(relay) ropts = RelayOptions().ping(False) - await client.add_relay_with_opts("wss://nostr.band", ropts) + await client.connect() config = DVMConfig await send_event(event, client=client, dvm_config=config) @@ -444,18 +444,18 @@ async def nostr_client(): # await nostr_client_test_translation("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20) #await nostr_client_test_image("a beautiful purple ostrich watching the sunset, eating a cashew nut") - #await nostr_client_custom_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "7a63849b684d90c0de983492578b12e147e56f5d79ed6585cc64e5aa8a122744") + await nostr_client_custom_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "7240284b84951cfedbc20fce26f0e3f0a36da3e9c1be85d7a06965f0d4fe25fb") #"a018ba05af400b52772e33162d8326fca4a167fe7b6d3cd2382e14cac2af6841" - #await nostr_client_duckduck_test(PublicKey.parse("aa8ab5b774d47e7b29a985dd739cfdcccf93451678bf7977ba1b2e094ecd8b30").to_hex() , "How do i create a dockerfile for python 3.12") - await nostr_client_flux_schnell("d57f1efb7582f58cade6f482d53eefa998d8082711b996aae3dc5f5527cbdd6e" , "topics") + # await nostr_client_duckduck_test(PublicKey.parse("7a63849b684d90c0de983492578b12e147e56f5d79ed6585cc64e5aa8a122744").to_hex() , "How do i create a dockerfile for python 3.12") + #await nostr_client_flux_schnell("d57f1efb7582f58cade6f482d53eefa998d8082711b996aae3dc5f5527cbdd6e" , "topics") # await nostr_client_test_search_profile("dontbelieve") #wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"] - # await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7") + #await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7") # await nostr_client_test_discovery_gallery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "4add3944eb596a27a650f9b954f5ed8dfefeec6ca50473605b0fbb058dd11306") - # await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", - # "2cf10ff849d2769b2b021bd93a0270d03eecfd14126d07f94c6ca2269cb3f3b1") + #await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", + # "7a63849b684d90c0de983492578b12e147e56f5d79ed6585cc64e5aa8a122744") # await nostr_client_test_censor_filter(wot) # await nostr_client_test_inactive_filter("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64") @@ -466,6 +466,8 @@ async def nostr_client(): # await nostr_client_test_image_private("a beautiful ostrich watching the sunset") nutzap_wallet = NutZapWallet() + + nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys) #dangerous, dont use this, except your wallet is messed up. delete = False @@ -486,7 +488,7 @@ async def nostr_client(): print( bcolors.BLUE + f"Received new event from {relay_url}: {event.as_json()}" + bcolors.ENDC) - if event.kind().as_u64() == 7000: + if event.kind().as_u16() == 7000: print("[Nostr Client]: " + event.as_json()) amount_sats = 0 status = "" @@ -512,15 +514,15 @@ async def nostr_client(): keys) - elif 6000 < event.kind().as_u64() < 6999: + elif 6000 < event.kind().as_u16() < 6999: print("[Nostr Client]: " + event.as_json()) print("[Nostr Client]: " + event.content()) - elif event.kind().as_u64() == 4: + elif event.kind().as_u16() == 4: dec_text = nip04_decrypt(sk, event.author(), event.content()) print("[Nostr Client]: " + f"Received new msg: {dec_text}") - elif event.kind().as_u64() == 9735: + elif event.kind().as_u16() == 9735: print("[Nostr Client]: " + f"Received new zap:") print(event.as_json()) diff --git a/tests/test_events.py b/tests/test_events.py index c65e58d..27fd52c 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -163,4 +163,3 @@ if __name__ == '__main__': asyncio.run(test_gallery()) - # works diff --git a/tests/wot.py b/tests/wot.py index fb059c7..43b7259 100644 --- a/tests/wot.py +++ b/tests/wot.py @@ -57,7 +57,7 @@ async def sync_db(): opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=5))) keys = Keys.parse("nsec1zmzllu40a7mr7ztl78uwfwslnp0pn0pww868adl05x52d4la237s6m8qfj") signer = NostrSigner.keys(keys) - database = await NostrDatabase.sqlite("db/nostr_followlists.db") + database = NostrDatabase.lmdb("db/nostr_followlists.db") cli = ClientBuilder().signer(signer).database(database).opts(opts).build() await cli.add_relay("wss://relay.damus.io") # TODO ADD MORE @@ -86,7 +86,7 @@ async def analyse_users(user_ids=None): print(npub) print(e) - database = await NostrDatabase.sqlite("db/nostr_followlists.db") + database = NostrDatabase.lmdb("db/nostr_followlists.db") followers_filter = Filter().authors(user_keys).kind(Kind(3)) followers = await database.query([followers_filter]) allfriends = [] diff --git a/tutorials/01_preparations.ipynb b/tutorials/01_preparations.ipynb new file mode 100644 index 0000000..6c6e6f4 --- /dev/null +++ b/tutorials/01_preparations.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": "", + "id": "1865ec9f54c7b22e" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "# Hello there, fellow DVM enthusiast\n", + "This is the first of a series of tutorials on how to use nostr-dvm.\n", + "\n", + "Before we start, we have to make sure you have a .env file in the working directory.\n", + "\n", + "In order to make sure you have it and to get you started quickly, here is a small function to create an inital .env file for you. Just make sure you set the parameters in the following field:" + ], + "id": "e1f5dcfcad069462" + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "lnbits_admin_key = \"\" #TODO set your key here\n", + "lnbits_wallet_id = \"\" #TODO set your key here\n", + "lnbits_host= \"https://demo.lnbits.com\" #TODO you can use demo.lnbits.com, but rather use your own instance\n", + "nostdress_domain = \"nostrdvm.com\" #TODO use your own nostdress instance, or use the default one" + ], + "id": "2e71e7aa2ebdac50" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "LNBITS_HOST is an Lnbits instance. We'll use Lnbits to generate wallets for each DVM automatically.\n", + "You can use the demo server, but it is highly recommended to move to your own instance,\n", + "or an instance of someone you trust.\n", + "\n", + "LNBITS_ADMIN_KEY and LNBITS_WALLET_ID are the API keys you get from Lnbits. You might also want to activate the User Manager module in LNBits.\n", + "\n", + "NOSTDRESS_DOMAIN: You can run your own instance of Nostdress https://github.com/believethehype/nostdress, so your DVMs get their own unique zapable lightning address, or you can use the default one. Make sure that the used identifier is unique, as otherwise the lnaddress will not work, if the nostdress acount already exists\n", + "\n", + " By the way, if that's not your kind of thing, you don't have to set an Lnbits account. If your DVM should receive zaps somehow, make sure you manually set a valid, zappable lightning address in its profile, once we created it. In this case leave the lnbits_admin_key and lnbits_wallet_id empty\n" + ], + "id": "bb26e7180f1d1f0" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "The following script will make an initial .env file with the parameters set in the field above, if it doesn't exist yet.\n", + "id": "f6b316e1cace5adc" + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2024-09-16T13:36:56.451990Z", + "start_time": "2024-09-16T13:36:56.448752Z" + } + }, + "cell_type": "code", + "source": [ + "config = \"LNBITS_ADMIN_KEY = \\\"\"+lnbits_admin_key+\"\\\"\\nLNBITS_WALLET_ID = \\\"\"+ lnbits_wallet_id +\"\\\"\\nLNBITS_HOST = \\\"\"+ lnbits_host + \"\\\"\\nNOSTDRESS_DOMAIN = \\\"\" + nostdress_domain + \"\\\"\"\n", + "\n", + "import os.path\n", + "if not os.path.isfile(\".env\"):\n", + " with open(\".env\", \"w\") as f: # Opens file and casts as f \n", + " f.write(config) # Writing\n", + " # File closed automatically\n", + "else:\n", + " print(\".env file already exists\")" + ], + "id": "a3e16002be388215", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".env file already exists\n" + ] + } + ], + "execution_count": 8 + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "Cool. That's it for the prepartion. Once the .env file exists we won't overwrite it here again. You can open the .env file in this folder (maybe refresh your IDE) and check if everything worked as expected", + "id": "f4e3ed80abdad23a" + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": "", + "id": "d32b5abd00600e64" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/02_run_dvm.py b/tutorials/02_run_dvm.py new file mode 100644 index 0000000..0d82a6e --- /dev/null +++ b/tutorials/02_run_dvm.py @@ -0,0 +1,82 @@ +# Welcome back, this time we don't use a notebook, but we run an actual Python Script. +# We use a GenericDVM kind to start with. Now what's this? We have many predefined tasks in the task folder, but +# the genericDVM gives you some control for simple manipulation without caring about the tasks. Important is that +# we set the Kind of the GenericDVM. In Line 28 you see that we give it Kind 5050 (Text generation). +# On https://www.data-vending-machines.org/ there's an overview on all current kinds. +# On https://github.com/nostr-protocol/data-vending-machines/ you can make a PR for your own kind, if you come up with one later. +# Check the run_dvm function for more explanations +import asyncio +import os +from pathlib import Path + +import dotenv + +from nostr_dvm.tasks.generic_dvm import GenericDVM +from nostr_sdk import Kind, Keys +from nostr_dvm.utils.admin_utils import AdminConfig +from nostr_dvm.utils.dvmconfig import build_default_config, DVMConfig +from nostr_dvm.utils.nip89_utils import NIP89Config +from nostr_dvm.utils.zap_utils import change_ln_address + + +def run_dvm(identifier): + # You have seen this one before, we did this in tutorial 2. This function will either create or load. the parameters of our DVMConfig. + # Make sure you replace the identifier down in the main function with the one you generated in tutorial 2, or we will create a new one here. + dvm_config = build_default_config(identifier) + # As we will use a GenericDVM we need to give it a kind. Here we use kind 5050 (Text Generation) as we want to reply with some simple text. + # There is a bunch of predefined DVMs in tasks that already have a kind set, but as we use the genericDVM we have to manually set it here. + dvm_config.KIND = Kind(5050) + + # We can set options that we can later use in our process function. They are stored in a simple JSON + options = { + "some_option": "#RunDVM", + } + # We give the DVM a human readable name + name = "My very first DVM" + # Next we initalize a GenericDVM with the name and the dvm_config and the options we just created, as well as + # an empty AdminConfig() and NIP89Config(). We will check these out in later tutorials, so don't worry about them now. + dvm = GenericDVM(name=name, dvm_config=dvm_config, options=options, + nip89config=NIP89Config(), admin_config=AdminConfig()) + + + # Normally we would define the dvm interface as we do in the tasks folder (we will do it later in the tutorials as well, + # but here is a small hack to quickly manipulate what our dvm will do. + async def process(request_form): + # First we always parse the options from our request_form, that is build internally in the create_request_from_nostr_event function. + options = dvm.set_options(request_form) + # We build our result we are giving back from some text + result = "The result of the DVM is: " + # and the option we defined above and handed over to our DVM (some_option) + result += options["some_option"] + print(result) + # Then we simply return the result + return result + + dvm.process = process # now we simply overwrite our DVM's process function with the one we defined here. + # and finally we run the DVM #RunDVM + dvm.run() + + # When the DVM is running you should see a blue message with the name and the public key in bech32 and hex format. + # For the next exercise, copy the Hex key, and let this DVM run, you will need it :) + + +if __name__ == '__main__': + #We open the .env file we created before. + env_path = Path('.env') + if not env_path.is_file(): + with open('.env', 'w') as f: + print("Writing new .env file") + f.write('') + if env_path.is_file(): + print(f'loading environment from {env_path.resolve()}') + dotenv.load_dotenv(env_path, verbose=True, override=True) + else: + raise FileNotFoundError(f'.env file not found at {env_path} ') + + # Replace the identifier with the one from the last notebook, or a new dvmconfig will be stored + identifier = "tutorial01" + + # psst, you can change your lightning address here: + #asyncio.run(change_ln_address(identifier, "test", DVMConfig(), True)) + + run_dvm(identifier) diff --git a/tutorials/03_client.py b/tutorials/03_client.py new file mode 100644 index 0000000..8e462d5 --- /dev/null +++ b/tutorials/03_client.py @@ -0,0 +1,130 @@ +# Welcome to part 3. This actually is is a simplistic client that will interact with our DVM. +# We will address the DVM we created in part 02, so make sure it's still running and run this Script in a new instance. +# Copy the DVM's hex key that pops up at the beginning and replace the one down in the main function with your DVM's key. +# This way we will tag it and it will know it should reply to us. + +import asyncio +from pathlib import Path + +from secp256k1 import PublicKey + +from nostr_dvm.utils.dvmconfig import DVMConfig +from nostr_dvm.utils.print import bcolors + +import dotenv +from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt, \ + NostrSigner, Event, Kind, PublicKey +from nostr_dvm.utils.nostr_utils import send_event, check_and_set_private_key +from nostr_dvm.utils.definitions import EventDefinitions + + + +async def nostr_client_generic_test(ptag): + # Create or manage some private keys for our client. + keys = Keys.parse(check_and_set_private_key("test_client")) + + # We tell the DVM to which relays it should reply + relay_list = ["wss://nostr.oxtr.dev", "wss://relay.primal.net"] + relaysTag = Tag.parse(["relays"] + relay_list) + # The alt tag is optional, and just describes what the event does. + alttag = Tag.parse(["alt", "This is a NIP90 DVM AI task"]) + # The ptag tags the DVM we want to address. Make sure to set it down in the main function. + pTag = Tag.parse(["p", PublicKey.parse(ptag).to_hex()]) + + # These are out tags + tags = [relaysTag, alttag, pTag] + + # We now send a 5050 Request (for Text Generation) with our tags. The content is optional. + event = EventBuilder(Kind(5050), "This is a test", + tags).to_event(keys) + + # We create a signer with some random keys + signer = NostrSigner.keys(keys) + client = Client(signer) + # We add the relays we defined above and told our DVM we would want to receive events to. + for relay in relay_list: + await client.add_relay(relay) + # We connect the client + await client.connect() + # and send the Event. + result = await send_event(event, client=client, dvm_config=DVMConfig()) + print(result) + + +async def nostr_client(target_dvm_npub): + + # This is some logic for listening to events. For example we want to see replies from the DVM. + keys = Keys.parse(check_and_set_private_key("test_client")) + sk = keys.secret_key() + pk = keys.public_key() + print(f"Nostr Client public key: {pk.to_bech32()}, Hex: {pk.to_hex()} ") + signer = NostrSigner.keys(keys) + client = Client(signer) + + dvmconfig = DVMConfig() + for relay in dvmconfig.RELAY_LIST: + await client.add_relay(relay) + await client.connect() + + dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM, + EventDefinitions.KIND_ZAP]).since(Timestamp.now()) + kinds = [EventDefinitions.KIND_NIP90_GENERIC] + for kind in range(6000, 7001): + if kind not in kinds: + kinds.append(Kind(kind)) + + dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()).pubkey(pk)) + await client.subscribe([dm_zap_filter, dvm_filter], None) + + # This will send a request to the DVM + await nostr_client_generic_test(target_dvm_npub) + + # We listen to + class NotificationHandler(HandleNotification): + last_event_time = 0 + async def handle(self, relay_url, subscription_id, event: Event): + + print( + bcolors.BLUE + f"Received new event from {relay_url}: {event.as_json()}" + bcolors.ENDC) + if event.kind().as_u16() == 7000: + print(bcolors.YELLOW + "[Nostr Client]: " + event.content() + bcolors.ENDC) + amount_sats = 0 + status = "" + for tag in event.tags(): + if tag.as_vec()[0] == "amount": + amount_sats = int(int(tag.as_vec()[1]) / 1000) # millisats + if tag.as_vec()[0] == "status": + status = tag.as_vec()[1] + + elif 6000 < event.kind().as_u16() < 6999: + print(bcolors.GREEN + "[Nostr Client]: " + event.content() + bcolors.ENDC) + + elif event.kind().as_u16() == 4: + dec_text = nip04_decrypt(sk, event.author(), event.content() ) + print("[Nostr Client]: " + f"Received new msg: {dec_text}") + + elif event.kind().as_u16() == 9735: + print("[Nostr Client]: " + f"Received new zap:") + print(event.as_json()) + + async def handle_msg(self, relay_url, msg): + return + + asyncio.create_task(client.handle_notifications(NotificationHandler())) + # await client.handle_notifications(NotificationHandler()) + while True: + await asyncio.sleep(2) + + +if __name__ == '__main__': + + env_path = Path('.env') + if env_path.is_file(): + print(f'loading environment from {env_path.resolve()}') + dotenv.load_dotenv(env_path, verbose=True, override=True) + else: + raise FileNotFoundError(f'.env file not found at {env_path} ') + + # Replace this key with the one from your DVM from part 3. + target_dvm_npub = "aaf3b2bda1f19651417af4b1ccc35ebb6675d718843fdc444bdca4da1c8cd2fc" + asyncio.run(nostr_client(target_dvm_npub)) diff --git a/ui/noogle/src/components/FilterGeneration.vue b/ui/noogle/src/components/FilterGeneration.vue index a4796ee..4bee0a7 100644 --- a/ui/noogle/src/components/FilterGeneration.vue +++ b/ui/noogle/src/components/FilterGeneration.vue @@ -75,8 +75,11 @@ async function generate_feed(id) { let tags = [] //tags.push(["param", "max_results", "200"]) tags.push(["param", "user", store.state.pubkey.toHex()]) - let r = store.state.relays.join(",") - tags.push(["relays", r]) + let r = ["relays"] + for (let relay of store.state.relays){ + r.push(relay) + } + tags.push(r) let res; let requestid; diff --git a/ui/noogle/src/components/ImageGeneration.vue b/ui/noogle/src/components/ImageGeneration.vue index 868f163..b640ce2 100644 --- a/ui/noogle/src/components/ImageGeneration.vue +++ b/ui/noogle/src/components/ImageGeneration.vue @@ -61,8 +61,12 @@ async function generate_image(message) { let tags = [ ["i", message, "text"] ] - let r = store.state.relays.join(",") - tags.push(["relays", r]) + + let r = ["relays"] + for (let relay of store.state.relays){ + r.push(relay) + } + tags.push(r) hasmultipleinputs = false if (urlinput.value !== "" && urlinput.value.startsWith('http')){ @@ -222,7 +226,7 @@ async function listen() { for (const el of store.state.nip89dvms) { - if (JSON.parse(el.event).pubkey === event.author.toHex().toString()) { + if (JSON.parse(el.event).pubkey === event.author.toHex().toString() && el.kind === "5100" ) { jsonentry.name = el.name jsonentry.about = el.about jsonentry.image = el.image @@ -439,54 +443,54 @@ const submitHandler = async () => { -
{{dvm.reactions.positive.length}} - +