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: 1 addition & 2 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"name": "Streamer Copilot",
"short_description": "Video tips/animations/webhooks",
"tile": "/copilot/static/bitcoin-streaming.png",
"version": "1.1.0",
"min_lnbits_version": "1.3.0",
"min_lnbits_version": "1.0.0",
"contributors": [
{
"name": "Ben Arc",
Expand Down
9 changes: 7 additions & 2 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import Query
from fastapi import Query, Request
from lnurl import encode as lnurl_encode
from pydantic import BaseModel


Expand Down Expand Up @@ -31,7 +32,7 @@ class Copilot(BaseModel):
user: str | None
title: str
lnurl_toggle: int
wallet: str
wallet: str | None
animation1: str | None
animation2: str | None
animation3: str | None
Expand All @@ -49,3 +50,7 @@ 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: 16 additions & 13 deletions templates/copilot/compose.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
<q-card-section>
<div class="row">
<div class="col" style="max-width: 100px">
<lnbits-qrcode :value="chatUrl"></lnbits-qrcode>
<lnbits-qrcode
:value="chatUrl"
class="rounded-borders"
></lnbits-qrcode>
</div>
<div class="col">
<div class="text-h6 q-ml-md">Trollbox</div>
Expand Down Expand Up @@ -71,15 +74,17 @@
"
>
<div class="col">
<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>
<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>
</div>
</div>

Expand Down Expand Up @@ -160,7 +165,7 @@
copilot: {},
animQueue: [],
queue: false,
url: '',
lnurl: '',
troll_box: false,
trollbox: [],
chatUrl: '',
Expand Down Expand Up @@ -313,8 +318,6 @@
)
.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: 9 additions & 5 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
from fastapi import APIRouter, Depends, Request
from fastapi.exceptions import HTTPException
from lnbits.core.models import WalletTypeInfo
from lnbits.core.services import websocket_updater
Expand Down Expand Up @@ -28,14 +28,18 @@ 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(copilot_id: str) -> Copilot:
async def api_copilot_retrieve(
req: Request,
copilot_id: str,
):
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"
)

return copilot
if not copilot.lnurl_toggle:
return copilot
return {**copilot.dict(), **{"lnurl": copilot.lnurl(req)}}


@copilot_api_router.post("/api/v1/copilot")
Expand Down
94 changes: 48 additions & 46 deletions views_lnurl.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,80 @@
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 import (
CallbackUrl,
LightningInvoice,
LnurlErrorResponse,
LnurlPayActionResponse,
LnurlPayMetadata,
LnurlPayResponse,
MilliSatoshi,
)
from pydantic import parse_obj_as
from lnurl.types import LnurlPayMetadata

from .crud import get_copilot

copilot_lnurl_router = APIRouter()


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

callback_url = parse_obj_as(
CallbackUrl, str(req.url_for("copilot.lnurl_callback", cp_id=cp_id))
)
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
)

pay_response = LnurlPayResponse(
callback=callback_url,
metadata=LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])),
minSendable=MilliSatoshi(10000),
maxSendable=MilliSatoshi(50000000),
)
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,
}

if cp.show_message:
pay_response.commentAllowed = 300

return pay_response
pay_response["commentAllowed"] = 300
return json.dumps(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:
return LnurlErrorResponse(reason="Copilot not found.")

raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
)
amount_received = int(amount)
amount_rounded = round(amount_received / 1000)

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

comment = ""
if comment:
if len(comment) > 300:
return LnurlErrorResponse(
reason=(
f"Got a comment with {len(comment)} characters, "
if len(comment or "") > 300:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=(
"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 @@ -80,6 +84,4 @@ async def lnurl_callback(
).encode(),
extra={"tag": "copilot", "copilotid": cp.id, "comment": comment},
)

invoice = parse_obj_as(LightningInvoice, payment.bolt11)
return LnurlPayActionResponse(pr=invoice)
return {"pr": payment.bolt11, "routes": []}