diff --git a/lnbits/db.py b/lnbits/db.py index f52b03914..321b23d0a 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -1,6 +1,7 @@ import asyncio import datetime import os +import re import time from contextlib import asynccontextmanager from typing import Optional @@ -73,18 +74,39 @@ class Connection(Compat): query = query.replace("?", "%s") return query + def rewrite_values(self, values): + # strip html + CLEANR = re.compile("<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});") + + def cleanhtml(raw_html): + if isinstance(raw_html, str): + cleantext = re.sub(CLEANR, "", raw_html) + return cleantext + else: + return raw_html + + # tuple to list and back to tuple + values = tuple([cleanhtml(l) for l in list(values)]) + return values + async def fetchall(self, query: str, values: tuple = ()) -> list: - result = await self.conn.execute(self.rewrite_query(query), values) + result = await self.conn.execute( + self.rewrite_query(query), self.rewrite_values(values) + ) return await result.fetchall() async def fetchone(self, query: str, values: tuple = ()): - result = await self.conn.execute(self.rewrite_query(query), values) + result = await self.conn.execute( + self.rewrite_query(query), self.rewrite_values(values) + ) row = await result.fetchone() await result.close() return row async def execute(self, query: str, values: tuple = ()): - return await self.conn.execute(self.rewrite_query(query), values) + return await self.conn.execute( + self.rewrite_query(query), self.rewrite_values(values) + ) class Database(Compat): diff --git a/lnbits/extensions/satspay/config.json b/lnbits/extensions/satspay/config.json index beb0071cb..fe9e3df49 100644 --- a/lnbits/extensions/satspay/config.json +++ b/lnbits/extensions/satspay/config.json @@ -2,7 +2,5 @@ "name": "SatsPay Server", "short_description": "Create onchain and LN charges", "icon": "payment", - "contributors": [ - "arcbtc" - ] + "contributors": ["arcbtc"] } diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 968c9ab01..7e34f6f80 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -10,7 +10,7 @@ from lnbits.helpers import urlsafe_short_hash from ..watchonly.crud import get_config, get_fresh_address from . import db from .helpers import fetch_onchain_balance -from .models import Charges, CreateCharge +from .models import Charges, CreateCharge, SatsPayThemes ###############CHARGES########################## @@ -53,9 +53,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: time, amount, balance, - extra + extra, + custom_css ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( charge_id, @@ -73,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges: data.amount, 0, data.extra, + data.custom_css, ), ) return await get_charge(charge_id) @@ -121,3 +123,101 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]: if invoice_status["paid"]: return await update_charge(charge_id=charge_id, balance=charge.amount) return await get_charge(charge_id) + + +################## SETTINGS ################### + + +async def save_theme(data: SatsPayThemes, css_id: str = None): + # insert or update + if css_id: + await db.execute( + """ + UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ? + """, + (data.custom_css, data.title, css_id), + ) + else: + css_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO satspay.themes ( + css_id, + title, + user, + custom_css + ) + VALUES (?, ?, ?, ?) + """, + ( + css_id, + data.title, + data.user, + data.custom_css, + ), + ) + return await get_theme(css_id) + + +async def get_theme(css_id: str) -> SatsPayThemes: + row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,)) + return SatsPayThemes.from_row(row) if row else None + + +async def get_themes(user_id: str) -> List[SatsPayThemes]: + rows = await db.fetchall( + """SELECT * FROM satspay.themes WHERE "user" = ? ORDER BY "timestamp" DESC """, + (user_id,), + ) + return await get_config(row.user) + + +################## SETTINGS ################### + + +async def save_theme(data: SatsPayThemes, css_id: str = None): + # insert or update + if css_id: + await db.execute( + """ + UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ? + """, + (data.custom_css, data.title, css_id), + ) + else: + css_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO satspay.themes ( + css_id, + title, + "user", + custom_css + ) + VALUES (?, ?, ?, ?) + """, + ( + css_id, + data.title, + data.user, + data.custom_css, + ), + ) + return await get_theme(css_id) + + +async def get_theme(css_id: str) -> SatsPayThemes: + row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,)) + return SatsPayThemes.from_row(row) if row else None + + +async def get_themes(user_id: str) -> List[SatsPayThemes]: + rows = await db.fetchall( + """SELECT * FROM satspay.themes WHERE "user" = ? ORDER BY "title" DESC """, + (user_id,), + ) + return [SatsPayThemes.from_row(row) for row in rows] + + +async def delete_theme(theme_id: str) -> None: + await db.execute("DELETE FROM satspay.themes WHERE css_id = ?", (theme_id,)) diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py index 2aa83e1f5..60c5ba4ab 100644 --- a/lnbits/extensions/satspay/helpers.py +++ b/lnbits/extensions/satspay/helpers.py @@ -19,10 +19,12 @@ def public_charge(charge: Charges): "time_elapsed": charge.time_elapsed, "time_left": charge.time_left, "paid": charge.paid, + "custom_css": charge.custom_css, } if charge.paid: c["completelink"] = charge.completelink + c["completelinktext"] = charge.completelinktext return c diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index 2579961f5..ff6b44be8 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -37,3 +37,28 @@ async def m002_add_charge_extra_data(db): ADD COLUMN extra TEXT DEFAULT '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}'; """ ) + + +async def m003_add_themes_table(db): + """ + Themes table + """ + + await db.execute( + """ + CREATE TABLE satspay.themes ( + css_id TEXT NOT NULL PRIMARY KEY, + "user" TEXT, + title TEXT, + custom_css TEXT + ); + """ + ) + + +async def m004_add_custom_css_to_charges(db): + """ + Add custom css option column to the 'charges' table + """ + + await db.execute("ALTER TABLE satspay.charges ADD COLUMN custom_css TEXT;") diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index 1e7c95c99..cfb3c7aca 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -14,6 +14,7 @@ class CreateCharge(BaseModel): webhook: str = Query(None) completelink: str = Query(None) completelinktext: str = Query(None) + custom_css: Optional[str] time: int = Query(..., ge=1) amount: int = Query(..., ge=1) extra: str = "{}" @@ -38,6 +39,7 @@ class Charges(BaseModel): completelink: Optional[str] completelinktext: Optional[str] = "Back to Merchant" extra: str = "{}" + custom_css: Optional[str] time: int amount: int balance: int @@ -72,3 +74,14 @@ class Charges(BaseModel): def must_call_webhook(self): return self.webhook and self.paid and self.config.webhook_success == False + + +class SatsPayThemes(BaseModel): + css_id: str = Query(None) + title: str = Query(None) + custom_css: str = Query(None) + user: Optional[str] + + @classmethod + def from_row(cls, row: Row) -> "SatsPayThemes": + return cls(**dict(row)) diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js index 929279554..2b1be8bdc 100644 --- a/lnbits/extensions/satspay/static/js/utils.js +++ b/lnbits/extensions/satspay/static/js/utils.js @@ -26,5 +26,10 @@ const mapCharge = (obj, oldObj = {}) => { return charge } +const mapCSS = (obj, oldObj = {}) => { + const theme = _.clone(obj) + return theme +} + const minutesToTime = min => min > 0 ? new Date(min * 1000).toISOString().substring(14, 19) : '' diff --git a/lnbits/extensions/satspay/templates/satspay/_api_docs.html b/lnbits/extensions/satspay/templates/satspay/_api_docs.html index ed6587357..6d5ae661e 100644 --- a/lnbits/extensions/satspay/templates/satspay/_api_docs.html +++ b/lnbits/extensions/satspay/templates/satspay/_api_docs.html @@ -5,7 +5,13 @@ WatchOnly extension, we highly reccomend using a fresh extended public Key specifically for SatsPayServer!
- Created by, Ben ArcBen Arc, + motorina0


diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html index a24ed84c7..8ea218bdd 100644 --- a/lnbits/extensions/satspay/templates/satspay/display.html +++ b/lnbits/extensions/satspay/templates/satspay/display.html @@ -174,7 +174,7 @@
- +{% endblock %} {% block styles %} + + {% endblock %} {% block scripts %} @@ -438,6 +448,10 @@ } }, created: async function () { + // Remove a user defined theme + if (this.charge.custom_css) { + document.body.setAttribute('data-theme', '') + } if (this.charge.payment_request) this.payInvoice() else this.payOnchain() diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index 60c4d5199..602b1a288 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -8,6 +8,26 @@ New charge + + New CSS Theme + + New CSS Theme + For security reason, custom css is only available to server + admins. @@ -259,6 +279,63 @@ + + + +
+
+
Themes
+
+
+ + {% raw %} + + + + {% endraw %} + +
+
@@ -303,32 +380,6 @@ > - - - - - - - -
@@ -377,6 +428,52 @@ label="Wallet *" > + +
+
+ + + + + + + + + +
+
+ + + + + + + +
+ Update CSS theme + Save CSS theme + Cancel +
+
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}