from os import path from http import HTTPStatus from quart import ( g, current_app, abort, jsonify, request, redirect, render_template, send_from_directory, url_for, ) from lnbits.core import core_app, db from lnbits.decorators import check_user_exists, validate_uuids from lnbits.settings import LNBITS_ALLOWED_USERS, SERVICE_FEE, LNBITS_SITE_TITLE from ..crud import ( create_account, get_user, update_user_extension, create_wallet, delete_wallet, get_balance_check, save_balance_notify, ) from ..services import redeem_lnurl_withdraw, pay_invoice @core_app.route("/favicon.ico") async def favicon(): 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) ) @core_app.route("/extensions") @validate_uuids(["usr"], required=True) @check_user_exists() async def extensions(): extension_to_enable = request.args.get("enable", type=str) 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." ) if extension_to_enable: 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 ) return await render_template("core/extensions.html", user=await get_user(g.user.id)) @core_app.route("/wallet") @validate_uuids(["usr", "wal"]) async def wallet(): user_id = request.args.get("usr", type=str) wallet_id = request.args.get("wal", type=str) wallet_name = request.args.get("nme", type=str) service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE # just wallet_name: create a new user, then create a new wallet for user with wallet_name # just user_id: return the first user wallet or create one if none found (with default wallet_name) # user_id and wallet_name: create a new wallet for user with wallet_name # user_id and wallet_id: return that wallet if user is the owner # nothing: create everything if not user_id: user = await get_user((await create_account()).id) else: user = await get_user(user_id) if not user: abort(HTTPStatus.NOT_FOUND, "User does not exist.") return if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS: abort(HTTPStatus.UNAUTHORIZED, "User not authorized.") if not wallet_id: if user.wallets and not wallet_name: wallet = user.wallets[0] else: wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name) return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id)) wallet = user.get_wallet(wallet_id) if not wallet: abort(HTTPStatus.FORBIDDEN, "Not your wallet.") return await render_template( "core/wallet.html", user=user, wallet=wallet, service_fee=service_fee ) @core_app.route("/withdraw") @validate_uuids(["usr", "wal"], required=True) async def lnurl_full_withdraw(): user = await get_user(request.args.get("usr")) if not user: return jsonify({"status": "ERROR", "reason": "User does not exist."}) wallet = user.get_wallet(request.args.get("wal")) if not wallet: return jsonify({"status": "ERROR", "reason": "Wallet does not exist."}) return jsonify( { "tag": "withdrawRequest", "callback": url_for( "core.lnurl_full_withdraw_callback", usr=user.id, wal=wallet.id, _external=True, ), "k1": "0", "minWithdrawable": 1 if wallet.withdrawable_balance else 0, "maxWithdrawable": wallet.withdrawable_balance, "defaultDescription": f"{LNBITS_SITE_TITLE} balance withdraw from {wallet.id[0:5]}", "balanceCheck": url_for( "core.lnurl_full_withdraw", usr=user.id, wal=wallet.id, _external=True ), } ) @core_app.route("/withdraw/cb") @validate_uuids(["usr", "wal"], required=True) async def lnurl_full_withdraw_callback(): user = await get_user(request.args.get("usr")) if not user: return jsonify({"status": "ERROR", "reason": "User does not exist."}) wallet = user.get_wallet(request.args.get("wal")) if not wallet: return jsonify({"status": "ERROR", "reason": "Wallet does not exist."}) pr = request.args.get("pr") async def pay(): try: await pay_invoice(wallet_id=wallet.id, payment_request=pr) except: pass current_app.nursery.start_soon(pay) balance_notify = request.args.get("balanceNotify") if balance_notify: await save_balance_notify(wallet.id, balance_notify) return jsonify({"status": "OK"}) @core_app.route("/deletewallet") @validate_uuids(["usr", "wal"], required=True) @check_user_exists() async def deletewallet(): wallet_id = request.args.get("wal", type=str) user_wallet_ids = g.user.wallet_ids if wallet_id not in user_wallet_ids: abort(HTTPStatus.FORBIDDEN, "Not your wallet.") else: await delete_wallet(user_id=g.user.id, wallet_id=wallet_id) user_wallet_ids.remove(wallet_id) if user_wallet_ids: return redirect(url_for("core.wallet", usr=g.user.id, wal=user_wallet_ids[0])) return redirect(url_for("core.home")) @core_app.route("/withdraw/notify/") @validate_uuids(["wal"], required=True) async def lnurl_balance_notify(service: str): bc = await get_balance_check(request.args.get("wal"), service) if bc: redeem_lnurl_withdraw(bc.wallet, bc.url) @core_app.route("/lnurlwallet") async def lnurlwallet(): async with db.connect() as conn: account = await create_account(conn=conn) user = await get_user(account.id, conn=conn) wallet = await create_wallet(user_id=user.id, conn=conn) current_app.nursery.start_soon( redeem_lnurl_withdraw, wallet.id, request.args.get("lightning"), "LNbits initial funding: voucher redeem.", {"tag": "lnurlwallet"}, 5, # wait 5 seconds before sending the invoice to the service ) return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id)) @core_app.route("/manifest/.webmanifest") async def manifest(usr: str): user = await get_user(usr) if not user: return "", HTTPStatus.NOT_FOUND return jsonify( { "short_name": "LNbits", "name": "LNbits Wallet", "icons": [ { "src": "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png", "type": "image/png", "sizes": "900x900", } ], "start_url": "/wallet?usr=" + usr, "background_color": "#3367D6", "description": "Weather forecast information", "display": "standalone", "scope": "/", "theme_color": "#3367D6", "shortcuts": [ { "name": wallet.name, "short_name": wallet.name, "description": wallet.name, "url": "/wallet?usr=" + usr + "&wal=" + wallet.id, } for wallet in user.wallets ], } )