diff --git a/__init__.py b/__init__.py index 7da5850..b376e2e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,9 +1,10 @@ +import asyncio from fastapi import APIRouter from fastapi.staticfiles import StaticFiles from lnbits.db import Database from lnbits.helpers import template_renderer -from lnbits.settings import settings +from lnbits.tasks import catch_everything_and_restart db = Database("ext_nostrrelay") @@ -22,12 +23,10 @@ def nostrrelay_renderer(): return template_renderer(["lnbits/extensions/nostrrelay/templates"]) -from .models import NostrRelay +from .tasks import wait_for_paid_invoices from .views import * # noqa from .views_api import * # noqa -# settings.lnbits_relay_information = { -# "name": "LNbits Nostr Relay", -# "description": "Multiple relays are supported", -# **NostrRelay.info(), -# } +def nostrrelay_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/client_manager.py b/client_manager.py index f5c8fec..f7b06cf 100644 --- a/client_manager.py +++ b/client_manager.py @@ -11,7 +11,6 @@ from .crud import ( get_config_for_all_active_relays, get_event, get_events, - get_prunable_events, get_storage_for_public_key, mark_events_deleted, prune_old_events, diff --git a/crud.py b/crud.py index 6a859b9..14a1016 100644 --- a/crud.py +++ b/crud.py @@ -2,11 +2,10 @@ import json from typing import Any, List, Optional, Tuple from . import db -from .models import NostrEvent, NostrFilter, NostrRelay, RelayPublicSpec, RelaySpec +from .models import NostrAccount, NostrEvent, NostrFilter, NostrRelay, RelayPublicSpec, RelaySpec ########################## RELAYS #################### - async def create_relay(user_id: str, r: NostrRelay) -> NostrRelay: await db.execute( """ @@ -318,3 +317,58 @@ def build_select_events_query(relay_id: str, filter: NostrFilter): query += f" LIMIT {filter.limit}" return query, values + + + +########################## ACCOUNTS #################### + +async def create_account(relay_id: str, a: NostrAccount) -> NostrAccount: + await db.execute( + """ + INSERT INTO nostrrelay.accounts (relay_id, pubkey, sats, storage, paid_to_join, allowed, blocked) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + relay_id, + a.pubkey, + a.sats, + a.storage, + a.paid_to_join, + a.allowed, + a.blocked, + ), + ) + account = await get_account(relay_id, a.pubkey) + assert account, "Created account cannot be retrieved" + return account + + +async def update_account(relay_id: str, a: NostrAccount) -> NostrAccount: + await db.execute( + """ + UPDATE nostrrelay.accounts + SET (sats, storage, paid_to_join, allowed, blocked) = (?, ?, ?, ?, ?) + WHERE relay_id = ? AND pubkey = ? + """, + ( + a.sats, + a.storage, + a.paid_to_join, + a.allowed, + a.blocked, + relay_id, + a.pubkey + ), + ) + + return a + + +async def get_account(relay_id: str, pubkey: str,) -> Optional[NostrAccount]: + row = await db.fetchone( + "SELECT * FROM nostrrelay.accounts WHERE relay_id = ? AND pubkey = ?", + (relay_id, pubkey), + ) + + return NostrAccount.from_row(row) if row else None + diff --git a/migrations.py b/migrations.py index e4837e8..6c122cc 100644 --- a/migrations.py +++ b/migrations.py @@ -45,3 +45,17 @@ async def m001_initial(db): ); """ ) + + await db.execute( + f""" + CREATE TABLE nostrrelay.accounts ( + relay_id TEXT NOT NULL, + pubkey TEXT NOT NULL, + sats {db.big_int} DEFAULT 0, + storage {db.big_int} DEFAULT 0, + paid_to_join BOOLEAN DEFAULT false, + allowed BOOLEAN DEFAULT false, + blocked BOOLEAN DEFAULT false + ); + """ + ) diff --git a/models.py b/models.py index 47c3dde..071c3fe 100644 --- a/models.py +++ b/models.py @@ -310,3 +310,16 @@ class NostrFilter(BaseModel): class RelayJoin(BaseModel): relay_id: str pubkey: str + + +class NostrAccount(BaseModel): + pubkey: str + sats = 0 + storage = 0 + paid_to_join = False + allowed = False + blocked = False + + @classmethod + def from_row(cls, row: Row) -> "NostrAccount": + return cls(**dict(row)) \ No newline at end of file diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..11dbc50 --- /dev/null +++ b/tasks.py @@ -0,0 +1,48 @@ +import asyncio +import re + +from loguru import logger + +from lnbits.core.models import Payment +from lnbits.extensions.nostrrelay.models import NostrAccount +from lnbits.helpers import get_current_extension_name +from lnbits.tasks import register_invoice_listener + +from .crud import create_account, get_account, update_account + + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue, get_current_extension_name()) + + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment): + if payment.extra.get("tag") != "nostrrely": + return + + relay_id = payment.extra.get("relay_id") + pubkey = payment.extra.get("pubkey") + + if payment.extra.get("action") == "join": + await invoice_paid_to_join(relay_id, pubkey) + return + +async def invoice_paid_to_join(relay_id: str, pubkey: str): + try: + account = await get_account(relay_id, pubkey) + if not account: + await create_account(relay_id, NostrAccount(pubkey=pubkey, paid_to_join=True)) + return + + if account.blocked or account.paid_to_join: + return + + account.paid_to_join = True + await update_account(relay_id, account) + + except Exception as ex: + logger.warning(ex) \ No newline at end of file diff --git a/views_api.py b/views_api.py index e4ad15f..d5f98c9 100644 --- a/views_api.py +++ b/views_api.py @@ -176,7 +176,7 @@ async def api_pay_to_join(data: RelayJoin): extra={ "tag": "nostrrely", "action": "join", - "relay": relay.id, + "relay_id": relay.id, "pubkey": pubkey, }, )