From 0d2ff2e6638f2738c107b82982fd8bea006420d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 23 Jul 2025 15:27:00 +0200 Subject: [PATCH] feat: add lud17 support for wallet parse dialog (#3289) --- lnbits/core/models/lnurl.py | 4 ++++ lnbits/core/views/lnurl_api.py | 21 ++++++++++++++++++++- lnbits/static/js/wallet.js | 32 ++++++++++++++++++++------------ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lnbits/core/models/lnurl.py b/lnbits/core/models/lnurl.py index 64047032..7cfcb640 100644 --- a/lnbits/core/models/lnurl.py +++ b/lnbits/core/models/lnurl.py @@ -14,3 +14,7 @@ class CreateLnurlPayment(BaseModel): class CreateLnurlWithdraw(BaseModel): lnurl_w: Lnurl + + +class LnurlScan(BaseModel): + lnurl: Lnurl diff --git a/lnbits/core/views/lnurl_api.py b/lnbits/core/views/lnurl_api.py index ec9a6c4a..76614f3a 100644 --- a/lnbits/core/views/lnurl_api.py +++ b/lnbits/core/views/lnurl_api.py @@ -23,7 +23,7 @@ from lnurl.models import ( from loguru import logger from lnbits.core.models import CreateLnurlWithdraw, Payment -from lnbits.core.models.lnurl import CreateLnurlPayment +from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan from lnbits.decorators import ( WalletTypeInfo, require_admin_key, @@ -40,6 +40,7 @@ lnurl_router = APIRouter(tags=["LNURL"]) @lnurl_router.get( "/api/v1/lnurlscan/{code}", dependencies=[Depends(require_invoice_key)], + deprecated=True, response_model=LnurlPayResponse | LnurlWithdrawResponse | LnurlAuthResponse @@ -58,6 +59,24 @@ async def api_lnurlscan(code: str) -> LnurlResponseModel: return res +@lnurl_router.post( + "/api/v1/lnurlscan", + dependencies=[Depends(require_invoice_key)], + response_model=LnurlPayResponse + | LnurlWithdrawResponse + | LnurlAuthResponse + | LnurlErrorResponse, +) +async def api_lnurlscan_post(scan: LnurlScan) -> LnurlResponseModel: + try: + res = await lnurl_handle(scan.lnurl, user_agent=settings.user_agent, timeout=5) + except LnurlResponseException as exc: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail=str(exc) + ) from exc + return res + + @lnurl_router.post("/api/v1/lnurlauth") async def api_perform_lnurlauth( data: LnurlAuthResponse, key_type: WalletTypeInfo = Depends(require_admin_key) diff --git a/lnbits/static/js/wallet.js b/lnbits/static/js/wallet.js index 67efc66f..a6af9805 100644 --- a/lnbits/static/js/wallet.js +++ b/lnbits/static/js/wallet.js @@ -352,11 +352,9 @@ window.WalletPageLogic = { }, lnurlScan() { LNbits.api - .request( - 'GET', - '/api/v1/lnurlscan/' + this.parse.data.request, - this.g.wallet.adminkey - ) + .request('POST', '/api/v1/lnurlscan', this.g.wallet.adminkey, { + lnurl: this.parse.data.request + }) .then(response => { const data = response.data if (data.status === 'ERROR') { @@ -404,19 +402,29 @@ window.WalletPageLogic = { this.decodeRequest() this.parse.camera.show = false }, + isLnurl(req) { + return ( + req.toLowerCase().startsWith('lnurl1') || + req.startsWith('lnurlp://') || + req.startsWith('lnurlw://') || + req.startsWith('lnurlauth://') || + req.match(/[\w.+-~_]+@[\w.+-~_]/) + ) + }, decodeRequest() { this.parse.show = true - this.parse.data.request = this.parse.data.request.trim().toLowerCase() - let req = this.parse.data.request + this.parse.data.request = this.parse.data.request.trim() + const req = this.parse.data.request.toLowerCase() if (req.startsWith('lightning:')) { - this.parse.data.request = req.slice(10) + this.parse.data.request = this.parse.data.request.slice(10) } else if (req.startsWith('lnurl:')) { - this.parse.data.request = req.slice(6) + this.parse.data.request = this.parse.data.request.slice(6) } else if (req.includes('lightning=lnurl1')) { - this.parse.data.request = req.split('lightning=')[1].split('&')[0] + this.parse.data.request = this.parse.data.request + .split('lightning=')[1] + .split('&')[0] } - req = this.parse.data.request - if (req.startsWith('lnurl1') || req.match(/[\w.+-~_]+@[\w.+-~_]/)) { + if (this.isLnurl(this.parse.data.request)) { this.lnurlScan() return }