2023-11-23 11:53:57 +01:00
import json
2023-11-29 10:46:51 +01:00
import os
import signal
2023-11-23 11:53:57 +01:00
import time
2023-11-23 13:30:09 +01:00
from datetime import timedelta
2023-11-23 11:53:57 +01:00
2023-11-24 22:07:00 +01:00
from nostr_sdk import ( Keys , Client , Timestamp , Filter , nip04_decrypt , HandleNotification , EventBuilder , PublicKey ,
2023-11-26 10:31:38 +01:00
Options , Tag , Event , nip04_encrypt )
2023-11-23 11:53:57 +01:00
2023-12-13 20:00:03 +01:00
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
from nostr_dvm . utils . definitions import EventDefinitions
from nostr_dvm . utils . nip89_utils import nip89_fetch_events_pubkey , NIP89Config
from nostr_dvm . utils . nostr_utils import send_event
from nostr_dvm . utils . output_utils import PostProcessFunctionType , post_process_list_to_users , post_process_list_to_events
from nostr_dvm . utils . zap_utils import parse_zap_event_tags , pay_bolt11_ln_bits , zap
from nostr_dvm . utils . cashu_utils import redeem_cashu
2023-11-23 11:53:57 +01:00
class Bot :
2023-11-26 10:31:38 +01:00
job_list : list
2023-11-29 15:09:35 +01:00
2023-11-26 10:31:38 +01:00
# This is a simple list just to keep track which events we created and manage, so we don't pay for other requests
2023-11-23 11:53:57 +01:00
def __init__ ( self , dvm_config , admin_config = None ) :
2023-11-24 17:20:29 +01:00
self . NAME = " Bot "
dvm_config . DB = " db/ " + self . NAME + " .db "
2023-11-23 11:53:57 +01:00
self . dvm_config = dvm_config
2023-12-02 21:27:29 +01:00
nip89config = NIP89Config ( )
nip89config . NAME = self . NAME
self . dvm_config . NIP89 = nip89config
2023-11-23 11:53:57 +01:00
self . admin_config = admin_config
self . keys = Keys . from_sk_str ( dvm_config . PRIVATE_KEY )
2023-11-23 13:30:09 +01:00
wait_for_send = True
skip_disconnected_relays = True
opts = ( Options ( ) . wait_for_send ( wait_for_send ) . send_timeout ( timedelta ( seconds = self . dvm_config . RELAY_TIMEOUT ) )
. skip_disconnected_relays ( skip_disconnected_relays ) )
self . client = Client . with_opts ( self . keys , opts )
2023-11-23 11:53:57 +01:00
pk = self . keys . public_key ( )
2023-11-26 10:31:38 +01:00
self . job_list = [ ]
2023-11-24 21:29:24 +01:00
print ( " Nostr BOT public key: " + str ( pk . to_bech32 ( ) ) + " Hex: " + str ( pk . to_hex ( ) ) + " Name: " + self . NAME +
" Supported DVM tasks: " +
2023-11-23 13:30:09 +01:00
' , ' . join ( p . NAME + " : " + p . TASK for p in self . dvm_config . SUPPORTED_DVMS ) + " \n " )
2023-11-23 11:53:57 +01:00
for relay in self . dvm_config . RELAY_LIST :
self . client . add_relay ( relay )
self . client . connect ( )
2023-11-24 17:20:29 +01:00
zap_filter = Filter ( ) . pubkey ( pk ) . kinds ( [ EventDefinitions . KIND_ZAP ] ) . since ( Timestamp . now ( ) )
2023-11-24 21:29:24 +01:00
dm_filter = Filter ( ) . pubkey ( pk ) . kinds ( [ EventDefinitions . KIND_DM ] ) . since ( Timestamp . now ( ) )
2023-11-26 10:31:38 +01:00
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 )
dvm_filter = ( Filter ( ) . kinds ( kinds ) . since ( Timestamp . now ( ) ) )
2023-11-24 17:20:29 +01:00
2023-11-26 10:31:38 +01:00
self . client . subscribe ( [ zap_filter , dm_filter , dvm_filter ] )
2023-11-23 11:53:57 +01:00
2023-11-23 13:30:09 +01:00
create_sql_table ( self . dvm_config . DB )
2023-11-23 11:53:57 +01:00
admin_make_database_updates ( adminconfig = self . admin_config , dvmconfig = self . dvm_config , client = self . client )
class NotificationHandler ( HandleNotification ) :
client = self . client
dvm_config = self . dvm_config
keys = self . keys
def handle ( self , relay_url , nostr_event ) :
2023-11-27 23:37:44 +01:00
if ( EventDefinitions . KIND_NIP90_EXTRACT_TEXT + 1000 < = nostr_event . kind ( )
< = EventDefinitions . KIND_NIP90_GENERIC + 1000 ) :
2023-11-26 10:31:38 +01:00
handle_nip90_response_event ( nostr_event )
elif nostr_event . kind ( ) == EventDefinitions . KIND_FEEDBACK :
handle_nip90_feedback ( nostr_event )
elif nostr_event . kind ( ) == EventDefinitions . KIND_DM :
2023-11-23 11:53:57 +01:00
handle_dm ( nostr_event )
elif nostr_event . kind ( ) == EventDefinitions . KIND_ZAP :
handle_zap ( nostr_event )
def handle_msg ( self , relay_url , msg ) :
return
def handle_dm ( nostr_event ) :
2023-11-24 17:20:29 +01:00
sender = nostr_event . pubkey ( ) . to_hex ( )
2023-11-23 11:53:57 +01:00
try :
decrypted_text = nip04_decrypt ( self . keys . secret_key ( ) , nostr_event . pubkey ( ) , nostr_event . content ( ) )
2023-11-24 21:29:24 +01:00
user = get_or_add_user ( db = self . dvm_config . DB , npub = sender , client = self . client , config = self . dvm_config )
2023-11-30 08:07:30 +01:00
print ( " [ " + self . NAME + " ] Message from " + user . name + " : " + decrypted_text )
2023-11-24 17:20:29 +01:00
2023-11-30 08:07:30 +01:00
# if user selects an index from the overview list...
2023-11-23 11:53:57 +01:00
if decrypted_text [ 0 ] . isdigit ( ) :
2023-11-30 08:07:30 +01:00
split = decrypted_text . split ( ' ' )
index = int ( split [ 0 ] ) - 1
# if user sends index info, e.g. 1 info, we fetch the nip89 information and reply with it.
if len ( split ) > 1 and split [ 1 ] . lower ( ) == " info " :
answer_nip89 ( nostr_event , index )
# otherwise we probably have to do some work, so build an event from input and send it to the DVM
2023-11-28 08:16:34 +01:00
else :
2023-11-29 15:09:35 +01:00
task = self . dvm_config . SUPPORTED_DVMS [ index ] . TASK
2023-11-30 08:07:30 +01:00
print ( " [ " + self . NAME + " ] Request from " + str ( user . name ) + " ( " + str ( user . nip05 ) +
" , Balance: " + str ( user . balance ) + " Sats) Task: " + str ( task ) )
2023-11-28 20:33:30 +01:00
if user . isblacklisted :
2023-11-30 08:07:30 +01:00
# If users are blacklisted for some reason, tell them.
answer_blacklisted ( nostr_event )
2023-11-28 20:33:30 +01:00
else :
2023-11-30 08:07:30 +01:00
# Parse inputs to params
tags = build_params ( decrypted_text , nostr_event , index )
2023-11-28 20:33:30 +01:00
p_tag = Tag . parse ( [ ' p ' , self . dvm_config . SUPPORTED_DVMS [ index ] . PUBLIC_KEY ] )
2023-11-30 08:07:30 +01:00
if self . dvm_config . SUPPORTED_DVMS [ index ] . SUPPORTS_ENCRYPTION :
tags_str = [ ]
for tag in tags :
tags_str . append ( tag . as_vec ( ) )
params_as_str = json . dumps ( tags_str )
print ( params_as_str )
# and encrypt them
encrypted_params = nip04_encrypt ( self . keys . secret_key ( ) ,
PublicKey . from_hex (
self . dvm_config . SUPPORTED_DVMS [ index ] . PUBLIC_KEY ) ,
params_as_str )
# add encrypted and p tag on the outside
encrypted_tag = Tag . parse ( [ ' encrypted ' ] )
# add the encrypted params to the content
nip90request = ( EventBuilder ( self . dvm_config . SUPPORTED_DVMS [ index ] . KIND ,
encrypted_params , [ p_tag , encrypted_tag ] ) .
to_event ( self . keys ) )
else :
tags . append ( p_tag )
nip90request = ( EventBuilder ( self . dvm_config . SUPPORTED_DVMS [ index ] . KIND ,
" " , tags ) .
to_event ( self . keys ) )
# remember in the job_list that we have made an event, if anybody asks for payment,
# we know we actually sent the request
entry = { " npub " : user . npub , " event_id " : nip90request . id ( ) . to_hex ( ) ,
2023-11-28 20:33:30 +01:00
" dvm_key " : self . dvm_config . SUPPORTED_DVMS [ index ] . PUBLIC_KEY , " is_paid " : False }
self . job_list . append ( entry )
2023-11-30 08:07:30 +01:00
# send the event to the DVM
send_event ( nip90request , client = self . client , dvm_config = self . dvm_config )
2023-12-01 12:12:46 +01:00
# print(nip90request.as_json())
2023-11-30 10:49:08 +01:00
2023-11-27 23:37:44 +01:00
2023-11-23 11:53:57 +01:00
2023-11-30 08:07:30 +01:00
elif decrypted_text . lower ( ) . startswith ( " balance " ) :
2023-11-28 20:33:30 +01:00
time . sleep ( 3.0 )
2023-11-23 11:53:57 +01:00
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , nostr_event . pubkey ( ) ,
2023-11-30 08:07:30 +01:00
" Your current balance is " + str (
user . balance ) + " Sats. Zap me to add to your balance. I will use your balance interact with the DVMs for you. \n "
" I support both public and private Zaps, as well as Zapplepay. \n "
" Alternativly you can add a #cashu token with \" -cashu cashuASomeToken \" to your command. \n Make sure the token is worth the requested amount + "
" mint fees (at least 3 sat). \n Not all DVMs might accept Cashu tokens. "
, None ) . to_event ( self . keys )
2023-11-23 11:53:57 +01:00
send_event ( evt , client = self . client , dvm_config = dvm_config )
2023-12-02 21:27:29 +01:00
elif decrypted_text . startswith ( " cashuA " ) :
print ( " Received Cashu token: " + decrypted_text )
2023-12-10 20:16:01 +01:00
cashu_redeemed , cashu_message , total_amount , fees = redeem_cashu ( decrypted_text , self . dvm_config ,
self . client )
2023-12-02 21:27:29 +01:00
print ( cashu_message )
if cashu_message == " success " :
update_user_balance ( self . dvm_config . DB , sender , total_amount , client = self . client ,
config = self . dvm_config )
else :
time . sleep ( 2.0 )
message = " Error: " + cashu_message + " . Token has not been redeemed. "
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , PublicKey . from_hex ( sender ) , message ,
None ) . to_event ( self . keys )
send_event ( evt , client = self . client , dvm_config = self . dvm_config )
2023-12-03 22:24:33 +01:00
elif decrypted_text . lower ( ) . startswith ( " what ' s the second best " ) :
time . sleep ( 3.0 )
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , nostr_event . pubkey ( ) ,
2023-12-10 20:16:01 +01:00
" No, there is no second best. \n \n https://cdn.nostr.build/p/mYLv.mp4 " ,
nostr_event . id ( ) ) . to_event ( self . keys )
2023-12-03 22:24:33 +01:00
send_event ( evt , client = self . client , dvm_config = self . dvm_config )
2023-11-30 08:07:30 +01:00
else :
# Build an overview of known DVMs and send it to the user
answer_overview ( nostr_event )
except Exception as e :
2023-11-24 21:29:24 +01:00
print ( " Error in bot " + str ( e ) )
2023-11-24 22:07:00 +01:00
2023-11-26 10:31:38 +01:00
def handle_nip90_feedback ( nostr_event ) :
2023-11-30 08:07:30 +01:00
print ( nostr_event . as_json ( ) )
2023-11-26 10:31:38 +01:00
try :
2023-11-27 23:37:44 +01:00
is_encrypted = False
2023-11-26 10:31:38 +01:00
status = " "
etag = " "
ptag = " "
2023-11-30 08:07:30 +01:00
content = nostr_event . content ( )
2023-11-26 10:31:38 +01:00
for tag in nostr_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " status " :
status = tag . as_vec ( ) [ 1 ]
2023-11-30 08:07:30 +01:00
if len ( tag . as_vec ( ) ) > 2 :
content = tag . as_vec ( ) [ 2 ]
2023-11-26 10:31:38 +01:00
elif tag . as_vec ( ) [ 0 ] == " e " :
etag = tag . as_vec ( ) [ 1 ]
elif tag . as_vec ( ) [ 0 ] == " p " :
ptag = tag . as_vec ( ) [ 1 ]
2023-11-27 23:37:44 +01:00
elif tag . as_vec ( ) [ 0 ] == " encrypted " :
is_encrypted = True
if is_encrypted :
2023-11-28 10:08:43 +01:00
if ptag == self . keys . public_key ( ) . to_hex ( ) :
2023-11-27 23:37:44 +01:00
tags_str = nip04_decrypt ( Keys . from_sk_str ( dvm_config . PRIVATE_KEY ) . secret_key ( ) ,
nostr_event . pubkey ( ) , nostr_event . content ( ) )
params = json . loads ( tags_str )
params . append ( Tag . parse ( [ " p " , ptag ] ) . as_vec ( ) )
params . append ( Tag . parse ( [ " encrypted " ] ) . as_vec ( ) )
event_as_json = json . loads ( nostr_event . as_json ( ) )
event_as_json [ ' tags ' ] = params
event_as_json [ ' content ' ] = " "
nostr_event = Event . from_json ( json . dumps ( event_as_json ) )
for tag in nostr_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " status " :
status = tag . as_vec ( ) [ 1 ]
2023-11-30 08:07:30 +01:00
if len ( tag . as_vec ( ) ) > 2 :
content = tag . as_vec ( ) [ 2 ]
2023-11-27 23:37:44 +01:00
elif tag . as_vec ( ) [ 0 ] == " e " :
etag = tag . as_vec ( ) [ 1 ]
elif tag . as_vec ( ) [ 0 ] == " content " :
content = tag . as_vec ( ) [ 1 ]
else :
return
2023-11-26 10:31:38 +01:00
2023-11-30 08:07:30 +01:00
if status == " success " or status == " error " or status == " processing " or status == " partial " and content != " " :
2023-11-26 10:31:38 +01:00
entry = next ( ( x for x in self . job_list if x [ ' event_id ' ] == etag ) , None )
2023-12-10 20:16:01 +01:00
if entry is not None and entry [ ' dvm_key ' ] == nostr_event . pubkey ( ) . to_hex ( ) :
2023-11-26 10:31:38 +01:00
user = get_or_add_user ( db = self . dvm_config . DB , npub = entry [ ' npub ' ] ,
client = self . client , config = self . dvm_config )
2023-12-01 21:17:32 +01:00
time . sleep ( 2.0 )
2023-11-26 10:31:38 +01:00
reply_event = EventBuilder . new_encrypted_direct_msg ( self . keys ,
PublicKey . from_hex ( user . npub ) ,
2023-11-27 23:37:44 +01:00
content ,
2023-11-26 10:31:38 +01:00
None ) . to_event ( self . keys )
2023-11-27 23:37:44 +01:00
print ( status + " : " + content )
2023-11-26 10:31:38 +01:00
print (
" [ " + self . NAME + " ] Received reaction from " + nostr_event . pubkey ( ) . to_hex ( ) + " message to orignal sender " + user . name )
send_event ( reply_event , client = self . client , dvm_config = dvm_config )
elif status == " payment-required " or status == " partial " :
for tag in nostr_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " amount " :
amount_msats = int ( tag . as_vec ( ) [ 1 ] )
2023-11-26 13:10:19 +01:00
amount = int ( amount_msats / 1000 )
entry = next ( ( x for x in self . job_list if x [ ' event_id ' ] == etag ) , None )
2023-11-29 15:09:35 +01:00
if entry is not None and entry [ ' is_paid ' ] is False and entry [
' dvm_key ' ] == nostr_event . pubkey ( ) . to_hex ( ) :
2023-11-27 23:37:44 +01:00
# if we get a bolt11, we pay and move on
user = get_or_add_user ( db = self . dvm_config . DB , npub = entry [ " npub " ] ,
client = self . client , config = self . dvm_config )
2023-12-01 12:12:46 +01:00
if user . balance > = amount :
2023-11-28 08:16:34 +01:00
balance = max ( user . balance - 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 ( ) )
evt = EventBuilder . new_encrypted_direct_msg ( self . keys ,
PublicKey . from_hex ( entry [ " npub " ] ) ,
" Paid " + str (
amount ) + " Sats from balance to DVM. New balance is " +
str ( balance )
+ " Sats. \n " ,
None ) . to_event ( self . keys )
2023-11-29 15:09:35 +01:00
print (
" [ " + self . NAME + " ] Replying " + user . name + " with \" scheduled \" confirmation " )
2023-11-28 08:16:34 +01:00
send_event ( evt , client = self . client , dvm_config = dvm_config )
else :
print ( " Bot payment-required " )
2023-12-04 21:41:18 +01:00
time . sleep ( 2.0 )
2023-11-29 15:09:35 +01:00
evt = EventBuilder . new_encrypted_direct_msg ( self . keys ,
PublicKey . from_hex ( entry [ " npub " ] ) ,
" Current balance: " + str (
user . balance ) + " Sats. Balance of " + str (
amount ) + " Sats required. Please zap me with at least " +
2023-11-28 08:16:34 +01:00
str ( int ( amount - user . balance ) )
+ " Sats, then try again. " ,
None ) . to_event ( self . keys )
send_event ( evt , client = self . client , dvm_config = dvm_config )
return
2023-11-27 23:37:44 +01:00
2023-11-26 13:10:19 +01:00
if len ( tag . as_vec ( ) ) > 2 :
bolt11 = tag . as_vec ( ) [ 2 ]
# else we create a zap
else :
2023-11-26 20:56:48 +01:00
user = get_or_add_user ( db = self . dvm_config . DB , npub = nostr_event . pubkey ( ) . to_hex ( ) ,
client = self . client , config = self . dvm_config )
2023-11-27 23:37:44 +01:00
print ( " Paying: " + user . name )
bolt11 = zap ( user . lud16 , amount , " Zap " , nostr_event , self . keys , self . dvm_config ,
" private " )
2023-11-26 13:10:19 +01:00
if bolt11 == None :
print ( " Receiver has no Lightning address " )
return
try :
2023-12-09 23:58:15 +01:00
print ( bolt11 )
2023-11-26 13:10:19 +01:00
payment_hash = pay_bolt11_ln_bits ( bolt11 , self . dvm_config )
self . job_list [ self . job_list . index ( entry ) ] [ ' is_paid ' ] = True
print ( " [ " + self . NAME + " ] payment_hash: " + payment_hash +
2023-11-26 20:56:48 +01:00
" Forwarding payment of " + str ( amount ) + " Sats to DVM " )
2023-11-26 13:10:19 +01:00
except Exception as e :
print ( e )
2023-11-26 10:31:38 +01:00
except Exception as e :
print ( e )
def handle_nip90_response_event ( nostr_event : Event ) :
try :
2023-11-27 23:37:44 +01:00
ptag = " "
2023-11-30 08:07:30 +01:00
etag = " "
2023-11-26 10:31:38 +01:00
is_encrypted = False
for tag in nostr_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " e " :
etag = tag . as_vec ( ) [ 1 ]
elif tag . as_vec ( ) [ 0 ] == " p " :
ptag = tag . as_vec ( ) [ 1 ]
elif tag . as_vec ( ) [ 0 ] == " encrypted " :
is_encrypted = True
entry = next ( ( x for x in self . job_list if x [ ' event_id ' ] == etag ) , None )
2023-12-10 20:16:01 +01:00
if entry is not None and entry [
' dvm_key ' ] == nostr_event . pubkey ( ) . to_hex ( ) :
2023-11-26 10:31:38 +01:00
print ( entry )
user = get_or_add_user ( db = self . dvm_config . DB , npub = entry [ ' npub ' ] ,
client = self . client , config = self . dvm_config )
self . job_list . remove ( entry )
content = nostr_event . content ( )
if is_encrypted :
2023-11-28 10:08:43 +01:00
if ptag == self . keys . public_key ( ) . to_hex ( ) :
2023-11-27 23:37:44 +01:00
content = nip04_decrypt ( self . keys . secret_key ( ) , nostr_event . pubkey ( ) , content )
else :
return
2023-11-26 10:31:38 +01:00
2023-12-01 12:12:46 +01:00
dvms = [ x for x in self . dvm_config . SUPPORTED_DVMS if
x . PUBLIC_KEY == nostr_event . pubkey ( ) . to_hex ( ) and x . KIND == nostr_event . kind ( ) - 1000 ]
if len ( dvms ) > 0 :
2023-12-10 20:16:01 +01:00
dvm = dvms [ 0 ]
if dvm . dvm_config . EXTERNAL_POST_PROCESS_TYPE != PostProcessFunctionType . NONE :
if dvm . dvm_config . EXTERNAL_POST_PROCESS_TYPE == PostProcessFunctionType . LIST_TO_EVENTS :
content = post_process_list_to_events ( content )
elif dvm . dvm_config . EXTERNAL_POST_PROCESS_TYPE == PostProcessFunctionType . LIST_TO_USERS :
content = post_process_list_to_users ( content )
2023-12-01 12:12:46 +01:00
2023-11-26 10:31:38 +01:00
print ( " [ " + self . NAME + " ] Received results, message to orignal sender " + user . name )
time . sleep ( 1.0 )
reply_event = EventBuilder . new_encrypted_direct_msg ( self . keys ,
PublicKey . from_hex ( user . npub ) ,
content ,
None ) . to_event ( self . keys )
send_event ( reply_event , client = self . client , dvm_config = dvm_config )
except Exception as e :
print ( e )
2023-11-23 11:53:57 +01:00
def handle_zap ( zap_event ) :
2023-11-24 21:29:24 +01:00
print ( " [ " + self . NAME + " ] Zap received " )
2023-11-23 11:53:57 +01:00
try :
2023-11-26 21:47:33 +01:00
invoice_amount , zapped_event , sender , message , anon = parse_zap_event_tags ( zap_event ,
2023-11-27 23:37:44 +01:00
self . keys , self . NAME ,
self . client , self . dvm_config )
2023-11-24 21:29:24 +01:00
2023-12-09 23:58:15 +01:00
etag = " "
for tag in zap_event . tags ( ) :
if tag . as_vec ( ) [ 0 ] == " e " :
etag = tag . as_vec ( ) [ 1 ]
2023-11-24 21:29:24 +01:00
user = get_or_add_user ( self . dvm_config . DB , sender , client = self . client , config = self . dvm_config )
2023-11-23 11:53:57 +01:00
2023-12-09 23:58:15 +01:00
entry = next ( ( x for x in self . job_list if x [ ' event_id ' ] == etag ) , None )
print ( entry )
2023-12-10 20:16:01 +01:00
# print(entry['dvm_key'])
2023-12-09 23:58:15 +01:00
# print(str(zapped_event.pubkey().to_hex()))
# print(str(zap_event.pubkey().to_hex()))
print ( sender )
if entry is not None and entry [ ' is_paid ' ] is True and entry [ ' dvm_key ' ] == sender :
# if we get a bolt11, we pay and move on
user = get_or_add_user ( db = self . dvm_config . DB , npub = entry [ " npub " ] ,
client = self . client , config = self . dvm_config )
sender = user . npub
if zapped_event is not None :
2023-12-10 20:16:01 +01:00
if not anon :
print ( " [ " + self . NAME + " ] Note Zap received for Bot balance: " + str (
invoice_amount ) + " Sats from " + str (
user . name ) )
update_user_balance ( self . dvm_config . DB , sender , invoice_amount , client = self . client ,
config = self . dvm_config )
# a regular note
2023-11-23 11:53:57 +01:00
elif not anon :
2023-11-24 22:07:00 +01:00
print ( " [ " + self . NAME + " ] Profile Zap received for Bot balance: " + str (
invoice_amount ) + " Sats from " + str (
2023-11-23 11:53:57 +01:00
user . name ) )
update_user_balance ( self . dvm_config . DB , sender , invoice_amount , client = self . client ,
config = self . dvm_config )
except Exception as e :
2023-11-24 21:29:24 +01:00
print ( " [ " + self . NAME + " ] Error during content decryption: " + str ( e ) )
2023-11-23 11:53:57 +01:00
2023-11-30 08:07:30 +01:00
def answer_overview ( nostr_event ) :
message = " DVMs that I support: \n \n "
index = 1
for p in self . dvm_config . SUPPORTED_DVMS :
if p . PER_UNIT_COST != 0 and p . PER_UNIT_COST is not None :
message + = ( str ( index ) + " " + p . NAME + " " + p . TASK + " " + str ( p . FIX_COST ) +
" Sats + " + str ( p . PER_UNIT_COST ) + " Sats per Second \n " )
else :
message + = ( str ( index ) + " " + p . NAME + " " + p . TASK + " " + str ( p . FIX_COST ) +
" Sats \n " )
index + = 1
time . sleep ( 3.0 )
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , nostr_event . pubkey ( ) ,
message + " \n Select an Index and provide an input ( "
" e.g. \" 2 A purple ostrich \" ) \n Type \" index info \" to learn "
" more about each DVM. (e.g. \" 2 info \" ) \n \n "
" Type \" balance \" to see your current balance " ,
nostr_event . id ( ) ) . to_event ( self . keys )
send_event ( evt , client = self . client , dvm_config = dvm_config )
def answer_blacklisted ( nostr_event ) :
# For some reason an admin might blacklist npubs, e.g. for abusing the service
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , nostr_event . pubkey ( ) ,
" Your are currently blocked from all "
" services. " , None ) . to_event ( self . keys )
send_event ( evt , client = self . client , dvm_config = dvm_config )
def answer_nip89 ( nostr_event , index ) :
info = print_dvm_info ( self . client , index )
time . sleep ( 2.0 )
if info is not None :
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , nostr_event . pubkey ( ) ,
info , None ) . to_event ( self . keys )
else :
evt = EventBuilder . new_encrypted_direct_msg ( self . keys , nostr_event . pubkey ( ) ,
" No NIP89 Info found for " +
self . dvm_config . SUPPORTED_DVMS [ index ] . NAME ,
None ) . to_event ( self . keys )
send_event ( evt , client = self . client , dvm_config = dvm_config )
def build_params ( decrypted_text , nostr_event , index ) :
tags = [ ]
split = decrypted_text . split ( ' ' )
# If only a command without parameters is sent, we assume no input is required, and that means the dvm might take in the user as input (e.g. for content discovery)
if len ( split ) == 1 :
tag = Tag . parse ( [ " param " , " user " , nostr_event . pubkey ( ) . to_hex ( ) ] )
tags . append ( tag )
output = Tag . parse ( [ " output " , " text/plain " ] )
tags . append ( output )
2023-12-01 21:17:32 +01:00
relay_list = [ " relays " ]
2023-11-30 08:07:30 +01:00
for relay in self . dvm_config . RELAY_LIST :
2023-12-01 21:17:32 +01:00
relay_list . append ( relay )
relays = Tag . parse ( relay_list )
2023-11-30 08:07:30 +01:00
tags . append ( relays )
return tags
2023-12-01 21:17:32 +01:00
tags = [ ]
2023-11-30 08:07:30 +01:00
command = decrypted_text . replace ( split [ 0 ] + " " , " " )
split = command . split ( " - " )
input = split [ 0 ] . rstrip ( )
if input . startswith ( " http " ) :
2023-12-01 21:17:32 +01:00
temp = input . split ( " " )
if len ( temp ) > 1 :
input_type = " url "
i_tag1 = Tag . parse ( [ " i " , temp [ 0 ] , input_type ] )
tags . append ( i_tag1 )
input_type = " text "
i_tag2 = Tag . parse ( [ " i " , input . replace ( temp [ 0 ] , " " ) . lstrip ( ) , input_type ] )
tags . append ( i_tag2 )
else :
input_type = " url "
i_tag = Tag . parse ( [ " i " , input , input_type ] )
tags . append ( i_tag )
else :
print ( input )
input_type = " text "
i_tag = Tag . parse ( [ " i " , input , input_type ] )
tags . append ( i_tag )
2023-11-30 08:07:30 +01:00
alt_tag = Tag . parse ( [ " alt " , self . dvm_config . SUPPORTED_DVMS [ index ] . TASK ] )
tags . append ( alt_tag )
relaylist = [ " relays " ]
for relay in self . dvm_config . RELAY_LIST :
relaylist . append ( relay )
relays_tag = Tag . parse ( relaylist )
2023-12-01 21:17:32 +01:00
tags . append ( relays_tag )
2023-12-02 17:01:29 +01:00
output_tag = Tag . parse ( [ " output " , " text/plain " ] )
tags . append ( output_tag )
2023-11-30 08:07:30 +01:00
remaining_text = command . replace ( input , " " )
print ( remaining_text )
2023-12-01 21:17:32 +01:00
2023-11-30 08:07:30 +01:00
params = remaining_text . rstrip ( ) . split ( " - " )
for i in params :
print ( i )
if i != " " :
try :
split = i . split ( " " )
if len ( split ) > 1 :
param = str ( split [ 0 ] )
print ( str ( param ) )
value = str ( split [ 1 ] )
print ( str ( value ) )
if param == " cashu " :
tag = Tag . parse ( [ param , value ] )
else :
2023-12-02 17:01:29 +01:00
if param == " user " :
if value . startswith ( " @ " ) or value . startswith ( " nostr: " ) or value . startswith ( " npub " ) :
2023-12-10 20:16:01 +01:00
value = PublicKey . from_bech32 (
value . replace ( " @ " , " " ) . replace ( " nostr: " , " " ) ) . to_hex ( )
2023-11-30 08:07:30 +01:00
tag = Tag . parse ( [ " param " , param , value ] )
tags . append ( tag )
print ( " Added params: " + str ( tag . as_vec ( ) ) )
except Exception as e :
print ( e )
print ( " Couldn ' t add " + str ( i ) )
return tags
2023-11-29 10:46:51 +01:00
2023-11-29 15:09:35 +01:00
def print_dvm_info ( client , index ) :
pubkey = self . dvm_config . SUPPORTED_DVMS [ index ] . dvm_config . PUBLIC_KEY
kind = self . dvm_config . SUPPORTED_DVMS [ index ] . KIND
nip89content_str = nip89_fetch_events_pubkey ( client , pubkey , kind )
print ( nip89content_str )
if nip89content_str is not None :
nip89content = json . loads ( nip89content_str )
info = " "
2023-12-03 22:05:53 +01:00
cashu_accepted = False
encryption_supported = False
2023-11-29 15:09:35 +01:00
if nip89content . get ( " name " ) :
info + = " Name: " + nip89content . get ( " name " ) + " \n "
2023-12-03 22:05:53 +01:00
if nip89content . get ( " image " ) :
2023-11-29 15:09:35 +01:00
info + = nip89content . get ( " image " ) + " \n "
2023-12-03 22:05:53 +01:00
if nip89content . get ( " about " ) :
info + = " About: \n " + nip89content . get ( " about " ) + " \n \n "
2023-12-03 22:10:43 +01:00
if nip89content . get ( " cashuAccepted " ) :
cashu_accepted = str ( nip89content . get ( " cashuAccepted " ) )
2023-12-03 22:05:53 +01:00
if nip89content . get ( " encryptionSupported " ) :
encryption_supported = str ( nip89content . get ( " encryptionSupported " ) )
info + = " Encryption supported: " + str ( encryption_supported ) + " \n "
info + = " Cashu accepted: " + str ( cashu_accepted ) + " \n \n "
if nip89content . get ( " nip90Params " ) :
2023-11-29 15:09:35 +01:00
params = nip89content [ " nip90Params " ]
info + = " \n Parameters: \n "
for param in params :
info + = " - " + param + ' \n '
info + = " Required: " + str ( params [ param ] [ ' required ' ] ) + ' \n '
info + = " Possible Values: " + json . dumps ( params [ param ] [ ' values ' ] ) + ' \n \n '
2023-12-03 22:18:03 +01:00
return info
2023-11-29 15:09:35 +01:00
return None
2023-11-30 08:07:30 +01:00
self . client . handle_notifications ( NotificationHandler ( ) )
2023-11-29 10:46:51 +01:00
try :
while True :
time . sleep ( 1.0 )
except KeyboardInterrupt :
print ( ' Stay weird! ' )
2023-11-29 11:00:15 +01:00
os . kill ( os . getpid ( ) , signal . SIGTERM )