From b938103a075162e36416b3fd489a961f3ec5aeec Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 14 Feb 2023 14:14:07 +0000 Subject: [PATCH] removes lnurldevice --- lnbits/extensions/lnurldevice/README.md | 3 - lnbits/extensions/lnurldevice/__init__.py | 35 - lnbits/extensions/lnurldevice/config.json | 6 - lnbits/extensions/lnurldevice/crud.py | 177 ---- lnbits/extensions/lnurldevice/lnurl.py | 295 ------ lnbits/extensions/lnurldevice/migrations.py | 139 --- lnbits/extensions/lnurldevice/models.py | 171 ---- .../lnurldevice/static/image/lnurldevice.png | Bin 13746 -> 0 bytes lnbits/extensions/lnurldevice/tasks.py | 36 - .../templates/lnurldevice/_api_docs.html | 190 ---- .../templates/lnurldevice/error.html | 34 - .../templates/lnurldevice/index.html | 881 ------------------ .../templates/lnurldevice/paid.html | 27 - lnbits/extensions/lnurldevice/views.py | 62 -- lnbits/extensions/lnurldevice/views_api.py | 88 -- 15 files changed, 2144 deletions(-) delete mode 100644 lnbits/extensions/lnurldevice/README.md delete mode 100644 lnbits/extensions/lnurldevice/__init__.py delete mode 100644 lnbits/extensions/lnurldevice/config.json delete mode 100644 lnbits/extensions/lnurldevice/crud.py delete mode 100644 lnbits/extensions/lnurldevice/lnurl.py delete mode 100644 lnbits/extensions/lnurldevice/migrations.py delete mode 100644 lnbits/extensions/lnurldevice/models.py delete mode 100644 lnbits/extensions/lnurldevice/static/image/lnurldevice.png delete mode 100644 lnbits/extensions/lnurldevice/tasks.py delete mode 100644 lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html delete mode 100644 lnbits/extensions/lnurldevice/templates/lnurldevice/error.html delete mode 100644 lnbits/extensions/lnurldevice/templates/lnurldevice/index.html delete mode 100644 lnbits/extensions/lnurldevice/templates/lnurldevice/paid.html delete mode 100644 lnbits/extensions/lnurldevice/views.py delete mode 100644 lnbits/extensions/lnurldevice/views_api.py diff --git a/lnbits/extensions/lnurldevice/README.md b/lnbits/extensions/lnurldevice/README.md deleted file mode 100644 index 01ce6382..00000000 --- a/lnbits/extensions/lnurldevice/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# LNURLDevice - -For offline LNURL devices diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py deleted file mode 100644 index 56e9d8f7..00000000 --- a/lnbits/extensions/lnurldevice/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -import asyncio - -from fastapi import APIRouter -from starlette.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_lnurldevice") - -lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice"]) - -lnurldevice_static_files = [ - { - "path": "/lnurldevice/static", - "app": StaticFiles(directory="lnbits/extensions/lnurldevice/static"), - "name": "lnurldevice_static", - } -] - - -def lnurldevice_renderer(): - return template_renderer(["lnbits/extensions/lnurldevice/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 lnurldevice_start(): - loop = asyncio.get_event_loop() - loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/lnurldevice/config.json b/lnbits/extensions/lnurldevice/config.json deleted file mode 100644 index 0712d729..00000000 --- a/lnbits/extensions/lnurldevice/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "LNURLDevice", - "short_description": "For offline LNURL devices", - "tile": "/lnurldevice/static/image/lnurldevice.png", - "contributors": ["arcbtc"] -} diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py deleted file mode 100644 index 8e15d4ec..00000000 --- a/lnbits/extensions/lnurldevice/crud.py +++ /dev/null @@ -1,177 +0,0 @@ -from typing import List, Optional - -import shortuuid - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import createLnurldevice, lnurldevicepayment, lnurldevices - - -async def create_lnurldevice( - data: createLnurldevice, -) -> lnurldevices: - if data.device == "pos" or data.device == "atm": - lnurldevice_id = shortuuid.uuid()[:5] - else: - lnurldevice_id = urlsafe_short_hash() - lnurldevice_key = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO lnurldevice.lnurldevices ( - id, - key, - title, - wallet, - currency, - device, - profit, - amount, - pin, - profit1, - amount1, - pin1, - profit2, - amount2, - pin2, - profit3, - amount3, - pin3, - profit4, - amount4, - pin4 - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - lnurldevice_id, - lnurldevice_key, - data.title, - data.wallet, - data.currency, - data.device, - data.profit, - data.amount, - data.pin, - data.profit1, - data.amount1, - data.pin1, - data.profit2, - data.amount2, - data.pin2, - data.profit3, - data.amount3, - data.pin3, - data.profit4, - data.amount4, - data.pin4, - ), - ) - device = await get_lnurldevice(lnurldevice_id) - assert device - return device - - -async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> lnurldevices: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE lnurldevice.lnurldevices SET {q} WHERE id = ?", - (*kwargs.values(), lnurldevice_id), - ) - row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) - ) - return lnurldevices(**row) - - -async def get_lnurldevice(lnurldevice_id: str) -> Optional[lnurldevices]: - row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) - ) - return lnurldevices(**row) if row else None - - -async def get_lnurldevices(wallet_ids: List[str]) -> List[lnurldevices]: - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f""" - SELECT * FROM lnurldevice.lnurldevices WHERE wallet IN ({q}) - ORDER BY id - """, - (*wallet_ids,), - ) - - return [lnurldevices(**row) for row in rows] - - -async def delete_lnurldevice(lnurldevice_id: str) -> None: - await db.execute( - "DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) - ) - - -async def create_lnurldevicepayment( - deviceid: str, - payload: Optional[str] = None, - pin: Optional[str] = None, - payhash: Optional[str] = None, - sats: Optional[int] = 0, -) -> lnurldevicepayment: - device = await get_lnurldevice(deviceid) - assert device - if device.device == "atm": - lnurldevicepayment_id = shortuuid.uuid(name=payload) - else: - lnurldevicepayment_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO lnurldevice.lnurldevicepayment ( - id, - deviceid, - payload, - pin, - payhash, - sats - ) - VALUES (?, ?, ?, ?, ?, ?) - """, - (lnurldevicepayment_id, deviceid, payload, pin, payhash, sats), - ) - dpayment = await get_lnurldevicepayment(lnurldevicepayment_id) - assert dpayment - return dpayment - - -async def update_lnurldevicepayment( - lnurldevicepayment_id: str, **kwargs -) -> Optional[lnurldevicepayment]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?", - (*kwargs.values(), lnurldevicepayment_id), - ) - row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", - (lnurldevicepayment_id,), - ) - return lnurldevicepayment(**row) if row else None - - -async def get_lnurldevicepayment( - lnurldevicepayment_id: str, -) -> Optional[lnurldevicepayment]: - row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", - (lnurldevicepayment_id,), - ) - return lnurldevicepayment(**row) if row else None - - -async def get_lnurlpayload( - lnurldevicepayment_payload: str, -) -> Optional[lnurldevicepayment]: - row = await db.fetchone( - "SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?", - (lnurldevicepayment_payload,), - ) - return lnurldevicepayment(**row) if row else None diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py deleted file mode 100644 index 20e113d5..00000000 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ /dev/null @@ -1,295 +0,0 @@ -import base64 -import hmac -from http import HTTPStatus -from io import BytesIO - -from embit import bech32, compact -from fastapi import HTTPException, Query, Request - -from lnbits import bolt11 -from lnbits.core.services import create_invoice -from lnbits.core.views.api import pay_invoice -from lnbits.utils.exchange_rates import fiat_amount_as_satoshis - -from . import lnurldevice_ext -from .crud import ( - create_lnurldevicepayment, - get_lnurldevice, - get_lnurldevicepayment, - update_lnurldevicepayment, -) - - -def bech32_decode(bech): - """tweaked version of bech32_decode that ignores length limitations""" - if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or ( - bech.lower() != bech and bech.upper() != bech - ): - return - bech = bech.lower() - device = bech.rfind("1") - if device < 1 or device + 7 > len(bech): - return - if not all(x in bech32.CHARSET for x in bech[device + 1 :]): - return - hrp = bech[:device] - data = [bech32.CHARSET.find(x) for x in bech[device + 1 :]] - encoding = bech32.bech32_verify_checksum(hrp, data) - if encoding is None: - return - bits = bech32.convertbits(data[:-6], 5, 8, False) - assert bits - return bytes(bits) - - -def xor_decrypt(key, blob): - s = BytesIO(blob) - variant = s.read(1)[0] - if variant != 1: - raise RuntimeError("Not implemented") - # reading nonce - l = s.read(1)[0] - nonce = s.read(l) - if len(nonce) != l: - raise RuntimeError("Missing nonce bytes") - if l < 8: - raise RuntimeError("Nonce is too short") - # reading payload - l = s.read(1)[0] - payload = s.read(l) - if len(payload) > 32: - raise RuntimeError("Payload is too long for this encryption method") - if len(payload) != l: - raise RuntimeError("Missing payload bytes") - hmacval = s.read() - expected = hmac.new( - key, b"Data:" + blob[: -len(hmacval)], digestmod="sha256" - ).digest() - if len(hmacval) < 8: - raise RuntimeError("HMAC is too short") - if hmacval != expected[: len(hmacval)]: - raise RuntimeError("HMAC is invalid") - secret = hmac.new(key, b"Round secret:" + nonce, digestmod="sha256").digest() - payload = bytearray(payload) - for i in range(len(payload)): - payload[i] = payload[i] ^ secret[i] - s = BytesIO(payload) - pin = compact.read_from(s) - amount_in_cent = compact.read_from(s) - return pin, amount_in_cent - - -@lnurldevice_ext.get( - "/api/v1/lnurl/{device_id}", - status_code=HTTPStatus.OK, - name="lnurldevice.lnurl_v1_params", -) -async def lnurl_v1_params( - request: Request, - device_id: str = Query(None), - p: str = Query(None), - atm: str = Query(None), - gpio: str = Query(None), - profit: str = Query(None), - amount: str = Query(None), -): - device = await get_lnurldevice(device_id) - if not device: - return { - "status": "ERROR", - "reason": f"lnurldevice {device_id} not found on this server", - } - if device.device == "switch": - # TODO: AMOUNT IN CENT was never reference here - amount_in_cent = 0 - price_msat = ( - await fiat_amount_as_satoshis(float(profit), device.currency) - if device.currency != "sat" - else amount_in_cent - ) * 1000 - - # Check they're not trying to trick the switch! - check = False - for switch in device.switches(request): - if switch[0] == gpio and switch[1] == profit and switch[2] == amount: - check = True - if not check: - return {"status": "ERROR", "reason": "Switch params wrong"} - - lnurldevicepayment = await create_lnurldevicepayment( - deviceid=device.id, - payload=amount, - sats=price_msat, - pin=gpio, - payhash="bla", - ) - if not lnurldevicepayment: - return {"status": "ERROR", "reason": "Could not create payment."} - return { - "tag": "payRequest", - "callback": request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id - ), - "minSendable": price_msat, - "maxSendable": price_msat, - "metadata": device.lnurlpay_metadata, - } - if len(p) % 4 > 0: - p += "=" * (4 - (len(p) % 4)) - - data = base64.urlsafe_b64decode(p) - pin = 0 - amount_in_cent = 0 - try: - result = xor_decrypt(device.key.encode(), data) - pin = result[0] - amount_in_cent = result[1] - except Exception as exc: - return {"status": "ERROR", "reason": str(exc)} - - price_msat = ( - await fiat_amount_as_satoshis(float(amount_in_cent) / 100, device.currency) - if device.currency != "sat" - else amount_in_cent - ) * 1000 - - if atm: - if device.device != "atm": - return {"status": "ERROR", "reason": "Not ATM device."} - price_msat = int(price_msat * (1 - (device.profit / 100)) / 1000) - try: - lnurldevicepayment = await create_lnurldevicepayment( - deviceid=device.id, - payload=p, - sats=price_msat * 1000, - pin=str(pin), - payhash="payment_hash", - ) - except: - return {"status": "ERROR", "reason": "Could not create ATM payment."} - if not lnurldevicepayment: - return {"status": "ERROR", "reason": "Could not create ATM payment."} - return { - "tag": "withdrawRequest", - "callback": request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id - ), - "k1": p, - "minWithdrawable": price_msat * 1000, - "maxWithdrawable": price_msat * 1000, - "defaultDescription": device.title, - } - price_msat = int(price_msat * ((device.profit / 100) + 1) / 1000) - - lnurldevicepayment = await create_lnurldevicepayment( - deviceid=device.id, - payload=p, - sats=price_msat * 1000, - pin=str(pin), - payhash="payment_hash", - ) - if not lnurldevicepayment: - return {"status": "ERROR", "reason": "Could not create payment."} - return { - "tag": "payRequest", - "callback": request.url_for( - "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id - ), - "minSendable": price_msat * 1000, - "maxSendable": price_msat * 1000, - "metadata": device.lnurlpay_metadata, - } - - -@lnurldevice_ext.get( - "/api/v1/lnurl/cb/{paymentid}", - status_code=HTTPStatus.OK, - name="lnurldevice.lnurl_callback", -) -async def lnurl_callback( - request: Request, - paymentid: str = Query(None), - pr: str = Query(None), - k1: str = Query(None), -): - lnurldevicepayment = await get_lnurldevicepayment(paymentid) - if not lnurldevicepayment: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="lnurldevicepayment not found." - ) - device = await get_lnurldevice(lnurldevicepayment.deviceid) - if not device: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found." - ) - if device.device == "atm": - if lnurldevicepayment.payload == lnurldevicepayment.payhash: - return {"status": "ERROR", "reason": "Payment already claimed"} - if not pr: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="No payment request" - ) - invoice = bolt11.decode(pr) - if not invoice.payment_hash: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not valid payment request" - ) - else: - if lnurldevicepayment.payload != k1: - return {"status": "ERROR", "reason": "Bad K1"} - if lnurldevicepayment.payhash != "payment_hash": - return {"status": "ERROR", "reason": "Payment already claimed"} - - lnurldevicepayment_updated = await update_lnurldevicepayment( - lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload - ) - assert lnurldevicepayment_updated - await pay_invoice( - wallet_id=device.wallet, - payment_request=pr, - max_sat=int(lnurldevicepayment_updated.sats / 1000), - extra={"tag": "withdraw"}, - ) - return {"status": "OK"} - if device.device == "switch": - payment_hash, payment_request = await create_invoice( - wallet_id=device.wallet, - amount=int(lnurldevicepayment.sats / 1000), - memo=device.id + " PIN " + str(lnurldevicepayment.pin), - unhashed_description=device.lnurlpay_metadata.encode(), - extra={ - "tag": "Switch", - "pin": str(lnurldevicepayment.pin), - "amount": str(lnurldevicepayment.payload), - "id": paymentid, - }, - ) - - lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=paymentid, payhash=payment_hash - ) - return { - "pr": payment_request, - "routes": [], - } - - payment_hash, payment_request = await create_invoice( - wallet_id=device.wallet, - amount=int(lnurldevicepayment.sats / 1000), - memo=device.title, - unhashed_description=device.lnurlpay_metadata.encode(), - extra={"tag": "PoS"}, - ) - lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=paymentid, payhash=payment_hash - ) - - return { - "pr": payment_request, - "successAction": { - "tag": "url", - "description": "Check the attached link", - "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid), - }, - "routes": [], - } diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py deleted file mode 100644 index 7fd7d6c4..00000000 --- a/lnbits/extensions/lnurldevice/migrations.py +++ /dev/null @@ -1,139 +0,0 @@ -from lnbits.db import Database - -db2 = Database("ext_lnurlpos") - - -async def m001_initial(db): - """ - Initial lnurldevice table. - """ - await db.execute( - f""" - CREATE TABLE lnurldevice.lnurldevices ( - id TEXT NOT NULL PRIMARY KEY, - key TEXT NOT NULL, - title TEXT NOT NULL, - wallet TEXT NOT NULL, - currency TEXT NOT NULL, - device TEXT NOT NULL, - profit FLOAT NOT NULL, - timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} - ); - """ - ) - await db.execute( - f""" - CREATE TABLE lnurldevice.lnurldevicepayment ( - id TEXT NOT NULL PRIMARY KEY, - deviceid TEXT NOT NULL, - payhash TEXT, - payload TEXT NOT NULL, - pin INT, - sats {db.big_int}, - timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} - ); - """ - ) - - -async def m002_redux(db): - """ - Moves everything from lnurlpos to lnurldevice - """ - try: - for row in [ - list(row) for row in await db2.fetchall("SELECT * FROM lnurlpos.lnurlposs") - ]: - await db.execute( - """ - INSERT INTO lnurldevice.lnurldevices ( - id, - key, - title, - wallet, - currency, - device, - profit - ) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - (row[0], row[1], row[2], row[3], row[4], "pos", 0), - ) - for row in [ - list(row) - for row in await db2.fetchall("SELECT * FROM lnurlpos.lnurlpospayment") - ]: - await db.execute( - """ - INSERT INTO lnurldevice.lnurldevicepayment ( - id, - deviceid, - payhash, - payload, - pin, - sats - ) - VALUES (?, ?, ?, ?, ?, ?) - """, - (row[0], row[1], row[3], row[4], row[5], row[6]), - ) - except: - return - - -async def m003_redux(db): - """ - Add 'meta' for storing various metadata about the wallet - """ - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;" - ) - - -async def m004_redux(db): - """ - Add 'meta' for storing various metadata about the wallet - """ - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0" - ) - - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0" - ) - - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0" - ) - - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0" - ) - - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0" - ) - await db.execute( - "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0" - ) diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py deleted file mode 100644 index f9640de1..00000000 --- a/lnbits/extensions/lnurldevice/models.py +++ /dev/null @@ -1,171 +0,0 @@ -import json -from sqlite3 import Row -from typing import List, Optional - -from fastapi import Request -from lnurl import encode as lnurl_encode -from lnurl.types import LnurlPayMetadata -from pydantic import BaseModel - - -class createLnurldevice(BaseModel): - title: str - wallet: str - currency: str - device: str - profit: float = 0 - amount: Optional[int] = 0 - pin: int = 0 - profit1: float = 0 - amount1: int = 0 - pin1: int = 0 - profit2: float = 0 - amount2: int = 0 - pin2: int = 0 - profit3: float = 0 - amount3: int = 0 - pin3: int = 0 - profit4: float = 0 - amount4: int = 0 - pin4: int = 0 - - -class lnurldevices(BaseModel): - id: str - key: str - title: str - wallet: str - currency: str - device: str - profit: float - amount: int - pin: int - profit1: float - amount1: int - pin1: int - profit2: float - amount2: int - pin2: int - profit3: float - amount3: int - pin3: int - profit4: float - amount4: int - pin4: int - timestamp: str - - @classmethod - def from_row(cls, row: Row) -> "lnurldevices": - return cls(**dict(row)) - - @property - def lnurlpay_metadata(self) -> LnurlPayMetadata: - return LnurlPayMetadata(json.dumps([["text/plain", self.title]])) - - def switches(self, req: Request) -> List: - switches = [] - if self.profit > 0: - url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append( - [ - str(self.pin), - str(self.profit), - str(self.amount), - lnurl_encode( - url - + "?gpio=" - + str(self.pin) - + "&profit=" - + str(self.profit) - + "&amount=" - + str(self.amount) - ), - ] - ) - if self.profit1 > 0: - url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append( - [ - str(self.pin1), - str(self.profit1), - str(self.amount1), - lnurl_encode( - url - + "?gpio=" - + str(self.pin1) - + "&profit=" - + str(self.profit1) - + "&amount=" - + str(self.amount1) - ), - ] - ) - if self.profit2 > 0: - url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append( - [ - str(self.pin2), - str(self.profit2), - str(self.amount2), - lnurl_encode( - url - + "?gpio=" - + str(self.pin2) - + "&profit=" - + str(self.profit2) - + "&amount=" - + str(self.amount2) - ), - ] - ) - if self.profit3 > 0: - url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append( - [ - str(self.pin3), - str(self.profit3), - str(self.amount3), - lnurl_encode( - url - + "?gpio=" - + str(self.pin3) - + "&profit=" - + str(self.profit3) - + "&amount=" - + str(self.amount3) - ), - ] - ) - if self.profit4 > 0: - url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id) - switches.append( - [ - str(self.pin4), - str(self.profit4), - str(self.amount4), - lnurl_encode( - url - + "?gpio=" - + str(self.pin4) - + "&profit=" - + str(self.profit4) - + "&amount=" - + str(self.amount4) - ), - ] - ) - return switches - - -class lnurldevicepayment(BaseModel): - id: str - deviceid: str - payhash: str - payload: str - pin: int - sats: int - timestamp: str - - @classmethod - def from_row(cls, row: Row) -> "lnurldevicepayment": - return cls(**dict(row)) diff --git a/lnbits/extensions/lnurldevice/static/image/lnurldevice.png b/lnbits/extensions/lnurldevice/static/image/lnurldevice.png deleted file mode 100644 index 3a5304f62bdb9db77cad3d7742ebad791200d151..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13746 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 diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py deleted file mode 100644 index 9aec173e..00000000 --- a/lnbits/extensions/lnurldevice/tasks.py +++ /dev/null @@ -1,36 +0,0 @@ -import asyncio - -from lnbits.core.models import Payment -from lnbits.core.services import websocketUpdater -from lnbits.helpers import get_current_extension_name -from lnbits.tasks import register_invoice_listener - -from .crud import get_lnurldevicepayment, update_lnurldevicepayment - - -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) -> None: - # (avoid loops) - if "Switch" == payment.extra.get("tag"): - lnurldevicepayment = await get_lnurldevicepayment(payment.extra["id"]) - if not lnurldevicepayment: - return - if lnurldevicepayment.payhash == "used": - return - lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=payment.extra["id"], payhash="used" - ) - assert lnurldevicepayment - return await websocketUpdater( - lnurldevicepayment.deviceid, - str(lnurldevicepayment.pin) + "-" + str(lnurldevicepayment.payload), - ) - return diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html deleted file mode 100644 index 712f0e59..00000000 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html +++ /dev/null @@ -1,190 +0,0 @@ - - -

- For LNURL based Points of Sale, ATMs, and relay devices
- Use with:
- LNPoS - - https://lnbits.github.io/lnpos
- bitcoinSwitch - - https://github.com/lnbits/bitcoinSwitch
- FOSSA - - https://github.com/lnbits/fossa
- - Created by, - Ben Arc, - BC, - Vlad Stan -

-
- - - - - - /lnurldevice/api/v1/lnurlpos -
Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X POST {{ request.base_url }}lnurldevice/api/v1/lnurlpos -d - '{"title": <string>, "message":<string>, "currency": - <integer>}' -H "Content-type: application/json" -H "X-Api-Key: - {{user.wallets[0].adminkey }}" - -
-
-
- - - - PUT - /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X POST {{ request.base_url - }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -d ''{"title": - <string>, "message":<string>, "currency": - <integer>} -H "Content-type: application/json" -H "X-Api-Key: - {{user.wallets[0].adminkey }}" - -
-
-
- - - - - GET - /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X GET {{ request.base_url - }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: - {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /lnurldevice/api/v1/lnurlpos -
Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json) -
-
- Returns 200 OK (application/json) -
- [<lnurldevice_object>, ...] -
Curl example
- curl -X GET {{ request.base_url }}lnurldevice/api/v1/lnurlpos -H - "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - DELETE - /lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}lnurldevice/api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: - {{ user.wallets[0].adminkey }}" - -
-
-
-
-
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/error.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/error.html deleted file mode 100644 index d8e41832..00000000 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/error.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
-

LNURL-pay not paid

-
- - -
-
-
-
-
- - {% endblock %} {% block scripts %} - - - - {% endblock %} -
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html deleted file mode 100644 index f308cc65..00000000 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ /dev/null @@ -1,881 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - {% raw %} - New LNURLDevice instance - - - - - - -
-
-
lNURLdevice
-
- -
- - - - Export to CSV -
-
- - - - - {% endraw %} - -
-
-
- -
- - -
- {{SITE_TITLE}} LNURLDevice Extension -
-
- - - {% include "lnurldevice/_api_docs.html" %} - -
-
- - - -
LNURLDevice device string
-
- {% raw %}{{wslocation}}/api/v1/ws/{{settingsDialog.data.id}}{% endraw - %} Click to copy URL - - {% raw - %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}}, - {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw - %} Click to copy URL - -
-
- -
-
-
- - - - - - - - - - - -
- - - -
-
-
- -
-
- -
-
- -
-
-
-
-
-
- -
-
- -
-
- -
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- -
-
-
- -
-
- -
-
- -
-
-
-
-
- Update lnurldevice - Create lnurldevice - Cancel -
-
-
-
- - - - - - - Copy LNURL - {% raw %}{{ wsMessage }}{% endraw %} - {% raw %}{{ wsMessage }}{% endraw %} -
-
- - Close -
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - - -{% endblock %} diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/paid.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/paid.html deleted file mode 100644 index c185ecce..00000000 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/paid.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -
-

{{ pin }}

-
-
-
-
-
- - {% endblock %} {% block scripts %} - - - - {% endblock %} -
diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py deleted file mode 100644 index a6256a41..00000000 --- a/lnbits/extensions/lnurldevice/views.py +++ /dev/null @@ -1,62 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, HTTPException, Query, Request -from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse, StreamingResponse - -from lnbits.core.crud import update_payment_status -from lnbits.core.models import User -from lnbits.core.views.api import api_payment -from lnbits.decorators import check_user_exists - -from . import lnurldevice_ext, lnurldevice_renderer -from .crud import get_lnurldevice, get_lnurldevicepayment - -templates = Jinja2Templates(directory="templates") - - -@lnurldevice_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return lnurldevice_renderer().TemplateResponse( - "lnurldevice/index.html", {"request": request, "user": user.dict()} - ) - - -@lnurldevice_ext.get( - "/{paymentid}", name="lnurldevice.displaypin", response_class=HTMLResponse -) -async def displaypin(request: Request, paymentid: str = Query(None)): - lnurldevicepayment = await get_lnurldevicepayment(paymentid) - if not lnurldevicepayment: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="No lmurldevice payment" - ) - device = await get_lnurldevice(lnurldevicepayment.deviceid) - if not device: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="lnurldevice not found." - ) - status = await api_payment(lnurldevicepayment.payhash) - if status["paid"]: - await update_payment_status( - checking_id=lnurldevicepayment.payhash, pending=True - ) - return lnurldevice_renderer().TemplateResponse( - "lnurldevice/paid.html", {"request": request, "pin": lnurldevicepayment.pin} - ) - return lnurldevice_renderer().TemplateResponse( - "lnurldevice/error.html", - {"request": request, "pin": "filler", "not_paid": True}, - ) - - -@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse) -async def img(request: Request, lnurldevice_id): - lnurldevice = await get_lnurldevice(lnurldevice_id) - if not lnurldevice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." - ) - # error: "lnurldevices" has no attribute "lnurl" - # return lnurldevice.lnurl(request) - return None diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py deleted file mode 100644 index 2fd1bd12..00000000 --- a/lnbits/extensions/lnurldevice/views_api.py +++ /dev/null @@ -1,88 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, HTTPException, Query, Request - -from lnbits.core.crud import get_user -from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.utils.exchange_rates import currencies - -from . import lnurldevice_ext -from .crud import ( - create_lnurldevice, - delete_lnurldevice, - get_lnurldevice, - get_lnurldevices, - update_lnurldevice, -) -from .models import createLnurldevice - - -@lnurldevice_ext.get("/api/v1/currencies") -async def api_list_currencies_available(): - return list(currencies.keys()) - - -@lnurldevice_ext.post("/api/v1/lnurlpos") -@lnurldevice_ext.put("/api/v1/lnurlpos/{lnurldevice_id}") -async def api_lnurldevice_create_or_update( - req: Request, - data: createLnurldevice, - wallet: WalletTypeInfo = Depends(require_admin_key), - lnurldevice_id: str = Query(None), -): - if not lnurldevice_id: - lnurldevice = await create_lnurldevice(data) - return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} - else: - lnurldevice = await update_lnurldevice(lnurldevice_id, **data.dict()) - return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} - - -@lnurldevice_ext.get("/api/v1/lnurlpos") -async def api_lnurldevices_retrieve( - req: Request, wallet: WalletTypeInfo = Depends(get_key_type) -): - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - try: - return [ - {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} - for lnurldevice in await get_lnurldevices(wallet_ids) - ] - except: - try: - return [ - {**lnurldevice.dict()} - for lnurldevice in await get_lnurldevices(wallet_ids) - ] - except: - return "" - - -@lnurldevice_ext.get( - "/api/v1/lnurlpos/{lnurldevice_id}", dependencies=[Depends(get_key_type)] -) -async def api_lnurldevice_retrieve( - req: Request, - lnurldevice_id: str = Query(None), -): - lnurldevice = await get_lnurldevice(lnurldevice_id) - if not lnurldevice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="lnurldevice does not exist" - ) - return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} - - -@lnurldevice_ext.delete( - "/api/v1/lnurlpos/{lnurldevice_id}", dependencies=[Depends(require_admin_key)] -) -async def api_lnurldevice_delete(lnurldevice_id: str = Query(None)): - lnurldevice = await get_lnurldevice(lnurldevice_id) - if not lnurldevice: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist." - ) - - await delete_lnurldevice(lnurldevice_id) - return "", HTTPStatus.NO_CONTENT