remove exception to black line-length and reformat.

This commit is contained in:
fiatjaf 2021-03-24 00:40:32 -03:00
parent 3333f1f3f3
commit 42bd5ea989
92 changed files with 1341 additions and 330 deletions

View File

@ -10,7 +10,14 @@ from .app import create_app
app = create_app()
from .settings import LNBITS_SITE_TITLE, SERVICE_FEE, DEBUG, LNBITS_DATA_FOLDER, WALLET, LNBITS_COMMIT
from .settings import (
LNBITS_SITE_TITLE,
SERVICE_FEE,
DEBUG,
LNBITS_DATA_FOLDER,
WALLET,
LNBITS_COMMIT,
)
print(
f"""Starting LNbits with

View File

@ -9,7 +9,12 @@ from secure import SecureHeaders # type: ignore
from .commands import db_migrate, handle_assets
from .core import core_app
from .helpers import get_valid_extensions, get_js_vendored, get_css_vendored, url_for_vendored
from .helpers import (
get_valid_extensions,
get_js_vendored,
get_css_vendored,
url_for_vendored,
)
from .proxy_fix import ASGIProxyFix
from .tasks import (
run_deferred_async,
@ -57,7 +62,9 @@ def check_funding_source(app: QuartTrio) -> None:
RuntimeWarning,
)
else:
print(f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat.")
print(
f" ✔️ {WALLET.__class__.__name__} seems to be connected and with a balance of {balance} msat."
)
def register_blueprints(app: QuartTrio) -> None:
@ -75,7 +82,9 @@ def register_blueprints(app: QuartTrio) -> None:
app.register_blueprint(bp, url_prefix=f"/{ext.code}")
except Exception:
raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.")
raise ImportError(
f"Please make sure that the extension `{ext.code}` follows conventions."
)
def register_commands(app: QuartTrio):

View File

@ -106,7 +106,9 @@ def decode(pr: str) -> Invoice:
key = VerifyingKey.from_string(unhexlify(invoice.payee), curve=SECP256k1)
key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string)
else:
keys = VerifyingKey.from_public_key_recovery(sig, message, SECP256k1, hashlib.sha256)
keys = VerifyingKey.from_public_key_recovery(
sig, message, SECP256k1, hashlib.sha256
)
signaling_byte = signature[64]
key = keys[int(signaling_byte)]
invoice.payee = key.to_string("compressed").hex()

View File

@ -7,7 +7,12 @@ import os
from sqlalchemy.exc import OperationalError # type: ignore
from .core import db as core_db, migrations as core_migrations
from .helpers import get_valid_extensions, get_css_vendored, get_js_vendored, url_for_vendored
from .helpers import (
get_valid_extensions,
get_css_vendored,
get_js_vendored,
url_for_vendored,
)
from .settings import LNBITS_PATH
@ -71,18 +76,23 @@ async def migrate_databases():
print(f"running migration {db_name}.{version}")
await migrate(db)
await core_conn.execute(
"INSERT OR REPLACE INTO dbversions (db, version) VALUES (?, ?)", (db_name, version)
"INSERT OR REPLACE INTO dbversions (db, version) VALUES (?, ?)",
(db_name, version),
)
await run_migration(core_conn, core_migrations)
for ext in get_valid_extensions():
try:
ext_migrations = importlib.import_module(f"lnbits.extensions.{ext.code}.migrations")
ext_migrations = importlib.import_module(
f"lnbits.extensions.{ext.code}.migrations"
)
ext_db = importlib.import_module(f"lnbits.extensions.{ext.code}").db
await run_migration(ext_db, ext_migrations)
except ImportError:
raise ImportError(f"Please make sure that the extension `{ext.code}` has a migrations file.")
raise ImportError(
f"Please make sure that the extension `{ext.code}` has a migrations file."
)
await core_txn.commit()
await core_conn.close()

View File

@ -4,7 +4,11 @@ from lnbits.db import Database
db = Database("database")
core_app: Blueprint = Blueprint(
"core", __name__, template_folder="templates", static_folder="static", static_url_path="/core/static"
"core",
__name__,
template_folder="templates",
static_folder="static",
static_url_path="/core/static",
)

View File

@ -25,7 +25,9 @@ async def create_account() -> User:
async def get_account(user_id: str) -> Optional[User]:
row = await db.fetchone("SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,))
row = await db.fetchone(
"SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,)
)
return User(**row) if row else None
@ -34,7 +36,9 @@ async def get_user(user_id: str) -> Optional[User]:
user = await db.fetchone("SELECT id, email FROM accounts WHERE id = ?", (user_id,))
if user:
extensions = await db.fetchall("SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,))
extensions = await db.fetchall(
"SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,)
)
wallets = await db.fetchall(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
@ -45,7 +49,15 @@ async def get_user(user_id: str) -> Optional[User]:
)
return (
User(**{**user, **{"extensions": [e[0] for e in extensions], "wallets": [Wallet(**w) for w in wallets]}})
User(
**{
**user,
**{
"extensions": [e[0] for e in extensions],
"wallets": [Wallet(**w) for w in wallets],
},
}
)
if user
else None
)
@ -72,7 +84,13 @@ async def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> W
INSERT INTO wallets (id, name, user, adminkey, inkey)
VALUES (?, ?, ?, ?, ?)
""",
(wallet_id, wallet_name or DEFAULT_WALLET_NAME, user_id, uuid4().hex, uuid4().hex),
(
wallet_id,
wallet_name or DEFAULT_WALLET_NAME,
user_id,
uuid4().hex,
uuid4().hex,
),
)
new_wallet = await get_wallet(wallet_id=wallet_id)
@ -280,7 +298,9 @@ async def create_payment(
int(pending),
memo,
fee,
json.dumps(extra) if extra and extra != {} and type(extra) is dict else None,
json.dumps(extra)
if extra and extra != {} and type(extra) is dict
else None,
webhook,
),
)

View File

@ -111,7 +111,12 @@ async def m002_add_fields_to_apipayments(db):
UPDATE apipayments SET extra = ?, memo = ?
WHERE checking_id = ? AND memo = ?
""",
(json.dumps({"tag": ext}), new, row["checking_id"], row["memo"]),
(
json.dumps({"tag": ext}),
new,
row["checking_id"],
row["memo"],
),
)
break
except OperationalError:

View File

@ -127,7 +127,9 @@ class Payment(NamedTuple):
@property
def is_uncheckable(self) -> bool:
return self.checking_id.startswith("temp_") or self.checking_id.startswith("internal_")
return self.checking_id.startswith("temp_") or self.checking_id.startswith(
"internal_"
)
async def set_pending(self, pending: bool) -> None:
from .crud import update_payment_status

View File

@ -18,7 +18,14 @@ from lnbits.settings import WALLET
from lnbits.wallets.base import PaymentStatus, PaymentResponse
from . import db
from .crud import get_wallet, create_payment, delete_payment, check_internal, update_payment_status, get_wallet_payment
from .crud import (
get_wallet,
create_payment,
delete_payment,
check_internal,
update_payment_status,
get_wallet_payment,
)
async def create_invoice(
@ -101,7 +108,9 @@ async def pay_invoice(
internal_checking_id = await check_internal(invoice.payment_hash)
if internal_checking_id:
# create a new payment from this wallet
await create_payment(checking_id=internal_id, fee=0, pending=False, **payment_kwargs)
await create_payment(
checking_id=internal_id, fee=0, pending=False, **payment_kwargs
)
else:
# create a temporary payment here so we can check if
# the balance is enough in the next step
@ -143,12 +152,16 @@ async def pay_invoice(
else:
await delete_payment(temp_id)
await db.commit()
raise Exception(payment.error_message or "Failed to pay_invoice on backend.")
raise Exception(
payment.error_message or "Failed to pay_invoice on backend."
)
return invoice.payment_hash
async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo: Optional[str] = None) -> None:
async def redeem_lnurl_withdraw(
wallet_id: str, res: LnurlWithdrawResponse, memo: Optional[str] = None
) -> None:
_, payment_request = await create_invoice(
wallet_id=wallet_id,
amount=res.max_sats,
@ -159,7 +172,10 @@ async def redeem_lnurl_withdraw(wallet_id: str, res: LnurlWithdrawResponse, memo
async with httpx.AsyncClient() as client:
await client.get(
res.callback.base,
params={**res.callback.query_params, **{"k1": res.k1, "pr": payment_request}},
params={
**res.callback.query_params,
**{"k1": res.k1, "pr": payment_request},
},
)

View File

@ -41,8 +41,18 @@ async def api_payments():
@api_validate_post_request(
schema={
"amount": {"type": "integer", "min": 1, "required": True},
"memo": {"type": "string", "empty": False, "required": True, "excludes": "description_hash"},
"description_hash": {"type": "string", "empty": False, "required": True, "excludes": "memo"},
"memo": {
"type": "string",
"empty": False,
"required": True,
"excludes": "description_hash",
},
"description_hash": {
"type": "string",
"empty": False,
"required": True,
"excludes": "memo",
},
"lnurl_callback": {"type": "string", "nullable": True, "required": False},
"extra": {"type": "dict", "nullable": True, "required": False},
"webhook": {"type": "string", "empty": False, "required": False},
@ -108,10 +118,14 @@ async def api_payments_create_invoice():
@api_check_wallet_key("admin")
@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}})
@api_validate_post_request(
schema={"bolt11": {"type": "string", "empty": False, "required": True}}
)
async def api_payments_pay_invoice():
try:
payment_hash = await pay_invoice(wallet_id=g.wallet.id, payment_request=g.data["bolt11"])
payment_hash = await pay_invoice(
wallet_id=g.wallet.id, payment_request=g.data["bolt11"]
)
except ValueError as e:
return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST
except PermissionError as e:
@ -147,8 +161,18 @@ async def api_payments_create():
"description_hash": {"type": "string", "empty": False, "required": True},
"callback": {"type": "string", "empty": False, "required": True},
"amount": {"type": "number", "empty": False, "required": True},
"comment": {"type": "string", "nullable": True, "empty": True, "required": False},
"description": {"type": "string", "nullable": True, "empty": True, "required": False},
"comment": {
"type": "string",
"nullable": True,
"empty": True,
"required": False,
},
"description": {
"type": "string",
"nullable": True,
"empty": True,
"required": False,
},
}
)
async def api_payments_pay_lnurl():
@ -168,7 +192,10 @@ async def api_payments_pay_lnurl():
params = json.loads(r.text)
if params.get("status") == "ERROR":
return jsonify({"message": f"{domain} said: '{params.get('reason', '')}'"}), HTTPStatus.BAD_REQUEST
return (
jsonify({"message": f"{domain} said: '{params.get('reason', '')}'"}),
HTTPStatus.BAD_REQUEST,
)
invoice = bolt11.decode(params["pr"])
if invoice.amount_msat != g.data["amount"]:
@ -236,7 +263,10 @@ async def api_payment(payment_hash):
except Exception:
return jsonify({"paid": False}), HTTPStatus.OK
return jsonify({"paid": not payment.pending, "preimage": payment.preimage}), HTTPStatus.OK
return (
jsonify({"paid": not payment.pending, "preimage": payment.preimage}),
HTTPStatus.OK,
)
@core_app.route("/api/v1/payments/sse", methods=["GET"])
@ -325,12 +355,22 @@ async def api_lnurlscan(code: str):
data: lnurl.LnurlResponseModel = lnurl.LnurlResponse.from_dict(jdata)
except (json.decoder.JSONDecodeError, lnurl.exceptions.LnurlResponseException):
return (
jsonify({"domain": domain, "message": f"got invalid response '{r.text[:200]}'"}),
jsonify(
{
"domain": domain,
"message": f"got invalid response '{r.text[:200]}'",
}
),
HTTPStatus.SERVICE_UNAVAILABLE,
)
if type(data) is lnurl.LnurlChannelResponse:
return jsonify({"domain": domain, "kind": "channel", "message": "unsupported"}), HTTPStatus.BAD_REQUEST
return (
jsonify(
{"domain": domain, "kind": "channel", "message": "unsupported"}
),
HTTPStatus.BAD_REQUEST,
)
params.update(**data.dict())

View File

@ -2,7 +2,15 @@ import trio # type: ignore
import httpx
from os import path
from http import HTTPStatus
from quart import g, abort, redirect, request, render_template, send_from_directory, url_for
from quart import (
g,
abort,
redirect,
request,
render_template,
send_from_directory,
url_for,
)
from lnurl import LnurlResponse, LnurlWithdrawResponse, decode as decode_lnurl # type: ignore
from lnbits.core import core_app
@ -22,12 +30,16 @@ from ..services import redeem_lnurl_withdraw
@core_app.route("/favicon.ico")
async def favicon():
return await send_from_directory(path.join(core_app.root_path, "static"), "favicon.ico")
return await send_from_directory(
path.join(core_app.root_path, "static"), "favicon.ico"
)
@core_app.route("/")
async def home():
return await render_template("core/index.html", lnurl=request.args.get("lightning", None))
return await render_template(
"core/index.html", lnurl=request.args.get("lightning", None)
)
@core_app.route("/extensions")
@ -38,12 +50,18 @@ async def extensions():
extension_to_disable = request.args.get("disable", type=str)
if extension_to_enable and extension_to_disable:
abort(HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension.")
abort(
HTTPStatus.BAD_REQUEST, "You can either `enable` or `disable` an extension."
)
if extension_to_enable:
await update_user_extension(user_id=g.user.id, extension=extension_to_enable, active=1)
await update_user_extension(
user_id=g.user.id, extension=extension_to_enable, active=1
)
elif extension_to_disable:
await update_user_extension(user_id=g.user.id, extension=extension_to_disable, active=0)
await update_user_extension(
user_id=g.user.id, extension=extension_to_disable, active=0
)
return await render_template("core/extensions.html", user=await get_user(g.user.id))
@ -85,7 +103,9 @@ async def wallet():
if not wallet:
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
return await render_template("core/wallet.html", user=user, wallet=wallet, service_fee=service_fee)
return await render_template(
"core/wallet.html", user=user, wallet=wallet, service_fee=service_fee
)
@core_app.route("/deletewallet")
@ -116,19 +136,33 @@ async def lnurlwallet():
withdraw_res = LnurlResponse.from_dict(r.json())
if not withdraw_res.ok:
return f"Could not process lnurl-withdraw: {withdraw_res.error_msg}", HTTPStatus.BAD_REQUEST
return (
f"Could not process lnurl-withdraw: {withdraw_res.error_msg}",
HTTPStatus.BAD_REQUEST,
)
if not isinstance(withdraw_res, LnurlWithdrawResponse):
return f"Expected an lnurl-withdraw code, got {withdraw_res.tag}", HTTPStatus.BAD_REQUEST
return (
f"Expected an lnurl-withdraw code, got {withdraw_res.tag}",
HTTPStatus.BAD_REQUEST,
)
except Exception as exc:
return f"Could not process lnurl-withdraw: {exc}", HTTPStatus.INTERNAL_SERVER_ERROR
return (
f"Could not process lnurl-withdraw: {exc}",
HTTPStatus.INTERNAL_SERVER_ERROR,
)
account = await create_account()
user = await get_user(account.id)
wallet = await create_wallet(user_id=user.id)
await db.commit()
g.nursery.start_soon(redeem_lnurl_withdraw, wallet.id, withdraw_res, "LNbits initial funding: voucher redeem.")
g.nursery.start_soon(
redeem_lnurl_withdraw,
wallet.id,
withdraw_res,
"LNbits initial funding: voucher redeem.",
)
await trio.sleep(3)
return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id))

View File

@ -18,7 +18,9 @@ class Database:
def session_connection(self) -> Tuple[Optional[Any], Optional[Any]]:
try:
return getattr(g, f"{self.db_name}_conn", None), getattr(g, f"{self.db_name}_txn", None)
return getattr(g, f"{self.db_name}_conn", None), getattr(
g, f"{self.db_name}_txn", None
)
except RuntimeError:
return None, None

View File

@ -77,11 +77,15 @@ def check_user_exists(param: str = "usr"):
return wrap
def validate_uuids(params: List[str], *, required: Union[bool, List[str]] = False, version: int = 4):
def validate_uuids(
params: List[str], *, required: Union[bool, List[str]] = False, version: int = 4
):
def wrap(view):
@wraps(view)
async def wrapped_view(**kwargs):
query_params = {param: request.args.get(param, type=str) for param in params}
query_params = {
param: request.args.get(param, type=str) for param in params
}
for param, value in query_params.items():
if not value and (required is True or (required and param in required)):

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_amilk")
amilk_ext: Blueprint = Blueprint("amilk", __name__, static_folder="static", template_folder="templates")
amilk_ext: Blueprint = Blueprint(
"amilk", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -31,7 +31,9 @@ async def get_amilks(wallet_ids: Union[str, List[str]]) -> List[AMilk]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM amilks WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM amilks WHERE wallet IN ({q})", (*wallet_ids,)
)
return [AMilk(**row) for row in rows]

View File

@ -21,7 +21,10 @@ async def api_amilks():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([amilk._asdict() for amilk in await get_amilks(wallet_ids)]), HTTPStatus.OK
return (
jsonify([amilk._asdict() for amilk in await get_amilks(wallet_ids)]),
HTTPStatus.OK,
)
@amilk_ext.route("/api/v1/amilk/milk/<amilk_id>", methods=["GET"])
@ -35,12 +38,18 @@ async def api_amilkit(amilk_id):
abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.")
payment_hash, payment_request = await create_invoice(
wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo, extra={"tag": "amilk"}
wallet_id=milk.wallet,
amount=withdraw_res.max_sats,
memo=memo,
extra={"tag": "amilk"},
)
r = httpx.get(
withdraw_res.callback.base,
params={**withdraw_res.callback.query_params, **{"k1": withdraw_res.k1, "pr": payment_request}},
params={
**withdraw_res.callback.query_params,
**{"k1": withdraw_res.k1, "pr": payment_request},
},
)
if r.is_error:
@ -68,7 +77,10 @@ async def api_amilkit(amilk_id):
)
async def api_amilk_create():
amilk = await create_amilk(
wallet_id=g.wallet.id, lnurl=g.data["lnurl"], atime=g.data["atime"], amount=g.data["amount"]
wallet_id=g.wallet.id,
lnurl=g.data["lnurl"],
atime=g.data["atime"],
amount=g.data["amount"],
)
return jsonify(amilk._asdict()), HTTPStatus.CREATED

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_bleskomat")
bleskomat_ext: Blueprint = Blueprint("bleskomat", __name__, static_folder="static", template_folder="templates")
bleskomat_ext: Blueprint = Blueprint(
"bleskomat", __name__, static_folder="static", template_folder="templates"
)
from .lnurl_api import * # noqa
from .views_api import * # noqa

View File

@ -8,7 +8,12 @@ from .helpers import generate_bleskomat_lnurl_hash
async def create_bleskomat(
*, wallet_id: str, name: str, fiat_currency: str, exchange_rate_provider: str, fee: str
*,
wallet_id: str,
name: str,
fiat_currency: str,
exchange_rate_provider: str,
fee: str,
) -> Bleskomat:
bleskomat_id = uuid4().hex
api_key_id = secrets.token_hex(8)
@ -42,7 +47,9 @@ async def get_bleskomat(bleskomat_id: str) -> Optional[Bleskomat]:
async def get_bleskomat_by_api_key_id(api_key_id: str) -> Optional[Bleskomat]:
row = await db.fetchone("SELECT * FROM bleskomats WHERE api_key_id = ?", (api_key_id,))
row = await db.fetchone(
"SELECT * FROM bleskomats WHERE api_key_id = ?", (api_key_id,)
)
return Bleskomat(**row) if row else None
@ -50,13 +57,17 @@ async def get_bleskomats(wallet_ids: Union[str, List[str]]) -> List[Bleskomat]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM bleskomats WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM bleskomats WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Bleskomat(**row) for row in rows]
async def update_bleskomat(bleskomat_id: str, **kwargs) -> Optional[Bleskomat]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE bleskomats SET {q} WHERE id = ?", (*kwargs.values(), bleskomat_id))
await db.execute(
f"UPDATE bleskomats SET {q} WHERE id = ?", (*kwargs.values(), bleskomat_id)
)
row = await db.fetchone("SELECT * FROM bleskomats WHERE id = ?", (bleskomat_id,))
return Bleskomat(**row) if row else None

View File

@ -3,7 +3,12 @@ import json
import os
fiat_currencies = json.load(
open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "fiat_currencies.json"), "r")
open(
os.path.join(
os.path.dirname(os.path.realpath(__file__)), "fiat_currencies.json"
),
"r",
)
)
exchange_rate_providers = {
@ -35,7 +40,9 @@ exchange_rate_providers = {
"name": "Kraken",
"domain": "kraken.com",
"api_url": "https://api.kraken.com/0/public/Ticker?pair=XBT{TO}",
"getter": lambda data, replacements: data["result"]["XXBTZ" + replacements["TO"]]["c"][0],
"getter": lambda data, replacements: data["result"][
"XXBTZ" + replacements["TO"]
]["c"][0],
},
}
@ -50,7 +57,12 @@ for ref, exchange_rate_provider in exchange_rate_providers.items():
async def fetch_fiat_exchange_rate(currency: str, provider: str):
replacements = {"FROM": "BTC", "from": "btc", "TO": currency.upper(), "to": currency.lower()}
replacements = {
"FROM": "BTC",
"from": "btc",
"TO": currency.upper(),
"to": currency.lower(),
}
url = exchange_rate_providers[provider]["api_url"]
for key in replacements.keys():

View File

@ -14,7 +14,9 @@ def generate_bleskomat_lnurl_hash(secret: str):
return m.hexdigest()
def generate_bleskomat_lnurl_signature(payload: str, api_key_secret: str, api_key_encoding: str = "hex"):
def generate_bleskomat_lnurl_signature(
payload: str, api_key_secret: str, api_key_encoding: str = "hex"
):
if api_key_encoding == "hex":
key = unhexlify(api_key_secret)
elif api_key_encoding == "base64":
@ -41,7 +43,11 @@ def is_supported_lnurl_subprotocol(tag: str) -> bool:
class LnurlHttpError(Exception):
def __init__(self, message: str = "", http_status: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR):
def __init__(
self,
message: str = "",
http_status: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
):
self.message = message
self.http_status = http_status
super().__init__(self.message)
@ -62,11 +68,15 @@ def prepare_lnurl_params(tag: str, query: Dict[str, str]):
if not params["minWithdrawable"] > 0:
raise LnurlValidationError('"minWithdrawable" must be greater than zero')
if not params["maxWithdrawable"] >= params["minWithdrawable"]:
raise LnurlValidationError('"maxWithdrawable" must be greater than or equal to "minWithdrawable"')
raise LnurlValidationError(
'"maxWithdrawable" must be greater than or equal to "minWithdrawable"'
)
return params
encode_uri_component_safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()"
encode_uri_component_safe_chars = (
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()"
)
def query_to_signing_payload(query: Dict[str, str]) -> str:
@ -76,19 +86,30 @@ def query_to_signing_payload(query: Dict[str, str]) -> str:
for key in sorted_keys:
if not key == "signature":
encoded_key = urllib.parse.quote(key, safe=encode_uri_component_safe_chars)
encoded_value = urllib.parse.quote(query[key], safe=encode_uri_component_safe_chars)
encoded_value = urllib.parse.quote(
query[key], safe=encode_uri_component_safe_chars
)
payload.append(f"{encoded_key}={encoded_value}")
return "&".join(payload)
unshorten_rules = {
"query": {"n": "nonce", "s": "signature", "t": "tag"},
"tags": {"c": "channelRequest", "l": "login", "p": "payRequest", "w": "withdrawRequest"},
"tags": {
"c": "channelRequest",
"l": "login",
"p": "payRequest",
"w": "withdrawRequest",
},
"params": {
"channelRequest": {"pl": "localAmt", "pp": "pushAmt"},
"login": {},
"payRequest": {"pn": "minSendable", "px": "maxSendable", "pm": "metadata"},
"withdrawRequest": {"pn": "minWithdrawable", "px": "maxWithdrawable", "pd": "defaultDescription"},
"withdrawRequest": {
"pn": "minWithdrawable",
"px": "maxWithdrawable",
"pd": "defaultDescription",
},
},
}

View File

@ -47,7 +47,10 @@ async def api_bleskomat_lnurl():
# The API key ID, nonce, and tag should be present in the query string.
for field in ["id", "nonce", "tag"]:
if not field in query:
raise LnurlHttpError(f'Failed API key signature check: Missing "{field}"', HTTPStatus.BAD_REQUEST)
raise LnurlHttpError(
f'Failed API key signature check: Missing "{field}"',
HTTPStatus.BAD_REQUEST,
)
# URL signing scheme is described here:
# https://github.com/chill117/lnurl-node#how-to-implement-url-signing-scheme
@ -58,7 +61,9 @@ async def api_bleskomat_lnurl():
raise LnurlHttpError("Unknown API key", HTTPStatus.BAD_REQUEST)
api_key_secret = bleskomat.api_key_secret
api_key_encoding = bleskomat.api_key_encoding
expected_signature = generate_bleskomat_lnurl_signature(payload, api_key_secret, api_key_encoding)
expected_signature = generate_bleskomat_lnurl_signature(
payload, api_key_secret, api_key_encoding
)
if signature != expected_signature:
raise LnurlHttpError("Invalid API key signature", HTTPStatus.FORBIDDEN)
@ -72,13 +77,16 @@ async def api_bleskomat_lnurl():
params = prepare_lnurl_params(tag, query)
if "f" in query:
rate = await fetch_fiat_exchange_rate(
currency=query["f"], provider=bleskomat.exchange_rate_provider
currency=query["f"],
provider=bleskomat.exchange_rate_provider,
)
# Convert fee (%) to decimal:
fee = float(bleskomat.fee) / 100
if tag == "withdrawRequest":
for key in ["minWithdrawable", "maxWithdrawable"]:
amount_sats = int(math.floor((params[key] / rate) * 1e8))
amount_sats = int(
math.floor((params[key] / rate) * 1e8)
)
fee_sats = int(math.floor(amount_sats * fee))
amount_sats_less_fee = amount_sats - fee_sats
# Convert to msats:
@ -87,7 +95,9 @@ async def api_bleskomat_lnurl():
raise LnurlHttpError(e.message, HTTPStatus.BAD_REQUEST)
# Create a new LNURL using the query parameters provided in the signed URL.
params = json.JSONEncoder().encode(params)
lnurl = await create_bleskomat_lnurl(bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1)
lnurl = await create_bleskomat_lnurl(
bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1
)
# Reply with LNURL response object.
return jsonify(lnurl.get_info_response_object(secret)), HTTPStatus.OK
@ -104,7 +114,9 @@ async def api_bleskomat_lnurl():
raise LnurlHttpError("Invalid secret", HTTPStatus.BAD_REQUEST)
if not lnurl.has_uses_remaining():
raise LnurlHttpError("Maximum number of uses already reached", HTTPStatus.BAD_REQUEST)
raise LnurlHttpError(
"Maximum number of uses already reached", HTTPStatus.BAD_REQUEST
)
try:
await lnurl.execute_action(query)
@ -115,6 +127,9 @@ async def api_bleskomat_lnurl():
return jsonify({"status": "ERROR", "reason": str(e)}), e.http_status
except Exception as e:
traceback.print_exc()
return jsonify({"status": "ERROR", "reason": "Unexpected error"}), HTTPStatus.INTERNAL_SERVER_ERROR
return (
jsonify({"status": "ERROR", "reason": "Unexpected error"}),
HTTPStatus.INTERNAL_SERVER_ERROR,
)
return jsonify({"status": "OK"}), HTTPStatus.OK

View File

@ -62,11 +62,17 @@ class BleskomatLnurl(NamedTuple):
try:
invoice = bolt11.decode(pr)
except ValueError as e:
raise LnurlValidationError('Invalid parameter ("pr"): Lightning payment request expected')
raise LnurlValidationError(
'Invalid parameter ("pr"): Lightning payment request expected'
)
if invoice.amount_msat < params["minWithdrawable"]:
raise LnurlValidationError('Amount in invoice must be greater than or equal to "minWithdrawable"')
raise LnurlValidationError(
'Amount in invoice must be greater than or equal to "minWithdrawable"'
)
if invoice.amount_msat > params["maxWithdrawable"]:
raise LnurlValidationError('Amount in invoice must be less than or equal to "maxWithdrawable"')
raise LnurlValidationError(
'Amount in invoice must be less than or equal to "maxWithdrawable"'
)
else:
raise LnurlValidationError(f'Unknown subprotocol: "{tag}"')

View File

@ -17,4 +17,6 @@ async def index():
"exchange_rate_providers": exchange_rate_providers_serializable,
"fiat_currencies": fiat_currencies,
}
return await render_template("bleskomat/index.html", user=g.user, bleskomat_vars=bleskomat_vars)
return await render_template(
"bleskomat/index.html", user=g.user, bleskomat_vars=bleskomat_vars
)

View File

@ -28,7 +28,12 @@ async def api_bleskomats():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([bleskomat._asdict() for bleskomat in await get_bleskomats(wallet_ids)]), HTTPStatus.OK
return (
jsonify(
[bleskomat._asdict() for bleskomat in await get_bleskomats(wallet_ids)]
),
HTTPStatus.OK,
)
@bleskomat_ext.route("/api/v1/bleskomat/<bleskomat_id>", methods=["GET"])
@ -37,7 +42,10 @@ async def api_bleskomat_retrieve(bleskomat_id):
bleskomat = await get_bleskomat(bleskomat_id)
if not bleskomat or bleskomat.wallet != g.wallet.id:
return jsonify({"message": "Bleskomat configuration not found."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Bleskomat configuration not found."}),
HTTPStatus.NOT_FOUND,
)
return jsonify(bleskomat._asdict()), HTTPStatus.OK
@ -48,8 +56,16 @@ async def api_bleskomat_retrieve(bleskomat_id):
@api_validate_post_request(
schema={
"name": {"type": "string", "empty": False, "required": True},
"fiat_currency": {"type": "string", "allowed": fiat_currencies.keys(), "required": True},
"exchange_rate_provider": {"type": "string", "allowed": exchange_rate_providers.keys(), "required": True},
"fiat_currency": {
"type": "string",
"allowed": fiat_currencies.keys(),
"required": True,
},
"exchange_rate_provider": {
"type": "string",
"allowed": exchange_rate_providers.keys(),
"required": True,
},
"fee": {"type": ["string", "float", "number", "integer"], "required": True},
}
)
@ -58,23 +74,35 @@ async def api_bleskomat_create_or_update(bleskomat_id=None):
try:
fiat_currency = g.data["fiat_currency"]
exchange_rate_provider = g.data["exchange_rate_provider"]
rate = await fetch_fiat_exchange_rate(currency=fiat_currency, provider=exchange_rate_provider)
rate = await fetch_fiat_exchange_rate(
currency=fiat_currency, provider=exchange_rate_provider
)
except Exception as e:
print(e)
return (
jsonify({"message": f'Failed to fetch BTC/{fiat_currency} currency pair from "{exchange_rate_provider}"'}),
jsonify(
{
"message": f'Failed to fetch BTC/{fiat_currency} currency pair from "{exchange_rate_provider}"'
}
),
HTTPStatus.INTERNAL_SERVER_ERROR,
)
if bleskomat_id:
bleskomat = await get_bleskomat(bleskomat_id)
if not bleskomat or bleskomat.wallet != g.wallet.id:
return jsonify({"message": "Bleskomat configuration not found."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Bleskomat configuration not found."}),
HTTPStatus.NOT_FOUND,
)
bleskomat = await update_bleskomat(bleskomat_id, **g.data)
else:
bleskomat = await create_bleskomat(wallet_id=g.wallet.id, **g.data)
return jsonify(bleskomat._asdict()), HTTPStatus.OK if bleskomat_id else HTTPStatus.CREATED
return (
jsonify(bleskomat._asdict()),
HTTPStatus.OK if bleskomat_id else HTTPStatus.CREATED,
)
@bleskomat_ext.route("/api/v1/bleskomat/<bleskomat_id>", methods=["DELETE"])
@ -83,7 +111,10 @@ async def api_bleskomat_delete(bleskomat_id):
bleskomat = await get_bleskomat(bleskomat_id)
if not bleskomat or bleskomat.wallet != g.wallet.id:
return jsonify({"message": "Bleskomat configuration not found."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Bleskomat configuration not found."}),
HTTPStatus.NOT_FOUND,
)
await delete_bleskomat(bleskomat_id)

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_captcha")
captcha_ext: Blueprint = Blueprint("captcha", __name__, static_folder="static", template_folder="templates")
captcha_ext: Blueprint = Blueprint(
"captcha", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -7,7 +7,13 @@ from .models import Captcha
async def create_captcha(
*, wallet_id: str, url: str, memo: str, description: Optional[str] = None, amount: int = 0, remembers: bool = True
*,
wallet_id: str,
url: str,
memo: str,
description: Optional[str] = None,
amount: int = 0,
remembers: bool = True,
) -> Captcha:
captcha_id = urlsafe_short_hash()
await db.execute(
@ -34,7 +40,9 @@ async def get_captchas(wallet_ids: Union[str, List[str]]) -> List[Captcha]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM captchas WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM captchas WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Captcha.from_row(row) for row in rows]

View File

@ -46,7 +46,9 @@ async def m002_redux(db):
)
await db.execute("CREATE INDEX IF NOT EXISTS wallet_idx ON captchas (wallet)")
for row in [list(row) for row in await db.fetchall("SELECT * FROM captchas_old")]:
for row in [
list(row) for row in await db.fetchall("SELECT * FROM captchas_old")
]:
await db.execute(
"""
INSERT INTO captchas (

View File

@ -16,5 +16,7 @@ async def index():
@captcha_ext.route("/<captcha_id>")
async def display(captcha_id):
captcha = await get_captcha(captcha_id) or abort(HTTPStatus.NOT_FOUND, "captcha does not exist.")
captcha = await get_captcha(captcha_id) or abort(
HTTPStatus.NOT_FOUND, "captcha does not exist."
)
return await render_template("captcha/display.html", captcha=captcha)

View File

@ -17,7 +17,10 @@ async def api_captchas():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([captcha._asdict() for captcha in await get_captchas(wallet_ids)]), HTTPStatus.OK
return (
jsonify([captcha._asdict() for captcha in await get_captchas(wallet_ids)]),
HTTPStatus.OK,
)
@captcha_ext.route("/api/v1/captchas", methods=["POST"])
@ -26,7 +29,12 @@ async def api_captchas():
schema={
"url": {"type": "string", "empty": False, "required": True},
"memo": {"type": "string", "empty": False, "required": True},
"description": {"type": "string", "empty": True, "nullable": True, "required": False},
"description": {
"type": "string",
"empty": True,
"nullable": True,
"required": False,
},
"amount": {"type": "integer", "min": 0, "required": True},
"remembers": {"type": "boolean", "required": True},
}
@ -53,26 +61,41 @@ async def api_captcha_delete(captcha_id):
@captcha_ext.route("/api/v1/captchas/<captcha_id>/invoice", methods=["POST"])
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
@api_validate_post_request(
schema={"amount": {"type": "integer", "min": 1, "required": True}}
)
async def api_captcha_create_invoice(captcha_id):
captcha = await get_captcha(captcha_id)
if g.data["amount"] < captcha.amount:
return jsonify({"message": f"Minimum amount is {captcha.amount} sat."}), HTTPStatus.BAD_REQUEST
return (
jsonify({"message": f"Minimum amount is {captcha.amount} sat."}),
HTTPStatus.BAD_REQUEST,
)
try:
amount = g.data["amount"] if g.data["amount"] > captcha.amount else captcha.amount
amount = (
g.data["amount"] if g.data["amount"] > captcha.amount else captcha.amount
)
payment_hash, payment_request = await create_invoice(
wallet_id=captcha.wallet, amount=amount, memo=f"{captcha.memo}", extra={"tag": "captcha"}
wallet_id=captcha.wallet,
amount=amount,
memo=f"{captcha.memo}",
extra={"tag": "captcha"},
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
HTTPStatus.CREATED,
)
@captcha_ext.route("/api/v1/captchas/<captcha_id>/check_invoice", methods=["POST"])
@api_validate_post_request(schema={"payment_hash": {"type": "string", "empty": False, "required": True}})
@api_validate_post_request(
schema={"payment_hash": {"type": "string", "empty": False, "required": True}}
)
async def api_paywal_check_invoice(captcha_id):
captcha = await get_captcha(captcha_id)
@ -90,6 +113,9 @@ async def api_paywal_check_invoice(captcha_id):
payment = await wallet.get_payment(g.data["payment_hash"])
await payment.set_pending(False)
return jsonify({"paid": True, "url": captcha.url, "remembers": captcha.remembers}), HTTPStatus.OK
return (
jsonify({"paid": True, "url": captcha.url, "remembers": captcha.remembers}),
HTTPStatus.OK,
)
return jsonify({"paid": False}), HTTPStatus.OK

View File

@ -1,7 +1,9 @@
from quart import Blueprint
diagonalley_ext: Blueprint = Blueprint("diagonalley", __name__, static_folder="static", template_folder="templates")
diagonalley_ext: Blueprint = Blueprint(
"diagonalley", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -21,7 +21,14 @@ regex = re.compile(
def create_diagonalleys_product(
*, wallet_id: str, product: str, categories: str, description: str, image: str, price: int, quantity: int
*,
wallet_id: str,
product: str,
categories: str,
description: str,
image: str,
price: int,
quantity: int,
) -> Products:
with open_ext_db("diagonalley") as db:
product_id = urlsafe_b64encode(uuid4().bytes_le).decode("utf-8")
@ -30,7 +37,16 @@ def create_diagonalleys_product(
INSERT INTO products (id, wallet, product, categories, description, image, price, quantity)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(product_id, wallet_id, product, categories, description, image, price, quantity),
(
product_id,
wallet_id,
product,
categories,
description,
image,
price,
quantity,
),
)
return get_diagonalleys_product(product_id)
@ -40,7 +56,9 @@ def update_diagonalleys_product(product_id: str, **kwargs) -> Optional[Indexers]
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
with open_ext_db("diagonalley") as db:
db.execute(f"UPDATE products SET {q} WHERE id = ?", (*kwargs.values(), product_id))
db.execute(
f"UPDATE products SET {q} WHERE id = ?", (*kwargs.values(), product_id)
)
row = db.fetchone("SELECT * FROM products WHERE id = ?", (product_id,))
return get_diagonalleys_indexer(product_id)
@ -59,7 +77,9 @@ def get_diagonalleys_products(wallet_ids: Union[str, List[str]]) -> List[Product
with open_ext_db("diagonalley") as db:
q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM products WHERE wallet IN ({q})", (*wallet_ids,))
rows = db.fetchall(
f"SELECT * FROM products WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Products(**row) for row in rows]
@ -110,7 +130,9 @@ def update_diagonalleys_indexer(indexer_id: str, **kwargs) -> Optional[Indexers]
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
with open_ext_db("diagonalley") as db:
db.execute(f"UPDATE indexers SET {q} WHERE id = ?", (*kwargs.values(), indexer_id))
db.execute(
f"UPDATE indexers SET {q} WHERE id = ?", (*kwargs.values(), indexer_id)
)
row = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,))
return get_diagonalleys_indexer(indexer_id)
@ -154,7 +176,9 @@ def get_diagonalleys_indexers(wallet_ids: Union[str, List[str]]) -> List[Indexer
with open_ext_db("diagonalley") as db:
q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM indexers WHERE wallet IN ({q})", (*wallet_ids,))
rows = db.fetchall(
f"SELECT * FROM indexers WHERE wallet IN ({q})", (*wallet_ids,)
)
for r in rows:
try:
@ -181,7 +205,9 @@ def get_diagonalleys_indexers(wallet_ids: Union[str, List[str]]) -> List[Indexer
print("An exception occurred")
with open_ext_db("diagonalley") as db:
q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM indexers WHERE wallet IN ({q})", (*wallet_ids,))
rows = db.fetchall(
f"SELECT * FROM indexers WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Indexers(**row) for row in rows]
@ -213,7 +239,19 @@ def create_diagonalleys_order(
INSERT INTO orders (id, productid, wallet, product, quantity, shippingzone, address, email, invoiceid, paid, shipped)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(order_id, productid, wallet, product, quantity, shippingzone, address, email, invoiceid, False, False),
(
order_id,
productid,
wallet,
product,
quantity,
shippingzone,
address,
email,
invoiceid,
False,
False,
),
)
return get_diagonalleys_order(order_id)
@ -232,7 +270,9 @@ def get_diagonalleys_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
with open_ext_db("diagonalley") as db:
q = ",".join(["?"] * len(wallet_ids))
rows = db.fetchall(f"SELECT * FROM orders WHERE wallet IN ({q})", (*wallet_ids,))
rows = db.fetchall(
f"SELECT * FROM orders WHERE wallet IN ({q})", (*wallet_ids,)
)
for r in rows:
PAID = WALLET.get_invoice_status(r["invoiceid"]).paid
if PAID:
@ -244,7 +284,9 @@ def get_diagonalleys_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
r["id"],
),
)
rows = db.fetchall(f"SELECT * FROM orders WHERE wallet IN ({q})", (*wallet_ids,))
rows = db.fetchall(
f"SELECT * FROM orders WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Orders(**row) for row in rows]

View File

@ -36,7 +36,12 @@ async def api_diagonalley_products():
if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids
return jsonify([product._asdict() for product in get_diagonalleys_products(wallet_ids)]), HTTPStatus.OK
return (
jsonify(
[product._asdict() for product in get_diagonalleys_products(wallet_ids)]
),
HTTPStatus.OK,
)
@diagonalley_ext.route("/api/v1/diagonalley/products", methods=["POST"])
@ -58,16 +63,25 @@ async def api_diagonalley_product_create(product_id=None):
product = get_diagonalleys_indexer(product_id)
if not product:
return jsonify({"message": "Withdraw product does not exist."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Withdraw product does not exist."}),
HTTPStatus.NOT_FOUND,
)
if product.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw product."}), HTTPStatus.FORBIDDEN
return (
jsonify({"message": "Not your withdraw product."}),
HTTPStatus.FORBIDDEN,
)
product = update_diagonalleys_product(product_id, **g.data)
else:
product = create_diagonalleys_product(wallet_id=g.wallet.id, **g.data)
return jsonify(product._asdict()), HTTPStatus.OK if product_id else HTTPStatus.CREATED
return (
jsonify(product._asdict()),
HTTPStatus.OK if product_id else HTTPStatus.CREATED,
)
@diagonalley_ext.route("/api/v1/diagonalley/products/<product_id>", methods=["DELETE"])
@ -97,7 +111,12 @@ async def api_diagonalley_indexers():
if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids
return jsonify([indexer._asdict() for indexer in get_diagonalleys_indexers(wallet_ids)]), HTTPStatus.OK
return (
jsonify(
[indexer._asdict() for indexer in get_diagonalleys_indexers(wallet_ids)]
),
HTTPStatus.OK,
)
@diagonalley_ext.route("/api/v1/diagonalley/indexers", methods=["POST"])
@ -120,16 +139,25 @@ async def api_diagonalley_indexer_create(indexer_id=None):
indexer = get_diagonalleys_indexer(indexer_id)
if not indexer:
return jsonify({"message": "Withdraw indexer does not exist."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Withdraw indexer does not exist."}),
HTTPStatus.NOT_FOUND,
)
if indexer.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw indexer."}), HTTPStatus.FORBIDDEN
return (
jsonify({"message": "Not your withdraw indexer."}),
HTTPStatus.FORBIDDEN,
)
indexer = update_diagonalleys_indexer(indexer_id, **g.data)
else:
indexer = create_diagonalleys_indexer(wallet_id=g.wallet.id, **g.data)
return jsonify(indexer._asdict()), HTTPStatus.OK if indexer_id else HTTPStatus.CREATED
return (
jsonify(indexer._asdict()),
HTTPStatus.OK if indexer_id else HTTPStatus.CREATED,
)
@diagonalley_ext.route("/api/v1/diagonalley/indexers/<indexer_id>", methods=["DELETE"])
@ -159,7 +187,10 @@ async def api_diagonalley_orders():
if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids
return jsonify([order._asdict() for order in get_diagonalleys_orders(wallet_ids)]), HTTPStatus.OK
return (
jsonify([order._asdict() for order in get_diagonalleys_orders(wallet_ids)]),
HTTPStatus.OK,
)
@diagonalley_ext.route("/api/v1/diagonalley/orders", methods=["POST"])
@ -221,13 +252,20 @@ async def api_diagonalleys_order_shipped(order_id):
)
order = db.fetchone("SELECT * FROM orders WHERE id = ?", (order_id,))
return jsonify([order._asdict() for order in get_diagonalleys_orders(order["wallet"])]), HTTPStatus.OK
return (
jsonify(
[order._asdict() for order in get_diagonalleys_orders(order["wallet"])]
),
HTTPStatus.OK,
)
###List products based on indexer id
@diagonalley_ext.route("/api/v1/diagonalley/stall/products/<indexer_id>", methods=["GET"])
@diagonalley_ext.route(
"/api/v1/diagonalley/stall/products/<indexer_id>", methods=["GET"]
)
async def api_diagonalleys_stall_products(indexer_id):
with open_ext_db("diagonalley") as db:
rows = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,))
@ -239,13 +277,20 @@ async def api_diagonalleys_stall_products(indexer_id):
if not products:
return jsonify({"message": "No products"}), HTTPStatus.NOT_FOUND
return jsonify([products._asdict() for products in get_diagonalleys_products(rows[1])]), HTTPStatus.OK
return (
jsonify(
[products._asdict() for products in get_diagonalleys_products(rows[1])]
),
HTTPStatus.OK,
)
###Check a product has been shipped
@diagonalley_ext.route("/api/v1/diagonalley/stall/checkshipped/<checking_id>", methods=["GET"])
@diagonalley_ext.route(
"/api/v1/diagonalley/stall/checkshipped/<checking_id>", methods=["GET"]
)
async def api_diagonalleys_stall_checkshipped(checking_id):
with open_ext_db("diagonalley") as db:
rows = db.fetchone("SELECT * FROM orders WHERE invoiceid = ?", (checking_id,))
@ -276,7 +321,9 @@ async def api_diagonalley_stall_order(indexer_id):
shippingcost = shipping.zone2cost
checking_id, payment_request = create_invoice(
wallet_id=product.wallet, amount=shippingcost + (g.data["quantity"] * product.price), memo=g.data["id"]
wallet_id=product.wallet,
amount=shippingcost + (g.data["quantity"] * product.price),
memo=g.data["id"],
)
selling_id = urlsafe_b64encode(uuid4().bytes_le).decode("utf-8")
with open_ext_db("diagonalley") as db:
@ -299,4 +346,7 @@ async def api_diagonalley_stall_order(indexer_id):
False,
),
)
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), HTTPStatus.OK
return (
jsonify({"checking_id": checking_id, "payment_request": payment_request}),
HTTPStatus.OK,
)

View File

@ -4,7 +4,9 @@ from lnbits.db import Database
db = Database("ext_events")
events_ext: Blueprint = Blueprint("events", __name__, static_folder="static", template_folder="templates")
events_ext: Blueprint = Blueprint(
"events", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -9,7 +9,9 @@ from .models import Tickets, Events
# TICKETS
async def create_ticket(payment_hash: str, wallet: str, event: str, name: str, email: str) -> Tickets:
async def create_ticket(
payment_hash: str, wallet: str, event: str, name: str, email: str
) -> Tickets:
await db.execute(
"""
INSERT INTO ticket (id, wallet, event, name, email, registered, paid)
@ -64,7 +66,9 @@ async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM ticket WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM ticket WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Tickets(**row) for row in rows]
@ -113,7 +117,9 @@ async def create_event(
async def update_event(event_id: str, **kwargs) -> Events:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE events SET {q} WHERE id = ?", (*kwargs.values(), event_id))
await db.execute(
f"UPDATE events SET {q} WHERE id = ?", (*kwargs.values(), event_id)
)
event = await get_event(event_id)
assert event, "Newly updated event couldn't be retrieved"
return event
@ -129,7 +135,9 @@ async def get_events(wallet_ids: Union[str, List[str]]) -> List[Events]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM events WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM events WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Events(**row) for row in rows]
@ -142,7 +150,9 @@ async def delete_event(event_id: str) -> None:
async def get_event_tickets(event_id: str, wallet_id: str) -> List[Tickets]:
rows = await db.fetchall("SELECT * FROM ticket WHERE wallet = ? AND event = ?", (wallet_id, event_id))
rows = await db.fetchall(
"SELECT * FROM ticket WHERE wallet = ? AND event = ?", (wallet_id, event_id)
)
return [Tickets(**row) for row in rows]

View File

@ -23,12 +23,16 @@ async def display(event_id):
if event.amount_tickets < 1:
return await render_template(
"events/error.html", event_name=event.name, event_error="Sorry, tickets are sold out :("
"events/error.html",
event_name=event.name,
event_error="Sorry, tickets are sold out :(",
)
datetime_object = datetime.strptime(event.closing_date, "%Y-%m-%d").date()
if date.today() > datetime_object:
return await render_template(
"events/error.html", event_name=event.name, event_error="Sorry, ticket closing date has passed :("
"events/error.html",
event_name=event.name,
event_error="Sorry, ticket closing date has passed :(",
)
return await render_template(
@ -51,7 +55,10 @@ async def ticket(ticket_id):
abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
return await render_template(
"events/ticket.html", ticket_id=ticket_id, ticket_name=event.name, ticket_info=event.info
"events/ticket.html",
ticket_id=ticket_id,
ticket_name=event.name,
ticket_info=event.info,
)
@ -62,5 +69,8 @@ async def register(event_id):
abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
return await render_template(
"events/register.html", event_id=event_id, event_name=event.name, wallet_id=event.wallet
"events/register.html",
event_id=event_id,
event_name=event.name,
wallet_id=event.wallet,
)

View File

@ -33,7 +33,10 @@ async def api_events():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([event._asdict() for event in await get_events(wallet_ids)]), HTTPStatus.OK
return (
jsonify([event._asdict() for event in await get_events(wallet_ids)]),
HTTPStatus.OK,
)
@events_ext.route("/api/v1/events", methods=["POST"])
@ -92,7 +95,10 @@ async def api_tickets():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([ticket._asdict() for ticket in await get_tickets(wallet_ids)]), HTTPStatus.OK
return (
jsonify([ticket._asdict() for ticket in await get_tickets(wallet_ids)]),
HTTPStatus.OK,
)
@events_ext.route("/api/v1/tickets/<event_id>/<sats>", methods=["POST"])
@ -108,17 +114,25 @@ async def api_ticket_make_ticket(event_id, sats):
return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND
try:
payment_hash, payment_request = await create_invoice(
wallet_id=event.wallet, amount=int(sats), memo=f"{event_id}", extra={"tag": "events"}
wallet_id=event.wallet,
amount=int(sats),
memo=f"{event_id}",
extra={"tag": "events"},
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
ticket = await create_ticket(payment_hash=payment_hash, wallet=event.wallet, event=event_id, **g.data)
ticket = await create_ticket(
payment_hash=payment_hash, wallet=event.wallet, event=event_id, **g.data
)
if not ticket:
return jsonify({"message": "Event could not be fetched."}), HTTPStatus.NOT_FOUND
return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
HTTPStatus.OK,
)
@events_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
@ -163,7 +177,14 @@ async def api_ticket_delete(ticket_id):
@events_ext.route("/api/v1/eventtickets/<wallet_id>/<event_id>", methods=["GET"])
async def api_event_tickets(wallet_id, event_id):
return (
jsonify([ticket._asdict() for ticket in await get_event_tickets(wallet_id=wallet_id, event_id=event_id)]),
jsonify(
[
ticket._asdict()
for ticket in await get_event_tickets(
wallet_id=wallet_id, event_id=event_id
)
]
),
HTTPStatus.OK,
)
@ -177,4 +198,7 @@ async def api_event_register_ticket(ticket_id):
if ticket.registered == True:
return jsonify({"message": "Ticket already registered"}), HTTPStatus.FORBIDDEN
return jsonify([ticket._asdict() for ticket in await reg_ticket(ticket_id)]), HTTPStatus.OK
return (
jsonify([ticket._asdict() for ticket in await reg_ticket(ticket_id)]),
HTTPStatus.OK,
)

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_example")
example_ext: Blueprint = Blueprint("example", __name__, static_folder="static", template_folder="templates")
example_ext: Blueprint = Blueprint(
"example", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_lndhub")
lndhub_ext: Blueprint = Blueprint("lndhub", __name__, static_folder="static", template_folder="templates")
lndhub_ext: Blueprint = Blueprint(
"lndhub", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -13,11 +13,15 @@ def check_wallet(requires_admin=False):
key_type, key = b64decode(token).decode("utf-8").split(":")
if requires_admin and key_type != "admin":
return jsonify({"error": True, "code": 2, "message": "insufficient permissions"})
return jsonify(
{"error": True, "code": 2, "message": "insufficient permissions"}
)
g.wallet = await get_wallet_for_key(key, key_type)
if not g.wallet:
return jsonify({"error": True, "code": 2, "message": "insufficient permissions"})
return jsonify(
{"error": True, "code": 2, "message": "insufficient permissions"}
)
return await view(**kwargs)
return wrapped_view

View File

@ -23,14 +23,20 @@ async def lndhub_getinfo():
schema={
"login": {"type": "string", "required": True, "excludes": "refresh_token"},
"password": {"type": "string", "required": True, "excludes": "refresh_token"},
"refresh_token": {"type": "string", "required": True, "excludes": ["login", "password"]},
"refresh_token": {
"type": "string",
"required": True,
"excludes": ["login", "password"],
},
}
)
async def lndhub_auth():
token = (
g.data["refresh_token"]
if "refresh_token" in g.data and g.data["refresh_token"]
else urlsafe_b64encode((g.data["login"] + ":" + g.data["password"]).encode("utf-8")).decode("ascii")
else urlsafe_b64encode(
(g.data["login"] + ":" + g.data["password"]).encode("utf-8")
).decode("ascii")
)
return jsonify({"refresh_token": token, "access_token": token})
@ -120,9 +126,15 @@ async def lndhub_balance():
@check_wallet()
async def lndhub_gettxs():
for payment in await g.wallet.get_payments(
complete=False, pending=True, outgoing=True, incoming=False, exclude_uncheckable=True
complete=False,
pending=True,
outgoing=True,
incoming=False,
exclude_uncheckable=True,
):
await payment.set_pending(WALLET.get_payment_status(payment.checking_id).pending)
await payment.set_pending(
WALLET.get_payment_status(payment.checking_id).pending
)
limit = int(request.args.get("limit", 200))
return jsonify(
@ -135,10 +147,16 @@ async def lndhub_gettxs():
"fee": payment.fee,
"value": int(payment.amount / 1000),
"timestamp": payment.time,
"memo": payment.memo if not payment.pending else "Payment in transition",
"memo": payment.memo
if not payment.pending
else "Payment in transition",
}
for payment in reversed(
(await g.wallet.get_payments(pending=True, complete=True, outgoing=True, incoming=False))[:limit]
(
await g.wallet.get_payments(
pending=True, complete=True, outgoing=True, incoming=False
)
)[:limit]
)
]
)
@ -149,9 +167,15 @@ async def lndhub_gettxs():
async def lndhub_getuserinvoices():
await delete_expired_invoices()
for invoice in await g.wallet.get_payments(
complete=False, pending=True, outgoing=False, incoming=True, exclude_uncheckable=True
complete=False,
pending=True,
outgoing=False,
incoming=True,
exclude_uncheckable=True,
):
await invoice.set_pending(WALLET.get_invoice_status(invoice.checking_id).pending)
await invoice.set_pending(
WALLET.get_invoice_status(invoice.checking_id).pending
)
limit = int(request.args.get("limit", 200))
return jsonify(
@ -169,7 +193,11 @@ async def lndhub_getuserinvoices():
"type": "user_invoice",
}
for invoice in reversed(
(await g.wallet.get_payments(pending=True, complete=True, incoming=True, outgoing=False))[:limit]
(
await g.wallet.get_payments(
pending=True, complete=True, incoming=True, outgoing=False
)
)[:limit]
)
]
)

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_lnticket")
lnticket_ext: Blueprint = Blueprint("lnticket", __name__, static_folder="static", template_folder="templates")
lnticket_ext: Blueprint = Blueprint(
"lnticket", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -60,7 +60,12 @@ async def set_ticket_paid(payment_hash: str) -> Tickets:
try:
r = await client.post(
formdata.webhook,
json={"form": ticket.form, "name": ticket.name, "email": ticket.email, "content": ticket.ltext},
json={
"form": ticket.form,
"name": ticket.name,
"email": ticket.email,
"content": ticket.ltext,
},
timeout=40,
)
except AssertionError:
@ -80,7 +85,9 @@ async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM ticket WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM ticket WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Tickets(**row) for row in rows]
@ -93,7 +100,12 @@ async def delete_ticket(ticket_id: str) -> None:
async def create_form(
*, wallet: str, name: str, webhook: Optional[str] = None, description: str, costpword: int
*,
wallet: str,
name: str,
webhook: Optional[str] = None,
description: str,
costpword: int,
) -> Forms:
form_id = urlsafe_short_hash()
await db.execute(
@ -127,7 +139,9 @@ async def get_forms(wallet_ids: Union[str, List[str]]) -> List[Forms]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM form WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM form WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Forms(**row) for row in rows]

View File

@ -32,7 +32,10 @@ async def api_forms():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([form._asdict() for form in await get_forms(wallet_ids)]), HTTPStatus.OK
return (
jsonify([form._asdict() for form in await get_forms(wallet_ids)]),
HTTPStatus.OK,
)
@lnticket_ext.route("/api/v1/forms", methods=["POST"])
@ -90,7 +93,10 @@ async def api_tickets():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([form._asdict() for form in await get_tickets(wallet_ids)]), HTTPStatus.OK
return (
jsonify([form._asdict() for form in await get_tickets(wallet_ids)]),
HTTPStatus.OK,
)
@lnticket_ext.route("/api/v1/tickets/<form_id>", methods=["POST"])
@ -117,12 +123,20 @@ async def api_ticket_make_ticket(form_id):
extra={"tag": "lnticket"},
)
ticket = await create_ticket(payment_hash=payment_hash, wallet=form.wallet, **g.data)
ticket = await create_ticket(
payment_hash=payment_hash, wallet=form.wallet, **g.data
)
if not ticket:
return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "LNTicket could not be fetched."}),
HTTPStatus.NOT_FOUND,
)
return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
HTTPStatus.OK,
)
@lnticket_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_lnurlp")
lnurlp_ext: Blueprint = Blueprint("lnurlp", __name__, static_folder="static", template_folder="templates")
lnurlp_ext: Blueprint = Blueprint(
"lnurlp", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -74,14 +74,18 @@ async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]:
async def update_pay_link(link_id: int, **kwargs) -> Optional[PayLink]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id))
await db.execute(
f"UPDATE pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)
)
row = await db.fetchone("SELECT * FROM pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None
async def increment_pay_link(link_id: int, **kwargs) -> Optional[PayLink]:
q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()])
await db.execute(f"UPDATE pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id))
await db.execute(
f"UPDATE pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)
)
row = await db.fetchone("SELECT * FROM pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None

View File

@ -15,7 +15,10 @@ from .crud import increment_pay_link
async def api_lnurl_response(link_id):
link = await increment_pay_link(link_id, served_meta=1)
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}),
HTTPStatus.OK,
)
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
resp = LnurlPayResponse(
@ -36,7 +39,10 @@ async def api_lnurl_response(link_id):
async def api_lnurl_callback(link_id):
link = await increment_pay_link(link_id, served_pr=1)
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}),
HTTPStatus.OK,
)
min, max = link.min, link.max
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
@ -51,12 +57,20 @@ async def api_lnurl_callback(link_id):
amount_received = int(request.args.get("amount"))
if amount_received < min:
return (
jsonify(LnurlErrorResponse(reason=f"Amount {amount_received} is smaller than minimum {min}.").dict()),
jsonify(
LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}."
).dict()
),
HTTPStatus.OK,
)
elif amount_received > max:
return (
jsonify(LnurlErrorResponse(reason=f"Amount {amount_received} is greater than maximum {max}.").dict()),
jsonify(
LnurlErrorResponse(
reason=f"Amount {amount_received} is greater than maximum {max}."
).dict()
),
HTTPStatus.OK,
)
@ -75,7 +89,9 @@ async def api_lnurl_callback(link_id):
wallet_id=link.wallet,
amount=int(amount_received / 1000),
memo=link.description,
description_hash=hashlib.sha256(link.lnurlpay_metadata.encode("utf-8")).digest(),
description_hash=hashlib.sha256(
link.lnurlpay_metadata.encode("utf-8")
).digest(),
extra={"tag": "lnurlp", "link": link.id, "comment": comment},
)

View File

@ -40,8 +40,12 @@ async def m003_min_max_comment_fiat(db):
Support for min/max amounts, comments and fiat prices that get
converted automatically to satoshis based on some API.
"""
await db.execute("ALTER TABLE pay_links ADD COLUMN currency TEXT;") # null = satoshis
await db.execute("ALTER TABLE pay_links ADD COLUMN comment_chars INTEGER DEFAULT 0;")
await db.execute(
"ALTER TABLE pay_links ADD COLUMN currency TEXT;"
) # null = satoshis
await db.execute(
"ALTER TABLE pay_links ADD COLUMN comment_chars INTEGER DEFAULT 0;"
)
await db.execute("ALTER TABLE pay_links RENAME COLUMN amount TO min;")
await db.execute("ALTER TABLE pay_links ADD COLUMN max INTEGER;")
await db.execute("UPDATE pay_links SET max = min;")

View File

@ -31,12 +31,21 @@ async def api_links():
try:
return (
jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in await get_pay_links(wallet_ids)]),
jsonify(
[
{**link._asdict(), **{"lnurl": link.lnurl}}
for link in await get_pay_links(wallet_ids)
]
),
HTTPStatus.OK,
)
except LnurlInvalidUrl:
return (
jsonify({"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."}),
jsonify(
{
"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."
}
),
HTTPStatus.UPGRADE_REQUIRED,
)
@ -83,7 +92,10 @@ async def api_link_create_or_update(link_id=None):
link = await get_pay_link(link_id)
if not link:
return jsonify({"message": "Pay link does not exist."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Pay link does not exist."}),
HTTPStatus.NOT_FOUND,
)
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your pay link."}), HTTPStatus.FORBIDDEN
@ -92,7 +104,10 @@ async def api_link_create_or_update(link_id=None):
else:
link = await create_pay_link(wallet_id=g.wallet.id, **g.data)
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED
return (
jsonify({**link._asdict(), **{"lnurl": link.lnurl}}),
HTTPStatus.OK if link_id else HTTPStatus.CREATED,
)
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])

View File

@ -4,7 +4,9 @@ from lnbits.db import Database
db = Database("ext_offlineshop")
offlineshop_ext: Blueprint = Blueprint("offlineshop", __name__, static_folder="static", template_folder="templates")
offlineshop_ext: Blueprint = Blueprint(
"offlineshop", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -18,7 +18,11 @@ async def lnurl_response(item_id):
if not item.enabled:
return jsonify({"status": "ERROR", "reason": "Item disabled."})
price_msat = (await fiat_amount_as_satoshis(item.price, item.unit) if item.unit != "sat" else item.price) * 1000
price_msat = (
await fiat_amount_as_satoshis(item.price, item.unit)
if item.unit != "sat"
else item.price
) * 1000
resp = LnurlPayResponse(
callback=url_for("offlineshop.lnurl_callback", item_id=item.id, _external=True),
@ -47,16 +51,26 @@ async def lnurl_callback(item_id):
amount_received = int(request.args.get("amount"))
if amount_received < min:
return jsonify(LnurlErrorResponse(reason=f"Amount {amount_received} is smaller than minimum {min}.").dict())
return jsonify(
LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}."
).dict()
)
elif amount_received > max:
return jsonify(LnurlErrorResponse(reason=f"Amount {amount_received} is greater than maximum {max}.").dict())
return jsonify(
LnurlErrorResponse(
reason=f"Amount {amount_received} is greater than maximum {max}."
).dict()
)
shop = await get_shop(item.shop)
payment_hash, payment_request = await create_invoice(
wallet_id=shop.wallet,
amount=int(amount_received / 1000),
memo=item.name,
description_hash=hashlib.sha256((await item.lnurlpay_metadata()).encode("utf-8")).digest(),
description_hash=hashlib.sha256(
(await item.lnurlpay_metadata()).encode("utf-8")
).digest(),
extra={"tag": "offlineshop", "item": item.id},
)

View File

@ -89,7 +89,9 @@ class Item(NamedTuple):
@property
def lnurl(self) -> str:
return lnurl_encode(url_for("offlineshop.lnurl_response", item_id=self.id, _external=True))
return lnurl_encode(
url_for("offlineshop.lnurl_response", item_id=self.id, _external=True)
)
def values(self):
values = self._asdict()
@ -104,11 +106,15 @@ class Item(NamedTuple):
return LnurlPayMetadata(json.dumps(metadata))
def success_action(self, shop: Shop, payment_hash: str) -> Optional[LnurlPaySuccessAction]:
def success_action(
self, shop: Shop, payment_hash: str
) -> Optional[LnurlPaySuccessAction]:
if not shop.wordlist:
return None
return UrlAction(
url=url_for("offlineshop.confirmation_code", p=payment_hash, _external=True),
url=url_for(
"offlineshop.confirmation_code", p=payment_hash, _external=True
),
description="Open to get the confirmation code for your purchase.",
)

View File

@ -24,7 +24,13 @@ async def print_qr_codes():
for item_id in request.args.get("items").split(","):
item = await get_item(item_id)
if item:
items.append({"lnurl": item.lnurl, "name": item.name, "price": f"{item.price} {item.unit}"})
items.append(
{
"lnurl": item.lnurl,
"name": item.name,
"price": f"{item.price} {item.unit}",
}
)
return await render_template("offlineshop/print.html", items=items)
@ -36,10 +42,14 @@ async def confirmation_code():
payment_hash = request.args.get("p")
payment: Payment = await get_standalone_payment(payment_hash)
if not payment:
return f"Couldn't find the payment {payment_hash}." + style, HTTPStatus.NOT_FOUND
return (
f"Couldn't find the payment {payment_hash}." + style,
HTTPStatus.NOT_FOUND,
)
if payment.pending:
return (
f"Payment {payment_hash} wasn't received yet. Please try again in a minute." + style,
f"Payment {payment_hash} wasn't received yet. Please try again in a minute."
+ style,
HTTPStatus.PAYMENT_REQUIRED,
)

View File

@ -43,7 +43,11 @@ async def api_shop_from_wallet():
)
except LnurlInvalidUrl:
return (
jsonify({"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."}),
jsonify(
{
"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."
}
),
HTTPStatus.UPGRADE_REQUIRED,
)
@ -98,7 +102,12 @@ async def api_delete_item(item_id):
@api_validate_post_request(
schema={
"method": {"type": "string", "required": True, "nullable": False},
"wordlist": {"type": "string", "empty": True, "nullable": True, "required": False},
"wordlist": {
"type": "string",
"empty": True,
"nullable": True,
"required": False,
},
}
)
async def api_set_method():

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_paywall")
paywall_ext: Blueprint = Blueprint("paywall", __name__, static_folder="static", template_folder="templates")
paywall_ext: Blueprint = Blueprint(
"paywall", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -7,7 +7,13 @@ from .models import Paywall
async def create_paywall(
*, wallet_id: str, url: str, memo: str, description: Optional[str] = None, amount: int = 0, remembers: bool = True
*,
wallet_id: str,
url: str,
memo: str,
description: Optional[str] = None,
amount: int = 0,
remembers: bool = True,
) -> Paywall:
paywall_id = urlsafe_short_hash()
await db.execute(
@ -34,7 +40,9 @@ async def get_paywalls(wallet_ids: Union[str, List[str]]) -> List[Paywall]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM paywalls WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM paywalls WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Paywall.from_row(row) for row in rows]

View File

@ -46,7 +46,9 @@ async def m002_redux(db):
)
await db.execute("CREATE INDEX IF NOT EXISTS wallet_idx ON paywalls (wallet)")
for row in [list(row) for row in await db.fetchall("SELECT * FROM paywalls_old")]:
for row in [
list(row) for row in await db.fetchall("SELECT * FROM paywalls_old")
]:
await db.execute(
"""
INSERT INTO paywalls (

View File

@ -16,5 +16,7 @@ async def index():
@paywall_ext.route("/<paywall_id>")
async def display(paywall_id):
paywall = await get_paywall(paywall_id) or abort(HTTPStatus.NOT_FOUND, "Paywall does not exist.")
paywall = await get_paywall(paywall_id) or abort(
HTTPStatus.NOT_FOUND, "Paywall does not exist."
)
return await render_template("paywall/display.html", paywall=paywall)

View File

@ -17,7 +17,10 @@ async def api_paywalls():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([paywall._asdict() for paywall in await get_paywalls(wallet_ids)]), HTTPStatus.OK
return (
jsonify([paywall._asdict() for paywall in await get_paywalls(wallet_ids)]),
HTTPStatus.OK,
)
@paywall_ext.route("/api/v1/paywalls", methods=["POST"])
@ -26,7 +29,12 @@ async def api_paywalls():
schema={
"url": {"type": "string", "empty": False, "required": True},
"memo": {"type": "string", "empty": False, "required": True},
"description": {"type": "string", "empty": True, "nullable": True, "required": False},
"description": {
"type": "string",
"empty": True,
"nullable": True,
"required": False,
},
"amount": {"type": "integer", "min": 0, "required": True},
"remembers": {"type": "boolean", "required": True},
}
@ -53,26 +61,41 @@ async def api_paywall_delete(paywall_id):
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/invoice", methods=["POST"])
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
@api_validate_post_request(
schema={"amount": {"type": "integer", "min": 1, "required": True}}
)
async def api_paywall_create_invoice(paywall_id):
paywall = await get_paywall(paywall_id)
if g.data["amount"] < paywall.amount:
return jsonify({"message": f"Minimum amount is {paywall.amount} sat."}), HTTPStatus.BAD_REQUEST
return (
jsonify({"message": f"Minimum amount is {paywall.amount} sat."}),
HTTPStatus.BAD_REQUEST,
)
try:
amount = g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount
amount = (
g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount
)
payment_hash, payment_request = await create_invoice(
wallet_id=paywall.wallet, amount=amount, memo=f"{paywall.memo}", extra={"tag": "paywall"}
wallet_id=paywall.wallet,
amount=amount,
memo=f"{paywall.memo}",
extra={"tag": "paywall"},
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
HTTPStatus.CREATED,
)
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_invoice", methods=["POST"])
@api_validate_post_request(schema={"payment_hash": {"type": "string", "empty": False, "required": True}})
@api_validate_post_request(
schema={"payment_hash": {"type": "string", "empty": False, "required": True}}
)
async def api_paywal_check_invoice(paywall_id):
paywall = await get_paywall(paywall_id)
@ -90,6 +113,9 @@ async def api_paywal_check_invoice(paywall_id):
payment = await wallet.get_payment(g.data["payment_hash"])
await payment.set_pending(False)
return jsonify({"paid": True, "url": paywall.url, "remembers": paywall.remembers}), HTTPStatus.OK
return (
jsonify({"paid": True, "url": paywall.url, "remembers": paywall.remembers}),
HTTPStatus.OK,
)
return jsonify({"paid": False}), HTTPStatus.OK

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_subdomains")
subdomains_ext: Blueprint = Blueprint("subdomains", __name__, static_folder="static", template_folder="templates")
subdomains_ext: Blueprint = Blueprint(
"subdomains", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -2,11 +2,20 @@ from lnbits.extensions.subdomains.models import Domains
import httpx, json
async def cloudflare_create_subdomain(domain: Domains, subdomain: str, record_type: str, ip: str):
async def cloudflare_create_subdomain(
domain: Domains, subdomain: str, record_type: str, ip: str
):
# Call to cloudflare sort of a dry-run - if success delete the domain and wait for payment
### SEND REQUEST TO CLOUDFLARE
url = "https://api.cloudflare.com/client/v4/zones/" + domain.cf_zone_id + "/dns_records"
header = {"Authorization": "Bearer " + domain.cf_token, "Content-Type": "application/json"}
url = (
"https://api.cloudflare.com/client/v4/zones/"
+ domain.cf_zone_id
+ "/dns_records"
)
header = {
"Authorization": "Bearer " + domain.cf_token,
"Content-Type": "application/json",
}
aRecord = subdomain + "." + domain.domain
cf_response = ""
async with httpx.AsyncClient() as client:
@ -30,8 +39,15 @@ async def cloudflare_create_subdomain(domain: Domains, subdomain: str, record_ty
async def cloudflare_deletesubdomain(domain: Domains, domain_id: str):
url = "https://api.cloudflare.com/client/v4/zones/" + domain.cf_zone_id + "/dns_records"
header = {"Authorization": "Bearer " + domain.cf_token, "Content-Type": "application/json"}
url = (
"https://api.cloudflare.com/client/v4/zones/"
+ domain.cf_zone_id
+ "/dns_records"
)
header = {
"Authorization": "Bearer " + domain.cf_token,
"Content-Type": "application/json",
}
async with httpx.AsyncClient() as client:
try:
r = await client.delete(

View File

@ -23,7 +23,18 @@ async def create_subdomain(
INSERT INTO subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid, record_type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(payment_hash, domain, email, subdomain, ip, wallet, sats, duration, False, record_type),
(
payment_hash,
domain,
email,
subdomain,
ip,
wallet,
sats,
duration,
False,
record_type,
),
)
subdomain = await get_subdomain(payment_hash)
@ -118,7 +129,18 @@ async def create_domain(
INSERT INTO domain (id, wallet, domain, webhook, cf_token, cf_zone_id, description, cost, amountmade, allowed_record_types)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(domain_id, wallet, domain, webhook, cf_token, cf_zone_id, description, cost, 0, allowed_record_types),
(
domain_id,
wallet,
domain,
webhook,
cf_token,
cf_zone_id,
description,
cost,
0,
allowed_record_types,
),
)
domain = await get_domain(domain_id)
@ -128,7 +150,9 @@ async def create_domain(
async def update_domain(domain_id: str, **kwargs) -> Domains:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE domain SET {q} WHERE id = ?", (*kwargs.values(), domain_id))
await db.execute(
f"UPDATE domain SET {q} WHERE id = ?", (*kwargs.values(), domain_id)
)
row = await db.fetchone("SELECT * FROM domain WHERE id = ?", (domain_id,))
assert row, "Newly updated domain couldn't be retrieved"
return Domains(**row)
@ -144,7 +168,9 @@ async def get_domains(wallet_ids: Union[str, List[str]]) -> List[Domains]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM domain WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM domain WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Domains(**row) for row in rows]

View File

@ -33,7 +33,10 @@ async def on_invoice_paid(payment: Payment) -> None:
### Create subdomain
cf_response = cloudflare_create_subdomain(
domain=domain, subdomain=subdomain.subdomain, record_type=subdomain.record_type, ip=subdomain.ip
domain=domain,
subdomain=subdomain.subdomain,
record_type=subdomain.record_type,
ip=subdomain.ip,
)
### Use webhook to notify about cloudflare registration

View File

@ -19,7 +19,9 @@ async def display(domain_id):
domain = await get_domain(domain_id)
if not domain:
abort(HTTPStatus.NOT_FOUND, "Domain does not exist.")
allowed_records = domain.allowed_record_types.replace('"', "").replace(" ", "").split(",")
allowed_records = (
domain.allowed_record_types.replace('"', "").replace(" ", "").split(",")
)
print(allowed_records)
return await render_template(
"subdomains/display.html",

View File

@ -37,7 +37,10 @@ async def api_domains():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([domain._asdict() for domain in await get_domains(wallet_ids)]), HTTPStatus.OK
return (
jsonify([domain._asdict() for domain in await get_domains(wallet_ids)]),
HTTPStatus.OK,
)
@subdomains_ext.route("/api/v1/domains", methods=["POST"])
@ -98,7 +101,10 @@ async def api_subdomains():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([domain._asdict() for domain in await get_subdomains(wallet_ids)]), HTTPStatus.OK
return (
jsonify([domain._asdict() for domain in await get_subdomains(wallet_ids)]),
HTTPStatus.OK,
)
@subdomains_ext.route("/api/v1/subdomains/<domain_id>", methods=["POST"])
@ -122,24 +128,42 @@ async def api_subdomain_make_subdomain(domain_id):
## If record_type is not one of the allowed ones reject the request
if g.data["record_type"] not in domain.allowed_record_types:
return jsonify({"message": g.data["record_type"] + "Not a valid record"}), HTTPStatus.BAD_REQUEST
return (
jsonify({"message": g.data["record_type"] + "Not a valid record"}),
HTTPStatus.BAD_REQUEST,
)
## If domain already exist in our database reject it
if await get_subdomainBySubdomain(g.data["subdomain"]) is not None:
return (
jsonify({"message": g.data["subdomain"] + "." + domain.domain + " domain already taken"}),
jsonify(
{
"message": g.data["subdomain"]
+ "."
+ domain.domain
+ " domain already taken"
}
),
HTTPStatus.BAD_REQUEST,
)
## Dry run cloudflare... (create and if create is sucessful delete it)
cf_response = await cloudflare_create_subdomain(
domain=domain, subdomain=g.data["subdomain"], record_type=g.data["record_type"], ip=g.data["ip"]
domain=domain,
subdomain=g.data["subdomain"],
record_type=g.data["record_type"],
ip=g.data["ip"],
)
if cf_response["success"] == True:
cloudflare_deletesubdomain(domain=domain, domain_id=cf_response["result"]["id"])
else:
return (
jsonify({"message": "Problem with cloudflare: " + cf_response["errors"][0]["message"]}),
jsonify(
{
"message": "Problem with cloudflare: "
+ cf_response["errors"][0]["message"]
}
),
HTTPStatus.BAD_REQUEST,
)
@ -152,12 +176,20 @@ async def api_subdomain_make_subdomain(domain_id):
extra={"tag": "lnsubdomain"},
)
subdomain = await create_subdomain(payment_hash=payment_hash, wallet=domain.wallet, **g.data)
subdomain = await create_subdomain(
payment_hash=payment_hash, wallet=domain.wallet, **g.data
)
if not subdomain:
return jsonify({"message": "LNsubdomain could not be fetched."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "LNsubdomain could not be fetched."}),
HTTPStatus.NOT_FOUND,
)
return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
HTTPStatus.OK,
)
@subdomains_ext.route("/api/v1/subdomains/<payment_hash>", methods=["GET"])

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_tpos")
tpos_ext: Blueprint = Blueprint("tpos", __name__, static_folder="static", template_folder="templates")
tpos_ext: Blueprint = Blueprint(
"tpos", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -31,7 +31,9 @@ async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]:
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM tposs WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM tposs WHERE wallet IN ({q})", (*wallet_ids,)
)
return [TPoS.from_row(row) for row in rows]

View File

@ -16,7 +16,10 @@ async def api_tposs():
if "all_wallets" in request.args:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return jsonify([tpos._asdict() for tpos in await get_tposs(wallet_ids)]), HTTPStatus.OK
return (
jsonify([tpos._asdict() for tpos in await get_tposs(wallet_ids)]),
HTTPStatus.OK,
)
@tpos_ext.route("/api/v1/tposs", methods=["POST"])
@ -49,7 +52,9 @@ async def api_tpos_delete(tpos_id):
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/", methods=["POST"])
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
@api_validate_post_request(
schema={"amount": {"type": "integer", "min": 1, "required": True}}
)
async def api_tpos_create_invoice(tpos_id):
tpos = await get_tpos(tpos_id)
@ -58,12 +63,18 @@ async def api_tpos_create_invoice(tpos_id):
try:
payment_hash, payment_request = await create_invoice(
wallet_id=tpos.wallet, amount=g.data["amount"], memo=f"{tpos.name}", extra={"tag": "tpos"}
wallet_id=tpos.wallet,
amount=g.data["amount"],
memo=f"{tpos.name}",
extra={"tag": "tpos"},
)
except Exception as e:
return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR
return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED
return (
jsonify({"payment_hash": payment_hash, "payment_request": payment_request}),
HTTPStatus.CREATED,
)
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/<payment_hash>", methods=["GET"])

View File

@ -3,7 +3,9 @@ from lnbits.db import Database
db = Database("ext_usermanager")
usermanager_ext: Blueprint = Blueprint("usermanager", __name__, static_folder="static", template_folder="templates")
usermanager_ext: Blueprint = Blueprint(
"usermanager", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -16,7 +16,9 @@ from .models import Users, Wallets
### Users
async def create_usermanager_user(user_name: str, wallet_name: str, admin_id: str) -> Users:
async def create_usermanager_user(
user_name: str, wallet_name: str, admin_id: str
) -> Users:
account = await create_account()
user = await get_user(account.id)
assert user, "Newly created user couldn't be retrieved"
@ -66,7 +68,9 @@ async def delete_usermanager_user(user_id: str) -> None:
### Wallets
async def create_usermanager_wallet(user_id: str, wallet_name: str, admin_id: str) -> Wallets:
async def create_usermanager_wallet(
user_id: str, wallet_name: str, admin_id: str
) -> Wallets:
wallet = await create_wallet(user_id=user_id, wallet_name=wallet_name)
await db.execute(
"""
@ -91,7 +95,9 @@ async def get_usermanager_wallets(user_id: str) -> List[Wallets]:
async def get_usermanager_wallet_transactions(wallet_id: str) -> List[Payment]:
return await get_payments(wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True)
return await get_payments(
wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True
)
async def delete_usermanager_wallet(wallet_id: str, user_id: str) -> None:

View File

@ -26,7 +26,10 @@ from lnbits.core import update_user_extension
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_users():
user_id = g.wallet.user
return jsonify([user._asdict() for user in await get_usermanager_users(user_id)]), HTTPStatus.OK
return (
jsonify([user._asdict() for user in await get_usermanager_users(user_id)]),
HTTPStatus.OK,
)
@usermanager_ext.route("/api/v1/users", methods=["POST"])
@ -39,7 +42,9 @@ async def api_usermanager_users():
}
)
async def api_usermanager_users_create():
user = await create_usermanager_user(g.data["user_name"], g.data["wallet_name"], g.data["admin_id"])
user = await create_usermanager_user(
g.data["user_name"], g.data["wallet_name"], g.data["admin_id"]
)
return jsonify(user._asdict()), HTTPStatus.CREATED
@ -69,7 +74,9 @@ async def api_usermanager_activate_extension():
user = await get_user(g.data["userid"])
if not user:
return jsonify({"message": "no such user"}), HTTPStatus.NOT_FOUND
update_user_extension(user_id=g.data["userid"], extension=g.data["extension"], active=g.data["active"])
update_user_extension(
user_id=g.data["userid"], extension=g.data["extension"], active=g.data["active"]
)
return jsonify({"extension": "updated"}), HTTPStatus.CREATED
@ -80,7 +87,12 @@ async def api_usermanager_activate_extension():
@api_check_wallet_key(key_type="invoice")
async def api_usermanager_wallets():
user_id = g.wallet.user
return jsonify([wallet._asdict() for wallet in await get_usermanager_wallets(user_id)]), HTTPStatus.OK
return (
jsonify(
[wallet._asdict() for wallet in await get_usermanager_wallets(user_id)]
),
HTTPStatus.OK,
)
@usermanager_ext.route("/api/v1/wallets", methods=["POST"])
@ -93,7 +105,9 @@ async def api_usermanager_wallets():
}
)
async def api_usermanager_wallets_create():
user = await create_usermanager_wallet(g.data["user_id"], g.data["wallet_name"], g.data["admin_id"])
user = await create_usermanager_wallet(
g.data["user_id"], g.data["wallet_name"], g.data["admin_id"]
)
return jsonify(user._asdict()), HTTPStatus.CREATED

View File

@ -4,7 +4,9 @@ from lnbits.db import Database
db = Database("ext_withdraw")
withdraw_ext: Blueprint = Blueprint("withdraw", __name__, static_folder="static", template_folder="templates")
withdraw_ext: Blueprint = Blueprint(
"withdraw", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa

View File

@ -66,7 +66,9 @@ async def get_withdraw_link(link_id: str, num=0) -> Optional[WithdrawLink]:
async def get_withdraw_link_by_hash(unique_hash: str, num=0) -> Optional[WithdrawLink]:
row = await db.fetchone("SELECT * FROM withdraw_link WHERE unique_hash = ?", (unique_hash,))
row = await db.fetchone(
"SELECT * FROM withdraw_link WHERE unique_hash = ?", (unique_hash,)
)
link = []
for item in row:
link.append(item)
@ -79,14 +81,18 @@ async def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[Withdraw
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(f"SELECT * FROM withdraw_link WHERE wallet IN ({q})", (*wallet_ids,))
rows = await db.fetchall(
f"SELECT * FROM withdraw_link WHERE wallet IN ({q})", (*wallet_ids,)
)
return [WithdrawLink.from_row(row) for row in rows]
async def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(f"UPDATE withdraw_link SET {q} WHERE id = ?", (*kwargs.values(), link_id))
await db.execute(
f"UPDATE withdraw_link SET {q} WHERE id = ?", (*kwargs.values(), link_id)
)
row = await db.fetchone("SELECT * FROM withdraw_link WHERE id = ?", (link_id,))
return WithdrawLink.from_row(row) if row else None
@ -123,7 +129,9 @@ async def create_hash_check(
async def get_hash_check(the_hash: str, lnurl_id: str) -> Optional[HashCheck]:
rowid = await db.fetchone("SELECT * FROM hash_check WHERE id = ?", (the_hash,))
rowlnurl = await db.fetchone("SELECT * FROM hash_check WHERE lnurl_id = ?", (lnurl_id,))
rowlnurl = await db.fetchone(
"SELECT * FROM hash_check WHERE lnurl_id = ?", (lnurl_id,)
)
if not rowlnurl:
await create_hash_check(the_hash, lnurl_id)
return {"lnurl": True, "hash": False}
@ -132,4 +140,4 @@ async def get_hash_check(the_hash: str, lnurl_id: str) -> Optional[HashCheck]:
await create_hash_check(the_hash, lnurl_id)
return {"lnurl": True, "hash": False}
else:
return {"lnurl": True, "hash": True}
return {"lnurl": True, "hash": True}

View File

@ -17,10 +17,16 @@ async def api_lnurl_response(unique_hash):
link = await get_withdraw_link_by_hash(unique_hash)
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}),
HTTPStatus.OK,
)
if link.is_spent:
return jsonify({"status": "ERROR", "reason": "Withdraw is spent."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "Withdraw is spent."}),
HTTPStatus.OK,
)
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
@ -33,10 +39,16 @@ async def api_lnurl_multi_response(unique_hash, id_unique_hash):
link = await get_withdraw_link_by_hash(unique_hash)
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}),
HTTPStatus.OK,
)
if link.is_spent:
return jsonify({"status": "ERROR", "reason": "Withdraw is spent."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "Withdraw is spent."}),
HTTPStatus.OK,
)
useslist = link.usescsv.split(",")
found = False
@ -45,7 +57,10 @@ async def api_lnurl_multi_response(unique_hash, id_unique_hash):
if id_unique_hash == shortuuid.uuid(name=tohash):
found = True
if not found:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}),
HTTPStatus.OK,
)
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
@ -61,16 +76,27 @@ async def api_lnurl_callback(unique_hash):
now = int(datetime.now().timestamp())
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}),
HTTPStatus.OK,
)
if link.is_spent:
return jsonify({"status": "ERROR", "reason": "Withdraw is spent."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "Withdraw is spent."}),
HTTPStatus.OK,
)
if link.k1 != k1:
return jsonify({"status": "ERROR", "reason": "Bad request."}), HTTPStatus.OK
if now < link.open_time:
return jsonify({"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}), HTTPStatus.OK
return (
jsonify(
{"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}
),
HTTPStatus.OK,
)
try:
await pay_invoice(
@ -85,12 +111,19 @@ async def api_lnurl_callback(unique_hash):
usecv = link.usescsv.split(",")
usescsv += "," + str(usecv[x])
usescsv = usescsv[1:]
changes = {"open_time": link.wait_time + now, "used": link.used + 1, "usescsv": usescsv}
changes = {
"open_time": link.wait_time + now,
"used": link.used + 1,
"usescsv": usescsv,
}
await update_withdraw_link(link.id, **changes)
except ValueError as e:
return jsonify({"status": "ERROR", "reason": str(e)}), HTTPStatus.OK
except PermissionError:
return jsonify({"status": "ERROR", "reason": "Withdraw link is empty."}), HTTPStatus.OK
return (
jsonify({"status": "ERROR", "reason": "Withdraw link is empty."}),
HTTPStatus.OK,
)
return jsonify({"status": "OK"}), HTTPStatus.OK

View File

@ -47,7 +47,9 @@ async def m002_change_withdraw_table(db):
"""
)
await db.execute("CREATE INDEX IF NOT EXISTS wallet_idx ON withdraw_link (wallet)")
await db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hash_idx ON withdraw_link (unique_hash)")
await db.execute(
"CREATE UNIQUE INDEX IF NOT EXISTS unique_hash_idx ON withdraw_link (unique_hash)"
)
for row in [list(row) for row in await db.fetchall("SELECT * FROM withdraw_links")]:
usescsv = ""
@ -107,4 +109,4 @@ async def m003_make_hash_check(db):
lnurl_id TEXT
);
"""
)
)

View File

@ -45,13 +45,19 @@ class WithdrawLink(NamedTuple):
_external=True,
)
else:
url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True)
url = url_for(
"withdraw.api_lnurl_response",
unique_hash=self.unique_hash,
_external=True,
)
return lnurl_encode(url)
@property
def lnurl_response(self) -> LnurlWithdrawResponse:
url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True)
url = url_for(
"withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True
)
return LnurlWithdrawResponse(
callback=url,
k1=self.k1,
@ -67,4 +73,4 @@ class HashCheck(NamedTuple):
@classmethod
def from_row(cls, row: Row) -> "Hash":
return cls(**dict(row))
return cls(**dict(row))

View File

@ -17,13 +17,17 @@ async def index():
@withdraw_ext.route("/<link_id>")
async def display(link_id):
link = await get_withdraw_link(link_id, 0) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
link = await get_withdraw_link(link_id, 0) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
return await render_template("withdraw/display.html", link=link, unique=True)
@withdraw_ext.route("/img/<link_id>")
async def img(link_id):
link = await get_withdraw_link(link_id, 0) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
link = await get_withdraw_link(link_id, 0) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
qr = pyqrcode.create(link.lnurl)
stream = BytesIO()
qr.svg(stream, scale=3)
@ -41,13 +45,17 @@ async def img(link_id):
@withdraw_ext.route("/print/<link_id>")
async def print_qr(link_id):
link = await get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
link = await get_withdraw_link(link_id) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
if link.uses == 0:
return await render_template("withdraw/print_qr.html", link=link, unique=False)
links = []
count = 0
for x in link.usescsv.split(","):
linkk = await get_withdraw_link(link_id, count) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
linkk = await get_withdraw_link(link_id, count) or abort(
HTTPStatus.NOT_FOUND, "Withdraw link does not exist."
)
links.append(str(linkk.lnurl))
count = count + 1
page_link = list(chunks(links, 2))

View File

@ -39,7 +39,11 @@ async def api_links():
)
except LnurlInvalidUrl:
return (
jsonify({"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."}),
jsonify(
{
"message": "LNURLs need to be delivered over a publically accessible `https` domain or Tor."
}
),
HTTPStatus.UPGRADE_REQUIRED,
)
@ -50,7 +54,10 @@ async def api_link_retrieve(link_id):
link = await get_withdraw_link(link_id, 0)
if not link:
return jsonify({"message": "Withdraw link does not exist."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Withdraw link does not exist."}),
HTTPStatus.NOT_FOUND,
)
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN
@ -74,7 +81,11 @@ async def api_link_retrieve(link_id):
async def api_link_create_or_update(link_id=None):
if g.data["max_withdrawable"] < g.data["min_withdrawable"]:
return (
jsonify({"message": "`max_withdrawable` needs to be at least `min_withdrawable`."}),
jsonify(
{
"message": "`max_withdrawable` needs to be at least `min_withdrawable`."
}
),
HTTPStatus.BAD_REQUEST,
)
@ -89,14 +100,22 @@ async def api_link_create_or_update(link_id=None):
if link_id:
link = await get_withdraw_link(link_id, 0)
if not link:
return jsonify({"message": "Withdraw link does not exist."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Withdraw link does not exist."}),
HTTPStatus.NOT_FOUND,
)
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN
link = await update_withdraw_link(link_id, **g.data, usescsv=usescsv, used=0)
else:
link = await create_withdraw_link(wallet_id=g.wallet.id, **g.data, usescsv=usescsv)
link = await create_withdraw_link(
wallet_id=g.wallet.id, **g.data, usescsv=usescsv
)
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED
return (
jsonify({**link._asdict(), **{"lnurl": link.lnurl}}),
HTTPStatus.OK if link_id else HTTPStatus.CREATED,
)
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
@ -105,7 +124,10 @@ async def api_link_delete(link_id):
link = await get_withdraw_link(link_id)
if not link:
return jsonify({"message": "Withdraw link does not exist."}), HTTPStatus.NOT_FOUND
return (
jsonify({"message": "Withdraw link does not exist."}),
HTTPStatus.NOT_FOUND,
)
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN
@ -119,4 +141,4 @@ async def api_link_delete(link_id):
@api_check_wallet_key("invoice")
async def api_hash_retrieve(the_hash, lnurl_id):
hashCheck = await get_hash_check(the_hash, lnurl_id)
return jsonify(hashCheck), HTTPStatus.OK
return jsonify(hashCheck), HTTPStatus.OK

View File

@ -20,15 +20,21 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
self._disabled: List[str] = LNBITS_DISABLED_EXTENSIONS
self._extension_folders: List[str] = [x[1] for x in os.walk(os.path.join(LNBITS_PATH, "extensions"))][0]
self._extension_folders: List[str] = [
x[1] for x in os.walk(os.path.join(LNBITS_PATH, "extensions"))
][0]
@property
def extensions(self) -> List[Extension]:
output = []
for extension in [ext for ext in self._extension_folders if ext not in self._disabled]:
for extension in [
ext for ext in self._extension_folders if ext not in self._disabled
]:
try:
with open(os.path.join(LNBITS_PATH, "extensions", extension, "config.json")) as json_file:
with open(
os.path.join(LNBITS_PATH, "extensions", extension, "config.json")
) as json_file:
config = json.load(json_file)
is_valid = True
except Exception:
@ -50,7 +56,9 @@ class ExtensionManager:
def get_valid_extensions() -> List[Extension]:
return [extension for extension in ExtensionManager().extensions if extension.is_valid]
return [
extension for extension in ExtensionManager().extensions if extension.is_valid
]
def urlsafe_short_hash() -> str:
@ -91,7 +99,9 @@ def get_css_vendored(prefer_minified: bool = False) -> List[str]:
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
paths: List[str] = []
for path in glob.glob(os.path.join(LNBITS_PATH, "static/vendor/**"), recursive=True):
for path in glob.glob(
os.path.join(LNBITS_PATH, "static/vendor/**"), recursive=True
):
if path.endswith(".min" + ext):
# path is minified
unminified = path.replace(".min" + ext, ext)

View File

@ -10,7 +10,9 @@ env = Env()
env.read_env()
wallets_module = importlib.import_module("lnbits.wallets")
wallet_class = getattr(wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet"))
wallet_class = getattr(
wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet")
)
ENV = env.str("QUART_ENV", default="production")
DEBUG = env.bool("QUART_DEBUG", default=False) or ENV == "development"
@ -18,9 +20,15 @@ HOST = env.str("HOST", default="127.0.0.1")
PORT = env.int("PORT", default=5000)
LNBITS_PATH = path.dirname(path.realpath(__file__))
LNBITS_DATA_FOLDER = env.str("LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data"))
LNBITS_ALLOWED_USERS: List[str] = env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
LNBITS_DISABLED_EXTENSIONS: List[str] = env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str)
LNBITS_DATA_FOLDER = env.str(
"LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data")
)
LNBITS_ALLOWED_USERS: List[str] = env.list(
"LNBITS_ALLOWED_USERS", default=[], subcast=str
)
LNBITS_DISABLED_EXTENSIONS: List[str] = env.list(
"LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str
)
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits")
WALLET = wallet_class()

View File

@ -4,7 +4,11 @@ from typing import Optional, List, Callable
from quart_trio import QuartTrio
from lnbits.settings import WALLET
from lnbits.core.crud import get_payments, get_standalone_payment, delete_expired_invoices
from lnbits.core.crud import (
get_payments,
get_standalone_payment,
delete_expired_invoices,
)
main_app: Optional[QuartTrio] = None
@ -67,7 +71,9 @@ async def invoice_listener(nursery):
async def check_pending_payments():
await delete_expired_invoices()
while True:
for payment in await get_payments(complete=False, pending=True, exclude_uncheckable=True):
for payment in await get_payments(
complete=False, pending=True, exclude_uncheckable=True
):
print(" - checking pending", payment.checking_id)
await payment.check_pending()

View File

@ -212,7 +212,12 @@ exchange_rate_providers = {
async def btc_price(currency: str) -> float:
replacements = {"FROM": "BTC", "from": "btc", "TO": currency.upper(), "to": currency.lower()}
replacements = {
"FROM": "BTC",
"from": "btc",
"TO": currency.upper(),
"to": currency.lower(),
}
rates = []
send_channel, receive_channel = trio.open_memory_channel(0)

View File

@ -37,7 +37,10 @@ class Wallet(ABC):
@abstractmethod
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
pass

View File

@ -10,13 +10,22 @@ import json
from os import getenv
from typing import Optional, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
Unsupported,
)
class CLightningWallet(Wallet):
def __init__(self):
if LightningRpc is None: # pragma: nocover
raise ImportError("The `pylightning` library must be installed to use `CLightningWallet`.")
raise ImportError(
"The `pylightning` library must be installed to use `CLightningWallet`."
)
self.rpc = getenv("CLIGHTNING_RPC")
self.ln = LightningRpc(self.rpc)
@ -52,7 +61,10 @@ class CLightningWallet(Wallet):
return StatusResponse(error_message, 0)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
label = "lbl{}".format(random.random())
msat = amount * 1000

View File

@ -3,7 +3,13 @@ import httpx
from os import getenv
from typing import Optional, Dict, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
class LNbitsWallet(Wallet):
@ -12,7 +18,11 @@ class LNbitsWallet(Wallet):
def __init__(self):
self.endpoint = getenv("LNBITS_ENDPOINT")
key = getenv("LNBITS_KEY") or getenv("LNBITS_ADMIN_KEY") or getenv("LNBITS_INVOICE_KEY")
key = (
getenv("LNBITS_KEY")
or getenv("LNBITS_ADMIN_KEY")
or getenv("LNBITS_INVOICE_KEY")
)
self.key = {"X-Api-Key": key}
def status(self) -> StatusResponse:
@ -20,7 +30,9 @@ class LNbitsWallet(Wallet):
try:
data = r.json()
except:
return StatusResponse(f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0)
return StatusResponse(
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
)
if r.is_error:
return StatusResponse(data["message"], 0)
@ -28,7 +40,10 @@ class LNbitsWallet(Wallet):
return StatusResponse(None, data["balance"])
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
data: Dict = {"out": False, "amount": amount}
if description_hash:
@ -41,7 +56,12 @@ class LNbitsWallet(Wallet):
headers=self.key,
json=data,
)
ok, checking_id, payment_request, error_message = not r.is_error, None, None, None
ok, checking_id, payment_request, error_message = (
not r.is_error,
None,
None,
None,
)
if r.is_error:
error_message = r.json()["message"]
@ -52,7 +72,11 @@ class LNbitsWallet(Wallet):
return InvoiceResponse(ok, checking_id, payment_request, error_message)
def pay_invoice(self, bolt11: str) -> PaymentResponse:
r = httpx.post(url=f"{self.endpoint}/api/v1/payments", headers=self.key, json={"out": True, "bolt11": bolt11})
r = httpx.post(
url=f"{self.endpoint}/api/v1/payments",
headers=self.key,
json={"out": True, "bolt11": bolt11},
)
ok, checking_id, fee_msat, error_message = not r.is_error, None, 0, None
if r.is_error:
@ -64,7 +88,9 @@ class LNbitsWallet(Wallet):
return PaymentResponse(ok, checking_id, fee_msat, error_message)
def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = httpx.get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.key)
r = httpx.get(
url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.key
)
if r.is_error:
return PaymentStatus(None)
@ -72,7 +98,9 @@ class LNbitsWallet(Wallet):
return PaymentStatus(r.json()["paid"])
def get_payment_status(self, checking_id: str) -> PaymentStatus:
r = httpx.get(url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.key)
r = httpx.get(
url=f"{self.endpoint}/api/v1/payments/{checking_id}", headers=self.key
)
if r.is_error:
return PaymentStatus(None)

View File

@ -15,7 +15,13 @@ import hashlib
from os import getenv
from typing import Optional, Dict, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
def get_ssl_context(cert_path: str):
@ -76,10 +82,14 @@ def stringify_checking_id(r_hash: bytes) -> str:
class LndWallet(Wallet):
def __init__(self):
if lndgrpc is None: # pragma: nocover
raise ImportError("The `lndgrpc` library must be installed to use `LndWallet`.")
raise ImportError(
"The `lndgrpc` library must be installed to use `LndWallet`."
)
if purerpc is None: # pragma: nocover
raise ImportError("The `purerpc` library must be installed to use `LndWallet`.")
raise ImportError(
"The `purerpc` library must be installed to use `LndWallet`."
)
endpoint = getenv("LND_GRPC_ENDPOINT")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
@ -111,7 +121,10 @@ class LndWallet(Wallet):
return StatusResponse(None, resp.balance * 1000)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
params: Dict = {"value": amount, "expiry": 600, "private": True}

View File

@ -5,7 +5,13 @@ import base64
from os import getenv
from typing import Optional, Dict, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
class LndRestWallet(Wallet):
@ -14,7 +20,9 @@ class LndRestWallet(Wallet):
def __init__(self):
endpoint = getenv("LND_REST_ENDPOINT")
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
endpoint = "https://" + endpoint if not endpoint.startswith("http") else endpoint
endpoint = (
"https://" + endpoint if not endpoint.startswith("http") else endpoint
)
self.endpoint = endpoint
macaroon = (
@ -47,14 +55,19 @@ class LndRestWallet(Wallet):
return StatusResponse(None, int(data["balance"]) * 1000)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
data: Dict = {
"value": amount,
"private": True,
}
if description_hash:
data["description_hash"] = base64.b64encode(description_hash).decode("ascii")
data["description_hash"] = base64.b64encode(description_hash).decode(
"ascii"
)
else:
data["memo"] = memo or ""
@ -131,7 +144,12 @@ class LndRestWallet(Wallet):
# check payment.status:
# https://api.lightning.community/rest/index.html?python#peersynctype
statuses = {"UNKNOWN": None, "IN_FLIGHT": None, "SUCCEEDED": True, "FAILED": False}
statuses = {
"UNKNOWN": None,
"IN_FLIGHT": None,
"SUCCEEDED": True,
"FAILED": False,
}
# for some reason our checking_ids are in base64 but the payment hashes
# returned here are in hex, lnd is weird

View File

@ -6,7 +6,13 @@ from http import HTTPStatus
from typing import Optional, Dict, AsyncGenerator
from quart import request
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
class LNPayWallet(Wallet):
@ -31,7 +37,8 @@ class LNPayWallet(Wallet):
data = r.json()
if data["statusType"]["name"] != "active":
return StatusResponse(
f"Wallet {data['user_label']} (data['id']) not active, but {data['statusType']['name']}", 0
f"Wallet {data['user_label']} (data['id']) not active, but {data['statusType']['name']}",
0,
)
return StatusResponse(None, data["balance"] * 1000)
@ -78,7 +85,9 @@ class LNPayWallet(Wallet):
try:
data = r.json()
except:
return PaymentResponse(False, None, 0, None, f"Got invalid JSON: {r.text[:200]}")
return PaymentResponse(
False, None, 0, None, f"Got invalid JSON: {r.text[:200]}"
)
if r.is_error:
return PaymentResponse(False, None, 0, None, data["message"])
@ -115,7 +124,11 @@ class LNPayWallet(Wallet):
except json.decoder.JSONDecodeError:
print(f"got something wrong on lnpay webhook endpoint: {text[:200]}")
data = None
if type(data) is not dict or "event" not in data or data["event"].get("name") != "wallet_receive":
if (
type(data) is not dict
or "event" not in data
or data["event"].get("name") != "wallet_receive"
):
return "", HTTPStatus.NO_CONTENT
lntx_id = data["data"]["wtx"]["lnTx"]["id"]

View File

@ -4,7 +4,13 @@ import httpx
from os import getenv
from typing import Optional, Dict, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
class LntxbotWallet(Wallet):
@ -14,7 +20,11 @@ class LntxbotWallet(Wallet):
endpoint = getenv("LNTXBOT_API_ENDPOINT")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
key = getenv("LNTXBOT_KEY") or getenv("LNTXBOT_ADMIN_KEY") or getenv("LNTXBOT_INVOICE_KEY")
key = (
getenv("LNTXBOT_KEY")
or getenv("LNTXBOT_ADMIN_KEY")
or getenv("LNTXBOT_INVOICE_KEY")
)
self.auth = {"Authorization": f"Basic {key}"}
def status(self) -> StatusResponse:
@ -26,7 +36,9 @@ class LntxbotWallet(Wallet):
try:
data = r.json()
except:
return StatusResponse(f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0)
return StatusResponse(
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
)
if data.get("error"):
return StatusResponse(data["message"], 0)
@ -34,7 +46,10 @@ class LntxbotWallet(Wallet):
return StatusResponse(None, data["BTC"]["AvailableBalance"] * 1000)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
data: Dict = {"amt": str(amount)}
if description_hash:

View File

@ -7,7 +7,14 @@ from os import getenv
from typing import Optional, AsyncGenerator
from quart import request, url_for
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
Unsupported,
)
class OpenNodeWallet(Wallet):
@ -17,7 +24,11 @@ class OpenNodeWallet(Wallet):
endpoint = getenv("OPENNODE_API_ENDPOINT")
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
key = getenv("OPENNODE_KEY") or getenv("OPENNODE_ADMIN_KEY") or getenv("OPENNODE_INVOICE_KEY")
key = (
getenv("OPENNODE_KEY")
or getenv("OPENNODE_ADMIN_KEY")
or getenv("OPENNODE_INVOICE_KEY")
)
self.auth = {"Authorization": key}
def status(self) -> StatusResponse:
@ -37,7 +48,10 @@ class OpenNodeWallet(Wallet):
return StatusResponse(None, data["balance"]["BTC"] / 100_000_000_000)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
if description_hash:
raise Unsupported("description_hash")
@ -93,7 +107,13 @@ class OpenNodeWallet(Wallet):
if r.is_error:
return PaymentStatus(None)
statuses = {"initial": None, "pending": None, "confirmed": True, "error": False, "failed": False}
statuses = {
"initial": None,
"pending": None,
"confirmed": True,
"error": False,
"failed": False,
}
return PaymentStatus(statuses[r.json()["data"]["status"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:

View File

@ -5,7 +5,13 @@ import httpx
from os import getenv
from typing import Optional, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
)
class SparkError(Exception):
@ -24,7 +30,9 @@ class SparkWallet(Wallet):
def __getattr__(self, key):
def call(*args, **kwargs):
if args and kwargs:
raise TypeError(f"must supply either named arguments or a list of arguments, not both: {args} {kwargs}")
raise TypeError(
f"must supply either named arguments or a list of arguments, not both: {args} {kwargs}"
)
elif args:
params = args
elif kwargs:
@ -67,7 +75,10 @@ class SparkWallet(Wallet):
)
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
label = "lbs{}".format(random.random())
checking_id = label
@ -81,7 +92,10 @@ class SparkWallet(Wallet):
)
else:
r = self.invoice(
msatoshi=amount * 1000, label=label, description=memo or "", exposeprivatechannels=True
msatoshi=amount * 1000,
label=label,
description=memo or "",
exposeprivatechannels=True,
)
ok, payment_request, error_message = True, r["bolt11"], ""
except (SparkError, UnknownError) as e:

View File

@ -1,11 +1,21 @@
from typing import Optional, AsyncGenerator
from .base import StatusResponse, InvoiceResponse, PaymentResponse, PaymentStatus, Wallet, Unsupported
from .base import (
StatusResponse,
InvoiceResponse,
PaymentResponse,
PaymentStatus,
Wallet,
Unsupported,
)
class VoidWallet(Wallet):
def create_invoice(
self, amount: int, memo: Optional[str] = None, description_hash: Optional[bytes] = None
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
) -> InvoiceResponse:
raise Unsupported("")

View File

@ -1,2 +0,0 @@
[tool.black]
line-length = 120