diff --git a/lnbits/extensions/copilot/__init__.py b/lnbits/extensions/copilot/__init__.py index 1f227d66..cefe5064 100644 --- a/lnbits/extensions/copilot/__init__.py +++ b/lnbits/extensions/copilot/__init__.py @@ -9,3 +9,4 @@ copilot_ext: Blueprint = Blueprint( from .views_api import * # noqa from .views import * # noqa +from .lnurl import * # noqa \ No newline at end of file diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py index 99d582f2..b5f09278 100644 --- a/lnbits/extensions/copilot/crud.py +++ b/lnbits/extensions/copilot/crud.py @@ -15,6 +15,7 @@ from quart import jsonify async def create_copilot( title: str, user: str, + wallet: str, animation1: Optional[str] = None, animation2: Optional[str] = None, animation3: Optional[str] = None, @@ -36,6 +37,7 @@ async def create_copilot( INSERT INTO copilots ( id, user, + wallet, title, animation1, animation2, @@ -52,11 +54,12 @@ async def create_copilot( lnurl_title, amount_made ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( copilot_id, user, + wallet, title, animation1, animation2, diff --git a/lnbits/extensions/copilot/lnurl.py b/lnbits/extensions/copilot/lnurl.py index 1ab8eec4..f06f888c 100644 --- a/lnbits/extensions/copilot/lnurl.py +++ b/lnbits/extensions/copilot/lnurl.py @@ -9,19 +9,19 @@ from . import copilot_ext from .crud import get_copilot -@copilot_ext.route("/lnurl/", methods=["GET"]) -async def lnurl_response(copilot_id): - copilot = await get_copilot(copilot_id) - if not copilot: +@copilot_ext.route("/lnurl/", methods=["GET"]) +async def lnurl_response(cp_id): + cp = await get_copilot(cp_id) + if not cp: return jsonify({"status": "ERROR", "reason": "Copilot not found."}) resp = LnurlPayResponse( callback=url_for( - "copilot.lnurl_callback", _external=True + "copilot.lnurl_callback", cp_id=cp_id, _external=True ), - min_sendable=copilot.amount, - max_sendable=copilot.amount, - metadata=copilot.lnurl_title, + min_sendable=10, + max_sendable=50000, + metadata=cp.lnurl_title, ) params = resp.dict() @@ -30,24 +30,27 @@ async def lnurl_response(copilot_id): return jsonify(params) -@copilot_ext.route("/lnurl/cb", methods=["GET"]) -async def lnurl_callback(): +@copilot_ext.route("/lnurl/cb/", methods=["GET"]) +async def lnurl_callback(cp_id): + cp = await get_copilot(cp_id) + if not cp: + return jsonify({"status": "ERROR", "reason": "Copilot not found."}) amount_received = int(request.args.get("amount")) - if amount_received < track.amount: + if amount_received < 10: return ( jsonify( LnurlErrorResponse( - reason=f"Amount {round(amount_received / 1000)} is smaller than minimum {math.floor(track.min_sendable)}." + reason=f"Amount {round(amount_received / 1000)} is smaller than minimum 10 sats." ).dict() ), ) - elif track.max_sendable < amount_received: + elif 50000 > amount_received/1000: return ( jsonify( LnurlErrorResponse( - reason=f"Amount {round(amount_received / 1000)} is greater than maximum {math.floor(track.max_sendable)}." + reason=f"Amount {round(amount_received / 1000)} is greater than maximum 50000." ).dict() ), ) @@ -60,21 +63,19 @@ async def lnurl_callback(): ).dict() ) - copilot = await get_copilot_by_track(track_id) - payment_hash, payment_request = await create_invoice( - wallet_id=copilot.wallet, + wallet_id=cp.wallet, amount=int(amount_received / 1000), - memo=await track.fullname(), + memo=cp.lnurl_title, description_hash=hashlib.sha256( - (await track.lnurlpay_metadata()).encode("utf-8") + (cp.lnurl_title).encode("utf-8") ).digest(), - extra={"tag": "copilot", "track": track.id, "comment": comment}, + extra={"tag": "copilot", "comment": comment}, ) if amount_received < track.price_msat: success_action = None - ecopilote: + else: success_action = track.success_action(payment_hash) resp = LnurlPayActionResponse( @@ -82,5 +83,8 @@ async def lnurl_callback(): success_action=success_action, routes=[], ) - - return jsonify(resp.dict()) + socket_sendererer = app.socket_sendererer() + async with socket_sendererer.websocket('/ws') as the_websocket: + await the_websocket.send("pay{payment_hash}") + + return jsonify(resp.dict()) \ No newline at end of file diff --git a/lnbits/extensions/copilot/migrations.py b/lnbits/extensions/copilot/migrations.py index 1bfdbed4..783609e5 100644 --- a/lnbits/extensions/copilot/migrations.py +++ b/lnbits/extensions/copilot/migrations.py @@ -9,6 +9,7 @@ async def m001_initial(db): id TEXT NOT NULL PRIMARY KEY, user TEXT, title TEXT, + wallet TEXT, animation1 TEXT, animation2 TEXT, animation3 TEXT, diff --git a/lnbits/extensions/copilot/models.py b/lnbits/extensions/copilot/models.py index 78c295c9..42ecff5d 100644 --- a/lnbits/extensions/copilot/models.py +++ b/lnbits/extensions/copilot/models.py @@ -1,12 +1,16 @@ from sqlite3 import Row from typing import NamedTuple import time - +from quart import url_for +from lnurl import Lnurl, encode as lnurl_encode # type: ignore +from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore class Copilots(NamedTuple): id: str user: str title: str + wallet: str animation1: str animation2: str animation3: str @@ -28,3 +32,8 @@ class Copilots(NamedTuple): @classmethod def from_row(cls, row: Row) -> "Copilots": return cls(**dict(row)) + + @property + def lnurl(self) -> Lnurl: + url = url_for("copilot.lnurl_response", cp_id=self.id, _external=True) + return lnurl_encode(url) \ No newline at end of file diff --git a/lnbits/extensions/copilot/static/bitcoin.gif b/lnbits/extensions/copilot/static/bitcoin.gif new file mode 100644 index 00000000..ef8c2ecd Binary files /dev/null and b/lnbits/extensions/copilot/static/bitcoin.gif differ diff --git a/lnbits/extensions/copilot/static/lnurl.png b/lnbits/extensions/copilot/static/lnurl.png new file mode 100644 index 00000000..ad2c9715 Binary files /dev/null and b/lnbits/extensions/copilot/static/lnurl.png differ diff --git a/lnbits/extensions/copilot/static/martijn.gif b/lnbits/extensions/copilot/static/martijn.gif new file mode 100644 index 00000000..e410677d Binary files /dev/null and b/lnbits/extensions/copilot/static/martijn.gif differ diff --git a/lnbits/extensions/copilot/static/rick.gif b/lnbits/extensions/copilot/static/rick.gif new file mode 100644 index 00000000..c36c7e19 Binary files /dev/null and b/lnbits/extensions/copilot/static/rick.gif differ diff --git a/lnbits/extensions/copilot/templates/copilot/compose.html b/lnbits/extensions/copilot/templates/copilot/compose.html index 322dcf85..7c4a21f4 100644 --- a/lnbits/extensions/copilot/templates/copilot/compose.html +++ b/lnbits/extensions/copilot/templates/copilot/compose.html @@ -1,17 +1,32 @@ -{% extends "public.html" %} {% block page %}fdgasdf - +{% extends "public.html" %} {% block page %} + - - + + + + {% endblock %} {% block scripts %} @@ -39,6 +54,10 @@ return {} }, methods: { + openURL: function (url) { + console.log(url) + return Quasar.utils.openURL(url) + }, initCamera() { var video = document.querySelector('#videoElement') @@ -52,6 +71,88 @@ console.log('Something went wrong!') }) } + }, + animation1: function () { + self = this + setTimeout(function () { + setInterval(function () { + self.connection.send('') + }, 1000) + }, 2000) + }, + reconnect: function () { + this.connection.addEventListener('open', function (event) { + this.connection.send('') + }) + + this.connection.addEventListener('message', function (event) { + res = event.data.split('-') + console.log(res[1]) + if (res[0] != this.oldRes) { + this.oldRes = res[0] + if (res[1] == 'rocket') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/rocket.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'face') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/face.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'bitcoin') { + document.getElementById('animations').style.width = '30%' + document.getElementById('animations').src = + '/copilot/static/bitcoin.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'confetti') { + document.getElementById('animations').style.width = '100%' + document.getElementById('animations').src = + '/copilot/static/confetti.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'martijn') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/martijn.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'rick') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/rick.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'true') { + document.getElementById('videoElement').style.width = '20%' + } + if (res[1] == 'false') { + document.getElementById('videoElement').style.width = '100%' + } + if (res[1].substring(0, 3) == 'htt') { + document.getElementById('iframe_main').src = res[1] + } + } + }) + + this.connection.addEventListener('close', function (event) { + console.log('The connection has been closed') + }) } }, mounted() { @@ -59,20 +160,98 @@ }, created: function () { console.log('{{ copilot.id }}') - - console.log('Starting connection to WebSocket Server') - this.connection = new WebSocket( - 'wss://' + document.domain + ':' + location.port + '/ws' - ) - - this.connection.onmessage = function (event) { - console.log(event) + if (location.protocol !== 'http:') { + this.connection = new WebSocket( + 'wss://' + + document.domain + + ':' + + location.port + + '/copilot/ws/compose/{{ copilot.id }}' + ) + } else { + this.connection = new WebSocket( + 'ws://' + + document.domain + + ':' + + location.port + + '/copilot/ws/compose/{{ copilot.id }}' + ) } - this.connection.onopen = function (event) { - console.log(event) - console.log('Successfully connected to the echo websocket server...') - } + this.connection.addEventListener('open', function (event) { + this.connection.send('') + }) + + this.connection.addEventListener('message', function (event) { + res = event.data.split('-') + console.log(res[1]) + if (res[0] != this.oldRes) { + this.oldRes = res[0] + if (res[1] == 'rocket') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/rocket.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'face') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/face.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'bitcoin') { + document.getElementById('animations').style.width = '30%' + document.getElementById('animations').src = + '/copilot/static/bitcoin.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'confetti') { + document.getElementById('animations').style.width = '100%' + document.getElementById('animations').src = + '/copilot/static/confetti.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'martijn') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/martijn.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'rick') { + document.getElementById('animations').style.width = '50%' + document.getElementById('animations').src = + '/copilot/static/rick.gif' + setTimeout(function () { + document.getElementById('animations').src = '' + }, 5000) + } + if (res[1] == 'true') { + document.getElementById('videoElement').style.width = '20%' + } + if (res[1] == 'false') { + document.getElementById('videoElement').style.width = '100%' + } + if (res[1].substring(0, 3) == 'htt') { + document.getElementById('iframe_main').src = res[1] + } + } + }) + + this.connection.addEventListener('close', function (event) { + console.log('The connection has been closed') + }) + var animation1 = this.animation1 + animation1() } }) diff --git a/lnbits/extensions/copilot/templates/copilot/index.html b/lnbits/extensions/copilot/templates/copilot/index.html index 04a30111..93203eb2 100644 --- a/lnbits/extensions/copilot/templates/copilot/index.html +++ b/lnbits/extensions/copilot/templates/copilot/index.html @@ -147,6 +147,14 @@ type="text" label="Title" > + @@ -218,6 +226,7 @@ v-model.trim="formDialogCopilot.data.animation2threshold" type="number" label="From *sats" + :rules="[ val <= formDialogCopilot.data.animation1threshold || 'Must be higher than last']" > @@ -226,7 +235,7 @@ filled dense v-model.trim="formDialogCopilot.data.animation2webhook" - type="number" + type="text" label="Webhook" > @@ -262,6 +271,7 @@ v-model.trim="formDialogCopilot.data.animation3threshold" type="number" label="From *sats" + :rules="[ val <= formDialogCopilot.data.animation2threshold || 'Must be higher than last']" > @@ -270,7 +280,7 @@ filled dense v-model.trim="formDialogCopilot.data.animation3webhook" - type="number" + type="text" label="Webhook" > @@ -409,20 +419,14 @@ data: { show_message: false, show_ack: true, - title: '', - animation1threshold: 0, - animation2threshold: 0, - animation3threshold: 0, - animation1webhook: '', - animation2webhook: '', - animation3webhook: '' + title: '' } }, qrCodeDialog: { show: false, data: null }, - options: ['moon_rocket', 'confetti', 'roller_coaster'] + options: ['bitcoin', 'confetti', 'rocket', 'face', 'martijn', 'rick'] } }, methods: { @@ -437,19 +441,15 @@ }, sendFormDataCopilot: function () { var self = this - var wallet = this.g.user.wallets[0].adminkey - var data = this.formDialogCopilot.data - console.log(data) - data.animation1threshold = parseInt(data.animation1threshold) - data.animation1threshold = parseInt(data.animation2threshold) - data.animation1threshold = parseInt(data.animation3threshold) - - this.createCopilot(wallet, data) + console.log(self.formDialogCopilot.data.animation1threshold) + this.createCopilot( + self.g.user.wallets[0].adminkey, + self.formDialogCopilot.data + ) }, createCopilot: function (wallet, data) { var self = this - LNbits.api .request('POST', '/copilot/api/v1/copilot', wallet, data) .then(function (response) { @@ -477,7 +477,7 @@ }, opencopilotCompose: function (copilot_id) { let params = - 'scrollbars=no, resizable=no,status=no,location=no,toolbar=no,menubar=no,width=900,height=500,left=200,top=200' + 'scrollbars=no, resizable=no,status=no,location=no,toolbar=no,menubar=no,width=1722,height=972,left=200,top=200' open('../copilot/cp/' + copilot_id, 'test', params) }, diff --git a/lnbits/extensions/copilot/templates/copilot/panel.html b/lnbits/extensions/copilot/templates/copilot/panel.html index a3b30ea6..2554d3cd 100644 --- a/lnbits/extensions/copilot/templates/copilot/panel.html +++ b/lnbits/extensions/copilot/templates/copilot/panel.html @@ -10,7 +10,7 @@ dense @click="openCompose" icon="face" - style="font-size: 100px" + style="font-size: 80px" > @@ -23,11 +23,11 @@
-
@@ -36,24 +36,71 @@ dense outlined bottom-slots - v-model="text" + v-model="iframe" label="iframe url" >
- +
- +
- + +
+
+
+
+ +
+
+ +
+
+
@@ -65,9 +112,6 @@ clearable type="textarea" label="Notes" - :shadow-text="textareaShadowText" - @keydown="processTextareaFill" - @focus="processTextareaFill" >
@@ -93,24 +137,67 @@ mixins: [windowMixin], data() { return { - newProgress: 0.4, - counter: 1, - newTimeLeft: '', - lnbtc: true, - onbtc: false, - formDialogCopilot: { - fullscreen_cam: true - } + fullscreen_cam: true, + textareaModel: '', + iframe: '' } }, methods: { + iframeChange: function (url) { + this.connection.send(String(url)) + }, + fullscreenToggle: function () { + console.log(this.fullscreen_cam) + this.connection.send(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=900,height=500,left=200,top=200` + let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=1722,height=972,left=200,top=200` open('../copilot/cp/{{ copilot.id }}', 'test', params) + }, + animationBTN: function (name) { + this.connection.send(name) + }, + stfu: function (name) { + this.connection.send('') } }, - created: function () {} + created: function () { + console.log('{{ copilot.id }}') + + if (location.protocol == 'https:') { + console.log(location.protocol) + this.connection = new WebSocket( + 'wss://' + + document.domain + + ':' + + location.port + + '/copilot/ws/panel/{{ copilot.id }}' + ) + } else { + this.connection = new WebSocket( + 'ws://' + + document.domain + + ':' + + location.port + + '/copilot/ws/panel/{{ copilot.id }}' + ) + } + this.connection.addEventListener('open', function (event) {}) + + this.connection.addEventListener('message', function (event) { + console.log('Message from server ', event.data) + }) + + this.connection.addEventListener('close', function (event) { + console.log('The connection has been closed') + }) + } }) {% endblock %} diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index 2fa9ac84..16aba211 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -6,11 +6,27 @@ from lnbits.decorators import check_user_exists, validate_uuids from . import copilot_ext from .crud import get_copilot -@copilot_ext.websocket('/ws') -async def ws(): +from quart import g, abort, render_template, jsonify, websocket +from functools import wraps +import trio +import shortuuid +from . import copilot_ext + +connected_websockets = {} + +@copilot_ext.websocket('/ws/panel/') +async def ws_panel(copilot_id): + global connected_websockets while True: data = await websocket.receive() - await websocket.send(f"echo {data}") + connected_websockets[copilot_id] = shortuuid.uuid() + '-' + data + +@copilot_ext.websocket('/ws/compose/') +async def ws_compose(copilot_id): + global connected_websockets + while True: + data = await websocket.receive() + await websocket.send(connected_websockets[copilot_id]) @copilot_ext.route("/") @validate_uuids(["usr"], required=True) @@ -24,7 +40,7 @@ async def compose(copilot_id): copilot = await get_copilot(copilot_id) or abort( HTTPStatus.NOT_FOUND, "Copilot link does not exist." ) - return await render_template("copilot/compose.html", copilot=copilot) + return await render_template("copilot/compose.html", copilot=copilot, lnurl=copilot.lnurl) @copilot_ext.route("/") async def panel(copilot_id): diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index e345e94f..959f875c 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -6,6 +6,8 @@ import httpx from lnbits.core.crud import get_user from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from . import copilot_ext + from lnbits.extensions.copilot import copilot_ext from .crud import ( create_copilot, @@ -24,12 +26,13 @@ from .crud import ( @api_validate_post_request( schema={ "title": {"type": "string", "empty": False, "required": True}, + "wallet": {"type": "string", "empty": False, "required": True}, "animation1": {"type": "string", "required": False}, "animation2": {"type": "string", "required": False}, "animation3": {"type": "string", "required": False}, - "animation1threshold": {"type": "integer", "required": False}, - "animation2threshold": {"type": "integer", "required": False}, - "animation3threshold": {"type": "integer", "required": False}, + "animation1threshold": {"type": "string", "required": False}, + "animation2threshold": {"type": "string", "required": False}, + "animation3threshold": {"type": "string", "required": False}, "animation1webhook": {"type": "string", "required": False}, "animation2webhook": {"type": "string", "required": False}, "animation3webhook": {"type": "string", "required": False},