diff --git a/docs/devs/extensions.md b/docs/devs/extensions.md index cd81a0210..5e5c2e8bc 100644 --- a/docs/devs/extensions.md +++ b/docs/devs/extensions.md @@ -28,7 +28,9 @@ Going over the example extension's structure: Adding new dependencies ----------------------- -If for some reason your extensions needs a new python package to work, you can add a new package using `venv`, or `poerty`: +DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others. + +If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `venv`, or `poerty`: ```sh $ poetry add @@ -37,8 +39,7 @@ $ ./venv/bin/pip install ``` **But we need an extra step to make sure LNbits doesn't break in production.** -Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry`. -`nix` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`. +Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`. SQLite to PostgreSQL migration diff --git a/lnbits/extensions/example/static/qrcode-example.png b/lnbits/extensions/example/static/qrcode-example.png new file mode 100644 index 000000000..29781f536 Binary files /dev/null and b/lnbits/extensions/example/static/qrcode-example.png differ diff --git a/lnbits/extensions/example/static/qrcode-example1.png b/lnbits/extensions/example/static/qrcode-example1.png new file mode 100644 index 000000000..a6c748f11 Binary files /dev/null and b/lnbits/extensions/example/static/qrcode-example1.png differ diff --git a/lnbits/extensions/example/static/websocket-example.png b/lnbits/extensions/example/static/websocket-example.png new file mode 100644 index 000000000..52171d309 Binary files /dev/null and b/lnbits/extensions/example/static/websocket-example.png differ diff --git a/lnbits/extensions/example/templates/example/index.html b/lnbits/extensions/example/templates/example/index.html index 03f66b5f7..36d325bb8 100644 --- a/lnbits/extensions/example/templates/example/index.html +++ b/lnbits/extensions/example/templates/example/index.html @@ -51,8 +51,15 @@
- {{SITE_TITLE}} Extension Development Guide - (Collection of resources for extension developers) + Extension Development Guide + (also check the + docs)
@@ -188,8 +195,8 @@

LNbits uses Vue - components for best-in-class high-performance and responsive - performance. + for best-in-class, responsive and high-performance + components.

Typical example of Vue components in a frontend script:

@@ -199,8 +206,7 @@ />

- In a page body, models can be called.
Content can be - conditionally rendered using Vue's + Content can be conditionally rendered using Vue's v-if:

MAGICAL G EXCHANGE RATES + QR CODES + WEBSOCKETS @@ -255,6 +263,85 @@ >:
+ +
QR Codes
+

+ For most purposes use Quasar's inbuilt VueQrcode library: +

+ +

+ LNbits does also include a handy + + QR code enpoint +

+ {% raw %} You can use via + {{protocol + location}}{% endraw + %}/api/v1/qrcode/some-data-you-want-in-a-qrcode:
+
+ +
+ + +
+
+ + +
Websockets
+

+ Fastapi includes a great + websocket tool +

+ {% raw %} +

+ A few LNbits extensions also make use of a weird and useful + websocket/GET tool built into LNbits, such as extensions + Copilot and LNURLDevices
+ You can subscribe to websocket with + wss:{{location}}/api/v1/ws/{SOME-ID}
+ You can post to any clients subscribed to the endpoint with + {{protocol + + location}}/api/v1/ws/{SOME-ID}/{THE-DATA-YOU-WANT-TO-POST}
+
+

+ DEMO: Hit + {{protocol + + location}}/api/v1/ws/32872r23g29/blah%20blah%20blah + in a different browser window to change this text to + `blah blah blah`. +
+
+ Function used in this demo:
+

+ + {% endraw %} @@ -296,6 +383,8 @@ data: function () { return { ///// Declare models/variables ///// + protocol: window.location.protocol, + location: '//' + window.location.hostname, thingDialog: { show: false, data: {} @@ -310,7 +399,7 @@ }, ///// Where functions live ///// methods: { - exampleFunction(data) { + exampleFunction: function (data) { var theData = data LNbits.api .request( @@ -325,6 +414,28 @@ 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) } @@ -333,6 +444,7 @@ 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() } }) diff --git a/lnbits/extensions/market/templates/market/_dialogs.html b/lnbits/extensions/market/templates/market/_dialogs.html index d2a8dd0ae..a0ab84b3f 100644 --- a/lnbits/extensions/market/templates/market/_dialogs.html +++ b/lnbits/extensions/market/templates/market/_dialogs.html @@ -55,8 +55,16 @@ > - + + - Link to pass to stall relay + Disabled: link to pass to stall relays when using + nostr {{ col.value }} diff --git a/lnbits/extensions/market/templates/market/index.html b/lnbits/extensions/market/templates/market/index.html index ffcb612b3..77e3acced 100644 --- a/lnbits/extensions/market/templates/market/index.html +++ b/lnbits/extensions/market/templates/market/index.html @@ -498,6 +498,7 @@ }, productDialog: { show: false, + url: true, data: {} }, stallDialog: { @@ -536,6 +537,9 @@ methods: { resetDialog(dialog) { this[dialog].show = false + if (dialog == 'productDialog') { + this[dialog].url = true + } this[dialog].data = {} }, toggleDA(value, evt) { @@ -798,11 +802,17 @@ var link = _.findWhere(self.products, {id: linkId}) self.productDialog.data = _.clone(link._data) - self.productDialog.data.categories = self.productDialog.data.categories.split( - ',' - ) + if (self.productDialog.data.categories) { + self.productDialog.data.categories = self.productDialog.data.categories.split( + ',' + ) + } + if (self.productDialog.data.image.startsWith('data:')) { + self.productDialog.url = false + } self.productDialog.show = true + console.log(self.productDialog) }, sendProductFormData: function () { let _data = {...this.productDialog.data} @@ -831,14 +841,8 @@ let canvas = document.createElement('canvas') canvas.setAttribute('width', fit.width) canvas.setAttribute('height', fit.height) - await pica.resize(image, canvas, { - quality: 0, - alpha: true, - unsharpAmount: 95, - unsharpRadius: 0.9, - unsharpThreshold: 70 - }) - this.productDialog.data.image = canvas.toDataURL() + output = await pica.resize(image, canvas) + this.productDialog.data.image = output.toDataURL('image/jpeg', 0.4) this.productDialog = {...this.productDialog} } }, diff --git a/lnbits/extensions/market/views_api.py b/lnbits/extensions/market/views_api.py index 045bc0fc9..31703e8d7 100644 --- a/lnbits/extensions/market/views_api.py +++ b/lnbits/extensions/market/views_api.py @@ -113,6 +113,23 @@ async def api_market_product_create( if stall.currency != "sat": data.price *= settings.fiat_base_multiplier + if data.image: + image_is_url = data.image.startswith("https://") or data.image.startswith( + "http://" + ) + + if not image_is_url: + + def size(b64string): + return int((len(b64string) * 3) / 4 - b64string.count("=", -2)) + + image_size = size(data.image) / 1024 + if image_size > 100: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Image size is too big, {int(image_size)}Kb. Max: 100kb, Compress the image at https://tinypng.com, or use an URL.", + ) + if product_id: product = await get_market_product(product_id) if not product: