remove exception to black line-length and reformat.
This commit is contained in:
parent
3333f1f3f3
commit
42bd5ea989
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"')
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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},
|
||||
)
|
||||
|
||||
|
|
|
@ -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;")
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
)
|
||||
|
||||
|
|
|
@ -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.",
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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("")
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
[tool.black]
|
||||
line-length = 120
|
Loading…
Reference in New Issue
Block a user