Merge branch 'main' into draggablecopilot

This commit is contained in:
ben 2023-01-16 10:01:40 +00:00
commit 615734e4a9
34 changed files with 751 additions and 516 deletions

View file

@ -54,8 +54,9 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador"
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet,
# LndWallet, LndRestWallet, CoreLightningWallet, EclairWallet,
# LnTipsWallet, LNbitsWallet, SparkWallet, FakeWallet,
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
# just so you can see the UI before dealing with this file.
@ -76,6 +77,14 @@ CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
LNBITS_ENDPOINT=https://legend.lnbits.com
LNBITS_KEY=LNBITS_ADMIN_KEY
# LndWallet
LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=10009
LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_GRPC_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
# To use an AES-encrypted macaroon, set
# LND_GRPC_MACAROON="eNcRyPtEdMaCaRoOn"
# LndRestWallet
LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"

View file

@ -15,8 +15,8 @@ By default, LNbits will use SQLite as its database. You can also use PostgreSQL
If you have problems installing LNbits using these instructions, please have a look at the [Troubleshooting](#troubleshooting) section.
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version
sudo apt update
@ -50,9 +50,10 @@ poetry run lnbits
#### Updating the server
```
cd lnbits-legend/
cd lnbits
# Stop LNbits with `ctrl + x`
git pull
# Keep your poetry install up to date, this can be done with `poetry self update`
poetry install --only main
# Start LNbits with `poetry run lnbits`
```
@ -62,8 +63,8 @@ poetry install --only main
> note: currently not supported while we make some architectural changes on the path to leave beta
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# Modern debian distros usually include Nix, however you can install with:
# 'sh <(curl -L https://nixos.org/nix/install) --daemon', or use setup here https://nixos.org/download.html#nix-verify-installation
@ -82,8 +83,8 @@ LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT
## Option 3: venv
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# ensure you have virtualenv installed, on debian/ubuntu 'apt install python3.9-venv'
python3.9 -m venv venv
# If you have problems here, try `sudo apt install -y pkg-config libpq-dev`
@ -105,9 +106,9 @@ If you want to host LNbits on the internet, run with the option `--host 0.0.0.0`
## Option 4: Docker
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend
docker build -t lnbits-legend .
git clone https://github.com/lnbits/lnbits.git
cd lnbits
docker build -t lnbits .
cp .env.example .env
mkdir data
docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits-legend
@ -135,8 +136,8 @@ You can either run those commands, then `source ~/.bash_profile` or, if you don'
Once installed, run the following commands.
```
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend
git clone https://github.com/lnbits/lnbits.git
cd lnbits
fly auth login
[complete login process]
fly launch
@ -437,8 +438,8 @@ If you want to run LNbits on your Umbrel but want it to be reached through clear
To install using docker you first need to build the docker image as:
```
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend
git clone https://github.com/lnbits/lnbits.git
cd lnbits
docker build -t lnbits-legend .
```

View file

@ -66,7 +66,7 @@
outline
color="grey"
type="a"
href="https://github.com/lnbits/lnbits-legend"
href="https://github.com/lnbits/lnbits"
target="_blank"
rel="noopener"
>View project in GitHub</q-btn

View file

@ -1,5 +1,6 @@
import json
import os
from typing import Callable, Dict, Union
import httpx
@ -12,7 +13,9 @@ fiat_currencies = json.load(
)
)
exchange_rate_providers = {
exchange_rate_providers: dict[
str, dict[str, Union[str, Callable[[dict, dict], str]]]
] = {
"bitfinex": {
"name": "Bitfinex",
"domain": "bitfinex.com",
@ -65,17 +68,19 @@ async def fetch_fiat_exchange_rate(currency: str, provider: str):
"to": currency.lower(),
}
url = exchange_rate_providers[provider]["api_url"]
if url:
api_url_or_none = exchange_rate_providers[provider]["api_url"]
if api_url_or_none is not None:
api_url = str(api_url_or_none)
for key in replacements.keys():
url = url.replace("{" + key + "}", replacements[key])
api_url = api_url.replace("{" + key + "}", replacements[key])
async with httpx.AsyncClient() as client:
r = await client.get(url)
r = await client.get(api_url)
r.raise_for_status()
data = r.json()
else:
data = {}
getter = exchange_rate_providers[provider]["getter"]
rate = float(getter(data, replacements))
print(getter)
if callable(getter):
rate = float(getter(data, replacements))
return rate

View file

@ -1,11 +1,11 @@
import base64
import hashlib
import hmac
import urllib
from http import HTTPStatus
from typing import Dict
from urllib import parse
from starlette.requests import Request
from fastapi import Request
def generate_bleskomat_lnurl_hash(secret: str):
@ -22,7 +22,7 @@ def generate_bleskomat_lnurl_signature(
elif api_key_encoding == "base64":
key = base64.b64decode(api_key_secret)
else:
key = bytes(f"{api_key_secret}")
key = bytes.fromhex(api_key_secret)
return hmac.new(key=key, msg=payload.encode(), digestmod=hashlib.sha256).hexdigest()
@ -57,8 +57,8 @@ class LnurlValidationError(Exception):
pass
def prepare_lnurl_params(tag: str, query: Dict[str, str]):
params = {}
def prepare_lnurl_params(tag: str, query: dict) -> dict:
params: dict = {}
if not is_supported_lnurl_subprotocol(tag):
raise LnurlValidationError(f'Unsupported subprotocol: "{tag}"')
if tag == "withdrawRequest":
@ -85,15 +85,15 @@ def query_to_signing_payload(query: Dict[str, str]) -> str:
payload = []
for key in sorted_keys:
if not key == "signature":
encoded_key = urllib.parse.quote(key, safe=encode_uri_component_safe_chars)
encoded_value = urllib.parse.quote(
encoded_key = parse.quote(key, safe=encode_uri_component_safe_chars)
encoded_value = parse.quote(
query[key], safe=encode_uri_component_safe_chars
)
payload.append(f"{encoded_key}={encoded_value}")
return "&".join(payload)
unshorten_rules = {
unshorten_rules: dict[str, dict] = {
"query": {"n": "nonce", "s": "signature", "t": "tag"},
"tags": {
"c": "channelRequest",
@ -114,7 +114,7 @@ unshorten_rules = {
}
def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
def unshorten_lnurl_query(query: dict) -> Dict[str, str]:
new_query = {}
rules = unshorten_rules
if "tag" in query:
@ -131,9 +131,9 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
if not tag in rules["params"]:
raise LnurlValidationError(f'Unknown tag: "{tag}"')
for key in query:
if key in rules["params"][tag]:
if key in rules["params"][str(tag)]:
short_param_key = key
long_param_key = rules["params"][tag][short_param_key]
long_param_key = rules["params"][str(tag)][short_param_key]
if short_param_key in query:
new_query[long_param_key] = query[short_param_key]
else:
@ -146,7 +146,7 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
if short_key in query:
new_query[long_key] = query[short_key]
else:
new_query[long_key] = query[long_key]
new_query[long_key] = query[str(long_key)]
else:
# Keep unknown key/value pairs unchanged:
new_query[key] = query[key]

View file

@ -1,6 +1,5 @@
import json
import math
import traceback
from http import HTTPStatus
from loguru import logger
@ -28,7 +27,7 @@ from .helpers import (
@bleskomat_ext.get("/u", name="bleskomat.api_bleskomat_lnurl")
async def api_bleskomat_lnurl(req: Request):
try:
query = req.query_params
query = dict(req.query_params)
# Unshorten query if "s" is used instead of "signature".
if "s" in query:
@ -89,11 +88,15 @@ async def api_bleskomat_lnurl(req: Request):
# Convert to msats:
params[key] = int(amount_sats_less_fee * 1e3)
except LnurlValidationError as e:
raise LnurlHttpError(e.message, HTTPStatus.BAD_REQUEST)
raise LnurlHttpError(str(e), HTTPStatus.BAD_REQUEST)
# Create a new LNURL using the query parameters provided in the signed URL.
params = json.JSONEncoder().encode(params)
json_params = json.JSONEncoder().encode(params)
lnurl = await create_bleskomat_lnurl(
bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1
bleskomat=bleskomat,
secret=secret,
tag=tag,
params=json_params,
uses=1,
)
# Reply with LNURL response object.

View file

@ -2,10 +2,9 @@ import json
import time
from typing import Dict
from fastapi.params import Query
from fastapi import Query, Request
from loguru import logger
from pydantic import BaseModel, validator
from starlette.requests import Request
from lnbits import bolt11
from lnbits.core.services import PaymentFailure, pay_invoice
@ -80,7 +79,7 @@ class BleskomatLnurl(BaseModel):
response["k1"] = secret
return response
def validate_action(self, query: Dict[str, str]) -> None:
def validate_action(self, query) -> None:
tag = self.tag
params = json.loads(self.params)
# Perform tag-specific checks.
@ -109,7 +108,7 @@ class BleskomatLnurl(BaseModel):
else:
raise LnurlValidationError(f'Unknown subprotocol: "{tag}"')
async def execute_action(self, query: Dict[str, str]):
async def execute_action(self, query):
self.validate_action(query)
used = False
async with db.connect() as conn:

View file

@ -1,5 +1,4 @@
from fastapi import Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse

View file

@ -6,7 +6,6 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.bleskomat.models import CreateBleskomat
from . import bleskomat_ext
from .crud import (
@ -17,6 +16,7 @@ from .crud import (
update_bleskomat,
)
from .exchange_rates import fetch_fiat_exchange_rate
from .models import CreateBleskomat
@bleskomat_ext.get("/api/v1/bleskomats")
@ -27,7 +27,8 @@ async def api_bleskomats(
wallet_ids = [wallet.wallet.id]
if all_wallets:
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 []
return [bleskomat.dict() for bleskomat in await get_bleskomats(wallet_ids)]
@ -54,9 +55,9 @@ async def api_bleskomat_create_or_update(
wallet: WalletTypeInfo = Depends(require_admin_key),
bleskomat_id=None,
):
fiat_currency = data.fiat_currency
exchange_rate_provider = data.exchange_rate_provider
try:
fiat_currency = data.fiat_currency
exchange_rate_provider = data.exchange_rate_provider
await fetch_fiat_exchange_rate(
currency=fiat_currency, provider=exchange_rate_provider
)
@ -79,6 +80,7 @@ async def api_bleskomat_create_or_update(
else:
bleskomat = await create_bleskomat(wallet_id=wallet.wallet.id, data=data)
assert bleskomat
return bleskomat.dict()

View file

@ -32,7 +32,7 @@
<a
class="text-secondary"
target="_blank"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/boltz"
href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/boltz"
>More details</a
>
</p>

View file

@ -4,18 +4,19 @@
<q-card class="q-pa-lg q-mb-xl">
<q-card-section class="q-pa-none">
<center>
<q-icon
name="account_balance"
class="text-grey"
style="font-size: 10rem"
></q-icon>
<h4 class="q-mt-none q-mb-md">{{ mint_name }}</h4>
<q-img
src="/cashu/static/image/cashu.png"
spinner-color="white"
style="max-width: 20%"
></q-img>
<h4 class="q-mt-sm q-mb-md">{{ mint_name }}</h4>
<!-- <a class="text-secondary">Mint URL: {{testfield}} </a> <br /> -->
<a
class="text-secondary"
class="q-my-xl text-white"
style="font-size: 1.5rem"
href="../wallet?mint_id={{ mint_id }}"
>Open wallet</a
>click to open wallet</a
>
</center>
</q-card-section>
@ -58,24 +59,34 @@
</p>
<p>
<strong>This service is in BETA</strong> <br />
We hold no responsibility for people losing access to funds. Use at
your own risk!
Cashu is still experimental and in active development. There are
likely bugs in this implementation so please use this with caution. We
hold no responsibility for people losing access to funds. Use at your
own risk!
</p>
</q-card-section>
</q-card>
</div>
{% endblock %} {% block scripts %}
<script>
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {}
}
})
</script>
{% endblock %}
</div>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
testfield: 'asd',
mintURL: {
location: window.location,
base_url: location.protocol + '//' + location.host + location.pathname
}
}
}
})
</script>
{% endblock %}

View file

@ -1479,15 +1479,15 @@ page_container %}
},
constructOutputs: async function (amounts, secrets) {
const blindedMessages = []
const outputs = []
const rs = []
for (let i = 0; i < amounts.length; i++) {
const {B_, r} = await step1Alice(secrets[i])
blindedMessages.push({amount: amounts[i], B_: B_})
outputs.push({amount: amounts[i], B_: B_})
rs.push(r)
}
return {
blindedMessages,
outputs,
rs
}
},
@ -1581,25 +1581,26 @@ page_container %}
mintApi: async function (amounts, payment_hash, verbose = true) {
/*
asks the mint to check whether the invoice with payment_hash has been paid
and requests signing of the attached outputs (blindedMessages)
and requests signing of the attached outputs.
*/
console.log('### promises', payment_hash)
try {
let secrets = await this.generateSecrets(amounts)
let {blindedMessages, rs} = await this.constructOutputs(
amounts,
secrets
)
let {outputs, rs} = await this.constructOutputs(amounts, secrets)
const promises = await LNbits.api.request(
'POST',
`/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`,
'',
{
blinded_messages: blindedMessages
outputs
}
)
console.log('### promises data', promises.data)
let proofs = await this.constructProofs(promises.data, secrets, rs)
console.log('### promises data', promises.data.promises)
let proofs = await this.constructProofs(
promises.data.promises,
secrets,
rs
)
return proofs
} catch (error) {
console.error(error)
@ -1682,16 +1683,11 @@ page_container %}
'number of secrets does not match number of outputs.'
)
}
let {blindedMessages, rs} = await this.constructOutputs(
amounts,
secrets
)
let {outputs, rs} = await this.constructOutputs(amounts, secrets)
const payload = {
amount,
proofs,
outputs: {
blinded_messages: blindedMessages
}
outputs
}
console.log('payload', JSON.stringify(payload))
@ -1881,10 +1877,7 @@ page_container %}
'amount with fees',
amount
)
// if (amount > balance()) {
// LNbits.utils.notifyApiError('Balance too low')
// return
// }
let {fristProofs, scndProofs} = await this.splitToSend(
this.proofs,
amount
@ -2132,6 +2125,19 @@ page_container %}
return paid
},
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
secret: token.secrets[index],
r: token.rs[index]
}
}
}
},
////////////// WORKERS //////////////
clearAllWorkers: function () {
@ -2220,76 +2226,6 @@ page_container %}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
secret: token.secrets[index],
r: token.rs[index]
}
}
}
},
// checkInvoice: function () {
// console.log('#### checkInvoice')
// try {
// const invoice = decode(this.payInvoiceData.data.request)
// const cleanInvoice = {
// msat: invoice.human_readable_part.amount,
// sat: invoice.human_readable_part.amount / 1000,
// fsat: LNbits.utils.formatSat(
// invoice.human_readable_part.amount / 1000
// )
// }
// _.each(invoice.data.tags, tag => {
// if (_.isObject(tag) && _.has(tag, 'description')) {
// if (tag.description === 'payment_hash') {
// cleanInvoice.hash = tag.value
// } else if (tag.description === 'description') {
// cleanInvoice.description = tag.value
// } else if (tag.description === 'expiry') {
// var expireDate = new Date(
// (invoice.data.time_stamp + tag.value) * 1000
// )
// cleanInvoice.expireDate = Quasar.utils.date.formatDate(
// expireDate,
// 'YYYY-MM-DDTHH:mm:ss.SSSZ'
// )
// cleanInvoice.expired = false // TODO
// }
// }
// this.payInvoiceData.invoice = cleanInvoice
// })
// console.log(
// '#### this.payInvoiceData.invoice',
// this.payInvoiceData.invoice
// )
// } catch (error) {
// this.$q.notify({
// timeout: 5000,
// type: 'warning',
// message: 'Could not decode invoice',
// caption: error + '',
// position: 'top',
// actions: [
// {
// icon: 'close',
// color: 'white',
// handler: () => {}
// }
// ]
// })
// throw error
// }
// },
////////////// STORAGE /////////////
getLocalstorageToFile: async function () {

View file

@ -12,7 +12,8 @@ from cashu.core.base import (
GetMintResponse,
Invoice,
MeltRequest,
MintRequest,
PostMintRequest,
PostMintResponse,
PostSplitResponse,
SplitRequest,
)
@ -204,10 +205,10 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
@cashu_ext.post("/api/v1/{cashu_id}/mint")
async def mint(
data: MintRequest,
data: PostMintRequest,
cashu_id: str = Query(None),
payment_hash: str = Query(None),
) -> List[BlindedSignature]:
) -> PostMintResponse:
"""
Requests the minting of tokens belonging to a paid payment request.
Call this endpoint after `GET /mint`.
@ -245,7 +246,7 @@ async def mint(
)
try:
total_requested = sum([bm.amount for bm in data.blinded_messages])
total_requested = sum([bm.amount for bm in data.outputs])
if total_requested > invoice.amount:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED,
@ -257,10 +258,8 @@ async def mint(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
promises = await ledger._generate_promises(
B_s=data.blinded_messages, keyset=keyset
)
return promises
promises = await ledger._generate_promises(B_s=data.outputs, keyset=keyset)
return PostMintResponse(promises=promises)
except (Exception, HTTPException) as e:
logger.debug(f"Cashu: /melt {str(e) or getattr(e, 'detail')}")
# unset issued flag because something went wrong
@ -274,10 +273,8 @@ async def mint(
)
else:
# only used for testing when LIGHTNING=false
promises = await ledger._generate_promises(
B_s=data.blinded_messages, keyset=keyset
)
return promises
promises = await ledger._generate_promises(B_s=data.outputs, keyset=keyset)
return PostMintResponse(promises=promises)
@cashu_ext.post("/api/v1/{cashu_id}/melt")
@ -421,7 +418,7 @@ async def split(
)
amount = payload.amount
outputs = payload.outputs.blinded_messages
outputs = payload.outputs
assert outputs, Exception("no outputs provided.")
split_return = None
try:

View file

@ -14,7 +14,7 @@
<a
class="text-secondary"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/lnaddress"
href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/lnaddress"
>More details</a
>
<br />

View file

@ -195,7 +195,7 @@
Check extension
<a
class="text-secondary"
href="https://github.com/lnbits/lnbits-legend/blob/main/lnbits/extensions/lnaddress/README.md"
href="https://github.com/lnbits/lnbits/blob/main/lnbits/extensions/lnaddress/README.md"
>documentation!</a
>
</template>

View file

@ -3,7 +3,7 @@ from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import Address, CreateAddressData, CreateDomainData, Domain
from .models import Address, CreateAddressData, CreateDomainData, Domain, EditDomainData
async def get_domain(domain_id: str) -> Optional[Domain]:
@ -170,6 +170,26 @@ async def create_address_internal(domain_id: str, data: CreateAddressData) -> Ad
return address
async def update_domain_internal(wallet_id: str, data: EditDomainData) -> Domain:
if data.currency != "Satoshis":
amount = data.amount * 100
else:
amount = data.amount
print(data)
await db.execute(
"""
UPDATE nostrnip5.domains
SET amount = ?, currency = ?
WHERE id = ?
""",
(int(amount), data.currency, data.id),
)
domain = await get_domain(data.id)
assert domain, "Domain couldn't be updated"
return domain
async def create_domain_internal(wallet_id: str, data: CreateDomainData) -> Domain:
domain_id = urlsafe_short_hash()

View file

@ -24,6 +24,16 @@ class CreateDomainData(BaseModel):
domain: str
class EditDomainData(BaseModel):
id: str
currency: str
amount: float = Query(..., ge=0.01)
@classmethod
def from_row(cls, row: Row) -> "EditDomainData":
return cls(**dict(row))
class Domain(BaseModel):
id: str
wallet: str

View file

@ -73,6 +73,14 @@
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="deleteDomain(props.row.id)"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="edit"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="editDomain(props.row.id)"
></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
@ -226,6 +234,39 @@
</q-form>
</q-card>
</q-dialog>
<q-dialog
v-model="editFormDialog.show"
position="top"
@hide="closeFormDialog"
>
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="saveEditedDomain" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="editFormDialog.data.currency"
:options="currencyOptions"
label="Currency *"
></q-select>
<q-input
filled
dense
v-model.trim="editFormDialog.data.amount"
label="Amount"
placeholder="How much do you want to charge?"
></q-input>
<div class="row q-mt-lg">
<q-btn unelevated color="primary" type="submit">Update Amount</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog
v-model="addressFormDialog.show"
position="top"
@ -513,6 +554,10 @@
show: false,
data: {}
},
editFormDialog: {
show: false,
data: {}
},
addressFormDialog: {
show: false,
data: {}
@ -578,6 +623,34 @@
LNbits.utils.notifyApiError(error)
})
},
saveEditedDomain: function () {
var data = this.editFormDialog.data
var self = this
LNbits.api
.request(
'PUT',
'/nostrnip5/api/v1/domain',
_.findWhere(this.g.user.wallets, {
id: this.editFormDialog.data.wallet
}).inkey,
data
)
.then(function (response) {
self.editFormDialog.show = false
self.editFormDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
editDomain: function (domain_id) {
var self = this
var data = _.findWhere(this.domains, {id: domain_id})
self.editFormDialog.show = true
self.editFormDialog.data = data
},
deleteDomain: function (domain_id) {
var self = this
var domain = _.findWhere(this.domains, {id: domain_id})

View file

@ -26,8 +26,14 @@ from .crud import (
get_domain_by_name,
get_domains,
rotate_address,
update_domain_internal,
)
from .models import (
CreateAddressData,
CreateDomainData,
EditDomainData,
RotateAddressData,
)
from .models import CreateAddressData, CreateDomainData, RotateAddressData
@nostrnip5_ext.get("/api/v1/domains", status_code=HTTPStatus.OK)
@ -89,6 +95,16 @@ async def api_domain_create(
return domain
@nostrnip5_ext.put("/api/v1/domain", status_code=HTTPStatus.OK)
async def api_domain_update(
data: EditDomainData, wallet: WalletTypeInfo = Depends(get_key_type)
):
domain = await update_domain_internal(wallet_id=wallet.wallet.id, data=data)
return domain
@nostrnip5_ext.delete("/api/v1/domain/{domain_id}", status_code=HTTPStatus.CREATED)
async def api_domain_delete(
domain_id: str,

View file

@ -4,6 +4,15 @@ Vue.component(VueQrcode.name, VueQrcode)
const pica = window.pica()
function imgSizeFit(img, maxWidth = 1024, maxHeight = 768) {
let ratio = Math.min(
1,
maxWidth / img.naturalWidth,
maxHeight / img.naturalHeight
)
return {width: img.naturalWidth * ratio, height: img.naturalHeight * ratio}
}
const defaultItemData = {
unit: 'sat'
}
@ -23,6 +32,7 @@ new Vue({
},
itemDialog: {
show: false,
urlImg: true,
data: {...defaultItemData},
units: ['sat']
}
@ -41,6 +51,9 @@ new Vue({
openUpdateDialog(itemId) {
this.itemDialog.show = true
let item = this.offlineshop.items.find(item => item.id === itemId)
if (item.image.startsWith('data:')) {
this.itemDialog.urlImg = false
}
this.itemDialog.data = item
},
imageAdded(file) {
@ -48,17 +61,12 @@ new Vue({
let image = new Image()
image.src = blobURL
image.onload = async () => {
let fit = imgSizeFit(image, 100, 100)
let canvas = document.createElement('canvas')
canvas.setAttribute('width', 100)
canvas.setAttribute('height', 100)
await pica.resize(image, canvas, {
quality: 0,
alpha: true,
unsharpAmount: 95,
unsharpRadius: 0.9,
unsharpThreshold: 70
})
this.itemDialog.data.image = canvas.toDataURL()
canvas.setAttribute('width', fit.width)
canvas.setAttribute('height', fit.height)
output = await pica.resize(image, canvas)
this.itemDialog.data.image = output.toDataURL('image/jpeg', 0.4)
this.itemDialog = {...this.itemDialog}
}
},
@ -155,6 +163,7 @@ new Vue({
this.loadShop()
this.itemDialog.show = false
this.itemDialog.urlImg = true
this.itemDialog.data = {...defaultItemData}
},
toggleItem(itemId) {

View file

@ -237,7 +237,7 @@
<q-responsive v-if="itemDialog.data.id" :ratio="1">
<qrcode
:value="'lightning:' + itemDialog.data.lnurl"
:options="{width: 800}"
:options="{width: 300}"
class="rounded-borders"
></qrcode>
</q-responsive>
@ -266,7 +266,16 @@
type="text"
label="Brief description"
></q-input>
<q-input
v-if="itemDialog.urlImg"
filled
dense
v-model.trim="itemDialog.data.image"
type="url"
label="Image URL"
></q-input>
<q-file
v-else
filled
dense
capture="environment"
@ -288,6 +297,10 @@
/>
</template>
</q-file>
<q-toggle
:label="`${itemDialog.urlImg ? 'Insert image URL' : 'Upload image file'}`"
v-model="itemDialog.urlImg"
></q-toggle>
<q-input
filled
dense

View file

@ -60,6 +60,22 @@ async def api_add_or_update_item(
):
shop = await get_or_create_shop_by_wallet(wallet.wallet.id)
assert shop
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 data.unit != "sat":
data.price = data.price * 100
if item_id == None:

View file

@ -1,10 +1,9 @@
from http import HTTPStatus
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import CreateEmail, CreateEmailaddress, Emailaddresses, Emails
from .models import CreateEmail, CreateEmailaddress, Email, Emailaddress
from .smtp import send_mail
@ -17,7 +16,7 @@ def get_test_mail(email, testemail):
)
async def create_emailaddress(data: CreateEmailaddress) -> Emailaddresses:
async def create_emailaddress(data: CreateEmailaddress) -> Emailaddress:
emailaddress_id = urlsafe_short_hash()
@ -50,7 +49,7 @@ async def create_emailaddress(data: CreateEmailaddress) -> Emailaddresses:
return new_emailaddress
async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddresses:
async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddress:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(
f"UPDATE smtp.emailaddress SET {q} WHERE id = ?",
@ -65,30 +64,22 @@ async def update_emailaddress(emailaddress_id: str, **kwargs) -> Emailaddresses:
await send_mail(row, email)
assert row, "Newly updated emailaddress couldn't be retrieved"
return Emailaddresses(**row)
return Emailaddress(**row)
async def get_emailaddress(emailaddress_id: str) -> Optional[Emailaddresses]:
async def get_emailaddress(emailaddress_id: str) -> Optional[Emailaddress]:
row = await db.fetchone(
"SELECT * FROM smtp.emailaddress WHERE id = ?", (emailaddress_id,)
)
return Emailaddresses(**row) if row else None
return Emailaddress(**row) if row else None
async def get_emailaddress_by_email(email: str) -> Optional[Emailaddresses]:
async def get_emailaddress_by_email(email: str) -> Optional[Emailaddress]:
row = await db.fetchone("SELECT * FROM smtp.emailaddress WHERE email = ?", (email,))
return Emailaddresses(**row) if row else None
return Emailaddress(**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]:
async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailaddress]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
@ -97,21 +88,22 @@ async def get_emailaddresses(wallet_ids: Union[str, List[str]]) -> List[Emailadd
f"SELECT * FROM smtp.emailaddress WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Emailaddresses(**row) for row in rows]
return [Emailaddress(**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:
async def create_email(wallet: str, data: CreateEmail, payment_hash: str = "") -> Email:
id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO smtp.email (id, wallet, emailaddress_id, subject, receiver, message, paid)
VALUES (?, ?, ?, ?, ?, ?, ?)
INSERT INTO smtp.email (id, payment_hash, wallet, emailaddress_id, subject, receiver, message, paid)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
id,
payment_hash,
wallet,
data.emailaddress_id,
@ -122,36 +114,34 @@ async def create_email(payment_hash, wallet, data: CreateEmail) -> Emails:
),
)
new_email = await get_email(payment_hash)
new_email = await get_email(id)
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)
async def set_email_paid(payment_hash: str) -> bool:
email = await get_email_by_payment_hash(payment_hash)
if email and email.paid == False:
await db.execute(
"""
UPDATE smtp.email
SET paid = true
WHERE id = ?
""",
(payment_hash,),
f"UPDATE smtp.email SET paid = true WHERE payment_hash = ?", (payment_hash,)
)
new_email = await get_email(payment_hash)
assert new_email, "Newly paid email couldn't be retrieved"
return new_email
return True
return False
async def get_email(email_id: str) -> Optional[Emails]:
async def get_email_by_payment_hash(payment_hash: str) -> Optional[Email]:
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,),
f"SELECT * FROM smtp.email WHERE payment_hash = ?", (payment_hash,)
)
return Emails(**row) if row else None
return Email(**row) if row else None
async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Emails]:
async def get_email(id: str) -> Optional[Email]:
row = await db.fetchone(f"SELECT * FROM smtp.email WHERE id = ?", (id,))
return Email(**row) if row else None
async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Email]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
@ -161,7 +151,7 @@ async def get_emails(wallet_ids: Union[str, List[str]]) -> List[Emails]:
(*wallet_ids,),
)
return [Emails(**row) for row in rows]
return [Email(**row) for row in rows]
async def delete_email(email_id: str) -> None:

View file

@ -33,3 +33,7 @@ async def m001_initial(db):
);
"""
)
async def m002_add_payment_hash(db):
await db.execute(f"ALTER TABLE smtp.email ADD COLUMN payment_hash TEXT;")

View file

@ -15,7 +15,7 @@ class CreateEmailaddress(BaseModel):
cost: int = Query(..., ge=0)
class Emailaddresses(BaseModel):
class Emailaddress(BaseModel):
id: str
wallet: str
email: str
@ -36,7 +36,7 @@ class CreateEmail(BaseModel):
message: str = Query(...)
class Emails(BaseModel):
class Email(BaseModel):
id: str
wallet: str
emailaddress_id: str

View file

@ -4,83 +4,107 @@ 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
from typing import Union
from loguru import logger
from starlette.exceptions import HTTPException
from .models import CreateEmail, CreateEmailaddress, Email, Emailaddress
async def send_mail(
emailaddress: Union[Emailaddress, CreateEmailaddress],
email: Union[Email, CreateEmail],
):
smtp_client = SmtpService(emailaddress)
message = smtp_client.create_message(email)
await smtp_client.send_mail(email.receiver, message)
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])?"
pat = r"[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)
log = f"SMTP - invalid email: {s}."
logger.error(log)
raise Exception(log)
async def send_mail(emailaddress, email):
valid_email(emailaddress.email)
valid_email(email.receiver)
class SmtpService:
def __init__(self, emailaddress: Union[Emailaddress, CreateEmailaddress]) -> None:
self.sender = emailaddress.email
self.smtp_server = emailaddress.smtp_server
self.smtp_port = emailaddress.smtp_port
self.smtp_user = emailaddress.smtp_user
self.smtp_password = emailaddress.smtp_password
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
signature = "Email sent anonymiously by LNbits Sendmail extension."
text = f"""
{email.message}
{signature}
"""
html = f"""
<html>
<head></head>
<body>
<p>{email.message}<p>
<br>
<p>{signature}</p>
</body>
</html>
"""
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
def render_email(self, email: Union[Email, CreateEmail]):
signature: str = "Email sent by LNbits SMTP extension."
text = f"{email.message}\n\n{signature}"
html = (
"""
<html>
<head></head>
<body>
<p>"""
+ email.message
+ """</p>
<p>"""
+ signature
+ """</p>
</body>
</html>
"""
)
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()
return text, html
def create_message(self, email: Union[Email, CreateEmail]):
ts = time.time()
date = formatdate(ts, True)
msg = MIMEMultipart("alternative")
msg["Date"] = date
msg["Subject"] = email.subject
msg["From"] = self.sender
msg["To"] = email.receiver
text, html = self.render_email(email)
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")
msg.attach(part1)
msg.attach(part2)
return msg
async def send_mail(self, receiver, msg: MIMEMultipart):
valid_email(self.sender)
valid_email(receiver)
try:
conn = SMTP(host=self.smtp_server, port=int(self.smtp_port), timeout=10)
logger.debug("SMTP - connected to smtp server.")
# conn.set_debuglevel(True)
except:
log = f"SMTP - error connecting to smtp server: {self.smtp_server}:{self.smtp_port}."
logger.debug(log)
raise Exception(log)
try:
conn.login(self.smtp_user, self.smtp_password)
logger.debug("SMTP - successful login to smtp server.")
except:
log = f"SMTP - error login into smtp {self.smtp_user}."
logger.error(log)
raise Exception(log)
try:
conn.sendmail(self.sender, receiver, msg.as_string())
logger.debug("SMTP - successfully send email.")
except socket.error as e:
log = f"SMTP - error sending email: {str(e)}."
logger.error(log)
raise Exception(log)
finally:
conn.quit()

View file

@ -5,7 +5,7 @@ from loguru import logger
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
from .crud import get_email, get_emailaddress, set_email_paid
from .crud import get_email_by_payment_hash, get_emailaddress, set_email_paid
from .smtp import send_mail
@ -21,7 +21,7 @@ async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") != "smtp":
return
email = await get_email(payment.checking_id)
email = await get_email_by_payment_hash(payment.checking_id)
if not email:
logger.error("SMTP: email can not by fetched")
return

View file

@ -57,6 +57,14 @@
:href="props.row.displayUrl"
target="_blank"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="email"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="showEmailDialog(props.row.id)"
></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
@ -154,6 +162,42 @@
</q-card>
</div>
<q-dialog v-model="emailDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendEmail()" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="emailDialog.data.receiver"
type="text"
label="Receiver"
></q-input>
<q-input
filled
dense
v-model.trim="emailDialog.data.subject"
type="text"
label="Subject"
></q-input>
<q-input
filled
dense
v-model.trim="emailDialog.data.message"
type="textarea"
label="Message "
></q-input>
<div class="row q-mt-lg">
<q-btn
unelevated
color="primary"
:disable="emailDialog.data.receiver == '' || emailDialog.data.subject == '' || emailDialog.data.message == ''"
type="submit"
>Submit</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="emailaddressDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md">
@ -316,10 +360,10 @@
emailsTable: {
columns: [
{
name: 'emailaddress',
name: 'emailaddress_id',
align: 'left',
label: 'From',
field: 'emailaddress'
field: 'emailaddress_id'
},
{
name: 'receiver',
@ -350,6 +394,10 @@
rowsPerPage: 10
}
},
emailDialog: {
show: false,
data: {}
},
emailaddressDialog: {
show: false,
data: {}
@ -453,6 +501,33 @@
LNbits.utils.notifyApiError(error)
})
},
sendEmail: function () {
var self = this
var emailaddress = _.findWhere(this.emailaddresses, {
id: self.emailDialog.data.emailaddress_id
})
var wallet = _.findWhere(this.g.user.wallets, {
id: emailaddress.wallet
})
LNbits.api
.request(
'POST',
'/smtp/api/v1/email/' + emailaddress.id + '/send',
wallet.adminkey,
self.emailDialog.data
)
.then(function (response) {
self.emailDialog.show = false
self.emailDialog.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
showEmailDialog: function (emailaddress_id) {
this.emailDialog.data.emailaddress_id = emailaddress_id
this.emailDialog.show = true
},
updateEmailaddressDialog: function (formId) {
var link = _.findWhere(this.emailaddresses, {id: formId})
this.emailaddressDialog.data = _.clone(link)

View file

@ -4,7 +4,7 @@ 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.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import smtp_ext
from .crud import (
@ -13,13 +13,14 @@ from .crud import (
delete_email,
delete_emailaddress,
get_email,
get_email_by_payment_hash,
get_emailaddress,
get_emailaddresses,
get_emails,
update_emailaddress,
)
from .models import CreateEmail, CreateEmailaddress
from .smtp import valid_email
from .smtp import send_mail, valid_email
## EMAILS
@ -37,13 +38,14 @@ async def api_email(
@smtp_ext.get("/api/v1/email/{payment_hash}")
async def api_smtp_send_email(payment_hash):
email = await get_email(payment_hash)
email = await get_email_by_payment_hash(payment_hash)
if not email:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="paymenthash is wrong"
)
emailaddress = await get_emailaddress(email.emailaddress_id)
assert emailaddress
try:
status = await check_transaction_status(email.wallet, payment_hash)
@ -59,11 +61,9 @@ async def api_smtp_send_email(payment_hash):
@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,
@ -94,6 +94,26 @@ async def api_smtp_make_email(emailaddress_id, data: CreateEmail):
return {"payment_hash": payment_hash, "payment_request": payment_request}
@smtp_ext.post(
"/api/v1/email/{emailaddress_id}/send", dependencies=[Depends(require_admin_key)]
)
async def api_smtp_make_email_send(emailaddress_id, data: CreateEmail):
valid_email(data.receiver)
emailaddress = await get_emailaddress(emailaddress_id)
if not emailaddress:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="Emailaddress address does not exist.",
)
email = await create_email(wallet=emailaddress.wallet, data=data)
if not email:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Email could not be fetched."
)
await send_mail(emailaddress, email)
return {"sent": True}
@smtp_ext.delete("/api/v1/email/{email_id}")
async def api_email_delete(email_id, g: WalletTypeInfo = Depends(get_key_type)):
email = await get_email(email_id)

View file

@ -14,7 +14,7 @@
<a
class="text-secondary"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/subdomains"
href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/subdomains"
>More details</a
>
<br />

View file

@ -8,7 +8,7 @@ from typing import AsyncGenerator, Dict, Optional
import httpx
from loguru import logger
# TODO: https://github.com/lnbits/lnbits-legend/issues/764
# TODO: https://github.com/lnbits/lnbits/issues/764
# mypy https://github.com/aaugustin/websockets/issues/940
from websockets import connect # type: ignore
from websockets.exceptions import (

340
poetry.lock generated
View file

@ -81,21 +81,22 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
[[package]]
name = "attrs"
version = "22.1.0"
version = "22.2.0"
description = "Classes Without Boilerplate"
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.6"
files = [
{file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
{file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
{file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
{file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
]
[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"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
dev = ["attrs[docs,tests]"]
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
tests = ["attrs[tests-no-zope]", "zope.interface"]
tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
[[package]]
name = "base58"
@ -176,61 +177,65 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cashu"
version = "0.6.0"
description = "Ecash wallet and mint with Bitcoin Lightning support"
version = "0.8.2"
description = "Ecash wallet and mint for Bitcoin Lightning"
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"},
{file = "cashu-0.8.2-py3-none-any.whl", hash = "sha256:53893911763c424255bc7112aaba356e0a265e850d770338c70b2d282f67bf99"},
{file = "cashu-0.8.2.tar.gz", hash = "sha256:4cdd34a50d14960d2dcc6f5b6120f462688090dd084387860f7a59d16aa102ff"},
]
[package.dependencies]
anyio = {version = "3.6.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
attrs = {version = "22.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
certifi = {version = "2022.12.7", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
colorama = {version = "0.4.6", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
cryptography = {version = "36.0.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
exceptiongroup = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}
fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
importlib-metadata = {version = "5.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
importlib-metadata = {version = "5.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
iniconfig = {version = "2.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
marshmallow = {version = "3.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
packaging = {version = "23.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pycryptodomex = {version = "3.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pydantic = {version = "1.10.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pysocks = {version = "1.7.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pytest = {version = "7.2.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
python-bitcoinlib = {version = "0.11.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
setuptools = {version = "65.7.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""}
typing-extensions = {version = "4.4.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""}
urllib3 = {version = "1.26.14", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
websocket-client = {version = "1.3.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
wheel = {version = "0.38.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
zipp = {version = "3.9.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
zipp = {version = "3.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
[[package]]
name = "cerberus"
@ -248,14 +253,14 @@ setuptools = "*"
[[package]]
name = "certifi"
version = "2022.9.24"
version = "2022.12.7"
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"},
{file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
{file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
]
[[package]]
@ -416,14 +421,14 @@ cffi = ">=1.3.0"
[[package]]
name = "colorama"
version = "0.4.5"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
@ -527,10 +532,10 @@ files = [
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"]
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)"]
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"]
@ -599,6 +604,21 @@ 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 = "exceptiongroup"
version = "1.1.0"
description = "Backport of PEP 654 (exception groups)"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
{file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
version = "0.83.0"
@ -798,14 +818,14 @@ files = [
[[package]]
name = "importlib-metadata"
version = "5.0.0"
version = "5.2.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"},
{file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"},
{file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"},
]
[package.dependencies]
@ -813,20 +833,20 @@ 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)"]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
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"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
category = "main"
optional = false
python-versions = "*"
python-versions = ">=3.7"
files = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
@ -982,23 +1002,23 @@ files = [
[[package]]
name = "marshmallow"
version = "3.18.0"
version = "3.19.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"},
{file = "marshmallow-3.19.0-py3-none-any.whl", hash = "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"},
{file = "marshmallow-3.19.0.tar.gz", hash = "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78"},
]
[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)"]
dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"]
docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.3.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"]
lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.10.25)", "mypy (==0.990)", "pre-commit (>=2.4,<3.0)"]
tests = ["pytest", "pytz", "simplejson"]
[[package]]
@ -1091,19 +1111,16 @@ attrs = ">=19.2.0"
[[package]]
name = "packaging"
version = "21.3"
version = "23.0"
description = "Core utilities for Python packages"
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
files = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
{file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
{file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
]
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pathlib2"
version = "2.3.7.post1"
@ -1239,18 +1256,6 @@ files = [
{file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"},
]
[[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"},
]
[[package]]
name = "pycparser"
version = "2.21"
@ -1265,89 +1270,88 @@ files = [
[[package]]
name = "pycryptodomex"
version = "3.14.1"
version = "3.16.0"
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"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:da8db8374295fb532b4b0c467e66800ef17d100e4d5faa2bbbd6df35502da125"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:d709572d64825d8d59ea112e11cc7faf6007f294e9951324b7574af4251e4de8"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-win32.whl", hash = "sha256:3da13c2535b7aea94cc2a6d1b1b37746814c74b6e80790daddd55ca5c120a489"},
{file = "pycryptodomex-3.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:298c00ea41a81a491d5b244d295d18369e5aac4b61b77b2de5b249ca61cd6659"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77931df40bb5ce5e13f4de2bfc982b2ddc0198971fbd947776c8bb5050896eb2"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c5dd3ffa663c982d7f1be9eb494a8924f6d40e2e2f7d1d27384cfab1b2ac0662"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa887683eee493e015545bd69d3d21ac8d5ad582674ec98f4af84511e353e45"},
{file = "pycryptodomex-3.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8085bd0ad2034352eee4d4f3e2da985c2749cb7344b939f4d95ead38c2520859"},
{file = "pycryptodomex-3.14.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e95a4a6c54d27a84a4624d2af8bb9ee178111604653194ca6880c98dcad92f48"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a4d412eba5679ede84b41dbe48b1bed8f33131ab9db06c238a235334733acc5e"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:d2cce1c82a7845d7e2e8a0956c6b7ed3f1661c9acf18eb120fc71e098ab5c6fe"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:f75009715dcf4a3d680c2338ab19dac5498f8121173a929872950f4fb3a48fbf"},
{file = "pycryptodomex-3.14.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:1ca8e1b4c62038bb2da55451385246f51f412c5f5eabd64812c01766a5989b4a"},
{file = "pycryptodomex-3.14.1-cp35-abi3-win32.whl", hash = "sha256:ee835def05622e0c8b1435a906491760a43d0c462f065ec9143ec4b8d79f8bff"},
{file = "pycryptodomex-3.14.1-cp35-abi3-win_amd64.whl", hash = "sha256:b5a185ae79f899b01ca49f365bdf15a45d78d9856f09b0de1a41b92afce1a07f"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:797a36bd1f69df9e2798e33edb4bd04e5a30478efc08f9428c087f17f65a7045"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:aebecde2adc4a6847094d3bd6a8a9538ef3438a5ea84ac1983fcb167db614461"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:f8524b8bc89470cec7ac51734907818d3620fb1637f8f8b542d650ebec42a126"},
{file = "pycryptodomex-3.14.1-pp27-pypy_73-win32.whl", hash = "sha256:4d0db8df9ffae36f416897ad184608d9d7a8c2b46c4612c6bc759b26c073f750"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b276cc4deb4a80f9dfd47a41ebb464b1fe91efd8b1b8620cf5ccf8b824b850d6"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e36c7e3b5382cd5669cf199c4a04a0279a43b2a3bdd77627e9b89778ac9ec08c"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:c4d8977ccda886d88dc3ca789de2f1adc714df912ff3934b3d0a3f3d777deafb"},
{file = "pycryptodomex-3.14.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151"},
{file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"},
{file = "pycryptodomex-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b3d04c00d777c36972b539fb79958790126847d84ec0129fce1efef250bfe3ce"},
{file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e5a670919076b71522c7d567a9043f66f14b202414a63c3a078b5831ae342c03"},
{file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ce338a9703f54b2305a408fc9890eb966b727ce72b69f225898bb4e9d9ed3f1f"},
{file = "pycryptodomex-3.16.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:a1c0ae7123448ecb034c75c713189cb00ebe2d415b11682865b6c54d200d9c93"},
{file = "pycryptodomex-3.16.0-cp27-cp27m-win32.whl", hash = "sha256:8851585ff19871e5d69e1790f4ca5f6fd1699d6b8b14413b472a4c0dbc7ea780"},
{file = "pycryptodomex-3.16.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8dd2d9e3c617d0712ed781a77efd84ea579e76c5f9b2a4bc0b684ebeddf868b2"},
{file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2ad9bb86b355b6104796567dd44c215b3dc953ef2fae5e0bdfb8516731df92cf"},
{file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e25a2f5667d91795f9417cb856f6df724ccdb0cdd5cbadb212ee9bf43946e9f8"},
{file = "pycryptodomex-3.16.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:b0789a8490114a2936ed77c87792cfe77582c829cb43a6d86ede0f9624ba8aa3"},
{file = "pycryptodomex-3.16.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0da835af786fdd1c9930994c78b23e88d816dc3f99aa977284a21bbc26d19735"},
{file = "pycryptodomex-3.16.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:22aed0868622d95179217c298e37ed7410025c7b29dac236d3230617d1e4ed56"},
{file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1619087fb5b31510b0b0b058a54f001a5ffd91e6ffee220d9913064519c6a69d"},
{file = "pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb"},
{file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7993d26dae4d83b8f4ce605bb0aecb8bee330bb3c95475ef06f3694403621e71"},
{file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1cda60207be8c1cf0b84b9138f9e3ca29335013d2b690774a5e94678ff29659a"},
{file = "pycryptodomex-3.16.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:04610536921c1ec7adba158ef570348550c9f3a40bc24be9f8da2ef7ab387981"},
{file = "pycryptodomex-3.16.0-cp35-abi3-win32.whl", hash = "sha256:daa67f5ebb6fbf1ee9c90decaa06ca7fc88a548864e5e484d52b0920a57fe8a5"},
{file = "pycryptodomex-3.16.0-cp35-abi3-win_amd64.whl", hash = "sha256:231dc8008cbdd1ae0e34645d4523da2dbc7a88c325f0d4a59635a86ee25b41dd"},
{file = "pycryptodomex-3.16.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:4dbbe18cc232b5980c7633972ae5417d0df76fe89e7db246eefd17ef4d8e6d7a"},
{file = "pycryptodomex-3.16.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:893f8a97d533c66cc3a56e60dd3ed40a3494ddb4aafa7e026429a08772f8a849"},
{file = "pycryptodomex-3.16.0-pp27-pypy_73-win32.whl", hash = "sha256:6a465e4f856d2a4f2a311807030c89166529ccf7ccc65bef398de045d49144b6"},
{file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba57ac7861fd2c837cdb33daf822f2a052ff57dd769a2107807f52a36d0e8d38"},
{file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f2b971a7b877348a27dcfd0e772a0343fb818df00b74078e91c008632284137d"},
{file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2453162f473c1eae4826eb10cd7bce19b5facac86d17fb5f29a570fde145abd"},
{file = "pycryptodomex-3.16.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0ba28aa97cdd3ff5ed1a4f2b7f5cd04e721166bd75bd2b929e2734433882b583"},
{file = "pycryptodomex-3.16.0.tar.gz", hash = "sha256:e9ba9d8ed638733c9e95664470b71d624a6def149e2db6cc52c1aca5a6a2df1d"},
]
[[package]]
name = "pydantic"
version = "1.10.2"
version = "1.10.4"
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"},
{file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
{file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
{file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
{file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
{file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
{file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
{file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
{file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
{file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
{file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
{file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
{file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
{file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
{file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
{file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
{file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
{file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
{file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
{file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
{file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
{file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
{file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
{file = "pydantic-1.10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854"},
{file = "pydantic-1.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817"},
{file = "pydantic-1.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c"},
{file = "pydantic-1.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e"},
{file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85"},
{file = "pydantic-1.10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6"},
{file = "pydantic-1.10.4-cp310-cp310-win_amd64.whl", hash = "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d"},
{file = "pydantic-1.10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53"},
{file = "pydantic-1.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3"},
{file = "pydantic-1.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a"},
{file = "pydantic-1.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"},
{file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72"},
{file = "pydantic-1.10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857"},
{file = "pydantic-1.10.4-cp311-cp311-win_amd64.whl", hash = "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3"},
{file = "pydantic-1.10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00"},
{file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978"},
{file = "pydantic-1.10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e"},
{file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa"},
{file = "pydantic-1.10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8"},
{file = "pydantic-1.10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903"},
{file = "pydantic-1.10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423"},
{file = "pydantic-1.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f"},
{file = "pydantic-1.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06"},
{file = "pydantic-1.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15"},
{file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c"},
{file = "pydantic-1.10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416"},
{file = "pydantic-1.10.4-cp38-cp38-win_amd64.whl", hash = "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6"},
{file = "pydantic-1.10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d"},
{file = "pydantic-1.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f"},
{file = "pydantic-1.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024"},
{file = "pydantic-1.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c"},
{file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28"},
{file = "pydantic-1.10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f"},
{file = "pydantic-1.10.4-cp39-cp39-win_amd64.whl", hash = "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4"},
{file = "pydantic-1.10.4-py3-none-any.whl", hash = "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774"},
{file = "pydantic-1.10.4.tar.gz", hash = "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648"},
]
[package.dependencies]
typing-extensions = ">=4.1.0"
typing-extensions = ">=4.2.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
@ -1400,21 +1404,6 @@ 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"},
]
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pypng"
version = "0.0.21"
@ -1472,25 +1461,25 @@ files = [
[[package]]
name = "pytest"
version = "7.1.3"
version = "7.2.1"
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"},
{file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"},
{file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"},
]
[package.dependencies]
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
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"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
@ -1695,18 +1684,18 @@ cffi = ">=1.3.0"
[[package]]
name = "setuptools"
version = "65.6.3"
version = "65.7.0"
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"},
{file = "setuptools-65.7.0-py3-none-any.whl", hash = "sha256:8ab4f1dbf2b4a65f7eec5ad0c620e84c34111a68d3349833494b9088212214dd"},
{file = "setuptools-65.7.0.tar.gz", hash = "sha256:4d3c92fac8f1118bb77a22181355e29c239cabfe2b9effdaa665c66b711136d7"},
]
[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"]
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-lint", "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"]
@ -1795,7 +1784,7 @@ mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mysql = ["mysqlclient"]
oracle = ["cx_oracle"]
oracle = ["cx-oracle"]
postgresql = ["psycopg2"]
postgresql-pg8000 = ["pg8000 (<1.16.6)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
@ -1926,14 +1915,14 @@ files = [
[[package]]
name = "urllib3"
version = "1.26.12"
version = "1.26.14"
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"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
{file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"},
{file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"},
]
[package.extras]
@ -2056,6 +2045,21 @@ files = [
{file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"},
]
[[package]]
name = "wheel"
version = "0.38.4"
description = "A built-package format for Python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"},
{file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"},
]
[package.extras]
test = ["pytest (>=3.0.0)"]
[[package]]
name = "win32-setctime"
version = "1.1.0"
@ -2073,14 +2077,14 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "zipp"
version = "3.9.0"
version = "3.11.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"},
{file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"},
{file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"},
]
[package.extras]
@ -2090,4 +2094,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "2.0"
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
content-hash = "73e1443abc1eed24639a5297a66b2eb16e80491f8849b8008e065f153de215c7"
content-hash = "9daf94dd600a7e23dcefcc8752fae1694e0084e56553dc578a63272776a8fe53"

View file

@ -1,8 +1,8 @@
[tool.poetry]
name = "lnbits"
version = "0.1.0"
description = ""
authors = ["matthewcroughan <matt@croughan.sh>"]
version = "0.9.5.3"
description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = ["Alan Bits <alan@lnbits.com>"]
[tool.poetry.build]
generate-setup-file = false
@ -12,10 +12,10 @@ script = "build.py"
python = "^3.10 | ^3.9 | ^3.8 | ^3.7"
aiofiles = "0.8.0"
asgiref = "3.4.1"
attrs = "22.1.0"
attrs = "22.2.0"
bech32 = "1.2.0"
bitstring = "3.1.9"
certifi = "2022.9.24"
certifi = "2022.12.7"
charset-normalizer = "2.0.12"
click = "8.0.4"
ecdsa = "0.18.0"
@ -26,15 +26,15 @@ httpcore = "0.15.0"
httptools = "0.4.0"
httpx = "0.23.0"
idna = "3.4"
importlib-metadata = "5.0.0"
importlib-metadata = "5.2.0"
jinja2 = "3.0.1"
lnurl = "0.3.6"
markupsafe = "2.0.1"
marshmallow = "3.18.0"
marshmallow = "3.19.0"
outcome = "1.2.0"
psycopg2-binary = "2.9.1"
pycryptodomex = "3.14.1"
pydantic = "1.10.2"
pycryptodomex = "3.16.0"
pydantic = "1.10.4"
pypng = "0.0.21"
pyqrcode = "1.2.1"
pyScss = "1.4.0"
@ -53,7 +53,7 @@ uvicorn = "0.18.3"
uvloop = "0.16.0"
watchgod = "0.7"
websockets = "10.0"
zipp = "3.9.0"
zipp = "3.11.0"
loguru = "0.6.0"
cffi = "1.15.1"
websocket-client = "1.3.3"
@ -62,7 +62,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
cashu = "^0.6.0"
cashu = "0.8.2"
[tool.poetry.dev-dependencies]
@ -88,8 +88,7 @@ profile = "black"
[tool.mypy]
files = "lnbits"
exclude = """(?x)(
^lnbits/extensions/bleskomat.
| ^lnbits/extensions/boltz.
^lnbits/extensions/boltz.
| ^lnbits/wallets/lnd_grpc_files.
)"""

View file

@ -3,57 +3,56 @@ anyio==3.6.2 ; python_version >= "3.7" and python_version < "4.0"
asgiref==3.4.1 ; python_version >= "3.7" and python_version < "4.0"
asn1crypto==1.5.1 ; python_version >= "3.7" and python_version < "4.0"
async-timeout==4.0.2 ; python_version >= "3.7" and python_version < "4.0"
attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0"
attrs==22.2.0 ; python_version >= "3.7" and python_version < "4.0"
base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0"
bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0"
cashu==0.6.0 ; python_version >= "3.7" and python_version < "4.0"
cashu==0.8.2 ; python_version >= "3.7" and python_version < "4.0"
cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0"
certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4.0"
certifi==2022.12.7 ; python_version >= "3.7" and python_version < "4.0"
cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0"
charset-normalizer==2.0.12 ; python_version >= "3.7" and python_version < "4.0"
click==8.0.4 ; python_version >= "3.7" and python_version < "4.0"
coincurve==17.0.0 ; python_version >= "3.7" and python_version < "4.0"
colorama==0.4.5 ; python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
colorama==0.4.6 ; python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
cryptography==36.0.2 ; python_version >= "3.7" and python_version < "4.0"
ecdsa==0.18.0 ; python_version >= "3.7" and python_version < "4.0"
embit==0.4.9 ; python_version >= "3.7" and python_version < "4.0"
enum34==1.1.10 ; python_version >= "3.7" and python_version < "4.0"
environs==9.5.0 ; python_version >= "3.7" and python_version < "4.0"
exceptiongroup==1.1.0 ; python_version >= "3.7" and python_version < "3.11"
fastapi==0.83.0 ; python_version >= "3.7" and python_version < "4.0"
grpcio==1.50.0 ; python_version >= "3.7" and python_version < "4.0"
grpcio==1.51.1 ; python_version >= "3.7" and python_version < "4.0"
h11==0.12.0 ; python_version >= "3.7" and python_version < "4.0"
httpcore==0.15.0 ; python_version >= "3.7" and python_version < "4.0"
httptools==0.4.0 ; python_version >= "3.7" and python_version < "4.0"
httpx==0.23.0 ; python_version >= "3.7" and python_version < "4.0"
idna==3.4 ; python_version >= "3.7" and python_version < "4.0"
importlib-metadata==5.0.0 ; python_version >= "3.7" and python_version < "4.0"
iniconfig==1.1.1 ; python_version >= "3.7" and python_version < "4.0"
importlib-metadata==5.2.0 ; python_version >= "3.7" and python_version < "4.0"
iniconfig==2.0.0 ; python_version >= "3.7" and python_version < "4.0"
jinja2==3.0.1 ; python_version >= "3.7" and python_version < "4.0"
lnurl==0.3.6 ; python_version >= "3.7" and python_version < "4.0"
loguru==0.6.0 ; python_version >= "3.7" and python_version < "4.0"
markupsafe==2.0.1 ; python_version >= "3.7" and python_version < "4.0"
marshmallow==3.18.0 ; python_version >= "3.7" and python_version < "4.0"
marshmallow==3.19.0 ; python_version >= "3.7" and python_version < "4.0"
outcome==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
packaging==21.3 ; python_version >= "3.7" and python_version < "4.0"
packaging==23.0 ; python_version >= "3.7" and python_version < "4.0"
pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0"
pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0"
protobuf==4.21.9 ; python_version >= "3.7" and python_version < "4.0"
protobuf==4.21.12 ; python_version >= "3.7" and python_version < "4.0"
psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0"
py==1.11.0 ; python_version >= "3.7" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0"
pycryptodomex==3.14.1 ; python_version >= "3.7" and python_version < "4.0"
pydantic==1.10.2 ; python_version >= "3.7" and python_version < "4.0"
pycryptodomex==3.16.0 ; python_version >= "3.7" and python_version < "4.0"
pydantic==1.10.4 ; python_version >= "3.7" and python_version < "4.0"
pyln-bolt7==1.0.246 ; python_version >= "3.7" and python_version < "4.0"
pyln-client==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
pyln-proto==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0"
pypng==0.0.21 ; python_version >= "3.7" and python_version < "4.0"
pyqrcode==1.2.1 ; python_version >= "3.7" and python_version < "4.0"
pyscss==1.4.0 ; python_version >= "3.7" and python_version < "4.0"
pysocks==1.7.1 ; python_version >= "3.7" and python_version < "4.0"
pytest-asyncio==0.19.0 ; python_version >= "3.7" and python_version < "4.0"
pytest==7.1.3 ; python_version >= "3.7" and python_version < "4.0"
pytest==7.2.1 ; python_version >= "3.7" and python_version < "4.0"
python-bitcoinlib==0.11.2 ; python_version >= "3.7" and python_version < "4.0"
python-dotenv==0.21.0 ; python_version >= "3.7" and python_version < "4.0"
pyyaml==5.4.1 ; python_version >= "3.7" and python_version < "4.0"
@ -62,7 +61,7 @@ requests==2.27.1 ; python_version >= "3.7" and python_version < "4.0"
rfc3986==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
rfc3986[idna2008]==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
secp256k1==0.14.0 ; python_version >= "3.7" and python_version < "4.0"
setuptools==65.6.3 ; python_version >= "3.7" and python_version < "4.0"
setuptools==65.7.0 ; python_version >= "3.7" and python_version < "4.0"
shortuuid==1.0.1 ; python_version >= "3.7" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.7" and python_version < "4.0"
sniffio==1.3.0 ; python_version >= "3.7" and python_version < "4.0"
@ -70,13 +69,14 @@ sqlalchemy-aio==0.17.0 ; python_version >= "3.7" and python_version < "4.0"
sqlalchemy==1.3.24 ; python_version >= "3.7" and python_version < "4.0"
sse-starlette==0.6.2 ; python_version >= "3.7" and python_version < "4.0"
starlette==0.19.1 ; python_version >= "3.7" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.7" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.7" and python_version < "3.11"
typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0"
urllib3==1.26.12 ; python_version >= "3.7" and python_version < "4"
urllib3==1.26.14 ; python_version >= "3.7" and python_version < "4.0"
uvicorn==0.18.3 ; python_version >= "3.7" and python_version < "4.0"
uvloop==0.16.0 ; python_version >= "3.7" and python_version < "4.0"
watchgod==0.7 ; python_version >= "3.7" and python_version < "4.0"
websocket-client==1.3.3 ; python_version >= "3.7" and python_version < "4.0"
websockets==10.0 ; python_version >= "3.7" and python_version < "4.0"
wheel==0.38.4 ; python_version >= "3.7" and python_version < "4.0"
win32-setctime==1.1.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
zipp==3.9.0 ; python_version >= "3.7" and python_version < "4.0"
zipp==3.11.0 ; python_version >= "3.7" and python_version < "4.0"