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

383
dvm.py
View File

@@ -19,96 +19,104 @@ 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() for relay in self.dvm_config.RELAY_LIST:
self.client.add_relay(relay)
self.client.connect()
dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now())
kinds = [EventDefinitions.KIND_NIP90_GENERIC] kinds = [EventDefinitions.KIND_NIP90_GENERIC]
for dvm in dvm_config.SUPPORTED_TASKS: for dvm in self.dvm_config.SUPPORTED_TASKS:
if dvm.KIND not in kinds: if dvm.KIND not in kinds:
kinds.append(dvm.KIND) kinds.append(dvm.KIND)
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now())) dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()))
client.subscribe([dm_zap_filter, dvm_filter]) self.client.subscribe([dm_zap_filter, dvm_filter])
create_sql_table() create_sql_table()
admin_make_database_updates(config=dvm_config, client=client) admin_make_database_updates(config=self.dvm_config, client=self.client)
class NotificationHandler(HandleNotification): class NotificationHandler(HandleNotification):
client = self.client
dvm_config = self.dvm_config
keys = self.keys
def handle(self, relay_url, nostr_event): def handle(self, relay_url, nostr_event):
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC:
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:
self.handle_nip90_job_event(nostr_event)
elif nostr_event.kind() == EventDefinitions.KIND_ZAP: elif nostr_event.kind() == EventDefinitions.KIND_ZAP:
handle_zap(nostr_event, dvm_config) self.handle_zap(nostr_event)
def handle_msg(self, relay_url, msg): def handle_msg(self, relay_url, msg):
return return
def handle_nip90_job_event(event, dvm_config): def handle_nip90_job_event(self, nip90_event):
user = get_or_add_user(event.pubkey().to_hex()) user = get_or_add_user(nip90_event.pubkey().to_hex())
task_supported, task, duration = check_task_is_supported(event, client=client, task_supported, task, duration = check_task_is_supported(nip90_event, client=self.client,
get_duration=(not user.iswhitelisted), get_duration=(not user.iswhitelisted),
config=dvm_config) config=self.dvm_config)
print(task) print(task)
if user.isblacklisted: if user.isblacklisted:
send_job_status_reaction(event, "error", client=client, config=dvm_config) send_job_status_reaction(nip90_event, "error", client=self.client, config=self.dvm_config)
print("[Nostr] Request by blacklisted user, skipped") print("[Nostr] Request by blacklisted user, skipped")
elif task_supported: elif task_supported:
print("Received new Task: " + task) print("Received new Task: " + task)
print(duration) amount = get_amount_per_task(task, self.dvm_config, duration)
amount = get_amount_per_task(task, dvm_config, duration)
if amount is None: if amount is None:
return return
task_is_free = False task_is_free = False
for dvm in dvm_config.SUPPORTED_TASKS: for dvm in self.dvm_config.SUPPORTED_TASKS:
if dvm.TASK == task and dvm.COST == 0: if dvm.TASK == task and dvm.COST == 0:
task_is_free = True task_is_free = True
if user.iswhitelisted or task_is_free: if user.iswhitelisted or task_is_free:
print("[Nostr] Free or Whitelisted for task " + task + ". Starting processing..") print("[Nostr] Free or Whitelisted for task " + task + ". Starting processing..")
send_job_status_reaction(event, "processing", True, 0, client=client, config=dvm_config) send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client,
do_work(event, is_from_bot=False) config=self.dvm_config)
do_work(nip90_event, is_from_bot=False)
# otherwise send payment request # otherwise send payment request
else: else:
bid = 0 bid = 0
for tag in event.tags(): for tag in nip90_event.tags():
if tag.as_vec()[0] == 'bid': if tag.as_vec()[0] == 'bid':
bid = int(tag.as_vec()[1]) bid = int(tag.as_vec()[1])
print("[Nostr][Payment required] New Nostr " + task + " Job event: " + event.as_json()) print("[Nostr][Payment required] New Nostr " + task + " Job event: " + nip90_event.as_json())
if bid > 0: if bid > 0:
bid_offer = int(bid / 1000) bid_offer = int(bid / 1000)
if bid_offer >= amount: if bid_offer >= amount:
send_job_status_reaction(event, "payment-required", False, send_job_status_reaction(nip90_event, "payment-required", False,
amount, # bid_offer amount, # bid_offer
client=client, config=dvm_config) client=self.client, 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("[Nostr] Requesting payment for Event: " + event.id().to_hex()) print("[Nostr] Requesting payment for Event: " + nip90_event.id().to_hex())
send_job_status_reaction(event, "payment-required", send_job_status_reaction(nip90_event, "payment-required",
False, amount, client=client, config=dvm_config) False, amount, client=self.client, 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, dvm_config): def handle_zap(self, event):
zapped_event = None zapped_event = None
invoice_amount = 0 invoice_amount = 0
anon = False anon = False
@@ -119,7 +127,7 @@ def DVM(config):
if tag.as_vec()[0] == 'bolt11': if tag.as_vec()[0] == 'bolt11':
invoice_amount = parse_bolt11_invoice(tag.as_vec()[1]) invoice_amount = parse_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=dvm_config) zapped_event = get_event_by_id(tag.as_vec()[1], 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(),
@@ -129,7 +137,7 @@ def DVM(config):
if len(ztag.as_vec()) > 1: if len(ztag.as_vec()) > 1:
print("Private Zap received.") print("Private Zap received.")
decrypted_content = decrypt_private_zap_message(ztag.as_vec()[1], decrypted_content = decrypt_private_zap_message(ztag.as_vec()[1],
keys.secret_key(), self.keys.secret_key(),
zap_request_event.pubkey()) zap_request_event.pubkey())
decrypted_private_event = Event.from_json(decrypted_content) decrypted_private_event = Event.from_json(decrypted_content)
if decrypted_private_event.kind() == 9733: if decrypted_private_event.kind() == 9733:
@@ -145,7 +153,7 @@ def DVM(config):
if zapped_event is not None: if zapped_event is not None:
if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped
if not dvm_config.IS_BOT: if not self.dvm_config.IS_BOT:
print("Zap received for NIP90 task: " + str(invoice_amount) + " Sats from " + str( print("Zap received for NIP90 task: " + str(invoice_amount) + " Sats from " + str(
user.name)) user.name))
amount = 0 amount = 0
@@ -154,15 +162,16 @@ def DVM(config):
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=dvm_config) 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=client, task_supported, task, duration = check_task_is_supported(job_event, client=self.client,
get_duration=False, config=dvm_config) get_duration=False,
config=self.dvm_config)
if job_event is not None and task_supported: if job_event is not None and task_supported:
if amount <= invoice_amount: if amount <= invoice_amount:
print("[Nostr] Payment-request fulfilled...") print("[Nostr] Payment-request fulfilled...")
send_job_status_reaction(job_event, "processing", client=client, send_job_status_reaction(job_event, "processing", client=self.client,
config=dvm_config) config=self.dvm_config)
indices = [i for i, x in enumerate(job_list) if indices = [i for i, x in enumerate(job_list) if
x.event_id == job_event.id().to_hex()] x.event_id == job_event.id().to_hex()]
index = -1 index = -1
@@ -171,8 +180,9 @@ def DVM(config):
if index > -1: if index > -1:
if job_list[index].is_processed: # If payment-required appears a processing if job_list[index].is_processed: # If payment-required appears a processing
job_list[index].is_paid = True job_list[index].is_paid = True
check_and_return_event(job_list[index].result, str(job_event.as_json()), check_and_return_event(job_list[index].result,
dvm_key=dvm_config.PRIVATE_KEY) str(job_event.as_json()),
dvm_key=self.dvm_config.PRIVATE_KEY)
elif not (job_list[index]).is_processed: elif not (job_list[index]).is_processed:
# If payment-required appears before processing # If payment-required appears before processing
job_list.pop(index) job_list.pop(index)
@@ -184,43 +194,26 @@ def DVM(config):
else: else:
send_job_status_reaction(job_event, "payment-rejected", send_job_status_reaction(job_event, "payment-rejected",
False, invoice_amount, client=client, config=dvm_config) False, invoice_amount, client=self.client,
config=self.dvm_config)
print("[Nostr] Invoice was not paid sufficiently") print("[Nostr] Invoice was not paid sufficiently")
elif zapped_event.kind() in EventDefinitions.ANY_RESULT: elif zapped_event.kind() in EventDefinitions.ANY_RESULT:
print("Someone zapped the result of an exisiting Task. Nice") print("Someone zapped the result of an exisiting Task. Nice")
elif not anon and not dvm_config.PASSIVE_MODE: elif not anon and not self.dvm_config.PASSIVE_MODE:
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(sender, invoice_amount, config=dvm_config) update_user_balance(sender, invoice_amount, config=self.dvm_config)
# a regular note # a regular note
elif not anon and not dvm_config.PASSIVE_MODE: elif not anon and not self.dvm_config.PASSIVE_MODE:
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(sender, invoice_amount, config=dvm_config) update_user_balance(sender, invoice_amount, 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}")
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): def check_event_has_not_unfinished_job_input(nevent, append, client, dvmconfig):
@@ -237,7 +230,8 @@ def DVM(config):
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, config=dvmconfig) evt = get_referenced_event_by_id(input, EventDefinitions.ANY_RESULT, client,
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())
@@ -249,7 +243,102 @@ def DVM(config):
else: else:
return True return True
def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None, content=None, config=None,
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 self.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=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?
if self.dvm_config.SHOWRESULTBEFOREPAYMENT and is_paid:
job_list.remove(x)
elif not self.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, 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:
keys = Keys.from_sk_str(dvm_key)
original_event = Event.from_json(originaleventstr)
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]
user = get_from_sql_table(sender)
if not user.iswhitelisted:
amount = int(user.balance) + get_amount_per_task(task, self.dvm_config)
update_sql_table(sender, amount, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
user.name,
Timestamp.now().as_secs())
message = "There was the following error : " + content + ". Credits have been reimbursed"
else:
# User didn't pay, so no reimbursement
message = "There was the following error : " + content
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message,
None).to_event(keys)
send_event(evt, key=keys)
def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None,
content=None,
config=None,
key=None): key=None):
dvmconfig = config dvmconfig = config
alt_description = "This is a reaction to a NIP90 DVM AI task. " alt_description = "This is a reaction to a NIP90 DVM AI task. "
@@ -272,11 +361,13 @@ def DVM(config):
elif status == "payment-required": elif status == "payment-required":
alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(amount) + " Sats. " alt_description = "NIP90 DVM AI task " + task + " requires payment of min " + str(
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":orange_heart:") reaction = alt_description + emoji.emojize(":orange_heart:")
elif status == "payment-rejected": elif status == "payment-rejected":
alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str(amount) + " Sats. " alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str(
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":thumbs_down:") reaction = alt_description + emoji.emojize(":thumbs_down:")
elif status == "user-blocked-from-service": elif status == "user-blocked-from-service":
alt_description = "NIP90 DVM AI task " + task + " can't be performed. User has been blocked from Service. " alt_description = "NIP90 DVM AI task " + task + " can't be performed. User has been blocked from Service. "
@@ -309,13 +400,16 @@ def DVM(config):
if not any(x.event_id == original_event.id().to_hex() for x in job_list): if not any(x.event_id == original_event.id().to_hex() for x in job_list):
job_list.append( job_list.append(
JobToWatch(event_id=original_event.id().to_hex(), timestamp=original_event.created_at().as_secs(), JobToWatch(event_id=original_event.id().to_hex(),
timestamp=original_event.created_at().as_secs(),
amount=amount, amount=amount,
is_paid=is_paid, is_paid=is_paid,
status=status, result="", is_processed=False, bolt11=bolt11, payment_hash=payment_hash, status=status, result="", is_processed=False, bolt11=bolt11,
payment_hash=payment_hash,
expires=expires, from_bot=False)) expires=expires, from_bot=False))
print(str(job_list)) print(str(job_list))
if status == "payment-required" or status == "payment-rejected" or (status == "processing" and not is_paid) or ( if status == "payment-required" or status == "payment-rejected" or (
status == "processing" and not is_paid) or (
status == "success" and not is_paid): status == "success" and not is_paid):
if dvmconfig.LNBITS_INVOICE_KEY != "": if dvmconfig.LNBITS_INVOICE_KEY != "":
@@ -331,124 +425,65 @@ def DVM(config):
send_event(event, key=keys) send_event(event, key=keys)
print( print(
"[Nostr] Sent Kind " + str(EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json()) "[Nostr] Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json())
return 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):
def check_and_return_event(data, original_event_str: str, dvm_key=""): task = get_task(job_event, client=self.client, dvmconfig=self.dvm_config)
original_event = Event.from_json(original_event_str) result = ""
keys = Keys.from_sk_str(dvm_key) for dvm in self.dvm_config.SUPPORTED_TASKS:
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: try:
post_processed_content = post_process_result(data, original_event) if task == dvm.TASK:
send_nostr_reply_event(post_processed_content, original_event_str, key=keys) 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: except Exception as e:
respond_to_error(e, original_event_str, False, dvm_config.PRIVATE_KEY) print(e)
respond_to_error(e, job_event.as_json(), is_from_bot, self.dvm_config.PRIVATE_KEY)
return
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(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)
original_event = Event.from_json(originaleventstr)
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]
user = get_from_sql_table(sender)
if not user.iswhitelisted:
amount = int(user.balance) + get_amount_per_task(task, dvm_config)
update_sql_table(sender, amount, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
user.name,
Timestamp.now().as_secs())
message = "There was the following error : " + content + ". Credits have been reimbursed"
else:
# User didn't pay, so no reimbursement
message = "There was the following error : " + content
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message, None).to_event(keys)
send_event(evt, key=keys)
self.client.handle_notifications(NotificationHandler())
while True: while True:
for job in job_list: for job in job_list:
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, 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=dvm_config) event = get_event_by_id(job.event_id, config=self.dvm_config)
if event != None: if event is not None:
send_job_status_reaction(event, "processing", True, 0, client=client, config=dvm_config) send_job_status_reaction(event, "processing", True, 0,
client=self.client,
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)
elif check_bolt11_ln_bits_is_paid(job.payment_hash, dvm_config) is None: # invoice expired elif check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config) is None: # invoice expired
try:
job_list.remove(job) job_list.remove(job)
except:
continue
if Timestamp.now().as_secs() > job.expires: if Timestamp.now().as_secs() > job.expires:
try:
job_list.remove(job) job_list.remove(job)
except:
continue
for job in jobs_on_hold_list: for job in jobs_on_hold_list:
if check_event_has_not_unfinished_job_input(job.event, False, client=client, dvmconfig=dvm_config): if check_event_has_not_unfinished_job_input(job.event, False, client=self.client,
handle_nip90_job_event(job.event) dvmconfig=self.dvm_config):
# handle_nip90_job_event(event=job.event)
try:
jobs_on_hold_list.remove(job) 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.. if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes..
jobs_on_hold_list.remove(job) jobs_on_hold_list.remove(job)

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 = ""
if self.default_lora == None:
lora = "" 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)