From e7d9c217f1e3656d7540f23ba68245d8abc98885 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:31:51 +0200 Subject: [PATCH] giftwrap for bot --- nostr_dvm/bot.py | 312 +++++++++++++++++------------- nostr_dvm/dvm.py | 2 +- nostr_dvm/utils/database_utils.py | 13 +- 3 files changed, 185 insertions(+), 142 deletions(-) diff --git a/nostr_dvm/bot.py b/nostr_dvm/bot.py index 3ddbcdd..7c73cdd 100644 --- a/nostr_dvm/bot.py +++ b/nostr_dvm/bot.py @@ -65,6 +65,7 @@ class Bot: create_sql_table(self.dvm_config.DB) admin_make_database_updates(adminconfig=self.admin_config, dvmconfig=self.dvm_config, client=self.client) + # add_sql_table_column(dvm_config.DB) class NotificationHandler(HandleNotification): @@ -87,14 +88,12 @@ class Bot: handle_dm(nostr_event, False) except Exception as e: print(f"Error during content NIP04 decryption: {e}") - elif nostr_event.kind() == KindEnum.GIFT_WRAP(): + elif nostr_event.kind().match_enum(KindEnum.GIFT_WRAP()): try: - handle_dm(nostr_event, True) + handle_dm(nostr_event, True) except Exception as e: print(f"Error during content NIP59 decryption: {e}") - - def handle_msg(self, relay_url, msg): return @@ -102,15 +101,14 @@ class Bot: sender = nostr_event.author().to_hex() if sender == self.keys.public_key().to_hex(): return - + decrypted_text = "" try: sealed = " " if giftwrap: - print("Decrypting NIP59 event") try: # Extract rumor unwrapped_gift = UnwrappedGift.from_gift_wrap(self.keys, nostr_event) - sender = unwrapped_gift.sender() + sender = unwrapped_gift.sender().to_hex() rumor: UnsignedEvent = unwrapped_gift.rumor() # Check timestamp of rumor @@ -126,112 +124,139 @@ class Bot: print(f"Error during content NIP59 decryption: {e}") else: - decrypted_text = nip04_decrypt(self.keys.secret_key(), nostr_event.author(), nostr_event.content()) + try: + decrypted_text = nip04_decrypt(self.keys.secret_key(), nostr_event.author(), + nostr_event.content()) + except Exception as e: + print(f"Error during content NIP04 decryption: {e}") + if decrypted_text != "": + user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, + config=self.dvm_config) + print("[" + self.NAME + "]" + sealed + "Message from " + user.name + ": " + decrypted_text) - user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, config=self.dvm_config) - print("[" + self.NAME + "]" + sealed + "Message from " + user.name + ": " + decrypted_text) - - # if user selects an index from the overview list... - if decrypted_text[0].isdigit(): - split = decrypted_text.split(' ') - index = int(split[0]) - 1 - # if user sends index info, e.g. 1 info, we fetch the nip89 information and reply with it. - if len(split) > 1 and split[1].lower() == "info": - answer_nip89(nostr_event, index) - # otherwise we probably have to do some work, so build an event from input and send it to the DVM - else: - task = self.dvm_config.SUPPORTED_DVMS[index].TASK - print("[" + self.NAME + "] Request from " + str(user.name) + " (" + str(user.nip05) + - ", Balance: " + str(user.balance) + " Sats) Task: " + str(task)) - - if user.isblacklisted: - # If users are blacklisted for some reason, tell them. - answer_blacklisted(nostr_event) + # if user selects an index from the overview list... + if decrypted_text != "" and decrypted_text[0].isdigit(): + split = decrypted_text.split(' ') + index = int(split[0]) - 1 + # if user sends index info, e.g. 1 info, we fetch the nip89 information and reply with it. + if len(split) > 1 and split[1].lower() == "info": + answer_nip89(nostr_event, index) + # otherwise we probably have to do some work, so build an event from input and send it to the DVM else: - # Parse inputs to params - tags = build_params(decrypted_text, nostr_event, index) - p_tag = Tag.parse(['p', self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY]) + task = self.dvm_config.SUPPORTED_DVMS[index].TASK + print("[" + self.NAME + "] Request from " + str(user.name) + " (" + str(user.nip05) + + ", Balance: " + str(user.balance) + " Sats) Task: " + str(task)) + + if user.isblacklisted: + # If users are blacklisted for some reason, tell them. + answer_blacklisted(nostr_event, giftwrap, sender) - if self.dvm_config.SUPPORTED_DVMS[index].SUPPORTS_ENCRYPTION: - tags_str = [] - for tag in tags: - tags_str.append(tag.as_vec()) - params_as_str = json.dumps(tags_str) - print(params_as_str) - # and encrypt them - encrypted_params = nip04_encrypt(self.keys.secret_key(), - PublicKey.from_hex( - self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY), - params_as_str) - # add encrypted and p tag on the outside - encrypted_tag = Tag.parse(['encrypted']) - # add the encrypted params to the content - nip90request = (EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND, - encrypted_params, [p_tag, encrypted_tag]). - to_event(self.keys)) else: - tags.append(p_tag) + # Parse inputs to params + tags = build_params(decrypted_text, nostr_event, index) + p_tag = Tag.parse(['p', self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY]) - nip90request = (EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND, - "", tags). - to_event(self.keys)) + if self.dvm_config.SUPPORTED_DVMS[index].SUPPORTS_ENCRYPTION: + tags_str = [] + for tag in tags: + tags_str.append(tag.as_vec()) + params_as_str = json.dumps(tags_str) + print(params_as_str) + # and encrypt them + encrypted_params = nip04_encrypt(self.keys.secret_key(), + PublicKey.from_hex( + self.dvm_config.SUPPORTED_DVMS[ + index].PUBLIC_KEY), + params_as_str) + # add encrypted and p tag on the outside + encrypted_tag = Tag.parse(['encrypted']) + # add the encrypted params to the content + nip90request = (EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND, + encrypted_params, [p_tag, encrypted_tag]). + to_event(self.keys)) + else: + tags.append(p_tag) - # remember in the job_list that we have made an event, if anybody asks for payment, - # we know we actually sent the request - entry = {"npub": user.npub, "event_id": nip90request.id().to_hex(), - "dvm_key": self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY, "is_paid": False} - self.job_list.append(entry) + nip90request = (EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND, + "", tags). + to_event(self.keys)) - # send the event to the DVM - send_event(nip90request, client=self.client, dvm_config=self.dvm_config) - # print(nip90request.as_json()) + # remember in the job_list that we have made an event, if anybody asks for payment, + # we know we actually sent the request + entry = {"npub": user.npub, "event_id": nip90request.id().to_hex(), + "dvm_key": self.dvm_config.SUPPORTED_DVMS[index].PUBLIC_KEY, "is_paid": False, "giftwrap": giftwrap} + self.job_list.append(entry) + + # send the event to the DVM + send_event(nip90request, client=self.client, dvm_config=self.dvm_config) + # print(nip90request.as_json()) - elif decrypted_text.lower().startswith("balance"): - time.sleep(3.0) - evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), - "Your current balance is " + str( - user.balance) + " Sats. Zap me to add to your balance. I will use your balance interact with the DVMs for you.\n" - "I support both public and private Zaps, as well as Zapplepay.\n" - "Alternativly you can add a #cashu token with \"-cashu cashuASomeToken\" to your command.\n Make sure the token is worth the requested amount + " - "mint fees (at least 3 sat).\n Not all DVMs might accept Cashu tokens." - , None).to_event(self.keys) - send_event(evt, client=self.client, dvm_config=dvm_config) + elif decrypted_text.lower().startswith("balance"): + time.sleep(3.0) + message = "Your current balance is " + str(user.balance) + ("Sats. Zap me to add to your " + "balance. I will use your " + "balance interact with the DVMs " + "for you.\n I support both " + "public and private Zaps, " + "as well as " + "Zapplepay.\nAlternativly you " + "can add a #cashu token with " + "\"-cashu cashuASomeToken\" to " + "your command.\n Make sure the " + "token is worth the requested " + "amount mint fees (at least 3 " + "sat).\n Not all DVMs might " + "accept Cashu tokens.") + if giftwrap: + self.client.send_sealed_msg(PublicKey.parse(sender), message, None) + else: + evt = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.parse(sender), + message,None).to_event(self.keys) + send_event(evt, client=self.client, dvm_config=dvm_config) + elif decrypted_text.startswith("cashuA"): + print("Received Cashu token:" + decrypted_text) + cashu_redeemed, cashu_message, total_amount, fees = redeem_cashu(decrypted_text, + self.dvm_config, + self.client) + print(cashu_message) + if cashu_message == "success": + update_user_balance(self.dvm_config.DB, sender, total_amount, client=self.client, + config=self.dvm_config) + else: + time.sleep(2.0) + message = "Error: " + cashu_message + ". Token has not been redeemed." + + if giftwrap: + self.client.send_sealed_msg(PublicKey.parse(sender), message, None) + else: + evt = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.from_hex(sender), message, + None).to_event(self.keys) + send_event(evt, client=self.client, dvm_config=self.dvm_config) + elif decrypted_text.lower().startswith("what's the second best"): + time.sleep(3.0) + message = "No, there is no second best.\n\nhttps://cdn.nostr.build/p/mYLv.mp4" + if giftwrap: + self.client.send_sealed_msg(PublicKey.parse(sender), message, None) + else: + evt = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.parse(sender), + message, + nostr_event.id()).to_event(self.keys) + send_event(evt, client=self.client, dvm_config=self.dvm_config) - elif decrypted_text.startswith("cashuA"): - print("Received Cashu token:" + decrypted_text) - cashu_redeemed, cashu_message, total_amount, fees = redeem_cashu(decrypted_text, self.dvm_config, - self.client) - print(cashu_message) - if cashu_message == "success": - update_user_balance(self.dvm_config.DB, sender, total_amount, client=self.client, - config=self.dvm_config) else: - time.sleep(2.0) - message = "Error: " + cashu_message + ". Token has not been redeemed." - evt = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.from_hex(sender), message, - None).to_event(self.keys) - send_event(evt, client=self.client, dvm_config=self.dvm_config) - elif decrypted_text.lower().startswith("what's the second best"): - time.sleep(3.0) - evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), - "No, there is no second best.\n\nhttps://cdn.nostr.build/p/mYLv.mp4", - nostr_event.id()).to_event(self.keys) - send_event(evt, client=self.client, dvm_config=self.dvm_config) - - else: - # Build an overview of known DVMs and send it to the user - answer_overview(nostr_event) + # Build an overview of known DVMs and send it to the user + answer_overview(nostr_event, giftwrap, sender) except Exception as e: print("Error in bot " + str(e)) def handle_nip90_feedback(nostr_event): - #print(nostr_event.as_json()) + # print(nostr_event.as_json()) try: is_encrypted = False status = "" @@ -281,14 +306,18 @@ class Bot: user = get_or_add_user(db=self.dvm_config.DB, npub=entry['npub'], client=self.client, config=self.dvm_config) time.sleep(2.0) - reply_event = EventBuilder.encrypted_direct_msg(self.keys, + if entry["giftwrap"]: + self.client.send_sealed_msg(PublicKey.parse(entry["npub"]), content, None) + else: + reply_event = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.from_hex(entry['npub']), content, None).to_event(self.keys) + + send_event(reply_event, client=self.client, dvm_config=dvm_config) print(status + ": " + content) print( "[" + self.NAME + "] Received reaction from " + nostr_event.author().to_hex() + " message to orignal sender " + user.name) - send_event(reply_event, client=self.client, dvm_config=dvm_config) elif status == "payment-required" or status == "partial": for tag in nostr_event.tags(): @@ -307,28 +336,30 @@ class Bot: iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted, nip05=user.nip05, lud16=user.lud16, name=user.name, lastactive=Timestamp.now().as_secs(), subscribed=user.subscribed) - evt = EventBuilder.encrypted_direct_msg(self.keys, - PublicKey.from_hex(entry["npub"]), - "Paid " + str( - amount) + " Sats from balance to DVM. New balance is " + - str(balance) - + " Sats.\n", - None).to_event(self.keys) + message = "Paid " + str(amount) + " Sats from balance to DVM. New balance is " + str(balance) + " Sats.\n" + if entry["giftwrap"]: + self.client.send_sealed_msg(PublicKey.parse(entry["npub"]), message, None) + else: + evt = EventBuilder.encrypted_direct_msg(self.keys, + PublicKey.parse(entry["npub"]), + message, + None).to_event(self.keys) + send_event(evt, client=self.client, dvm_config=dvm_config) print( "[" + self.NAME + "] Replying " + user.name + " with \"scheduled\" confirmation") - send_event(evt, client=self.client, dvm_config=dvm_config) + else: print("Bot payment-required") time.sleep(2.0) evt = EventBuilder.encrypted_direct_msg(self.keys, - PublicKey.from_hex(entry["npub"]), - "Current balance: " + str( - user.balance) + " Sats. Balance of " + str( - amount) + " Sats required. Please zap me with at least " + - str(int(amount - user.balance)) - + " Sats, then try again.", - None).to_event(self.keys) + PublicKey.parse(entry["npub"]), + "Current balance: " + str( + user.balance) + " Sats. Balance of " + str( + amount) + " Sats required. Please zap me with at least " + + str(int(amount - user.balance)) + + " Sats, then try again.", + None).to_event(self.keys) send_event(evt, client=self.client, dvm_config=dvm_config) return @@ -398,11 +429,14 @@ class Bot: print("[" + self.NAME + "] Received results, message to orignal sender " + user.name) time.sleep(1.0) - reply_event = EventBuilder.encrypted_direct_msg(self.keys, - PublicKey.from_hex(user.npub), + if entry["giftwrap"]: + self.client.send_sealed_msg(PublicKey.parse(user.npub), content, None) + else: + reply_event = EventBuilder.encrypted_direct_msg(self.keys, + PublicKey.parse(user.npub), content, None).to_event(self.keys) - send_event(reply_event, client=self.client, dvm_config=dvm_config) + send_event(reply_event, client=self.client, dvm_config=dvm_config) except Exception as e: print(e) @@ -453,7 +487,7 @@ class Bot: except Exception as e: print("[" + self.NAME + "] Error during content decryption:" + str(e)) - def answer_overview(nostr_event): + def answer_overview(nostr_event, giftwrap, sender): message = "DVMs that I support:\n\n" index = 1 for p in self.dvm_config.SUPPORTED_DVMS: @@ -466,35 +500,39 @@ class Bot: index += 1 time.sleep(3.0) - evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), - message + "\nSelect an Index and provide an input (" - "e.g. \"2 A purple ostrich\")\nType \"index info\" to learn " - "more about each DVM. (e.g. \"2 info\")\n\n" - "Type \"balance\" to see your current balance", + + text = message + "\nSelect an Index and provide an input (e.g. \"2 A purple ostrich\")\nType \"index info\" to learn more about each DVM. (e.g. \"2 info\")\n\n Type \"balance\" to see your current balance" + if giftwrap: + self.client.send_sealed_msg(PublicKey.parse(sender), text, None) + else: + evt = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.parse(sender), + text, nostr_event.id()).to_event(self.keys) - send_event(evt, client=self.client, dvm_config=dvm_config) + send_event(evt, client=self.client, dvm_config=dvm_config) - def answer_blacklisted(nostr_event): - # For some reason an admin might blacklist npubs, e.g. for abusing the service - evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), - "Your are currently blocked from all " - "services.", None).to_event(self.keys) - send_event(evt, client=self.client, dvm_config=dvm_config) - - def answer_nip89(nostr_event, index): - info = print_dvm_info(self.client, index) - time.sleep(2.0) - if info is not None: - evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), - info, None).to_event(self.keys) + def answer_blacklisted(nostr_event, giftwrap, sender): + message = "Your are currently blocked from this service." + if giftwrap: + self.client.send_sealed_msg(PublicKey.parse(sender), message, None) else: + # For some reason an admin might blacklist npubs, e.g. for abusing the service evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), - "No NIP89 Info found for " + - self.dvm_config.SUPPORTED_DVMS[index].NAME, - None).to_event(self.keys) + message, None).to_event(self.keys) + send_event(evt, client=self.client, dvm_config=dvm_config) - send_event(evt, client=self.client, dvm_config=dvm_config) + def answer_nip89(nostr_event, index, giftwrap, sender): + info = print_dvm_info(self.client, index) + if info is None: + info = "No NIP89 Info found for " + self.dvm_config.SUPPORTED_DVMS[index].NAME + time.sleep(2.0) + + if giftwrap: + self.client.send_sealed_msg(PublicKey.parse(sender), info, None) + else: + evt = EventBuilder.encrypted_direct_msg(self.keys, nostr_event.author(), + info, None).to_event(self.keys) + send_event(evt, client=self.client, dvm_config=dvm_config) def build_params(decrypted_text, nostr_event, index): tags = [] diff --git a/nostr_dvm/dvm.py b/nostr_dvm/dvm.py index 88ed935..405a626 100644 --- a/nostr_dvm/dvm.py +++ b/nostr_dvm/dvm.py @@ -5,7 +5,7 @@ from datetime import timedelta from sys import platform from nostr_sdk import PublicKey, Keys, Client, Tag, Event, EventBuilder, Filter, HandleNotification, Timestamp, \ - init_logger, LogLevel, Options, nip04_encrypt, NostrSigner, Kind, SubscribeAutoCloseOptions + init_logger, LogLevel, Options, nip04_encrypt, NostrSigner, Kind import time diff --git a/nostr_dvm/utils/database_utils.py b/nostr_dvm/utils/database_utils.py index feddbe3..4662ddd 100644 --- a/nostr_dvm/utils/database_utils.py +++ b/nostr_dvm/utils/database_utils.py @@ -169,7 +169,7 @@ def list_db(db): print(e) -def update_user_balance(db, npub, additional_sats, client, config): +def update_user_balance(db, npub, additional_sats, client, config, giftwrap=False): user = get_from_sql_table(db, npub) if user is None: name, nip05, lud16 = fetch_user_metadata(npub, client) @@ -192,9 +192,14 @@ def update_user_balance(db, npub, additional_sats, client, config): message = ("Added " + str(additional_sats) + " Sats to balance. New balance is " + str( new_balance) + " Sats.") - evt = EventBuilder.encrypted_direct_msg(keys, PublicKey.from_hex(npub), message, - None).to_event(keys) - send_event(evt, client=client, dvm_config=config) + if giftwrap: + client.send_sealed_msg(PublicKey.parse(npub), message, None) + else: + + + evt = EventBuilder.encrypted_direct_msg(keys, PublicKey.parse(npub), message, + None).to_event(keys) + send_event(evt, client=client, dvm_config=config) def update_user_subscription(npub, subscribed_until, client, dvm_config):