Started streamer copilot extension
This commit is contained in:
parent
237a6b369f
commit
f29a852e9d
11 changed files with 1254 additions and 0 deletions
3
lnbits/extensions/copilot/README.md
Normal file
3
lnbits/extensions/copilot/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Stream Copilot
|
||||||
|
|
||||||
|
Tool to help streamers accept sats for tips
|
||||||
13
lnbits/extensions/copilot/__init__.py
Normal file
13
lnbits/extensions/copilot/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from quart import Blueprint
|
||||||
|
from lnbits.db import Database
|
||||||
|
|
||||||
|
db = Database("ext_copilot")
|
||||||
|
|
||||||
|
|
||||||
|
copilot_ext: Blueprint = Blueprint(
|
||||||
|
"copilot", __name__, static_folder="static", template_folder="templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
from .views_api import * # noqa
|
||||||
|
from .views import * # noqa
|
||||||
8
lnbits/extensions/copilot/config.json
Normal file
8
lnbits/extensions/copilot/config.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "StreamCopilot",
|
||||||
|
"short_description": "Tipping and animations for streamers",
|
||||||
|
"icon": "face",
|
||||||
|
"contributors": [
|
||||||
|
"arcbtc"
|
||||||
|
]
|
||||||
|
}
|
||||||
71
lnbits/extensions/copilot/crud.py
Normal file
71
lnbits/extensions/copilot/crud.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
# from lnbits.db import open_ext_db
|
||||||
|
from . import db
|
||||||
|
from .models import Copilots
|
||||||
|
|
||||||
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
||||||
|
from quart import jsonify
|
||||||
|
|
||||||
|
|
||||||
|
###############COPILOTS##########################
|
||||||
|
|
||||||
|
|
||||||
|
async def create_copilot(
|
||||||
|
title: str,
|
||||||
|
user: str,
|
||||||
|
animation: str = None,
|
||||||
|
show_message: Optional[str] = None,
|
||||||
|
amount: Optional[str] = None,
|
||||||
|
lnurl_title: Optional[str] = None,
|
||||||
|
) -> Copilots:
|
||||||
|
copilot_id = urlsafe_short_hash()
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO copilots (
|
||||||
|
id,
|
||||||
|
user,
|
||||||
|
title,
|
||||||
|
animation,
|
||||||
|
show_message,
|
||||||
|
amount,
|
||||||
|
lnurl_title
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
copilot_id,
|
||||||
|
user,
|
||||||
|
title,
|
||||||
|
animation,
|
||||||
|
show_message,
|
||||||
|
amount,
|
||||||
|
lnurl_title
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return await get_copilot(copilot_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_copilot(copilot_id: str, **kwargs) -> Optional[Copilots]:
|
||||||
|
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||||
|
await db.execute(
|
||||||
|
f"UPDATE copilots SET {q} WHERE id = ?", (*kwargs.values(), copilot_id)
|
||||||
|
)
|
||||||
|
row = await db.fetchone("SELECT * FROM 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 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 copilots WHERE user = ?", (user,))
|
||||||
|
return [Copilots.from_row(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_copilot(copilot_id: str) -> None:
|
||||||
|
await db.execute("DELETE FROM copilots WHERE id = ?", (copilot_id,))
|
||||||
19
lnbits/extensions/copilot/migrations.py
Normal file
19
lnbits/extensions/copilot/migrations.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
async def m001_initial(db):
|
||||||
|
"""
|
||||||
|
Initial copilot table.
|
||||||
|
"""
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS copilots (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
user TEXT,
|
||||||
|
title TEXT,
|
||||||
|
animation INTEGER,
|
||||||
|
show_message TEXT,
|
||||||
|
amount INTEGER,
|
||||||
|
lnurl_title TEXT,
|
||||||
|
timestamp TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
24
lnbits/extensions/copilot/models.py
Normal file
24
lnbits/extensions/copilot/models.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from sqlite3 import Row
|
||||||
|
from typing import NamedTuple
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class Copilots(NamedTuple):
|
||||||
|
id: str
|
||||||
|
user: str
|
||||||
|
title: str
|
||||||
|
animation: str
|
||||||
|
show_message: bool
|
||||||
|
amount: int
|
||||||
|
lnurl_title: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, row: Row) -> "Charges":
|
||||||
|
return cls(**dict(row))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def paid(self):
|
||||||
|
if self.balance >= self.amount:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
169
lnbits/extensions/copilot/templates/copilot/_api_docs.html
Normal file
169
lnbits/extensions/copilot/templates/copilot/_api_docs.html
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<p>
|
||||||
|
StreamCopilot: get tips and show an animation<br />
|
||||||
|
<small>
|
||||||
|
Created by, <a 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-expansion-item group="api" dense expand-separator label="Create charge">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-blue">POST</span> /satspay/api/v1/charge</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>[<charge_object>, ...]</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X POST {{ request.url_root }}api/v1/charge -d
|
||||||
|
'{"onchainwallet": <string, watchonly_wallet_id>,
|
||||||
|
"description": <string>, "webhook":<string>, "time":
|
||||||
|
<integer>, "amount": <integer>, "lnbitswallet":
|
||||||
|
<string, lnbits_wallet_id>}' -H "Content-type:
|
||||||
|
application/json" -H "X-Api-Key: {{g.user.wallets[0].adminkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Update charge">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-blue">PUT</span>
|
||||||
|
/satspay/api/v1/charge/<charge_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>[<charge_object>, ...]</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X POST {{ request.url_root }}api/v1/charge/<charge_id>
|
||||||
|
-d '{"onchainwallet": <string, watchonly_wallet_id>,
|
||||||
|
"description": <string>, "webhook":<string>, "time":
|
||||||
|
<integer>, "amount": <integer>, "lnbitswallet":
|
||||||
|
<string, lnbits_wallet_id>}' -H "Content-type:
|
||||||
|
application/json" -H "X-Api-Key: {{g.user.wallets[0].adminkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Get charge">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-blue">GET</span>
|
||||||
|
/satspay/api/v1/charge/<charge_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>[<charge_object>, ...]</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X GET {{ request.url_root }}api/v1/charge/<charge_id>
|
||||||
|
-H "X-Api-Key: {{ g.user.wallets[0].inkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Get charges">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-blue">GET</span> /satspay/api/v1/charges</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>[<charge_object>, ...]</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X GET {{ request.url_root }}api/v1/charges -H "X-Api-Key: {{
|
||||||
|
g.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>
|
||||||
|
/satspay/api/v1/charge/<charge_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.url_root
|
||||||
|
}}api/v1/charge/<charge_id> -H "X-Api-Key: {{
|
||||||
|
g.user.wallets[0].adminkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-expansion-item group="api" dense expand-separator label="Get balances">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-blue">GET</span>
|
||||||
|
/satspay/api/v1/charges/balance/<charge_id></code
|
||||||
|
>
|
||||||
|
<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>[<charge_object>, ...]</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
<code
|
||||||
|
>curl -X GET {{ request.url_root
|
||||||
|
}}api/v1/charges/balance/<charge_id> -H "X-Api-Key: {{
|
||||||
|
g.user.wallets[0].inkey }}"
|
||||||
|
</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-card>
|
||||||
318
lnbits/extensions/copilot/templates/copilot/display.html
Normal file
318
lnbits/extensions/copilot/templates/copilot/display.html
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
{% extends "public.html" %} {% block page %}
|
||||||
|
<div class="q-pa-sm theCard">
|
||||||
|
<q-card class="my-card">
|
||||||
|
<div class="column">
|
||||||
|
<center>
|
||||||
|
<div class="col theHeading">{{ charge.description }}</div>
|
||||||
|
</center>
|
||||||
|
<div class="col">
|
||||||
|
<div
|
||||||
|
class="col"
|
||||||
|
color="white"
|
||||||
|
style="background-color: grey; height: 30px; padding: 5px"
|
||||||
|
v-if="charge_time_elapsed == 'True'"
|
||||||
|
>
|
||||||
|
<center>Time elapsed</center>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="col"
|
||||||
|
color="white"
|
||||||
|
style="background-color: grey; height: 30px; padding: 5px"
|
||||||
|
v-else-if="charge_paid == 'True'"
|
||||||
|
>
|
||||||
|
<center>Charge paid</center>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<q-linear-progress size="30px" :value="newProgress" color="grey">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item style="padding: 3px">
|
||||||
|
<q-spinner color="white" size="0.8em"></q-spinner
|
||||||
|
><span style="font-size: 15px; color: white"
|
||||||
|
><span class="q-pr-xl q-pl-md"> Awaiting payment...</span>
|
||||||
|
<span class="q-pl-xl" style="color: white">
|
||||||
|
{% raw %} {{ newTimeLeft }} {% endraw %}</span
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
</q-item>
|
||||||
|
</q-item-section>
|
||||||
|
</q-linear-progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col" style="margin: 2px 15px; max-height: 100px">
|
||||||
|
<center>
|
||||||
|
<q-btn flat dense outline @click="copyText('{{ charge.id }}')"
|
||||||
|
>Charge ID: {{ charge.id }}</q-btn
|
||||||
|
>
|
||||||
|
</center>
|
||||||
|
<span
|
||||||
|
><small
|
||||||
|
>{% raw %} Total to pay: {{ charge_amount }}sats<br />
|
||||||
|
Amount paid: {{ charge_balance }}</small
|
||||||
|
><br />
|
||||||
|
Amount due: {{ charge_amount - charge_balance }}sats {% endraw %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
disable
|
||||||
|
v-if="'{{ charge.lnbitswallet }}' == 'None' || charge_time_elapsed == 'True'"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="lightning⚡"
|
||||||
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
bitcoin onchain payment method not available
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
v-else
|
||||||
|
@click="payLN"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="lightning⚡"
|
||||||
|
>
|
||||||
|
<q-tooltip> pay with lightning </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
disable
|
||||||
|
v-if="'{{ charge.onchainwallet }}' == 'None' || charge_time_elapsed == 'True'"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="onchain⛓️"
|
||||||
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
bitcoin lightning payment method not available
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
v-else
|
||||||
|
@click="payON"
|
||||||
|
style="color: primary; width: 100%"
|
||||||
|
label="onchain⛓️"
|
||||||
|
>
|
||||||
|
<q-tooltip> pay onchain </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-card class="q-pa-lg" v-if="lnbtc">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<div class="text-center q-pt-md">
|
||||||
|
<div v-if="charge_time_elapsed == 'True' && charge_paid == 'False'">
|
||||||
|
<q-icon
|
||||||
|
name="block"
|
||||||
|
style="color: #ccc; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="charge_paid == 'True'">
|
||||||
|
<q-icon
|
||||||
|
name="check"
|
||||||
|
style="color: green; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
v-if="'{{ charge.webhook }}' != 'None'"
|
||||||
|
type="a"
|
||||||
|
href="{{ charge.completelink }}"
|
||||||
|
label="{{ charge.completelinktext }}"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<center>
|
||||||
|
<span class="text-subtitle2"
|
||||||
|
>Pay this <br />
|
||||||
|
lightning-network invoice</span
|
||||||
|
>
|
||||||
|
</center>
|
||||||
|
<a href="lightning://{{ charge.payment_request }}">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
|
<qrcode
|
||||||
|
:value="'{{ charge.payment_request }}'"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</a>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
@click="copyText('{{ charge.payment_request }}')"
|
||||||
|
>Copy invoice</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
<q-card class="q-pa-lg" v-if="onbtc">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<div class="text-center q-pt-md">
|
||||||
|
<div v-if="charge_time_elapsed == 'True' && charge_paid == 'False'">
|
||||||
|
<q-icon
|
||||||
|
name="block"
|
||||||
|
style="color: #ccc; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="charge_paid == 'True'">
|
||||||
|
<q-icon
|
||||||
|
name="check"
|
||||||
|
style="color: green; font-size: 21.4em"
|
||||||
|
></q-icon>
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
v-if="'{{ charge.webhook }}' != 'None'"
|
||||||
|
type="a"
|
||||||
|
href="{{ charge.completelink }}"
|
||||||
|
label="{{ charge.completelinktext }}"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<center>
|
||||||
|
<span class="text-subtitle2"
|
||||||
|
>Send {{ charge.amount }}sats<br />
|
||||||
|
to this onchain address</span
|
||||||
|
>
|
||||||
|
</center>
|
||||||
|
<a href="bitcoin://{{ charge.onchainaddress }}">
|
||||||
|
<q-responsive :ratio="1" class="q-mx-md">
|
||||||
|
<qrcode
|
||||||
|
:value="'{{ charge.onchainaddress }}'"
|
||||||
|
:options="{width: 800}"
|
||||||
|
class="rounded-borders"
|
||||||
|
></qrcode>
|
||||||
|
</q-responsive>
|
||||||
|
</a>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
outline
|
||||||
|
color="grey"
|
||||||
|
@click="copyText('{{ charge.onchainaddress }}')"
|
||||||
|
>Copy address</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %} {% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
|
<style>
|
||||||
|
.theCard {
|
||||||
|
width: 360px;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
.theHeading {
|
||||||
|
margin: 15px;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
newProgress: 0.4,
|
||||||
|
counter: 1,
|
||||||
|
newTimeLeft: '',
|
||||||
|
lnbtc: true,
|
||||||
|
onbtc: false,
|
||||||
|
charge_time_elapsed: '{{charge.time_elapsed}}',
|
||||||
|
charge_amount: '{{charge.amount}}',
|
||||||
|
charge_balance: '{{charge.balance}}',
|
||||||
|
charge_paid: '{{charge.paid}}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkBalance: function () {
|
||||||
|
var self = this
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/satspay/api/v1/charges/balance/{{ charge.id }}',
|
||||||
|
'filla'
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
console.log(response.data)
|
||||||
|
self.charge_time_elapsed = response.data.time_elapsed
|
||||||
|
self.charge_amount = response.data.amount
|
||||||
|
self.charge_balance = response.data.balance
|
||||||
|
if (self.charge_balance >= self.charge_amount) {
|
||||||
|
self.charge_paid = 'True'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
payLN: function () {
|
||||||
|
this.lnbtc = true
|
||||||
|
this.onbtc = false
|
||||||
|
},
|
||||||
|
payON: function () {
|
||||||
|
this.lnbtc = false
|
||||||
|
this.onbtc = true
|
||||||
|
},
|
||||||
|
getTheTime: function () {
|
||||||
|
var timeToComplete =
|
||||||
|
parseInt('{{ charge.time }}') * 60 -
|
||||||
|
(Date.now() / 1000 - parseInt('{{ charge.timestamp }}'))
|
||||||
|
var timeLeft = Quasar.utils.date.formatDate(
|
||||||
|
new Date((timeToComplete - 3600) * 1000),
|
||||||
|
'HH:mm:ss'
|
||||||
|
)
|
||||||
|
this.newTimeLeft = timeLeft
|
||||||
|
},
|
||||||
|
getThePercentage: function () {
|
||||||
|
var timeToComplete =
|
||||||
|
parseInt('{{ charge.time }}') * 60 -
|
||||||
|
(Date.now() / 1000 - parseInt('{{ charge.timestamp }}'))
|
||||||
|
this.newProgress =
|
||||||
|
1 - timeToComplete / (parseInt('{{ charge.time }}') * 60)
|
||||||
|
},
|
||||||
|
|
||||||
|
timerCount: function () {
|
||||||
|
self = this
|
||||||
|
var refreshIntervalId = setInterval(function () {
|
||||||
|
if (self.charge_paid == 'True') {
|
||||||
|
console.log('did this work?')
|
||||||
|
clearInterval(refreshIntervalId)
|
||||||
|
}
|
||||||
|
self.getTheTime()
|
||||||
|
self.getThePercentage()
|
||||||
|
self.counter++
|
||||||
|
if (self.counter % 10 === 0) {
|
||||||
|
self.checkBalance()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
if ('{{ charge.lnbitswallet }}' == 'None') {
|
||||||
|
this.lnbtc = false
|
||||||
|
this.onbtc = true
|
||||||
|
}
|
||||||
|
this.getTheTime()
|
||||||
|
this.getThePercentage()
|
||||||
|
var timerCount = this.timerCount
|
||||||
|
if ('{{ charge.paid }}' == 'False') {
|
||||||
|
timerCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
521
lnbits/extensions/copilot/templates/copilot/index.html
Normal file
521
lnbits/extensions/copilot/templates/copilot/index.html
Normal file
|
|
@ -0,0 +1,521 @@
|
||||||
|
{% 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="deep-purple"
|
||||||
|
@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 auto-width></q-th>
|
||||||
|
<q-th auto-width></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 auto-width>
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
dense
|
||||||
|
size="xs"
|
||||||
|
icon="link"
|
||||||
|
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||||
|
type="a"
|
||||||
|
:href="props.row.displayUrl"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<q-tooltip> Payment link </q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-td>
|
||||||
|
<q-td auto-width>
|
||||||
|
<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
|
||||||
|
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">LNbits 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.description"
|
||||||
|
type="text"
|
||||||
|
label="*Description"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCopilot.data.amount"
|
||||||
|
type="number"
|
||||||
|
label="*Amount (sats)"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCopilot.data.time"
|
||||||
|
type="number"
|
||||||
|
max="1440"
|
||||||
|
label="*Mins valid for (max 1440)"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCopilot.data.webhook"
|
||||||
|
type="url"
|
||||||
|
label="Webhook (URL to send transaction data to once paid)"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCopilot.data.completelink"
|
||||||
|
type="url"
|
||||||
|
label="Completed button URL"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialogCopilot.data.completelinktext"
|
||||||
|
type="text"
|
||||||
|
label="Completed button text (ie 'Back to merchant')"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div v-if="walletLinks.length > 0">
|
||||||
|
<q-checkbox
|
||||||
|
v-model="formDialogCopilot.data.onchain"
|
||||||
|
label="Onchain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<q-checkbox :value="false" label="Onchain" disabled>
|
||||||
|
<q-tooltip>
|
||||||
|
Watch-Only extension MUST be activated and have a wallet
|
||||||
|
</q-tooltip>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="formDialogCopilot.data.lnbits"
|
||||||
|
label="LNbits wallet"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="formDialogCopilot.data.onchain">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
emit-value
|
||||||
|
v-model="formDialogCopilot.data.onchainwallet"
|
||||||
|
:options="walletLinks"
|
||||||
|
label="Onchain Wallet"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-select
|
||||||
|
v-if="formDialogCopilot.data.lnbits"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
emit-value
|
||||||
|
v-model="formDialogCopilot.data.lnbitswallet"
|
||||||
|
:options="g.user.walletOptions"
|
||||||
|
label="Wallet *"
|
||||||
|
>
|
||||||
|
</q-select>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="deep-purple"
|
||||||
|
:disable="
|
||||||
|
formDialogCopilot.data.time == null ||
|
||||||
|
formDialogCopilot.data.amount == null"
|
||||||
|
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 src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
|
<style></style>
|
||||||
|
<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.displayUrl = ['/copilot/', obj.id].join('')
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [windowMixin],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
filter: '',
|
||||||
|
watchonlyactive: false,
|
||||||
|
balance: null,
|
||||||
|
checker: null,
|
||||||
|
walletLinks: [],
|
||||||
|
CopilotLinks: [],
|
||||||
|
CopilotLinksObj: [],
|
||||||
|
onchainwallet: '',
|
||||||
|
currentaddress: '',
|
||||||
|
Addresses: {
|
||||||
|
show: false,
|
||||||
|
data: null
|
||||||
|
},
|
||||||
|
mempool: {
|
||||||
|
endpoint: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
CopilotsTable: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'theId',
|
||||||
|
align: 'left',
|
||||||
|
label: 'ID',
|
||||||
|
field: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Title',
|
||||||
|
field: 'description'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timeleft',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Time left',
|
||||||
|
field: 'date'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'time to pay',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Time to Pay',
|
||||||
|
field: 'time'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Amount to pay',
|
||||||
|
field: 'amount'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'balance',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Balance',
|
||||||
|
field: 'balance'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'onchain address',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Onchain Address',
|
||||||
|
field: 'onchainaddress'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'LNbits wallet',
|
||||||
|
align: 'left',
|
||||||
|
label: 'LNbits wallet',
|
||||||
|
field: 'lnbitswallet'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Webhook link',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Webhook link',
|
||||||
|
field: 'webhook'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Paid link',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Paid link',
|
||||||
|
field: 'completelink'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
formDialogCopilot: {
|
||||||
|
show: false,
|
||||||
|
data: {
|
||||||
|
onchain: false,
|
||||||
|
lnbits: false,
|
||||||
|
description: '',
|
||||||
|
time: null,
|
||||||
|
amount: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
qrCodeDialog: {
|
||||||
|
show: false,
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
cancelCopilot: function (data) {
|
||||||
|
var self = this
|
||||||
|
self.formDialogCopilot.data.description = ''
|
||||||
|
self.formDialogCopilot.data.onchainwallet = ''
|
||||||
|
self.formDialogCopilot.data.lnbitswallet = ''
|
||||||
|
self.formDialogCopilot.data.time = null
|
||||||
|
self.formDialogCopilot.data.amount = null
|
||||||
|
self.formDialogCopilot.data.webhook = ''
|
||||||
|
self.formDialogCopilot.data.completelink = ''
|
||||||
|
self.formDialogCopilot.show = false
|
||||||
|
},
|
||||||
|
|
||||||
|
getWalletLinks: function () {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/watchonly/api/v1/wallet',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
for (i = 0; i < response.data.length; i++) {
|
||||||
|
self.walletLinks.push(response.data[i].id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeFormDialog: function () {
|
||||||
|
this.formDialog.data = {
|
||||||
|
is_unique: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openQrCodeDialog: function (linkId) {
|
||||||
|
var self = this
|
||||||
|
var getAddresses = this.getAddresses
|
||||||
|
getAddresses(linkId)
|
||||||
|
self.current = linkId
|
||||||
|
self.Addresses.show = true
|
||||||
|
},
|
||||||
|
getCopilots: function () {
|
||||||
|
var self = this
|
||||||
|
var getAddressBalance = this.getAddressBalance
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/copilot/api/v1/copilots',
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.CopilotLinks = response.data.map(mapCopilot)
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sendFormDataCopilot: function () {
|
||||||
|
var self = this
|
||||||
|
var wallet = this.g.user.wallets[0].adminkey
|
||||||
|
var data = this.formDialogCopilot.data
|
||||||
|
data.amount = parseInt(data.amount)
|
||||||
|
data.time = parseInt(data.time)
|
||||||
|
this.createCopilot(wallet, data)
|
||||||
|
},
|
||||||
|
timerCount: function () {
|
||||||
|
self = this
|
||||||
|
var refreshIntervalId = setInterval(function () {
|
||||||
|
for (i = 0; i < self.CopilotLinks.length - 1; i++) {
|
||||||
|
if (self.CopilotLinks[i]['paid'] == 'True') {
|
||||||
|
setTimeout(function () {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'GET',
|
||||||
|
'/copilot/api/v1/copilots/balance/' +
|
||||||
|
self.CopilotLinks[i]['id'],
|
||||||
|
'filla'
|
||||||
|
)
|
||||||
|
.then(function (response) {})
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.getCopilots()
|
||||||
|
}, 20000)
|
||||||
|
},
|
||||||
|
createCopilot: function (wallet, data) {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request('POST', '/copilot/api/v1/copilot', wallet, data)
|
||||||
|
.then(function (response) {
|
||||||
|
self.CopilotLinks.push(mapCopilot(response.data))
|
||||||
|
self.formDialogCopilot.show = false
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
exportcopilotCSV: function () {
|
||||||
|
var self = this
|
||||||
|
LNbits.utils.exportCSV(self.CopilotsTable.columns, this.CopilotLinks)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
var self = this
|
||||||
|
var getCopilots = this.getCopilots
|
||||||
|
getCopilots()
|
||||||
|
var getWalletLinks = this.getWalletLinks
|
||||||
|
getWalletLinks()
|
||||||
|
var timerCount = this.timerCount
|
||||||
|
timerCount()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
22
lnbits/extensions/copilot/views.py
Normal file
22
lnbits/extensions/copilot/views.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
from quart import g, abort, render_template, jsonify
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
||||||
|
from . import copilot_ext
|
||||||
|
from .crud import get_copilot
|
||||||
|
|
||||||
|
|
||||||
|
@copilot_ext.route("/")
|
||||||
|
@validate_uuids(["usr"], required=True)
|
||||||
|
@check_user_exists()
|
||||||
|
async def index():
|
||||||
|
return await render_template("copilot/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
|
@copilot_ext.route("/<copilot_id>")
|
||||||
|
async def display(copilot_id):
|
||||||
|
copilot = await get_copilot(copilot_id) or abort(
|
||||||
|
HTTPStatus.NOT_FOUND, "Charge link does not exist."
|
||||||
|
)
|
||||||
|
return await render_template("copilot/display.html", copilot=copilot)
|
||||||
86
lnbits/extensions/copilot/views_api.py
Normal file
86
lnbits/extensions/copilot/views_api.py
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import hashlib
|
||||||
|
from quart import g, jsonify, url_for
|
||||||
|
from http import HTTPStatus
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from lnbits.core.crud import get_user
|
||||||
|
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||||
|
|
||||||
|
from lnbits.extensions.copilot import copilot_ext
|
||||||
|
from .crud import (
|
||||||
|
create_copilot,
|
||||||
|
update_copilot,
|
||||||
|
get_copilot,
|
||||||
|
get_copilots,
|
||||||
|
delete_copilot,
|
||||||
|
)
|
||||||
|
|
||||||
|
#############################COPILOT##########################
|
||||||
|
|
||||||
|
|
||||||
|
@copilot_ext.route("/api/v1/copilot", methods=["POST"])
|
||||||
|
@copilot_ext.route("/api/v1/copilot/<copilot_id>", methods=["PUT"])
|
||||||
|
@api_check_wallet_key("admin")
|
||||||
|
@api_validate_post_request(
|
||||||
|
schema={
|
||||||
|
"title": {"type": "string", "empty": False, "required": True},
|
||||||
|
"animation": {"type": "string", "empty": False, "required": True},
|
||||||
|
"show_message": {"type": "integer", "empty": False, "required": True},
|
||||||
|
"amount": {"type": "integer", "empty": False, "required": True},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def api_copilot_create_or_update(copilot_id=None):
|
||||||
|
if not copilot_id:
|
||||||
|
copilot = await create_copilot(user=g.wallet.user, **g.data)
|
||||||
|
return jsonify(copilot._asdict()), HTTPStatus.CREATED
|
||||||
|
else:
|
||||||
|
copilot = await update_copilot(copilot_id=copilot_id, **g.data)
|
||||||
|
return jsonify(copilot._asdict()), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
|
@copilot_ext.route("/api/v1/copilot", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_copilot_retrieve(copilot_id):
|
||||||
|
copilots = await get_copilots(user=g.wallet.user)
|
||||||
|
|
||||||
|
if not copilots:
|
||||||
|
return jsonify({"message": "copilot does not exist"}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
return (
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
copilots._asdict()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
HTTPStatus.OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
@copilot_ext.route("/api/v1/copilot/<copilot_id>", methods=["GET"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_copilot_retrieve(copilot_id):
|
||||||
|
copilot = await get_copilot(copilot_id)
|
||||||
|
|
||||||
|
if not copilot:
|
||||||
|
return jsonify({"message": "copilot does not exist"}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
return (
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
copilot._asdict()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
HTTPStatus.OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@copilot_ext.route("/api/v1/copilot/<copilot_id>", methods=["DELETE"])
|
||||||
|
@api_check_wallet_key("invoice")
|
||||||
|
async def api_copilot_delete(copilot_id):
|
||||||
|
copilot = await get_copilot(copilot_id)
|
||||||
|
|
||||||
|
if not copilot:
|
||||||
|
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
await delete_copilot(copilot_id)
|
||||||
|
|
||||||
|
return "", HTTPStatus.NO_CONTENT
|
||||||
Loading…
Add table
Add a link
Reference in a new issue