From 43a974af838944ae2e77b576ac6b848dfabdd5fa Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Tue, 29 Nov 2022 23:18:24 +0100 Subject: [PATCH 01/84] initializing the deezy extension --- lnbits/extensions/deezy/README.md | 11 ++++ lnbits/extensions/deezy/__init__.py | 16 +++++ lnbits/extensions/deezy/config.json | 6 ++ lnbits/extensions/deezy/migrations.py | 10 ++++ lnbits/extensions/deezy/models.py | 5 ++ .../deezy/templates/example/index.html | 59 +++++++++++++++++++ lnbits/extensions/deezy/views.py | 21 +++++++ lnbits/extensions/deezy/views_api.py | 35 +++++++++++ 8 files changed, 163 insertions(+) create mode 100644 lnbits/extensions/deezy/README.md create mode 100644 lnbits/extensions/deezy/__init__.py create mode 100644 lnbits/extensions/deezy/config.json create mode 100644 lnbits/extensions/deezy/migrations.py create mode 100644 lnbits/extensions/deezy/models.py create mode 100644 lnbits/extensions/deezy/templates/example/index.html create mode 100644 lnbits/extensions/deezy/views.py create mode 100644 lnbits/extensions/deezy/views_api.py diff --git a/lnbits/extensions/deezy/README.md b/lnbits/extensions/deezy/README.md new file mode 100644 index 00000000..27729459 --- /dev/null +++ b/lnbits/extensions/deezy/README.md @@ -0,0 +1,11 @@ +

Example Extension

+

*tagline*

+This is an example extension to help you organise and build you own. + +Try to include an image + + + +

If your extension has API endpoints, include useful ones here

+ +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" diff --git a/lnbits/extensions/deezy/__init__.py b/lnbits/extensions/deezy/__init__.py new file mode 100644 index 00000000..663920cd --- /dev/null +++ b/lnbits/extensions/deezy/__init__.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter + +from lnbits.db import Database +from lnbits.helpers import template_renderer + +db = Database("ext_deezy") + +deezy_ext: APIRouter = APIRouter(prefix="/deezy", tags=["deezy"]) + + +def deezy_renderer(): + return template_renderer(["lnbits/extensions/deezy/templates"]) + + +from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/deezy/config.json b/lnbits/extensions/deezy/config.json new file mode 100644 index 00000000..200d1220 --- /dev/null +++ b/lnbits/extensions/deezy/config.json @@ -0,0 +1,6 @@ +{ + "name": "Deezy", + "short_description": "Join us, make an extension", + "icon": "info", + "contributors": ["Uthpala"] +} diff --git a/lnbits/extensions/deezy/migrations.py b/lnbits/extensions/deezy/migrations.py new file mode 100644 index 00000000..99d7c362 --- /dev/null +++ b/lnbits/extensions/deezy/migrations.py @@ -0,0 +1,10 @@ +# 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} +# ); +# """ +# ) diff --git a/lnbits/extensions/deezy/models.py b/lnbits/extensions/deezy/models.py new file mode 100644 index 00000000..bfeb7517 --- /dev/null +++ b/lnbits/extensions/deezy/models.py @@ -0,0 +1,5 @@ +# from pydantic import BaseModel + +# class Example(BaseModel): +# id: str +# wallet: str diff --git a/lnbits/extensions/deezy/templates/example/index.html b/lnbits/extensions/deezy/templates/example/index.html new file mode 100644 index 00000000..d732ef37 --- /dev/null +++ b/lnbits/extensions/deezy/templates/example/index.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + + +
+ Frameworks used by {{SITE_TITLE}} +
+ + + {% raw %} + + + {{ tool.name }} + {{ tool.language }} + + {% endraw %} + + + +

+ A magical "g" is always available, with info about the user, wallets and + extensions: +

+ {% raw %}{{ g }}{% endraw %} +
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/deezy/views.py b/lnbits/extensions/deezy/views.py new file mode 100644 index 00000000..b8efeae8 --- /dev/null +++ b/lnbits/extensions/deezy/views.py @@ -0,0 +1,21 @@ +from fastapi import FastAPI, Request +from fastapi.params import Depends +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 deezy_ext, deezy_renderer + +templates = Jinja2Templates(directory="templates") + + +@deezy_ext.get("/", response_class=HTMLResponse) +async def index( + request: Request, + user: User = Depends(check_user_exists), # type: ignore +): + return deezy_renderer().TemplateResponse( + "example/index.html", {"request": request, "user": user.dict()} + ) diff --git a/lnbits/extensions/deezy/views_api.py b/lnbits/extensions/deezy/views_api.py new file mode 100644 index 00000000..e8d270a9 --- /dev/null +++ b/lnbits/extensions/deezy/views_api.py @@ -0,0 +1,35 @@ +# views_api.py is for you API endpoints that could be hit by another service + +# add your dependencies here + +# import httpx +# (use httpx just like requests, except instead of response.ok there's only the +# response.is_error that is its inverse) + +from . import deezy_ext + +# add your endpoints here + + +@deezy_ext.get("/api/v1/tools") +async def api_example(): + """Try to add descriptions for others.""" + tools = [ + { + "name": "fastAPI", + "url": "https://fastapi.tiangolo.com/", + "language": "Python", + }, + { + "name": "Vue.js", + "url": "https://vuejs.org/", + "language": "JavaScript", + }, + { + "name": "Quasar Framework", + "url": "https://quasar.dev/", + "language": "JavaScript", + }, + ] + + return tools From 4645b8338b1719abde50fdf6f6f8ff15b971343d Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Sun, 4 Dec 2022 23:29:57 +0100 Subject: [PATCH 02/84] added the reverse swap as well --- lnbits/extensions/deezy/config.json | 2 +- .../deezy/templates/deezy/index.html | 255 ++++++++++++++++++ .../deezy/templates/example/index.html | 59 ---- lnbits/extensions/deezy/views.py | 2 +- 4 files changed, 257 insertions(+), 61 deletions(-) create mode 100644 lnbits/extensions/deezy/templates/deezy/index.html delete mode 100644 lnbits/extensions/deezy/templates/example/index.html diff --git a/lnbits/extensions/deezy/config.json b/lnbits/extensions/deezy/config.json index 200d1220..27c4df2d 100644 --- a/lnbits/extensions/deezy/config.json +++ b/lnbits/extensions/deezy/config.json @@ -1,6 +1,6 @@ { "name": "Deezy", "short_description": "Join us, make an extension", - "icon": "info", + "icon": "swap_horiz", "contributors": ["Uthpala"] } diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html new file mode 100644 index 00000000..007144f0 --- /dev/null +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -0,0 +1,255 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + + +
+ Deezy +
+ + + + + + Send onchain funds offchain (BTC -> LN) + + + + + Send offchain funds to onchain address (LN -> BTC) + + + + +
+
Lightning Btc -> Btc
+ + + + + + Cancel + + + + +
+
Pay invoice to complete swap
+ + + +
+
+ + + + + + + +
+
+
+
+
Btc -> Lightning Btc
+ + + + + + Cancel + + + + +
+
Response - Important
+ + + +
+
+ {% raw %} + + Address - {{ swapBtcToLn.response.address }} + + + Commitment - {{ swapBtcToLn.response.commitment }} + + + Secret Access Key - {{ swapBtcToLn.response.secret_access_key }} + + + Signature - {{ swapBtcToLn.response.signature }} + + + Webhook Url - {{ swapBtcToLn.response.webhook_url }} + + {% endraw %} +
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/deezy/templates/example/index.html b/lnbits/extensions/deezy/templates/example/index.html deleted file mode 100644 index d732ef37..00000000 --- a/lnbits/extensions/deezy/templates/example/index.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} - - -
- Frameworks used by {{SITE_TITLE}} -
- - - {% raw %} - - - {{ tool.name }} - {{ tool.language }} - - {% endraw %} - - - -

- A magical "g" is always available, with info about the user, wallets and - extensions: -

- {% raw %}{{ g }}{% endraw %} -
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/deezy/views.py b/lnbits/extensions/deezy/views.py index b8efeae8..131c03b2 100644 --- a/lnbits/extensions/deezy/views.py +++ b/lnbits/extensions/deezy/views.py @@ -17,5 +17,5 @@ async def index( user: User = Depends(check_user_exists), # type: ignore ): return deezy_renderer().TemplateResponse( - "example/index.html", {"request": request, "user": user.dict()} + "deezy/index.html", {"request": request, "user": user.dict()} ) From 6ec5b9abf6771071daab96f446619f74c3f3a650 Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Mon, 5 Dec 2022 21:07:38 +0100 Subject: [PATCH 03/84] convert string to ints --- lnbits/extensions/deezy/templates/deezy/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index 007144f0..0d68f0a9 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -53,7 +53,7 @@ filled dense emit-value - v-model.trim="swapLnToBtc.data.onChainFees" + v-model.trim="swapLnToBtc.data.on_chain_sats_per_vbyte" label="On chain fees" min="1" type="number" @@ -204,9 +204,9 @@ sendLnToBtc() { var self = this axios.post('https://api-testnet.deezy.io/v1/swap', { - amount_sats: self.swapLnToBtc.data.amount, + amount_sats: parseInt(self.swapLnToBtc.data.amount), on_chain_address: self.swapLnToBtc.data.on_chain_address, - on_chain_sats_per_vbyte: self.swapLnToBtc.data.on_chain_sats_per_vbyte + on_chain_sats_per_vbyte: parseInt(self.swapLnToBtc.data.on_chain_sats_per_vbyte) }) .then(function (response) { self.swapLnToBtc = { From 209750386edcafec949a60fe0ce2881c67da8ff4 Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Mon, 5 Dec 2022 21:12:17 +0100 Subject: [PATCH 04/84] forgot to update this one --- lnbits/extensions/deezy/templates/deezy/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index 0d68f0a9..95bdc14b 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -45,7 +45,7 @@ filled dense emit-value - v-model.trim="swapLnToBtc.data.refund_address" + v-model.trim="swapLnToBtc.data.on_chain_address" type="string" label="Onchain address to receive funds" > From 1b2e8d218d00babcd2058f8724d84dc4ecbab921 Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Tue, 6 Dec 2022 22:39:45 +0100 Subject: [PATCH 05/84] add api docs and update according to requirements --- .../deezy/templates/deezy/_api_docs.html | 221 ++++++++++++ .../deezy/templates/deezy/index.html | 332 +++++++++--------- 2 files changed, 384 insertions(+), 169 deletions(-) create mode 100644 lnbits/extensions/deezy/templates/deezy/_api_docs.html diff --git a/lnbits/extensions/deezy/templates/deezy/_api_docs.html b/lnbits/extensions/deezy/templates/deezy/_api_docs.html new file mode 100644 index 00000000..88e64e1f --- /dev/null +++ b/lnbits/extensions/deezy/templates/deezy/_api_docs.html @@ -0,0 +1,221 @@ + + + + +
+ Deezy.io: Do onchain to offchain and vice-versa swaps +
+

+ Link : + + https://deezy.io/ + +

+

+ API DOCS +

+

+ Created by, + Uthpala +

+
+
+
+ + + + + +
+ Get the current info about the swap service for converting LN btc to on-chain BTC. +
+ + GET (mainnet) + https://api.deezy.io/v1/swap/info + +
+ + GET (testnet) + https://api-testnet.deezy.io/v1/swap/info + +
Response
+
+            {
+              "liquidity_fee_ppm": 2000,
+              "on_chain_bytes_estimate": 300,
+              "max_swap_amount_sats": 100000000,
+              "min_swap_amount_sats": 100000,
+              "available": true
+            }
+          
+
+
+
+ + + +
+ Initiate a new swap to send lightning btc in exchange for on-chain btc +
+ + POST (mainnet) + https://api.deezy.io/v1/swap + +
+ + POST (testnet) + https://api-testnet.deezy.io/v1/swap + +
Payload
+
+            {
+              "amount_sats": 500000,
+              "on_chain_address": "tb1qrcdhlm0m...",
+              "on_chain_sats_per_vbyte": 2
+            }
+          
+
Response
+
+            {
+              "bolt11_invoice": "lntb603u1p3vmxj7p...",
+              "fee_sats": 600
+            }
+          
+
+
+
+ + + +
+ Lookup the on-chain transaction information for an existing swap +
+ + GET (mainnet) + https://api.deezy.io/v1/swap/lookup + +
+ + GET (testnet) + https://api-testnet.deezy.io/v1/swap/lookup + +
Query Parameter
+
+            "bolt11_invoice": "lntb603u1p3vmxj7pp54...",
+          
+
Response
+
+            {
+              "on_chain_txid": "string",
+              "tx_hex": "string"
+            }
+          
+
+
+
+
+ + + + +
+ Generate an on-chain deposit address for your lnurl or lightning address. +
+ + POST (mainnet) + https://api.deezy.io/v1/source + +
+ + POST (testnet) + https://api-testnet.deezy.io/v1/source + +
Payload
+
+            {
+              "lnurl_or_lnaddress": "LNURL1DP68GURN8GHJ...",
+              "secret_access_key": "b3c6056d2845867fa7..",
+              "webhook_url": "https://your.website.com/dee.."
+            }
+          
+
Response
+
+            {
+              "address": "bc1qkceyc5...",
+              "secret_access_key": "b3c6056d28458...",
+              "commitment": "for any satoshis sent to bc1..",
+              "signature": "d69j6aj1ssz5egmsr..",
+              "webhook_url": "https://your.website.com/deez.."
+            }
+          
+
+
+
+ + + +
+ Lookup (BTC to LN) swaps +
+ + GET (mainnet) + https://api.deezy.io/v1/source/lookup + +
+ + GET (testnet) + https://api-testnet.deezy.io/v1/source/lookup + +
Response
+
+            {
+              "swaps": [
+                {
+                  "lnurl_or_lnaddress": "string",
+                  "deposit_address": "string",
+                  "utxo_key": "string",
+                  "deposit_amount_sats": 0,
+                  "target_payout_amount_sats": 0,
+                  "paid_amount_sats": 0,
+                  "deezy_fee_sats": 0,
+                  "status": "string"
+                }
+              ],
+              "total_sent_sats": 0,
+              "total_received_sats": 0,
+              "total_pending_payout_sats": 0,
+              "total_deezy_fees_sats": 0
+            }
+          
+
+
+
+
+
diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index 95bdc14b..233fce34 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -1,176 +1,172 @@ {% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %} - - -
- Deezy -
- +
+
- - - Send onchain funds offchain (BTC -> LN) - - - - - Send offchain funds to onchain address (LN -> BTC) - - +
+ Deezy +
+ + + + + + Send onchain funds offchain (BTC -> LN) + + + + + Send offchain funds to onchain address (LN -> BTC) + + + + +
+
LIGHTNING BTC -> BTC
+ + + + + + Cancel + + + + +
+
Pay invoice to complete swap
+ + + +
+
+ + + + + + + +
+
+
+
+
BTC -> LIGHTNING BTC
+ + + + Cancel + + + + +
+
Response - Important
+ + + +
+
+ {% raw %} + + Address - {{ swapBtcToLn.response.address }} + + + Commitment - {{ swapBtcToLn.response.commitment }} + + + Secret Access Key - {{ swapBtcToLn.response.secret_access_key }} + + + Signature - {{ swapBtcToLn.response.signature }} + + {% endraw %} +
+
+
+
+
+
+ + +
{{SITE_TITLE}} Boltz extension
+
+ + + {% include "deezy/_api_docs.html" %}
-
-
Lightning Btc -> Btc
- - - - - - Cancel - - - - -
-
Pay invoice to complete swap
- - - -
-
- - - - - - - -
-
-
-
-
Btc -> Lightning Btc
- - - - - - Cancel - - - - -
-
Response - Important
- - - -
-
- {% raw %} - - Address - {{ swapBtcToLn.response.address }} - - - Commitment - {{ swapBtcToLn.response.commitment }} - - - Secret Access Key - {{ swapBtcToLn.response.secret_access_key }} - - - Signature - {{ swapBtcToLn.response.signature }} - - - Webhook Url - {{ swapBtcToLn.response.webhook_url }} - - {% endraw %} -
-
-
- +
+
{% endblock %} {% block scripts %} {{ window_vars(user) }} From d338e3889a74602854057a4084060aa78c39cee9 Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Thu, 8 Dec 2022 16:42:33 +0100 Subject: [PATCH 12/84] Add on chain fee suggestions --- .../deezy/templates/deezy/index.html | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index 537ed887..60b31a09 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -12,7 +12,7 @@ label="SWAP (LIGHTNING -> BTC)" unelevated color="primary" - @click="swapLnToBtc.show = true; swapBtcToLn.show = false" + @click="showLnToBtcForm" > Send lightning btc and receive on-chain btc @@ -60,7 +60,9 @@ label="On chain fee rate (sats/vbyte)" min="1" type="number" - > + :hint="swapLnToBtc.suggested_fees && `Economy Fee - ${swapLnToBtc.suggested_fees?.economyFee} | Half an hour fee - ${swapLnToBtc.suggested_fees?.halfHourFee} | Fastest fee - ${swapLnToBtc.suggested_fees?.fastestFee}`" + > + { + console.log(result.data) + this.swapLnToBtc.suggested_fees = result.data + }) + }, checkIfInvoiceIsPaid() { if (this.swapLnToBtc.response && !this.swapLnToBtc.invoicePaid) { var self = this From c2423605ccff3604640d765e5304ea424c280782 Mon Sep 17 00:00:00 2001 From: Danny Diekroeger Date: Sat, 10 Dec 2022 11:00:36 -0800 Subject: [PATCH 13/84] add migrations --- lnbits/extensions/deezy/migrations.py | 39 ++++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lnbits/extensions/deezy/migrations.py b/lnbits/extensions/deezy/migrations.py index 99d7c362..86edcf09 100644 --- a/lnbits/extensions/deezy/migrations.py +++ b/lnbits/extensions/deezy/migrations.py @@ -1,10 +1,29 @@ -# 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} -# ); -# """ -# ) +async def m001_initial(db): + await db.execute( + f""" + CREATE TABLE deezy.ln_to_btc_swap ( + id TEXT PRIMARY KEY, + amount_sats {db.big_int} NOT NULL, + on_chain_address TEXT NOT NULL, + on_chain_sats_per_vbyte INT NOT NULL, + bolt11_invoice TEXT NOT NULL, + fee_sats {db.big_int} NOT NULL, + txid TEXT NULL, + tx_hex TEXT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """ + ) + await db.execute( + f""" + CREATE TABLE deezy.btc_to_ln_swap ( + id TEXT PRIMARY KEY, + ln_address TEXT NOT NULL, + on_chain_address TEXT NOT NULL, + secret_access_key TEXT NOT NULL, + commitment TEXT NOT NULL, + signature TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """ + ) From 663cee8fbe1b5836d9421c854d7f075b9c081fa5 Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Sun, 11 Dec 2022 17:42:47 +0100 Subject: [PATCH 14/84] fix ui bug --- lnbits/extensions/deezy/templates/deezy/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index 60b31a09..98067fd7 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -223,12 +223,12 @@ showLnToBtcForm() { this.getSuggestedOnChainFees() this.swapLnToBtc.show = true + this.swapBtcToLn.show = false }, getSuggestedOnChainFees() { axios .get('https://mempool.space/api/v1/fees/recommended') .then(result => { - console.log(result.data) this.swapLnToBtc.suggested_fees = result.data }) }, From 7218057ca268bbfb87e27a65d66a222e23393978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 27 Dec 2022 13:20:07 +0100 Subject: [PATCH 15/84] update lock file and workflows to poetry 1.3.1 --- .github/workflows/formatting.yml | 2 +- .github/workflows/migrations.yml | 2 +- .github/workflows/mypy.yml | 2 +- .github/workflows/regtest.yml | 6 +- .github/workflows/tests.yml | 6 +- poetry.lock | 2321 +++++++++++++++--------------- 6 files changed, 1165 insertions(+), 1174 deletions(-) diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index e3d0fd35..b6966bfa 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index c280ad7d..11429665 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index d80da678..6868455e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml index 2d7aae6b..99687032 100644 --- a/.github/workflows/regtest.yml +++ b/.github/workflows/regtest.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -51,7 +51,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -95,7 +95,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 487411ed..7409b03e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -31,7 +31,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -67,7 +67,7 @@ jobs: strategy: matrix: python-version: ["3.9"] - poetry-version: ["1.2.1"] + poetry-version: ["1.3.1"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/poetry.lock b/poetry.lock index ce70fb81..c0e1258c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "aiofiles" version = "0.8.0" @@ -5,6 +7,10 @@ description = "File support for asyncio." category = "main" optional = false python-versions = ">=3.6,<4.0" +files = [ + {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, + {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, +] [[package]] name = "anyio" @@ -13,6 +19,10 @@ description = "High level compatibility layer for multiple asynchronous event lo category = "main" optional = false python-versions = ">=3.6.2" +files = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] [package.dependencies] idna = ">=2.8" @@ -31,6 +41,10 @@ description = "ASGI specs, helper code, and adapters" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, +] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -45,6 +59,10 @@ description = "Fast ASN.1 parser and serializer with definitions for private key category = "main" optional = false python-versions = "*" +files = [ + {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, + {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, +] [[package]] name = "async-timeout" @@ -53,6 +71,10 @@ description = "Timeout context manager for asyncio programs" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""} @@ -64,6 +86,10 @@ description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -78,6 +104,10 @@ description = "Base58 and Base58Check implementation." category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, + {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, +] [package.extras] tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", "pytest-cov", "pytest-flake8"] @@ -89,6 +119,10 @@ description = "Reference implementation for Bech32 and segwit addresses." category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"}, + {file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"}, +] [[package]] name = "bitstring" @@ -97,14 +131,33 @@ description = "Simple construction, analysis and modification of binary data." category = "main" optional = false python-versions = "*" +files = [ + {file = "bitstring-3.1.9-py2-none-any.whl", hash = "sha256:e3e340e58900a948787a05e8c08772f1ccbe133f6f41fe3f0fa19a18a22bbf4f"}, + {file = "bitstring-3.1.9-py3-none-any.whl", hash = "sha256:0de167daa6a00c9386255a7cac931b45e6e24e0ad7ea64f1f92a64ac23ad4578"}, + {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, +] [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] [package.dependencies] click = ">=8.0.0" @@ -128,6 +181,10 @@ description = "Ecash wallet and mint with Bitcoin Lightning support" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"}, + {file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"}, +] [package.dependencies] anyio = {version = "3.6.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""} @@ -182,6 +239,9 @@ description = "Lightweight, extensible schema and data validation tool for Pytho category = "main" optional = false python-versions = ">=2.7" +files = [ + {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, +] [package.dependencies] setuptools = "*" @@ -193,6 +253,10 @@ description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] [[package]] name = "cffi" @@ -201,1022 +265,7 @@ description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.0.12" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.0.4" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "coincurve" -version = "17.0.0" -description = "Cross-platform Python CFFI bindings for libsecp256k1" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -asn1crypto = "*" -cffi = ">=1.3.0" - -[[package]] -name = "colorama" -version = "0.4.5" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "coverage" -version = "6.5.0" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cryptography" -version = "36.0.2" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] - -[[package]] -name = "ecdsa" -version = "0.18.0" -description = "ECDSA cryptographic signature library (pure python)" -category = "main" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - -[[package]] -name = "embit" -version = "0.4.9" -description = "yet another bitcoin library" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "enum34" -version = "1.1.10" -description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "environs" -version = "9.5.0" -description = "simplified environment variable parsing" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -marshmallow = ">=3.0.0" -python-dotenv = "*" - -[package.extras] -dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] -django = ["dj-database-url", "dj-email-url", "django-cache-url"] -lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] - -[[package]] -name = "fastapi" -version = "0.83.0" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" -starlette = "0.19.1" - -[package.extras] -all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] -dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] -doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] -test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] - -[[package]] -name = "grpcio" -version = "1.51.1" -description = "HTTP/2-based RPC framework" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -protobuf = ["grpcio-tools (>=1.51.1)"] - -[[package]] -name = "h11" -version = "0.12.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "httpcore" -version = "0.15.0" -description = "A minimal low-level HTTP client." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -anyio = ">=3.0.0,<4.0.0" -certifi = "*" -h11 = ">=0.11,<0.13" -sniffio = ">=1.0.0,<2.0.0" - -[package.extras] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "httptools" -version = "0.4.0" -description = "A collection of framework independent HTTP protocol utils." -category = "main" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] - -[[package]] -name = "httpx" -version = "0.23.0" -description = "The next generation HTTP client." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -certifi = "*" -httpcore = ">=0.15.0,<0.16.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "5.0.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "jinja2" -version = "3.0.1" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lnurl" -version = "0.3.6" -description = "LNURL implementation for Python." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -bech32 = "*" -pydantic = "*" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "loguru" -version = "0.6.0" -description = "Python logging made (stupidly) simple" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} -win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} - -[package.extras] -dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"] - -[[package]] -name = "markupsafe" -version = "2.0.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "marshmallow" -version = "3.18.0" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "mock" -version = "4.0.3" -description = "Rolling backport of unittest.mock for all Pythons" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest (<5.4)", "pytest-cov"] - -[[package]] -name = "mypy" -version = "0.971" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -mypy-extensions = ">=0.4.3" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.10" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "outcome" -version = "1.2.0" -description = "Capture the outcome of Python function calls." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pathlib2" -version = "2.3.7.post1" -description = "Object-oriented filesystem paths" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[[package]] -name = "pathspec" -version = "0.10.2" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "platformdirs" -version = "2.5.4" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "protobuf" -version = "4.21.10" -description = "" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "psycopg2-binary" -version = "2.9.1" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pycryptodomex" -version = "3.14.1" -description = "Cryptographic library for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pydantic" -version = "1.10.2" -description = "Data validation and settings management using python type hints" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = ">=4.1.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pyln-bolt7" -version = "1.0.246" -description = "BOLT7" -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[[package]] -name = "pyln-client" -version = "0.11.1" -description = "Client library and plugin library for Core Lightning" -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -pyln-bolt7 = ">=1.0,<2.0" -pyln-proto = ">=0.11,<0.12" - -[[package]] -name = "pyln-proto" -version = "0.11.1" -description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." -category = "main" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -base58 = ">=2.1.1,<3.0.0" -bitstring = ">=3.1.9,<4.0.0" -coincurve = ">=17.0.0,<18.0.0" -cryptography = ">=36.0.1,<37.0.0" -PySocks = ">=1.7.1,<2.0.0" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pypng" -version = "0.0.21" -description = "Pure Python library for saving and loading PNG images" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyqrcode" -version = "1.2.1" -description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -png = ["pypng (>=0.0.13)"] - -[[package]] -name = "pyscss" -version = "1.4.0" -description = "pyScss, a Scss compiler for Python" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -enum34 = "*" -pathlib2 = "*" -six = "*" - -[[package]] -name = "pysocks" -version = "1.7.1" -description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pytest" -version = "7.1.3" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.19.0" -description = "Pytest support for asyncio" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pytest = ">=6.1.0" -typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} - -[package.extras] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] - -[[package]] -name = "pytest-cov" -version = "3.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "python-bitcoinlib" -version = "0.11.2" -description = "The Swiss Army Knife of the Bitcoin protocol." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "python-dotenv" -version = "0.21.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "pyyaml" -version = "5.4.1" -description = "YAML parser and emitter for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[[package]] -name = "represent" -version = "1.6.0.post0" -description = "Create __repr__ automatically or declaratively." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -six = ">=1.8.0" - -[package.extras] -test = ["ipython", "mock", "pytest (>=3.0.5)"] - -[[package]] -name = "requests" -version = "2.27.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "secp256k1" -version = "0.14.0" -description = "FFI bindings to libsecp256k1" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -cffi = ">=1.3.0" - -[[package]] -name = "setuptools" -version = "65.6.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "shortuuid" -version = "1.0.1" -description = "A generator library for concise, unambiguous and URL-safe UUIDs." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "sqlalchemy" -version = "1.3.24" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mysql = ["mysqlclient"] -oracle = ["cx_oracle"] -postgresql = ["psycopg2"] -postgresql-pg8000 = ["pg8000 (<1.16.6)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] - -[[package]] -name = "sqlalchemy-aio" -version = "0.17.0" -description = "Async support for SQLAlchemy." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -outcome = "*" -represent = ">=1.4" -sqlalchemy = "<1.4" - -[package.extras] -test = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)", "pytest-trio (>=0.6)"] -test-noextras = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)"] -trio = ["trio (>=0.15)"] - -[[package]] -name = "sse-starlette" -version = "0.6.2" -description = "SSE plugin for Starlette" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "starlette" -version = "0.19.1" -description = "The little ASGI library that shines." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -anyio = ">=3.4.0,<5" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "types-protobuf" -version = "3.20.4.6" -description = "Typing stubs for protobuf" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.12" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "uvicorn" -version = "0.18.3" -description = "The lightning-fast ASGI server." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] - -[[package]] -name = "uvloop" -version = "0.16.0" -description = "Fast implementation of asyncio event loop on top of libuv" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] - -[[package]] -name = "watchgod" -version = "0.7" -description = "Simple, modern file watching and code reload in python." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "websocket-client" -version = "1.3.3" -description = "WebSocket client for Python with low level API options" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "websockets" -version = "10.0" -description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "win32-setctime" -version = "1.1.0" -description = "A small Python utility to set file creation time on Windows" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] - -[[package]] -name = "zipp" -version = "3.9.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" -content-hash = "7f75ca0b067a11f19520dc2121f0789e16738b573a8da84ba3838ed8a466a6e1" - - -[metadata.files] -aiofiles = [ - {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, - {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, -] -anyio = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, -] -asgiref = [ - {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, - {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, -] -asn1crypto = [ - {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, - {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, -] -async-timeout = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -base58 = [ - {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"}, - {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"}, -] -bech32 = [ - {file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"}, - {file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"}, -] -bitstring = [ - {file = "bitstring-3.1.9-py2-none-any.whl", hash = "sha256:e3e340e58900a948787a05e8c08772f1ccbe133f6f41fe3f0fa19a18a22bbf4f"}, - {file = "bitstring-3.1.9-py3-none-any.whl", hash = "sha256:0de167daa6a00c9386255a7cac931b45e6e24e0ad7ea64f1f92a64ac23ad4578"}, - {file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"}, -] -black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] -cashu = [ - {file = "cashu-0.6.0-py3-none-any.whl", hash = "sha256:54096af145643aab45943b235f95a3357b0ec697835c1411e66523049ffb81f6"}, - {file = "cashu-0.6.0.tar.gz", hash = "sha256:503a90c4ca8d25d0b2c3f78a11b163c32902a726ea5b58e5337dc00eca8e96ad"}, -] -cerberus = [ - {file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"}, -] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] -cffi = [ +files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, @@ -1282,15 +331,49 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] -charset-normalizer = [ + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" +files = [ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] -click = [ + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] -coincurve = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "coincurve" +version = "17.0.0" +description = "Cross-platform Python CFFI bindings for libsecp256k1" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"}, {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"}, {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"}, @@ -1326,63 +409,98 @@ coincurve = [ {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"}, {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"}, ] -colorama = [ + +[package.dependencies] +asn1crypto = "*" +cffi = ">=1.3.0" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -coverage = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, + +[[package]] +name = "coverage" +version = "7.0.1" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, + {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, + {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, + {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, + {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, + {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, + {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, + {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, + {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, + {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, + {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, + {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, + {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, + {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, ] -cryptography = [ + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "36.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"}, {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"}, @@ -1404,27 +522,113 @@ cryptography = [ {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"}, {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] -ecdsa = [ + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools_rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, ] -embit = [ + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "embit" +version = "0.4.9" +description = "yet another bitcoin library" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"}, ] -enum34 = [ + +[[package]] +name = "enum34" +version = "1.1.10" +description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"}, {file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"}, {file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"}, ] -environs = [ + +[[package]] +name = "environs" +version = "9.5.0" +description = "simplified environment variable parsing" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, ] -fastapi = [ + +[package.dependencies] +marshmallow = ">=3.0.0" +python-dotenv = "*" + +[package.extras] +dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] +django = ["dj-database-url", "dj-email-url", "django-cache-url"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] +tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] + +[[package]] +name = "fastapi" +version = "0.83.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6.1" +files = [ {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"}, {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"}, ] -grpcio = [ + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.19.1" + +[package.extras] +all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] +test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] + +[[package]] +name = "grpcio" +version = "1.51.1" +description = "HTTP/2-based RPC framework" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "grpcio-1.51.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3"}, {file = "grpcio-1.51.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef"}, {file = "grpcio-1.51.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2"}, @@ -1471,15 +675,52 @@ grpcio = [ {file = "grpcio-1.51.1-cp39-cp39-win_amd64.whl", hash = "sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c"}, {file = "grpcio-1.51.1.tar.gz", hash = "sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a"}, ] -h11 = [ + +[package.extras] +protobuf = ["grpcio-tools (>=1.51.1)"] + +[[package]] +name = "h11" +version = "0.12.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, ] -httpcore = [ + +[[package]] +name = "httpcore" +version = "0.15.0" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"}, {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, ] -httptools = [ + +[package.dependencies] +anyio = ">=3.0.0,<4.0.0" +certifi = "*" +h11 = ">=0.11,<0.13" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httptools" +version = "0.4.0" +description = "A collection of framework independent HTTP protocol utils." +category = "main" +optional = false +python-versions = ">=3.5.0" +files = [ {file = "httptools-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcddfe70553be717d9745990dfdb194e22ee0f60eb8f48c0794e7bfeda30d2d5"}, {file = "httptools-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1ee0b459257e222b878a6c09ccf233957d3a4dcb883b0847640af98d2d9aac23"}, {file = "httptools-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceafd5e960b39c7e0d160a1936b68eb87c5e79b3979d66e774f0c77d4d8faaed"}, @@ -1515,39 +756,159 @@ httptools = [ {file = "httptools-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:34d2903dd2a3dd85d33705b6fde40bf91fc44411661283763fd0746723963c83"}, {file = "httptools-0.4.0.tar.gz", hash = "sha256:2c9a930c378b3d15d6b695fb95ebcff81a7395b4f9775c4f10a076beb0b2c1ff"}, ] -httpx = [ + +[package.extras] +test = ["Cython (>=0.29.24,<0.30.0)"] + +[[package]] +name = "httpx" +version = "0.23.0" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, ] -idna = [ + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.16.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -importlib-metadata = [ + +[[package]] +name = "importlib-metadata" +version = "5.0.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, ] -iniconfig = [ + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, + +[[package]] +name = "isort" +version = "5.11.4" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "isort-5.11.4-py3-none-any.whl", hash = "sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"}, + {file = "isort-5.11.4.tar.gz", hash = "sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6"}, ] -jinja2 = [ + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jinja2" +version = "3.0.1" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] -lnurl = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lnurl" +version = "0.3.6" +description = "LNURL implementation for Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "lnurl-0.3.6-py3-none-any.whl", hash = "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92"}, {file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"}, ] -loguru = [ + +[package.dependencies] +bech32 = "*" +pydantic = "*" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "loguru" +version = "0.6.0" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, ] -markupsafe = [ + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"] + +[[package]] +name = "markupsafe" +version = "2.0.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, @@ -1618,15 +979,53 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] -marshmallow = [ + +[[package]] +name = "marshmallow" +version = "3.18.0" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"}, {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"}, ] -mock = [ + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mock" +version = "4.0.3" +description = "Rolling backport of unittest.mock for all Pythons" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, ] -mypy = [ + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest (<5.4)", "pytest-cov"] + +[[package]] +name = "mypy" +version = "0.971" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, @@ -1651,51 +1050,154 @@ mypy = [ {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, ] -mypy-extensions = [ + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -outcome = [ + +[[package]] +name = "outcome" +version = "1.2.0" +description = "Capture the outcome of Python function calls." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, ] -packaging = [ + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pathlib2 = [ + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathlib2" +version = "2.3.7.post1" +description = "Object-oriented filesystem paths" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, ] -pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, + +[package.dependencies] +six = "*" + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, ] -platformdirs = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, + +[[package]] +name = "platformdirs" +version = "2.6.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, + {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, ] -pluggy = [ + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -protobuf = [ - {file = "protobuf-4.21.10-cp310-abi3-win32.whl", hash = "sha256:e92768d17473657c87e98b79a4c7724b0ddfa23211b05ce137bfdc55e734e36f"}, - {file = "protobuf-4.21.10-cp310-abi3-win_amd64.whl", hash = "sha256:0c968753028cb14b1d24cc839723f7e9505b305fc588a37a9e0f7d270cb59d89"}, - {file = "protobuf-4.21.10-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:e53165dd14d19abc7f50733f365de431e51d1d262db40c0ee22e271a074fac59"}, - {file = "protobuf-4.21.10-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5efa8a8162ada7e10847140308fbf84fdc5b89dc21655d12ec04aed87284fe07"}, - {file = "protobuf-4.21.10-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:2a172741b5b041a896b621cef4277077afd571e0d3a6e524e7171f1c70e33200"}, - {file = "protobuf-4.21.10-cp37-cp37m-win32.whl", hash = "sha256:05cbcb9a25cd781fd949f93f6f98a911883868c0360c6d2264fc99a903c8f0d7"}, - {file = "protobuf-4.21.10-cp37-cp37m-win_amd64.whl", hash = "sha256:3f08f04b4f101dd469efbcc1731fbf48068eccd8a42f4e2ea530aa012a5f56f8"}, - {file = "protobuf-4.21.10-cp38-cp38-win32.whl", hash = "sha256:6b809f20923b6ef49dc1755cb50bdb21be179b4a3c7ffcab1fe5d3f139b58a51"}, - {file = "protobuf-4.21.10-cp38-cp38-win_amd64.whl", hash = "sha256:81b233a06c62387ea5c9be2cd9aedd2ba09940e91da53b920e9ff5bd98e48e7f"}, - {file = "protobuf-4.21.10-cp39-cp39-win32.whl", hash = "sha256:b78d7c2c36b51c0041b9bf000be4adb09f4112bfc40bc7a9d48ac0b0dfad139e"}, - {file = "protobuf-4.21.10-cp39-cp39-win_amd64.whl", hash = "sha256:0413addc126c40a5440ee59be098de1007183d68e9f5f20ed5fbc44848f417ca"}, - {file = "protobuf-4.21.10-py2.py3-none-any.whl", hash = "sha256:a5e89eabaa0ca72ce1b7c8104a740d44cdb67942cbbed00c69a4c0541de17107"}, - {file = "protobuf-4.21.10-py3-none-any.whl", hash = "sha256:5096b3922b45e4b7a04d3d3cb855d13bb5ccd4d5e44b129e706232ebf0ffb870"}, - {file = "protobuf-4.21.10.tar.gz", hash = "sha256:4d97c16c0d11155b3714a29245461f0eb60cace294455077f3a3b8a629afa383"}, + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "protobuf" +version = "4.21.12" +description = "" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.21.12-cp310-abi3-win32.whl", hash = "sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1"}, + {file = "protobuf-4.21.12-cp310-abi3-win_amd64.whl", hash = "sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2"}, + {file = "protobuf-4.21.12-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791"}, + {file = "protobuf-4.21.12-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97"}, + {file = "protobuf-4.21.12-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7"}, + {file = "protobuf-4.21.12-cp37-cp37m-win32.whl", hash = "sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717"}, + {file = "protobuf-4.21.12-cp37-cp37m-win_amd64.whl", hash = "sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"}, + {file = "protobuf-4.21.12-cp38-cp38-win32.whl", hash = "sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec"}, + {file = "protobuf-4.21.12-cp38-cp38-win_amd64.whl", hash = "sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30"}, + {file = "protobuf-4.21.12-cp39-cp39-win32.whl", hash = "sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc"}, + {file = "protobuf-4.21.12-cp39-cp39-win_amd64.whl", hash = "sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b"}, + {file = "protobuf-4.21.12-py2.py3-none-any.whl", hash = "sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5"}, + {file = "protobuf-4.21.12-py3-none-any.whl", hash = "sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462"}, + {file = "protobuf-4.21.12.tar.gz", hash = "sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab"}, ] -psycopg2-binary = [ + +[[package]] +name = "psycopg2-binary" +version = "2.9.1" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"}, {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"}, {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"}, @@ -1733,15 +1235,39 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"}, {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"}, ] -py = [ + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] -pycparser = [ + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pycryptodomex = [ + +[[package]] +name = "pycryptodomex" +version = "3.14.1" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "pycryptodomex-3.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca88f2f7020002638276439a01ffbb0355634907d1aa5ca91f3dc0c2e44e8f3b"}, {file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:8536bc08d130cae6dcba1ea689f2913dfd332d06113904d171f2f56da6228e89"}, {file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:406ec8cfe0c098fadb18d597dc2ee6de4428d640c0ccafa453f3d9b2e58d29e2"}, @@ -1770,7 +1296,15 @@ pycryptodomex = [ {file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151"}, {file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"}, ] -pydantic = [ + +[[package]] +name = "pydantic" +version = "1.10.2" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, @@ -1808,58 +1342,229 @@ pydantic = [ {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, ] -pyln-bolt7 = [ + +[package.dependencies] +typing-extensions = ">=4.1.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pyln-bolt7" +version = "1.0.246" +description = "BOLT7" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"}, {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"}, ] -pyln-client = [ + +[[package]] +name = "pyln-client" +version = "0.11.1" +description = "Client library and plugin library for Core Lightning" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {file = "pyln-client-0.11.1.tar.gz", hash = "sha256:f5ea648840b030e2bbcf8c66ee72d25a5817f89854434a28d30e887547138c8e"}, {file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"}, ] -pyln-proto = [ + +[package.dependencies] +pyln-bolt7 = ">=1.0,<2.0" +pyln-proto = ">=0.11,<0.12" + +[[package]] +name = "pyln-proto" +version = "0.11.1" +description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"}, {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"}, ] -pyparsing = [ + +[package.dependencies] +base58 = ">=2.1.1,<3.0.0" +bitstring = ">=3.1.9,<4.0.0" +coincurve = ">=17.0.0,<18.0.0" +cryptography = ">=36.0.1,<37.0.0" +PySocks = ">=1.7.1,<2.0.0" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" +files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -pypng = [ + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pypng" +version = "0.0.21" +description = "Pure Python library for saving and loading PNG images" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"}, ] -pyqrcode = [ + +[[package]] +name = "pyqrcode" +version = "1.2.1" +description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"}, {file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"}, ] -pyscss = [ + +[package.extras] +png = ["pypng (>=0.0.13)"] + +[[package]] +name = "pyscss" +version = "1.4.0" +description = "pyScss, a Scss compiler for Python" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"}, ] -pysocks = [ + +[package.dependencies] +enum34 = "*" +pathlib2 = "*" +six = "*" + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, ] -pytest = [ + +[[package]] +name = "pytest" +version = "7.1.3" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, ] -pytest-asyncio = [ + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.19.0" +description = "Pytest support for asyncio" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, ] -pytest-cov = [ + +[package.dependencies] +pytest = ">=6.1.0" +typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} + +[package.extras] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] -python-bitcoinlib = [ + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "python-bitcoinlib" +version = "0.11.2" +description = "The Swiss Army Knife of the Bitcoin protocol." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "python-bitcoinlib-0.11.2.tar.gz", hash = "sha256:61ba514e0d232cc84741e49862dcedaf37199b40bba252a17edc654f63d13f39"}, {file = "python_bitcoinlib-0.11.2-py3-none-any.whl", hash = "sha256:78bd4ee717fe805cd760dfdd08765e77b7c7dbef4627f8596285e84953756508"}, ] -python-dotenv = [ + +[[package]] +name = "python-dotenv" +version = "0.21.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, ] -pyyaml = [ + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "5.4.1" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, @@ -1890,19 +1595,73 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -represent = [ + +[[package]] +name = "represent" +version = "1.6.0.post0" +description = "Create __repr__ automatically or declaratively." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, ] -requests = [ + +[package.dependencies] +six = ">=1.8.0" + +[package.extras] +test = ["ipython", "mock", "pytest (>=3.0.5)"] + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] -rfc3986 = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] -secp256k1 = [ + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "secp256k1" +version = "0.14.0" +description = "FFI bindings to libsecp256k1" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "secp256k1-0.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f666c67dcf1dc69e1448b2ede5e12aaf382b600204a61dbc65e4f82cea444405"}, {file = "secp256k1-0.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fcabb3c3497a902fb61eec72d1b69bf72747d7bcc2a732d56d9319a1e8322262"}, {file = "secp256k1-0.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a27c479ab60571502516a1506a562d0a9df062de8ad645313fabfcc97252816"}, @@ -1927,23 +1686,71 @@ secp256k1 = [ {file = "secp256k1-0.14.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9e7c024ff17e9b9d7c392bb2a917da231d6cb40ab119389ff1f51dca10339a4"}, {file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"}, ] -setuptools = [ + +[package.dependencies] +cffi = ">=1.3.0" + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, ] -shortuuid = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shortuuid" +version = "1.0.1" +description = "A generator library for concise, unambiguous and URL-safe UUIDs." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"}, {file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"}, ] -six = [ + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -sniffio = [ + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] -sqlalchemy = [ + +[[package]] +name = "sqlalchemy" +version = "1.3.24" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, @@ -1979,22 +1786,91 @@ sqlalchemy = [ {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, ] -sqlalchemy-aio = [ + +[package.extras] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mysql = ["mysqlclient"] +oracle = ["cx_oracle"] +postgresql = ["psycopg2"] +postgresql-pg8000 = ["pg8000 (<1.16.6)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] + +[[package]] +name = "sqlalchemy-aio" +version = "0.17.0" +description = "Async support for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"}, {file = "sqlalchemy_aio-0.17.0.tar.gz", hash = "sha256:f531c7982662d71dfc0b117e77bb2ed544e25cd5361e76cf9f5208edcfb71f7b"}, ] -sse-starlette = [ + +[package.dependencies] +outcome = "*" +represent = ">=1.4" +sqlalchemy = "<1.4" + +[package.extras] +test = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)", "pytest-trio (>=0.6)"] +test-noextras = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)"] +trio = ["trio (>=0.15)"] + +[[package]] +name = "sse-starlette" +version = "0.6.2" +description = "SSE plugin for Starlette" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "sse-starlette-0.6.2.tar.gz", hash = "sha256:1c0cc62cc7d021a386dc06a16a9ddc3e2861d19da6bc2e654e65cc111e820456"}, ] -starlette = [ + +[[package]] +name = "starlette" +version = "0.19.1" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, ] -tomli = [ + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typed-ast = [ + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, @@ -2020,23 +1896,76 @@ typed-ast = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] -types-protobuf = [ + +[[package]] +name = "types-protobuf" +version = "3.20.4.6" +description = "Typing stubs for protobuf" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "types-protobuf-3.20.4.6.tar.gz", hash = "sha256:ba27443c592bbec1629dd69494a24c84461c63f0d3b7d648ce258aaae9680965"}, {file = "types_protobuf-3.20.4.6-py3-none-any.whl", hash = "sha256:ab2d315ba82246b83d28f8797c98dc0fe1dd5cfd187909e56faf87239aedaae3"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +files = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] -uvicorn = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "uvicorn" +version = "0.18.3" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, ] -uvloop = [ + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] + +[[package]] +name = "uvloop" +version = "0.16.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, @@ -2054,15 +1983,49 @@ uvloop = [ {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, ] -watchgod = [ + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "watchgod" +version = "0.7" +description = "Simple, modern file watching and code reload in python." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, ] -websocket-client = [ + +[[package]] +name = "websocket-client" +version = "1.3.3" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "websocket-client-1.3.3.tar.gz", hash = "sha256:d58c5f284d6a9bf8379dab423259fe8f85b70d5fa5d2916d5791a84594b122b1"}, {file = "websocket_client-1.3.3-py3-none-any.whl", hash = "sha256:5d55652dc1d0b3c734f044337d929aaf83f4f9138816ec680c1aefefb4dc4877"}, ] -websockets = [ + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "websockets" +version = "10.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"}, {file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"}, {file = "websockets-10.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8"}, @@ -2089,11 +2052,39 @@ websockets = [ {file = "websockets-10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567"}, {file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, ] -win32-setctime = [ + +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] -zipp = [ + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + +[[package]] +name = "zipp" +version = "3.9.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7" +content-hash = "73e1443abc1eed24639a5297a66b2eb16e80491f8849b8008e065f153de215c7" From cd05eba183bc7b8bfdbd1c98d52bd07fae90a256 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 5 Jan 2023 00:52:08 +0000 Subject: [PATCH 16/84] Added tile and tweaked description for size limitation --- lnbits/extensions/deezy/__init__.py | 9 +++++++++ lnbits/extensions/deezy/config.json | 4 ++-- lnbits/extensions/deezy/static/deezy.png | Bin 0 -> 5196 bytes 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 lnbits/extensions/deezy/static/deezy.png diff --git a/lnbits/extensions/deezy/__init__.py b/lnbits/extensions/deezy/__init__.py index 120124e7..97f6d9ef 100644 --- a/lnbits/extensions/deezy/__init__.py +++ b/lnbits/extensions/deezy/__init__.py @@ -1,4 +1,5 @@ from fastapi import APIRouter +from starlette.staticfiles import StaticFiles from lnbits.db import Database from lnbits.helpers import template_renderer @@ -7,6 +8,14 @@ db = Database("ext_deezy") deezy_ext: APIRouter = APIRouter(prefix="/deezy", tags=["deezy"]) +deezy_static_files = [ + { + "path": "/deezy/static", + "app": StaticFiles(directory="lnbits/extensions/deezy/static"), + "name": "deezy_static", + } +] + def deezy_renderer(): return template_renderer(["lnbits/extensions/deezy/templates"]) diff --git a/lnbits/extensions/deezy/config.json b/lnbits/extensions/deezy/config.json index e3c1a3d7..4f945a79 100644 --- a/lnbits/extensions/deezy/config.json +++ b/lnbits/extensions/deezy/config.json @@ -1,6 +1,6 @@ { "name": "Deezy", - "short_description": "Swap lightning to on-chain, or receive on-chain to lightning addresses.", - "icon": "swap_horiz", + "short_description": "LN to onchain, onchain to LN swaps", + "tile": "/deezy/static/deezy.png", "contributors": ["Uthpala"] } diff --git a/lnbits/extensions/deezy/static/deezy.png b/lnbits/extensions/deezy/static/deezy.png new file mode 100644 index 0000000000000000000000000000000000000000..cb526705c4f83e165699236de8bdf4d69b61e1b7 GIT binary patch literal 5196 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 QU|?YIboFyt=akR{09nk?t^fc4 literal 0 HcmV?d00001 From 2b46fa6c5642d5293dd1901c2c66fd6a12499db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 5 Jan 2023 09:28:38 +0100 Subject: [PATCH 17/84] fix livestream mypy issues --- lnbits/extensions/livestream/lnurl.py | 29 +++++++++++++---------- lnbits/extensions/livestream/models.py | 16 ++++++------- lnbits/extensions/livestream/tasks.py | 3 +++ lnbits/extensions/livestream/views.py | 24 +++++++++++-------- lnbits/extensions/livestream/views_api.py | 15 ++++++------ pyproject.toml | 1 - 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/lnbits/extensions/livestream/lnurl.py b/lnbits/extensions/livestream/lnurl.py index 89e431e5..f50912b3 100644 --- a/lnbits/extensions/livestream/lnurl.py +++ b/lnbits/extensions/livestream/lnurl.py @@ -1,12 +1,9 @@ -import hashlib import math from http import HTTPStatus -from os import name -from fastapi.exceptions import HTTPException -from fastapi.params import Query +from fastapi import HTTPException, Query, Request from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse -from starlette.requests import Request # type: ignore +from lnurl.models import ClearnetUrl, LightningInvoice, MilliSatoshi from lnbits.core.services import create_invoice @@ -29,9 +26,12 @@ async def lnurl_livestream(ls_id, request: Request): ) resp = LnurlPayResponse( - callback=request.url_for("livestream.lnurl_callback", track_id=track.id), - min_sendable=track.min_sendable, - max_sendable=track.max_sendable, + callback=ClearnetUrl( + request.url_for("livestream.lnurl_callback", track_id=track.id), + scheme="https", + ), + minSendable=MilliSatoshi(track.min_sendable), + maxSendable=MilliSatoshi(track.max_sendable), metadata=await track.lnurlpay_metadata(), ) @@ -48,9 +48,12 @@ async def lnurl_track(track_id, request: Request): raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Track not found.") resp = LnurlPayResponse( - callback=request.url_for("livestream.lnurl_callback", track_id=track.id), - min_sendable=track.min_sendable, - max_sendable=track.max_sendable, + callback=ClearnetUrl( + request.url_for("livestream.lnurl_callback", track_id=track.id), + scheme="https", + ), + minSendable=MilliSatoshi(track.min_sendable), + maxSendable=MilliSatoshi(track.max_sendable), metadata=await track.lnurlpay_metadata(), ) @@ -85,6 +88,7 @@ async def lnurl_callback( ).dict() ls = await get_livestream_by_track(track_id) + assert ls payment_hash, payment_request = await create_invoice( wallet_id=ls.wallet, @@ -94,13 +98,14 @@ async def lnurl_callback( extra={"tag": "livestream", "track": track.id, "comment": comment}, ) + assert track.price_msat if amount_received < track.price_msat: success_action = None else: success_action = track.success_action(payment_hash, request=request) resp = LnurlPayActionResponse( - pr=payment_request, success_action=success_action, routes=[] + pr=LightningInvoice(payment_request), successAction=success_action, routes=[] ) return resp.dict() diff --git a/lnbits/extensions/livestream/models.py b/lnbits/extensions/livestream/models.py index 0034f4a7..5d617da9 100644 --- a/lnbits/extensions/livestream/models.py +++ b/lnbits/extensions/livestream/models.py @@ -1,13 +1,12 @@ import json from typing import Optional -from fastapi import Query +from fastapi import Query, Request from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl import encode as lnurl_encode +from lnurl.models import ClearnetUrl, Max144Str, UrlAction +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel -from starlette.requests import Request class CreateTrack(BaseModel): @@ -32,7 +31,7 @@ class Livestream(BaseModel): class Track(BaseModel): id: int download_url: Optional[str] - price_msat: Optional[int] + price_msat: int = 0 name: str producer: int @@ -71,7 +70,7 @@ class Track(BaseModel): def success_action( self, payment_hash: str, request: Request - ) -> Optional[LnurlPaySuccessAction]: + ) -> Optional[UrlAction]: if not self.download_url: return None @@ -79,7 +78,8 @@ class Track(BaseModel): url_with_query = f"{url}?p={payment_hash}" return UrlAction( - url=url_with_query, description=f"Download the track {self.name}!" + url=ClearnetUrl(url_with_query, scheme="https"), + description=Max144Str(f"Download the track {self.name}!"), ) diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py index d081332f..4e978769 100644 --- a/lnbits/extensions/livestream/tasks.py +++ b/lnbits/extensions/livestream/tasks.py @@ -22,6 +22,9 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: + if not payment.extra: + return + if payment.extra.get("tag") != "livestream": # not a livestream invoice return diff --git a/lnbits/extensions/livestream/views.py b/lnbits/extensions/livestream/views.py index 97f803a3..ca12f16b 100644 --- a/lnbits/extensions/livestream/views.py +++ b/lnbits/extensions/livestream/views.py @@ -1,20 +1,16 @@ from http import HTTPStatus -from fastapi.param_functions import Depends -from fastapi.params import Query -from starlette.exceptions import HTTPException -from starlette.requests import Request +from fastapi import Depends, HTTPException, Query, Request +from starlette.datastructures import URL from starlette.responses import HTMLResponse, RedirectResponse from lnbits.core.crud import get_wallet_payment -from lnbits.core.models import Payment, User +from lnbits.core.models import User from lnbits.decorators import check_user_exists from . import livestream_ext, livestream_renderer from .crud import get_livestream_by_track, get_track -# from mmap import MAP_DENYWRITE - @livestream_ext.get("/", response_class=HTMLResponse) async def index(request: Request, user: User = Depends(check_user_exists)): @@ -28,12 +24,18 @@ async def track_redirect_download(track_id, p: str = Query(...)): payment_hash = p track = await get_track(track_id) ls = await get_livestream_by_track(track_id) - payment: Payment = await get_wallet_payment(ls.wallet, payment_hash) + assert ls + payment = await get_wallet_payment(ls.wallet, payment_hash) if not payment: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, - detail=f"Couldn't find the payment {payment_hash} or track {track.id}.", + detail=f"Couldn't find the payment {payment_hash}.", + ) + if not track: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail=f"Couldn't find the track {track_id}.", ) if payment.pending: @@ -41,4 +43,6 @@ async def track_redirect_download(track_id, p: str = Query(...)): status_code=HTTPStatus.PAYMENT_REQUIRED, detail=f"Payment {payment_hash} wasn't received yet. Please try again in a minute.", ) - return RedirectResponse(url=track.download_url) + + assert track.download_url + return RedirectResponse(url=URL(track.download_url)) diff --git a/lnbits/extensions/livestream/views_api.py b/lnbits/extensions/livestream/views_api.py index 0c169a71..63a01742 100644 --- a/lnbits/extensions/livestream/views_api.py +++ b/lnbits/extensions/livestream/views_api.py @@ -1,9 +1,7 @@ from http import HTTPStatus -from fastapi.param_functions import Depends +from fastapi import Depends, HTTPException, Request from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl -from starlette.exceptions import HTTPException -from starlette.requests import Request # type: ignore from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.extensions.livestream.models import CreateTrack @@ -27,6 +25,7 @@ async def api_livestream_from_wallet( req: Request, g: WalletTypeInfo = Depends(get_key_type) ): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls tracks = await get_tracks(ls.id) producers = await get_producers(ls.id) @@ -55,17 +54,17 @@ async def api_update_track(track_id, g: WalletTypeInfo = Depends(get_key_type)): id = int(track_id) except ValueError: id = 0 - if id <= 0: - id = None ls = await get_or_create_livestream_by_wallet(g.wallet.id) - await update_current_track(ls.id, id) + assert ls + await update_current_track(ls.id, None if id <= 0 else id) return "", HTTPStatus.NO_CONTENT @livestream_ext.put("/api/v1/livestream/fee/{fee_pct}") async def api_update_fee(fee_pct, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls await update_livestream_fee(ls.id, int(fee_pct)) return "", HTTPStatus.NO_CONTENT @@ -76,9 +75,10 @@ async def api_add_track( data: CreateTrack, id=None, g: WalletTypeInfo = Depends(get_key_type) ): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls if data.producer_id: - p_id = data.producer_id + p_id = int(data.producer_id) elif data.producer_name: p_id = await add_producer(ls.id, data.producer_name) else: @@ -96,5 +96,6 @@ async def api_add_track( @livestream_ext.delete("/api/v1/livestream/tracks/{track_id}") async def api_delete_track(track_id, g: WalletTypeInfo = Depends(get_key_type)): ls = await get_or_create_livestream_by_wallet(g.wallet.id) + assert ls await delete_track_from_livestream(ls.id, track_id) return "", HTTPStatus.NO_CONTENT diff --git a/pyproject.toml b/pyproject.toml index e2116ed0..d251c91e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,6 @@ exclude = """(?x)( ^lnbits/extensions/bleskomat. | ^lnbits/extensions/boltz. | ^lnbits/extensions/boltcards. - | ^lnbits/extensions/livestream. | ^lnbits/extensions/lnaddress. | ^lnbits/extensions/lnurldevice. | ^lnbits/extensions/satspay. From 93d6d1279e8092f4b9c769a736ce4dede402f3c5 Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Thu, 5 Jan 2023 11:46:01 +0100 Subject: [PATCH 18/84] adding deezy token --- lnbits/extensions/deezy/__init__.py | 1 + lnbits/extensions/deezy/crud.py | 38 +++++ lnbits/extensions/deezy/migrations.py | 8 + lnbits/extensions/deezy/models.py | 9 +- .../deezy/templates/deezy/index.html | 151 +++++++++++++++++- lnbits/extensions/deezy/views_api.py | 31 ++++ 6 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 lnbits/extensions/deezy/crud.py create mode 100644 lnbits/extensions/deezy/views_api.py diff --git a/lnbits/extensions/deezy/__init__.py b/lnbits/extensions/deezy/__init__.py index 97f6d9ef..05d1c9a7 100644 --- a/lnbits/extensions/deezy/__init__.py +++ b/lnbits/extensions/deezy/__init__.py @@ -22,3 +22,4 @@ def deezy_renderer(): from .views import * # noqa +from .views_api import * # noqa diff --git a/lnbits/extensions/deezy/crud.py b/lnbits/extensions/deezy/crud.py new file mode 100644 index 00000000..69630610 --- /dev/null +++ b/lnbits/extensions/deezy/crud.py @@ -0,0 +1,38 @@ +from http import HTTPStatus +from typing import List + +from . import db +from .models import ( + Token, +) + +""" +Get Deezy Token +""" + + +async def get_token() -> Token: + + row = await db.fetchone( + f"SELECT * FROM deezy.token ORDER BY created_at DESC", + ) + + return Token(**row) if row else None + + +async def save_token( + data: Token, +) -> Token: + + await db.execute( + """ + INSERT INTO deezy.token ( + deezy_token + ) + VALUES (?) + """, + ( + data.deezy_token, + ), + ) + return data diff --git a/lnbits/extensions/deezy/migrations.py b/lnbits/extensions/deezy/migrations.py index 86edcf09..67455d6b 100644 --- a/lnbits/extensions/deezy/migrations.py +++ b/lnbits/extensions/deezy/migrations.py @@ -27,3 +27,11 @@ async def m001_initial(db): ); """ ) + await db.execute( + f""" + CREATE TABLE deezy.token ( + deezy_token TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """ + ) diff --git a/lnbits/extensions/deezy/models.py b/lnbits/extensions/deezy/models.py index bfeb7517..751191f0 100644 --- a/lnbits/extensions/deezy/models.py +++ b/lnbits/extensions/deezy/models.py @@ -1,5 +1,6 @@ -# from pydantic import BaseModel +from pydantic.main import BaseModel +from sqlalchemy.engine import base # type: ignore -# class Example(BaseModel): -# id: str -# wallet: str + +class Token(BaseModel): + deezy_token: str diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index 98067fd7..5c2f9ae4 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -5,6 +5,30 @@
Deezy
+

Due to regulatory reasons you need to get a access token from deezy. Contact - support@deezy.io or @dannydeezy on telegram

+
+
+ Deezy token + Add or Update token +
+

+
+ + + + @@ -189,6 +213,14 @@ +
+ +
{% endblock %} {% block scripts %} {{ window_vars(user) }} -{% endblock %} +{% endblock %} \ No newline at end of file From af472ba8330ef28ff2174a25ed648b156c949e7c Mon Sep 17 00:00:00 2001 From: Uthpala Heenatigala Date: Fri, 6 Jan 2023 20:34:31 +0100 Subject: [PATCH 24/84] fix formatting --- .../deezy/templates/deezy/index.html | 182 ++++++++++++++---- 1 file changed, 145 insertions(+), 37 deletions(-) diff --git a/lnbits/extensions/deezy/templates/deezy/index.html b/lnbits/extensions/deezy/templates/deezy/index.html index b1e06362..858d3255 100644 --- a/lnbits/extensions/deezy/templates/deezy/index.html +++ b/lnbits/extensions/deezy/templates/deezy/index.html @@ -6,50 +6,105 @@
Deezy

- An access token is required to use the swap service. - Email support@deezy.io or contact @dannydeezy on telegram to get one. + An access token is required to use the swap service. Email + support@deezy.io or contact @dannydeezy on telegram to get one.

Deezy token - Add or Update token + Add or Update token

- - + + - + Send lightning btc and receive on-chain btc - + Send on-chain btc and receive via lightning -
+
LIGHTNING BTC -> BTC
- - - + + + - - Cancel + + Cancel @@ -62,22 +117,52 @@
- + - - + +
-
+
BTC -> LIGHTNING BTC
- - - Cancel + + + Cancel @@ -90,15 +175,28 @@
- + - + - + @@ -135,10 +233,20 @@
- +
- +
{% endblock %} {% block scripts %} {{ window_vars(user) }} @@ -176,8 +284,8 @@ align: 'left', field: 'fee_sats' }, - { name: 'txid', label: 'Tx Id', align: 'left', field: 'txid' }, - { name: 'tx_hex', label: 'Tx Hex', align: 'left', field: 'tx_hex' }, + {name: 'txid', label: 'Tx Id', align: 'left', field: 'txid'}, + {name: 'tx_hex', label: 'Tx Hex', align: 'left', field: 'tx_hex'}, { name: 'created_at', label: 'Created at', @@ -477,4 +585,4 @@ } }) -{% endblock %} \ No newline at end of file +{% endblock %} From ef456c66d7deba6d9659306b0232997566cd348f Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 7 Jan 2023 21:59:10 +0000 Subject: [PATCH 25/84] Format --- lnbits/extensions/deezy/crud.py | 11 ++--------- lnbits/extensions/deezy/models.py | 1 + lnbits/extensions/deezy/views_api.py | 16 +++++----------- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lnbits/extensions/deezy/crud.py b/lnbits/extensions/deezy/crud.py index d3b3f935..8df50eb6 100644 --- a/lnbits/extensions/deezy/crud.py +++ b/lnbits/extensions/deezy/crud.py @@ -2,12 +2,7 @@ from http import HTTPStatus from typing import List from . import db -from .models import ( - Token, - LnToBtcSwap, - BtcToLnSwap, - UpdateLnToBtcSwap -) +from .models import BtcToLnSwap, LnToBtcSwap, Token, UpdateLnToBtcSwap async def get_ln_to_btc() -> List[LnToBtcSwap]: @@ -48,9 +43,7 @@ async def save_token( ) VALUES (?) """, - ( - data.deezy_token, - ), + (data.deezy_token,), ) return data diff --git a/lnbits/extensions/deezy/models.py b/lnbits/extensions/deezy/models.py index 0fcc3ce8..e69db355 100644 --- a/lnbits/extensions/deezy/models.py +++ b/lnbits/extensions/deezy/models.py @@ -1,4 +1,5 @@ from typing import Optional + from pydantic.main import BaseModel from sqlalchemy.engine import base # type: ignore diff --git a/lnbits/extensions/deezy/views_api.py b/lnbits/extensions/deezy/views_api.py index 85741b43..6af3bd72 100644 --- a/lnbits/extensions/deezy/views_api.py +++ b/lnbits/extensions/deezy/views_api.py @@ -7,22 +7,16 @@ # response.is_error that is its inverse) from . import deezy_ext -from .models import ( - Token, - LnToBtcSwap, - BtcToLnSwap, - UpdateLnToBtcSwap, -) - from .crud import ( - get_token, - get_ln_to_btc, get_btc_to_ln, - save_token, + get_ln_to_btc, + get_token, save_btc_to_ln, save_ln_to_btc, - update_ln_to_btc + save_token, + update_ln_to_btc, ) +from .models import BtcToLnSwap, LnToBtcSwap, Token, UpdateLnToBtcSwap @deezy_ext.get("/api/v1/token") From 7f1a08e16ac0d75aa03e7cfaa9b9c2349e2acb6f Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 7 Jan 2023 22:06:35 +0000 Subject: [PATCH 26/84] mypy --- lnbits/extensions/deezy/crud.py | 4 +++- lnbits/extensions/deezy/views_api.py | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/deezy/crud.py b/lnbits/extensions/deezy/crud.py index 8df50eb6..23eca774 100644 --- a/lnbits/extensions/deezy/crud.py +++ b/lnbits/extensions/deezy/crud.py @@ -23,7 +23,7 @@ async def get_btc_to_ln() -> List[BtcToLnSwap]: return [BtcToLnSwap(**row) for row in rows] -async def get_token() -> Token: +async def get_token() -> Optional[Token]: row = await db.fetchone( f"SELECT * FROM deezy.token ORDER BY created_at DESC", @@ -75,6 +75,7 @@ async def save_ln_to_btc( data.tx_hex, ), ) + return async def update_ln_to_btc(data: UpdateLnToBtcSwap) -> str: @@ -113,3 +114,4 @@ async def save_btc_to_ln( data.signature, ), ) + return diff --git a/lnbits/extensions/deezy/views_api.py b/lnbits/extensions/deezy/views_api.py index 6af3bd72..1006edeb 100644 --- a/lnbits/extensions/deezy/views_api.py +++ b/lnbits/extensions/deezy/views_api.py @@ -20,46 +20,46 @@ from .models import BtcToLnSwap, LnToBtcSwap, Token, UpdateLnToBtcSwap @deezy_ext.get("/api/v1/token") -async def api_deezy(): +async def api_deezy_get_token(): rows = await get_token() return rows @deezy_ext.get("/api/v1/ln-to-btc") -async def api_deezy(): +async def api_deezy_get_ln_to_btc(): rows = await get_ln_to_btc() return rows @deezy_ext.get("/api/v1/btc-to-ln") -async def api_deezy(): +async def api_deezy_get_btc_to_ln(): rows = await get_btc_to_ln() return rows @deezy_ext.post("/api/v1/store-token") -async def api_deezy(data: Token): +async def api_deezy_save_toke(data: Token): await save_token(data) return data.deezy_token @deezy_ext.post("/api/v1/store-ln-to-btc") -async def api_deezy(data: LnToBtcSwap): +async def api_deezy_save_ln_to_btc(data: LnToBtcSwap): response = await save_ln_to_btc(data) return response @deezy_ext.post("/api/v1/update-ln-to-btc") -async def api_deezy(data: UpdateLnToBtcSwap): +async def api_deezy_update_ln_to_btc(data: UpdateLnToBtcSwap): response = await update_ln_to_btc(data) return response @deezy_ext.post("/api/v1/store-btc-to-ln") -async def api_deezy(data: BtcToLnSwap): +async def api_deezy_save_btc_to_ln(data: BtcToLnSwap): response = await save_btc_to_ln(data) return response From ccdb6d76bdf12e53dd55f29ee3ede44a373c1741 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 7 Jan 2023 22:11:17 +0000 Subject: [PATCH 27/84] added Optional --- lnbits/extensions/deezy/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/deezy/crud.py b/lnbits/extensions/deezy/crud.py index 23eca774..c2768e25 100644 --- a/lnbits/extensions/deezy/crud.py +++ b/lnbits/extensions/deezy/crud.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import List +from typing import List, Optional from . import db from .models import BtcToLnSwap, LnToBtcSwap, Token, UpdateLnToBtcSwap From dfd86ed2e05703b553b5cbd6291849bec6a89864 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 7 Jan 2023 22:16:36 +0000 Subject: [PATCH 28/84] mypy --- lnbits/extensions/deezy/crud.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/deezy/crud.py b/lnbits/extensions/deezy/crud.py index c2768e25..75549349 100644 --- a/lnbits/extensions/deezy/crud.py +++ b/lnbits/extensions/deezy/crud.py @@ -52,7 +52,7 @@ async def save_ln_to_btc( data: LnToBtcSwap, ) -> LnToBtcSwap: - await db.execute( + return await db.execute( """ INSERT INTO deezy.ln_to_btc_swap ( amount_sats, @@ -75,7 +75,6 @@ async def save_ln_to_btc( data.tx_hex, ), ) - return async def update_ln_to_btc(data: UpdateLnToBtcSwap) -> str: @@ -95,7 +94,7 @@ async def save_btc_to_ln( data: BtcToLnSwap, ) -> BtcToLnSwap: - await db.execute( + return await db.execute( """ INSERT INTO deezy.btc_to_ln_swap ( ln_address, @@ -114,4 +113,3 @@ async def save_btc_to_ln( data.signature, ), ) - return From d5ead2543c4c583910443bf41c62e4259b770add Mon Sep 17 00:00:00 2001 From: Bitkarrot <73979971+bitkarrot@users.noreply.github.com> Date: Sat, 7 Jan 2023 16:15:08 -0800 Subject: [PATCH 29/84] add poetry <1.2 version note to troubleshooting --- docs/guide/installation.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 9f8b26da..460f7d00 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -206,6 +206,11 @@ poetry add setuptools wheel ./venv/bin/pip install setuptools wheel ``` +### poetry + +If your poetry version is less than ^1.2, for `poetry install`, ignore the -- flags, it will install just fine. + + ### Optional: PostgreSQL database If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and setup a database for LNbits: From 2577b3208b451308564f5b363cf1cb8c1cd857f2 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 8 Jan 2023 12:16:39 +0100 Subject: [PATCH 30/84] clean up readme --- README.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3bc169dd..0f81b99f 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,29 @@ LNbits ![Lightning network wallet](https://i.imgur.com/EHvK6Lq.png) -# LNbits v0.9 BETA, free and open-source lightning-network wallet/accounts system +# LNbits v0.9 BETA, free and open-source Lightning wallet accounts system (Join us on [https://t.me/lnbits](https://t.me/lnbits)) -(LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me) +LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me Use [legend.lnbits.com](https://legend.lnbits.com), or run your own LNbits server! -LNbits is a very simple Python server that sits on top of any funding source, and can be used as: +LNbits is a Python server that sits on top of any funding source. It can be used as: -* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet -* Extendable platform for exploring lightning-network functionality via LNbits extension framework +* Accounts system to mitigate the risk of exposing applications to your full balance via unique API keys for each wallet +* Extendable platform for exploring Lightning network functionality via the LNbits extension framework * Part of a development stack via LNbits API * Fallback wallet for the LNURL scheme * Instant wallet for LN demonstrations -LNbits can run on top of any lightning-network funding source, currently there is support for LND, c-lightning, Spark, LNpay, OpenNode, lntxbot, with more being added regularly. +LNbits can run on top of any Lightning funding source. It supports LND, CLN, Eclair, Spark, LNpay, OpenNode, lntxbot, LightningTipBot, and with more being added regularly. See [docs.lnbits.org](https://docs.lnbits.org) for more detailed documentation. Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series. -LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as excellent funding sources for LNbits. +LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as funding sources for LNbits. ## Running LNbits @@ -58,16 +58,15 @@ Example use would be an ATM, which utilises LNURL, if the user scans the QR with ![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg) -## LNbits as an insta-wallet +## LNbits as an instant wallet -Wallets can be easily generated and given out to people at events (one click multi-wallet generation to be added soon). -"Go to this website", has a lot less friction than "Download this app". +Wallets can be easily generated and given out to people at events. "Go to this website", has a lot less friction than "Download this app". ![lnurl ATM](https://i.imgur.com/xFWDnwy.png) ## Tip us -If you like this project and might even use or extend it, why not [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)! +If you like this project [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)! [docs]: https://docs.lnbits.org/ From aa067df380e4311b05ae2c82421bbaf71340f20e Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Sun, 8 Jan 2023 12:19:58 +0000 Subject: [PATCH 31/84] remove extraneous not payment.extra check followup to fca7e7bec3a40de8a5f7f65ac6030513c02e8db7 --- lnbits/extensions/tpos/tasks.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index 80ee1085..4b7bd9f9 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -20,9 +20,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra: - return - if payment.extra.get("tag") != "tpos": return From 67db763ec9f2f2e80be560903a69ae95e1a05e08 Mon Sep 17 00:00:00 2001 From: Joel Klabo Date: Sun, 8 Jan 2023 14:23:18 -0800 Subject: [PATCH 32/84] Example Caddy Configuration for NIP-05 Extension --- lnbits/extensions/nostrnip5/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnbits/extensions/nostrnip5/README.md b/lnbits/extensions/nostrnip5/README.md index b8912fa2..2bcbf054 100644 --- a/lnbits/extensions/nostrnip5/README.md +++ b/lnbits/extensions/nostrnip5/README.md @@ -41,4 +41,19 @@ location /.well-known/nostr.json { proxy_cache_valid 200 300s; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; } +``` + +Example Caddy configuration + +``` +my.lnbits.instance { + reverse_proxy {your_lnbits} +} + +nip.5.domain { + route /.well-known/nostr.json { + rewrite * /nostrnip5/api/v1/domain/{domain_id}/nostr.json + reverse_proxy {your_lnbits} + } +} ``` \ No newline at end of file From 49d286e7ea9ec5901f5b916f9783caeed2e869fd Mon Sep 17 00:00:00 2001 From: Joel Klabo Date: Sun, 8 Jan 2023 21:31:41 -0800 Subject: [PATCH 33/84] Add Satoshis as Currency Option Nostr NIP-5 --- lnbits/extensions/nostrnip5/templates/nostrnip5/index.html | 3 ++- lnbits/extensions/nostrnip5/views_api.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html index b1459ee3..820d8718 100644 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html +++ b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html @@ -201,7 +201,7 @@ dense v-model.trim="formDialog.data.amount" label="Amount" - placeholder="10.00" + placeholder="How much do you want to charge?" > Date: Mon, 9 Jan 2023 09:41:19 +0100 Subject: [PATCH 34/84] copy initial copy from my fork --- lnbits/extensions/smtp/README.md | 26 + lnbits/extensions/smtp/__init__.py | 25 + lnbits/extensions/smtp/config.json | 6 + lnbits/extensions/smtp/crud.py | 170 ++++++ lnbits/extensions/smtp/migrations.py | 35 ++ lnbits/extensions/smtp/models.py | 47 ++ lnbits/extensions/smtp/smtp.py | 90 +++ lnbits/extensions/smtp/tasks.py | 46 ++ .../smtp/templates/smtp/_api_docs.html | 23 + .../smtp/templates/smtp/display.html | 185 ++++++ .../extensions/smtp/templates/smtp/index.html | 528 ++++++++++++++++++ lnbits/extensions/smtp/views.py | 44 ++ lnbits/extensions/smtp/views_api.py | 175 ++++++ 13 files changed, 1400 insertions(+) create mode 100644 lnbits/extensions/smtp/README.md create mode 100644 lnbits/extensions/smtp/__init__.py create mode 100644 lnbits/extensions/smtp/config.json create mode 100644 lnbits/extensions/smtp/crud.py create mode 100644 lnbits/extensions/smtp/migrations.py create mode 100644 lnbits/extensions/smtp/models.py create mode 100644 lnbits/extensions/smtp/smtp.py create mode 100644 lnbits/extensions/smtp/tasks.py create mode 100644 lnbits/extensions/smtp/templates/smtp/_api_docs.html create mode 100644 lnbits/extensions/smtp/templates/smtp/display.html create mode 100644 lnbits/extensions/smtp/templates/smtp/index.html create mode 100644 lnbits/extensions/smtp/views.py create mode 100644 lnbits/extensions/smtp/views_api.py diff --git a/lnbits/extensions/smtp/README.md b/lnbits/extensions/smtp/README.md new file mode 100644 index 00000000..339f210a --- /dev/null +++ b/lnbits/extensions/smtp/README.md @@ -0,0 +1,26 @@ +

SMTP Extension

+ +This extension allows you to setup a smtp, to offer sending emails with it for a small fee. + +## Requirements + +- SMTP Server + +## Usage + +1. Create new emailaddress +2. Verify if email goes to your testemail. Testmail is send on create and update +3. enjoy + +## API Endpoints + +- **Emailaddresses** + - GET /api/v1/emailaddress + - POST /api/v1/emailaddress + - PUT /api/v1/emailaddress/ + - DELETE /api/v1/emailaddress/ +- **Emails** + - GET /api/v1/email + - POST /api/v1/email/ + - GET /api/v1/email/ + - DELETE /api/v1/email/ diff --git a/lnbits/extensions/smtp/__init__.py b/lnbits/extensions/smtp/__init__.py new file mode 100644 index 00000000..1d951b31 --- /dev/null +++ b/lnbits/extensions/smtp/__init__.py @@ -0,0 +1,25 @@ +import asyncio + +from fastapi import APIRouter + +from lnbits.db import Database +from lnbits.helpers import template_renderer +from lnbits.tasks import catch_everything_and_restart + +db = Database("ext_smtp") + +smtp_ext: APIRouter = APIRouter(prefix="/smtp", tags=["smtp"]) + + +def smtp_renderer(): + return template_renderer(["lnbits/extensions/smtp/templates"]) + + +from .tasks import wait_for_paid_invoices +from .views import * # noqa +from .views_api import * # noqa + + +def smtp_start(): + loop = asyncio.get_event_loop() + loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/smtp/config.json b/lnbits/extensions/smtp/config.json new file mode 100644 index 00000000..8b2cb764 --- /dev/null +++ b/lnbits/extensions/smtp/config.json @@ -0,0 +1,6 @@ +{ + "name": "SMTP", + "short_description": "Let users send emails via your SMTP and earn sats", + "icon": "email", + "contributors": ["dni"] +} diff --git a/lnbits/extensions/smtp/crud.py b/lnbits/extensions/smtp/crud.py new file mode 100644 index 00000000..e5ab1d1f --- /dev/null +++ b/lnbits/extensions/smtp/crud.py @@ -0,0 +1,170 @@ +from http import HTTPStatus +from typing import List, Optional, Union + +from starlette.exceptions import HTTPException + +from lnbits.helpers import urlsafe_short_hash + +from . import db +from .models import CreateEmail, CreateEmailaddress, Emailaddresses, Emails +from .smtp import send_mail + + +def get_test_mail(email, testemail): + return CreateEmail( + emailaddress_id=email, + subject="LNBits SMTP - Test Email", + message="This is a test email from the LNBits SMTP extension! email is working!", + receiver=testemail, + ) + + +async def create_emailaddress(data: CreateEmailaddress) -> Emailaddresses: + + emailaddress_id = urlsafe_short_hash() + + # send test mail for checking connection + email = get_test_mail(data.email, data.testemail) + await send_mail(data, email) + + await db.execute( + """ + INSERT INTO smtp.emailaddress (id, wallet, email, testemail, smtp_server, smtp_user, smtp_password, smtp_port, anonymize, description, cost) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + emailaddress_id, + data.wallet, + data.email, + data.testemail, + data.smtp_server, + data.smtp_user, + data.smtp_password, + data.smtp_port, + data.anonymize, + data.description, + data.cost, + ), + ) + + new_emailaddress = await get_emailaddress(emailaddress_id) + assert new_emailaddress, "Newly created emailaddress couldn't be retrieved" + return new_emailaddress + + +async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddresses: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE smtp.emailaddress SET {q} WHERE id = ?", + (*kwargs.values(), emailaddress_id), + ) + row = await db.fetchone( + "SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,) + ) + + # send test mail for checking connection + email = get_test_mail(row.email, row.testemail) + await send_mail(row, email) + + assert row, "Newly updated emailaddress couldn't be retrieved" + return Emailaddresses(**row) + + +async def get_emailaddress(emailaddress_id: str) -> Optional[Emailaddresses]: + row = await db.fetchone( + "SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,) + ) + return Emailaddresses(**row) if row else None + + +async def get_emailaddress_by_email(email: str) -> Optional[Emailaddresses]: + row = await db.fetchone("SELECT * FROM smtp.emailaddress WHERE email = ?", (email,)) + return Emailaddresses(**row) if row else None + + +# async def get_emailAddressByEmail(email: str) -> Optional[Emails]: +# row = await db.fetchone( +# "SELECT s.*, d.emailaddress as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.emailaddress = ?", +# (email,), +# ) +# return Subdomains(**row) if row else None + + +async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailaddresses]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM smtp.emailaddress WHERE wallet IN ({q})", (*wallet_ids,) + ) + + return [Emailaddresses(**row) for row in rows] + + +async def delete_emailaddress(emailaddress_id: str) -> None: + await db.execute("DELETE FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,)) + + +## create emails +async def create_email(payment_hash, wallet, data: CreateEmail) -> Emails: + await db.execute( + """ + INSERT INTO smtp.email (id, wallet, emailaddress_id, subject, receiver, message, paid) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + payment_hash, + wallet, + data.emailaddress_id, + data.subject, + data.receiver, + data.message, + False, + ), + ) + + new_email = await get_email(payment_hash) + assert new_email, "Newly created email couldn't be retrieved" + return new_email + + +async def set_email_paid(payment_hash: str) -> Emails: + email = await get_email(payment_hash) + if email and email.paid == False: + await db.execute( + """ + UPDATE smtp.email + SET paid = true + WHERE id = ? + """, + (payment_hash,), + ) + new_email = await get_email(payment_hash) + assert new_email, "Newly paid email couldn't be retrieved" + return new_email + + +async def get_email(email_id: str) -> Optional[Emails]: + row = await db.fetchone( + "SELECT s.*, d.email as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.id = ?", + (email_id,), + ) + return Emails(**row) if row else None + + +async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Emails]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT s.*, d.email as emailaddress FROM smtp.email s INNER JOIN smtp.emailaddress d ON (s.emailaddress_id = d.id) WHERE s.wallet IN ({q})", + (*wallet_ids,), + ) + + return [Emails(**row) for row in rows] + + +async def delete_email(email_id: str) -> None: + await db.execute("DELETE FROM smtp.email WHERE id = ?", (email_id,)) diff --git a/lnbits/extensions/smtp/migrations.py b/lnbits/extensions/smtp/migrations.py new file mode 100644 index 00000000..16d50166 --- /dev/null +++ b/lnbits/extensions/smtp/migrations.py @@ -0,0 +1,35 @@ +async def m001_initial(db): + + await db.execute( + f""" + CREATE TABLE smtp.emailaddress ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + email TEXT NOT NULL, + testemail TEXT NOT NULL, + smtp_server TEXT NOT NULL, + smtp_user TEXT NOT NULL, + smtp_password TEXT NOT NULL, + smtp_port TEXT NOT NULL, + anonymize BOOLEAN NOT NULL, + description TEXT NOT NULL, + cost INTEGER NOT NULL, + time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} + ); + """ + ) + + await db.execute( + f""" + CREATE TABLE smtp.email ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + emailaddress_id TEXT NOT NULL, + subject TEXT NOT NULL, + receiver TEXT NOT NULL, + message TEXT NOT NULL, + paid BOOLEAN NOT NULL, + time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now} + ); + """ + ) diff --git a/lnbits/extensions/smtp/models.py b/lnbits/extensions/smtp/models.py new file mode 100644 index 00000000..0b3138e9 --- /dev/null +++ b/lnbits/extensions/smtp/models.py @@ -0,0 +1,47 @@ +from fastapi.params import Query +from pydantic.main import BaseModel + + +class CreateEmailaddress(BaseModel): + wallet: str = Query(...) # type: ignore + email: str = Query(...) # type: ignore + testemail: str = Query(...) # type: ignore + smtp_server: str = Query(...) # type: ignore + smtp_user: str = Query(...) # type: ignore + smtp_password: str = Query(...) # type: ignore + smtp_port: str = Query(...) # type: ignore + description: str = Query(...) # type: ignore + anonymize: bool + cost: int = Query(..., ge=0) # type: ignore + + +class Emailaddresses(BaseModel): + id: str + wallet: str + email: str + testemail: str + smtp_server: str + smtp_user: str + smtp_password: str + smtp_port: str + anonymize: bool + description: str + cost: int + + +class CreateEmail(BaseModel): + emailaddress_id: str = Query(...) # type: ignore + subject: str = Query(...) # type: ignore + receiver: str = Query(...) # type: ignore + message: str = Query(...) # type: ignore + + +class Emails(BaseModel): + id: str + wallet: str + emailaddress_id: str + subject: str + receiver: str + message: str + paid: bool + time: int diff --git a/lnbits/extensions/smtp/smtp.py b/lnbits/extensions/smtp/smtp.py new file mode 100644 index 00000000..b9a2dce3 --- /dev/null +++ b/lnbits/extensions/smtp/smtp.py @@ -0,0 +1,90 @@ +import os +import re +import socket +import sys +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from http import HTTPStatus +from smtplib import SMTP_SSL as SMTP + +from loguru import logger +from starlette.exceptions import HTTPException + + +def valid_email(s): + # https://regexr.com/2rhq7 + pat = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" + if re.match(pat, s): + return True + msg = f"SMTP - invalid email: {s}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + + +async def send_mail(emailaddress, email): + valid_email(emailaddress.email) + valid_email(email.receiver) + + msg = MIMEMultipart("alternative") + msg["Subject"] = email.subject + msg["From"] = emailaddress.email + msg["To"] = email.receiver + + signature = "Email sent anonymiously by LNbits Sendmail extension." + text = ( + """\ + """ + + email.message + + """ + """ + + signature + + """ + """ + ) + + html = ( + """\ + + + +

""" + + email.message + + """

+


""" + + signature + + """

+ + + """ + ) + part1 = MIMEText(text, "plain") + part2 = MIMEText(html, "html") + msg.attach(part1) + msg.attach(part2) + + try: + conn = SMTP( + host=emailaddress.smtp_server, port=emailaddress.smtp_port, timeout=10 + ) + logger.debug("SMTP - connected to smtp server.") + # conn.set_debuglevel(True) + except: + msg = f"SMTP - error connecting to smtp server: {emailaddress.smtp_server}:{emailaddress.smtp_port}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + try: + conn.login(emailaddress.smtp_user, emailaddress.smtp_password) + logger.debug("SMTP - successful login to smtp server.") + except: + msg = f"SMTP - error login into smtp {emailaddress.smtp_user}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + try: + conn.sendmail(emailaddress.email, email.receiver, msg.as_string()) + logger.debug("SMTP - successfully send email.") + except socket.error as e: + msg = f"SMTP - error sending email: {str(e)}." + logger.error(msg) + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg) + finally: + conn.quit() diff --git a/lnbits/extensions/smtp/tasks.py b/lnbits/extensions/smtp/tasks.py new file mode 100644 index 00000000..ed569dae --- /dev/null +++ b/lnbits/extensions/smtp/tasks.py @@ -0,0 +1,46 @@ +import asyncio +from http import HTTPStatus + +import httpx +from loguru import logger +from starlette.exceptions import HTTPException + +from lnbits.core.models import Payment +from lnbits.tasks import register_invoice_listener + +from .crud import ( + delete_email, + get_email, + get_emailaddress, + get_emailaddress_by_email, + set_email_paid, +) +from .smtp import send_mail + + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue) + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + if not payment.extra or "smtp" != payment.extra.get("tag"): + # not an lnurlp invoice + return + + email = await get_email(payment.checking_id) + if not email: + logger.error("SMTP: email can not by fetched") + return + + emailaddress = await get_emailaddress(email.emailaddress_id) + if not emailaddress: + logger.error("SMTP: emailaddress can not by fetched") + return + + await payment.set_pending(False) + await send_mail(emailaddress, email) + await set_email_paid(payment_hash=payment.payment_hash) diff --git a/lnbits/extensions/smtp/templates/smtp/_api_docs.html b/lnbits/extensions/smtp/templates/smtp/_api_docs.html new file mode 100644 index 00000000..c7ed44de --- /dev/null +++ b/lnbits/extensions/smtp/templates/smtp/_api_docs.html @@ -0,0 +1,23 @@ + + + +
+ LNBits SMTP: Get paid sats to send emails +
+

+ Charge people for using sending an email via your smtp server
+ More details +
+ Created by, dni +

+
+
+
diff --git a/lnbits/extensions/smtp/templates/smtp/display.html b/lnbits/extensions/smtp/templates/smtp/display.html new file mode 100644 index 00000000..7db4a0d6 --- /dev/null +++ b/lnbits/extensions/smtp/templates/smtp/display.html @@ -0,0 +1,185 @@ +{% extends "public.html" %} {% block page %} +
+
+ + +

{{ email }}

+
+
{{ desc }}
+
+ + + + +

Total cost: {{ cost }} sats

+
+ Submit + Cancel +
+
+
+
+
+ + + + + + +
+ Copy invoice + Close +
+
+
+
+ +{% endblock %} {% block scripts %} + +{% endblock %} diff --git a/lnbits/extensions/smtp/templates/smtp/index.html b/lnbits/extensions/smtp/templates/smtp/index.html new file mode 100644 index 00000000..bf43ad7f --- /dev/null +++ b/lnbits/extensions/smtp/templates/smtp/index.html @@ -0,0 +1,528 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + +
+
+ + + New Emailaddress + + + + + +
+
+
Emailaddresses
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Emails
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+
+
+ + +
+ {{SITE_TITLE}} Sendmail extension +
+
+ + + {% include "smtp/_api_docs.html" %} + +
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ Update Form + Create Emailaddress + Cancel +
+
+
+
+
+ +{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/smtp/views.py b/lnbits/extensions/smtp/views.py new file mode 100644 index 00000000..1ba53341 --- /dev/null +++ b/lnbits/extensions/smtp/views.py @@ -0,0 +1,44 @@ +from http import HTTPStatus + +from fastapi import Request +from fastapi.params import Depends +from fastapi.templating import Jinja2Templates +from starlette.exceptions import HTTPException +from starlette.responses import HTMLResponse + +from lnbits.core.models import User +from lnbits.decorators import check_user_exists + +from . import smtp_ext, smtp_renderer +from .crud import get_emailaddress + +templates = Jinja2Templates(directory="templates") + + +@smtp_ext.get("/", response_class=HTMLResponse) +async def index( + request: Request, user: User = Depends(check_user_exists) # type: ignore +): + return smtp_renderer().TemplateResponse( + "smtp/index.html", {"request": request, "user": user.dict()} + ) + + +@smtp_ext.get("/{emailaddress_id}") +async def display(request: Request, emailaddress_id): + emailaddress = await get_emailaddress(emailaddress_id) + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Emailaddress does not exist." + ) + + return smtp_renderer().TemplateResponse( + "smtp/display.html", + { + "request": request, + "emailaddress_id": emailaddress.id, + "email": emailaddress.email, + "desc": emailaddress.description, + "cost": emailaddress.cost, + }, + ) diff --git a/lnbits/extensions/smtp/views_api.py b/lnbits/extensions/smtp/views_api.py new file mode 100644 index 00000000..5001c1a5 --- /dev/null +++ b/lnbits/extensions/smtp/views_api.py @@ -0,0 +1,175 @@ +from http import HTTPStatus + +from fastapi import Query +from fastapi.params import Depends +from starlette.exceptions import HTTPException + +from lnbits.core.crud import get_user +from lnbits.core.services import check_transaction_status, create_invoice +from lnbits.decorators import WalletTypeInfo, get_key_type +from lnbits.extensions.smtp.models import CreateEmail, CreateEmailaddress + +from . import smtp_ext +from .crud import ( + create_email, + create_emailaddress, + delete_email, + delete_emailaddress, + get_email, + get_emailaddress, + get_emailaddress_by_email, + get_emailaddresses, + get_emails, + update_emailaddress, +) +from .smtp import send_mail, valid_email + + +## EMAILS +@smtp_ext.get("/api/v1/email") +async def api_email( + g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) # type: ignore +): + wallet_ids = [g.wallet.id] + if all_wallets: + user = await get_user(g.wallet.user) + if user: + wallet_ids = user.wallet_ids + return [email.dict() for email in await get_emails(wallet_ids)] + + +@smtp_ext.get("/api/v1/email/{payment_hash}") +async def api_smtp_send_email(payment_hash): + email = await get_email(payment_hash) + if not email: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="paymenthash is wrong" + ) + + emailaddress = await get_emailaddress(email.emailaddress_id) + + try: + status = await check_transaction_status(email.wallet, payment_hash) + is_paid = not status.pending + except Exception: + return {"paid": False} + if is_paid: + if emailaddress.anonymize: + await delete_email(email.id) + return {"paid": True} + return {"paid": False} + + +@smtp_ext.post("/api/v1/email/{emailaddress_id}") +async def api_smtp_make_email(emailaddress_id, data: CreateEmail): + + valid_email(data.receiver) + + emailaddress = await get_emailaddress(emailaddress_id) + # If the request is coming for the non-existant emailaddress + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Emailaddress address does not exist.", + ) + try: + memo = f"sent email from {emailaddress.email} to {data.receiver}" + if emailaddress.anonymize: + memo = "sent email" + + payment_hash, payment_request = await create_invoice( + wallet_id=emailaddress.wallet, + amount=emailaddress.cost, + memo=memo, + extra={"tag": "smtp"}, + ) + except Exception as e: + raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)) + + email = await create_email( + payment_hash=payment_hash, wallet=emailaddress.wallet, data=data + ) + + if not email: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Email could not be fetched." + ) + return {"payment_hash": payment_hash, "payment_request": payment_request} + + +@smtp_ext.delete("/api/v1/email/{email_id}") +async def api_email_delete( + email_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore +): + email = await get_email(email_id) + + if not email: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="LNsubdomain does not exist." + ) + + if email.wallet != g.wallet.id: + raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your email.") + + await delete_email(email_id) + raise HTTPException(status_code=HTTPStatus.NO_CONTENT) + + +## EMAILADDRESSES +@smtp_ext.get("/api/v1/emailaddress") +async def api_emailaddresses( + g: WalletTypeInfo = Depends(get_key_type), # type: ignore + all_wallets: bool = Query(False), # type: ignore +): + wallet_ids = [g.wallet.id] + if all_wallets: + user = await get_user(g.wallet.user) + if user: + wallet_ids = user.wallet_ids + return [ + emailaddress.dict() for emailaddress in await get_emailaddresses(wallet_ids) + ] + + +@smtp_ext.post("/api/v1/emailaddress") +@smtp_ext.put("/api/v1/emailaddress/{emailaddress_id}") +async def api_emailaddress_create( + data: CreateEmailaddress, + emailaddress_id=None, + g: WalletTypeInfo = Depends(get_key_type), # type: ignore +): + if emailaddress_id: + emailaddress = await get_emailaddress(emailaddress_id) + + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Emailadress does not exist." + ) + if emailaddress.wallet != g.wallet.id: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not your emailaddress." + ) + + emailaddress = await update_emailaddress(emailaddress_id, **data.dict()) + else: + emailaddress = await create_emailaddress(data=data) + return emailaddress.dict() + + +@smtp_ext.delete("/api/v1/emailaddress/{emailaddress_id}") +async def api_emailaddress_delete( + emailaddress_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore +): + emailaddress = await get_emailaddress(emailaddress_id) + + if not emailaddress: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Emailaddress does not exist." + ) + if emailaddress.wallet != g.wallet.id: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="Not your Emailaddress." + ) + + await delete_emailaddress(emailaddress_id) + raise HTTPException(status_code=HTTPStatus.NO_CONTENT) From e9f625f00832e7361fe71ace45b63657d1d9c75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 9 Jan 2023 09:54:17 +0100 Subject: [PATCH 35/84] mypy fixes, date issue --- lnbits/extensions/smtp/crud.py | 2 -- lnbits/extensions/smtp/models.py | 30 +++++++++++++-------------- lnbits/extensions/smtp/smtp.py | 9 ++++++-- lnbits/extensions/smtp/tasks.py | 14 ++----------- lnbits/extensions/smtp/views.py | 8 ++------ lnbits/extensions/smtp/views_api.py | 21 ++++++++----------- package-lock.json | 32 ----------------------------- 7 files changed, 34 insertions(+), 82 deletions(-) delete mode 100644 package-lock.json diff --git a/lnbits/extensions/smtp/crud.py b/lnbits/extensions/smtp/crud.py index e5ab1d1f..2eee4c3d 100644 --- a/lnbits/extensions/smtp/crud.py +++ b/lnbits/extensions/smtp/crud.py @@ -1,8 +1,6 @@ from http import HTTPStatus from typing import List, Optional, Union -from starlette.exceptions import HTTPException - from lnbits.helpers import urlsafe_short_hash from . import db diff --git a/lnbits/extensions/smtp/models.py b/lnbits/extensions/smtp/models.py index 0b3138e9..e2f3fc13 100644 --- a/lnbits/extensions/smtp/models.py +++ b/lnbits/extensions/smtp/models.py @@ -1,18 +1,18 @@ -from fastapi.params import Query -from pydantic.main import BaseModel +from fastapi import Query +from pydantic import BaseModel class CreateEmailaddress(BaseModel): - wallet: str = Query(...) # type: ignore - email: str = Query(...) # type: ignore - testemail: str = Query(...) # type: ignore - smtp_server: str = Query(...) # type: ignore - smtp_user: str = Query(...) # type: ignore - smtp_password: str = Query(...) # type: ignore - smtp_port: str = Query(...) # type: ignore - description: str = Query(...) # type: ignore + wallet: str = Query(...) + email: str = Query(...) + testemail: str = Query(...) + smtp_server: str = Query(...) + smtp_user: str = Query(...) + smtp_password: str = Query(...) + smtp_port: str = Query(...) + description: str = Query(...) anonymize: bool - cost: int = Query(..., ge=0) # type: ignore + cost: int = Query(..., ge=0) class Emailaddresses(BaseModel): @@ -30,10 +30,10 @@ class Emailaddresses(BaseModel): class CreateEmail(BaseModel): - emailaddress_id: str = Query(...) # type: ignore - subject: str = Query(...) # type: ignore - receiver: str = Query(...) # type: ignore - message: str = Query(...) # type: ignore + emailaddress_id: str = Query(...) + subject: str = Query(...) + receiver: str = Query(...) + message: str = Query(...) class Emails(BaseModel): diff --git a/lnbits/extensions/smtp/smtp.py b/lnbits/extensions/smtp/smtp.py index b9a2dce3..a8830254 100644 --- a/lnbits/extensions/smtp/smtp.py +++ b/lnbits/extensions/smtp/smtp.py @@ -1,9 +1,9 @@ -import os import re import socket -import sys +import time from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +from email.utils import formatdate from http import HTTPStatus from smtplib import SMTP_SSL as SMTP @@ -25,7 +25,12 @@ async def send_mail(emailaddress, email): valid_email(emailaddress.email) valid_email(email.receiver) + ts = time.time() + date = formatdate(ts, True) + msg = MIMEMultipart("alternative") + msg = MIMEMultipart("alternative") + msg["Date"] = date msg["Subject"] = email.subject msg["From"] = emailaddress.email msg["To"] = email.receiver diff --git a/lnbits/extensions/smtp/tasks.py b/lnbits/extensions/smtp/tasks.py index ed569dae..9c544473 100644 --- a/lnbits/extensions/smtp/tasks.py +++ b/lnbits/extensions/smtp/tasks.py @@ -1,20 +1,11 @@ import asyncio -from http import HTTPStatus -import httpx from loguru import logger -from starlette.exceptions import HTTPException from lnbits.core.models import Payment from lnbits.tasks import register_invoice_listener -from .crud import ( - delete_email, - get_email, - get_emailaddress, - get_emailaddress_by_email, - set_email_paid, -) +from .crud import get_email, get_emailaddress, set_email_paid from .smtp import send_mail @@ -27,8 +18,7 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if not payment.extra or "smtp" != payment.extra.get("tag"): - # not an lnurlp invoice + if payment.extra.get("tag") != "smtp": return email = await get_email(payment.checking_id) diff --git a/lnbits/extensions/smtp/views.py b/lnbits/extensions/smtp/views.py index 1ba53341..df208a77 100644 --- a/lnbits/extensions/smtp/views.py +++ b/lnbits/extensions/smtp/views.py @@ -1,9 +1,7 @@ from http import HTTPStatus -from fastapi import Request -from fastapi.params import Depends +from fastapi import Depends, HTTPException, Request from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse from lnbits.core.models import User @@ -16,9 +14,7 @@ templates = Jinja2Templates(directory="templates") @smtp_ext.get("/", response_class=HTMLResponse) -async def index( - request: Request, user: User = Depends(check_user_exists) # type: ignore -): +async def index(request: Request, user: User = Depends(check_user_exists)): return smtp_renderer().TemplateResponse( "smtp/index.html", {"request": request, "user": user.dict()} ) diff --git a/lnbits/extensions/smtp/views_api.py b/lnbits/extensions/smtp/views_api.py index 5001c1a5..08a05ef3 100644 --- a/lnbits/extensions/smtp/views_api.py +++ b/lnbits/extensions/smtp/views_api.py @@ -1,8 +1,6 @@ from http import HTTPStatus -from fastapi import Query -from fastapi.params import Depends -from starlette.exceptions import HTTPException +from fastapi import Depends, HTTPException, Query from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice @@ -17,18 +15,17 @@ from .crud import ( delete_emailaddress, get_email, get_emailaddress, - get_emailaddress_by_email, get_emailaddresses, get_emails, update_emailaddress, ) -from .smtp import send_mail, valid_email +from .smtp import valid_email ## EMAILS @smtp_ext.get("/api/v1/email") async def api_email( - g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) # type: ignore + g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) ): wallet_ids = [g.wallet.id] if all_wallets: @@ -98,9 +95,7 @@ async def api_smtp_make_email(emailaddress_id, data: CreateEmail): @smtp_ext.delete("/api/v1/email/{email_id}") -async def api_email_delete( - email_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore -): +async def api_email_delete(email_id, g: WalletTypeInfo = Depends(get_key_type)): email = await get_email(email_id) if not email: @@ -118,8 +113,8 @@ async def api_email_delete( ## EMAILADDRESSES @smtp_ext.get("/api/v1/emailaddress") async def api_emailaddresses( - g: WalletTypeInfo = Depends(get_key_type), # type: ignore - all_wallets: bool = Query(False), # type: ignore + g: WalletTypeInfo = Depends(get_key_type), + all_wallets: bool = Query(False), ): wallet_ids = [g.wallet.id] if all_wallets: @@ -136,7 +131,7 @@ async def api_emailaddresses( async def api_emailaddress_create( data: CreateEmailaddress, emailaddress_id=None, - g: WalletTypeInfo = Depends(get_key_type), # type: ignore + g: WalletTypeInfo = Depends(get_key_type), ): if emailaddress_id: emailaddress = await get_emailaddress(emailaddress_id) @@ -158,7 +153,7 @@ async def api_emailaddress_create( @smtp_ext.delete("/api/v1/emailaddress/{emailaddress_id}") async def api_emailaddress_delete( - emailaddress_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore + emailaddress_id, g: WalletTypeInfo = Depends(get_key_type) ): emailaddress = await get_emailaddress(emailaddress_id) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index f2ff24bd..00000000 --- a/package-lock.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "lnbits-legend", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "devDependencies": { - "prettier": "2.1.1" - } - }, - "node_modules/prettier": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", - "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - } - }, - "dependencies": { - "prettier": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", - "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", - "dev": true - } - } -} From 0c1eb13d93dfb4287623ae2542eb14411fb3ab2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 9 Jan 2023 10:13:54 +0100 Subject: [PATCH 36/84] fine tunings ;) --- lnbits/extensions/smtp/README.md | 16 +------- lnbits/extensions/smtp/smtp.py | 41 ++++++++----------- .../smtp/templates/smtp/_api_docs.html | 2 +- 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/lnbits/extensions/smtp/README.md b/lnbits/extensions/smtp/README.md index 339f210a..5b7757e2 100644 --- a/lnbits/extensions/smtp/README.md +++ b/lnbits/extensions/smtp/README.md @@ -9,18 +9,6 @@ This extension allows you to setup a smtp, to offer sending emails with it for a ## Usage 1. Create new emailaddress -2. Verify if email goes to your testemail. Testmail is send on create and update -3. enjoy +2. Verify if email goes to your testemail. Testmail is sent on create and update +3. Share the link with the email form. -## API Endpoints - -- **Emailaddresses** - - GET /api/v1/emailaddress - - POST /api/v1/emailaddress - - PUT /api/v1/emailaddress/ - - DELETE /api/v1/emailaddress/ -- **Emails** - - GET /api/v1/email - - POST /api/v1/email/ - - GET /api/v1/email/ - - DELETE /api/v1/email/ diff --git a/lnbits/extensions/smtp/smtp.py b/lnbits/extensions/smtp/smtp.py index a8830254..e77bc0fa 100644 --- a/lnbits/extensions/smtp/smtp.py +++ b/lnbits/extensions/smtp/smtp.py @@ -36,32 +36,23 @@ async def send_mail(emailaddress, email): msg["To"] = email.receiver signature = "Email sent anonymiously by LNbits Sendmail extension." - text = ( - """\ - """ - + email.message - + """ - """ - + signature - + """ - """ - ) + text = f""" +{email.message} + +{signature} +""" + + html = f""" + + + +

{email.message}

+
+

{signature}

+ + +""" - html = ( - """\ - - - -

""" - + email.message - + """

-


""" - + signature - + """

- - - """ - ) part1 = MIMEText(text, "plain") part2 = MIMEText(html, "html") msg.attach(part1) diff --git a/lnbits/extensions/smtp/templates/smtp/_api_docs.html b/lnbits/extensions/smtp/templates/smtp/_api_docs.html index c7ed44de..cfb811d1 100644 --- a/lnbits/extensions/smtp/templates/smtp/_api_docs.html +++ b/lnbits/extensions/smtp/templates/smtp/_api_docs.html @@ -12,7 +12,7 @@

Charge people for using sending an email via your smtp server
More details
From e4ab966d2fd21722cc4ea0d323a030ab66d9a1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 9 Jan 2023 10:15:38 +0100 Subject: [PATCH 37/84] readd the package-lock.json from main --- package-lock.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f2ff24bd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,32 @@ +{ + "name": "lnbits-legend", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "2.1.1" + } + }, + "node_modules/prettier": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + } + }, + "dependencies": { + "prettier": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", + "dev": true + } + } +} From 6ea50698359f1b7ed30fcd621fa3e24815c500f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 5 Jan 2023 13:40:44 +0100 Subject: [PATCH 38/84] fix mypy watchonly issues --- lnbits/extensions/watchonly/crud.py | 13 +++--- lnbits/extensions/watchonly/models.py | 10 ++-- lnbits/extensions/watchonly/views.py | 3 +- lnbits/extensions/watchonly/views_api.py | 58 ++++++++++++------------ pyproject.toml | 1 - 5 files changed, 43 insertions(+), 42 deletions(-) diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py index 61e47cfe..1d9abcec 100644 --- a/lnbits/extensions/watchonly/crud.py +++ b/lnbits/extensions/watchonly/crud.py @@ -41,8 +41,9 @@ async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount: w.meta, ), ) - - return await get_watch_wallet(wallet_id) + wallet = await get_watch_wallet(wallet_id) + assert wallet + return wallet async def get_watch_wallet(wallet_id: str) -> Optional[WalletAccount]: @@ -121,11 +122,11 @@ async def create_fresh_addresses( change_address=False, ) -> List[Address]: if start_address_index > end_address_index: - return None + return [] wallet = await get_watch_wallet(wallet_id) if not wallet: - return None + return [] branch_index = 1 if change_address else 0 @@ -150,7 +151,7 @@ async def create_fresh_addresses( # return fresh addresses rows = await db.fetchall( """ - SELECT * FROM watchonly.addresses + SELECT * FROM watchonly.addresses WHERE wallet = ? AND branch_index = ? AND address_index >= ? AND address_index < ? ORDER BY branch_index, address_index """, @@ -172,7 +173,7 @@ async def get_address_at_index( ) -> Optional[Address]: row = await db.fetchone( """ - SELECT * FROM watchonly.addresses + SELECT * FROM watchonly.addresses WHERE wallet = ? AND branch_index = ? AND address_index = ? """, ( diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index c6265d6c..24d63bfd 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -1,7 +1,7 @@ from sqlite3 import Row from typing import List, Optional -from fastapi.param_functions import Query +from fastapi import Query from pydantic import BaseModel @@ -35,7 +35,7 @@ class Address(BaseModel): amount: int = 0 branch_index: int = 0 address_index: int - note: str = None + note: Optional[str] = None has_activity: bool = False @classmethod @@ -57,9 +57,9 @@ class TransactionInput(BaseModel): class TransactionOutput(BaseModel): amount: int address: str - branch_index: int = None - address_index: int = None - wallet: str = None + branch_index: Optional[int] = None + address_index: Optional[int] = None + wallet: Optional[str] = None class MasterPublicKey(BaseModel): diff --git a/lnbits/extensions/watchonly/views.py b/lnbits/extensions/watchonly/views.py index 819d1248..8cebc6cc 100644 --- a/lnbits/extensions/watchonly/views.py +++ b/lnbits/extensions/watchonly/views.py @@ -1,6 +1,5 @@ -from fastapi.params import Depends +from fastapi import Depends, Request from fastapi.templating import Jinja2Templates -from starlette.requests import Request from starlette.responses import HTMLResponse from lnbits.core.models import User diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index c6e15ea6..5bb43661 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -1,5 +1,6 @@ import json from http import HTTPStatus +from typing import List import httpx from embit import finalizer, script @@ -7,9 +8,7 @@ from embit.ec import PublicKey from embit.networks import NETWORKS from embit.psbt import PSBT, DerivationPath from embit.transaction import Transaction, TransactionInput, TransactionOutput -from fastapi import Query, Request -from fastapi.params import Depends -from starlette.exceptions import HTTPException +from fastapi import Depends, HTTPException, Query, Request from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.extensions.watchonly import watchonly_ext @@ -57,10 +56,8 @@ async def api_wallets_retrieve( return [] -@watchonly_ext.get("/api/v1/wallet/{wallet_id}") -async def api_wallet_retrieve( - wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) -): +@watchonly_ext.get("/api/v1/wallet/{wallet_id}", dependencies=[Depends(get_key_type)]) +async def api_wallet_retrieve(wallet_id: str): w_wallet = await get_watch_wallet(wallet_id) if not w_wallet: @@ -76,7 +73,8 @@ async def api_wallet_create_or_update( data: CreateWallet, w: WalletTypeInfo = Depends(require_admin_key) ): try: - (descriptor, network) = parse_key(data.masterpub) + # TODO: talk to motorina about this + (descriptor, network) = parse_key(data.masterpub) # type: ignore if data.network != network["name"]: raise ValueError( "Account network error. This account is for '{}'".format( @@ -126,8 +124,10 @@ async def api_wallet_create_or_update( return wallet.dict() -@watchonly_ext.delete("/api/v1/wallet/{wallet_id}") -async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin_key)): +@watchonly_ext.delete( + "/api/v1/wallet/{wallet_id}", dependencies=[Depends(require_admin_key)] +) +async def api_wallet_delete(wallet_id: str): wallet = await get_watch_wallet(wallet_id) if not wallet: @@ -144,16 +144,15 @@ async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin #############################ADDRESSES########################## -@watchonly_ext.get("/api/v1/address/{wallet_id}") -async def api_fresh_address(wallet_id, w: WalletTypeInfo = Depends(get_key_type)): +@watchonly_ext.get("/api/v1/address/{wallet_id}", dependencies=[Depends(get_key_type)]) +async def api_fresh_address(wallet_id: str): address = await get_fresh_address(wallet_id) + assert address return address.dict() -@watchonly_ext.put("/api/v1/address/{id}") -async def api_update_address( - id: str, req: Request, w: WalletTypeInfo = Depends(require_admin_key) -): +@watchonly_ext.put("/api/v1/address/{id}", dependencies=[Depends(require_admin_key)]) +async def api_update_address(id: str, req: Request): body = await req.json() params = {} # amout is only updated if the address has history @@ -162,9 +161,10 @@ async def api_update_address( params["has_activity"] = True if "note" in body: - params["note"] = str(body["note"]) + params["note"] = body["note"] address = await update_address(**params, id=id) + assert address wallet = ( await get_watch_wallet(address.wallet) @@ -189,6 +189,7 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type) addresses = await get_addresses(wallet_id) config = await get_config(w.wallet.user) + assert config if not addresses: await create_fresh_addresses(wallet_id, 0, config.receive_gap_limit) @@ -229,10 +230,8 @@ async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type) #############################PSBT########################## -@watchonly_ext.post("/api/v1/psbt") -async def api_psbt_create( - data: CreatePsbt, w: WalletTypeInfo = Depends(require_admin_key) -): +@watchonly_ext.post("/api/v1/psbt", dependencies=[Depends(require_admin_key)]) +async def api_psbt_create(data: CreatePsbt): try: vin = [ TransactionInput(bytes.fromhex(inp.tx_id), inp.vout) for inp in data.inputs @@ -246,7 +245,7 @@ async def api_psbt_create( for _, masterpub in enumerate(data.masterpubs): descriptors[masterpub.id] = parse_key(masterpub.public_key) - inputs_extra = [] + inputs_extra: List[dict] = [] for i, inp in enumerate(data.inputs): bip32_derivations = {} @@ -266,14 +265,15 @@ async def api_psbt_create( tx = Transaction(vin=vin, vout=vout) psbt = PSBT(tx) - for i, inp in enumerate(inputs_extra): - psbt.inputs[i].bip32_derivations = inp["bip32_derivations"] - psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None) + for i, inp_extra in enumerate(inputs_extra): + psbt.inputs[i].bip32_derivations = inp_extra["bip32_derivations"] + psbt.inputs[i].non_witness_utxo = inp_extra.get("non_witness_utxo", None) outputs_extra = [] bip32_derivations = {} for i, out in enumerate(data.outputs): if out.branch_index == 1: + assert out.wallet descriptor = descriptors[out.wallet][0] d = descriptor.derive(out.address_index, out.branch_index) for k in d.keys: @@ -282,8 +282,8 @@ async def api_psbt_create( ) outputs_extra.append({"bip32_derivations": bip32_derivations}) - for i, out in enumerate(outputs_extra): - psbt.outputs[i].bip32_derivations = out["bip32_derivations"] + for i, out_extra in enumerate(outputs_extra): + psbt.outputs[i].bip32_derivations = out_extra["bip32_derivations"] return psbt.to_string() @@ -360,7 +360,8 @@ async def api_tx_broadcast( else config.mempool_endpoint + "/testnet" ) async with httpx.AsyncClient() as client: - r = await client.post(endpoint + "/api/tx", data=data.tx_hex) + r = await client.post(endpoint + "/api/tx", content=data.tx_hex) + r.raise_for_status() tx_id = r.text return tx_id except Exception as e: @@ -375,6 +376,7 @@ async def api_update_config( data: Config, w: WalletTypeInfo = Depends(require_admin_key) ): config = await update_config(data, user=w.wallet.user) + assert config return config.dict() diff --git a/pyproject.toml b/pyproject.toml index 03dbbc8d..b41cff10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,6 @@ exclude = """(?x)( | ^lnbits/extensions/boltz. | ^lnbits/extensions/livestream. | ^lnbits/extensions/lnurldevice. - | ^lnbits/extensions/watchonly. | ^lnbits/wallets/lnd_grpc_files. )""" From d51ba2b4fbb36e2417f12453efd460f4f8826128 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 11:16:48 +0200 Subject: [PATCH 39/84] refactor: remove redundant `# type: ignore` --- lnbits/extensions/watchonly/helpers.py | 6 +++--- lnbits/extensions/watchonly/views_api.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lnbits/extensions/watchonly/helpers.py b/lnbits/extensions/watchonly/helpers.py index 74125dde..8db9ff57 100644 --- a/lnbits/extensions/watchonly/helpers.py +++ b/lnbits/extensions/watchonly/helpers.py @@ -1,6 +1,6 @@ -from embit.descriptor import Descriptor, Key # type: ignore -from embit.descriptor.arguments import AllowedDerivation # type: ignore -from embit.networks import NETWORKS # type: ignore +from embit.descriptor import Descriptor, Key +from embit.descriptor.arguments import AllowedDerivation +from embit.networks import NETWORKS def detect_network(k): diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 5bb43661..a7086423 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -73,8 +73,7 @@ async def api_wallet_create_or_update( data: CreateWallet, w: WalletTypeInfo = Depends(require_admin_key) ): try: - # TODO: talk to motorina about this - (descriptor, network) = parse_key(data.masterpub) # type: ignore + (descriptor, network) = parse_key(data.masterpub) if data.network != network["name"]: raise ValueError( "Account network error. This account is for '{}'".format( From 96ed9192a3854fc38cd01463d9ebe61aa0939405 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Jan 2023 11:01:15 +0000 Subject: [PATCH 40/84] format --- lnbits/extensions/nostrnip5/views_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/nostrnip5/views_api.py b/lnbits/extensions/nostrnip5/views_api.py index 43c06389..79768d62 100644 --- a/lnbits/extensions/nostrnip5/views_api.py +++ b/lnbits/extensions/nostrnip5/views_api.py @@ -199,7 +199,9 @@ async def api_address_create( if domain.currency == "Satoshis": price_in_sats = domain.amount else: - price_in_sats = await fiat_amount_as_satoshis(domain.amount / 100, domain.currency) + price_in_sats = await fiat_amount_as_satoshis( + domain.amount / 100, domain.currency + ) try: payment_hash, payment_request = await create_invoice( From d66efcaa32c6673b6515a42315e26cdf9ea2ef7d Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:13:16 +0100 Subject: [PATCH 41/84] Apply suggestions from code review --- docs/guide/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 460f7d00..871857a7 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -206,9 +206,9 @@ poetry add setuptools wheel ./venv/bin/pip install setuptools wheel ``` -### poetry +#### Poetry -If your poetry version is less than ^1.2, for `poetry install`, ignore the -- flags, it will install just fine. +If your Poetry version is older than 1.2, for `poetry install`, ignore the `--only main` flag. ### Optional: PostgreSQL database From b228364a4196e669973743f061e91565eb978d49 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:13:43 +0100 Subject: [PATCH 42/84] Apply suggestions from code review --- docs/guide/installation.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 871857a7..2bbdfb11 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -210,7 +210,6 @@ poetry add setuptools wheel If your Poetry version is older than 1.2, for `poetry install`, ignore the `--only main` flag. - ### Optional: PostgreSQL database If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and setup a database for LNbits: From ad2a6c7bc4bab9d74fe053a2f24722a1eab6cc2d Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Jan 2023 11:26:20 +0000 Subject: [PATCH 43/84] Added tile --- lnbits/extensions/smtp/__init__.py | 9 +++++++++ lnbits/extensions/smtp/config.json | 4 ++-- .../smtp/static/smtp-bitcoin-email.png | Bin 0 -> 18854 bytes 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 lnbits/extensions/smtp/static/smtp-bitcoin-email.png diff --git a/lnbits/extensions/smtp/__init__.py b/lnbits/extensions/smtp/__init__.py index 1d951b31..e7419852 100644 --- a/lnbits/extensions/smtp/__init__.py +++ b/lnbits/extensions/smtp/__init__.py @@ -1,6 +1,7 @@ import asyncio from fastapi import APIRouter +from fastapi.staticfiles import StaticFiles from lnbits.db import Database from lnbits.helpers import template_renderer @@ -8,6 +9,14 @@ from lnbits.tasks import catch_everything_and_restart db = Database("ext_smtp") +smtp_static_files = [ + { + "path": "/smtp/static", + "app": StaticFiles(directory="lnbits/extensions/smtp/static"), + "name": "smtp_static", + } +] + smtp_ext: APIRouter = APIRouter(prefix="/smtp", tags=["smtp"]) diff --git a/lnbits/extensions/smtp/config.json b/lnbits/extensions/smtp/config.json index 8b2cb764..325ebfa7 100644 --- a/lnbits/extensions/smtp/config.json +++ b/lnbits/extensions/smtp/config.json @@ -1,6 +1,6 @@ { "name": "SMTP", - "short_description": "Let users send emails via your SMTP and earn sats", - "icon": "email", + "short_description": "Charge sats for sending emails", + "tile": "/smtp/static/smtp-bitcoin-email.png", "contributors": ["dni"] } diff --git a/lnbits/extensions/smtp/static/smtp-bitcoin-email.png b/lnbits/extensions/smtp/static/smtp-bitcoin-email.png new file mode 100644 index 0000000000000000000000000000000000000000..e80b6c9aeccd0474ba735fdbfc6ea7d46b240d5f GIT binary patch literal 18854 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 Date: Mon, 9 Jan 2023 14:21:14 +0200 Subject: [PATCH 44/84] fix: (rollback refactoring) Use the `tipjar` id to fetch the created `tipjar` --- lnbits/extensions/tipjar/crud.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/tipjar/crud.py b/lnbits/extensions/tipjar/crud.py index 1b58a43d..080eaf1c 100644 --- a/lnbits/extensions/tipjar/crud.py +++ b/lnbits/extensions/tipjar/crud.py @@ -33,7 +33,11 @@ async def create_tip( async def create_tipjar(data: createTipJar) -> TipJar: """Create a new TipJar""" - await db.execute( + + returning = "" if db.type == SQLITE else "RETURNING ID" + method = db.execute if db.type == SQLITE else db.fetchone + + result = await (method)( f""" INSERT INTO tipjar.TipJars ( name, @@ -42,11 +46,16 @@ async def create_tipjar(data: createTipJar) -> TipJar: onchain ) VALUES (?, ?, ?, ?) + {returning} """, (data.name, data.wallet, data.webhook, data.onchain), ) - row = await db.fetchone("SELECT * FROM tipjar.TipJars LIMIT 1") - tipjar = TipJar(**row) + if db.type == SQLITE: + tipjar_id = result._result_proxy.lastrowid + else: + tipjar_id = result[0] + + tipjar = await get_tipjar(tipjar_id) assert tipjar return tipjar From 4d5c7133bc773b5ecac99663035195334c6c722f Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 9 Jan 2023 12:30:21 +0000 Subject: [PATCH 45/84] create smaller base64 image in UI --- lnbits/extensions/market/templates/market/index.html | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/market/templates/market/index.html b/lnbits/extensions/market/templates/market/index.html index ffcb612b..c9542bc1 100644 --- a/lnbits/extensions/market/templates/market/index.html +++ b/lnbits/extensions/market/templates/market/index.html @@ -831,14 +831,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} } }, From 44fa30fdc3e4b9e14ae8230561688098f8567b49 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 9 Jan 2023 12:31:12 +0000 Subject: [PATCH 46/84] check uploaded image size in API --- lnbits/extensions/market/views_api.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lnbits/extensions/market/views_api.py b/lnbits/extensions/market/views_api.py index 045bc0fc..31703e8d 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: From 61c9a22fe1475034d81239cb447d8b9f2a53a9fc Mon Sep 17 00:00:00 2001 From: Joel Klabo Date: Mon, 9 Jan 2023 07:20:22 -0800 Subject: [PATCH 47/84] Don't Multiply Sat Price by 100 for NIP-5 Verification --- lnbits/extensions/nostrnip5/crud.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/nostrnip5/crud.py b/lnbits/extensions/nostrnip5/crud.py index 12adc05a..2060d08a 100644 --- a/lnbits/extensions/nostrnip5/crud.py +++ b/lnbits/extensions/nostrnip5/crud.py @@ -173,12 +173,17 @@ async def create_address_internal(domain_id: str, data: CreateAddressData) -> Ad async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain: domain_id = urlsafe_short_hash() + if data.currency != "Satoshis": + amount = data.amount * 100 + else: + amount = data.amount + await db.execute( """ INSERT INTO nostrnip5.domains (id, wallet, currency, amount, domain) VALUES (?, ?, ?, ?, ?) """, - (domain_id, wallet_id, data.currency, int(data.amount * 100), data.domain), + (domain_id, wallet_id, data.currency, int(amount), data.domain), ) domain = await get_domain(domain_id) From 66c0aac3a1cc6eb3c70223db015c1a5f98395a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 5 Jan 2023 14:11:17 +0100 Subject: [PATCH 48/84] fix mypy lnurldevices issues --- lnbits/extensions/lnurldevice/crud.py | 33 +++++++------- lnbits/extensions/lnurldevice/lnurl.py | 50 +++++++++++----------- lnbits/extensions/lnurldevice/models.py | 9 ++-- lnbits/extensions/lnurldevice/tasks.py | 16 +++---- lnbits/extensions/lnurldevice/views.py | 11 ++--- lnbits/extensions/lnurldevice/views_api.py | 31 +++++--------- pyproject.toml | 1 - 7 files changed, 67 insertions(+), 84 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 182df743..16baae1e 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -7,8 +7,6 @@ from lnbits.helpers import urlsafe_short_hash from . import db from .models import createLnurldevice, lnurldevicepayment, lnurldevices -###############lnurldeviceS########################## - async def create_lnurldevice( data: createLnurldevice, @@ -69,10 +67,12 @@ async def create_lnurldevice( data.pin4, ), ) - return await get_lnurldevice(lnurldevice_id) + device = await get_lnurldevice(lnurldevice_id) + assert device + return device -async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> Optional[lnurldevices]: +async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> lnurldevices: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) await db.execute( f"UPDATE lnurldevice.lnurldevices SET {q} WHERE id = ?", @@ -81,19 +81,18 @@ async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> Optional[lnurldev row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) ) - return lnurldevices(**row) if row else None + return lnurldevices(**row) -async def get_lnurldevice(lnurldevice_id: str) -> lnurldevices: +async def get_lnurldevice(lnurldevice_id: str) -> Optional[lnurldevices]: row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) ) return lnurldevices(**row) if row else None -async def get_lnurldevices(wallet_ids: Union[str, List[str]]) -> List[lnurldevices]: - wallet_ids = [wallet_ids] - q = ",".join(["?"] * len(wallet_ids[0])) +async def get_lnurldevices(wallet_ids: List[str]) -> List[lnurldevices]: + q = ",".join(["?"] * len(wallet_ids)) rows = await db.fetchall( f""" SELECT * FROM lnurldevice.lnurldevices WHERE wallet IN ({q}) @@ -102,7 +101,7 @@ async def get_lnurldevices(wallet_ids: Union[str, List[str]]) -> List[lnurldevic (*wallet_ids,), ) - return [lnurldevices(**row) if row else None for row in rows] + return [lnurldevices(**row) for row in rows] async def delete_lnurldevice(lnurldevice_id: str) -> None: @@ -110,8 +109,6 @@ async def delete_lnurldevice(lnurldevice_id: str) -> None: "DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) ) - ########################lnuldevice payments########################### - async def create_lnurldevicepayment( deviceid: str, @@ -139,7 +136,9 @@ async def create_lnurldevicepayment( """, (lnurldevicepayment_id, deviceid, payload, pin, payhash, sats), ) - return await get_lnurldevicepayment(lnurldevicepayment_id) + dpayment = await get_lnurldevicepayment(lnurldevicepayment_id) + assert dpayment + return dpayment async def update_lnurldevicepayment( @@ -157,7 +156,9 @@ async def update_lnurldevicepayment( return lnurldevicepayment(**row) if row else None -async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayment: +async def get_lnurldevicepayment( + lnurldevicepayment_id: str, +) -> Optional[lnurldevicepayment]: row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", (lnurldevicepayment_id,), @@ -165,7 +166,9 @@ async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayme return lnurldevicepayment(**row) if row else None -async def get_lnurlpayload(lnurldevicepayment_payload: str) -> lnurldevicepayment: +async def get_lnurlpayload( + lnurldevicepayment_payload: str, +) -> Optional[lnurldevicepayment]: row = await db.fetchone( "SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?", (lnurldevicepayment_payload,), diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 34de20fa..978ea0e1 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -1,16 +1,11 @@ import base64 -import hashlib import hmac from http import HTTPStatus from io import BytesIO -from typing import Optional import shortuuid from embit import bech32, compact -from fastapi import Request -from fastapi.param_functions import Query -from loguru import logger -from starlette.exceptions import HTTPException +from fastapi import HTTPException, Query, Request from lnbits import bolt11 from lnbits.core.services import create_invoice @@ -44,7 +39,9 @@ def bech32_decode(bech): encoding = bech32.bech32_verify_checksum(hrp, data) if encoding is None: return - return bytes(bech32.convertbits(data[:-6], 5, 8, False)) + bits = bech32.convertbits(data[:-6], 5, 8, False) + assert bits + return bytes(bits) def xor_decrypt(key, blob): @@ -105,6 +102,8 @@ async def lnurl_v1_params( "reason": f"lnurldevice {device_id} not found on this server", } if device.device == "switch": + # TODO: AMOUNT IN CENT was never reference here + amount_in_cent = 0 price_msat = ( await fiat_amount_as_satoshis(float(profit), device.currency) if device.currency != "sat" @@ -160,21 +159,13 @@ async def lnurl_v1_params( if device.device != "atm": return {"status": "ERROR", "reason": "Not ATM device."} price_msat = int(price_msat * (1 - (device.profit / 100)) / 1000) - lnurldevicepayment = await get_lnurldevicepayment(shortuuid.uuid(name=p)) - if lnurldevicepayment: - logger.debug("lnurldevicepayment") - logger.debug(lnurldevicepayment) - logger.debug("lnurldevicepayment") - if lnurldevicepayment.payload == lnurldevicepayment.payhash: - return {"status": "ERROR", "reason": f"Payment already claimed"} - else: - lnurldevicepayment = await create_lnurldevicepayment( - deviceid=device.id, - payload=p, - sats=price_msat * 1000, - pin=pin, - payhash="payment_hash", - ) + lnurldevicepayment = await create_lnurldevicepayment( + deviceid=device.id, + payload=p, + sats=price_msat * 1000, + pin=str(pin), + payhash="payment_hash", + ) if not lnurldevicepayment: return {"status": "ERROR", "reason": "Could not create payment."} return { @@ -193,7 +184,7 @@ async def lnurl_v1_params( deviceid=device.id, payload=p, sats=price_msat * 1000, - pin=pin, + pin=str(pin), payhash="payment_hash", ) if not lnurldevicepayment: @@ -221,6 +212,10 @@ async def lnurl_callback( k1: str = Query(None), ): lnurldevicepayment = await get_lnurldevicepayment(paymentid) + if not lnurldevicepayment: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, detail="lnurldevicepayment not found." + ) device = await get_lnurldevice(lnurldevicepayment.deviceid) if not device: raise HTTPException( @@ -241,13 +236,20 @@ async def lnurl_callback( else: if lnurldevicepayment.payload != k1: return {"status": "ERROR", "reason": "Bad K1"} +<<<<<<< HEAD lnurldevicepayment = await update_lnurldevicepayment( +======= + if lnurldevicepayment.payhash != "payment_hash": + return {"status": "ERROR", "reason": f"Payment already claimed"} + lnurldevicepayment_updated = await update_lnurldevicepayment( +>>>>>>> c2f4a7c9 (fix mypy lnurldevices issues) lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) + assert lnurldevicepayment_updated await pay_invoice( wallet_id=device.wallet, payment_request=pr, - max_sat=lnurldevicepayment.sats / 1000, + max_sat=int(lnurldevicepayment_updated.sats / 1000), extra={"tag": "withdraw"}, ) return {"status": "OK"} diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py index 66b215f2..f9640de1 100644 --- a/lnbits/extensions/lnurldevice/models.py +++ b/lnbits/extensions/lnurldevice/models.py @@ -3,13 +3,9 @@ from sqlite3 import Row from typing import List, Optional from fastapi import Request -from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore -from loguru import logger +from lnurl import encode as lnurl_encode +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel -from pydantic.main import BaseModel class createLnurldevice(BaseModel): @@ -58,6 +54,7 @@ class lnurldevices(BaseModel): pin4: int timestamp: str + @classmethod def from_row(cls, row: Row) -> "lnurldevices": return cls(**dict(row)) diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index 8ad9772c..9aec173e 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -1,18 +1,11 @@ import asyncio -import json -from http import HTTPStatus -from urllib.parse import urlparse -import httpx -from fastapi import HTTPException - -from lnbits import bolt11 from lnbits.core.models import Payment -from lnbits.core.services import pay_invoice, websocketUpdater +from lnbits.core.services import websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment +from .crud import get_lnurldevicepayment, update_lnurldevicepayment async def wait_for_paid_invoices(): @@ -27,14 +20,15 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: # (avoid loops) if "Switch" == payment.extra.get("tag"): - lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id")) + lnurldevicepayment = await get_lnurldevicepayment(payment.extra["id"]) if not lnurldevicepayment: return if lnurldevicepayment.payhash == "used": return lnurldevicepayment = await update_lnurldevicepayment( - lnurldevicepayment_id=payment.extra.get("id"), payhash="used" + lnurldevicepayment_id=payment.extra["id"], payhash="used" ) + assert lnurldevicepayment return await websocketUpdater( lnurldevicepayment.deviceid, str(lnurldevicepayment.pin) + "-" + str(lnurldevicepayment.payload), diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py index f1be4f0d..a6256a41 100644 --- a/lnbits/extensions/lnurldevice/views.py +++ b/lnbits/extensions/lnurldevice/views.py @@ -1,12 +1,7 @@ from http import HTTPStatus -from io import BytesIO -import pyqrcode -from fastapi import Request -from fastapi.param_functions import Query -from fastapi.params import Depends +from fastapi import Depends, HTTPException, Query, Request from fastapi.templating import Jinja2Templates -from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse, StreamingResponse from lnbits.core.crud import update_payment_status @@ -62,4 +57,6 @@ async def img(request: Request, lnurldevice_id): raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist." ) - return lnurldevice.lnurl(request) + # error: "lnurldevices" has no attribute "lnurl" + # return lnurldevice.lnurl(request) + return None diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index c6766423..d657c879 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -1,9 +1,6 @@ from http import HTTPStatus -from fastapi import Request -from fastapi.param_functions import Query -from fastapi.params import Depends -from starlette.exceptions import HTTPException +from fastapi import Depends, HTTPException, Query, Request from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key @@ -26,9 +23,6 @@ async def api_list_currencies_available(): return list(currencies.keys()) -#######################lnurldevice########################## - - @lnurldevice_ext.post("/api/v1/lnurlpos") @lnurldevice_ext.put("/api/v1/lnurlpos/{lnurldevice_id}") async def api_lnurldevice_create_or_update( @@ -41,7 +35,7 @@ async def api_lnurldevice_create_or_update( lnurldevice = await create_lnurldevice(data) return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} else: - lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id) + lnurldevice = await update_lnurldevice(lnurldevice_id, **data.dict()) return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} @@ -49,7 +43,8 @@ async def api_lnurldevice_create_or_update( async def api_lnurldevices_retrieve( req: Request, wallet: WalletTypeInfo = Depends(get_key_type) ): - wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids + user = await get_user(wallet.wallet.user) + wallet_ids = user.wallet_ids if user else [] try: return [ {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} @@ -65,10 +60,11 @@ async def api_lnurldevices_retrieve( return "" -@lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}") +@lnurldevice_ext.get( + "/api/v1/lnurlpos/{lnurldevice_id}", dependencies=[Depends(get_key_type)] +) async def api_lnurldevice_retrieve( req: Request, - wallet: WalletTypeInfo = Depends(get_key_type), lnurldevice_id: str = Query(None), ): lnurldevice = await get_lnurldevice(lnurldevice_id) @@ -76,23 +72,18 @@ async def api_lnurldevice_retrieve( raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="lnurldevice does not exist" ) - if not lnurldevice.lnurl_toggle: - return {**lnurldevice.dict()} return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}} -@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") -async def api_lnurldevice_delete( - wallet: WalletTypeInfo = Depends(require_admin_key), - lnurldevice_id: str = Query(None), -): +@lnurldevice_ext.delete( + "/api/v1/lnurlpos/{lnurldevice_id}", dependencies=[Depends(require_admin_key)] +) +async def api_lnurldevice_delete(lnurldevice_id: str = Query(None)): lnurldevice = await get_lnurldevice(lnurldevice_id) - if not lnurldevice: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist." ) await delete_lnurldevice(lnurldevice_id) - return "", HTTPStatus.NO_CONTENT diff --git a/pyproject.toml b/pyproject.toml index b41cff10..c3026c6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,6 @@ exclude = """(?x)( ^lnbits/extensions/bleskomat. | ^lnbits/extensions/boltz. | ^lnbits/extensions/livestream. - | ^lnbits/extensions/lnurldevice. | ^lnbits/wallets/lnd_grpc_files. )""" From 840de18a91199c0157ecfb0ba8fc1eb22772eb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Mon, 9 Jan 2023 16:39:31 +0100 Subject: [PATCH 49/84] fixup --- lnbits/extensions/lnurldevice/crud.py | 1 + lnbits/extensions/lnurldevice/lnurl.py | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py index 16baae1e..0ab520da 100644 --- a/lnbits/extensions/lnurldevice/crud.py +++ b/lnbits/extensions/lnurldevice/crud.py @@ -118,6 +118,7 @@ async def create_lnurldevicepayment( sats: Optional[int] = 0, ) -> lnurldevicepayment: device = await get_lnurldevice(deviceid) + assert device if device.device == "atm": lnurldevicepayment_id = shortuuid.uuid(name=payload) else: diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index 978ea0e1..b14bd613 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -236,13 +236,10 @@ async def lnurl_callback( else: if lnurldevicepayment.payload != k1: return {"status": "ERROR", "reason": "Bad K1"} -<<<<<<< HEAD - lnurldevicepayment = await update_lnurldevicepayment( -======= if lnurldevicepayment.payhash != "payment_hash": return {"status": "ERROR", "reason": f"Payment already claimed"} + lnurldevicepayment_updated = await update_lnurldevicepayment( ->>>>>>> c2f4a7c9 (fix mypy lnurldevices issues) lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload ) assert lnurldevicepayment_updated From 18737402941f50cf96096a8568134786ba1fbc53 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Jan 2023 16:14:46 +0000 Subject: [PATCH 50/84] format --- lnbits/extensions/nostrnip5/crud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/nostrnip5/crud.py b/lnbits/extensions/nostrnip5/crud.py index 2060d08a..fe71b981 100644 --- a/lnbits/extensions/nostrnip5/crud.py +++ b/lnbits/extensions/nostrnip5/crud.py @@ -177,7 +177,7 @@ async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Doma amount = data.amount * 100 else: amount = data.amount - + await db.execute( """ INSERT INTO nostrnip5.domains (id, wallet, currency, amount, domain) From b413bbb8ee42bf7ac8a4ac2006c492680e9acfd4 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Jan 2023 16:37:35 +0000 Subject: [PATCH 51/84] Added try for failing multiple ATM withdraws to avoid error dump --- lnbits/extensions/lnurldevice/lnurl.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py index b14bd613..eba2a693 100644 --- a/lnbits/extensions/lnurldevice/lnurl.py +++ b/lnbits/extensions/lnurldevice/lnurl.py @@ -159,15 +159,18 @@ async def lnurl_v1_params( if device.device != "atm": return {"status": "ERROR", "reason": "Not ATM device."} price_msat = int(price_msat * (1 - (device.profit / 100)) / 1000) - lnurldevicepayment = await create_lnurldevicepayment( - deviceid=device.id, - payload=p, - sats=price_msat * 1000, - pin=str(pin), - payhash="payment_hash", - ) + try: + lnurldevicepayment = await create_lnurldevicepayment( + deviceid=device.id, + payload=p, + sats=price_msat * 1000, + pin=str(pin), + payhash="payment_hash", + ) + except: + return {"status": "ERROR", "reason": "Could not create ATM payment."} if not lnurldevicepayment: - return {"status": "ERROR", "reason": "Could not create payment."} + return {"status": "ERROR", "reason": "Could not create ATM payment."} return { "tag": "withdrawRequest", "callback": request.url_for( From 679b371eb8da56f797eb2a4b2c4e7b1cd2c617a0 Mon Sep 17 00:00:00 2001 From: Joel Klabo Date: Mon, 9 Jan 2023 08:44:18 -0800 Subject: [PATCH 52/84] Update Formatting of Satoshi Amount (No decimal places) --- lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html index c78639e0..5124dcd4 100644 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html +++ b/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html @@ -37,9 +37,15 @@ context %} {% block page %}

The current price is + {% if domain.currency != "Satoshis" %} {{ "{:0,.2f}".format(domain.amount / 100) }} {{ domain.currency }} + {% else %} + {{ "{}".format(domain.amount) }} {{ domain.currency }} + {% endif %} for an account (if you do not own the domain, the service provider can disable at any time).

From ab73698a456d11e7d715169131dc7ef9ca820d4e Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Mon, 9 Jan 2023 19:11:16 +0000 Subject: [PATCH 53/84] add product image with url --- .../market/templates/market/_dialogs.html | 14 +++++++++++++- .../extensions/market/templates/market/index.html | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/market/templates/market/_dialogs.html b/lnbits/extensions/market/templates/market/_dialogs.html index d2a8dd0a..a0ab84b3 100644 --- a/lnbits/extensions/market/templates/market/_dialogs.html +++ b/lnbits/extensions/market/templates/market/_dialogs.html @@ -55,8 +55,16 @@ > - + + Date: Mon, 9 Jan 2023 19:23:03 +0000 Subject: [PATCH 54/84] format --- .../nostrnip5/templates/nostrnip5/signup.html | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html index 5124dcd4..15294817 100644 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html +++ b/lnbits/extensions/nostrnip5/templates/nostrnip5/signup.html @@ -36,18 +36,14 @@ context %} {% block page %} the {{ domain.domain }} domain.

- The current price is - {% if domain.currency != "Satoshis" %} + The current price is {% if domain.currency != "Satoshis" %} {{ "{:0,.2f}".format(domain.amount / 100) }} {{ domain.currency }} {% else %} - {{ "{}".format(domain.amount) }} {{ domain.currency }} - {% endif %} - for an account (if you do not own the domain, the service provider can - disable at any time). + {{ "{}".format(domain.amount) }} {{ domain.currency }} + {% endif %} for an account (if you do not own the domain, the service + provider can disable at any time).

After submitting payment, your address will be

From e97fb086cce7ffae27aa322bff08436cd15efef8 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 11:53:30 +0200 Subject: [PATCH 55/84] feat: explicitly specify for which modules to `ignore_missing_imports` --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c3026c6f..1eca3408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,6 @@ lnbits = "lnbits.server:main" profile = "black" [tool.mypy] -ignore_missing_imports = "True" files = "lnbits" exclude = """(?x)( ^lnbits/extensions/bleskomat. @@ -95,6 +94,10 @@ exclude = """(?x)( | ^lnbits/wallets/lnd_grpc_files. )""" +[[tool.mypy.overrides]] +module = "embit.*,secp256k1.*,uvicorn.*,sqlalchemy.*,sqlalchemy_aio.*,websocket.*,pyqrcode.*,cashu.*,shortuuid.*,grpc.*,lnurl.*" +ignore_missing_imports = "True" + [tool.pytest.ini_options] addopts = "--durations=1 -s --cov=lnbits --cov-report=xml" testpaths = [ From 8cd6c7c9bc2113bd8cbbfe1350b0f3f2eb1708b6 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 11:54:17 +0200 Subject: [PATCH 56/84] refactor: use relative imports for own module --- lnbits/extensions/events/tasks.py | 2 +- lnbits/extensions/events/views_api.py | 2 +- lnbits/extensions/lnaddress/cloudflare.py | 2 +- lnbits/extensions/lnaddress/views_api.py | 2 +- lnbits/extensions/lnticket/views_api.py | 2 +- lnbits/extensions/market/notifier.py | 4 ++-- lnbits/extensions/market/views.py | 6 +++--- lnbits/extensions/satspay/tasks.py | 3 +-- lnbits/extensions/satspay/views.py | 2 +- lnbits/extensions/streamalerts/views_api.py | 6 +----- lnbits/extensions/subdomains/cloudflare.py | 2 +- lnbits/extensions/subdomains/views_api.py | 2 +- 12 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lnbits/extensions/events/tasks.py b/lnbits/extensions/events/tasks.py index 5eae7373..945e2d28 100644 --- a/lnbits/extensions/events/tasks.py +++ b/lnbits/extensions/events/tasks.py @@ -1,10 +1,10 @@ import asyncio from lnbits.core.models import Payment -from lnbits.extensions.events.models import CreateTicket from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener +from .models import CreateTicket from .views_api import api_ticket_send_ticket diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 4ed3932f..5ec9b916 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -7,7 +7,6 @@ from lnbits.core.crud import get_user from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.events.models import CreateEvent, CreateTicket from . import events_ext from .crud import ( @@ -24,6 +23,7 @@ from .crud import ( reg_ticket, update_event, ) +from .models import CreateEvent, CreateTicket # Events diff --git a/lnbits/extensions/lnaddress/cloudflare.py b/lnbits/extensions/lnaddress/cloudflare.py index 679cb515..cf8feaf0 100644 --- a/lnbits/extensions/lnaddress/cloudflare.py +++ b/lnbits/extensions/lnaddress/cloudflare.py @@ -2,7 +2,7 @@ import json import httpx -from lnbits.extensions.lnaddress.models import Domains +from .models import Domains async def cloudflare_create_record(domain: Domains, ip: str): diff --git a/lnbits/extensions/lnaddress/views_api.py b/lnbits/extensions/lnaddress/views_api.py index d9e50e9d..7d15a55f 100644 --- a/lnbits/extensions/lnaddress/views_api.py +++ b/lnbits/extensions/lnaddress/views_api.py @@ -6,7 +6,6 @@ from fastapi import Depends, HTTPException, Query, Request from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.lnaddress.models import CreateAddress, CreateDomain from . import lnaddress_ext from .cloudflare import cloudflare_create_record @@ -23,6 +22,7 @@ from .crud import ( get_domains, update_domain, ) +from .models import CreateAddress, CreateDomain # DOMAINS diff --git a/lnbits/extensions/lnticket/views_api.py b/lnbits/extensions/lnticket/views_api.py index 35d6eaff..4462688b 100644 --- a/lnbits/extensions/lnticket/views_api.py +++ b/lnbits/extensions/lnticket/views_api.py @@ -8,7 +8,6 @@ from lnbits.core.crud import get_user from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.lnticket.models import CreateFormData, CreateTicketData from . import lnticket_ext from .crud import ( @@ -23,6 +22,7 @@ from .crud import ( set_ticket_paid, update_form, ) +from .models import CreateFormData, CreateTicketData # FORMS diff --git a/lnbits/extensions/market/notifier.py b/lnbits/extensions/market/notifier.py index e2bf7c91..88a1a4a3 100644 --- a/lnbits/extensions/market/notifier.py +++ b/lnbits/extensions/market/notifier.py @@ -10,8 +10,8 @@ from collections import defaultdict from fastapi import WebSocket from loguru import logger -from lnbits.extensions.market.crud import create_chat_message -from lnbits.extensions.market.models import CreateChatMessage +from .crud import create_chat_message +from .models import CreateChatMessage class Notifier: diff --git a/lnbits/extensions/market/views.py b/lnbits/extensions/market/views.py index 23bc5706..27ec7a97 100644 --- a/lnbits/extensions/market/views.py +++ b/lnbits/extensions/market/views.py @@ -17,10 +17,8 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists # type: ignore -from lnbits.extensions.market import market_ext, market_renderer -from lnbits.extensions.market.models import CreateChatMessage, SetSettings -from lnbits.extensions.market.notifier import Notifier +from . import market_ext, market_renderer from .crud import ( create_chat_message, create_market_settings, @@ -35,6 +33,8 @@ from .crud import ( get_market_zones, update_market_product_stock, ) +from .models import CreateChatMessage, SetSettings +from .notifier import Notifier templates = Jinja2Templates(directory="templates") diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py index 992e5eb6..2c636351 100644 --- a/lnbits/extensions/satspay/tasks.py +++ b/lnbits/extensions/satspay/tasks.py @@ -4,11 +4,10 @@ import json from loguru import logger from lnbits.core.models import Payment -from lnbits.extensions.satspay.crud import check_address_balance, get_charge from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener -from .crud import update_charge +from .crud import check_address_balance, get_charge, update_charge from .helpers import call_webhook diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py index 15a4403d..175b00bd 100644 --- a/lnbits/extensions/satspay/views.py +++ b/lnbits/extensions/satspay/views.py @@ -6,10 +6,10 @@ from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists -from lnbits.extensions.satspay.helpers import public_charge from . import satspay_ext, satspay_renderer from .crud import get_charge, get_theme +from .helpers import public_charge templates = Jinja2Templates(directory="templates") diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index 0134fe82..25033e27 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -8,11 +8,6 @@ from starlette.responses import RedirectResponse from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.extensions.satspay.models import CreateCharge -from lnbits.extensions.streamalerts.models import ( - CreateDonation, - CreateService, - ValidateDonation, -) from lnbits.utils.exchange_rates import btc_price from ..satspay.crud import create_charge, get_charge @@ -33,6 +28,7 @@ from .crud import ( update_donation, update_service, ) +from .models import CreateDonation, CreateService, ValidateDonation @streamalerts_ext.post("/api/v1/services") diff --git a/lnbits/extensions/subdomains/cloudflare.py b/lnbits/extensions/subdomains/cloudflare.py index 679ca843..5b951b21 100644 --- a/lnbits/extensions/subdomains/cloudflare.py +++ b/lnbits/extensions/subdomains/cloudflare.py @@ -2,7 +2,7 @@ import json import httpx -from lnbits.extensions.subdomains.models import Domains +from .models import Domains async def cloudflare_create_subdomain( diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py index 2b20bd1f..9fbae4f3 100644 --- a/lnbits/extensions/subdomains/views_api.py +++ b/lnbits/extensions/subdomains/views_api.py @@ -6,7 +6,6 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.subdomains.models import CreateDomain, CreateSubdomain from . import subdomains_ext from .cloudflare import cloudflare_create_subdomain, cloudflare_deletesubdomain @@ -22,6 +21,7 @@ from .crud import ( get_subdomains, update_domain, ) +from .models import CreateDomain, CreateSubdomain # domainS From c370bd18c676a9e2413cb7d935a365055926d5e6 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 12:14:44 +0200 Subject: [PATCH 57/84] refactor: remove redundant ` # type: ignore` --- lnbits/bolt11.py | 6 +++--- lnbits/core/migrations.py | 2 +- lnbits/core/models.py | 4 ++-- lnbits/core/services.py | 4 ++-- lnbits/core/views/public_api.py | 2 +- lnbits/db.py | 4 ++-- lnbits/extensions/boltz/models.py | 16 ++++++++-------- lnbits/extensions/boltz/views_api.py | 8 ++++---- lnbits/extensions/cashu/__init__.py | 2 +- lnbits/extensions/copilot/lnurl.py | 2 +- lnbits/extensions/copilot/models.py | 4 ++-- lnbits/extensions/copilot/views.py | 2 +- lnbits/extensions/lndhub/decorators.py | 2 +- lnbits/extensions/lnurlp/lnurl.py | 6 +----- lnbits/extensions/lnurlp/models.py | 4 ++-- lnbits/extensions/lnurlp/views_api.py | 2 +- lnbits/extensions/market/views.py | 2 +- lnbits/extensions/ngrok/views.py | 3 +-- lnbits/extensions/satsdice/lnurl.py | 2 +- lnbits/extensions/satsdice/models.py | 4 ++-- lnbits/extensions/satsdice/views_api.py | 2 +- lnbits/extensions/satspay/crud.py | 2 +- lnbits/extensions/scrub/models.py | 2 +- lnbits/extensions/streamalerts/crud.py | 1 + lnbits/extensions/streamalerts/views_api.py | 7 +++++-- lnbits/extensions/subdomains/tasks.py | 2 +- lnbits/extensions/tipjar/crud.py | 1 + lnbits/extensions/tipjar/views_api.py | 5 +++-- lnbits/helpers.py | 2 +- pyproject.toml | 2 +- 30 files changed, 54 insertions(+), 53 deletions(-) diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 0bc40158..4e20208c 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -4,12 +4,12 @@ import time from decimal import Decimal from typing import List, NamedTuple, Optional -import bitstring # type: ignore +import bitstring import embit import secp256k1 from bech32 import CHARSET, bech32_decode, bech32_encode -from ecdsa import SECP256k1, VerifyingKey # type: ignore -from ecdsa.util import sigdecode_string # type: ignore +from ecdsa import SECP256k1, VerifyingKey +from ecdsa.util import sigdecode_string class Route(NamedTuple): diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index 41ba5644..66254d11 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -1,7 +1,7 @@ import datetime from loguru import logger -from sqlalchemy.exc import OperationalError # type: ignore +from sqlalchemy.exc import OperationalError from lnbits import bolt11 diff --git a/lnbits/core/models.py b/lnbits/core/models.py index 31383667..eca1bf50 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -6,9 +6,9 @@ import time from sqlite3 import Row from typing import Dict, List, Optional -from ecdsa import SECP256k1, SigningKey # type: ignore +from ecdsa import SECP256k1, SigningKey from fastapi import Query -from lnurl import encode as lnurl_encode # type: ignore +from lnurl import encode as lnurl_encode from loguru import logger from pydantic import BaseModel diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 8dc973e7..eefb2f99 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -7,7 +7,7 @@ from urllib.parse import parse_qs, urlparse import httpx from fastapi import Depends, WebSocket from lnurl import LnurlErrorResponse -from lnurl import decode as decode_lnurl # type: ignore +from lnurl import decode as decode_lnurl from loguru import logger from lnbits import bolt11 @@ -44,7 +44,7 @@ from .crud import ( from .models import Payment try: - from typing import TypedDict # type: ignore + from typing import TypedDict except ImportError: # pragma: nocover from typing_extensions import TypedDict diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 56afc176..b5773bbe 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -16,7 +16,7 @@ from ..tasks import api_invoice_listeners @core_app.get("/.well-known/lnurlp/{username}") async def lnaddress(username: str, request: Request): - from lnbits.extensions.lnaddress.lnurl import lnurl_response + from lnbits.extensions.lnaddress.lnurl import lnurl_response # type: ignore domain = urlparse(str(request.url)).netloc return await lnurl_response(username, domain, request) diff --git a/lnbits/db.py b/lnbits/db.py index 1bef7bf2..77f3cf33 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -9,7 +9,7 @@ from typing import Optional from loguru import logger from sqlalchemy import create_engine from sqlalchemy_aio.base import AsyncConnection -from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore +from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY from lnbits.settings import settings @@ -129,7 +129,7 @@ class Database(Compat): else: self.type = POSTGRES - import psycopg2 # type: ignore + import psycopg2 def _parse_timestamp(value, _): if value is None: diff --git a/lnbits/extensions/boltz/models.py b/lnbits/extensions/boltz/models.py index c8ec5646..4f4ec9e2 100644 --- a/lnbits/extensions/boltz/models.py +++ b/lnbits/extensions/boltz/models.py @@ -3,7 +3,7 @@ from typing import Dict, List, Optional from fastapi.params import Query from pydantic.main import BaseModel -from sqlalchemy.engine import base # type: ignore +from sqlalchemy.engine import base class SubmarineSwap(BaseModel): @@ -24,9 +24,9 @@ class SubmarineSwap(BaseModel): class CreateSubmarineSwap(BaseModel): - wallet: str = Query(...) # type: ignore - refund_address: str = Query(...) # type: ignore - amount: int = Query(...) # type: ignore + wallet: str = Query(...) + refund_address: str = Query(...) + amount: int = Query(...) class ReverseSubmarineSwap(BaseModel): @@ -48,13 +48,13 @@ class ReverseSubmarineSwap(BaseModel): class CreateReverseSubmarineSwap(BaseModel): - wallet: str = Query(...) # type: ignore - amount: int = Query(...) # type: ignore - instant_settlement: bool = Query(...) # type: ignore + wallet: str = Query(...) + amount: int = Query(...) + instant_settlement: bool = Query(...) # validate on-address, bcrt1 for regtest addresses onchain_address: str = Query( ..., regex="^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$" - ) # type: ignore + ) class SwapStatus(BaseModel): diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py index 18ca14cb..34f4033e 100644 --- a/lnbits/extensions/boltz/views_api.py +++ b/lnbits/extensions/boltz/views_api.py @@ -111,7 +111,7 @@ async def api_submarineswap( ) async def api_submarineswap_refund( swap_id: str, - g: WalletTypeInfo = Depends(require_admin_key), # type: ignore + g: WalletTypeInfo = Depends(require_admin_key), ): if swap_id == None: raise HTTPException( @@ -160,7 +160,7 @@ async def api_submarineswap_refund( ) async def api_submarineswap_create( data: CreateSubmarineSwap, - wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore + wallet: WalletTypeInfo = Depends(require_admin_key), ): try: swap_data = await create_swap(data) @@ -257,7 +257,7 @@ async def api_reverse_submarineswap_create( }, ) async def api_swap_status( - swap_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) # type: ignore + swap_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) ): swap = await get_submarine_swap(swap_id) or await get_reverse_submarine_swap( swap_id @@ -290,7 +290,7 @@ async def api_swap_status( response_description="list of pending swaps", ) async def api_check_swaps( - g: WalletTypeInfo = Depends(require_admin_key), # type: ignore + g: WalletTypeInfo = Depends(require_admin_key), all_wallets: bool = Query(False), ): wallet_ids = [g.wallet.id] diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index e6507bba..83d8ce27 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -1,6 +1,6 @@ import asyncio -from environs import Env # type: ignore +from environs import Env from fastapi import APIRouter from fastapi.staticfiles import StaticFiles diff --git a/lnbits/extensions/copilot/lnurl.py b/lnbits/extensions/copilot/lnurl.py index d8ededf3..b0bc83bc 100644 --- a/lnbits/extensions/copilot/lnurl.py +++ b/lnbits/extensions/copilot/lnurl.py @@ -6,7 +6,7 @@ from fastapi import Request from fastapi.param_functions import Query from lnurl.types import LnurlPayMetadata from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.services import create_invoice diff --git a/lnbits/extensions/copilot/models.py b/lnbits/extensions/copilot/models.py index b9b43ccf..7ca2fc96 100644 --- a/lnbits/extensions/copilot/models.py +++ b/lnbits/extensions/copilot/models.py @@ -4,11 +4,11 @@ from typing import Dict, Optional from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse from fastapi.param_functions import Query -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel from starlette.requests import Request -from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnbits.lnurl import encode as lnurl_encode class CreateCopilotData(BaseModel): diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index 7b66366d..ff69dfba 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -2,7 +2,7 @@ from typing import List from fastapi import Depends, Request from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists diff --git a/lnbits/extensions/lndhub/decorators.py b/lnbits/extensions/lndhub/decorators.py index fcadce27..48118087 100644 --- a/lnbits/extensions/lndhub/decorators.py +++ b/lnbits/extensions/lndhub/decorators.py @@ -5,7 +5,7 @@ from fastapi.param_functions import Security from fastapi.security.api_key import APIKeyHeader from starlette.exceptions import HTTPException -from lnbits.decorators import WalletTypeInfo, get_key_type # type: ignore +from lnbits.decorators import WalletTypeInfo, get_key_type api_key_header_auth = APIKeyHeader( name="AUTHORIZATION", diff --git a/lnbits/extensions/lnurlp/lnurl.py b/lnbits/extensions/lnurlp/lnurl.py index 8f6aa623..99de459c 100644 --- a/lnbits/extensions/lnurlp/lnurl.py +++ b/lnbits/extensions/lnurlp/lnurl.py @@ -3,11 +3,7 @@ import math from http import HTTPStatus from fastapi import Request -from lnurl import ( # type: ignore - LnurlErrorResponse, - LnurlPayActionResponse, - LnurlPayResponse, -) +from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse from starlette.exceptions import HTTPException from lnbits.core.services import create_invoice diff --git a/lnbits/extensions/lnurlp/models.py b/lnbits/extensions/lnurlp/models.py index 42ea2926..1c6b6f71 100644 --- a/lnbits/extensions/lnurlp/models.py +++ b/lnbits/extensions/lnurlp/models.py @@ -4,11 +4,11 @@ from typing import Dict, Optional from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse from fastapi.param_functions import Query -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel from starlette.requests import Request -from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnbits.lnurl import encode as lnurl_encode class CreatePayLinkData(BaseModel): diff --git a/lnbits/extensions/lnurlp/views_api.py b/lnbits/extensions/lnurlp/views_api.py index 0fa739b0..a7bf0761 100644 --- a/lnbits/extensions/lnurlp/views_api.py +++ b/lnbits/extensions/lnurlp/views_api.py @@ -2,7 +2,7 @@ import json from http import HTTPStatus from fastapi import Depends, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore +from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from starlette.exceptions import HTTPException from lnbits.core.crud import get_user diff --git a/lnbits/extensions/market/views.py b/lnbits/extensions/market/views.py index 27ec7a97..e6c8eeff 100644 --- a/lnbits/extensions/market/views.py +++ b/lnbits/extensions/market/views.py @@ -16,7 +16,7 @@ from starlette.exceptions import HTTPException from starlette.responses import HTMLResponse from lnbits.core.models import User -from lnbits.decorators import check_user_exists # type: ignore +from lnbits.decorators import check_user_exists from . import market_ext, market_renderer from .crud import ( diff --git a/lnbits/extensions/ngrok/views.py b/lnbits/extensions/ngrok/views.py index 81c8b24e..d84ecd2d 100644 --- a/lnbits/extensions/ngrok/views.py +++ b/lnbits/extensions/ngrok/views.py @@ -1,4 +1,3 @@ -# type: ignore from os import getenv from fastapi import Depends, Request @@ -36,5 +35,5 @@ ngrok_tunnel = ngrok.connect(port) @ngrok_ext.get("/") async def index(request: Request, user: User = Depends(check_user_exists)): return ngrok_renderer().TemplateResponse( - "ngrok/index.html", {"request": request, "ngrok": string5, "user": user.dict()} + "ngrok/index.html", {"request": request, "ngrok": string5, "user": user.dict()} # type: ignore ) diff --git a/lnbits/extensions/satsdice/lnurl.py b/lnbits/extensions/satsdice/lnurl.py index 1e9c6c09..f766d8cb 100644 --- a/lnbits/extensions/satsdice/lnurl.py +++ b/lnbits/extensions/satsdice/lnurl.py @@ -5,7 +5,7 @@ from http import HTTPStatus from fastapi import Request from fastapi.param_functions import Query from starlette.exceptions import HTTPException -from starlette.responses import HTMLResponse # type: ignore +from starlette.responses import HTMLResponse from lnbits.core.services import create_invoice, pay_invoice diff --git a/lnbits/extensions/satsdice/models.py b/lnbits/extensions/satsdice/models.py index 2537f8d7..b0a9a4cd 100644 --- a/lnbits/extensions/satsdice/models.py +++ b/lnbits/extensions/satsdice/models.py @@ -5,8 +5,8 @@ from typing import Dict, Optional from fastapi import Request from fastapi.param_functions import Query from lnurl import Lnurl -from lnurl import encode as lnurl_encode # type: ignore -from lnurl.types import LnurlPayMetadata # type: ignore +from lnurl import encode as lnurl_encode +from lnurl.types import LnurlPayMetadata from pydantic import BaseModel from pydantic.main import BaseModel diff --git a/lnbits/extensions/satsdice/views_api.py b/lnbits/extensions/satsdice/views_api.py index 77c2f1d4..57ab26b8 100644 --- a/lnbits/extensions/satsdice/views_api.py +++ b/lnbits/extensions/satsdice/views_api.py @@ -1,7 +1,7 @@ from http import HTTPStatus from fastapi import Depends, Query, Request -from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore +from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from starlette.exceptions import HTTPException from lnbits.core.crud import get_user diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index 4fb14695..01abe24e 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -7,7 +7,7 @@ from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.helpers import urlsafe_short_hash -from ..watchonly.crud import get_config, get_fresh_address +from ..watchonly.crud import get_config, get_fresh_address # type: ignore from . import db from .helpers import fetch_onchain_balance from .models import Charges, CreateCharge, SatsPayThemes diff --git a/lnbits/extensions/scrub/models.py b/lnbits/extensions/scrub/models.py index db05e4f1..8079f358 100644 --- a/lnbits/extensions/scrub/models.py +++ b/lnbits/extensions/scrub/models.py @@ -3,7 +3,7 @@ from sqlite3 import Row from pydantic import BaseModel from starlette.requests import Request -from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnbits.lnurl import encode as lnurl_encode class CreateScrubLink(BaseModel): diff --git a/lnbits/extensions/streamalerts/crud.py b/lnbits/extensions/streamalerts/crud.py index 37583117..94113447 100644 --- a/lnbits/extensions/streamalerts/crud.py +++ b/lnbits/extensions/streamalerts/crud.py @@ -7,6 +7,7 @@ from lnbits.core.crud import get_wallet from lnbits.db import SQLITE from lnbits.helpers import urlsafe_short_hash +# todo: use the API, not direct import from ..satspay.crud import delete_charge # type: ignore from . import db from .models import CreateService, Donation, Service diff --git a/lnbits/extensions/streamalerts/views_api.py b/lnbits/extensions/streamalerts/views_api.py index 25033e27..7bf952c7 100644 --- a/lnbits/extensions/streamalerts/views_api.py +++ b/lnbits/extensions/streamalerts/views_api.py @@ -7,10 +7,13 @@ from starlette.responses import RedirectResponse from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.satspay.models import CreateCharge + +# todo: use the API, not direct import +from lnbits.extensions.satspay.models import CreateCharge # type: ignore from lnbits.utils.exchange_rates import btc_price -from ..satspay.crud import create_charge, get_charge +# todo: use the API, not direct import +from ..satspay.crud import create_charge, get_charge # type: ignore from . import streamalerts_ext from .crud import ( authenticate_service, diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py index f9e0c8ee..ca57950b 100644 --- a/lnbits/extensions/subdomains/tasks.py +++ b/lnbits/extensions/subdomains/tasks.py @@ -30,7 +30,7 @@ async def on_invoice_paid(payment: Payment) -> None: ### Create subdomain cf_response = await cloudflare_create_subdomain( - domain=domain, + domain=domain, # type: ignore subdomain=subdomain.subdomain, record_type=subdomain.record_type, ip=subdomain.ip, diff --git a/lnbits/extensions/tipjar/crud.py b/lnbits/extensions/tipjar/crud.py index 080eaf1c..3ea45d0d 100644 --- a/lnbits/extensions/tipjar/crud.py +++ b/lnbits/extensions/tipjar/crud.py @@ -2,6 +2,7 @@ from typing import Optional from lnbits.db import SQLITE +# todo: use the API, not direct import from ..satspay.crud import delete_charge # type: ignore from . import db from .models import Tip, TipJar, createTipJar diff --git a/lnbits/extensions/tipjar/views_api.py b/lnbits/extensions/tipjar/views_api.py index d0c7ac7d..7d3df920 100644 --- a/lnbits/extensions/tipjar/views_api.py +++ b/lnbits/extensions/tipjar/views_api.py @@ -6,8 +6,9 @@ from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type -from ..satspay.crud import create_charge -from ..satspay.models import CreateCharge +# todo: use the API, not direct import +from ..satspay.crud import create_charge # type: ignore +from ..satspay.models import CreateCharge # type: ignore from . import tipjar_ext from .crud import ( create_tip, diff --git a/lnbits/helpers.py b/lnbits/helpers.py index d3a4e6ea..4804bdea 100644 --- a/lnbits/helpers.py +++ b/lnbits/helpers.py @@ -4,7 +4,7 @@ import os from typing import Any, List, NamedTuple, Optional import jinja2 -import shortuuid # type: ignore +import shortuuid from lnbits.jinja2_templating import Jinja2Templates from lnbits.requestvars import g diff --git a/pyproject.toml b/pyproject.toml index 1eca3408..dd470e44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ exclude = """(?x)( )""" [[tool.mypy.overrides]] -module = "embit.*,secp256k1.*,uvicorn.*,sqlalchemy.*,sqlalchemy_aio.*,websocket.*,pyqrcode.*,cashu.*,shortuuid.*,grpc.*,lnurl.*" +module = "embit.*,secp256k1.*,uvicorn.*,sqlalchemy.*,sqlalchemy_aio.*,websocket.*,websockets.*,pyqrcode.*,cashu.*,shortuuid.*,grpc.*,lnurl.*,bitstring.*,ecdsa.*,psycopg2.*,pyngrok.*" ignore_missing_imports = "True" [tool.pytest.ini_options] From 5547bb69e6afe85eefa2b01d8c7ffb5d9939dc12 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 12:14:59 +0200 Subject: [PATCH 58/84] fix: use relative import --- lnbits/extensions/satspay/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 98c338ed..200773fb 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -11,8 +11,8 @@ from lnbits.decorators import ( require_admin_key, require_invoice_key, ) -from lnbits.extensions.satspay import satspay_ext +from . import satspay_ext from .crud import ( check_address_balance, create_charge, From 54139371f6b2e8f9a3915451704072f29f2e0289 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 15:12:32 +0200 Subject: [PATCH 59/84] fix: mypy --- lnbits/extensions/smtp/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/smtp/views_api.py b/lnbits/extensions/smtp/views_api.py index 08a05ef3..4ae1f966 100644 --- a/lnbits/extensions/smtp/views_api.py +++ b/lnbits/extensions/smtp/views_api.py @@ -5,7 +5,6 @@ from fastapi import Depends, HTTPException, Query from lnbits.core.crud import get_user from lnbits.core.services import check_transaction_status, create_invoice from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.smtp.models import CreateEmail, CreateEmailaddress from . import smtp_ext from .crud import ( @@ -19,6 +18,7 @@ from .crud import ( get_emails, update_emailaddress, ) +from .models import CreateEmail, CreateEmailaddress from .smtp import valid_email From 6235215c89d0ae223180fe53b885da7cbaee3a55 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 16:57:05 +0200 Subject: [PATCH 60/84] fix: use relative import --- lnbits/extensions/watchonly/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index a7086423..2e3fc45d 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -11,8 +11,8 @@ from embit.transaction import Transaction, TransactionInput, TransactionOutput from fastapi import Depends, HTTPException, Query, Request from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.watchonly import watchonly_ext +from . import watchonly_ext from .crud import ( create_config, create_fresh_addresses, From 5ce79d9440b836d9029023d43481bc7041691ca7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 17:35:19 +0200 Subject: [PATCH 61/84] chore: use `list[]` instead of `csv` for `ignore_missing_imports` --- pyproject.toml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dd470e44..7c83e6ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,24 @@ exclude = """(?x)( )""" [[tool.mypy.overrides]] -module = "embit.*,secp256k1.*,uvicorn.*,sqlalchemy.*,sqlalchemy_aio.*,websocket.*,websockets.*,pyqrcode.*,cashu.*,shortuuid.*,grpc.*,lnurl.*,bitstring.*,ecdsa.*,psycopg2.*,pyngrok.*" +module = [ + "embit.*", + "secp256k1.*", + "uvicorn.*", + "sqlalchemy.*", + "sqlalchemy_aio.*", + "websocket.*", + "websockets.*", + "pyqrcode.*", + "cashu.*", + "shortuuid.*", + "grpc.*", + "lnurl.*", + "bitstring.*", + "ecdsa.*", + "psycopg2.*", + "pyngrok.*" +] ignore_missing_imports = "True" [tool.pytest.ini_options] From c54a0b4f2a6fcc31dcb7cd3e1bdcaab563e10cd9 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 17:38:59 +0200 Subject: [PATCH 62/84] fix: narrow down the list of `ignore_missing_imports` modules --- pyproject.toml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7c83e6ed..560c3434 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,21 +97,7 @@ exclude = """(?x)( [[tool.mypy.overrides]] module = [ "embit.*", - "secp256k1.*", - "uvicorn.*", - "sqlalchemy.*", - "sqlalchemy_aio.*", - "websocket.*", - "websockets.*", - "pyqrcode.*", - "cashu.*", - "shortuuid.*", - "grpc.*", - "lnurl.*", - "bitstring.*", - "ecdsa.*", - "psycopg2.*", - "pyngrok.*" + "cashu.*" ] ignore_missing_imports = "True" From 9ed5a19bd0e61dd87544206746c499580bf7c953 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 9 Jan 2023 17:56:49 +0200 Subject: [PATCH 63/84] Revert "fix: narrow down the list of `ignore_missing_imports` modules" This reverts commit 7b6dfe1f498646cefe1bd8a5f25f3abe2d341b36. --- pyproject.toml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 560c3434..7c83e6ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,21 @@ exclude = """(?x)( [[tool.mypy.overrides]] module = [ "embit.*", - "cashu.*" + "secp256k1.*", + "uvicorn.*", + "sqlalchemy.*", + "sqlalchemy_aio.*", + "websocket.*", + "websockets.*", + "pyqrcode.*", + "cashu.*", + "shortuuid.*", + "grpc.*", + "lnurl.*", + "bitstring.*", + "ecdsa.*", + "psycopg2.*", + "pyngrok.*" ] ignore_missing_imports = "True" From 4d1480eff3fc54c591c7a043c5f9f46a10a8ad15 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 10 Jan 2023 09:50:35 +0200 Subject: [PATCH 64/84] fix: `mypy` - double import of `lnurldevice_ext` --- lnbits/extensions/lnurldevice/views_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py index d657c879..2fd1bd12 100644 --- a/lnbits/extensions/lnurldevice/views_api.py +++ b/lnbits/extensions/lnurldevice/views_api.py @@ -4,7 +4,6 @@ from fastapi import Depends, HTTPException, Query, Request from lnbits.core.crud import get_user from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key -from lnbits.extensions.lnurldevice import lnurldevice_ext from lnbits.utils.exchange_rates import currencies from . import lnurldevice_ext From 31c8973f93ef38cc22aed29a8d353a5c8021acc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Tue, 10 Jan 2023 11:09:09 +0100 Subject: [PATCH 65/84] remove livestream from mypy ignore and remove dead code from smtp --- lnbits/extensions/smtp/templates/smtp/display.html | 10 ---------- pyproject.toml | 1 - 2 files changed, 11 deletions(-) diff --git a/lnbits/extensions/smtp/templates/smtp/display.html b/lnbits/extensions/smtp/templates/smtp/display.html index 7db4a0d6..f60d22c1 100644 --- a/lnbits/extensions/smtp/templates/smtp/display.html +++ b/lnbits/extensions/smtp/templates/smtp/display.html @@ -38,9 +38,6 @@ type="submit" >Submit
- Cancel
@@ -104,13 +101,6 @@ }, methods: { - resetForm: function (e) { - e.preventDefault() - this.formDialog.data.subject = '' - this.formDialog.data.receiver = '' - this.formDialog.data.message = '' - }, - closeReceiveDialog: function () { var checker = this.receive.paymentChecker dismissMsg() diff --git a/pyproject.toml b/pyproject.toml index c3026c6f..e9c4aa90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,6 @@ files = "lnbits" exclude = """(?x)( ^lnbits/extensions/bleskomat. | ^lnbits/extensions/boltz. - | ^lnbits/extensions/livestream. | ^lnbits/wallets/lnd_grpc_files. )""" From d6a1aa50095fda8419f6574a6d008c29745dfef0 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 10 Jan 2023 14:20:46 +0000 Subject: [PATCH 66/84] fix error when no categories are defined --- lnbits/extensions/market/templates/market/index.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/market/templates/market/index.html b/lnbits/extensions/market/templates/market/index.html index c6227023..77e3acce 100644 --- a/lnbits/extensions/market/templates/market/index.html +++ b/lnbits/extensions/market/templates/market/index.html @@ -802,9 +802,11 @@ 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 } From c2bfc199e17d0fe7c0ceede4cfcba7b675eebb75 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 10 Jan 2023 14:26:10 +0000 Subject: [PATCH 67/84] Adds websocket and qrcode examples to example extension --- .../example/static/qrcode-example.png | Bin 0 -> 10045 bytes .../example/static/qrcode-example1.png | Bin 0 -> 34299 bytes .../example/static/websocket-example.png | Bin 0 -> 50070 bytes .../example/templates/example/index.html | 77 ++++++++++++++++-- 4 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 lnbits/extensions/example/static/qrcode-example.png create mode 100644 lnbits/extensions/example/static/qrcode-example1.png create mode 100644 lnbits/extensions/example/static/websocket-example.png diff --git a/lnbits/extensions/example/static/qrcode-example.png b/lnbits/extensions/example/static/qrcode-example.png new file mode 100644 index 0000000000000000000000000000000000000000..29781f5361e1a8973d89c8166286448c871d9d3b GIT binary patch literal 10045 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 literal 0 HcmV?d00001 diff --git a/lnbits/extensions/example/static/websocket-example.png b/lnbits/extensions/example/static/websocket-example.png new file mode 100644 index 0000000000000000000000000000000000000000..52171d309ddb02bb65479e3c54a90953b0d676f8 GIT binary patch literal 50070 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 literal 0 HcmV?d00001 diff --git a/lnbits/extensions/example/templates/example/index.html b/lnbits/extensions/example/templates/example/index.html index 03f66b5f..645e0ce4 100644 --- a/lnbits/extensions/example/templates/example/index.html +++ b/lnbits/extensions/example/templates/example/index.html @@ -51,8 +51,8 @@
- {{SITE_TITLE}} Extension Development Guide - (Collection of resources for extension developers) + Extension Development Guide + (also check the docs)
@@ -188,8 +188,7 @@

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,7 +198,7 @@ />

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

@@ -220,6 +219,8 @@ MAGICAL G EXCHANGE RATES + QR CODES + WEBSOCKETS @@ -255,6 +256,45 @@ >:
+ +
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 +336,8 @@ data: function () { return { ///// Declare models/variables ///// + protocol: window.location.protocol, + location: "//" + window.location.hostname, thingDialog: { show: false, data: {} @@ -310,7 +352,7 @@ }, ///// Where functions live ///// methods: { - exampleFunction(data) { + exampleFunction: function(data) { var theData = data LNbits.api .request( @@ -325,6 +367,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 +397,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() } }) From 1d980afb62c5998c9b9e5113cbbedbe15d124cc2 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 10 Jan 2023 14:29:05 +0000 Subject: [PATCH 68/84] format --- .../example/templates/example/index.html | 139 ++++++++++++------ 1 file changed, 93 insertions(+), 46 deletions(-) diff --git a/lnbits/extensions/example/templates/example/index.html b/lnbits/extensions/example/templates/example/index.html index 645e0ce4..36d325bb 100644 --- a/lnbits/extensions/example/templates/example/index.html +++ b/lnbits/extensions/example/templates/example/index.html @@ -52,7 +52,14 @@
Extension Development Guide - (also check the docs) + (also check the + docs)
@@ -188,7 +195,8 @@

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

Typical example of Vue components in a frontend script:

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

- Content can be - conditionally rendered using Vue's + Content can be conditionally rendered using Vue's v-if:

QR Codes
-

For most purposes use Quasar's inbuilt VueQrcode library:

+

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

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

- {% raw - %} You can use via {{protocol + location}}{% endraw %}/api/v1/qrcode/some-data-you-want-in-a-qrcode:
-
- -
- - + {% 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:
- -
+ 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 %} @@ -337,7 +384,7 @@ return { ///// Declare models/variables ///// protocol: window.location.protocol, - location: "//" + window.location.hostname, + location: '//' + window.location.hostname, thingDialog: { show: false, data: {} @@ -352,7 +399,7 @@ }, ///// Where functions live ///// methods: { - exampleFunction: function(data) { + exampleFunction: function (data) { var theData = data LNbits.api .request( @@ -369,26 +416,26 @@ }, initWs: async function () { if (location.protocol !== 'http:') { - localUrl = - 'wss://' + - document.domain + - ':' + - location.port + - '/api/v1/ws/32872r23g29' + 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 - }) -}, + 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) } From ee3ddb2ef64d8c1add880417640a11fd0b7aba28 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 10 Jan 2023 14:39:27 +0000 Subject: [PATCH 69/84] DO NOT ADD NEW DEPENDENCIES --- docs/devs/extensions.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/devs/extensions.md b/docs/devs/extensions.md index cd81a021..5e5c2e8b 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 From 6e719c8fba41cc70c135a0bc1ae610998041ed81 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 15:46:03 +0100 Subject: [PATCH 70/84] update mock data to 6 --- tests/data/mock_data.zip | Bin 43064 -> 49254 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/mock_data.zip b/tests/data/mock_data.zip index d5169e128e9d84563a8f8e090ea1e0346bb03308..f14bb45f2474027dffb125d660aab9956920428a 100644 GIT binary patch delta 24627 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 delta 19003 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 XBw!p4NbspKs4=JuFfja{4vJ_13KL@` From 162960956981c551fe232217ae07216493ee70e4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 15:59:25 +0100 Subject: [PATCH 71/84] remove subfolder from mock_data.zip --- tests/data/mock_data.zip | Bin 49254 -> 47284 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/mock_data.zip b/tests/data/mock_data.zip index f14bb45f2474027dffb125d660aab9956920428a..8356cd5474bf6b97754537bcf43ff3287b2d14ef 100644 GIT binary patch delta 2950 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 Ygfw}<4mLgm1_Oo&5e9}It3jav086{iTL1t6 From 64c330459b128e451d2c621325f1b2299af918d7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:16:17 +0100 Subject: [PATCH 72/84] conv.py: warn if table is empty --- tools/conv.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tools/conv.py b/tools/conv.py index 2ba723f2..a1c0699d 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -1,3 +1,8 @@ +# Python script to migrate an LNbits SQLite DB to Postgres +# All credits to @Fritz446 for the awesome work + +# pip install psycopg2 OR psycopg2-binary + import argparse import os import sqlite3 @@ -8,13 +13,6 @@ import psycopg2 from lnbits.settings import settings -# Python script to migrate an LNbits SQLite DB to Postgres -# All credits to @Fritz446 for the awesome work - -# pip install psycopg2 OR psycopg2-binary - -# Change these values as needed - sqfolder = settings.lnbits_data_folder db_url = settings.lnbits_database_url @@ -31,7 +29,7 @@ else: pgschema = "" -def get_sqlite_cursor(sqdb) -> sqlite3: +def get_sqlite_cursor(sqdb): consq = sqlite3.connect(sqdb) return consq.cursor() @@ -112,12 +110,15 @@ def migrate_core(file: str, exclude_tables: List[str] = []): def migrate_ext(file: str): filename = os.path.basename(file) schema = filename.replace("ext_", "").split(".")[0] - print(f"Migrating ext: {file}.{schema}") + print(f"Migrating ext: {schema} from file {file}") migrate_db(file, schema) print(f"✅ Migrated ext: {schema}") def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): + # first we check if this file exists: + assert os.path.isfile(file), f"{file} does not exist!" + sq = get_sqlite_cursor(file) tables = sq.execute( """ @@ -126,6 +127,9 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): """ ).fetchall() + if len(tables) == 0: + print(f"⚠️ You sneaky dev! Schema {schema} is empty!") + for table in tables: tableName = table[0] print(f"Migrating table {tableName}") From 3f61286cadf95a7b3a0a8bc9c457d330039661e5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:19:56 +0100 Subject: [PATCH 73/84] correct placement of warning --- tools/conv.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/conv.py b/tools/conv.py index a1c0699d..58cade5e 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -127,9 +127,6 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): """ ).fetchall() - if len(tables) == 0: - print(f"⚠️ You sneaky dev! Schema {schema} is empty!") - for table in tables: tableName = table[0] print(f"Migrating table {tableName}") @@ -143,6 +140,10 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): q = build_insert_query(schema, tableName, columns) data = sq.execute(f"SELECT * FROM {tableName};").fetchall() + + if len(data) == 0: + print(f"⚠️ You sneaky dev! Table {tableName} is empty!") + insert_to_pg(q, data) sq.close() From a1a75ddf0bec55013b7d29f46b19d6bcb44379d0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 16:25:28 +0100 Subject: [PATCH 74/84] change sign --- tools/conv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/conv.py b/tools/conv.py index 58cade5e..f01295fc 100644 --- a/tools/conv.py +++ b/tools/conv.py @@ -142,7 +142,7 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []): data = sq.execute(f"SELECT * FROM {tableName};").fetchall() if len(data) == 0: - print(f"⚠️ You sneaky dev! Table {tableName} is empty!") + print(f"🛑 You sneaky dev! Table {tableName} is empty!") insert_to_pg(q, data) sq.close() From 2718773b606a7960469d77dea257d4c39552c5a4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:25:12 +0100 Subject: [PATCH 75/84] fix: add missing entries --- lnbits/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/settings.py b/lnbits/settings.py index 59eb376b..6ec4db0c 100644 --- a/lnbits/settings.py +++ b/lnbits/settings.py @@ -98,6 +98,8 @@ class LndRestFundingSource(LNbitsSettings): lnd_cert: Optional[str] = Field(default=None) lnd_admin_macaroon: Optional[str] = Field(default=None) lnd_invoice_macaroon: Optional[str] = Field(default=None) + lnd_rest_admin_macaroon: Optional[str] = Field(default=None) + lnd_rest_invoice_macaroon: Optional[str] = Field(default=None) class LndGrpcFundingSource(LNbitsSettings): From 822a30412780c4bf8a7ed624ba391874d71a80d0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 10 Jan 2023 17:35:43 +0100 Subject: [PATCH 76/84] specify in settings panel --- lnbits/core/templates/admin/index.html | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lnbits/core/templates/admin/index.html b/lnbits/core/templates/admin/index.html index 81357101..3e688fd6 100644 --- a/lnbits/core/templates/admin/index.html +++ b/lnbits/core/templates/admin/index.html @@ -141,15 +141,15 @@ return { settings: {}, lnbits_theme_options: [ - 'classic', - 'bitcoin', - 'flamingo', - 'freedom', - 'mint', - 'autumn', - 'monochrome', - 'salvador' - ], + 'classic', + 'bitcoin', + 'flamingo', + 'freedom', + 'mint', + 'autumn', + 'monochrome', + 'salvador' + ], formData: {}, formAddAdmin: '', formAddUser: '', @@ -204,11 +204,11 @@ value: null, label: 'Certificate' }, - lnd_admin_macaroon: { + lnd_rest_admin_macaroon: { value: null, label: 'Admin Macaroon' }, - lnd_invoice_macaroon: { + lnd_rest_invoice_macaroon: { value: null, label: 'Invoice Macaroon' } From f237bdd4b79b116268605a7ab0eca418018560fb Mon Sep 17 00:00:00 2001 From: Joel Klabo Date: Tue, 10 Jan 2023 09:35:43 -0800 Subject: [PATCH 77/84] Fix Satoshis Formatting on NIP-5 Index Page --- lnbits/extensions/nostrnip5/templates/nostrnip5/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html index 820d8718..079fbf58 100644 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html +++ b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html @@ -280,7 +280,9 @@ 'YYYY-MM-DD HH:mm' ) - obj.amount = parseFloat(obj.amount / 100).toFixed(2) + if (obj.currency != "Satoshis") { + obj.amount = parseFloat(obj.amount / 100).toFixed(2) + } return obj } From 2ae97817a720f4735e2822cda0e0842aeaf077a6 Mon Sep 17 00:00:00 2001 From: Joel Klabo Date: Tue, 10 Jan 2023 09:41:15 -0800 Subject: [PATCH 78/84] Run Formatting --- lnbits/extensions/nostrnip5/templates/nostrnip5/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html index 079fbf58..6b805ccc 100644 --- a/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html +++ b/lnbits/extensions/nostrnip5/templates/nostrnip5/index.html @@ -280,7 +280,7 @@ 'YYYY-MM-DD HH:mm' ) - if (obj.currency != "Satoshis") { + if (obj.currency != 'Satoshis') { obj.amount = parseFloat(obj.amount / 100).toFixed(2) } From a85cd942340163459a573e6d70e34b047fd40b96 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 10 Jan 2023 18:30:09 +0000 Subject: [PATCH 79/84] Elaborated nostr reference --- lnbits/extensions/market/templates/market/_tables.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/market/templates/market/_tables.html b/lnbits/extensions/market/templates/market/_tables.html index c6fd665b..b6abcb58 100644 --- a/lnbits/extensions/market/templates/market/_tables.html +++ b/lnbits/extensions/market/templates/market/_tables.html @@ -200,7 +200,7 @@ :href="props.row.wallet" target="_blank" > - Link to pass to stall relay + Disabled: link to pass to stall relays when using nostr {{ col.value }} From 2602f044dabb45c0fbfa718b0870103bc2afb7ab Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 10 Jan 2023 18:37:54 +0000 Subject: [PATCH 80/84] format --- lnbits/extensions/market/templates/market/_tables.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/market/templates/market/_tables.html b/lnbits/extensions/market/templates/market/_tables.html index b6abcb58..280bb9f1 100644 --- a/lnbits/extensions/market/templates/market/_tables.html +++ b/lnbits/extensions/market/templates/market/_tables.html @@ -200,7 +200,10 @@ :href="props.row.wallet" target="_blank" > - Disabled: link to pass to stall relays when using nostr + Disabled: link to pass to stall relays when using + nostr {{ col.value }} From dbaf8a165b6e9d1ba2f925ef9b1ccc76c46639a5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 11 Jan 2023 12:19:35 +0100 Subject: [PATCH 81/84] update poetry.lock to 1.3.1 --- poetry.lock | 117 +++++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/poetry.lock b/poetry.lock index c0e1258c..698d700d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -428,63 +428,63 @@ files = [ [[package]] name = "coverage" -version = "7.0.1" +version = "7.0.5" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, - {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, - {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, - {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, - {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, - {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, - {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, - {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, - {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, - {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, - {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, - {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, - {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, - {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, - {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, - {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, - {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, - {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, - {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, - {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, - {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, + {file = "coverage-7.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b"}, + {file = "coverage-7.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89"}, + {file = "coverage-7.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40"}, + {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2"}, + {file = "coverage-7.0.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289"}, + {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0"}, + {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8"}, + {file = "coverage-7.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809"}, + {file = "coverage-7.0.5-cp310-cp310-win32.whl", hash = "sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21"}, + {file = "coverage-7.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad"}, + {file = "coverage-7.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca"}, + {file = "coverage-7.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0"}, + {file = "coverage-7.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196"}, + {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0"}, + {file = "coverage-7.0.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc"}, + {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45"}, + {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757"}, + {file = "coverage-7.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095"}, + {file = "coverage-7.0.5-cp311-cp311-win32.whl", hash = "sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831"}, + {file = "coverage-7.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea"}, + {file = "coverage-7.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c"}, + {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7"}, + {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96"}, + {file = "coverage-7.0.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029"}, + {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3"}, + {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2"}, + {file = "coverage-7.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47"}, + {file = "coverage-7.0.5-cp37-cp37m-win32.whl", hash = "sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882"}, + {file = "coverage-7.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d"}, + {file = "coverage-7.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb"}, + {file = "coverage-7.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6"}, + {file = "coverage-7.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd"}, + {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a"}, + {file = "coverage-7.0.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2"}, + {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7"}, + {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0"}, + {file = "coverage-7.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499"}, + {file = "coverage-7.0.5-cp38-cp38-win32.whl", hash = "sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16"}, + {file = "coverage-7.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af"}, + {file = "coverage-7.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab"}, + {file = "coverage-7.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637"}, + {file = "coverage-7.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4"}, + {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded"}, + {file = "coverage-7.0.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b"}, + {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209"}, + {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78"}, + {file = "coverage-7.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1"}, + {file = "coverage-7.0.5-cp39-cp39-win32.whl", hash = "sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904"}, + {file = "coverage-7.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f"}, + {file = "coverage-7.0.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0"}, + {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, ] [package.dependencies] @@ -1133,19 +1133,22 @@ files = [ [[package]] name = "platformdirs" -version = "2.6.0" +version = "2.6.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"}, - {file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"}, + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} + [package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" From c57ddf6aff08327d9e72dbc7802562b9e876c179 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 11 Jan 2023 12:40:02 +0100 Subject: [PATCH 82/84] ignore livestream --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1dca70f6..88a9dd55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,7 +110,8 @@ module = [ "bitstring.*", "ecdsa.*", "psycopg2.*", - "pyngrok.*" + "pyngrok.*", + "livestream.*" ] ignore_missing_imports = "True" From 6afdff0f29f84a173e7de63f111fde891c93f99e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 11 Jan 2023 12:49:11 +0100 Subject: [PATCH 83/84] relative import --- lnbits/extensions/livestream/views_api.py | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lnbits/extensions/livestream/views_api.py b/lnbits/extensions/livestream/views_api.py index 63a01742..b3d10b07 100644 --- a/lnbits/extensions/livestream/views_api.py +++ b/lnbits/extensions/livestream/views_api.py @@ -4,7 +4,7 @@ from fastapi import Depends, HTTPException, Request from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from lnbits.decorators import WalletTypeInfo, get_key_type -from lnbits.extensions.livestream.models import CreateTrack +from .models import CreateTrack from . import livestream_ext from .crud import ( diff --git a/pyproject.toml b/pyproject.toml index 88a9dd55..46808433 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,6 @@ module = [ "ecdsa.*", "psycopg2.*", "pyngrok.*", - "livestream.*" ] ignore_missing_imports = "True" From 1f23d45d11ab1645adfa45c30cee3442b76114e4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 11 Jan 2023 12:52:47 +0100 Subject: [PATCH 84/84] make format --- lnbits/extensions/livestream/views_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnbits/extensions/livestream/views_api.py b/lnbits/extensions/livestream/views_api.py index b3d10b07..b346f353 100644 --- a/lnbits/extensions/livestream/views_api.py +++ b/lnbits/extensions/livestream/views_api.py @@ -4,7 +4,6 @@ from fastapi import Depends, HTTPException, Request from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from lnbits.decorators import WalletTypeInfo, get_key_type -from .models import CreateTrack from . import livestream_ext from .crud import ( @@ -18,6 +17,7 @@ from .crud import ( update_livestream_fee, update_track, ) +from .models import CreateTrack @livestream_ext.get("/api/v1/livestream")