rebuild so each dvm runs on its own.

This commit is contained in:
Believethehype
2023-11-20 22:09:38 +01:00
parent 5a3f5606df
commit 13bffaea96
13 changed files with 547 additions and 482 deletions

View File

@@ -9,6 +9,7 @@ LNBITS_HOST = "https://lnbits.com"
TASK_TEXTEXTRACTION_NIP89_DTAG = "asdd" TASK_TEXTEXTRACTION_NIP89_DTAG = "asdd"
TASK_TRANSLATION_NIP89_DTAG = "abcded" TASK_TRANSLATION_NIP89_DTAG = "abcded"
TASK_IMAGEGENERATION_NIP89_DTAG = "fgdfgdf" TASK_IMAGEGENERATION_NIP89_DTAG = "fgdfgdf"
TASK_IMAGEGENERATION_NIP89_DTAG2 = "fgdfgdf"
#Backend Specific Options for tasks that require them #Backend Specific Options for tasks that require them
NOVA_SERVER = "127.0.0.1:37318" NOVA_SERVER = "127.0.0.1:37318"

View File

@@ -29,7 +29,7 @@ in the module that is calling the server
def send_request_to_nova_server(request_form, address): def send_request_to_nova_server(request_form, address):
print("Sending job to NOVA-Server") print("Sending job to NOVA-Server")
url = ('http://' + address + '/' + str(request_form["mode"]).lower()) url = ('http://' + address + '/process')
headers = {'Content-type': 'application/x-www-form-urlencoded'} headers = {'Content-type': 'application/x-www-form-urlencoded'}
response = requests.post(url, headers=headers, data=request_form) response = requests.post(url, headers=headers, data=request_form)
return response.content return response.content

823
dvm.py
View File

@@ -19,438 +19,473 @@ if use_logger:
job_list = [] job_list = []
jobs_on_hold_list = [] jobs_on_hold_list = []
dvm_config = DVMConfig()
def DVM(config): class DVM:
dvm_config = config dvm_config: DVMConfig
keys = Keys.from_sk_str(dvm_config.PRIVATE_KEY) keys: Keys
pk = keys.public_key() client: Client
print(f"Nostr DVM public key: {pk.to_bech32()}, Hex: {pk.to_hex()} ") def __init__(self, config):
print('Supported DVM tasks: ' + ', '.join(p.NAME + ":" + p.TASK for p in dvm_config.SUPPORTED_TASKS)) self.dvm_config = config
self.keys = Keys.from_sk_str(config.PRIVATE_KEY)
self.client = Client(self.keys)
pk = self.keys.public_key()
client = Client(keys) print("Nostr DVM public key: " + str(pk.to_bech32()) + "Hex: " + str(pk.to_hex()) + " Supported DVM tasks: " +
for relay in dvm_config.RELAY_LIST: ', '.join(p.NAME + ":" + p.TASK for p in self.dvm_config.SUPPORTED_TASKS) + "\n")
client.add_relay(relay)
client.connect()
dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) for relay in self.dvm_config.RELAY_LIST:
self.client.add_relay(relay)
self.client.connect()
kinds = [EventDefinitions.KIND_NIP90_GENERIC] dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now())
for dvm in dvm_config.SUPPORTED_TASKS:
if dvm.KIND not in kinds:
kinds.append(dvm.KIND)
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()))
client.subscribe([dm_zap_filter, dvm_filter])
create_sql_table() kinds = [EventDefinitions.KIND_NIP90_GENERIC]
admin_make_database_updates(config=dvm_config, client=client) for dvm in self.dvm_config.SUPPORTED_TASKS:
if dvm.KIND not in kinds:
kinds.append(dvm.KIND)
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()))
self.client.subscribe([dm_zap_filter, dvm_filter])
class NotificationHandler(HandleNotification): create_sql_table()
def handle(self, relay_url, nostr_event): admin_make_database_updates(config=self.dvm_config, client=self.client)
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC:
class NotificationHandler(HandleNotification):
client = self.client
dvm_config = self.dvm_config
keys = self.keys
def handle(self, relay_url, nostr_event):
print(f"[Nostr] Received new NIP90 Job Request from {relay_url}: {nostr_event.as_json()}") print(f"[Nostr] Received new NIP90 Job Request from {relay_url}: {nostr_event.as_json()}")
handle_nip90_job_event(nostr_event, dvm_config) if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC:
elif nostr_event.kind() == EventDefinitions.KIND_ZAP: self.handle_nip90_job_event(nostr_event)
handle_zap(nostr_event, dvm_config) elif nostr_event.kind() == EventDefinitions.KIND_ZAP:
self.handle_zap(nostr_event)
def handle_msg(self, relay_url, msg): def handle_msg(self, relay_url, msg):
return
def handle_nip90_job_event(event, dvm_config):
user = get_or_add_user(event.pubkey().to_hex())
task_supported, task, duration = check_task_is_supported(event, client=client,
get_duration=(not user.iswhitelisted),
config=dvm_config)
print(task)
if user.isblacklisted:
send_job_status_reaction(event, "error", client=client, config=dvm_config)
print("[Nostr] Request by blacklisted user, skipped")
elif task_supported:
print("Received new Task: " + task)
print(duration)
amount = get_amount_per_task(task, dvm_config, duration)
if amount is None:
return return
task_is_free = False def handle_nip90_job_event(self, nip90_event):
for dvm in dvm_config.SUPPORTED_TASKS: user = get_or_add_user(nip90_event.pubkey().to_hex())
if dvm.TASK == task and dvm.COST == 0: task_supported, task, duration = check_task_is_supported(nip90_event, client=self.client,
task_is_free = True get_duration=(not user.iswhitelisted),
config=self.dvm_config)
print(task)
if user.iswhitelisted or task_is_free: if user.isblacklisted:
print("[Nostr] Free or Whitelisted for task " + task + ". Starting processing..") send_job_status_reaction(nip90_event, "error", client=self.client, config=self.dvm_config)
send_job_status_reaction(event, "processing", True, 0, client=client, config=dvm_config) print("[Nostr] Request by blacklisted user, skipped")
do_work(event, is_from_bot=False)
# otherwise send payment request
else:
bid = 0
for tag in event.tags():
if tag.as_vec()[0] == 'bid':
bid = int(tag.as_vec()[1])
print("[Nostr][Payment required] New Nostr " + task + " Job event: " + event.as_json()) elif task_supported:
if bid > 0: print("Received new Task: " + task)
bid_offer = int(bid / 1000) amount = get_amount_per_task(task, self.dvm_config, duration)
if bid_offer >= amount: if amount is None:
send_job_status_reaction(event, "payment-required", False, return
amount, # bid_offer
client=client, config=dvm_config)
else: # If there is no bid, just request server rate from user task_is_free = False
print("[Nostr] Requesting payment for Event: " + event.id().to_hex()) for dvm in self.dvm_config.SUPPORTED_TASKS:
send_job_status_reaction(event, "payment-required", if dvm.TASK == task and dvm.COST == 0:
False, amount, client=client, config=dvm_config) task_is_free = True
else:
print("Task not supported on this DVM, skipping..")
def handle_zap(event, dvm_config): if user.iswhitelisted or task_is_free:
zapped_event = None print("[Nostr] Free or Whitelisted for task " + task + ". Starting processing..")
invoice_amount = 0 send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client,
anon = False config=self.dvm_config)
sender = event.pubkey() do_work(nip90_event, is_from_bot=False)
# otherwise send payment request
else:
bid = 0
for tag in nip90_event.tags():
if tag.as_vec()[0] == 'bid':
bid = int(tag.as_vec()[1])
try: print("[Nostr][Payment required] New Nostr " + task + " Job event: " + nip90_event.as_json())
for tag in event.tags(): if bid > 0:
if tag.as_vec()[0] == 'bolt11': bid_offer = int(bid / 1000)
invoice_amount = parse_bolt11_invoice(tag.as_vec()[1]) if bid_offer >= amount:
elif tag.as_vec()[0] == 'e': send_job_status_reaction(nip90_event, "payment-required", False,
zapped_event = get_event_by_id(tag.as_vec()[1], config=dvm_config) amount, # bid_offer
elif tag.as_vec()[0] == 'description': client=self.client, config=self.dvm_config)
zap_request_event = Event.from_json(tag.as_vec()[1])
sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(),
zap_request_event.content())
for ztag in zap_request_event.tags():
if ztag.as_vec()[0] == 'anon':
if len(ztag.as_vec()) > 1:
print("Private Zap received.")
decrypted_content = decrypt_private_zap_message(ztag.as_vec()[1],
keys.secret_key(),
zap_request_event.pubkey())
decrypted_private_event = Event.from_json(decrypted_content)
if decrypted_private_event.kind() == 9733:
sender = decrypted_private_event.pubkey().to_hex()
message = decrypted_private_event.content()
if message != "":
print("Zap Message: " + message)
else:
anon = True
print("Anonymous Zap received. Unlucky, I don't know from whom, and never will")
user = get_or_add_user(sender)
print(str(user))
if zapped_event is not None: else: # If there is no bid, just request server rate from user
if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped print("[Nostr] Requesting payment for Event: " + nip90_event.id().to_hex())
if not dvm_config.IS_BOT: send_job_status_reaction(nip90_event, "payment-required",
print("Zap received for NIP90 task: " + str(invoice_amount) + " Sats from " + str( False, amount, client=self.client, config=self.dvm_config)
user.name))
amount = 0
job_event = None
for tag in zapped_event.tags():
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=dvm_config)
task_supported, task, duration = check_task_is_supported(job_event, client=client,
get_duration=False, config=dvm_config)
if job_event is not None and task_supported:
if amount <= invoice_amount:
print("[Nostr] Payment-request fulfilled...")
send_job_status_reaction(job_event, "processing", client=client,
config=dvm_config)
indices = [i for i, x in enumerate(job_list) if
x.event_id == job_event.id().to_hex()]
index = -1
if len(indices) > 0:
index = indices[0]
if index > -1:
if job_list[index].is_processed: # If payment-required appears a processing
job_list[index].is_paid = True
check_and_return_event(job_list[index].result, str(job_event.as_json()),
dvm_key=dvm_config.PRIVATE_KEY)
elif not (job_list[index]).is_processed:
# If payment-required appears before processing
job_list.pop(index)
print("Starting work...")
do_work(job_event, is_from_bot=False)
else:
print("Job not in List, but starting work...")
do_work(job_event, is_from_bot=False)
else:
send_job_status_reaction(job_event, "payment-rejected",
False, invoice_amount, client=client, config=dvm_config)
print("[Nostr] Invoice was not paid sufficiently")
elif zapped_event.kind() in EventDefinitions.ANY_RESULT:
print("Someone zapped the result of an exisiting Task. Nice")
elif not anon and not dvm_config.PASSIVE_MODE:
print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
user.name))
update_user_balance(sender, invoice_amount, config=dvm_config)
# a regular note
elif not anon and not dvm_config.PASSIVE_MODE:
print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
user.name))
update_user_balance(sender, invoice_amount, config=dvm_config)
except Exception as e:
print(f"Error during content decryption: {e}")
def do_work(job_event, is_from_bot=False):
if ((EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= job_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC)
or job_event.kind() == EventDefinitions.KIND_DM):
task = get_task(job_event, client=client, dvmconfig=dvm_config)
result = ""
for dvm in dvm_config.SUPPORTED_TASKS:
try:
if task == dvm.TASK:
request_form = dvm.create_request_form_from_nostr_event(job_event, client, dvm_config)
result = dvm.process(request_form)
check_and_return_event(result, str(job_event.as_json()), dvm_key=dvm_config.PRIVATE_KEY)
except Exception as e:
print(e)
respond_to_error(e, job_event.as_json(), is_from_bot, dvm_config.PRIVATE_KEY)
return
def check_event_has_not_unfinished_job_input(nevent, append, client, dvmconfig):
task_supported, task, duration = check_task_is_supported(nevent, client, False, config=dvmconfig)
if not task_supported:
return False
for tag in nevent.tags():
if tag.as_vec()[0] == 'i':
if len(tag.as_vec()) < 3:
print("Job Event missing/malformed i tag, skipping..")
return False
else: else:
input = tag.as_vec()[1] print("Task not supported on this DVM, skipping..")
input_type = tag.as_vec()[2]
if input_type == "job":
evt = get_referenced_event_by_id(input, EventDefinitions.ANY_RESULT, client, config=dvmconfig)
if evt is None:
if append:
job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs())
jobs_on_hold_list.append(job)
send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client,
config=dvmconfig)
return False def handle_zap(self, event):
else: zapped_event = None
return True invoice_amount = 0
anon = False
sender = event.pubkey()
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:")
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", alt_description])
status_tag = Tag.parse(["status", status])
tags = [e_tag, p_tag, alt_tag, status_tag]
if status == "success" or status == "error": #
for x in job_list:
if x.event_id == original_event.id():
is_paid = x.is_paid
amount = x.amount
break
bolt11 = ""
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 != "":
try: try:
bolt11, payment_hash = create_bolt11_ln_bits(amount, dvmconfig) for tag in event.tags():
if tag.as_vec()[0] == 'bolt11':
invoice_amount = parse_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)
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(),
zap_request_event.content())
for ztag in zap_request_event.tags():
if ztag.as_vec()[0] == 'anon':
if len(ztag.as_vec()) > 1:
print("Private Zap received.")
decrypted_content = decrypt_private_zap_message(ztag.as_vec()[1],
self.keys.secret_key(),
zap_request_event.pubkey())
decrypted_private_event = Event.from_json(decrypted_content)
if decrypted_private_event.kind() == 9733:
sender = decrypted_private_event.pubkey().to_hex()
message = decrypted_private_event.content()
if message != "":
print("Zap Message: " + message)
else:
anon = True
print("Anonymous Zap received. Unlucky, I don't know from whom, and never will")
user = get_or_add_user(sender)
print(str(user))
if zapped_event is not None:
if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped
if not self.dvm_config.IS_BOT:
print("Zap received for NIP90 task: " + str(invoice_amount) + " Sats from " + str(
user.name))
amount = 0
job_event = None
for tag in zapped_event.tags():
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)
task_supported, task, duration = check_task_is_supported(job_event, client=self.client,
get_duration=False,
config=self.dvm_config)
if job_event is not None and task_supported:
if amount <= invoice_amount:
print("[Nostr] Payment-request fulfilled...")
send_job_status_reaction(job_event, "processing", client=self.client,
config=self.dvm_config)
indices = [i for i, x in enumerate(job_list) if
x.event_id == job_event.id().to_hex()]
index = -1
if len(indices) > 0:
index = indices[0]
if index > -1:
if job_list[index].is_processed: # If payment-required appears a processing
job_list[index].is_paid = True
check_and_return_event(job_list[index].result,
str(job_event.as_json()),
dvm_key=self.dvm_config.PRIVATE_KEY)
elif not (job_list[index]).is_processed:
# If payment-required appears before processing
job_list.pop(index)
print("Starting work...")
do_work(job_event, is_from_bot=False)
else:
print("Job not in List, but starting work...")
do_work(job_event, is_from_bot=False)
else:
send_job_status_reaction(job_event, "payment-rejected",
False, invoice_amount, client=self.client,
config=self.dvm_config)
print("[Nostr] Invoice was not paid sufficiently")
elif zapped_event.kind() in EventDefinitions.ANY_RESULT:
print("Someone zapped the result of an exisiting Task. Nice")
elif not anon and not self.dvm_config.PASSIVE_MODE:
print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
user.name))
update_user_balance(sender, invoice_amount, config=self.dvm_config)
# a regular note
elif not anon and not self.dvm_config.PASSIVE_MODE:
print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
user.name))
update_user_balance(sender, invoice_amount, config=self.dvm_config)
except Exception as e: except Exception as e:
print(e) print(f"Error during content decryption: {e}")
if not any(x.event_id == original_event.id().to_hex() for x in job_list):
job_list.append(
JobToWatch(event_id=original_event.id().to_hex(), timestamp=original_event.created_at().as_secs(),
amount=amount,
is_paid=is_paid,
status=status, result="", is_processed=False, bolt11=bolt11, payment_hash=payment_hash,
expires=expires, from_bot=False))
print(str(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 dvmconfig.LNBITS_INVOICE_KEY != "":
amount_tag = Tag.parse(["amount", str(amount * 1000), bolt11]) def check_event_has_not_unfinished_job_input(nevent, append, client, dvmconfig):
task_supported, task, duration = check_task_is_supported(nevent, client, False, config=dvmconfig)
if not task_supported:
return False
for tag in nevent.tags():
if tag.as_vec()[0] == 'i':
if len(tag.as_vec()) < 3:
print("Job Event missing/malformed i tag, skipping..")
return False
else:
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)
if evt is None:
if append:
job = RequiredJobToWatch(event=nevent, timestamp=Timestamp.now().as_secs())
jobs_on_hold_list.append(job)
send_job_status_reaction(nevent, "chain-scheduled", True, 0, client=client,
config=dvmconfig)
return False
else: else:
amount_tag = Tag.parse(["amount", str(amount * 1000)]) # to millisats return True
tags.append(amount_tag)
if key is not None:
keys = Keys.from_sk_str(key)
else:
keys = Keys.from_sk_str(dvmconfig.PRIVATE_KEY)
event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys)
send_event(event, key=keys)
print(
"[Nostr] Sent Kind " + str(EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json())
return event.as_json()
def check_and_return_event(data, original_event_str: str, dvm_key=""):
original_event = Event.from_json(original_event_str)
keys = Keys.from_sk_str(dvm_key)
for x in job_list:
if x.event_id == original_event.id().to_hex():
is_paid = x.is_paid
amount = x.amount
x.result = data
x.is_processed = True
if dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid:
send_nostr_reply_event(data, original_event_str, key=keys)
send_job_status_reaction(original_event, "success", amount,
config=dvm_config) # or payment-required, or both?
elif not dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid:
send_job_status_reaction(original_event, "success", amount,
config=dvm_config) # or payment-required, or both?
if dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
job_list.remove(x)
elif not dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
job_list.remove(x)
send_nostr_reply_event(data, original_event_str, key=keys)
break
try:
post_processed_content = post_process_result(data, original_event)
send_nostr_reply_event(post_processed_content, original_event_str, key=keys)
except Exception as e:
respond_to_error(e, original_event_str, False, dvm_config.PRIVATE_KEY)
def check_and_return_event(data, original_event_str: str, dvm_key=""):
def send_nostr_reply_event(content, original_event_as_str, key=None): original_event = Event.from_json(original_event_str)
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():
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)
if key is None:
key = Keys.from_sk_str(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("[Nostr] " + str(response_kind) + " Job Response event sent: " + event.as_json())
return event.as_json()
client.handle_notifications(NotificationHandler())
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(dvm_config.PRIVATE_KEY)
else:
keys = Keys.from_sk_str(dvm_key) keys = Keys.from_sk_str(dvm_key)
original_event = Event.from_json(originaleventstr) for x in job_list:
sender = "" if x.event_id == original_event.id().to_hex():
task = "" is_paid = x.is_paid
if not is_from_bot: amount = x.amount
send_job_status_reaction(original_event, "error", content=str(content), key=dvm_key) x.result = data
# TODO Send Zap back x.is_processed = True
else: if self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid:
for tag in original_event.tags(): send_nostr_reply_event(data, original_event_str, key=keys)
if tag.as_vec()[0] == "p": send_job_status_reaction(original_event, "success", amount,
sender = tag.as_vec()[1] config=self.dvm_config) # or payment-required, or both?
elif tag.as_vec()[0] == "i": elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and not is_paid:
task = tag.as_vec()[1] send_job_status_reaction(original_event, "success", amount,
config=self.dvm_config) # or payment-required, or both?
user = get_from_sql_table(sender) if self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
if not user.iswhitelisted: job_list.remove(x)
amount = int(user.balance) + get_amount_per_task(task, dvm_config) elif not self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
update_sql_table(sender, amount, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16, job_list.remove(x)
user.name, send_nostr_reply_event(data, original_event_str, key=keys)
Timestamp.now().as_secs()) break
message = "There was the following error : " + content + ". Credits have been reimbursed"
try:
post_processed_content = post_process_result(data, original_event)
send_nostr_reply_event(post_processed_content, original_event_str, key=keys)
except Exception as e:
respond_to_error(e, original_event_str, False, self.dvm_config.PRIVATE_KEY)
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():
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)
if key is None:
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("[Nostr] " + str(response_kind) + " Job Response event sent: " + event.as_json())
return 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: else:
# User didn't pay, so no reimbursement keys = Keys.from_sk_str(dvm_key)
message = "There was the following error : " + content
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message, None).to_event(keys) original_event = Event.from_json(originaleventstr)
send_event(evt, key=keys) sender = ""
task = ""
if not is_from_bot:
send_job_status_reaction(original_event, "error", content=str(content), key=dvm_key)
# TODO Send Zap back
else:
for tag in original_event.tags():
if tag.as_vec()[0] == "p":
sender = tag.as_vec()[1]
elif tag.as_vec()[0] == "i":
task = tag.as_vec()[1]
while True: user = get_from_sql_table(sender)
for job in job_list: if not user.iswhitelisted:
if job.bolt11 != "" and job.payment_hash != "" and not job.is_paid: amount = int(user.balance) + get_amount_per_task(task, self.dvm_config)
if str(check_bolt11_ln_bits_is_paid(job.payment_hash, dvm_config)) == "True": update_sql_table(sender, amount, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
job.is_paid = True user.name,
event = get_event_by_id(job.event_id, config=dvm_config) Timestamp.now().as_secs())
if event != None: message = "There was the following error : " + content + ". Credits have been reimbursed"
send_job_status_reaction(event, "processing", True, 0, client=client, config=dvm_config) else:
print("do work from joblist") # User didn't pay, so no reimbursement
message = "There was the following error : " + content
do_work(event, is_from_bot=False) evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message,
elif check_bolt11_ln_bits_is_paid(job.payment_hash, dvm_config) is None: # invoice expired None).to_event(keys)
job_list.remove(job) send_event(evt, key=keys)
if Timestamp.now().as_secs() > job.expires: def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None,
job_list.remove(job) 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
for job in jobs_on_hold_list: elif status == "payment-required":
if check_event_has_not_unfinished_job_input(job.event, False, client=client, dvmconfig=dvm_config):
handle_nip90_job_event(job.event)
jobs_on_hold_list.remove(job)
if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes.. alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(
jobs_on_hold_list.remove(job) amount) + " Sats. "
reaction = alt_description + emoji.emojize(":orange_heart:")
time.sleep(1.0) 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()])
p_tag = Tag.parse(["p", original_event.pubkey().to_hex()])
alt_tag = Tag.parse(["alt", alt_description])
status_tag = Tag.parse(["status", status])
tags = [e_tag, p_tag, alt_tag, status_tag]
if status == "success" or status == "error": #
for x in job_list:
if x.event_id == original_event.id():
is_paid = x.is_paid
amount = x.amount
break
bolt11 = ""
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 != "":
try:
bolt11, payment_hash = create_bolt11_ln_bits(amount, dvmconfig)
except Exception as e:
print(e)
if not any(x.event_id == original_event.id().to_hex() for x in job_list):
job_list.append(
JobToWatch(event_id=original_event.id().to_hex(),
timestamp=original_event.created_at().as_secs(),
amount=amount,
is_paid=is_paid,
status=status, result="", is_processed=False, bolt11=bolt11,
payment_hash=payment_hash,
expires=expires, from_bot=False))
print(str(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 dvmconfig.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)
event = EventBuilder(EventDefinitions.KIND_FEEDBACK, reaction, tags).to_event(keys)
send_event(event, key=keys)
print(
"[Nostr] Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json())
return event.as_json()
def do_work(job_event, is_from_bot=False):
if ((
EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= job_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC)
or job_event.kind() == EventDefinitions.KIND_DM):
task = get_task(job_event, client=self.client, dvmconfig=self.dvm_config)
result = ""
for dvm in self.dvm_config.SUPPORTED_TASKS:
try:
if task == dvm.TASK:
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)
except Exception as e:
print(e)
respond_to_error(e, job_event.as_json(), is_from_bot, self.dvm_config.PRIVATE_KEY)
return
self.client.handle_notifications(NotificationHandler())
while True:
for job in job_list:
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)
if event is not None:
send_job_status_reaction(event, "processing", True, 0,
client=self.client,
config=self.dvm_config)
print("do work from joblist")
do_work(event, is_from_bot=False)
elif check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config) is None: # invoice expired
try:
job_list.remove(job)
except:
continue
if Timestamp.now().as_secs() > job.expires:
try:
job_list.remove(job)
except:
continue
for job in jobs_on_hold_list:
if check_event_has_not_unfinished_job_input(job.event, False, client=self.client,
dvmconfig=self.dvm_config):
# handle_nip90_job_event(event=job.event)
try:
jobs_on_hold_list.remove(job)
except:
continue
if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes..
jobs_on_hold_list.remove(job)
time.sleep(1.0)

View File

@@ -1,11 +1,19 @@
from utils.nip89_utils import NIP89Announcement
class DVMTaskInterface: class DVMTaskInterface:
KIND: int KIND: int
TASK: str TASK: str
COST: int COST: int
PK: str
def NIP89_announcement(self, d_tag, content): def NIP89_announcement(self, d_tag, content):
"""Define the NIP89 Announcement""" nip89 = NIP89Announcement()
pass nip89.kind = self.KIND
nip89.pk = self.PK
nip89.dtag = d_tag
nip89.content = content
return nip89
def is_input_supported(self, input_type, input_content) -> bool: def is_input_supported(self, input_type, input_content) -> bool:
"""Check if input is supported for current Task.""" """Check if input is supported for current Task."""

62
main.py
View File

@@ -8,19 +8,22 @@ from tasks.imagegenerationsdxl import ImageGenerationSDXL
from tasks.textextractionpdf import TextExtractionPDF from tasks.textextractionpdf import TextExtractionPDF
from tasks.translation import Translation from tasks.translation import Translation
def run_nostr_dvm_with_local_config(): def run_nostr_dvm_with_local_config():
from dvm import DVM, DVMConfig from dvm import DVM, DVMConfig
dvm_config = DVMConfig()
dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY)
# Spawn the DVMs # Spawn the DVMs
# Add NIP89 events for each DVM (set rebroad_cast = True for the next start in admin_utils) # Add NIP89 events for each DVM (set rebroadcast = True for the next start in admin_utils)
# Add the dtag here or in your .env file, so you can update your dvm later and change the content as needed. # Add the dtag here or in your .env file, so you can update your dvm later and change the content as needed.
# Get a dtag and the content at vendata.io # Get a dtag and the content at vendata.io
# Spawn DVM1 Kind 5000 Text Ectractor from PDFs # Spawn DVM1 Kind 5000 Text Ectractor from PDFs
pdfextactor = TextExtractionPDF("PDF Extractor", os.getenv(env.NOSTR_PRIVATE_KEY)) dvm_config = DVMConfig()
dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY)
dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY)
dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST)
pdfextactor = TextExtractionPDF("PDF Extractor", dvm_config)
d_tag = os.getenv(env.TASK_TEXTEXTRACTION_NIP89_DTAG) d_tag = os.getenv(env.TASK_TEXTEXTRACTION_NIP89_DTAG)
content = "{\"name\":\"" + pdfextactor.NAME + ("\",\"image\":\"https://image.nostr.build" content = "{\"name\":\"" + pdfextactor.NAME + ("\",\"image\":\"https://image.nostr.build"
"/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669" "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669"
@@ -29,7 +32,13 @@ def run_nostr_dvm_with_local_config():
dvm_config.NIP89s.append(pdfextactor.NIP89_announcement(d_tag, content)) dvm_config.NIP89s.append(pdfextactor.NIP89_announcement(d_tag, content))
# Spawn DVM2 Kind 5002 Text Translation # Spawn DVM2 Kind 5002 Text Translation
translator = Translation("Translator", os.getenv(env.NOSTR_PRIVATE_KEY)) dvm_config = DVMConfig()
dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY)
dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY)
dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST)
translator = Translation("Translator", dvm_config)
d_tag = os.getenv(env.TASK_TRANSLATION_NIP89_DTAG) d_tag = os.getenv(env.TASK_TRANSLATION_NIP89_DTAG)
content = "{\"name\":\"" + translator.NAME + ("\",\"image\":\"https://image.nostr.build" content = "{\"name\":\"" + translator.NAME + ("\",\"image\":\"https://image.nostr.build"
"/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669" "/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669"
@@ -48,31 +57,36 @@ def run_nostr_dvm_with_local_config():
"\"sm\",\"sn\",\"so\",\"sq\",\"sr\",\"st\",\"su\",\"sv\",\"sw\"," "\"sm\",\"sn\",\"so\",\"sq\",\"sr\",\"st\",\"su\",\"sv\",\"sw\","
"\"ta\",\"te\",\"tg\",\"th\",\"tl\",\"tr\",\"ug\",\"uk\",\"ur\"," "\"ta\",\"te\",\"tg\",\"th\",\"tl\",\"tr\",\"ug\",\"uk\",\"ur\","
"\"uz\",\"vi\",\"xh\",\"yi\",\"yo\",\"zh\",\"zu\"]}}}") "\"uz\",\"vi\",\"xh\",\"yi\",\"yo\",\"zh\",\"zu\"]}}}")
dvm_config.NIP89s.append(translator.NIP89_announcement(d_tag, content)) dvm_config.NIP89s.append(translator.NIP89_announcement(d_tag, content))
# Spawn DVM3 Kind 5100 Image Generation This one uses a specific backend called nova-server. If you want to use # Spawn DVM3 Kind 5100 Image Generation This one uses a specific backend called nova-server. If you want to use
# it see the instructions in backends/nova_server # it see the instructions in backends/nova_server
artist = ImageGenerationSDXL("Unstable Diffusion", os.getenv(env.NOSTR_PRIVATE_KEY)) dvm_config = DVMConfig()
d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG) dvm_config.PRIVATE_KEY = os.getenv(env.NOSTR_PRIVATE_KEY)
content = "{\"name\":\"" + artist.NAME + ("\",\"image\":\"https://image.nostr.build"
"/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669.jpg"
"\",\"about\":\"I draw images based on a prompt with Stable Diffusion "
"XL 1.0.\",\"nip90Params\":{}}")
dvm_config.NIP89s.append(artist.NIP89_announcement(d_tag, content))
# Add the DVMS you want to use to the config
dvm_config.SUPPORTED_TASKS = [pdfextactor, translator, artist]
# SET Lnbits Invoice Key and Server if DVM should provide invoices directly, else make sure you have a lnaddress
# on the profile
dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY) dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY)
dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST) dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST)
# Start the Server unstableartist = ImageGenerationSDXL("Unstable Diffusion", dvm_config, "unstable")
nostr_dvm_thread = Thread(target=DVM, args=[dvm_config]) d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG)
nostr_dvm_thread.start() content = "{\"name\":\"" + unstableartist.NAME + ("\",\"image\":\"https://image.nostr.build"
"/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669.jpg"
"\",\"about\":\"I draw images based on a prompt with a Model called unstable diffusion.\",\"nip90Params\":{}}")
dvm_config.NIP89s.append(unstableartist.NIP89_announcement(d_tag, content))
dvm_config = DVMConfig()
dvm_config.PRIVATE_KEY = "73b262d31edc6ea1316dffcc7daa772651d661e6475761b7b78291482c1bf5cb"
dvm_config.LNBITS_INVOICE_KEY = os.getenv(env.LNBITS_INVOICE_KEY)
dvm_config.LNBITS_URL = os.getenv(env.LNBITS_HOST)
# Spawn another Instance of text-to-image but use a different model and lora this time.
sketcher = ImageGenerationSDXL("Sketcher", dvm_config, "mohawk", "timburton")
d_tag = os.getenv(env.TASK_IMAGEGENERATION_NIP89_DTAG2)
content = "{\"name\":\"" + sketcher.NAME + ("\",\"image\":\"https://image.nostr.build"
"/c33ca6fc4cc038ca4adb46fdfdfda34951656f87ee364ef59095bae1495ce669.jpg"
"\",\"about\":\"I draw images based on a prompt in kind of Tim Burton style\",\"nip90Params\":{}}")
dvm_config.NIP89s.append(sketcher.NIP89_announcement(d_tag, content))
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -1,6 +1,9 @@
import os import os
from multiprocessing.pool import ThreadPool from multiprocessing.pool import ThreadPool
from threading import Thread
from backends.nova_server import check_nova_server_status, send_request_to_nova_server from backends.nova_server import check_nova_server_status, send_request_to_nova_server
from dvm import DVM
from interfaces.dvmtaskinterface import DVMTaskInterface from interfaces.dvmtaskinterface import DVMTaskInterface
from utils.definitions import EventDefinitions from utils.definitions import EventDefinitions
from utils.nip89_utils import NIP89Announcement from utils.nip89_utils import NIP89Announcement
@@ -15,21 +18,23 @@ Outputs: An url to an Image
class ImageGenerationSDXL(DVMTaskInterface): class ImageGenerationSDXL(DVMTaskInterface):
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 = 0 COST: int = 50
PK: str
def __init__(self, name, pk): def __init__(self, name, dvm_config, default_model=None, default_lora=None):
self.NAME = name self.NAME = name
self.PK = pk dvm_config.SUPPORTED_TASKS = [self]
self.PK = dvm_config.PRIVATE_KEY
self.default_model = default_model
self.default_lora = default_lora
dvm = DVM
nostr_dvm_thread = Thread(target=dvm, args=[dvm_config])
nostr_dvm_thread.start()
def NIP89_announcement(self, d_tag, content):
nip89 = NIP89Announcement()
nip89.kind = self.KIND
nip89.pk = self.PK
nip89.dtag = d_tag
nip89.content = content
return nip89
def is_input_supported(self, input_type, input_content): def is_input_supported(self, input_type, input_content):
if input_type != "text": if input_type != "text":
@@ -38,19 +43,24 @@ class ImageGenerationSDXL(DVMTaskInterface):
def create_request_form_from_nostr_event(self, event, client=None, dvm_config=None): def create_request_form_from_nostr_event(self, event, client=None, dvm_config=None):
request_form = {"jobID": event.id().to_hex() + "_"+ self.NAME.replace(" ", "")} request_form = {"jobID": event.id().to_hex() + "_"+ self.NAME.replace(" ", "")}
request_form["mode"] = "PROCESS"
request_form["trainerFilePath"] = 'modules\\stablediffusionxl\\stablediffusionxl.trainer' request_form["trainerFilePath"] = 'modules\\stablediffusionxl\\stablediffusionxl.trainer'
prompt = "" prompt = ""
negative_prompt = "" negative_prompt = ""
#model = "stabilityai/stable-diffusion-xl-base-1.0" if self.default_model is None:
model = "unstable" model = "stabilityai/stable-diffusion-xl-base-1.0"
else:
model = self.default_model
# models: juggernautXL, dynavisionXL, colossusProjectXL, newrealityXL, unstable # models: juggernautXL, dynavisionXL, colossusProjectXL, newrealityXL, unstable
ratio_width = "1" ratio_width = "1"
ratio_height = "1" ratio_height = "1"
width = "" width = ""
height = "" height = ""
lora = "" if self.default_lora == None:
lora = ""
else:
lora = self.default_lora
lora_weight = "" lora_weight = ""
strength = "" strength = ""
guidance_scale = "" guidance_scale = ""

View File

@@ -1,6 +1,8 @@
import os import os
import re import re
from threading import Thread
from dvm import DVM
from interfaces.dvmtaskinterface import DVMTaskInterface from interfaces.dvmtaskinterface import DVMTaskInterface
from utils.definitions import EventDefinitions from utils.definitions import EventDefinitions
from utils.nip89_utils import NIP89Announcement from utils.nip89_utils import NIP89Announcement
@@ -13,21 +15,23 @@ Accepted Inputs: Url to pdf file, Event containing an URL to a PDF file
Outputs: Text containing the extracted contents of the PDF file Outputs: Text containing the extracted contents of the PDF file
""" """
class TextExtractionPDF(DVMTaskInterface): class TextExtractionPDF(DVMTaskInterface):
NAME: str
KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
TASK: str = "pdf-to-text" TASK: str = "pdf-to-text"
COST: int = 20 COST: int = 20
PK: str
def __init__(self, name, pk):
def __init__(self, name, dvm_config):
self.NAME = name self.NAME = name
self.PK = pk dvm_config.SUPPORTED_TASKS = [self]
self.PK = dvm_config.PRIVATE_KEY
dvm = DVM
nostr_dvm_thread = Thread(target=dvm, args=[dvm_config])
nostr_dvm_thread.start()
def NIP89_announcement(self, d_tag, content):
nip89 = NIP89Announcement()
nip89.kind = self.KIND
nip89.pk = self.PK
nip89.dtag = d_tag
nip89.content = content
return nip89
def is_input_supported(self, input_type, input_content): def is_input_supported(self, input_type, input_content):
if input_type != "url" and input_type != "event": if input_type != "url" and input_type != "event":

View File

@@ -1,5 +1,7 @@
import os import os
from threading import Thread
from dvm import DVM
from interfaces.dvmtaskinterface import DVMTaskInterface from interfaces.dvmtaskinterface import DVMTaskInterface
from utils.definitions import EventDefinitions from utils.definitions import EventDefinitions
from utils.nip89_utils import NIP89Announcement from utils.nip89_utils import NIP89Announcement
@@ -15,21 +17,20 @@ Outputs: Text containing the Translation in the desired language.
class Translation(DVMTaskInterface): class Translation(DVMTaskInterface):
NAME: str
KIND: int = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT KIND: int = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT
TASK: str = "translation" TASK: str = "translation"
COST: int = 0 COST: int = 0
PK: str
def __init__(self, name, pk): def __init__(self, name, dvm_config):
self.NAME = name self.NAME = name
self.PK = pk dvm_config.SUPPORTED_TASKS = [self]
self.PK = dvm_config.PRIVATE_KEY
def NIP89_announcement(self, d_tag, content): dvm = DVM
nip89 = NIP89Announcement() nostr_dvm_thread = Thread(target=dvm, args=[dvm_config])
nip89.kind = self.KIND nostr_dvm_thread.start()
nip89.pk = self.PK
nip89.dtag = d_tag
nip89.content = content
return nip89
def is_input_supported(self, input_type, input_content): def is_input_supported(self, input_type, input_content):
if input_type != "event" and input_type != "job" and input_type != "text": if input_type != "event" and input_type != "job" and input_type != "text":

View File

@@ -20,8 +20,7 @@ def admin_make_database_updates(config=None, client=None):
whitelistuser = False whitelistuser = False
unwhitelistuser = False unwhitelistuser = False
blacklistuser = False blacklistuser = False
addbalance = False
additional_balance = 50
# publickey = PublicKey.from_bech32("npub1...").to_hex() # publickey = PublicKey.from_bech32("npub1...").to_hex()
# use this if you have the npub # use this if you have the npub
@@ -42,17 +41,6 @@ def admin_make_database_updates(config=None, client=None):
user = get_from_sql_table(publickey) user = get_from_sql_table(publickey)
update_sql_table(user.npub, user.balance, False, True, user.nip05, user.lud16, user.name, user.lastactive) update_sql_table(user.npub, user.balance, False, True, user.nip05, user.lud16, user.name, user.lastactive)
if addbalance:
user = get_from_sql_table(publickey)
update_sql_table(user[0], (int(user.balance) + additional_balance), user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16, user.name, user.lastactive)
time.sleep(1.0)
message = str(additional_balance) + " Sats have been added to your balance. Your new balance is " + str(
(int(user.balance) + additional_balance)) + " Sats."
keys = Keys.from_sk_str(config.PRIVATE_KEY)
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(publickey), message,
None).to_event(keys)
send_event(evt, key=keys)
if deleteuser: if deleteuser:
delete_from_sql_table(publickey) delete_from_sql_table(publickey)

View File

@@ -2,10 +2,6 @@ import requests
from utils.definitions import EventDefinitions from utils.definitions import EventDefinitions
from utils.nostr_utils import get_event_by_id from utils.nostr_utils import get_event_by_id
from tasks.textextractionpdf import TextExtractionPDF
from tasks.translation import Translation
from tasks.imagegenerationsdxl import ImageGenerationSDXL
def get_task(event, client, dvmconfig): def get_task(event, client, dvmconfig):
if event.kind() == EventDefinitions.KIND_NIP90_GENERIC: # use this for events that have no id yet if event.kind() == EventDefinitions.KIND_NIP90_GENERIC: # use this for events that have no id yet
@@ -28,7 +24,7 @@ def get_task(event, client, dvmconfig):
if tag.as_vec()[2] == "url": if tag.as_vec()[2] == "url":
file_type = check_url_is_readable(tag.as_vec()[1]) file_type = check_url_is_readable(tag.as_vec()[1])
if file_type == "pdf": if file_type == "pdf":
return TextExtractionPDF.TASK return "pdf-to-text"
else: else:
return "unknown job" return "unknown job"
elif tag.as_vec()[2] == "event": elif tag.as_vec()[2] == "event":
@@ -46,9 +42,9 @@ def get_task(event, client, dvmconfig):
return "unknown type" return "unknown type"
elif event.kind() == EventDefinitions.KIND_NIP90_TRANSLATE_TEXT: elif event.kind() == EventDefinitions.KIND_NIP90_TRANSLATE_TEXT:
return Translation.TASK return "translation"
elif event.kind() == EventDefinitions.KIND_NIP90_GENERATE_IMAGE: elif event.kind() == EventDefinitions.KIND_NIP90_GENERATE_IMAGE:
return ImageGenerationSDXL.TASK return "text-to-image"
else: else:
return "unknown type" return "unknown type"
@@ -60,6 +56,8 @@ def check_task_is_supported(event, client, get_duration=False, config=None):
input_type = "" input_type = ""
duration = 1 duration = 1
for tag in event.tags(): for tag in event.tags():
if tag.as_vec()[0] == 'i': if tag.as_vec()[0] == 'i':
if len(tag.as_vec()) < 3: if len(tag.as_vec()) < 3:
@@ -82,6 +80,10 @@ def check_task_is_supported(event, client, get_duration=False, config=None):
return False, "", 0 return False, "", 0
task = get_task(event, client=client, dvmconfig=dvm_config) task = get_task(event, client=client, dvmconfig=dvm_config)
for dvm in dvm_config.SUPPORTED_TASKS:
if dvm.TASK == task:
if not dvm.is_input_supported(input_type, event.content()):
return False, task, duration
if input_type == 'url' and check_url_is_readable(input_value) is None: if input_type == 'url' and check_url_is_readable(input_value) is None:
print("url not readable") print("url not readable")
@@ -90,10 +92,7 @@ def check_task_is_supported(event, client, get_duration=False, config=None):
if task not in (x.TASK for x in dvm_config.SUPPORTED_TASKS): if task not in (x.TASK for x in dvm_config.SUPPORTED_TASKS):
return False, task, duration return False, task, duration
for dvm in dvm_config.SUPPORTED_TASKS:
if dvm.TASK == task:
if not dvm.is_input_supported(input_type, event.content()):
return False, task, duration
return True, task, duration return True, task, duration
@@ -107,9 +106,9 @@ def check_url_is_readable(url):
if content_type == 'audio/x-wav' or str(url).endswith(".wav") or content_type == 'audio/mpeg' or str(url).endswith( if content_type == 'audio/x-wav' or str(url).endswith(".wav") or content_type == 'audio/mpeg' or str(url).endswith(
".mp3") or content_type == 'audio/ogg' or str(url).endswith(".ogg"): ".mp3") or content_type == 'audio/ogg' or str(url).endswith(".ogg"):
return "audio" return "audio"
elif content_type == 'image/png' or str(url).endswith(".png") or content_type == 'image/jpg' or str(url).endswith( elif (content_type == 'image/png' or str(url).endswith(".png") or content_type == 'image/jpg' or str(url).endswith(
".jpg") or content_type == 'image/jpeg' or str(url).endswith(".jpeg") or content_type == 'image/png' or str( ".jpg") or content_type == 'image/jpeg' or str(url).endswith(".jpeg") or content_type == 'image/png' or
url).endswith(".png"): str(url).endswith(".png")):
return "image" return "image"
elif content_type == 'video/mp4' or str(url).endswith(".mp4") or content_type == 'video/avi' or str(url).endswith( elif content_type == 'video/mp4' or str(url).endswith(".mp4") or content_type == 'video/avi' or str(url).endswith(
".avi") or content_type == 'video/mov' or str(url).endswith(".mov"): ".avi") or content_type == 'video/mov' or str(url).endswith(".mov"):

View File

@@ -1,7 +1,10 @@
import os
from dataclasses import dataclass from dataclasses import dataclass
from nostr_sdk import Event from nostr_sdk import Event
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", RELAY_LIST = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine",
@@ -42,7 +45,7 @@ class EventDefinitions:
class DVMConfig: class DVMConfig:
SUPPORTED_TASKS = [] SUPPORTED_TASKS = []
PRIVATE_KEY: str PRIVATE_KEY: str = os.getenv(env.NOSTR_PRIVATE_KEY)
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",

View File

@@ -9,5 +9,6 @@ LNBITS_HOST = "LNBITS_HOST"
TASK_TRANSLATION_NIP89_DTAG = "TASK_TRANSLATION_NIP89_DTAG" TASK_TRANSLATION_NIP89_DTAG = "TASK_TRANSLATION_NIP89_DTAG"
TASK_TEXTEXTRACTION_NIP89_DTAG = "TASK_TEXTEXTRACTION_NIP89_DTAG" TASK_TEXTEXTRACTION_NIP89_DTAG = "TASK_TEXTEXTRACTION_NIP89_DTAG"
TASK_IMAGEGENERATION_NIP89_DTAG = "TASK_IMAGEGENERATION_NIP89_DTAG" TASK_IMAGEGENERATION_NIP89_DTAG = "TASK_IMAGEGENERATION_NIP89_DTAG"
TASK_IMAGEGENERATION_NIP89_DTAG2 = "TASK_IMAGEGENERATION_NIP89_DTAG2"

View File

@@ -33,6 +33,7 @@ def parse_bolt11_invoice(invoice):
return int(number) return int(number)
def create_bolt11_ln_bits(sats, config): def create_bolt11_ln_bits(sats, config):
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"}
@@ -45,6 +46,7 @@ def create_bolt11_ln_bits(sats, config):
print(e) print(e)
return None return None
def check_bolt11_ln_bits_is_paid(payment_hash, config): def check_bolt11_ln_bits_is_paid(payment_hash, config):
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'}
@@ -92,4 +94,3 @@ def decrypt_private_zap_message(msg, privkey, pubkey):
return decoded return decoded
except Exception as ex: except Exception as ex:
return str(ex) return str(ex)