diff --git a/lnbits/extensions/scrub/README.md b/lnbits/extensions/scrub/README.md deleted file mode 100644 index 3b8d0b2d7..000000000 --- a/lnbits/extensions/scrub/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Scrub - -## Automatically forward funds (Scrub) that get paid to the wallet to an LNURLpay or Lightning Address - -SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress! - -Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet! - -[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets) - -## Usage - -1. Create an scrub (New Scrub link)\ - ![create scrub](https://i.imgur.com/LUeNkzM.jpg) - - - select the wallet to be _scrubbed_ - - make a small description - - enter either an LNURL pay or a lightning address - - Make sure your LNURL or LNaddress is correct! - -2. A new scrub will show on the _Scrub links_ section\ - ![scrub](https://i.imgur.com/LNoFkeu.jpg) - - - only one scrub can be created for each wallet! - - You can _edit_ or _delete_ the Scrub at any time\ - ![edit scrub](https://i.imgur.com/Qu65lGG.jpg) - -3. On your wallet, you'll see a transaction of a payment received and another right after it as apayment sent, marked with **#scrubed**\ - ![wallet view](https://i.imgur.com/S6EWWCP.jpg) diff --git a/lnbits/extensions/scrub/__init__.py b/lnbits/extensions/scrub/__init__.py deleted file mode 100644 index 29428af96..000000000 --- a/lnbits/extensions/scrub/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -import asyncio - -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_scrub") - -scrub_static_files = [ - { - "path": "/scrub/static", - "app": StaticFiles(directory="lnbits/extensions/scrub/static"), - "name": "scrub_static", - } -] - -scrub_ext: APIRouter = APIRouter(prefix="/scrub", tags=["scrub"]) - - -def scrub_renderer(): - return template_renderer(["lnbits/extensions/scrub/templates"]) - - -from .tasks import wait_for_paid_invoices -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 - - -def scrub_start(): - loop = asyncio.get_event_loop() - loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/scrub/config.json b/lnbits/extensions/scrub/config.json deleted file mode 100644 index 93eb871ae..000000000 --- a/lnbits/extensions/scrub/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Scrub", - "short_description": "Pass payments to LNURLp/LNaddress", - "tile": "/scrub/static/image/scrub.png", - "contributors": ["arcbtc", "talvasconcelos"] -} diff --git a/lnbits/extensions/scrub/crud.py b/lnbits/extensions/scrub/crud.py deleted file mode 100644 index 1772a8c5b..000000000 --- a/lnbits/extensions/scrub/crud.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import CreateScrubLink, ScrubLink - - -async def create_scrub_link(data: CreateScrubLink) -> ScrubLink: - scrub_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO scrub.scrub_links ( - id, - wallet, - description, - payoraddress - ) - VALUES (?, ?, ?, ?) - """, - ( - scrub_id, - data.wallet, - data.description, - data.payoraddress, - ), - ) - link = await get_scrub_link(scrub_id) - assert link, "Newly created link couldn't be retrieved" - return link - - -async def get_scrub_link(link_id: str) -> Optional[ScrubLink]: - row = await db.fetchone("SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,)) - return ScrubLink(**row) if row else None - - -async def get_scrub_links(wallet_ids: Union[str, List[str]]) -> List[ScrubLink]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f""" - SELECT * FROM scrub.scrub_links WHERE wallet IN ({q}) - ORDER BY id - """, - (*wallet_ids,), - ) - return [ScrubLink(**row) for row in rows] - - -async def update_scrub_link(link_id: int, **kwargs) -> Optional[ScrubLink]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE scrub.scrub_links SET {q} WHERE id = ?", - (*kwargs.values(), link_id), - ) - row = await db.fetchone("SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,)) - return ScrubLink(**row) if row else None - - -async def delete_scrub_link(link_id: int) -> None: - await db.execute("DELETE FROM scrub.scrub_links WHERE id = ?", (link_id,)) - - -async def get_scrub_by_wallet(wallet_id) -> Optional[ScrubLink]: - row = await db.fetchone( - "SELECT * from scrub.scrub_links WHERE wallet = ?", - (wallet_id,), - ) - return ScrubLink(**row) if row else None - - -async def unique_scrubed_wallet(wallet_id): - (row,) = await db.fetchone( - "SELECT COUNT(wallet) FROM scrub.scrub_links WHERE wallet = ?", - (wallet_id,), - ) - return row diff --git a/lnbits/extensions/scrub/migrations.py b/lnbits/extensions/scrub/migrations.py deleted file mode 100644 index f1f4badef..000000000 --- a/lnbits/extensions/scrub/migrations.py +++ /dev/null @@ -1,14 +0,0 @@ -async def m001_initial(db): - """ - Initial scrub table. - """ - await db.execute( - """ - CREATE TABLE scrub.scrub_links ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - description TEXT NOT NULL, - payoraddress TEXT NOT NULL - ); - """ - ) diff --git a/lnbits/extensions/scrub/models.py b/lnbits/extensions/scrub/models.py deleted file mode 100644 index 8079f358a..000000000 --- a/lnbits/extensions/scrub/models.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlite3 import Row - -from pydantic import BaseModel -from starlette.requests import Request - -from lnbits.lnurl import encode as lnurl_encode - - -class CreateScrubLink(BaseModel): - wallet: str - description: str - payoraddress: str - - -class ScrubLink(BaseModel): - id: str - wallet: str - description: str - payoraddress: str - - @classmethod - def from_row(cls, row: Row) -> "ScrubLink": - data = dict(row) - return cls(**data) - - def lnurl(self, req: Request) -> str: - url = req.url_for("scrub.api_lnurl_response", link_id=self.id) - return lnurl_encode(url) diff --git a/lnbits/extensions/scrub/static/image/scrub.png b/lnbits/extensions/scrub/static/image/scrub.png deleted file mode 100644 index b3d4d24f2..000000000 Binary files a/lnbits/extensions/scrub/static/image/scrub.png and /dev/null differ diff --git a/lnbits/extensions/scrub/static/js/index.js b/lnbits/extensions/scrub/static/js/index.js deleted file mode 100644 index 439907921..000000000 --- a/lnbits/extensions/scrub/static/js/index.js +++ /dev/null @@ -1,143 +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 mapScrubLink = 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, obj.id].join('') - return obj -} - -new Vue({ - el: '#vue', - mixins: [windowMixin], - data() { - return { - checker: null, - payLinks: [], - payLinksTable: { - pagination: { - rowsPerPage: 10 - } - }, - formDialog: { - show: false, - data: {} - }, - qrCodeDialog: { - show: false, - data: null - } - } - }, - methods: { - getScrubLinks() { - LNbits.api - .request( - 'GET', - '/scrub/api/v1/links?all_wallets=true', - this.g.user.wallets[0].inkey - ) - .then(response => { - this.payLinks = response.data.map(mapScrubLink) - }) - .catch(err => { - clearInterval(this.checker) - LNbits.utils.notifyApiError(err) - }) - }, - closeFormDialog() { - this.resetFormData() - }, - openUpdateDialog(linkId) { - const link = _.findWhere(this.payLinks, {id: linkId}) - - this.formDialog.data = _.clone(link._data) - this.formDialog.show = true - }, - sendFormData() { - const wallet = _.findWhere(this.g.user.wallets, { - id: this.formDialog.data.wallet - }) - let data = Object.freeze(this.formDialog.data) - console.log(wallet, data) - - if (data.id) { - this.updateScrubLink(wallet, data) - } else { - this.createScrubLink(wallet, data) - } - }, - resetFormData() { - this.formDialog = { - show: false, - data: {} - } - }, - updateScrubLink(wallet, data) { - LNbits.api - .request('PUT', '/scrub/api/v1/links/' + data.id, wallet.adminkey, data) - .then(response => { - this.payLinks = _.reject(this.payLinks, obj => obj.id === data.id) - this.payLinks.push(mapScrubLink(response.data)) - this.formDialog.show = false - this.resetFormData() - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - createScrubLink(wallet, data) { - LNbits.api - .request('POST', '/scrub/api/v1/links', wallet.adminkey, data) - .then(response => { - console.log('RES', response) - this.getScrubLinks() - this.formDialog.show = false - this.resetFormData() - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - deleteScrubLink(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', - '/scrub/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) - }) - }) - } - }, - created() { - if (this.g.user.wallets.length) { - var getScrubLinks = this.getScrubLinks - getScrubLinks() - } - } -}) diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py deleted file mode 100644 index 26249bb1b..000000000 --- a/lnbits/extensions/scrub/tasks.py +++ /dev/null @@ -1,89 +0,0 @@ -import asyncio -import json -from http import HTTPStatus -from math import floor -from urllib.parse import urlparse - -import httpx -from fastapi import HTTPException - -from lnbits import bolt11 -from lnbits.core.models import Payment -from lnbits.core.services import pay_invoice -from lnbits.helpers import get_current_extension_name -from lnbits.tasks import register_invoice_listener - -from .crud import get_scrub_by_wallet - - -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): - # (avoid loops) - if payment.extra.get("tag") == "scrubed": - # already scrubbed - return - - scrub_link = await get_scrub_by_wallet(payment.wallet_id) - - if not scrub_link: - return - - from lnbits.core.views.api import api_lnurlscan - - # DECODE LNURLP OR LNADDRESS - data = await api_lnurlscan(scrub_link.payoraddress) - - # I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267 - domain = urlparse(data["callback"]).netloc - rounded_amount = floor(payment.amount / 1000) * 1000 - - async with httpx.AsyncClient() as client: - try: - r = await client.get( - data["callback"], - params={"amount": rounded_amount}, - timeout=40, - ) - if r.is_error: - raise httpx.ConnectError("issue with scrub callback") - except (httpx.ConnectError, httpx.RequestError): - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, - detail=f"Failed to connect to {domain}.", - ) - - params = json.loads(r.text) - if params.get("status") == "ERROR": - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, - detail=f"{domain} said: '{params.get('reason', '')}'", - ) - - invoice = bolt11.decode(params["pr"]) - - if invoice.amount_msat != rounded_amount: - raise HTTPException( - status_code=HTTPStatus.BAD_REQUEST, - detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.", - ) - - payment_hash = await pay_invoice( - wallet_id=payment.wallet_id, - payment_request=params["pr"], - description=data["description"], - extra={"tag": "scrubed"}, - ) - - return { - "payment_hash": payment_hash, - # maintain backwards compatibility with API clients: - "checking_id": payment_hash, - } diff --git a/lnbits/extensions/scrub/templates/scrub/_api_docs.html b/lnbits/extensions/scrub/templates/scrub/_api_docs.html deleted file mode 100644 index ae3f44d88..000000000 --- a/lnbits/extensions/scrub/templates/scrub/_api_docs.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - GET /scrub/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 }}scrub/api/v1/links?all_wallets=true - -H "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - GET - /scrub/api/v1/links/<scrub_id> -
Headers
- {"X-Api-Key": <invoice_key>}
-
Body (application/json)
-
- Returns 200 OK (application/json) -
- {"id": <string>, "wallet": <string>, "description": - <string>, "payoraddress": <string>} -
Curl example
- curl -X GET {{ request.base_url }}scrub/api/v1/links/<pay_id> - -H "X-Api-Key: {{ user.wallets[0].inkey }}" - -
-
-
- - - - POST /scrub/api/v1/links -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"wallet": <string>, "description": <string>, - "payoraddress": <string>} -
- Returns 201 CREATED (application/json) -
- {"id": <string>, "wallet": <string>, "description": - <string>, "payoraddress": <string>} -
Curl example
- curl -X POST {{ request.base_url }}scrub/api/v1/links -d '{"wallet": - <string>, "description": <string>, "payoraddress": - <string>}' -H "Content-type: application/json" -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
- - - - PUT - /scrub/api/v1/links/<pay_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Body (application/json)
- {"wallet": <string>, "description": <string>, - "payoraddress": <string>} -
- Returns 200 OK (application/json) -
- {"id": <string>, "wallet": <string>, "description": - <string>, "payoraddress": <string>} -
Curl example
- curl -X PUT {{ request.base_url }}scrub/api/v1/links/<pay_id> - -d '{"wallet": <string>, "description": <string>, - "payoraddress": <string>}' -H "Content-type: application/json" - -H "X-Api-Key: {{ user.wallets[0].adminkey }}" - -
-
-
- - - - DELETE - /scrub/api/v1/links/<pay_id> -
Headers
- {"X-Api-Key": <admin_key>}
-
Returns 204 NO CONTENT
- -
Curl example
- curl -X DELETE {{ request.base_url - }}scrub/api/v1/links/<pay_id> -H "X-Api-Key: {{ - user.wallets[0].adminkey }}" - -
-
-
-
diff --git a/lnbits/extensions/scrub/templates/scrub/_lnurl.html b/lnbits/extensions/scrub/templates/scrub/_lnurl.html deleted file mode 100644 index f2ba86611..000000000 --- a/lnbits/extensions/scrub/templates/scrub/_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/scrub/templates/scrub/index.html b/lnbits/extensions/scrub/templates/scrub/index.html deleted file mode 100644 index a3756df35..000000000 --- a/lnbits/extensions/scrub/templates/scrub/index.html +++ /dev/null @@ -1,156 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - New scrub link - - - - - -
-
-
Scrub links
-
-
- - {% raw %} - - - {% endraw %} - -
-
-
- -
- - -
{{SITE_TITLE}} Scrub extension
-

- Automatically forward funds (Scrub) that get paid to the LNbits - wallet, to an LNURLpay or Lightning Address. -
- More info in Scrub's - readme. -

-

- Important: wallet will need a float to account for - any fees, before being able to push a payment -

-
- - - - {% include "scrub/_api_docs.html" %} - - {% include "scrub/_lnurl.html" %} - - -
-
- - - - - - - - - -
- Update pay link - Create pay link - Cancel -
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/scrub/views.py b/lnbits/extensions/scrub/views.py deleted file mode 100644 index 489580133..000000000 --- a/lnbits/extensions/scrub/views.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi import Depends, Request -from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import scrub_ext, scrub_renderer - -templates = Jinja2Templates(directory="templates") - - -@scrub_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return scrub_renderer().TemplateResponse( - "scrub/index.html", {"request": request, "user": user.dict()} - ) diff --git a/lnbits/extensions/scrub/views_api.py b/lnbits/extensions/scrub/views_api.py deleted file mode 100644 index eae0098d9..000000000 --- a/lnbits/extensions/scrub/views_api.py +++ /dev/null @@ -1,107 +0,0 @@ -from http import HTTPStatus - -from fastapi import Depends, Query -from starlette.exceptions import HTTPException - -from lnbits.core.crud import get_user -from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key - -from . import scrub_ext -from .crud import ( - create_scrub_link, - delete_scrub_link, - get_scrub_link, - get_scrub_links, - unique_scrubed_wallet, - update_scrub_link, -) -from .models import CreateScrubLink - - -@scrub_ext.get("/api/v1/links", status_code=HTTPStatus.OK) -async def api_links( - 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() for link in await get_scrub_links(wallet_ids)] - - except: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, - detail="No SCRUB links made yet", - ) - - -@scrub_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_type)): - link = await get_scrub_link(link_id) - - if not link: - raise HTTPException( - detail="Scrub 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 - - -@scrub_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED) -@scrub_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) -async def api_scrub_create_or_update( - data: CreateScrubLink, - link_id=None, - wallet: WalletTypeInfo = Depends(require_admin_key), -): - if link_id: - link = await get_scrub_link(link_id) - - if not link: - raise HTTPException( - detail="Scrub 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_scrub_link(**data.dict(), link_id=link_id) - else: - wallet_has_scrub = await unique_scrubed_wallet(wallet_id=data.wallet) - if wallet_has_scrub > 0: - raise HTTPException( - detail="Wallet is already being Scrubbed", - status_code=HTTPStatus.FORBIDDEN, - ) - link = await create_scrub_link(data=data) - - return link - - -@scrub_ext.delete("/api/v1/links/{link_id}") -async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(require_admin_key)): - link = await get_scrub_link(link_id) - - if not link: - raise HTTPException( - detail="Scrub 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_scrub_link(link_id) - return "", HTTPStatus.NO_CONTENT