diff --git a/.gitignore b/.gitignore index 01d9c35..f91cfaa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules .mypy_cache .venv .DS_Store +data/ diff --git a/config.json b/config.json index ada80ad..16e131e 100644 --- a/config.json +++ b/config.json @@ -9,7 +9,7 @@ "role": "Lead dev" } ], - "version": "1.1.0", - "min_lnbits_version": "1.5.0", + "version": "1.1.1", + "min_lnbits_version": "1.5.4", "license": "MIT" } diff --git a/package-lock.json b/package-lock.json index f3566df..d9a5f24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "decoder", - "version": "1.0.1", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "decoder", - "version": "1.0.1", + "version": "1.1.1", "license": "MIT", "dependencies": { "prettier": "^3.2.5", diff --git a/package.json b/package.json index 10861ab..9ba90ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decoder", - "version": "1.0.5", + "version": "1.1.1", "description": "", "main": "index.js", "scripts": { diff --git a/pyproject.toml b/pyproject.toml index cb84f8d..5f285c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "decoder" -version = "1.1.0" +version = "1.1.1" requires-python = ">=3.10,<3.13" description = "Decode lnurls." authors = [{name = "Bitkarrot", email = "info@bitkarrot.co"}] @@ -19,7 +19,7 @@ dev = [ [tool.poetry] name = "lnbits-decoder" -version = "1.1.0" +version = "1.1.1" description = "LNbits decoder extension" authors = ["Bitkarrot "] package-mode = false diff --git a/static/js/index.js b/static/js/index.js index b5d773e..15399ed 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -9,9 +9,13 @@ window.PageDecoder = { lnurlData: '', lnaddress: '', lnaddressData: '', - invoiceData: '' + invoiceData: '', + version: '' } }, + mounted() { + this.getVersion() + }, methods: { blankallFields() { this.invoice = '' @@ -22,7 +26,11 @@ window.PageDecoder = { this.lnaddressData = '' }, sendFormData() { - let url = this.isValidLNaddress(this.input) + let input = this.input + if (input.toLowerCase().startsWith('lightning:')) { + input = input.slice(10) + } + let url = this.isValidLNaddress(input) if (url != 'invalid') { this.lnaddress = this.input this.invoice = '' @@ -32,7 +40,7 @@ window.PageDecoder = { this.decoderData = '' this.getLNAddressData(url) } else { - this.decoderFunction({data: this.input}) + this.decoderFunction({data: input}) } }, isValidLNaddress(address) { @@ -110,11 +118,30 @@ window.PageDecoder = { } }) .catch(error => { + if (error.response && error.response.data) { + if (typeof error.response.data === 'string') { + error.response.data = { detail: error.response.data } + } else if (error.response.data.message && !error.response.data.detail) { + error.response.data = { detail: error.response.data.message } + } + } LNbits.utils.notifyApiError(error) }) }, isObject(val) { return val !== null && typeof val === 'object' && !Array.isArray(val) + }, + async getVersion() { + try { + const response = await fetch('/decoder/api/v1/version') + if (!response.ok) { + throw new Error('Network response was not ok') + } + const data = await response.json() + this.version = data.version + } catch (error) { + console.error('Failed to fetch decoder version:', error) + } } } } diff --git a/static/js/index.vue b/static/js/index.vue index ee037ae..bc1860a 100644 --- a/static/js/index.vue +++ b/static/js/index.vue @@ -63,7 +63,7 @@ -

Decoder Extension

+

Decoder Extension v{{ version }}

Decoder is a simple BOLT11, LNURL, and Lightning Address decoder for Lightning. Paste in your invoice or LNURL to diff --git a/templates/decoder/_decoder.html b/templates/decoder/_decoder.html deleted file mode 100644 index c50a554..0000000 --- a/templates/decoder/_decoder.html +++ /dev/null @@ -1,19 +0,0 @@ - - -

Decoder Extension

-

- Decoder is a simple BOLT11, LNURL, and Lightning Address decoder for - Lightning. Paste in your invoice or LNURL to decode. Useful for testing - and debugging while developing applications on the lightning network. -

- Created by - Bitkarrot - -
-
diff --git a/templates/decoder/index.html b/templates/decoder/index.html deleted file mode 100644 index 7b510f6..0000000 --- a/templates/decoder/index.html +++ /dev/null @@ -1,224 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} - -
-
- - -
-
- - Decode Lightning BOLT11, LNURL, and Lightning Address - -
-
-
-
- - - - Go - -
-
-
-
- - -
- Invoice:{% raw %} {{ invoice }}{% endraw %} - -
-
-
- LNURL:{% raw %} {{ lnurl }}{% endraw %} - -
-
-
- Lightning Address:{% raw %} {{ lnaddress }}{% endraw - %} - -
-
-
-
-
- -
- - -
- {{ SITE_TITLE }} Decoder extension -
-
- - - {% include "decoder/_decoder.html" %} - -
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - - -{% endblock %} diff --git a/tests/test_decoder.py b/tests/test_decoder.py new file mode 100644 index 0000000..5be7506 --- /dev/null +++ b/tests/test_decoder.py @@ -0,0 +1,63 @@ +import httpx +import pytest +from bolt11 import decode as bolt11_decode +from lnurl import Lnurl, url_decode + +from ..views import _load_version + +SAMPLE_INVOICE = ( + "LNBC2220N1P4RNXA9PP587AZAHDX7PHQ9LHVY8HWZZ9C7WK3MW2LVXKJ8AMKE3AHVTXEVLESS" + "P5JVEMH3SHMWKRJXZ8PS4AU0C34UJE9P8NQ4ETRU75YX9A3FCPT0WSXQRRSSNP4QVYNDEAQZMA" + "N7H898JXM98DZKM0MLRSX36S93SMRUR7H0AZYYUXC5RZJQ25CARZEPGD4VQSYN44JRK85EZRPJ" + "U92XYRK9APW4CDJH6YRWT5JGQQQQRT49LMTCQQQQQQQQQQQ86QQ9QCQZPUDQ2F38XY6T5WV9QYY" + "SSQAYYKSWSMM3SSLA72MA0VE3E9V69Z64CW8NCLL5954XAQR60ESXV3ATQQVUFT2ZX7WJ89ZPWD" + "AC23RSEUHESFEZDF4X46RQDZK0YWVGCQVFSSZE" +) +SAMPLE_LNURL = Lnurl("https://primal.net/.well-known/lnurlp/bitkarrot").bech32 +SAMPLE_LIGHTNING_ADDRESS = "bitkarrot@primal.net" + + +def _strip_lightning_prefix(input_str: str) -> str: + if input_str.lower().startswith("lightning:"): + return input_str[10:] + return input_str + + +@pytest.mark.asyncio +async def test_bolt11_decode(): + invoice = _strip_lightning_prefix(f"LIGHTNING:{SAMPLE_INVOICE}") + decoded = bolt11_decode(invoice) + assert decoded.data["currency"] == "bc" + assert decoded.data["amount_msat"] == 222000 + assert decoded.data["description"] == "LNbits" + assert "payment_hash" in decoded.data + + +@pytest.mark.asyncio +async def test_lnurl_decode(): + url = str(url_decode(SAMPLE_LNURL)) + assert url.startswith("https://") + + +@pytest.mark.asyncio +async def test_lightning_address_resolution(): + name, domain = SAMPLE_LIGHTNING_ADDRESS.split("@") + url = f"https://{domain}/.well-known/lnurlp/{name}" + async with httpx.AsyncClient() as client: + response = await client.get(url) + assert response.status_code == 200 + data = response.json() + assert "callback" in data + assert data.get("tag") == "payRequest" + + +@pytest.mark.asyncio +async def test_lightning_prefix_is_stripped(): + prefixed = f"LIGHTNING:{SAMPLE_INVOICE}" + assert _strip_lightning_prefix(prefixed) == SAMPLE_INVOICE + assert _strip_lightning_prefix(SAMPLE_INVOICE) == SAMPLE_INVOICE + + +@pytest.mark.asyncio +async def test_version_loaded_from_config(): + assert _load_version() == "1.1.1" diff --git a/tests/test_init.py b/tests/test_init.py index 32e06b0..953443c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,7 +1,9 @@ import pytest -from fastapi import APIRouter +from fastapi import APIRouter, FastAPI +from httpx import AsyncClient from .. import decoder_ext +from ..views import __version__ # just import router and add it to a test router @@ -9,3 +11,14 @@ async def test_router(): router = APIRouter() router.include_router(decoder_ext) + + +@pytest.mark.asyncio +async def test_version_endpoint(): + app = FastAPI() + app.include_router(decoder_ext) + async with AsyncClient(app=app, base_url="http://test") as client: + response = await client.get("/decoder/api/v1/version") + assert response.status_code == 200 + assert response.json() == {"version": __version__} + assert __version__ == "1.1.1" diff --git a/uv.lock b/uv.lock index 886b36f..8fff39d 100644 --- a/uv.lock +++ b/uv.lock @@ -593,7 +593,7 @@ wheels = [ [[package]] name = "decoder" -version = "1.1.0" +version = "1.1.1" source = { virtual = "." } dependencies = [ { name = "lnbits" }, diff --git a/views.py b/views.py index a9eba9b..998f3cb 100644 --- a/views.py +++ b/views.py @@ -1,12 +1,35 @@ +import json +from pathlib import Path + from fastapi import APIRouter, Depends from lnbits.core.views.generic import index -from lnbits.decorators import check_account_id_exists +from lnbits.decorators import check_user_exists + + +def _load_version() -> str: + config_path = Path(__file__).parent / "config.json" + try: + with config_path.open() as f: + return json.load(f).get("version", "unknown") + except Exception: + return "unknown" + + +__version__ = _load_version() decoder_generic_router: APIRouter = APIRouter() + decoder_generic_router.add_api_route( "/", methods=["GET"], endpoint=index, - dependencies=[Depends(check_account_id_exists)], + dependencies=[Depends(check_user_exists)], +) + + +decoder_generic_router.add_api_route( + "/api/v1/version", + methods=["GET"], + endpoint=lambda: {"version": __version__}, )