ext: withdraw ported + lnurlp fixes

This commit is contained in:
Tiago vasconcelos 2021-09-30 14:42:04 +01:00
parent 65aa1b24d8
commit c0280838e7
5 changed files with 151 additions and 97 deletions

View File

@ -1,7 +1,7 @@
import hashlib
import math
from http import HTTPStatus
from quart import jsonify, url_for, request
from fastapi import FastAPI, Request
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore
from lnbits.core.services import create_invoice
@ -11,18 +11,18 @@ from . import lnurlp_ext
from .crud import increment_pay_link
@lnurlp_ext.route("/api/v1/lnurl/<link_id>", methods=["GET"])
async def api_lnurl_response(link_id):
@lnurlp_ext.get("/api/v1/lnurl/{link_id}", status_code=HTTPStatus.OK)
async def api_lnurl_response(request: Request, 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,
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Pay link does not exist."
)
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
resp = LnurlPayResponse(
callback=url_for("lnurlp.api_lnurl_callback", link_id=link.id, extra=request.args.get('extra'), _external=True),
callback=url_for("lnurlp.api_lnurl_callback", link_id=link.id, extra=request.path_params['extra'], _external=True),
min_sendable=math.ceil(link.min * rate) * 1000,
max_sendable=round(link.max * rate) * 1000,
metadata=link.lnurlpay_metadata,
@ -32,16 +32,16 @@ async def api_lnurl_response(link_id):
if link.comment_chars > 0:
params["commentAllowed"] = link.comment_chars
return jsonify(params), HTTPStatus.OK
return params
@lnurlp_ext.route("/api/v1/lnurl/cb/<link_id>", methods=["GET"])
async def api_lnurl_callback(link_id):
@lnurlp_ext.get("/api/v1/lnurl/cb/{link_id}", status_code=HTTPStatus.OK)
async def api_lnurl_callback(request: Request, 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,
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Pay link does not exist."
)
min, max = link.min, link.max
@ -54,36 +54,23 @@ async def api_lnurl_callback(link_id):
min = link.min * 1000
max = link.max * 1000
amount_received = int(request.args.get("amount") or 0)
amount_received = int(request.path_params['amount'] or 0)
if amount_received < min:
return (
jsonify(
LnurlErrorResponse(
return LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}."
).dict()
),
HTTPStatus.OK,
)
elif amount_received > max:
return (
jsonify(
LnurlErrorResponse(
return LnurlErrorResponse(
reason=f"Amount {amount_received} is greater than maximum {max}."
).dict()
),
HTTPStatus.OK,
)
comment = request.args.get("comment")
comment = request.path_params["comment"]
if len(comment or "") > link.comment_chars:
return (
jsonify(
LnurlErrorResponse(
return LnurlErrorResponse(
reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}"
).dict()
),
HTTPStatus.OK,
)
payment_hash, payment_request = await create_invoice(
wallet_id=link.wallet,
@ -92,7 +79,7 @@ async def api_lnurl_callback(link_id):
description_hash=hashlib.sha256(
link.lnurlpay_metadata.encode("utf-8")
).digest(),
extra={"tag": "lnurlp", "link": link.id, "comment": comment, 'extra': request.args.get('extra')},
extra={"tag": "lnurlp", "link": link.id, "comment": comment, 'extra': request.path_params['amount']},
)
success_action = link.success_action(payment_hash)
@ -108,4 +95,4 @@ async def api_lnurl_callback(link_id):
routes=[],
)
return jsonify(resp.dict()), HTTPStatus.OK
return resp.dict())

View File

@ -8,13 +8,17 @@ from fastapi import FastAPI, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from lnbits.core.models import User
templates = Jinja2Templates(directory="templates")
@lnurlp_ext.get("/", response_class=HTMLResponse)
@validate_uuids(["usr"], required=True)
# @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)):
return lnurlp_renderer().TemplateResponse("lnurlp/index.html", {"request": request, "user": user})
return lnurlp_renderer().TemplateResponse("lnurlp/index.html", {"request": request, "user": user.dict()})
@lnurlp_ext.get("/{link_id}", response_class=HTMLResponse)
@ -27,7 +31,7 @@ async def display(request: Request,link_id):
)
# abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
return await lnurlp_renderer().TemplateResponse("lnurlp/display.html", {"request": request, "link":link})
return lnurlp_renderer().TemplateResponse("lnurlp/display.html", {"request": request, "link":link})
@lnurlp_ext.get("/print/{link_id}", response_class=HTMLResponse)
@ -40,4 +44,4 @@ async def print_qr(request: Request,link_id):
)
# abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
return await lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", {"request": request, "link":link})
return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", {"request": request, "link":link})

View File

@ -1,14 +1,28 @@
from quart import Blueprint
from fastapi import APIRouter
from lnbits.db import Database
db = Database("ext_withdraw")
withdraw_ext: Blueprint = Blueprint(
"withdraw", __name__, static_folder="static", template_folder="templates"
withdraw_ext: APIRouter = APIRouter(
prefix="/withdraw",
static_folder="static"
# "withdraw", __name__, static_folder="static", template_folder="templates"
)
def withdraw_renderer():
return template_renderer(
[
"lnbits/extensions/withdraw/templates",
]
)
from .views_api import * # noqa
from .views import * # noqa
from .lnurl import * # noqa
@withdraw_ext.on_event("startup")
def _do_it():
register_listeners()

View File

@ -12,41 +12,57 @@ from .crud import get_withdraw_link_by_hash, update_withdraw_link
# FOR LNURLs WHICH ARE NOT UNIQUE
@withdraw_ext.get("/api/v1/lnurl/<unique_hash>")
@withdraw_ext.get("/api/v1/lnurl/{unique_hash}", status_code=HTTPStatus.OK)
async def api_lnurl_response(unique_hash):
link = await get_withdraw_link_by_hash(unique_hash)
if not link:
return ({"status": "ERROR", "reason": "LNURL-withdraw not found."},
HTTPStatus.OK,
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Withdraw link does not exist."
)
# return ({"status": "ERROR", "reason": "LNURL-withdraw not found."},
# HTTPStatus.OK,
# )
if link.is_spent:
return ({"status": "ERROR", "reason": "Withdraw is spent."},
HTTPStatus.OK,
raise HTTPException(
# WHAT STATUS_CODE TO USE??
detail="Withdraw is spent."
)
# return ({"status": "ERROR", "reason": "Withdraw is spent."},
# HTTPStatus.OK,
# )
return link.lnurl_response.dict(), HTTPStatus.OK
return link.lnurl_response.dict()
# FOR LNURLs WHICH ARE UNIQUE
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>/<id_unique_hash>")
@withdraw_ext.get("/api/v1/lnurl/{unique_hash}/{id_unique_hash}", status_code=HTTPStatus.OK)
async def api_lnurl_multi_response(unique_hash, id_unique_hash):
link = await get_withdraw_link_by_hash(unique_hash)
if not link:
return (
{"status": "ERROR", "reason": "LNURL-withdraw not found."},
HTTPStatus.OK,
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="LNURL-withdraw not found."
)
# return (
# {"status": "ERROR", "reason": "LNURL-withdraw not found."},
# HTTPStatus.OK,
# )
if link.is_spent:
return (
{"status": "ERROR", "reason": "Withdraw is spent."},
HTTPStatus.OK,
raise HTTPException(
# WHAT STATUS_CODE TO USE??
detail="Withdraw is spent."
)
# return (
# {"status": "ERROR", "reason": "Withdraw is spent."},
# HTTPStatus.OK,
# )
useslist = link.usescsv.split(",")
found = False
@ -55,44 +71,57 @@ 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 (
{"status": "ERROR", "reason": "LNURL-withdraw not found."},
HTTPStatus.OK,
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="LNURL-withdraw not found."
)
# return (
# {"status": "ERROR", "reason": "LNURL-withdraw not found."},
# HTTPStatus.OK,
# )
return link.lnurl_response.dict(), HTTPStatus.OK
return link.lnurl_response.dict()
# CALLBACK
@withdraw_ext.get("/api/v1/lnurl/cb/<unique_hash>")
@withdraw_ext.get("/api/v1/lnurl/cb/{unique_hash}", status_code=HTTPStatus.OK)
async def api_lnurl_callback(unique_hash):
link = await get_withdraw_link_by_hash(unique_hash)
k1 = request.args.get("k1", type=str)
payment_request = request.args.get("pr", type=str)
k1 = request.path_params['k1']
payment_request = request.path_params['pr']
now = int(datetime.now().timestamp())
if not link:
return (
{"status": "ERROR", "reason": "LNURL-withdraw not found."},
HTTPStatus.OK,
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="LNURL-withdraw not found."
)
# return (
# {"status": "ERROR", "reason": "LNURL-withdraw not found."},
# HTTPStatus.OK,
# )
if link.is_spent:
return (
{"status": "ERROR", "reason": "Withdraw is spent."},
HTTPStatus.OK,
raise HTTPException(
# WHAT STATUS_CODE TO USE??
detail="Withdraw is spent."
)
# return (
# {"status": "ERROR", "reason": "Withdraw is spent."},
# HTTPStatus.OK,
# )
if link.k1 != k1:
return {"status": "ERROR", "reason": "Bad request."}, HTTPStatus.OK
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Bad request."
)
# return {"status": "ERROR", "reason": "Bad request."}, HTTPStatus.OK
if now < link.open_time:
return (
{"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."},
HTTPStatus.OK,
)
return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}
try:
usescsv = ""
@ -122,6 +151,7 @@ async def api_lnurl_callback(unique_hash):
max_sat=link.max_withdrawable,
extra={"tag": "withdraw"},
)
# should these be "raise" instead of the "return" ??
except ValueError as e:
await update_withdraw_link(link.id, **changesback)
return {"status": "ERROR", "reason": str(e)}
@ -132,4 +162,4 @@ async def api_lnurl_callback(unique_hash):
await update_withdraw_link(link.id, **changesback)
return {"status": "ERROR", "reason": str(e)}
return {"status": "OK"}, HTTPStatus.OK
return {"status": "OK"}

View File

@ -1,39 +1,50 @@
from quart import g, abort, render_template
from http import HTTPStatus
import pyqrcode
from io import BytesIO
from lnbits.decorators import check_user_exists, validate_uuids
from . import withdraw_ext
from . import withdraw_ext, withdraw_renderer
from .crud import get_withdraw_link, chunks
from fastapi import FastAPI, Request, Response
from fastapi import FastAPI, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from lnbits.core.models import User
templates = Jinja2Templates(directory="templates")
@withdraw_ext.get("/", status_code=HTTPStatus.OK)
@withdraw_ext.get("/", response_class=HTMLResponse)
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index(request: Request):
return await templates.TemplateResponse("withdraw/index.html", {"request":request,"user":g.user})
# @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)):
return withdraw_renderer().TemplateResponse("withdraw/index.html", {"request":request,"user": user.dict()})
@withdraw_ext.get("/{link_id}", status_code=HTTPStatus.OK)
async def display(request: Request, link_id, response: Response):
@withdraw_ext.get("/{link_id}", response_class=HTMLResponse)
async def display(request: Request, link_id):
link = await get_withdraw_link(link_id, 0)
if not link:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist." #probably here is where we should return the 404??
return await templates.TemplateResponse("withdraw/display.html", {"request":request,"link":link, "unique":True})
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Withdraw link does not exist."
)
# response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist." #probably here is where we should return the 404??
return withdraw_renderer().TemplateResponse("withdraw/display.html", {"request":request,"link":link, "unique":True})
@withdraw_ext.get("/img/{link_id}", status_code=HTTPStatus.OK)
@withdraw_ext.get("/img/{link_id}", response_class=HTMLResponse)
async def img(request: Request, link_id, response: Response):
link = await get_withdraw_link(link_id, 0)
if not link:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist."
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Withdraw link does not exist."
)
# response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist."
qr = pyqrcode.create(link.lnurl)
stream = BytesIO()
qr.svg(stream, scale=3)
@ -49,23 +60,31 @@ async def img(request: Request, link_id, response: Response):
)
@withdraw_ext.get("/print/{link_id}", status_code=HTTPStatus.OK)
async def print_qr(request: Request, link_id, response: Response):
@withdraw_ext.get("/print/{link_id}", response_class=HTMLResponse)
async def print_qr(request: Request, link_id):
link = await get_withdraw_link(link_id)
if not link:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist."
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Withdraw link does not exist."
)
# response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist."
if link.uses == 0:
return await templates.TemplateResponse("withdraw/print_qr.html", {"request":request,link:link, unique:False})
return withdraw_renderer().TemplateResponse("withdraw/print_qr.html", {"request":request,link:link, unique:False})
links = []
count = 0
for x in link.usescsv.split(","):
linkk = await get_withdraw_link(link_id, count)
if not linkk:
response.status_code = HTTPStatus.NOT_FOUND
return "Withdraw link does not exist."
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Withdraw link does not exist."
)
# response.status_code = HTTPStatus.NOT_FOUND
# return "Withdraw link does not exist."
links.append(str(linkk.lnurl))
count = count + 1
page_link = list(chunks(links, 2))
linked = list(chunks(page_link, 5))
return await templates.TemplateResponse("withdraw/print_qr.html", {"request":request,"link":linked, "unique":True})
return withdraw_renderer().TemplateResponse("withdraw/print_qr.html", {"request":request,"link":linked, "unique":True})