add discover people based on wot, update dependencies (get rid of secp256k1 for now), update nostr-sdk

This commit is contained in:
Believethehype 2024-06-14 18:50:04 +02:00
parent b21a5a1055
commit 738768749a
9 changed files with 656 additions and 21 deletions

6
.idea/dvm.iml generated
View File

@ -6,7 +6,13 @@
<excludeFolder url="file://$MODULE_DIR$/venv" />
<excludeFolder url="file://$MODULE_DIR$/venv_package_test" />
<excludeFolder url="file://$MODULE_DIR$/venv_package_test2" />
<excludeFolder url="file://$MODULE_DIR$/p12venv" />
<excludeFolder url="file://$MODULE_DIR$/venv12" />
<excludeFolder url="file://$MODULE_DIR$/venvp12" />
<excludeFolder url="file://$MODULE_DIR$/p12" />
<excludeFolder url="file://$MODULE_DIR$/p123" />
<excludeFolder url="file://$MODULE_DIR$/p1234" />
<excludeFolder url="file://$MODULE_DIR$/p23" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (dvm)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@ -74,7 +74,7 @@ def check_server_status(jobID, address) -> str | pd.DataFrame:
if log != "":
print(log)
# WAITING = 0, RUNNING = 1, FINISHED = 2, ERROR = 3
asyncio.sleep(1.0)
time.sleep(1.0)
if status == 2:

View File

@ -5,7 +5,6 @@ from io import BytesIO
import requests
from PIL import Image
from nostr_sdk import Kind
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils.admin_utils import AdminConfig
@ -25,10 +24,12 @@ Outputs: An url to an Image
class ImageGenerationDALLE(DVMTaskInterface):
KIND: Kind = EventDefinitions.KIND_NIP90_GENERATE_IMAGE
KIND: EventDefinitions.KIND_NIP90_GENERATE_IMAGE
TASK: str = "text-to-image"
FIX_COST: float = 120
dependencies = [("nostr-dvm", "nostr-dvm"),
("requests", "requests"),
("pillow", "pillow"),
("openai", "openai==1.3.5")]
async def init_dvm(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,

View File

@ -0,0 +1,412 @@
import asyncio
import csv
import json
import os
from datetime import timedelta
import networkx as nx
import pandas as pd
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, NostrDatabase, \
ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel, Event, EventId, Kind, \
RelayOptions
from nostr_dvm.interfaces.dvmtaskinterface import DVMTaskInterface, process_venv
from nostr_dvm.utils import definitions
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.dvmconfig import DVMConfig, build_default_config
from nostr_dvm.utils.nip88_utils import NIP88Config, check_and_set_d_tag_nip88, check_and_set_tiereventid_nip88
from nostr_dvm.utils.nip89_utils import NIP89Config, check_and_set_d_tag, create_amount_tag
from nostr_dvm.utils.output_utils import post_process_list_to_events, post_process_list_to_users
"""
This File contains a Module to discover users followed by users you follow, based on WOT
Accepted Inputs: none
Outputs: A list of users
Params: None
"""
class DiscoverPeopleWOT(DVMTaskInterface):
KIND: Kind = EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY
TASK: str = "discover-people"
FIX_COST: float = 0
dvm_config: DVMConfig
request_form = None
last_schedule: int
db_since = 3600
db_name = "db/nostr_followlists.db"
min_reactions = 2
personalized = True
result = ""
async def init_dvm(self, name, dvm_config: DVMConfig, nip89config: NIP89Config, nip88config: NIP88Config = None,
admin_config: AdminConfig = None, options=None):
dvm_config.SCRIPT = os.path.abspath(__file__)
self.request_form = {"jobID": "generic"}
opts = {
"max_results": 200,
}
self.request_form['options'] = json.dumps(opts)
self.last_schedule = Timestamp.now().as_secs()
if self.options.get("db_name"):
self.db_name = self.options.get("db_name")
if self.options.get("db_since"):
self.db_since = int(self.options.get("db_since"))
use_logger = False
if use_logger:
init_logger(LogLevel.DEBUG)
if self.dvm_config.UPDATE_DATABASE:
await self.sync_db()
if not self.personalized:
self.result = await self.calculate_result(self.request_form)
def is_input_supported(self, tags, client=None, dvm_config=None):
for tag in tags:
if tag.as_vec()[0] == 'i':
input_value = tag.as_vec()[1]
input_type = tag.as_vec()[2]
if input_type != "text":
return False
return True
def create_request_from_nostr_event(self, event, client=None, dvm_config=None):
self.dvm_config = dvm_config
request_form = {"jobID": event.id().to_hex()}
# default values
max_results = 20
user = event.author().to_hex()
print(user)
depth = 2
for tag in event.tags():
if tag.as_vec()[0] == 'i':
input_type = tag.as_vec()[2]
elif tag.as_vec()[0] == 'param':
param = tag.as_vec()[1]
if param == "max_results": # check for param type
max_results = int(tag.as_vec()[2])
elif param == "user": # check for param type
user = tag.as_vec()[2]
print(user)
options = {
"user": user,
"max_results": max_results,
"depth": depth,
}
request_form['options'] = json.dumps(options)
return request_form
async def process(self, request_form):
# if the dvm supports individual results, recalculate it every time for the request
if self.personalized:
return await self.calculate_result(request_form)
# else return the result that gets updated once every scheduled update. In this case on database update.
else:
return self.result
async def calculate_result(self, request_form):
from nostr_sdk import Filter
from types import SimpleNamespace
ns = SimpleNamespace()
options = self.set_options(request_form)
file = "db/friends223.csv"
try:
print("Deleting existing file, creating new one")
os.remove(file)
except:
print("Creating new file")
# sync the database, this might take a while if it's empty or hasn't been updated in a long time
user_id = PublicKey.parse(options["user"]).to_hex()
user_friends_level1 = await analyse_users([user_id])
friendlist = []
for npub in user_friends_level1[0].friends:
friendlist.append(npub)
me = Friend(user_id, friendlist)
write_to_csv([me], file)
# for every npub we follow, we look at the npubs they follow (this might take a while)
if int(options["depth"]) >= 2:
friendlist2 = []
for friend in user_friends_level1:
for npub in friend.friends:
friendlist2.append(npub)
user_friends_level2 = await analyse_users(friendlist2)
write_to_csv(user_friends_level2, file)
if int(options["depth"]) >= 3:
friendlist3 = []
for friend in user_friends_level2:
for npub in friend.friends:
friendlist3.append(npub)
print(len(friendlist3))
user_friends_level3 = await analyse_users(friendlist3)
write_to_csv(user_friends_level3, file)
df = pd.read_csv(file, sep=',')
df.info()
df.tail()
G_fb = nx.read_edgelist(file, delimiter=",", create_using=nx.DiGraph(), nodetype=str)
print(G_fb)
pr = nx.pagerank(G_fb)
# Use this to find people your followers follow
user_id = PublicKey.parse(options["user"]).to_hex()
user_friends_level1 = await analyse_users([user_id])
friendlist = []
for npub in user_friends_level1[0].friends:
friendlist.append(npub)
sorted_nodes = sorted([(node, pagerank) for node, pagerank in pr.items() if node not in friendlist],
key=lambda x: pr[x[0]],
reverse=True)[:int(options["max_results"])]
for node in sorted_nodes:
print(node[0] + "," + str(node[1]))
result_list = []
for entry in sorted_nodes:
# print(EventId.parse(entry[0]).to_bech32() + "/" + EventId.parse(entry[0]).to_hex() + ": " + str(entry[1]))
e_tag = Tag.parse(["p", entry[0], str(entry[1])])
result_list.append(e_tag.as_vec())
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print("[" + self.dvm_config.NIP89.NAME + "] Filtered " + str(
len(result_list)) + " fitting events.")
return json.dumps(result_list)
def post_process(self, result, event):
"""Overwrite the interface function to return a social client readable format, if requested"""
for tag in event.tags():
if tag.as_vec()[0] == 'output':
format = tag.as_vec()[1]
if format == "text/plain": # check for output type
result = post_process_list_to_users(result)
# if not text/plain, don't post-process
return result
async def schedule(self, dvm_config):
if dvm_config.SCHEDULE_UPDATES_SECONDS == 0:
return 0
else:
if Timestamp.now().as_secs() >= self.last_schedule + dvm_config.SCHEDULE_UPDATES_SECONDS:
if self.dvm_config.UPDATE_DATABASE:
await self.sync_db()
self.last_schedule = Timestamp.now().as_secs()
if not self.personalized:
self.result = await self.calculate_result(self.request_form)
return 1
async def sync_db(self):
opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=self.dvm_config.RELAY_LONG_TIMEOUT)))
sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY)
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
await cli.add_relay(relay)
await cli.connect()
timestamp_since = Timestamp.now().as_secs() - self.db_since
since = Timestamp.from_secs(timestamp_since)
filter1 = Filter().kind(Kind(3))
# filter = Filter().author(keys.public_key())
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print("[" + self.dvm_config.NIP89.NAME + "] Syncing notes of the last " + str(
self.db_since) + " seconds.. this might take a while..")
dbopts = NegentropyOptions().direction(NegentropyDirection.DOWN)
await cli.reconcile(filter1, dbopts)
await cli.database().delete(Filter().until(Timestamp.from_secs(
Timestamp.now().as_secs() - self.db_since))) # Clear old events so db doesn't get too full.
await cli.shutdown()
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print(
"[" + self.dvm_config.NIP89.NAME + "] Done Syncing Notes of the last " + str(
self.db_since) + " seconds..")
async def analyse_users(user_ids=None):
if user_ids is None:
user_ids = []
try:
user_keys = []
for npub in user_ids:
try:
user_keys.append(PublicKey.parse(npub))
except Exception as e:
print(npub)
print(e)
database = await NostrDatabase.sqlite("db/nostr_followlists.db")
followers_filter = Filter().authors(user_keys).kind(Kind(3))
followers = await database.query([followers_filter])
allfriends = []
if len(followers) > 0:
for follower in followers:
frens = []
for tag in follower.tags():
if tag.as_vec()[0] == "p":
frens.append(tag.as_vec()[1])
allfriends.append(Friend(follower.author().to_hex(), frens))
return allfriends
else:
print("no followers")
return []
except Exception as e:
print(e)
return []
class Friend(object):
def __init__(self, user_id, friends):
self.user_id = user_id
self.friends = friends
def write_to_csv(friends, file="friends222.csv"):
with open(file, 'a') as f:
writer = csv.writer(f)
friendcounter = 0
for friend in friends:
print(friendcounter)
friendcounter += 1
for fren in friend.friends:
row = [friend.user_id, fren]
writer.writerow(row)
# We build an example here that we can call by either calling this file directly from the main directory,
# or by adding it to our playground. You can call the example and adjust it to your needs or redefine it in the
# playground or elsewhere
def build_example(name, identifier, admin_config, options, cost=0, update_rate=180, processing_msg=None,
update_db=True):
dvm_config = build_default_config(identifier)
dvm_config.USE_OWN_VENV = False
dvm_config.SHOWLOG = True
dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 10 minutes
dvm_config.UPDATE_DATABASE = update_db
# Activate these to use a subscription based model instead
# dvm_config.SUBSCRIPTION_REQUIRED = True
# dvm_config.SUBSCRIPTION_DAILY_COST = 1
dvm_config.FIX_COST = cost
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
admin_config.LUD16 = dvm_config.LN_ADDRESS
image = "https://image.nostr.build/3cdd70113e05375e6240f2ecca5d9f4ee783ab386b00cc07ca907b601ab91a85.jpg",
# Add NIP89
nip89info = {
"name": name,
"image": image,
"picture": image,
"about": "I show notes that are currently popular",
"lud16": dvm_config.LN_ADDRESS,
"encryptionSupported": True,
"cashuAccepted": True,
"personalized": False,
"amount": create_amount_tag(cost),
"nip90Params": {
"max_results": {
"required": False,
"values": [],
"description": "The number of maximum results to return (default currently 100)"
}
}
}
nip89config = NIP89Config()
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
# admin_config.UPDATE_PROFILE = False
# admin_config.REBROADCAST_NIP89 = False
return DiscoverPeopleWOT(name=name, dvm_config=dvm_config, nip89config=nip89config,
admin_config=admin_config, options=options)
def build_example_subscription(name, identifier, admin_config, options, update_rate=180, processing_msg=None,
update_db=True):
dvm_config = build_default_config(identifier)
dvm_config.USE_OWN_VENV = False
dvm_config.SHOWLOG = True
dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 3 minutes
dvm_config.UPDATE_DATABASE = update_db
# Activate these to use a subscription based model instead
# dvm_config.SUBSCRIPTION_DAILY_COST = 1
dvm_config.FIX_COST = 0
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
admin_config.LUD16 = dvm_config.LN_ADDRESS
image = "https://image.nostr.build/3cdd70113e05375e6240f2ecca5d9f4ee783ab386b00cc07ca907b601ab91a85.jpg",
# Add NIP89
nip89info = {
"name": name,
"image": image,
"picture": image,
"about": "I show notes that are currently popular all over Nostr. I'm also used for testing subscriptions.",
"lud16": dvm_config.LN_ADDRESS,
"encryptionSupported": True,
"cashuAccepted": True,
"subscription": True,
"personalized": False,
"nip90Params": {
"max_results": {
"required": False,
"values": [],
"description": "The number of maximum results to return (default currently 100)"
}
}
}
nip89config = NIP89Config()
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
nip88config = NIP88Config()
nip88config.DTAG = check_and_set_d_tag_nip88(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip88config.TIER_EVENT = check_and_set_tiereventid_nip88(identifier, "1")
nip89config.NAME = name
nip88config.IMAGE = nip89info["image"]
nip88config.TITLE = name
nip88config.AMOUNT_DAILY = 100
nip88config.AMOUNT_MONTHLY = 2000
nip88config.CONTENT = "Subscribe to the DVM for unlimited use during your subscription"
nip88config.PERK1DESC = "Unlimited requests"
nip88config.PERK2DESC = "Support NostrDVM & NostrSDK development"
nip88config.PAYMENT_VERIFIER_PUBKEY = "5b5c045ecdf66fb540bdf2049fe0ef7f1a566fa427a4fe50d400a011b65a3a7e"
# admin_config.UPDATE_PROFILE = False
# admin_config.REBROADCAST_NIP89 = False
# admin_config.REBROADCAST_NIP88 = False
# admin_config.FETCH_NIP88 = True
# admin_config.EVENTID = ""
# admin_config.PRIVKEY = dvm_config.PRIVATE_KEY
return DiscoverPeopleWOT(name=name, dvm_config=dvm_config, nip89config=nip89config,
nip88config=nip88config, options=options,
admin_config=admin_config)
if __name__ == '__main__':
process_venv(DiscoverPeopleWOT)

View File

@ -49,7 +49,7 @@ class DVMConfig:
LN_ADDRESS = ''
SCRIPT = ''
IDENTIFIER = ''
USE_OWN_VENV = True # Make an own venv for each dvm's process function.Disable if you want to install packages into main venv. Only recommended if you dont want to run dvms with different dependency versions
USE_OWN_VENV = False # Make an own venv for each dvm's process function.Disable if you want to install packages into main venv. Only recommended if you dont want to run dvms with different dependency versions
DB: str
NEW_USER_BALANCE: int = 0 # Free credits for new users
SUBSCRIPTION_MANAGEMENT = 'https://noogle.lol/discovery'

View File

@ -4,6 +4,7 @@ import os
import urllib.parse
from pathlib import Path
import bech32
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
@ -12,7 +13,6 @@ from nostr_sdk import PublicKey, SecretKey, Event, EventBuilder, Tag, Keys, gene
Timestamp
from nostr_dvm.utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags
import lnurl
from hashlib import sha256
import dotenv
@ -112,7 +112,8 @@ def create_bolt11_ln_bits(sats: int, config) -> (str, str):
def create_bolt11_lud16(lud16, amount):
if lud16.startswith("LNURL") or lud16.startswith("lnurl"):
url = lnurl.decode(lud16)
url = decode_bech32(lud16)
print(url)
elif '@' in lud16: # LNaddress
url = 'https://' + str(lud16).split('@')[1] + '/.well-known/lnurlp/' + str(lud16).split('@')[0]
else: # No lud16 set or format invalid
@ -240,13 +241,29 @@ def decrypt_private_zap_message(msg: str, privkey: SecretKey, pubkey: PublicKey)
return str(ex)
def decode_bech32(encoded_lnurl):
# Decode the bech32 encoded string
hrp, data = bech32.bech32_decode(encoded_lnurl)
if hrp != 'lnurl':
raise ValueError("Invalid human-readable part (hrp)")
# Convert the data back from 5-bit words to 8-bit bytes
decoded_bytes = bech32.convertbits(data, 5, 8, False)
# Convert the bytes back to a string
decoded_url = bytes(decoded_bytes).decode('utf-8')
return decoded_url
def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys, relay_list, zaptype="public"):
print(lud16)
print(str(amount))
print(content)
print(zapped_user.to_hex())
if lud16.startswith("LNURL") or lud16.startswith("lnurl"):
url = lnurl.decode(lud16)
url = decode_bech32(lud16)
elif '@' in lud16: # LNaddress
url = 'https://' + str(lud16).split('@')[1] + '/.well-known/lnurlp/' + str(lud16).split('@')[0]
else: # No lud16 set or format invalid
@ -256,7 +273,13 @@ def zaprequest(lud16: str, amount: int, content, zapped_event, zapped_user, keys
ob = json.loads(response.content)
callback = ob["callback"]
print(ob["callback"])
encoded_lnurl = lnurl.encode(url)
#encoded_lnurl = lnurl.encode(url)
url_bytes = url.encode()
encoded_lnurl = bech32.bech32_encode('lnurl', bech32.convertbits(url_bytes, 8, 5))
amount_tag = Tag.parse(['amount', str(amount * 1000)])
relays_tag = Tag.parse(['relays', str(relay_list)])
lnurl_tag = Tag.parse(['lnurl', encoded_lnurl])

View File

@ -1,6 +1,6 @@
from setuptools import setup, find_packages
VERSION = '0.6.8'
VERSION = '0.6.9'
DESCRIPTION = 'A framework to build and run Nostr NIP90 Data Vending Machines'
LONG_DESCRIPTION = ('A framework to build and run Nostr NIP90 Data Vending Machines. See the github repository for more information')
@ -14,25 +14,24 @@ setup(
long_description=LONG_DESCRIPTION,
packages=find_packages(include=['nostr_dvm', 'nostr_dvm.*']),
install_requires=["nostr-sdk==0.32.1",
install_requires=["nostr-sdk==0.32.2",
"bech32==1.2.0",
"networkx==3.3",
"scipy==1.13.1",
"beautifulsoup4==4.12.3",
"pycryptodome==3.20.0",
"python-dotenv==1.0.0",
"emoji==2.8.0",
"emoji==2.12.1",
"ffmpegio==0.9.1",
"lnurl",
"pandas==2.1.3",
"Pillow==10.1.0",
"PyUpload==0.1.4",
"requests==2.31.0",
"requests==2.32.3",
"instaloader==4.10.1",
"pytube==15.0.0",
"moviepy==2.0.0.dev2",
"zipp==3.17.0",
"urllib3==2.1.0",
"urllib3==2.2.1",
"networkx==3.3",
"scipy==1.13.1",
"beautifulsoup4==4.12.3"
],
keywords=['nostr', 'nip90', 'dvm', 'data vending machine'],
url="https://github.com/believethehype/nostrdvm",

165
tests/discovery_people.py Normal file
View File

@ -0,0 +1,165 @@
import json
import os
import threading
from pathlib import Path
import dotenv
from nostr_sdk import init_logger, LogLevel, Keys, NostrLibrary
from nostr_dvm.tasks.people_discovery_wot import DiscoverPeopleWOT
from nostr_dvm.utils.admin_utils import AdminConfig
from nostr_dvm.utils.dvmconfig import build_default_config
from nostr_dvm.utils.nip89_utils import create_amount_tag, NIP89Config, check_and_set_d_tag
rebroadcast_NIP89 = False # Announce NIP89 on startup
rebroadcast_NIP65_Relay_List = False
update_profile = False
global_update_rate = 1200 # set this high on first sync so db can fully sync before another process trys to.
use_logger = True
AVOID_PAID_OUTBOX_RELAY_LIST = ["wss://nostrelay.yeghro.site", "wss://nostr.wine", "wss://filter.nostr.wine"
"wss://nostr21.com", "wss://nostr.bitcoiner.social", "wss://nostr.orangepill.dev",
"wss://relay.lnpay.me", "wss://relay.snort.social", "wss://relay.minds.com/nostr/v1/ws",
"wss://nostr-pub.semisol.dev", "wss://mostr.mostr.pub", "wss://relay.mostr.pub", "wss://minds.com",
"wss://yabu.me", "wss://relay.yozora.world", "wss://filter.nostr.wine/?global=all", "wss://eden.nostr.land",
"wss://relay.orangepill.ovh", "wss://nostr.jcloud.es", "wss://af.purplerelay.com", "wss://za.purplerelay.com",
"wss://relay.nostrich.land", "wss://relay.nostrplebs.com", "wss://relay.nostrich.land",
"wss://rss.nos.social", "wss://atlas.nostr.land", "wss://puravida.nostr.land", "wss://nostr.inosta.cc",
"wss://relay.orangepill.dev", "wss://no.str.cr", "wss://nostr.milou.lol", "wss://relay.nostr.com.au",
"wss://puravida.nostr.land", "wss://atlas.nostr.land", "wss://nostr-pub.wellorder.net", "wss://eelay.current.fyi",
"wss://nostr.thesamecat.io", "wss://nostr.plebchain.org", "wss://relay.noswhere.com", "wss://nostr.uselessshit.co",
"wss://bitcoiner.social", "wss://relay.stoner.com", "wss://nostr.l00p.org", "wss://relay.nostr.ro", "wss://nostr.kollider.xyz",
"wss://relay.valera.co", "wss://relay.austrich.net", "wss://relay.nostrich.de", "wss://nostr.azte.co", "wss://nostr-relay.schnitzel.world",
"wss://relay.nostriches.org", "wss://happytavern.co", "wss://onlynotes.lol", "wss://offchain.pub", "wss://purplepag.es", "wss://relay.plebstr.com",
"wss://poster.place/relay", "wss://relayable.org", "wss://bbb.santos.lol", "wss://relay.bitheaven.social", "wss://theforest.nostr1.com",
"wss://relay.nostrati.com", "wss://purplerelay.com", "wss://hist.nostr.land", "wss://creatr.nostr.wine", "ws://localhost:4869",
"wss://pyramid.fiatjaf.com", "wss://relay.nos.social", "wss://nostr.thank.eu", "wss://inbox.nostr.wine", "wss://relay.pleb.to", "wss://welcome.nostr.wine",
"wss://relay.nostrview.com", "wss://nostr.land", "wss://eu.purplerelay.com", "wss://xmr.usenostr.org", "wss://relay.pleb.to", "wss://nostr-relay.app"
]
RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io"] # , "wss://relay.snort.social"]
if use_logger:
init_logger(LogLevel.INFO)
def build_example_wot(name, identifier, admin_config, options, image, cost=0, update_rate=180, processing_msg=None,
update_db=True):
dvm_config = build_default_config(identifier)
dvm_config.USE_OWN_VENV = False
dvm_config.LOGLEVEL = LogLevel.INFO
# dvm_config.SHOWLOG = True
dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 10 minutes
dvm_config.UPDATE_DATABASE = update_db
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.LOGLEVEL = LogLevel.DEBUG
dvm_config.FIX_COST = cost
#dvm_config.RELAY_LIST = ["wss://dvms.f7z.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg",
#"wss://relay.nostr.net"]
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_PAID_OUTBOX_RELAY_LIST
#dvm_config.RELAY_LIST = ["wss://dvms.f7z.io",
# "wss://nostr.mom", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg"
# ]
admin_config.LUD16 = dvm_config.LN_ADDRESS
# Add NIP89
nip89info = {
"name": name,
"image": image,
"picture": image,
"about": "I show people to follow from your WOT",
"lud16": dvm_config.LN_ADDRESS,
"encryptionSupported": True,
"cashuAccepted": True,
"personalized": True,
"amount": create_amount_tag(cost),
"nip90Params": {
"max_results": {
"required": False,
"values": [],
"description": "The number of maximum results to return (default currently 200)"
}
}
}
nip89config = NIP89Config()
nip89config.DTAG = check_and_set_d_tag(identifier, name, dvm_config.PRIVATE_KEY, nip89info["image"])
nip89config.CONTENT = json.dumps(nip89info)
return DiscoverPeopleWOT(name=name, dvm_config=dvm_config, nip89config=nip89config,
admin_config=admin_config, options=options)
def playground():
# Popular Global
admin_config_global_wot = AdminConfig()
admin_config_global_wot.REBROADCAST_NIP89 = rebroadcast_NIP89
admin_config_global_wot.REBROADCAST_NIP65_RELAY_LIST = rebroadcast_NIP65_Relay_List
admin_config_global_wot.UPDATE_PROFILE = update_profile
# admin_config_global_popular.DELETE_NIP89 = True
# admin_config_global_popular.PRIVKEY = ""
# admin_config_global_popular.EVENTID = "2fea4ee2ccf0fa11db171113ffd7a676f800f34121478b7c9a4e73c2f1990028"
# admin_config_global_popular.POW = True
custom_processing_msg = ["Looking for people, that your WOT follows"]
update_db = True
options_wot = {
"db_name": "db/nostr_followlists.db",
"db_since": 60 * 60 * 24 * 365, # 1h since gmt,
}
cost = 0
image = "https://image.nostr.build/b29b6ec4bf9b6184f69d33cb44862db0d90a2dd9a506532e7ba5698af7d36210.jpg"
discovery_wot = build_example_wot("People you might know",
"discovery_people_wot",
admin_config=admin_config_global_wot,
options=options_wot,
image=image,
cost=cost,
update_rate=global_update_rate,
processing_msg=custom_processing_msg,
update_db=update_db)
discovery_wot.run()
# discovery_test_sub = content_discovery_currently_popular.build_example_subscription("Currently Popular Notes DVM (with Subscriptions)", "discovery_content_test", admin_config)
# discovery_test_sub.run()
# Subscription Manager DVM
# subscription_config = DVMConfig()
# subscription_config.PRIVATE_KEY = check_and_set_private_key("dvm_subscription")
# npub = Keys.parse(subscription_config.PRIVATE_KEY).public_key().to_bech32()
# invoice_key, admin_key, wallet_id, user_id, lnaddress = check_and_set_ln_bits_keys("dvm_subscription", npub)
# subscription_config.LNBITS_INVOICE_KEY = invoice_key
# subscription_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back
# subscription_config.LNBITS_URL = os.getenv("LNBITS_HOST")
# sub_admin_config = AdminConfig()
# sub_admin_config.USERNPUBS = ["7782f93c5762538e1f7ccc5af83cd8018a528b9cd965048386ca1b75335f24c6"] #Add npubs of services that can contact the subscription handler
# currently there is none, but add this once subscriptions are live.
# x = threading.Thread(target=Subscription, args=(Subscription(subscription_config, sub_admin_config),))
# x.start()
# keep_alive()
if __name__ == '__main__':
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} ')
playground()

View File

@ -136,7 +136,6 @@ async def nostr_client_test_inactive_filter(user):
tags = [relaysTag, alttag, paramTag, paramTag2]
event = EventBuilder(EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY, str("Give me inactive users"),
tags).to_event(keys)
@ -151,6 +150,7 @@ async def nostr_client_test_inactive_filter(user):
await send_event(event, client=client, dvm_config=config)
return event.as_json()
async def nostr_client_test_tts(prompt):
keys = Keys.parse(check_and_set_private_key("test_client"))
@ -190,7 +190,6 @@ async def nostr_client_test_disovery(user, ptag):
tags = [relaysTag, alttag, paramTag, pTag]
event = EventBuilder(EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY, str("Give me content"),
tags).to_event(keys)
@ -206,6 +205,33 @@ async def nostr_client_test_disovery(user, ptag):
return event.as_json()
async def nostr_client_test_disovery_user(user, ptag):
keys = Keys.parse(check_and_set_private_key("test_client"))
relay_list = ["wss://relay.damus.io", "wss://dvms.f7z.io",
]
relaysTag = Tag.parse(relay_list)
alttag = Tag.parse(["alt", "This is a NIP90 DVM AI task to find users"])
paramTag = Tag.parse(["param", "user", user])
pTag = Tag.parse(["p", ptag])
tags = [relaysTag, alttag, paramTag, pTag]
event = EventBuilder(EventDefinitions.KIND_NIP90_PEOPLE_DISCOVERY, str("Give me people"),
tags).to_event(keys)
signer = NostrSigner.keys(keys)
client = Client(signer)
for relay in relay_list:
await client.add_relay(relay)
await client.connect()
config = DVMConfig
eventid = await send_event(event, client=client, dvm_config=config)
print(eventid.to_hex())
return event.as_json()
async def nostr_client_test_image_private(prompt, cashutoken):
keys = Keys.parse(check_and_set_private_key("test_client"))
receiver_keys = Keys.parse(check_and_set_private_key("replicate_sdxl"))
@ -274,7 +300,10 @@ async def nostr_client():
# await nostr_client_test_image("a beautiful purple ostrich watching the sunset")
# await nostr_client_test_search_profile("dontbelieve")
wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"]
await nostr_client_test_disovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "a21592a70ef9a00695efb3f7e816e17742d251559aff154b16d063a408bcd74d")
# aawait nostr_client_test_disovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "a21592a70ef9a00695efb3f7e816e17742d251559aff154b16d063a408bcd74d")
await nostr_client_test_disovery_user("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64",
"58c52fdca7593dffea63ba6f758779d8251c6732f54e9dc0e56d7a1afe1bb1b6")
# await nostr_client_test_censor_filter(wot)
# await nostr_client_test_inactive_filter("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64")
@ -299,7 +328,7 @@ async def nostr_client():
print("[Nostr Client]: " + f"Received new zap:")
print(event.as_json())
def handle_msg(self, relay_url, msg):
async def handle_msg(self, relay_url, msg):
return
await client.handle_notifications(NotificationHandler())