diff --git a/nostr_dvm/subscription.py b/nostr_dvm/subscription.py index 82dd3cd..a4bfb3f 100644 --- a/nostr_dvm/subscription.py +++ b/nostr_dvm/subscription.py @@ -1,13 +1,12 @@ import json +import math import os import signal import time from datetime import timedelta -from nostr_sdk import ( - Keys, Client, Timestamp, Filter, nip04_decrypt, HandleNotification, EventBuilder, - PublicKey, Options, Tag, Event, nip04_encrypt, NostrSigner, EventId, Kind -) +from nostr_sdk import (Keys, Client, Timestamp, Filter, nip04_decrypt, HandleNotification, EventBuilder, PublicKey, + Options, Tag, Event, nip04_encrypt, NostrSigner, EventId, Nip19Event, nip44_decrypt, Kind) from nostr_dvm.utils.database_utils import fetch_user_metadata from nostr_dvm.utils.definitions import EventDefinitions @@ -16,17 +15,19 @@ from nostr_dvm.utils.nip88_utils import nip88_has_active_subscription from nostr_dvm.utils.nip89_utils import NIP89Config from nostr_dvm.utils.nostr_utils import send_event from nostr_dvm.utils.nwc_tools import nwc_zap -from nostr_dvm.utils.subscription_utils import ( - create_subscription_sql_table, add_to_subscription_sql_table, get_from_subscription_sql_table, - update_subscription_sql_table, get_all_subscriptions_from_sql_table, delete_from_subscription_sql_table -) +from nostr_dvm.utils.subscription_utils import create_subscription_sql_table, add_to_subscription_sql_table, \ + get_from_subscription_sql_table, update_subscription_sql_table, get_all_subscriptions_from_sql_table, \ + delete_from_subscription_sql_table from nostr_dvm.utils.zap_utils import create_bolt11_lud16, zaprequest class Subscription: + job_list: list + + # This is a simple list just to keep track which events we created and manage, so we don't pay for other requests def __init__(self, dvm_config, admin_config=None): self.NAME = "Subscription Handler" - dvm_config.DB = "db/subscriptions.db" + dvm_config.DB = "db/" + "subscriptions" + ".db" self.dvm_config = dvm_config nip89config = NIP89Config() nip89config.NAME = self.NAME @@ -40,349 +41,379 @@ class Subscription: signer = NostrSigner.keys(self.keys) self.client = Client.with_opts(signer, opts) - public_key = self.keys.public_key() + pk = self.keys.public_key() self.job_list = [] - print(f"Nostr Subscription Handler public key: {public_key.to_bech32()} Hex: {public_key.to_hex()}\n") + print("Nostr Subscription Handler public key: " + str(pk.to_bech32()) + " Hex: " + str( + pk.to_hex()) + "\n") for relay in self.dvm_config.RELAY_LIST: self.client.add_relay(relay) self.client.connect() - zap_filter = Filter().pubkey(public_key).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) + zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) cancel_subscription_filter = Filter().kinds([EventDefinitions.KIND_NIP88_STOP_SUBSCRIPTION_EVENT]).since( Timestamp.now()) authors = [] if admin_config is not None: for key in admin_config.USERNPUBS: authors.append(PublicKey.parse(key)) - dvm_filter = Filter().authors(authors).pubkey(public_key).kinds([EventDefinitions.KIND_NIP90_DVM_SUBSCRIPTION]).since(Timestamp.now()) + dvm_filter = Filter().authors(authors).pubkey(pk).kinds([EventDefinitions.KIND_NIP90_DVM_SUBSCRIPTION.as_u64()]).since(Timestamp.now()) self.client.subscribe([zap_filter, dvm_filter, cancel_subscription_filter], None) create_subscription_sql_table(dvm_config.DB) - self.client.handle_notifications(self.NotificationHandler(self)) + class NotificationHandler(HandleNotification): + client = self.client + dvm_config = self.dvm_config + keys = self.keys + + def handle(self, relay_url, subscription_id, nostr_event: Event): + if nostr_event.kind().as_u64() == EventDefinitions.KIND_NIP90_DVM_SUBSCRIPTION.as_u64(): + handle_nwc_request(nostr_event) + elif nostr_event.kind().as_u64() == EventDefinitions.KIND_NIP88_STOP_SUBSCRIPTION_EVENT.as_u64(): + handle_cancel(nostr_event) + + def handle_msg(self, relay_url, msg): + return + + def handle_cancel(nostr_event): + print(nostr_event.as_json()) + sender = nostr_event.author().to_hex() + kind7001eventid = "" + recipient = "" + if sender == self.keys.public_key().to_hex(): + return + + for tag in nostr_event.tags(): + if tag.as_vec()[0] == "p": + recipient = tag.as_vec()[1] + elif tag.as_vec()[0] == "e": + kind7001eventid = tag.as_vec()[1] + + if kind7001eventid != "": + subscription = get_from_subscription_sql_table(dvm_config.DB, kind7001eventid) + + if subscription is not None: + update_subscription_sql_table(dvm_config.DB, kind7001eventid, recipient, + subscription.subscriber, subscription.nwc, subscription.cadence, + subscription.amount, subscription.unit, subscription.begin, subscription.end, + subscription.tier_dtag, subscription.zaps, subscription.recipe, + False, Timestamp.now().as_secs(), subscription.tier) + # send_status_canceled(kind7001eventid, nostr_event) # TODO, waiting for spec + + def infer_subscription_end_time(start, cadence): + end = start + if cadence == "daily": + end = start + 60 * 60 * 24 + elif cadence == "weekly": + end = start + 60 * 60 * 24 * 7 + elif cadence == "monthly": + # TODO check days of month -.- + end = start + 60 * 60 * 24 * 31 + elif cadence == "yearly": + # TODO check extra day every 4 years + end = start + 60 * 60 * 24 * 356 + return end + + def send_status_success(original_event, domain): + + e_tag = Tag.parse(["e", original_event.id().to_hex()]) + p_tag = Tag.parse(["p", original_event.author().to_hex()]) + status_tag = Tag.parse(["status", "success", "Job has been scheduled, you can manage it on " + domain]) + reply_tags = [status_tag] + encryption_tags = [] + + encrypted_tag = Tag.parse(["encrypted"]) + encryption_tags.append(encrypted_tag) + encryption_tags.append(p_tag) + encryption_tags.append(e_tag) + + str_tags = [] + for element in reply_tags: + str_tags.append(element.as_vec()) + + content = json.dumps(str_tags) + content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.author().to_hex()), + content) + reply_tags = encryption_tags + + keys = Keys.parse(dvm_config.PRIVATE_KEY) + reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys) + send_event(reaction_event, client=self.client, dvm_config=self.dvm_config) + print("[" + self.dvm_config.NIP89.NAME + "]" + ": Sent Kind " + str( + EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + "success" + " " + reaction_event.as_json()) + + + def pay_zap_split(nwc, overall_amount, zaps, tier, unit="msats"): + overallsplit = 0 + + for zap in zaps: + print(zap) + overallsplit += int(zap[3]) + + print(overallsplit) + zapped_amount = 0 + for zap in zaps: + if zap[1] == "": + zap[1] = Keys.parse(self.dvm_config.PRIVATE_KEY).public_key().to_hex() + + name, nip05, lud16 = fetch_user_metadata(zap[1], self.client) + splitted_amount = math.floor( + (int(zap[3]) / overallsplit) * int(overall_amount) / 1000) + # invoice = create_bolt11_lud16(lud16, splitted_amount) + # TODO add details about DVM in message + + invoice = zaprequest(lud16, splitted_amount, tier, None, + PublicKey.parse(zap[1]), self.keys, DVMConfig.RELAY_LIST) + print(invoice) + if invoice is not None: + nwc_event_id = nwc_zap(nwc, invoice, self.keys, zap[2]) + if nwc_event_id is None: + print("error zapping " + lud16) + else: + zapped_amount = zapped_amount + (splitted_amount * 1000) + print(str(zapped_amount) + "/" + str(overall_amount)) + + if zapped_amount < overall_amount * 0.8: # TODO how do we handle failed zaps for some addresses? we are ok with 80% for now + success = False + else: + success = True + # if no active subscription exists OR the subscription ended, pay + + return success + + def make_subscription_zap_recipe(event7001, recipient, subscriber, start, end, tier_dtag): + message = "payed by subscription service" + pTag = Tag.parse(["p", recipient]) + PTag = Tag.parse(["P", subscriber]) + eTag = Tag.parse(["e", event7001]) + validTag = Tag.parse(["valid", str(start), str(end)]) + tierTag = Tag.parse(["tier", tier_dtag]) + alttag = Tag.parse(["alt", "This is a NIP90 DVM Subscription Payment Recipe"]) + + tags = [pTag, PTag, eTag, validTag, tierTag, alttag] + + event = EventBuilder(EventDefinitions.KIND_NIP88_PAYMENT_RECIPE, + message, tags).to_event(self.keys) + + dvmconfig = DVMConfig() + signer = NostrSigner.keys(self.keys) + client = Client(signer) + for relay in dvmconfig.RELAY_LIST: + client.add_relay(relay) + client.connect() + recipeid = client.send_event(event) + recipe = recipeid.to_hex() + return recipe + + def handle_nwc_request(nostr_event): + print(nostr_event.as_json()) + sender = nostr_event.author().to_hex() + if sender == self.keys.public_key().to_hex(): + return + + try: + decrypted_text = nip04_decrypt(self.keys.secret_key(), nostr_event.author(), nostr_event.content()) + try: + jsonevent = json.loads(decrypted_text) + for entry in jsonevent: + if entry[1] == "nwc": + nwc = entry[2] + elif entry[1] == "p": + subscriber = entry[2] + + subscriptionfilter = Filter().kind(EventDefinitions.KIND_NIP88_SUBSCRIBE_EVENT).author( + PublicKey.parse(subscriber)).limit(1) + evts = self.client.get_events_of([subscriptionfilter], timedelta(seconds=3)) + print(evts) + if len(evts) > 0: + event7001id = evts[0].id().to_hex() + print(evts[0].as_json()) + tier_dtag = "" + recipient = "" + cadence = "" + unit = "msats" + zaps = [] + tier = "DVM" + overall_amount = 0 + for tag in evts[0].tags(): + if tag.as_vec()[0] == "amount": + overall_amount = int(tag.as_vec()[1]) + + unit = tag.as_vec()[2] + cadence = tag.as_vec()[3] + print(str(overall_amount) + " " + unit + " " + cadence) + elif tag.as_vec()[0] == "p": + recipient = tag.as_vec()[1] + elif tag.as_vec()[0] == "e": + subscription_event_id = tag.as_vec()[1] + elif tag.as_vec()[0] == "event": + jsonevent = json.loads(tag.as_vec()[1]) + subscription_event = Event.from_json(jsonevent) + + for tag in subscription_event.tags(): + if tag.as_vec()[0] == "d": + tier_dtag = tag.as_vec()[1] + elif tag.as_vec()[0] == "zap": + zaps.append(tag.as_vec()) + elif tag.as_vec()[0] == "title": + tier = tag.as_vec()[1] + + if tier_dtag == "" or len(zaps) == 0: + tierfilter = Filter().id(EventId.parse(subscription_event_id)) + evts = self.client.get_events_of([tierfilter], timedelta(seconds=3)) + if len(evts) > 0: + for tag in evts[0].tags(): + if tag.as_vec()[0] == "d": + tier_dtag = tag.as_vec()[0] + + start = Timestamp.now().as_secs() + + isactivesubscription = False + recipe = "" + + subscription = get_from_subscription_sql_table(dvm_config.DB, event7001id) + # if subscription is not None and subscription.end > start: + # start = subscription.end + # isactivesubscription = True + + zapsstr = json.dumps(zaps) + print(zapsstr) + success = True + if subscription is None or subscription.end <= Timestamp.now().as_secs(): + # rather check nostr if our db is right + subscription_status = nip88_has_active_subscription( + PublicKey.parse(subscriber), + tier_dtag, self.client, recipient, checkCanceled=False) + + if not subscription_status["isActive"]: + + success = pay_zap_split(nwc, overall_amount, zaps, tier, unit) + start = Timestamp.now().as_secs() + end = infer_subscription_end_time(start, cadence) + else: + start = Timestamp.now().as_secs() + end = subscription_status["validUntil"] + else: + start = subscription.begin + end = subscription.end + + if success: + recipe = make_subscription_zap_recipe(event7001id, recipient, subscriber, start, end, + tier_dtag) + print("RECIPE " + recipe) + isactivesubscription = True + + if subscription is None: + add_to_subscription_sql_table(dvm_config.DB, event7001id, recipient, subscriber, nwc, + cadence, overall_amount, unit, start, end, tier_dtag, + zapsstr, recipe, isactivesubscription, + Timestamp.now().as_secs(), tier) + print("new subscription entry") + else: + update_subscription_sql_table(dvm_config.DB, event7001id, recipient, subscriber, nwc, + cadence, overall_amount, unit, start, end, + tier_dtag, zapsstr, recipe, isactivesubscription, + Timestamp.now().as_secs(), tier) + print("updated subscription entry") + + send_status_success(nostr_event, "noogle.lol") + + + keys = Keys.parse(dvm_config.PRIVATE_KEY) + message = ("Subscribed to DVM " + tier + ". Renewing on: " + str( + Timestamp.from_secs(end).to_human_datetime().replace("Z", " ").replace("T", " "))) + evt = EventBuilder.encrypted_direct_msg(keys, PublicKey.parse(subscriber), message, + None).to_event(keys) + send_event(evt, client=self.client, dvm_config=dvm_config) + + + + except Exception as e: + print(e) + + except Exception as e: + print("Error in Subscriber " + str(e)) + + self.client.handle_notifications(NotificationHandler()) try: while True: time.sleep(60.0) - self.check_subscriptions() + subscriptions = get_all_subscriptions_from_sql_table(dvm_config.DB) + + for subscription in subscriptions: + if subscription.active: + if subscription.end < Timestamp.now().as_secs(): + # We could directly zap, but let's make another check if our subscription expired + subscription_status = nip88_has_active_subscription( + PublicKey.parse(subscription.subscriber), + subscription.tier_dtag, self.client, subscription.recipent) + + if subscription_status["expires"]: + update_subscription_sql_table(dvm_config.DB, subscription_status["subscriptionId"], + subscription.recipent, + subscription.subscriber, subscription.nwc, + subscription.cadence, subscription.amount, subscription.unit, + subscription.begin, subscription.end, + subscription.tier_dtag, subscription.zaps, + subscription.recipe, + False, + Timestamp.now().as_secs(), subscription.tier) + else: + zaps = json.loads(subscription.zaps) + success = pay_zap_split(subscription.nwc, subscription.amount, zaps, subscription.tier, subscription.unit) + if success: + end = infer_subscription_end_time(Timestamp.now().as_secs(), subscription.cadence) + recipe = make_subscription_zap_recipe(subscription.id, subscription.recipent, + subscription.subscriber, subscription.begin, + end, subscription.tier_dtag) + else: + end = Timestamp.now().as_secs() + recipe = subscription.recipe + + update_subscription_sql_table(dvm_config.DB, subscription.id, + subscription.recipent, + subscription.subscriber, subscription.nwc, + subscription.cadence, subscription.amount, subscription.unit, + subscription.begin, end, + subscription.tier_dtag, subscription.zaps, recipe, + success, + Timestamp.now().as_secs(), subscription.tier) + + print("updated subscription entry") + + + keys = Keys.parse(dvm_config.PRIVATE_KEY) + message = ("Renewed Subscription to DVM " + subscription.tier + ". Next renewal: " + str( + Timestamp.from_secs(end).to_human_datetime().replace("Z", " ").replace( "T", " "))) + evt = EventBuilder.encrypted_direct_msg(keys, PublicKey.parse(subscription.subscriber), message, + None).to_event(keys) + send_event(evt, client=self.client, dvm_config=dvm_config) + + + + else: + delete_threshold = 60 * 60 * 24 * 365 + if subscription.cadence == "daily": + delete_threshold = 60 * 60 * 24 * 3 # After 3 days, delete the subscription, user can make a new one + elif subscription.cadence == "weekly": + delete_threshold = 60 * 60 * 24 * 21 # After 21 days, delete the subscription, user can make a new one + elif subscription.cadence == "monthly": + delete_threshold = 60 * 60 * 24 * 60 # After 60 days, delete the subscription, user can make a new one + elif subscription.cadence == "yearly": + delete_threshold = 60 * 60 * 24 * 500 # After 500 days, delete the subscription, user can make a new one + + if subscription.end < (Timestamp.now().as_secs() - delete_threshold): + delete_from_subscription_sql_table(dvm_config.DB, subscription.id) + print("Delete expired subscription") + + print(str(Timestamp.now().as_secs()) + ": Checking " + str( + len(subscriptions)) + " Subscription entries..") except KeyboardInterrupt: print('Stay weird!') - os.kill(os.getpid(), signal.SIGTERM) - - def check_subscriptions(self): - subscriptions = get_all_subscriptions_from_sql_table(self.dvm_config.DB) - - for subscription in subscriptions: - if subscription.active: - if subscription.end < Timestamp.now().as_secs(): - self.handle_subscription_renewal(subscription) - else: - self.handle_expired_subscription(subscription) - - print(f"{Timestamp.now().as_secs()}: Checking {len(subscriptions)} Subscription entries..") - - def handle_subscription_renewal(self, subscription): - subscription_status = nip88_has_active_subscription( - PublicKey.parse(subscription.subscriber), - subscription.tier_dtag, self.client, subscription.recipent) - - if subscription_status["expires"]: - update_subscription_sql_table(self.dvm_config.DB, subscription_status["subscriptionId"], - subscription.recipent, - subscription.subscriber, subscription.nwc, - subscription.cadence, subscription.amount, subscription.unit, - subscription.begin, subscription.end, - subscription.tier_dtag, subscription.zaps, - subscription.recipe, - False, - Timestamp.now().as_secs(), subscription.tier) - else: - zaps = json.loads(subscription.zaps) - success = self.pay_zap_split(subscription.nwc, subscription.amount, zaps, subscription.tier, - subscription.unit) - if success: - end = self.infer_subscription_end_time(Timestamp.now().as_secs(), subscription.cadence) - recipe = self.make_subscription_zap_recipe(subscription.id, subscription.recipent, - subscription.subscriber, subscription.begin, - end, subscription.tier_dtag) - else: - end = Timestamp.now().as_secs() - recipe = subscription.recipe - - update_subscription_sql_table(self.dvm_config.DB, subscription.id, - subscription.recipent, - subscription.subscriber, subscription.nwc, - subscription.cadence, subscription.amount, subscription.unit, - subscription.begin, end, - subscription.tier_dtag, subscription.zaps, recipe, - success, - Timestamp.now().as_secs(), subscription.tier) - - print("Updated subscription entry") - - message = (f"Renewed Subscription to DVM {subscription.tier}. " - f"Next renewal: {Timestamp.from_secs(end).to_human_datetime().replace('Z', ' ').replace('T', ' ')}") - self.send_direct_message(subscription.subscriber, message) - - def handle_expired_subscription(self, subscription): - delete_threshold = 60 * 60 * 24 * 365 - if subscription.cadence == "daily": - delete_threshold = 60 * 60 * 24 * 3 # After 3 days, delete the subscription, user can make a new one - elif subscription.cadence == "weekly": - delete_threshold = 60 * 60 * 24 * 21 # After 21 days, delete the subscription, user can make a new one - elif subscription.cadence == "monthly": - delete_threshold = 60 * 60 * 24 * 60 # After 60 days, delete the subscription, user can make a new one - elif subscription.cadence == "yearly": - delete_threshold = 60 * 60 * 24 * 500 # After 500 days, delete the subscription, user can make a new one - - if subscription.end < (Timestamp.now().as_secs() - delete_threshold): - delete_from_subscription_sql_table(self.dvm_config.DB, subscription.id) - print("Deleted expired subscription") - - def pay_zap_split(self, nwc, overall_amount, zaps, tier, unit="msats"): - overallsplit = sum(int(zap[3]) for zap in zaps) - zapped_amount = 0 - - for zap in zaps: - if zap[1] == "": - zap[1] = self.keys.public_key().to_hex() - - name, nip05, lud16 = fetch_user_metadata(zap[1], self.client) - splitted_amount = int((int(zap[3]) / overallsplit) * int(overall_amount) / 1000) - - invoice = zaprequest(lud16, splitted_amount, tier, None, - PublicKey.parse(zap[1]), self.keys, DVMConfig.RELAY_LIST) - - if invoice is not None: - nwc_event_id = nwc_zap(nwc, invoice, self.keys, zap[2]) - if nwc_event_id is None: - print(f"Error zapping {lud16}") - else: - zapped_amount += splitted_amount * 1000 - print(f"{zapped_amount}/{overall_amount}") - - return zapped_amount >= overall_amount * 0.8 - - def make_subscription_zap_recipe(self, event7001, recipient, subscriber, start, end, tier_dtag): - message = "Paid by subscription service" - p_tag = Tag.parse(["p", recipient]) - p_subscriber_tag = Tag.parse(["P", subscriber]) - e_tag = Tag.parse(["e", event7001]) - valid_tag = Tag.parse(["valid", str(start), str(end)]) - tier_tag = Tag.parse(["tier", tier_dtag]) - alt_tag = Tag.parse(["alt", "This is a NIP90 DVM Subscription Payment Recipe"]) - - tags = [p_tag, p_subscriber_tag, e_tag, valid_tag, tier_tag, alt_tag] - - event = EventBuilder(EventDefinitions.KIND_NIP88_PAYMENT_RECIPE, - message, tags).to_event(self.keys) - - recipeid = self.client.send_event(event) - recipe = recipeid.to_hex() - return recipe - - def infer_subscription_end_time(self, start, cadence): - end = start - if cadence == "daily": - end = start + 60 * 60 * 24 - elif cadence == "weekly": - end = start + 60 * 60 * 24 * 7 - elif cadence == "monthly": - end = start + 60 * 60 * 24 * 31 - elif cadence == "yearly": - end = start + 60 * 60 * 24 * 356 - return end - - def send_direct_message(self, recipient, message): - evt = EventBuilder.encrypted_direct_msg(self.keys, PublicKey.parse(recipient), message, None).to_event( - self.keys) - send_event(evt, client=self.client, dvm_config=self.dvm_config) - - class NotificationHandler(HandleNotification): - def __init__(self, subscription): - self.subscription = subscription - - def handle(self, relay_url, subscription_id, nostr_event: Event): - if nostr_event.kind().as_u64() == EventDefinitions.KIND_NIP90_DVM_SUBSCRIPTION.as_u64(): - self.subscription.handle_nwc_request(nostr_event) - elif nostr_event.kind().as_u64() == EventDefinitions.KIND_NIP88_STOP_SUBSCRIPTION_EVENT.as_u64(): - self.subscription.handle_cancel(nostr_event) - - def handle_msg(self, relay_url, msg): - pass - - def send_status_success(self, original_event, domain): - - e_tag = Tag.parse(["e", original_event.id().to_hex()]) - p_tag = Tag.parse(["p", original_event.author().to_hex()]) - status_tag = Tag.parse(["status", "success", "Job has been scheduled, you can manage it on " + domain]) - reply_tags = [status_tag] - encryption_tags = [] - - encrypted_tag = Tag.parse(["encrypted"]) - encryption_tags.append(encrypted_tag) - encryption_tags.append(p_tag) - encryption_tags.append(e_tag) - - str_tags = [] - for element in reply_tags: - str_tags.append(element.as_vec()) - - content = json.dumps(str_tags) - content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.author().to_hex()), - content) - reply_tags = encryption_tags - - keys = Keys.parse(self.dvm_config.PRIVATE_KEY) - reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys) - send_event(reaction_event, client=self.client, dvm_config=self.dvm_config) - - def handle_cancel(self, nostr_event): - print(nostr_event.as_json()) - sender = nostr_event.author().to_hex() - if sender == self.keys.public_key().to_hex(): - return - - kind7001eventid = "" - recipient = "" - for tag in nostr_event.tags(): - if tag.as_vec()[0] == "p": - recipient = tag.as_vec()[1] - elif tag.as_vec()[0] == "e": - kind7001eventid = tag.as_vec()[1] - - if kind7001eventid != "": - subscription = get_from_subscription_sql_table(self.dvm_config.DB, kind7001eventid) - - if subscription is not None: - update_subscription_sql_table(self.dvm_config.DB, kind7001eventid, recipient, - subscription.subscriber, subscription.nwc, subscription.cadence, - subscription.amount, subscription.unit, subscription.begin, - subscription.end, - subscription.tier_dtag, subscription.zaps, subscription.recipe, - False, Timestamp.now().as_secs(), subscription.tier) - - def handle_nwc_request(self, nostr_event): - print(nostr_event.as_json()) - sender = nostr_event.author().to_hex() - if sender == self.keys.public_key().to_hex(): - return - - try: - decrypted_text = nip04_decrypt(self.keys.secret_key(), nostr_event.author(), nostr_event.content()) - try: - jsonevent = json.loads(decrypted_text) - nwc = "" - subscriber = "" - for entry in jsonevent: - if entry[1] == "nwc": - nwc = entry[2] - elif entry[1] == "p": - subscriber = entry[2] - - subscriptionfilter = Filter().kind(EventDefinitions.KIND_NIP88_SUBSCRIBE_EVENT).author( - PublicKey.parse(subscriber)).limit(1) - evts = self.client.get_events_of([subscriptionfilter], timedelta(seconds=3)) - - if len(evts) > 0: - self.process_subscription_event(evts[0], nwc, subscriber) - except Exception as e: - print(f"Error processing JSON event: {e}") - except Exception as e: - print(f"Error decrypting event content: {e}") - - def process_subscription_event(self, subscription_event, nwc, subscriber): - event7001id = subscription_event.id().to_hex() - tier_dtag = "" - recipient = "" - cadence = "" - unit = "msats" - zaps = [] - tier = "DVM" - overall_amount = 0 - subscription_event_id = "" - - for tag in subscription_event.tags(): - if tag.as_vec()[0] == "amount": - overall_amount = int(tag.as_vec()[1]) - unit = tag.as_vec()[2] - cadence = tag.as_vec()[3] - print(f"{overall_amount} {unit} {cadence}") - elif tag.as_vec()[0] == "p": - recipient = tag.as_vec()[1] - elif tag.as_vec()[0] == "e": - subscription_event_id = tag.as_vec()[1] - elif tag.as_vec()[0] == "event": - jsonevent = json.loads(tag.as_vec()[1]) - subscription_event = Event.from_json(jsonevent) - - for tag in subscription_event.tags(): - if tag.as_vec()[0] == "d": - tier_dtag = tag.as_vec()[1] - elif tag.as_vec()[0] == "zap": - zaps.append(tag.as_vec()) - elif tag.as_vec()[0] == "title": - tier = tag.as_vec()[1] - - if tier_dtag == "" or len(zaps) == 0: - tierfilter = Filter().id(EventId.parse(subscription_event_id)) - evts = self.client.get_events_of([tierfilter], timedelta(seconds=3)) - if len(evts) > 0: - for tag in evts[0].tags(): - if tag.as_vec()[0] == "d": - tier_dtag = tag.as_vec()[0] - - isactivesubscription = False - recipe = "" - - subscription = get_from_subscription_sql_table(self.dvm_config.DB, event7001id) - zapsstr = json.dumps(zaps) - print(zapsstr) - success = True - if subscription is None or subscription.end <= Timestamp.now().as_secs(): - # rather check nostr if our db is right - subscription_status = nip88_has_active_subscription( - PublicKey.parse(subscriber), - tier_dtag, self.client, recipient, checkCanceled=False) - - if not subscription_status["isActive"]: - - success = self.pay_zap_split(nwc, overall_amount, zaps, tier, unit) - start = Timestamp.now().as_secs() - end = self.infer_subscription_end_time(start, cadence) - else: - start = Timestamp.now().as_secs() - end = subscription_status["validUntil"] - else: - start = subscription.begin - end = subscription.end - - if success: - recipe = self.make_subscription_zap_recipe(event7001id, recipient, subscriber, start, end, - tier_dtag) - print("RECIPE " + recipe) - isactivesubscription = True - - if subscription is None: - add_to_subscription_sql_table(self.dvm_config.DB, event7001id, recipient, subscriber, nwc, - cadence, overall_amount, unit, start, end, tier_dtag, - zapsstr, recipe, isactivesubscription, - Timestamp.now().as_secs(), tier) - print("new subscription entry") - else: - update_subscription_sql_table(self.dvm_config.DB, event7001id, recipient, subscriber, nwc, - cadence, overall_amount, unit, start, end, - tier_dtag, zapsstr, recipe, isactivesubscription, - Timestamp.now().as_secs(), tier) - print("updated subscription entry") - - self.send_status_success(subscription_event, "noogle.lol") - - message = ("Subscribed to DVM " + tier + ". Renewing on: " + str( - Timestamp.from_secs(end).to_human_datetime().replace("Z", " ").replace("T", " "))) - self.send_direct_message(subscriber, message) + os.kill(os.getpid(), signal.SIGTERM) \ No newline at end of file