add bot example, add legacy code for nip04 support (on lifeline)

This commit is contained in:
Believethehype 2024-09-25 10:21:30 +02:00
parent 882dba0f2c
commit f7c4f718d3
16 changed files with 213 additions and 35 deletions

View File

@ -2,7 +2,7 @@ import asyncio
import json
import os
import signal
import time
from datetime import timedelta
from nostr_sdk import (Keys, Client, Timestamp, Filter, nip04_decrypt, HandleNotification, EventBuilder, PublicKey,
@ -13,12 +13,13 @@ 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, InvoiceToWatch
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.nostr_utils import send_event, send_nip04_dm
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, zaprequest, create_bolt11_ln_bits, \
check_bolt11_ln_bits_is_paid, parse_amount_from_bolt11_invoice
from nostr_dvm.utils.cashu_utils import redeem_cashu
from nostr_dvm.utils.print_utils import bcolors
class Bot:
@ -57,7 +58,7 @@ class Bot:
self.job_list = []
print("Nostr BOT public key: " + str(pk.to_bech32()) + " Hex: " + str(pk.to_hex()) + " Name: " + self.NAME) # +
print(bcolors.BLUE + "Nostr BOT public key: " + str(pk.to_bech32()) + " Hex: " + str(pk.to_hex()) + " Name: " + self.NAME + bcolors.ENDC) # +
# " Supported DVM tasks: " +
# ', '.join(p.NAME + ":" + p.TASK for p in self.dvm_config.SUPPORTED_DVMS) + "\n")
if dvm_config.CHATBOT is not None:
@ -245,7 +246,8 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
else:
#await self.client.send_direct_msg(PublicKey.parse(sender), message, None)
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
#await self.client.send_private_msg(PublicKey.parse(sender), message, None)
await send_nip04_dm(self.client, message, sender, self.dvm_config)
@ -264,8 +266,9 @@ class Bot:
if giftwrap:
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
else:
await send_nip04_dm(self.client, message, sender, self.dvm_config)
#await self.client.send_direct_msg(PublicKey.parse(sender), message, None)
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
#await self.client.send_private_msg(PublicKey.parse(sender), message, None)
elif decrypted_text.startswith("cashuA"):
print("Received Cashu token:" + decrypted_text)
cashu_redeemed, cashu_message, total_amount, fees = await redeem_cashu(decrypted_text,
@ -283,7 +286,9 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
else:
#await self.client.send_direct_msg(PublicKey.parse(sender), message, None)
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
#await self.client.send_private_msg(PublicKey.parse(sender), message, None)
#await send_nip04_dm(self.client, message, sender, self.keys)
await send_nip04_dm(self.client, message, sender, self.dvm_config)
elif decrypted_text.lower().startswith("what's the second best"):
await asyncio.sleep(2.0)
message = "No, there is no second best.\n\nhttps://cdn.nostr.build/p/mYLv.mp4"
@ -291,8 +296,8 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
else:
#await self.client.send_direct_msg(PublicKey.parse(sender), message, nostr_event.id())
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
#await self.client.send_private_msg(PublicKey.parse(sender), message, None)
await send_nip04_dm(self.client, message, sender, self.dvm_config)
else:
# Build an overview of known DVMs and send it to the user
@ -389,8 +394,9 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(entry["npub"]), content, None)
else:
#await self.client.send_direct_msg(PublicKey.from_hex(entry['npub']), content, None)
await self.client.send_private_msg(PublicKey.parse(entry['npub']),
content, None)
#await self.client.send_private_msg(PublicKey.parse(entry['npub']),
# content, None)
await send_nip04_dm(self.client, content, PublicKey.parse(entry['npub']), self.dvm_config)
print(status + ": " + content)
print(
"[" + self.NAME + "] Received reaction from " + nostr_event.author().to_hex() + " message to orignal sender " + user.name)
@ -421,8 +427,9 @@ class Bot:
None)
else:
#await self.client.send_direct_msg(PublicKey.parse(PublicKey.parse(entry["npub"])), message, None)
await self.client.send_private_msg(PublicKey.parse(entry["npub"]), message, None)
#await self.client.send_private_msg(PublicKey.parse(entry["npub"]), message, None)
await send_nip04_dm(self.client, content, PublicKey.parse(entry['npub']),
self.dvm_config)
print(
"[" + self.NAME + "] Replying " + user.name + " with \"scheduled\" confirmation")
@ -432,8 +439,13 @@ class Bot:
message = "Current balance: " + str( user.balance) + " Sats. Balance of " + str(amount) + " Sats required. Please zap me with at least " + str(int(amount - user.balance))+ " Sats, then try again.",
#await self.client.send_direct_msg(PublicKey.parse(PublicKey.parse(entry["npub"])),
# message, None)
await self.client.send_private_msg(PublicKey.parse(entry["npub"]),
message, None)
#await self.client.send_private_msg(PublicKey.parse(entry["npub"]),
#
if entry["giftwrap"]:
await self.client.send_private_msg(PublicKey.parse(entry["npub"]), message,
None)
else:
await send_nip04_dm(self.client, message, PublicKey.parse(entry['npub']), self.dvm_config)
return
if len(tag.as_vec()) > 2:
@ -508,7 +520,8 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(user.npub), content, None)
else:
#await self.client.send_direct_msg(PublicKey.parse(user.npub), content, None)
await self.client.send_private_msg(PublicKey.parse(user.npub), content, None)
#await self.client.send_private_msg(PublicKey.parse(user.npub), content, None)
await send_nip04_dm(self.client, content, PublicKey.parse(user.npub), self.dvm_config)
except Exception as e:
print(e)
@ -581,7 +594,8 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(sender), text, nostr_event.id())
else:
#await self.client.send_direct_msg(PublicKey.parse(sender), text, nostr_event.id())
await self.client.send_private_msg(PublicKey.parse(sender), text, nostr_event.id())
#await self.client.send_private_msg(PublicKey.parse(sender), text, nostr_event.id())
await send_nip04_dm(self.client, text, PublicKey.parse(sender), self.dvm_config)
async def answer_blacklisted(nostr_event, giftwrap, sender):
message = "Your are currently blocked from this service."
@ -589,7 +603,8 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
else:
#await self.client.send_direct_msg(nostr_event.author(), message, None)
await self.client.send_private_msg(PublicKey.parse(sender), message, None)
#await self.client.send_private_msg(PublicKey.parse(sender), message, None)
await send_nip04_dm(self.client, message, PublicKey.parse(sender), self.dvm_config)
@ -603,7 +618,7 @@ class Bot:
await self.client.send_private_msg(PublicKey.parse(sender), info, None)
else:
#await self.client.send_direct_msg(nostr_event.author(), info, None)
await self.client.send_private_msg(PublicKey.parse(sender), info, None)
await send_nip04_dm(self.client, info, PublicKey.parse(sender), self.dvm_config)
def build_params(decrypted_text, author, index):
tags = []

View File

@ -22,7 +22,7 @@ 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, \
parse_amount_from_bolt11_invoice, zaprequest, pay_bolt11_ln_bits, create_bolt11_lud16
from nostr_dvm.utils.cashu_utils import redeem_cashu
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
class DVM:

View File

@ -7,10 +7,12 @@ from dataclasses import dataclass
from datetime import timedelta
from logging import Filter
from coincurve import PrivateKey
from nostr_sdk import Timestamp, Keys, PublicKey, EventBuilder, Filter, Kind
from nostr_dvm.utils.definitions import relay_timeout
from nostr_dvm.utils.nostr_utils import send_event
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nostr_utils import send_event, send_nip04_dm
@dataclass
@ -197,7 +199,8 @@ async def update_user_balance(db, npub, additional_sats, client, config, giftwra
await client.send_private_msg(PublicKey.parse(npub), message, None)
else:
#await client.send_direct_msg(PublicKey.parse(npub), message, None)
await client.send_private_msg(PublicKey.parse(npub), message, None)
#await client.send_private_msg(PublicKey.parse(npub), message, None)
await send_nip04_dm(client, message, npub, config)
def update_user_subscription(npub, subscribed_until, client, dvm_config):

View File

@ -15,7 +15,7 @@ class DVMConfig:
FIX_COST: float = None
PER_UNIT_COST: float = None
RELAY_LIST = ["wss://relay.primal.net",
RELAY_LIST = [
"wss://nostr.mom", "wss://nostr.oxtr.dev",
"wss://relay.nostr.net"
]

View File

@ -2,7 +2,7 @@ from nostr_sdk import Tag, Keys, EventBuilder, Kind
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.nostr_utils import send_event
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
async def gallery_announce_list(tags, dvm_config, client):

View File

@ -1,11 +1,38 @@
from nostr_sdk import Tag, Keys, EventBuilder
from nostr_sdk import Tag, Keys, EventBuilder, Kind
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.nostr_utils import send_event
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
async def announce_dm_relays(dvm_config, client):
tags = []
for relay in dvm_config.RELAY_LIST:
r_tag = Tag.parse(["r", relay])
tags.append(r_tag)
keys = Keys.parse(dvm_config.NIP89.PK)
content = ""
event = EventBuilder(Kind(10050), content, tags).to_event(keys)
eventid = await send_event(event, client=client, dvm_config=dvm_config)
if (eventid is not None):
print(
bcolors.BLUE + "[" + dvm_config.NIP89.NAME + "] Announced DM relays for " + dvm_config.NIP89.NAME + " (EventID: " + str(
eventid.id.to_hex()) + ")" + bcolors.ENDC)
else:
print(
bcolors.RED + "[" + dvm_config.NIP89.NAME + "] Could not announce DM relays for " + dvm_config.NIP89.NAME + bcolors.ENDC)
async def nip65_announce_relays(dvm_config, client):
# todo we might want to call the dm relays seperately but for now we do it together with the inbox relays
await announce_dm_relays(dvm_config, client)
tags = []
for relay in dvm_config.RELAY_LIST:
@ -24,3 +51,4 @@ async def nip65_announce_relays(dvm_config, client):
else:
print(
bcolors.RED + "[" + dvm_config.NIP89.NAME + "] Could not announce NIP 65 for " + dvm_config.NIP89.NAME + bcolors.ENDC)

View File

@ -8,7 +8,7 @@ from nostr_sdk import Tag, Keys, EventBuilder, Filter, Alphabet, PublicKey, Clie
from nostr_dvm.utils.definitions import EventDefinitions, relay_timeout
from nostr_dvm.utils.nostr_utils import send_event
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
class NIP89Config:

View File

@ -7,7 +7,7 @@ from typing import List
import dotenv
from nostr_sdk import Filter, Client, Alphabet, EventId, Event, PublicKey, Tag, Keys, nip04_decrypt, Metadata, Options, \
Nip19Event, SingleLetterTag, RelayOptions, RelayLimits, SecretKey, NostrSigner, Connection, ConnectionTarget, \
EventSource
EventSource, EventBuilder, Kind, nip04_encrypt
from nostr_dvm.utils.definitions import EventDefinitions, relay_timeout, relay_timeout_long
@ -141,6 +141,29 @@ async def get_inbox_relays(event_to_send: Event, client: Client, dvm_config):
return relays
async def get_dm_relays(event_to_send: Event, client: Client, dvm_config):
ptags = []
for tag in event_to_send.tags():
if tag.as_vec()[0] == 'p':
ptag = PublicKey.parse(tag.as_vec()[1])
ptags.append(ptag)
filter = Filter().kinds([Kind(10050)]).authors(ptags)
events = await client.get_events_of([filter], relay_timeout)
if len(events) == 0:
return []
else:
nip65event = events[0]
relays = []
for tag in nip65event.tags():
if ((tag.as_vec()[0] == 'r' and len(tag.as_vec()) == 2)
or ((tag.as_vec()[0] == 'r' and len(tag.as_vec()) == 3) and tag.as_vec()[2] == "read")):
rtag = tag.as_vec()[1]
if rtag.rstrip("/") not in dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST:
if rtag.startswith("ws") and " " not in rtag:
relays.append(rtag)
return relays
async def get_main_relays(event_to_send: Event, client: Client, dvm_config):
ptags = []
for tag in event_to_send.tags():
@ -404,6 +427,38 @@ async def update_profile(dvm_config, client, lud16=""):
await client.set_metadata(metadata)
async def send_nip04_dm(client: Client, msg, receiver: PublicKey, dvm_config ):
signer = NostrSigner.keys(Keys.parse(dvm_config.PRIVATE_KEY))
content = await signer.nip04_encrypt(receiver, msg)
ptag = Tag.parse(["p", receiver.to_hex()])
event = EventBuilder(Kind(4), content, [ptag]).to_event(Keys.parse(dvm_config.PRIVATE_KEY))
await client.send_event(event)
# relays = await get_dm_relays(event, client, dvm_config)
#
# outboxclient = Client(signer)
# print("[" + dvm_config.NIP89.NAME + "] Receiver Inbox relays: " + str(relays))
#
# for relay in relays[:5]:
# try:
# await outboxclient.add_relay(relay)
# except:
# print("[" + dvm_config.NIP89.NAME + "] " + relay + " couldn't be added to outbox relays")
# #
# await outboxclient.connect()
# try:
# print("Connected, sending event")
# event_id = await outboxclient.send_event(event)
# print(event_id.output)
# except Exception as e:
# print(e)
def check_and_set_private_key(identifier):
if not os.getenv("DVM_PRIVATE_KEY_" + identifier.upper()):
pk = Keys.generate().secret_key().to_hex()

View File

@ -12,7 +12,7 @@ from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.zap_utils import pay_bolt11_ln_bits, zaprequest
from nostr_sdk import Tag, Keys, nip44_encrypt, nip44_decrypt, Nip44Version, EventBuilder, Client, Filter, Kind, \
EventId, nip04_decrypt, nip04_encrypt, Options, NostrSigner, PublicKey, init_logger, LogLevel, Metadata
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
class NutWallet(object):

View File

@ -10,7 +10,7 @@ from nostr_sdk import Tag, PublicKey, EventId, Keys, nip04_encrypt, EventBuilder
from pyupload.uploader import CatboxUploader
import pandas
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nip98_utils import generate_nip98_header

View File

@ -4,7 +4,7 @@ from nostr_sdk import Tag, Keys, EventBuilder, Kind, NostrSigner, Client
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nostr_utils import send_event, check_and_set_private_key
from nostr_dvm.utils.print import bcolors
from nostr_dvm.utils.print_utils import bcolors
async def create_reaction(keys, title, dtag):

View File

@ -2,7 +2,10 @@ import asyncio
import time
from nostr_sdk import Client, NostrSigner, Keys, Event, UnsignedEvent, Filter, \
HandleNotification, Timestamp, nip04_decrypt, UnwrappedGift, init_logger, LogLevel, Kind, KindEnum
HandleNotification, Timestamp, nip04_decrypt, UnwrappedGift, init_logger, LogLevel, Kind, KindEnum, EventBuilder, \
Tag
from nostr_dvm.utils.nostr_utils import send_nip04_dm
async def test():
@ -39,8 +42,7 @@ async def test():
try:
msg = nip04_decrypt(sk, event.author(), event.content())
print(f"Received new msg: {msg}")
#await client.send_direct_msg(event.author(), f"Echo: {msg}", event.id())
await client.send_private_msg(event.author(), f"Echo: {msg}", event.id())
await send_nip04_dm(client, msg, event.author(), sk)
except Exception as e:

View File

@ -28,7 +28,7 @@
"lnbits_admin_key = \"\" #TODO set your key here\n",
"lnbits_wallet_id = \"\" #TODO set your key here\n",
"lnbits_host= \"https://demo.lnbits.com\" #TODO you can use demo.lnbits.com, but rather use your own instance\n",
"nostdress_domain = \"nostrdvm.com\" #TODO use your own nostdress instance, or use the default one"
"nostdress_domain = \"nostrdvm.com\" # use your own nostdress instance, or use the default one"
],
"id": "2e71e7aa2ebdac50"
},

View File

@ -35,8 +35,15 @@ def run_dvm(identifier):
name = "My very first DVM"
# Next we initalize a GenericDVM with the name and the dvm_config and the options we just created, as well as
# an empty AdminConfig() and NIP89Config(). We will check these out in later tutorials, so don't worry about them now.
# We add an admin config. By configuring it we can perform certain tasks, for example on start of the DVM
admin_config = AdminConfig()
# We broadcast our NIP65 inbox relays so other clients know where to write to so we receive it
admin_config.REBROADCAST_NIP65_RELAY_LIST = True
dvm = GenericDVM(name=name, dvm_config=dvm_config, options=options,
nip89config=NIP89Config(), admin_config=AdminConfig())
nip89config=NIP89Config(), admin_config=admin_config)
# Normally we would define the dvm interface as we do in the tasks folder (we will do it later in the tutorials as well,

View File

@ -0,0 +1,68 @@
# In tutorial 2 we have written a very simplistic DVM that replies with "The result of the DVM is: #RunDVM"
# In tutorial 3 we have written a client that requests a response from the DVM and gets the reply back.
# In this tutorial we build a simple bot that bridges the communication between the user and the Kind 5050
# (Text generation) DVM.
import asyncio
import os
import threading
from pathlib import Path
import dotenv
from nostr_dvm.bot import Bot
from nostr_dvm.tasks.generic_dvm import GenericDVM
from nostr_sdk import Kind, Keys
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import build_default_config, DVMConfig
from nostr_dvm.utils.nip89_utils import NIP89Config
from nostr_dvm.utils.zap_utils import change_ln_address
def run_dvm(identifier):
identifier = "bot_test"
bot_config = build_default_config(identifier)
# The main purpose is of the Bot is to be an indexable overview of multiple DVMs. But we will use it in "chatbot" mode here
# by setting the CHATBOT option to true
bot_config.CHATBOT = True
# And we simply hand over the publickey of our DVM from tutorial 1
bot_config.DVM_KEY = "aa8ab5b774d47e7b29a985dd739cfdcccf93451678bf7977ba1b2e094ecd8b30" # TODO replace with your DVM
# We update our relay list and profile and Start the bot
admin_config = AdminConfig()
admin_config.REBROADCAST_NIP65_RELAY_LIST = True
admin_config.UPDATE_PROFILE = True
x = threading.Thread(target=Bot, args=([bot_config, admin_config]))
x.start()
# Now you can copy the npub to a Social client of your choice and (if tutorials 2 and 4 are running) it should reply
# in your client.
if __name__ == '__main__':
#We open the .env file we created before.
env_path = Path('.env')
if not env_path.is_file():
with open('.env', 'w') as f:
print("Writing new .env file")
f.write('')
if env_path.is_file():
print(f'loading environment from {env_path.resolve()}')
dotenv.load_dotenv(env_path, verbose=True, override=True)
else:
raise FileNotFoundError(f'.env file not found at {env_path} ')
# Replace the identifier with the one from the last notebook, or a new dvmconfig will be stored
identifier = "tutorial01"
# psst, you can change your lightning address here:
#asyncio.run(change_ln_address(identifier, "test", DVMConfig(), True))
run_dvm(identifier)