From 1346ad12f1c0f2c850d552f987e22ba72079169e Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Tue, 12 Oct 2021 22:34:43 +0100 Subject: [PATCH] Endpoints changed but still not working --- lnbits/extensions/copilot/__init__.py | 9 +- lnbits/extensions/copilot/crud.py | 74 ++++++-------- lnbits/extensions/copilot/lnurl.py | 73 ++++++++------ lnbits/extensions/copilot/models.py | 2 +- lnbits/extensions/copilot/tasks.py | 37 +++---- lnbits/extensions/copilot/views.py | 52 +++++----- lnbits/extensions/copilot/views_api.py | 134 +++++++++++++------------ lnbits/extensions/jukebox/__init__.py | 2 - lnbits/extensions/jukebox/views.py | 1 + 9 files changed, 181 insertions(+), 203 deletions(-) diff --git a/lnbits/extensions/copilot/__init__.py b/lnbits/extensions/copilot/__init__.py index d14f15655..94d1e74c8 100644 --- a/lnbits/extensions/copilot/__init__.py +++ b/lnbits/extensions/copilot/__init__.py @@ -1,16 +1,13 @@ import asyncio - from fastapi import APIRouter, FastAPI from fastapi.staticfiles import StaticFiles from starlette.routing import Mount - from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart db = Database("ext_copilot") - copilot_static_files = [ { "path": "/copilot/static", @@ -18,11 +15,7 @@ copilot_static_files = [ "name": "copilot_static", } ] -copilot_ext: APIRouter = APIRouter( - prefix="/copilot", - tags=["copilot"] - # "lnurlp", __name__, static_folder="static", template_folder="templates" -) +copilot_ext: APIRouter = APIRouter(prefix="/copilot", tags=["copilot"]) def copilot_renderer(): diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py index d083675e2..ce4a28043 100644 --- a/lnbits/extensions/copilot/crud.py +++ b/lnbits/extensions/copilot/crud.py @@ -1,36 +1,14 @@ from typing import List, Optional, Union -# from lnbits.db import open_ext_db from . import db -from .models import Copilots - +from .models import Copilots, CreateCopilotData from lnbits.helpers import urlsafe_short_hash -from quart import jsonify - - ###############COPILOTS########################## async def create_copilot( - title: str, - user: str, - lnurl_toggle: Optional[int] = 0, - wallet: Optional[str] = None, - animation1: Optional[str] = None, - animation2: Optional[str] = None, - animation3: Optional[str] = None, - animation1threshold: Optional[int] = None, - animation2threshold: Optional[int] = None, - animation3threshold: Optional[int] = None, - animation1webhook: Optional[str] = None, - animation2webhook: Optional[str] = None, - animation3webhook: Optional[str] = None, - lnurl_title: Optional[str] = None, - show_message: Optional[int] = 0, - show_ack: Optional[int] = 0, - show_price: Optional[str] = None, - amount_made: Optional[int] = None, + data: CreateCopilotData, inkey: Optional[str] = "" ) -> Copilots: copilot_id = urlsafe_short_hash() @@ -60,24 +38,24 @@ async def create_copilot( VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( - copilot_id, - user, - int(lnurl_toggle), - wallet, - title, - animation1, - animation2, - animation3, - animation1threshold, - animation2threshold, - animation3threshold, - animation1webhook, - animation2webhook, - animation3webhook, - lnurl_title, - int(show_message), - int(show_ack), - show_price, + data.copilot_id, + data.user, + int(data.lnurl_toggle), + data.wallet, + data.title, + data.animation1, + data.animation2, + data.animation3, + data.animation1threshold, + data.animation2threshold, + data.animation3threshold, + data.animation1webhook, + data.animation2webhook, + data.animation3webhook, + data.lnurl_title, + int(data.show_message), + int(data.show_ack), + data.show_price, 0, ), ) @@ -89,17 +67,23 @@ async def update_copilot(copilot_id: str, **kwargs) -> Optional[Copilots]: await db.execute( f"UPDATE copilot.copilots SET {q} WHERE id = ?", (*kwargs.values(), copilot_id) ) - row = await db.fetchone("SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,)) + row = await db.fetchone( + "SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,) + ) return Copilots.from_row(row) if row else None async def get_copilot(copilot_id: str) -> Copilots: - row = await db.fetchone("SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,)) + row = await db.fetchone( + "SELECT * FROM copilot.copilots WHERE id = ?", (copilot_id,) + ) return Copilots.from_row(row) if row else None async def get_copilots(user: str) -> List[Copilots]: - rows = await db.fetchall("""SELECT * FROM copilot.copilots WHERE "user" = ?""", (user,)) + rows = await db.fetchall( + """SELECT * FROM copilot.copilots WHERE "user" = ?""", (user,) + ) return [Copilots.from_row(row) for row in rows] diff --git a/lnbits/extensions/copilot/lnurl.py b/lnbits/extensions/copilot/lnurl.py index 0a10e29bc..a47421d73 100644 --- a/lnbits/extensions/copilot/lnurl.py +++ b/lnbits/extensions/copilot/lnurl.py @@ -1,23 +1,36 @@ import json import hashlib import math -from quart import jsonify, url_for, request +from fastapi import Request +import hashlib +from http import HTTPStatus + +from starlette.exceptions import HTTPException +from starlette.responses import HTMLResponse, JSONResponse # type: ignore +import base64 from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore from lnurl.types import LnurlPayMetadata from lnbits.core.services import create_invoice - +from .models import Copilots, CreateCopilotData from . import copilot_ext from .crud import get_copilot +from typing import Optional +from fastapi.params import Depends +from fastapi.param_functions import Query +from .models import CreateJukeLinkData, CreateJukeboxPayment -@copilot_ext.route("/lnurl/", methods=["GET"]) -async def lnurl_response(cp_id): +@copilot_ext.get("/lnurl/{cp_id}", response_class=HTMLResponse) +async def lnurl_response(req: Request, cp_id: str = Query(None)): cp = await get_copilot(cp_id) if not cp: - return jsonify({"status": "ERROR", "reason": "Copilot not found."}) + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Copilot not found", + ) resp = LnurlPayResponse( - callback=url_for("copilot.lnurl_callback", cp_id=cp_id, _external=True), + callback=req.url_for("copilot.lnurl_callback", cp_id=cp_id, _external=True), min_sendable=10000, max_sendable=50000000, metadata=LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])), @@ -27,42 +40,36 @@ async def lnurl_response(cp_id): if cp.show_message: params["commentAllowed"] = 300 - return jsonify(params) + return params -@copilot_ext.route("/lnurl/cb/", methods=["GET"]) -async def lnurl_callback(cp_id): +@copilot_ext.get("/lnurl/cb/{cp_id}", response_class=HTMLResponse) +async def lnurl_callback( + cp_id: str = Query(None), amount: str = Query(None), comment: str = Query(None) +): cp = await get_copilot(cp_id) if not cp: - return jsonify({"status": "ERROR", "reason": "Copilot not found."}) + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Copilot not found", + ) - amount_received = int(request.args.get("amount")) + amount_received = int(amount) if amount_received < 10000: - return ( - jsonify( - LnurlErrorResponse( - reason=f"Amount {round(amount_received / 1000)} is smaller than minimum 10 sats." - ).dict() - ), - ) + return LnurlErrorResponse( + reason=f"Amount {round(amount_received / 1000)} is smaller than minimum 10 sats." + ).dict() elif amount_received / 1000 > 10000000: - return ( - jsonify( - LnurlErrorResponse( - reason=f"Amount {round(amount_received / 1000)} is greater than maximum 50000." - ).dict() - ), - ) + return LnurlErrorResponse( + reason=f"Amount {round(amount_received / 1000)} is greater than maximum 50000." + ).dict() comment = "" - if request.args.get("comment"): - comment = request.args.get("comment") + if comment: if len(comment or "") > 300: - return jsonify( - LnurlErrorResponse( - reason=f"Got a comment with {len(comment)} characters, but can only accept 300" - ).dict() - ) + return LnurlErrorResponse( + reason=f"Got a comment with {len(comment)} characters, but can only accept 300" + ).dict() if len(comment) < 1: comment = "none" @@ -83,4 +90,4 @@ async def lnurl_callback(cp_id): disposable=False, routes=[], ) - return jsonify(resp.dict()) + return resp.dict() diff --git a/lnbits/extensions/copilot/models.py b/lnbits/extensions/copilot/models.py index db58d35a0..230825a03 100644 --- a/lnbits/extensions/copilot/models.py +++ b/lnbits/extensions/copilot/models.py @@ -9,7 +9,7 @@ from sqlite3 import Row from pydantic import BaseModel -class CreateCopilots(BaseModel): +class CreateCopilotData(BaseModel): id: str = Query(None) user: str = Query(None) title: str = Query(None) diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index ff291e9ac..77db918fd 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -1,8 +1,6 @@ -import trio # type: ignore +import asyncio import json import httpx -from quart import g, jsonify, url_for, websocket -from http import HTTPStatus from lnbits.core import db as core_db from lnbits.core.models import Payment @@ -11,16 +9,17 @@ from lnbits.tasks import register_invoice_listener from .crud import get_copilot from .views import updater import shortuuid +from http import HTTPStatus +from starlette.exceptions import HTTPException +from starlette.responses import HTMLResponse, JSONResponse # type: ignore -async def register_listeners(): - invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2) - register_invoice_listener(invoice_paid_chan_send) - await wait_for_paid_invoices(invoice_paid_chan_recv) +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue) - -async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel): - async for payment in invoice_paid_chan: + while True: + payment = await invoice_queue.get() await on_invoice_paid(payment) @@ -38,9 +37,9 @@ async def on_invoice_paid(payment: Payment) -> None: copilot = await get_copilot(payment.extra.get("copilot", -1)) if not copilot: - return ( - jsonify({"message": "Copilot link link does not exist."}), - HTTPStatus.NOT_FOUND, + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Copilot does not exist", ) if copilot.animation1threshold: if int(payment.amount / 1000) >= copilot.animation1threshold: @@ -74,15 +73,3 @@ async def on_invoice_paid(payment: Payment) -> None: await updater(copilot.id, data, payment.extra.get("comment")) else: await updater(copilot.id, data, "none") - - -async def mark_webhook_sent(payment: Payment, status: int) -> None: - payment.extra["wh_status"] = status - - await core_db.execute( - """ - UPDATE apipayments SET extra = ? - WHERE hash = ? - """, - (json.dumps(payment.extra), payment.payment_hash), - ) diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index aee38a3c4..274367b3c 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -1,8 +1,8 @@ from http import HTTPStatus import httpx from collections import defaultdict -from lnbits.decorators import check_user_exists, validate_uuids - +from lnbits.decorators import check_user_exists +import asyncio from .crud import get_copilot from functools import wraps @@ -10,13 +10,15 @@ from functools import wraps from lnbits.decorators import check_user_exists from . import copilot_ext, copilot_renderer -from fastapi import FastAPI, Request +from fastapi import FastAPI, Request, WebSocket from fastapi.params import Depends from fastapi.templating import Jinja2Templates - +from fastapi.param_functions import Query from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse +from starlette.responses import HTMLResponse, JSONResponse # type: ignore from lnbits.core.models import User +import base64 + templates = Jinja2Templates(directory="templates") @@ -50,25 +52,25 @@ async def panel(request: Request): connected_websockets = defaultdict(set) -@copilot_ext.websocket("/ws//") -async def wss(id): - copilot = await get_copilot(id) - if not copilot: - return "", HTTPStatus.FORBIDDEN - global connected_websockets - send_channel, receive_channel = trio.open_memory_channel(0) - connected_websockets[id].add(send_channel) - try: - while True: - data = await receive_channel.receive() - await websocket.send(data) - finally: - connected_websockets[id].remove(send_channel) +# @copilot_ext.websocket("/ws/{id}/") +# async def websocket_endpoint(websocket: WebSocket, id: str = Query(None)): +# copilot = await get_copilot(id) +# if not copilot: +# return "", HTTPStatus.FORBIDDEN +# await websocket.accept() +# invoice_queue = asyncio.Queue() +# connected_websockets[id].add(invoice_queue) +# try: +# while True: +# data = await websocket.receive_text() +# await websocket.send_text(f"Message text was: {data}") +# finally: +# connected_websockets[id].remove(invoice_queue) -async def updater(copilot_id, data, comment): - copilot = await get_copilot(copilot_id) - if not copilot: - return - for queue in connected_websockets[copilot_id]: - await queue.send(f"{data + '-' + comment}") +# async def updater(copilot_id, data, comment): +# copilot = await get_copilot(copilot_id) +# if not copilot: +# return +# for queue in connected_websockets[copilot_id]: +# await queue.send(f"{data + '-' + comment}") diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index bf3b4eb75..9cf2e536e 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -1,15 +1,29 @@ +from fastapi import Request import hashlib -from quart import g, jsonify, url_for, websocket from http import HTTPStatus -import httpx +from starlette.exceptions import HTTPException +from starlette.responses import HTMLResponse, JSONResponse # type: ignore +import base64 from lnbits.core.crud import get_user -from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from lnbits.core.services import create_invoice, check_invoice_status +import json +from typing import Optional +from fastapi.params import Depends +from fastapi.param_functions import Query +from .models import Copilots, CreateCopilotData +from lnbits.decorators import ( + WalletAdminKeyChecker, + WalletInvoiceKeyChecker, + api_validate_post_request, + check_user_exists, + WalletTypeInfo, + get_key_type, + api_validate_post_request, +) from .views import updater - +import httpx from . import copilot_ext - -from lnbits.extensions.copilot import copilot_ext from .crud import ( create_copilot, update_copilot, @@ -21,89 +35,81 @@ from .crud import ( #######################COPILOT########################## -@copilot_ext.route("/api/v1/copilot", methods=["POST"]) -@copilot_ext.route("/api/v1/copilot/", methods=["PUT"]) -@api_check_wallet_key("admin") -@api_validate_post_request( - schema={ - "title": {"type": "string", "empty": False, "required": True}, - "lnurl_toggle": {"type": "integer", "empty": False}, - "wallet": {"type": "string", "empty": False, "required": False}, - "animation1": {"type": "string", "empty": True, "required": False}, - "animation2": {"type": "string", "empty": True, "required": False}, - "animation3": {"type": "string", "empty": True, "required": False}, - "animation1threshold": {"type": "integer", "empty": True, "required": False}, - "animation2threshold": {"type": "integer", "empty": True, "required": False}, - "animation3threshold": {"type": "integer", "empty": True, "required": False}, - "animation1webhook": {"type": "string", "empty": True, "required": False}, - "animation2webhook": {"type": "string", "empty": True, "required": False}, - "animation3webhook": {"type": "string", "empty": True, "required": False}, - "lnurl_title": {"type": "string", "empty": True, "required": False}, - "show_message": {"type": "integer", "empty": True, "required": False}, - "show_ack": {"type": "integer", "empty": True}, - "show_price": {"type": "string", "empty": True}, - } -) -async def api_copilot_create_or_update(copilot_id=None): +@copilot_ext.post("/api/v1/copilot", response_class=HTMLResponse) +@copilot_ext.put("/api/v1/copilot/{juke_id}", response_class=HTMLResponse) +async def api_copilot_create_or_update( + data: CreateCopilotData, + copilot_id: str = Query(None), + wallet: WalletTypeInfo = Depends(get_key_type), +): if not copilot_id: - copilot = await create_copilot(user=g.wallet.user, **g.data) - return jsonify(copilot._asdict()), HTTPStatus.CREATED + copilot = await create_copilot(data, user=wallet.wallet.user) + return copilot, HTTPStatus.CREATED else: - copilot = await update_copilot(copilot_id=copilot_id, **g.data) - return jsonify(copilot._asdict()), HTTPStatus.OK + copilot = await update_copilot(data, copilot_id=copilot_id) + return copilot -@copilot_ext.route("/api/v1/copilot", methods=["GET"]) -@api_check_wallet_key("invoice") -async def api_copilots_retrieve(): +@copilot_ext.get("/api/v1/copilot", response_class=HTMLResponse) +async def api_copilots_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): try: - return ( - jsonify( - [{**copilot._asdict()} for copilot in await get_copilots(g.wallet.user)] - ), - HTTPStatus.OK, - ) + return [{copilot} for copilot in await get_copilots(wallet.wallet.user)] except: return "" -@copilot_ext.route("/api/v1/copilot/", methods=["GET"]) -@api_check_wallet_key("invoice") -async def api_copilot_retrieve(copilot_id): +@copilot_ext.get("/api/v1/copilot/{copilot_id}", response_class=HTMLResponse) +async def api_copilot_retrieve( + copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) +): copilot = await get_copilot(copilot_id) if not copilot: - return jsonify({"message": "copilot does not exist"}), HTTPStatus.NOT_FOUND - if not copilot.lnurl_toggle: - return ( - jsonify({**copilot._asdict()}), - HTTPStatus.OK, + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Copilot not found", ) - return ( - jsonify({**copilot._asdict(), **{"lnurl": copilot.lnurl}}), - HTTPStatus.OK, - ) + if not copilot.lnurl_toggle: + return copilot.dict() + return {**copilot.dict(), **{"lnurl": copilot.lnurl}} -@copilot_ext.route("/api/v1/copilot/", methods=["DELETE"]) -@api_check_wallet_key("admin") -async def api_copilot_delete(copilot_id): +@copilot_ext.delete("/api/v1/copilot/{copilot_id}", response_class=HTMLResponse) +async def api_copilot_delete( + copilot_id: str = Query(None), + wallet: WalletTypeInfo = Depends(WalletAdminKeyChecker()), +): copilot = await get_copilot(copilot_id) if not copilot: - return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Copilot does not exist", + ) await delete_copilot(copilot_id) return "", HTTPStatus.NO_CONTENT -@copilot_ext.route("/api/v1/copilot/ws///", methods=["GET"]) -async def api_copilot_ws_relay(copilot_id, comment, data): +@copilot_ext.get( + "/api/v1/copilot/ws/{copilot_id}/{comment}/{data}", response_class=HTMLResponse +) +async def api_copilot_ws_relay( + copilot_id: str = Query(None), + comment: str = Query(None), + data: str = Query(None), +): copilot = await get_copilot(copilot_id) if not copilot: - return jsonify({"message": "copilot does not exist"}), HTTPStatus.NOT_FOUND + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Copilot does not exist", + ) try: await updater(copilot_id, data, comment) except: - return "", HTTPStatus.FORBIDDEN - return "", HTTPStatus.OK + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, + detail="Not your copilot", + ) + return "" diff --git a/lnbits/extensions/jukebox/__init__.py b/lnbits/extensions/jukebox/__init__.py index f38b0ec75..93e1157a0 100644 --- a/lnbits/extensions/jukebox/__init__.py +++ b/lnbits/extensions/jukebox/__init__.py @@ -1,9 +1,7 @@ import asyncio - from fastapi import APIRouter, FastAPI from fastapi.staticfiles import StaticFiles from starlette.routing import Mount - from lnbits.db import Database from lnbits.helpers import template_renderer from lnbits.tasks import catch_everything_and_restart diff --git a/lnbits/extensions/jukebox/views.py b/lnbits/extensions/jukebox/views.py index b0bd06401..230a61e3f 100644 --- a/lnbits/extensions/jukebox/views.py +++ b/lnbits/extensions/jukebox/views.py @@ -1,5 +1,6 @@ import json import time + from datetime import datetime from http import HTTPStatus from lnbits.decorators import check_user_exists, WalletTypeInfo, get_key_type