mirror of
https://github.com/believethehype/nostrdvm.git
synced 2025-03-17 21:31:52 +01:00
add discover people based on wot, update dependencies (get rid of secp256k1 for now), update nostr-sdk
This commit is contained in:
parent
b21a5a1055
commit
738768749a
6
.idea/dvm.iml
generated
6
.idea/dvm.iml
generated
@ -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" />
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
412
nostr_dvm/tasks/people_discovery_wot.py
Normal file
412
nostr_dvm/tasks/people_discovery_wot.py
Normal 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)
|
@ -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'
|
||||
|
@ -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])
|
||||
|
17
setup.py
17
setup.py
@ -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
165
tests/discovery_people.py
Normal 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()
|
@ -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())
|
||||
|
Loading…
x
Reference in New Issue
Block a user