2024-06-06 02:25:34 +02:00
import asyncio
2023-11-23 11:53:57 +01:00
import json
2023-12-16 00:42:06 +01:00
import os
2023-11-23 13:30:09 +01:00
from datetime import timedelta
2023-12-20 18:21:40 +01:00
from sys import platform
2023-11-23 11:53:57 +01:00
2023-11-18 20:20:18 +01:00
from nostr_sdk import PublicKey , Keys , Client , Tag , Event , EventBuilder , Filter , HandleNotification , Timestamp , \
2024-06-01 19:17:24 +02:00
init_logger , LogLevel , Options , nip04_encrypt , NostrSigner , Kind , RelayLimits
2023-11-21 23:51:48 +01:00
2023-12-13 20:00:03 +01:00
from nostr_dvm . utils . definitions import EventDefinitions , RequiredJobToWatch , JobToWatch
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
2024-03-18 19:44:44 +01:00
from nostr_dvm . utils . database_utils import create_sql_table , get_or_add_user , update_user_balance , update_sql_table , \
update_user_subscription
2023-12-13 20:00:03 +01:00
from nostr_dvm . utils . mediasource_utils import input_data_file_duration
2024-03-18 19:44:44 +01:00
from nostr_dvm . utils . nip88_utils import nip88_has_active_subscription
2024-06-03 20:24:29 +02:00
from nostr_dvm . utils . nostr_utils import get_event_by_id , get_referenced_event_by_id , send_event , check_and_decrypt_tags , \
send_event_outbox
2024-08-19 09:50:18 +02:00
from nostr_dvm . utils . nut_wallet_utils import NutZapWallet
2023-12-13 20:00:03 +01:00
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 , \
2024-01-06 17:47:22 +01:00
parse_amount_from_bolt11_invoice , zaprequest , pay_bolt11_ln_bits , create_bolt11_lud16
2023-12-13 20:00:03 +01:00
from nostr_dvm . utils . cashu_utils import redeem_cashu
2024-09-25 10:21:30 +02:00
from nostr_dvm . utils . print_utils import bcolors
2023-11-18 20:20:18 +01:00
2023-11-20 22:09:38 +01:00
class DVM :
dvm_config : DVMConfig
2023-11-21 11:11:12 +01:00
admin_config : AdminConfig
2023-11-20 22:09:38 +01:00
keys : Keys
client : Client
2023-11-21 10:03:04 +01:00
job_list : list
jobs_on_hold_list : list
2023-11-18 20:20:18 +01:00
2024-08-19 15:10:03 +02:00
def __init__ ( self , dvm_config , admin_config = None ) :
asyncio . run ( self . run_dvm ( dvm_config , admin_config ) )
2024-06-06 13:55:47 +02:00
async def run_dvm ( self , dvm_config , admin_config ) :
2024-08-19 13:25:03 +02:00
2024-06-06 13:55:47 +02:00
self . dvm_config = dvm_config
self . admin_config = admin_config
self . keys = Keys . parse ( dvm_config . PRIVATE_KEY )
2024-03-21 13:41:11 +01:00
wait_for_send = False
2023-11-23 13:30:09 +01:00
skip_disconnected_relays = True
2024-06-01 19:17:24 +02:00
relaylimits = RelayLimits . disable ( )
2024-06-06 02:25:34 +02:00
opts = (
Options ( ) . wait_for_send ( wait_for_send ) . send_timeout ( timedelta ( seconds = self . dvm_config . RELAY_TIMEOUT ) )
. skip_disconnected_relays ( skip_disconnected_relays ) . relay_limits ( relaylimits ) )
2023-11-23 13:30:09 +01:00
2024-02-17 20:40:56 +01:00
signer = NostrSigner . keys ( self . keys )
2024-03-04 15:42:37 +01:00
self . client = Client . with_opts ( signer , opts )
2023-11-21 10:03:04 +01:00
self . job_list = [ ]
self . jobs_on_hold_list = [ ]
2023-11-20 22:09:38 +01:00
pk = self . keys . public_key ( )
2024-06-13 10:49:09 +02:00
print ( bcolors . BLUE + " [ " + self . dvm_config . NIP89 . NAME + " ] " + " Nostr DVM public key: " + str (
2024-05-31 10:27:18 +02:00
pk . to_bech32 ( ) ) + " Hex: " +
str ( pk . to_hex ( ) ) + " Supported DVM tasks: " +
' , ' . join ( p . NAME + " : " + p . TASK for p in self . dvm_config . SUPPORTED_DVMS ) + bcolors . ENDC )
2023-11-18 20:20:18 +01:00
2023-11-20 22:09:38 +01:00
for relay in self . dvm_config . RELAY_LIST :
2024-06-06 02:25:34 +02:00
await self . client . add_relay ( relay )
await self . client . connect ( )
2023-11-19 20:35:40 +01:00
2024-08-19 13:25:03 +02:00
zap_filter = Filter ( ) . pubkey ( pk ) . kinds ( [ EventDefinitions . KIND_ZAP , EventDefinitions . KIND_NIP61_NUT_ZAP ] ) . since ( Timestamp . now ( ) )
2023-11-20 22:09:38 +01:00
kinds = [ EventDefinitions . KIND_NIP90_GENERIC ]
2023-11-23 13:30:09 +01:00
for dvm in self . dvm_config . SUPPORTED_DVMS :
2023-11-20 22:09:38 +01:00
if dvm . KIND not in kinds :
kinds . append ( dvm . KIND )
dvm_filter = ( Filter ( ) . kinds ( kinds ) . since ( Timestamp . now ( ) ) )
2024-06-06 02:25:34 +02:00
create_sql_table ( self . dvm_config . DB )
await admin_make_database_updates ( adminconfig = self . admin_config , dvmconfig = self . dvm_config , client = self . client )
await self . client . subscribe ( [ dvm_filter , zap_filter ] , None )
2024-03-18 19:44:44 +01:00
2024-08-19 09:50:18 +02:00
if self . dvm_config . ENABLE_NUTZAP :
nutzap_wallet = NutZapWallet ( )
nut_wallet = await nutzap_wallet . get_nut_wallet ( self . client , self . keys )
if nut_wallet is None :
await nutzap_wallet . create_new_nut_wallet ( self . dvm_config . NUZAP_MINTS , self . dvm_config . NUTZAP_RELAYS ,
self . client , self . keys , " DVM " , " DVM Nutsack " )
nut_wallet = await nutzap_wallet . get_nut_wallet ( self . client , self . keys )
2024-08-19 13:25:03 +02:00
await nutzap_wallet . announce_nutzap_info_event ( nut_wallet , self . client , self . keys )
2024-08-19 09:50:18 +02:00
2023-11-20 22:09:38 +01:00
class NotificationHandler ( HandleNotification ) :
client = self . client
dvm_config = self . dvm_config
keys = self . keys
2023-11-19 20:35:40 +01:00
2024-06-06 02:25:34 +02:00
async def handle ( self , relay_url , subscription_id , nostr_event : Event ) :
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . DEBUG . value :
print ( nostr_event . as_json ( ) )
2024-09-10 09:20:22 +02:00
if EventDefinitions . KIND_NIP90_EXTRACT_TEXT . as_u16 ( ) < = nostr_event . kind ( ) . as_u16 ( ) < = EventDefinitions . KIND_NIP90_GENERIC . as_u16 ( ) :
2024-06-06 02:25:34 +02:00
await handle_nip90_job_event ( nostr_event )
2024-09-10 09:20:22 +02:00
elif nostr_event . kind ( ) . as_u16 ( ) == EventDefinitions . KIND_ZAP . as_u16 ( ) :
2024-06-06 02:25:34 +02:00
await handle_zap ( nostr_event )
2024-09-10 09:20:22 +02:00
elif nostr_event . kind ( ) . as_u16 ( ) == EventDefinitions . KIND_NIP61_NUT_ZAP . as_u16 ( ) :
2024-08-19 09:50:18 +02:00
await handle_nutzap ( nostr_event )
2023-11-19 20:35:40 +01:00
2024-06-06 02:25:34 +02:00
async def handle_msg ( self , relay_url , msg ) :
2023-11-20 22:09:38 +01:00
return
2023-11-20 19:17:10 +01:00
2024-06-06 02:25:34 +02:00
async def handle_nip90_job_event ( nip90_event ) :
2024-03-19 12:33:26 +01:00
# decrypted encrypted events
2023-11-27 10:35:58 +01:00
nip90_event = check_and_decrypt_tags ( nip90_event , self . dvm_config )
2024-03-19 12:33:26 +01:00
# if event is encrypted, but we can't decrypt it (e.g. because its directed to someone else), return
2023-11-27 10:35:58 +01:00
if nip90_event is None :
2023-11-26 21:47:33 +01:00
return
2024-05-31 10:27:18 +02:00
2024-03-19 12:59:16 +01:00
task_is_free = False
user_has_active_subscription = False
cashu = " "
p_tag_str = " "
for tag in nip90_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " cashu " :
cashu = tag . as_vec ( ) [ 1 ]
elif tag . as_vec ( ) [ 0 ] == " p " :
p_tag_str = tag . as_vec ( ) [ 1 ]
if p_tag_str != " " and p_tag_str != self . dvm_config . PUBLIC_KEY :
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . DEBUG . value :
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] No public request, also not addressed to me. " )
2024-03-19 12:59:16 +01:00
return
2024-03-19 12:33:26 +01:00
# check if task is supported by the current DVM
2024-06-17 09:12:48 +02:00
task_supported , task = await check_task_is_supported ( nip90_event , client = self . client ,
2024-08-19 09:50:18 +02:00
config = self . dvm_config )
2024-03-19 12:33:26 +01:00
# if task is supported, continue, else do nothing.
if task_supported :
# fetch or add user contacting the DVM from/to local database
2024-06-06 02:25:34 +02:00
user = await get_or_add_user ( self . dvm_config . DB , nip90_event . author ( ) . to_hex ( ) , client = self . client ,
2024-06-07 23:45:26 +02:00
config = self . dvm_config , skip_meta = False )
2024-03-19 12:33:26 +01:00
# if user is blacklisted for some reason, send an error reaction and return
if user . isblacklisted :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nip90_event , " error " , client = self . client , dvm_config = self . dvm_config )
2024-03-19 12:33:26 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Request by blacklisted user, skipped " )
return
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . INFO . value :
print (
2024-09-15 11:48:02 +02:00
bcolors . MAGENTA + " [ " + self . dvm_config . NIP89 . NAME + " ] Received new Request: " + task + " from " + user . name + " ( " + PublicKey . parse ( user . npub ) . to_bech32 ( ) + " ) " + bcolors . ENDC )
2024-06-16 01:07:47 +02:00
duration = await input_data_file_duration ( nip90_event , dvm_config = self . dvm_config , client = self . client )
2023-11-20 23:18:05 +01:00
amount = get_amount_per_task ( task , self . dvm_config , duration )
if amount is None :
return
2024-03-19 12:33:26 +01:00
# If this is a subscription DVM and the Task is directed to us, check for active subscription
2024-03-18 19:44:44 +01:00
if dvm_config . NIP88 is not None and p_tag_str == self . dvm_config . PUBLIC_KEY :
2024-08-19 09:50:18 +02:00
# await send_job_status_reaction(nip90_event, "subscription-required", True, amount, self.client,
2024-07-11 10:55:44 +02:00
# "Checking Subscription Status, please wait..", self.dvm_config)
2024-03-18 19:44:44 +01:00
# if we stored in the database that the user has an active subscription, we don't need to check it
2024-03-19 11:13:20 +01:00
print ( " User Subscription: " + str ( user . subscribed ) + " Current time: " + str (
Timestamp . now ( ) . as_secs ( ) ) )
2024-03-19 12:33:26 +01:00
# if we have an entry in the db that user is subscribed, continue
2024-03-19 12:59:16 +01:00
if int ( user . subscribed ) > int ( Timestamp . now ( ) . as_secs ( ) ) :
2024-03-18 19:44:44 +01:00
print ( " User subscribed until: " + str ( Timestamp . from_secs ( user . subscribed ) . to_human_datetime ( ) ) )
user_has_active_subscription = True
2024-07-11 11:34:17 +02:00
await send_job_status_reaction ( nip90_event , " subscription-active " , True , amount ,
2024-06-07 23:45:26 +02:00
self . client , " User subscripton active until " +
Timestamp . from_secs (
int ( user . subscribed ) ) . to_human_datetime ( ) . replace (
" Z " , " " ) . replace ( " T " , " " ) + " GMT " , self . dvm_config )
2024-07-11 11:34:17 +02:00
# otherwise we check for an active subscription by checking recipie events
# sleep a little to not get rate limited
await asyncio . sleep ( 0.5 )
2024-03-18 19:44:44 +01:00
else :
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Checking Subscription status " )
2024-08-19 09:50:18 +02:00
# await send_job_status_reaction(nip90_event, "subscription-required", True, amount, self.client,
2024-07-11 10:49:49 +02:00
# "I Don't have information about subscription status, checking on the Nostr. This might take a few seconds",
# self.dvm_config)
2024-03-21 13:41:11 +01:00
2024-06-08 20:13:01 +02:00
subscription_status = await nip88_has_active_subscription ( PublicKey . parse ( user . npub ) ,
2024-06-13 10:49:09 +02:00
self . dvm_config . NIP88 . DTAG ,
self . client ,
self . dvm_config . PUBLIC_KEY )
2024-03-18 19:44:44 +01:00
if subscription_status [ " isActive " ] :
2024-06-07 23:45:26 +02:00
await send_job_status_reaction ( nip90_event , " subscription-required " , True , amount ,
self . client ,
" User subscripton active until " + Timestamp . from_secs ( int (
subscription_status [
" validUntil " ] ) ) . to_human_datetime ( ) . replace ( " Z " ,
" " ) . replace (
" T " , " " ) + " GMT " ,
self . dvm_config )
2024-07-11 11:34:17 +02:00
2024-03-19 10:28:46 +01:00
print ( " Checked Recipe: User subscribed until: " + str (
2024-03-18 19:44:44 +01:00
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 )
2024-07-11 11:34:17 +02:00
2024-08-19 09:50:18 +02:00
# sleep a little before sending next status update
2024-07-11 11:34:17 +02:00
2024-03-18 19:44:44 +01:00
else :
print ( " No active subscription found " )
2024-06-07 23:45:26 +02:00
await send_job_status_reaction ( nip90_event , " subscription-required " , True , amount ,
self . client ,
" No active subscription found. Manage your subscription at: " + self . dvm_config . SUBSCRIPTION_MANAGEMENT ,
self . dvm_config )
2024-03-18 19:44:44 +01:00
2023-11-23 13:30:09 +01:00
for dvm in self . dvm_config . SUPPORTED_DVMS :
2024-09-03 10:35:32 +02:00
if ( dvm . TASK == task or dvm . TASK == " generic " ) and dvm . FIX_COST == 0 and dvm . PER_UNIT_COST == 0 and dvm_config . NIP88 is None :
2023-11-20 23:18:05 +01:00
task_is_free = True
2023-11-27 00:02:56 +01:00
cashu_redeemed = False
if cashu != " " :
2023-11-28 18:25:59 +01:00
print ( cashu )
2024-06-07 23:45:26 +02:00
cashu_redeemed , cashu_message , redeem_amount , fees = await redeem_cashu ( cashu , self . dvm_config ,
self . client , int ( amount ) )
2023-11-28 18:25:59 +01:00
print ( cashu_message )
2023-11-28 08:16:34 +01:00
if cashu_message != " success " :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nip90_event , " error " , False , amount , self . client , cashu_message ,
2024-06-07 23:45:26 +02:00
self . dvm_config )
2023-11-27 23:37:44 +01:00
return
2023-11-26 21:47:33 +01:00
# if user is whitelisted or task is free, just do the job
2024-03-18 19:44:44 +01:00
if ( user . iswhitelisted or task_is_free or cashu_redeemed ) and (
p_tag_str == " " or p_tag_str ==
self . dvm_config . PUBLIC_KEY ) :
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . DEBUG . value :
print (
" [ " + self . dvm_config . NIP89 . NAME + " ] Free task or Whitelisted for task " + task +
" . Starting processing.. " )
2023-11-26 10:31:38 +01:00
2024-01-22 19:01:39 +01:00
if dvm_config . SEND_FEEDBACK_EVENTS :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nip90_event , " processing " , True , 0 ,
2024-06-07 23:45:26 +02:00
content = self . dvm_config . CUSTOM_PROCESSING_MESSAGE ,
client = self . client , dvm_config = self . dvm_config , user = user )
2023-11-22 19:20:34 +01:00
2023-12-09 23:58:15 +01:00
# 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
2024-06-06 02:25:34 +02:00
await do_work ( nip90_event , amount )
2024-03-18 19:44:44 +01:00
# 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 (
2024-03-19 12:33:26 +01:00
amount ) and dvm_config . NIP88 is None ) or (
p_tag_str == self . dvm_config . PUBLIC_KEY and user_has_active_subscription ) ) :
2024-03-18 19:44:44 +01:00
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 ( ) , subscribed = user . subscribed )
2023-11-26 10:31:38 +01:00
2024-03-18 19:44:44 +01:00
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 ) )
2023-11-26 10:31:38 +01:00
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nip90_event , " processing " , True , 0 ,
2024-06-07 23:45:26 +02:00
content = self . dvm_config . CUSTOM_PROCESSING_MESSAGE ,
client = self . client , dvm_config = self . dvm_config )
2023-11-26 10:31:38 +01:00
2024-06-06 02:25:34 +02:00
await do_work ( nip90_event , amount )
2023-11-26 10:31:38 +01:00
2023-11-26 21:47:33 +01:00
# else send a payment required event to user
2023-11-30 08:07:30 +01:00
elif p_tag_str == " " or p_tag_str == self . dvm_config . PUBLIC_KEY :
2023-11-20 23:18:05 +01:00
2024-03-18 19:44:44 +01:00
if dvm_config . NIP88 is not None :
2023-11-24 21:29:24 +01:00
print (
2024-03-18 19:44:44 +01:00
" [ " + self . dvm_config . NIP89 . NAME + " ] Hinting user for Subscription: " +
2023-11-24 22:07:00 +01:00
nip90_event . id ( ) . to_hex ( ) )
2024-08-19 09:50:18 +02:00
# await send_job_status_reaction(nip90_event, "subscription-required",
2024-07-11 11:05:22 +02:00
# False, 0, client=self.client,
# dvm_config=self.dvm_config)
2024-03-18 19:44:44 +01:00
else :
bid = 0
for tag in nip90_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == ' bid ' :
bid = int ( tag . as_vec ( ) [ 1 ] )
print (
" [ " + self . dvm_config . NIP89 . NAME + " ] Payment required: New Nostr " + task + " Job event: "
+ nip90_event . as_json ( ) )
if bid > 0 :
bid_offer = int ( bid / 1000 )
if bid_offer > = int ( amount ) :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nip90_event , " payment-required " , False ,
2024-06-07 23:45:26 +02:00
int ( amount ) , # bid_offer
client = self . client , dvm_config = self . dvm_config )
2024-03-18 19:44:44 +01:00
else : # If there is no bid, just request server rate from user
print (
2024-06-27 14:46:05 +02:00
" [ " + self . dvm_config . NIP89 . NAME + " ] Requesting payment for Event: " +
2024-03-18 19:44:44 +01:00
nip90_event . id ( ) . to_hex ( ) )
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nip90_event , " payment-required " ,
2024-06-07 23:45:26 +02:00
False , int ( amount ) , client = self . client ,
dvm_config = self . dvm_config )
2024-03-18 19:44:44 +01:00
2023-11-30 08:07:30 +01:00
else :
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . DEBUG . value :
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Job addressed to someone else, skipping.. " )
2023-11-28 16:20:56 +01:00
# else:
2023-11-29 15:09:35 +01:00
# print("[" + self.dvm_config.NIP89.NAME + "] Task " + task + " not supported on this DVM, skipping..")
2023-11-20 23:18:05 +01:00
2024-08-19 09:50:18 +02:00
async def handle_nutzap ( nut_zap_event ) :
if self . dvm_config . ENABLE_NUTZAP :
nut_wallet = await nutzap_wallet . get_nut_wallet ( self . client , self . keys )
if nut_wallet is not None :
received_amount , message , sender = await nutzap_wallet . reedeem_nutzap ( nut_zap_event , nut_wallet ,
self . client , self . keys )
user = await get_or_add_user ( db = self . dvm_config . DB , npub = sender , client = self . client ,
config = self . dvm_config )
2024-08-19 13:25:03 +02:00
zapped_event = None
for tag in nut_zap_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == ' e ' :
zapped_event = await get_event_by_id ( tag . as_vec ( ) [ 1 ] , client = self . client ,
config = self . dvm_config )
2024-08-19 09:50:18 +02:00
2024-08-19 13:25:03 +02:00
if zapped_event is not None :
if zapped_event . kind ( ) == EventDefinitions . KIND_FEEDBACK :
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 )
elif tag . as_vec ( ) [ 0 ] == ' e ' :
job_event = await get_event_by_id ( tag . as_vec ( ) [ 1 ] , client = self . client ,
config = self . dvm_config )
if job_event is not None :
job_event = check_and_decrypt_tags ( job_event , self . dvm_config )
if job_event is None :
return
else :
return
elif tag . as_vec ( ) [ 0 ] == ' status ' :
status = tag . as_vec ( ) [ 1 ]
2024-08-20 10:42:06 +02:00
2024-08-19 13:25:03 +02:00
# if a reaction by us got zapped
print ( status )
task_supported , task = await check_task_is_supported ( job_event , client = self . client ,
config = self . dvm_config )
if job_event is not None and task_supported :
print ( " NutZap received for NIP90 task: " + str ( received_amount ) + " Sats from " + str (
2024-09-09 11:25:51 +02:00
user . name + " ( " + user . npub + " ) " ) )
2024-08-19 13:25:03 +02:00
if amount < = received_amount :
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Payment-request fulfilled... " )
await send_job_status_reaction ( job_event , " processing " , client = self . client ,
content = self . dvm_config . CUSTOM_PROCESSING_MESSAGE ,
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 :
self . job_list [ index ] . is_paid = True
await check_and_return_event ( self . job_list [ index ] . result , job_event )
elif not ( self . job_list [ index ] ) . is_processed :
# If payment-required appears before processing
self . job_list . pop ( index )
print ( " Starting work... " )
await do_work ( job_event , received_amount )
else :
print ( " Job not in List, but starting work... " )
await do_work ( job_event , received_amount )
else :
await send_job_status_reaction ( job_event , " payment-rejected " ,
False , received_amount , client = self . client ,
dvm_config = self . dvm_config )
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Invoice was not paid sufficiently " )
2024-08-19 09:50:18 +02:00
if self . dvm_config . ENABLE_AUTO_MELT :
balance = nut_wallet . balance + received_amount
if balance > self . dvm_config . AUTO_MELT_AMOUNT :
2024-08-19 13:25:03 +02:00
lud16 = self . admin_config . LUD16
npub = self . dvm_config . PUBLIC_KEY
2024-08-19 09:50:18 +02:00
mint_index = 0
await nutzap_wallet . melt_cashu ( nut_wallet , self . dvm_config . NUZAP_MINTS [ mint_index ] ,
self . dvm_config . AUTO_MELT_AMOUNT , self . client , self . keys ,
lud16 , npub )
else :
print ( " NutZaps not enabled for this DVM. " )
2024-06-06 02:25:34 +02:00
async def handle_zap ( zap_event ) :
2023-11-20 23:18:05 +01:00
try :
2024-07-11 10:10:42 +02:00
invoice_amount , zapped_event , sender , message , anon = await parse_zap_event_tags ( zap_event ,
2024-08-19 09:50:18 +02:00
self . keys ,
self . dvm_config . NIP89 . NAME ,
self . client ,
self . dvm_config )
2024-06-07 23:45:26 +02:00
user = await get_or_add_user ( db = self . dvm_config . DB , npub = sender , client = self . client ,
config = self . dvm_config )
2023-11-20 23:18:05 +01:00
if zapped_event is not None :
2024-04-15 11:06:46 +02:00
if zapped_event . kind ( ) == EventDefinitions . KIND_FEEDBACK :
2023-11-26 21:47:33 +01:00
2023-11-20 23:18:05 +01:00
amount = 0
job_event = None
2023-11-26 21:47:33 +01:00
p_tag_str = " "
2024-03-18 19:44:44 +01:00
status = " "
2023-11-20 23:18:05 +01:00
for tag in zapped_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == ' amount ' :
amount = int ( float ( tag . as_vec ( ) [ 1 ] ) / 1000 )
elif tag . as_vec ( ) [ 0 ] == ' e ' :
2024-08-19 09:50:18 +02:00
job_event = await get_event_by_id ( tag . as_vec ( ) [ 1 ] , client = self . client ,
config = self . dvm_config )
2023-11-27 10:35:58 +01:00
if job_event is not None :
job_event = check_and_decrypt_tags ( job_event , self . dvm_config )
2023-11-27 23:37:44 +01:00
if job_event is None :
return
2023-11-27 10:35:58 +01:00
else :
return
2024-03-18 19:44:44 +01:00
elif tag . as_vec ( ) [ 0 ] == ' status ' :
status = tag . as_vec ( ) [ 1 ]
print ( status )
2023-11-26 21:47:33 +01:00
2023-11-27 10:35:58 +01:00
# if a reaction by us got zapped
2024-03-18 19:44:44 +01:00
print ( status )
2024-04-15 11:06:46 +02:00
if job_event . kind ( ) == EventDefinitions . KIND_NIP88_SUBSCRIBE_EVENT :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( job_event , " subscription-success " , client = self . client ,
2024-06-07 23:45:26 +02:00
dvm_config = self . dvm_config , user = user )
2024-03-18 19:44:44 +01:00
else :
2024-06-17 09:12:48 +02:00
task_supported , task = await check_task_is_supported ( job_event , client = self . client ,
2024-08-19 09:50:18 +02:00
config = self . dvm_config )
2024-03-18 19:44:44 +01:00
if job_event is not None and task_supported :
print ( " Zap received for NIP90 task: " + str ( invoice_amount ) + " Sats from " + str (
2024-09-09 11:25:51 +02:00
user . name + " ( " + user . npub + " ) " ) )
2024-03-18 19:44:44 +01:00
if amount < = invoice_amount :
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Payment-request fulfilled... " )
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( job_event , " processing " , client = self . client ,
2024-06-07 23:45:26 +02:00
content = self . dvm_config . CUSTOM_PROCESSING_MESSAGE ,
dvm_config = self . dvm_config , user = user )
2024-03-18 19:44:44 +01:00
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 :
self . job_list [ index ] . is_paid = True
2024-06-06 02:25:34 +02:00
await check_and_return_event ( self . job_list [ index ] . result , job_event )
2024-03-18 19:44:44 +01:00
elif not ( self . job_list [ index ] ) . is_processed :
# If payment-required appears before processing
self . job_list . pop ( index )
print ( " Starting work... " )
2024-06-06 02:25:34 +02:00
await do_work ( job_event , invoice_amount )
2024-03-18 19:44:44 +01:00
else :
print ( " Job not in List, but starting work... " )
2024-06-06 02:25:34 +02:00
await do_work ( job_event , invoice_amount )
2023-11-20 23:18:05 +01:00
2024-03-18 19:44:44 +01:00
else :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( job_event , " payment-rejected " ,
2024-06-07 23:45:26 +02:00
False , invoice_amount , client = self . client ,
dvm_config = self . dvm_config )
2024-03-18 19:44:44 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Invoice was not paid sufficiently " )
2024-04-15 11:06:46 +02:00
elif zapped_event . kind ( ) == EventDefinitions . KIND_NIP88_SUBSCRIBE_EVENT :
2024-03-18 19:44:44 +01:00
print ( " new subscription, doing nothing " )
2023-11-20 23:18:05 +01:00
elif zapped_event . kind ( ) in EventDefinitions . ANY_RESULT :
2023-11-29 15:09:35 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] "
2023-11-24 22:07:00 +01:00
" Someone zapped the result of an exisiting Task. Nice " )
2023-11-20 23:18:05 +01:00
elif not anon :
2023-11-29 15:09:35 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Note Zap received for DVM balance: " +
2024-09-09 11:25:51 +02:00
str ( invoice_amount ) + " Sats from " + str ( user . name + " ( " + user . npub + " ) " ) )
2024-05-17 22:29:58 +02:00
# update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client,
# config=self.dvm_config)
2023-11-19 18:47:05 +01:00
2024-05-31 10:27:18 +02:00
# a regular note
2024-03-18 22:50:48 +01:00
elif not anon and dvm_config . NIP88 is None :
2023-11-29 15:09:35 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Profile Zap received for DVM balance: " +
2024-09-09 11:25:51 +02:00
str ( invoice_amount ) + " Sats from " + str ( user . name + " ( " + user . npub + " ) " ) )
2024-05-31 10:27:18 +02:00
# update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client,
# config=self.dvm_config)
2023-11-20 22:09:38 +01:00
2023-11-20 23:18:05 +01:00
except Exception as e :
2023-11-29 15:09:35 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Error during content decryption: " + str ( e ) )
2023-11-20 22:09:38 +01:00
2024-06-06 02:25:34 +02:00
async def check_event_has_not_unfinished_job_input ( nevent , append , client , dvmconfig ) :
2024-06-17 09:12:48 +02:00
task_supported , task = await check_task_is_supported ( nevent , client , config = dvmconfig )
2023-11-20 22:09:38 +01:00
if not task_supported :
return False
for tag in nevent . tags ( ) :
if tag . as_vec ( ) [ 0 ] == ' i ' :
if len ( tag . as_vec ( ) ) < 3 :
print ( " Job Event missing/malformed i tag, skipping.. " )
return False
else :
input = tag . as_vec ( ) [ 1 ]
input_type = tag . as_vec ( ) [ 2 ]
if input_type == " job " :
2024-06-17 09:12:48 +02:00
evt = await get_referenced_event_by_id ( event_id = input , client = client ,
2024-08-19 09:50:18 +02:00
kinds = EventDefinitions . ANY_RESULT ,
dvm_config = dvmconfig )
2023-11-20 22:09:38 +01:00
if evt is None :
if append :
2023-11-26 10:31:38 +01:00
job_ = RequiredJobToWatch ( event = nevent , timestamp = Timestamp . now ( ) . as_secs ( ) )
self . jobs_on_hold_list . append ( job_ )
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( nevent , " chain-scheduled " , True , 0 ,
2024-06-07 23:45:26 +02:00
client = client , dvm_config = dvmconfig )
2023-11-20 22:09:38 +01:00
return False
2023-11-19 18:47:05 +01:00
else :
2023-11-20 22:09:38 +01:00
return True
2023-11-19 18:47:05 +01:00
2024-06-06 02:25:34 +02:00
async def check_and_return_event ( data , original_event : Event ) :
2023-12-10 14:17:06 +01:00
amount = 0
2023-11-21 10:03:04 +01:00
for x in self . job_list :
2023-11-26 10:31:38 +01:00
if x . event == original_event :
2023-11-19 18:47:05 +01:00
is_paid = x . is_paid
amount = x . amount
2023-11-20 22:09:38 +01:00
x . result = data
x . is_processed = True
2023-11-22 19:20:34 +01:00
if self . dvm_config . SHOW_RESULT_BEFORE_PAYMENT and not is_paid :
2024-06-06 02:25:34 +02:00
await send_nostr_reply_event ( data , original_event . as_json ( ) )
await send_job_status_reaction ( original_event , " success " , amount ,
2024-06-07 23:45:26 +02:00
dvm_config = self . dvm_config ,
) # or payment-required, or both?
2023-11-22 19:20:34 +01:00
elif not self . dvm_config . SHOW_RESULT_BEFORE_PAYMENT and not is_paid :
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( original_event , " success " , amount ,
2024-06-07 23:45:26 +02:00
dvm_config = self . dvm_config ,
) # or payment-required, or both?
2023-11-20 22:09:38 +01:00
2023-11-22 19:20:34 +01:00
if self . dvm_config . SHOW_RESULT_BEFORE_PAYMENT and is_paid :
2023-11-21 10:03:04 +01:00
self . job_list . remove ( x )
2023-11-22 19:20:34 +01:00
elif not self . dvm_config . SHOW_RESULT_BEFORE_PAYMENT and is_paid :
2023-11-21 10:03:04 +01:00
self . job_list . remove ( x )
2024-06-06 02:25:34 +02:00
await send_nostr_reply_event ( data , original_event . as_json ( ) )
2023-11-19 18:47:05 +01:00
break
2024-06-17 09:12:48 +02:00
task = await get_task ( original_event , self . client , self . dvm_config )
2023-11-30 08:07:30 +01:00
for dvm in self . dvm_config . SUPPORTED_DVMS :
2024-08-29 16:00:38 +02:00
if task == dvm . TASK or dvm . TASK == " generic " :
2023-12-09 23:58:15 +01:00
try :
2024-06-17 09:12:48 +02:00
post_processed = await dvm . post_process ( data , original_event )
2024-06-06 02:25:34 +02:00
await send_nostr_reply_event ( post_processed , original_event . as_json ( ) )
2023-12-09 23:58:15 +01:00
except Exception as e :
2024-03-19 14:28:08 +01:00
print ( e )
2023-12-10 14:17:06 +01:00
# Zapping back by error in post-processing is a risk for the DVM because work has been done,
# but maybe something with parsing/uploading failed. Try to avoid errors here as good as possible
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( original_event , " error " ,
2024-06-07 23:45:26 +02:00
content = " Error in Post-processing: " + str ( e ) ,
dvm_config = self . dvm_config ,
)
2023-12-12 10:37:59 +01:00
if amount > 0 and self . dvm_config . LNBITS_ADMIN_KEY != " " :
2024-06-06 02:25:34 +02:00
user = await get_or_add_user ( self . dvm_config . DB , original_event . author ( ) . to_hex ( ) ,
2024-06-07 23:45:26 +02:00
client = self . client , config = self . dvm_config )
2023-12-10 14:17:06 +01:00
print ( user . lud16 + " " + str ( amount ) )
2023-12-29 17:58:07 +01:00
bolt11 = zaprequest ( user . lud16 , amount , " Couldn ' t finish job, returning sats " ,
2024-01-19 18:19:28 +01:00
original_event , " " ,
self . keys , self . dvm_config . RELAY_LIST , zaptype = " private " )
2023-12-10 14:17:06 +01:00
if bolt11 is None :
print ( " Receiver has no Lightning address, can ' t zap back. " )
return
try :
payment_hash = pay_bolt11_ln_bits ( bolt11 , self . dvm_config )
except Exception as e :
print ( e )
2023-11-20 22:09:38 +01:00
2024-06-06 02:25:34 +02:00
async def send_nostr_reply_event ( content , original_event_as_str ) :
2023-11-21 23:51:48 +01:00
original_event = Event . from_json ( original_event_as_str )
2024-01-03 20:17:25 +01:00
request_tag = Tag . parse ( [ " request " , original_event_as_str ] )
2023-11-21 23:51:48 +01:00
e_tag = Tag . parse ( [ " e " , original_event . id ( ) . to_hex ( ) ] )
2024-01-19 18:19:28 +01:00
p_tag = Tag . parse ( [ " p " , original_event . author ( ) . to_hex ( ) ] )
2023-11-21 23:51:48 +01:00
alt_tag = Tag . parse ( [ " alt " , " This is the result of a NIP90 DVM AI task with kind " + str (
2024-09-10 09:20:22 +02:00
original_event . kind ( ) . as_u16 ( ) ) + " . The task was: " + original_event . content ( ) ] )
2023-11-21 23:51:48 +01:00
status_tag = Tag . parse ( [ " status " , " success " ] )
2023-11-27 23:37:44 +01:00
reply_tags = [ request_tag , e_tag , p_tag , alt_tag , status_tag ]
2024-06-03 20:24:29 +02:00
relay_tag = None
for tag in original_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " relays " :
relay_tag = tag
2024-09-05 16:57:25 +02:00
if tag . as_vec ( ) [ 0 ] == " client " :
client = tag . as_vec ( ) [ 1 ]
reply_tags . append ( Tag . parse ( [ " client " , client ] ) )
2024-06-03 20:24:29 +02:00
if relay_tag is not None :
reply_tags . append ( relay_tag )
2023-11-26 10:31:38 +01:00
encrypted = False
for tag in original_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " encrypted " :
encrypted = True
encrypted_tag = Tag . parse ( [ " encrypted " ] )
reply_tags . append ( encrypted_tag )
2023-11-21 23:51:48 +01:00
for tag in original_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " i " :
2023-11-26 10:31:38 +01:00
i_tag = tag
if not encrypted :
reply_tags . append ( i_tag )
if encrypted :
2024-01-03 20:17:25 +01:00
print ( content )
2024-01-19 18:19:28 +01:00
content = nip04_encrypt ( self . keys . secret_key ( ) , PublicKey . from_hex ( original_event . author ( ) . to_hex ( ) ) ,
2023-11-26 10:31:38 +01:00
content )
2023-11-21 23:51:48 +01:00
2024-09-10 09:20:22 +02:00
reply_event = EventBuilder ( Kind ( original_event . kind ( ) . as_u16 ( ) + 1000 ) , str ( content ) , reply_tags ) . to_event (
2024-03-19 12:33:26 +01:00
self . keys )
2023-11-21 23:51:48 +01:00
2024-06-03 20:24:29 +02:00
# send_event(reply_event, client=self.client, dvm_config=self.dvm_config)
2024-06-06 02:25:34 +02:00
await send_event_outbox ( reply_event , client = self . client , dvm_config = self . dvm_config )
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . DEBUG . value :
print ( bcolors . GREEN + " [ " + self . dvm_config . NIP89 . NAME + " ] " + str (
2024-09-10 09:20:22 +02:00
original_event . kind ( ) . as_u16 ( ) + 1000 ) + " Job Response event sent: " + reply_event . as_json ( ) + bcolors . ENDC )
2024-06-13 10:49:09 +02:00
elif self . dvm_config . LOGLEVEL . value > = LogLevel . INFO . value :
print ( bcolors . GREEN + " [ " + self . dvm_config . NIP89 . NAME + " ] " + str (
2024-09-10 09:20:22 +02:00
original_event . kind ( ) . as_u16 ( ) + 1000 ) + " Job Response event sent: " + reply_event . id ( ) . to_hex ( ) + bcolors . ENDC )
2023-11-20 22:09:38 +01:00
2024-06-06 02:25:34 +02:00
async def send_job_status_reaction ( original_event , status , is_paid = True , amount = 0 , client = None ,
2024-06-07 23:45:26 +02:00
content = None ,
dvm_config = None , user = None ) :
2023-11-26 10:31:38 +01:00
2024-06-17 09:12:48 +02:00
task = await get_task ( original_event , client = client , dvm_config = dvm_config )
2024-02-05 18:20:22 +01:00
alt_description , reaction = build_status_reaction ( status , task , amount , content , dvm_config )
2023-11-20 23:18:05 +01:00
e_tag = Tag . parse ( [ " e " , original_event . id ( ) . to_hex ( ) ] )
2024-01-19 18:19:28 +01:00
p_tag = Tag . parse ( [ " p " , original_event . author ( ) . to_hex ( ) ] )
2023-11-20 23:18:05 +01:00
alt_tag = Tag . parse ( [ " alt " , alt_description ] )
status_tag = Tag . parse ( [ " status " , status ] )
2024-06-03 20:24:29 +02:00
2023-11-27 23:37:44 +01:00
reply_tags = [ e_tag , alt_tag , status_tag ]
2024-06-03 20:24:29 +02:00
relay_tag = None
for tag in original_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " relays " :
relay_tag = tag
break
if relay_tag is not None :
reply_tags . append ( relay_tag )
2023-11-27 23:37:44 +01:00
encryption_tags = [ ]
2023-11-26 10:31:38 +01:00
2023-11-27 23:37:44 +01:00
encrypted = False
2023-11-27 10:35:58 +01:00
for tag in original_event . tags ( ) :
2023-11-27 23:37:44 +01:00
if tag . as_vec ( ) [ 0 ] == " encrypted " :
encrypted = True
encrypted_tag = Tag . parse ( [ " encrypted " ] )
encryption_tags . append ( encrypted_tag )
2023-11-26 10:31:38 +01:00
2023-11-27 23:37:44 +01:00
if encrypted :
encryption_tags . append ( p_tag )
2024-03-22 13:36:57 +01:00
encryption_tags . append ( e_tag )
2023-11-27 23:37:44 +01:00
else :
reply_tags . append ( p_tag )
2023-11-20 23:18:05 +01:00
if status == " success " or status == " error " : #
2023-11-21 10:03:04 +01:00
for x in self . job_list :
2023-11-26 10:31:38 +01:00
if x . event == original_event :
2023-11-20 23:18:05 +01:00
is_paid = x . is_paid
amount = x . amount
break
bolt11 = " "
payment_hash = " "
expires = original_event . created_at ( ) . as_secs ( ) + ( 60 * 60 * 24 )
2024-03-18 19:44:44 +01:00
if status == " payment-required " or (
status == " processing " and not is_paid ) :
2023-11-21 23:51:48 +01:00
if dvm_config . LNBITS_INVOICE_KEY != " " :
2023-11-20 23:18:05 +01:00
try :
2024-03-04 15:42:37 +01:00
bolt11 , payment_hash = create_bolt11_ln_bits ( amount , dvm_config )
2023-11-20 23:18:05 +01:00
except Exception as e :
print ( e )
2024-01-06 17:47:22 +01:00
try :
bolt11 , payment_hash = create_bolt11_lud16 ( dvm_config . LN_ADDRESS ,
2024-03-04 15:42:37 +01:00
amount )
2024-01-06 17:47:22 +01:00
except Exception as e :
2024-03-04 15:42:37 +01:00
print ( e )
bolt11 = None
2024-01-06 17:47:22 +01:00
elif dvm_config . LN_ADDRESS != " " :
try :
bolt11 , payment_hash = create_bolt11_lud16 ( dvm_config . LN_ADDRESS , amount )
except Exception as e :
print ( e )
bolt11 = None
2023-11-20 23:18:05 +01:00
2023-11-26 10:31:38 +01:00
if not any ( x . event == original_event for x in self . job_list ) :
2023-11-21 10:03:04 +01:00
self . job_list . append (
2023-11-26 10:31:38 +01:00
JobToWatch ( event = original_event ,
2023-11-20 23:18:05 +01:00
timestamp = original_event . created_at ( ) . as_secs ( ) ,
amount = amount ,
is_paid = is_paid ,
status = status , result = " " , is_processed = False , bolt11 = bolt11 ,
payment_hash = payment_hash ,
2023-11-26 10:31:38 +01:00
expires = expires ) )
2023-11-24 21:29:24 +01:00
# print(str(self.job_list))
2023-11-21 23:51:48 +01:00
if ( status == " payment-required " or status == " payment-rejected " or (
status == " processing " and not is_paid )
or ( status == " success " and not is_paid ) ) :
2023-11-20 23:18:05 +01:00
2024-02-20 10:00:52 +01:00
if dvm_config . LNBITS_INVOICE_KEY != " " and bolt11 is not None :
2023-11-20 23:18:05 +01:00
amount_tag = Tag . parse ( [ " amount " , str ( amount * 1000 ) , bolt11 ] )
2023-11-20 22:09:38 +01:00
else :
2023-11-20 23:18:05 +01:00
amount_tag = Tag . parse ( [ " amount " , str ( amount * 1000 ) ] ) # to millisats
2023-11-27 23:37:44 +01:00
reply_tags . append ( amount_tag )
if encrypted :
content_tag = Tag . parse ( [ " content " , reaction ] )
reply_tags . append ( content_tag )
str_tags = [ ]
for element in reply_tags :
str_tags . append ( element . as_vec ( ) )
content = json . dumps ( str_tags )
2024-01-19 18:19:28 +01:00
content = nip04_encrypt ( self . keys . secret_key ( ) , PublicKey . from_hex ( original_event . author ( ) . to_hex ( ) ) ,
2023-11-27 23:37:44 +01:00
content )
reply_tags = encryption_tags
else :
content = reaction
2023-11-21 23:51:48 +01:00
2024-02-17 20:40:56 +01:00
keys = Keys . parse ( dvm_config . PRIVATE_KEY )
2023-11-27 23:37:44 +01:00
reaction_event = EventBuilder ( EventDefinitions . KIND_FEEDBACK , str ( content ) , reply_tags ) . to_event ( keys )
2024-06-03 20:24:29 +02:00
# send_event(reaction_event, client=self.client, dvm_config=self.dvm_config)
2024-06-06 02:25:34 +02:00
await send_event_outbox ( reaction_event , client = self . client , dvm_config = self . dvm_config )
2024-05-31 10:27:18 +02:00
2024-06-13 10:49:09 +02:00
if self . dvm_config . LOGLEVEL . value > = LogLevel . DEBUG . value :
print ( bcolors . YELLOW + " [ " + self . dvm_config . NIP89 . NAME + " ] " + " Sent Kind " + str (
2024-09-10 09:20:22 +02:00
EventDefinitions . KIND_FEEDBACK . as_u16 ( ) ) + " Reaction: " + status + " " + reaction_event . as_json ( ) + bcolors . ENDC )
2024-06-13 10:49:09 +02:00
elif self . dvm_config . LOGLEVEL . value > = LogLevel . INFO . value :
print ( bcolors . YELLOW + " [ " + self . dvm_config . NIP89 . NAME + " ] " + " Sent Kind " + str (
2024-09-10 09:20:22 +02:00
EventDefinitions . KIND_FEEDBACK . as_u16 ( ) ) + " Reaction: " + status + " " + reaction_event . id ( ) . to_hex ( ) + bcolors . ENDC )
2024-06-13 10:49:09 +02:00
2023-11-26 10:31:38 +01:00
return reaction_event . as_json ( )
2023-11-20 23:18:05 +01:00
2024-06-26 10:55:17 +02:00
async def _read_stream ( stream , cb ) :
while True :
line = await stream . readline ( )
if line :
cb ( line )
else :
break
async def _stream_subprocess ( cmd , stdout_cb , stderr_cb ) :
process = await asyncio . create_subprocess_exec ( * cmd ,
stdout = asyncio . subprocess . PIPE ,
stderr = asyncio . subprocess . PIPE )
async def run_subprocess ( python_bin , dvm_config , request_form , stdout_cb , stderr_cb ) :
print ( " Running subprocess, please wait.. " )
2024-06-14 20:35:34 +02:00
process = await asyncio . create_subprocess_exec (
python_bin , dvm_config . SCRIPT ,
' --request ' , json . dumps ( request_form ) ,
' --identifier ' , dvm_config . IDENTIFIER ,
' --output ' , ' output.txt ' ,
stdout = asyncio . subprocess . PIPE ,
stderr = asyncio . subprocess . PIPE
)
2024-06-26 10:55:17 +02:00
await asyncio . gather (
_read_stream ( process . stdout , stdout_cb ) ,
_read_stream ( process . stderr , stderr_cb )
)
return await process . wait ( )
2024-06-14 20:35:34 +02:00
2024-08-19 09:50:18 +02:00
# stdout, stderr = await process.communicate()
2024-06-14 20:35:34 +02:00
2024-08-19 09:50:18 +02:00
# retcode = process.returncode
2024-06-26 10:55:17 +02:00
2024-08-19 09:50:18 +02:00
# if retcode != 0:
2024-06-26 10:55:17 +02:00
# print(f"Error: {stderr.decode()}")
2024-08-19 09:50:18 +02:00
# else:
2024-06-26 10:55:17 +02:00
# print(f"Output: {stdout.decode()}")
2024-06-14 20:35:34 +02:00
2024-08-19 09:50:18 +02:00
# return retcode
2024-06-14 20:35:34 +02:00
2024-06-06 02:25:34 +02:00
async def do_work ( job_event , amount ) :
2024-03-19 12:33:26 +01:00
if ( (
2024-09-10 09:20:22 +02:00
EventDefinitions . KIND_NIP90_EXTRACT_TEXT . as_u16 ( ) < = job_event . kind ( ) . as_u16 ( ) < = EventDefinitions . KIND_NIP90_GENERIC . as_u16 ( ) )
or job_event . kind ( ) . as_u16 ( ) == EventDefinitions . KIND_DM . as_u16 ( ) ) :
2023-11-20 22:09:38 +01:00
2024-06-17 09:12:48 +02:00
task = await get_task ( job_event , client = self . client , dvm_config = self . dvm_config )
2023-11-28 07:32:57 +01:00
2023-11-23 13:30:09 +01:00
for dvm in self . dvm_config . SUPPORTED_DVMS :
2023-12-30 20:36:38 +01:00
result = " "
2023-11-20 22:09:38 +01:00
try :
2024-08-29 16:00:38 +02:00
if task == dvm . TASK or dvm . TASK == " generic " :
2023-12-16 00:42:06 +01:00
2024-08-19 09:50:18 +02:00
request_form = await dvm . create_request_from_nostr_event ( job_event , self . client ,
self . dvm_config )
2023-12-17 14:38:58 +01:00
if dvm_config . USE_OWN_VENV :
2023-12-20 18:21:40 +01:00
python_location = " /bin/python "
if platform == " win32 " :
python_location = " /Scripts/python "
2023-12-29 17:58:07 +01:00
python_bin = ( r ' cache/venvs/ ' + os . path . basename ( dvm_config . SCRIPT ) . split ( " .py " ) [ 0 ]
+ python_location )
2024-08-19 09:50:18 +02:00
# retcode = subprocess.call([python_bin, dvm_config.SCRIPT,
2024-06-14 20:35:34 +02:00
# '--request', json.dumps(request_form),
# '--identifier', dvm_config.IDENTIFIER,
# '--output', 'output.txt'])
2024-06-26 10:55:17 +02:00
await run_subprocess ( python_bin , dvm_config , request_form ,
lambda x : print ( " %s " % x . decode ( " utf-8 " ) . replace ( " \n " , " " ) ) ,
lambda x : print ( " STDERR: %s " % x . decode ( " utf-8 " ) ) )
2023-12-17 14:38:58 +01:00
print ( " Finished processing, loading data.. " )
2024-06-25 11:35:08 +02:00
with open ( os . path . abspath ( ' output.txt ' ) , encoding = " utf-8 " ) as f :
2023-12-19 15:46:30 +01:00
resultall = f . readlines ( )
for line in resultall :
if line != ' \n ' :
result + = line
2023-12-17 14:38:58 +01:00
os . remove ( os . path . abspath ( ' output.txt ' ) )
2023-12-30 20:36:38 +01:00
assert not result . startswith ( " Error: " )
print ( result )
2023-12-29 17:58:07 +01:00
else : # Some components might have issues with running code in otuside venv.
# We install locally in these cases for now
2024-06-06 02:25:34 +02:00
result = await dvm . process ( request_form )
2023-11-30 08:07:30 +01:00
try :
2024-06-17 09:12:48 +02:00
post_processed = await dvm . post_process ( result , job_event )
2024-06-06 02:25:34 +02:00
await send_nostr_reply_event ( post_processed , job_event . as_json ( ) )
2023-11-30 08:07:30 +01:00
except Exception as e :
2024-06-03 20:24:29 +02:00
print ( bcolors . RED + " [ " + self . dvm_config . NIP89 . NAME + " ] Error: " + str (
e ) + bcolors . ENDC )
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( job_event , " error " , content = str ( e ) ,
2024-06-07 23:45:26 +02:00
dvm_config = self . dvm_config )
2023-11-20 22:09:38 +01:00
except Exception as e :
2024-05-31 10:27:18 +02:00
print (
bcolors . RED + " [ " + self . dvm_config . NIP89 . NAME + " ] Error: " + str ( e ) + bcolors . ENDC )
2023-11-30 08:07:30 +01:00
# we could send the exception here to the user, but maybe that's not a good idea after all.
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( job_event , " error " , content = result ,
2024-06-07 23:45:26 +02:00
dvm_config = self . dvm_config )
2023-12-09 23:58:15 +01:00
# Zapping back the user on error
2023-12-12 10:37:59 +01:00
if amount > 0 and self . dvm_config . LNBITS_ADMIN_KEY != " " :
2024-06-06 02:25:34 +02:00
user = await get_or_add_user ( self . dvm_config . DB , job_event . author ( ) . to_hex ( ) ,
2024-06-07 23:45:26 +02:00
client = self . client , config = self . dvm_config )
2023-12-09 23:58:15 +01:00
print ( user . lud16 + " " + str ( amount ) )
2024-03-04 15:42:37 +01:00
bolt11 = zaprequest ( user . lud16 , amount , " Couldn ' t finish job, returning sats " , job_event ,
2024-03-19 00:24:49 +01:00
PublicKey . parse ( user . npub ) ,
2023-12-29 23:13:06 +01:00
self . keys , self . dvm_config . RELAY_LIST , zaptype = " private " )
2023-12-09 23:58:15 +01:00
if bolt11 is None :
print ( " Receiver has no Lightning address, can ' t zap back. " )
return
try :
payment_hash = pay_bolt11_ln_bits ( bolt11 , self . dvm_config )
except Exception as e :
print ( e )
2023-11-20 22:09:38 +01:00
return
2024-06-07 23:45:26 +02:00
asyncio . create_task ( self . client . handle_notifications ( NotificationHandler ( ) ) )
2024-03-04 15:42:37 +01:00
2024-06-07 23:45:26 +02:00
while True :
2024-03-04 15:42:37 +01:00
for dvm in self . dvm_config . SUPPORTED_DVMS :
2024-06-07 23:45:26 +02:00
await dvm . schedule ( self . dvm_config )
2024-03-04 15:42:37 +01:00
2023-11-21 10:03:04 +01:00
for job in self . job_list :
2024-02-20 10:29:29 +01:00
if job . bolt11 != " " and job . payment_hash != " " and not job . payment_hash is None and not job . is_paid :
2024-06-13 10:49:09 +02:00
ispaid = check_bolt11_ln_bits_is_paid ( job . payment_hash , self . dvm_config )
2023-11-26 10:31:38 +01:00
if ispaid and job . is_paid is False :
print ( " is paid " )
2024-03-18 19:44:44 +01:00
job . is_paid = True
amount = parse_amount_from_bolt11_invoice ( job . bolt11 )
2023-11-26 10:31:38 +01:00
2023-11-20 22:09:38 +01:00
job . is_paid = True
2024-06-06 02:25:34 +02:00
await send_job_status_reaction ( job . event , " processing " , True , 0 ,
2024-06-13 10:49:09 +02:00
content = self . dvm_config . CUSTOM_PROCESSING_MESSAGE ,
client = self . client ,
dvm_config = self . dvm_config )
2023-11-29 15:09:35 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] doing work from joblist " )
2024-06-06 02:25:34 +02:00
await do_work ( job . event , amount )
2023-11-26 10:31:38 +01:00
elif ispaid is None : # invoice expired
self . job_list . remove ( job )
2023-11-20 22:09:38 +01:00
if Timestamp . now ( ) . as_secs ( ) > job . expires :
2023-11-26 10:31:38 +01:00
self . job_list . remove ( job )
2023-11-20 22:09:38 +01:00
2023-11-21 10:03:04 +01:00
for job in self . jobs_on_hold_list :
2024-06-13 10:49:09 +02:00
if await check_event_has_not_unfinished_job_input ( job . event , False , client = self . client ,
dvmconfig = self . dvm_config ) :
2024-06-06 02:25:34 +02:00
await handle_nip90_job_event ( nip90_event = job . event )
2023-11-20 22:09:38 +01:00
try :
2023-11-21 10:03:04 +01:00
self . jobs_on_hold_list . remove ( job )
2023-11-20 22:09:38 +01:00
except :
2023-11-29 15:09:35 +01:00
print ( " [ " + self . dvm_config . NIP89 . NAME + " ] Error removing Job on Hold from List after expiry " )
2023-11-20 22:09:38 +01:00
if Timestamp . now ( ) . as_secs ( ) > job . timestamp + 60 * 20 : # remove jobs to look for after 20 minutes..
2023-11-21 10:03:04 +01:00
self . jobs_on_hold_list . remove ( job )
2023-11-20 22:09:38 +01:00
2024-06-07 23:45:26 +02:00
await asyncio . sleep ( 1 )