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 000000000..277294592 --- /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 000000000..663920cd7 --- /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 000000000..200d1220f --- /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 000000000..99d7c362d --- /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 000000000..bfeb7517f --- /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 000000000..d732ef376 --- /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 000000000..b8efeae8f --- /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 000000000..e8d270a9b --- /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 200d1220f..27c4df2d0 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 000000000..007144f0e --- /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 d732ef376..000000000 --- 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 b8efeae8f..131c03b2a 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 007144f0e..0d68f0a97 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 0d68f0a97..95bdc14b0 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 000000000..88e64e1fd --- /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 95bdc14b0..233fce34d 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 537ed8872..60b31a093 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 99d7c362d..86edcf099 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 60b31a093..98067fd7e 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 e3d0fd35d..b6966bfa4 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 c280ad7d9..11429665e 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 d80da678a..6868455e4 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 2d7aae6b3..99687032b 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 487411ed6..7409b03e5 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 ce70fb812..c0e1258ce 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 120124e75..97f6d9ef5 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 e3c1a3d71..4f945a791 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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 At^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 89e431e5c..f50912b3a 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 0034f4a79..5d617da99 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 d081332f5..4e9787692 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 97f803a31..ca12f16bd 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 0c169a71f..63a017428 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 e2116ed08..d251c91e2 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 97f6d9ef5..05d1c9a70 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 000000000..69630610d --- /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 86edcf099..67455d6b5 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 bfeb7517f..751191f0b 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 98067fd7e..5c2f9ae42 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 b1e063628..858d32550 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 d3b3f935d..8df50eb68 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 0fcc3ce88..e69db355a 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 85741b434..6af3bd721 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 8df50eb68..23eca7748 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 6af3bd721..1006edeb8 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 23eca7748..c2768e250 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 c2768e250..75549349e 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 9f8b26da0..460f7d005 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 3bc169dd0..0f81b99f2 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 80ee1085d..4b7bd9f9c 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 b8912fa22..2bcbf0548 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 b1459ee3e..820d87185 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 000000000..339f210a6 --- /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 000000000..1d951b310 --- /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 000000000..8b2cb7640 --- /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 000000000..e5ab1d1f7 --- /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 000000000..16d501665 --- /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 000000000..0b3138e9a --- /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 000000000..b9a2dce38 --- /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 000000000..ed569daeb --- /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 000000000..c7ed44de8 --- /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 000000000..7db4a0d66 --- /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 000000000..bf43ad7fe --- /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 000000000..1ba53341d --- /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 000000000..5001c1a5a --- /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 e5ab1d1f7..2eee4c3d9 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 0b3138e9a..e2f3fc13f 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 b9a2dce38..a8830254d 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 ed569daeb..9c544473f 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 1ba53341d..df208a77b 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 5001c1a5a..08a05ef3d 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 f2ff24bdd..000000000 --- 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 339f210a6..5b7757e2f 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 a8830254d..e77bc0fa5 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 c7ed44de8..cfb811d16 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 000000000..f2ff24bdd --- /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 61e47cfec..1d9abcec4 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 c6265d6c5..24d63bfd9 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 819d1248f..8cebc6cc0 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 c6e15ea6f..5bb43661f 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 03dbbc8d1..b41cff10b 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 74125dde8..8db9ff573 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 5bb43661f..a70864230 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 43c06389d..79768d628 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 460f7d005..871857a75 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 871857a75..2bbdfb110 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 1d951b310..e7419852a 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 8b2cb7640..325ebfa78 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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 WO_y@~_~Ru2KuSzrv|898@c#g#4ghQb literal 0 HcmV?d00001 From b764c93e7c82e2c2ab33c7a1456202a8eddafcf6 Mon Sep 17 00:00:00 2001 From: Vlad Stan 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 1b58a43d7..080eaf1cf 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 ffcb612b3..c9542bc10 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 045bc0fc9..31703e8d7 100644 --- a/lnbits/extensions/market/views_api.py +++ b/lnbits/extensions/market/views_api.py @@ -113,6 +113,23 @@ async def api_market_product_create( if stall.currency != "sat": data.price *= settings.fiat_base_multiplier + if data.image: + image_is_url = data.image.startswith("https://") or data.image.startswith( + "http://" + ) + + if not image_is_url: + + def size(b64string): + return int((len(b64string) * 3) / 4 - b64string.count("=", -2)) + + image_size = size(data.image) / 1024 + if image_size > 100: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"Image size is too big, {int(image_size)}Kb. Max: 100kb, Compress the image at https://tinypng.com, or use an URL.", + ) + if product_id: product = await get_market_product(product_id) if not product: 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 12adc05a6..2060d08a8 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 182df743a..16baae1e3 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 34de20fae..978ea0e15 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 66b215f27..f9640de1c 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 8ad9772ca..9aec173ec 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 f1be4f0da..a6256a415 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 c6766423c..d657c879b 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 b41cff10b..c3026c6f0 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 16baae1e3..0ab520da7 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 978ea0e15..b14bd613a 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 2060d08a8..fe71b981f 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 b14bd613a..eba2a6930 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 c78639e0b..5124dcd4d 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 d2a8dd0ae..a0ab84b3f 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 5124dcd4d..152948171 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 c3026c6f0..1eca3408c 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 5eae7373d..945e2d281 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 4ed3932fb..5ec9b916e 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 679cb5153..cf8feaf02 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 d9e50e9d4..7d15a55f7 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 35d6eaff8..4462688b6 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 e2bf7c91c..88a1a4a38 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 23bc57069..27ec7a973 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 992e5eb63..2c636351b 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 15a4403db..175b00bd4 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 0134fe825..25033e274 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 679ca8436..5b951b21a 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 2b20bd1fd..9fbae4f3a 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 0bc401581..4e20208c9 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 41ba56446..66254d11d 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 31383667a..eca1bf50a 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 8dc973e76..eefb2f990 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 56afc1765..b5773bbe9 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 1bef7bf21..77f3cf33a 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 c8ec5646d..4f4ec9e29 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 18ca14cb7..34f4033e8 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 e6507bba9..83d8ce274 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 d8ededf30..b0bc83bc8 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 b9b43ccfb..7ca2fc961 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 7b66366de..ff69dfba9 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 fcadce279..48118087c 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 8f6aa6237..99de459cf 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 42ea29267..1c6b6f711 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 0fa739b08..a7bf0761a 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 27ec7a973..e6c8eeff1 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 81c8b24e3..d84ecd2d9 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 1e9c6c09b..f766d8cbd 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 2537f8d79..b0a9a4cda 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 77c2f1d40..57ab26b84 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 4fb14695e..01abe24e4 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 db05e4f17..8079f358a 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 37583117b..941134473 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 25033e274..7bf952c7d 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 f9e0c8eef..ca57950b3 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 080eaf1cf..3ea45d0d2 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 d0c7ac7d7..7d3df9205 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 d3a4e6ea1..4804bdeab 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 1eca3408c..dd470e44a 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 98c338edc..200773fb8 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 08a05ef3d..4ae1f9665 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 a70864230..2e3fc45d8 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 dd470e44a..7c83e6ede 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 7c83e6ede..560c34344 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 560c34344..7c83e6ede 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 d657c879b..2fd1bd122 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 7db4a0d66..f60d22c15 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 c3026c6f0..e9c4aa90e 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 c6227023f..77e3acced 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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 Gq5liq_H`xz literal 0 HcmV?d00001 diff --git a/lnbits/extensions/example/static/qrcode-example1.png b/lnbits/extensions/example/static/qrcode-example1.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c748f1115ecd0cb6448d17f94456c7dad274e0 GIT binary patch literal 34299 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 B66pW{ literal 0 HcmV?d00001 diff --git a/lnbits/extensions/example/templates/example/index.html b/lnbits/extensions/example/templates/example/index.html index 03f66b5f7..645e0ce4f 100644 --- a/lnbits/extensions/example/templates/example/index.html +++ b/lnbits/extensions/example/templates/example/index.html @@ -51,8 +51,8 @@ @@ -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 645e0ce4f..36d325bb8 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 cd81a0210..5e5c2e8bc 100644 --- a/docs/devs/extensions.md +++ b/docs/devs/extensions.md @@ -28,7 +28,9 @@ Going over the example extension's structure: Adding new dependencies ----------------------- -If for some reason your extensions needs a new python package to work, you can add a new package using `venv`, or `poerty`: +DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others. + +If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `venv`, or `poerty`: ```sh $ poetry add @@ -37,8 +39,7 @@ $ ./venv/bin/pip install ``` **But we need an extra step to make sure LNbits doesn't break in production.** -Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry`. -`nix` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`. +Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`. SQLite to PostgreSQL migration 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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 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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 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 2ba723f25..a1c0699d0 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 a1c0699d0..58cade5e5 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 58cade5e5..f01295fc7 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 59eb376b6..6ec4db0c7 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 813571014..3e688fd65 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 820d87185..079fbf586 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 079fbf586..6b805cccf 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 c6fd665be..b6abcb589 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 b6abcb589..280bb9f18 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 c0e1258ce..698d700dc 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 1dca70f64..88a9dd557 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 63a017428..b3d10b079 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 88a9dd557..46808433c 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 b3d10b079..b346f3534 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")