From 1026b242bad11d15a1c04e6d029c15475c9510f9 Mon Sep 17 00:00:00 2001 From: Arc <33088785+arcbtc@users.noreply.github.com> Date: Fri, 17 Feb 2023 14:21:53 +0000 Subject: [PATCH] Removed lnurlp (#1511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Removed lnurlp * comment out extension checks for lnurlp --------- Co-authored-by: dni ⚡ --- lnbits/extensions/lnurlp/README.md | 27 -- lnbits/extensions/lnurlp/__init__.py | 38 -- lnbits/extensions/lnurlp/config.json | 10 - lnbits/extensions/lnurlp/crud.py | 95 ----- lnbits/extensions/lnurlp/lnurl.py | 106 ------ lnbits/extensions/lnurlp/migrations.py | 148 -------- lnbits/extensions/lnurlp/models.py | 75 ---- .../lnurlp/static/image/lnurl-pay.png | Bin 12984 -> 0 bytes lnbits/extensions/lnurlp/static/js/index.js | 264 -------------- lnbits/extensions/lnurlp/tasks.py | 79 ---- .../lnurlp/templates/lnurlp/_api_docs.html | 138 ------- .../lnurlp/templates/lnurlp/_lnurl.html | 31 -- .../lnurlp/templates/lnurlp/display.html | 54 --- .../lnurlp/templates/lnurlp/index.html | 345 ------------------ .../lnurlp/templates/lnurlp/print_qr.html | 27 -- lnbits/extensions/lnurlp/views.py | 43 --- lnbits/extensions/lnurlp/views_api.py | 180 --------- tests/core/views/test_generic.py | 41 ++- 18 files changed, 21 insertions(+), 1680 deletions(-) delete mode 100644 lnbits/extensions/lnurlp/README.md delete mode 100644 lnbits/extensions/lnurlp/__init__.py delete mode 100644 lnbits/extensions/lnurlp/config.json delete mode 100644 lnbits/extensions/lnurlp/crud.py delete mode 100644 lnbits/extensions/lnurlp/lnurl.py delete mode 100644 lnbits/extensions/lnurlp/migrations.py delete mode 100644 lnbits/extensions/lnurlp/models.py delete mode 100644 lnbits/extensions/lnurlp/static/image/lnurl-pay.png delete mode 100644 lnbits/extensions/lnurlp/static/js/index.js delete mode 100644 lnbits/extensions/lnurlp/tasks.py delete mode 100644 lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html delete mode 100644 lnbits/extensions/lnurlp/templates/lnurlp/_lnurl.html delete mode 100644 lnbits/extensions/lnurlp/templates/lnurlp/display.html delete mode 100644 lnbits/extensions/lnurlp/templates/lnurlp/index.html delete mode 100644 lnbits/extensions/lnurlp/templates/lnurlp/print_qr.html delete mode 100644 lnbits/extensions/lnurlp/views.py delete mode 100644 lnbits/extensions/lnurlp/views_api.py diff --git a/lnbits/extensions/lnurlp/README.md b/lnbits/extensions/lnurlp/README.md deleted file mode 100644 index 0832bfb7d..000000000 --- a/lnbits/extensions/lnurlp/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# LNURLp - -## Create a static QR code people can use to pay over Lightning Network - -LNURL is a range of lightning-network standards that allow us to use lightning-network differently. An LNURL-pay is a link that wallets use to fetch an invoice from a server on-demand. The link or QR code is fixed, but each time it is read by a compatible wallet a new invoice is issued by the service and sent to the wallet. - -[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets) - -## Usage - -1. Create an LNURLp (New Pay link)\ - ![create lnurlp](https://i.imgur.com/rhUBJFy.jpg) - - - select your wallets - - make a small description - - enter amount - - if _Fixed amount_ is unchecked you'll have the option to configure a Max and Min amount - - you can set the currency to something different than sats. For example if you choose EUR, the satoshi amount will be calculated when a user scans the LNURLp - - You can ask the user to send a comment that will be sent along with the payment (for example a comment to a blog post) - - Webhook URL allows to call an URL when the LNURLp is paid - - Success mesage, will send a message back to the user after a successful payment, for example a thank you note - - Success URL, will send back a clickable link to the user. Access to some hidden content, or a download link - -2. Use the shareable link or view the LNURLp you just created\ - ![LNURLp](https://i.imgur.com/C8s1P0Q.jpg) - - you can now open your LNURLp and copy the LNURL, get the shareable link or print it\ - ![view lnurlp](https://i.imgur.com/4n41S7T.jpg) diff --git a/lnbits/extensions/lnurlp/__init__.py b/lnbits/extensions/lnurlp/__init__.py deleted file mode 100644 index aa13bb921..000000000 --- a/lnbits/extensions/lnurlp/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -import asyncio -from typing import List - -from fastapi import APIRouter -from fastapi.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer -from lnbits.tasks import catch_everything_and_restart - -db = Database("ext_lnurlp") - -lnurlp_static_files = [ - { - "path": "/lnurlp/static", - "app": StaticFiles(packages=[("lnbits", "extensions/lnurlp/static")]), - "name": "lnurlp_static", - } -] -scheduled_tasks: List[asyncio.Task] = [] - -lnurlp_ext: APIRouter = APIRouter(prefix="/lnurlp", tags=["lnurlp"]) - - -def lnurlp_renderer(): - return template_renderer(["lnbits/extensions/lnurlp/templates"]) - - -from .lnurl import * # noqa: F401,F403 -from .tasks import wait_for_paid_invoices -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 - - -def lnurlp_start(): - loop = asyncio.get_event_loop() - task = loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) - scheduled_tasks.append(task) diff --git a/lnbits/extensions/lnurlp/config.json b/lnbits/extensions/lnurlp/config.json deleted file mode 100644 index d3e046def..000000000 --- a/lnbits/extensions/lnurlp/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "LNURLp", - "short_description": "Make reusable LNURL pay links", - "tile": "/lnurlp/static/image/lnurl-pay.png", - "contributors": [ - "arcbtc", - "eillarra", - "fiatjaf" - ] -} diff --git a/lnbits/extensions/lnurlp/crud.py b/lnbits/extensions/lnurlp/crud.py deleted file mode 100644 index 4acb4a41e..000000000 --- a/lnbits/extensions/lnurlp/crud.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import CreatePayLinkData, PayLink - - -async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: - link_id = urlsafe_short_hash()[:6] - - result = await db.execute( - """ - INSERT INTO lnurlp.pay_links ( - id, - wallet, - description, - min, - max, - served_meta, - served_pr, - webhook_url, - webhook_headers, - webhook_body, - success_text, - success_url, - comment_chars, - currency, - fiat_base_multiplier - ) - VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - link_id, - wallet_id, - data.description, - data.min, - data.max, - data.webhook_url, - data.webhook_headers, - data.webhook_body, - data.success_text, - data.success_url, - data.comment_chars, - data.currency, - data.fiat_base_multiplier, - ), - ) - assert result - - link = await get_pay_link(link_id) - assert link, "Newly created link couldn't be retrieved" - return link - - -async def get_pay_link(link_id: str) -> Optional[PayLink]: - row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,)) - return PayLink.from_row(row) if row else None - - -async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f""" - SELECT * FROM lnurlp.pay_links WHERE wallet IN ({q}) - ORDER BY Id - """, - (*wallet_ids,), - ) - return [PayLink.from_row(row) for row in rows] - - -async def update_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) - ) - row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,)) - return PayLink.from_row(row) if row else None - - -async def increment_pay_link(link_id: int, **kwargs) -> Optional[PayLink]: - q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) - ) - row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,)) - return PayLink.from_row(row) if row else None - - -async def delete_pay_link(link_id: int) -> None: - await db.execute("DELETE FROM lnurlp.pay_links WHERE id = ?", (link_id,)) diff --git a/lnbits/extensions/lnurlp/lnurl.py b/lnbits/extensions/lnurlp/lnurl.py deleted file mode 100644 index 918a5bd39..000000000 --- a/lnbits/extensions/lnurlp/lnurl.py +++ /dev/null @@ -1,106 +0,0 @@ -from http import HTTPStatus - -from fastapi import Request -from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse -from starlette.exceptions import HTTPException - -from lnbits.core.services import create_invoice -from lnbits.utils.exchange_rates import get_fiat_rate_satoshis - -from . import lnurlp_ext -from .crud import increment_pay_link - - -@lnurlp_ext.get( - "/api/v1/lnurl/{link_id}", # Backwards compatibility for old LNURLs / QR codes (with long URL) - status_code=HTTPStatus.OK, - name="lnurlp.api_lnurl_response.deprecated", -) -@lnurlp_ext.get( - "/{link_id}", - status_code=HTTPStatus.OK, - name="lnurlp.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="Pay link does not exist." - ) - - rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 - - resp = LnurlPayResponse( - callback=request.url_for("lnurlp.api_lnurl_callback", link_id=link.id), - min_sendable=round(link.min * rate) * 1000, - max_sendable=round(link.max * rate) * 1000, - metadata=link.lnurlpay_metadata, - ) - params = resp.dict() - - if link.comment_chars > 0: - params["commentAllowed"] = link.comment_chars - - return params - - -@lnurlp_ext.get( - "/api/v1/lnurl/cb/{link_id}", - status_code=HTTPStatus.OK, - name="lnurlp.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="Pay 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, - unhashed_description=link.lnurlpay_metadata.encode(), - extra={ - "tag": "lnurlp", - "link": link.id, - "comment": comment, - "extra": request.query_params.get("amount"), - }, - ) - - success_action = link.success_action(payment_hash) - if success_action: - resp = LnurlPayActionResponse( - pr=payment_request, success_action=success_action, routes=[] - ) - else: - resp = LnurlPayActionResponse(pr=payment_request, routes=[]) - - return resp.dict() diff --git a/lnbits/extensions/lnurlp/migrations.py b/lnbits/extensions/lnurlp/migrations.py deleted file mode 100644 index 1ec85eb0c..000000000 --- a/lnbits/extensions/lnurlp/migrations.py +++ /dev/null @@ -1,148 +0,0 @@ -async def m001_initial(db): - """ - Initial pay table. - """ - await db.execute( - f""" - CREATE TABLE lnurlp.pay_links ( - id {db.serial_primary_key}, - wallet TEXT NOT NULL, - description TEXT NOT NULL, - amount {db.big_int} NOT NULL, - served_meta INTEGER NOT NULL, - served_pr INTEGER NOT NULL - ); - """ - ) - - -async def m002_webhooks_and_success_actions(db): - """ - Webhooks and success actions. - """ - await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_url TEXT;") - await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN success_text TEXT;") - await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN success_url TEXT;") - await db.execute( - f""" - CREATE TABLE lnurlp.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 lnurlp.pay_links ADD COLUMN currency TEXT;" - ) # null = satoshis - await db.execute( - "ALTER TABLE lnurlp.pay_links ADD COLUMN comment_chars INTEGER DEFAULT 0;" - ) - await db.execute("ALTER TABLE lnurlp.pay_links RENAME COLUMN amount TO min;") - await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN max INTEGER;") - await db.execute("UPDATE lnurlp.pay_links SET max = min;") - await db.execute("DROP TABLE lnurlp.invoices") - - -async def m004_fiat_base_multiplier(db): - """ - Store the multiplier for fiat prices. We store the price in cents and - remember to multiply by 100 when we use it to convert to Dollars. - """ - await db.execute( - "ALTER TABLE lnurlp.pay_links ADD COLUMN fiat_base_multiplier INTEGER DEFAULT 1;" - ) - - -async def m005_webhook_headers_and_body(db): - """ - Add headers and body to webhooks - """ - await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_headers TEXT;") - await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_body TEXT;") - - -async def m006_redux(db): - """ - Migrate ID column type to string for UUIDs and migrate existing data - """ - # we can simply change the column type for postgres - if db.type != "SQLITE": - await db.execute("ALTER TABLE lnurlp.pay_links ALTER COLUMN id TYPE TEXT;") - else: - # but we have to do this for sqlite - await db.execute("ALTER TABLE lnurlp.pay_links RENAME TO pay_links_old") - await db.execute( - f""" - CREATE TABLE lnurlp.pay_links ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - description TEXT NOT NULL, - min {db.big_int} NOT NULL, - max {db.big_int}, - currency TEXT, - fiat_base_multiplier INTEGER DEFAULT 1, - served_meta INTEGER NOT NULL, - served_pr INTEGER NOT NULL, - webhook_url TEXT, - success_text TEXT, - success_url TEXT, - comment_chars INTEGER DEFAULT 0, - webhook_headers TEXT, - webhook_body TEXT - ); - """ - ) - - for row in [ - list(row) for row in await db.fetchall("SELECT * FROM lnurlp.pay_links_old") - ]: - await db.execute( - """ - INSERT INTO lnurlp.pay_links ( - id, - wallet, - description, - min, - served_meta, - served_pr, - webhook_url, - success_text, - success_url, - currency, - comment_chars, - max, - fiat_base_multiplier, - webhook_headers, - webhook_body - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - row[0], - row[1], - row[2], - row[3], - row[4], - row[5], - row[6], - row[7], - row[8], - row[9], - row[10], - row[11], - row[12], - row[13], - row[14], - ), - ) - - await db.execute("DROP TABLE lnurlp.pay_links_old") diff --git a/lnbits/extensions/lnurlp/models.py b/lnbits/extensions/lnurlp/models.py deleted file mode 100644 index de66d4064..000000000 --- a/lnbits/extensions/lnurlp/models.py +++ /dev/null @@ -1,75 +0,0 @@ -import json -from sqlite3 import Row -from typing import Dict, Optional -from urllib.parse import ParseResult, urlparse, urlunparse - -from fastapi.param_functions import Query -from lnurl.types import LnurlPayMetadata -from pydantic import BaseModel -from starlette.requests import Request - -from lnbits.lnurl import encode as lnurl_encode - - -class CreatePayLinkData(BaseModel): - description: str - min: float = Query(1, ge=0.01) - max: float = Query(1, ge=0.01) - currency: str = Query(None) - comment_chars: int = Query(0, ge=0, lt=800) - webhook_url: str = Query(None) - webhook_headers: str = Query(None) - webhook_body: str = Query(None) - success_text: str = Query(None) - success_url: str = Query(None) - fiat_base_multiplier: int = Query(100, ge=1) - - -class PayLink(BaseModel): - id: str - wallet: str - description: str - min: float - served_meta: int - served_pr: int - webhook_url: Optional[str] - webhook_headers: Optional[str] - webhook_body: Optional[str] - success_text: Optional[str] - success_url: Optional[str] - currency: Optional[str] - comment_chars: int - max: float - fiat_base_multiplier: int - - @classmethod - def from_row(cls, row: Row) -> "PayLink": - data = dict(row) - if data["currency"] and data["fiat_base_multiplier"]: - data["min"] /= data["fiat_base_multiplier"] - data["max"] /= data["fiat_base_multiplier"] - return cls(**data) - - def lnurl(self, req: Request) -> str: - url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id) - return lnurl_encode(url) - - @property - def lnurlpay_metadata(self) -> LnurlPayMetadata: - return LnurlPayMetadata(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 = parse_qs(url.query) - # setattr(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 diff --git a/lnbits/extensions/lnurlp/static/image/lnurl-pay.png b/lnbits/extensions/lnurlp/static/image/lnurl-pay.png deleted file mode 100644 index 36af81a734a86592411a06dd4fe68a290b35d2f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12984 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
A)JdA-HKF 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 diff --git a/lnbits/extensions/lnurlp/static/js/index.js b/lnbits/extensions/lnurlp/static/js/index.js deleted file mode 100644 index c1372bec4..000000000 --- a/lnbits/extensions/lnurlp/static/js/index.js +++ /dev/null @@ -1,264 +0,0 @@ -/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */ - -Vue.component(VueQrcode.name, VueQrcode) - -var locationPath = [ - window.location.protocol, - '//', - window.location.host, - window.location.pathname -].join('') - -var mapPayLink = obj => { - obj._data = _.clone(obj) - obj.date = Quasar.utils.date.formatDate( - new Date(obj.time * 1000), - 'YYYY-MM-DD HH:mm' - ) - obj.amount = new Intl.NumberFormat(LOCALE).format(obj.amount) - obj.print_url = [locationPath, 'print/', obj.id].join('') - obj.pay_url = [locationPath, 'link/', obj.id].join('') - return obj -} - -new Vue({ - el: '#vue', - mixins: [windowMixin], - data() { - return { - currencies: [], - fiatRates: {}, - checker: null, - payLinks: [], - payLinksTable: { - pagination: { - rowsPerPage: 10 - } - }, - nfcTagWriting: false, - formDialog: { - show: false, - fixedAmount: true, - data: {} - }, - qrCodeDialog: { - show: false, - data: null - } - } - }, - methods: { - getPayLinks() { - LNbits.api - .request( - 'GET', - '/lnurlp/api/v1/links?all_wallets=true', - this.g.user.wallets[0].inkey - ) - .then(response => { - this.payLinks = response.data.map(mapPayLink) - }) - .catch(err => { - clearInterval(this.checker) - LNbits.utils.notifyApiError(err) - }) - }, - closeFormDialog() { - this.resetFormData() - }, - openQrCodeDialog(linkId) { - var link = _.findWhere(this.payLinks, {id: linkId}) - if (link.currency) this.updateFiatRate(link.currency) - - this.qrCodeDialog.data = { - id: link.id, - amount: - (link.min === link.max ? link.min : `${link.min} - ${link.max}`) + - ' ' + - (link.currency || 'sat'), - currency: link.currency, - comments: link.comment_chars - ? `${link.comment_chars} characters` - : 'no', - webhook: link.webhook_url || 'nowhere', - success: - link.success_text || link.success_url - ? 'Display message "' + - link.success_text + - '"' + - (link.success_url ? ' and URL "' + link.success_url + '"' : '') - : 'do nothing', - lnurl: link.lnurl, - pay_url: link.pay_url, - print_url: link.print_url - } - this.qrCodeDialog.show = true - }, - openUpdateDialog(linkId) { - const link = _.findWhere(this.payLinks, {id: linkId}) - if (link.currency) this.updateFiatRate(link.currency) - - this.formDialog.data = _.clone(link._data) - this.formDialog.show = true - this.formDialog.fixedAmount = - this.formDialog.data.min === this.formDialog.data.max - }, - sendFormData() { - const wallet = _.findWhere(this.g.user.wallets, { - id: this.formDialog.data.wallet - }) - var data = _.omit(this.formDialog.data, 'wallet') - - if (this.formDialog.fixedAmount) data.max = data.min - if (data.currency === 'satoshis') data.currency = null - if (isNaN(parseInt(data.comment_chars))) data.comment_chars = 0 - - if (data.id) { - this.updatePayLink(wallet, data) - } else { - this.createPayLink(wallet, data) - } - }, - resetFormData() { - this.formDialog = { - show: false, - fixedAmount: true, - data: {} - } - }, - updatePayLink(wallet, data) { - let values = _.omit( - _.pick( - data, - 'description', - 'min', - 'max', - 'webhook_url', - 'success_text', - 'success_url', - 'comment_chars', - 'currency' - ), - (value, key) => - (key === 'webhook_url' || - key === 'success_text' || - key === 'success_url') && - (value === null || value === '') - ) - - LNbits.api - .request( - 'PUT', - '/lnurlp/api/v1/links/' + data.id, - wallet.adminkey, - values - ) - .then(response => { - this.payLinks = _.reject(this.payLinks, obj => obj.id === data.id) - this.payLinks.push(mapPayLink(response.data)) - this.formDialog.show = false - this.resetFormData() - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - createPayLink(wallet, data) { - LNbits.api - .request('POST', '/lnurlp/api/v1/links', wallet.adminkey, data) - .then(response => { - this.getPayLinks() - this.formDialog.show = false - this.resetFormData() - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - deletePayLink(linkId) { - var link = _.findWhere(this.payLinks, {id: linkId}) - - LNbits.utils - .confirmDialog('Are you sure you want to delete this pay link?') - .onOk(() => { - LNbits.api - .request( - 'DELETE', - '/lnurlp/api/v1/links/' + linkId, - _.findWhere(this.g.user.wallets, {id: link.wallet}).adminkey - ) - .then(response => { - this.payLinks = _.reject(this.payLinks, obj => obj.id === linkId) - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }) - }, - updateFiatRate(currency) { - LNbits.api - .request('GET', '/lnurlp/api/v1/rate/' + currency, null) - .then(response => { - let rates = _.clone(this.fiatRates) - rates[currency] = response.data.rate - this.fiatRates = rates - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - writeNfcTag: async function (lnurl) { - try { - if (typeof NDEFReader == 'undefined') { - throw { - toString: function () { - return 'NFC not supported on this device or browser.' - } - } - } - - const ndef = new NDEFReader() - - this.nfcTagWriting = true - this.$q.notify({ - message: 'Tap your NFC tag to write the LNURL-pay link to it.' - }) - - await ndef.write({ - records: [{recordType: 'url', data: 'lightning:' + lnurl, lang: 'en'}] - }) - - this.nfcTagWriting = false - this.$q.notify({ - type: 'positive', - message: 'NFC tag written successfully.' - }) - } catch (error) { - this.nfcTagWriting = false - this.$q.notify({ - type: 'negative', - message: error - ? error.toString() - : 'An unexpected error has occurred.' - }) - } - } - }, - created() { - if (this.g.user.wallets.length) { - var getPayLinks = this.getPayLinks - getPayLinks() - this.checker = setInterval(() => { - getPayLinks() - }, 20000) - } - LNbits.api - .request('GET', '/lnurlp/api/v1/currencies') - .then(response => { - this.currencies = ['satoshis', ...response.data] - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - } -}) diff --git a/lnbits/extensions/lnurlp/tasks.py b/lnbits/extensions/lnurlp/tasks.py deleted file mode 100644 index ea01e04f8..000000000 --- a/lnbits/extensions/lnurlp/tasks.py +++ /dev/null @@ -1,79 +0,0 @@ -import asyncio -import json - -import httpx -from loguru import logger - -from lnbits.core.crud import update_payment_extra -from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name -from lnbits.tasks import register_invoice_listener - -from .crud import get_pay_link - - -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") != "lnurlp": - return - - if payment.extra.get("wh_status"): - # this webhook has already been sent - 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: httpx.Response = 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"), - "lnurlp": pay_link.id, - "body": json.loads(pay_link.webhook_body) - if pay_link.webhook_body - else "", - }, - headers=json.loads(pay_link.webhook_headers) - if pay_link.webhook_headers - else None, - timeout=40, - ) - await mark_webhook_sent( - payment.payment_hash, - r.status_code, - r.is_success, - r.reason_phrase, - r.text, - ) - except Exception as ex: - logger.error(ex) - await mark_webhook_sent( - payment.payment_hash, -1, False, "Unexpected Error", str(ex) - ) - - -async def mark_webhook_sent( - payment_hash: str, status: int, is_success: bool, reason_phrase="", text="" -) -> None: - - await update_payment_extra( - payment_hash, - { - "wh_status": status, # keep for backwards compability - "wh_success": is_success, - "wh_message": reason_phrase, - "wh_response": text, - }, - ) diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html b/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html deleted file mode 100644 index abb37e90c..000000000 --- a/lnbits/extensions/lnurlp/templates/lnurlp/_api_docs.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - GET /lnurlp/api/v1/links -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- [<pay_link_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}lnurlp/api/v1/links -H "X-Api-Key: - {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /lnurlp/api/v1/links/<pay_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 201 CREATED (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X GET {{ request.base_url }}lnurlp/api/v1/links/<pay_id> - -H "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - - POST /lnurlp/api/v1/links -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"description": <string> "amount": <integer> "max": - <integer> "min": <integer> "comment_chars": - <integer>} -
- Returns 201 CREATED (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X POST {{ request.base_url }}lnurlp/api/v1/links -d - '{"description": <string>, "amount": <integer>, "max": - <integer>, "min": <integer>, "comment_chars": - <integer>}' -H "Content-type: application/json" -H "X-Api-Key: - {{ user.wallets[0].adminkey }}" - -
-
-
- - - - PUT - /lnurlp/api/v1/links/<pay_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"description": <string>, "amount": <integer>} -
- Returns 200 OK (application/json) -
- {"lnurl": <string>} -
Curl example
- curl -X PUT {{ request.base_url }}lnurlp/api/v1/links/<pay_id> - -d '{"description": <string>, "amount": <integer>}' -H - "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - DELETE - /lnurlp/api/v1/links/<pay_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}lnurlp/api/v1/links/<pay_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
-
diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/_lnurl.html b/lnbits/extensions/lnurlp/templates/lnurlp/_lnurl.html deleted file mode 100644 index f2ba86611..000000000 --- a/lnbits/extensions/lnurlp/templates/lnurlp/_lnurl.html +++ /dev/null @@ -1,31 +0,0 @@ - - - -

- WARNING: LNURL must be used over https or TOR
- LNURL is a range of lightning-network standards that allow us to use - lightning-network differently. An LNURL-pay is a link that wallets use - to fetch an invoice from a server on-demand. The link or QR code is - fixed, but each time it is read by a compatible wallet a new QR code is - issued by the service. It can be used to activate machines without them - having to maintain an electronic screen to generate and show invoices - locally, or to sell any predefined good or service automatically. -

-

- Exploring LNURL and finding use cases, is really helping inform - lightning protocol development, rather than the protocol dictating how - lightning-network should be engaged with. -

- Check - Awesome LNURL - for further information. -
-
-
diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/display.html b/lnbits/extensions/lnurlp/templates/lnurlp/display.html deleted file mode 100644 index 7d440378e..000000000 --- a/lnbits/extensions/lnurlp/templates/lnurlp/display.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - - -
- Copy LNURL - -
-
-
-
-
- - -
LNbits LNURL-pay link
-

Use an LNURL compatible bitcoin wallet to pay.

-
- - - {% include "lnurlp/_lnurl.html" %} - -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/index.html b/lnbits/extensions/lnurlp/templates/lnurlp/index.html deleted file mode 100644 index 3fbd3446b..000000000 --- a/lnbits/extensions/lnurlp/templates/lnurlp/index.html +++ /dev/null @@ -1,345 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New pay link - - - - - -
-
-
Pay links
-
-
- - {% raw %} - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} LNURL-pay extension -
-
- - - - {% include "lnurlp/_api_docs.html" %} - - {% include "lnurlp/_lnurl.html" %} - - -
-
- - - - - - - - -
- - - -
-
-
- -
-
- -
-
- - - - - - - - -
- Update pay link - Create pay link - Cancel -
-
-
-
- - - - {% 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 - Copy sharable link - - Write to NFC - - Print - Close -
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/lnurlp/templates/lnurlp/print_qr.html b/lnbits/extensions/lnurlp/templates/lnurlp/print_qr.html deleted file mode 100644 index 5f3129d62..000000000 --- a/lnbits/extensions/lnurlp/templates/lnurlp/print_qr.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "print.html" %} {% block page %} -
-
- -
-
-{% endblock %} {% block styles %} - -{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/lnurlp/views.py b/lnbits/extensions/lnurlp/views.py deleted file mode 100644 index c5fa35823..000000000 --- a/lnbits/extensions/lnurlp/views.py +++ /dev/null @@ -1,43 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Request -from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import lnurlp_ext, lnurlp_renderer -from .crud import get_pay_link - -templates = Jinja2Templates(directory="templates") - - -@lnurlp_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return lnurlp_renderer().TemplateResponse( - "lnurlp/index.html", {"request": request, "user": user.dict()} - ) - - -@lnurlp_ext.get("/link/{link_id}", response_class=HTMLResponse) -async def display(request: Request, link_id): - link = await get_pay_link(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." - ) - ctx = {"request": request, "lnurl": link.lnurl(req=request)} - return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx) - - -@lnurlp_ext.get("/print/{link_id}", response_class=HTMLResponse) -async def print_qr(request: Request, link_id): - link = await get_pay_link(link_id) - if not link: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." - ) - ctx = {"request": request, "lnurl": link.lnurl(req=request)} - return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx) diff --git a/lnbits/extensions/lnurlp/views_api.py b/lnbits/extensions/lnurlp/views_api.py deleted file mode 100644 index b4af29493..000000000 --- a/lnbits/extensions/lnurlp/views_api.py +++ /dev/null @@ -1,180 +0,0 @@ -import json -from asyncio.log import logger -from http import HTTPStatus - -from fastapi import Depends, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user -from lnbits.decorators import WalletTypeInfo, check_admin, get_key_type -from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis - -from . import lnurlp_ext, scheduled_tasks -from .crud import ( - create_pay_link, - delete_pay_link, - get_pay_link, - get_pay_links, - update_pay_link, -) -from .models import CreatePayLinkData - - -@lnurlp_ext.get("/api/v1/currencies") -async def api_list_currencies_available(): - return list(currencies.keys()) - - -@lnurlp_ext.get("/api/v1/links", status_code=HTTPStatus.OK) -async def api_links( - req: Request, - wallet: WalletTypeInfo = Depends(get_key_type), - all_wallets: bool = Query(False), -): - wallet_ids = [wallet.wallet.id] - - if all_wallets: - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - - try: - return [ - {**link.dict(), "lnurl": link.lnurl(req)} - for link in await get_pay_links(wallet_ids) - ] - - except LnurlInvalidUrl: - raise HTTPException( - status_code=HTTPStatus.UPGRADE_REQUIRED, - detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.", - ) - - -@lnurlp_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_retrieve( - r: Request, link_id, wallet: WalletTypeInfo = Depends(get_key_type) -): - link = await get_pay_link(link_id) - - if not link: - raise HTTPException( - detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN - ) - - return {**link.dict(), **{"lnurl": link.lnurl(r)}} - - -@lnurlp_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED) -@lnurlp_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_create_or_update( - data: CreatePayLinkData, - request: Request, - link_id=None, - wallet: WalletTypeInfo = Depends(get_key_type), -): - - if data.min > data.max: - raise HTTPException( - detail="Min is greater than max.", status_code=HTTPStatus.BAD_REQUEST - ) - - if data.currency is None and ( - round(data.min) != data.min or round(data.max) != data.max or data.min < 1 - ): - raise HTTPException( - detail="Must use full satoshis.", status_code=HTTPStatus.BAD_REQUEST - ) - - if data.webhook_headers: - try: - json.loads(data.webhook_headers) - except ValueError: - raise HTTPException( - detail="Invalid JSON in webhook_headers.", - status_code=HTTPStatus.BAD_REQUEST, - ) - - if data.webhook_body: - try: - json.loads(data.webhook_body) - except ValueError: - raise HTTPException( - detail="Invalid JSON in webhook_body.", - status_code=HTTPStatus.BAD_REQUEST, - ) - - # database only allows int4 entries for min and max. For fiat currencies, - # we multiply by data.fiat_base_multiplier (usually 100) to save the value in cents. - if data.currency and data.fiat_base_multiplier: - data.min *= data.fiat_base_multiplier - data.max *= data.fiat_base_multiplier - - if data.success_url is not None and not data.success_url.startswith("https://"): - raise HTTPException( - detail="Success URL must be secure https://...", - status_code=HTTPStatus.BAD_REQUEST, - ) - - if link_id: - link = await get_pay_link(link_id) - - if not link: - raise HTTPException( - detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN - ) - - link = await update_pay_link(**data.dict(), link_id=link_id) - else: - link = await create_pay_link(data, wallet_id=wallet.wallet.id) - assert link - return {**link.dict(), "lnurl": link.lnurl(request)} - - -@lnurlp_ext.delete("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(get_key_type)): - link = await get_pay_link(link_id) - - if not link: - raise HTTPException( - detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND - ) - - if link.wallet != wallet.wallet.id: - raise HTTPException( - detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN - ) - - await delete_pay_link(link_id) - return {"success": True} - - -@lnurlp_ext.get("/api/v1/rate/{currency}", status_code=HTTPStatus.OK) -async def api_check_fiat_rate(currency): - try: - rate = await get_fiat_rate_satoshis(currency) - except AssertionError: - rate = None - - return {"rate": rate} - - -@lnurlp_ext.delete("/api/v1", status_code=HTTPStatus.OK) -async def api_stop(wallet: WalletTypeInfo = Depends(check_admin)): - for t in scheduled_tasks: - try: - t.cancel() - except Exception as ex: - logger.warning(ex) - - return {"success": True} diff --git a/tests/core/views/test_generic.py b/tests/core/views/test_generic.py index 333251575..0a8e71a56 100644 --- a/tests/core/views/test_generic.py +++ b/tests/core/views/test_generic.py @@ -125,14 +125,27 @@ async def test_get_extensions_no_user(client): # check GET /extensions: enable extension -@pytest.mark.asyncio -async def test_get_extensions_enable(client, to_user): - response = await client.get( - "extensions", params={"usr": to_user.id, "enable": "lnurlp"} - ) - assert response.status_code == 200, ( - str(response.url) + " " + str(response.status_code) - ) +# TODO: test fails because of removing lnurlp extension +# @pytest.mark.asyncio +# async def test_get_extensions_enable(client, to_user): +# response = await client.get( +# "extensions", params={"usr": to_user.id, "enable": "lnurlp"} +# ) +# assert response.status_code == 200, ( +# str(response.url) + " " + str(response.status_code) +# ) + + +# check GET /extensions: enable and disable extensions, expect code 400 bad request +# @pytest.mark.asyncio +# async def test_get_extensions_enable_and_disable(client, to_user): +# response = await client.get( +# "extensions", +# params={"usr": to_user.id, "enable": "lnurlp", "disable": "lnurlp"}, +# ) +# assert response.status_code == 400, ( +# str(response.url) + " " + str(response.status_code) +# ) # check GET /extensions: enable nonexistent extension, expect code 400 bad request @@ -144,15 +157,3 @@ async def test_get_extensions_enable_nonexistent_extension(client, to_user): assert response.status_code == 400, ( str(response.url) + " " + str(response.status_code) ) - - -# check GET /extensions: enable and disable extensions, expect code 400 bad request -@pytest.mark.asyncio -async def test_get_extensions_enable_and_disable(client, to_user): - response = await client.get( - "extensions", - params={"usr": to_user.id, "enable": "lnurlp", "disable": "lnurlp"}, - ) - assert response.status_code == 400, ( - str(response.url) + " " + str(response.status_code) - )