subscriptions / sdk update work in progress

This commit is contained in:
Believethehype
2024-03-18 19:44:44 +01:00
parent 53658548e3
commit 41785313a4
45 changed files with 1312 additions and 307 deletions

View File

@@ -5,7 +5,8 @@ import time
from datetime import timedelta
from nostr_sdk import (Keys, Client, Timestamp, Filter, nip04_decrypt, HandleNotification, EventBuilder, PublicKey,
Options, Tag, Event, nip04_encrypt, NostrSigner, EventId, Nip19Event)
Options, Tag, Event, nip04_encrypt, NostrSigner, EventId, Nip19Event, Kind, KindEnum,
UnsignedEvent, nip59_extract_rumor)
from nostr_dvm.utils.admin_utils import admin_make_database_updates
from nostr_dvm.utils.database_utils import get_or_add_user, update_user_balance, create_sql_table, update_sql_table
@@ -55,10 +56,10 @@ class Bot:
kinds = [EventDefinitions.KIND_NIP90_GENERIC, EventDefinitions.KIND_FEEDBACK]
for dvm in self.dvm_config.SUPPORTED_DVMS:
if dvm.KIND not in kinds:
kinds.append(dvm.KIND + 1000)
kinds.append(Kind(dvm.KIND.as_u64() + 1000))
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()))
self.client.subscribe([zap_filter, dm_filter, dvm_filter])
self.client.subscribe([zap_filter, dm_filter, dvm_filter], None)
create_sql_table(self.dvm_config.DB)
admin_make_database_updates(adminconfig=self.admin_config, dvmconfig=self.dvm_config, client=self.client)
@@ -68,17 +69,34 @@ class Bot:
dvm_config = self.dvm_config
keys = self.keys
def handle(self, relay_url, nostr_event):
if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT + 1000 <= nostr_event.kind()
<= EventDefinitions.KIND_NIP90_GENERIC + 1000):
def handle(self, relay_url, subscription_id, nostr_event):
if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() + 1000 <= nostr_event.kind().as_u64()
<= EventDefinitions.KIND_NIP90_GENERIC.as_u64() + 1000):
handle_nip90_response_event(nostr_event)
elif nostr_event.kind() == EventDefinitions.KIND_FEEDBACK:
elif nostr_event.kind().as_u64() == EventDefinitions.KIND_FEEDBACK.as_u64():
handle_nip90_feedback(nostr_event)
elif nostr_event.kind() == EventDefinitions.KIND_DM:
handle_dm(nostr_event)
elif nostr_event.kind() == EventDefinitions.KIND_ZAP:
elif nostr_event.kind().as_u64() == EventDefinitions.KIND_ZAP.as_u64():
handle_zap(nostr_event)
elif nostr_event.kind().match_enum(KindEnum.ENCRYPTED_DIRECT_MESSAGE()):
try:
handle_dm(nostr_event)
except Exception as e:
print(f"Error during content NIP04 decryption: {e}")
elif nostr_event.kind().match_enum(KindEnum.GIFT_WRAP()):
print("Decrypting NIP59 event")
try:
rumor: UnsignedEvent = nip59_extract_rumor(self.keys, nostr_event)
if rumor.kind().match_enum(KindEnum.SEALED_DIRECT()):
msg = rumor.content()
print(f"Received new msg [sealed]: {msg}")
self.client.send_sealed_msg(rumor.author(), "Nip44 is not supported yet, but coming soon", None)
else:
print(f"{rumor.as_json()}")
except Exception as e:
print(f"Error during content NIP59 decryption: {e}")
def handle_msg(self, relay_url, msg):
return
@@ -265,7 +283,7 @@ class Bot:
update_sql_table(db=self.dvm_config.DB, npub=user.npub, balance=balance,
iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted,
nip05=user.nip05, lud16=user.lud16, name=user.name,
lastactive=Timestamp.now().as_secs())
lastactive=Timestamp.now().as_secs(), subscribed=user.subscribed)
evt = EventBuilder.encrypted_direct_msg(self.keys,
PublicKey.from_hex(entry["npub"]),
"Paid " + str(

View File

@@ -5,7 +5,7 @@ from datetime import timedelta
from sys import platform
from nostr_sdk import PublicKey, Keys, Client, Tag, Event, EventBuilder, Filter, HandleNotification, Timestamp, \
init_logger, LogLevel, Options, nip04_encrypt, NostrSigner
init_logger, LogLevel, Options, nip04_encrypt, NostrSigner, Kind, SubscribeAutoCloseOptions
import time
@@ -13,8 +13,10 @@ from nostr_dvm.utils.definitions import EventDefinitions, RequiredJobToWatch, Jo
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.admin_utils import admin_make_database_updates, AdminConfig
from nostr_dvm.utils.backend_utils import get_amount_per_task, check_task_is_supported, get_task
from nostr_dvm.utils.database_utils import create_sql_table, get_or_add_user, update_user_balance, update_sql_table
from nostr_dvm.utils.database_utils import create_sql_table, get_or_add_user, update_user_balance, update_sql_table, \
update_user_subscription
from nostr_dvm.utils.mediasource_utils import input_data_file_duration
from nostr_dvm.utils.nip88_utils import nip88_has_active_subscription
from nostr_dvm.utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, send_event, check_and_decrypt_tags
from nostr_dvm.utils.output_utils import build_status_reaction
from nostr_dvm.utils.zap_utils import check_bolt11_ln_bits_is_paid, create_bolt11_ln_bits, parse_zap_event_tags, \
@@ -46,8 +48,7 @@ class DVM:
self.jobs_on_hold_list = []
pk = self.keys.public_key()
print("Nostr DVM public key: " + str(pk.to_bech32()) + " Hex: " + str(pk.to_hex()) + " Supported DVM tasks: " +
', '.join(p.NAME + ":" + p.TASK for p in self.dvm_config.SUPPORTED_DVMS) + "\n")
print("Nostr DVM public key: " + str(pk.to_bech32()) + " Hex: " + str(pk.to_hex()) + "\n")
for relay in self.dvm_config.RELAY_LIST:
self.client.add_relay(relay)
@@ -59,7 +60,8 @@ class DVM:
if dvm.KIND not in kinds:
kinds.append(dvm.KIND)
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()))
self.client.subscribe([dvm_filter, zap_filter])
self.client.subscribe([dvm_filter, zap_filter], None)
create_sql_table(self.dvm_config.DB)
admin_make_database_updates(adminconfig=self.admin_config, dvmconfig=self.dvm_config, client=self.client)
@@ -69,10 +71,11 @@ class DVM:
dvm_config = self.dvm_config
keys = self.keys
def handle(self, relay_url, nostr_event):
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= nostr_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC:
def handle(self, relay_url, subscription_id, nostr_event: Event):
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() <= nostr_event.kind().as_u64() <= EventDefinitions.KIND_NIP90_GENERIC.as_u64():
handle_nip90_job_event(nostr_event)
elif nostr_event.kind() == EventDefinitions.KIND_ZAP:
elif nostr_event.kind().as_u64() == EventDefinitions.KIND_ZAP.as_u64():
handle_zap(nostr_event)
def handle_msg(self, relay_url, msg):
@@ -81,6 +84,7 @@ class DVM:
def handle_nip90_job_event(nip90_event):
nip90_event = check_and_decrypt_tags(nip90_event, self.dvm_config)
if nip90_event is None:
return
@@ -96,7 +100,7 @@ class DVM:
task_supported, task = check_task_is_supported(nip90_event, client=self.client,
config=self.dvm_config)
print(task_supported)
if user.isblacklisted:
send_job_status_reaction(nip90_event, "error", client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "] Request by blacklisted user, skipped")
@@ -109,8 +113,33 @@ class DVM:
return
task_is_free = False
user_has_active_subscription = False
if dvm_config.NIP88 is not None and p_tag_str == self.dvm_config.PUBLIC_KEY:
# if we stored in the database that the user has an active subscription, we don't need to check it
if user.subscribed > Timestamp.now().as_secs():
print("User subscribed until: " + str(Timestamp.from_secs(user.subscribed).to_human_datetime()))
user_has_active_subscription = True
else:
# otherwise we check for an active subscription
print("[" + self.dvm_config.NIP89.NAME + "] Checking Subscription status")
subscription_status = nip88_has_active_subscription(PublicKey.parse(user.npub),
self.dvm_config.NIP88.DTAG, self.client,
self.dvm_config)
if subscription_status["isActive"]:
print("User subscribed until: " + str(
Timestamp.from_secs(int(subscription_status["validUntil"])).to_human_datetime()))
user_has_active_subscription = True
update_user_subscription(user.npub,
int(subscription_status["validUntil"]),
self.client, self.dvm_config)
else:
print("No active subscription found")
for dvm in self.dvm_config.SUPPORTED_DVMS:
if dvm.TASK == task and dvm.FIX_COST == 0 and dvm.PER_UNIT_COST == 0:
if dvm.TASK == task and dvm.FIX_COST == 0 and dvm.PER_UNIT_COST == 0 and dvm_config.NIP88 is None:
task_is_free = True
cashu_redeemed = False
@@ -124,7 +153,8 @@ class DVM:
self.dvm_config)
return
# if user is whitelisted or task is free, just do the job
if (user.iswhitelisted or task_is_free or cashu_redeemed) and (p_tag_str == "" or p_tag_str ==
if (user.iswhitelisted or task_is_free or cashu_redeemed) and (
p_tag_str == "" or p_tag_str ==
self.dvm_config.PUBLIC_KEY):
print(
"[" + self.dvm_config.NIP89.NAME + "] Free task or Whitelisted for task " + task +
@@ -132,23 +162,30 @@ class DVM:
if dvm_config.SEND_FEEDBACK_EVENTS:
send_job_status_reaction(nip90_event, "processing", True, 0,
client=self.client, dvm_config=self.dvm_config)
client=self.client, dvm_config=self.dvm_config, user=user)
# when we reimburse users on error make sure to not send anything if it was free
if user.iswhitelisted or task_is_free:
amount = 0
do_work(nip90_event, amount)
# if task is directed to us via p tag and user has balance, do the job and update balance
elif p_tag_str == self.dvm_config.PUBLIC_KEY and user.balance >= int(amount):
# if task is directed to us via p tag and user has balance or is subscribed, do the job and update balance
elif (p_tag_str == self.dvm_config.PUBLIC_KEY and (
user.balance >= int(
amount) and dvm_config.NIP88 is None) or (p_tag_str == self.dvm_config.PUBLIC_KEY and user_has_active_subscription)):
if not user_has_active_subscription:
balance = max(user.balance - int(amount), 0)
update_sql_table(db=self.dvm_config.DB, npub=user.npub, balance=balance,
iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted,
nip05=user.nip05, lud16=user.lud16, name=user.name,
lastactive=Timestamp.now().as_secs())
lastactive=Timestamp.now().as_secs(), subscribed=user.subscribed)
print(
"[" + self.dvm_config.NIP89.NAME + "] Using user's balance for task: " + task +
". Starting processing.. New balance is: " + str(balance))
else:
print("[" + self.dvm_config.NIP89.NAME + "] User has active subscription for task: " + task +
". Starting processing.. Balance remains at: " + str(user.balance))
send_job_status_reaction(nip90_event, "processing", True, 0,
client=self.client, dvm_config=self.dvm_config)
@@ -157,6 +194,15 @@ class DVM:
# else send a payment required event to user
elif p_tag_str == "" or p_tag_str == self.dvm_config.PUBLIC_KEY:
if dvm_config.NIP88 is not None:
print(
"[" + self.dvm_config.NIP89.NAME + "] Hinting user for Subscription: " +
nip90_event.id().to_hex())
send_job_status_reaction(nip90_event, "subscription-required",
False, 0, client=self.client,
dvm_config=self.dvm_config)
else:
bid = 0
for tag in nip90_event.tags():
if tag.as_vec()[0] == 'bid':
@@ -178,6 +224,10 @@ class DVM:
nip90_event.id().to_hex())
send_job_status_reaction(nip90_event, "payment-required",
False, int(amount), client=self.client, dvm_config=self.dvm_config)
else:
print("[" + self.dvm_config.NIP89.NAME + "] Job addressed to someone else, skipping..")
# else:
@@ -192,11 +242,12 @@ class DVM:
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 zapped_event.kind().as_u64() == EventDefinitions.KIND_FEEDBACK.as_u64():
amount = 0
job_event = None
p_tag_str = ""
status = ""
for tag in zapped_event.tags():
if tag.as_vec()[0] == 'amount':
amount = int(float(tag.as_vec()[1]) / 1000)
@@ -208,9 +259,19 @@ class DVM:
return
else:
return
elif tag.as_vec()[0] == 'status':
status = tag.as_vec()[1]
print(status)
# if a reaction by us got zapped
print(status)
if job_event.kind().as_u64() == EventDefinitions.KIND_NIP88_SUBSCRIBE_EVENT.as_u64():
send_job_status_reaction(job_event, "subscription-success", client=self.client,
dvm_config=self.dvm_config, user=user)
else:
task_supported, task = check_task_is_supported(job_event, client=self.client,
config=self.dvm_config)
if job_event is not None and task_supported:
@@ -219,14 +280,14 @@ class DVM:
if amount <= invoice_amount:
print("[" + self.dvm_config.NIP89.NAME + "] Payment-request fulfilled...")
send_job_status_reaction(job_event, "processing", client=self.client,
dvm_config=self.dvm_config)
dvm_config=self.dvm_config, user=user)
indices = [i for i, x in enumerate(self.job_list) if
x.event == job_event]
index = -1
if len(indices) > 0:
index = indices[0]
if index > -1:
if self.job_list[index].is_processed: # If payment-required appears a processing
if self.job_list[index].is_processed:
self.job_list[index].is_paid = True
check_and_return_event(self.job_list[index].result, job_event)
elif not (self.job_list[index]).is_processed:
@@ -243,6 +304,8 @@ class DVM:
False, invoice_amount, client=self.client,
dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "] Invoice was not paid sufficiently")
elif zapped_event.kind().as_u64() == EventDefinitions.KIND_NIP88_SUBSCRIBE_EVENT.as_u64():
print("new subscription, doing nothing")
elif zapped_event.kind() in EventDefinitions.ANY_RESULT:
print("[" + self.dvm_config.NIP89.NAME + "] "
@@ -350,7 +413,7 @@ class DVM:
e_tag = Tag.parse(["e", original_event.id().to_hex()])
p_tag = Tag.parse(["p", original_event.author().to_hex()])
alt_tag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str(
original_event.kind()) + ". The task was: " + original_event.content()])
original_event.kind().as_u64()) + ". The task was: " + original_event.content()])
status_tag = Tag.parse(["status", "success"])
reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag]
encrypted = False
@@ -371,15 +434,15 @@ class DVM:
content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.author().to_hex()),
content)
reply_event = EventBuilder(original_event.kind() + 1000, str(content), reply_tags).to_event(self.keys)
reply_event = EventBuilder(Kind(original_event.kind().as_u64() + 1000), str(content), reply_tags).to_event(self.keys)
send_event(reply_event, client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "] " + str(
original_event.kind() + 1000) + " Job Response event sent: " + reply_event.as_json())
original_event.kind().as_u64() + 1000) + " Job Response event sent: " + reply_event.as_json())
def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None,
content=None,
dvm_config=None):
dvm_config=None, user=None):
task = get_task(original_event, client=client, dvm_config=dvm_config)
alt_description, reaction = build_status_reaction(status, task, amount, content, dvm_config)
@@ -413,7 +476,8 @@ class DVM:
bolt11 = ""
payment_hash = ""
expires = original_event.created_at().as_secs() + (60 * 60 * 24)
if status == "payment-required" or (status == "processing" and not is_paid):
if status == "payment-required" or (
status == "processing" and not is_paid):
if dvm_config.LNBITS_INVOICE_KEY != "":
try:
bolt11, payment_hash = create_bolt11_ln_bits(amount, dvm_config)
@@ -471,12 +535,12 @@ class DVM:
reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys)
send_event(reaction_event, client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "]" + ": Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK) + " Reaction: " + status + " " + reaction_event.as_json())
EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.as_json())
return reaction_event.as_json()
def do_work(job_event, amount):
if ((EventDefinitions.KIND_NIP90_EXTRACT_TEXT <= job_event.kind() <= EventDefinitions.KIND_NIP90_GENERIC)
or job_event.kind() == EventDefinitions.KIND_DM):
if ((EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() <= job_event.kind().as_u64() <= EventDefinitions.KIND_NIP90_GENERIC.as_u64())
or job_event.kind().as_u64() == EventDefinitions.KIND_DM.as_u64()):
task = get_task(job_event, client=self.client, dvm_config=self.dvm_config)
@@ -545,19 +609,19 @@ class DVM:
for dvm in self.dvm_config.SUPPORTED_DVMS:
scheduled_result = dvm.schedule(self.dvm_config)
for job in self.job_list:
if job.bolt11 != "" and job.payment_hash != "" and not job.payment_hash is None and not job.is_paid:
ispaid = check_bolt11_ln_bits_is_paid(job.payment_hash, self.dvm_config)
if ispaid and job.is_paid is False:
print("is paid")
job.is_paid = True
amount = parse_amount_from_bolt11_invoice(job.bolt11)
job.is_paid = True
send_job_status_reaction(job.event, "processing", True, 0,
client=self.client,
dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "] doing work from joblist")
amount = parse_amount_from_bolt11_invoice(job.bolt11)
do_work(job.event, amount)
elif ispaid is None: # invoice expired
self.job_list.remove(job)

View File

@@ -7,17 +7,18 @@ import sys
from sys import platform
from threading import Thread
from venv import create
from nostr_sdk import Keys
from nostr_sdk import Keys, Kind
from nostr_dvm.dvm import DVM
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_result
class DVMTaskInterface:
NAME: str
KIND: int
KIND: Kind
TASK: str = ""
FIX_COST: float = 0
PER_UNIT_COST: float = 0
@@ -30,13 +31,14 @@ class DVMTaskInterface:
admin_config: AdminConfig
dependencies = []
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, admin_config: AdminConfig = None,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config,
admin_config: AdminConfig = None,
options=None, task=None):
self.init(name, dvm_config, admin_config, nip89config, task)
self.init(name, dvm_config, admin_config, nip88config, nip89config, task)
self.options = options
self.install_dependencies(dvm_config)
def init(self, name, dvm_config, admin_config=None, nip89config=None, task=None):
def init(self, name, dvm_config, admin_config=None, nip88config=None, nip89config=None, task=None):
self.NAME = name
self.PRIVATE_KEY = dvm_config.PRIVATE_KEY
if dvm_config.PUBLIC_KEY == "" or dvm_config.PUBLIC_KEY is None:
@@ -55,6 +57,12 @@ class DVMTaskInterface:
self.KIND = nip89config.KIND
dvm_config.NIP89 = self.NIP89_announcement(nip89config)
if nip88config is None:
dvm_config.NIP88 = NIP88Config()
else:
dvm_config.NIP88 = nip88config
self.dvm_config = dvm_config
self.admin_config = admin_config
@@ -150,4 +158,3 @@ def process_venv(identifier):
DVMTaskInterface.write_output(result, args.output)
except Exception as e:
DVMTaskInterface.write_output("Error: " + str(e), args.output)

View File

@@ -1,12 +1,13 @@
import json
import os
from datetime import timedelta
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_events
@@ -19,15 +20,16 @@ Params: None
class AdvancedSearch(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_CONTENT_SEARCH
KIND: Kind = EventDefinitions.KIND_NIP90_CONTENT_SEARCH
TASK: str = "search-content"
FIX_COST: float = 0
dvm_config: DVMConfig
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -3,12 +3,13 @@ import os
from datetime import timedelta
import requests
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, Event
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, Event, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_events
@@ -21,15 +22,16 @@ Params: None
class AdvancedSearchWine(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_CONTENT_SEARCH
KIND: Kind = EventDefinitions.KIND_NIP90_CONTENT_SEARCH
TASK: str = "search-content"
FIX_COST: float = 0
dvm_config: DVMConfig
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -2,12 +2,14 @@ import json
import os
from datetime import timedelta
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, NostrDatabase, \
ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel, Event, EventId
ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel, Event, EventId, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils import definitions
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config, check_and_set_d_tag_nip88, check_and_set_tiereventid_nip88
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_events, post_process_list_to_users
@@ -20,23 +22,24 @@ Params: None
class DicoverContentCurrentlyPopular(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY
KIND: Kind = EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY
TASK: str = "discover-content"
FIX_COST: float = 0
dvm_config: DVMConfig
last_schedule: int
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
self.last_schedule = Timestamp.now().as_secs()
use_logger = False
if use_logger:
init_logger(LogLevel.DEBUG)
super().__init__(name, dvm_config, nip89config, admin_config, options)
self.sync_db()
def is_input_supported(self, tags, client=None, dvm_config=None):
@@ -94,20 +97,20 @@ class DicoverContentCurrentlyPopular(DVMTaskInterface):
# Query events from database
timestamp_hour_ago = Timestamp.now().as_secs() - 3600
lasthour = Timestamp.from_secs(timestamp_hour_ago)
filter1 = Filter().kind(1).since(lasthour)
filter1 = Filter().kind(definitions.EventDefinitions.KIND_NOTE).since(lasthour)
events = cli.database().query([filter1])
ns.finallist = {}
for event in events:
if event.created_at().as_secs() > timestamp_hour_ago:
ns.finallist[event.id().to_hex()] = 0
filt = Filter().kinds([9735, 7, 1]).event(event.id()).since(lasthour)
filt = Filter().kinds([definitions.EventDefinitions.KIND_ZAP, definitions.EventDefinitions.KIND_REACTION, definitions.EventDefinitions.KIND_NOTE]).event(event.id()).since(lasthour)
reactions = cli.database().query([filt])
ns.finallist[event.id().to_hex()] = len(reactions)
result_list = []
finallist_sorted = sorted(ns.finallist.items(), key=lambda x: x[1], reverse=True)[:int(options["max_results"])]
for entry in finallist_sorted:
print(EventId.parse(entry[0]).to_bech32() + "/" + EventId.parse(entry[0]).to_hex() + ": " + str(entry[1]))
#print(EventId.parse(entry[0]).to_bech32() + "/" + EventId.parse(entry[0]).to_hex() + ": " + str(entry[1]))
e_tag = Tag.parse(["e", entry[0]])
result_list.append(e_tag.as_vec())
@@ -146,7 +149,8 @@ class DicoverContentCurrentlyPopular(DVMTaskInterface):
timestamp_hour_ago = Timestamp.now().as_secs() - 3600
lasthour = Timestamp.from_secs(timestamp_hour_ago)
filter1 = Filter().kinds([1, 7, 9735]).since(lasthour) # Notes, reactions, zaps
filter1 = Filter().kinds([definitions.EventDefinitions.KIND_NOTE, definitions.EventDefinitions.KIND_REACTION, definitions.EventDefinitions.KIND_ZAP]).since(lasthour) # Notes, reactions, zaps
# filter = Filter().author(keys.public_key())
print("Syncing Notes of last hour.. this might take a while..")
@@ -163,13 +167,20 @@ def build_example(name, identifier, admin_config):
dvm_config.USE_OWN_VENV = False
dvm_config.SHOWLOG = True
dvm_config.SCHEDULE_UPDATES_SECONDS = 600 # Every 10 minutes
# Activate these to use a subscription based model instead
# dvm_config.SUBSCRIPTION_REQUIRED = True
# dvm_config.SUBSCRIPTION_DAILY_COST = 1
dvm_config.FIX_COST = 0
# Add NIP89
nip89info = {
"name": name,
"image": "https://image.nostr.build/f720192abfbfbcc21ce78281aca4bbd1ccf89ee7c90b54ae16b71ae9c1ad88e0.png",
"about": "I show popular content",
"image": "https://image.nostr.build/b29b6ec4bf9b6184f69d33cb44862db0d90a2dd9a506532e7ba5698af7d36210.jpg",
"about": "I show notes that are currently popular",
"lud16": dvm_config.LN_ADDRESS,
"encryptionSupported": True,
"cashuAccepted": True,
"amount": "free",
"nip90Params": {
"max_results": {
"required": False,
@@ -183,9 +194,69 @@ def build_example(name, identifier, admin_config):
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
admin_config.UPDATE_PROFILE = False
admin_config.REBROADCAST_NIP89 = True
return DicoverContentCurrentlyPopular(name=name, dvm_config=dvm_config, nip89config=nip89config,
admin_config=admin_config)
def build_example_subscription(name, identifier, admin_config):
dvm_config = build_default_config(identifier)
dvm_config.USE_OWN_VENV = False
dvm_config.SHOWLOG = True
dvm_config.SCHEDULE_UPDATES_SECONDS = 600 # Every 10 minutes
# Activate these to use a subscription based model instead
# dvm_config.SUBSCRIPTION_DAILY_COST = 1
dvm_config.FIX_COST = 0
# Add NIP89
nip89info = {
"name": name,
"image": "https://image.nostr.build/b29b6ec4bf9b6184f69d33cb44862db0d90a2dd9a506532e7ba5698af7d36210.jpg",
"about": "I show notes that are currently popular, just like the free DVM, I'm also used for testing subscriptions. (beta, might break"
")",
"lud16": dvm_config.LN_ADDRESS,
"encryptionSupported": True,
"cashuAccepted": True,
"amount": "subscription",
"nip90Params": {
"max_results": {
"required": False,
"values": [],
"description": "The number of maximum results to return (default currently 100)"
}
}
}
nip89config = NIP89Config()
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
nip88config = NIP88Config()
nip88config.DTAG = check_and_set_d_tag_nip88(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip88config.TIER_EVENT = check_and_set_tiereventid_nip88(identifier, "1")
nip89config.NAME = name
nip88config.IMAGE = nip89info["image"]
nip88config.TITLE = name
nip88config.AMOUNT_DAILY = 100
nip88config.AMOUNT_MONTHLY = 2000
nip88config.CONTENT = "Subscribe to the DVM for unlimited use during your subscription"
nip88config.PERK1DESC = "Unlimited requests"
nip88config.PAYMENT_VERIFIER_PUBKEY = "5b5c045ecdf66fb540bdf2049fe0ef7f1a566fa427a4fe50d400a011b65a3a7e"
admin_config.UPDATE_PROFILE = False
admin_config.REBROADCAST_NIP89 = True
admin_config.REBROADCAST_NIP88 = False
# admin_config.FETCH_NIP88 = True
# admin_config.EVENTID = "63a791cdc7bf78c14031616963105fce5793f532bb231687665b14fb6d805fdb"
# admin_config.PRIVKEY = dvm_config.PRIVATE_KEY
return DicoverContentCurrentlyPopular(name=name, dvm_config=dvm_config, nip89config=nip89config,
nip88config=nip88config,
admin_config=admin_config)
if __name__ == '__main__':
process_venv(DicoverContentCurrentlyPopular)

View File

@@ -1,9 +1,13 @@
import json
import os
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config
from nostr_dvm.utils.mediasource_utils import organize_input_media_data
from nostr_dvm.utils.output_utils import upload_media_to_hoster
@@ -18,15 +22,16 @@ Params: -language The target language
class MediaConverter(DVMTaskInterface):
KIND = EventDefinitions.KIND_NIP90_CONVERT_VIDEO
KIND: Kind = EventDefinitions.KIND_NIP90_CONVERT_VIDEO
TASK = "convert"
FIX_COST = 20
PER_UNIT_COST = 0.1
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -3,12 +3,13 @@ import os
from datetime import timedelta
from threading import Thread
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_users
@@ -22,16 +23,17 @@ Params: None
class DiscoverInactiveFollows(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY
KIND: Kind = EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY
TASK: str = "inactive-follows"
FIX_COST: float = 50
client: Client
dvm_config: DVMConfig
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
# no input required

View File

@@ -3,12 +3,13 @@ import os
from datetime import timedelta
from threading import Thread
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_users
@@ -22,16 +23,17 @@ Params: None
class DiscoverNonFollowers(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY
KIND: Kind = EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY
TASK: str = "non-followers"
FIX_COST: float = 50
client: Client
dvm_config: DVMConfig
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
# no input required
@@ -66,7 +68,7 @@ class DiscoverNonFollowers(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
cli = Client.with_opts(signer, opts)
#cli.add_relay("wss://relay.nostr.band")
# cli.add_relay("wss://relay.nostr.band")
for relay in self.dvm_config.RELAY_LIST:
cli.add_relay(relay)
cli.connect()
@@ -96,7 +98,6 @@ class DiscoverNonFollowers(DVMTaskInterface):
ns.dic[following] = "True"
print("Followings: " + str(len(followings)))
def scanList(users, instance, i, st):
from nostr_sdk import Filter
@@ -109,7 +110,6 @@ class DiscoverNonFollowers(DVMTaskInterface):
cli.add_relay(relay)
cli.connect()
for i in range(i, i + st):
filters = []
filter1 = Filter().author(PublicKey.from_hex(users[i])).kind(3)
@@ -135,7 +135,7 @@ class DiscoverNonFollowers(DVMTaskInterface):
if not foundfollower:
instance.dic[best_entry.author().to_hex()] = "False"
print( "DIDNT FIND " + best_entry.author().to_nostr_uri())
print("DIDNT FIND " + best_entry.author().to_nostr_uri())
print(str(i) + "/" + str(len(users)))
cli.disconnect()
@@ -147,7 +147,7 @@ class DiscoverNonFollowers(DVMTaskInterface):
args = [followings, ns, begin, step]
t = Thread(target=scanList, args=args)
threads.append(t)
begin = begin + step -1
begin = begin + step - 1
# last to step size
missing_scans = (len(followings) - begin)

View File

@@ -1 +1,299 @@
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, EventId
import json
import os
import random
import re
from datetime import timedelta
from threading import Thread
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, EventId, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_users
"""
This File contains a Module to find inactive follows for a user on nostr
Accepted Inputs: None needed
Outputs: A list of users that have been inactive
Params: None
"""
class DiscoverInactiveFollows(DVMTaskInterface):
KIND: Kind = EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY
TASK: str = "inactive-follows"
FIX_COST: float = 50
client: Client
dvm_config: DVMConfig
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
# no input required
return True
def create_request_from_nostr_event(self, event, client=None, dvm_config=None):
self.dvm_config = dvm_config
request_form = {"jobID": event.id().to_hex()}
# default values
user = event.author().to_hex()
since_days = 90
for tag in event.tags():
if tag.as_vec()[0] == 'param':
param = tag.as_vec()[1]
if param == "user": # check for param type
user = tag.as_vec()[2]
elif param == "since_days": # check for param type
since_days = int(tag.as_vec()[2])
options = {
"user": user,
"since_days": since_days
}
request_form['options'] = json.dumps(options)
return request_form
def process(self, request_form):
from nostr_sdk import Filter
from types import SimpleNamespace
ns = SimpleNamespace()
opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=self.dvm_config.RELAY_TIMEOUT)))
sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY)
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
cli = Client.with_opts(signer, opts)
for relay in self.dvm_config.RELAY_LIST:
cli.add_relay(relay)
cli.connect()
options = DVMTaskInterface.set_options(request_form)
inactivefollowerslist = ""
relay_list = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol", "wss://nostr.wine",
"wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev",
"wss://relay.nostr.bg", "wss://relay.f7z.io"]
relaytimeout = 5
step = 20
keys = Keys.parse(os.getenv(env.NOSTR_PRIVATE_KEY))
opts = Options().wait_for_send(False).send_timeout(timedelta(seconds=5)).skip_disconnected_relays(
True)
cl = Client.with_opts(keys, opts)
for relay in relay_list:
cl.add_relay(relay)
cl.connect()
timeinseconds = 3 * 24 * 60 * 60
dif = Timestamp.now().as_secs() - timeinseconds
considernotessince = Timestamp.from_secs(dif)
filt = Filter().author(user).kind(1).since(considernotessince)
reactions = cl.get_events_of([filt], timedelta(seconds=relaytimeout))
list = []
random.shuffle(reactions)
for reaction in reactions:
if reaction.kind() == 1:
list.append(reaction.content())
all = json.dumps(list)
all = all.replace("\n", " ").replace("\n\n", " ")
cleared = ""
tokens = all.split()
for item in tokens:
item = item.replace("\n", " ").lstrip("\"").rstrip(",").rstrip(("."))
# print(item)
if item.__contains__("http") or item.__contains__("\nhttp") or item.__contains__(
"\n\nhttp") or item.lower().__contains__("nostr:") or item.lower().__contains__(
"nevent") or item.__contains__("\\u"):
cleareditem = ""
else:
cleareditem = item
cleared = cleared + " " + cleareditem
cleared = cleared.replace("\n", " ")
# res = re.sub(r"[^ a-zA-Z0-9.!?/\\:,]+", '', all)
# print(cleared)
try:
answer = LLAMA2(
"Give me the 15 most important substantives as keywords of the following input: " + cleared,
"nostruser",
"Reply only with a comma-seperated keywords. return topics starting with a *", clear=True)
except:
answer = ""
promptarr = answer.split(":")
if len(promptarr) > 1:
# print(promptarr[1])
prompt = promptarr[1].lstrip("\n").replace("\n", ",").replace("*", ",").replace("", ",")
else:
prompt = promptarr[0].replace("\n", ",").replace("*", "")
pattern = r"[^a-zA-Z,#'\s]"
text = re.sub(pattern, "", prompt) + ","
# text = (text.replace("Let's,", "").replace("Why,", "").replace("GM,", "")
# .replace("Remember,", "").replace("I,", "").replace("Think,", "")
# .replace("Already,", ""))
# print(text)
keywords = text.split(', ')
keywords = [x.lstrip().rstrip(',') for x in keywords if x]
print(keywords)
# answer = LLAMA2("Extent the given list with 5 synonyms per entry " + str(keywords), user,
# "Reply only with a comma-seperated keywords. return topics starting with a *")
# answer.replace(" - Alternatives:", ",")
# print(answer)
# promptarr = answer.split(":")
# if len(promptarr) > 1:
# # print(promptarr[1])
# prompt = promptarr[1].lstrip("\n").replace("\n", ",").replace("*", "").replace("•", "")
# else:
# prompt = promptarr[0].replace("\n", ",").replace("*", "")
# pattern = r"[^a-zA-Z,'\s]"
# text = re.sub(pattern, "", prompt) + ","
# keywords = text.split(', ')
# print(keywords)
timeinseconds = 30 * 60 # last 30 min?
dif = Timestamp.now().as_secs() - timeinseconds
looksince = Timestamp.from_secs(dif)
filt2 = Filter().kind(1).since(looksince)
notes = cl.get_events_of([filt2], timedelta(seconds=6))
# finallist = []
ns.finallist = {}
print("Notes found: " + str(len(notes)))
def scanList(noteid: EventId, instance, i, length):
relay_list = ["wss://relay.damus.io", "wss://nostr-pub.wellorder.net", "wss://nos.lol",
"wss://nostr.wine",
"wss://relay.nostfiles.dev", "wss://nostr.mom", "wss://nostr.oxtr.dev",
"wss://relay.nostr.bg", "wss://relay.f7z.io"]
keys = Keys.parse(os.getenv(env.NOSTR_PRIVATE_KEY))
opts = Options().wait_for_send(wait_for_send).send_timeout(
timedelta(seconds=5)).skip_disconnected_relays(True)
cli = Client.with_opts(keys, opts)
for relay in relay_list:
cli.add_relay(relay)
cli.connect()
filters = []
instance.finallist[noteid.to_hex()] = 0
filt = Filter().kinds([9735, 7, 1]).event(noteid)
reactions = cl.get_events_of([filt], timedelta(seconds=5))
print(str(len(reactions)) + " " + str(j) + "/" + str(len(notes)))
instance.finallist[noteid.to_hex()] = len(reactions)
print(str(i) + "/" + str(length))
cli.disconnect()
j = 0
threads = []
for note in notes:
j = j + 1
res = [ele for ele in keywords if (ele.replace(',', "") in note.content())]
if bool(res):
if not note.id().to_hex() in ns.finallist and note.pubkey().to_hex() != user:
args = [note.id(), ns, j, len(notes)]
t = Thread(target=scanList, args=args)
threads.append(t)
# Start all threads
for x in threads:
x.start()
# Wait for all of them to finish
for x in threads:
x.join()
finallist_sorted = sorted(ns.finallist.items(), key=lambda x: x[1], reverse=True)
converted_dict = dict(finallist_sorted)
print(json.dumps(converted_dict))
notelist = ""
resultlist = []
i = 0
notelist = "Based on topics: " + json.dumps(keywords).lstrip("[").rstrip(("]")) + "\n\n"
for k in converted_dict:
# print(k)
if is_bot:
i = i + 1
notelist = notelist + "nostr:" + EventId.from_hex(k).to_bech32() + "\n\n"
if i == 25:
break
else:
p_tag = Tag.parse(["p", k])
resultlist.append(p_tag.as_vec())
else:
return json.dumps(resultlist[:25])
def post_process(self, result, event):
"""Overwrite the interface function to return a social client readable format, if requested"""
for tag in event.tags():
if tag.as_vec()[0] == 'output':
format = tag.as_vec()[1]
if format == "text/plain": # check for output type
result = post_process_list_to_users(result)
# if not text/plain, don't post-process
return result
# We build an example here that we can call by either calling this file directly from the main directory,
# or by adding it to our playground. You can call the example and adjust it to your needs or redefine it in the
# playground or elsewhere
def build_example(name, identifier, admin_config):
dvm_config = build_default_config(identifier)
admin_config.LUD16 = dvm_config.LN_ADDRESS
# Add NIP89
nip89info = {
"name": name,
"image": "https://image.nostr.build/b29b6ec4bf9b6184f69d33cb44862db0d90a2dd9a506532e7ba5698af7d36210.jpg",
"about": "I discover users you follow, but that have been inactive on Nostr",
"encryptionSupported": True,
"cashuAccepted": True,
"amount": "Free",
"nip90Params": {
"user": {
"required": False,
"values": [],
"description": "Do the task for another user"
},
"since_days": {
"required": False,
"values": [],
"description": "The number of days a user has not been active to be considered inactive"
}
}
}
nip89config = NIP89Config()
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
return DiscoverInactiveFollows(name=name, dvm_config=dvm_config, nip89config=nip89config,
admin_config=admin_config)
if __name__ == '__main__':
process_venv(DiscoverInactiveFollows)

View File

@@ -1,11 +1,12 @@
import json
import os
from nostr_sdk import Tag
from nostr_sdk import Tag, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_events
@@ -18,15 +19,16 @@ Params: None
class TrendingNotesNostrBand(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY
KIND: Kind = EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY
TASK: str = "trending-content"
FIX_COST: float = 0
dvm_config: DVMConfig
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -5,11 +5,13 @@ from io import BytesIO
import requests
from PIL import Image
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import upload_media_to_hoster
from nostr_dvm.utils.zap_utils import get_price_per_sat
@@ -23,16 +25,17 @@ Outputs: An url to an Image
class ImageGenerationDALLE(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image"
FIX_COST: float = 120
dependencies = [("nostr-dvm", "nostr-dvm"),
("openai", "openai==1.3.5")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -3,11 +3,13 @@ import os
from io import BytesIO
import requests
from PIL import Image
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import upload_media_to_hoster
from nostr_dvm.utils.zap_utils import get_price_per_sat
@@ -22,16 +24,17 @@ Params:
class ImageGenerationReplicateSDXL(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image"
FIX_COST: float = 120
dependencies = [("nostr-dvm", "nostr-dvm"),
("replicate", "replicate")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,12 +1,14 @@
import json
import os
from PIL import Image
from nostr_sdk import Kind
from tqdm import tqdm
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import upload_media_to_hoster
from nostr_dvm.utils.zap_utils import get_price_per_sat
@@ -21,7 +23,7 @@ Params:
class ImageGenerationMLX(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image"
FIX_COST: float = 120
dependencies = [("nostr-dvm", "nostr-dvm"),
@@ -32,10 +34,11 @@ class ImageGenerationMLX(DVMTaskInterface):
("tqdm", "tqdm"),
]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,10 +1,13 @@
import json
from multiprocessing.pool import ThreadPool
from nostr_sdk import Kind
from nostr_dvm.backends.nova_server.utils import check_server_status, send_request_to_server
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -19,13 +22,14 @@ Params: -model # models: juggernaut, dynavision, colossusProject, newrea
class ImageGenerationSDXL(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image"
FIX_COST: float = 50
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,10 +1,13 @@
import json
from multiprocessing.pool import ThreadPool
from nostr_sdk import Kind
from nostr_dvm.backends.nova_server.utils import check_server_status, send_request_to_server
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -19,13 +22,14 @@ Params: -model # models: juggernaut, dynavision, colossusProject, newrea
class ImageGenerationSDXLIMG2IMG(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "image-to-image"
FIX_COST: float = 70
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
hasurl = False

View File

@@ -1,10 +1,13 @@
import json
from multiprocessing.pool import ThreadPool
from nostr_sdk import Kind
from nostr_dvm.backends.nova_server.utils import check_server_status, send_request_to_server
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -18,13 +21,14 @@ Outputs: An textual description of the image
class ImageInterrogator(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
TASK: str = "image-to-text"
FIX_COST: float = 80
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config,
admin_config: AdminConfig = None, options=None):
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
hasurl = False

View File

@@ -1,10 +1,13 @@
import json
from multiprocessing.pool import ThreadPool
from nostr_sdk import Kind
from nostr_dvm.backends.nova_server.utils import check_server_status, send_request_to_server
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -18,13 +21,14 @@ Params: -upscale 2,3,4
class ImageUpscale(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "image-to-image"
FIX_COST: float = 20
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
hasurl = False

View File

@@ -2,12 +2,13 @@ import json
import os
from datetime import timedelta
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, NostrDatabase, \
ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel
ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import post_process_list_to_events, post_process_list_to_users
@@ -20,23 +21,22 @@ Params: None
class SearchUser(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_USER_SEARCH
KIND: Kind = EventDefinitions.KIND_NIP90_USER_SEARCH
TASK: str = "search-user"
FIX_COST: float = 0
dvm_config: DVMConfig
last_schedule: int
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
self.last_schedule = Timestamp.now().as_secs()
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
use_logger = False
if use_logger:
init_logger(LogLevel.DEBUG)
super().__init__(name, dvm_config, nip89config, admin_config, options)
self.sync_db()
def is_input_supported(self, tags, client=None, dvm_config=None):

View File

@@ -5,9 +5,10 @@ from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.nostr_utils import get_referenced_event_by_id, get_event_by_id, get_events_by_ids
from nostr_sdk import Tag
from nostr_sdk import Tag, Kind
"""
This File contains a Module to summarize Text, based on a prompt using a the HuggingChat LLM on Huggingface
@@ -18,16 +19,17 @@ Outputs: Generated text
class TextSummarizationHuggingChat(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_SUMMARIZE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_SUMMARIZE_TEXT
TASK: str = "summarization"
FIX_COST: float = 0
dependencies = [("nostr-dvm", "nostr-dvm"),
("hugchat", "hugchat")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,11 +1,12 @@
import json
import os
import re
from nostr_sdk import Tag
from nostr_sdk import Tag, Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.nostr_utils import get_referenced_event_by_id, get_events_by_ids, get_event_by_id
@@ -18,16 +19,17 @@ Outputs: Generated text
class SummarizationUnleashedChat(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_SUMMARIZE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_SUMMARIZE_TEXT
TASK: str = "text-to-text"
FIX_COST: float = 10
dependencies = [("nostr-dvm", "nostr-dvm"),
("openai", "openai")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -2,10 +2,13 @@ import json
import os
import time
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.mediasource_utils import organize_input_media_data
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -19,17 +22,18 @@ Outputs: Transcribed text
class SpeechToTextGoogle(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
TASK: str = "speech-to-text"
FIX_COST: float = 10
PER_UNIT_COST: float = 0.1
dependencies = [("nostr-dvm", "nostr-dvm"),
("speech_recognition", "SpeechRecognition==3.10.0")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
if options is None:
self.options = {}

View File

@@ -2,10 +2,13 @@ import json
import os
import re
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.nostr_utils import get_event_by_id
@@ -19,16 +22,17 @@ Params: None
class TextExtractionPDF(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
TASK: str = "pdf-to-text"
FIX_COST: float = 0
dependencies = [("nostr-dvm", "nostr-dvm"),
("pypdf", "pypdf==3.17.1")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -2,11 +2,15 @@ import json
import os
import time
from multiprocessing.pool import ThreadPool
from nostr_sdk import Kind
from nostr_dvm.backends.nova_server.utils import check_server_status, send_request_to_server, send_file_to_server
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.mediasource_utils import organize_input_media_data
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -20,14 +24,16 @@ Outputs: Transcribed text
class SpeechToTextWhisperX(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_EXTRACT_TEXT
TASK: str = "speech-to-text"
FIX_COST: float = 10
PER_UNIT_COST: float = 0.1
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
super().__init__(name, dvm_config, nip89config, admin_config, options)
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,10 +1,13 @@
import json
import os
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
"""
@@ -16,16 +19,17 @@ Outputs: Generated text
class TextGenerationHuggingChat(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_TEXT
TASK: str = "text-to-text"
FIX_COST: float = 0
dependencies = [("nostr-dvm", "nostr-dvm"),
("hugchat", "hugchat")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,10 +1,13 @@
import json
import os
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
"""
@@ -16,16 +19,17 @@ Outputs: Generated text
class TextGenerationLLMLite(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_TEXT
TASK: str = "text-to-text"
FIX_COST: float = 0
dependencies = [("nostr-dvm", "nostr-dvm"),
("litellm", "litellm==1.12.3")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,10 +1,13 @@
import json
import os
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
"""
@@ -16,16 +19,17 @@ Outputs: Generated text
class TextGenerationUnleashedChat(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_TEXT
TASK: str = "text-to-text"
FIX_COST: float = 10
dependencies = [("nostr-dvm", "nostr-dvm"),
("openai", "openai")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,6 +1,10 @@
import json
import os
from nostr_sdk import Kind
from nostr_dvm.utils.nip88_utils import NIP88Config
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
from pathlib import Path
import urllib.request
@@ -22,17 +26,18 @@ Outputs: Generated Audiofile
class TextToSpeech(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_TEXT_TO_SPEECH
KIND: Kind = EventDefinitions.KIND_NIP90_TEXT_TO_SPEECH
TASK: str = "text-to-speech"
FIX_COST: float = 50
PER_UNIT_COST = 0.5
dependencies = [("nostr-dvm", "nostr-dvm"),
("TTS", "TTS==0.22.0")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,9 +1,13 @@
import json
import os
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.nostr_utils import get_referenced_event_by_id, get_event_by_id
@@ -17,16 +21,17 @@ Params: -language The target language
class TranslationGoogle(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT
TASK: str = "translation"
FIX_COST: float = 0
dependencies = [("nostr-dvm", "nostr-dvm"),
("translatepy", "translatepy==2.3")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,11 +1,13 @@
import json
import os
import requests
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.nostr_utils import get_referenced_event_by_id, get_event_by_id
@@ -21,14 +23,15 @@ Requires API key or self-hosted instance
class TranslationLibre(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT
KIND: Kind = EventDefinitions.KIND_NIP90_TRANSLATE_TEXT
TASK: str = "translation"
FIX_COST: float = 0
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
admin_config: AdminConfig = None, options=None, task=None):
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options, task)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -4,11 +4,13 @@ from io import BytesIO
import requests
import urllib.request
from PIL import Image
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.output_utils import upload_media_to_hoster
from nostr_dvm.utils.zap_utils import get_price_per_sat
@@ -23,16 +25,17 @@ Params:
class VideoGenerationReplicateSVD(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_VIDEO
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_VIDEO
TASK: str = "image-to-video"
FIX_COST: float = 120
dependencies = [("nostr-dvm", "nostr-dvm"),
("replicate", "replicate")]
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -1,10 +1,14 @@
import json
import os
from multiprocessing.pool import ThreadPool
from nostr_sdk import Kind
from nostr_dvm.backends.nova_server.utils import check_server_status, send_request_to_server
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag
from nostr_dvm.utils.definitions import EventDefinitions
@@ -18,13 +22,14 @@ Outputs: An url to a video
class VideoGenerationSVD(DVMTaskInterface):
KIND: int = EventDefinitions.KIND_NIP90_GENERATE_VIDEO
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_VIDEO
TASK: str = "image-to-video"
FIX_COST: float = 120
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config,
def __init__(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
super().__init__(name, dvm_config, nip89config, admin_config, options)
super().__init__(name=name, dvm_config=dvm_config, nip89config=nip89config, nip88config=nip88config,
admin_config=admin_config, options=options)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:

View File

@@ -5,23 +5,28 @@ from nostr_sdk import Keys, PublicKey, Client
from nostr_dvm.utils.database_utils import get_from_sql_table, list_db, delete_from_sql_table, update_sql_table, \
get_or_add_user, clean_db
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nip89_utils import nip89_announce_tasks, fetch_nip89_paramters_for_deletion
from nostr_dvm.utils.nip88_utils import nip88_announce_tier, fetch_nip88_parameters_for_deletion, fetch_nip88_event, \
check_and_set_tiereventid_nip88
from nostr_dvm.utils.nip89_utils import nip89_announce_tasks, fetch_nip89_parameters_for_deletion
from nostr_dvm.utils.nostr_utils import update_profile
class AdminConfig:
REBROADCAST_NIP89: bool = False
REBROADCAST_NIP88: bool = False
UPDATE_PROFILE: bool = False
DELETE_NIP89: bool = False
DELETE_NIP88: bool = False
FETCH_NIP88: bool = False
WHITELISTUSER: bool = False
UNWHITELISTUSER: bool = False
BLACKLISTUSER: bool = False
DELETEUSER: bool = False
LISTDATABASE: bool = False
ClEANDB: bool = False
INDEX: str = "1"
USERNPUBS: list = []
LUD16: str = ""
EVENTID: str = ""
PRIVKEY: str = ""
@@ -56,17 +61,17 @@ def admin_make_database_updates(adminconfig: AdminConfig = None, dvmconfig: DVMC
if adminconfig.WHITELISTUSER:
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)
update_sql_table(db, user.npub, user.balance, True, False, user.nip05, user.lud16, user.name, user.lastactive, user.subscribed)
user = get_from_sql_table(db, publickey)
print(str(user.name) + " is whitelisted: " + str(user.iswhitelisted))
if adminconfig.UNWHITELISTUSER:
user = get_from_sql_table(db, publickey)
update_sql_table(db, user.npub, user.balance, False, False, user.nip05, user.lud16, user.name, user.lastactive)
update_sql_table(db, user.npub, user.balance, False, False, user.nip05, user.lud16, user.name, user.lastactive, user.subscribed)
if adminconfig.BLACKLISTUSER:
user = get_from_sql_table(db, publickey)
update_sql_table(db, user.npub, user.balance, False, True, user.nip05, user.lud16, user.name, user.lastactive)
update_sql_table(db, user.npub, user.balance, False, True, user.nip05, user.lud16, user.name, user.lastactive, user.subscribed)
if adminconfig.DELETEUSER:
delete_from_sql_table(db, publickey)
@@ -80,11 +85,27 @@ def admin_make_database_updates(adminconfig: AdminConfig = None, dvmconfig: DVMC
if adminconfig.REBROADCAST_NIP89:
nip89_announce_tasks(dvmconfig, client=client)
if adminconfig.REBROADCAST_NIP88:
annotier_id = nip88_announce_tier(dvmconfig, client=client)
check_and_set_tiereventid_nip88(dvmconfig.IDENTIFIER, adminconfig.INDEX, annotier_id.to_hex())
if adminconfig.DELETE_NIP89:
event_id = adminconfig.EVENTID
keys = Keys.parse(
adminconfig.PRIVKEY) # Private key from sender of Event (e.g. the key of an nip89 announcement you want to delete)
fetch_nip89_paramters_for_deletion(keys, event_id, client, dvmconfig)
fetch_nip89_parameters_for_deletion(keys, event_id, client, dvmconfig)
if adminconfig.DELETE_NIP88:
event_id = adminconfig.EVENTID
keys = Keys.parse(
adminconfig.PRIVKEY) # Private key from sender of Event (e.g. the key of an nip89 announcement you want to delete)
fetch_nip88_parameters_for_deletion(keys, event_id, client, dvmconfig)
if adminconfig.FETCH_NIP88:
event_id = adminconfig.EVENTID
keys = Keys.parse(
adminconfig.PRIVKEY)
fetch_nip88_event(keys, event_id, client, dvmconfig)
if adminconfig.UPDATE_PROFILE:
update_profile(dvmconfig, client, lud16=adminconfig.LUD16)
update_profile(dvmconfig, client, lud16=dvmconfig.LN_ADDRESS)

View File

@@ -12,7 +12,7 @@ from nostr_dvm.utils.nostr_utils import get_event_by_id, get_referenced_event_by
def get_task(event, client, dvm_config):
try:
if event.kind() == EventDefinitions.KIND_NIP90_GENERIC: # use this for events that have no id yet, inclufr j tag
if event.kind().as_u64() == EventDefinitions.KIND_NIP90_GENERIC.as_u64(): # use this for events that have no id yet, inclufr j tag
for tag in event.tags():
if tag.as_vec()[0] == 'j':
return tag.as_vec()[1]
@@ -26,7 +26,7 @@ def get_task(event, client, dvm_config):
return "unknown job: " + event.as_json()
# 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:
elif event.kind().as_u64() == EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64():
for tag in event.tags():
if tag.as_vec()[0] == "i":
if tag.as_vec()[2] == "url":
@@ -57,7 +57,7 @@ def get_task(event, client, dvm_config):
return "unknown type"
else:
return "unknown job"
elif event.kind() == EventDefinitions.KIND_NIP90_GENERATE_IMAGE:
elif event.kind().as_u64() == EventDefinitions.KIND_NIP90_GENERATE_IMAGE.as_u64():
has_image_tag = False
has_text_tag = False
for tag in event.tags():
@@ -92,7 +92,7 @@ def get_task(event, client, dvm_config):
else:
for dvm in dvm_config.SUPPORTED_DVMS:
if dvm.KIND == event.kind():
if dvm.KIND.as_u64() == event.kind().as_u64():
return dvm.TASK
except Exception as e:
print("Get task: " + str(e))

View File

@@ -7,7 +7,7 @@ from dataclasses import dataclass
from datetime import timedelta
from logging import Filter
from nostr_sdk import Timestamp, Keys, PublicKey, EventBuilder, Filter
from nostr_sdk import Timestamp, Keys, PublicKey, EventBuilder, Filter, Kind
from nostr_dvm.utils.nostr_utils import send_event
@@ -21,6 +21,7 @@ class User:
nip05: str
lud16: str
lastactive: int
subscribed: int
def create_sql_table(db):
@@ -40,7 +41,8 @@ def create_sql_table(db):
nip05 text,
lud16 text,
name text,
lastactive integer
lastactive integer,
subscribed integer
); """)
cur.execute("SELECT name FROM sqlite_master")
con.close()
@@ -53,29 +55,29 @@ def add_sql_table_column(db):
try:
con = sqlite3.connect(db)
cur = con.cursor()
cur.execute(""" ALTER TABLE users ADD COLUMN lastactive 'integer' """)
cur.execute(""" ALTER TABLE users ADD COLUMN subscribed 'integer' """)
con.close()
except Error as e:
print(e)
def add_to_sql_table(db, npub, sats, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive):
def add_to_sql_table(db, npub, sats, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed):
try:
con = sqlite3.connect(db)
cur = con.cursor()
data = (npub, sats, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive)
cur.execute("INSERT or IGNORE INTO users VALUES(?, ?, ?, ?, ?, ?, ?, ?)", data)
data = (npub, sats, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed)
cur.execute("INSERT or IGNORE INTO users VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", data)
con.commit()
con.close()
except Error as e:
print("Error when Adding to DB: " + str(e))
def update_sql_table(db, npub, balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive):
def update_sql_table(db, npub, balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed):
try:
con = sqlite3.connect(db)
cur = con.cursor()
data = (balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, npub)
data = (balance, iswhitelisted, isblacklisted, nip05, lud16, name, lastactive, subscribed, npub)
cur.execute(""" UPDATE users
SET sats = ? ,
@@ -84,7 +86,8 @@ def update_sql_table(db, npub, balance, iswhitelisted, isblacklisted, nip05, lud
nip05 = ? ,
lud16 = ? ,
name = ? ,
lastactive = ?
lastactive = ?,
subscribed = ?
WHERE npub = ?""", data)
con.commit()
con.close()
@@ -111,6 +114,7 @@ def get_from_sql_table(db, npub):
user.lud16 = row[5]
user.name = row[6]
user.lastactive = row[7]
user.subscribed = row[8]
return user
@@ -162,14 +166,14 @@ def update_user_balance(db, npub, additional_sats, client, config):
if user is None:
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())
nip05, lud16, name, Timestamp.now().as_secs(), 0)
print("Adding User: " + npub + " (" + npub + ")")
else:
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())
Timestamp.now().as_secs(), user.subscribed)
print("Updated user balance for: " + str(user.name) +
" Zap amount: " + str(additional_sats) + " Sats. New balance: " + str(new_balance) +" Sats")
@@ -184,6 +188,33 @@ def update_user_balance(db, npub, additional_sats, client, config):
send_event(evt, client=client, dvm_config=config)
def update_user_subscription(npub, subscribed_until, client, dvm_config):
user = get_from_sql_table(dvm_config.DB, npub)
if user is None:
name, nip05, lud16 = fetch_user_metadata(npub, client)
add_to_sql_table(dvm_config.DB, npub, dvm_config.NEW_USER_BALANCE, False, False,
nip05, lud16, name, Timestamp.now().as_secs(), 0)
print("Adding User: " + npub + " (" + npub + ")")
else:
user = get_from_sql_table(dvm_config.DB, npub)
update_sql_table(dvm_config.DB, npub, user.balance, user.iswhitelisted, user.isblacklisted, user.nip05, user.lud16,
user.name,
Timestamp.now().as_secs(), subscribed_until)
print("Updated user subscription for: " + str(user.name))
if dvm_config is not None:
keys = Keys.parse(dvm_config.PRIVATE_KEY)
#time.sleep(1.0)
message = ("Subscribed to DVM " + dvm_config.NIP89.NAME + " until: " + str(Timestamp.from_secs(subscribed_until).to_human_datetime().replace("Z", " ").replace("T", " ")))
evt = EventBuilder.encrypted_direct_msg(keys, PublicKey.from_hex(npub), message,
None).to_event(keys)
send_event(evt, client=client, dvm_config=dvm_config)
def get_or_add_user(db, npub, client, config, update=False):
user = get_from_sql_table(db, npub)
if user is None:
@@ -191,7 +222,7 @@ def get_or_add_user(db, npub, client, config, update=False):
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())
lud16, name, Timestamp.now().as_secs(), 0)
user = get_from_sql_table(db, npub)
return user
except Exception as e:
@@ -201,7 +232,7 @@ def get_or_add_user(db, npub, client, config, update=False):
name, nip05, lud16 = fetch_user_metadata(npub, client)
print("Updating User: " + npub + " (" + npub + ")")
update_sql_table(db, user.npub, user.balance, user.iswhitelisted, user.isblacklisted, nip05,
lud16, name, Timestamp.now().as_secs())
lud16, name, Timestamp.now().as_secs(), user.subscribed)
user = get_from_sql_table(db, npub)
return user
except Exception as e:
@@ -214,9 +245,9 @@ def fetch_user_metadata(npub, client):
name = ""
nip05 = ""
lud16 = ""
pk = PublicKey.from_hex(npub)
pk = PublicKey.parse(npub)
print(f"\nGetting profile metadata for {pk.to_bech32()}...")
profile_filter = Filter().kind(0).author(pk).limit(1)
profile_filter = Filter().kind(Kind(0)).author(pk).limit(1)
events = client.get_events_of([profile_filter], timedelta(seconds=1))
if len(events) > 0:
latest_entry = events[0]

View File

@@ -1,41 +1,49 @@
import os
from dataclasses import dataclass
from nostr_sdk import Event
from nostr_sdk import Event, Kind
class EventDefinitions:
KIND_DM = 4
KIND_ZAP = 9735
KIND_ANNOUNCEMENT = 31990
KIND_NIP94_METADATA = 1063
KIND_FEEDBACK = 7000
KIND_NIP90_EXTRACT_TEXT = 5000
KIND_NIP90_RESULT_EXTRACT_TEXT = KIND_NIP90_EXTRACT_TEXT + 1000
KIND_NIP90_SUMMARIZE_TEXT = 5001
KIND_NIP90_RESULT_SUMMARIZE_TEXT = KIND_NIP90_SUMMARIZE_TEXT + 1000
KIND_NIP90_TRANSLATE_TEXT = 5002
KIND_NIP90_RESULT_TRANSLATE_TEXT = KIND_NIP90_TRANSLATE_TEXT + 1000
KIND_NIP90_GENERATE_TEXT = 5050
KIND_NIP90_RESULT_GENERATE_TEXT = KIND_NIP90_GENERATE_TEXT + 1000
KIND_NIP90_GENERATE_IMAGE = 5100
KIND_NIP90_RESULT_GENERATE_IMAGE = KIND_NIP90_GENERATE_IMAGE + 1000
KIND_NIP90_CONVERT_VIDEO = 5200
KIND_NIP90_RESULT_CONVERT_VIDEO = KIND_NIP90_CONVERT_VIDEO + 1000
KIND_NIP90_GENERATE_VIDEO = 5202
KIND_NIP90_RESULT_GENERATE_VIDEO = KIND_NIP90_GENERATE_VIDEO + 1000
KIND_NIP90_TEXT_TO_SPEECH = 5250
KIND_NIP90_RESULT_TEXT_TO_SPEECH = KIND_NIP90_TEXT_TO_SPEECH + 1000
KIND_NIP90_CONTENT_DISCOVERY = 5300
KIND_NIP90_RESULT_CONTENT_DISCOVERY = KIND_NIP90_CONTENT_DISCOVERY + 1000
KIND_NIP90_PEOPLE_DISCOVERY = 5301
KIND_NIP90_RESULT_PEOPLE_DISCOVERY = KIND_NIP90_PEOPLE_DISCOVERY + 1000
KIND_NIP90_CONTENT_SEARCH = 5302
KIND_NIP90_RESULTS_CONTENT_SEARCH = KIND_NIP90_CONTENT_SEARCH + 1000
KIND_NIP90_USER_SEARCH = 5303
KIND_NIP90_RESULTS_USER_SEARCH = KIND_NIP90_USER_SEARCH + 1000
KIND_NIP90_GENERIC = 5999
KIND_NIP90_RESULT_GENERIC = KIND_NIP90_GENERIC + 1000
KIND_NOTE = Kind(1)
KIND_DM = Kind(4)
KIND_REACTION = Kind(7)
KIND_ZAP = Kind(9735)
KIND_ANNOUNCEMENT = Kind(31990)
KIND_NIP94_METADATA = Kind(1063)
KIND_FEEDBACK = Kind(7000)
KIND_NIP90_EXTRACT_TEXT = Kind(5000)
KIND_NIP90_RESULT_EXTRACT_TEXT = Kind(6000)
KIND_NIP90_SUMMARIZE_TEXT = Kind(5001)
KIND_NIP90_RESULT_SUMMARIZE_TEXT = Kind(6001)
KIND_NIP90_TRANSLATE_TEXT = Kind(5002)
KIND_NIP90_RESULT_TRANSLATE_TEXT = Kind(6002)
KIND_NIP90_GENERATE_TEXT = Kind(5050)
KIND_NIP90_RESULT_GENERATE_TEXT = Kind(6050)
KIND_NIP90_GENERATE_IMAGE = Kind(5100)
KIND_NIP90_RESULT_GENERATE_IMAGE = Kind(6100)
KIND_NIP90_CONVERT_VIDEO = Kind(5200)
KIND_NIP90_RESULT_CONVERT_VIDEO = Kind(6200)
KIND_NIP90_GENERATE_VIDEO = Kind(5202)
KIND_NIP90_RESULT_GENERATE_VIDEO =Kind(6202)
KIND_NIP90_TEXT_TO_SPEECH = Kind(5250)
KIND_NIP90_RESULT_TEXT_TO_SPEECH = Kind(5650)
KIND_NIP90_CONTENT_DISCOVERY = Kind(5300)
KIND_NIP90_RESULT_CONTENT_DISCOVERY = Kind(6300)
KIND_NIP90_PEOPLE_DISCOVERY = Kind(5301)
KIND_NIP90_RESULT_PEOPLE_DISCOVERY = Kind(6301)
KIND_NIP90_CONTENT_SEARCH = Kind(5302)
KIND_NIP90_RESULTS_CONTENT_SEARCH = Kind(6302)
KIND_NIP90_USER_SEARCH = Kind(5303)
KIND_NIP90_RESULTS_USER_SEARCH = Kind(6303)
KIND_NIP90_GENERIC = Kind(5999)
KIND_NIP90_RESULT_GENERIC = Kind(6999)
KIND_NIP88_SUBSCRIBE_EVENT = Kind(7001)
KIND_NIP88_STOP_SUBSCRIPTION_EVENT = Kind(7002)
KIND_NIP88_PAYMENT_RECIPE = Kind(7003)
KIND_NIP88_TIER_EVENT = Kind(37001)
ANY_RESULT = [KIND_NIP90_RESULT_EXTRACT_TEXT,
KIND_NIP90_RESULT_SUMMARIZE_TEXT,
KIND_NIP90_RESULT_TRANSLATE_TEXT,

View File

@@ -2,6 +2,7 @@ import os
from nostr_sdk import Keys
from nostr_dvm.utils.nip88_utils import NIP88Config
from nostr_dvm.utils.nip89_utils import NIP89Config
from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.output_utils import PostProcessFunctionType
@@ -32,12 +33,12 @@ class DVMConfig:
USE_OWN_VENV = True # Make an own venv for each dvm's process function.Disable if you want to install packages into main venv. Only recommended if you dont want to run dvms with different dependency versions
DB: str
NEW_USER_BALANCE: int = 0 # Free credits for new users
NIP88: NIP88Config
NIP89: NIP89Config
SEND_FEEDBACK_EVENTS = True
SHOW_RESULT_BEFORE_PAYMENT: bool = False # if this is true show results even when not paid right after autoprocess
SCHEDULE_UPDATES_SECONDS = 0
def build_default_config(identifier):
dvm_config = DVMConfig()
dvm_config.PRIVATE_KEY = check_and_set_private_key(identifier)

View File

@@ -0,0 +1,201 @@
import os
from datetime import timedelta
from hashlib import sha256
from pathlib import Path
import dotenv
from nostr_sdk import Filter, Tag, Keys, EventBuilder, Client, EventId, PublicKey, Event, Timestamp, SingleLetterTag, \
Alphabet
from nostr_sdk.nostr_sdk import Duration
from nostr_dvm.utils import definitions
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.nostr_utils import send_event
class NIP88Config:
DTAG: str = ""
TITLE: str = ""
CONTENT: str = ""
IMAGE: str = ""
TIER_EVENT: str = ""
PERK1DESC: str = ""
PERK2DESC: str = ""
PERK3DESC: str = ""
PERK4DESC: str = ""
PAYMENT_VERIFIER_PUBKEY: str = ""
AMOUNT_DAILY: int = None
AMOUNT_MONTHLY: int = None
AMOUNT_YEARLY: int = None
def nip88_create_d_tag(name, pubkey, image):
key_str = str(name + image + pubkey)
d_tag = sha256(key_str.encode('utf-8')).hexdigest()[:16]
return d_tag
def fetch_nip88_parameters_for_deletion(keys, eventid, client, dvmconfig):
idfilter = Filter().id(EventId.from_hex(eventid)).limit(1)
nip88events = client.get_events_of([idfilter], timedelta(seconds=dvmconfig.RELAY_TIMEOUT))
d_tag = ""
if len(nip88events) == 0:
print("Event not found. Potentially gone.")
for event in nip88events:
print(event.as_json())
for tag in event.tags():
if tag.as_vec()[0] == "d":
d_tag = tag.as_vec()[1]
if d_tag == "":
print("No dtag found")
return
if event.author().to_hex() == keys.public_key().to_hex():
nip88_delete_announcement(event.id().to_hex(), keys, d_tag, client, dvmconfig)
print("NIP88 announcement deleted from known relays!")
else:
print("Privatekey does not belong to event")
def fetch_nip88_event(keys, eventid, client, dvmconfig):
idfilter = Filter().id(EventId.parse(eventid)).limit(1)
nip88events = client.get_events_of([idfilter], timedelta(seconds=dvmconfig.RELAY_TIMEOUT))
d_tag = ""
if len(nip88events) == 0:
print("Event not found. Potentially gone.")
for event in nip88events:
for tag in event.tags():
if tag.as_vec()[0] == "d":
d_tag = tag.as_vec()[1]
if d_tag == "":
print("No dtag found")
return
if event.author().to_hex() == keys.public_key().to_hex():
print(event.as_json())
else:
print("Privatekey does not belong to event")
def nip88_delete_announcement(eid: str, keys: Keys, dtag: str, client: Client, config):
e_tag = Tag.parse(["e", eid])
a_tag = Tag.parse(
["a", str(EventDefinitions.KIND_NIP88_TIER_EVENT) + ":" + keys.public_key().to_hex() + ":" + dtag])
event = EventBuilder(5, "", [e_tag, a_tag]).to_event(keys)
send_event(event, client, config)
def nip88_has_active_subscription(user: PublicKey, tiereventdtag, client: Client, dvm_config):
subscription_status = {
"isActive": False,
"validUntil": 0,
"subscriptionId": "",
}
subscriptionfilter = Filter().kind(definitions.EventDefinitions.KIND_NIP88_PAYMENT_RECIPE).pubkey(
PublicKey.parse(dvm_config.PUBLIC_KEY)).custom_tag(SingleLetterTag.uppercase(Alphabet.P),
[user.to_hex()]).limit(1)
evts = client.get_events_of([subscriptionfilter], timedelta(seconds=5))
if len(evts) > 0:
print(evts[0].as_json())
matchesdtag = False
for tag in evts[0].tags():
if tag.as_vec()[0] == "valid":
subscription_status["validUntil"] = int(tag.as_vec()[2])
elif tag.as_vec()[0] == "e":
subscription_status["subscriptionId"] = tag.as_vec()[1]
elif tag.as_vec()[0] == "tier":
if tag.as_vec()[1] == tiereventdtag:
matchesdtag = True
if subscription_status["validUntil"] > Timestamp.now().as_secs() & matchesdtag:
subscription_status["isActive"] = True
return subscription_status
def nip88_announce_tier(dvm_config, client):
title_tag = Tag.parse(["title", str(dvm_config.NIP88.TITLE)])
image_tag = Tag.parse(["image", str(dvm_config.NIP88.IMAGE)])
d_tag = Tag.parse(["d", dvm_config.NIP88.DTAG])
# may todo
zaptag1 = Tag.parse(["zap", dvm_config.PUBLIC_KEY, "wss://damus.io", "19"])
zaptag2 = Tag.parse(["zap", "", "wss://damus.io", "1"])
p_tag = Tag.parse(["p", dvm_config.NIP88.PAYMENT_VERIFIER_PUBKEY])
tags = [title_tag, image_tag, zaptag1, zaptag2, d_tag, p_tag]
if dvm_config.NIP88.AMOUNT_DAILY is not None:
amount_tag = Tag.parse(["amount", str(dvm_config.NIP88.AMOUNT_DAILY * 1000), "msats", "daily"])
tags.append(amount_tag)
if dvm_config.NIP88.AMOUNT_MONTHLY is not None:
amount_tag = Tag.parse(["amount", str(dvm_config.NIP88.AMOUNT_MONTHLY * 1000), "msats", "monthly"])
tags.append(amount_tag)
if dvm_config.NIP88.AMOUNT_YEARLY is not None:
amount_tag = Tag.parse(["amount", str(dvm_config.NIP88.AMOUNT_YEARLY * 1000), "msats", "yearly"])
tags.append(amount_tag)
if dvm_config.NIP88.PERK1DESC != "":
perk_tag = Tag.parse(["perk", str(dvm_config.NIP88.PERK1DESC)])
tags.append(perk_tag)
if dvm_config.NIP88.PERK2DESC != "":
perk_tag = Tag.parse(["perk", str(dvm_config.NIP88.PERK2DESC)])
tags.append(perk_tag)
if dvm_config.NIP88.PERK3DESC != "":
perk_tag = Tag.parse(["perk", str(dvm_config.NIP88.PERK3DESC)])
tags.append(perk_tag)
if dvm_config.NIP88.PERK4DESC != "":
perk_tag = Tag.parse(["perk", str(dvm_config.NIP88.PERK4DESC)])
tags.append(perk_tag)
keys = Keys.parse(dvm_config.NIP89.PK)
content = dvm_config.NIP88.CONTENT
event = EventBuilder(EventDefinitions.KIND_NIP88_TIER_EVENT, content, tags).to_event(keys)
annotier_id = send_event(event, client=client, dvm_config=dvm_config)
print("Announced NIP 88 Tier for " + dvm_config.NIP89.NAME)
return annotier_id
# Relay and payment-verification
# ["r", "wss://my-subscribers-only-relay.com"],
# ["p", "<payment-verifier-pubkey>"],
def check_and_set_d_tag_nip88(identifier, name, pk, imageurl):
if not os.getenv("NIP88_DTAG_" + identifier.upper()):
new_dtag = nip88_create_d_tag(name, Keys.parse(pk).public_key().to_hex(),
imageurl)
nip88_add_dtag_to_env_file("NIP88_DTAG_" + identifier.upper(), new_dtag)
print("Some new dtag:" + new_dtag)
return new_dtag
else:
return os.getenv("NIP88_DTAG_" + identifier.upper())
def check_and_set_tiereventid_nip88(identifier, index="1", eventid=None):
if eventid is None:
if not os.getenv("NIP88_TIEREVENT_" + index + identifier.upper()):
print("No Tier Event ID set")
return None
else:
return os.getenv("NIP88_TIEREVENT_" + index + identifier.upper())
else:
nip88_add_dtag_to_env_file("NIP88_TIEREVENT_" + index + identifier.upper(), eventid)
return eventid
def nip88_add_dtag_to_env_file(dtag, oskey):
env_path = Path('.env')
if env_path.is_file():
print(f'loading environment from {env_path.resolve()}')
dotenv.load_dotenv(env_path, verbose=True, override=True)
dotenv.set_key(env_path, dtag, oskey)

View File

@@ -25,7 +25,7 @@ def nip89_create_d_tag(name, pubkey, image):
def nip89_announce_tasks(dvm_config, client):
k_tag = Tag.parse(["k", str(dvm_config.NIP89.KIND)])
k_tag = Tag.parse(["k", str(dvm_config.NIP89.KIND.as_u64())])
d_tag = Tag.parse(["d", dvm_config.NIP89.DTAG])
keys = Keys.parse(dvm_config.NIP89.PK)
content = dvm_config.NIP89.CONTENT
@@ -34,7 +34,7 @@ def nip89_announce_tasks(dvm_config, client):
print("Announced NIP 89 for " + dvm_config.NIP89.NAME)
def fetch_nip89_paramters_for_deletion(keys, eventid, client, dvmconfig):
def fetch_nip89_parameters_for_deletion(keys, eventid, client, dvmconfig):
idfilter = Filter().id(EventId.from_hex(eventid)).limit(1)
nip89events = client.get_events_of([idfilter], timedelta(seconds=dvmconfig.RELAY_TIMEOUT))
d_tag = ""

View File

@@ -1,44 +1,84 @@
import json
import os
from datetime import timedelta
import requests
from nostr_sdk import Keys, PublicKey, Client, nip04_encrypt, EventBuilder, Tag, NostrSigner
from nostr_sdk import Keys, PublicKey, Client, nip04_encrypt, EventBuilder, Tag, NostrSigner, Filter, Timestamp, \
NostrWalletConnectUri, Nwc
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.zap_utils import zaprequest
def nwc_zap(connectionstr, bolt11, keys):
target_pubkey, relay, secret = parse_connection_str(connectionstr)
SecretSK = Keys.parse(secret)
def nwc_zap(connectionstr, bolt11, keys, externalrelay=None):
uri = NostrWalletConnectUri.parse(connectionstr)
content = {
"method": "pay_invoice",
"params": {
"invoice": bolt11
}
}
# Initialize NWC client
nwc = Nwc(uri)
signer = NostrSigner.keys(keys)
client = Client(signer)
client.add_relay(relay)
client.connect()
info = nwc.get_info()
print(info)
client_public_key = PublicKey.from_hex(target_pubkey)
encrypted_content = nip04_encrypt(SecretSK.secret_key(), client_public_key, json.dumps(content))
balance = nwc.get_balance()
print(f"Balance: {balance} SAT")
pTag = Tag.parse(["p", client_public_key.to_hex()])
event = EventBuilder(23194, encrypted_content,
[pTag]).to_event(keys)
event_id = nwc.pay_invoice(bolt11)
print("NWC event: " + event_id)
event_id = client.send_event(event)
print(event_id.to_hex())
#target_pubkey, relay, secret = parse_connection_str(connectionstr)
#print(target_pubkey)
#print(relay)
#print(secret)
#SecretSK = Keys.parse(secret)
#content = {
# "method": "pay_invoice",
# "params": {
# "invoice": bolt11
# }
#}
#signer = NostrSigner.keys(keys)
#client = Client(signer)
#client.add_relay(relay)
#if externalrelay is not None:
# client.add_relay(externalrelay)
#client.connect()
#client_public_key = PublicKey.from_hex(target_pubkey)
#encrypted_content = nip04_encrypt(SecretSK.secret_key(), client_public_key, json.dumps(content))
#pTag = Tag.parse(["p", client_public_key.to_hex()])
#event = EventBuilder(23194, encrypted_content,
# [pTag]).to_event(keys)
#ts = Timestamp.now()
#event_id = client.send_event(event)
#nwc_response_filter = Filter().kind(23195).since(ts)
#events = client.get_events_of([nwc_response_filter], timedelta(seconds=5))
#if len(events) > 0:
# for evt in events:
# print(evt.as_json())
#else:
# print("No response found")
return event_id
def parse_connection_str(connectionstring):
split = connectionstring.split("?")
targetpubkey = split[0].split(":")[1]
targetpubkey = split[0].split(":")[1].replace("//", "")
split2 = split[1].split("&")
relay = split2[0].split("=")[1]
relay = relay.replace("%3A%2F%2F", "://")

View File

@@ -206,6 +206,12 @@ def build_status_reaction(status, task, amount, content, dvm_config):
amount) + " Sats. "
reaction = alt_description + emoji.emojize(":orange_heart:")
elif status == "subscription-required":
alt_description = "NIP90 DVM AI task " + task + " requires payment for subscription"
reaction = alt_description + emoji.emojize(":orange_heart:")
elif status == "payment-rejected":
alt_description = "NIP90 DVM AI task " + task + " payment is below required amount of " + str(
amount) + " Sats. "

View File

@@ -0,0 +1,130 @@
import sqlite3
from dataclasses import dataclass
from sqlite3 import Error
@dataclass
class Subscription:
id: str
recipent: str
subscriber: str
nwc: str
cadence: str
amount: int
begin: int
end: int
tier_dtag: str
zaps: str
recipe: str
active: bool
def create_subscription_sql_table(db):
try:
import os
if not os.path.exists(r'db'):
os.makedirs(r'db')
if not os.path.exists(r'outputs'):
os.makedirs(r'outputs')
con = sqlite3.connect(db)
cur = con.cursor()
cur.execute(""" CREATE TABLE IF NOT EXISTS subscriptions (
id text PRIMARY KEY,
recipient text,
subscriber text,
nwc text NOT NULL,
cadence text,
amount int,
begin int,
end int,
tier_dtag text,
zaps text,
recipe text,
active boolean
); """)
cur.execute("SELECT name FROM sqlite_master")
con.close()
except Error as e:
print(e)
def add_to_subscription_sql_table(db, id, recipient, subscriber, nwc, cadence, amount, begin, end, tier_dtag, zaps,
recipe, active):
try:
con = sqlite3.connect(db)
cur = con.cursor()
data = (id, recipient, subscriber, nwc, cadence, amount, begin, end, tier_dtag, zaps, recipe, active)
print(id)
print(recipient)
print(subscriber)
print(nwc)
cur.execute("INSERT or IGNORE INTO subscriptions VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", data)
con.commit()
con.close()
except Error as e:
print("Error when Adding to DB: " + str(e))
def get_from_subscription__sql_table(db, id):
try:
con = sqlite3.connect(db)
cur = con.cursor()
cur.execute("SELECT * FROM subscriptions WHERE id=?", (id,))
row = cur.fetchone()
con.close()
if row is None:
return None
else:
subscription = Subscription
subscription.id = row[0]
subscription.recipent = row[1]
subscription.subscriber = row[2]
subscription.nwc = row[3]
subscription.cadence = row[4]
subscription.amount = row[5]
subscription.begin = row[6]
subscription.end = row[7]
subscription.tier_dtag = row[8]
subscription.zaps = row[9]
subscription.recipe = row[10]
subscription.active = row[11]
return subscription
except Error as e:
print("Error Getting from DB: " + str(e))
return None
def update_subscription_sql_table(db, id, recipient, subscriber, nwc, cadence, amount, begin, end, tier_dtag, zaps,
recipe, active):
try:
con = sqlite3.connect(db)
cur = con.cursor()
data = (recipient, subscriber, nwc, cadence, amount, begin, end, tier_dtag, zaps, recipe, active, id)
cur.execute(""" UPDATE subscriptions
SET recipient = ? ,
subscriber = ? ,
nwc = ? ,
cadence = ? ,
amount = ? ,
begin = ? ,
end = ?,
tier_dtag = ?,
zaps = ?,
recipe = ?,
active = ?
WHERE id = ?""", data)
con.commit()
con.close()
except Error as e:
print("Error Updating DB: " + str(e))

View File

@@ -8,7 +8,7 @@ import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from bech32 import bech32_decode, convertbits, bech32_encode
from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event, EventBuilder, Tag, Keys, generate_shared_key
from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event, EventBuilder, Tag, Keys, generate_shared_key, Kind
from nostr_dvm.utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags
import lnurl
@@ -132,7 +132,7 @@ def create_bolt11_lud16(lud16, amount):
def create_lnbits_account(name):
if os.getenv("LNBITS_ADMIN_ID") is None or os.getenv("LNBITS_ADMIN_ID") == "":
print("No admin id set, no wallet created.")
return "","","","", "failed"
return "", "", "", "", "failed"
data = {
'admin_id': os.getenv("LNBITS_ADMIN_ID"),
'wallet_name': name,
@@ -240,6 +240,10 @@ def decrypt_private_zap_message(msg: str, privkey: SecretKey, pubkey: PublicKey)
def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys, relay_list, zaptype="public"):
print(lud16)
print(str(amount))
print(content)
print(zapped_user.to_hex())
if lud16.startswith("LNURL") or lud16.startswith("lnurl"):
url = lnurl.decode(lud16)
elif '@' in lud16: # LNaddress
@@ -250,6 +254,7 @@ def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys
response = requests.get(url)
ob = json.loads(response.content)
callback = ob["callback"]
print(ob["callback"])
encoded_lnurl = lnurl.encode(url)
amount_tag = Tag.parse(['amount', str(amount * 1000)])
relays_tag = Tag.parse(['relays', str(relay_list)])
@@ -262,12 +267,11 @@ def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys
p_tag = Tag.parse(['p', zapped_user.to_hex()])
tags = [amount_tag, relays_tag, p_tag, lnurl_tag]
if zaptype == "private":
key_str = keys.secret_key().to_hex() + zapped_event.id().to_hex() + str(zapped_event.created_at().as_secs())
encryption_key = sha256(key_str.encode('utf-8')).hexdigest()
zap_request = EventBuilder(9733, content,
zap_request = EventBuilder(Kind(9733), content,
[p_tag, e_tag]).to_event(keys).as_json()
keys = Keys.parse(encryption_key)
encrypted_content = enrypt_private_zap_message(zap_request, keys.secret_key(), zapped_event.author())
@@ -275,7 +279,7 @@ def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys
tags.append(anon_tag)
content = ""
zap_request = EventBuilder(9734, content,
zap_request = EventBuilder(Kind(9734), content,
tags).to_event(keys).as_json()
response = requests.get(callback + "?amount=" + str(int(amount) * 1000) + "&nostr=" + urllib.parse.quote_plus(
@@ -287,6 +291,7 @@ def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys
print("ZAP REQUEST: " + e)
return None
def get_price_per_sat(currency):
import requests
@@ -334,8 +339,6 @@ def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain):
return "", ""
def check_and_set_ln_bits_keys(identifier, npub):
if not os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper()):
invoicekey, adminkey, walletid, userid, success = create_lnbits_account(identifier)

View File

@@ -15,7 +15,7 @@ setup(
long_description=LONG_DESCRIPTION,
packages=find_packages(include=['nostr_dvm', 'nostr_dvm.*']),
install_requires=["nostr-sdk==0.9.1",
install_requires=["nostr-sdk==0.10.0",
"bech32",
"pycryptodome==3.20.0",
"python-dotenv==1.0.0",

View File

@@ -4,9 +4,13 @@ from pathlib import Path
import dotenv
from nostr_sdk import Keys
from nostr_dvm.subscription import Subscription
from nostr_dvm.tasks import content_discovery_currently_popular
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.backend_utils import keep_alive
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.zap_utils import check_and_set_ln_bits_keys
def playground():
@@ -19,8 +23,21 @@ def playground():
admin_config.REBROADCAST_NIP89 = False
admin_config.UPDATE_PROFILE = False
discovery_test = content_discovery_currently_popular.build_example("Currently Popular Notes DVM", "discovery_content_test", admin_config)
discovery_test.run()
discovery_test_sub = content_discovery_currently_popular.build_example_subscription("Currently Popular Notes DVM (with Subscriptions)", "discovery_content_test", admin_config)
discovery_test_sub.run()
#discovery_test = content_discovery_currently_popular.build_example("Currently Popular Notes DVM",
# "discovery_content_test", admin_config)
#discovery_test.run()
subscription_config = DVMConfig()
subscription_config.PRIVATE_KEY = check_and_set_private_key("dvm_subscription")
npub = Keys.parse(subscription_config.PRIVATE_KEY).public_key().to_bech32()
invoice_key, admin_key, wallet_id, user_id, lnaddress = check_and_set_ln_bits_keys("dvm_subscription", npub)
subscription_config.LNBITS_INVOICE_KEY = invoice_key
subscription_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back
subscription_config.LNBITS_URL = os.getenv("LNBITS_HOST")
Subscription(subscription_config)
keep_alive()