Removed example
|
@ -1,11 +0,0 @@
|
|||
<h1>Example Extension</h1>
|
||||
<h2>*tagline*</h2>
|
||||
This is an example extension to help you organise and build you own.
|
||||
|
||||
Try to include an image
|
||||
<img src="https://i.imgur.com/9i4xcQB.png">
|
||||
|
||||
|
||||
<h2>If your extension has API endpoints, include useful ones here</h2>
|
||||
|
||||
<code>curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"</code>
|
|
@ -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_example")
|
||||
|
||||
example_ext: APIRouter = APIRouter(prefix="/example", tags=["example"])
|
||||
|
||||
example_static_files = [
|
||||
{
|
||||
"path": "/example/static",
|
||||
"app": StaticFiles(packages=[("lnbits", "extensions/example/static")]),
|
||||
"name": "example_static",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def example_renderer():
|
||||
return template_renderer(["lnbits/extensions/example/templates"])
|
||||
|
||||
|
||||
from .tasks import wait_for_paid_invoices
|
||||
from .views import * # noqa: F401,F403
|
||||
from .views_api import * # noqa: F401,F403
|
||||
|
||||
|
||||
def tpos_start():
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "Build your own!",
|
||||
"short_description": "Extension building guide",
|
||||
"tile": "/example/static/bitcoin-extension.png",
|
||||
"contributors": ["github_username"]
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
# crud.py is for communication with your extensions database
|
||||
|
||||
# add your dependencies here
|
||||
|
||||
# add your fnctions here
|
|
@ -1,12 +0,0 @@
|
|||
# migrations.py is for building your database
|
||||
|
||||
# async def m001_initial(db):
|
||||
# await db.execute(
|
||||
# f"""
|
||||
# CREATE TABLE example.example (
|
||||
# id TEXT PRIMARY KEY,
|
||||
# wallet TEXT NOT NULL,
|
||||
# time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
||||
# );
|
||||
# """
|
||||
# )
|
|
@ -1,5 +0,0 @@
|
|||
# from pydantic import BaseModel
|
||||
|
||||
# class Example(BaseModel):
|
||||
# id: str
|
||||
# wallet: str
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 49 KiB |
|
@ -1,29 +0,0 @@
|
|||
# tasks.py is for asynchronous when invoices get paid
|
||||
|
||||
# add your dependencies here
|
||||
|
||||
import asyncio
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.helpers import get_current_extension_name
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
|
||||
|
||||
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") != "example"
|
||||
): # Will grab any payment with the tag "example"
|
||||
logger.debug(payment)
|
||||
# Do something
|
||||
return
|
|
@ -1,451 +0,0 @@
|
|||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
|
||||
<q-dialog v-model="thingDialog.show" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form @submit="sendThingDialog" class="q-gutter-md">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="thingDialog.data.name"
|
||||
type="text"
|
||||
label="Name *"
|
||||
></q-input>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="thingDialog.data.email"
|
||||
type="email"
|
||||
label="Name *"
|
||||
></q-input>
|
||||
<q-select
|
||||
filled
|
||||
dense
|
||||
emit-value
|
||||
v-model="thingDialog.data.wallet"
|
||||
:options="g.user.walletOptions"
|
||||
label="Wallet *"
|
||||
></q-select>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn
|
||||
v-if="thingDialog.data.id"
|
||||
unelevated
|
||||
color="primary"
|
||||
type="submit"
|
||||
>Update thing</q-btn
|
||||
>
|
||||
<q-btn
|
||||
v-else
|
||||
unelevated
|
||||
color="primary"
|
||||
:disable="thingDialog.data.name == null"
|
||||
type="submit"
|
||||
>Create thing</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-card flat>
|
||||
<q-card-section>
|
||||
<div class="text-h5 q-mb-md">
|
||||
Extension Development Guide
|
||||
<small
|
||||
>(also check the
|
||||
<a
|
||||
class="text-primary"
|
||||
href="http://docs.lnbits.org/devs/development.html"
|
||||
>docs</a
|
||||
>)</small
|
||||
>
|
||||
</div>
|
||||
|
||||
<q-card unelevated flat>
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
dense
|
||||
class="text-grey"
|
||||
active-color="primary"
|
||||
indicator-color="primary"
|
||||
align="justify"
|
||||
narrow-indicator
|
||||
>
|
||||
<q-tab name="frameworks" label="Frameworks"></q-tab>
|
||||
<q-tab name="tools" label="Useful Tools"></q-tab>
|
||||
<q-tab name="goodpractice" label="Good Practice"></q-tab>
|
||||
<q-tab name="enviroment" label="Dev Enviroment"></q-tab>
|
||||
<q-tab name="submission" label="Submisson to LNbits repo"></q-tab>
|
||||
</q-tabs>
|
||||
|
||||
<q-separator></q-separator>
|
||||
|
||||
<q-tab-panels v-model="tab">
|
||||
<q-tab-panel name="frameworks">
|
||||
<div class="text-h6">Frameworks</div>
|
||||
|
||||
<div>
|
||||
<template>
|
||||
<q-tabs align="left" v-model="framworktab" inline-label>
|
||||
<q-tab name="fastapi"
|
||||
><img src="./static/fastapi-framework.png" />FASTAPI</q-tab
|
||||
>
|
||||
<q-tab name="quasar"
|
||||
><img src="./static/quasar-framework.png" />QUASAR</q-tab
|
||||
>
|
||||
<q-tab name="vuejs"
|
||||
><img src="./static/vuejs-framework.png" />VUE-JS</q-tab
|
||||
>
|
||||
</q-tabs>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<q-tab-panels v-model="framworktab">
|
||||
<q-tab-panel name="fastapi" class="text-body1">
|
||||
<a href="https://fastapi.tiangolo.com/"
|
||||
><img src="./static/fastapilogo.png"
|
||||
/></a>
|
||||
<p>
|
||||
LNbits API is built using
|
||||
<a href="https://fastapi.tiangolo.com/" class="text-primary"
|
||||
>FastAPI</a
|
||||
>, a high-performance, easy to code API framework.<br /><br />
|
||||
FastAPI auto-generates swagger UI docs for testing endpoints
|
||||
<a class="text-primary" href="../docs">/docs</a>
|
||||
</p>
|
||||
|
||||
<i>
|
||||
<strong>TIP:</strong> Although it is possible for extensions
|
||||
to use other extensions API endpoints (such as with the
|
||||
Satspay and Onchain extension), ideally an extension should
|
||||
only use LNbits
|
||||
<a href="../docs#/default" class="text-primary">core</a>
|
||||
endpoints. </i
|
||||
><br /><br />
|
||||
|
||||
<code class="bg-grey-3 text-black">views.py</code> is used for
|
||||
setting application routes:
|
||||
<img src="./static/fastapi-example.png" /><br /><br />
|
||||
<code class="bg-grey-3 text-black">views_api.py</code> is used
|
||||
for setting application API endpoints:<br />
|
||||
<img src="./static/fastapi-example2.png" />
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="quasar" class="text-body1">
|
||||
<a href="https://quasar.dev/"
|
||||
><img src="./static/quasarlogo.png"
|
||||
/></a>
|
||||
<p>
|
||||
LNbits uses
|
||||
<a class="text-primary" href="https://quasar.dev/"
|
||||
>Quasar Framework</a
|
||||
>
|
||||
for frontend deisgn elements. Quasar Framework is an
|
||||
open-source Vue.js based framework for building apps.
|
||||
</p>
|
||||
|
||||
<i>
|
||||
<strong>TIP:</strong> Look through
|
||||
<code class="bg-grey-3 text-black">/template</code> files in
|
||||
other extensions for examples of Quasar elements being used. </i
|
||||
><br /><br />
|
||||
|
||||
<p>
|
||||
In the below example we make a dialogue popup box (box can
|
||||
be triggered
|
||||
<q-btn
|
||||
size="sm"
|
||||
color="primary"
|
||||
@click="thingDialog.show = true"
|
||||
>here</q-btn
|
||||
>): <q-tooltip>Exmple of a tooltip!</q-tooltip>
|
||||
</p>
|
||||
<img src="./static/quasar-example.png" /><br /><br />
|
||||
<div class="text-h6">Useful links:</div>
|
||||
<q-btn
|
||||
color="primary"
|
||||
type="a"
|
||||
href="https://quasar.dev/style/"
|
||||
>Style (typography, spacing, etc)</q-btn
|
||||
>
|
||||
<q-btn
|
||||
color="primary"
|
||||
type="a"
|
||||
href="https://quasar.dev/vue-components/"
|
||||
>Genral components (cards, buttons, popup dialogs,
|
||||
etc)</q-btn
|
||||
>
|
||||
<q-btn
|
||||
color="primary"
|
||||
type="a"
|
||||
href="https://quasar.dev/layout/grid"
|
||||
>Layouts (rows/columns, flexbox)</q-btn
|
||||
>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel
|
||||
v-if="someBool == true"
|
||||
name="vuejs"
|
||||
class="text-body1"
|
||||
>
|
||||
<a href="https://vuejs.org/">
|
||||
<img src="./static/vuejslogo.png"
|
||||
/></a>
|
||||
<p>
|
||||
LNbits uses
|
||||
<a href="https://vuejs.org/" class="text-primary">Vue</a>
|
||||
for best-in-class, responsive and high-performance
|
||||
components.
|
||||
</p>
|
||||
|
||||
<p>Typical example of Vue components in a frontend script:</p>
|
||||
<img
|
||||
src="./static/script-example.png"
|
||||
style="max-width: 800px"
|
||||
/><br /><br />
|
||||
|
||||
<p>
|
||||
Content can be conditionally rendered using Vue's
|
||||
<code class="bg-grey-3 text-black">v-if</code>:
|
||||
</p>
|
||||
<img
|
||||
src="./static/vif-example.png"
|
||||
style="max-width: 800px"
|
||||
/>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</template>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="tools">
|
||||
<div class="text-h6">Useful Tools</div>
|
||||
<div>
|
||||
<template>
|
||||
<q-tabs v-model="usefultab" align="left">
|
||||
<q-tab name="magicalg">MAGICAL G</q-tab>
|
||||
<q-tab name="exchange">EXCHANGE RATES</q-tab>
|
||||
<q-tab name="qrcodes">QR CODES</q-tab>
|
||||
<q-tab name="websockets">WEBSOCKETS</q-tab>
|
||||
</q-tabs>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<q-tab-panels v-model="usefultab">
|
||||
<q-tab-panel name="magicalg" class="text-body1">
|
||||
<div class="text-h5 q-mb-md">Magical G</div>
|
||||
<p>
|
||||
A magical "g" (ie
|
||||
<code class="bg-grey-3 text-black"
|
||||
>this.g.user.wallets[0].inkey</code
|
||||
>) is always available, with info about the user, wallets
|
||||
and extensions:
|
||||
</p>
|
||||
<code class="text-caption">{% raw %}{{ g }}{% endraw %}</code>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="exchange">
|
||||
<div class="text-h6">Exchange rates</div>
|
||||
<p>
|
||||
LNbits includes a handy
|
||||
<a
|
||||
href="../docs#/default/api_fiat_as_sats_api_v1_conversion_post"
|
||||
class="text-primary"
|
||||
>exchange rate function</a
|
||||
>, that streams rates from 6 different sources.
|
||||
</p>
|
||||
Exchange rate API:<br />
|
||||
<img src="./static/conversion-example.png" /><br /><br />
|
||||
Exchange rate functions, included using
|
||||
<code class="bg-grey-3 text-black"
|
||||
>from lnbits.utils.exchange_rates import
|
||||
fiat_amount_as_satoshis</code
|
||||
>:<br />
|
||||
<img src="./static/conversion-example2.png" />
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="qrcodes" class="text-body1">
|
||||
<div class="text-h5 q-mb-md">QR Codes</div>
|
||||
<p>
|
||||
For most purposes use Quasar's inbuilt VueQrcode library:
|
||||
</p>
|
||||
<img src="./static/qrcode-example1.png" />
|
||||
<p>
|
||||
LNbits does also include a handy
|
||||
<a
|
||||
href="../docs#/default/img_api_v1_qrcode__data__get"
|
||||
class="text-primary"
|
||||
>
|
||||
QR code enpoint</a
|
||||
>
|
||||
</p>
|
||||
{% raw %} You can use via
|
||||
<a
|
||||
href="/api/v1/qrcode/some-data-you-want-in-a-qrcode"
|
||||
class="text-primary"
|
||||
>{{protocol + location}}{% endraw
|
||||
%}/api/v1/qrcode/some-data-you-want-in-a-qrcode:</a
|
||||
><br />
|
||||
<br />
|
||||
<img src="./static/qrcode-example.png" />
|
||||
<br />
|
||||
<img
|
||||
class="bg-white"
|
||||
width="300px"
|
||||
src="/api/v1/qrcode/some-data-you-want-in-a-qrcode"
|
||||
/>
|
||||
|
||||
<br />
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="websockets" class="text-body1">
|
||||
<div class="text-h5 q-mb-md">Websockets</div>
|
||||
<p>
|
||||
Fastapi includes a great
|
||||
<a
|
||||
class="text-primary"
|
||||
href="https://fastapi.tiangolo.com/advanced/websockets/#websockets-client"
|
||||
>websocket tool</a
|
||||
>
|
||||
</p>
|
||||
{% raw %}
|
||||
<p>
|
||||
A few LNbits extensions also make use of a weird and useful
|
||||
websocket/GET tool built into LNbits, such as extensions
|
||||
Copilot and LNURLDevices<br />
|
||||
You can subscribe to websocket with
|
||||
<code class="bg-grey-3 text-black"
|
||||
>wss:{{location}}/api/v1/ws/{SOME-ID}</code
|
||||
><br />
|
||||
You can post to any clients subscribed to the endpoint with
|
||||
<code class="bg-grey-3 text-black"
|
||||
>{{protocol +
|
||||
location}}/api/v1/ws/{SOME-ID}/{THE-DATA-YOU-WANT-TO-POST}</code
|
||||
><br />
|
||||
<br />
|
||||
<strong
|
||||
><div id="text-to-change">
|
||||
DEMO: Hit
|
||||
<a
|
||||
target="_blank"
|
||||
href="/api/v1/ws/32872r23g29/blah%20blah%20blah"
|
||||
class="text-primary"
|
||||
>{{protocol +
|
||||
location}}/api/v1/ws/32872r23g29/blah%20blah%20blah</a
|
||||
>
|
||||
in a different browser window to change this text to
|
||||
`blah blah blah`.
|
||||
</div></strong
|
||||
>
|
||||
<br />
|
||||
Function used in this demo:<br />
|
||||
<img src="./static/websocket-example.png" /></p
|
||||
></q-tab-panel>
|
||||
|
||||
{% endraw %}
|
||||
</q-tab-panels>
|
||||
</template>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="goodpractice">
|
||||
<div class="text-h6">Good Practice</div>
|
||||
Coming soon...
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="enviroment">
|
||||
<div class="text-h6">Dev Enviroment</div>
|
||||
Coming soon...
|
||||
</q-tab-panel>
|
||||
|
||||
<q-tab-panel name="submission">
|
||||
<div class="text-h6">Submission</div>
|
||||
Coming soon...
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</q-card>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
|
||||
<script>
|
||||
var someMapObject = obj => {
|
||||
obj._data = _.clone(obj)
|
||||
obj.date = Quasar.utils.date.formatDate(
|
||||
new Date(obj.time * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
// here you can do something with the mapped data
|
||||
return obj
|
||||
}
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
///// Declare models/variables /////
|
||||
protocol: window.location.protocol,
|
||||
location: '//' + window.location.hostname,
|
||||
thingDialog: {
|
||||
show: false,
|
||||
data: {}
|
||||
},
|
||||
someBool: true,
|
||||
splitterModel: 20,
|
||||
exampleData: [],
|
||||
tab: 'frameworks',
|
||||
framworktab: 'fastapi',
|
||||
usefultab: 'magicalg'
|
||||
}
|
||||
},
|
||||
///// Where functions live /////
|
||||
methods: {
|
||||
exampleFunction: function (data) {
|
||||
var theData = data
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET', // Type of request
|
||||
'/example/api/v1/test/' + theData, // URL of the endpoint
|
||||
this.g.user.wallets[0].inkey // Often endpoints require a key
|
||||
)
|
||||
.then(response => {
|
||||
this.exampleData = response.data.map(someMapObject) // Often whats returned is mapped onto some model
|
||||
})
|
||||
.catch(error => {
|
||||
LNbits.utils.notifyApiError(error) // Error will be passed to the frontend
|
||||
})
|
||||
},
|
||||
initWs: async function () {
|
||||
if (location.protocol !== 'http:') {
|
||||
localUrl =
|
||||
'wss://' +
|
||||
document.domain +
|
||||
':' +
|
||||
location.port +
|
||||
'/api/v1/ws/32872r23g29'
|
||||
} else {
|
||||
localUrl =
|
||||
'ws://' +
|
||||
document.domain +
|
||||
':' +
|
||||
location.port +
|
||||
'/api/v1/ws/32872r23g29'
|
||||
}
|
||||
this.ws = new WebSocket(localUrl)
|
||||
this.ws.addEventListener('message', async ({data}) => {
|
||||
const res = data.toString()
|
||||
document.getElementById('text-to-change').innerHTML = res
|
||||
})
|
||||
},
|
||||
sendThingDialog() {
|
||||
console.log(this.thingDialog)
|
||||
}
|
||||
},
|
||||
///// To run on startup /////
|
||||
created: function () {
|
||||
self = this // Often used to run a real object, rather than the event (all a bit confusing really)
|
||||
self.exampleFunction('lorum')
|
||||
self.initWs()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,20 +0,0 @@
|
|||
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 example_ext, example_renderer
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@example_ext.get("/", response_class=HTMLResponse)
|
||||
async def index(
|
||||
request: Request,
|
||||
user: User = Depends(check_user_exists),
|
||||
):
|
||||
return example_renderer().TemplateResponse(
|
||||
"example/index.html", {"request": request, "user": user.dict()}
|
||||
)
|
|
@ -1,13 +0,0 @@
|
|||
# views_api.py is for you API endpoints that could be hit by another service
|
||||
|
||||
# add your dependencies here
|
||||
|
||||
from . import example_ext
|
||||
|
||||
# add your endpoints here
|
||||
|
||||
|
||||
@example_ext.get("/api/v1/test/{test_data}")
|
||||
async def api_example(test_data):
|
||||
# Do some python things and return the data
|
||||
return test_data
|