mirror of
https://github.com/believethehype/nostrdvm.git
synced 2025-03-17 21:31:52 +01:00
basic bot is working, spawn dvms dependent on config
This commit is contained in:
parent
215916c1ef
commit
6be3372d76
@ -24,7 +24,5 @@ TASK_IMAGE_GENERATION_NIP89_DTAG3 = "asdasd"
|
||||
|
||||
|
||||
#Backend Specific Options for tasks that require them
|
||||
#nova-server is a local backend supporting some AI modules and needs to be installed separately,
|
||||
#if dvms supporting it should be used
|
||||
OPENAI_API_KEY = "your-openai-api-key"
|
||||
NOVA_SERVER = "127.0.0.1:37318"
|
||||
OPENAI_API_KEY = "" # Enter your OpenAI API Key to use DVMs with OpenAI services
|
||||
NOVA_SERVER = "" # Enter the address of a nova-server instance, locally or on a machine in your network host:port
|
33
README.md
33
README.md
@ -1,19 +1,28 @@
|
||||
# NostrAI Data Vending Machine
|
||||
# NostrAI: Nostr NIP90 Data Vending Machine Framework
|
||||
|
||||
This example DVM implementation in Python currently supports simple translations using Google translate, as well as extraction of text from links with pdf files.
|
||||
This framework provides a way to easily build and/or run `Nostr NIP90 DVMs in Python`.
|
||||
|
||||
At a later stage, additional example tasks will be added, as well as the integration into a larger Machine Learning backend
|
||||
This project is currently under development and additional tasks and features are added along the way.
|
||||
This means the project is in alpha status, interfaces might still change/break.
|
||||
|
||||
|
||||
Place .env file (based on .env_example) in main folder, install requirements.txt (python 3.10) run main.py. Optionally supports LNbits to create invoices instead of lnaddresses.
|
||||
## To get started:
|
||||
(Tested on Python 3.10)
|
||||
|
||||
Use vendata.io to create a nip89 announcement of your dvm and save the dtag in your .env config.
|
||||
Create a new venv by running `"python -m venv venv"`
|
||||
- Place .env file (based on .env_example) in main folder.
|
||||
- Set your own private hex keys, create NIP89 dtags on vendata.io,
|
||||
- Install requirements.txt
|
||||
- Run python main.py.
|
||||
|
||||
A tutorial on how to add additional tasks, as well as the larger server backend will be added soon.
|
||||
In `playground.py` some DVMs are already prepared. Feel free to play along with the existing ones.
|
||||
You can also add new tasks by using the interface, just like the existing tasks in the `tasks` folder.
|
||||
|
||||
Known Issues:
|
||||
- After refactoring DVMs work independent from each other for the most part.
|
||||
- Some functions might work easier than they did before (need some refactoring)
|
||||
- Bot currently not implemented
|
||||
- Some basic functionality is still missing, e.g. handling various mediasources
|
||||
- Interface might still change a lot and brick things.
|
||||
A `bot` is running by default that lists and communicates with the `DVMs` added to it,
|
||||
so your DVMs can be controled via any regular client as well.
|
||||
|
||||
The Framework optionally supports `LNbits` to create invoices instead of using a `lightning address`. If LNBits is not used,
|
||||
make sure your nostr accounts have a valid lightning address.
|
||||
|
||||
|
||||
A tutorial on how to add additional tasks, as well as the larger server backend will be added at a later stage.
|
||||
|
95
bot.py
95
bot.py
@ -1,6 +1,7 @@
|
||||
import json
|
||||
import time
|
||||
from datetime import timedelta
|
||||
from threading import Thread
|
||||
|
||||
from nostr_sdk import Keys, Client, Timestamp, Filter, nip04_decrypt, HandleNotification, EventBuilder, PublicKey, \
|
||||
Event, Options
|
||||
@ -10,7 +11,8 @@ from utils.backend_utils import get_amount_per_task
|
||||
from utils.database_utils import get_or_add_user, update_user_balance, create_sql_table, update_sql_table, User
|
||||
from utils.definitions import EventDefinitions
|
||||
from utils.nostr_utils import send_event, get_event_by_id
|
||||
from utils.zap_utils import parse_amount_from_bolt11_invoice, check_for_zapplepay, decrypt_private_zap_message
|
||||
from utils.zap_utils import parse_amount_from_bolt11_invoice, check_for_zapplepay, decrypt_private_zap_message, \
|
||||
parse_zap_event_tags
|
||||
|
||||
|
||||
class Bot:
|
||||
@ -28,7 +30,8 @@ class Bot:
|
||||
|
||||
pk = self.keys.public_key()
|
||||
|
||||
print("Nostr BOT public key: " + str(pk.to_bech32()) + " Hex: " + str(pk.to_hex()) + " Name: " + self.NAME + " Supported DVM tasks: " +
|
||||
print("Nostr BOT public key: " + str(pk.to_bech32()) + " Hex: " + str(pk.to_hex()) + " Name: " + self.NAME +
|
||||
" Supported DVM tasks: " +
|
||||
', '.join(p.NAME + ":" + p.TASK for p in self.dvm_config.SUPPORTED_DVMS) + "\n")
|
||||
|
||||
for relay in self.dvm_config.RELAY_LIST:
|
||||
@ -36,8 +39,7 @@ class Bot:
|
||||
self.client.connect()
|
||||
|
||||
zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now())
|
||||
dm_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM]).since(
|
||||
Timestamp.now())
|
||||
dm_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM]).since(Timestamp.now())
|
||||
|
||||
self.client.subscribe([zap_filter, dm_filter])
|
||||
|
||||
@ -50,7 +52,7 @@ class Bot:
|
||||
keys = self.keys
|
||||
|
||||
def handle(self, relay_url, nostr_event):
|
||||
if EventDefinitions.KIND_DM:
|
||||
if nostr_event.kind() == EventDefinitions.KIND_DM:
|
||||
handle_dm(nostr_event)
|
||||
elif nostr_event.kind() == EventDefinitions.KIND_ZAP:
|
||||
handle_zap(nostr_event)
|
||||
@ -63,24 +65,20 @@ class Bot:
|
||||
|
||||
try:
|
||||
decrypted_text = nip04_decrypt(self.keys.secret_key(), nostr_event.pubkey(), nostr_event.content())
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client)
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, config=self.dvm_config)
|
||||
|
||||
# user = User
|
||||
# user.npub = sender
|
||||
# user.balance = 250
|
||||
# user.iswhitelisted = False
|
||||
# user.isblacklisted = False
|
||||
# user.name = "Test"
|
||||
# user.nip05 = "Test@test"
|
||||
# user.lud16 = "Test@test"
|
||||
|
||||
# We do a selection of tasks now, maybe change this later, Idk.
|
||||
if decrypted_text[0].isdigit():
|
||||
index = int(decrypted_text.split(' ')[0]) - 1
|
||||
task = self.dvm_config.SUPPORTED_DVMS[index].TASK
|
||||
print("["+ self.NAME + "] Request from " + str(user.name) + " (" + str(user.nip05) + ", Balance: "+ str(user.balance)+ " Sats) Task: " + str(task))
|
||||
print("[" + self.NAME + "] Request from " + str(user.name) + " (" + str(user.nip05) + ", Balance: "
|
||||
+ str(user.balance)+ " Sats) Task: " + str(task))
|
||||
|
||||
duration = 1
|
||||
required_amount = get_amount_per_task(self.dvm_config.SUPPORTED_DVMS[index].TASK,
|
||||
self.dvm_config, duration)
|
||||
|
||||
required_amount = self.dvm_config.SUPPORTED_DVMS[index].COST
|
||||
|
||||
if user.isblacklisted:
|
||||
# For some reason an admin might blacklist npubs, e.g. for abusing the service
|
||||
@ -135,8 +133,8 @@ class Bot:
|
||||
print("payment-required")
|
||||
time.sleep(2.0)
|
||||
evt = EventBuilder.new_encrypted_direct_msg(self.keys, nostr_event.pubkey(),
|
||||
"Balance required, please zap me with at least " + str(
|
||||
int(required_amount - user.balance))
|
||||
"Balance required, please zap me with at least " +
|
||||
str(int(required_amount - user.balance))
|
||||
+ " Sats, then try again.",
|
||||
nostr_event.id()).to_event(self.keys)
|
||||
time.sleep(2.0)
|
||||
@ -149,7 +147,8 @@ class Bot:
|
||||
|
||||
dvm_result = json.loads(decrypted_text)
|
||||
user_npub_hex = dvm_result["sender"]
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=user_npub_hex, client=self.client)
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=user_npub_hex,
|
||||
client=self.client, config=self.dvm_config)
|
||||
print("[" + self.NAME + "] Received results, message to orignal sender " + user.name)
|
||||
reply_event = EventBuilder.new_encrypted_direct_msg(self.keys,
|
||||
PublicKey.from_hex(user.npub),
|
||||
@ -159,7 +158,7 @@ class Bot:
|
||||
send_event(reply_event, client=self.client, dvm_config=dvm_config)
|
||||
|
||||
else:
|
||||
print("Message from " + user.name + ": " + decrypted_text)
|
||||
print("[" + self.NAME + "] Message from " + user.name + ": " + decrypted_text)
|
||||
message = "DVMs that I support:\n\n"
|
||||
index = 1
|
||||
for p in self.dvm_config.SUPPORTED_DVMS:
|
||||
@ -173,61 +172,45 @@ class Bot:
|
||||
#nostr_event.id()).to_event(self.keys)
|
||||
time.sleep(3)
|
||||
send_event(evt, client=self.client, dvm_config=dvm_config)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
# TODO we still receive (broken content) events after fetching the metadata, but we don't listen to them.
|
||||
# probably in client.get_events_of in fetch_user_metadata
|
||||
print("Error in bot " + str(e))
|
||||
def handle_zap(zap_event):
|
||||
zapped_event = None
|
||||
invoice_amount = 0
|
||||
anon = False
|
||||
sender = zap_event.pubkey()
|
||||
print("Zap received")
|
||||
print("[" + self.NAME + "] Zap received")
|
||||
|
||||
try:
|
||||
for tag in zap_event.tags():
|
||||
if tag.as_vec()[0] == 'bolt11':
|
||||
invoice_amount = parse_amount_from_bolt11_invoice(tag.as_vec()[1])
|
||||
elif tag.as_vec()[0] == 'e':
|
||||
zapped_event = get_event_by_id(tag.as_vec()[1], client=self.client, config=self.dvm_config)
|
||||
elif tag.as_vec()[0] == 'description':
|
||||
zap_request_event = Event.from_json(tag.as_vec()[1])
|
||||
sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(),
|
||||
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("Private Zap received.")
|
||||
decrypted_content = decrypt_private_zap_message(z_tag.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(self.dvm_config.DB, sender, client=self.client)
|
||||
invoice_amount, zapped_event, sender, anon = parse_zap_event_tags(zap_event,
|
||||
self.keys, self.NAME,
|
||||
self.client, self.dvm_config)
|
||||
|
||||
user = get_or_add_user(self.dvm_config.DB, sender, client=self.client, config=self.dvm_config)
|
||||
|
||||
if zapped_event is not None:
|
||||
if not anon:
|
||||
print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
|
||||
print("[" + self.NAME + "] Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
|
||||
user.name))
|
||||
update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client,
|
||||
config=self.dvm_config)
|
||||
|
||||
# a regular note
|
||||
elif not anon:
|
||||
print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
|
||||
print("[" + self.NAME + "] Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
|
||||
user.name))
|
||||
update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client,
|
||||
config=self.dvm_config)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during content decryption: {e}")
|
||||
print("[" + self.NAME + "] Error during content decryption:" + str(e))
|
||||
|
||||
self.client.handle_notifications(NotificationHandler())
|
||||
while True:
|
||||
time.sleep(1.0)
|
||||
|
||||
def run(self):
|
||||
bot = Bot
|
||||
nostr_dvm_thread = Thread(target=bot, args=[self.dvm_config])
|
||||
nostr_dvm_thread.start()
|
||||
|
||||
|
85
dvm.py
85
dvm.py
@ -15,15 +15,13 @@ from utils.database_utils import update_sql_table, get_from_sql_table, \
|
||||
from utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event
|
||||
from utils.output_utils import post_process_result, build_status_reaction
|
||||
from utils.zap_utils import check_bolt11_ln_bits_is_paid, parse_amount_from_bolt11_invoice, \
|
||||
check_for_zapplepay, decrypt_private_zap_message, create_bolt11_ln_bits
|
||||
check_for_zapplepay, decrypt_private_zap_message, create_bolt11_ln_bits, parse_zap_event_tags
|
||||
|
||||
use_logger = False
|
||||
if use_logger:
|
||||
init_logger(LogLevel.DEBUG)
|
||||
|
||||
|
||||
|
||||
|
||||
class DVM:
|
||||
dvm_config: DVMConfig
|
||||
admin_config: AdminConfig
|
||||
@ -32,7 +30,7 @@ class DVM:
|
||||
job_list: list
|
||||
jobs_on_hold_list: list
|
||||
|
||||
def __init__(self, dvmconfig, adminconfig = None):
|
||||
def __init__(self, dvmconfig, adminconfig=None):
|
||||
self.dvm_config = dvmconfig
|
||||
self.admin_config = adminconfig
|
||||
self.keys = Keys.from_sk_str(dvmconfig.PRIVATE_KEY)
|
||||
@ -56,7 +54,8 @@ class DVM:
|
||||
self.client.connect()
|
||||
|
||||
zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now())
|
||||
bot_dm_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM]).authors(self.dvm_config.DM_ALLOWED).since(Timestamp.now())
|
||||
bot_dm_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM]).authors(self.dvm_config.DM_ALLOWED).since(
|
||||
Timestamp.now())
|
||||
|
||||
kinds = [EventDefinitions.KIND_NIP90_GENERIC]
|
||||
for dvm in self.dvm_config.SUPPORTED_DVMS:
|
||||
@ -75,7 +74,8 @@ class DVM:
|
||||
|
||||
def handle(self, relay_url, nostr_event):
|
||||
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC:
|
||||
print("[" + self.dvm_config.NIP89.name + "] " + f"Received new NIP90 Job Request from {relay_url}: {nostr_event.as_json()}")
|
||||
print(
|
||||
"[" + self.dvm_config.NIP89.name + "] " + f"Received new NIP90 Job Request from {relay_url}: {nostr_event.as_json()}")
|
||||
handle_nip90_job_event(nostr_event)
|
||||
elif nostr_event.kind() == EventDefinitions.KIND_ZAP:
|
||||
handle_zap(nostr_event)
|
||||
@ -86,7 +86,8 @@ class DVM:
|
||||
return
|
||||
|
||||
def handle_nip90_job_event(nip90_event):
|
||||
user = get_or_add_user(self.dvm_config.DB, nip90_event.pubkey().to_hex(), client=self.client)
|
||||
user = get_or_add_user(self.dvm_config.DB, nip90_event.pubkey().to_hex(), client=self.client,
|
||||
config=self.dvm_config)
|
||||
task_supported, task, duration = check_task_is_supported(nip90_event, client=self.client,
|
||||
get_duration=(not user.iswhitelisted),
|
||||
config=self.dvm_config)
|
||||
@ -107,7 +108,8 @@ class DVM:
|
||||
task_is_free = True
|
||||
|
||||
if user.iswhitelisted or task_is_free:
|
||||
print("[" + self.dvm_config.NIP89.name + "] Free task or Whitelisted for task " + task + ". Starting processing..")
|
||||
print(
|
||||
"[" + self.dvm_config.NIP89.name + "] Free task or Whitelisted for task " + task + ". Starting processing..")
|
||||
send_job_status_reaction(nip90_event, "processing", True, 0, client=self.client,
|
||||
dvm_config=self.dvm_config)
|
||||
do_work(nip90_event, is_from_bot=False)
|
||||
@ -118,7 +120,8 @@ class DVM:
|
||||
if tag.as_vec()[0] == 'bid':
|
||||
bid = int(tag.as_vec()[1])
|
||||
|
||||
print("[" + self.dvm_config.NIP89.name + "] Payment required: New Nostr " + task + " Job event: " + nip90_event.as_json())
|
||||
print(
|
||||
"[" + self.dvm_config.NIP89.name + "] Payment required: New Nostr " + task + " Job event: " + nip90_event.as_json())
|
||||
if bid > 0:
|
||||
bid_offer = int(bid / 1000)
|
||||
if bid_offer >= amount:
|
||||
@ -127,47 +130,21 @@ class DVM:
|
||||
client=self.client, dvm_config=self.dvm_config)
|
||||
|
||||
else: # If there is no bid, just request server rate from user
|
||||
print("[" + self.dvm_config.NIP89.name + "] Requesting payment for Event: " + nip90_event.id().to_hex())
|
||||
print(
|
||||
"[" + self.dvm_config.NIP89.name + "] Requesting payment for Event: " + nip90_event.id().to_hex())
|
||||
send_job_status_reaction(nip90_event, "payment-required",
|
||||
False, amount, client=self.client, dvm_config=self.dvm_config)
|
||||
else:
|
||||
print("Task not supported on this DVM, skipping..")
|
||||
|
||||
def handle_zap(zap_event):
|
||||
zapped_event = None
|
||||
invoice_amount = 0
|
||||
anon = False
|
||||
sender = zap_event.pubkey()
|
||||
print("Zap received")
|
||||
|
||||
try:
|
||||
for tag in zap_event.tags():
|
||||
if tag.as_vec()[0] == 'bolt11':
|
||||
invoice_amount = parse_amount_from_bolt11_invoice(tag.as_vec()[1])
|
||||
elif tag.as_vec()[0] == 'e':
|
||||
zapped_event = get_event_by_id(tag.as_vec()[1], client=self.client, config=self.dvm_config)
|
||||
elif tag.as_vec()[0] == 'description':
|
||||
zap_request_event = Event.from_json(tag.as_vec()[1])
|
||||
sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(),
|
||||
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(self.dvm_config.DB, sender, client=self.client)
|
||||
|
||||
invoice_amount, zapped_event, sender, anon = parse_zap_event_tags(zap_event,
|
||||
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:
|
||||
if zapped_event.kind() == EventDefinitions.KIND_FEEDBACK: # if a reaction by us got zapped
|
||||
@ -219,13 +196,15 @@ class DVM:
|
||||
elif not anon:
|
||||
print("Note Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
|
||||
user.name))
|
||||
update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client, config=self.dvm_config)
|
||||
update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client,
|
||||
config=self.dvm_config)
|
||||
|
||||
# a regular note
|
||||
elif not anon:
|
||||
print("Profile Zap received for Bot balance: " + str(invoice_amount) + " Sats from " + str(
|
||||
user.name))
|
||||
update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client, config=self.dvm_config)
|
||||
update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client,
|
||||
config=self.dvm_config)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during content decryption: {e}")
|
||||
@ -264,7 +243,8 @@ class DVM:
|
||||
input = tag.as_vec()[1]
|
||||
input_type = tag.as_vec()[2]
|
||||
if input_type == "job":
|
||||
evt = get_referenced_event_by_id(event_id=input, client=client, kinds=EventDefinitions.ANY_RESULT,
|
||||
evt = get_referenced_event_by_id(event_id=input, client=client,
|
||||
kinds=EventDefinitions.ANY_RESULT,
|
||||
dvm_config=dvmconfig)
|
||||
if evt is None:
|
||||
if append:
|
||||
@ -287,7 +267,7 @@ class DVM:
|
||||
x.result = data
|
||||
x.is_processed = True
|
||||
if self.dvm_config.SHOW_RESULT_BEFORE_PAYMENT and not is_paid:
|
||||
send_nostr_reply_event(data, original_event_str,)
|
||||
send_nostr_reply_event(data, original_event_str, )
|
||||
send_job_status_reaction(original_event, "success", amount,
|
||||
dvm_config=self.dvm_config) # or payment-required, or both?
|
||||
elif not self.dvm_config.SHOW_RESULT_BEFORE_PAYMENT and not is_paid:
|
||||
@ -318,10 +298,11 @@ class DVM:
|
||||
}
|
||||
message = json.dumps(params)
|
||||
print(message)
|
||||
response_event = EventBuilder.new_encrypted_direct_msg(self.keys, receiver_key, message, None).to_event(self.keys)
|
||||
response_event = EventBuilder.new_encrypted_direct_msg(self.keys, receiver_key, message,
|
||||
None).to_event(self.keys)
|
||||
send_event(response_event, client=self.client, dvm_config=self.dvm_config)
|
||||
else:
|
||||
#Regular DVM reply
|
||||
# Regular DVM reply
|
||||
send_nostr_reply_event(post_processed_content, original_event_str)
|
||||
except Exception as e:
|
||||
respond_to_error(str(e), original_event_str, False)
|
||||
@ -347,7 +328,8 @@ class DVM:
|
||||
response_kind = original_event.kind() + 1000
|
||||
reply_event = EventBuilder(response_kind, str(content), reply_tags).to_event(key)
|
||||
send_event(reply_event, client=self.client, dvm_config=self.dvm_config)
|
||||
print("[" + self.dvm_config.NIP89.name + "] " + str(response_kind) + " Job Response event sent: " + reply_event.as_json())
|
||||
print("[" + self.dvm_config.NIP89.name + "] " + str(
|
||||
response_kind) + " Job Response event sent: " + reply_event.as_json())
|
||||
return reply_event.as_json()
|
||||
|
||||
def respond_to_error(content: str, original_event_as_str: str, is_from_bot=False):
|
||||
@ -366,7 +348,7 @@ class DVM:
|
||||
elif tag.as_vec()[0] == "i":
|
||||
task = tag.as_vec()[1]
|
||||
|
||||
user = get_or_add_user(self.dvm_config.DB, sender, self.client)
|
||||
user = get_or_add_user(db=self.dvm_config.DB, npub=sender, client=self.client, config=self.dvm_config)
|
||||
if not user.iswhitelisted:
|
||||
amount = int(user.balance) + get_amount_per_task(task, self.dvm_config)
|
||||
update_sql_table(self.dvm_config.DB, sender, amount, user.iswhitelisted, user.isblacklisted,
|
||||
@ -419,7 +401,7 @@ class DVM:
|
||||
status=status, result="", is_processed=False, bolt11=bolt11,
|
||||
payment_hash=payment_hash,
|
||||
expires=expires, from_bot=False))
|
||||
#print(str(self.job_list))
|
||||
# print(str(self.job_list))
|
||||
if (status == "payment-required" or status == "payment-rejected" or (
|
||||
status == "processing" and not is_paid)
|
||||
or (status == "success" and not is_paid)):
|
||||
@ -435,7 +417,7 @@ class DVM:
|
||||
|
||||
send_event(event, client=self.client, dvm_config=self.dvm_config)
|
||||
print("[" + self.dvm_config.NIP89.name + "]" + ": Sent Kind " + str(
|
||||
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json())
|
||||
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + event.as_json())
|
||||
return event.as_json()
|
||||
|
||||
def do_work(job_event, is_from_bot=False):
|
||||
@ -495,4 +477,3 @@ class DVM:
|
||||
self.jobs_on_hold_list.remove(job)
|
||||
|
||||
time.sleep(1.0)
|
||||
|
||||
|
@ -26,6 +26,19 @@ class DVMTaskInterface:
|
||||
nip89.content = nip89config.CONTENT
|
||||
return nip89
|
||||
|
||||
def init(self, name, dvm_config, admin_config, nip89config):
|
||||
self.NAME = name
|
||||
self.PK = dvm_config.PRIVATE_KEY
|
||||
if dvm_config.COST is not None:
|
||||
self.COST = dvm_config.COST
|
||||
|
||||
dvm_config.SUPPORTED_DVMS = [self]
|
||||
dvm_config.DB = "db/" + self.NAME + ".db"
|
||||
dvm_config.NIP89 = self.NIP89_announcement(nip89config)
|
||||
self.dvm_config = dvm_config
|
||||
self.admin_config = admin_config
|
||||
|
||||
|
||||
def run(self):
|
||||
nostr_dvm_thread = Thread(target=self.DVM, args=[self.dvm_config, self.admin_config])
|
||||
nostr_dvm_thread.start()
|
||||
|
64
main.py
64
main.py
@ -17,41 +17,49 @@ 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()
|
||||
|
||||
# Spawn some DVMs in the playground and run them
|
||||
# You can add arbitrary DVMs there and instantiate them here
|
||||
|
||||
# Spawn DVM1 Kind 5000 Text Extractor from PDFs
|
||||
pdfextractor = build_pdf_extractor("PDF Extractor", [bot_publickey])
|
||||
pdfextractor.run()
|
||||
|
||||
# Spawn DVM2 Kind 5002 Text Translation
|
||||
translator = build_translator("Translator", [bot_publickey])
|
||||
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
|
||||
unstable_artist = build_unstable_diffusion("Unstable Diffusion", [bot_publickey])
|
||||
unstable_artist.run()
|
||||
|
||||
# Spawn DVM4, another Instance of text-to-image, as before but use a different privatekey, model and lora this time.
|
||||
sketcher = build_sketcher("Sketcher", [bot_publickey])
|
||||
sketcher.run()
|
||||
|
||||
dalle = build_dalle("Dall-E 3", [bot_publickey])
|
||||
dalle.run()
|
||||
|
||||
# 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()
|
||||
bot_config.PRIVATE_KEY = os.getenv("BOT_PRIVATE_KEY")
|
||||
bot_config.LNBITS_INVOICE_KEY = os.getenv("LNBITS_INVOICE_KEY")
|
||||
bot_config.LNBITS_URL = os.getenv("LNBITS_HOST")
|
||||
# Finally we add some of the DVMs we created before to the Bot and start it.
|
||||
bot_config.SUPPORTED_DVMS = [sketcher, unstable_artist, dalle, translator]
|
||||
|
||||
bot = Bot
|
||||
nostr_dvm_thread = Thread(target=bot, args=[bot_config])
|
||||
nostr_dvm_thread.start()
|
||||
# Spawn some DVMs in the playground and run them
|
||||
# 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])
|
||||
# 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])
|
||||
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])
|
||||
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])
|
||||
bot_config.SUPPORTED_DVMS.append(sketcher) # We also add Sketcher to the bot
|
||||
sketcher.run()
|
||||
|
||||
# Spawn DVM5, this one requires an OPENAI API Key and balance with OpenAI, you will move the task to them and pay
|
||||
# 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])
|
||||
bot_config.SUPPORTED_DVMS.append(dalle)
|
||||
dalle.run()
|
||||
|
||||
bot = Bot(bot_config)
|
||||
bot.run()
|
||||
|
||||
# Keep the main function alive for libraries like openai
|
||||
try:
|
||||
|
@ -8,6 +8,7 @@ from tasks.translation import Translation
|
||||
from utils.admin_utils import AdminConfig
|
||||
from utils.dvmconfig import DVMConfig
|
||||
from utils.nip89_utils import NIP89Config
|
||||
|
||||
"""
|
||||
This File is a playground to create DVMs. It shows some examples of DVMs that make use of the modules in the tasks folder
|
||||
These DVMs should be considered examples and will be extended in the future. env variables are used to not commit keys,
|
||||
@ -32,6 +33,8 @@ task, for example an address or an API key.
|
||||
# their NIP89 announcement
|
||||
admin_config = AdminConfig()
|
||||
admin_config.REBROADCAST_NIP89 = False
|
||||
# Set rebroadcast to true once you have set your NIP89 descriptions and d tags. You only need to rebroadcast once you
|
||||
# want to update your NIP89 descriptions
|
||||
|
||||
|
||||
def build_pdf_extractor(name, dm_allowed_keys):
|
||||
@ -67,11 +70,11 @@ def build_translator(name, dm_allowed_keys):
|
||||
"language": {
|
||||
"required": False,
|
||||
"values": ["en", "az", "be", "bg", "bn", "bs", "ca", "ceb", "co", "cs", "cy", "da", "de", "el", "eo", "es",
|
||||
"et", "eu", "fa", "fi", "fr", "fy", "ga", "gd", "gl","gu", "ha", "haw", "hi", "hmn", "hr", "ht",
|
||||
"hu", "hy", "id", "ig", "is", "it", "he", "ja", "jv", "ka", "kk","km", "kn", "ko", "ku", "ky",
|
||||
"et", "eu", "fa", "fi", "fr", "fy", "ga", "gd", "gl", "gu", "ha", "haw", "hi", "hmn", "hr", "ht",
|
||||
"hu", "hy", "id", "ig", "is", "it", "he", "ja", "jv", "ka", "kk", "km", "kn", "ko", "ku", "ky",
|
||||
"la", "lb", "lo", "lt", "lv", "mg", "mi", "mk", "ml", "mn", "mr", "ms", "mt", "my", "ne", "nl",
|
||||
"no", "ny", "or", "pa", "pl", "ps", "pt", "ro", "ru", "sd", "si", "sk", "sl", "sm", "sn", "so",
|
||||
"sq", "sr", "st", "su", "sv", "sw", "ta", "te","tg", "th", "tl", "tr", "ug", "uk", "ur", "uz",
|
||||
"sq", "sr", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "tl", "tr", "ug", "uk", "ur", "uz",
|
||||
"vi", "xh", "yi", "yo", "zh", "zu"]
|
||||
}
|
||||
}
|
||||
@ -158,12 +161,15 @@ def build_sketcher(name, dm_allowed_keys):
|
||||
return ImageGenerationSDXL(name=name, dvm_config=dvm_config, nip89config=nip89config,
|
||||
admin_config=admin_config, options=options)
|
||||
|
||||
|
||||
def build_dalle(name, dm_allowed_keys):
|
||||
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))
|
||||
|
||||
nip90params = {
|
||||
"size": {
|
||||
@ -186,4 +192,24 @@ def build_dalle(name, dm_allowed_keys):
|
||||
nip89config.CONTENT = json.dumps(nip89info)
|
||||
# We add an optional AdminConfig for this one, and tell the dvm to rebroadcast its NIP89
|
||||
return ImageGenerationDALLE(name=name, dvm_config=dvm_config, nip89config=nip89config,
|
||||
admin_config=admin_config)
|
||||
admin_config=admin_config)
|
||||
|
||||
|
||||
# Little Gimmick:
|
||||
# For Dalle where we have to pay 4cent per image, we fetch current sat price in fiat
|
||||
# and update cost at each start
|
||||
def get_price_per_sat(currency):
|
||||
import requests
|
||||
|
||||
url = "https://api.coinstats.app/public/v1/coins"
|
||||
params = {"skip": 0, "limit": 1, "currency": currency}
|
||||
try:
|
||||
response = requests.get(url, params=params)
|
||||
response_json = response.json()
|
||||
|
||||
bitcoin_price = response_json["coins"][0]["price"]
|
||||
price_currency_per_sat = bitcoin_price / 100000000.0
|
||||
except:
|
||||
price_currency_per_sat = 0.0004
|
||||
|
||||
return price_currency_per_sat
|
||||
|
@ -23,23 +23,14 @@ Params: -model # models: juggernaut, dynavision, colossusProject, newrea
|
||||
|
||||
|
||||
class ImageGenerationDALLE(DVMTaskInterface):
|
||||
NAME: str = ""
|
||||
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
|
||||
TASK: str = "text-to-image"
|
||||
COST: int = 120
|
||||
PK: str
|
||||
DVM = DVM
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None,
|
||||
options=None):
|
||||
self.NAME = name
|
||||
self.PK = dvm_config.PRIVATE_KEY
|
||||
|
||||
dvm_config.SUPPORTED_DVMS = [self]
|
||||
dvm_config.DB = "db/" + self.NAME + ".db"
|
||||
dvm_config.NIP89 = self.NIP89_announcement(nip89config)
|
||||
self.dvm_config = dvm_config
|
||||
self.admin_config = admin_config
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
|
@ -20,22 +20,12 @@ Params: -model # models: juggernaut, dynavision, colossusProject, newrea
|
||||
|
||||
|
||||
class ImageGenerationSDXL(DVMTaskInterface):
|
||||
NAME: str = ""
|
||||
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
|
||||
TASK: str = "text-to-image"
|
||||
COST: int = 50
|
||||
PK: str
|
||||
DVM = DVM
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None, options=None):
|
||||
self.NAME = name
|
||||
self.PK = dvm_config.PRIVATE_KEY
|
||||
|
||||
dvm_config.SUPPORTED_DVMS = [self]
|
||||
dvm_config.DB = "db/" + self.NAME + ".db"
|
||||
dvm_config.NIP89 = self.NIP89_announcement(nip89config)
|
||||
self.dvm_config = dvm_config
|
||||
self.admin_config = admin_config
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
|
@ -21,22 +21,13 @@ Params: None
|
||||
|
||||
|
||||
class TextExtractionPDF(DVMTaskInterface):
|
||||
NAME: str = ""
|
||||
KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
|
||||
TASK: str = "pdf-to-text"
|
||||
COST: int = 0
|
||||
PK: str
|
||||
DVM = DVM
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None):
|
||||
self.NAME = name
|
||||
self.PK = dvm_config.PRIVATE_KEY
|
||||
|
||||
dvm_config.SUPPORTED_DVMS = [self]
|
||||
dvm_config.DB = "db/" + self.NAME + ".db"
|
||||
dvm_config.NIP89 = self.NIP89_announcement(nip89config)
|
||||
self.dvm_config = dvm_config
|
||||
self.admin_config = admin_config
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None, options=None):
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
|
@ -19,22 +19,14 @@ Params: -language The target language
|
||||
|
||||
|
||||
class Translation(DVMTaskInterface):
|
||||
NAME: str = ""
|
||||
KIND: int = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT
|
||||
TASK: str = "translation"
|
||||
COST: int = 0
|
||||
PK: str
|
||||
DVM = DVM
|
||||
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None):
|
||||
self.NAME = name
|
||||
self.PK = dvm_config.PRIVATE_KEY
|
||||
|
||||
dvm_config.SUPPORTED_DVMS = [self]
|
||||
dvm_config.DB = "db/" + self.NAME + ".db"
|
||||
dvm_config.NIP89 = self.NIP89_announcement(nip89config)
|
||||
self.dvm_config = dvm_config
|
||||
self.admin_config = admin_config
|
||||
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None,
|
||||
options=None):
|
||||
self.init(name, dvm_config, admin_config, nip89config)
|
||||
self.options = options
|
||||
|
||||
def is_input_supported(self, input_type, input_content):
|
||||
if input_type != "event" and input_type != "job" and input_type != "text":
|
||||
|
@ -50,7 +50,7 @@ def admin_make_database_updates(adminconfig: AdminConfig = None, dvmconfig: DVMC
|
||||
|
||||
|
||||
if whitelistuser:
|
||||
user = get_or_add_user(db, publickey, client=client)
|
||||
user = get_or_add_user(db, publickey, client=client, config=dvmconfig)
|
||||
update_sql_table(db, user.npub, user.balance, True, False, user.nip05, user.lud16, user.name, user.lastactive)
|
||||
user = get_from_sql_table(db, publickey)
|
||||
print(str(user.name) + " is whitelisted: " + str(user.iswhitelisted))
|
||||
|
@ -8,9 +8,7 @@ from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from logging import Filter
|
||||
|
||||
from nostr_sdk import Timestamp, Keys, PublicKey, EventBuilder, Metadata, Filter, Options, Client
|
||||
|
||||
from utils.definitions import NEW_USER_BALANCE
|
||||
from nostr_sdk import Timestamp, Keys, PublicKey, EventBuilder, Filter, Client, Options
|
||||
from utils.nostr_utils import send_event
|
||||
|
||||
|
||||
@ -66,7 +64,7 @@ def add_to_sql_table(db, npub, sats, iswhitelisted, isblacklisted, nip05, lud16,
|
||||
con.commit()
|
||||
con.close()
|
||||
except Error as e:
|
||||
print(e)
|
||||
print("Error when Adding to DB: " + str(e))
|
||||
|
||||
|
||||
def update_sql_table(db, npub, balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive):
|
||||
@ -87,7 +85,7 @@ def update_sql_table(db, npub, balance, iswhitelisted, isblacklisted, nip05, lud
|
||||
con.commit()
|
||||
con.close()
|
||||
except Error as e:
|
||||
print(e)
|
||||
print("Error Updating DB: " + str(e))
|
||||
|
||||
|
||||
def get_from_sql_table(db, npub):
|
||||
@ -113,7 +111,7 @@ def get_from_sql_table(db, npub):
|
||||
return user
|
||||
|
||||
except Error as e:
|
||||
print(e)
|
||||
print("Error Getting from DB: " + str(e))
|
||||
|
||||
|
||||
def delete_from_sql_table(db, npub):
|
||||
@ -155,72 +153,70 @@ def list_db(db):
|
||||
print(e)
|
||||
|
||||
|
||||
def update_user_balance(db, sender, sats, client, config):
|
||||
user = get_from_sql_table(db, sender)
|
||||
def update_user_balance(db, npub, additional_sats, client, config):
|
||||
user = get_from_sql_table(db, npub)
|
||||
if user is None:
|
||||
add_to_sql_table(db, sender, (int(sats) + NEW_USER_BALANCE), False, False,
|
||||
"", "", "", Timestamp.now().as_secs())
|
||||
print("NEW USER: " + sender + " Zap amount: " + str(sats) + " Sats.")
|
||||
name, nip05, lud16 = fetch_user_metadata(npub, client)
|
||||
add_to_sql_table(db, npub, (int(additional_sats) + config.NEW_USER_BALANCE), False, False,
|
||||
nip05, lud16, name, Timestamp.now().as_secs())
|
||||
print("Adding User: " + npub + " (" + npub + ")")
|
||||
else:
|
||||
user = get_from_sql_table(db, sender)
|
||||
print(str(sats))
|
||||
|
||||
if user.nip05 is None:
|
||||
user.nip05 = ""
|
||||
if user.lud16 is None:
|
||||
user.lud16 = ""
|
||||
if user.name is None:
|
||||
user.name = ""
|
||||
|
||||
new_balance = int(user.balance) + int(sats)
|
||||
update_sql_table(db, sender, new_balance, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
|
||||
user = get_from_sql_table(db, npub)
|
||||
new_balance = int(user.balance) + int(additional_sats)
|
||||
update_sql_table(db, npub, new_balance, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
|
||||
user.name,
|
||||
Timestamp.now().as_secs())
|
||||
print("UPDATE USER BALANCE: " + str(user.name) + " Zap amount: " + str(sats) + " Sats.")
|
||||
print("Updated user balance for: " + str(user.name) +
|
||||
" Zap amount: " + str(additional_sats) + " Sats. New balance: " + str(new_balance) +" Sats")
|
||||
|
||||
if config is not None:
|
||||
keys = Keys.from_sk_str(config.PRIVATE_KEY)
|
||||
time.sleep(1.0)
|
||||
|
||||
message = ("Added " + str(sats) + " Sats to balance. New balance is " + str(new_balance) + " Sats. ")
|
||||
message = ("Added " + str(additional_sats) + " Sats to balance. New balance is " + str(new_balance) + " Sats.")
|
||||
|
||||
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(sender), message,
|
||||
evt = EventBuilder.new_encrypted_direct_msg(keys, PublicKey.from_hex(npub), message,
|
||||
None).to_event(keys)
|
||||
send_event(evt, client=client, dvm_config=config)
|
||||
|
||||
|
||||
def get_or_add_user(db, npub, client):
|
||||
def get_or_add_user(db, npub, client, config):
|
||||
user = get_from_sql_table(db, npub)
|
||||
if user is None:
|
||||
name, nip05, lud16 = fetch_user_metadata(npub, client)
|
||||
print("Adding User: " + npub + " (" + npub + ")")
|
||||
add_to_sql_table(db, npub, NEW_USER_BALANCE, False, False, nip05,
|
||||
lud16, name, Timestamp.now().as_secs())
|
||||
user = get_from_sql_table(db, npub)
|
||||
return user
|
||||
try:
|
||||
name, nip05, lud16 = fetch_user_metadata(npub, client)
|
||||
print("Adding User: " + npub + " (" + npub + ")")
|
||||
add_to_sql_table(db, npub, config.NEW_USER_BALANCE, False, False, nip05,
|
||||
lud16, name, Timestamp.now().as_secs())
|
||||
user = get_from_sql_table(db, npub)
|
||||
return user
|
||||
except Exception as e:
|
||||
print("Error Adding User to DB: " + str(e))
|
||||
|
||||
return user
|
||||
|
||||
|
||||
class DvmConfig:
|
||||
pass
|
||||
|
||||
|
||||
def fetch_user_metadata(npub, client):
|
||||
name = ""
|
||||
nip05 = ""
|
||||
lud16 = ""
|
||||
|
||||
# Get metadata
|
||||
pk = PublicKey.from_hex(npub)
|
||||
print(f"\nGetting profile metadata for {pk.to_bech32()}...")
|
||||
filter = Filter().kind(0).author(pk).limit(1)
|
||||
events = client.get_events_of([filter], timedelta(seconds=3))
|
||||
profile_filter = Filter().kind(0).author(pk).limit(1)
|
||||
events = client.get_events_of([profile_filter], timedelta(seconds=5))
|
||||
#TODO, it seems our client is still subscribed after that
|
||||
|
||||
if len(events) > 0:
|
||||
latest_entry = events[0]
|
||||
newest = 0
|
||||
latest_time = 0
|
||||
for entry in events:
|
||||
if entry.created_at().as_secs() > newest:
|
||||
newest = entry.created_at().as_secs()
|
||||
if entry.created_at().as_secs() > latest_time:
|
||||
latest_time = entry.created_at().as_secs()
|
||||
latest_entry = entry
|
||||
|
||||
print(latest_entry.content())
|
||||
profile = json.loads(latest_entry.content())
|
||||
if profile.get("name"):
|
||||
name = profile['name']
|
||||
@ -228,32 +224,4 @@ def fetch_user_metadata(npub, client):
|
||||
nip05 = profile['nip05']
|
||||
if profile.get("lud16"):
|
||||
lud16 = profile['lud16']
|
||||
|
||||
return name, nip05, lud16
|
||||
|
||||
|
||||
def fetch_user_metadata2(sender, client) -> (str, str, str):
|
||||
name = ""
|
||||
nip05 = ""
|
||||
lud16 = ""
|
||||
try:
|
||||
pk = PublicKey.from_hex(sender)
|
||||
print(f"\nGetting profile metadata for {pk.to_bech32()}...")
|
||||
profile_filter = Filter().kind(0).author(pk).limit(1)
|
||||
events = client.get_events_of([profile_filter], timedelta(seconds=1))
|
||||
if len(events) > 0:
|
||||
ev = events[0]
|
||||
metadata = Metadata.from_json(ev.content())
|
||||
name = metadata.get_display_name()
|
||||
if str(name) == "" or name is None:
|
||||
name = metadata.get_name()
|
||||
nip05 = metadata.get_nip05()
|
||||
lud16 = metadata.get_lud16()
|
||||
else:
|
||||
print("Profile not found")
|
||||
return name, nip05, lud16
|
||||
|
||||
except:
|
||||
print("Couldn't get meta information")
|
||||
|
||||
return name, nip05, lud16
|
||||
|
@ -2,10 +2,6 @@ import os
|
||||
from dataclasses import dataclass
|
||||
|
||||
from nostr_sdk import Event
|
||||
|
||||
NEW_USER_BALANCE: int = 250 # Free credits for new users
|
||||
|
||||
|
||||
class EventDefinitions:
|
||||
KIND_DM: int = 4
|
||||
KIND_ZAP: int = 9735
|
||||
|
@ -6,6 +6,7 @@ from utils.nip89_utils import NIP89Announcement
|
||||
class DVMConfig:
|
||||
SUPPORTED_DVMS= []
|
||||
PRIVATE_KEY: str = os.getenv("NOSTR_PRIVATE_KEY")
|
||||
COST: int = None
|
||||
|
||||
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",
|
||||
@ -15,11 +16,11 @@ class DVMConfig:
|
||||
LNBITS_INVOICE_KEY = ''
|
||||
LNBITS_URL = 'https://lnbits.com'
|
||||
DB: str
|
||||
NEW_USER_BALANCE: int = 250 # Free credits for new users
|
||||
NIP89: NIP89Announcement
|
||||
DM_ALLOWED = []
|
||||
|
||||
REQUIRES_NIP05: bool = False
|
||||
SHOW_RESULT_BEFORE_PAYMENT: bool = True # if this is true show results even when not paid right after autoprocess
|
||||
SHOW_RESULT_BEFORE_PAYMENT: bool = False # if this is true show results even when not paid right after autoprocess
|
||||
|
||||
|
||||
|
||||
|
@ -4,8 +4,9 @@ import json
|
||||
import requests
|
||||
from Crypto.Cipher import AES
|
||||
from bech32 import bech32_decode, convertbits
|
||||
from nostr_sdk import nostr_sdk, PublicKey, SecretKey
|
||||
from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event
|
||||
from utils.dvmconfig import DVMConfig
|
||||
from utils.nostr_utils import get_event_by_id
|
||||
|
||||
|
||||
def parse_amount_from_bolt11_invoice(bolt11_invoice: str) -> int:
|
||||
@ -35,6 +36,42 @@ def parse_amount_from_bolt11_invoice(bolt11_invoice: str) -> int:
|
||||
return int(number)
|
||||
|
||||
|
||||
def parse_zap_event_tags(zap_event, keys, name, client, config):
|
||||
zapped_event = None
|
||||
invoice_amount = 0
|
||||
anon = False
|
||||
sender = zap_event.pubkey()
|
||||
|
||||
for tag in zap_event.tags():
|
||||
if tag.as_vec()[0] == 'bolt11':
|
||||
invoice_amount = parse_amount_from_bolt11_invoice(tag.as_vec()[1])
|
||||
elif tag.as_vec()[0] == 'e':
|
||||
zapped_event = get_event_by_id(tag.as_vec()[1], 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")
|
||||
|
||||
return invoice_amount, zapped_event, sender, anon
|
||||
|
||||
|
||||
def create_bolt11_ln_bits(sats: int, config: DVMConfig) -> (str, str):
|
||||
url = config.LNBITS_URL + "/api/v1/payments"
|
||||
data = {'out': False, 'amount': sats, 'memo': "Nostr-DVM " + config.NIP89.name}
|
||||
|
Loading…
x
Reference in New Issue
Block a user