Removed streamer copilot, as we need a demo
|
|
@ -62,8 +62,8 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<q-img
|
<q-img
|
||||||
v-if="extension.iconUrl"
|
v-if="extension.icon"
|
||||||
src="extension.iconUrl"
|
src="extension.icon"
|
||||||
spinner-color="white"
|
spinner-color="white"
|
||||||
style="max-width: 100%"
|
style="max-width: 100%"
|
||||||
></q-img>
|
></q-img>
|
||||||
|
|
@ -114,7 +114,9 @@
|
||||||
style="font-size: 9px; height: 34px"
|
style="font-size: 9px; height: 34px"
|
||||||
>
|
>
|
||||||
{{ extension.shortDescription }}
|
{{ extension.shortDescription }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{{ extension }}
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# StreamerCopilot
|
|
||||||
|
|
||||||
Tool to help streamers accept sats for tips
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
|
|
||||||
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",
|
|
||||||
"app": StaticFiles(packages=[("lnbits", "extensions/copilot/static")]),
|
|
||||||
"name": "copilot_static",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
copilot_ext: APIRouter = APIRouter(prefix="/copilot", tags=["copilot"])
|
|
||||||
|
|
||||||
|
|
||||||
def copilot_renderer():
|
|
||||||
return template_renderer(["lnbits/extensions/copilot/templates"])
|
|
||||||
|
|
||||||
|
|
||||||
from .lnurl import * # noqa
|
|
||||||
from .tasks import wait_for_paid_invoices
|
|
||||||
from .views import * # noqa
|
|
||||||
from .views_api import * # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def copilot_start():
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Streamer Copilot",
|
|
||||||
"short_description": "Video tips/animations/webhooks",
|
|
||||||
"tile": "/copilot/static/bitcoin-streaming.png",
|
|
||||||
"contributors": [
|
|
||||||
"arcbtc"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
|
|
||||||
from . import db
|
|
||||||
from .models import Copilots, CreateCopilotData
|
|
||||||
|
|
||||||
###############COPILOTS##########################
|
|
||||||
|
|
||||||
|
|
||||||
async def create_copilot(
|
|
||||||
data: CreateCopilotData, inkey: Optional[str] = ""
|
|
||||||
) -> Optional[Copilots]:
|
|
||||||
copilot_id = urlsafe_short_hash()
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO copilot.newer_copilots (
|
|
||||||
id,
|
|
||||||
"user",
|
|
||||||
lnurl_toggle,
|
|
||||||
wallet,
|
|
||||||
title,
|
|
||||||
animation1,
|
|
||||||
animation2,
|
|
||||||
animation3,
|
|
||||||
animation1threshold,
|
|
||||||
animation2threshold,
|
|
||||||
animation3threshold,
|
|
||||||
animation1webhook,
|
|
||||||
animation2webhook,
|
|
||||||
animation3webhook,
|
|
||||||
lnurl_title,
|
|
||||||
show_message,
|
|
||||||
show_ack,
|
|
||||||
show_price,
|
|
||||||
fullscreen_cam,
|
|
||||||
iframe_url,
|
|
||||||
amount_made
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
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,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return await get_copilot(copilot_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def update_copilot(
|
|
||||||
data: CreateCopilotData, copilot_id: str
|
|
||||||
) -> Optional[Copilots]:
|
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in data])
|
|
||||||
items = [f"{field[1]}" for field in data]
|
|
||||||
items.append(copilot_id)
|
|
||||||
await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items,))
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
|
|
||||||
)
|
|
||||||
return Copilots(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_copilot(copilot_id: str) -> Optional[Copilots]:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
|
|
||||||
)
|
|
||||||
return Copilots(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_copilots(user: str) -> List[Copilots]:
|
|
||||||
rows = await db.fetchall(
|
|
||||||
'SELECT * FROM copilot.newer_copilots WHERE "user" = ?', (user,)
|
|
||||||
)
|
|
||||||
return [Copilots(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_copilot(copilot_id: str) -> None:
|
|
||||||
await db.execute("DELETE FROM copilot.newer_copilots WHERE id = ?", (copilot_id,))
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
from fastapi import Request
|
|
||||||
from fastapi.param_functions import Query
|
|
||||||
from lnurl.types import LnurlPayMetadata
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
|
|
||||||
from lnbits.core.services import create_invoice
|
|
||||||
|
|
||||||
from . import copilot_ext
|
|
||||||
from .crud import get_copilot
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get(
|
|
||||||
"/lnurl/{cp_id}", response_class=HTMLResponse, name="copilot.lnurl_response"
|
|
||||||
)
|
|
||||||
async def lnurl_response(req: Request, cp_id: str = Query(None)):
|
|
||||||
cp = await get_copilot(cp_id)
|
|
||||||
if not cp:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
payResponse = {
|
|
||||||
"tag": "payRequest",
|
|
||||||
"callback": 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:
|
|
||||||
payResponse["commentAllowed"] = 300
|
|
||||||
return json.dumps(payResponse)
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get(
|
|
||||||
"/lnurl/cb/{cp_id}", response_class=HTMLResponse, name="copilot.lnurl_callback"
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
|
|
||||||
)
|
|
||||||
amount_received = int(amount)
|
|
||||||
|
|
||||||
if amount_received < 10000:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.FORBIDDEN,
|
|
||||||
detail="Amount {round(amount_received / 1000)} 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.",
|
|
||||||
)
|
|
||||||
comment = ""
|
|
||||||
if comment:
|
|
||||||
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"
|
|
||||||
_, payment_request = await create_invoice(
|
|
||||||
wallet_id=cp.wallet,
|
|
||||||
amount=int(amount_received / 1000),
|
|
||||||
memo=cp.lnurl_title,
|
|
||||||
unhashed_description=(
|
|
||||||
LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]]))
|
|
||||||
).encode(),
|
|
||||||
extra={"tag": "copilot", "copilotid": cp.id, "comment": comment},
|
|
||||||
)
|
|
||||||
payResponse = {"pr": payment_request, "routes": []}
|
|
||||||
return json.dumps(payResponse)
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
async def m001_initial(db):
|
|
||||||
"""
|
|
||||||
Initial copilot table.
|
|
||||||
"""
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE copilot.copilots (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"user" TEXT,
|
|
||||||
title TEXT,
|
|
||||||
lnurl_toggle INTEGER,
|
|
||||||
wallet TEXT,
|
|
||||||
animation1 TEXT,
|
|
||||||
animation2 TEXT,
|
|
||||||
animation3 TEXT,
|
|
||||||
animation1threshold INTEGER,
|
|
||||||
animation2threshold INTEGER,
|
|
||||||
animation3threshold INTEGER,
|
|
||||||
animation1webhook TEXT,
|
|
||||||
animation2webhook TEXT,
|
|
||||||
animation3webhook TEXT,
|
|
||||||
lnurl_title TEXT,
|
|
||||||
show_message INTEGER,
|
|
||||||
show_ack INTEGER,
|
|
||||||
show_price INTEGER,
|
|
||||||
amount_made INTEGER,
|
|
||||||
fullscreen_cam INTEGER,
|
|
||||||
iframe_url TEXT,
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def m002_fix_data_types(db):
|
|
||||||
"""
|
|
||||||
Fix data types.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if db.type != "SQLITE":
|
|
||||||
await db.execute(
|
|
||||||
"ALTER TABLE copilot.copilots ALTER COLUMN show_price TYPE TEXT;"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def m003_fix_data_types(db):
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE copilot.newer_copilots (
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
"user" TEXT,
|
|
||||||
title TEXT,
|
|
||||||
lnurl_toggle INTEGER,
|
|
||||||
wallet TEXT,
|
|
||||||
animation1 TEXT,
|
|
||||||
animation2 TEXT,
|
|
||||||
animation3 TEXT,
|
|
||||||
animation1threshold INTEGER,
|
|
||||||
animation2threshold INTEGER,
|
|
||||||
animation3threshold INTEGER,
|
|
||||||
animation1webhook TEXT,
|
|
||||||
animation2webhook TEXT,
|
|
||||||
animation3webhook TEXT,
|
|
||||||
lnurl_title TEXT,
|
|
||||||
show_message INTEGER,
|
|
||||||
show_ack INTEGER,
|
|
||||||
show_price TEXT,
|
|
||||||
amount_made INTEGER,
|
|
||||||
fullscreen_cam INTEGER,
|
|
||||||
iframe_url TEXT,
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
"INSERT INTO copilot.newer_copilots SELECT * FROM copilot.copilots"
|
|
||||||
)
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import json
|
|
||||||
from sqlite3 import Row
|
|
||||||
from typing import Dict, Optional
|
|
||||||
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
|
|
||||||
|
|
||||||
from fastapi.param_functions import Query
|
|
||||||
from lnurl.types import LnurlPayMetadata
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from starlette.requests import Request
|
|
||||||
|
|
||||||
from lnbits.lnurl import encode as lnurl_encode
|
|
||||||
|
|
||||||
|
|
||||||
class CreateCopilotData(BaseModel):
|
|
||||||
user: str = Query(None)
|
|
||||||
title: str = Query(None)
|
|
||||||
lnurl_toggle: int = Query(0)
|
|
||||||
wallet: str = Query(None)
|
|
||||||
animation1: str = Query(None)
|
|
||||||
animation2: str = Query(None)
|
|
||||||
animation3: str = Query(None)
|
|
||||||
animation1threshold: int = Query(None)
|
|
||||||
animation2threshold: int = Query(None)
|
|
||||||
animation3threshold: int = Query(None)
|
|
||||||
animation1webhook: str = Query(None)
|
|
||||||
animation2webhook: str = Query(None)
|
|
||||||
animation3webhook: str = Query(None)
|
|
||||||
lnurl_title: str = Query(None)
|
|
||||||
show_message: int = Query(0)
|
|
||||||
show_ack: int = Query(0)
|
|
||||||
show_price: str = Query(None)
|
|
||||||
amount_made: int = Query(0)
|
|
||||||
timestamp: int = Query(0)
|
|
||||||
fullscreen_cam: int = Query(0)
|
|
||||||
iframe_url: str = Query(None)
|
|
||||||
success_url: str = Query(None)
|
|
||||||
|
|
||||||
|
|
||||||
class Copilots(BaseModel):
|
|
||||||
id: str
|
|
||||||
user: str = Query(None)
|
|
||||||
title: str = Query(None)
|
|
||||||
lnurl_toggle: int = Query(0)
|
|
||||||
wallet: str = Query(None)
|
|
||||||
animation1: str = Query(None)
|
|
||||||
animation2: str = Query(None)
|
|
||||||
animation3: str = Query(None)
|
|
||||||
animation1threshold: int = Query(None)
|
|
||||||
animation2threshold: int = Query(None)
|
|
||||||
animation3threshold: int = Query(None)
|
|
||||||
animation1webhook: str = Query(None)
|
|
||||||
animation2webhook: str = Query(None)
|
|
||||||
animation3webhook: str = Query(None)
|
|
||||||
lnurl_title: str = Query(None)
|
|
||||||
show_message: int = Query(0)
|
|
||||||
show_ack: int = Query(0)
|
|
||||||
show_price: str = Query(None)
|
|
||||||
amount_made: int = Query(0)
|
|
||||||
timestamp: int = Query(0)
|
|
||||||
fullscreen_cam: int = Query(0)
|
|
||||||
iframe_url: str = Query(None)
|
|
||||||
success_url: str = Query(None)
|
|
||||||
|
|
||||||
def lnurl(self, req: Request) -> str:
|
|
||||||
url = req.url_for("copilot.lnurl_response", cp_id=self.id)
|
|
||||||
return lnurl_encode(url)
|
|
||||||
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 536 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 504 KiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 577 KiB |
|
|
@ -1,84 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
|
|
||||||
from lnbits.core import db as core_db
|
|
||||||
from lnbits.core.models import Payment
|
|
||||||
from lnbits.core.services import websocketUpdater
|
|
||||||
from lnbits.helpers import get_current_extension_name
|
|
||||||
from lnbits.tasks import register_invoice_listener
|
|
||||||
|
|
||||||
from .crud import get_copilot
|
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
|
||||||
invoice_queue = asyncio.Queue()
|
|
||||||
register_invoice_listener(invoice_queue, get_current_extension_name())
|
|
||||||
|
|
||||||
while True:
|
|
||||||
payment = await invoice_queue.get()
|
|
||||||
await on_invoice_paid(payment)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_invoice_paid(payment: Payment) -> None:
|
|
||||||
if payment.extra.get("tag") != "copilot":
|
|
||||||
# not an copilot invoice
|
|
||||||
return
|
|
||||||
|
|
||||||
webhook = None
|
|
||||||
data = None
|
|
||||||
copilot = await get_copilot(payment.extra.get("copilotid", -1))
|
|
||||||
|
|
||||||
if not copilot:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist"
|
|
||||||
)
|
|
||||||
if copilot.animation1threshold:
|
|
||||||
if int(payment.amount / 1000) >= copilot.animation1threshold:
|
|
||||||
data = copilot.animation1
|
|
||||||
webhook = copilot.animation1webhook
|
|
||||||
if copilot.animation2threshold:
|
|
||||||
if int(payment.amount / 1000) >= copilot.animation2threshold:
|
|
||||||
data = copilot.animation2
|
|
||||||
webhook = copilot.animation1webhook
|
|
||||||
if copilot.animation3threshold:
|
|
||||||
if int(payment.amount / 1000) >= copilot.animation3threshold:
|
|
||||||
data = copilot.animation3
|
|
||||||
webhook = copilot.animation1webhook
|
|
||||||
if webhook:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
try:
|
|
||||||
r = await client.post(
|
|
||||||
webhook,
|
|
||||||
json={
|
|
||||||
"payment_hash": payment.payment_hash,
|
|
||||||
"payment_request": payment.bolt11,
|
|
||||||
"amount": payment.amount,
|
|
||||||
"comment": payment.extra.get("comment"),
|
|
||||||
},
|
|
||||||
timeout=40,
|
|
||||||
)
|
|
||||||
await mark_webhook_sent(payment, r.status_code)
|
|
||||||
except (httpx.ConnectError, httpx.RequestError):
|
|
||||||
await mark_webhook_sent(payment, -1)
|
|
||||||
if payment.extra.get("comment"):
|
|
||||||
await websocketUpdater(
|
|
||||||
copilot.id, str(data) + "-" + str(payment.extra.get("comment"))
|
|
||||||
)
|
|
||||||
|
|
||||||
await websocketUpdater(copilot.id, str(data) + "-none")
|
|
||||||
|
|
||||||
|
|
||||||
async def mark_webhook_sent(payment: Payment, status: int) -> None:
|
|
||||||
if payment.extra:
|
|
||||||
payment.extra["wh_status"] = status
|
|
||||||
await core_db.execute(
|
|
||||||
"""
|
|
||||||
UPDATE apipayments SET extra = ?
|
|
||||||
WHERE hash = ?
|
|
||||||
""",
|
|
||||||
(json.dumps(payment.extra), payment.payment_hash),
|
|
||||||
)
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<p>
|
|
||||||
StreamerCopilot: get tips via static QR (lnurl-pay) and show an
|
|
||||||
animation<br />
|
|
||||||
<small>
|
|
||||||
Created by,
|
|
||||||
<a class="text-secondary" href="https://github.com/benarc"
|
|
||||||
>Ben Arc</a
|
|
||||||
></small
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</q-card-section>
|
|
||||||
<q-expansion-item
|
|
||||||
group="extras"
|
|
||||||
icon="swap_vertical_circle"
|
|
||||||
label="API info"
|
|
||||||
:content-inset-level="0.5"
|
|
||||||
>
|
|
||||||
<q-btn flat label="Swagger API" type="a" href="../docs#/copilot"></q-btn>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Create copilot">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">POST</span> /copilot/api/v1/copilot</code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<copilot_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X POST {{ request.base_url }}copilot/api/v1/copilot -d
|
|
||||||
'{"title": <string>, "animation": <string>,
|
|
||||||
"show_message":<string>, "amount": <integer>,
|
|
||||||
"lnurl_title": <string>}' -H "Content-type: application/json"
|
|
||||||
-H "X-Api-Key: {{user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Update copilot">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">PUT</span>
|
|
||||||
/copilot/api/v1/copilot/<copilot_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<copilot_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X POST {{ request.base_url
|
|
||||||
}}copilot/api/v1/copilot/<copilot_id> -d '{"title":
|
|
||||||
<string>, "animation": <string>,
|
|
||||||
"show_message":<string>, "amount": <integer>,
|
|
||||||
"lnurl_title": <string>}' -H "Content-type: application/json"
|
|
||||||
-H "X-Api-Key: {{user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Get copilot">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">GET</span>
|
|
||||||
/copilot/api/v1/copilot/<copilot_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<copilot_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X GET {{ request.base_url
|
|
||||||
}}copilot/api/v1/copilot/<copilot_id> -H "X-Api-Key: {{
|
|
||||||
user.wallets[0].inkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item group="api" dense expand-separator label="Get copilots">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">GET</span> /copilot/api/v1/copilots</code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Body (application/json)
|
|
||||||
</h5>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
|
||||||
Returns 200 OK (application/json)
|
|
||||||
</h5>
|
|
||||||
<code>[<copilot_object>, ...]</code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X GET {{ request.base_url }}copilot/api/v1/copilots -H
|
|
||||||
"X-Api-Key: {{ user.wallets[0].inkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Delete a pay link"
|
|
||||||
class="q-pb-md"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-pink">DELETE</span>
|
|
||||||
/copilot/api/v1/copilot/<copilot_id></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Returns 204 NO CONTENT</h5>
|
|
||||||
<code></code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X DELETE {{ request.base_url
|
|
||||||
}}copilot/api/v1/copilot/<copilot_id> -H "X-Api-Key: {{
|
|
||||||
user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Trigger an animation"
|
|
||||||
class="q-pb-md"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<code
|
|
||||||
><span class="text-blue">GET</span>
|
|
||||||
/api/v1/copilot/ws/<copilot_id>/<comment>/<data></code
|
|
||||||
>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
|
||||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Returns 200</h5>
|
|
||||||
<code></code>
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
|
||||||
<code
|
|
||||||
>curl -X GET {{ request.base_url
|
|
||||||
}}copilot/api/v1/copilot/ws/<string, copilot_id>/<string,
|
|
||||||
comment>/<string, gif name> -H "X-Api-Key: {{
|
|
||||||
user.wallets[0].adminkey }}"
|
|
||||||
</code>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-card>
|
|
||||||
|
|
@ -1,305 +0,0 @@
|
||||||
{% extends "public.html" %} {% block page %}<q-page>
|
|
||||||
<video
|
|
||||||
autoplay="true"
|
|
||||||
id="videoScreen"
|
|
||||||
style="width: 100%"
|
|
||||||
class="fixed-bottom-right"
|
|
||||||
></video>
|
|
||||||
<video
|
|
||||||
autoplay="true"
|
|
||||||
id="videoCamera"
|
|
||||||
style="width: 100%"
|
|
||||||
class="fixed-bottom-right"
|
|
||||||
></video>
|
|
||||||
<img src="" style="width: 100%" id="animations" class="fixed-bottom-left" />
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="copilot.lnurl_toggle == 1"
|
|
||||||
id="draggableqr"
|
|
||||||
class="rounded-borders"
|
|
||||||
style="
|
|
||||||
width: 250px;
|
|
||||||
background-color: white;
|
|
||||||
height: 300px;
|
|
||||||
margin-top: 10%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="col">
|
|
||||||
<qrcode
|
|
||||||
:value="'lightning:' + copilot.lnurl"
|
|
||||||
:options="{width:250}"
|
|
||||||
class="rounded-borders"
|
|
||||||
></qrcode>
|
|
||||||
<center class="absolute-bottom" style="color: black; font-size: 20px">
|
|
||||||
{% raw %}{{ copilot.lnurl_title }}{% endraw %}
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2
|
|
||||||
id="draggableprice"
|
|
||||||
v-if="copilot.show_price != 0"
|
|
||||||
class="text-bold"
|
|
||||||
style="
|
|
||||||
margin: 60px 60px;
|
|
||||||
font-size: 110px;
|
|
||||||
text-shadow: 4px 8px 4px black;
|
|
||||||
color: white;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{% raw %}{{ price }}{% endraw %}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p
|
|
||||||
v-if="copilot.show_ack != 0"
|
|
||||||
class="fixed-top"
|
|
||||||
style="
|
|
||||||
font-size: 22px;
|
|
||||||
text-shadow: 2px 4px 1px black;
|
|
||||||
color: white;
|
|
||||||
padding-left: 40%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Powered by LNbits/StreamerCopilot
|
|
||||||
</p>
|
|
||||||
</q-page>
|
|
||||||
{% endblock %} {% block scripts %}
|
|
||||||
<style>
|
|
||||||
body.body--dark .q-drawer,
|
|
||||||
body.body--dark .q-footer,
|
|
||||||
body.body--dark .q-header,
|
|
||||||
.q-drawer,
|
|
||||||
.q-footer,
|
|
||||||
.q-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.q-page {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
#draggableqr {
|
|
||||||
width: 250px;
|
|
||||||
height: 300px;
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
#draggableprice {
|
|
||||||
width: 550px;
|
|
||||||
height: 60px;
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
|
|
||||||
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
|
|
||||||
<script>
|
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
price: '',
|
|
||||||
counter: 1,
|
|
||||||
colours: ['teal', 'purple', 'indigo', 'pink', 'green'],
|
|
||||||
copilot: {},
|
|
||||||
animQueue: [],
|
|
||||||
queue: false,
|
|
||||||
lnurl: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showNotif: function (userMessage) {
|
|
||||||
var colour =
|
|
||||||
this.colours[Math.floor(Math.random() * this.colours.length)]
|
|
||||||
this.$q.notify({
|
|
||||||
color: colour,
|
|
||||||
icon: 'chat_bubble_outline',
|
|
||||||
html: true,
|
|
||||||
message: '<h4 style="color: white;">' + userMessage + '</h4>',
|
|
||||||
position: 'top-left',
|
|
||||||
timeout: 5000
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openURL: function (url) {
|
|
||||||
return Quasar.utils.openURL(url)
|
|
||||||
},
|
|
||||||
initCamera() {
|
|
||||||
var video = document.querySelector('#videoCamera')
|
|
||||||
|
|
||||||
if (navigator.mediaDevices.getUserMedia) {
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getUserMedia({video: true})
|
|
||||||
.then(function (stream) {
|
|
||||||
video.srcObject = stream
|
|
||||||
})
|
|
||||||
.catch(function (err0r) {
|
|
||||||
console.log('Something went wrong!')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initScreenShare() {
|
|
||||||
var video = document.querySelector('#videoScreen')
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getDisplayMedia({video: true})
|
|
||||||
.then(function (stream) {
|
|
||||||
video.srcObject = stream
|
|
||||||
})
|
|
||||||
.catch(function (err0r) {
|
|
||||||
console.log('Something went wrong!')
|
|
||||||
})
|
|
||||||
},
|
|
||||||
pushAnim(content) {
|
|
||||||
document.getElementById('animations').style.width = content[0]
|
|
||||||
document.getElementById('animations').src = content[1]
|
|
||||||
if (content[2] != 'none') {
|
|
||||||
self.showNotif(content[2])
|
|
||||||
}
|
|
||||||
setTimeout(function () {
|
|
||||||
document.getElementById('animations').src = ''
|
|
||||||
}, 5000)
|
|
||||||
},
|
|
||||||
launch() {
|
|
||||||
self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/copilot/api/v1/copilot/ws/' +
|
|
||||||
self.copilot.id +
|
|
||||||
'/launching/rocket'
|
|
||||||
)
|
|
||||||
.then(function (response1) {
|
|
||||||
self.$q.notify({
|
|
||||||
color: 'green',
|
|
||||||
message: 'Sent!'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initCamera()
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
$(function () {
|
|
||||||
$('#draggableqr').draggable()
|
|
||||||
$('#draggableprice').draggable()
|
|
||||||
}),
|
|
||||||
(self = this)
|
|
||||||
self.copilot = JSON.parse(localStorage.getItem('copilot'))
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/copilot/api/v1/copilot/' + self.copilot.id,
|
|
||||||
localStorage.getItem('inkey')
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.copilot = response.data
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connectionBitStamp = new WebSocket('wss://ws.bitstamp.net')
|
|
||||||
|
|
||||||
const obj = JSON.stringify({
|
|
||||||
event: 'bts:subscribe',
|
|
||||||
data: {channel: 'live_trades_' + self.copilot.show_price}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connectionBitStamp.onmessage = function (e) {
|
|
||||||
if (self.copilot.show_price) {
|
|
||||||
if (self.copilot.show_price == 'btcusd') {
|
|
||||||
self.price = String(
|
|
||||||
new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'USD'
|
|
||||||
}).format(JSON.parse(e.data).data.price)
|
|
||||||
)
|
|
||||||
} else if (self.copilot.show_price == 'btceur') {
|
|
||||||
self.price = String(
|
|
||||||
new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'EUR'
|
|
||||||
}).format(JSON.parse(e.data).data.price)
|
|
||||||
)
|
|
||||||
} else if (self.copilot.show_price == 'btcgbp') {
|
|
||||||
self.price = String(
|
|
||||||
new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'GBP'
|
|
||||||
}).format(JSON.parse(e.data).data.price)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.connectionBitStamp.onopen = () => this.connectionBitStamp.send(obj)
|
|
||||||
|
|
||||||
const fetch = data =>
|
|
||||||
new Promise(resolve => setTimeout(resolve, 5000, this.pushAnim(data)))
|
|
||||||
|
|
||||||
const addTask = (() => {
|
|
||||||
let pending = Promise.resolve()
|
|
||||||
const run = async data => {
|
|
||||||
try {
|
|
||||||
await pending
|
|
||||||
} finally {
|
|
||||||
return fetch(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data => (pending = run(data))
|
|
||||||
})()
|
|
||||||
|
|
||||||
if (location.protocol !== 'http:') {
|
|
||||||
localUrl =
|
|
||||||
'wss://' +
|
|
||||||
document.domain +
|
|
||||||
':' +
|
|
||||||
location.port +
|
|
||||||
'/api/v1/ws/' +
|
|
||||||
self.copilot.id
|
|
||||||
} else {
|
|
||||||
localUrl =
|
|
||||||
'ws://' +
|
|
||||||
document.domain +
|
|
||||||
':' +
|
|
||||||
location.port +
|
|
||||||
'/api/v1/ws/' +
|
|
||||||
self.copilot.id
|
|
||||||
}
|
|
||||||
this.connection = new WebSocket(localUrl)
|
|
||||||
this.connection.onmessage = function (e) {
|
|
||||||
console.log(e)
|
|
||||||
res = e.data.split('-')
|
|
||||||
if (res[0] == 'rocket') {
|
|
||||||
addTask(['40%', '/copilot/static/rocket.gif', res[1]])
|
|
||||||
}
|
|
||||||
if (res[0] == 'face') {
|
|
||||||
addTask(['35%', '/copilot/static/face.gif', res[1]])
|
|
||||||
}
|
|
||||||
if (res[0] == 'bitcoin') {
|
|
||||||
addTask(['30%', '/copilot/static/bitcoin.gif', res[1]])
|
|
||||||
}
|
|
||||||
if (res[0] == 'confetti') {
|
|
||||||
addTask(['100%', '/copilot/static/confetti.gif', res[1]])
|
|
||||||
}
|
|
||||||
if (res[0] == 'martijn') {
|
|
||||||
addTask(['40%', '/copilot/static/martijn.gif', res[1]])
|
|
||||||
}
|
|
||||||
if (res[0] == 'rick') {
|
|
||||||
addTask(['40%', '/copilot/static/rick.gif', res[1]])
|
|
||||||
}
|
|
||||||
if (res[0] == 'true') {
|
|
||||||
document.getElementById('videoCamera').style.width = '20%'
|
|
||||||
self.initScreenShare()
|
|
||||||
}
|
|
||||||
if (res[0] == 'false') {
|
|
||||||
document.getElementById('videoCamera').style.width = '100%'
|
|
||||||
document.getElementById('videoScreen').src = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.connection.onopen = () => this.launch
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,660 +0,0 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
||||||
%} {% block page %}
|
|
||||||
|
|
||||||
<div class="row q-col-gutter-md">
|
|
||||||
<div class="col-12 col-md-7 q-gutter-y-md">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
{% raw %}
|
|
||||||
<q-btn unelevated color="primary" @click="formDialogCopilot.show = true"
|
|
||||||
>New copilot instance
|
|
||||||
</q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col">
|
|
||||||
<h5 class="text-subtitle1 q-my-none">Copilots</h5>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-input
|
|
||||||
borderless
|
|
||||||
dense
|
|
||||||
debounce="300"
|
|
||||||
v-model="filter"
|
|
||||||
placeholder="Search"
|
|
||||||
>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="search"></q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<q-btn flat color="grey" @click="exportcopilotCSV"
|
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-table
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
:data="CopilotLinks"
|
|
||||||
row-key="id"
|
|
||||||
:columns="CopilotsTable.columns"
|
|
||||||
:pagination.sync="CopilotsTable.pagination"
|
|
||||||
:filter="filter"
|
|
||||||
>
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th style="width: 5%"></q-th>
|
|
||||||
<q-th style="width: 5%"></q-th>
|
|
||||||
<q-th style="width: 5%"></q-th>
|
|
||||||
<q-th style="width: 5%"></q-th>
|
|
||||||
|
|
||||||
<q-th
|
|
||||||
v-for="col in props.cols"
|
|
||||||
:key="col.name"
|
|
||||||
:props="props"
|
|
||||||
auto-width
|
|
||||||
>
|
|
||||||
<div v-if="col.name == 'id'"></div>
|
|
||||||
<div v-else>{{ col.label }}</div>
|
|
||||||
</q-th>
|
|
||||||
<!-- <q-th auto-width></q-th> -->
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="apps"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openCopilotPanel(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip> Panel </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="face"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openCopilotCompose(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip> Compose window </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="deleteCopilotLink(props.row.id)"
|
|
||||||
icon="cancel"
|
|
||||||
color="pink"
|
|
||||||
>
|
|
||||||
<q-tooltip> Delete copilot </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="openUpdateCopilotLink(props.row.id)"
|
|
||||||
icon="edit"
|
|
||||||
color="light-blue"
|
|
||||||
>
|
|
||||||
<q-tooltip> Edit copilot </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td
|
|
||||||
v-for="col in props.cols"
|
|
||||||
:key="col.name"
|
|
||||||
:props="props"
|
|
||||||
auto-width
|
|
||||||
>
|
|
||||||
<div v-if="col.name == 'id'"></div>
|
|
||||||
<div v-else>{{ col.value }}</div>
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-md-5 q-gutter-y-md">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<h6 class="text-subtitle1 q-my-none">
|
|
||||||
{{SITE_TITLE}} StreamCopilot Extension
|
|
||||||
</h6>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<q-separator></q-separator>
|
|
||||||
<q-list> {% include "copilot/_api_docs.html" %} </q-list>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
<q-dialog
|
|
||||||
v-model="formDialogCopilot.show"
|
|
||||||
position="top"
|
|
||||||
@hide="closeFormDialog"
|
|
||||||
>
|
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
||||||
<q-form @submit="sendFormDataCopilot" class="q-gutter-md">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.title"
|
|
||||||
type="text"
|
|
||||||
label="Title"
|
|
||||||
></q-input>
|
|
||||||
<div class="row">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="formDialogCopilot.data.lnurl_toggle"
|
|
||||||
label="Include lnurl payment QR? (requires https)"
|
|
||||||
left-label
|
|
||||||
></q-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="formDialogCopilot.data.lnurl_toggle">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="formDialogCopilot.data.show_message"
|
|
||||||
left-label
|
|
||||||
label="Show lnurl-pay messages? (supported by few wallets)"
|
|
||||||
></q-checkbox>
|
|
||||||
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="formDialogCopilot.data.wallet"
|
|
||||||
:options="g.user.walletOptions"
|
|
||||||
label="Wallet *"
|
|
||||||
></q-select>
|
|
||||||
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Payment threshold 1"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation1"
|
|
||||||
:options="options"
|
|
||||||
label="Animation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col q-pl-xs">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation1threshold"
|
|
||||||
type="number"
|
|
||||||
step="1"
|
|
||||||
label="From *sats (min. 10)"
|
|
||||||
:rules="[ val => val >= 10 || 'Please use minimum 10' ]"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col q-pl-xs">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation1webhook"
|
|
||||||
type="text"
|
|
||||||
label="Webhook"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Payment threshold 2 (Must be higher than last)"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div
|
|
||||||
class="row"
|
|
||||||
v-if="formDialogCopilot.data.animation1threshold > 0"
|
|
||||||
>
|
|
||||||
<div class="col">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation2"
|
|
||||||
:options="options"
|
|
||||||
label="Animation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col q-pl-xs">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model="formDialogCopilot.data.animation2threshold"
|
|
||||||
type="number"
|
|
||||||
step="1"
|
|
||||||
label="From *sats"
|
|
||||||
:min="formDialogCopilot.data.animation1threshold"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col q-pl-xs">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation2webhook"
|
|
||||||
type="text"
|
|
||||||
label="Webhook"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<q-expansion-item
|
|
||||||
group="api"
|
|
||||||
dense
|
|
||||||
expand-separator
|
|
||||||
label="Payment threshold 3 (Must be higher than last)"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div
|
|
||||||
class="row"
|
|
||||||
v-if="formDialogCopilot.data.animation2threshold > formDialogCopilot.data.animation1threshold"
|
|
||||||
>
|
|
||||||
<div class="col">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation3"
|
|
||||||
:options="options"
|
|
||||||
label="Animation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col q-pl-xs">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model="formDialogCopilot.data.animation3threshold"
|
|
||||||
type="number"
|
|
||||||
step="1"
|
|
||||||
label="From *sats"
|
|
||||||
:min="formDialogCopilot.data.animation2threshold"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col q-pl-xs">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.animation3webhook"
|
|
||||||
type="text"
|
|
||||||
label="Webhook"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialogCopilot.data.lnurl_title"
|
|
||||||
type="text"
|
|
||||||
max="1440"
|
|
||||||
label="Lnurl title (message with QR code)"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="q-gutter-sm">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
style="width: 50%"
|
|
||||||
v-model.trim="formDialogCopilot.data.show_price"
|
|
||||||
:options="currencyOptions"
|
|
||||||
label="Show price"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="q-gutter-sm">
|
|
||||||
<div class="row">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="formDialogCopilot.data.show_ack"
|
|
||||||
left-label
|
|
||||||
label="Show 'powered by LNbits'"
|
|
||||||
></q-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
v-if="formDialogCopilot.data.id"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="
|
|
||||||
formDialogCopilot.data.title == ''"
|
|
||||||
type="submit"
|
|
||||||
>Update Copilot</q-btn
|
|
||||||
>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="
|
|
||||||
formDialogCopilot.data.title == ''"
|
|
||||||
type="submit"
|
|
||||||
>Create Copilot</q-btn
|
|
||||||
>
|
|
||||||
<q-btn @click="cancelCopilot" flat color="grey" class="q-ml-auto"
|
|
||||||
>Cancel</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</div>
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script>
|
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
|
||||||
|
|
||||||
var mapCopilot = obj => {
|
|
||||||
obj._data = _.clone(obj)
|
|
||||||
|
|
||||||
obj.theTime = obj.time * 60 - (Date.now() / 1000 - obj.timestamp)
|
|
||||||
obj.time = obj.time + 'mins'
|
|
||||||
|
|
||||||
if (obj.time_elapsed) {
|
|
||||||
obj.date = 'Time elapsed'
|
|
||||||
} else {
|
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
|
||||||
new Date((obj.theTime - 3600) * 1000),
|
|
||||||
'HH:mm:ss'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
obj.displayComposeUrl = ['/copilot/cp/', obj.id].join('')
|
|
||||||
obj.displayPanelUrl = ['/copilot/', obj.id].join('')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
filter: '',
|
|
||||||
CopilotLinks: [],
|
|
||||||
CopilotLinksObj: [],
|
|
||||||
CopilotsTable: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'theId',
|
|
||||||
align: 'left',
|
|
||||||
label: 'id',
|
|
||||||
field: 'id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lnurl_toggle',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Show lnurl pay link',
|
|
||||||
field: 'lnurl_toggle'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
align: 'left',
|
|
||||||
label: 'title',
|
|
||||||
field: 'title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'amount_made',
|
|
||||||
align: 'left',
|
|
||||||
label: 'amount made',
|
|
||||||
field: 'amount_made'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
passedCopilot: {},
|
|
||||||
formDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
formDialogCopilot: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
lnurl_toggle: false,
|
|
||||||
show_message: false,
|
|
||||||
show_ack: false,
|
|
||||||
show_price: 'None',
|
|
||||||
title: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
qrCodeDialog: {
|
|
||||||
show: false,
|
|
||||||
data: null
|
|
||||||
},
|
|
||||||
options: ['bitcoin', 'confetti', 'rocket', 'face', 'martijn', 'rick'],
|
|
||||||
currencyOptions: ['None', 'btcusd', 'btceur', 'btcgbp']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
cancelCopilot: function (data) {
|
|
||||||
var self = this
|
|
||||||
self.formDialogCopilot.show = false
|
|
||||||
self.clearFormDialogCopilot()
|
|
||||||
},
|
|
||||||
closeFormDialog: function () {
|
|
||||||
this.clearFormDialogCopilot()
|
|
||||||
this.formDialog.data = {
|
|
||||||
is_unique: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sendFormDataCopilot: function () {
|
|
||||||
var self = this
|
|
||||||
if (self.formDialogCopilot.data.id) {
|
|
||||||
this.updateCopilot(
|
|
||||||
self.g.user.wallets[0].adminkey,
|
|
||||||
self.formDialogCopilot.data
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
console.log(self.g.user.wallets[0].adminkey)
|
|
||||||
console.log(self.formDialogCopilot.data)
|
|
||||||
this.createCopilot(
|
|
||||||
self.g.user.wallets[0].adminkey,
|
|
||||||
self.formDialogCopilot.data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createCopilot: function (wallet, data) {
|
|
||||||
var self = this
|
|
||||||
var updatedData = {}
|
|
||||||
for (const property in data) {
|
|
||||||
if (data[property]) {
|
|
||||||
updatedData[property] = data[property]
|
|
||||||
}
|
|
||||||
if (property == 'animation1threshold' && data[property]) {
|
|
||||||
updatedData[property] = parseInt(data[property])
|
|
||||||
}
|
|
||||||
if (property == 'animation2threshold' && data[property]) {
|
|
||||||
updatedData[property] = parseInt(data[property])
|
|
||||||
}
|
|
||||||
if (property == 'animation3threshold' && data[property]) {
|
|
||||||
updatedData[property] = parseInt(data[property])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LNbits.api
|
|
||||||
.request('POST', '/copilot/api/v1/copilot', wallet, updatedData)
|
|
||||||
.then(function (response) {
|
|
||||||
self.CopilotLinks.push(mapCopilot(response.data))
|
|
||||||
self.formDialogCopilot.show = false
|
|
||||||
self.clearFormDialogCopilot()
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getCopilots: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/copilot/api/v1/copilot',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
if (response.data) {
|
|
||||||
self.CopilotLinks = response.data.map(mapCopilot)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getCopilot: function (copilot_id) {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/copilot/api/v1/copilot/' + copilot_id,
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
localStorage.setItem('copilot', JSON.stringify(response.data))
|
|
||||||
localStorage.setItem('inkey', self.g.user.wallets[0].inkey)
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openCopilotCompose: function (copilot_id) {
|
|
||||||
this.getCopilot(copilot_id)
|
|
||||||
let params =
|
|
||||||
'scrollbars=no, resizable=no,status=no,location=no,toolbar=no,menubar=no,width=1200,height=644,left=410,top=100'
|
|
||||||
open('../copilot/cp/', '_blank', params)
|
|
||||||
},
|
|
||||||
openCopilotPanel: function (copilot_id) {
|
|
||||||
this.getCopilot(copilot_id)
|
|
||||||
let params =
|
|
||||||
'scrollbars=no, resizable=no,status=no,location=no,toolbar=no,menubar=no,width=300,height=450,left=10,top=400'
|
|
||||||
open('../copilot/pn/', '_blank', params)
|
|
||||||
},
|
|
||||||
deleteCopilotLink: function (copilotId) {
|
|
||||||
var self = this
|
|
||||||
var link = _.findWhere(this.CopilotLinks, {id: copilotId})
|
|
||||||
LNbits.utils
|
|
||||||
.confirmDialog('Are you sure you want to delete this pay link?')
|
|
||||||
.onOk(function () {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
'/copilot/api/v1/copilot/' + copilotId,
|
|
||||||
self.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.CopilotLinks = _.reject(self.CopilotLinks, function (obj) {
|
|
||||||
return obj.id === copilotId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openUpdateCopilotLink: function (copilotId) {
|
|
||||||
var self = this
|
|
||||||
var copilot = _.findWhere(this.CopilotLinks, {id: copilotId})
|
|
||||||
self.formDialogCopilot.data = _.clone(copilot._data)
|
|
||||||
self.formDialogCopilot.show = true
|
|
||||||
},
|
|
||||||
updateCopilot: function (wallet, data) {
|
|
||||||
var self = this
|
|
||||||
var updatedData = {}
|
|
||||||
for (const property in data) {
|
|
||||||
if (data[property]) {
|
|
||||||
updatedData[property] = data[property]
|
|
||||||
}
|
|
||||||
if (property == 'animation1threshold' && data[property]) {
|
|
||||||
updatedData[property] = parseInt(data[property])
|
|
||||||
}
|
|
||||||
if (property == 'animation2threshold' && data[property]) {
|
|
||||||
updatedData[property] = parseInt(data[property])
|
|
||||||
}
|
|
||||||
if (property == 'animation3threshold' && data[property]) {
|
|
||||||
updatedData[property] = parseInt(data[property])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
'/copilot/api/v1/copilot/' + updatedData.id,
|
|
||||||
wallet,
|
|
||||||
updatedData
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.CopilotLinks = _.reject(self.CopilotLinks, function (obj) {
|
|
||||||
return obj.id === updatedData.id
|
|
||||||
})
|
|
||||||
self.CopilotLinks.push(mapCopilot(response.data))
|
|
||||||
self.formDialogCopilot.show = false
|
|
||||||
self.clearFormDialogCopilot()
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
clearFormDialogCopilot() {
|
|
||||||
this.formDialogCopilot.data = {
|
|
||||||
lnurl_toggle: false,
|
|
||||||
show_message: false,
|
|
||||||
show_ack: false,
|
|
||||||
show_price: 'None',
|
|
||||||
title: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
exportcopilotCSV: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.utils.exportCSV(self.CopilotsTable.columns, this.CopilotLinks)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
var self = this
|
|
||||||
var getCopilots = this.getCopilots
|
|
||||||
getCopilots()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
{% extends "public.html" %} {% block page %}
|
|
||||||
<div class="q-pa-sm" style="width: 240px; margin: 10px auto">
|
|
||||||
<q-card class="my-card">
|
|
||||||
<div class="column">
|
|
||||||
<div class="col">
|
|
||||||
<center>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
round
|
|
||||||
dense
|
|
||||||
@click="openCompose"
|
|
||||||
icon="face"
|
|
||||||
style="font-size: 60px"
|
|
||||||
></q-btn>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
<center>
|
|
||||||
<div class="col" style="margin: 15px; font-size: 22px">
|
|
||||||
Title: {% raw %} {{ copilot.title }} {% endraw %}
|
|
||||||
</div>
|
|
||||||
</center>
|
|
||||||
<q-separator></q-separator>
|
|
||||||
<div class="col">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
class="q-mt-sm q-ml-sm"
|
|
||||||
color="primary"
|
|
||||||
@click="fullscreenToggle"
|
|
||||||
label="Screen share"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-pa-sm">
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
style="width: 95%"
|
|
||||||
color="primary"
|
|
||||||
@click="animationBTN('rocket')"
|
|
||||||
label="rocket"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
style="width: 95%"
|
|
||||||
color="primary"
|
|
||||||
@click="animationBTN('confetti')"
|
|
||||||
label="confetti"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
style="width: 95%"
|
|
||||||
color="primary"
|
|
||||||
@click="animationBTN('face')"
|
|
||||||
label="face"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-pa-sm">
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
style="width: 95%"
|
|
||||||
color="primary"
|
|
||||||
@click="animationBTN('rick')"
|
|
||||||
label="rick"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
style="width: 95%"
|
|
||||||
color="primary"
|
|
||||||
@click="animationBTN('martijn')"
|
|
||||||
label="martijn"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<q-btn
|
|
||||||
style="width: 95%"
|
|
||||||
color="primary"
|
|
||||||
@click="animationBTN('bitcoin')"
|
|
||||||
label="bitcoin"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
|
||||||
<script>
|
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fullscreen_cam: true,
|
|
||||||
textareaModel: '',
|
|
||||||
iframe: '',
|
|
||||||
copilot: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
iframeChange: function (url) {
|
|
||||||
this.connection.send(String(url))
|
|
||||||
},
|
|
||||||
fullscreenToggle: function () {
|
|
||||||
self = this
|
|
||||||
self.animationBTN(String(this.fullscreen_cam))
|
|
||||||
if (this.fullscreen_cam) {
|
|
||||||
this.fullscreen_cam = false
|
|
||||||
} else {
|
|
||||||
this.fullscreen_cam = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openCompose: function () {
|
|
||||||
let params =
|
|
||||||
'scrollbars=no, resizable=no,status=no,location=no,toolbar=no,menubar=no,width=1200,height=644,left=410,top=100'
|
|
||||||
open('../cp/', 'test', params)
|
|
||||||
},
|
|
||||||
animationBTN: function (name) {
|
|
||||||
self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/copilot/api/v1/copilot/ws/' + self.copilot.id + '/none/' + name
|
|
||||||
)
|
|
||||||
.then(function (response1) {
|
|
||||||
self.$q.notify({
|
|
||||||
color: 'green',
|
|
||||||
message: 'Sent!'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
self = this
|
|
||||||
self.copilot = JSON.parse(localStorage.getItem('copilot'))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import Depends, Request
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
|
|
||||||
from lnbits.core.models import User
|
|
||||||
from lnbits.decorators import check_user_exists
|
|
||||||
|
|
||||||
from . import copilot_ext, copilot_renderer
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get("/", response_class=HTMLResponse)
|
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
|
||||||
return copilot_renderer().TemplateResponse(
|
|
||||||
"copilot/index.html", {"request": request, "user": user.dict()}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get("/cp/", response_class=HTMLResponse)
|
|
||||||
async def compose(request: Request):
|
|
||||||
return copilot_renderer().TemplateResponse(
|
|
||||||
"copilot/compose.html", {"request": request}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get("/pn/", response_class=HTMLResponse)
|
|
||||||
async def panel(request: Request):
|
|
||||||
return copilot_renderer().TemplateResponse(
|
|
||||||
"copilot/panel.html", {"request": request}
|
|
||||||
)
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
from fastapi import Depends, Query, Request
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
|
|
||||||
from lnbits.core.services import websocketUpdater
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
|
||||||
|
|
||||||
from . import copilot_ext
|
|
||||||
from .crud import (
|
|
||||||
create_copilot,
|
|
||||||
delete_copilot,
|
|
||||||
get_copilot,
|
|
||||||
get_copilots,
|
|
||||||
update_copilot,
|
|
||||||
)
|
|
||||||
from .models import CreateCopilotData
|
|
||||||
|
|
||||||
#######################COPILOT##########################
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get("/api/v1/copilot")
|
|
||||||
async def api_copilots_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
|
||||||
wallet_user = wallet.wallet.user
|
|
||||||
copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)]
|
|
||||||
try:
|
|
||||||
return copilots
|
|
||||||
except:
|
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT, detail="No copilots")
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get("/api/v1/copilot/{copilot_id}")
|
|
||||||
async def api_copilot_retrieve(
|
|
||||||
req: Request,
|
|
||||||
copilot_id: str = Query(None),
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
):
|
|
||||||
copilot = await get_copilot(copilot_id)
|
|
||||||
if not copilot:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
|
|
||||||
)
|
|
||||||
if not copilot.lnurl_toggle:
|
|
||||||
return copilot.dict()
|
|
||||||
return {**copilot.dict(), **{"lnurl": copilot.lnurl(req)}}
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.post("/api/v1/copilot")
|
|
||||||
@copilot_ext.put("/api/v1/copilot/{juke_id}")
|
|
||||||
async def api_copilot_create_or_update(
|
|
||||||
data: CreateCopilotData,
|
|
||||||
copilot_id: str = Query(None),
|
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
||||||
):
|
|
||||||
data.user = wallet.wallet.user
|
|
||||||
data.wallet = wallet.wallet.id
|
|
||||||
if copilot_id:
|
|
||||||
copilot = await update_copilot(data, copilot_id=copilot_id)
|
|
||||||
else:
|
|
||||||
copilot = await create_copilot(data, inkey=wallet.wallet.inkey)
|
|
||||||
return copilot
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.delete("/api/v1/copilot/{copilot_id}")
|
|
||||||
async def api_copilot_delete(
|
|
||||||
copilot_id: str = Query(None),
|
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
||||||
):
|
|
||||||
copilot = await get_copilot(copilot_id)
|
|
||||||
|
|
||||||
if not copilot:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
await delete_copilot(copilot_id)
|
|
||||||
|
|
||||||
return "", HTTPStatus.NO_CONTENT
|
|
||||||
|
|
||||||
|
|
||||||
@copilot_ext.get("/api/v1/copilot/ws/{copilot_id}/{comment}/{data}")
|
|
||||||
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:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await websocketUpdater(copilot_id, str(data) + "-" + str(comment))
|
|
||||||
except:
|
|
||||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your copilot")
|
|
||||||
return ""
|
|
||||||