Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "Streamer Copilot",
"short_description": "Video tips/animations/webhooks",
"tile": "/copilot/static/bitcoin-streaming.png",
"min_lnbits_version": "1.0.0",
"version": "1.1.0",
"min_lnbits_version": "1.3.0",
"contributors": [
{
"name": "Ben Arc",
Expand Down
9 changes: 2 additions & 7 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from fastapi import Query, Request
from lnurl import encode as lnurl_encode
from fastapi import Query
from pydantic import BaseModel


Expand Down Expand Up @@ -32,7 +31,7 @@ class Copilot(BaseModel):
user: str | None
title: str
lnurl_toggle: int
wallet: str | None
wallet: str
animation1: str | None
animation2: str | None
animation3: str | None
Expand All @@ -50,7 +49,3 @@ class Copilot(BaseModel):
timestamp: int
fullscreen_cam: int
iframe_url: str | None

def lnurl(self, req: Request) -> str:
url = str(req.url_for("copilot.lnurl_response", cp_id=self.id))
return lnurl_encode(url)
29 changes: 13 additions & 16 deletions templates/copilot/compose.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@
<q-card-section>
<div class="row">
<div class="col" style="max-width: 100px">
<lnbits-qrcode
:value="chatUrl"
class="rounded-borders"
></lnbits-qrcode>
<lnbits-qrcode :value="chatUrl"></lnbits-qrcode>
</div>
<div class="col">
<div class="text-h6 q-ml-md">Trollbox</div>
Expand Down Expand Up @@ -74,17 +71,15 @@
"
>
<div class="col">
<a class="text-secondary" :href="'lightning:' + copilot.lnurl">
<lnbits-qrcode
:value="'lightning:' + copilot.lnurl"
class="rounded-borders"
></lnbits-qrcode>
<center
class="absolute-bottom"
style="color: black; font-size: 20px"
v-text="copilot.lnurl_title"
></center>
</a>
<lnbits-qrcode-lnurl
:url="url"
:show-buttons="false"
></lnbits-qrcode-lnurl>
<center
class="absolute-bottom"
style="color: black; font-size: 20px"
v-text="copilot.lnurl_title"
></center>
</div>
</div>

Expand Down Expand Up @@ -165,7 +160,7 @@
copilot: {},
animQueue: [],
queue: false,
lnurl: '',
url: '',
troll_box: false,
trollbox: [],
chatUrl: '',
Expand Down Expand Up @@ -318,6 +313,8 @@
)
.then(response => {
this.copilot = response.data
this.url =
window.location.origin + '/copilot/lnurl/' + this.copilot.id
})
.catch(err => {
LNbits.utils.notifyApiError(err)
Expand Down
14 changes: 5 additions & 9 deletions views_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from http import HTTPStatus

from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends
from fastapi.exceptions import HTTPException
from lnbits.core.models import WalletTypeInfo
from lnbits.core.services import websocket_updater
Expand Down Expand Up @@ -28,18 +28,14 @@ async def api_copilots_retrieve(wallet: WalletTypeInfo = Depends(require_invoice
@copilot_api_router.get(
"/api/v1/copilot/{copilot_id}", dependencies=[Depends(require_invoice_key)]
)
async def api_copilot_retrieve(
req: Request,
copilot_id: str,
):
async def api_copilot_retrieve(copilot_id: str) -> Copilot:
copilot = await get_copilot(copilot_id)
if not copilot:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found."
)
if not copilot.lnurl_toggle:
return copilot
return {**copilot.dict(), **{"lnurl": copilot.lnurl(req)}}

return copilot


@copilot_api_router.post("/api/v1/copilot")
Expand Down
94 changes: 46 additions & 48 deletions views_lnurl.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,76 @@
import json
from http import HTTPStatus

from fastapi import APIRouter, Query, Request
from fastapi.exceptions import HTTPException
from fastapi.responses import HTMLResponse
from lnbits.core.services import create_invoice
from lnurl.types import LnurlPayMetadata
from lnurl import (
CallbackUrl,
LightningInvoice,
LnurlErrorResponse,
LnurlPayActionResponse,
LnurlPayMetadata,
LnurlPayResponse,
MilliSatoshi,
)
from pydantic import parse_obj_as

from .crud import get_copilot

copilot_lnurl_router = APIRouter()


@copilot_lnurl_router.get(
"/lnurl/{cp_id}", response_class=HTMLResponse, name="copilot.lnurl_response"
)
async def lnurl_response(req: Request, cp_id: str):
@copilot_lnurl_router.get("/lnurl/{cp_id}", name="copilot.lnurl_response")
async def lnurl_response(
req: Request, cp_id: str
) -> LnurlPayResponse | LnurlErrorResponse:
cp = await get_copilot(cp_id)
if not cp:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
)
return LnurlErrorResponse(reason="Copilot not found.")

pay_response = {
"tag": "payRequest",
"callback": str(req.url_for("copilot.lnurl_callback", cp_id=cp_id)),
"metadata": LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])),
"maxSendable": 50000000,
"minSendable": 10000,
}
callback_url = parse_obj_as(
CallbackUrl, str(req.url_for("copilot.lnurl_callback", cp_id=cp_id))
)

pay_response = LnurlPayResponse(
callback=callback_url,
metadata=LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])),
minSendable=MilliSatoshi(10000),
maxSendable=MilliSatoshi(50000000),
)

if cp.show_message:
pay_response["commentAllowed"] = 300
return json.dumps(pay_response)
pay_response.commentAllowed = 300

return pay_response


@copilot_lnurl_router.get("/lnurl/cb/{cp_id}", name="copilot.lnurl_callback")
async def lnurl_callback(
cp_id: str, amount: str = Query(None), comment: str = Query(None)
):
) -> LnurlPayActionResponse | LnurlErrorResponse:
cp = await get_copilot(cp_id)
if not cp:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
)
amount_received = int(amount)
return LnurlErrorResponse(reason="Copilot not found.")

amount_received = int(amount)
amount_rounded = round(amount_received / 1000)
if amount_received < 10000:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=(
"Amount {round(amount_received / 1000)} "
"is smaller than minimum 10 sats."
),
return LnurlErrorResponse(
reason=f"Amount {amount_rounded} is smaller than minimum 10 sats."
)
elif amount_received / 1000 > 10000000:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=(
"Amount {round(amount_received / 1000)} "
"is greater than maximum 50000."
),
return LnurlErrorResponse(
reason=f"Amount {amount_rounded} is greater than maximum 10000000 sats."
)
comment = ""

if comment:
if len(comment or "") > 300:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=(
"Got a comment with {len(comment)} characters, "
if len(comment) > 300:
return LnurlErrorResponse(
reason=(
f"Got a comment with {len(comment)} characters, "
"but can only accept 300"
),
)
)
if len(comment) < 1:
comment = "none"
assert cp.wallet, "Copilot wallet not found"

payment = await create_invoice(
wallet_id=cp.wallet,
amount=int(amount_received / 1000),
Expand All @@ -84,4 +80,6 @@ async def lnurl_callback(
).encode(),
extra={"tag": "copilot", "copilotid": cp.id, "comment": comment},
)
return {"pr": payment.bolt11, "routes": []}

invoice = parse_obj_as(LightningInvoice, payment.bolt11)
return LnurlPayActionResponse(pr=invoice)