added confetti

This commit is contained in:
benarc 2024-01-19 17:40:43 +00:00
parent 1735cc0d66
commit d90babebc8
15 changed files with 177 additions and 135 deletions

View file

@ -27,6 +27,6 @@ from .tasks import wait_for_paid_invoices
from .views import * from .views import *
from .views_api import * from .views_api import *
def temp_start(): def myextension_start():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,7 +1,7 @@
{ {
"name": "MyExtension", "name": "MyExtension",
"short_description": "Minimal extension to build on", "short_description": "Minimal extension to build on",
"tile": "/myextension/static/image/myextension.png", "tile": "/myextension/static/myextension.png",
"contributors": ["arcbtc"], "contributors": ["arcbtc"],
"min_lnbits_version": "0.0.1" "min_lnbits_version": "0.0.1"
} }

26
crud.py
View file

@ -8,7 +8,7 @@ from loguru import logger
from fastapi import Request from fastapi import Request
from lnurl import encode as lnurl_encode from lnurl import encode as lnurl_encode
async def create_myextension(wallet_id: str, data: CreateMyExtensionData) -> MyExtension: async def create_myextension(wallet_id: str, data: CreateMyExtensionData, req: Request) -> MyExtension:
myextension_id = urlsafe_short_hash() myextension_id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
@ -23,21 +23,23 @@ async def create_myextension(wallet_id: str, data: CreateMyExtensionData) -> MyE
data.lnurlwithdrawamount data.lnurlwithdrawamount
), ),
) )
myextension = await get_myextension(myextension_id) myextension = await get_myextension(myextension_id, req)
assert myextension, "Newly created table couldn't be retrieved" assert myextension, "Newly created table couldn't be retrieved"
return myextension return myextension
async def get_myextension(myextension_id: str, req: Request) -> Optional[MyExtension]: async def get_myextension(myextension_id: str, req: Optional[Request] = None) -> Optional[MyExtension]:
logger.debug(myextension_id)
row = await db.fetchone("SELECT * FROM myextension.maintable WHERE id = ?", (myextension_id,)) row = await db.fetchone("SELECT * FROM myextension.maintable WHERE id = ?", (myextension_id,))
if not row: if not row:
return None return None
rowAmended = MyExtension(**row) rowAmended = MyExtension(**row)
rowAmended.lnurlpay = lnurl_encode(req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url) if req:
rowAmended.lnurlwithdraw = lnurl_encode(req.url_for("myextension.api_lnurl_withdraw", myextension_id=row.id)._url) rowAmended.lnurlpay = lnurl_encode(req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url)
rowAmended.lnurlwithdraw = lnurl_encode(req.url_for("myextension.api_lnurl_withdraw", myextension_id=row.id)._url)
return rowAmended return rowAmended
async def get_myextensions(wallet_ids: Union[str, List[str]], req: Request) -> List[MyExtension]: async def get_myextensions(wallet_ids: Union[str, List[str]], req: Optional[Request] = None) -> List[MyExtension]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -46,17 +48,19 @@ async def get_myextensions(wallet_ids: Union[str, List[str]], req: Request) -> L
f"SELECT * FROM myextension.maintable WHERE wallet IN ({q})", (*wallet_ids,) f"SELECT * FROM myextension.maintable WHERE wallet IN ({q})", (*wallet_ids,)
) )
tempRows = [MyExtension(**row) for row in rows] tempRows = [MyExtension(**row) for row in rows]
for row in tempRows: if req:
row.lnurlpay = lnurl_encode(req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url) for row in tempRows:
row.lnurlwithdraw = lnurl_encode(req.url_for("myextension.api_lnurl_withdraw", myextension_id=row.id)._url) row.lnurlpay = lnurl_encode(req.url_for("myextension.api_lnurl_pay", myextension_id=row.id)._url)
row.lnurlwithdraw = lnurl_encode(req.url_for("myextension.api_lnurl_withdraw", myextension_id=row.id)._url)
return tempRows return tempRows
async def update_myextension(myextension_id: str, **kwargs) -> MyExtension: async def update_myextension(myextension_id: str, req: Optional[Request] = None, **kwargs) -> MyExtension:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
logger.debug( kwargs.items())
await db.execute( await db.execute(
f"UPDATE myextension.maintable SET {q} WHERE id = ?", (*kwargs.values(), myextension_id) f"UPDATE myextension.maintable SET {q} WHERE id = ?", (*kwargs.values(), myextension_id)
) )
myextension = await get_myextension(myextension_id) myextension = await get_myextension(myextension_id, req)
assert myextension, "Newly updated myextension couldn't be retrieved" assert myextension, "Newly updated myextension couldn't be retrieved"
return myextension return myextension

8
static/confetti.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

@ -7,20 +7,21 @@ from lnbits.core.services import create_invoice, pay_invoice, websocketUpdater
from lnbits.helpers import get_current_extension_name from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from .crud import get_myextension from .crud import get_myextension, update_myextension
####################################### #######################################
########## RUN YOU TASKS HERE ######### ########## RUN YOUR TASKS HERE #########
####################################### #######################################
# the usual task is to listen to invoices related to this extension # the usual task is to listen to invoices related to this extension
async def wait_for_paid_invoices(): async def wait_for_paid_invoices():
logger.debug(get_current_extension_name())
invoice_queue = asyncio.Queue() invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue, get_current_extension_name()) register_invoice_listener(invoice_queue, get_current_extension_name())
while True: while True:
payment = await invoice_queue.get() payment = await invoice_queue.get()
await on_invoice_paid(payment) await on_invoice_paid(payment)
@ -33,11 +34,8 @@ async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") != "MyExtension": if payment.extra.get("tag") != "MyExtension":
return return
myextension_id = payment.extra.get("tempId") myextension_id = payment.extra.get("myextensionId")
assert myextension_id
myextension = await get_myextension(myextension_id) myextension = await get_myextension(myextension_id)
assert myextension
# update something in the db # update something in the db
@ -45,7 +43,7 @@ async def on_invoice_paid(payment: Payment) -> None:
"total": myextension.total + payment.amount "total": myextension.total + payment.amount
} }
await update_myextension(myextension_id=myextension_id, **data_to_update.dict()) await update_myextension(myextension_id=myextension_id, **data_to_update)
# here we could send some data to a websocket on wss://<your-lnbits>/api/v1/ws/<myextension_id> # here we could send some data to a websocket on wss://<your-lnbits>/api/v1/ws/<myextension_id>
# and then listen to it on the frontend, which we do with index.html connectWebocket() # and then listen to it on the frontend, which we do with index.html connectWebocket()

View file

@ -4,76 +4,5 @@
label="API info" label="API info"
:content-inset-level="0.5" :content-inset-level="0.5"
> >
<q-btn flat label="Swagger API" type="a" href="../docs#/myextension"></q-btn> <q-btn flat label="Swagger API" type="a" href="../docs#/MyExtension"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List MyExtension">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /myextension/api/v1/temps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;temp_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}myextension/api/v1/temps -H "X-Api-Key:
&lt;invoice_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Create a MyExtension">
<q-card>
<q-card-section>
<code><span class="text-green">POST</span> /myextension/api/v1/temps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"name": &lt;string&gt;, "currency": &lt;string*ie USD*&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code
>{"currency": &lt;string&gt;, "id": &lt;string&gt;, "name":
&lt;string&gt;, "wallet": &lt;string&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url }}myextension/api/v1/temps -d '{"name":
&lt;string&gt;, "currency": &lt;string&gt;}' -H "Content-type:
application/json" -H "X-Api-Key: &lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a MyExtension"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/myextension/api/v1/temps/&lt;myextension_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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
}}myextension/api/v1/temps/&lt;myextension_id&gt; -H "X-Api-Key: &lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item> </q-expansion-item>

View file

@ -1,11 +1,11 @@
<q-expansion-item group="extras" icon="info" label="About MyExtension"> <q-expansion-item group="extras" icon="info" label="More info">
<q-card> <q-card>
<q-card-section> <q-card-section>
<p> <p>
MyExtension extension is a basic extension that you can use to get started building your own extension Some more info about my excellent extension.
</p> </p>
<small <small
>Created by >Created by
<a <a
class="text-secondary" class="text-secondary"
href="https://github.com/benarc" href="https://github.com/benarc"
@ -13,6 +13,15 @@
>Ben Arc</a >Ben Arc</a
>.</small >.</small
> >
<small
>Repo
<a
class="text-secondary"
href="https://github.com/lnbits/myextension"
target="_blank"
>MyExtension</a
>.</small
>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>

View file

@ -1,6 +1,6 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context {% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %} %} {% block page %}
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md" id="makeItRain">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> <div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
@ -67,7 +67,32 @@
target="_blank" target="_blank"
><q-tooltip>Open public page</q-tooltip></q-btn ><q-tooltip>Open public page</q-tooltip></q-btn
></q-td> ></q-td>
<q-td>
<q-btn
flat
dense
size="xs"
@click="updateMyExtensionForm(props.row.id)"
icon="edit"
color="light-blue"
>
<q-tooltip> Edit copilot </q-tooltip>
</q-btn>
</q-td>
<q-td>
<q-btn
flat
dense
size="xs"
@click="deleteMyExtension(props.row.id)"
icon="cancel"
color="pink"
>
<q-tooltip> Delete copilot </q-tooltip>
</q-btn>
</q-td>
</q-tr> </q-tr>
</template> </template>
@ -194,6 +219,7 @@
</q-dialog> </q-dialog>
</div> </div>
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.4.0/dist/confetti.browser.min.js"></script>
<script> <script>
// The object returned here will be merged with the data object of the Vue instance // The object returned here will be merged with the data object of the Vue instance
const mapMyExtension = obj => { const mapMyExtension = obj => {
@ -204,7 +230,6 @@
obj.myextension = ['/myextension/', obj.id].join('') obj.myextension = ['/myextension/', obj.id].join('')
return obj return obj
} }
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
@ -280,9 +305,10 @@
const wallet = _.findWhere(this.g.user.wallets, { const wallet = _.findWhere(this.g.user.wallets, {
id: this.formDialog.data.wallet id: this.formDialog.data.wallet
}) })
console.log(data) if (this.formDialog.data.id) {
console.log(wallet) data.id = this.formDialog.data.id
if (data.id) { data.wallet = wallet.id
data.total = this.formDialog.data.total
this.updateMyExtension(wallet, data) this.updateMyExtension(wallet, data)
} else { } else {
this.createMyExtension(wallet, data) this.createMyExtension(wallet, data)
@ -291,8 +317,7 @@
updateMyExtensionForm(tempId) { updateMyExtensionForm(tempId) {
const myextension = _.findWhere(this.temps, {id: tempId}) const myextension = _.findWhere(this.temps, {id: tempId})
this.formDialog.data = { this.formDialog.data = {
...myextension, ...myextension
tip_options: JSON.parse(myextension.tip_options)
} }
if (this.formDialog.data.tip_wallet != '') { if (this.formDialog.data.tip_wallet != '') {
this.formDialog.advanced.tips = true this.formDialog.advanced.tips = true
@ -325,7 +350,7 @@
this.temps = _.reject(this.temps, obj => { this.temps = _.reject(this.temps, obj => {
return obj.id == data.id return obj.id == data.id
}) })
this.temps.push(mapTemp(response.data)) this.temps.push(mapMyExtension(response.data))
this.closeFormDialog() this.closeFormDialog()
}) })
.catch(error => { .catch(error => {
@ -410,10 +435,10 @@
amount: this.invoiceAmount, amount: this.invoiceAmount,
memo: 'Invoice created by MyExtension', memo: 'Invoice created by MyExtension',
extra: { extra: {
tag: 'MyExtension' tag: 'MyExtension',
myextensionId: myextensionId
} }
} }
console.log(dataToSend)
LNbits.api LNbits.api
.request( .request(
'POST', 'POST',
@ -423,16 +448,46 @@
) )
.then(response => { .then(response => {
this.qrValue = response.data.payment_request this.qrValue = response.data.payment_request
this.connectWebocket(myextensionId)
}) })
.catch(error => { .catch(error => {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
makeItRain() {
document.getElementById("vue").disabled = true
var end = Date.now() + (2 * 1000)
var colors = ['#FFD700', '#ffffff']
function frame() {
confetti({
particleCount: 2,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: colors,
zIndex: 999999
})
confetti({
particleCount: 2,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: colors,
zIndex: 999999
})
if (Date.now() < end) {
requestAnimationFrame(frame)
}
else {
document.getElementById("vue").disabled = false
}
}
frame()
},
connectWebocket(id){ connectWebocket(id){
////////////////////////////////////////////////// //////////////////////////////////////////////////
///wait for pay action to happen and do a thing//// ///wait for pay action to happen and do a thing////
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
self = this
if (location.protocol !== 'http:') { if (location.protocol !== 'http:') {
localUrl = localUrl =
'wss://' + 'wss://' +
@ -452,14 +507,13 @@
} }
this.connection = new WebSocket(localUrl) this.connection = new WebSocket(localUrl)
this.connection.onmessage = function (e) { this.connection.onmessage = function (e) {
console.log(e) self.makeItRain()
} }
} }
}, },
created: function () { created: function () {
if (this.g.user.wallets.length) { if (this.g.user.wallets.length) {
this.getMyExtensions() this.getMyExtensions()
} }
} }
}) })

View file

@ -62,36 +62,73 @@
mixins: [windowMixin], mixins: [windowMixin],
data: function () { data: function () {
return { return {
qrValue: '' qrValue: '',
myExtensionID: ''
} }
}, },
created: function () { created: function () {
this.qrValue = '{{lnurlpay}}' this.qrValue = '{{lnurlpay}}'
this.myExtensionID = '{{myextension_id}}'
this.connectWebocket(this.myExtensionID)
}, },
methods: { methods: {
createInvoice(walletId) { makeItRain() {
console.log(walletId) document.getElementById("vue").disabled = true
const wallet = _.findWhere(this.g.user.wallets, { var end = Date.now() + (2 * 1000)
id: walletId var colors = ['#FFD700', '#ffffff']
}) function frame() {
console.log(wallet.inkey) confetti({
LNbits.api particleCount: 2,
.request( angle: 60,
'POST', spread: 55,
`/api/v1/payments`, origin: { x: 0 },
wallet.inkey, colors: colors,
{ zIndex: 999999
out: false,
amount: this.invoiceAmount,
}
)
.then(response => {
this.qrValue = response.data.payment_request
}) })
.catch(error => { confetti({
LNbits.utils.notifyApiError(error) particleCount: 2,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: colors,
zIndex: 999999
}) })
if (Date.now() < end) {
requestAnimationFrame(frame)
}
else {
document.getElementById("vue").disabled = false
}
}
frame()
}, },
connectWebocket(id){
//////////////////////////////////////////////////
///wait for pay action to happen and do a thing////
///////////////////////////////////////////////////
self = this
if (location.protocol !== 'http:') {
localUrl =
'wss://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
} else {
localUrl =
'ws://' +
document.domain +
':' +
location.port +
'/api/v1/ws/' +
id
}
this.connection = new WebSocket(localUrl)
this.connection.onmessage = function (e) {
self.makeItRain()
}
}
}, },
}) })
</script> </script>

View file

@ -66,6 +66,7 @@ async def api_myextension(
@myextension_ext.put("/api/v1/temps/{myextension_id}") @myextension_ext.put("/api/v1/temps/{myextension_id}")
async def api_myextension_update( async def api_myextension_update(
req: Request,
data: CreateMyExtensionData, data: CreateMyExtensionData,
myextension_id: str, myextension_id: str,
wallet: WalletTypeInfo = Depends(get_key_type), wallet: WalletTypeInfo = Depends(get_key_type),
@ -74,12 +75,12 @@ async def api_myextension_update(
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist."
) )
myextension = await get_myextension(myextension_id) myextension = await get_myextension(myextension_id, req)
assert myextension, "MyExtension couldn't be retrieved" assert myextension, "MyExtension couldn't be retrieved"
if wallet.wallet.id != myextension.wallet: if wallet.wallet.id != myextension.wallet:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension.") raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension.")
myextension = await update_myextension(myextension_id=myextension_id, **data.dict()) myextension = await update_myextension(myextension_id=myextension_id, **data.dict(), req=req)
return myextension.dict() return myextension.dict()
@ -87,9 +88,11 @@ async def api_myextension_update(
@myextension_ext.post("/api/v1/temps", status_code=HTTPStatus.CREATED) @myextension_ext.post("/api/v1/temps", status_code=HTTPStatus.CREATED)
async def api_myextension_create( async def api_myextension_create(
data: CreateMyExtensionData, wallet: WalletTypeInfo = Depends(get_key_type) req: Request,
data: CreateMyExtensionData,
wallet: WalletTypeInfo = Depends(get_key_type)
): ):
myextension = await create_myextension(wallet_id=wallet.wallet.id, data=data) myextension = await create_myextension(wallet_id=wallet.wallet.id, data=data, req=req)
return myextension.dict() return myextension.dict()