From 5e28183a24672d9a3b98546e8b4a011be3c6f75f Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 19 May 2022 11:39:59 +0100 Subject: [PATCH] bits bobs --- lnbits/extensions/scrub/crud.py | 85 +++++------- lnbits/extensions/scrub/lnurl.py | 109 --------------- lnbits/extensions/scrub/migrations.py | 47 +------ lnbits/extensions/scrub/models.py | 40 +----- lnbits/extensions/scrub/tasks.py | 18 +-- .../scrub/templates/lnurlp/index.html | 125 +----------------- lnbits/extensions/scrub/views_api.py | 18 +-- 7 files changed, 51 insertions(+), 391 deletions(-) delete mode 100644 lnbits/extensions/scrub/lnurl.py diff --git a/lnbits/extensions/scrub/crud.py b/lnbits/extensions/scrub/crud.py index df97f990..0dee689c 100644 --- a/lnbits/extensions/scrub/crud.py +++ b/lnbits/extensions/scrub/crud.py @@ -5,87 +5,62 @@ from . import db from .models import ScrubLink, CreateScrubLinkData -async def create_pay_link(data: CreateScrubLinkData, wallet_id: str) -> ScrubLink: - - returning = "" if db.type == SQLITE else "RETURNING ID" - method = db.execute if db.type == SQLITE else db.fetchone - result = await (method)( - f""" - INSERT INTO scrub.pay_links ( +async def create_scrub_link(wallet_id: str, data: CreateSatsDiceLink) -> satsdiceLink: + satsdice_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO scrub.scrub_links ( + id, wallet, description, - min, - max, - served_meta, - served_pr, - webhook_url, - success_text, - success_url, - comment_chars, - currency + payoraddress, ) - VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?) - {returning} + VALUES (?, ?, ?) """, ( - wallet_id, - data.description, - data.min, - data.max, - data.webhook_url, - data.success_text, - data.success_url, - data.comment_chars, - data.currency, + satsdice_id, + wallet, + description, + payoraddress, ), ) - if db.type == SQLITE: - link_id = result._result_proxy.lastrowid - else: - link_id = result[0] - - link = await get_pay_link(link_id) + link = await get_satsdice_pay(satsdice_id) assert link, "Newly created link couldn't be retrieved" return link -async def get_pay_link(link_id: int) -> Optional[ScrubLink]: - row = await db.fetchone("SELECT * FROM scrub.pay_links WHERE id = ?", (link_id,)) - return ScrubLink.from_row(row) if row else None +async def get_scrub_link(link_id: str) -> Optional[satsdiceLink]: + row = await db.fetchone( + "SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,) + ) + return satsdiceLink(**row) if row else None -async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[ScrubLink]: +async def get_scrub_links(wallet_ids: Union[str, List[str]]) -> List[satsdiceLink]: if isinstance(wallet_ids, str): wallet_ids = [wallet_ids] q = ",".join(["?"] * len(wallet_ids)) rows = await db.fetchall( f""" - SELECT * FROM scrub.pay_links WHERE wallet IN ({q}) - ORDER BY Id + SELECT * FROM scrub.scrub_links WHERE wallet IN ({q}) + ORDER BY id """, (*wallet_ids,), ) - return [ScrubLink.from_row(row) for row in rows] + return [satsdiceLink(**row) for row in rows] -async def update_pay_link(link_id: int, **kwargs) -> Optional[ScrubLink]: +async def update_scrub_link(link_id: int, **kwargs) -> Optional[satsdiceLink]: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( - f"UPDATE scrub.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) + f"UPDATE scrub.scrub_links SET {q} WHERE id = ?", + (*kwargs.values(), link_id), ) - row = await db.fetchone("SELECT * FROM scrub.pay_links WHERE id = ?", (link_id,)) - return ScrubLink.from_row(row) if row else None - - -async def increment_pay_link(link_id: int, **kwargs) -> Optional[ScrubLink]: - q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE scrub.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) + row = await db.fetchone( + "SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,) ) - row = await db.fetchone("SELECT * FROM scrub.pay_links WHERE id = ?", (link_id,)) - return ScrubLink.from_row(row) if row else None + return satsdiceLink(**row) if row else None - -async def delete_pay_link(link_id: int) -> None: - await db.execute("DELETE FROM scrub.pay_links WHERE id = ?", (link_id,)) +async def delete_scrub_link(link_id: int) -> None: + await db.execute("DELETE FROM scrub.scrub_links WHERE id = ?", (link_id,)) diff --git a/lnbits/extensions/scrub/lnurl.py b/lnbits/extensions/scrub/lnurl.py deleted file mode 100644 index 6d33479f..00000000 --- a/lnbits/extensions/scrub/lnurl.py +++ /dev/null @@ -1,109 +0,0 @@ -import hashlib -import math -from http import HTTPStatus - -from fastapi import Request -from lnurl import ( # type: ignore - LnurlErrorResponse, - LnurlScrubActionResponse, - LnurlScrubResponse, -) -from starlette.exceptions import HTTPException - -from lnbits.core.services import create_invoice -from lnbits.utils.exchange_rates import get_fiat_rate_satoshis - -from . import scrub_ext -from .crud import increment_pay_link - - -@scrub_ext.get( - "/api/v1/lnurl/{link_id}", - status_code=HTTPStatus.OK, - name="scrub.api_lnurl_response", -) -async def api_lnurl_response(request: Request, link_id): - link = await increment_pay_link(link_id, served_meta=1) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Scrub link does not exist." - ) - - rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 - - resp = LnurlScrubResponse( - callback=request.url_for("scrub.api_lnurl_callback", link_id=link.id), - min_sendable=math.ceil(link.min * rate) * 1000, - max_sendable=round(link.max * rate) * 1000, - metadata=link.scrubay_metadata, - ) - params = resp.dict() - - if link.comment_chars > 0: - params["commentAllowed"] = link.comment_chars - - return params - - -@scrub_ext.get( - "/api/v1/lnurl/cb/{link_id}", - status_code=HTTPStatus.OK, - name="scrub.api_lnurl_callback", -) -async def api_lnurl_callback(request: Request, link_id): - link = await increment_pay_link(link_id, served_pr=1) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Scrub link does not exist." - ) - min, max = link.min, link.max - rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 - if link.currency: - # allow some fluctuation (as the fiat price may have changed between the calls) - min = rate * 995 * link.min - max = rate * 1010 * link.max - else: - min = link.min * 1000 - max = link.max * 1000 - - amount_received = int(request.query_params.get("amount") or 0) - if amount_received < min: - return LnurlErrorResponse( - reason=f"Amount {amount_received} is smaller than minimum {min}." - ).dict() - - elif amount_received > max: - return LnurlErrorResponse( - reason=f"Amount {amount_received} is greater than maximum {max}." - ).dict() - - comment = request.query_params.get("comment") - if len(comment or "") > link.comment_chars: - return LnurlErrorResponse( - reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}" - ).dict() - - payment_hash, payment_request = await create_invoice( - wallet_id=link.wallet, - amount=int(amount_received / 1000), - memo=link.description, - description_hash=hashlib.sha256( - link.scrubay_metadata.encode("utf-8") - ).digest(), - extra={ - "tag": "scrub", - "link": link.id, - "comment": comment, - "extra": request.query_params.get("amount"), - }, - ) - - success_action = link.success_action(payment_hash) - if success_action: - resp = LnurlScrubActionResponse( - pr=payment_request, success_action=success_action, routes=[] - ) - else: - resp = LnurlScrubActionResponse(pr=payment_request, routes=[]) - - return resp.dict() diff --git a/lnbits/extensions/scrub/migrations.py b/lnbits/extensions/scrub/migrations.py index f16a61b1..c52a62fe 100644 --- a/lnbits/extensions/scrub/migrations.py +++ b/lnbits/extensions/scrub/migrations.py @@ -1,51 +1,14 @@ async def m001_initial(db): """ - Initial pay table. + Initial scrub table. """ await db.execute( f""" - CREATE TABLE scrub.pay_links ( - id {db.serial_primary_key}, + CREATE TABLE scrub.scrub_links ( + id TEXT PRIMARY KEY, wallet TEXT NOT NULL, description TEXT NOT NULL, - webhook INTEGER NOT NULL, - payoraddress INTEGER NOT NULL + payoraddress TEXT NOT NULL ); """ - ) - - -async def m002_webhooks_and_success_actions(db): - """ - Webhooks and success actions. - """ - await db.execute("ALTER TABLE scrub.pay_links ADD COLUMN webhook_url TEXT;") - await db.execute("ALTER TABLE scrub.pay_links ADD COLUMN success_text TEXT;") - await db.execute("ALTER TABLE scrub.pay_links ADD COLUMN success_url TEXT;") - await db.execute( - f""" - CREATE TABLE scrub.invoices ( - pay_link INTEGER NOT NULL REFERENCES {db.references_schema}pay_links (id), - payment_hash TEXT NOT NULL, - webhook_sent INT, -- null means not sent, otherwise store status - expiry INT - ); - """ - ) - - -async def m003_min_max_comment_fiat(db): - """ - Support for min/max amounts, comments and fiat prices that get - converted automatically to satoshis based on some API. - """ - await db.execute( - "ALTER TABLE scrub.pay_links ADD COLUMN currency TEXT;" - ) # null = satoshis - await db.execute( - "ALTER TABLE scrub.pay_links ADD COLUMN comment_chars INTEGER DEFAULT 0;" - ) - await db.execute("ALTER TABLE scrub.pay_links RENAME COLUMN amount TO min;") - await db.execute("ALTER TABLE scrub.pay_links ADD COLUMN max INTEGER;") - await db.execute("UPDATE scrub.pay_links SET max = min;") - await db.execute("DROP TABLE scrub.invoices") + ) \ No newline at end of file diff --git a/lnbits/extensions/scrub/models.py b/lnbits/extensions/scrub/models.py index 6a4ee9d7..b4eae631 100644 --- a/lnbits/extensions/scrub/models.py +++ b/lnbits/extensions/scrub/models.py @@ -8,31 +8,11 @@ from lnurl.types import LnurlScrubMetadata # type: ignore from sqlite3 import Row from pydantic import BaseModel - -class CreateScrubLinkData(BaseModel): - description: str - min: int = Query(0.01, ge=0.01) - max: int = Query(0.01, ge=0.01) - currency: str = Query(None) - comment_chars: int = Query(0, ge=0, lt=800) - webhook_url: str = Query(None) - success_text: str = Query(None) - success_url: str = Query(None) - - class ScrubLink(BaseModel): id: int wallet: str description: str - min: int - served_meta: int - served_pr: int - webhook_url: Optional[str] - success_text: Optional[str] - success_url: Optional[str] - currency: Optional[str] - comment_chars: int - max: int + payoraddress: str @classmethod def from_row(cls, row: Row) -> "ScrubLink": @@ -45,20 +25,4 @@ class ScrubLink(BaseModel): @property def scrubay_metadata(self) -> LnurlScrubMetadata: - return LnurlScrubMetadata(json.dumps([["text/plain", self.description]])) - - def success_action(self, payment_hash: str) -> Optional[Dict]: - if self.success_url: - url: ParseResult = urlparse(self.success_url) - qs: Dict = parse_qs(url.query) - qs["payment_hash"] = payment_hash - url = url._replace(query=urlencode(qs, doseq=True)) - return { - "tag": "url", - "description": self.success_text or "~", - "url": urlunparse(url), - } - elif self.success_text: - return {"tag": "message", "message": self.success_text} - else: - return None + return LnurlScrubMetadata(json.dumps([["text/plain", self.description]])) \ No newline at end of file diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py index af281e37..b99dc35c 100644 --- a/lnbits/extensions/scrub/tasks.py +++ b/lnbits/extensions/scrub/tasks.py @@ -28,23 +28,7 @@ async def on_invoice_paid(payment: Scrubment) -> None: return pay_link = await get_pay_link(payment.extra.get("link", -1)) - if pay_link and pay_link.webhook_url: - async with httpx.AsyncClient() as client: - try: - r = await client.post( - pay_link.webhook_url, - json={ - "payment_hash": payment.payment_hash, - "payment_request": payment.bolt11, - "amount": payment.amount, - "comment": payment.extra.get("comment"), - "scrub": pay_link.id, - }, - timeout=40, - ) - await mark_webhook_sent(payment, r.status_code) - except (httpx.ConnectError, httpx.RequestError): - await mark_webhook_sent(payment, -1) + # PAY LNURLP AND LNADDRESS async def mark_webhook_sent(payment: Scrubment, status: int) -> None: diff --git a/lnbits/extensions/scrub/templates/lnurlp/index.html b/lnbits/extensions/scrub/templates/lnurlp/index.html index 269cff7d..ec9aafea 100644 --- a/lnbits/extensions/scrub/templates/lnurlp/index.html +++ b/lnbits/extensions/scrub/templates/lnurlp/index.html @@ -154,76 +154,13 @@ type="text" label="Item description *" > -
- - -
-
-
- -
-
- -
-
+ - - -
Create pay link - - - - {% raw %} - - - -

- ID: {{ qrCodeDialog.data.id }}
- Amount: {{ qrCodeDialog.data.amount }}
- {{ qrCodeDialog.data.currency }} price: {{ - fiatRates[qrCodeDialog.data.currency] ? - fiatRates[qrCodeDialog.data.currency] + ' sat' : 'Loading...' }}
- Accepts comments: {{ qrCodeDialog.data.comments }}
- Dispatches webhook to: {{ qrCodeDialog.data.webhook - }}
- On success: {{ qrCodeDialog.data.success }}
-

- {% endraw %} -
- Copy LNURL - Shareable link - - Close -
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }} diff --git a/lnbits/extensions/scrub/views_api.py b/lnbits/extensions/scrub/views_api.py index a264a42e..805d481b 100644 --- a/lnbits/extensions/scrub/views_api.py +++ b/lnbits/extensions/scrub/views_api.py @@ -12,11 +12,11 @@ from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis from . import scrub_ext from .crud import ( - create_pay_link, - delete_pay_link, - get_pay_link, - get_pay_links, - update_pay_link, + create_scrub_link, + delete_scrub_link, + get_scrub_link, + get_scrub_links, + update_scrub_link, ) from .models import CreateScrubLinkData @@ -39,14 +39,14 @@ async def api_links( try: return [ - {**link.dict(), "lnurl": link.lnurl(req)} + {**link.dict()} for link in await get_pay_links(wallet_ids) ] - except LnurlInvalidUrl: + except: raise HTTPException( - status_code=HTTPStatus.UPGRADE_REQUIRED, - detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.", + status_code=HTTPStatus.NOT_FOUND, + detail="No links available", )