some more cleanup, preparations to update nostr sdk

This commit is contained in:
Believethehype
2023-11-21 23:51:48 +01:00
parent 1735a45513
commit 2286701453
15 changed files with 173 additions and 208 deletions

View File

@@ -7,7 +7,7 @@ import pandas as pd
import requests import requests
import PIL.Image as Image import PIL.Image as Image
from utils.output_utils import uploadMediaToHoster from utils.output_utils import upload_media_to_hoster
""" """
This file contains basic calling functions for ML tasks that are outsourced to nova-server This file contains basic calling functions for ML tasks that are outsourced to nova-server
@@ -78,7 +78,7 @@ def check_nova_server_status(jobID, address):
if content_type == "image/jpeg": if content_type == "image/jpeg":
image = Image.open(io.BytesIO(response.content)) image = Image.open(io.BytesIO(response.content))
image.save("./outputs/image.jpg") image.save("./outputs/image.jpg")
result = uploadMediaToHoster("./outputs/image.jpg") result = upload_media_to_hoster("./outputs/image.jpg")
os.remove("./outputs/image.jpg") os.remove("./outputs/image.jpg")
elif content_type == 'text/plain; charset=utf-8': elif content_type == 'text/plain; charset=utf-8':
result = response.content.decode('utf-8') result = response.content.decode('utf-8')
@@ -95,8 +95,9 @@ def check_nova_server_status(jobID, address):
print(result) print(result)
with open("response.zip", "wb") as f: with open("response.zip", "wb") as f:
f.write(response.content) f.write(response.content)
except: except Exception as e:
zf.extractall() #zf.extractall()
print(e)
return result return result
except Exception as e: except Exception as e:

192
dvm.py
View File

@@ -1,7 +1,7 @@
from nostr_sdk import PublicKey, Keys, Client, Tag, Event, EventBuilder, Filter, HandleNotification, Timestamp, \ from nostr_sdk import PublicKey, Keys, Client, Tag, Event, EventBuilder, Filter, HandleNotification, Timestamp, \
init_logger, LogLevel init_logger, LogLevel
import time import time
import emoji
from utils.definitions import EventDefinitions, RequiredJobToWatch, JobToWatch from utils.definitions import EventDefinitions, RequiredJobToWatch, JobToWatch
from utils.dvmconfig import DVMConfig from utils.dvmconfig import DVMConfig
@@ -10,8 +10,8 @@ from utils.backend_utils import get_amount_per_task, check_task_is_supported, ge
from utils.database_utils import update_sql_table, get_from_sql_table, \ from utils.database_utils import update_sql_table, get_from_sql_table, \
create_sql_table, get_or_add_user, update_user_balance create_sql_table, get_or_add_user, update_user_balance
from utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event from utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event
from utils.output_utils import post_process_result from utils.output_utils import post_process_result, build_status_reaction
from utils.zap_utils import check_bolt11_ln_bits_is_paid, parse_bolt11_invoice, \ from utils.zap_utils import check_bolt11_ln_bits_is_paid, parse_amount_from_bolt11_invoice, \
check_for_zapplepay, decrypt_private_zap_message, create_bolt11_ln_bits check_for_zapplepay, decrypt_private_zap_message, create_bolt11_ln_bits
use_logger = False use_logger = False
@@ -81,7 +81,7 @@ class DVM:
print(task) print(task)
if user.isblacklisted: if user.isblacklisted:
send_job_status_reaction(nip90_event, "error", client=self.client, config=self.dvm_config) send_job_status_reaction(nip90_event, "error", client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.name + "] Request by blacklisted user, skipped") print("[" + self.dvm_config.NIP89.name + "] Request by blacklisted user, skipped")
elif task_supported: elif task_supported:
@@ -98,7 +98,7 @@ class DVM:
if user.iswhitelisted or task_is_free: if user.iswhitelisted or task_is_free:
print("[" + self.dvm_config.NIP89.name + "] Free or Whitelisted for task " + task + ". Starting processing..") print("[" + self.dvm_config.NIP89.name + "] Free or Whitelisted for task " + task + ". Starting processing..")
send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client, send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client,
config=self.dvm_config) dvm_config=self.dvm_config)
do_work(nip90_event, is_from_bot=False) do_work(nip90_event, is_from_bot=False)
# otherwise send payment request # otherwise send payment request
else: else:
@@ -113,28 +113,28 @@ class DVM:
if bid_offer >= amount: if bid_offer >= amount:
send_job_status_reaction(nip90_event, "payment-required", False, send_job_status_reaction(nip90_event, "payment-required", False,
amount, # bid_offer amount, # bid_offer
client=self.client, config=self.dvm_config) client=self.client, dvm_config=self.dvm_config)
else: # If there is no bid, just request server rate from user else: # If there is no bid, just request server rate from user
print("[" + self.dvm_config.NIP89.name + "] Requesting payment for Event: " + nip90_event.id().to_hex()) print("[" + self.dvm_config.NIP89.name + "] Requesting payment for Event: " + nip90_event.id().to_hex())
send_job_status_reaction(nip90_event, "payment-required", send_job_status_reaction(nip90_event, "payment-required",
False, amount, client=self.client, config=self.dvm_config) False, amount, client=self.client, dvm_config=self.dvm_config)
else: else:
print("Task not supported on this DVM, skipping..") print("Task not supported on this DVM, skipping..")
def handle_zap(event): def handle_zap(zap_event):
zapped_event = None zapped_event = None
invoice_amount = 0 invoice_amount = 0
anon = False anon = False
sender = event.pubkey() sender = zap_event.pubkey()
print("Zap received") print("Zap received")
try: try:
for tag in event.tags(): for tag in zap_event.tags():
if tag.as_vec()[0] == 'bolt11': if tag.as_vec()[0] == 'bolt11':
invoice_amount = parse_bolt11_invoice(tag.as_vec()[1]) invoice_amount = parse_amount_from_bolt11_invoice(tag.as_vec()[1])
elif tag.as_vec()[0] == 'e': elif tag.as_vec()[0] == 'e':
zapped_event = get_event_by_id(tag.as_vec()[1], config=self.dvm_config) zapped_event = get_event_by_id(tag.as_vec()[1], client=self.client, config=self.dvm_config)
elif tag.as_vec()[0] == 'description': elif tag.as_vec()[0] == 'description':
zap_request_event = Event.from_json(tag.as_vec()[1]) zap_request_event = Event.from_json(tag.as_vec()[1])
sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(), sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(),
@@ -168,7 +168,7 @@ class DVM:
if tag.as_vec()[0] == 'amount': if tag.as_vec()[0] == 'amount':
amount = int(float(tag.as_vec()[1]) / 1000) amount = int(float(tag.as_vec()[1]) / 1000)
elif tag.as_vec()[0] == 'e': elif tag.as_vec()[0] == 'e':
job_event = get_event_by_id(tag.as_vec()[1], config=self.dvm_config) job_event = get_event_by_id(tag.as_vec()[1], client=self.client, config=self.dvm_config)
task_supported, task, duration = check_task_is_supported(job_event, client=self.client, task_supported, task, duration = check_task_is_supported(job_event, client=self.client,
get_duration=False, get_duration=False,
@@ -177,7 +177,7 @@ class DVM:
if amount <= invoice_amount: if amount <= invoice_amount:
print("[" + self.dvm_config.NIP89.name + "] Payment-request fulfilled...") print("[" + self.dvm_config.NIP89.name + "] Payment-request fulfilled...")
send_job_status_reaction(job_event, "processing", client=self.client, send_job_status_reaction(job_event, "processing", client=self.client,
config=self.dvm_config) dvm_config=self.dvm_config)
indices = [i for i, x in enumerate(self.job_list) if indices = [i for i, x in enumerate(self.job_list) if
x.event_id == job_event.id().to_hex()] x.event_id == job_event.id().to_hex()]
index = -1 index = -1
@@ -187,8 +187,7 @@ class DVM:
if self.job_list[index].is_processed: # If payment-required appears a processing if self.job_list[index].is_processed: # If payment-required appears a processing
self.job_list[index].is_paid = True self.job_list[index].is_paid = True
check_and_return_event(self.job_list[index].result, check_and_return_event(self.job_list[index].result,
str(job_event.as_json()), str(job_event.as_json()))
dvm_key=self.dvm_config.PRIVATE_KEY)
elif not (self.job_list[index]).is_processed: elif not (self.job_list[index]).is_processed:
# If payment-required appears before processing # If payment-required appears before processing
self.job_list.pop(index) self.job_list.pop(index)
@@ -201,7 +200,7 @@ class DVM:
else: else:
send_job_status_reaction(job_event, "payment-rejected", send_job_status_reaction(job_event, "payment-rejected",
False, invoice_amount, client=self.client, False, invoice_amount, client=self.client,
config=self.dvm_config) dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.name + "] Invoice was not paid sufficiently") print("[" + self.dvm_config.NIP89.name + "] Invoice was not paid sufficiently")
elif zapped_event.kind() in EventDefinitions.ANY_RESULT: elif zapped_event.kind() in EventDefinitions.ANY_RESULT:
@@ -209,13 +208,13 @@ class DVM:
elif not anon: elif not anon:
print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str( print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
user.name)) user.name))
update_user_balance(self.dvm_config.DB, sender, invoice_amount, config=self.dvm_config) update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client, config=self.dvm_config)
# a regular note # a regular note
elif not anon: elif not anon:
print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str( print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
user.name)) user.name))
update_user_balance(self.dvm_config.DB, sender, invoice_amount, config=self.dvm_config) update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client, config=self.dvm_config)
except Exception as e: except Exception as e:
print(f"Error during content decryption: {e}") print(f"Error during content decryption: {e}")
@@ -234,22 +233,21 @@ class DVM:
input = tag.as_vec()[1] input = tag.as_vec()[1]
input_type = tag.as_vec()[2] input_type = tag.as_vec()[2]
if input_type == "job": if input_type == "job":
evt = get_referenced_event_by_id(input, EventDefinitions.ANY_RESULT, client, evt = get_referenced_event_by_id(event_id=input, client=client, kinds=EventDefinitions.ANY_RESULT,
config=dvmconfig) dvm_config=dvmconfig)
if evt is None: if evt is None:
if append: if append:
job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs()) job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs())
self.jobs_on_hold_list.append(job) self.jobs_on_hold_list.append(job)
send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client, send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client,
config=dvmconfig) dvm_config=dvmconfig)
return False return False
else: else:
return True return True
def check_and_return_event(data, original_event_str: str, dvm_key=""): def check_and_return_event(data, original_event_str: str):
original_event = Event.from_json(original_event_str) original_event = Event.from_json(original_event_str)
keys = Keys.from_sk_str(dvm_key)
for x in self.job_list: for x in self.job_list:
if x.event_id == original_event.id().to_hex(): if x.event_id == original_event.id().to_hex():
@@ -258,63 +256,58 @@ class DVM:
x.result = data x.result = data
x.is_processed = True x.is_processed = True
if self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: if self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid:
send_nostr_reply_event(data, original_event_str, key=keys) send_nostr_reply_event(data, original_event_str,)
send_job_status_reaction(original_event, "success", amount, send_job_status_reaction(original_event, "success", amount,
config=self.dvm_config) # or payment-required, or both? dvm_config=self.dvm_config) # or payment-required, or both?
elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid: elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid:
send_job_status_reaction(original_event, "success", amount, send_job_status_reaction(original_event, "success", amount,
config=self.dvm_config) # or payment-required, or both? dvm_config=self.dvm_config) # or payment-required, or both?
if self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: if self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
self.job_list.remove(x) self.job_list.remove(x)
elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid: elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
self.job_list.remove(x) self.job_list.remove(x)
send_nostr_reply_event(data, original_event_str, key=keys) send_nostr_reply_event(data, original_event_str)
break break
try: try:
post_processed_content = post_process_result(data, original_event) post_processed_content = post_process_result(data, original_event)
send_nostr_reply_event(post_processed_content, original_event_str, key=keys) send_nostr_reply_event(post_processed_content, original_event_str)
except Exception as e: except Exception as e:
respond_to_error(e, original_event_str, False, self.dvm_config.PRIVATE_KEY) respond_to_error(str(e), original_event_str, False)
def send_nostr_reply_event(content, original_event_as_str, key=None): def send_nostr_reply_event(content, original_event_as_str):
originalevent = Event.from_json(original_event_as_str) original_event = Event.from_json(original_event_as_str)
requesttag = Tag.parse(["request", original_event_as_str.replace("\\", "")]) request_tag = Tag.parse(["request", original_event_as_str.replace("\\", "")])
etag = Tag.parse(["e", originalevent.id().to_hex()]) e_tag = Tag.parse(["e", original_event.id().to_hex()])
ptag = Tag.parse(["p", originalevent.pubkey().to_hex()]) p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
alttag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str( alt_tag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str(
originalevent.kind()) + ". The task was: " + originalevent.content()]) original_event.kind()) + ". The task was: " + original_event.content()])
statustag = Tag.parse(["status", "success"]) status_tag = Tag.parse(["status", "success"])
replytags = [requesttag, etag, ptag, alttag, statustag] reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag]
for tag in originalevent.tags(): for tag in original_event.tags():
if tag.as_vec()[0] == "i": if tag.as_vec()[0] == "i":
icontent = tag.as_vec()[1] i_content = tag.as_vec()[1]
ikind = tag.as_vec()[2] i_kind = tag.as_vec()[2]
itag = Tag.parse(["i", icontent, ikind]) i_tag = Tag.parse(["i", i_content, i_kind])
replytags.append(itag) reply_tags.append(i_tag)
if key is None: key = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY)
key = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY)
response_kind = originalevent.kind() + 1000 response_kind = original_event.kind() + 1000
event = EventBuilder(response_kind, str(content), replytags).to_event(key) reply_event = EventBuilder(response_kind, str(content), reply_tags).to_event(key)
send_event(event, key=key) send_event(reply_event, client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.name + "]" + str(response_kind) + " Job Response event sent: " + event.as_json()) print("[" + self.dvm_config.NIP89.name + "]" + str(response_kind) + " Job Response event sent: " + reply_event.as_json())
return event.as_json() return reply_event.as_json()
def respond_to_error(content, originaleventstr, is_from_bot=False, dvm_key=None): def respond_to_error(content: str, original_event_as_str: str, is_from_bot=False):
print("ERROR") print("ERROR: " + str(content))
if dvm_key is None: keys = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY)
keys = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY) original_event = Event.from_json(original_event_as_str)
else:
keys = Keys.from_sk_str(dvm_key)
original_event = Event.from_json(originaleventstr)
sender = "" sender = ""
task = "" task = ""
if not is_from_bot: if not is_from_bot:
send_job_status_reaction(original_event, "error", content=str(content), key=dvm_key) send_job_status_reaction(original_event, "error", content=content, dvm_config=self.dvm_config)
# TODO Send Zap back # TODO Send Zap back
else: else:
for tag in original_event.tags(): for tag in original_event.tags():
@@ -336,46 +329,14 @@ class DVM:
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message, evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message,
None).to_event(keys) None).to_event(keys)
send_event(evt, key=keys) send_event(evt, client=self.client, dvm_config=self.dvm_config)
def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None, def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None,
content=None, content=None,
config=None, dvm_config=None):
key=None): dvm_config = dvm_config
dvmconfig = config task = get_task(original_event, client=client, dvmconfig=dvm_config)
alt_description = "This is a reaction to a NIP90 DVM AI task. " alt_description, reaction = build_status_reaction(status, task, amount, content)
task = get_task(original_event, client=client, dvmconfig=dvmconfig)
if status == "processing":
alt_description = "NIP90 DVM AI task " + task + " started processing. "
reaction = alt_description + emoji.emojize(":thumbs_up:")
elif status == "success":
alt_description = "NIP90 DVM AI task " + task + " finished successfully. "
reaction = alt_description + emoji.emojize(":call_me_hand:")
elif status == "chain-scheduled":
alt_description = "NIP90 DVM AI task " + task + " Chain Task scheduled"
reaction = alt_description + emoji.emojize(":thumbs_up:")
elif status == "error":
alt_description = "NIP90 DVM AI task " + task + " had an error. "
if content is None:
reaction = alt_description + emoji.emojize(":thumbs_down:")
else:
reaction = alt_description + emoji.emojize(":thumbs_down:") + content
elif status == "payment-required":
alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":orange_heart:")
elif status == "payment-rejected":
alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str(
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":thumbs_down:")
elif status == "user-blocked-from-service":
alt_description = "NIP90 DVM AI task " + task + " can't be performed. User has been blocked from Service. "
reaction = alt_description + emoji.emojize(":thumbs_down:")
else:
reaction = emoji.emojize(":thumbs_down:")
e_tag = Tag.parse(["e", original_event.id().to_hex()]) e_tag = Tag.parse(["e", original_event.id().to_hex()])
p_tag = Tag.parse(["p", original_event.pubkey().to_hex()]) p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
@@ -394,9 +355,9 @@ class DVM:
payment_hash = "" payment_hash = ""
expires = original_event.created_at().as_secs() + (60 * 60 * 24) expires = original_event.created_at().as_secs() + (60 * 60 * 24)
if status == "payment-required" or (status == "processing" and not is_paid): if status == "payment-required" or (status == "processing" and not is_paid):
if dvmconfig.LNBITS_INVOICE_KEY != "": if dvm_config.LNBITS_INVOICE_KEY != "":
try: try:
bolt11, payment_hash = create_bolt11_ln_bits(amount, dvmconfig) bolt11, payment_hash = create_bolt11_ln_bits(amount, dvm_config)
except Exception as e: except Exception as e:
print(e) print(e)
@@ -410,22 +371,20 @@ class DVM:
payment_hash=payment_hash, payment_hash=payment_hash,
expires=expires, from_bot=False)) expires=expires, from_bot=False))
#print(str(self.job_list)) #print(str(self.job_list))
if status == "payment-required" or status == "payment-rejected" or ( if (status == "payment-required" or status == "payment-rejected" or (
status == "processing" and not is_paid) or ( status == "processing" and not is_paid)
status == "success" and not is_paid): or (status == "success" and not is_paid)):
if dvmconfig.LNBITS_INVOICE_KEY != "": if dvm_config.LNBITS_INVOICE_KEY != "":
amount_tag = Tag.parse(["amount", str(amount * 1000), bolt11]) amount_tag = Tag.parse(["amount", str(amount * 1000), bolt11])
else: else:
amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats
tags.append(amount_tag) tags.append(amount_tag)
if key is not None:
keys = Keys.from_sk_str(key) keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY)
else:
keys = Keys.from_sk_str(dvmconfig.PRIVATE_KEY)
event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys) event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys)
send_event(event, key=keys) send_event(event, client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.name + "]" + ": Sent Kind " + str( print("[" + self.dvm_config.NIP89.name + "]" + ": Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json()) EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json())
return event.as_json() return event.as_json()
@@ -443,12 +402,11 @@ class DVM:
request_form = dvm.create_request_form_from_nostr_event(job_event, self.client, request_form = dvm.create_request_form_from_nostr_event(job_event, self.client,
self.dvm_config) self.dvm_config)
result = dvm.process(request_form) result = dvm.process(request_form)
check_and_return_event(result, str(job_event.as_json()), check_and_return_event(result, str(job_event.as_json()))
dvm_key=self.dvm_config.PRIVATE_KEY)
except Exception as e: except Exception as e:
print(e) print(e)
respond_to_error(e, job_event.as_json(), is_from_bot, self.dvm_config.PRIVATE_KEY) respond_to_error(str(e), job_event.as_json(), is_from_bot)
return return
self.client.handle_notifications(NotificationHandler()) self.client.handle_notifications(NotificationHandler())
@@ -457,11 +415,11 @@ class DVM:
if job.bolt11 != "" and job.payment_hash != "" and not job.is_paid: if job.bolt11 != "" and job.payment_hash != "" and not job.is_paid:
if str(check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config)) == "True": if str(check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config)) == "True":
job.is_paid = True job.is_paid = True
event = get_event_by_id(job.event_id, config=self.dvm_config) event = get_event_by_id(job.event_id, client=self.client, config=self.dvm_config)
if event is not None: if event is not None:
send_job_status_reaction(event, "processing", True, 0, send_job_status_reaction(event, "processing", True, 0,
client=self.client, client=self.client,
config=self.dvm_config) dvm_config=self.dvm_config)
print("do work from joblist") print("do work from joblist")
do_work(event, is_from_bot=False) do_work(event, is_from_bot=False)
@@ -469,13 +427,13 @@ class DVM:
try: try:
self.job_list.remove(job) self.job_list.remove(job)
except: except:
continue print("Error removing Job from List after payment")
if Timestamp.now().as_secs() > job.expires: if Timestamp.now().as_secs() > job.expires:
try: try:
self.job_list.remove(job) self.job_list.remove(job)
except: except:
continue print("Error removing Job from List after expiry")
for job in self.jobs_on_hold_list: for job in self.jobs_on_hold_list:
if check_event_has_not_unfinished_job_input(job.event, False, client=self.client, if check_event_has_not_unfinished_job_input(job.event, False, client=self.client,
@@ -484,9 +442,9 @@ class DVM:
try: try:
self.jobs_on_hold_list.remove(job) self.jobs_on_hold_list.remove(job)
except: except:
continue print("Error removing Job on Hold from List after expiry")
if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes.. if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes..
self.jobs_on_hold_list.remove(job) self.jobs_on_hold_list.remove(job)
time.sleep(2.0) time.sleep(1.0)

View File

@@ -21,7 +21,7 @@ class ImageGenerationSDXL(DVMTaskInterface):
NAME: str = "" NAME: str = ""
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image" TASK: str = "text-to-image"
COST: int = 5 COST: int = 50
PK: str PK: str
def __init__(self, name, dvm_config: DVMConfig, admin_config: AdminConfig = None, default_model=None, default_lora=None): def __init__(self, name, dvm_config: DVMConfig, admin_config: AdminConfig = None, default_model=None, default_lora=None):
@@ -102,8 +102,6 @@ class ImageGenerationSDXL(DVMTaskInterface):
if len(split) > 1: if len(split) > 1:
width = split[0] width = split[0]
height = split[1] height = split[1]
print(width)
print(height)
elif tag.as_vec()[1] == "model": elif tag.as_vec()[1] == "model":
model = tag.as_vec()[2] model = tag.as_vec()[2]

View File

@@ -56,7 +56,7 @@ class TextExtractionPDF(DVMTaskInterface):
url = input_content url = input_content
# if event contains url to pdf, we checked for a pdf link before # if event contains url to pdf, we checked for a pdf link before
elif input_type == "event": elif input_type == "event":
evt = get_event_by_id(input_content, config=dvm_config) evt = get_event_by_id(input_content, client=client, config=dvm_config)
url = re.search("(?P<url>https?://[^\s]+)", evt.content()).group("url") url = re.search("(?P<url>https?://[^\s]+)", evt.content()).group("url")
request_form["optStr"] = 'url=' + url request_form["optStr"] = 'url=' + url

View File

@@ -58,7 +58,7 @@ class Translation(DVMTaskInterface):
if input_type == "event": if input_type == "event":
for tag in event.tags(): for tag in event.tags():
if tag.as_vec()[0] == 'i': if tag.as_vec()[0] == 'i':
evt = get_event_by_id(tag.as_vec()[1], config=dvm_config) evt = get_event_by_id(tag.as_vec()[1], client=client, config=dvm_config)
text = evt.content() text = evt.content()
break break
@@ -71,12 +71,11 @@ class Translation(DVMTaskInterface):
elif input_type == "job": elif input_type == "job":
for tag in event.tags(): for tag in event.tags():
if tag.as_vec()[0] == 'i': if tag.as_vec()[0] == 'i':
evt = get_referenced_event_by_id(tag.as_vec()[1], evt = get_referenced_event_by_id(event_id=tag.as_vec()[1], client=client,
[EventDefinitions.KIND_NIP90_RESULT_EXTRACT_TEXT, kinds=[EventDefinitions.KIND_NIP90_RESULT_EXTRACT_TEXT,
EventDefinitions.KIND_NIP90_RESULT_SUMMARIZE_TEXT, EventDefinitions.KIND_NIP90_RESULT_SUMMARIZE_TEXT,
EventDefinitions.KIND_NIP90_RESULT_TRANSLATE_TEXT], EventDefinitions.KIND_NIP90_RESULT_TRANSLATE_TEXT],
client, dvm_config=dvm_config)
config=dvm_config)
text = evt.content() text = evt.content()
break break

View File

@@ -7,6 +7,7 @@ from threading import Thread
import dotenv import dotenv
from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt
from utils.dvmconfig import DVMConfig
from utils.nostr_utils import send_event from utils.nostr_utils import send_event
from utils.definitions import EventDefinitions, RELAY_LIST from utils.definitions import EventDefinitions, RELAY_LIST
@@ -36,7 +37,8 @@ def nostr_client_test_translation(input, kind, lang, sats, satsmax):
for relay in relay_list: for relay in relay_list:
client.add_relay(relay) client.add_relay(relay)
client.connect() client.connect()
send_event(event, client, keys) config = DVMConfig
send_event(event, client=client, dvm_config=config)
return event.as_json() return event.as_json()
@@ -62,7 +64,8 @@ def nostr_client_test_image(prompt):
for relay in relay_list: for relay in relay_list:
client.add_relay(relay) client.add_relay(relay)
client.connect() client.connect()
send_event(event, client, keys) config = DVMConfig
send_event(event, client=client, dvm_config=config)
return event.as_json() return event.as_json()
def nostr_client(): def nostr_client():

View File

@@ -73,4 +73,4 @@ def admin_make_database_updates(adminconfig: AdminConfig = None, dvmconfig: DVMC
list_db(db) list_db(db)
if rebroadcast_nip89: if rebroadcast_nip89:
nip89_announce_tasks(dvmconfig) nip89_announce_tasks(dvmconfig, client=client)

View File

@@ -28,7 +28,7 @@ def get_task(event, client, dvmconfig):
else: else:
return "unknown job" return "unknown job"
elif tag.as_vec()[2] == "event": elif tag.as_vec()[2] == "event":
evt = get_event_by_id(tag.as_vec()[1], config=dvmconfig) evt = get_event_by_id(tag.as_vec()[1], client=client, config=dvmconfig)
if evt is not None: if evt is not None:
if evt.kind() == 1063: if evt.kind() == 1063:
for tg in evt.tags(): for tg in evt.tags():
@@ -65,7 +65,7 @@ def check_task_is_supported(event, client, get_duration=False, config=None):
input_value = tag.as_vec()[1] input_value = tag.as_vec()[1]
input_type = tag.as_vec()[2] input_type = tag.as_vec()[2]
if input_type == "event": if input_type == "event":
evt = get_event_by_id(input_value, config=dvm_config) evt = get_event_by_id(input_value, client=client, config=dvm_config)
if evt is None: if evt is None:
print("Event not found") print("Event not found")
return False, "", 0 return False, "", 0

View File

@@ -155,7 +155,7 @@ def list_db(db):
print(e) print(e)
def update_user_balance(db, sender, sats, config=None): def update_user_balance(db, sender, sats, client, config):
user = get_from_sql_table(db, sender) user = get_from_sql_table(db, sender)
if user is None: if user is None:
add_to_sql_table(db, sender, (int(sats) + NEW_USER_BALANCE), False, False, add_to_sql_table(db, sender, (int(sats) + NEW_USER_BALANCE), False, False,
@@ -186,7 +186,7 @@ def update_user_balance(db, sender, sats, config=None):
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message, evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message,
None).to_event(keys) None).to_event(keys)
send_event(evt, key=keys) send_event(evt, client=client, dvm_config=config)
def get_or_add_user(db, sender): def get_or_add_user(db, sender):

View File

@@ -7,9 +7,7 @@ from utils import env
NEW_USER_BALANCE: int = 250 # Free credits for new users NEW_USER_BALANCE: int = 250 # Free credits for new users
RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine",
"wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg",
"wss://relay.f7z.io"]
class EventDefinitions: class EventDefinitions:

View File

@@ -11,6 +11,7 @@ class DVMConfig:
RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine", RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine",
"wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg",
"wss://relay.f7z.io"] "wss://relay.f7z.io"]
RELAY_TIMEOUT = 5 RELAY_TIMEOUT = 5
LNBITS_INVOICE_KEY = '' LNBITS_INVOICE_KEY = ''
LNBITS_URL = 'https://lnbits.com' LNBITS_URL = 'https://lnbits.com'

View File

@@ -1,6 +1,8 @@
from nostr_sdk import Tag, Keys, EventBuilder from nostr_sdk import Tag, Keys, EventBuilder
from utils.nostr_utils import send_event from utils.nostr_utils import send_event
class NIP89Announcement: class NIP89Announcement:
name: str name: str
kind: int kind: int
@@ -8,11 +10,12 @@ class NIP89Announcement:
pk: str pk: str
content: str content: str
def nip89_announce_tasks(dvmconfig):
k_tag = Tag.parse(["k", str(dvmconfig.NIP89.kind)]) def nip89_announce_tasks(dvm_config, client):
d_tag = Tag.parse(["d", dvmconfig.NIP89.dtag]) k_tag = Tag.parse(["k", str(dvm_config.NIP89.kind)])
keys = Keys.from_sk_str(dvmconfig.NIP89.pk) d_tag = Tag.parse(["d", dvm_config.NIP89.dtag])
content = dvmconfig.NIP89.content keys = Keys.from_sk_str(dvm_config.NIP89.pk)
content = dvm_config.NIP89.content
event = EventBuilder(31990, content, [k_tag, d_tag]).to_event(keys) event = EventBuilder(31990, content, [k_tag, d_tag]).to_event(keys)
send_event(event, key=keys) send_event(event, client=client, dvm_config=dvm_config)
print("Announced NIP 89 for " + dvmconfig.NIP89.name) print("Announced NIP 89 for " + dvm_config.NIP89.name)

View File

@@ -1,19 +1,8 @@
from datetime import timedelta from datetime import timedelta
from nostr_sdk import Keys, Filter, Client, Alphabet, EventId, Options from nostr_sdk import Keys, Filter, Client, Alphabet, EventId, Options, Event
from utils.definitions import RELAY_LIST
def get_event_by_id(event_id, client=None, config=None): def get_event_by_id(event_id: str, client: Client, config=None) -> Event | None:
is_new_client = False
if client is None:
keys = Keys.from_sk_str(config.PRIVATE_KEY)
client = Client(keys)
for relay in config.RELAY_LIST:
client.add_relay(relay)
client.connect()
is_new_client = True
split = event_id.split(":") split = event_id.split(":")
if len(split) == 3: if len(split) == 3:
id_filter = Filter().author(split[1]).custom_tag(Alphabet.D, [split[2]]) id_filter = Filter().author(split[1]).custom_tag(Alphabet.D, [split[2]])
@@ -23,69 +12,44 @@ def get_event_by_id(event_id, client=None, config=None):
event_id = EventId.from_bech32(event_id).to_hex() event_id = EventId.from_bech32(event_id).to_hex()
id_filter = Filter().id(event_id).limit(1) id_filter = Filter().id(event_id).limit(1)
events = client.get_events_of([id_filter], timedelta(seconds=config.RELAY_TIMEOUT)) events = client.get_events_of([id_filter], timedelta(seconds=config.RELAY_TIMEOUT))
if is_new_client:
client.disconnect()
if len(events) > 0: if len(events) > 0:
return events[0] return events[0]
else: else:
return None return None
def get_referenced_event_by_id(event_id, kinds=None, client=None, config=None): def get_referenced_event_by_id(event_id, client, dvm_config, kinds) -> Event | None:
if kinds is None:
kinds = []
is_new_client = False
if client is None:
keys = Keys.from_sk_str(config.PRIVATE_KEY)
client = Client(keys)
for relay in config.RELAY_LIST:
client.add_relay(relay)
client.connect()
is_new_client = True
if kinds is None: if kinds is None:
kinds = [] kinds = []
if len(kinds) > 0: if len(kinds) > 0:
job_id_filter = Filter().kinds(kinds).event(EventId.from_hex(event_id)).limit(1) job_id_filter = Filter().kinds(kinds).event(EventId.from_hex(event_id)).limit(1)
else: else:
job_id_filter = Filter().event(EventId.from_hex(event_id)).limit(1) job_id_filter = Filter().event(EventId.from_hex(event_id)).limit(1)
events = client.get_events_of([job_id_filter], timedelta(seconds=config.RELAY_TIMEOUT)) events = client.get_events_of([job_id_filter], timedelta(seconds=dvm_config.RELAY_TIMEOUT))
if is_new_client:
client.disconnect()
if len(events) > 0: if len(events) > 0:
return events[0] return events[0]
else: else:
return None return None
def send_event(event, client=None, key=None): def send_event(event: Event, client: Client, dvm_config) -> EventId:
relays = [] relays = []
is_new_client = False
for tag in event.tags(): for tag in event.tags():
if tag.as_vec()[0] == 'relays': if tag.as_vec()[0] == 'relays':
relays = tag.as_vec()[1].split(',') relays = tag.as_vec()[1].split(',')
if client is None:
opts = Options().wait_for_send(False).send_timeout(timedelta(seconds=5)).skip_disconnected_relays(True)
client = Client.with_opts(key, opts)
for relay in RELAY_LIST:
client.add_relay(relay)
client.connect()
is_new_client = True
for relay in relays: for relay in relays:
if relay not in RELAY_LIST: if relay not in dvm_config.RELAY_LIST:
client.add_relay(relay) client.add_relay(relay)
event_id = client.send_event(event) event_id = client.send_event(event)
for relay in relays: for relay in relays:
if relay not in RELAY_LIST: if relay not in dvm_config.RELAY_LIST:
client.remove_relay(relay) client.remove_relay(relay)
if is_new_client:
client.disconnect()
return event_id return event_id

View File

@@ -2,6 +2,8 @@ import json
import datetime as datetime import datetime as datetime
import os import os
from types import NoneType from types import NoneType
import emoji
import requests import requests
from pyupload.uploader import CatboxUploader from pyupload.uploader import CatboxUploader
@@ -109,7 +111,7 @@ Will probably need to switch to another system in the future.
''' '''
def uploadMediaToHoster(filepath): def upload_media_to_hoster(filepath: str):
print("Uploading image: " + filepath) print("Uploading image: " + filepath)
try: try:
files = {'file': open(filepath, 'rb')} files = {'file': open(filepath, 'rb')}
@@ -144,3 +146,40 @@ def uploadMediaToHoster(filepath):
return result return result
except: except:
return "Upload not possible, all hosters didn't work" return "Upload not possible, all hosters didn't work"
def build_status_reaction(status, task, amount, content):
alt_description = "This is a reaction to a NIP90 DVM AI task. "
if status == "processing":
alt_description = "NIP90 DVM AI task " + task + " started processing. "
reaction = alt_description + emoji.emojize(":thumbs_up:")
elif status == "success":
alt_description = "NIP90 DVM AI task " + task + " finished successfully. "
reaction = alt_description + emoji.emojize(":call_me_hand:")
elif status == "chain-scheduled":
alt_description = "NIP90 DVM AI task " + task + " Chain Task scheduled"
reaction = alt_description + emoji.emojize(":thumbs_up:")
elif status == "error":
alt_description = "NIP90 DVM AI task " + task + " had an error. "
if content is None:
reaction = alt_description + emoji.emojize(":thumbs_down:")
else:
reaction = alt_description + emoji.emojize(":thumbs_down:") + content
elif status == "payment-required":
alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":orange_heart:")
elif status == "payment-rejected":
alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str(
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":thumbs_down:")
elif status == "user-blocked-from-service":
alt_description = "NIP90 DVM AI task " + task + " can't be performed. User has been blocked from Service. "
reaction = alt_description + emoji.emojize(":thumbs_down:")
else:
reaction = emoji.emojize(":thumbs_down:")
return alt_description, reaction

View File

@@ -4,10 +4,11 @@ import json
import requests import requests
from Crypto.Cipher import AES from Crypto.Cipher import AES
from bech32 import bech32_decode, convertbits from bech32 import bech32_decode, convertbits
from nostr_sdk import PublicKey, nostr_sdk from nostr_sdk import nostr_sdk, PublicKey, SecretKey
from utils.dvmconfig import DVMConfig
def parse_bolt11_invoice(invoice): def parse_amount_from_bolt11_invoice(bolt11_invoice: str) -> int:
def get_index_of_first_letter(ip): def get_index_of_first_letter(ip):
index = 0 index = 0
for c in ip: for c in ip:
@@ -17,7 +18,7 @@ def parse_bolt11_invoice(invoice):
index = index + 1 index = index + 1
return len(ip) return len(ip)
remaining_invoice = invoice[4:] remaining_invoice = bolt11_invoice[4:]
index = get_index_of_first_letter(remaining_invoice) index = get_index_of_first_letter(remaining_invoice)
identifier = remaining_invoice[index] identifier = remaining_invoice[index]
number_string = remaining_invoice[:index] number_string = remaining_invoice[:index]
@@ -34,9 +35,9 @@ def parse_bolt11_invoice(invoice):
return int(number) return int(number)
def create_bolt11_ln_bits(sats, config): def create_bolt11_ln_bits(sats: int, config: DVMConfig) -> (str, str):
url = config.LNBITS_URL + "/api/v1/payments" url = config.LNBITS_URL + "/api/v1/payments"
data = {'out': False, 'amount': sats, 'memo': "Nostr-DVM"} data = {'out': False, 'amount': sats, 'memo': "Nostr-DVM " + config.NIP89.name}
headers = {'X-API-Key': config.LNBITS_INVOICE_KEY, 'Content-Type': 'application/json', 'charset': 'UTF-8'} headers = {'X-API-Key': config.LNBITS_INVOICE_KEY, 'Content-Type': 'application/json', 'charset': 'UTF-8'}
try: try:
res = requests.post(url, json=data, headers=headers) res = requests.post(url, json=data, headers=headers)
@@ -44,35 +45,35 @@ def create_bolt11_ln_bits(sats, config):
return obj["payment_request"], obj["payment_hash"] return obj["payment_request"], obj["payment_hash"]
except Exception as e: except Exception as e:
print("LNBITS: " + str(e)) print("LNBITS: " + str(e))
return None return None, None
def check_bolt11_ln_bits_is_paid(payment_hash, config): def check_bolt11_ln_bits_is_paid(payment_hash: str, config: DVMConfig):
url = config.LNBITS_URL + "/api/v1/payments/" + payment_hash url = config.LNBITS_URL + "/api/v1/payments/" + payment_hash
headers = {'X-API-Key': config.LNBITS_INVOICE_KEY, 'Content-Type': 'application/json', 'charset': 'UTF-8'} headers = {'X-API-Key': config.LNBITS_INVOICE_KEY, 'Content-Type': 'application/json', 'charset': 'UTF-8'}
try: try:
res = requests.get(url, headers=headers) res = requests.get(url, headers=headers)
obj = json.loads(res.text) obj = json.loads(res.text)
return obj["paid"] return obj["paid"] #TODO cast
except Exception as e: except Exception as e:
return None return None
# DECRYPT ZAPS # DECRYPT ZAPS
def check_for_zapplepay(sender, content): def check_for_zapplepay(pubkey_hex: str, content: str):
try: try:
# Special case Zapplepay # Special case Zapplepay
if sender == PublicKey.from_bech32("npub1wxl6njlcgygduct7jkgzrvyvd9fylj4pqvll6p32h59wyetm5fxqjchcan").to_hex(): if pubkey_hex == PublicKey.from_bech32("npub1wxl6njlcgygduct7jkgzrvyvd9fylj4pqvll6p32h59wyetm5fxqjchcan").to_hex():
real_sender_bech32 = content.replace("From: nostr:", "") real_sender_bech32 = content.replace("From: nostr:", "")
sender = PublicKey.from_bech32(real_sender_bech32).to_hex() pubkey_hex = PublicKey.from_bech32(real_sender_bech32).to_hex()
return sender return pubkey_hex
except Exception as e: except Exception as e:
print(e) print(e)
return sender return pubkey_hex
def decrypt_private_zap_message(msg, privkey, pubkey): def decrypt_private_zap_message(msg: str, privkey: SecretKey, pubkey: PublicKey):
shared_secret = nostr_sdk.generate_shared_key(privkey, pubkey) shared_secret = nostr_sdk.generate_shared_key(privkey, pubkey)
if len(shared_secret) != 16 and len(shared_secret) != 32: if len(shared_secret) != 16 and len(shared_secret) != 32:
return "invalid shared secret size" return "invalid shared secret size"