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 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
@ -78,7 +78,7 @@ def check_nova_server_status(jobID, address):
if content_type == "image/jpeg":
image = Image.open(io.BytesIO(response.content))
image.save("./outputs/image.jpg")
result = uploadMediaToHoster("./outputs/image.jpg")
result = upload_media_to_hoster("./outputs/image.jpg")
os.remove("./outputs/image.jpg")
elif content_type == 'text/plain; charset=utf-8':
result = response.content.decode('utf-8')
@ -95,8 +95,9 @@ def check_nova_server_status(jobID, address):
print(result)
with open("response.zip", "wb") as f:
f.write(response.content)
except:
zf.extractall()
except Exception as e:
#zf.extractall()
print(e)
return result
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, \
init_logger, LogLevel
import time
import emoji
from utils.definitions import EventDefinitions, RequiredJobToWatch, JobToWatch
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, \
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.output_utils import post_process_result
from utils.zap_utils import check_bolt11_ln_bits_is_paid, parse_bolt11_invoice, \
from utils.output_utils import post_process_result, build_status_reaction
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
use_logger = False
@ -81,7 +81,7 @@ class DVM:
print(task)
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")
elif task_supported:
@ -98,7 +98,7 @@ class DVM:
if user.iswhitelisted or task_is_free:
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,
config=self.dvm_config)
dvm_config=self.dvm_config)
do_work(nip90_event, is_from_bot=False)
# otherwise send payment request
else:
@ -113,28 +113,28 @@ class DVM:
if bid_offer >= amount:
send_job_status_reaction(nip90_event, "payment-required", False,
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
print("[" + self.dvm_config.NIP89.name + "] Requesting payment for Event: " + nip90_event.id().to_hex())
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:
print("Task not supported on this DVM, skipping..")
def handle_zap(event):
def handle_zap(zap_event):
zapped_event = None
invoice_amount = 0
anon = False
sender = event.pubkey()
sender = zap_event.pubkey()
print("Zap received")
try:
for tag in event.tags():
for tag in zap_event.tags():
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':
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':
zap_request_event = Event.from_json(tag.as_vec()[1])
sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(),
@ -168,7 +168,7 @@ class DVM:
if tag.as_vec()[0] == 'amount':
amount = int(float(tag.as_vec()[1]) / 1000)
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,
get_duration=False,
@ -177,7 +177,7 @@ class DVM:
if amount <= invoice_amount:
print("[" + self.dvm_config.NIP89.name + "] Payment-request fulfilled...")
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
x.event_id == job_event.id().to_hex()]
index = -1
@ -187,8 +187,7 @@ class DVM:
if self.job_list[index].is_processed: # If payment-required appears a processing
self.job_list[index].is_paid = True
check_and_return_event(self.job_list[index].result,
str(job_event.as_json()),
dvm_key=self.dvm_config.PRIVATE_KEY)
str(job_event.as_json()))
elif not (self.job_list[index]).is_processed:
# If payment-required appears before processing
self.job_list.pop(index)
@ -201,7 +200,7 @@ class DVM:
else:
send_job_status_reaction(job_event, "payment-rejected",
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")
elif zapped_event.kind() in EventDefinitions.ANY_RESULT:
@ -209,13 +208,13 @@ class DVM:
elif not anon:
print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
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
elif not anon:
print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
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:
print(f"Error during content decryption: {e}")
@ -234,22 +233,21 @@ class DVM:
input = tag.as_vec()[1]
input_type = tag.as_vec()[2]
if input_type == "job":
evt = get_referenced_event_by_id(input, EventDefinitions.ANY_RESULT, client,
config=dvmconfig)
evt = get_referenced_event_by_id(event_id=input, client=client, kinds=EventDefinitions.ANY_RESULT,
dvm_config=dvmconfig)
if evt is None:
if append:
job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs())
self.jobs_on_hold_list.append(job)
send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client,
config=dvmconfig)
dvm_config=dvmconfig)
return False
else:
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)
keys = Keys.from_sk_str(dvm_key)
for x in self.job_list:
if x.event_id == original_event.id().to_hex():
@ -258,63 +256,58 @@ class DVM:
x.result = data
x.is_processed = True
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,
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:
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:
self.job_list.remove(x)
elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
self.job_list.remove(x)
send_nostr_reply_event(data, original_event_str, key=keys)
send_nostr_reply_event(data, original_event_str)
break
try:
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:
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):
originalevent = Event.from_json(original_event_as_str)
requesttag = Tag.parse(["request", original_event_as_str.replace("\\", "")])
etag = Tag.parse(["e", originalevent.id().to_hex()])
ptag = Tag.parse(["p", originalevent.pubkey().to_hex()])
alttag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str(
originalevent.kind()) + ". The task was: " + originalevent.content()])
statustag = Tag.parse(["status", "success"])
replytags = [requesttag, etag, ptag, alttag, statustag]
for tag in originalevent.tags():
def send_nostr_reply_event(content, original_event_as_str):
original_event = Event.from_json(original_event_as_str)
request_tag = Tag.parse(["request", original_event_as_str.replace("\\", "")])
e_tag = Tag.parse(["e", original_event.id().to_hex()])
p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
alt_tag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str(
original_event.kind()) + ". The task was: " + original_event.content()])
status_tag = Tag.parse(["status", "success"])
reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag]
for tag in original_event.tags():
if tag.as_vec()[0] == "i":
icontent = tag.as_vec()[1]
ikind = tag.as_vec()[2]
itag = Tag.parse(["i", icontent, ikind])
replytags.append(itag)
i_content = tag.as_vec()[1]
i_kind = tag.as_vec()[2]
i_tag = Tag.parse(["i", i_content, i_kind])
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
event = EventBuilder(response_kind, str(content), replytags).to_event(key)
send_event(event, key=key)
print("[" + self.dvm_config.NIP89.name + "]" + str(response_kind) + " Job Response event sent: " + event.as_json())
return event.as_json()
response_kind = original_event.kind() + 1000
reply_event = EventBuilder(response_kind, str(content), reply_tags).to_event(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: " + reply_event.as_json())
return reply_event.as_json()
def respond_to_error(content, originaleventstr, is_from_bot=False, dvm_key=None):
print("ERROR")
if dvm_key is None:
keys = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY)
else:
keys = Keys.from_sk_str(dvm_key)
original_event = Event.from_json(originaleventstr)
def respond_to_error(content: str, original_event_as_str: str, is_from_bot=False):
print("ERROR: " + str(content))
keys = Keys.from_sk_str(self.dvm_config.PRIVATE_KEY)
original_event = Event.from_json(original_event_as_str)
sender = ""
task = ""
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
else:
for tag in original_event.tags():
@ -336,46 +329,14 @@ class DVM:
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message,
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,
content=None,
config=None,
key=None):
dvmconfig = config
alt_description = "This is a reaction to a NIP90 DVM AI task. "
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:")
dvm_config=None):
dvm_config = dvm_config
task = get_task(original_event, client=client, dvmconfig=dvm_config)
alt_description, reaction = build_status_reaction(status, task, amount, content)
e_tag = Tag.parse(["e", original_event.id().to_hex()])
p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
@ -394,9 +355,9 @@ class DVM:
payment_hash = ""
expires = original_event.created_at().as_secs() + (60 * 60 * 24)
if status == "payment-required" or (status == "processing" and not is_paid):
if dvmconfig.LNBITS_INVOICE_KEY != "":
if dvm_config.LNBITS_INVOICE_KEY != "":
try:
bolt11, payment_hash = create_bolt11_ln_bits(amount, dvmconfig)
bolt11, payment_hash = create_bolt11_ln_bits(amount, dvm_config)
except Exception as e:
print(e)
@ -410,22 +371,20 @@ class DVM:
payment_hash=payment_hash,
expires=expires, from_bot=False))
#print(str(self.job_list))
if status == "payment-required" or status == "payment-rejected" or (
status == "processing" and not is_paid) or (
status == "success" and not is_paid):
if (status == "payment-required" or status == "payment-rejected" or (
status == "processing" 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])
else:
amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats
tags.append(amount_tag)
if key is not None:
keys = Keys.from_sk_str(key)
else:
keys = Keys.from_sk_str(dvmconfig.PRIVATE_KEY)
keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY)
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(
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + 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,
self.dvm_config)
result = dvm.process(request_form)
check_and_return_event(result, str(job_event.as_json()),
dvm_key=self.dvm_config.PRIVATE_KEY)
check_and_return_event(result, str(job_event.as_json()))
except Exception as 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
self.client.handle_notifications(NotificationHandler())
@ -457,11 +415,11 @@ class DVM:
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":
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:
send_job_status_reaction(event, "processing", True, 0,
client=self.client,
config=self.dvm_config)
dvm_config=self.dvm_config)
print("do work from joblist")
do_work(event, is_from_bot=False)
@ -469,13 +427,13 @@ class DVM:
try:
self.job_list.remove(job)
except:
continue
print("Error removing Job from List after payment")
if Timestamp.now().as_secs() > job.expires:
try:
self.job_list.remove(job)
except:
continue
print("Error removing Job from List after expiry")
for job in self.jobs_on_hold_list:
if check_event_has_not_unfinished_job_input(job.event, False, client=self.client,
@ -484,9 +442,9 @@ class DVM:
try:
self.jobs_on_hold_list.remove(job)
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..
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 = ""
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image"
COST: int = 5
COST: int = 50
PK: str
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:
width = split[0]
height = split[1]
print(width)
print(height)
elif tag.as_vec()[1] == "model":
model = tag.as_vec()[2]

View File

@ -56,7 +56,7 @@ class TextExtractionPDF(DVMTaskInterface):
url = input_content
# if event contains url to pdf, we checked for a pdf link before
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")
request_form["optStr"] = 'url=' + url

View File

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

View File

@ -7,6 +7,7 @@ from threading import Thread
import dotenv
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.definitions import EventDefinitions, RELAY_LIST
@ -36,7 +37,8 @@ def nostr_client_test_translation(input, kind, lang, sats, satsmax):
for relay in relay_list:
client.add_relay(relay)
client.connect()
send_event(event, client, keys)
config = DVMConfig
send_event(event, client=client, dvm_config=config)
return event.as_json()
@ -62,7 +64,8 @@ def nostr_client_test_image(prompt):
for relay in relay_list:
client.add_relay(relay)
client.connect()
send_event(event, client, keys)
config = DVMConfig
send_event(event, client=client, dvm_config=config)
return event.as_json()
def nostr_client():

View File

@ -73,4 +73,4 @@ def admin_make_database_updates(adminconfig: AdminConfig = None, dvmconfig: DVMC
list_db(db)
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:
return "unknown job"
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.kind() == 1063:
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_type = tag.as_vec()[2]
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:
print("Event not found")
return False, "", 0

View File

@ -155,7 +155,7 @@ def list_db(db):
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)
if user is None:
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,
None).to_event(keys)
send_event(evt, key=keys)
send_event(evt, client=client, dvm_config=config)
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
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:

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",
"wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg",
"wss://relay.f7z.io"]
RELAY_TIMEOUT = 5
LNBITS_INVOICE_KEY = ''
LNBITS_URL = 'https://lnbits.com'

View File

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

View File

@ -1,19 +1,8 @@
from datetime import timedelta
from nostr_sdk import Keys, Filter, Client, Alphabet, EventId, Options
from utils.definitions import RELAY_LIST
from nostr_sdk import Keys, Filter, Client, Alphabet, EventId, Options, Event
def get_event_by_id(event_id, client=None, config=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
def get_event_by_id(event_id: str, client: Client, config=None) -> Event | None:
split = event_id.split(":")
if len(split) == 3:
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()
id_filter = Filter().id(event_id).limit(1)
events = client.get_events_of([id_filter], timedelta(seconds=config.RELAY_TIMEOUT))
if is_new_client:
client.disconnect()
if len(events) > 0:
return events[0]
else:
return None
def get_referenced_event_by_id(event_id, kinds=None, client=None, config=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
def get_referenced_event_by_id(event_id, client, dvm_config, kinds) -> Event | None:
if kinds is None:
kinds = []
if len(kinds) > 0:
job_id_filter = Filter().kinds(kinds).event(EventId.from_hex(event_id)).limit(1)
else:
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:
return events[0]
else:
return None
def send_event(event, client=None, key=None):
def send_event(event: Event, client: Client, dvm_config) -> EventId:
relays = []
is_new_client = False
for tag in event.tags():
if tag.as_vec()[0] == 'relays':
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:
if relay not in RELAY_LIST:
if relay not in dvm_config.RELAY_LIST:
client.add_relay(relay)
event_id = client.send_event(event)
for relay in relays:
if relay not in RELAY_LIST:
if relay not in dvm_config.RELAY_LIST:
client.remove_relay(relay)
if is_new_client:
client.disconnect()
return event_id

View File

@ -2,6 +2,8 @@ import json
import datetime as datetime
import os
from types import NoneType
import emoji
import requests
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)
try:
files = {'file': open(filepath, 'rb')}
@ -144,3 +146,40 @@ def uploadMediaToHoster(filepath):
return result
except:
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
from Crypto.Cipher import AES
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):
index = 0
for c in ip:
@ -17,7 +18,7 @@ def parse_bolt11_invoice(invoice):
index = index + 1
return len(ip)
remaining_invoice = invoice[4:]
remaining_invoice = bolt11_invoice[4:]
index = get_index_of_first_letter(remaining_invoice)
identifier = remaining_invoice[index]
number_string = remaining_invoice[:index]
@ -34,9 +35,9 @@ def parse_bolt11_invoice(invoice):
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"
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'}
try:
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"]
except Exception as 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
headers = {'X-API-Key': config.LNBITS_INVOICE_KEY, 'Content-Type': 'application/json', 'charset': 'UTF-8'}
try:
res = requests.get(url, headers=headers)
obj = json.loads(res.text)
return obj["paid"]
return obj["paid"] #TODO cast
except Exception as e:
return None
# DECRYPT ZAPS
def check_for_zapplepay(sender, content):
def check_for_zapplepay(pubkey_hex: str, content: str):
try:
# 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:", "")
sender = PublicKey.from_bech32(real_sender_bech32).to_hex()
return sender
pubkey_hex = PublicKey.from_bech32(real_sender_bech32).to_hex()
return pubkey_hex
except Exception as 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)
if len(shared_secret) != 16 and len(shared_secret) != 32:
return "invalid shared secret size"