mirror of
https://github.com/aljazceru/nostrdvm.git
synced 2026-01-29 10:54:32 +01:00
12
bot.py
12
bot.py
@@ -136,15 +136,16 @@ class Bot:
|
||||
bid_tag = Tag.parse(['bid', bid, bid])
|
||||
relays_tag = Tag.parse(["relays", json.dumps(self.dvm_config.RELAY_LIST)])
|
||||
alt_tag = Tag.parse(["alt", self.dvm_config.SUPPORTED_DVMS[index].TASK])
|
||||
p_tag = Tag.parse(['p', dvm_keys.public_key().to_hex()])
|
||||
|
||||
encrypted_params_string = json.dumps([i_tag.as_vec(), bid_tag.as_vec(),
|
||||
relays_tag.as_vec(), alt_tag.as_vec()])
|
||||
relays_tag.as_vec(), alt_tag.as_vec(), p_tag.as_vec()])
|
||||
|
||||
print(encrypted_params_string)
|
||||
|
||||
encrypted_params = nip04_encrypt(self.keys.secret_key(), dvm_keys.public_key(),
|
||||
encrypted_params_string)
|
||||
p_tag = Tag.parse(['p', dvm_keys.public_key().to_hex()])
|
||||
|
||||
encrypted_tag = Tag.parse(['encrypted'])
|
||||
nip90request = EventBuilder(self.dvm_config.SUPPORTED_DVMS[index].KIND, encrypted_params,
|
||||
[p_tag, encrypted_tag]).to_event(self.keys)
|
||||
@@ -220,14 +221,14 @@ class Bot:
|
||||
|
||||
elif status == "payment-required" or status == "partial":
|
||||
amount = 0
|
||||
|
||||
for tag in nostr_event.tags():
|
||||
if tag.as_vec()[0] == "amount":
|
||||
amount_msats = int(tag.as_vec()[1])
|
||||
amount = int(amount_msats / 1000)
|
||||
|
||||
entry = next((x for x in self.job_list if x['event_id'] == etag), None)
|
||||
if entry is not None and entry['is_paid'] is False and entry['dvm_key'] == ptag:
|
||||
|
||||
print("PAYMENT: " + nostr_event.as_json())
|
||||
#if we get a bolt11, we pay and move on
|
||||
if len(tag.as_vec()) > 2:
|
||||
bolt11 = tag.as_vec()[2]
|
||||
@@ -236,7 +237,8 @@ class Bot:
|
||||
else:
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=nostr_event.pubkey().to_hex(),
|
||||
client=self.client, config=self.dvm_config)
|
||||
bolt11 = zap(user.lud16, amount, "Zap", nostr_event, self.keys, self.dvm_config, "private")
|
||||
print("PAYING: " + user.name)
|
||||
bolt11 = zap(user.lud16, amount, "Zap", nostr_event, self.keys, self.dvm_config, "public")
|
||||
if bolt11 == None:
|
||||
print("Receiver has no Lightning address")
|
||||
return
|
||||
|
||||
47
dvm.py
47
dvm.py
@@ -14,7 +14,7 @@ from utils.backend_utils import get_amount_per_task, check_task_is_supported, ge
|
||||
from utils.database_utils import create_sql_table, get_or_add_user, update_user_balance, update_sql_table
|
||||
from utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event, check_and_decrypt_tags
|
||||
from utils.output_utils import post_process_result, build_status_reaction
|
||||
from utils.zap_utils import check_bolt11_ln_bits_is_paid, create_bolt11_ln_bits, parse_zap_event_tags
|
||||
from utils.zap_utils import check_bolt11_ln_bits_is_paid, create_bolt11_ln_bits, parse_zap_event_tags, redeem_cashu
|
||||
|
||||
use_logger = False
|
||||
if use_logger:
|
||||
@@ -83,19 +83,23 @@ class DVM:
|
||||
return
|
||||
|
||||
def handle_nip90_job_event(nip90_event):
|
||||
"""
|
||||
:type nip90_event: Event
|
||||
"""
|
||||
|
||||
tags: typing.List[Tag]
|
||||
tags, p_tag_str = check_and_decrypt_tags(nip90_event, self.dvm_config)
|
||||
if p_tag_str is None:
|
||||
nip90_event = check_and_decrypt_tags(nip90_event, self.dvm_config)
|
||||
if nip90_event is None:
|
||||
return
|
||||
nip90_event.tags = tags
|
||||
|
||||
user = get_or_add_user(self.dvm_config.DB, nip90_event.pubkey().to_hex(), client=self.client,
|
||||
config=self.dvm_config)
|
||||
|
||||
|
||||
cashu = ""
|
||||
p_tag_str = ""
|
||||
for tag in nip90_event.tags():
|
||||
if tag.as_vec()[0] == "cashu":
|
||||
cashu = tag.as_vec()[0]
|
||||
elif tag.as_vec()[0] == "p":
|
||||
p_tag_str = tag.as_vec()[0]
|
||||
|
||||
task_supported, task, duration = check_task_is_supported(nip90_event, client=self.client,
|
||||
get_duration=(not user.iswhitelisted),
|
||||
config=self.dvm_config)
|
||||
@@ -115,8 +119,12 @@ class DVM:
|
||||
if dvm.TASK == task and dvm.COST == 0:
|
||||
task_is_free = True
|
||||
|
||||
cashu_redeemed = False
|
||||
if cashu != "":
|
||||
cashu_redeemed = redeem_cashu(cashu, self.dvm_config)
|
||||
|
||||
# if user is whitelisted or task is free, just do the job
|
||||
if user.iswhitelisted or task_is_free:
|
||||
if user.iswhitelisted or task_is_free or cashu_redeemed:
|
||||
print(
|
||||
"[" + self.dvm_config.NIP89.name + "] Free task or Whitelisted for task " + task +
|
||||
". Starting processing..")
|
||||
@@ -126,6 +134,7 @@ class DVM:
|
||||
|
||||
do_work(nip90_event)
|
||||
# if task is directed to us via p tag and user has balance, do the job and update balance
|
||||
|
||||
elif p_tag_str == Keys.from_sk_str(
|
||||
self.dvm_config.PRIVATE_KEY).public_key().to_hex() and user.balance >= amount:
|
||||
balance = max(user.balance - amount, 0)
|
||||
@@ -147,7 +156,7 @@ class DVM:
|
||||
# else send a payment required event to user
|
||||
else:
|
||||
bid = 0
|
||||
for tag in nip90_event.tags:
|
||||
for tag in nip90_event.tags():
|
||||
if tag.as_vec()[0] == 'bid':
|
||||
bid = int(tag.as_vec()[1])
|
||||
|
||||
@@ -169,14 +178,15 @@ class DVM:
|
||||
False, amount, client=self.client, dvm_config=self.dvm_config)
|
||||
|
||||
else:
|
||||
print("Task not supported on this DVM, skipping..")
|
||||
print("[" + self.dvm_config.NIP89.name + "] Task " + task + " not supported on this DVM, skipping..")
|
||||
|
||||
def handle_zap(zap_event):
|
||||
|
||||
try:
|
||||
invoice_amount, zapped_event, sender, message, anon = parse_zap_event_tags(zap_event,
|
||||
self.keys, self.dvm_config.NIP89.name,
|
||||
self.client, self.dvm_config)
|
||||
self.keys,
|
||||
self.dvm_config.NIP89.name,
|
||||
self.client, self.dvm_config)
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, config=self.dvm_config)
|
||||
|
||||
if zapped_event is not None:
|
||||
@@ -190,14 +200,15 @@ class DVM:
|
||||
amount = int(float(tag.as_vec()[1]) / 1000)
|
||||
elif tag.as_vec()[0] == 'e':
|
||||
job_event = get_event_by_id(tag.as_vec()[1], client=self.client, config=self.dvm_config)
|
||||
tags: typing.List[Tag]
|
||||
tags, p_tag_str = check_and_decrypt_tags(job_event, self.dvm_config)
|
||||
job_event.tags = tags
|
||||
if job_event is not None:
|
||||
job_event = check_and_decrypt_tags(job_event, self.dvm_config)
|
||||
else:
|
||||
return
|
||||
|
||||
if p_tag_str is None:
|
||||
return
|
||||
|
||||
# if a reaction by us got zapped
|
||||
# if a reaction by us got zapped
|
||||
|
||||
task_supported, task, duration = check_task_is_supported(job_event,
|
||||
client=self.client,
|
||||
@@ -364,7 +375,7 @@ class DVM:
|
||||
status_tag = Tag.parse(["status", status])
|
||||
|
||||
tags = [e_tag, alt_tag, status_tag]
|
||||
for tag in original_event.tags:
|
||||
for tag in original_event.tags():
|
||||
|
||||
if tag.as_vec()[0] == "p":
|
||||
p_tag = tag
|
||||
|
||||
13
main.py
13
main.py
@@ -14,9 +14,6 @@ from utils.dvmconfig import DVMConfig
|
||||
|
||||
|
||||
def run_nostr_dvm_with_local_config():
|
||||
# We extract the Publickey from our bot, so the DVMs know who they should listen and react to.
|
||||
bot_publickey = Keys.from_sk_str(os.getenv("BOT_PRIVATE_KEY")).public_key()
|
||||
|
||||
# We will run an optional bot that can communicate with the DVMs
|
||||
# Note this is very basic for now and still under development
|
||||
bot_config = DVMConfig()
|
||||
@@ -29,25 +26,25 @@ def run_nostr_dvm_with_local_config():
|
||||
# You can add arbitrary DVMs there and instantiate them here
|
||||
|
||||
# Spawn DVM1 Kind 5000: A local Text Extractor from PDFs
|
||||
pdfextractor = build_pdf_extractor("PDF Extractor", [bot_publickey])
|
||||
pdfextractor = build_pdf_extractor("PDF Extractor")
|
||||
# If we don't add it to the bot, the bot will not provide access to the DVM
|
||||
pdfextractor.run()
|
||||
|
||||
# Spawn DVM2 Kind 5002 Local Text Translation, calling the free Google API.
|
||||
translator = build_translator("Translator", [bot_publickey])
|
||||
translator = build_translator("Translator")
|
||||
bot_config.SUPPORTED_DVMS.append(translator) # We add translator to the bot
|
||||
translator.run()
|
||||
|
||||
# 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
|
||||
if os.getenv("NOVA_SERVER") is not None and os.getenv("NOVA_SERVER") != "":
|
||||
unstable_artist = build_unstable_diffusion("Unstable Diffusion", [bot_publickey])
|
||||
unstable_artist = build_unstable_diffusion("Unstable Diffusion")
|
||||
bot_config.SUPPORTED_DVMS.append(unstable_artist) # We add unstable Diffusion to the bot
|
||||
unstable_artist.run()
|
||||
|
||||
# Spawn DVM4, another Instance of text-to-image, as before but use a different privatekey, model and lora this time.
|
||||
if os.getenv("NOVA_SERVER") is not None and os.getenv("NOVA_SERVER") != "":
|
||||
sketcher = build_sketcher("Sketcher", [bot_publickey])
|
||||
sketcher = build_sketcher("Sketcher")
|
||||
bot_config.SUPPORTED_DVMS.append(sketcher) # We also add Sketcher to the bot
|
||||
sketcher.run()
|
||||
|
||||
@@ -55,7 +52,7 @@ def run_nostr_dvm_with_local_config():
|
||||
# per call. Make sure you have enough balance and the DVM's cost is set higher than what you pay yourself, except, you know,
|
||||
# you're being generous.
|
||||
if os.getenv("OPENAI_API_KEY") is not None and os.getenv("OPENAI_API_KEY") != "":
|
||||
dalle = build_dalle("Dall-E 3", [bot_publickey])
|
||||
dalle = build_dalle("Dall-E 3")
|
||||
bot_config.SUPPORTED_DVMS.append(dalle)
|
||||
dalle.run()
|
||||
|
||||
|
||||
@@ -37,12 +37,11 @@ admin_config.REBROADCAST_NIP89 = False
|
||||
# want to update your NIP89 descriptions
|
||||
|
||||
|
||||
def build_pdf_extractor(name, dm_allowed_keys):
|
||||
def build_pdf_extractor(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
dvm_config.DM_ALLOWED = dm_allowed_keys
|
||||
# Add NIP89
|
||||
nip90params = {}
|
||||
nip89info = {
|
||||
@@ -59,12 +58,11 @@ def build_pdf_extractor(name, dm_allowed_keys):
|
||||
admin_config=admin_config)
|
||||
|
||||
|
||||
def build_translator(name, dm_allowed_keys):
|
||||
def build_translator(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
dvm_config.DM_ALLOWED = dm_allowed_keys
|
||||
|
||||
nip90params = {
|
||||
"language": {
|
||||
@@ -92,12 +90,11 @@ def build_translator(name, dm_allowed_keys):
|
||||
admin_config=admin_config)
|
||||
|
||||
|
||||
def build_unstable_diffusion(name, dm_allowed_keys):
|
||||
def build_unstable_diffusion(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
dvm_config.LNBITS_INVOICE_KEY = "" #This one will not use Lnbits to create invoices, but rely on zaps
|
||||
dvm_config.LNBITS_URL = ""
|
||||
dvm_config.DM_ALLOWED = dm_allowed_keys
|
||||
|
||||
# A module might have options it can be initialized with, here we set a default model, and the nova-server
|
||||
# address it should use. These parameters can be freely defined in the task component
|
||||
@@ -126,12 +123,11 @@ def build_unstable_diffusion(name, dm_allowed_keys):
|
||||
admin_config=admin_config, options=options)
|
||||
|
||||
|
||||
def build_sketcher(name, dm_allowed_keys):
|
||||
def build_sketcher(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY2")
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
dvm_config.DM_ALLOWED = dm_allowed_keys
|
||||
|
||||
nip90params = {
|
||||
"negative_prompt": {
|
||||
@@ -162,12 +158,11 @@ def build_sketcher(name, dm_allowed_keys):
|
||||
admin_config=admin_config, options=options)
|
||||
|
||||
|
||||
def build_dalle(name, dm_allowed_keys):
|
||||
def build_dalle(name):
|
||||
dvm_config = DVMConfig()
|
||||
dvm_config.PRIVATE_KEY = os.getenv("NOSTR_PRIVATE_KEY3")
|
||||
dvm_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
dvm_config.DM_ALLOWED = dm_allowed_keys
|
||||
profit_in_sats = 10
|
||||
dvm_config.COST = int(((4.0 / (get_price_per_sat("USD") * 100)) + profit_in_sats))
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class ImageGenerationDALLE(DVMTaskInterface):
|
||||
model = "dall-e-3"
|
||||
quality = "standard"
|
||||
|
||||
for tag in event.tags:
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "text":
|
||||
|
||||
@@ -55,7 +55,7 @@ class ImageGenerationSDXL(DVMTaskInterface):
|
||||
lora_weight = ""
|
||||
strength = ""
|
||||
guidance_scale = ""
|
||||
for tag in event.tags:
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "text":
|
||||
|
||||
@@ -43,7 +43,7 @@ class TextExtractionPDF(DVMTaskInterface):
|
||||
input_content = ""
|
||||
url = ""
|
||||
|
||||
for tag in event.tags:
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
input_type = tag.as_vec()[2]
|
||||
input_content = tag.as_vec()[1]
|
||||
|
||||
@@ -42,7 +42,7 @@ class Translation(DVMTaskInterface):
|
||||
text = ""
|
||||
translation_lang = "en"
|
||||
|
||||
for tag in event.tags:
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
input_type = tag.as_vec()[2]
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import datetime as datetime
|
||||
@@ -5,7 +6,8 @@ from pathlib import Path
|
||||
from threading import Thread
|
||||
|
||||
import dotenv
|
||||
from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt
|
||||
from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt, \
|
||||
nip04_encrypt
|
||||
|
||||
from utils.dvmconfig import DVMConfig
|
||||
from utils.nostr_utils import send_event
|
||||
@@ -66,6 +68,46 @@ def nostr_client_test_image(prompt):
|
||||
send_event(event, client=client, dvm_config=config)
|
||||
return event.as_json()
|
||||
|
||||
|
||||
def nostr_client_test_image_private(prompt, cashutoken):
|
||||
keys = Keys.from_sk_str(os.getenv("NOSTR_TEST_CLIENT_PRIVATE_KEY"))
|
||||
receiver_keys = Keys.from_sk_str(os.getenv("NOSTR_PRIVATE_KEY"))
|
||||
|
||||
|
||||
# TODO more advanced logic, more parsing, params etc, just very basic test functions for now
|
||||
|
||||
relay_list = ["wss://relay.damus.io", "wss://blastr.f7z.xyz", "wss://relayable.org",
|
||||
"wss://nostr-pub.wellorder.net"]
|
||||
i_tag = Tag.parse(["i", prompt, "text"])
|
||||
outTag = Tag.parse(["output", "image/png;format=url"])
|
||||
paramTag1 = Tag.parse(["param", "size", "1024x1024"])
|
||||
tTag = Tag.parse(["t", "bitcoin"])
|
||||
|
||||
bid = str(50 * 1000)
|
||||
bid_tag = Tag.parse(['bid', bid, bid])
|
||||
relays_tag = Tag.parse(["relays", json.dumps(relay_list)])
|
||||
alt_tag = Tag.parse(["alt", "Super secret test"])
|
||||
cashu_tag = Tag.parse(["cashu", cashutoken])
|
||||
|
||||
encrypted_params_string = json.dumps([i_tag.as_vec(), outTag.as_vec(), paramTag1.as_vec(), tTag, bid_tag.as_vec(),
|
||||
relays_tag.as_vec(), alt_tag.as_vec(), cashu_tag.as_vec()])
|
||||
|
||||
|
||||
encrypted_params = nip04_encrypt(keys.secret_key(), receiver_keys.public_key(),
|
||||
encrypted_params_string)
|
||||
|
||||
p_tag = Tag.parse(['p', keys.public_key().to_hex()])
|
||||
encrypted_tag = Tag.parse(['encrypted'])
|
||||
nip90request = EventBuilder(EventDefinitions.KIND_NIP90_GENERATE_IMAGE, encrypted_params,
|
||||
[p_tag, encrypted_tag]).to_event(keys)
|
||||
client = Client(keys)
|
||||
for relay in relay_list:
|
||||
client.add_relay(relay)
|
||||
client.connect()
|
||||
config = DVMConfig
|
||||
send_event(nip90request, client=client, dvm_config=config)
|
||||
return nip90request.as_json()
|
||||
|
||||
def nostr_client():
|
||||
keys = Keys.from_sk_str(os.getenv("NOSTR_TEST_CLIENT_PRIVATE_KEY"))
|
||||
sk = keys.secret_key()
|
||||
@@ -89,6 +131,8 @@ def nostr_client():
|
||||
#nostr_client_test_translation("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20)
|
||||
|
||||
nostr_client_test_image("a beautiful purple ostrich watching the sunset")
|
||||
|
||||
#nostr_client_test_image_private("a beautiful ostrich watching the sunset", cashutoken )
|
||||
class NotificationHandler(HandleNotification):
|
||||
def handle(self, relay_url, event):
|
||||
print(f"Received new event from {relay_url}: {event.as_json()}")
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import typing
|
||||
|
||||
import requests
|
||||
from nostr_sdk import Event, Tag
|
||||
|
||||
from utils.definitions import EventDefinitions
|
||||
from utils.nostr_utils import get_event_by_id
|
||||
|
||||
@@ -19,7 +23,7 @@ def get_task(event, client, dvmconfig):
|
||||
|
||||
# This looks a bit more complicated, but we do several tasks for text-extraction in the future
|
||||
elif event.kind() == EventDefinitions.KIND_NIP90_EXTRACT_TEXT:
|
||||
for tag in event.tags():
|
||||
for tag in event.tags:
|
||||
if tag.as_vec()[0] == "i":
|
||||
if tag.as_vec()[2] == "url":
|
||||
file_type = check_url_is_readable(tag.as_vec()[1])
|
||||
@@ -50,43 +54,47 @@ def get_task(event, client, dvmconfig):
|
||||
return "unknown type"
|
||||
|
||||
|
||||
def check_task_is_supported(event, client, get_duration=False, config=None):
|
||||
def check_task_is_supported(event: Event, client, get_duration=False, config=None):
|
||||
try:
|
||||
dvm_config = config
|
||||
input_value = ""
|
||||
input_type = ""
|
||||
duration = 1
|
||||
|
||||
task = get_task(event, client=client, dvmconfig=dvm_config)
|
||||
|
||||
for tag in event.tags:
|
||||
if tag.as_vec()[0] == 'i':
|
||||
if len(tag.as_vec()) < 3:
|
||||
print("Job Event missing/malformed i tag, skipping..")
|
||||
return False, "", 0
|
||||
else:
|
||||
input_value = tag.as_vec()[1]
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "event":
|
||||
evt = get_event_by_id(input_value, client=client, config=dvm_config)
|
||||
if evt is None:
|
||||
print("Event not found")
|
||||
return False, "", 0
|
||||
elif input_type == 'url' and check_url_is_readable(input_value) is None:
|
||||
print("Url not readable / supported")
|
||||
return False, task, duration
|
||||
try:
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'i':
|
||||
if len(tag.as_vec()) < 3:
|
||||
print("Job Event missing/malformed i tag, skipping..")
|
||||
return False, "", 0
|
||||
else:
|
||||
input_value = tag.as_vec()[1]
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "event":
|
||||
evt = get_event_by_id(input_value, client=client, config=dvm_config)
|
||||
if evt is None:
|
||||
print("Event not found")
|
||||
return False, "", 0
|
||||
elif input_type == 'url' and check_url_is_readable(input_value) is None:
|
||||
print("Url not readable / supported")
|
||||
return False, task, duration #
|
||||
|
||||
elif tag.as_vec()[0] == 'output':
|
||||
output = tag.as_vec()[1]
|
||||
if not (output == "text/plain"
|
||||
or output == "text/json" or output == "json"
|
||||
or output == "image/png" or "image/jpg"
|
||||
or output == "image/png;format=url" or output == "image/jpg;format=url"
|
||||
or output == ""):
|
||||
print("Output format not supported, skipping..")
|
||||
return False, "", 0
|
||||
elif tag.as_vec()[0] == 'output':
|
||||
# TODO move this to individual modules
|
||||
output = tag.as_vec()[1]
|
||||
if not (output == "text/plain"
|
||||
or output == "text/json" or output == "json"
|
||||
or output == "image/png" or "image/jpg"
|
||||
or output == "image/png;format=url" or output == "image/jpg;format=url"
|
||||
or output == ""):
|
||||
print("Output format not supported, skipping..")
|
||||
return False, "", 0
|
||||
except Exception as e:
|
||||
print("Check task 2: " + str(e))
|
||||
|
||||
for dvm in dvm_config.SUPPORTED_DVMS:
|
||||
print(dvm.TASK)
|
||||
if dvm.TASK == task:
|
||||
if not dvm.is_input_supported(input_type, event.content()):
|
||||
return False, task, duration
|
||||
@@ -96,6 +104,7 @@ def check_task_is_supported(event, client, get_duration=False, config=None):
|
||||
|
||||
return True, task, duration
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print("Check task: " + str(e))
|
||||
|
||||
@@ -124,10 +133,11 @@ def check_url_is_readable(url):
|
||||
|
||||
|
||||
def get_amount_per_task(task, dvm_config, duration=1):
|
||||
for dvm in dvm_config.SUPPORTED_DVMS: #this is currently just one
|
||||
for dvm in dvm_config.SUPPORTED_DVMS: # this is currently just one
|
||||
if dvm.TASK == task:
|
||||
amount = dvm.COST * duration
|
||||
return amount
|
||||
else:
|
||||
print("["+dvm_config.SUPPORTED_DVMS[0].NAME +"] Task " + task + " is currently not supported by this instance, skipping")
|
||||
print("[" + dvm_config.SUPPORTED_DVMS[
|
||||
0].NAME + "] Task " + task + " is currently not supported by this instance, skipping")
|
||||
return None
|
||||
|
||||
@@ -206,15 +206,18 @@ def fetch_user_metadata(npub, client):
|
||||
if len(events) > 0:
|
||||
latest_entry = events[0]
|
||||
latest_time = 0
|
||||
for entry in events:
|
||||
if entry.created_at().as_secs() > latest_time:
|
||||
latest_time = entry.created_at().as_secs()
|
||||
latest_entry = entry
|
||||
profile = json.loads(latest_entry.content())
|
||||
if profile.get("name"):
|
||||
name = profile['name']
|
||||
if profile.get("nip05"):
|
||||
nip05 = profile['nip05']
|
||||
if profile.get("lud16"):
|
||||
lud16 = profile['lud16']
|
||||
try:
|
||||
for entry in events:
|
||||
if entry.created_at().as_secs() > latest_time:
|
||||
latest_time = entry.created_at().as_secs()
|
||||
latest_entry = entry
|
||||
profile = json.loads(latest_entry.content())
|
||||
if profile.get("name"):
|
||||
name = profile['name']
|
||||
if profile.get("nip05"):
|
||||
nip05 = profile['nip05']
|
||||
if profile.get("lud16"):
|
||||
lud16 = profile['lud16']
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return name, nip05, lud16
|
||||
|
||||
@@ -19,8 +19,6 @@ class DVMConfig:
|
||||
DB: str
|
||||
NEW_USER_BALANCE: int = 250 # Free credits for new users
|
||||
NIP89: NIP89Announcement
|
||||
DM_ALLOWED = []
|
||||
|
||||
SHOW_RESULT_BEFORE_PAYMENT: bool = False # if this is true show results even when not paid right after autoprocess
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import typing
|
||||
from datetime import timedelta
|
||||
from nostr_sdk import Filter, Client, Alphabet, EventId, Event, PublicKey, Tag, Keys, nip04_decrypt
|
||||
from nostr_sdk import Filter, Client, Alphabet, EventId, Event, PublicKey, Tag, Keys, nip04_decrypt, EventBuilder
|
||||
|
||||
|
||||
def get_event_by_id(event_id: str, client: Client, config=None) -> Event | None:
|
||||
@@ -61,38 +62,38 @@ def send_event(event: Event, client: Client, dvm_config) -> EventId:
|
||||
|
||||
|
||||
def check_and_decrypt_tags(event, dvm_config):
|
||||
tags = []
|
||||
is_encrypted = False
|
||||
p = ""
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'encrypted':
|
||||
is_encrypted = True
|
||||
elif tag.as_vec()[0] == 'p':
|
||||
p = tag.as_vec()[1]
|
||||
try:
|
||||
tags = []
|
||||
is_encrypted = False
|
||||
p = ""
|
||||
sender = event.pubkey()
|
||||
for tag in event.tags():
|
||||
if tag.as_vec()[0] == 'encrypted':
|
||||
is_encrypted = True
|
||||
elif tag.as_vec()[0] == 'p':
|
||||
p = tag.as_vec()[1]
|
||||
|
||||
if is_encrypted:
|
||||
if p != Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex():
|
||||
print("[" + dvm_config.NIP89.name + "] Task encrypted and not addressed to this DVM, "
|
||||
"skipping..")
|
||||
return None, None
|
||||
if is_encrypted:
|
||||
if p != Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex():
|
||||
print("[" + dvm_config.NIP89.name + "] Task encrypted and not addressed to this DVM, "
|
||||
"skipping..")
|
||||
return None
|
||||
|
||||
elif p == Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex():
|
||||
encrypted_tag = Tag.parse(["encrypted"])
|
||||
p_tag = Tag.parse(["p", p])
|
||||
elif p == Keys.from_sk_str(dvm_config.PRIVATE_KEY).public_key().to_hex():
|
||||
print("encrypted")
|
||||
#encrypted_tag = Tag.parse(["encrypted"])
|
||||
#p_tag = Tag.parse(["p", p])
|
||||
|
||||
tags_str = nip04_decrypt(Keys.from_sk_str(dvm_config.PRIVATE_KEY).secret_key(),
|
||||
event.pubkey(), event.content())
|
||||
params = json.loads(tags_str)
|
||||
|
||||
for element in params:
|
||||
tags.append(Tag.parse(element))
|
||||
|
||||
# Keep the encrypted tag
|
||||
tags.append(p_tag)
|
||||
tags.append(encrypted_tag)
|
||||
|
||||
return tags, p
|
||||
|
||||
else:
|
||||
return event.tags, p
|
||||
tags_str = nip04_decrypt(Keys.from_sk_str(dvm_config.PRIVATE_KEY).secret_key(),
|
||||
event.pubkey(), event.content())
|
||||
#TODO add outer p tag so it doesnt have to be sent twice
|
||||
params = json.loads(tags_str)
|
||||
eventasjson = json.loads(event.as_json())
|
||||
eventasjson['tags'] = params
|
||||
eventasjson['content'] = ""
|
||||
event = Event.from_json(json.dumps(eventasjson))
|
||||
print(event.as_json())
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return event
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# LIGHTNING FUNCTIONS
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
@@ -54,26 +55,26 @@ def parse_zap_event_tags(zap_event, keys, name, client, config):
|
||||
elif tag.as_vec()[0] == 'e':
|
||||
zapped_event = get_event_by_id(tag.as_vec()[1], client=client, config=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 z_tag in zap_request_event.tags():
|
||||
if z_tag.as_vec()[0] == 'anon':
|
||||
if len(z_tag.as_vec()) > 1:
|
||||
#print("[" + name + "] Private Zap received.")
|
||||
decrypted_content = decrypt_private_zap_message(z_tag.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(
|
||||
"[" + name + "] Anonymous Zap received. Unlucky, I don't know from whom, and never will")
|
||||
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 z_tag in zap_request_event.tags():
|
||||
if z_tag.as_vec()[0] == 'anon':
|
||||
if len(z_tag.as_vec()) > 1:
|
||||
# print("[" + name + "] Private Zap received.")
|
||||
decrypted_content = decrypt_private_zap_message(z_tag.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(
|
||||
"[" + name + "] Anonymous Zap received. Unlucky, I don't know from whom, and never will")
|
||||
|
||||
return invoice_amount, zapped_event, sender, message, anon
|
||||
|
||||
@@ -212,3 +213,52 @@ def zap(lud16: str, amount: int, content, zapped_event: Event, keys, dvm_config,
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
|
||||
def parse_cashu(cashuToken):
|
||||
try:
|
||||
base64token = cashuToken.replace("cashuA", "")
|
||||
cashu = json.loads(base64.b64decode(base64token).decode('utf-8'))
|
||||
token = cashu["token"][0]
|
||||
proofs = token["proofs"]
|
||||
mint = token["mint"]
|
||||
|
||||
totalAmount = 0
|
||||
for proof in proofs:
|
||||
totalAmount += proof["amount"]
|
||||
|
||||
fees = max(int(totalAmount * 0.02), 2)
|
||||
redeemInvoiceAmount = totalAmount - fees
|
||||
|
||||
return cashuToken, mint, totalAmount, fees, redeemInvoiceAmount, proofs
|
||||
except Exception as e:
|
||||
print("Could not parse this cashu token")
|
||||
return None, None, None, None, None, None
|
||||
|
||||
|
||||
def redeem_cashu(cashu, config):
|
||||
# TODO untested
|
||||
is_redeemed = False
|
||||
cashuToken, mint, totalAmount, fees, redeemInvoiceAmount, proofs = parse_cashu(cashu)
|
||||
invoice = create_bolt11_ln_bits(totalAmount, config)
|
||||
try:
|
||||
url = mint + "/melt" # Melt cashu tokens at Mint
|
||||
json_object = {"proofs": proofs, "pr": invoice}
|
||||
|
||||
headers = {"Content-Type": "application/json; charset=utf-8"}
|
||||
request = requests.post(url, json=json_object, headers=headers)
|
||||
|
||||
if request.status_code == 200:
|
||||
tree = json.loads(request.text)
|
||||
successful = tree.get("paid") == "true"
|
||||
if successful:
|
||||
is_redeemed = True
|
||||
else:
|
||||
msg = tree.get("detail", "").split('.')[0].strip() if tree.get("detail") else None
|
||||
is_redeemed = False
|
||||
print(msg)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return is_redeemed
|
||||
|
||||
Reference in New Issue
Block a user