Merge pull request #30 from believethehype/lmdb

Lmdb
This commit is contained in:
dbth
2024-09-19 22:21:49 +02:00
committed by GitHub
62 changed files with 899 additions and 397 deletions

View File

@@ -3,7 +3,7 @@
#Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot
LNBITS_ADMIN_KEY = "" # In order to pay invoices, e.g. from the bot to DVMs, or reimburse users. Keep this secret and use responsibly.
LNBITS_ADMIN_ID = ""
LNBITS_WALLET_ID = ""
LNBITS_HOST = "https://demo.lnbits.com/"
# In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain.
# We will use the api to create and manage zapable lightning addresses

1
.gitignore vendored
View File

@@ -191,3 +191,4 @@ tests/db/Cashu/wallet.sqlite3
tests/pagerank/index_map_99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64.json
tests/pagerank/network_graph_99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64.json
tests/test_data/wallet_mint_api/wallet.sqlite3
.env_bkp2

View File

@@ -1,6 +1,6 @@
#Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot
LNBITS_ADMIN_KEY = ""
LNBITS_ADMIN_ID = ""
LNBITS_WALLET_ID = ""
LNBITS_HOST = "https://lnbits.com" #Use your own/a trusted instance ideally.
# In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain.
# We will use the api to create and manage zapable lightning addresses

View File

@@ -9,7 +9,7 @@ Projects in this folder contain ready-to-use DVMs. To tun the DVM following the
Create a new venv in this directory by opening the terminal here, or navigate to this directory and type: `"python -m venv venv"`
- Place .env file (based on .env_example) in this folder.
- Recommended but optional:
- Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_ADMIN_ID`, `LNBITS_HOST`.
- Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `LNBITS_HOST`.
- If you are running an own instance of `Nostdress` enter `NOSTDRESS_DOMAIN` or use the default one.
- Activate the venv with
- MacOS/Linux: source ./venv/bin/activate

View File

@@ -65,7 +65,7 @@ async def nostr_client():
print(f"Received new event from {relay_url}: {event.as_json()}")
if event.kind() == 7000:
print("[Nostr Client]: " + event.as_json())
elif 6000 < event.kind().as_u64() < 6999:
elif 6000 < event.kind().as_u16() < 6999:
print("[Nostr Client]: " + event.as_json())
print("[Nostr Client]: " + event.content())

View File

@@ -1,6 +1,6 @@
#Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot
LNBITS_ADMIN_KEY = ""
LNBITS_ADMIN_ID = ""
LNBITS_WALLET_ID = ""
LNBITS_HOST = "https://lnbits.com" #Use your own/a trusted instance ideally.
# In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain.
# We will use the api to create and manage zapable lightning addresses

View File

@@ -9,7 +9,7 @@ Projects in this folder contain ready-to-use DVMs. To tun the DVM following the
Create a new venv in this directory by opening the terminal here, or navigate to this directory and type: `"python -m venv venv"`
- Place .env file (based on .env_example) in this folder.
- Recommended but optional:
- Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_ADMIN_ID`, `LNBITS_HOST`.
- Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `LNBITS_HOST`.
- If you are running an own instance of `Nostdress` enter `NOSTDRESS_DOMAIN` or use the default one.
- Activate the venv with
- MacOS/Linux: source ./venv/bin/activate

View File

@@ -71,7 +71,7 @@ async def nostr_client():
print(f"Received new event from {relay_url}: {event.as_json()}")
if event.kind() == 7000:
print("[Nostr Client]: " + event.as_json())
elif 6000 < event.kind().as_u64() < 6999:
elif 6000 < event.kind().as_u16() < 6999:
print("[Nostr Client]: " + event.as_json())
print("[Nostr Client]: " + event.content())

View File

@@ -1,6 +1,6 @@
#Create an account with a lnbits instance of your choice, add the admin key and id here. This account will be used to create a new lnbits wallet for each dvm/bot
LNBITS_ADMIN_KEY = ""
LNBITS_ADMIN_ID = ""
LNBITS_WALLET_ID = ""
LNBITS_HOST = "https://lnbits.com" #Use your own/a trusted instance ideally.
# In order to create a zappable lightning address, host nostdress on your domain or use this preinstalled domain.
# We will use the api to create and manage zapable lightning addresses

View File

@@ -9,7 +9,7 @@ Projects in this folder contain ready-to-use DVMs. To tun the DVM following the
Create a new venv in this directory by opening the terminal here, or navigate to this directory and type: `"python -m venv venv"`
- Place .env file (based on .env_example) in this folder.
- Recommended but optional:
- Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_ADMIN_ID`, `LNBITS_HOST`.
- Create a `LNbits` account on an accessible instance of your choice, enter one account's id and admin key (this account will create other accounts for the dvms) Open the .env file and enter this info to `LNBITS_ADMIN_KEY`, `LNBITS_WALLET_ID`, `LNBITS_HOST`.
- If you are running an own instance of `Nostdress` enter `NOSTDRESS_DOMAIN` or use the default one.
- Activate the venv with
- MacOS/Linux: source ./venv/bin/activate

View File

@@ -70,7 +70,7 @@ async def nostr_client():
print(f"Received new event from {relay_url}: {event.as_json()}")
if event.kind() == 7000:
print("[Nostr Client]: " + event.as_json())
elif 6000 < event.kind().as_u64() < 6999:
elif 6000 < event.kind().as_u16() < 6999:
print("[Nostr Client " + event.author().to_bech32() + "]: " + event.as_json())
print("[Nostr Client " + event.author().to_bech32() + "]: " + event.content())

View File

@@ -68,7 +68,7 @@ class Bot:
kinds = [EventDefinitions.KIND_NIP90_GENERIC, EventDefinitions.KIND_FEEDBACK]
for dvm in self.dvm_config.SUPPORTED_DVMS:
if dvm.KIND not in kinds:
kinds.append(Kind(dvm.KIND.as_u64() + 1000))
kinds.append(Kind(dvm.KIND.as_u16() + 1000))
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()))
await self.client.subscribe([zap_filter, dm_filter, nip17_filter, dvm_filter], None)
@@ -82,8 +82,8 @@ class Bot:
keys = self.keys
async def handle(self, relay_url, subscription_id, nostr_event):
if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() + 1000 <= nostr_event.kind().as_u64()
<= EventDefinitions.KIND_NIP90_GENERIC.as_u64() + 1000):
if (EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u16() + 1000 <= nostr_event.kind().as_u16()
<= EventDefinitions.KIND_NIP90_GENERIC.as_u16() + 1000):
await handle_nip90_response_event(nostr_event)
elif nostr_event.kind() == EventDefinitions.KIND_FEEDBACK:
await handle_nip90_feedback(nostr_event)
@@ -459,7 +459,7 @@ class Bot:
return
dvms = [x for x in self.dvm_config.SUPPORTED_DVMS if
x.PUBLIC_KEY == nostr_event.author().to_hex() and x.KIND.as_u64() == nostr_event.kind().as_u64() - 1000]
x.PUBLIC_KEY == nostr_event.author().to_hex() and x.KIND.as_u16() == nostr_event.kind().as_u16() - 1000]
if len(dvms) > 0:
dvm = dvms[0]
if dvm.dvm_config.EXTERNAL_POST_PROCESS_TYPE != PostProcessFunctionType.NONE:

View File

@@ -91,11 +91,11 @@ class DVM:
async def handle(self, relay_url, subscription_id, nostr_event: Event):
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print(nostr_event.as_json())
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() <= nostr_event.kind().as_u64() <= EventDefinitions.KIND_NIP90_GENERIC.as_u64():
if EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u16() <= nostr_event.kind().as_u16() <= EventDefinitions.KIND_NIP90_GENERIC.as_u16():
await handle_nip90_job_event(nostr_event)
elif nostr_event.kind().as_u64() == EventDefinitions.KIND_ZAP.as_u64():
elif nostr_event.kind().as_u16() == EventDefinitions.KIND_ZAP.as_u16():
await handle_zap(nostr_event)
elif nostr_event.kind().as_u64() == EventDefinitions.KIND_NIP61_NUT_ZAP.as_u64():
elif nostr_event.kind().as_u16() == EventDefinitions.KIND_NIP61_NUT_ZAP.as_u16():
await handle_nutzap(nostr_event)
async def handle_msg(self, relay_url, msg):
@@ -139,7 +139,7 @@ class DVM:
return
if self.dvm_config.LOGLEVEL.value >= LogLevel.INFO.value:
print(
bcolors.MAGENTA + "[" + self.dvm_config.NIP89.NAME + "] Received new Request: " + task + " from " + user.name + " (" + user.npub + ")" + bcolors.ENDC)
bcolors.MAGENTA + "[" + self.dvm_config.NIP89.NAME + "] Received new Request: " + task + " from " + user.name + " (" + PublicKey.parse(user.npub).to_bech32() + ")" + bcolors.ENDC)
duration = await input_data_file_duration(nip90_event, dvm_config=self.dvm_config, client=self.client)
amount = get_amount_per_task(task, self.dvm_config, duration)
if amount is None:
@@ -575,7 +575,7 @@ class DVM:
e_tag = Tag.parse(["e", original_event.id().to_hex()])
p_tag = Tag.parse(["p", original_event.author().to_hex()])
alt_tag = Tag.parse(["alt", "This is the result of a NIP90 DVM AI task with kind " + str(
original_event.kind().as_u64()) + ". The task was: " + original_event.content()])
original_event.kind().as_u16()) + ". The task was: " + original_event.content()])
status_tag = Tag.parse(["status", "success"])
reply_tags = [request_tag, e_tag, p_tag, alt_tag, status_tag]
@@ -607,17 +607,17 @@ class DVM:
content = nip04_encrypt(self.keys.secret_key(), PublicKey.from_hex(original_event.author().to_hex()),
content)
reply_event = EventBuilder(Kind(original_event.kind().as_u64() + 1000), str(content), reply_tags).to_event(
reply_event = EventBuilder(Kind(original_event.kind().as_u16() + 1000), str(content), reply_tags).to_event(
self.keys)
# send_event(reply_event, client=self.client, dvm_config=self.dvm_config)
await send_event_outbox(reply_event, client=self.client, dvm_config=self.dvm_config)
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print(bcolors.GREEN + "[" + self.dvm_config.NIP89.NAME + "] " + str(
original_event.kind().as_u64() + 1000) + " Job Response event sent: " + reply_event.as_json() + bcolors.ENDC)
original_event.kind().as_u16() + 1000) + " Job Response event sent: " + reply_event.as_json() + bcolors.ENDC)
elif self.dvm_config.LOGLEVEL.value >= LogLevel.INFO.value:
print(bcolors.GREEN + "[" + self.dvm_config.NIP89.NAME + "] " + str(
original_event.kind().as_u64() + 1000) + " Job Response event sent: " + reply_event.id().to_hex() + bcolors.ENDC)
original_event.kind().as_u16() + 1000) + " Job Response event sent: " + reply_event.id().to_hex() + bcolors.ENDC)
async def send_job_status_reaction(original_event, status, is_paid=True, amount=0, client=None,
content=None,
@@ -729,10 +729,10 @@ class DVM:
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print(bcolors.YELLOW + "[" + self.dvm_config.NIP89.NAME + "]" + " Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC)
EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC)
elif self.dvm_config.LOGLEVEL.value >= LogLevel.INFO.value:
print(bcolors.YELLOW + "[" + self.dvm_config.NIP89.NAME + "]" + " Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC)
EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC)
return reaction_event.as_json()
@@ -779,8 +779,8 @@ class DVM:
async def do_work(job_event, amount):
if ((
EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u64() <= job_event.kind().as_u64() <= EventDefinitions.KIND_NIP90_GENERIC.as_u64())
or job_event.kind().as_u64() == EventDefinitions.KIND_DM.as_u64()):
EventDefinitions.KIND_NIP90_EXTRACT_TEXT.as_u16() <= job_event.kind().as_u16() <= EventDefinitions.KIND_NIP90_GENERIC.as_u16())
or job_event.kind().as_u16() == EventDefinitions.KIND_DM.as_u16()):
task = await get_task(job_event, client=self.client, dvm_config=self.dvm_config)

View File

@@ -159,7 +159,7 @@ class Subscription:
reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content), reply_tags).to_event(keys)
await send_event(reaction_event, client=self.client, dvm_config=self.dvm_config)
print("[" + self.dvm_config.NIP89.NAME + "]" + ": Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + "success" + " " + reaction_event.as_json())
EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + "success" + " " + reaction_event.as_json())
async def pay_zap_split(nwc, overall_amount, zaps, tier, unit="msats"):
overallsplit = 0

View File

@@ -112,7 +112,7 @@ class DicoverContentLatestLongForm(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite(self.db_name)
database = NostrDatabase.lmdb(self.db_name)
#print(self.db_name)
cli = ClientBuilder().database(database).signer(signer).opts(opts).build()
await cli.connect()
@@ -178,7 +178,7 @@ class DicoverContentLatestLongForm(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -112,7 +112,7 @@ class DicoverContentLatestWiki(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite(self.db_name)
database = NostrDatabase.lmdb(self.db_name)
#print(self.db_name)
cli = ClientBuilder().database(database).signer(signer).opts(opts).build()
await cli.connect()
@@ -178,7 +178,7 @@ class DicoverContentLatestWiki(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -109,7 +109,7 @@ class DicoverContentCurrentlyPopular(DVMTaskInterface):
ns = SimpleNamespace()
options = self.set_options(request_form)
database = await NostrDatabase.sqlite(self.db_name)
database = NostrDatabase.lmdb(self.db_name)
timestamp_since = Timestamp.now().as_secs() - self.db_since
since = Timestamp.from_secs(timestamp_since)
@@ -175,7 +175,7 @@ class DicoverContentCurrentlyPopular(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -115,7 +115,7 @@ class DicoverContentCurrentlyPopularZaps(DVMTaskInterface):
#keys = Keys.parse(sk.to_hex())
#signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite(self.db_name)
database = NostrDatabase.lmdb(self.db_name)
#cli = ClientBuilder().database(database).signer(signer).opts(opts).build()
#await cli.connect()
@@ -230,7 +230,7 @@ class DicoverContentCurrentlyPopularZaps(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -101,7 +101,7 @@ class DicoverContentCurrentlyPopularFollowers(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite(self.db_name)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().database(database).signer(signer).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
await cli.add_relay(relay)
@@ -199,7 +199,7 @@ class DicoverContentCurrentlyPopularFollowers(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -112,7 +112,7 @@ class DicoverContentCurrentlyPopularGallery(DVMTaskInterface):
ns = SimpleNamespace()
options = self.set_options(request_form)
databasegallery = await NostrDatabase.sqlite(self.db_name)
databasegallery = NostrDatabase.lmdb(self.db_name)
timestamp_since = Timestamp.now().as_secs() - self.db_since
since = Timestamp.from_secs(timestamp_since)
@@ -250,7 +250,7 @@ class DicoverContentCurrentlyPopularGallery(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -106,6 +106,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface):
from nostr_sdk import Filter
from types import SimpleNamespace
ns = SimpleNamespace()
options = self.set_options(request_form)
@@ -114,9 +115,17 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
try:
await database.delete(Filter().until(Timestamp.from_secs(
Timestamp.now().as_secs() - self.db_since)))
except Exception as e:
print(e)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
timestamp_since = Timestamp.now().as_secs() - self.db_since
since = Timestamp.from_secs(timestamp_since)
@@ -153,6 +162,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface):
# await cli.shutdown()
return json.dumps(result_list)
async def post_process(self, result, event):
"""Overwrite the interface function to return a social client readable format, if requested"""
for tag in event.tags():
@@ -183,7 +193,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
@@ -221,7 +231,7 @@ class DicoverContentCurrentlyPopularMostr(DVMTaskInterface):
except Exception as e:
print(e)
# Do not delete profiles
await cli.database().delete(Filter().kinds([EventDefinitions.KIND_NOTE, EventDefinitions.KIND_ZAP, EventDefinitions.KIND_REPOST, EventDefinitions.KIND_REACTION]).until(Timestamp.from_secs(
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:

View File

@@ -146,7 +146,7 @@ class DicoverContentCurrentlyPopularNonFollowers(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
if self.database is None:
self.database = await NostrDatabase.sqlite(self.db_name)
self.database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().database(self.database).signer(signer).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
@@ -230,7 +230,7 @@ class DicoverContentCurrentlyPopularNonFollowers(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -93,6 +93,7 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface):
# default values
max_results = 200
user = event.author().to_hex()
for tag in event.tags():
if tag.as_vec()[0] == 'i':
@@ -101,6 +102,8 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface):
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])
elif param == "search_list": # check for param type
self.search_list = str(tag.as_vec()[2]).split(",")
print(self.search_list)
@@ -113,6 +116,8 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface):
options = {
"max_results": max_results,
"request_event_id": event.id().to_hex(),
"request_event_author": event.author().to_hex()
}
request_form['options'] = json.dumps(options)
self.request_form = request_form
@@ -144,29 +149,34 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface):
options = self.set_options(request_form)
if self.database is None:
self.database = await NostrDatabase.sqlite(self.db_name)
self.database = NostrDatabase.lmdb(self.db_name)
timestamp_since = Timestamp.now().as_secs() - self.db_since
since = Timestamp.from_secs(timestamp_since)
filter1 = Filter().kind(definitions.EventDefinitions.KIND_NOTE).since(since)
filters = []
for word in self.search_list:
filter = Filter().kind(definitions.EventDefinitions.KIND_NOTE).since(since).search(word)
filters.append(filter)
events = await self.database.query([filter1])
events = await self.database.query(filters)
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print("[" + self.dvm_config.NIP89.NAME + "] Considering " + str(len(events)) + " Events")
ns.finallist = {}
for event in events:
if all(ele in event.content().lower() for ele in self.must_list):
if any(ele in event.content().lower() for ele in self.search_list):
if not any(ele in event.content().lower() for ele in self.avoid_list):
filt = Filter().kinds(
[definitions.EventDefinitions.KIND_ZAP, definitions.EventDefinitions.KIND_REACTION,
definitions.EventDefinitions.KIND_REPOST,
definitions.EventDefinitions.KIND_NOTE]).event(event.id()).since(since)
reactions = await self.database.query([filt])
if len(reactions) >= self.min_reactions:
ns.finallist[event.id().to_hex()] = len(reactions)
#if any(ele in event.content().lower() for ele in self.search_list):
if not any(ele in event.content().lower() for ele in self.avoid_list):
filt = Filter().kinds(
[definitions.EventDefinitions.KIND_ZAP, definitions.EventDefinitions.KIND_REACTION,
definitions.EventDefinitions.KIND_REPOST,
definitions.EventDefinitions.KIND_NOTE]).event(event.id()).since(since)
reactions = await self.database.query([filt])
if len(reactions) >= self.min_reactions:
ns.finallist[event.id().to_hex()] = len(reactions)
result_list = []
finallist_sorted = sorted(ns.finallist.items(), key=lambda x: x[1], reverse=True)[:int(options["max_results"])]
@@ -198,7 +208,7 @@ class DicoverContentCurrentlyPopularbyTopic(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:

View File

@@ -1,7 +1,11 @@
import asyncio
import json
import os
import time
from datetime import timedelta
from itertools import islice
import networkx as nx
from nostr_sdk import Client, Timestamp, PublicKey, Tag, Keys, Options, SecretKey, NostrSigner, NostrDatabase, \
ClientBuilder, Filter, NegentropyOptions, NegentropyDirection, init_logger, LogLevel, Event, EventId, Kind, \
RelayLimits
@@ -14,7 +18,7 @@ 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
from nostr_dvm.utils.wot_utils import build_wot_network, save_network, print_results
"""
This File contains a Module to update the database for content discovery dvms
@@ -132,24 +136,49 @@ class DicoverContentDBUpdateScheduler(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
if self.database is None:
self.database = await NostrDatabase.sqlite(self.db_name)
self.database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(self.database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
await cli.add_relay(relay)
await cli.connect()
if self.dvm_config.WOT_FILTERING:
print("Calculating WOT for " + str(self.dvm_config.WOT_BASED_ON_NPUBS))
filtering = cli.filtering()
index_map, G = await build_wot_network(self.dvm_config.WOT_BASED_ON_NPUBS, depth=self.dvm_config.WOT_DEPTH, max_batch=500, max_time_request=10)
# Do we actually need pagerank here?
#print('computing global pagerank...')
#tic = time.time()
#p_G = nx.pagerank(G, tol=1e-12)
# print("network after pagerank: " + str(len(p_G)))
wot_keys = []
for item in islice(G, len(G)):
key = next((PublicKey.parse(pubkey) for pubkey, id in index_map.items() if id == item),
None)
wot_keys.append(key)
#toc = time.time()
#print(f'finished in {toc - tic} seconds')
await filtering.add_public_keys(wot_keys)
# Mute public key
await cli.mute_public_keys(self.dvm_config.MUTE)
#await cli. (self.dvm_config.MUTE)
timestamp_since = Timestamp.now().as_secs() - self.db_since
since = Timestamp.from_secs(timestamp_since)
filter1 = Filter().kinds([definitions.EventDefinitions.KIND_NOTE, definitions.EventDefinitions.KIND_REACTION,
definitions.EventDefinitions.KIND_ZAP]).since(since) # Notes, reactions, zaps
definitions.EventDefinitions.KIND_ZAP]).since(since) # Notes, reactions, zaps
# filter = Filter().author(keys.public_key())
if self.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:

View File

@@ -77,7 +77,7 @@ class DiscoveryBotFarms(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite("db/nostr_profiles.db")
database = NostrDatabase.lmdb("db/nostr_profiles.db")
cli = ClientBuilder().database(database).signer(signer).opts(opts).build()
await cli.add_relay("wss://relay.damus.io")
@@ -137,7 +137,7 @@ class DiscoveryBotFarms(DVMTaskInterface):
sk = SecretKey.from_hex(self.dvm_config.PRIVATE_KEY)
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite("db/nostr_profiles.db")
database = NostrDatabase.lmdb("db/nostr_profiles.db")
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
await cli.add_relay("wss://relay.damus.io")

View File

@@ -51,9 +51,8 @@ class GenericDVM(DVMTaskInterface):
request_form = {"jobID": event.id().to_hex()}
self.options["user"] = user
self.options["request_event_id"] = event.id().to_hex()
self.options["request_event_author"] = event.author().to_hex()
self.options["request_event_author"] = user
if prompt != "":
self.options["input"] = prompt
request_form['options'] = json.dumps(self.options)

View File

@@ -19,7 +19,7 @@ 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
from nostr_dvm.utils.wot_utils import build_network_from, save_network, load_network, print_results, \
from nostr_dvm.utils.wot_utils import build_wot_network, save_network, load_network, print_results, \
convert_index_to_hex
"""
@@ -141,7 +141,7 @@ class DiscoverPeopleMyWOT(DVMTaskInterface):
#hop1
user_id = PublicKey.parse(options["user"]).to_hex()
index_map, G = await build_network_from(options["user"], depth=int(options["hops"]), max_batch=500, max_time_request=10)
index_map, G = await build_wot_network(options["user"], depth=int(options["hops"]), max_batch=500, max_time_request=10)
if use_files:
save_network(index_map, G, options["user"])
@@ -217,7 +217,7 @@ class DiscoverPeopleMyWOT(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
@@ -257,7 +257,7 @@ async def analyse_users(user_ids=None, dunbar=100000000):
print(npub)
print(e)
database = await NostrDatabase.sqlite("db/nostr_followlists.db")
database = NostrDatabase.lmdb("db/nostr_followlists.db")
followers_filter = Filter().authors(user_keys).kind(Kind(3))
followers = await database.query([followers_filter])
allfriends = []

View File

@@ -215,7 +215,7 @@ class DiscoverPeopleWOT(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
for relay in self.dvm_config.RECONCILE_DB_RELAY_LIST:
@@ -255,7 +255,7 @@ async def analyse_users(user_ids=None, dunbar=100000000):
print(npub)
print(e)
database = await NostrDatabase.sqlite("db/nostr_followlists.db")
database = NostrDatabase.lmdb("db/nostr_followlists.db")
followers_filter = Filter().authors(user_keys).kind(Kind(3))
followers = await database.query([followers_filter])
allfriends = []

View File

@@ -83,7 +83,7 @@ class SearchUser(DVMTaskInterface):
keys = Keys.parse(sk.to_hex())
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite(self.db_name)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().database(database).signer(signer).opts(opts).build()
await cli.add_relay(self.relay)
@@ -143,7 +143,7 @@ class SearchUser(DVMTaskInterface):
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)
database = NostrDatabase.lmdb(self.db_name)
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
await cli.add_relay(self.relay)

View File

@@ -92,7 +92,7 @@ async def get_task(event, client, dvm_config):
else:
for dvm in dvm_config.SUPPORTED_DVMS:
if dvm.KIND.as_u64() == event.kind().as_u64():
if dvm.KIND.as_u16() == event.kind().as_u16():
return dvm.TASK
except Exception as e:
print("Get task: " + str(e))

View File

@@ -20,30 +20,16 @@ class DVMConfig:
"wss://relay.nostr.net"
]
RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io", "wss://nostr21.com",
"wss://nostr.oxtr.dev",
"wss://relay.nostr.net" , "wss://relay.primal.net"] #, "wss://relay.snort.social"]
RECONCILE_DB_RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev",
"wss://relay.nostr.net" , "wss://relay.primal.net"]
# Straight Censorship (reply guy spam)
MUTE = [PublicKey.parse("npub1x5vhtx7j2prvueeenwf7tmesrzmuzc50zs0aakgd75v5c30ekj3s5zjckj"),
PublicKey.parse("npub1l03urys27uet2u6wq6u90rnzf7kv5c3wfu3cyndqz9lq75g46c5q0wkpsj"),
PublicKey.parse("npub17g7qhlu4caefd88vateedm9wau9ys6xt6jhjcfu2kqyw9xmnucxs5d6crj"),
PublicKey.parse("npub1epwccahqndqhseh6q02seu40cqa2ghk3u9tvu92yh4hd6lmxg33spwzujc"),
PublicKey.parse("npub1v0kgu3hymtd4fw9zrlem6l74c3cwl8jdqentt4qsxrrzan6paxaqkkf6dr"),
PublicKey.parse("npub1y8teqt2jay2ulww87wlmpe97gxhjqvhva60jv3ghp8emgk2da3psc2x7lt"),
PublicKey.parse("npub1drz9ts6esv22vplg9vunajwhts4ecaxvusmpxwy8yy683ejnzfnqvfvtk9"),
PublicKey.parse("npub1af5hgjkjpdqavpu3xqz092xjrvrpr3nzfftp4pgh4nez635hznas7m0vvn"),
PublicKey.parse("npub1rwxfxn33mmt9fe66qwg25lm9pm3nwtj5za5qm5cpqjsxlhk3dtsqkmunfe"),
PublicKey.parse("npub1l6y4l424ggvstc8a40n5c4rf8wwt5hlt5vuhn4dzvx9xf0eff9uqnc2fuw"),
PublicKey.parse("npub1q57y985vcazhx87naj5qhdyxxmtrrqakfq7lmvhxppvduqpfkesq3n46e8"),
PublicKey.parse("npub1dryv50m3rl6cx6ajeakmh3ygz83vjcdf6cga99yllmfx9ugqa04st0nk3w"),
PublicKey.parse("npub1xh3n3q2cp2wf9r66mmzmkyunyj8cf5r8aszsvwtdld6upqjmkxcsxgejsd"),
PublicKey.parse("npub1cek65lcyks0jjwx4y47c0zxmx8y22zn7zmhxktfjr32h2z5wtgeqn70vjc"),
PublicKey.parse("npub1y2v5mqw32m7n8mz4wpypcy7wt0t4hg93g67qw0fuyspjdspk0etqm0xx9y"),
PublicKey.parse("npub1cfgr520quxl8p74f5r3u5snt05dxyshfk88tcm5tuj78ypkdhgyqcpxkx2"),
PublicKey.parse("npub1620alhm0dyn063wlrlp6r8xzxlzdk9pp94xfag99gghj92wentlq23t6rz"),
]
WOT_FILTERING = False
WOT_BASED_ON_NPUBS = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64",
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
"3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"
]
WOT_DEPTH = 2
AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
@@ -91,7 +77,7 @@ def build_default_config(identifier):
dvm_config.PRIVATE_KEY = check_and_set_private_key(identifier)
dvm_config.IDENTIFIER = identifier
npub = Keys.parse(dvm_config.PRIVATE_KEY).public_key().to_bech32()
invoice_key, admin_key, wallet_id, user_id, lnaddress = check_and_set_ln_bits_keys(identifier, npub)
invoice_key, admin_key, wallet_id, lnaddress = check_and_set_ln_bits_keys(identifier, npub)
dvm_config.LNBITS_INVOICE_KEY = invoice_key
dvm_config.LNBITS_ADMIN_KEY = admin_key # The dvm might pay failed jobs back
dvm_config.LNBITS_URL = os.getenv("LNBITS_HOST")

View File

@@ -16,7 +16,7 @@ async def nip65_announce_relays(dvm_config, client):
content = ""
event = EventBuilder(EventDefinitions.KIND_RELAY_ANNOUNCEMENT, content, tags).to_event(keys)
eventid = await send_event(event, client=client, dvm_config=dvm_config, blastr=True)
eventid = await send_event(event, client=client, dvm_config=dvm_config)
if (eventid is not None):
print(
bcolors.BLUE + "[" + dvm_config.NIP89.NAME + "] Announced NIP 65 for " + dvm_config.NIP89.NAME + " (EventID: " + str(

View File

@@ -26,7 +26,7 @@ def nip89_create_d_tag(name, pubkey, image):
async def nip89_announce_tasks(dvm_config, client):
k_tag = Tag.parse(["k", str(dvm_config.NIP89.KIND.as_u64())])
k_tag = Tag.parse(["k", str(dvm_config.NIP89.KIND.as_u16())])
d_tag = Tag.parse(["d", dvm_config.NIP89.DTAG])
keys = Keys.parse(dvm_config.NIP89.PK)
content = dvm_config.NIP89.CONTENT
@@ -65,7 +65,7 @@ async def fetch_nip89_parameters_for_deletion(keys, eventid, client, dvmconfig,
async def nip89_delete_announcement(eid: str, keys: Keys, dtag: str, client: Client, config):
e_tag = Tag.parse(["e", eid])
a_tag = Tag.parse(
["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u64()) + ":" + keys.public_key().to_hex() + ":" + dtag])
["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u16()) + ":" + keys.public_key().to_hex() + ":" + dtag])
event = EventBuilder(Kind(5), "", [e_tag, a_tag]).to_event(keys)
print(f"POW event: {event.as_json()}")
await send_event(event, client, config)
@@ -73,8 +73,8 @@ async def nip89_delete_announcement(eid: str, keys: Keys, dtag: str, client: Cli
async def nip89_delete_announcement_pow(eid: str, keys: Keys, dtag: str, client: Client, config):
e_tag = Tag.parse(["e", eid])
a_tag = Tag.parse(
["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u64()) + ":" + keys.public_key().to_hex() + ":" + dtag])
event = EventBuilder(Kind(5), "", [e_tag, a_tag]).to_pow_event(keys, 28)
["a", str(EventDefinitions.KIND_ANNOUNCEMENT.as_u16()) + ":" + keys.public_key().to_hex() + ":" + dtag])
event = EventBuilder(Kind(5), "", [e_tag, a_tag]).pow(28).to_event(keys)
print(f"POW event: {event.as_json()}")
await send_event(event, client, config)
@@ -91,7 +91,7 @@ async def nip89_fetch_all_dvms(client):
async def nip89_fetch_events_pubkey(client, pubkey, kind):
ktags = [str(kind.as_u64())]
ktags = [str(kind.as_u16())]
nip89filter = (Filter().kind(EventDefinitions.KIND_ANNOUNCEMENT).author(PublicKey.parse(pubkey)).
custom_tag(SingleLetterTag.lowercase(Alphabet.K), ktags))
events = await client.get_events_of([nip89filter], relay_timeout)

View File

@@ -234,13 +234,15 @@ async def send_event_outbox(event: Event, client, dvm_config) -> EventId:
# 5. Fallback, if we couldn't send the event to any relay, we try to send to generic relays instead.
if event_id is None:
for relay in relays:
await outboxclient.remove_relay(relay)
try:
await outboxclient.remove_relay(relay)
except:
print("Error removing relay: " + relay)
relays = await get_main_relays(event, client, dvm_config)
for relay in relays:
opts = RelayOptions().ping(False)
await outboxclient.add_relay_with_opts(relay, opts)
await outboxclient.add_relay(relay)
try:
await outboxclient.connect()
event_id = await outboxclient.send_event(event)
@@ -256,7 +258,7 @@ async def send_event_outbox(event: Event, client, dvm_config) -> EventId:
async def send_event(event: Event, client: Client, dvm_config, blastr=False):
async def send_event(event: Event, client: Client, dvm_config):
try:
relays = []
for tag in event.tags():
@@ -275,11 +277,8 @@ async def send_event(event: Event, client: Client, dvm_config, blastr=False):
if relay not in dvm_config.RELAY_LIST:
await client.add_relay(relay)
#if blastr:
# client.add_relay("wss://nostr.mutinywallet.com")
try:
event_id = await client.send_event(event)
#event_id = output.id
except Exception as e:
print(e)
event_id = None
@@ -288,8 +287,6 @@ async def send_event(event: Event, client: Client, dvm_config, blastr=False):
if relay not in dvm_config.RELAY_LIST:
if relay not in dvm_config.RELAY_LIST:
await client.remove_relay(relay)
#if blastr:
# client.remove_relay("wss://nostr.mutinywallet.com")
return event_id
except Exception as e:
print(e)
@@ -360,6 +357,24 @@ def check_and_decrypt_own_tags(event, dvm_config):
return event
async def update_profile_lnaddress(private_key, dvm_config, lud16="",):
keys = Keys.parse(private_key)
opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=5))
.skip_disconnected_relays(True))
signer = NostrSigner.keys(keys)
client = Client.with_opts(signer, opts)
for relay in dvm_config.RELAY_LIST:
await client.add_relay(relay)
await client.connect()
metadata = Metadata() \
.set_lud16(lud16) \
.set_nip05(lud16)
await client.set_metadata(metadata)
async def update_profile(dvm_config, client, lud16=""):
keys = Keys.parse(dvm_config.PRIVATE_KEY)
try:

View File

@@ -6,7 +6,7 @@ from datetime import timedelta
import requests
from nostr_dvm.utils.database_utils import fetch_user_metadata
from nostr_dvm.utils.definitions import EventDefinitions
from nostr_dvm.utils.definitions import EventDefinitions, relay_timeout, relay_timeout_long
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.nostr_utils import check_and_set_private_key
from nostr_dvm.utils.zap_utils import pay_bolt11_ln_bits, zaprequest
@@ -14,6 +14,7 @@ from nostr_sdk import Tag, Keys, nip44_encrypt, nip44_decrypt, Nip44Version, Eve
EventId, nip04_decrypt, nip04_encrypt, Options, NostrSigner, PublicKey, init_logger, LogLevel, Metadata
from nostr_dvm.utils.print import bcolors
class NutWallet(object):
def __init__(self):
self.name: str = "NutWallet"
@@ -28,7 +29,7 @@ class NutWallet(object):
self.a: str = ""
self.legacy_encryption: bool = False # Use Nip04 instead of Nip44, for reasons, turn to False ASAP.
self.trust_unknown_mints: bool = False
self.missing_balance_strategy: str = "mint" #swap to use existing tokens from other mints (fees!) or mint to mint from lightning
self.missing_balance_strategy: str = "mint" #none to do nothing until manually minted, mint to mint from lightning or swap to use existing tokens from other mints (fees!) (not working yet!)
class NutMint(object):
@@ -46,7 +47,6 @@ class NutMint(object):
return balance
class NutZapWallet:
async def client_connect(self, relay_list):
@@ -72,8 +72,8 @@ class NutZapWallet:
new_nut_wallet.description = description
new_nut_wallet.mints = mint_urls
new_nut_wallet.relays = relays
new_nut_wallet.d = "wallet" # sha256(str(new_nut_wallet.name + new_nut_wallet.description).encode('utf-8')).hexdigest()[:16]
new_nut_wallet.a = str(Kind(7375).as_u64()) + ":" + keys.public_key().to_hex() + ":" + new_nut_wallet.d
new_nut_wallet.d = "wallet"
new_nut_wallet.a = str(Kind(7375).as_u16()) + ":" + keys.public_key().to_hex() + ":" + new_nut_wallet.d
print("Creating Wallet..")
send_response_id = await self.create_or_update_nut_wallet_event(new_nut_wallet, client, keys)
@@ -120,7 +120,8 @@ class NutZapWallet:
nut_wallet = None
wallet_filter = Filter().kind(EventDefinitions.KIND_NUT_WALLET).author(keys.public_key())
wallets = await client.get_events_of([wallet_filter], timedelta(10))
#relay_timeout = EventSource.relays(timedelta(seconds=10))
wallets = await client.get_events_of([wallet_filter], relay_timeout_long)
if len(wallets) > 0:
@@ -194,7 +195,8 @@ class NutZapWallet:
# Now all proof events
proof_filter = Filter().kind(Kind(7375)).author(keys.public_key())
proof_events = await client.get_events_of([proof_filter], timedelta(5))
#relay_timeout = EventSource.relays(timedelta(seconds=5))
proof_events = await client.get_events_of([proof_filter], relay_timeout)
latest_proof_sec = 0
latest_proof_event_id = EventId
@@ -247,7 +249,7 @@ class NutZapWallet:
nut_proof.amount = proof['amount']
nut_proof.C = proof['C']
nut_mint.proofs.append(nut_proof)
#print(proof)
# print(proof)
mints = [x for x in nut_wallet.nutmints if x.mint_url == mint_url]
if len(mints) == 0:
@@ -347,8 +349,8 @@ class NutZapWallet:
"secret": proof['secret'],
"C": proof['C']
}
#print("Mint proofs:")
#print(proof)
# print("Mint proofs:")
# print(proof)
new_proofs.append(proofjson)
old_event_id = mint.previous_event_id
@@ -447,7 +449,8 @@ class NutZapWallet:
async def fetch_mint_info_event(self, pubkey, client):
mint_info_filter = Filter().kind(Kind(10019)).author(PublicKey.parse(pubkey))
preferences = await client.get_events_of([mint_info_filter], timedelta(5))
#relay_timeout = EventSource.relays(timedelta(seconds=5))
preferences = await client.get_events_of([mint_info_filter], relay_timeout)
mints = []
relays = []
pubkey = ""
@@ -517,21 +520,23 @@ class NutZapWallet:
return await self.update_nut_wallet(nut_wallet, [mint_url], client, keys)
async def handle_low_balance_on_mint(self, nut_wallet, outgoing_mint_url, mint, amount, client, keys):
async def handle_low_balance_on_mint(self, nut_wallet, mint_to_send, mint, amount, client, keys):
required_amount = amount - mint.available_balance()
if nut_wallet.missing_balance_strategy == "mint":
await self.mint_cashu(nut_wallet, outgoing_mint_url, client, keys, required_amount)
await self.mint_cashu(nut_wallet, mint_to_send, client, keys, required_amount)
elif nut_wallet.missing_balance_strategy == "swap":
for nutmint in nut_wallet.nutmints:
estimated_fees = 3
if nutmint.available_balance() > required_amount+estimated_fees:
await self.swap(required_amount, nutmint.mint_url, outgoing_mint_url)
if nutmint.available_balance() > required_amount + estimated_fees:
print(nutmint.mint_url)
await self.swap(required_amount, mint_to_send, nutmint.mint_url, nut_wallet)
break
else:
print(bcolors.RED + "[" + nut_wallet.name + "] Not enough Balance on Mint, mint some tokens first. " + str(
amount) + " " + nut_wallet.unit + bcolors.ENDC)
async def send_nut_zap(self, amount, comment, nut_wallet: NutWallet, zapped_event, zapped_user, client: Client,
keys: Keys):
@@ -562,7 +567,6 @@ class NutZapWallet:
if mint.available_balance() < amount:
await self.handle_low_balance_on_mint(nut_wallet, mint_url, mint, amount, client, keys)
# If that's not the case, iterate over the recipents mints and try to mint there. This might be a bit dangerous as not all mints might give cashu, so loss of ln is possible
if mint_url is None:
if nut_wallet.trust_unknown_mints:
@@ -687,39 +691,39 @@ class NutZapWallet:
from cashu.wallet.wallet import Wallet
from cashu.core.base import Proof
from cashu.core.crypto.keys import PrivateKey
proofs = []
mint_url = ""
amount = 0
unit = "sat"
zapped_user = ""
zapped_event = ""
sender = event.author().to_hex()
message = event.content()
for tag in event.tags():
if tag.as_vec()[0] == "proof":
proof_json = json.loads(tag.as_vec()[1])
proof = Proof().from_dict(proof_json)
proofs.append(proof)
elif tag.as_vec()[0] == "u":
mint_url = tag.as_vec()[1]
elif tag.as_vec()[0] == "amount":
amount = int(tag.as_vec()[1])
elif tag.as_vec()[0] == "unit":
unit = tag.as_vec()[1]
elif tag.as_vec()[0] == "p":
zapped_user = tag.as_vec()[1]
elif tag.as_vec()[0] == "e":
zapped_event = tag.as_vec()[1]
cashu_wallet = await Wallet.with_db(
url=mint_url,
db="db/Receiver",
name="receiver",
)
cashu_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True)
await cashu_wallet.load_mint()
try:
proofs = []
mint_url = ""
amount = 0
unit = "sat"
zapped_user = ""
zapped_event = ""
sender = event.author().to_hex()
message = event.content()
for tag in event.tags():
if tag.as_vec()[0] == "proof":
proof_json = json.loads(tag.as_vec()[1])
proof = Proof().from_dict(proof_json)
proofs.append(proof)
elif tag.as_vec()[0] == "u":
mint_url = tag.as_vec()[1]
elif tag.as_vec()[0] == "amount":
amount = int(tag.as_vec()[1])
elif tag.as_vec()[0] == "unit":
unit = tag.as_vec()[1]
elif tag.as_vec()[0] == "p":
zapped_user = tag.as_vec()[1]
elif tag.as_vec()[0] == "e":
zapped_event = tag.as_vec()[1]
cashu_wallet = await Wallet.with_db(
url=mint_url,
db="db/Receiver",
name="receiver",
)
cashu_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True)
await cashu_wallet.load_mint()
new_proofs, _ = await cashu_wallet.redeem(proofs)
mint = self.get_mint(nut_wallet, mint_url)
print(mint.proofs)
@@ -735,7 +739,6 @@ class NutZapWallet:
print(bcolors.RED + str(e) + bcolors.ENDC)
return None, message, sender
async def melt_cashu(self, nut_wallet, mint_url, total_amount, client, keys, lud16=None, npub=None):
from cashu.wallet.wallet import Wallet
mint = self.get_mint(nut_wallet, mint_url)
@@ -774,35 +777,43 @@ class NutZapWallet:
total_amount - estimated_fees) + " (Fees: " + str(estimated_fees) + ") " + nut_wallet.unit
+ bcolors.ENDC)
async def swap(self, amountinsats, outgoing_mint_url, incoming_mint_url):
async def swap(self, amountinsats, incoming_mint_url, outgoing_mint_url, nut_wallet):
#TODO this doesn't seem to work yet.
from cashu.wallet.cli.cli_helpers import print_mint_balances
from cashu.wallet.wallet import Wallet
# print("Select the mint to swap from:")
# outgoing_wallet = await get_mint_wallet(ctx, force_select=True)
from cashu.core.crypto.keys import PrivateKey
outgoing_mint = self.get_mint(nut_wallet, outgoing_mint_url)
outgoing_wallet = await Wallet.with_db(
url=outgoing_mint_url,
db="db/Sender",
name="sender",
db="db/Cashu",
name="outgoing",
)
outgoing_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True)
await outgoing_wallet.load_mint()
outgoing_wallet.proofs = outgoing_mint.proofs
print("Select the mint to swap to:")
# incoming_wallet = await get_mint_wallet(ctx, force_select=True)
print(outgoing_wallet.available_balance)
incoming_mint = self.get_mint(nut_wallet, incoming_mint_url)
incoming_wallet = await Wallet.with_db(
url=incoming_mint_url,
db="db/Receiver",
name="reeciver",
db="db/Cashu",
name="incoming",
)
incoming_wallet.private_key = PrivateKey(bytes.fromhex(nut_wallet.privkey), raw=True)
await incoming_wallet.load_mint()
await outgoing_wallet.load_mint()
incoming_wallet.proofs = incoming_mint.proofs
if incoming_wallet.url == outgoing_wallet.url:
raise Exception("mints for swap have to be different")
print("Incoming Mint units: " + incoming_wallet.unit.name)
assert amountinsats > 0, "amount is not positive"
# request invoice from incoming mint
@@ -816,9 +827,13 @@ class NutZapWallet:
send_proofs, fees = await outgoing_wallet.select_to_send(
outgoing_wallet.proofs, total_amount, set_reserved=True
)
await outgoing_wallet.melt(
send_proofs, invoice.bolt11, quote.fee_reserve, quote.quote
)
try:
await outgoing_wallet.melt(
proofs=send_proofs, invoice=invoice.bolt11, fee_reserve_sat=quote.fee_reserve, quote_id=quote.quote
)
except:
print("anyways..")
# mint token in incoming mint
await incoming_wallet.mint(amountinsats, id=invoice.id)

View File

@@ -318,9 +318,9 @@ async def send_job_status_reaction(original_event_id_hex, original_event_author_
if dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print(bcolors.YELLOW + "[" + dvm_config.NIP89.NAME + "]" + " Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC)
EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.as_json() + bcolors.ENDC)
elif dvm_config.LOGLEVEL.value >= LogLevel.INFO.value:
print(bcolors.YELLOW + "[" + dvm_config.NIP89.NAME + "]" + " Sent Kind " + str(
EventDefinitions.KIND_FEEDBACK.as_u64()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC)
EventDefinitions.KIND_FEEDBACK.as_u16()) + " Reaction: " + status + " " + reaction_event.id().to_hex() + bcolors.ENDC)
return reaction_event.as_json()

View File

@@ -0,0 +1,68 @@
import asyncio
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
async def create_reaction(keys, title, dtag):
d_tag = Tag.parse(["d", dtag])
title_tag = Tag.parse(["title", title])
emoji_tags = []
# add more if you want
name = "ThugAmy"
url = "https://image.nostr.build/ccc229cbe11f5a13a1cc7fd24e13ac53fc78f287ecce0d9a674807e2e20f6fd5.png"
emoji_tag1 = Tag.parse(["emoji", name, url])
emoji_tags.append(emoji_tag1)
keys = Keys.parse(keys)
content = ""
event = EventBuilder(Kind(30030), content, [d_tag, title_tag] + emoji_tags).to_event(keys)
signer = NostrSigner.keys(keys)
client = Client(signer)
# We add the relays we defined above and told our DVM we would want to receive events to.
for relay in DVMConfig().RELAY_LIST:
await client.add_relay(relay)
# We connect the client
await client.connect()
eventid = await send_event(event, client=client, dvm_config=DVMConfig())
print(
bcolors.BLUE + "[" + "Reaction" + "] Announced (" + eventid.id.to_nostr_uri() +
" Hex: " + eventid.id.to_hex() + ")" + bcolors.ENDC)
async def delete_reaction(keys, eid: str, dtag: str):
keys = Keys.parse(keys)
e_tag = Tag.parse(["e", eid])
a_tag = Tag.parse(
["a", "30030:" + keys.public_key().to_hex() + ":" + dtag])
event = EventBuilder(Kind(5), "", [e_tag, a_tag]).to_event(keys)
signer = NostrSigner.keys(keys)
client = Client(signer)
# We add the relays we defined above and told our DVM we would want to receive events to.
for relay in DVMConfig().RELAY_LIST:
await client.add_relay(relay)
# We connect the client
await client.connect()
eventid = await send_event(event, client, DVMConfig())
print(
bcolors.BLUE + "[" + "Reaction" + "] deleted (" + eventid.id.to_nostr_uri() +
" Hex: " + eventid.id.to_hex() + ")" + bcolors.ENDC)
keys = check_and_set_private_key("test_client")
eventid = "da05cefc512ad43363f84131343f5d2a80303ea3b9368b9ad7f010e07db37d90"
asyncio.run(create_reaction(keys=keys, title="ThugAmy", dtag="ThugAmy"))
#asyncio.run(delete_reaction(keys=keys, eid=eventid, dtag="ThugAmy"))

View File

@@ -93,7 +93,7 @@ async def get_following(pks, max_time_request=10, newer_than_time=None):
return following
async def build_network_from(seed_pks, depth=2, max_batch=500, max_time_request=10):
async def build_wot_network(seed_pks, depth=2, max_batch=500, max_time_request=10):
if not seed_pks:
print('Error: seed_pks cannot be empty')
return
@@ -636,5 +636,5 @@ async def convert_index_to_hex(graph, index_map, show_results_num):
def test():
# WARNING, DEPENDING ON DEPTH THIS TAKES LONG
user = '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'
index_map, network_graph = asyncio.run(build_network_from(user, depth=2, max_batch=500, max_time_request=10))
index_map, network_graph = asyncio.run(build_wot_network(user, depth=2, max_batch=500, max_time_request=10))
save_network(index_map, network_graph, user)

View File

@@ -1,6 +1,8 @@
# LIGHTNING/ZAP FUNCTIONS
import json
import os
import random
import string
import urllib.parse
from pathlib import Path
@@ -12,7 +14,8 @@ from bech32 import bech32_decode, convertbits, bech32_encode
from nostr_sdk import PublicKey, SecretKey, Event, EventBuilder, Tag, Keys, generate_shared_key, Kind, \
Timestamp
from nostr_dvm.utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags
from nostr_dvm.utils.nostr_utils import get_event_by_id, check_and_decrypt_own_tags, update_profile, \
update_profile_lnaddress
from hashlib import sha256
import dotenv
@@ -51,7 +54,7 @@ async def parse_zap_event_tags(zap_event, keys, name, client, config):
keys.secret_key(),
zap_request_event.author())
decrypted_private_event = Event.from_json(decrypted_content)
if decrypted_private_event.kind().as_u64() == 9733:
if decrypted_private_event.kind().as_u16() == 9733:
sender = decrypted_private_event.author().to_hex()
message = decrypted_private_event.content()
# if message != "":
@@ -132,11 +135,11 @@ def create_bolt11_lud16(lud16, amount):
def create_lnbits_account(name):
if os.getenv("LNBITS_ADMIN_ID") is None or os.getenv("LNBITS_ADMIN_ID") == "":
if os.getenv("LNBITS_WALLET_ID") is None or os.getenv("LNBITS_WALLET_ID") == "":
print("No admin id set, no wallet created.")
return "", "", "", "", "failed"
data = {
'admin_id': os.getenv("LNBITS_ADMIN_ID"),
'admin_id': os.getenv("LNBITS_WALLET_ID"),
'wallet_name': name,
'user_name': name,
}
@@ -347,20 +350,26 @@ def get_price_per_sat(currency):
return price_currency_per_sat
def randomword(length):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(length))
def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain, newname = " ", currentname=" "):
if newname == " ":
newname = identifier
def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain):
print(os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper()))
data = {
'name': identifier,
'name': newname,
'domain': nostdressdomain,
'kind': "lnbits",
'host': os.getenv("LNBITS_HOST"),
'key': os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper()),
'pin': pin,
'npub': npub,
'currentname': " "
'currentname': currentname
}
try:
url = "https://" + nostdressdomain + "/api/easy/"
res = requests.post(url, data=data)
@@ -368,10 +377,22 @@ def make_ln_address_nostdress(identifier, npub, pin, nostdressdomain):
obj = json.loads(res.text)
if obj.get("ok"):
return identifier + "@" + nostdressdomain, obj["pin"]
return data["name"] + "@" + nostdressdomain, obj["pin"]
except Exception as e:
print(e)
return "", ""
print("Creating random name..")
data["name"] = data["name"] + "_" + randomword(10)
try:
url = "https://" + nostdressdomain + "/api/easy/"
res = requests.post(url, data=data)
print(res.text)
obj = json.loads(res.text)
if obj.get("ok"):
return data["name"] + "@" + nostdressdomain, obj["pin"]
except Exception as e:
return "", ""
def check_and_set_ln_bits_keys(identifier, npub):
@@ -379,26 +400,36 @@ def check_and_set_ln_bits_keys(identifier, npub):
invoicekey, adminkey, walletid, userid, success = create_lnbits_account(identifier)
add_key_to_env_file("LNBITS_INVOICE_KEY_" + identifier.upper(), invoicekey)
add_key_to_env_file("LNBITS_ADMIN_KEY_" + identifier.upper(), adminkey)
add_key_to_env_file("LNBITS_USER_ID_" + identifier.upper(), userid)
add_key_to_env_file("LNBITS_WALLET_ID_" + identifier.upper(), userid)
lnaddress = ""
pin = ""
if os.getenv("NOSTDRESS_DOMAIN") and success != "failed":
print(os.getenv("NOSTDRESS_DOMAIN"))
lnaddress, pin = make_ln_address_nostdress(identifier, npub, " ", os.getenv("NOSTDRESS_DOMAIN"))
lnaddress, pin = make_ln_address_nostdress(identifier, npub, " ", os.getenv("NOSTDRESS_DOMAIN"), identifier)
add_key_to_env_file("LNADDRESS_" + identifier.upper(), lnaddress)
add_key_to_env_file("LNADDRESS_PIN_" + identifier.upper(), pin)
return invoicekey, adminkey, userid, walletid, lnaddress
return invoicekey, adminkey, walletid, lnaddress
else:
return (os.getenv("LNBITS_INVOICE_KEY_" + identifier.upper()),
os.getenv("LNBITS_ADMIN_KEY_" + identifier.upper()),
os.getenv("LNBITS_USER_ID_" + identifier.upper()),
os.getenv("LNBITS_WALLET_ID_" + identifier.upper()),
os.getenv("LNADDRESS_" + identifier.upper()))
async def change_ln_address(identifier, new_identifier, dvm_config, updateprofile=False):
previous_identifier = os.getenv("LNADDRESS_" + identifier.upper()).split("@")[0]
pin = os.getenv("LNADDRESS_PIN_" + identifier.upper())
npub = Keys.parse(os.getenv("DVM_PRIVATE_KEY_" + identifier.upper())).public_key().to_hex()
lnaddress, pin = make_ln_address_nostdress(identifier, npub, pin, os.getenv("NOSTDRESS_DOMAIN"), new_identifier, currentname=previous_identifier)
add_key_to_env_file("LNADDRESS_" + identifier.upper(), lnaddress)
add_key_to_env_file("LNADDRESS_PIN_" + identifier.upper(), pin)
print("changed lnaddress")
if updateprofile:
private_key = os.getenv("DVM_PRIVATE_KEY_" + identifier.upper())
await update_profile_lnaddress(private_key, dvm_config, lud16=lnaddress)
def add_key_to_env_file(value, oskey):
env_path = Path('.env')
if env_path.is_file():

View File

@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
VERSION = '0.8.20'
VERSION = '0.8.21'
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,7 +14,7 @@ setup(
long_description=LONG_DESCRIPTION,
packages=find_packages(include=['nostr_dvm', 'nostr_dvm.*']),
install_requires=["nostr-sdk==0.34.0",
install_requires=["nostr-sdk==0.35.0",
"bech32==1.2.0",
"pycryptodome==3.20.0",
"yt-dlp==2024.5.27",

View File

@@ -8,7 +8,7 @@ print(keys.public_key().to_bech32())
async def reconcile_db():
# Create/open SQLite database
database = await NostrDatabase.sqlite("nostr.db")
database = NostrDatabase.lmdb("nostr.db")
# NOT AVAILABLE ON WINDOWS AT THE MOMENT!
# Create/open nostrdb database
@@ -28,7 +28,7 @@ async def reconcile_db():
await do_some_work()
async def do_some_work():
database = await NostrDatabase.sqlite("nostr.db")
database = NostrDatabase.lmdb("nostr.db")
f = Filter().author(keys.public_key()).limit(10)
events = await database.query([f])

View File

@@ -37,12 +37,12 @@ rebroadcast_NIP65_Relay_List = True
update_profile = False
global_update_rate = 180 # set this high on first sync so db can fully sync before another process trys to.
use_logger = False
use_logger = True
log_level = LogLevel.INFO
RECONCILE_DB_RELAY_LIST = [ "wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.oxtr.dev"]
RECONCILE_DB_RELAY_LIST = [ "wss://relay.nostr.net", "wss://relay.damus.io", "wss://nostr.oxtr.dev"]
RELAY_LIST = ["wss://relay.primal.net",
"wss://nostr.mom", "wss://nostr.oxtr.dev",
"wss://relay.nostr.net"
@@ -63,6 +63,7 @@ def build_db_scheduler(name, identifier, admin_config, options, image, descripti
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.DATABASE = database
dvm_config.WOT_FILTERING = True
# Activate these to use a subscription based model instead
# dvm_config.SUBSCRIPTION_REQUIRED = True
@@ -147,11 +148,9 @@ def build_example_nostrband(name, identifier, admin_config, image, about, custom
dvm_config.USE_OWN_VENV = False
dvm_config.CUSTOM_PROCESSING_MESSAGE = custom_processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.LOGLEVEL = LogLevel.INFO
#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
@@ -288,11 +287,9 @@ def build_example_topic(name, identifier, admin_config, options, image, descript
dvm_config.LOGLEVEL = LogLevel.INFO
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.DATABASE = database
#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
@@ -326,21 +323,15 @@ def build_example_topic(name, identifier, admin_config, options, image, descript
def build_example_popular(name, identifier, admin_config, options, image, cost=0, update_rate=180, processing_msg=None,
update_db=True, database=None):
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.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_OUTBOX_RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.DATABASE = database
#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
@@ -381,10 +372,8 @@ def build_example_popular_followers(name, identifier, admin_config, options, ima
dvm_config.FIX_COST = cost
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = 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
@@ -427,11 +416,12 @@ def build_example_popular_non_followers(name, identifier, admin_config, options,
dvm_config.UPDATE_DATABASE = update_db
dvm_config.DATABASE = database
# Activate these to use a subscription based model instead
dvm_config.FIX_COST = cost
dvm_config.FIX_COST = 10
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.SUBSCRIPTION_REQUIRED = True
dvm_config.SUBSCRIPTION_REQUIRED = False
admin_config.LUD16 = dvm_config.LN_ADDRESS
admin_config.REBROADCAST_NIP88 = False
#admin_config.REBROADCAST_NIP89 = True
@@ -446,8 +436,8 @@ def build_example_popular_non_followers(name, identifier, admin_config, options,
"lud16": dvm_config.LN_ADDRESS,
"encryptionSupported": True,
"cashuAccepted": True,
"subscription": True,
"personalized": False,
"subscription": False,
"personalized": True,
"nip90Params": {
"max_results": {
"required": False,
@@ -479,7 +469,7 @@ def build_example_popular_non_followers(name, identifier, admin_config, options,
admin_config.PRIVKEY = dvm_config.PRIVATE_KEY
return DicoverContentCurrentlyPopularNonFollowers(name=name, dvm_config=dvm_config, nip89config=nip89config,
nip88config=nip88config,
#nip88config=nip88config,
admin_config=admin_config,
options=options)
@@ -497,11 +487,9 @@ def build_example_top_zapped(name, identifier, admin_config, options, image, cos
dvm_config.FIX_COST = cost
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.DATABASE = database
#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
@@ -544,17 +532,13 @@ def build_example_mostr(name, identifier, admin_config, options, image, cost=0,
# 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.RELAY_LIST = RELAY_LIST
dvm_config.RECONCILE_DB_RELAY_LIST = ["wss://nfrelay.app/?user=activitypub"]
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.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
@@ -594,12 +578,9 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c
dvm_config.UPDATE_DATABASE = False
dvm_config.LOGLEVEL = LogLevel.DEBUG
dvm_config.FIX_COST = cost
dvm_config.RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.primal.net"]
# "wss://relay.nostr.net"]
dvm_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
# 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
@@ -630,7 +611,7 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c
async def init_db(database):
return await NostrDatabase.sqlite(database)
return NostrDatabase.lmdb(database)
def playground():
@@ -640,7 +621,7 @@ def playground():
admin_config_db_scheduler= AdminConfig()
options_animal = {
"db_name": main_db,
"db_since": 12 * 60 * 60, # 48h since gmt,
"db_since": 48 * 60 * 60, # 48h since gmt,
"personalized": False,
"logger": False}
image = ""
@@ -831,6 +812,34 @@ def playground():
update_db=True)
discovery_mostr.run()
# Popular Garden&Plants
admin_config_asknostr = AdminConfig()
admin_config_asknostr.REBROADCAST_NIP89 =rebroadcast_NIP89
admin_config_asknostr.REBROADCAST_NIP65_RELAY_LIST = rebroadcast_NIP65_Relay_List
admin_config_asknostr.UPDATE_PROFILE = update_profile
options_plants = {
"search_list": ["#asknostr"],
"avoid_list": [],
"db_name": "db/nostr_recent_notes.db",
"db_since": 24 * 60 * 60, # 12h since gmt
"personalized": False,
"logger": False}
image = "https://i.nostr.build/vIixmuRacIhULsrP.png"
description = "I show popular questions #asknostr"
custom_processing_msg = ["Finding the best notes for you.. #asknostr"]
update_db = False
cost = 0
discovery_asknostr = build_example_topic("Popular on #asknostr", "discovery_content_asknostr",
admin_config_asknostr, options_plants,
image=image,
description=description,
update_rate=global_update_rate,
cost=cost,
processing_msg=custom_processing_msg,
update_db=update_db,
database=DATABASE)
discovery_asknostr.run()
# Popular Animals (Fluffy frens)
admin_config_animals = AdminConfig()
@@ -1049,7 +1058,7 @@ def playground():
options_global_popular = {
"db_name": "db/nostr_recent_notes.db",
"db_since": 60 * 60, # 1h since gmt,
"db_since": 60 * 60 * 1, # 1h since gmt,
}
cost = 0
#image = "https://image.nostr.build/b29b6ec4bf9b6184f69d33cb44862db0d90a2dd9a506532e7ba5698af7d36210.jpg"
@@ -1119,7 +1128,9 @@ def playground():
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)
invoice_key, admin_key, wallet_id, lnaddress = check_and_set_ln_bits_keys("dvm_subscription", npub)
subscription_config.RECONCILE_DB_RELAY_LIST = RECONCILE_DB_RELAY_LIST
subscription_config.RELAY_LIST = RELAY_LIST
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")

View File

@@ -33,12 +33,7 @@ def build_example_gallery(name, identifier, admin_config, options, image, cost=0
dvm_config.UPDATE_DATABASE = update_db
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.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

View File

@@ -37,12 +37,7 @@ def build_example_mostr(name, identifier, admin_config, options, image, cost=0,
dvm_config.RECONCILE_DB_RELAY_LIST = ["wss://nfrelay.app/?user=activitypub"]
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.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

View File

@@ -38,11 +38,7 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c
dvm_config.LOGLEVEL = LogLevel.DEBUG
dvm_config.FIX_COST = cost
dvm_config.RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.primal.net"]
# "wss://relay.nostr.net"]
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
# 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

View File

@@ -42,12 +42,7 @@ def build_example_wot(name, identifier, admin_config, options, image, cost=0, up
#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.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

View File

@@ -149,9 +149,6 @@ def build_example_nostrband(name, identifier, admin_config, image, about, custom
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RELAY_LIST = RELAY_LIST
dvm_config.LOGLEVEL = LogLevel.INFO
#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
@@ -289,9 +286,6 @@ def build_example_topic(name, identifier, admin_config, options, image, descript
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RELAY_LIST = 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
@@ -331,14 +325,9 @@ def build_example_popular(name, identifier, admin_config, options, image, cost=0
dvm_config.SCHEDULE_UPDATES_SECONDS = update_rate # Every 10 minutes
dvm_config.UPDATE_DATABASE = update_db
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_OUTBOX_RELAY_LIST
dvm_config.RELAY_LIST = 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
@@ -380,9 +369,6 @@ def build_example_popular_followers(name, identifier, admin_config, options, ima
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RELAY_LIST = 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
@@ -495,9 +481,6 @@ def build_example_top_zapped(name, identifier, admin_config, options, image, cos
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
dvm_config.AVOID_PAID_OUTBOX_RELAY_LIST = AVOID_OUTBOX_RELAY_LIST
dvm_config.RELAY_LIST = 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
@@ -545,12 +528,7 @@ def build_example_mostr(name, identifier, admin_config, options, image, cost=0,
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.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
@@ -591,11 +569,7 @@ def build_example_oneperfollow(name, identifier, admin_config, options, image, c
dvm_config.LOGLEVEL = LogLevel.DEBUG
dvm_config.FIX_COST = cost
dvm_config.RELAY_LIST = ["wss://relay.damus.io", "wss://nostr.oxtr.dev", "wss://relay.nostr.bg", "wss://relay.primal.net"]
# "wss://relay.nostr.net"]
dvm_config.CUSTOM_PROCESSING_MESSAGE = processing_msg
# 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

View File

@@ -8,6 +8,7 @@ from duck_chat import ModelType
from nostr_sdk import Kind, Filter, PublicKey, SecretKey, Keys, NostrSigner, RelayLimits, Options, Client, Tag, \
LogLevel, Timestamp, NostrDatabase
from nostr_dvm.tasks.generic_dvm import GenericDVM
from nostr_dvm.utils import definitions
from nostr_dvm.utils.admin_utils import AdminConfig
@@ -50,13 +51,13 @@ def playground(announce=False):
dvm_config = build_default_config(identifier)
dvm_config.KIND = Kind(kind) # Manually set the Kind Number (see data-vending-machines.org)
dvm_config.CUSTOM_PROCESSING_MESSAGE = "Creating a personalized feed based on the topics you write about. This might take a moment."
dvm_config.FIX_COST = 10
dvm_config.FIX_COST = 0
admin_config.DELETE_NIP89 = True
admin_config.POW = True
admin_config.EVENTID = "5322b731230cf8961f8403d025722a381af9b012b5d5f6dcc09f88e160f4e4ff"
admin_config.PRIVKEY = dvm_config.PRIVATE_KEY
#admin_config.DELETE_NIP89 = True
#admin_config.POW = True
#admin_config.EVENTID = "5322b731230cf8961f8403d025722a381af9b012b5d5f6dcc09f88e160f4e4ff"
#admin_config.PRIVKEY = dvm_config.PRIVATE_KEY
# Add NIP89
@@ -83,12 +84,10 @@ def playground(announce=False):
admin_config=admin_config, options=options)
async def process_request(request_form, prompt):
async def process_request(options, prompt):
result = ""
try:
from duck_chat import DuckChat
options = dvm.set_options(request_form)
result = ""
async with DuckChat(model=ModelType.GPT4o) as chat:
query = prompt
result = await chat.ask_question(query)
@@ -119,7 +118,8 @@ def playground(announce=False):
await cli.connect()
#pip install -U https://github.com/mrgick/duckduckgo-chat-ai/archive/master.zip
author = PublicKey.parse(options["user"])
author = PublicKey.parse(options["request_event_author"])
print(options["request_event_author"])
filterauth = Filter().kind(definitions.EventDefinitions.KIND_NOTE).author(author).limit(100)
evts = await cli.get_events_of([filterauth], relay_timeout)
@@ -128,12 +128,14 @@ def playground(announce=False):
for event in evts:
text = text + event.content() + ";"
text = text[:6000]
prompt = "Only reply with the result. Here is a list of notes, seperated by ;. Find the 20 most important keywords and return them by a comma seperated list: " + text
#loop = asyncio.get_running_loop()
result = asyncio.run(process_request(request_form, prompt))
result = await process_request(options, prompt)
print(result)
content = "I identified these as your topics:\n\n"+result.replace(",", ", ") + "\n\nProcessing, just a few more seconds..."
await send_job_status_reaction(original_event_id_hex=dvm.options["request_event_id"], original_event_author_hex=dvm.options["request_event_author"], client=cli, dvm_config=dvm_config, content=content)
@@ -144,11 +146,11 @@ def playground(announce=False):
# result = await chat.ask_question(query)
# result = result.replace(", ", ",")
# print(result)
result = ""
from types import SimpleNamespace
ns = SimpleNamespace()
database = await NostrDatabase.sqlite("db/nostr_recent_notes.db")
database = NostrDatabase.lmdb("db/nostr_recent_notes.db")
timestamp_since = Timestamp.now().as_secs() - since
since = Timestamp.from_secs(timestamp_since)
@@ -159,7 +161,7 @@ def playground(announce=False):
if dvm.dvm_config.LOGLEVEL.value >= LogLevel.DEBUG.value:
print("[" + dvm.dvm_config.NIP89.NAME + "] Considering " + str(len(events)) + " Events")
ns.finallist = {}
search_list = result.split('')
#search_list = result.split(',')
for event in events:
#if all(ele in event.content().lower() for ele in []):

View File

@@ -27,7 +27,7 @@
"outputs": [],
"source": [
"# Imports\n",
"from nostr_dvm.utils.wot_utils import build_network_from, save_network, load_network, get_mc_pagerank, get_subrank, get_metadata, print_results\n",
"from nostr_dvm.utils.wot_utils import build_wot_network, save_network, load_network, get_mc_pagerank, get_subrank, get_metadata, print_results\n",
"import time\n",
"import networkx as nx\n",
"import random\n",
@@ -85,7 +85,7 @@
}
],
"source": [
"index_map, G = await build_network_from(user, depth=2, max_batch=500, max_time_request=10)\n",
"index_map, G = await build_wot_network(user, depth=2, max_batch=500, max_time_request=10)\n",
"if use_files:\n",
" save_network(index_map, G, user)"
],

View File

@@ -224,7 +224,7 @@ async def nostr_client_custom_discovery(user, ptag):
pTag = Tag.parse(["p", ptag])
tags = [relaysTag, alttag, paramTag, pTag, paramTagSearch, paramTagMust, paramTagAvoid]
tags = [relaysTag, alttag, paramTag, pTag]# paramTagSearch, paramTagMust, paramTagAvoid]
event = EventBuilder(EventDefinitions.KIND_NIP90_CONTENT_DISCOVERY, str("Give me content"),
tags).to_event(keys)
@@ -234,7 +234,7 @@ async def nostr_client_custom_discovery(user, ptag):
for relay in relay_list:
await client.add_relay(relay)
ropts = RelayOptions().ping(False)
await client.add_relay_with_opts("wss://nostr.band", ropts)
await client.connect()
config = DVMConfig
await send_event(event, client=client, dvm_config=config)
@@ -444,18 +444,18 @@ async def nostr_client():
# await nostr_client_test_translation("44a0a8b395ade39d46b9d20038b3f0c8a11168e67c442e3ece95e4a1703e2beb", "event", "zh", 20, 20)
#await nostr_client_test_image("a beautiful purple ostrich watching the sunset, eating a cashew nut")
#await nostr_client_custom_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "7a63849b684d90c0de983492578b12e147e56f5d79ed6585cc64e5aa8a122744")
await nostr_client_custom_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "7240284b84951cfedbc20fce26f0e3f0a36da3e9c1be85d7a06965f0d4fe25fb")
#"a018ba05af400b52772e33162d8326fca4a167fe7b6d3cd2382e14cac2af6841"
#await nostr_client_duckduck_test(PublicKey.parse("aa8ab5b774d47e7b29a985dd739cfdcccf93451678bf7977ba1b2e094ecd8b30").to_hex() , "How do i create a dockerfile for python 3.12")
await nostr_client_flux_schnell("d57f1efb7582f58cade6f482d53eefa998d8082711b996aae3dc5f5527cbdd6e" , "topics")
# await nostr_client_duckduck_test(PublicKey.parse("7a63849b684d90c0de983492578b12e147e56f5d79ed6585cc64e5aa8a122744").to_hex() , "How do i create a dockerfile for python 3.12")
#await nostr_client_flux_schnell("d57f1efb7582f58cade6f482d53eefa998d8082711b996aae3dc5f5527cbdd6e" , "topics")
# await nostr_client_test_search_profile("dontbelieve")
#wot = ["99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64"]
# await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7")
#await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "ab6cdf12ca3ae5109416295b8cd8a53fdec3a9d54beb7a9aee0ebfb67cb4edf7")
# await nostr_client_test_discovery_gallery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64", "4add3944eb596a27a650f9b954f5ed8dfefeec6ca50473605b0fbb058dd11306")
# await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64",
# "2cf10ff849d2769b2b021bd93a0270d03eecfd14126d07f94c6ca2269cb3f3b1")
#await nostr_client_test_discovery("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64",
# "7a63849b684d90c0de983492578b12e147e56f5d79ed6585cc64e5aa8a122744")
# await nostr_client_test_censor_filter(wot)
# await nostr_client_test_inactive_filter("99bb5591c9116600f845107d31f9b59e2f7c7e09a1ff802e84f1d43da557ca64")
@@ -466,6 +466,8 @@ async def nostr_client():
# await nostr_client_test_image_private("a beautiful ostrich watching the sunset")
nutzap_wallet = NutZapWallet()
nut_wallet = await nutzap_wallet.get_nut_wallet(client, keys)
#dangerous, dont use this, except your wallet is messed up.
delete = False
@@ -486,7 +488,7 @@ async def nostr_client():
print(
bcolors.BLUE + f"Received new event from {relay_url}: {event.as_json()}" + bcolors.ENDC)
if event.kind().as_u64() == 7000:
if event.kind().as_u16() == 7000:
print("[Nostr Client]: " + event.as_json())
amount_sats = 0
status = ""
@@ -512,15 +514,15 @@ async def nostr_client():
keys)
elif 6000 < event.kind().as_u64() < 6999:
elif 6000 < event.kind().as_u16() < 6999:
print("[Nostr Client]: " + event.as_json())
print("[Nostr Client]: " + event.content())
elif event.kind().as_u64() == 4:
elif event.kind().as_u16() == 4:
dec_text = nip04_decrypt(sk, event.author(), event.content())
print("[Nostr Client]: " + f"Received new msg: {dec_text}")
elif event.kind().as_u64() == 9735:
elif event.kind().as_u16() == 9735:
print("[Nostr Client]: " + f"Received new zap:")
print(event.as_json())

View File

@@ -163,4 +163,3 @@ if __name__ == '__main__':
asyncio.run(test_gallery())
# works

View File

@@ -57,7 +57,7 @@ async def sync_db():
opts = (Options().wait_for_send(False).send_timeout(timedelta(seconds=5)))
keys = Keys.parse("nsec1zmzllu40a7mr7ztl78uwfwslnp0pn0pww868adl05x52d4la237s6m8qfj")
signer = NostrSigner.keys(keys)
database = await NostrDatabase.sqlite("db/nostr_followlists.db")
database = NostrDatabase.lmdb("db/nostr_followlists.db")
cli = ClientBuilder().signer(signer).database(database).opts(opts).build()
await cli.add_relay("wss://relay.damus.io") # TODO ADD MORE
@@ -86,7 +86,7 @@ async def analyse_users(user_ids=None):
print(npub)
print(e)
database = await NostrDatabase.sqlite("db/nostr_followlists.db")
database = NostrDatabase.lmdb("db/nostr_followlists.db")
followers_filter = Filter().authors(user_keys).kind(Kind(3))
followers = await database.query([followers_filter])
allfriends = []

View File

@@ -0,0 +1,122 @@
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": "",
"id": "1865ec9f54c7b22e"
},
{
"metadata": {},
"cell_type": "markdown",
"source": [
"# Hello there, fellow DVM enthusiast\n",
"This is the first of a series of tutorials on how to use nostr-dvm.\n",
"\n",
"Before we start, we have to make sure you have a .env file in the working directory.\n",
"\n",
"In order to make sure you have it and to get you started quickly, here is a small function to create an inital .env file for you. Just make sure you set the parameters in the following field:"
],
"id": "e1f5dcfcad069462"
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"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"
],
"id": "2e71e7aa2ebdac50"
},
{
"metadata": {},
"cell_type": "markdown",
"source": [
"LNBITS_HOST is an Lnbits instance. We'll use Lnbits to generate wallets for each DVM automatically.\n",
"You can use the demo server, but it is highly recommended to move to your own instance,\n",
"or an instance of someone you trust.\n",
"\n",
"LNBITS_ADMIN_KEY and LNBITS_WALLET_ID are the API keys you get from Lnbits. You might also want to activate the User Manager module in LNBits.\n",
"\n",
"NOSTDRESS_DOMAIN: You can run your own instance of Nostdress https://github.com/believethehype/nostdress, so your DVMs get their own unique zapable lightning address, or you can use the default one. Make sure that the used identifier is unique, as otherwise the lnaddress will not work, if the nostdress acount already exists\n",
"\n",
" By the way, if that's not your kind of thing, you don't have to set an Lnbits account. If your DVM should receive zaps somehow, make sure you manually set a valid, zappable lightning address in its profile, once we created it. In this case leave the lnbits_admin_key and lnbits_wallet_id empty\n"
],
"id": "bb26e7180f1d1f0"
},
{
"metadata": {},
"cell_type": "markdown",
"source": "The following script will make an initial .env file with the parameters set in the field above, if it doesn't exist yet.\n",
"id": "f6b316e1cace5adc"
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2024-09-16T13:36:56.451990Z",
"start_time": "2024-09-16T13:36:56.448752Z"
}
},
"cell_type": "code",
"source": [
"config = \"LNBITS_ADMIN_KEY = \\\"\"+lnbits_admin_key+\"\\\"\\nLNBITS_WALLET_ID = \\\"\"+ lnbits_wallet_id +\"\\\"\\nLNBITS_HOST = \\\"\"+ lnbits_host + \"\\\"\\nNOSTDRESS_DOMAIN = \\\"\" + nostdress_domain + \"\\\"\"\n",
"\n",
"import os.path\n",
"if not os.path.isfile(\".env\"):\n",
" with open(\".env\", \"w\") as f: # Opens file and casts as f \n",
" f.write(config) # Writing\n",
" # File closed automatically\n",
"else:\n",
" print(\".env file already exists\")"
],
"id": "a3e16002be388215",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
".env file already exists\n"
]
}
],
"execution_count": 8
},
{
"metadata": {},
"cell_type": "markdown",
"source": "Cool. That's it for the prepartion. Once the .env file exists we won't overwrite it here again. You can open the .env file in this folder (maybe refresh your IDE) and check if everything worked as expected",
"id": "f4e3ed80abdad23a"
},
{
"metadata": {},
"cell_type": "markdown",
"source": "",
"id": "d32b5abd00600e64"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

82
tutorials/02_run_dvm.py Normal file
View File

@@ -0,0 +1,82 @@
# Welcome back, this time we don't use a notebook, but we run an actual Python Script.
# We use a GenericDVM kind to start with. Now what's this? We have many predefined tasks in the task folder, but
# the genericDVM gives you some control for simple manipulation without caring about the tasks. Important is that
# we set the Kind of the GenericDVM. In Line 28 you see that we give it Kind 5050 (Text generation).
# On https://www.data-vending-machines.org/ there's an overview on all current kinds.
# On https://github.com/nostr-protocol/data-vending-machines/ you can make a PR for your own kind, if you come up with one later.
# Check the run_dvm function for more explanations
import asyncio
import os
from pathlib import Path
import dotenv
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):
# You have seen this one before, we did this in tutorial 2. This function will either create or load. the parameters of our DVMConfig.
# Make sure you replace the identifier down in the main function with the one you generated in tutorial 2, or we will create a new one here.
dvm_config = build_default_config(identifier)
# As we will use a GenericDVM we need to give it a kind. Here we use kind 5050 (Text Generation) as we want to reply with some simple text.
# There is a bunch of predefined DVMs in tasks that already have a kind set, but as we use the genericDVM we have to manually set it here.
dvm_config.KIND = Kind(5050)
# We can set options that we can later use in our process function. They are stored in a simple JSON
options = {
"some_option": "#RunDVM",
}
# We give the DVM a human readable name
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.
dvm = GenericDVM(name=name, dvm_config=dvm_config, options=options,
nip89config=NIP89Config(), admin_config=AdminConfig())
# Normally we would define the dvm interface as we do in the tasks folder (we will do it later in the tutorials as well,
# but here is a small hack to quickly manipulate what our dvm will do.
async def process(request_form):
# First we always parse the options from our request_form, that is build internally in the create_request_from_nostr_event function.
options = dvm.set_options(request_form)
# We build our result we are giving back from some text
result = "The result of the DVM is: "
# and the option we defined above and handed over to our DVM (some_option)
result += options["some_option"]
print(result)
# Then we simply return the result
return result
dvm.process = process # now we simply overwrite our DVM's process function with the one we defined here.
# and finally we run the DVM #RunDVM
dvm.run()
# When the DVM is running you should see a blue message with the name and the public key in bech32 and hex format.
# For the next exercise, copy the Hex key, and let this DVM run, you will need it :)
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)

130
tutorials/03_client.py Normal file
View File

@@ -0,0 +1,130 @@
# Welcome to part 3. This actually is is a simplistic client that will interact with our DVM.
# We will address the DVM we created in part 02, so make sure it's still running and run this Script in a new instance.
# Copy the DVM's hex key that pops up at the beginning and replace the one down in the main function with your DVM's key.
# This way we will tag it and it will know it should reply to us.
import asyncio
from pathlib import Path
from secp256k1 import PublicKey
from nostr_dvm.utils.dvmconfig import DVMConfig
from nostr_dvm.utils.print import bcolors
import dotenv
from nostr_sdk import Keys, Client, Tag, EventBuilder, Filter, HandleNotification, Timestamp, nip04_decrypt, \
NostrSigner, Event, Kind, PublicKey
from nostr_dvm.utils.nostr_utils import send_event, check_and_set_private_key
from nostr_dvm.utils.definitions import EventDefinitions
async def nostr_client_generic_test(ptag):
# Create or manage some private keys for our client.
keys = Keys.parse(check_and_set_private_key("test_client"))
# We tell the DVM to which relays it should reply
relay_list = ["wss://nostr.oxtr.dev", "wss://relay.primal.net"]
relaysTag = Tag.parse(["relays"] + relay_list)
# The alt tag is optional, and just describes what the event does.
alttag = Tag.parse(["alt", "This is a NIP90 DVM AI task"])
# The ptag tags the DVM we want to address. Make sure to set it down in the main function.
pTag = Tag.parse(["p", PublicKey.parse(ptag).to_hex()])
# These are out tags
tags = [relaysTag, alttag, pTag]
# We now send a 5050 Request (for Text Generation) with our tags. The content is optional.
event = EventBuilder(Kind(5050), "This is a test",
tags).to_event(keys)
# We create a signer with some random keys
signer = NostrSigner.keys(keys)
client = Client(signer)
# We add the relays we defined above and told our DVM we would want to receive events to.
for relay in relay_list:
await client.add_relay(relay)
# We connect the client
await client.connect()
# and send the Event.
result = await send_event(event, client=client, dvm_config=DVMConfig())
print(result)
async def nostr_client(target_dvm_npub):
# This is some logic for listening to events. For example we want to see replies from the DVM.
keys = Keys.parse(check_and_set_private_key("test_client"))
sk = keys.secret_key()
pk = keys.public_key()
print(f"Nostr Client public key: {pk.to_bech32()}, Hex: {pk.to_hex()} ")
signer = NostrSigner.keys(keys)
client = Client(signer)
dvmconfig = DVMConfig()
for relay in dvmconfig.RELAY_LIST:
await client.add_relay(relay)
await client.connect()
dm_zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM,
EventDefinitions.KIND_ZAP]).since(Timestamp.now())
kinds = [EventDefinitions.KIND_NIP90_GENERIC]
for kind in range(6000, 7001):
if kind not in kinds:
kinds.append(Kind(kind))
dvm_filter = (Filter().kinds(kinds).since(Timestamp.now()).pubkey(pk))
await client.subscribe([dm_zap_filter, dvm_filter], None)
# This will send a request to the DVM
await nostr_client_generic_test(target_dvm_npub)
# We listen to
class NotificationHandler(HandleNotification):
last_event_time = 0
async def handle(self, relay_url, subscription_id, event: Event):
print(
bcolors.BLUE + f"Received new event from {relay_url}: {event.as_json()}" + bcolors.ENDC)
if event.kind().as_u16() == 7000:
print(bcolors.YELLOW + "[Nostr Client]: " + event.content() + bcolors.ENDC)
amount_sats = 0
status = ""
for tag in event.tags():
if tag.as_vec()[0] == "amount":
amount_sats = int(int(tag.as_vec()[1]) / 1000) # millisats
if tag.as_vec()[0] == "status":
status = tag.as_vec()[1]
elif 6000 < event.kind().as_u16() < 6999:
print(bcolors.GREEN + "[Nostr Client]: " + event.content() + bcolors.ENDC)
elif event.kind().as_u16() == 4:
dec_text = nip04_decrypt(sk, event.author(), event.content() )
print("[Nostr Client]: " + f"Received new msg: {dec_text}")
elif event.kind().as_u16() == 9735:
print("[Nostr Client]: " + f"Received new zap:")
print(event.as_json())
async def handle_msg(self, relay_url, msg):
return
asyncio.create_task(client.handle_notifications(NotificationHandler()))
# await client.handle_notifications(NotificationHandler())
while True:
await asyncio.sleep(2)
if __name__ == '__main__':
env_path = Path('.env')
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 this key with the one from your DVM from part 3.
target_dvm_npub = "aaf3b2bda1f19651417af4b1ccc35ebb6675d718843fdc444bdca4da1c8cd2fc"
asyncio.run(nostr_client(target_dvm_npub))

View File

@@ -75,8 +75,11 @@ async function generate_feed(id) {
let tags = []
//tags.push(["param", "max_results", "200"])
tags.push(["param", "user", store.state.pubkey.toHex()])
let r = store.state.relays.join(",")
tags.push(["relays", r])
let r = ["relays"]
for (let relay of store.state.relays){
r.push(relay)
}
tags.push(r)
let res;
let requestid;

View File

@@ -61,8 +61,12 @@ async function generate_image(message) {
let tags = [
["i", message, "text"]
]
let r = store.state.relays.join(",")
tags.push(["relays", r])
let r = ["relays"]
for (let relay of store.state.relays){
r.push(relay)
}
tags.push(r)
hasmultipleinputs = false
if (urlinput.value !== "" && urlinput.value.startsWith('http')){
@@ -222,7 +226,7 @@ async function listen() {
for (const el of store.state.nip89dvms) {
if (JSON.parse(el.event).pubkey === event.author.toHex().toString()) {
if (JSON.parse(el.event).pubkey === event.author.toHex().toString() && el.kind === "5100" ) {
jsonentry.name = el.name
jsonentry.about = el.about
jsonentry.image = el.image
@@ -439,54 +443,54 @@ const submitHandler = async () => {
</div>
<div v-if="dvm.result && store.state.pubkey.toHex() !== Keys.parse(store.state.nooglekey).publicKey.toHex() && !dvm.reactions.negativeUser && !dvm.reactions.positiveUser" style="margin-right: 5px">
<div v-if="dvm.result && store.state.pubkey.toHex() !== Keys.parse(store.state.nooglekey).publicKey.toHex()" style="margin-right: 5px">
<!-- && !dvm.reactions.negativeUser && !dvm.reactions.positiveUser-->
<button @click="react_to_dvm(dvm, '👍')" class="w-8 h-8 rounded-full bg-nostr border-white border-1 text-white flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black tooltip" data-top='Share' aria-label="make note" role="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
<path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
</svg>
</button>
</div>
<!-- <button @click="react_to_dvm(dvm, '👍')" class="w-8 h-8 rounded-full bg-nostr border-white border-1 text-white flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black tooltip" data-top='Share' aria-label="make note" role="button">-->
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">-->
<!-- <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>-->
<!-- </svg>-->
<!-- </button>-->
<!-- </div>-->
<div v-if="dvm.result && store.state.pubkey.toHex() !== Keys.parse(store.state.nooglekey).publicKey.toHex() && !dvm.reactions.negativeUser && !dvm.reactions.positiveUser" >
<button @click="react_to_dvm(dvm, '👎')" class="w-8 h-8 rounded-full bg-nostr border-white border-1 text-white flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black tooltip" data-top='Share' aria-label="make note" role="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
<path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
</svg>
</button>
<!-- <div v-if="dvm.result && store.state.pubkey.toHex() !== Keys.parse(store.state.nooglekey).publicKey.toHex() && !dvm.reactions.negativeUser && !dvm.reactions.positiveUser" >-->
<!-- <button @click="react_to_dvm(dvm, '👎')" class="w-8 h-8 rounded-full bg-nostr border-white border-1 text-white flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black tooltip" data-top='Share' aria-label="make note" role="button">-->
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">-->
<!-- <path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>-->
<!--</svg>-->
<!-- </button>-->
</div>
</div>
<div>
<p class="flex"> {{dvm.reactions.positive.length}}
<!-- <p class="flex"> {{dvm.reactions.positiveUser.length}}-->
<div>
<div className="dropdown">
<div tabIndex={0} role="button" class="button" >
<svg style="margin-left: 3px; margin-right: 10px; margin-top: 3px" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
<path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
</svg>
</div>
<div tabIndex={0} className="dropdown-content -start-56 z-[1] horizontal card card-compact w-64 p-2 shadow bg-nostr text-primary-content">
<div className="card-body">
<h3 className="card-title">Liked results by</h3>
<div class="flex" >
<div v-for="user in dvm.reactions.positive">
<div className="wotplayeauthor-wrapper">
<figure>
<img className="wotavatar" v-if="user.profile && user.profile.picture" :src="user.profile.picture" onerror="this.src='https://noogle.lol/favicon.ico'" alt="DVM Picture" />
<img class="wotavatar" v-else src="@/assets/nostr-purple.svg" />
</figure>
</div>
</div>
<!-- <div className="dropdown">-->
<!-- <div tabIndex={0} role="button" class="button" >-->
<!-- <svg style="margin-left: 3px; margin-right: 10px; margin-top: 3px" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">-->
<!-- <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2 2 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a10 10 0 0 0-.443.05 9.4 9.4 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a9 9 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.2 2.2 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.9.9 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>-->
<!-- </svg>-->
<!-- </div>-->
<!-- <div tabIndex={0} className="dropdown-content -start-56 z-[1] horizontal card card-compact w-64 p-2 shadow bg-nostr text-primary-content">-->
<!-- <div className="card-body">-->
<!-- <h3 className="card-title">Liked results by</h3>-->
<!-- <div class="flex" >-->
<!-- <div v-for="user in dvm.reactions.positive">-->
<!-- <div className="wotplayeauthor-wrapper">-->
<!-- <figure>-->
<!-- <img className="wotavatar" v-if="user.profile && user.profile.picture" :src="user.profile.picture" onerror="this.src='https://noogle.lol/favicon.ico'" alt="DVM Picture" />-->
<!-- <img class="wotavatar" v-else src="@/assets/nostr-purple.svg" />-->
<!-- </figure>-->
<!-- </div>-->
<!-- </div>-->
</div>
@@ -500,53 +504,53 @@ const submitHandler = async () => {
<div style="width: 10px"></div>
{{dvm.reactions.negative.length}}
<div>
<!-- {{dvm.reactions.negative.length}}-->
<!-- <div>-->
<div className="dropdown">
<div tabIndex={0} role="button" class="button" >
<svg style="margin-left: 3px; margin-top: 3px" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">
<path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>
</svg>
</div>
<div tabIndex={0} className="dropdown-content -start-56 z-[1] horizontal card card-compact w-64 p-2 shadow bg-nostr text-primary-content">
<div className="card-body">
<h3 className="card-title">Disliked results by</h3>
<div class="flex" >
<div v-for="user in dvm.reactions.negative">
<div className="wotplayeauthor-wrapper">
<!-- <div className="dropdown">-->
<!-- <div tabIndex={0} role="button" class="button" >-->
<!-- <svg style="margin-left: 3px; margin-top: 3px" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-hand-thumbs-down" viewBox="0 0 16 16">-->
<!-- <path d="M8.864 15.674c-.956.24-1.843-.484-1.908-1.42-.072-1.05-.23-2.015-.428-2.59-.125-.36-.479-1.012-1.04-1.638-.557-.624-1.282-1.179-2.131-1.41C2.685 8.432 2 7.85 2 7V3c0-.845.682-1.464 1.448-1.546 1.07-.113 1.564-.415 2.068-.723l.048-.029c.272-.166.578-.349.97-.484C6.931.08 7.395 0 8 0h3.5c.937 0 1.599.478 1.934 1.064.164.287.254.607.254.913 0 .152-.023.312-.077.464.201.262.38.577.488.9.11.33.172.762.004 1.15.069.13.12.268.159.403.077.27.113.567.113.856s-.036.586-.113.856c-.035.12-.08.244-.138.363.394.571.418 1.2.234 1.733-.206.592-.682 1.1-1.2 1.272-.847.283-1.803.276-2.516.211a10 10 0 0 1-.443-.05 9.36 9.36 0 0 1-.062 4.51c-.138.508-.55.848-1.012.964zM11.5 1H8c-.51 0-.863.068-1.14.163-.281.097-.506.229-.776.393l-.04.025c-.555.338-1.198.73-2.49.868-.333.035-.554.29-.554.55V7c0 .255.226.543.62.65 1.095.3 1.977.997 2.614 1.709.635.71 1.064 1.475 1.238 1.977.243.7.407 1.768.482 2.85.025.362.36.595.667.518l.262-.065c.16-.04.258-.144.288-.255a8.34 8.34 0 0 0-.145-4.726.5.5 0 0 1 .595-.643h.003l.014.004.058.013a9 9 0 0 0 1.036.157c.663.06 1.457.054 2.11-.163.175-.059.45-.301.57-.651.107-.308.087-.67-.266-1.021L12.793 7l.353-.354c.043-.042.105-.14.154-.315.048-.167.075-.37.075-.581s-.027-.414-.075-.581c-.05-.174-.111-.273-.154-.315l-.353-.354.353-.354c.047-.047.109-.176.005-.488a2.2 2.2 0 0 0-.505-.804l-.353-.354.353-.354c.006-.005.041-.05.041-.17a.9.9 0 0 0-.121-.415C12.4 1.272 12.063 1 11.5 1"/>-->
<!--</svg>-->
<!-- </div>-->
<!-- <div tabIndex={0} className="dropdown-content -start-56 z-[1] horizontal card card-compact w-64 p-2 shadow bg-nostr text-primary-content">-->
<!-- <div className="card-body">-->
<!-- <h3 className="card-title">Disliked results by</h3>-->
<!-- <div class="flex" >-->
<!-- <div v-for="user in dvm.reactions.negative">-->
<!-- <div className="wotplayeauthor-wrapper">-->
<figure>
<!-- <figure>-->
<img className="wotavatar" v-if="user.profile && user.profile.picture" :src="user.profile.picture" onerror="this.src='https://noogle.lol/favicon.ico'" alt="DVM Picture" />
<img class="wotavatar" v-else src="@/assets/nostr-purple.svg" />
</figure>
<!-- <img className="wotavatar" v-if="user.profile && user.profile.picture" :src="user.profile.picture" onerror="this.src='https://noogle.lol/favicon.ico'" alt="DVM Picture" />-->
<!-- <img class="wotavatar" v-else src="@/assets/nostr-purple.svg" />-->
<!-- </figure>-->
</div>
</div>
<!-- </div>-->
<!-- </div>-->
</div>
<!--</div>-->
</div>
</div>
</div>
<!--<p>{{ this.current_user }}</p> -->
</div>
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- &lt;!&ndash;<p>{{ this.current_user }}</p> &ndash;&gt;-->
<!-- </div>-->
</p>
</div>
</div>
<!-- </div>-->
</div>
</div>
</div>
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</template>
<style scoped>

View File

@@ -205,7 +205,7 @@ import {loadNWCObject} from "@/components/helper/Zap.vue"
import {useDark, useEventListener, useToggle} from "@vueuse/core";
import {ref} from "vue";
import {webln} from "@getalby/sdk";
import {nip04Decrypt} from "@rust-nostr/nostr-sdk/pkg/nostr_sdk_js_bg.wasm.js";
import {contact_new, nip04Decrypt} from "@rust-nostr/nostr-sdk/pkg/nostr_sdk_js_bg.wasm.js";
import app from "@/App.vue";
const isDark = useDark();
@@ -752,6 +752,10 @@ export default {
for (const entry of evts){
try {
for (const tag in entry.tags){
if (entry.tags[tag].asVec()[0] === "k")
if(entry.tags[tag].asVec()[1] >= 5000 && entry.tags[tag].asVec()[1] <= 5999 && deadnip89s.filter(i => i.id === entry.id.toHex() ).length === 0) { // blocklist.indexOf(entry.id.toHex()) < 0){
@@ -760,7 +764,8 @@ export default {
try {
let jsonentry = JSON.parse(entry.content)
let jsonentry = JSON.parse(entry.content)
let nip88 = {
title: "",
@@ -868,7 +873,17 @@ export default {
//jsonentry.reactions = await dvmreactions(entry.author.toHex())
// console.log("REACTIONS:" + jsonentry.reactions)
jsonentry.id = entry.author.toHex()
jsonentry.about = await parseandreplacenpubs(jsonentry.about)
try{
if (jsonentry.about != null){
jsonentry.about = await parseandreplacenpubs(jsonentry.about)
}
}
catch(Error){
console.log(Error)
}
jsonentry.event = entry.asJson()
jsonentry.kind = entry.tags[tag].asVec()[1]
@@ -880,11 +895,13 @@ export default {
}
catch (error){
console.log(error)
}
}
}
}
catch {}
}
store.commit('set_nip89dvms', nip89dvms)

View File

@@ -100,7 +100,7 @@
<h2 class="card-title">{{ dvm.name }}</h2>
<h3 class="fa-cut text-gray" >Kind: {{ dvm.kind }}</h3>
<h3 class="fa-cut" v-html="StringUtil.parseHyperlinks(dvm.about)"></h3>
<h3 v-if="dvm.about !== null" class="fa-cut" v-html="dvm.about"></h3>
<div class="card-actions justify-end">
<button className="btn" @click="copyDoiToClipboard(dvm.event);">Copy Event Json</button>
</div>

View File

@@ -93,8 +93,13 @@ async function generate_feed(id) {
let tags = []
tags.push(["param", "max_results", "200"])
tags.push(["param", "user", store.state.pubkey.toHex()])
let r = store.state.relays.join(",")
tags.push(["relays", r])
let r = ["relays"]
for (let relay of store.state.relays){
r.push(relay)
}
tags.push(r)
let res;
let requestid;

View File

@@ -153,8 +153,11 @@ async function send_search_request(msg) {
["param", "until", ((dateto.value/1000).toFixed(0))],
['param', 'users', JSON.stringify(users)]
]
let r = store.state.relays.join(",")
tags.push(["relays", r])
let r = ["relays"]
for (let relay of store.state.relays){
r.push(relay)
}
tags.push(r)
let tags_as_str = JSON.stringify(tags)

View File

@@ -63,8 +63,11 @@ let sortedIds = eventids.sort(function(a,b) {return (a.index > b.index) ? 1 : ((
catch{}
}
let r = store.state.relays.join(",")
tags.push(["relays", r])
let r = ["relays"]
for (let relay of store.state.relays){
r.push(relay)
}
tags.push(r)
let res;