Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efafd00b43 | ||
|
|
07946d873f | ||
|
|
91b28ebf83 | ||
|
|
dfb6378976 | ||
|
|
64fde0093c | ||
|
|
c8f9473e01 | ||
|
|
fea6183aef | ||
|
|
adae3b4302 | ||
|
|
ce82439fd8 | ||
|
|
0a210f766d | ||
|
|
fe315d0a9b | ||
|
|
156a19f05c | ||
|
|
06fa6c10c1 | ||
|
|
0b596c00ca | ||
|
|
0af22a6256 | ||
|
|
1ac9d2572d | ||
|
|
4a72a5b6b3 | ||
|
|
b465ecf22a | ||
|
|
42b7cd3046 | ||
|
|
45b36b896b | ||
|
|
99964ee6ab | ||
|
|
d82118db20 | ||
|
|
ffb0f4ccb0 | ||
|
|
de38f38723 | ||
|
|
8c3b4a87b1 | ||
|
|
8ee6be30eb | ||
|
|
cf3b169480 | ||
|
|
95a718c6c0 | ||
|
|
2473e0e962 | ||
|
|
90d1e1ebb6 | ||
|
|
554ad88cbd | ||
|
|
0d14d2b56e | ||
|
|
d7e7d89e9a | ||
|
|
1d0fcaa579 | ||
|
|
af212c820c | ||
|
|
0a74ca3972 | ||
|
|
47df94178e | ||
|
|
255638a9ce | ||
|
|
50149c360c | ||
|
|
6fae6dab92 | ||
|
|
be5f909d1c | ||
|
|
2a1a0f3a2f | ||
|
|
5dbd9a9192 | ||
|
|
1f6a961840 | ||
|
|
9b7bda4e4e | ||
|
|
a8103ba091 | ||
|
|
234b508368 | ||
|
|
f766f9b2c3 | ||
|
|
370ec7e366 | ||
|
|
0822058661 | ||
|
|
7be46587db | ||
|
|
fa58a5fa4c | ||
|
|
486631b455 | ||
|
|
435743a265 | ||
|
|
72386033a9 | ||
|
|
69c0cd0276 | ||
|
|
abfee29676 | ||
|
|
9c8a79eb9b | ||
|
|
0607cb1d6e | ||
|
|
02306148df | ||
|
|
3855cf47f3 | ||
|
|
b2bc378a6f | ||
|
|
e59a218912 | ||
|
|
689c443d72 | ||
|
|
58c3b05e29 | ||
|
|
8ce84ce592 | ||
|
|
fe9e821af5 | ||
|
|
a9bdf24425 | ||
|
|
43c9c9754b | ||
|
|
9127ebc621 | ||
|
|
407a61cd29 | ||
|
|
994b203a8e | ||
|
|
239f166a14 | ||
|
|
e1e34def8c | ||
|
|
a805d2a0e8 | ||
|
|
281d37f79c | ||
|
|
0039c60a27 | ||
|
|
3317bc1bc5 | ||
|
|
9cf43dcea1 | ||
|
|
b3910da6d7 | ||
|
|
ec1caa6015 | ||
|
|
0d8d580fc9 | ||
|
|
4dd206da2a | ||
|
|
fbe982af36 | ||
|
|
a360a47be9 | ||
|
|
552f253b5d | ||
|
|
5021d86488 | ||
|
|
c257e42222 | ||
|
|
17cdf57b8f | ||
|
|
375b575eed | ||
|
|
f860c170fc | ||
|
|
095206e2ed | ||
|
|
4f3159339a | ||
|
|
9ba2983572 | ||
|
|
acaf4849d2 | ||
|
|
3fa4135a12 | ||
|
|
a8f534cd2e | ||
|
|
c58fac39cd | ||
|
|
161cb9cfbc | ||
|
|
892d8ffa62 | ||
|
|
3d55d98d33 | ||
|
|
1e39e7dd63 |
@@ -3,7 +3,6 @@ data
|
||||
docker
|
||||
docs
|
||||
tests
|
||||
venv
|
||||
|
||||
lnbits/static/css/*
|
||||
lnbits/static/bundle.js
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[flake8]
|
||||
max-line-length = 150
|
||||
exclude = lnbits/wallets/lnd_grpc_files/, lnbits/extensions/
|
||||
ignore =
|
||||
# E203 whitespace before ':' black does not like it
|
||||
E203
|
||||
# E402: module level import not at top of file
|
||||
E402,
|
||||
# W503: line break before binary operator
|
||||
W503,
|
||||
# F821: undefined name - should be addressed in future PR
|
||||
F821,
|
||||
# E722 do not use bare 'except' - should be addressed in future PR
|
||||
E722,
|
||||
# flake8-requirements import checks
|
||||
I
|
||||
@@ -1,10 +1,6 @@
|
||||
name: formatting
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
|
||||
@@ -3,29 +3,6 @@ name: tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
venv-sqlite:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9"]
|
||||
poetry-version: ["1.3.1"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
env:
|
||||
VIRTUAL_ENV: ./venv
|
||||
PATH: ${{ env.VIRTUAL_ENV }}/bin:${{ env.PATH }}
|
||||
run: |
|
||||
python -m venv ${{ env.VIRTUAL_ENV }}
|
||||
./venv/bin/python -m pip install --upgrade pip
|
||||
./venv/bin/pip install -r requirements.txt
|
||||
./venv/bin/pip install pytest pytest-asyncio pytest-cov requests mock
|
||||
- name: Run tests
|
||||
run: make test-venv
|
||||
sqlite:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
||||
@@ -23,8 +23,6 @@ tests/data/*.sqlite3
|
||||
*.pyc
|
||||
*.env
|
||||
.env
|
||||
.venv
|
||||
venv
|
||||
|
||||
data
|
||||
*.sqlite3
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
*.yml
|
||||
|
||||
**/lnbits/extensions/*
|
||||
|
||||
**/lnbits/static/vendor
|
||||
**/lnbits/static/bundle.*
|
||||
**/lnbits/static/css/*
|
||||
|
||||
+10
-3
@@ -3,18 +3,25 @@ FROM python:3.10-slim
|
||||
RUN apt-get clean
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y curl pkg-config build-essential
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
ENV PATH="/root/.local/bin:$PATH"
|
||||
|
||||
# needed for backups postgresql-client version 14 (pg_dump)
|
||||
RUN apt-get install -y wget
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt bullseye-pgdg main" > /etc/apt/sources.list.d/pgdg.list
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y postgresql-client-14
|
||||
|
||||
WORKDIR /app
|
||||
RUN mkdir -p lnbits/data
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir data
|
||||
|
||||
RUN poetry config virtualenvs.create false
|
||||
RUN poetry install --only main
|
||||
RUN poetry run python tools/build.py
|
||||
|
||||
ENV LNBITS_PORT="5000"
|
||||
ENV LNBITS_HOST="0.0.0.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.PHONY: test
|
||||
|
||||
all: format check requirements.txt
|
||||
all: format check
|
||||
|
||||
format: prettier isort black
|
||||
|
||||
@@ -50,14 +50,6 @@ test-real-wallet:
|
||||
DEBUG=true \
|
||||
poetry run pytest
|
||||
|
||||
test-venv:
|
||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||
FAKE_WALLET_SECRET="ToTheMoon1" \
|
||||
LNBITS_DATA_FOLDER="./tests/data" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
./venv/bin/pytest --durations=1 -s --cov=lnbits --cov-report=xml tests
|
||||
|
||||
test-migration:
|
||||
rm -rf ./migration-data
|
||||
mkdir -p ./migration-data
|
||||
@@ -79,3 +71,22 @@ migration:
|
||||
|
||||
bak:
|
||||
# LNBITS_DATABASE_URL=postgres://postgres:postgres@0.0.0.0:5432/postgres
|
||||
#
|
||||
|
||||
updatevendor:
|
||||
npm install
|
||||
cp ./node_modules/moment/moment.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/underscore/underscore.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/axios/dist/axios.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/vue/dist/vue.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/vue-router/dist/vue-router.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.browser.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/@chenfengyuan/vue-qrcode/dist/vue-qrcode.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/vuex/dist/vuex.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/quasar/dist/quasar.ie.polyfills.umd.min.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/quasar/dist/quasar.umd.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/chart.js/dist/Chart.bundle.js ./lnbits/static/vendor/
|
||||
cp ./node_modules/quasar/dist/quasar.css ./lnbits/static/vendor/
|
||||
cp ./node_modules/chart.js/dist/Chart.css ./lnbits/static/vendor/
|
||||
cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.css ./lnbits/static/vendor/
|
||||
cp ./node_modules/vue-i18n/dist/vue-i18n.js ./lnbits/static/vendor/
|
||||
|
||||
@@ -25,7 +25,7 @@ LNbits is a Python server that sits on top of any funding source. It can be used
|
||||
|
||||
LNbits can run on top of any Lightning funding source. It supports LND, CLN, Eclair, Spark, LNpay, OpenNode, LightningTipBot, and with more being added regularly.
|
||||
|
||||
See [LNbits Wiki](github.com/lnbits/lnbits/wiki/) for more detailed documentation.
|
||||
See [LNbits Wiki](https://github.com/lnbits/lnbits/wiki/) for more detailed documentation.
|
||||
|
||||
Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series.
|
||||
|
||||
@@ -33,7 +33,7 @@ LNbits is inspired by all the great work of [opennode.com](https://www.opennode.
|
||||
|
||||
## Running LNbits
|
||||
|
||||
See the [install guide](github.com/lnbits/lnbits/wiki/) for details on installation and setup.
|
||||
See the [install guide](https://github.com/lnbits/lnbits/wiki/LNbits-Documentation#install-options) for details on installation and setup.
|
||||
|
||||
## LNbits as an account system
|
||||
|
||||
|
||||
@@ -9,11 +9,14 @@ nav_order: 2
|
||||
Making extensions
|
||||
=================
|
||||
|
||||
Start off by copying the example extension in `lnbits/extensions/example` into your own:
|
||||
Start off by cloning the [example extension](https://github.com/lnbits/example) into your `lnbits/extensions` folder and renaming it to `mysuperplugin`:
|
||||
```sh
|
||||
cp lnbits/extensions/example lnbits/extensions/mysuperplugin -r # Let's not use dashes or anything; it doesn't like those.
|
||||
cd lnbits/extensions/mysuperplugin
|
||||
cd lnbits/extensions
|
||||
git clone https://github.com/lnbits/example.git --depth=1 mysuperplugin # Let's not use dashes or anything; it doesn't like those.
|
||||
cd mysuperplugin
|
||||
rm -rf .git/
|
||||
find . -type f -print0 | xargs -0 sed -i 's/example/mysuperplugin/g' # Change all occurrences of 'example' to your plugin name 'mysuperplugin'.
|
||||
mv templates/example templates/mysuperplugin # Rename templates folder.
|
||||
```
|
||||
- if you are on macOS and having difficulty with 'sed', consider `brew install gnu-sed` and use 'gsed', without -0 option after xargs.
|
||||
|
||||
@@ -28,18 +31,16 @@ Going over the example extension's structure:
|
||||
Adding new dependencies
|
||||
-----------------------
|
||||
|
||||
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
|
||||
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
|
||||
|
||||
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `venv`, or `poerty`:
|
||||
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `poerty`:
|
||||
|
||||
```sh
|
||||
$ poetry add <package>
|
||||
# or
|
||||
$ ./venv/bin/pip install <package>
|
||||
```
|
||||
|
||||
**But we need an extra step to make sure LNbits doesn't break in production.**
|
||||
Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
|
||||
Dependencies need to be added to `pyproject.toml`, then tested by running on `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
|
||||
|
||||
|
||||
SQLite to PostgreSQL migration
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Extension Install
|
||||
|
||||
Anyone can create an extension by following the [example extension](https://github.com/lnbits/lnbits/tree/extension_install_02/lnbits/extensions/example).
|
||||
Anyone can create an extension by following the [example extension](https://github.com/lnbits/example) and [making extensions](https://github.com/lnbits/lnbits/blob/main/docs/devs/extensions.md) dev guide.
|
||||
|
||||
Extensions can be installed by an admin user after the **LNbits** instance has been started.
|
||||
|
||||
@@ -12,7 +12,7 @@ Go to `Manage Server` > `Server` > `Extensions Manifests`
|
||||
|
||||
|
||||
An `Extension Manifest` is a link to a `JSON` file which contains information about various extensions that can be installed (repository of extensions).
|
||||
Multiple repositories can be configured. For more information check the [Manifest File](https://github.com/lnbits/lnbits/blob/extension_install_02/docs/guide/extension-install.md#manifest-file) section.
|
||||
Multiple repositories can be configured. For more information check the [Manifest File](https://github.com/lnbits/lnbits/blob/main/docs/guide/extension-install.md#manifest-file) section.
|
||||
|
||||
|
||||
**LNbits** administrators should configure their instances to use repositories that they trust (like the [lnbits-extensions](https://github.com/lnbits/lnbits-extensions/) one).
|
||||
|
||||
+71
-37
@@ -6,7 +6,7 @@ nav_order: 2
|
||||
|
||||
# Basic installation
|
||||
|
||||
You can choose between four package managers, `poetry`, `nix` and `venv`.
|
||||
You can choose between four package managers, `poetry` and `nix`
|
||||
|
||||
By default, LNbits will use SQLite as its database. You can also use PostgreSQL which is recommended for applications with a high load (see guide below).
|
||||
|
||||
@@ -80,30 +80,8 @@ mkdir data
|
||||
LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT=https://legend.lnbits.com LNBITS_KEY=7b1a78d6c78f48b09a202f2dcb2d22eb ./result/bin/lnbits --port 9000
|
||||
```
|
||||
|
||||
## Option 3: venv
|
||||
|
||||
```sh
|
||||
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`
|
||||
./venv/bin/pip install -r requirements.txt
|
||||
# create the data folder and the .env file
|
||||
mkdir data && cp .env.example .env
|
||||
# build the static files
|
||||
./venv/bin/python tools/build.py
|
||||
```
|
||||
|
||||
#### Running the server
|
||||
|
||||
```sh
|
||||
./venv/bin/uvicorn lnbits.__main__:app --port 5000
|
||||
```
|
||||
|
||||
If you want to host LNbits on the internet, run with the option `--host 0.0.0.0`.
|
||||
|
||||
## Option 4: Docker
|
||||
## Option 3: Docker
|
||||
|
||||
use latest version from docker hub
|
||||
```sh
|
||||
@@ -122,7 +100,7 @@ mkdir data
|
||||
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbitsdocker/lnbits-legend
|
||||
```
|
||||
|
||||
## Option 5: Fly.io
|
||||
## Option 4: Fly.io
|
||||
|
||||
Fly.io is a docker container hosting platform that has a generous free tier. You can host LNbits for free on Fly.io for personal use.
|
||||
|
||||
@@ -210,9 +188,6 @@ sudo apt install python3.9-dev gcc build-essential
|
||||
# if the secp256k1 build fails:
|
||||
# if you used poetry
|
||||
poetry add setuptools wheel
|
||||
|
||||
# if you used venv
|
||||
./venv/bin/pip install setuptools wheel
|
||||
```
|
||||
|
||||
#### Poetry
|
||||
@@ -303,7 +278,7 @@ Description=LNbits
|
||||
|
||||
[Service]
|
||||
# replace with the absolute path of your lnbits installation
|
||||
WorkingDirectory=/home/lnbits/lnbits-legend
|
||||
WorkingDirectory=/home/lnbits/lnbits
|
||||
# same here. run `which poetry` if you can't find the poetry binary
|
||||
ExecStart=/home/lnbits/.local/bin/poetry run lnbits
|
||||
# replace with the user that you're running lnbits on
|
||||
@@ -323,7 +298,8 @@ Save the file and run the following commands:
|
||||
sudo systemctl enable lnbits.service
|
||||
sudo systemctl start lnbits.service
|
||||
```
|
||||
## Reverse proxy with automatic https using Caddy
|
||||
|
||||
## Reverse proxy with automatic HTTPS using Caddy
|
||||
|
||||
Use Caddy to make your LNbits install accessible over clearnet with a domain and https cert.
|
||||
|
||||
@@ -335,11 +311,15 @@ https://caddyserver.com/docs/install#debian-ubuntu-raspbian
|
||||
```
|
||||
sudo caddy stop
|
||||
```
|
||||
|
||||
Create a Caddyfile
|
||||
|
||||
```
|
||||
sudo nano Caddyfile
|
||||
```
|
||||
|
||||
Assuming your LNbits is running on port `5000` add:
|
||||
|
||||
```
|
||||
yourdomain.com {
|
||||
handle /api/v1/payments/sse* {
|
||||
@@ -356,22 +336,30 @@ yourdomain.com {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save and exit `CTRL + x`
|
||||
|
||||
```
|
||||
sudo caddy start
|
||||
```
|
||||
|
||||
## Running behind an apache2 reverse proxy over https
|
||||
Install apache2 and enable apache2 mods
|
||||
## Running behind an Apache2 reverse proxy over HTTPS
|
||||
|
||||
Install Apache2 and enable Apache2 mods:
|
||||
|
||||
```sh
|
||||
apt-get install apache2 certbot
|
||||
a2enmod headers ssl proxy proxy-http
|
||||
```
|
||||
create a ssl certificate with letsencrypt
|
||||
|
||||
Create a SSL certificate with LetsEncrypt:
|
||||
|
||||
```sh
|
||||
certbot certonly --webroot --agree-tos --text --non-interactive --webroot-path /var/www/html -d lnbits.org
|
||||
certbot certonly --webroot --agree-tos --non-interactive --webroot-path /var/www/html -d lnbits.org
|
||||
```
|
||||
create a apache2 vhost at: /etc/apache2/sites-enabled/lnbits.conf
|
||||
|
||||
Create an Apache2 vhost at: `/etc/apache2/sites-enabled/lnbits.conf`:
|
||||
|
||||
```sh
|
||||
cat <<EOF > /etc/apache2/sites-enabled/lnbits.conf
|
||||
<VirtualHost *:443>
|
||||
@@ -396,11 +384,57 @@ cat <<EOF > /etc/apache2/sites-enabled/lnbits.conf
|
||||
</VirtualHost>
|
||||
EOF
|
||||
```
|
||||
restart apache2
|
||||
|
||||
Restart Apache2:
|
||||
|
||||
```sh
|
||||
service restart apache2
|
||||
```
|
||||
|
||||
## Running behind an Nginx reverse proxy over HTTPS
|
||||
|
||||
Install nginx:
|
||||
|
||||
```sh
|
||||
apt-get install nginx certbot
|
||||
```
|
||||
|
||||
Create a SSL certificate with LetsEncrypt:
|
||||
|
||||
```sh
|
||||
certbot certonly --nginx --agree-tos -d lnbits.org
|
||||
```
|
||||
|
||||
Create an nginx vhost at `/etc/nginx/sites-enabled/lnbits.org`:
|
||||
|
||||
```sh
|
||||
cat <<EOF > /etc/nginx/sites-enabled/lnbits.org
|
||||
server {
|
||||
server_name lnbits.org;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
}
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/lnbits.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/lnbits.org/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Restart nginx:
|
||||
|
||||
```sh
|
||||
service restart nginx
|
||||
```
|
||||
|
||||
## Using https without reverse proxy
|
||||
The most common way of using LNbits via https is to use a reverse proxy such as Caddy, nginx, or ngriok. However, you can also run LNbits via https without additional software. This is useful for development purposes or if you want to use LNbits in your local network.
|
||||
@@ -433,7 +467,7 @@ mkcert localhost 127.0.0.1 ::1
|
||||
You can then pass the certificate files to uvicorn when you start LNbits:
|
||||
|
||||
```sh
|
||||
./venv/bin/uvicorn lnbits.__main__:app --host 0.0.0.0 --port 5000 --ssl-keyfile ./key.pem --ssl-certfile ./cert.pem
|
||||
poetry run uvicorn lnbits.__main__:app --host 0.0.0.0 --port 5000 --ssl-keyfile ./key.pem --ssl-certfile ./cert.pem
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ You can also use an AES-encrypted macaroon (more info) instead by using
|
||||
|
||||
- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
|
||||
|
||||
To encrypt your macaroon, run `./venv/bin/python lnbits/wallets/macaroon/macaroon.py`.
|
||||
To encrypt your macaroon, run `poetry run python lnbits/wallets/macaroon/macaroon.py`.
|
||||
|
||||
### LNbits
|
||||
|
||||
@@ -57,7 +57,7 @@ To encrypt your macaroon, run `./venv/bin/python lnbits/wallets/macaroon/macaroo
|
||||
|
||||
### LNPay
|
||||
|
||||
For the invoice listener to work you have a publicly accessible URL in your LNbits and must set up [LNPay webhooks](https://dashboard.lnpay.co/webhook/) pointing to `<your LNbits host>/wallet/webhook` with the "Wallet Receive" event and no secret. For example, `https://mylnbits/wallet/webhook` will be the Endpoint Url that gets notified about the payment.
|
||||
For the invoice listener to work you have a publicly accessible URL in your LNbits and must set up [LNPay webhooks](https://dashboard.lnpay.co/webhook/) pointing to `<your LNbits host>/wallet/webhook` with the "Wallet Receive" event and no secret. For example, `https://mylnbits/wallet/webhook` will be the Endpoint Url that gets notified about the payment.
|
||||
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **LNPayWallet**
|
||||
- `LNPAY_API_ENDPOINT`: https://api.lnpay.co/v1/
|
||||
|
||||
Generated
+12
-25
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1656928814,
|
||||
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
|
||||
"lastModified": 1676283394,
|
||||
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
|
||||
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,11 +17,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1657114324,
|
||||
"narHash": "sha256-fWuaUNXrHcz/ciHRHlcSO92dvV3EVS0GJQUSBO5JIB4=",
|
||||
"lastModified": 1678470307,
|
||||
"narHash": "sha256-OEeMUr3ueLIXyW/OaFUX5jUdimyQwMg/7e+/Q0gC/QE=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a5c867d9fe9e4380452628e8f171c26b69fa9d3d",
|
||||
"rev": "0c4800d579af4ed98ecc47d464a5e7b0870c4b1f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -31,32 +31,19 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1657261001,
|
||||
"narHash": "sha256-sUZeuRYfhG59uD6xafM07bc7bAIkpcGq84Vj4B+cyms=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0be91cefefde5701f8fa957904618a13e3bb51d8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1657149754,
|
||||
"narHash": "sha256-iSnZoqwNDDVoO175whSuvl4sS9lAb/2zZ3Sa4ywo970=",
|
||||
"lastModified": 1678513806,
|
||||
"narHash": "sha256-bEto1lp9bIQ+DlJsXQyRxN5l6B/oy3Skb7DueYRJJBo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "fc1930e011dea149db81863aac22fe701f36f1b5",
|
||||
"rev": "bf239d53fafb53cf439a72b3a50e86dd6a3984a5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
{
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
poetry2nix.url = "github:nix-community/poetry2nix";
|
||||
poetry2nix = {
|
||||
url = "github:nix-community/poetry2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
outputs = { self, nixpkgs, poetry2nix }@inputs:
|
||||
let
|
||||
@@ -17,6 +22,7 @@
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodePackages.prettier
|
||||
poetry
|
||||
];
|
||||
};
|
||||
});
|
||||
@@ -29,7 +35,6 @@
|
||||
default = self.packages.${system}.${projectName};
|
||||
${projectName} = pkgs.poetry2nix.mkPoetryApplication {
|
||||
projectDir = ./.;
|
||||
python = pkgs.python39;
|
||||
};
|
||||
});
|
||||
nixosModules = {
|
||||
|
||||
+8
-22
@@ -33,12 +33,7 @@ from .core import (
|
||||
from .core.services import check_admin_settings
|
||||
from .core.views.generic import core_html_routes
|
||||
from .extension_manager import Extension, InstallableExtension, get_valid_extensions
|
||||
from .helpers import (
|
||||
get_css_vendored,
|
||||
get_js_vendored,
|
||||
template_renderer,
|
||||
url_for_vendored,
|
||||
)
|
||||
from .helpers import template_renderer
|
||||
from .middleware import ExtensionsRedirectMiddleware, InstalledExtensionMiddleware
|
||||
from .requestvars import g
|
||||
from .tasks import (
|
||||
@@ -57,6 +52,7 @@ def create_app() -> FastAPI:
|
||||
app = FastAPI(
|
||||
title="LNbits API",
|
||||
description="API for LNbits, the free and open source bitcoin wallet and accounts system with plugins.",
|
||||
version=settings.version,
|
||||
license_info={
|
||||
"name": "MIT License",
|
||||
"url": "https://raw.githubusercontent.com/lnbits/lnbits/main/LICENSE",
|
||||
@@ -83,7 +79,6 @@ def create_app() -> FastAPI:
|
||||
app.add_middleware(ExtensionsRedirectMiddleware)
|
||||
|
||||
register_startup(app)
|
||||
register_assets(app)
|
||||
register_routes(app)
|
||||
register_async_tasks(app)
|
||||
register_exception_handlers(app)
|
||||
@@ -202,7 +197,7 @@ def check_installed_extension(ext: InstallableExtension) -> bool:
|
||||
os.path.join(settings.lnbits_data_folder, "extensions", "*.zip")
|
||||
)
|
||||
|
||||
if ext.zip_path not in zip_files:
|
||||
if f"./{str(ext.zip_path)}" not in zip_files:
|
||||
ext.download_archive()
|
||||
ext.extract_archive()
|
||||
|
||||
@@ -305,6 +300,7 @@ def register_startup(app: FastAPI):
|
||||
|
||||
def log_server_info():
|
||||
logger.info("Starting LNbits")
|
||||
logger.info(f"Version: {settings.version}")
|
||||
logger.info(f"Baseurl: {settings.lnbits_baseurl}")
|
||||
logger.info(f"Host: {settings.host}")
|
||||
logger.info(f"Port: {settings.port}")
|
||||
@@ -328,19 +324,6 @@ def get_db_vendor_name():
|
||||
)
|
||||
|
||||
|
||||
def register_assets(app: FastAPI):
|
||||
"""Serve each vendored asset separately or a bundle."""
|
||||
|
||||
@app.on_event("startup")
|
||||
async def vendored_assets_variable():
|
||||
if settings.debug:
|
||||
g().VENDORED_JS = map(url_for_vendored, get_js_vendored())
|
||||
g().VENDORED_CSS = map(url_for_vendored, get_css_vendored())
|
||||
else:
|
||||
g().VENDORED_JS = ["/static/bundle.js"]
|
||||
g().VENDORED_CSS = ["/static/bundle.css"]
|
||||
|
||||
|
||||
def register_async_tasks(app):
|
||||
@app.route("/wallet/webhook")
|
||||
async def webhook_listener():
|
||||
@@ -445,7 +428,10 @@ class Formatter:
|
||||
self.padding = 0
|
||||
self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
|
||||
if settings.debug:
|
||||
self.fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n"
|
||||
self.fmt: str = (
|
||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | "
|
||||
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n"
|
||||
)
|
||||
else:
|
||||
self.fmt: str = self.minimal_fmt
|
||||
|
||||
|
||||
+33
-30
@@ -66,11 +66,12 @@ def decode(pr: str) -> Invoice:
|
||||
invoice.amount_msat = _unshorten_amount(amountstr)
|
||||
|
||||
# pull out date
|
||||
invoice.date = data.read(35).uint
|
||||
date_bin = data.read(35)
|
||||
invoice.date = date_bin.uint # type: ignore
|
||||
|
||||
while data.pos != data.len:
|
||||
tag, tagdata, data = _pull_tagged(data)
|
||||
data_length = len(tagdata) / 5
|
||||
data_length = len(tagdata or []) / 5
|
||||
|
||||
if tag == "d":
|
||||
invoice.description = _trim_to_bytes(tagdata).decode()
|
||||
@@ -79,7 +80,7 @@ def decode(pr: str) -> Invoice:
|
||||
elif tag == "p" and data_length == 52:
|
||||
invoice.payment_hash = _trim_to_bytes(tagdata).hex()
|
||||
elif tag == "x":
|
||||
invoice.expiry = tagdata.uint
|
||||
invoice.expiry = tagdata.uint # type: ignore
|
||||
elif tag == "n":
|
||||
invoice.payee = _trim_to_bytes(tagdata).hex()
|
||||
# this won't work in most cases, we must extract the payee
|
||||
@@ -90,11 +91,11 @@ def decode(pr: str) -> Invoice:
|
||||
s = bitstring.ConstBitStream(tagdata)
|
||||
while s.pos + 264 + 64 + 32 + 32 + 16 < s.len:
|
||||
route = Route(
|
||||
pubkey=s.read(264).tobytes().hex(),
|
||||
short_channel_id=_readable_scid(s.read(64).intbe),
|
||||
base_fee_msat=s.read(32).intbe,
|
||||
ppm_fee=s.read(32).intbe,
|
||||
cltv=s.read(16).intbe,
|
||||
pubkey=s.read(264).tobytes().hex(), # type: ignore
|
||||
short_channel_id=_readable_scid(s.read(64).intbe), # type: ignore
|
||||
base_fee_msat=s.read(32).intbe, # type: ignore
|
||||
ppm_fee=s.read(32).intbe, # type: ignore
|
||||
cltv=s.read(16).intbe, # type: ignore
|
||||
)
|
||||
invoice.route_hints.append(route)
|
||||
|
||||
@@ -171,7 +172,7 @@ def lnencode(addr, privkey):
|
||||
else:
|
||||
amount = addr.currency if addr.currency else ""
|
||||
|
||||
hrp = "ln" + amount + "0n"
|
||||
hrp = f"ln{amount}0n"
|
||||
|
||||
# Start with the timestamp
|
||||
data = bitstring.pack("uint:35", addr.date)
|
||||
@@ -202,7 +203,8 @@ def lnencode(addr, privkey):
|
||||
)
|
||||
data += tagged("r", route)
|
||||
elif k == "f":
|
||||
data += encode_fallback(v, addr.currency)
|
||||
# NOTE: there was an error fallback here that's now removed
|
||||
continue
|
||||
elif k == "d":
|
||||
data += tagged_bytes("d", v.encode())
|
||||
elif k == "x":
|
||||
@@ -244,7 +246,13 @@ def lnencode(addr, privkey):
|
||||
|
||||
class LnAddr:
|
||||
def __init__(
|
||||
self, paymenthash=None, amount=None, currency="bc", tags=None, date=None
|
||||
self,
|
||||
paymenthash=None,
|
||||
amount=None,
|
||||
currency="bc",
|
||||
tags=None,
|
||||
date=None,
|
||||
fallback=None,
|
||||
):
|
||||
self.date = int(time.time()) if not date else int(date)
|
||||
self.tags = [] if not tags else tags
|
||||
@@ -252,12 +260,14 @@ class LnAddr:
|
||||
self.paymenthash = paymenthash
|
||||
self.signature = None
|
||||
self.pubkey = None
|
||||
self.fallback = fallback
|
||||
self.currency = currency
|
||||
self.amount = amount
|
||||
|
||||
def __str__(self):
|
||||
pubkey = bytes.hex(self.pubkey.serialize()).decode()
|
||||
tags = ", ".join([k + "=" + str(v) for k, v in self.tags])
|
||||
assert self.pubkey, "LnAddr, pubkey must be set"
|
||||
pubkey = bytes.hex(self.pubkey.serialize())
|
||||
tags = ", ".join([f"{k}={v}" for k, v in self.tags])
|
||||
return f"LnAddr[{pubkey}, amount={self.amount}{self.currency} tags=[{tags}]]"
|
||||
|
||||
|
||||
@@ -266,6 +276,7 @@ def shorten_amount(amount):
|
||||
# Convert to pico initially
|
||||
amount = int(amount * 10**12)
|
||||
units = ["p", "n", "u", "m", ""]
|
||||
unit = ""
|
||||
for unit in units:
|
||||
if amount % 1000 == 0:
|
||||
amount //= 1000
|
||||
@@ -304,32 +315,24 @@ def _pull_tagged(stream):
|
||||
return (CHARSET[tag], stream.read(length * 5), stream)
|
||||
|
||||
|
||||
def is_p2pkh(currency, prefix):
|
||||
return prefix == base58_prefix_map[currency][0]
|
||||
|
||||
|
||||
def is_p2sh(currency, prefix):
|
||||
return prefix == base58_prefix_map[currency][1]
|
||||
|
||||
|
||||
# Tagged field containing BitArray
|
||||
def tagged(char, l):
|
||||
def tagged(char, bits):
|
||||
# Tagged fields need to be zero-padded to 5 bits.
|
||||
while l.len % 5 != 0:
|
||||
l.append("0b0")
|
||||
while bits.len % 5 != 0:
|
||||
bits.append("0b0")
|
||||
return (
|
||||
bitstring.pack(
|
||||
"uint:5, uint:5, uint:5",
|
||||
CHARSET.find(char),
|
||||
(l.len / 5) / 32,
|
||||
(l.len / 5) % 32,
|
||||
(bits.len / 5) / 32,
|
||||
(bits.len / 5) % 32,
|
||||
)
|
||||
+ l
|
||||
+ bits
|
||||
)
|
||||
|
||||
|
||||
def tagged_bytes(char, l):
|
||||
return tagged(char, bitstring.BitArray(l))
|
||||
def tagged_bytes(char, bits):
|
||||
return tagged(char, bitstring.BitArray(bits))
|
||||
|
||||
|
||||
def _trim_to_bytes(barr):
|
||||
@@ -359,5 +362,5 @@ def bitarray_to_u5(barr):
|
||||
ret = []
|
||||
s = bitstring.ConstBitStream(barr)
|
||||
while s.pos != s.len:
|
||||
ret.append(s.read(5).uint)
|
||||
ret.append(s.read(5).uint) # type: ignore
|
||||
return ret
|
||||
|
||||
+1
-15
@@ -13,7 +13,6 @@ from .core.crud import get_dbversions, get_inactive_extensions
|
||||
from .core.helpers import migrate_extension_database, run_migration
|
||||
from .db import COCKROACH, POSTGRES, SQLITE
|
||||
from .extension_manager import get_valid_extensions
|
||||
from .helpers import get_css_vendored, get_js_vendored, url_for_vendored
|
||||
|
||||
|
||||
@click.command("migrate")
|
||||
@@ -24,7 +23,6 @@ def db_migrate():
|
||||
@click.command("assets")
|
||||
def handle_assets():
|
||||
transpile_scss()
|
||||
bundle_vendored()
|
||||
|
||||
|
||||
def transpile_scss():
|
||||
@@ -39,23 +37,11 @@ def transpile_scss():
|
||||
css.write(compile_string(scss.read()))
|
||||
|
||||
|
||||
def bundle_vendored():
|
||||
for getfiles, outputpath in [
|
||||
(get_js_vendored, os.path.join(settings.lnbits_path, "static/bundle.js")),
|
||||
(get_css_vendored, os.path.join(settings.lnbits_path, "static/bundle.css")),
|
||||
]:
|
||||
output = ""
|
||||
for path in getfiles():
|
||||
with open(path) as f:
|
||||
output += "/* " + url_for_vendored(path) + " */\n" + f.read() + ";\n"
|
||||
with open(outputpath, "w") as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
async def migrate_databases():
|
||||
"""Creates the necessary databases if they don't exist already; or migrates them."""
|
||||
|
||||
async with core_db.connect() as conn:
|
||||
exists = False
|
||||
if conn.type == SQLITE:
|
||||
exists = await conn.fetchone(
|
||||
"SELECT * FROM sqlite_master WHERE type='table' AND name='dbversions'"
|
||||
|
||||
+39
-23
@@ -7,7 +7,7 @@ from uuid import uuid4
|
||||
import shortuuid
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.db import COCKROACH, POSTGRES, Connection
|
||||
from lnbits.db import COCKROACH, POSTGRES, Connection, Filters
|
||||
from lnbits.extension_manager import InstallableExtension
|
||||
from lnbits.settings import AdminSettings, EditableSettings, SuperSettings, settings
|
||||
|
||||
@@ -62,7 +62,9 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
|
||||
return User(
|
||||
id=user["id"],
|
||||
email=user["email"],
|
||||
extensions=[e[0] for e in extensions],
|
||||
extensions=[
|
||||
e[0] for e in extensions if User.is_extension_for_user(e[0], user["id"])
|
||||
],
|
||||
wallets=[Wallet(**w) for w in wallets],
|
||||
admin=user["id"] == settings.super_user
|
||||
or user["id"] in settings.lnbits_admin_users,
|
||||
@@ -206,7 +208,7 @@ async def create_wallet(
|
||||
async def update_wallet(
|
||||
wallet_id: str, new_name: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Wallet]:
|
||||
return await (conn or db).execute(
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
UPDATE wallets SET
|
||||
name = ?
|
||||
@@ -214,6 +216,9 @@ async def update_wallet(
|
||||
""",
|
||||
(new_name, wallet_id),
|
||||
)
|
||||
wallet = await get_wallet(wallet_id=wallet_id, conn=conn)
|
||||
assert wallet, "updated created wallet couldn't be retrieved"
|
||||
return wallet
|
||||
|
||||
|
||||
async def delete_wallet(
|
||||
@@ -347,8 +352,7 @@ async def get_payments(
|
||||
incoming: bool = False,
|
||||
since: Optional[int] = None,
|
||||
exclude_uncheckable: bool = False,
|
||||
limit: Optional[int] = None,
|
||||
offset: Optional[int] = None,
|
||||
filters: Optional[Filters[Payment]] = None,
|
||||
conn: Optional[Connection] = None,
|
||||
) -> List[Payment]:
|
||||
"""
|
||||
@@ -393,29 +397,20 @@ async def get_payments(
|
||||
clause.append("checking_id NOT LIKE 'temp_%'")
|
||||
clause.append("checking_id NOT LIKE 'internal_%'")
|
||||
|
||||
limit_clause = f"LIMIT {limit}" if type(limit) == int and limit > 0 else ""
|
||||
offset_clause = f"OFFSET {offset}" if type(offset) == int and offset > 0 else ""
|
||||
# combine limit and offset clauses
|
||||
limit_offset_clause = (
|
||||
f"{limit_clause} {offset_clause}"
|
||||
if limit_clause and offset_clause
|
||||
else limit_clause or offset_clause
|
||||
)
|
||||
|
||||
where = ""
|
||||
if clause:
|
||||
where = f"WHERE {' AND '.join(clause)}"
|
||||
if not filters:
|
||||
filters = Filters(limit=None, offset=None)
|
||||
|
||||
rows = await (conn or db).fetchall(
|
||||
f"""
|
||||
SELECT *
|
||||
FROM apipayments
|
||||
{where}
|
||||
{filters.where(clause)}
|
||||
ORDER BY time DESC
|
||||
{limit_offset_clause}
|
||||
{filters.pagination()}
|
||||
""",
|
||||
tuple(args),
|
||||
filters.values(args),
|
||||
)
|
||||
|
||||
return [Payment.from_row(row) for row in rows]
|
||||
|
||||
|
||||
@@ -604,6 +599,23 @@ async def check_internal(
|
||||
return row["checking_id"]
|
||||
|
||||
|
||||
async def check_internal_pending(
|
||||
payment_hash: str, conn: Optional[Connection] = None
|
||||
) -> bool:
|
||||
"""Returns False if the internal payment is not pending anymore (and thus paid), otherwise True"""
|
||||
row = await (conn or db).fetchone(
|
||||
"""
|
||||
SELECT pending FROM apipayments
|
||||
WHERE hash = ? AND amount > 0
|
||||
""",
|
||||
(payment_hash,),
|
||||
)
|
||||
if not row:
|
||||
return True
|
||||
else:
|
||||
return row["pending"]
|
||||
|
||||
|
||||
# balance_check
|
||||
# -------------
|
||||
|
||||
@@ -705,15 +717,19 @@ async def update_admin_settings(data: EditableSettings):
|
||||
await db.execute("UPDATE settings SET editable_settings = ?", (json.dumps(data),))
|
||||
|
||||
|
||||
async def update_super_user(super_user: str):
|
||||
async def update_super_user(super_user: str) -> SuperSettings:
|
||||
await db.execute("UPDATE settings SET super_user = ?", (super_user,))
|
||||
return await get_super_settings()
|
||||
settings = await get_super_settings()
|
||||
assert settings, "updated super_user settings could not be retrieved"
|
||||
return settings
|
||||
|
||||
|
||||
async def create_admin_settings(super_user: str, new_settings: dict):
|
||||
sql = "INSERT INTO settings (super_user, editable_settings) VALUES (?, ?)"
|
||||
await db.execute(sql, (super_user, json.dumps(new_settings)))
|
||||
return await get_super_settings()
|
||||
settings = await get_super_settings()
|
||||
assert settings, "created admin settings could not be retrieved"
|
||||
return settings
|
||||
|
||||
|
||||
# db versions
|
||||
|
||||
@@ -107,7 +107,7 @@ async def m002_add_fields_to_apipayments(db):
|
||||
continue
|
||||
|
||||
for ext in ["withdraw", "events", "lnticket", "paywall", "tpos"]:
|
||||
prefix = "#" + ext + " "
|
||||
prefix = f"#{ext} "
|
||||
if row["memo"].startswith(prefix):
|
||||
new = row["memo"][len(prefix) :]
|
||||
await db.execute(
|
||||
@@ -168,7 +168,8 @@ async def m004_ensure_fees_are_always_negative(db):
|
||||
|
||||
async def m005_balance_check_balance_notify(db):
|
||||
"""
|
||||
Keep track of balanceCheck-enabled lnurl-withdrawals to be consumed by an LNbits wallet and of balanceNotify URLs supplied by users to empty their wallets.
|
||||
Keep track of balanceCheck-enabled lnurl-withdrawals to be consumed by an
|
||||
LNbits wallet and of balanceNotify URLs supplied by users to empty their wallets.
|
||||
"""
|
||||
|
||||
await db.execute(
|
||||
|
||||
+11
-1
@@ -13,7 +13,7 @@ from pydantic import BaseModel
|
||||
|
||||
from lnbits.db import Connection
|
||||
from lnbits.helpers import url_for
|
||||
from lnbits.settings import get_wallet_class
|
||||
from lnbits.settings import get_wallet_class, settings
|
||||
from lnbits.wallets.base import PaymentStatus
|
||||
|
||||
|
||||
@@ -75,6 +75,16 @@ class User(BaseModel):
|
||||
w = [wallet for wallet in self.wallets if wallet.id == wallet_id]
|
||||
return w[0] if w else None
|
||||
|
||||
@classmethod
|
||||
def is_extension_for_user(cls, ext: str, user: str) -> bool:
|
||||
if ext not in settings.lnbits_admin_extensions:
|
||||
return True
|
||||
if user == settings.super_user:
|
||||
return True
|
||||
if user in settings.lnbits_admin_users:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Payment(BaseModel):
|
||||
checking_id: str
|
||||
|
||||
+45
-38
@@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
from io import BytesIO
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Dict, List, Optional, Tuple, TypedDict
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import httpx
|
||||
@@ -13,10 +13,11 @@ from loguru import logger
|
||||
from lnbits import bolt11
|
||||
from lnbits.db import Connection
|
||||
from lnbits.decorators import WalletTypeInfo, require_admin_key
|
||||
from lnbits.helpers import url_for, urlsafe_short_hash
|
||||
from lnbits.helpers import url_for
|
||||
from lnbits.settings import (
|
||||
FAKE_WALLET,
|
||||
EditableSettings,
|
||||
SuperSettings,
|
||||
get_wallet_class,
|
||||
readonly_variables,
|
||||
send_admin_user_to_saas,
|
||||
@@ -27,6 +28,7 @@ from lnbits.wallets.base import PaymentResponse, PaymentStatus
|
||||
from . import db
|
||||
from .crud import (
|
||||
check_internal,
|
||||
check_internal_pending,
|
||||
create_account,
|
||||
create_admin_settings,
|
||||
create_payment,
|
||||
@@ -42,11 +44,6 @@ from .crud import (
|
||||
)
|
||||
from .models import Payment
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
except ImportError: # pragma: nocover
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
|
||||
class PaymentFailure(Exception):
|
||||
pass
|
||||
@@ -153,7 +150,11 @@ async def pay_invoice(
|
||||
extra=extra,
|
||||
)
|
||||
|
||||
# check_internal() returns the checking_id of the invoice we're waiting for
|
||||
# we check if an internal invoice exists that has already been paid (not pending anymore)
|
||||
if not await check_internal_pending(invoice.payment_hash, conn=conn):
|
||||
raise PaymentFailure("Internal invoice already paid.")
|
||||
|
||||
# check_internal() returns the checking_id of the invoice we're waiting for (pending only)
|
||||
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn)
|
||||
if internal_checking_id:
|
||||
logger.debug(f"creating temporary internal payment with id {internal_id}")
|
||||
@@ -169,16 +170,21 @@ async def pay_invoice(
|
||||
logger.debug(f"creating temporary payment with id {temp_id}")
|
||||
# create a temporary payment here so we can check if
|
||||
# the balance is enough in the next step
|
||||
await create_payment(
|
||||
checking_id=temp_id,
|
||||
fee=-fee_reserve_msat,
|
||||
conn=conn,
|
||||
**payment_kwargs,
|
||||
)
|
||||
try:
|
||||
await create_payment(
|
||||
checking_id=temp_id,
|
||||
fee=-fee_reserve_msat,
|
||||
conn=conn,
|
||||
**payment_kwargs,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"could not create temporary payment: {e}")
|
||||
# happens if the same wallet tries to pay an invoice twice
|
||||
raise PaymentFailure("Could not make payment.")
|
||||
|
||||
# do the balance check
|
||||
wallet = await get_wallet(wallet_id, conn=conn)
|
||||
assert wallet
|
||||
assert wallet, "Wallet for balancecheck could not be fetched"
|
||||
if wallet.balance_msat < 0:
|
||||
logger.debug("balance is too low, deleting temporary payment")
|
||||
if not internal_checking_id and wallet.balance_msat > -fee_reserve_msat:
|
||||
@@ -326,19 +332,20 @@ async def perform_lnurlauth(
|
||||
|
||||
return b
|
||||
|
||||
def encode_strict_der(r_int, s_int, order):
|
||||
def encode_strict_der(r: int, s: int, order: int):
|
||||
# if s > order/2 verification will fail sometimes
|
||||
# so we must fix it here (see https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/ec/index.js#L146-L147)
|
||||
if s_int > order // 2:
|
||||
s_int = order - s_int
|
||||
# so we must fix it here see:
|
||||
# https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/ec/index.js#L146-L147
|
||||
if s > order // 2:
|
||||
s = order - s
|
||||
|
||||
# now we do the strict DER encoding copied from
|
||||
# https://github.com/KiriKiri/bip66 (without any checks)
|
||||
r = int_to_bytes_suitable_der(r_int)
|
||||
s = int_to_bytes_suitable_der(s_int)
|
||||
r_temp = int_to_bytes_suitable_der(r)
|
||||
s_temp = int_to_bytes_suitable_der(s)
|
||||
|
||||
r_len = len(r)
|
||||
s_len = len(s)
|
||||
r_len = len(r_temp)
|
||||
s_len = len(s_temp)
|
||||
sign_len = 6 + r_len + s_len
|
||||
|
||||
signature = BytesIO()
|
||||
@@ -346,16 +353,17 @@ async def perform_lnurlauth(
|
||||
signature.write((sign_len - 2).to_bytes(1, "big", signed=False))
|
||||
signature.write(0x02.to_bytes(1, "big", signed=False))
|
||||
signature.write(r_len.to_bytes(1, "big", signed=False))
|
||||
signature.write(r)
|
||||
signature.write(r_temp)
|
||||
signature.write(0x02.to_bytes(1, "big", signed=False))
|
||||
signature.write(s_len.to_bytes(1, "big", signed=False))
|
||||
signature.write(s)
|
||||
signature.write(s_temp)
|
||||
|
||||
return signature.getvalue()
|
||||
|
||||
sig = key.sign_digest_deterministic(k1, sigencode=encode_strict_der)
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
assert key.verifying_key, "LNURLauth verifying_key does not exist"
|
||||
r = await client.get(
|
||||
callback,
|
||||
params={
|
||||
@@ -386,7 +394,7 @@ async def check_transaction_status(
|
||||
return PaymentStatus(None)
|
||||
if not payment.pending:
|
||||
# note: before, we still checked the status of the payment again
|
||||
return PaymentStatus(True)
|
||||
return PaymentStatus(True, fee_msat=payment.fee)
|
||||
|
||||
status: PaymentStatus = await payment.check_status()
|
||||
return status
|
||||
@@ -400,21 +408,20 @@ def fee_reserve(amount_msat: int) -> int:
|
||||
|
||||
|
||||
async def update_wallet_balance(wallet_id: str, amount: int):
|
||||
internal_id = f"internal_{urlsafe_short_hash()}"
|
||||
payment = await create_payment(
|
||||
payment_hash, _ = await create_invoice(
|
||||
wallet_id=wallet_id,
|
||||
checking_id=internal_id,
|
||||
payment_request="admin_internal",
|
||||
payment_hash="admin_internal",
|
||||
amount=amount * 1000,
|
||||
amount=amount,
|
||||
memo="Admin top up",
|
||||
pending=False,
|
||||
internal=True,
|
||||
)
|
||||
# manually send this for now
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
async with db.connect() as conn:
|
||||
checking_id = await check_internal(payment_hash, conn=conn)
|
||||
assert checking_id, "newly created checking_id cannot be retrieved"
|
||||
await update_payment_status(checking_id=checking_id, pending=False, conn=conn)
|
||||
# notify receiver asynchronously
|
||||
from lnbits.tasks import internal_invoice_queue
|
||||
|
||||
await internal_invoice_queue.put(internal_id)
|
||||
return payment
|
||||
await internal_invoice_queue.put(checking_id)
|
||||
|
||||
|
||||
async def check_admin_settings():
|
||||
@@ -460,7 +467,7 @@ def update_cached_settings(sets_dict: dict):
|
||||
setattr(settings, "super_user", sets_dict["super_user"])
|
||||
|
||||
|
||||
async def init_admin_settings(super_user: str = None):
|
||||
async def init_admin_settings(super_user: Optional[str] = None) -> SuperSettings:
|
||||
account = None
|
||||
if super_user:
|
||||
account = await get_account(super_user)
|
||||
|
||||
@@ -148,27 +148,27 @@ new Vue({
|
||||
{
|
||||
name: 'memo',
|
||||
align: 'left',
|
||||
label: 'Memo',
|
||||
label: this.$t('memo'),
|
||||
field: 'memo'
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
align: 'left',
|
||||
label: 'Date',
|
||||
label: this.$t('date'),
|
||||
field: 'date',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'sat',
|
||||
align: 'right',
|
||||
label: 'Amount (' + LNBITS_DENOMINATION + ')',
|
||||
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
||||
field: 'sat',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'fee',
|
||||
align: 'right',
|
||||
label: 'Fee (m' + LNBITS_DENOMINATION + ')',
|
||||
label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')',
|
||||
field: 'fee'
|
||||
}
|
||||
],
|
||||
@@ -466,6 +466,16 @@ new Vue({
|
||||
return
|
||||
}
|
||||
|
||||
// BIP-21 support
|
||||
if (this.parse.data.request.toLowerCase().includes('lightning')) {
|
||||
this.parse.data.request = this.parse.data.request.split('lightning=')[1]
|
||||
|
||||
// fail safe to check there's nothing after the lightning= part
|
||||
if (this.parse.data.request.includes('&')) {
|
||||
this.parse.data.request = this.parse.data.request.split('&')[0]
|
||||
}
|
||||
}
|
||||
|
||||
let invoice
|
||||
try {
|
||||
invoice = decode(this.parse.data.request)
|
||||
@@ -510,7 +520,7 @@ new Vue({
|
||||
payInvoice: function () {
|
||||
let dismissPaymentMsg = this.$q.notify({
|
||||
timeout: 0,
|
||||
message: 'Processing payment...'
|
||||
message: this.$t('processing_payment')
|
||||
})
|
||||
|
||||
LNbits.api
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
multiple
|
||||
hint="Extensions only user with admin privileges can use"
|
||||
label="Admin extensions"
|
||||
:options="g.extensions.map(e => e.name)"
|
||||
:options="g.extensions.map(e => e.code)"
|
||||
></q-select>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
<div class="row q-col-gutter-md justify-center">
|
||||
<div class="col q-my-md">
|
||||
<q-btn
|
||||
label="Save"
|
||||
:label="$t('save')"
|
||||
color="primary"
|
||||
@click="updateSettings"
|
||||
:disabled="!checkChanges"
|
||||
>
|
||||
<q-tooltip v-if="checkChanges"> Save your changes </q-tooltip>
|
||||
<q-tooltip v-if="checkChanges"
|
||||
>{%raw%}{{ $t('save_tooltip') }}{%endraw%}</q-tooltip
|
||||
>
|
||||
|
||||
<q-badge
|
||||
v-if="checkChanges"
|
||||
@@ -21,13 +23,13 @@
|
||||
|
||||
<q-btn
|
||||
v-if="isSuperUser"
|
||||
label="Restart server"
|
||||
:label="$t('restart')"
|
||||
color="primary"
|
||||
@click="restartServer"
|
||||
>
|
||||
<q-tooltip v-if="needsRestart">
|
||||
Restart the server for changes to take effect
|
||||
</q-tooltip>
|
||||
<q-tooltip v-if="needsRestart"
|
||||
>{%raw%}{{ $t('restart_tooltip') }}{%endraw%}</q-tooltip
|
||||
>
|
||||
|
||||
<q-badge
|
||||
v-if="needsRestart"
|
||||
@@ -40,28 +42,24 @@
|
||||
|
||||
<q-btn
|
||||
v-if="isSuperUser"
|
||||
label="Topup"
|
||||
:label="$t('topup')"
|
||||
color="primary"
|
||||
@click="topUpDialog.show = true"
|
||||
>
|
||||
<q-tooltip> Add funds to a wallet. </q-tooltip>
|
||||
<q-tooltip>{%raw%}{{ $t('add_funds_tooltip') }}{%endraw%}</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
label="Download Database Backup"
|
||||
flat
|
||||
@click="downloadBackup"
|
||||
></q-btn>
|
||||
<q-btn :label="$t('download_backup')" flat @click="downloadBackup"></q-btn>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
v-if="isSuperUser"
|
||||
label="Reset to defaults"
|
||||
:label="$t('reset_defaults')"
|
||||
color="primary"
|
||||
@click="deleteSettings"
|
||||
class="float-right"
|
||||
>
|
||||
<q-tooltip> Delete all settings and reset to defaults. </q-tooltip>
|
||||
<q-tooltip>{%raw%}{{ $t('reset_defaults_tooltip') }}{%endraw%}</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,25 +72,25 @@
|
||||
<q-tabs v-model="tab" active-color="primary" align="justify">
|
||||
<q-tab
|
||||
name="funding"
|
||||
label="Funding"
|
||||
:label="$t('funding')"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
|
||||
<q-tab
|
||||
name="users"
|
||||
label="Users"
|
||||
:label="$t('users')"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
|
||||
<q-tab
|
||||
name="server"
|
||||
label="Server"
|
||||
:label="$t('server')"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
|
||||
<q-tab
|
||||
name="theme"
|
||||
label="Theme"
|
||||
:label="$t('theme')"
|
||||
@update="val => tab = val.name"
|
||||
></q-tab>
|
||||
</q-tabs>
|
||||
@@ -113,7 +111,7 @@
|
||||
<q-dialog v-if="isSuperUser" v-model="topUpDialog.show" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form class="q-gutter-md">
|
||||
<p>TopUp a wallet</p>
|
||||
<p v-text="$t('topup_wallet')"></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@@ -123,7 +121,7 @@
|
||||
filled
|
||||
v-model="wallet.id"
|
||||
label="Wallet ID"
|
||||
hint="Use the wallet ID to topup any wallet"
|
||||
:hint="$t('topup_hint')"
|
||||
></q-input>
|
||||
|
||||
<br />
|
||||
@@ -135,15 +133,25 @@
|
||||
type="number"
|
||||
filled
|
||||
v-model="wallet.amount"
|
||||
label="Topup amount"
|
||||
:label="$t('amount')"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn label="Topup" color="primary" @click="topupWallet"></q-btn>
|
||||
<q-btn
|
||||
:label="$t('topup')"
|
||||
color="primary"
|
||||
@click="topupWallet"
|
||||
></q-btn>
|
||||
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('cancel')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
@@ -217,18 +225,6 @@
|
||||
lnd_rest_macaroon_encrypted: {
|
||||
value: null,
|
||||
label: 'Encrypted Macaroon'
|
||||
},
|
||||
lnd_cert: {
|
||||
value: null,
|
||||
label: 'Certificate'
|
||||
},
|
||||
lnd_rest_admin_macaroon: {
|
||||
value: null,
|
||||
label: 'Admin Macaroon'
|
||||
},
|
||||
lnd_rest_invoice_macaroon: {
|
||||
value: null,
|
||||
label: 'Invoice Macaroon'
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -251,10 +247,6 @@
|
||||
value: null,
|
||||
label: 'Admin Macaroon'
|
||||
},
|
||||
lnd_grpc_invoice_macaroon: {
|
||||
value: null,
|
||||
label: 'Invoice Macaroon'
|
||||
},
|
||||
lnd_grpc_macaroon_encrypted: {
|
||||
value: null,
|
||||
label: 'Encrypted Macaroon'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<q-expansion-item
|
||||
group="extras"
|
||||
icon="swap_vertical_circle"
|
||||
label="API info"
|
||||
:label="$t('api_docs')"
|
||||
:content-inset-level="0.5"
|
||||
>
|
||||
<q-card-section>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
color="primary"
|
||||
type="a"
|
||||
:href="['/install?usr=', user.id].join('')"
|
||||
>Manage Extensions</q-btn
|
||||
>
|
||||
:label="$t('manage_extensions')"
|
||||
></q-btn>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="!filteredExtensions.length">
|
||||
You don't have any extensions installed :(
|
||||
</p>
|
||||
<p v-if="!filteredExtensions.length" v-text="$t('no_extensions')"></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div
|
||||
class="col-6 col-md-4 col-lg-3"
|
||||
|
||||
@@ -12,24 +12,23 @@
|
||||
@click="processing"
|
||||
type="a"
|
||||
href="{{ url_for('core.lnurlwallet') }}?lightning={{ lnurl }}"
|
||||
>
|
||||
Press to claim bitcoin
|
||||
</q-btn>
|
||||
v-text="$t('press_to_claim')"
|
||||
></q-btn>
|
||||
{% else %}
|
||||
<q-form @submit="createWallet" class="q-gutter-md">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model="walletName"
|
||||
label="Name your {{SITE_TITLE}} wallet *"
|
||||
:label='$t("name_your_wallet", { name: "{{ SITE_TITLE }} *" })'
|
||||
></q-input>
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
:disable="walletName == ''"
|
||||
type="submit"
|
||||
>Add a new wallet</q-btn
|
||||
>
|
||||
:label="$t('add_wallet')"
|
||||
></q-btn>
|
||||
</q-form>
|
||||
{% endif %}
|
||||
</q-card-section>
|
||||
@@ -40,27 +39,7 @@
|
||||
<h3 class="q-my-none">{{SITE_TITLE}}</h3>
|
||||
<h5 class="q-my-md">{{SITE_TAGLINE}}</h5>
|
||||
<div v-if="'{{SITE_TITLE}}' == 'LNbits'">
|
||||
<p>
|
||||
Easy to set up and lightweight, LNbits can run on any
|
||||
lightning-network funding source, currently supporting LND,
|
||||
c-lightning, OpenNode, LNPay and even LNbits itself!
|
||||
</p>
|
||||
<p>
|
||||
You can run LNbits for yourself, or easily offer a custodian
|
||||
solution for others.
|
||||
</p>
|
||||
<p>
|
||||
Each wallet has its own API keys and there is no limit to the number
|
||||
of wallets you can make. Being able to partition funds makes LNbits
|
||||
a useful tool for money management and as a development tool.
|
||||
</p>
|
||||
<p>
|
||||
Extensions add extra functionality to LNbits so you can experiment
|
||||
with a range of cutting-edge technologies on the lightning network.
|
||||
We have made developing extensions as easy as possible, and as a
|
||||
free and open-source project, we encourage people to develop and
|
||||
submit their own.
|
||||
</p>
|
||||
<p v-text="$t('lnbits_description')"></p>
|
||||
<div class="row q-mt-md q-gutter-sm">
|
||||
<q-btn
|
||||
outline
|
||||
@@ -69,8 +48,8 @@
|
||||
href="https://github.com/lnbits/lnbits"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>View project in GitHub</q-btn
|
||||
>
|
||||
:label="$t('view_github')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
outline
|
||||
color="grey"
|
||||
@@ -78,8 +57,8 @@
|
||||
href="https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Donate</q-btn
|
||||
>
|
||||
:label="$t('donate')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else>{{SITE_DESCRIPTION | safe}}</p>
|
||||
@@ -94,7 +73,7 @@
|
||||
<q-btn
|
||||
flat
|
||||
color="secondary"
|
||||
label="Runs on"
|
||||
:label="$t('runs_on')"
|
||||
class="full-width"
|
||||
></q-btn>
|
||||
<div class="row">
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
>
|
||||
<q-input
|
||||
filled
|
||||
label="{{LNBITS_DENOMINATION}} to credit"
|
||||
hint="Press Enter to credit account"
|
||||
:label='$t("credit_label", { denomination: "{{LNBITS_DENOMINATION}}"})'
|
||||
:hint="$t('credit_hint')"
|
||||
v-model="scope.value"
|
||||
dense
|
||||
autofocus
|
||||
@@ -63,8 +63,8 @@
|
||||
color="primary"
|
||||
class="full-width"
|
||||
@click="showParseDialog"
|
||||
>Paste Request</q-btn
|
||||
>
|
||||
:label="$t('paste_request')"
|
||||
></q-btn>
|
||||
</div>
|
||||
<div class="col">
|
||||
<q-btn
|
||||
@@ -72,8 +72,8 @@
|
||||
color="primary"
|
||||
class="full-width"
|
||||
@click="showReceiveDialog"
|
||||
>Create Invoice</q-btn
|
||||
>
|
||||
:label="$t('create_invoice')"
|
||||
></q-btn>
|
||||
</div>
|
||||
<div class="col">
|
||||
<q-btn
|
||||
@@ -81,8 +81,11 @@
|
||||
color="secondary"
|
||||
icon="photo_camera"
|
||||
@click="showCamera"
|
||||
>scan
|
||||
<q-tooltip>Use camera to scan an invoice/QR</q-tooltip>
|
||||
:label="$t('scan')"
|
||||
>
|
||||
<q-tooltip
|
||||
>{% raw %}{{$t('camera_tooltip')}}{% endraw %}</q-tooltip
|
||||
>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,12 +95,18 @@
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap q-mb-sm">
|
||||
<div class="col">
|
||||
<h5 class="text-subtitle1 q-my-none">Transactions</h5>
|
||||
<h5
|
||||
class="text-subtitle1 q-my-none"
|
||||
:v-text="$t('transactions')"
|
||||
></h5>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn flat color="grey" @click="exportCSV"
|
||||
>Export to CSV</q-btn
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
color="grey"
|
||||
@click="exportCSV"
|
||||
:label="$t('export_csv')"
|
||||
></q-btn>
|
||||
<!--<q-btn v-if="pendingPaymentsExist" dense flat round icon="update" color="grey" @click="checkPendingPayments">
|
||||
<q-tooltip>Check pending</q-tooltip>
|
||||
</q-btn>-->
|
||||
@@ -109,7 +118,9 @@
|
||||
color="grey"
|
||||
@click="showChart"
|
||||
>
|
||||
<q-tooltip>Show chart</q-tooltip>
|
||||
<q-tooltip
|
||||
>{% raw %}{{$t('chart_tooltip')}}{% endraw %}</q-tooltip
|
||||
>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,7 +131,7 @@
|
||||
clearable
|
||||
v-model="paymentsTable.filter"
|
||||
debounce="300"
|
||||
placeholder="Search by tag, memo, amount"
|
||||
:placeholder="$t('search_by_tag_memo_amount')"
|
||||
class="q-mb-md"
|
||||
>
|
||||
</q-input>
|
||||
@@ -131,7 +142,7 @@
|
||||
:row-key="paymentTableRowKey"
|
||||
:columns="paymentsTable.columns"
|
||||
:pagination.sync="paymentsTable.pagination"
|
||||
no-data-label="No transactions made yet"
|
||||
:no-data-label="$t('no_transactions')"
|
||||
:filter="paymentsTable.filter"
|
||||
>
|
||||
{% raw %}
|
||||
@@ -159,7 +170,7 @@
|
||||
color="grey"
|
||||
@click="props.expand = !props.expand"
|
||||
>
|
||||
<q-tooltip>Pending</q-tooltip>
|
||||
<q-tooltip>{{$t('pending')}}</q-tooltip>
|
||||
</q-icon>
|
||||
</q-td>
|
||||
<q-td
|
||||
@@ -209,7 +220,7 @@
|
||||
<div class="text-center q-mb-lg">
|
||||
<div v-if="props.row.isIn && props.row.pending">
|
||||
<q-icon name="settings_ethernet" color="grey"></q-icon>
|
||||
Invoice waiting to be paid
|
||||
<span v-text="$t('invoice_waiting')"></span>
|
||||
<lnbits-payment-details
|
||||
:payment="props.row"
|
||||
></lnbits-payment-details>
|
||||
@@ -232,15 +243,15 @@
|
||||
outline
|
||||
color="grey"
|
||||
@click="copyText(props.row.bolt11)"
|
||||
>Copy invoice</q-btn
|
||||
>
|
||||
:label="$t('copy_invoice')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
>Close</q-btn
|
||||
>
|
||||
:label="$t('close')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="props.row.isPaid && props.row.isIn">
|
||||
@@ -249,7 +260,7 @@
|
||||
:name="'call_received'"
|
||||
:color="'green'"
|
||||
></q-icon>
|
||||
Payment Received
|
||||
<span v-text="$t('payment_received')"></span>
|
||||
<lnbits-payment-details
|
||||
:payment="props.row"
|
||||
></lnbits-payment-details>
|
||||
@@ -260,14 +271,14 @@
|
||||
:name="'call_made'"
|
||||
:color="'pink'"
|
||||
></q-icon>
|
||||
Payment Sent
|
||||
<span v-text="$t('payment_sent')"></span>
|
||||
<lnbits-payment-details
|
||||
:payment="props.row"
|
||||
></lnbits-payment-details>
|
||||
</div>
|
||||
<div v-else-if="props.row.isOut && props.row.pending">
|
||||
<q-icon name="settings_ethernet" color="grey"></q-icon>
|
||||
Outgoing payment pending
|
||||
<span v-text="$t('outgoing_payment_pending')"></span>
|
||||
<lnbits-payment-details
|
||||
:payment="props.row"
|
||||
></lnbits-payment-details>
|
||||
@@ -304,26 +315,17 @@
|
||||
<q-expansion-item
|
||||
group="extras"
|
||||
icon="crop_free"
|
||||
label="Drain Funds"
|
||||
:label="$t('drain_funds')"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section class="text-center">
|
||||
<p>
|
||||
This is an LNURL-withdraw QR code for slurping
|
||||
everything from this wallet. Do not share with anyone.
|
||||
</p>
|
||||
<a href="lightning:{{wallet.lnurlwithdraw_full}}">
|
||||
<qrcode
|
||||
value="lightning:{{wallet.lnurlwithdraw_full}}"
|
||||
:options="{width:240}"
|
||||
></qrcode>
|
||||
</a>
|
||||
<p>
|
||||
It is compatible with <code>balanceCheck</code> and
|
||||
<code>balanceNotify</code> so your wallet may keep
|
||||
pulling the funds continuously from here after the first
|
||||
withdraw.
|
||||
</p>
|
||||
<p v-text="$('drain_funds_desc')"></p>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
@@ -333,15 +335,11 @@
|
||||
<q-expansion-item
|
||||
group="extras"
|
||||
icon="settings_cell"
|
||||
label="Export to Phone with QR Code"
|
||||
:label="$t('export_to_phone')"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section class="text-center">
|
||||
<p>
|
||||
This QR code contains your wallet URL with full access.
|
||||
You can scan it from your phone to open your wallet from
|
||||
there.
|
||||
</p>
|
||||
<p v-text="$t('export_to_phone_desc')"></p>
|
||||
<qrcode
|
||||
:value="'{{request.base_url}}' +'wallet?usr={{user.id}}&wal={{wallet.id}}'"
|
||||
:options="{width:240}"
|
||||
@@ -353,7 +351,7 @@
|
||||
<q-expansion-item
|
||||
group="extras"
|
||||
icon="edit"
|
||||
label="Rename wallet"
|
||||
:label="$t('rename_wallet')"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
@@ -371,9 +369,9 @@
|
||||
unelevated
|
||||
class="q-mt-sm"
|
||||
color="primary"
|
||||
:label="$t('update_name')"
|
||||
@click="updateWalletName()"
|
||||
>Update name</q-btn
|
||||
>
|
||||
></q-btn>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
@@ -381,20 +379,17 @@
|
||||
<q-expansion-item
|
||||
group="extras"
|
||||
icon="remove_circle"
|
||||
label="Delete wallet"
|
||||
:label="$t('delete_wallet')"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<p>
|
||||
This whole wallet will be deleted, the funds will be
|
||||
<strong>UNRECOVERABLE</strong>.
|
||||
</p>
|
||||
<p v-text="$t('delete_wallet_desc')"></p>
|
||||
<q-btn
|
||||
unelevated
|
||||
color="red-10"
|
||||
@click="deleteWallet('{{ wallet.id }}', '{{ user.id }}')"
|
||||
>Delete wallet</q-btn
|
||||
>
|
||||
:label="$t('delete_wallet')"
|
||||
></q-btn>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
@@ -445,7 +440,7 @@
|
||||
filled
|
||||
dense
|
||||
v-model.number="receive.data.amount"
|
||||
label="Amount ({{LNBITS_DENOMINATION}}) *"
|
||||
:label="$('amount')+' ({{LNBITS_DENOMINATION}}) *'"
|
||||
mask="#.##"
|
||||
fill-mask="0"
|
||||
reverse-fill-mask
|
||||
@@ -459,7 +454,7 @@
|
||||
dense
|
||||
v-model="receive.unit"
|
||||
type="text"
|
||||
label="Unit"
|
||||
:label="$t('unit')"
|
||||
:options="receive.units"
|
||||
></q-select>
|
||||
<q-input
|
||||
@@ -467,7 +462,7 @@
|
||||
filled
|
||||
dense
|
||||
v-model.number="receive.data.amount"
|
||||
:label="'Amount (' + receive.unit + ') *'"
|
||||
:label="$t('amount') + ' (' + receive.unit + ') *'"
|
||||
:mask="receive.unit != 'sat' ? '#.##' : '#'"
|
||||
fill-mask="0"
|
||||
reverse-fill-mask
|
||||
@@ -482,7 +477,7 @@
|
||||
filled
|
||||
dense
|
||||
v-model.trim="receive.data.memo"
|
||||
label="Memo"
|
||||
:label="$t('memo')"
|
||||
></q-input>
|
||||
{% raw %}
|
||||
<div v-if="receive.status == 'pending'" class="row q-mt-lg">
|
||||
@@ -493,13 +488,17 @@
|
||||
type="submit"
|
||||
>
|
||||
<span v-if="receive.lnurl">
|
||||
Withdraw from {{receive.lnurl.domain}}
|
||||
{{$t('withdraw_from')}} {{receive.lnurl.domain}}
|
||||
</span>
|
||||
<span v-else> Create invoice </span>
|
||||
<span v-else v-text="$t('create_invoice')"></span>
|
||||
</q-btn>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('cancel')"
|
||||
></q-btn>
|
||||
</div>
|
||||
<q-spinner
|
||||
v-if="receive.status == 'loading'"
|
||||
@@ -521,12 +520,19 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn outline color="grey" @click="copyText(receive.paymentReq)"
|
||||
>Copy invoice</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Close</q-btn
|
||||
>
|
||||
<q-btn
|
||||
outline
|
||||
color="grey"
|
||||
@click="copyText(receive.paymentReq)"
|
||||
:label="$t('copy_invoice')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('close')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
{% endraw %}
|
||||
@@ -545,24 +551,42 @@
|
||||
</h6>
|
||||
<q-separator class="q-my-sm"></q-separator>
|
||||
<p class="text-wrap">
|
||||
<strong>Description:</strong> {{ parse.invoice.description }}<br />
|
||||
<strong v-text="$t('description')">:</strong> {{
|
||||
parse.invoice.description }}<br />
|
||||
<strong>Expire date:</strong> {{ parse.invoice.expireDate }}<br />
|
||||
<strong>Hash:</strong> {{ parse.invoice.hash }}
|
||||
</p>
|
||||
{% endraw %}
|
||||
<div v-if="canPay" class="row q-mt-lg">
|
||||
<q-btn unelevated color="primary" @click="payInvoice">Pay</q-btn>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
@click="payInvoice"
|
||||
:label="$t('pay')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('cancel')"
|
||||
></q-btn>
|
||||
</div>
|
||||
<div v-else class="row q-mt-lg">
|
||||
<q-btn unelevated disabled color="yellow" text-color="black"
|
||||
>Not enough funds!</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
<q-btn
|
||||
:label="$t('not_enough_funds')"
|
||||
unelevated
|
||||
disabled
|
||||
color="yellow"
|
||||
text-color="black"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('cancel')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="parse.lnurlauth">
|
||||
@@ -584,9 +608,13 @@
|
||||
</p>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn unelevated color="primary" type="submit">Login</q-btn>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
<q-btn
|
||||
:label="$t('cancel')"
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
{% endraw %}
|
||||
@@ -657,9 +685,13 @@
|
||||
<q-btn unelevated color="primary" type="submit"
|
||||
>Send {{LNBITS_DENOMINATION}}</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
<q-btn
|
||||
:label="$t('cancel')"
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
{% endraw %}
|
||||
@@ -675,7 +707,7 @@
|
||||
dense
|
||||
v-model.trim="parse.data.request"
|
||||
type="textarea"
|
||||
label="Paste an invoice, payment request or lnurl code *"
|
||||
:label="$t('paste_invoice_label')"
|
||||
>
|
||||
</q-input>
|
||||
<div class="row q-mt-lg">
|
||||
@@ -684,11 +716,15 @@
|
||||
color="primary"
|
||||
:disable="parse.data.request == ''"
|
||||
type="submit"
|
||||
>Read</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
:label="$t('read')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('cancel')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
<div v-else>
|
||||
@@ -700,8 +736,13 @@
|
||||
></qrcode-stream>
|
||||
</q-responsive>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn @click="closeCamera" flat color="grey" class="q-ml-auto">
|
||||
Cancel
|
||||
<q-btn
|
||||
:label="$t('cancel')"
|
||||
@click="closeCamera"
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -719,9 +760,13 @@
|
||||
></qrcode-stream>
|
||||
</div>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn @click="closeCamera" flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
<q-btn
|
||||
@click="closeCamera"
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('cancel')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
@@ -755,31 +800,21 @@
|
||||
<q-dialog v-model="disclaimerDialog.show">
|
||||
<q-card class="q-pa-lg">
|
||||
<h6 class="q-my-md text-primary">Warning</h6>
|
||||
<p>
|
||||
Login functionality to be released in a future update, for now,
|
||||
<strong
|
||||
>make sure you bookmark this page for future access to your
|
||||
wallet</strong
|
||||
>!
|
||||
</p>
|
||||
<p>
|
||||
This service is in BETA, and we hold no responsibility for people
|
||||
losing access to funds. {% if service_fee > 0 %} To encourage you to
|
||||
run your own LNbits installation, any balance on {% raw %}{{
|
||||
disclaimerDialog.location.host }}{% endraw %} will incur a charge of
|
||||
<strong>{{ service_fee }}% service fee</strong> per week. {% endif
|
||||
%}
|
||||
</p>
|
||||
<p v-text="$t('disclaimer_dialog')"></p>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn
|
||||
outline
|
||||
color="grey"
|
||||
@click="copyText(disclaimerDialog.location.href)"
|
||||
>Copy wallet URL</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>I understand</q-btn
|
||||
>
|
||||
:label="$t('copy_wallet_url')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
color="grey"
|
||||
class="q-ml-auto"
|
||||
:label="$t('i_understand')"
|
||||
></q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
+27
-17
@@ -34,10 +34,12 @@ from lnbits.core.helpers import (
|
||||
stop_extension_background_work,
|
||||
)
|
||||
from lnbits.core.models import Payment, User, Wallet
|
||||
from lnbits.db import Filters
|
||||
from lnbits.decorators import (
|
||||
WalletTypeInfo,
|
||||
check_admin,
|
||||
get_key_type,
|
||||
parse_filters,
|
||||
require_admin_key,
|
||||
require_invoice_key,
|
||||
)
|
||||
@@ -48,7 +50,7 @@ from lnbits.extension_manager import (
|
||||
InstallableExtension,
|
||||
get_valid_extensions,
|
||||
)
|
||||
from lnbits.helpers import url_for
|
||||
from lnbits.helpers import generate_filter_params_openapi, url_for
|
||||
from lnbits.settings import get_wallet_class, settings
|
||||
from lnbits.utils.exchange_rates import (
|
||||
currencies,
|
||||
@@ -114,18 +116,23 @@ async def api_update_wallet(
|
||||
}
|
||||
|
||||
|
||||
@core_app.get("/api/v1/payments")
|
||||
@core_app.get(
|
||||
"/api/v1/payments",
|
||||
name="Payment List",
|
||||
summary="get list of payments",
|
||||
response_description="list of payments",
|
||||
response_model=List[Payment],
|
||||
openapi_extra=generate_filter_params_openapi(Payment),
|
||||
)
|
||||
async def api_payments(
|
||||
limit: Optional[int] = None,
|
||||
offset: Optional[int] = None,
|
||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||
filters: Filters = Depends(parse_filters(Payment)),
|
||||
):
|
||||
pendingPayments = await get_payments(
|
||||
wallet_id=wallet.wallet.id,
|
||||
pending=True,
|
||||
exclude_uncheckable=True,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
filters=filters,
|
||||
)
|
||||
for payment in pendingPayments:
|
||||
await check_transaction_status(
|
||||
@@ -135,8 +142,7 @@ async def api_payments(
|
||||
wallet_id=wallet.wallet.id,
|
||||
pending=True,
|
||||
complete=True,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
filters=filters,
|
||||
)
|
||||
|
||||
|
||||
@@ -405,8 +411,7 @@ async def subscribe_wallet_invoices(request: Request, wallet: Wallet):
|
||||
typ, data = await send_queue.get()
|
||||
if data:
|
||||
jdata = json.dumps(dict(data.dict(), pending=False))
|
||||
|
||||
yield dict(data=jdata, event=typ)
|
||||
yield dict(data=jdata, event=typ)
|
||||
except asyncio.CancelledError:
|
||||
logger.debug(f"removing listener for wallet {uid}")
|
||||
api_invoice_listeners.pop(uid)
|
||||
@@ -425,11 +430,12 @@ async def api_payments_sse(
|
||||
)
|
||||
|
||||
|
||||
# TODO: refactor this route into a public and admin one
|
||||
@core_app.get("/api/v1/payments/{payment_hash}")
|
||||
async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
|
||||
# We use X_Api_Key here because we want this call to work with and without keys
|
||||
# If a valid key is given, we also return the field "details", otherwise not
|
||||
wallet = await get_wallet_for_key(X_Api_Key) if type(X_Api_Key) == str else None
|
||||
wallet = await get_wallet_for_key(X_Api_Key) if type(X_Api_Key) == str else None # type: ignore
|
||||
|
||||
# we have to specify the wallet id here, because postgres and sqlite return internal payments in different order
|
||||
# and get_standalone_payment otherwise just fetches the first one, causing unpredictable results
|
||||
@@ -499,6 +505,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
|
||||
params.update(callback=url) # with k1 already in it
|
||||
|
||||
lnurlauth_key = wallet.wallet.lnurlauth_key(domain)
|
||||
assert lnurlauth_key.verifying_key
|
||||
params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex())
|
||||
else:
|
||||
async with httpx.AsyncClient() as client:
|
||||
@@ -564,7 +571,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
|
||||
if k == "text/plain":
|
||||
params.update(description=v)
|
||||
if k in ("image/jpeg;base64", "image/png;base64"):
|
||||
data_uri = "data:" + k + "," + v
|
||||
data_uri = f"data:{k},{v}"
|
||||
params.update(image=data_uri)
|
||||
if k in ("text/email", "text/identifier"):
|
||||
params.update(targetUser=v)
|
||||
@@ -687,7 +694,7 @@ async def api_auditor():
|
||||
if not error_message:
|
||||
delta = node_balance - total_balance
|
||||
else:
|
||||
node_balance, delta = None, None
|
||||
node_balance, delta = 0, 0
|
||||
|
||||
return {
|
||||
"node_balance_msats": int(node_balance),
|
||||
@@ -697,7 +704,7 @@ async def api_auditor():
|
||||
}
|
||||
|
||||
|
||||
##################UNIVERSAL WEBSOCKET MANAGER########################
|
||||
# UNIVERSAL WEBSOCKET MANAGER
|
||||
|
||||
|
||||
@core_app.websocket("/api/v1/ws/{item_id}")
|
||||
@@ -739,6 +746,7 @@ async def api_install_extension(
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Release not found"
|
||||
)
|
||||
|
||||
ext_info = InstallableExtension(
|
||||
id=data.ext_id, name=data.ext_id, installed_release=release, icon=release.icon
|
||||
)
|
||||
@@ -818,8 +826,10 @@ async def api_uninstall_extension(ext_id: str, user: User = Depends(check_admin)
|
||||
)
|
||||
|
||||
|
||||
@core_app.get("/api/v1/extension/{ext_id}/releases")
|
||||
async def get_extension_releases(ext_id: str, user: User = Depends(check_admin)):
|
||||
@core_app.get(
|
||||
"/api/v1/extension/{ext_id}/releases", dependencies=[Depends(check_admin)]
|
||||
)
|
||||
async def get_extension_releases(ext_id: str):
|
||||
try:
|
||||
extension_releases: List[
|
||||
ExtensionRelease
|
||||
@@ -833,7 +843,7 @@ async def get_extension_releases(ext_id: str, user: User = Depends(check_admin))
|
||||
)
|
||||
|
||||
|
||||
############################TINYURL##################################
|
||||
# TINYURL
|
||||
|
||||
|
||||
@core_app.post("/api/v1/tinyurl")
|
||||
|
||||
@@ -369,7 +369,7 @@ async def manifest(usr: str):
|
||||
"sizes": "900x900",
|
||||
}
|
||||
],
|
||||
"start_url": "/wallet?usr=" + usr + "&wal=" + user.wallets[0].id,
|
||||
"start_url": f"/wallet?usr={usr}&wal={user.wallets[0].id}",
|
||||
"background_color": "#1F2234",
|
||||
"description": "Bitcoin Lightning Wallet",
|
||||
"display": "standalone",
|
||||
@@ -380,7 +380,7 @@ async def manifest(usr: str):
|
||||
"name": wallet.name,
|
||||
"short_name": wallet.name,
|
||||
"description": wallet.name,
|
||||
"url": "/wallet?usr=" + usr + "&wal=" + wallet.id,
|
||||
"url": f"/wallet?usr={usr}&wal={wallet.id}",
|
||||
}
|
||||
for wallet in user.wallets
|
||||
],
|
||||
|
||||
@@ -40,19 +40,18 @@ async def api_public_payment_longpolling(payment_hash):
|
||||
|
||||
response = None
|
||||
|
||||
async def payment_info_receiver(cancel_scope):
|
||||
async for payment in payment_queue.get():
|
||||
async def payment_info_receiver():
|
||||
for payment in await payment_queue.get():
|
||||
if payment.payment_hash == payment_hash:
|
||||
nonlocal response
|
||||
response = {"status": "paid"}
|
||||
cancel_scope.cancel()
|
||||
|
||||
async def timeouter(cancel_scope):
|
||||
await asyncio.sleep(45)
|
||||
cancel_scope.cancel()
|
||||
|
||||
asyncio.create_task(payment_info_receiver())
|
||||
asyncio.create_task(timeouter())
|
||||
cancel_scope = asyncio.create_task(payment_info_receiver())
|
||||
asyncio.create_task(timeouter(cancel_scope))
|
||||
|
||||
if response:
|
||||
return response
|
||||
|
||||
+132
-14
@@ -4,9 +4,11 @@ import os
|
||||
import re
|
||||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
from typing import Any, Generic, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy_aio.base import AsyncConnection
|
||||
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY
|
||||
@@ -94,7 +96,7 @@ class Connection(Compat):
|
||||
|
||||
# tuple to list and back to tuple
|
||||
value_list = [values] if isinstance(values, str) else list(values)
|
||||
values = tuple([cleanhtml(l) for l in value_list])
|
||||
values = tuple([cleanhtml(val) for val in value_list])
|
||||
return values
|
||||
|
||||
async def fetchall(self, query: str, values: tuple = ()) -> list:
|
||||
@@ -129,7 +131,7 @@ class Database(Compat):
|
||||
else:
|
||||
self.type = POSTGRES
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extensions import DECIMAL, new_type, register_type
|
||||
|
||||
def _parse_timestamp(value, _):
|
||||
if value is None:
|
||||
@@ -139,15 +141,15 @@ class Database(Compat):
|
||||
f = "%Y-%m-%d %H:%M:%S"
|
||||
return time.mktime(datetime.datetime.strptime(value, f).timetuple())
|
||||
|
||||
psycopg2.extensions.register_type(
|
||||
psycopg2.extensions.new_type(
|
||||
psycopg2.extensions.DECIMAL.values,
|
||||
register_type(
|
||||
new_type(
|
||||
DECIMAL.values,
|
||||
"DEC2FLOAT",
|
||||
lambda value, curs: float(value) if value is not None else None,
|
||||
)
|
||||
)
|
||||
psycopg2.extensions.register_type(
|
||||
psycopg2.extensions.new_type(
|
||||
register_type(
|
||||
new_type(
|
||||
(1082, 1083, 1266),
|
||||
"DATE2INT",
|
||||
lambda value, curs: time.mktime(value.timetuple())
|
||||
@@ -156,11 +158,7 @@ class Database(Compat):
|
||||
)
|
||||
)
|
||||
|
||||
psycopg2.extensions.register_type(
|
||||
psycopg2.extensions.new_type(
|
||||
(1184, 1114), "TIMESTAMP2INT", _parse_timestamp
|
||||
)
|
||||
)
|
||||
register_type(new_type((1184, 1114), "TIMESTAMP2INT", _parse_timestamp))
|
||||
else:
|
||||
if os.path.isdir(settings.lnbits_data_folder):
|
||||
self.path = os.path.join(
|
||||
@@ -187,7 +185,7 @@ class Database(Compat):
|
||||
async def connect(self):
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
async with self.engine.connect() as conn:
|
||||
async with self.engine.connect() as conn: # type: ignore
|
||||
async with conn.begin() as txn:
|
||||
wconn = Connection(conn, txn, self.type, self.name, self.schema)
|
||||
|
||||
@@ -224,3 +222,123 @@ class Database(Compat):
|
||||
@asynccontextmanager
|
||||
async def reuse_conn(self, conn: Connection):
|
||||
yield conn
|
||||
|
||||
|
||||
class Operator(Enum):
|
||||
GT = "gt"
|
||||
LT = "lt"
|
||||
EQ = "eq"
|
||||
NE = "ne"
|
||||
INCLUDE = "in"
|
||||
EXCLUDE = "ex"
|
||||
|
||||
@property
|
||||
def as_sql(self):
|
||||
if self == Operator.EQ:
|
||||
return "="
|
||||
elif self == Operator.NE:
|
||||
return "!="
|
||||
elif self == Operator.INCLUDE:
|
||||
return "IN"
|
||||
elif self == Operator.EXCLUDE:
|
||||
return "NOT IN"
|
||||
elif self == Operator.GT:
|
||||
return ">"
|
||||
elif self == Operator.LT:
|
||||
return "<"
|
||||
else:
|
||||
raise ValueError("Unknown SQL Operator")
|
||||
|
||||
|
||||
TModel = TypeVar("TModel", bound=BaseModel)
|
||||
|
||||
|
||||
class Filter(BaseModel, Generic[TModel]):
|
||||
field: str
|
||||
nested: Optional[list[str]]
|
||||
op: Operator = Operator.EQ
|
||||
values: list[Any]
|
||||
|
||||
@classmethod
|
||||
def parse_query(cls, key: str, raw_values: list[Any], model: Type[TModel]):
|
||||
# Key format:
|
||||
# key[operator]
|
||||
# e.g. name[eq]
|
||||
if key.endswith("]"):
|
||||
split = key[:-1].split("[")
|
||||
if len(split) != 2:
|
||||
raise ValueError("Invalid key")
|
||||
field_names = split[0].split(".")
|
||||
op = Operator(split[1])
|
||||
else:
|
||||
field_names = key.split(".")
|
||||
op = Operator("eq")
|
||||
|
||||
field = field_names[0]
|
||||
nested = field_names[1:]
|
||||
|
||||
if field in model.__fields__:
|
||||
compare_field = model.__fields__[field]
|
||||
values = []
|
||||
for raw_value in raw_values:
|
||||
# If there is a nested field, pydantic expects a dict, so the raw value is turned into a dict before
|
||||
# and the converted value is extracted afterwards
|
||||
for name in reversed(nested):
|
||||
raw_value = {name: raw_value}
|
||||
|
||||
validated, errors = compare_field.validate(raw_value, {}, loc="none")
|
||||
if errors:
|
||||
raise ValidationError(errors=[errors], model=model)
|
||||
|
||||
for name in nested:
|
||||
if isinstance(validated, dict):
|
||||
validated = validated[name]
|
||||
else:
|
||||
validated = getattr(validated, name)
|
||||
|
||||
values.append(validated)
|
||||
else:
|
||||
raise ValueError("Unknown filter field")
|
||||
|
||||
return cls(field=field, op=op, nested=nested, values=values)
|
||||
|
||||
@property
|
||||
def statement(self):
|
||||
accessor = self.field
|
||||
if self.nested:
|
||||
for name in self.nested:
|
||||
accessor = f"({accessor} ->> '{name}')"
|
||||
if self.op in (Operator.INCLUDE, Operator.EXCLUDE):
|
||||
placeholders = ", ".join(["?"] * len(self.values))
|
||||
stmt = [f"{accessor} {self.op.as_sql} ({placeholders})"]
|
||||
else:
|
||||
stmt = [f"{accessor} {self.op.as_sql} ?"] * len(self.values)
|
||||
return " OR ".join(stmt)
|
||||
|
||||
|
||||
class Filters(BaseModel, Generic[TModel]):
|
||||
filters: List[Filter[TModel]] = []
|
||||
limit: Optional[int]
|
||||
offset: Optional[int]
|
||||
|
||||
def pagination(self) -> str:
|
||||
stmt = ""
|
||||
if self.limit:
|
||||
stmt += f"LIMIT {self.limit} "
|
||||
if self.offset:
|
||||
stmt += f"OFFSET {self.offset}"
|
||||
return stmt
|
||||
|
||||
def where(self, where_stmts: List[str]) -> str:
|
||||
if self.filters:
|
||||
for filter in self.filters:
|
||||
where_stmts.append(filter.statement)
|
||||
if where_stmts:
|
||||
return "WHERE " + " AND ".join(where_stmts)
|
||||
return ""
|
||||
|
||||
def values(self, values: List[str]) -> Tuple:
|
||||
if self.filters:
|
||||
for filter in self.filters:
|
||||
values.extend(filter.values)
|
||||
return tuple(values)
|
||||
|
||||
+46
-9
@@ -1,22 +1,27 @@
|
||||
from http import HTTPStatus
|
||||
from typing import Optional, Type
|
||||
|
||||
from fastapi import Security, status
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi import HTTPException, Request, Security, status
|
||||
from fastapi.openapi.models import APIKey, APIKeyIn
|
||||
from fastapi.security.api_key import APIKeyHeader, APIKeyQuery
|
||||
from fastapi.security import APIKeyHeader, APIKeyQuery
|
||||
from fastapi.security.base import SecurityBase
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import UUID4
|
||||
from starlette.requests import Request
|
||||
|
||||
from lnbits.core.crud import get_user, get_wallet_for_key
|
||||
from lnbits.core.models import User, Wallet
|
||||
from lnbits.db import Filter, Filters
|
||||
from lnbits.requestvars import g
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
||||
# TODO: fix type ignores
|
||||
class KeyChecker(SecurityBase):
|
||||
def __init__(
|
||||
self, scheme_name: str = None, auto_error: bool = True, api_key: str = None
|
||||
self,
|
||||
scheme_name: Optional[str] = None,
|
||||
auto_error: bool = True,
|
||||
api_key: Optional[str] = None,
|
||||
):
|
||||
self.scheme_name = scheme_name or self.__class__.__name__
|
||||
self.auto_error = auto_error
|
||||
@@ -24,13 +29,13 @@ class KeyChecker(SecurityBase):
|
||||
self._api_key = api_key
|
||||
if api_key:
|
||||
key = APIKey(
|
||||
**{"in": APIKeyIn.query},
|
||||
**{"in": APIKeyIn.query}, # type: ignore
|
||||
name="X-API-KEY",
|
||||
description="Wallet API Key - QUERY",
|
||||
)
|
||||
else:
|
||||
key = APIKey(
|
||||
**{"in": APIKeyIn.header},
|
||||
**{"in": APIKeyIn.header}, # type: ignore
|
||||
name="X-API-KEY",
|
||||
description="Wallet API Key - HEADER",
|
||||
)
|
||||
@@ -70,7 +75,10 @@ class WalletInvoiceKeyChecker(KeyChecker):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, scheme_name: str = None, auto_error: bool = True, api_key: str = None
|
||||
self,
|
||||
scheme_name: Optional[str] = None,
|
||||
auto_error: bool = True,
|
||||
api_key: Optional[str] = None,
|
||||
):
|
||||
super().__init__(scheme_name, auto_error, api_key)
|
||||
self._key_type = "invoice"
|
||||
@@ -86,7 +94,10 @@ class WalletAdminKeyChecker(KeyChecker):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, scheme_name: str = None, auto_error: bool = True, api_key: str = None
|
||||
self,
|
||||
scheme_name: Optional[str] = None,
|
||||
auto_error: bool = True,
|
||||
api_key: Optional[str] = None,
|
||||
):
|
||||
super().__init__(scheme_name, auto_error, api_key)
|
||||
self._key_type = "admin"
|
||||
@@ -266,3 +277,29 @@ async def check_super_user(usr: UUID4) -> User:
|
||||
detail="User not authorized. No super user privileges.",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
def parse_filters(model: Type[BaseModel]):
|
||||
"""
|
||||
Parses the query params as filters.
|
||||
:param model: model used for validation of filter values
|
||||
"""
|
||||
|
||||
def dependency(
|
||||
request: Request, limit: Optional[int] = None, offset: Optional[int] = None
|
||||
):
|
||||
params = request.query_params
|
||||
filters = []
|
||||
for key in params.keys():
|
||||
try:
|
||||
filters.append(Filter.parse_query(key, params.getlist(key), model))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
return Filters(
|
||||
filters=filters,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
return dependency
|
||||
|
||||
+155
-164
@@ -3,20 +3,145 @@ import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from http import HTTPStatus
|
||||
from pathlib import Path
|
||||
from typing import Any, List, NamedTuple, Optional, Tuple
|
||||
from urllib import request
|
||||
|
||||
import httpx
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
||||
class ExplicitRelease(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
version: str
|
||||
archive: str
|
||||
hash: str
|
||||
dependencies: List[str] = []
|
||||
icon: Optional[str]
|
||||
short_description: Optional[str]
|
||||
html_url: Optional[str]
|
||||
details: Optional[str]
|
||||
info_notification: Optional[str]
|
||||
critical_notification: Optional[str]
|
||||
|
||||
|
||||
class GitHubRelease(BaseModel):
|
||||
id: str
|
||||
organisation: str
|
||||
repository: str
|
||||
|
||||
|
||||
class Manifest(BaseModel):
|
||||
featured: List[str] = []
|
||||
extensions: List["ExplicitRelease"] = []
|
||||
repos: List["GitHubRelease"] = []
|
||||
|
||||
|
||||
class GitHubRepoRelease(BaseModel):
|
||||
name: str
|
||||
tag_name: str
|
||||
zipball_url: str
|
||||
html_url: str
|
||||
|
||||
|
||||
class GitHubRepo(BaseModel):
|
||||
stargazers_count: str
|
||||
html_url: str
|
||||
default_branch: str
|
||||
|
||||
|
||||
class ExtensionConfig(BaseModel):
|
||||
name: str
|
||||
short_description: str
|
||||
tile: str = ""
|
||||
|
||||
|
||||
def download_url(url, save_path):
|
||||
with request.urlopen(url) as dl_file:
|
||||
with open(save_path, "wb") as out_file:
|
||||
out_file.write(dl_file.read())
|
||||
|
||||
|
||||
def file_hash(filename):
|
||||
h = hashlib.sha256()
|
||||
b = bytearray(128 * 1024)
|
||||
mv = memoryview(b)
|
||||
with open(filename, "rb", buffering=0) as f:
|
||||
while n := f.readinto(mv):
|
||||
h.update(mv[:n])
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
async def fetch_github_repo_info(
|
||||
org: str, repository: str
|
||||
) -> Tuple[GitHubRepo, GitHubRepoRelease, ExtensionConfig]:
|
||||
repo_url = f"https://api.github.com/repos/{org}/{repository}"
|
||||
error_msg = "Cannot fetch extension repo"
|
||||
repo = await gihub_api_get(repo_url, error_msg)
|
||||
github_repo = GitHubRepo.parse_obj(repo)
|
||||
|
||||
lates_release_url = (
|
||||
f"https://api.github.com/repos/{org}/{repository}/releases/latest"
|
||||
)
|
||||
error_msg = "Cannot fetch extension releases"
|
||||
latest_release: Any = await gihub_api_get(lates_release_url, error_msg)
|
||||
|
||||
config_url = f"https://raw.githubusercontent.com/{org}/{repository}/{github_repo.default_branch}/config.json"
|
||||
error_msg = "Cannot fetch config for extension"
|
||||
config = await gihub_api_get(config_url, error_msg)
|
||||
|
||||
return (
|
||||
github_repo,
|
||||
GitHubRepoRelease.parse_obj(latest_release),
|
||||
ExtensionConfig.parse_obj(config),
|
||||
)
|
||||
|
||||
|
||||
async def fetch_manifest(url) -> Manifest:
|
||||
error_msg = "Cannot fetch extensions manifest"
|
||||
manifest = await gihub_api_get(url, error_msg)
|
||||
return Manifest.parse_obj(manifest)
|
||||
|
||||
|
||||
async def fetch_github_releases(org: str, repo: str) -> List[GitHubRepoRelease]:
|
||||
releases_url = f"https://api.github.com/repos/{org}/{repo}/releases"
|
||||
error_msg = "Cannot fetch extension releases"
|
||||
releases = await gihub_api_get(releases_url, error_msg)
|
||||
return [GitHubRepoRelease.parse_obj(r) for r in releases]
|
||||
|
||||
|
||||
async def gihub_api_get(url: str, error_msg: Optional[str]) -> Any:
|
||||
async with httpx.AsyncClient() as client:
|
||||
headers = (
|
||||
{"Authorization": "Bearer " + settings.lnbits_ext_github_token}
|
||||
if settings.lnbits_ext_github_token
|
||||
else None
|
||||
)
|
||||
resp = await client.get(
|
||||
url,
|
||||
headers=headers,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
logger.warning(f"{error_msg} ({url}): {resp.text}")
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
|
||||
def icon_to_github_url(source_repo: str, path: Optional[str]) -> str:
|
||||
if not path:
|
||||
return ""
|
||||
_, _, *rest = path.split("/")
|
||||
tail = "/".join(rest)
|
||||
return f"https://github.com/{source_repo}/raw/main/{tail}"
|
||||
|
||||
|
||||
class Extension(NamedTuple):
|
||||
code: str
|
||||
is_valid: bool
|
||||
@@ -97,12 +222,12 @@ class ExtensionRelease(BaseModel):
|
||||
version: str
|
||||
archive: str
|
||||
source_repo: str
|
||||
is_github_release = False
|
||||
hash: Optional[str]
|
||||
html_url: Optional[str]
|
||||
description: Optional[str]
|
||||
is_github_release: bool = False
|
||||
hash: Optional[str] = None
|
||||
html_url: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
details_html: Optional[str] = None
|
||||
icon: Optional[str]
|
||||
icon: Optional[str] = None
|
||||
|
||||
@classmethod
|
||||
def from_github_release(
|
||||
@@ -132,52 +257,6 @@ class ExtensionRelease(BaseModel):
|
||||
return []
|
||||
|
||||
|
||||
class ExplicitRelease(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
version: str
|
||||
archive: str
|
||||
hash: str
|
||||
dependencies: List[str] = []
|
||||
icon: Optional[str]
|
||||
short_description: Optional[str]
|
||||
html_url: Optional[str]
|
||||
details: Optional[str]
|
||||
info_notification: Optional[str]
|
||||
critical_notification: Optional[str]
|
||||
|
||||
|
||||
class GitHubRelease(BaseModel):
|
||||
id: str
|
||||
organisation: str
|
||||
repository: str
|
||||
|
||||
|
||||
class Manifest(BaseModel):
|
||||
featured: List[str] = []
|
||||
extensions: List["ExplicitRelease"] = []
|
||||
repos: List["GitHubRelease"] = []
|
||||
|
||||
|
||||
class GitHubRepoRelease(BaseModel):
|
||||
name: str
|
||||
tag_name: str
|
||||
zipball_url: str
|
||||
html_url: str
|
||||
|
||||
|
||||
class GitHubRepo(BaseModel):
|
||||
stargazers_count: str
|
||||
html_url: str
|
||||
default_branch: str
|
||||
|
||||
|
||||
class ExtensionConfig(BaseModel):
|
||||
name: str
|
||||
short_description: str
|
||||
tile: str = ""
|
||||
|
||||
|
||||
class InstallableExtension(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
@@ -187,8 +266,9 @@ class InstallableExtension(BaseModel):
|
||||
is_admin_only: bool = False
|
||||
stars: int = 0
|
||||
featured = False
|
||||
latest_release: Optional[ExtensionRelease]
|
||||
installed_release: Optional[ExtensionRelease]
|
||||
latest_release: Optional[ExtensionRelease] = None
|
||||
installed_release: Optional[ExtensionRelease] = None
|
||||
archive: Optional[str] = None
|
||||
|
||||
@property
|
||||
def hash(self) -> str:
|
||||
@@ -201,18 +281,18 @@ class InstallableExtension(BaseModel):
|
||||
return "not-installed"
|
||||
|
||||
@property
|
||||
def zip_path(self) -> str:
|
||||
extensions_data_dir = os.path.join(settings.lnbits_data_folder, "extensions")
|
||||
def zip_path(self) -> Path:
|
||||
extensions_data_dir = Path(settings.lnbits_data_folder, "extensions")
|
||||
os.makedirs(extensions_data_dir, exist_ok=True)
|
||||
return os.path.join(extensions_data_dir, f"{self.id}.zip")
|
||||
return Path(extensions_data_dir, f"{self.id}.zip")
|
||||
|
||||
@property
|
||||
def ext_dir(self) -> str:
|
||||
return os.path.join("lnbits", "extensions", self.id)
|
||||
def ext_dir(self) -> Path:
|
||||
return Path(settings.lnbits_path, "extensions", self.id)
|
||||
|
||||
@property
|
||||
def ext_upgrade_dir(self) -> str:
|
||||
return os.path.join("lnbits", "upgrades", f"{self.id}-{self.hash}")
|
||||
def ext_upgrade_dir(self) -> Path:
|
||||
return Path("lnbits", "upgrades", f"{self.id}-{self.hash}")
|
||||
|
||||
@property
|
||||
def module_name(self) -> str:
|
||||
@@ -224,21 +304,17 @@ class InstallableExtension(BaseModel):
|
||||
|
||||
@property
|
||||
def has_installed_version(self) -> bool:
|
||||
if not Path(self.ext_dir).is_dir():
|
||||
if not self.ext_dir.is_dir():
|
||||
return False
|
||||
config_file = os.path.join(self.ext_dir, "config.json")
|
||||
if not Path(config_file).is_file():
|
||||
return False
|
||||
with open(config_file, "r") as json_file:
|
||||
config_json = json.load(json_file)
|
||||
return config_json.get("is_installed") is True
|
||||
return Path(self.ext_dir, "config.json").is_file()
|
||||
|
||||
def download_archive(self):
|
||||
logger.info(f"Downloading extension {self.name}.")
|
||||
ext_zip_file = self.zip_path
|
||||
if os.path.isfile(ext_zip_file):
|
||||
if ext_zip_file.is_file():
|
||||
os.remove(ext_zip_file)
|
||||
try:
|
||||
assert self.installed_release, "installed_release is none."
|
||||
download_url(self.installed_release.archive, ext_zip_file)
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
@@ -250,7 +326,7 @@ class InstallableExtension(BaseModel):
|
||||
archive_hash = file_hash(ext_zip_file)
|
||||
if self.installed_release.hash and self.installed_release.hash != archive_hash:
|
||||
# remove downloaded archive
|
||||
if os.path.isfile(ext_zip_file):
|
||||
if ext_zip_file.is_file():
|
||||
os.remove(ext_zip_file)
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
@@ -259,26 +335,22 @@ class InstallableExtension(BaseModel):
|
||||
|
||||
def extract_archive(self):
|
||||
logger.info(f"Extracting extension {self.name}.")
|
||||
os.makedirs(os.path.join("lnbits", "upgrades"), exist_ok=True)
|
||||
os.makedirs(Path("lnbits", "upgrades"), exist_ok=True)
|
||||
shutil.rmtree(self.ext_upgrade_dir, True)
|
||||
with zipfile.ZipFile(self.zip_path, "r") as zip_ref:
|
||||
zip_ref.extractall(self.ext_upgrade_dir)
|
||||
generated_dir_name = os.listdir(self.ext_upgrade_dir)[0]
|
||||
os.rename(
|
||||
os.path.join(self.ext_upgrade_dir, generated_dir_name),
|
||||
os.path.join(self.ext_upgrade_dir, self.id),
|
||||
Path(self.ext_upgrade_dir, generated_dir_name),
|
||||
Path(self.ext_upgrade_dir, self.id),
|
||||
)
|
||||
|
||||
# Pre-packed extensions can be upgraded
|
||||
# Mark the extension as installed so we know it is not the pre-packed version
|
||||
with open(
|
||||
os.path.join(self.ext_upgrade_dir, self.id, "config.json"), "r+"
|
||||
Path(self.ext_upgrade_dir, self.id, "config.json"), "r+"
|
||||
) as json_file:
|
||||
config_json = json.load(json_file)
|
||||
config_json["is_installed"] = True
|
||||
json_file.seek(0)
|
||||
json.dump(config_json, json_file)
|
||||
json_file.truncate()
|
||||
|
||||
self.name = config_json.get("name")
|
||||
self.short_description = config_json.get("short_description")
|
||||
@@ -294,8 +366,8 @@ class InstallableExtension(BaseModel):
|
||||
|
||||
shutil.rmtree(self.ext_dir, True)
|
||||
shutil.copytree(
|
||||
os.path.join(self.ext_upgrade_dir, self.id),
|
||||
os.path.join("lnbits", "extensions", self.id),
|
||||
Path(self.ext_upgrade_dir, self.id),
|
||||
Path(settings.lnbits_path, "extensions", self.id),
|
||||
)
|
||||
logger.success(f"Extension {self.name} installed.")
|
||||
|
||||
@@ -314,7 +386,7 @@ class InstallableExtension(BaseModel):
|
||||
|
||||
def clean_extension_files(self):
|
||||
# remove downloaded archive
|
||||
if os.path.isfile(self.zip_path):
|
||||
if self.zip_path.is_file():
|
||||
os.remove(self.zip_path)
|
||||
|
||||
# remove module from extensions
|
||||
@@ -343,8 +415,7 @@ class InstallableExtension(BaseModel):
|
||||
id=github_release.id,
|
||||
name=config.name,
|
||||
short_description=config.short_description,
|
||||
version="0",
|
||||
stars=repo.stargazers_count,
|
||||
stars=int(repo.stargazers_count),
|
||||
icon=icon_to_github_url(
|
||||
f"{github_release.organisation}/{github_release.repository}",
|
||||
config.tile,
|
||||
@@ -363,7 +434,6 @@ class InstallableExtension(BaseModel):
|
||||
id=e.id,
|
||||
name=e.name,
|
||||
archive=e.archive,
|
||||
hash=e.hash,
|
||||
short_description=e.short_description,
|
||||
icon=e.icon,
|
||||
dependencies=e.dependencies,
|
||||
@@ -462,82 +532,3 @@ def get_valid_extensions() -> List[Extension]:
|
||||
return [
|
||||
extension for extension in ExtensionManager().extensions if extension.is_valid
|
||||
]
|
||||
|
||||
|
||||
def download_url(url, save_path):
|
||||
with urllib.request.urlopen(url) as dl_file:
|
||||
with open(save_path, "wb") as out_file:
|
||||
out_file.write(dl_file.read())
|
||||
|
||||
|
||||
def file_hash(filename):
|
||||
h = hashlib.sha256()
|
||||
b = bytearray(128 * 1024)
|
||||
mv = memoryview(b)
|
||||
with open(filename, "rb", buffering=0) as f:
|
||||
while n := f.readinto(mv):
|
||||
h.update(mv[:n])
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def icon_to_github_url(source_repo: str, path: Optional[str]) -> str:
|
||||
if not path:
|
||||
return ""
|
||||
_, _, *rest = path.split("/")
|
||||
tail = "/".join(rest)
|
||||
return f"https://github.com/{source_repo}/raw/main/{tail}"
|
||||
|
||||
|
||||
async def fetch_github_repo_info(
|
||||
org: str, repository: str
|
||||
) -> Tuple[GitHubRepo, GitHubRepoRelease, ExtensionConfig]:
|
||||
repo_url = f"https://api.github.com/repos/{org}/{repository}"
|
||||
error_msg = "Cannot fetch extension repo"
|
||||
repo = await gihub_api_get(repo_url, error_msg)
|
||||
github_repo = GitHubRepo.parse_obj(repo)
|
||||
|
||||
lates_release_url = (
|
||||
f"https://api.github.com/repos/{org}/{repository}/releases/latest"
|
||||
)
|
||||
error_msg = "Cannot fetch extension releases"
|
||||
latest_release: Any = await gihub_api_get(lates_release_url, error_msg)
|
||||
|
||||
config_url = f"https://raw.githubusercontent.com/{org}/{repository}/{github_repo.default_branch}/config.json"
|
||||
error_msg = "Cannot fetch config for extension"
|
||||
config = await gihub_api_get(config_url, error_msg)
|
||||
|
||||
return (
|
||||
github_repo,
|
||||
GitHubRepoRelease.parse_obj(latest_release),
|
||||
ExtensionConfig.parse_obj(config),
|
||||
)
|
||||
|
||||
|
||||
async def fetch_manifest(url) -> Manifest:
|
||||
error_msg = "Cannot fetch extensions manifest"
|
||||
manifest = await gihub_api_get(url, error_msg)
|
||||
return Manifest.parse_obj(manifest)
|
||||
|
||||
|
||||
async def fetch_github_releases(org: str, repo: str) -> List[GitHubRepoRelease]:
|
||||
releases_url = f"https://api.github.com/repos/{org}/{repo}/releases"
|
||||
error_msg = "Cannot fetch extension releases"
|
||||
releases = await gihub_api_get(releases_url, error_msg)
|
||||
return [GitHubRepoRelease.parse_obj(r) for r in releases]
|
||||
|
||||
|
||||
async def gihub_api_get(url: str, error_msg: Optional[str]) -> Any:
|
||||
async with httpx.AsyncClient() as client:
|
||||
headers = (
|
||||
{"Authorization": "Bearer " + settings.lnbits_ext_github_token}
|
||||
if settings.lnbits_ext_github_token
|
||||
else None
|
||||
)
|
||||
resp = await client.get(
|
||||
url,
|
||||
headers=headers,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
logger.warning(f"{error_msg} ({url}): {resp.text}")
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
+68
-71
@@ -1,9 +1,13 @@
|
||||
import glob
|
||||
import os
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, List, Optional, Type
|
||||
|
||||
import jinja2
|
||||
import shortuuid # type: ignore
|
||||
import shortuuid
|
||||
from pydantic import BaseModel
|
||||
from pydantic.schema import (
|
||||
field_schema,
|
||||
get_flat_models_from_fields,
|
||||
get_model_name_map,
|
||||
)
|
||||
|
||||
from lnbits.jinja2_templating import Jinja2Templates
|
||||
from lnbits.requestvars import g
|
||||
@@ -11,75 +15,32 @@ from lnbits.settings import settings
|
||||
|
||||
from .extension_manager import get_valid_extensions
|
||||
|
||||
vendored_js = [
|
||||
"/static/vendor/moment.js",
|
||||
"/static/vendor/underscore.js",
|
||||
"/static/vendor/axios.js",
|
||||
"/static/vendor/vue.js",
|
||||
"/static/vendor/vue-router.js",
|
||||
"/static/vendor/vue-qrcode-reader.browser.js",
|
||||
"/static/vendor/vue-qrcode.js",
|
||||
"/static/vendor/vue-i18n.js",
|
||||
"/static/vendor/vuex.js",
|
||||
"/static/vendor/quasar.ie.polyfills.umd.min.js",
|
||||
"/static/vendor/quasar.umd.js",
|
||||
"/static/vendor/Chart.bundle.js",
|
||||
]
|
||||
|
||||
vendored_css = [
|
||||
"/static/vendor/quasar.css",
|
||||
"/static/vendor/Chart.css",
|
||||
"/static/vendor/vue-qrcode-reader.css",
|
||||
]
|
||||
|
||||
|
||||
def urlsafe_short_hash() -> str:
|
||||
return shortuuid.uuid()
|
||||
|
||||
|
||||
def get_js_vendored(prefer_minified: bool = False) -> List[str]:
|
||||
paths = get_vendored(".js", prefer_minified)
|
||||
|
||||
def sorter(key: str):
|
||||
if "moment@" in key:
|
||||
return 1
|
||||
if "vue@" in key:
|
||||
return 2
|
||||
if "vue-router@" in key:
|
||||
return 3
|
||||
if "polyfills" in key:
|
||||
return 4
|
||||
return 9
|
||||
|
||||
return sorted(paths, key=sorter)
|
||||
|
||||
|
||||
def get_css_vendored(prefer_minified: bool = False) -> List[str]:
|
||||
paths = get_vendored(".css", prefer_minified)
|
||||
|
||||
def sorter(key: str):
|
||||
if "quasar@" in key:
|
||||
return 1
|
||||
if "vue@" in key:
|
||||
return 2
|
||||
if "chart.js@" in key:
|
||||
return 100
|
||||
return 9
|
||||
|
||||
return sorted(paths, key=sorter)
|
||||
|
||||
|
||||
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
|
||||
paths: List[str] = []
|
||||
for path in glob.glob(
|
||||
os.path.join(settings.lnbits_path, "static/vendor/**"), recursive=True
|
||||
):
|
||||
if path.endswith(".min" + ext):
|
||||
# path is minified
|
||||
unminified = path.replace(".min" + ext, ext)
|
||||
if prefer_minified:
|
||||
paths.append(path)
|
||||
if unminified in paths:
|
||||
paths.remove(unminified)
|
||||
elif unminified not in paths:
|
||||
paths.append(path)
|
||||
|
||||
elif path.endswith(ext):
|
||||
# path is not minified
|
||||
minified = path.replace(ext, ".min" + ext)
|
||||
if not prefer_minified:
|
||||
paths.append(path)
|
||||
if minified in paths:
|
||||
paths.remove(minified)
|
||||
elif minified not in paths:
|
||||
paths.append(path)
|
||||
|
||||
return sorted(paths)
|
||||
|
||||
|
||||
def url_for_vendored(abspath: str) -> str:
|
||||
return "/" + os.path.relpath(abspath, settings.lnbits_path)
|
||||
|
||||
|
||||
def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> str:
|
||||
base = g().base_url if external else ""
|
||||
url_params = "?"
|
||||
@@ -89,7 +50,7 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
|
||||
return url
|
||||
|
||||
|
||||
def template_renderer(additional_folders: List = None) -> Jinja2Templates:
|
||||
def template_renderer(additional_folders: Optional[List] = None) -> Jinja2Templates:
|
||||
|
||||
folders = ["lnbits/templates", "lnbits/core/templates"]
|
||||
if additional_folders:
|
||||
@@ -118,8 +79,8 @@ def template_renderer(additional_folders: List = None) -> Jinja2Templates:
|
||||
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
|
||||
|
||||
if settings.debug:
|
||||
t.env.globals["VENDORED_JS"] = map(url_for_vendored, get_js_vendored())
|
||||
t.env.globals["VENDORED_CSS"] = map(url_for_vendored, get_css_vendored())
|
||||
t.env.globals["VENDORED_JS"] = vendored_js
|
||||
t.env.globals["VENDORED_CSS"] = vendored_css
|
||||
else:
|
||||
t.env.globals["VENDORED_JS"] = ["/static/bundle.js"]
|
||||
t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"]
|
||||
@@ -148,3 +109,39 @@ def get_current_extension_name() -> str:
|
||||
except:
|
||||
ext_name = extension_director_name
|
||||
return ext_name
|
||||
|
||||
|
||||
def generate_filter_params_openapi(model: Type[BaseModel], keep_optional=False):
|
||||
"""
|
||||
Generate openapi documentation for Filters. This is intended to be used along parse_filters (see example)
|
||||
:param model: Filter model
|
||||
:param keep_optional: If false, all parameters will be optional, otherwise inferred from model
|
||||
"""
|
||||
fields = list(model.__fields__.values())
|
||||
models = get_flat_models_from_fields(fields, set())
|
||||
namemap = get_model_name_map(models)
|
||||
params = []
|
||||
for field in fields:
|
||||
schema, definitions, _ = field_schema(field, model_name_map=namemap)
|
||||
|
||||
# Support nested definition
|
||||
if "$ref" in schema:
|
||||
name = schema["$ref"].split("/")[-1]
|
||||
schema = definitions[name]
|
||||
|
||||
description = "Supports Filtering"
|
||||
if schema["type"] == "object":
|
||||
description += f". Nested attributes can be filtered too, e.g. `{field.alias}.[additional].[attributes]`"
|
||||
|
||||
parameter = {
|
||||
"name": field.alias,
|
||||
"in": "query",
|
||||
"required": field.required if keep_optional else False,
|
||||
"schema": schema,
|
||||
"description": description,
|
||||
}
|
||||
params.append(parameter)
|
||||
|
||||
return {
|
||||
"parameters": params,
|
||||
}
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
# Borrowed from the excellent accent-starlette
|
||||
# https://github.com/accent-starlette/starlette-core/blob/master/starlette_core/templating.py
|
||||
|
||||
import typing
|
||||
|
||||
from starlette import templating
|
||||
from jinja2 import BaseLoader, Environment, pass_context
|
||||
from starlette.datastructures import QueryParams
|
||||
from starlette.requests import Request
|
||||
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError: # pragma: nocover
|
||||
jinja2 = None # type: ignore
|
||||
from starlette.templating import Jinja2Templates as SuperJinja2Templates
|
||||
|
||||
|
||||
class Jinja2Templates(templating.Jinja2Templates):
|
||||
def __init__(self, loader: jinja2.BaseLoader) -> None: # pylint: disable=W0231
|
||||
assert jinja2 is not None, "jinja2 must be installed to use Jinja2Templates"
|
||||
class Jinja2Templates(SuperJinja2Templates):
|
||||
def __init__(self, loader: BaseLoader) -> None:
|
||||
super().__init__("")
|
||||
self.env = self.get_environment(loader)
|
||||
|
||||
def get_environment(self, loader: "jinja2.BaseLoader") -> "jinja2.Environment":
|
||||
@jinja2.pass_context
|
||||
def get_environment(self, loader: BaseLoader) -> Environment:
|
||||
@pass_context
|
||||
def url_for(context: dict, name: str, **path_params: typing.Any) -> str:
|
||||
request: Request = context["request"]
|
||||
return request.app.url_path_for(name, **path_params)
|
||||
@@ -29,7 +22,7 @@ class Jinja2Templates(templating.Jinja2Templates):
|
||||
values.update(new)
|
||||
return QueryParams(**values)
|
||||
|
||||
env = jinja2.Environment(loader=loader, autoescape=True)
|
||||
env = Environment(loader=loader, autoescape=True)
|
||||
env.globals["url_for"] = url_for
|
||||
env.globals["url_params_update"] = url_params_update
|
||||
return env
|
||||
|
||||
+64
-6
@@ -1,9 +1,11 @@
|
||||
from http import HTTPStatus
|
||||
from typing import List, Tuple
|
||||
from typing import Any, List, Tuple, Union
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
|
||||
from lnbits.helpers import template_renderer
|
||||
from lnbits.settings import settings
|
||||
|
||||
|
||||
@@ -26,12 +28,21 @@ class InstalledExtensionMiddleware:
|
||||
else:
|
||||
_, path_name = path_elements
|
||||
path_type = None
|
||||
rest = []
|
||||
|
||||
headers = scope.get("headers", [])
|
||||
|
||||
# block path for all users if the extension is disabled
|
||||
if path_name in settings.lnbits_deactivated_extensions:
|
||||
response = JSONResponse(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
content={"detail": f"Extension '{path_name}' disabled"},
|
||||
response = self._response_by_accepted_type(
|
||||
headers, f"Extension '{path_name}' disabled", HTTPStatus.NOT_FOUND
|
||||
)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
|
||||
if not self._user_allowed_to_extension(path_name, scope):
|
||||
response = self._response_by_accepted_type(
|
||||
headers, "User not authorized.", HTTPStatus.FORBIDDEN
|
||||
)
|
||||
await response(scope, receive, send)
|
||||
return
|
||||
@@ -51,6 +62,53 @@ class InstalledExtensionMiddleware:
|
||||
|
||||
await self.app(scope, receive, send)
|
||||
|
||||
def _user_allowed_to_extension(self, ext_name: str, scope) -> bool:
|
||||
if ext_name not in settings.lnbits_admin_extensions:
|
||||
return True
|
||||
if "query_string" not in scope:
|
||||
return True
|
||||
|
||||
# parse the URL query string into a `dict`
|
||||
q = parse_qs(scope["query_string"].decode("UTF-8"))
|
||||
user = q.get("usr", [""])[0]
|
||||
if not user:
|
||||
return True
|
||||
|
||||
if user == settings.super_user or user in settings.lnbits_admin_users:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _response_by_accepted_type(
|
||||
self, headers: List[Any], msg: str, status_code: HTTPStatus
|
||||
) -> Union[HTMLResponse, JSONResponse]:
|
||||
"""
|
||||
Build an HTTP response containing the `msg` as HTTP body and the `status_code` as HTTP code.
|
||||
If the `accept` HTTP header is present int the request and contains the value of `text/html`
|
||||
then return an `HTMLResponse`, otherwise return an `JSONResponse`.
|
||||
"""
|
||||
accept_header: str = next(
|
||||
(
|
||||
h[1].decode("UTF-8")
|
||||
for h in headers
|
||||
if len(h) >= 2 and h[0].decode("UTF-8") == "accept"
|
||||
),
|
||||
"",
|
||||
)
|
||||
|
||||
if "text/html" in [a for a in accept_header.split(",")]:
|
||||
return HTMLResponse(
|
||||
status_code=status_code,
|
||||
content=template_renderer()
|
||||
.TemplateResponse("error.html", {"request": {}, "err": msg})
|
||||
.body,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
status_code=status_code,
|
||||
content={"detail": msg},
|
||||
)
|
||||
|
||||
|
||||
class ExtensionsRedirectMiddleware:
|
||||
# Extensions are allowed to specify redirect paths.
|
||||
@@ -88,7 +146,7 @@ class ExtensionsRedirectMiddleware:
|
||||
if "from_path" not in redirect:
|
||||
return False
|
||||
header_filters = (
|
||||
redirect["header_filters"] if "header_filters" in redirect else []
|
||||
redirect["header_filters"] if "header_filters" in redirect else {}
|
||||
)
|
||||
return self._has_common_path(redirect["from_path"], path) and self._has_headers(
|
||||
header_filters, req_headers
|
||||
|
||||
+13
-4
@@ -1,4 +1,5 @@
|
||||
import importlib
|
||||
import importlib.metadata
|
||||
import inspect
|
||||
import json
|
||||
import subprocess
|
||||
@@ -23,6 +24,7 @@ def list_parse_fallback(v):
|
||||
|
||||
|
||||
class LNbitsSettings(BaseSettings):
|
||||
@classmethod
|
||||
def validate(cls, val):
|
||||
if type(val) == str:
|
||||
val = val.split(",") if val else []
|
||||
@@ -102,6 +104,8 @@ class FakeWalletFundingSource(LNbitsSettings):
|
||||
class LNbitsFundingSource(LNbitsSettings):
|
||||
lnbits_endpoint: str = Field(default="https://legend.lnbits.com")
|
||||
lnbits_key: Optional[str] = Field(default=None)
|
||||
lnbits_admin_key: Optional[str] = Field(default=None)
|
||||
lnbits_invoice_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class ClicheFundingSource(LNbitsSettings):
|
||||
@@ -144,11 +148,14 @@ class LnPayFundingSource(LNbitsSettings):
|
||||
lnpay_api_endpoint: Optional[str] = Field(default=None)
|
||||
lnpay_api_key: Optional[str] = Field(default=None)
|
||||
lnpay_wallet_key: Optional[str] = Field(default=None)
|
||||
lnpay_admin_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class OpenNodeFundingSource(LNbitsSettings):
|
||||
opennode_api_endpoint: Optional[str] = Field(default=None)
|
||||
opennode_key: Optional[str] = Field(default=None)
|
||||
opennode_admin_key: Optional[str] = Field(default=None)
|
||||
opennode_invoice_key: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class SparkFundingSource(LNbitsSettings):
|
||||
@@ -207,8 +214,9 @@ class EditableSettings(
|
||||
"lnbits_admin_extensions",
|
||||
pre=True,
|
||||
)
|
||||
@classmethod
|
||||
def validate_editable_settings(cls, val):
|
||||
return super().validate(cls, val)
|
||||
return super().validate(val)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
@@ -225,6 +233,7 @@ class EnvSettings(LNbitsSettings):
|
||||
lnbits_path: str = Field(default=".")
|
||||
lnbits_commit: str = Field(default="unknown")
|
||||
super_user: str = Field(default="")
|
||||
version: str = Field(default="0.0.0")
|
||||
|
||||
|
||||
class SaaSSettings(LNbitsSettings):
|
||||
@@ -279,8 +288,9 @@ class ReadOnlySettings(
|
||||
"lnbits_allowed_funding_sources",
|
||||
pre=True,
|
||||
)
|
||||
@classmethod
|
||||
def validate_readonly_settings(cls, val):
|
||||
return super().validate(cls, val)
|
||||
return super().validate(val)
|
||||
|
||||
@classmethod
|
||||
def readonly_fields(cls):
|
||||
@@ -345,8 +355,6 @@ def send_admin_user_to_saas():
|
||||
)
|
||||
|
||||
|
||||
############### INIT #################
|
||||
|
||||
readonly_variables = ReadOnlySettings.readonly_fields()
|
||||
transient_variables = TransientSettings.readonly_fields()
|
||||
|
||||
@@ -366,6 +374,7 @@ try:
|
||||
except:
|
||||
settings.lnbits_commit = "docker"
|
||||
|
||||
settings.version = importlib.metadata.version("lnbits")
|
||||
|
||||
# printing environment variable for debugging
|
||||
if not settings.lnbits_admin_ui:
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
window.localisation.de = {
|
||||
server: 'Server',
|
||||
theme: 'Theme',
|
||||
funding: 'Funding',
|
||||
users: 'Benutzer',
|
||||
unit: 'Einheit',
|
||||
restart: 'Server neu starten',
|
||||
save: 'Speichern',
|
||||
save_tooltip: 'Änderungen speichern',
|
||||
topup: 'Aufladen',
|
||||
topup_wallet: 'Wallet aufladen',
|
||||
topup_hint: 'Nutze die Wallet-ID um eine beliebige Wallet aufzuladen',
|
||||
restart_tooltip: 'Starte den Server neu um die Änderungen zu übernehmen',
|
||||
add_funds_tooltip: 'Füge Geld zu einer Wallet hinzu.',
|
||||
reset_defaults: 'Zurücksetzen',
|
||||
reset_defaults_tooltip:
|
||||
'Lösche alle Einstellungen und setze auf die Standardeinstellungen zurück.',
|
||||
download_backup: 'Datenbank-Backup herunterladen',
|
||||
name_your_wallet: 'Benenne deine %{name} Wallet',
|
||||
paste_invoice_label:
|
||||
'Füge eine Rechnung, Zahlungsanforderung oder lnurl ein *',
|
||||
lnbits_description:
|
||||
'Einfach zu installieren und leichtgewichtig, LNbits kann auf jeder Lightning-Netzwerk-Funding-Quelle laufen, derzeit unterstützt LND, c-lightning, OpenNode, LNPay und sogar LNbits selbst! Du kannst LNbits für dich selbst laufen lassen oder anderen eine kundige Lösung anbieten. Jede Wallet hat ihre eigenen API-Schlüssel und es gibt keine Begrenzung für die Anzahl der Wallets, die du erstellen kannst. Die Möglichkeit, Gelder zu partitionieren, macht LNbits zu einem nützlichen Werkzeug für die Geldverwaltung und als Entwicklungswerkzeug. Erweiterungen fügen der LNbits zusätzliche Funktionalität hinzu, so dass du mit einer Reihe von vorderen Technologien auf dem Lightning-Netzwerk experimentieren kannst. Wir haben es so einfach wie möglich gemacht, Erweiterungen zu entwickeln, und als freies und Open-Source-Projekt ermutigen wir Menschen, sich selbst zu entwickeln und ihre eigenen Beiträge einzureichen.',
|
||||
export_to_phone: 'Exportieren Sie auf das Telefon mit QR-Code',
|
||||
export_to_phone_desc:
|
||||
'Dieser QR-Code enthält Ihre Wallet-URL mit vollständigem Zugriff. Sie können es von Ihrem Telefon aus scannen, um Ihre Wallet dort zu öffnen.',
|
||||
wallets: 'Wallets',
|
||||
add_wallet: 'Füge eine neue Wallet hinzu',
|
||||
delete_wallet: 'Wallet löschen',
|
||||
delete_wallet_desc:
|
||||
'Diese ganze Wallet wird gelöscht, die Mittel sind UNWIEDERBRINGLICH.',
|
||||
rename_wallet: 'Wallet umbenennen',
|
||||
update_name: 'Name aktualisieren',
|
||||
press_to_claim: 'Drücken Sie, um Bitcoin zu beanspruchen',
|
||||
donate: 'Spenden',
|
||||
view_github: 'Auf GitHub anzeigen',
|
||||
voidwallet_active: 'VoidWallet ist aktiv! Zahlungen deaktiviert',
|
||||
use_with_caution:
|
||||
'VERWENDEN SIE MIT VORSICHT - %{name} Wallet ist noch in der BETA',
|
||||
toggle_darkmode: 'Dark Mode umschalten',
|
||||
view_swagger_docs: 'LNbits Swagger API-Dokumente anzeigen',
|
||||
api_docs: 'Api docs',
|
||||
commit_version: 'Commit version',
|
||||
runs_on: 'Läuft auf',
|
||||
credit_hint: 'Drücken Sie Enter, um das Konto zu belasten',
|
||||
credit_label: '%{denomination} zu belasten',
|
||||
paste_request: 'Anfrage einfügen',
|
||||
create_invoice: 'Rechnung erstellen',
|
||||
camera_tooltip:
|
||||
'Verwenden Sie die Kamera, um eine Rechnung / QR-Code zu scannen',
|
||||
export_csv: 'Exportieren Sie nach CSV',
|
||||
transactions: 'Transaktionen',
|
||||
chart_tooltip: 'Diagramm anzeigen',
|
||||
pending: 'Ausstehend',
|
||||
copy_invoice: 'Rechnung kopieren',
|
||||
close: 'Schließen',
|
||||
cancel: 'Stornieren',
|
||||
scan: 'Scannen',
|
||||
read: 'Lesen',
|
||||
pay: 'Zahlen',
|
||||
memo: 'Memo',
|
||||
date: 'Datum',
|
||||
processing_payment: 'Zahlung wird bearbeitet ...',
|
||||
not_enough_funds: 'Nicht genug Geld!',
|
||||
search_by_tag_memo_amount: 'Suche nach Tag, Memo, Betrag',
|
||||
invoice_waiting: 'Rechnung wartet auf Zahlung',
|
||||
payment_received: 'Zahlung erhalten',
|
||||
payment_sent: 'Zahlung gesendet',
|
||||
outgoing_payment_pending: 'Ausgehende Zahlung ausstehend',
|
||||
drain_funds: 'Leeren Sie die Mittel',
|
||||
drain_funds_desc:
|
||||
'Dies ist ein LNURL-withdraw QR-Code zum Absaugen aller Mittel aus dieser Brieftasche. Teilen Sie es nicht mit jemandem. Es ist mit balanceCheck und balanceNotify kompatibel, so dass Ihre Brieftasche die Mittel nach dem ersten Abzug kontinuierlich von hier ziehen kann.',
|
||||
i_understand: 'Ich verstehe',
|
||||
copy_wallet_url: 'Wallet-URL kopieren',
|
||||
disclaimer_dialog:
|
||||
'Login-Funktionalität wird in einem zukünftigen Update veröffentlicht, für jetzt, stellen Sie sicher, dass Sie diese Seite als Lesezeichen speichern, um zukünftigen Zugriff auf Ihre Wallet zu erhalten! Dieser Service ist in BETA und wir übernehmen keine Verantwortung für Personen, die den Zugriff auf ihre Mittel verlieren.',
|
||||
no_transactions: 'Noch keine Transaktionen',
|
||||
manage_extensions: 'Erweiterungen verwalten',
|
||||
manage_server: 'Server verwalten',
|
||||
extensions: 'Erweiterungen',
|
||||
no_extensions: 'Sie haben keine Erweiterungen installiert :(',
|
||||
created: 'Erstellt',
|
||||
payment_hash: 'Zahlungs-Hash',
|
||||
fee: 'Gebühr',
|
||||
amount: 'Menge',
|
||||
description: 'Beschreibung',
|
||||
expiry: 'Ablauf',
|
||||
webhook: 'Webhook',
|
||||
payment_proof: 'Zahlungsbeleg'
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
window.localisation.en = {
|
||||
server: 'Server',
|
||||
theme: 'Theme',
|
||||
funding: 'Funding',
|
||||
users: 'Users',
|
||||
restart: 'Restart server',
|
||||
save: 'Save',
|
||||
save_tooltip: 'Save your changes',
|
||||
topup: 'Topup',
|
||||
topup_wallet: 'Topup a wallet',
|
||||
topup_hint: 'Use the wallet ID to topup any wallet',
|
||||
restart_tooltip: 'Restart the server for changes to take effect',
|
||||
add_funds_tooltip: 'Add funds to a wallet.',
|
||||
reset_defaults: 'Reset to defaults',
|
||||
reset_defaults_tooltip: 'Delete all settings and reset to defaults.',
|
||||
download_backup: 'Download database backup',
|
||||
name_your_wallet: 'Name your %{name} wallet',
|
||||
paste_invoice_label: 'Paste an invoice, payment request or lnurl code *',
|
||||
lnbits_description:
|
||||
'Easy to set up and lightweight, LNbits can run on any lightning-network funding source, currently supporting LND, c-lightning, OpenNode, LNPay and even LNbits itself! You can run LNbits for yourself, or easily offer a custodian solution for others. Each wallet has its own API keys and there is no limit to the number of wallets you can make. Being able to partition funds makes LNbits a useful tool for money management and as a development tool. Extensions add extra functionality to LNbits so you can experiment with a range of cutting-edge technologies on the lightning network. We have made developing extensions as easy as possible, and as a free and open-source project, we encourage people to develop and submit their own.',
|
||||
export_to_phone: 'Export to Phone with QR Code',
|
||||
export_to_phone_desc:
|
||||
'This QR code contains your wallet URL with full access. You can scan it from your phone to open your wallet from there.',
|
||||
wallets: 'Wallets',
|
||||
add_wallet: 'Add a new wallet',
|
||||
delete_wallet: 'Delete wallet',
|
||||
delete_wallet_desc:
|
||||
'This whole wallet will be deleted, the funds will be UNRECOVERABLE.',
|
||||
rename_wallet: 'Rename wallet',
|
||||
update_name: 'Update name',
|
||||
press_to_claim: 'Press to claim bitcoin',
|
||||
donate: 'Donate',
|
||||
view_github: 'View on GitHub',
|
||||
voidwallet_active: 'VoidWallet is active! Payments disabled',
|
||||
use_with_caution: 'USE WITH CAUTION - %{name} wallet is still in BETA',
|
||||
toggle_darkmode: 'Toggle Dark Mode',
|
||||
view_swagger_docs: 'View LNbits Swagger API docs',
|
||||
api_docs: 'Api docs',
|
||||
commit_version: 'Commit version',
|
||||
runs_on: 'Runs on',
|
||||
credit_hint: 'Press Enter to credit account',
|
||||
credit_label: '%{denomination} to credit',
|
||||
paste_request: 'Paste Request',
|
||||
create_invoice: 'Create Invoice',
|
||||
camera_tooltip: 'Use camera to scan an invoice/QR',
|
||||
export_csv: 'Export to CSV',
|
||||
transactions: 'Transactions',
|
||||
chart_tooltip: 'Show chart',
|
||||
pending: 'Pending',
|
||||
copy_invoice: 'Copy invoice',
|
||||
close: 'Close',
|
||||
cancel: 'Cancel',
|
||||
scan: 'Scan',
|
||||
read: 'Read',
|
||||
pay: 'Pay',
|
||||
memo: 'Memo',
|
||||
date: 'Date',
|
||||
processing_payment: 'Processing payment...',
|
||||
not_enough_funds: 'Not enough funds!',
|
||||
search_by_tag_memo_amount: 'Search by tag, memo, amount',
|
||||
invoice_waiting: 'Invoice waiting to be paid',
|
||||
payment_received: 'Payment Received',
|
||||
payment_sent: 'Payment Sent',
|
||||
outgoing_payment_pending: 'Outgoing payment pending',
|
||||
drain_funds: 'Drain Funds',
|
||||
drain_funds_desc:
|
||||
'This is an LNURL-withdraw QR code for slurping everything from this wallet. Do not share with anyone. It is compatible with balanceCheck and balanceNotify so your wallet may keep pulling the funds continuously from here after the first withdraw.',
|
||||
i_understand: 'I understand',
|
||||
copy_wallet_url: 'Copy wallet URL',
|
||||
disclaimer_dialog:
|
||||
'Login functionality to be released in a future update, for now, make sure you bookmark this page for future access to your wallet! This service is in BETA, and we hold no responsibility for people losing access to funds.',
|
||||
no_transactions: 'No transactions made yet',
|
||||
manage_extensions: 'Manage Extensions',
|
||||
manage_server: 'Manage Server',
|
||||
extensions: 'Extensions',
|
||||
no_extensions: "You don't have any extensions installed :(",
|
||||
created: 'Created',
|
||||
payment_hash: 'Payment Hash',
|
||||
fee: 'Fee',
|
||||
amount: 'Amount',
|
||||
unit: 'Unit',
|
||||
description: 'Description',
|
||||
expiry: 'Expiry',
|
||||
webhook: 'Webhook',
|
||||
payment_proof: 'Payment Proof'
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
window.localisation.es = {
|
||||
server: 'Servidor',
|
||||
theme: 'Tema',
|
||||
funding: 'Financiación',
|
||||
unit: 'Unidad',
|
||||
users: 'Usuarios',
|
||||
restart: 'Reiniciar el servidor',
|
||||
save: 'Guardar',
|
||||
save_tooltip: 'Guardar cambios',
|
||||
topup: 'Recargar',
|
||||
topup_wallet: 'Recargar billetera',
|
||||
topup_hint: 'Utilice el ID de billetera para recargar cualquier billetera',
|
||||
restart_tooltip: 'Reinicie el servidor para aplicar los cambios',
|
||||
add_funds_tooltip: 'Agregue fondos a una billetera.',
|
||||
reset_defaults: 'Restablecer',
|
||||
reset_defaults_tooltip:
|
||||
'Borrar todas las configuraciones y restablecer a los valores predeterminados.',
|
||||
download_backup: 'Descargar copia de seguridad de la base de datos',
|
||||
name_your_wallet: 'Nombre de su billetera %{name}',
|
||||
paste_invoice_label: 'Pegue la factura aquí',
|
||||
lnbits_description:
|
||||
'Fácil de instalar y liviano, LNbits puede ejecutarse en cualquier fuente de financiación de la red Lightning, actualmente compatible con LND, c-lightning, OpenNode, LNPay y hasta LNbits mismo! Puede ejecutar LNbits para usted mismo o ofrecer una solución competente a otros. Cada billetera tiene su propia clave API y no hay límite para la cantidad de billeteras que puede crear. La capacidad de particionar fondos hace de LNbits una herramienta útil para la administración de fondos y como herramienta de desarrollo. Las extensiones agregan funcionalidad adicional a LNbits, por lo que puede experimentar con una variedad de tecnologías de vanguardia en la red Lightning. Lo hemos hecho lo más simple posible para desarrollar extensiones y, como un proyecto gratuito y de código abierto, animamos a las personas a que se desarrollen a sí mismas y envíen sus propios contribuciones.',
|
||||
export_to_phone: 'Exportar a teléfono con código QR',
|
||||
export_to_phone_desc:
|
||||
'Este código QR contiene su URL de billetera con acceso completo. Puede escanearlo desde su teléfono para abrir su billetera allí.',
|
||||
wallets: 'Billeteras',
|
||||
add_wallet: 'Agregar nueva billetera',
|
||||
delete_wallet: 'Eliminar billetera',
|
||||
delete_wallet_desc:
|
||||
'Esta billetera completa se eliminará, los fondos son IRREVERSIBLES.',
|
||||
rename_wallet: 'Cambiar el nombre de la billetera',
|
||||
update_name: 'Actualizar nombre',
|
||||
press_to_claim: 'Presione para reclamar Bitcoin',
|
||||
donate: 'Donar',
|
||||
view_github: 'Ver en GitHub',
|
||||
voidwallet_active: '¡VoidWallet está activo! Pagos desactivados',
|
||||
use_with_caution: 'USAR CON CUIDADO - %{name} Wallet aún está en BETA',
|
||||
toggle_darkmode: 'Cambiar modo oscuro',
|
||||
view_swagger_docs: 'Ver documentos de API de LNbits Swagger',
|
||||
api_docs: 'Documentos de API',
|
||||
commit_version: 'Versión de compromiso',
|
||||
runs_on: 'Corre en',
|
||||
credit_hint: 'Presione Enter para cargar la cuenta',
|
||||
credit_label: 'Cargar %{denomination}',
|
||||
paste_request: 'Pegar solicitud',
|
||||
create_invoice: 'Crear factura',
|
||||
camera_tooltip: 'Utilice la cámara para escanear una factura / código QR',
|
||||
export_csv: 'Exportar a CSV',
|
||||
transactions: 'Transacciones',
|
||||
chart_tooltip: 'Mostrar gráfico',
|
||||
pending: 'Pendiente',
|
||||
copy_invoice: 'Copiar factura',
|
||||
close: 'Cerrar',
|
||||
cancel: 'Cancelar',
|
||||
scan: 'Escanear',
|
||||
read: 'Leer',
|
||||
pay: 'Pagar',
|
||||
memo: 'Memo',
|
||||
date: 'Fecha',
|
||||
processing_payment: 'Procesando pago ...',
|
||||
not_enough_funds: '¡No hay suficientes fondos!',
|
||||
search_by_tag_memo_amount: 'Buscar por etiqueta, memo, cantidad',
|
||||
invoice_waiting: 'Factura esperando pago',
|
||||
payment_received: 'Pago recibido',
|
||||
payment_sent: 'Pago enviado',
|
||||
outgoing_payment_pending: 'Pago saliente pendiente',
|
||||
drain_funds: 'Drenar fondos',
|
||||
drain_funds_desc:
|
||||
'Este es un código QR LNURL-withdraw para drenar todos los fondos de esta billetera. No lo comparta con nadie. Es compatible con balanceCheck y balanceNotify, por lo que su billetera puede continuar drenando los fondos de aquí después del primer drenaje.',
|
||||
i_understand: 'Lo entiendo',
|
||||
copy_wallet_url: 'Copiar URL de billetera',
|
||||
disclaimer_dialog:
|
||||
'La funcionalidad de inicio de sesión se lanzará en una actualización futura, por ahora, asegúrese de guardar esta página como marcador para acceder a su billetera en el futuro. Este servicio está en BETA y no asumimos ninguna responsabilidad por personas que pierdan el acceso a sus fondos.',
|
||||
no_transactions: 'No hay transacciones todavía',
|
||||
manage_extensions: 'Administrar extensiones',
|
||||
manage_server: 'Administrar servidor',
|
||||
extensions: 'Extensiones',
|
||||
no_extensions: 'No tienes extensiones instaladas :(',
|
||||
created: 'Creado',
|
||||
payment_hash: 'Hash de pago',
|
||||
fee: 'Cuota',
|
||||
amount: 'Cantidad',
|
||||
description: 'Descripción',
|
||||
expiry: 'Expiración',
|
||||
webhook: 'Webhook',
|
||||
payment_proof: 'Prueba de pago'
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
window.localisation = {}
|
||||
@@ -0,0 +1,85 @@
|
||||
window.localisation.jp = {
|
||||
server: 'サーバー',
|
||||
theme: 'テーマ',
|
||||
funding: '資金調達',
|
||||
users: 'ユーザー',
|
||||
restart: 'サーバーを再起動する',
|
||||
save: '保存',
|
||||
unit: '単位',
|
||||
save_tooltip: '変更を保存する',
|
||||
topup: 'トップアップ',
|
||||
topup_wallet: 'ウォレットをトップアップする',
|
||||
topup_hint: 'ウォレットIDを使用して、任意のウォレットをトップアップできます',
|
||||
restart_tooltip: 'サーバーを再起動して変更を適用します',
|
||||
add_funds_tooltip: 'ウォレットに資金を追加します。',
|
||||
reset_defaults: 'リセット',
|
||||
reset_defaults_tooltip: 'すべての設定を削除してデフォルトに戻します。',
|
||||
download_backup: 'データベースのバックアップをダウンロードする',
|
||||
name_your_wallet: 'あなたのウォレットの名前 %{name}',
|
||||
paste_invoice_label: '請求書を貼り付けてください',
|
||||
lnbits_description:
|
||||
'簡単にインストールでき、軽量で、LNbitsは現在LND、c-lightning、OpenNode、LNPay、さらにLNbits自身で動作する任意のLightningネットワークの資金源で実行できます! LNbitsを自分で実行することも、他の人に優れたソリューションを提供することもできます。各ウォレットには独自のAPIキーがあり、作成できるウォレットの数に制限はありません。資金を分割する機能は、LNbitsを資金管理ツールとして使用したり、開発ツールとして使用したりするための便利なツールです。拡張機能は、LNbitsに追加の機能を追加します。そのため、LNbitsは最先端の技術をネットワークLightningで試すことができます。拡張機能を開発するのは簡単で、無料でオープンソースのプロジェクトであるため、人々が自分で開発し、自分の貢献を送信することを奨励しています。',
|
||||
export_to_phone: '電話にエクスポート',
|
||||
export_to_phone_desc:
|
||||
'ウォレットを電話にエクスポートすると、ウォレットを削除する前にウォレットを復元できます。ウォレットを削除すると、ウォレットの秘密鍵が削除され、ウォレットを復元することはできません。',
|
||||
wallets: 'ウォレット',
|
||||
add_wallet: 'ウォレットを追加',
|
||||
delete_wallet: 'ウォレットを削除',
|
||||
delete_wallet_desc:
|
||||
'ウォレットを削除すると、ウォレットの秘密鍵が削除され、ウォレットを復元することはできません。',
|
||||
rename_wallet: 'ウォレットの名前を変更',
|
||||
update_name: '名前を更新',
|
||||
press_to_claim: 'クレームするには押してください',
|
||||
donate: '寄付',
|
||||
voidwallet_active: 'Voidwalletアクティブ',
|
||||
use_with_caution: '注意して使用してください',
|
||||
toggle_dark_mode: 'ダークモードを切り替える',
|
||||
view_swagger_docs: 'Swaggerドキュメントを表示',
|
||||
api_docs: 'APIドキュメント',
|
||||
commit_version: 'コミットバージョン',
|
||||
runs_on: 'で実行',
|
||||
credit_hint:
|
||||
'クレジットカードを使用して資金を追加するには、LNbitsを使用してください。',
|
||||
credit_label: 'クレジットカード',
|
||||
paste_request: 'リクエストを貼り付ける',
|
||||
create_invoice: '請求書を作成する',
|
||||
camera_tooltip: 'QRコードを読み取る',
|
||||
export_csv: 'CSVでエクスポート',
|
||||
transactions: 'トランザクション',
|
||||
chart_tooltip: 'チャートを表示するには、グラフの上にカーソルを合わせます',
|
||||
pending: '保留中',
|
||||
copy_invoice: '請求書をコピー',
|
||||
close: '閉じる',
|
||||
cancel: 'キャンセル',
|
||||
scan: 'スキャン',
|
||||
read: '読む',
|
||||
pay: '支払う',
|
||||
memo: 'メモ',
|
||||
date: '日付',
|
||||
processing_payment: '支払い処理中',
|
||||
not_enough_funds: '資金が不足しています',
|
||||
search_by_tag_memo_amount: 'タグ、メモ、金額で検索',
|
||||
invoice_waiting: '請求書を待っています',
|
||||
payment_received: 'お支払いありがとうございます',
|
||||
payment_sent: '支払いが完了しました',
|
||||
outgoing_payment_pending: '支払い保留中',
|
||||
drain_funds: '資金を排出する',
|
||||
drain_funds_desc: 'ウォレットの残高をすべて他のウォレットに送金します',
|
||||
i_understand: '理解した',
|
||||
copy_wallet_url: 'ウォレットURLをコピー',
|
||||
disclaimer_dialog:
|
||||
'ウォレットを削除すると、ウォレットの秘密鍵が削除され、ウォレットを復元することはできません。ウォレットを削除する前に、ウォレットをエクスポートしてください。',
|
||||
no_transactions: 'トランザクションはありません',
|
||||
manage_extensions: '拡張機能を管理する',
|
||||
manage_server: 'サーバーを管理する',
|
||||
extensions: '拡張機能',
|
||||
no_extensions: '拡張機能はありません',
|
||||
created: '作成済み',
|
||||
payment_hash: '支払いハッシュ',
|
||||
fee: '料金',
|
||||
amount: '量',
|
||||
description: '説明',
|
||||
expiry: '有効期限',
|
||||
webhook: 'ウェブフック',
|
||||
payment_proof: '支払い証明'
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
/* globals crypto, moment, Vue, axios, Quasar, _ */
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
window.LOCALE = 'en'
|
||||
window.i18n = new VueI18n({
|
||||
locale: window.LOCALE,
|
||||
fallbackLocale: window.LOCALE,
|
||||
messages: window.localisation
|
||||
})
|
||||
|
||||
window.EventHub = new Vue()
|
||||
window.LNbits = {
|
||||
api: {
|
||||
@@ -320,6 +328,7 @@ window.LNbits = {
|
||||
}
|
||||
|
||||
window.windowMixin = {
|
||||
i18n: window.i18n,
|
||||
data: function () {
|
||||
return {
|
||||
g: {
|
||||
@@ -335,6 +344,10 @@ window.windowMixin = {
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeLanguage: function (newValue) {
|
||||
window.i18n.locale = newValue
|
||||
this.$q.localStorage.set('lnbits.lang', newValue)
|
||||
},
|
||||
changeColor: function (newValue) {
|
||||
document.body.setAttribute('data-theme', newValue)
|
||||
this.$q.localStorage.set('lnbits.theme', newValue)
|
||||
@@ -364,6 +377,12 @@ window.windowMixin = {
|
||||
}
|
||||
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
|
||||
|
||||
let locale = this.$q.localStorage.getItem('lnbits.lang')
|
||||
if (locale) {
|
||||
window.LOCALE = locale
|
||||
window.i18n.locale = locale
|
||||
}
|
||||
|
||||
addEventListener('offline', event => {
|
||||
this.g.offline = true
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ Vue.component('lnbits-wallet-list', {
|
||||
},
|
||||
template: `
|
||||
<q-list v-if="user && user.wallets.length" dense class="lnbits-drawer__q-list">
|
||||
<q-item-label header>Wallets</q-item-label>
|
||||
<q-item-label header v-text="$t('wallets')"></q-item-label>
|
||||
<q-item v-for="wallet in wallets" :key="wallet.id"
|
||||
clickable
|
||||
:active="activeWallet && activeWallet.id === wallet.id"
|
||||
@@ -56,7 +56,7 @@ Vue.component('lnbits-wallet-list', {
|
||||
<q-icon :name="(showForm) ? 'remove' : 'add'" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1" class="text-caption">Add a wallet</q-item-label>
|
||||
<q-item-label lines="1" class="text-caption" v-text="$t('add_wallet')"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item v-if="showForm">
|
||||
@@ -112,7 +112,7 @@ Vue.component('lnbits-extension-list', {
|
||||
},
|
||||
template: `
|
||||
<q-list v-if="user" dense class="lnbits-drawer__q-list">
|
||||
<q-item-label header>Extensions</q-item-label>
|
||||
<q-item-label header v-text="$t('extensions')"></q-item-label>
|
||||
<q-item v-for="extension in userExtensions" :key="extension.code"
|
||||
clickable
|
||||
:active="extension.isActive"
|
||||
@@ -137,7 +137,7 @@ Vue.component('lnbits-extension-list', {
|
||||
<q-icon name="clear_all" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1" class="text-caption">Extensions</q-item-label>
|
||||
<q-item-label lines="1" class="text-caption" v-text="$t('extensions')"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable tag="a" :href="['/install?usr=', user.id].join('')">
|
||||
@@ -145,7 +145,7 @@ Vue.component('lnbits-extension-list', {
|
||||
<q-icon name="playlist_add" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1" class="text-caption">Manage Extensions</q-item-label>
|
||||
<q-item-label lines="1" class="text-caption" v-text="$t('manage_extensions')"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
@@ -199,7 +199,7 @@ Vue.component('lnbits-admin-ui', {
|
||||
<q-icon name="admin_panel_settings" color="grey-5" size="md"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1" class="text-caption">Manage Server</q-item-label>
|
||||
<q-item-label lines="1" class="text-caption" v-text="$t('manage_server')"></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
@@ -214,6 +214,7 @@ Vue.component('lnbits-admin-ui', {
|
||||
|
||||
Vue.component('lnbits-payment-details', {
|
||||
props: ['payment'],
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
LNBITS_DENOMINATION: LNBITS_DENOMINATION
|
||||
@@ -227,31 +228,34 @@ Vue.component('lnbits-payment-details', {
|
||||
</q-badge>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3"><b>Created</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('created')"></b>:</div>
|
||||
<div class="col-9">{{ payment.date }} ({{ payment.dateFrom }})</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3"><b>Expiry</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('expiry')"></b>:</div>
|
||||
<div class="col-9">{{ payment.expirydate }} ({{ payment.expirydateFrom }})</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3"><b>Description</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('description')"></b>:</div>
|
||||
<div class="col-9">{{ payment.memo }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3"><b>Amount</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('amount')"></b>:</div>
|
||||
<div class="col-9">{{ (payment.amount / 1000).toFixed(3) }} {{LNBITS_DENOMINATION}}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3"><b>Fee</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('fee')"></b>:</div>
|
||||
<div class="col-9">{{ (payment.fee / 1000).toFixed(3) }} {{LNBITS_DENOMINATION}}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-3"><b>Payment hash</b>:</div>
|
||||
<div class="col-9 text-wrap mono">{{ payment.payment_hash }}</div>
|
||||
<div class="col-3"><b v-text="$t('payment_hash')"></b>:</div>
|
||||
<div class="col-9 text-wrap mono">
|
||||
{{ payment.payment_hash }}
|
||||
<q-icon name="content_copy" @click="copyText(payment.payment_hash)" size="1em" color="grey" class="q-mb-xs cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="payment.webhook">
|
||||
<div class="col-3"><b>Webhook</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('webhook')"></b>:</div>
|
||||
<div class="col-9 text-wrap mono">
|
||||
{{ payment.webhook }}
|
||||
<q-badge :color="webhookStatusColor" text-color="white">
|
||||
@@ -260,7 +264,7 @@ Vue.component('lnbits-payment-details', {
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="hasPreimage">
|
||||
<div class="col-3"><b>Payment proof</b>:</div>
|
||||
<div class="col-3"><b v-text="$t('payment_proof')"></b>:</div>
|
||||
<div class="col-9 text-wrap mono">{{ payment.preimage }}</div>
|
||||
</div>
|
||||
<div class="row" v-for="entry in extras">
|
||||
|
||||
+20776
File diff suppressed because it is too large
Load Diff
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* DOM element rendering detection
|
||||
* https://davidwalsh.name/detect-node-insertion
|
||||
*/
|
||||
@keyframes chartjs-render-animation {
|
||||
from { opacity: 0.99; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.chartjs-render-monitor {
|
||||
animation: chartjs-render-animation 0.001s;
|
||||
}
|
||||
|
||||
/*
|
||||
* DOM element resizing detection
|
||||
* https://github.com/marcj/css-element-queries
|
||||
*/
|
||||
.chartjs-size-monitor,
|
||||
.chartjs-size-monitor-expand,
|
||||
.chartjs-size-monitor-shrink {
|
||||
position: absolute;
|
||||
direction: ltr;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.chartjs-size-monitor-expand > div {
|
||||
position: absolute;
|
||||
width: 1000000px;
|
||||
height: 1000000px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.chartjs-size-monitor-shrink > div {
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
Vendored
+2964
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}
|
||||
File diff suppressed because one or more lines are too long
Vendored
+5685
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Vendored
+11680
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+2042
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Vendored
+2312
File diff suppressed because it is too large
Load Diff
Vendored
+5509
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-6
File diff suppressed because one or more lines are too long
-6
File diff suppressed because one or more lines are too long
+4
-4
@@ -3,7 +3,7 @@ import time
|
||||
import traceback
|
||||
import uuid
|
||||
from http import HTTPStatus
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional
|
||||
|
||||
from fastapi.exceptions import HTTPException
|
||||
from loguru import logger
|
||||
@@ -42,7 +42,7 @@ class SseListenersDict(dict):
|
||||
A dict of sse listeners.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str = None):
|
||||
def __init__(self, name: Optional[str] = None):
|
||||
self.name = name or f"sse_listener_{str(uuid.uuid4())[:8]}"
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
@@ -65,7 +65,7 @@ class SseListenersDict(dict):
|
||||
invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict("invoice_listeners")
|
||||
|
||||
|
||||
def register_invoice_listener(send_chan: asyncio.Queue, name: str = None):
|
||||
def register_invoice_listener(send_chan: asyncio.Queue, name: Optional[str] = None):
|
||||
"""
|
||||
A method intended for extensions (and core/tasks.py) to call when they want to be notified about
|
||||
new invoice payments incoming. Will emit all incoming payments.
|
||||
@@ -164,7 +164,7 @@ async def check_pending_payments():
|
||||
async def perform_balance_checks():
|
||||
while True:
|
||||
for bc in await get_balance_checks():
|
||||
redeem_lnurl_withdraw(bc.wallet, bc.url)
|
||||
await redeem_lnurl_withdraw(bc.wallet, bc.url)
|
||||
|
||||
await asyncio.sleep(60 * 60 * 6) # every 6 hours
|
||||
|
||||
|
||||
+52
-11
@@ -46,15 +46,14 @@
|
||||
</q-toolbar-title>
|
||||
{% block beta %} {% if VOIDWALLET %}
|
||||
<q-badge color="red" text-color="black" class="q-mr-md">
|
||||
<span>VoidWallet is active! Payments disabled</span>
|
||||
{% raw %}{{ $t('voidwallet_active') }}{% endraw %}
|
||||
</q-badge>
|
||||
{%endif%}
|
||||
<q-badge color="yellow" text-color="black" class="q-mr-md">
|
||||
<span
|
||||
><span v-show="$q.screen.gt.sm"
|
||||
>USE WITH CAUTION - {{SITE_TITLE}} wallet is still in </span
|
||||
>BETA</span
|
||||
>
|
||||
v-show="$q.screen.gt.sm"
|
||||
v-text='$t("use_with_caution", { name: "{{ SITE_TITLE }}" })'
|
||||
></span>
|
||||
</q-badge>
|
||||
{% endblock %}
|
||||
<q-badge
|
||||
@@ -63,8 +62,39 @@
|
||||
text-color="white"
|
||||
class="q-mr-md"
|
||||
>
|
||||
<span> OFFLINE </span>
|
||||
<span>OFFLINE</span>
|
||||
</q-badge>
|
||||
<q-btn-dropdown
|
||||
dense
|
||||
flat
|
||||
round
|
||||
size="sm"
|
||||
icon="language"
|
||||
class="q-pl-md"
|
||||
>
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup @click="changeLanguage('en')">
|
||||
<q-item-section>
|
||||
<q-item-label>EN</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="changeLanguage('de')">
|
||||
<q-item-section>
|
||||
<q-item-label>DE</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="changeLanguage('es')">
|
||||
<q-item-section>
|
||||
<q-item-label>ES</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="changeLanguage('jp')">
|
||||
<q-item-section>
|
||||
<q-item-label>JP</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<q-btn-dropdown
|
||||
v-if="g.allowedThemes && g.allowedThemes.length > 1"
|
||||
dense
|
||||
@@ -175,7 +205,9 @@
|
||||
:icon="($q.dark.isActive) ? 'brightness_3' : 'wb_sunny'"
|
||||
size="sm"
|
||||
>
|
||||
<q-tooltip>Toggle Dark Mode</q-tooltip>
|
||||
<q-tooltip
|
||||
>{% raw %}{{ $t('toggle_darkmode') }}{% endraw %}</q-tooltip
|
||||
>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
@@ -213,7 +245,8 @@
|
||||
<br />
|
||||
|
||||
<small v-if="'{{SITE_TITLE}}' == 'LNbits'"
|
||||
>Commit version: {{LNBITS_VERSION}}</small
|
||||
>{% raw %}{{ $t('commit_version') }}{% endraw %}:
|
||||
{{LNBITS_VERSION}}</small
|
||||
>
|
||||
</q-toolbar-title>
|
||||
<q-space></q-space>
|
||||
@@ -226,8 +259,10 @@
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
API DOCS
|
||||
<q-tooltip>View LNbits Swagger API docs</q-tooltip>
|
||||
{% raw %}{{ $t('api_docs') }}{% endraw %}
|
||||
<q-tooltip
|
||||
>{% raw %}{{ $t('view_swagger_docs') }}{% endraw %}</q-tooltip
|
||||
>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
@@ -239,7 +274,7 @@
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<q-tooltip>View project in GitHub</q-tooltip>
|
||||
<q-tooltip>{% raw %}{{ $t('view_github') }}{% endraw %}</q-tooltip>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
</q-footer>
|
||||
@@ -253,8 +288,14 @@
|
||||
<script src="{{ url }}"></script>
|
||||
{% endfor %}
|
||||
<!---->
|
||||
<script src="/static/i18n/i18n.js"></script>
|
||||
<script src="/static/i18n/en.js"></script>
|
||||
<script src="/static/i18n/de.js"></script>
|
||||
<script src="/static/i18n/es.js"></script>
|
||||
<script src="/static/i18n/jp.js"></script>
|
||||
<script src="/static/js/base.js"></script>
|
||||
<script src="/static/js/components.js"></script>
|
||||
<script src="/static/js/bolt11-decoder.js"></script>
|
||||
<script type="text/javascript">
|
||||
const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
|
||||
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson}}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# flake8: noqa: F401
|
||||
|
||||
|
||||
from .cliche import ClicheWallet
|
||||
from .cln import CoreLightningWallet # legacy .env support
|
||||
from .cln import CoreLightningWallet
|
||||
from .cln import CoreLightningWallet as CLightningWallet
|
||||
from .eclair import EclairWallet
|
||||
from .fake import FakeWallet
|
||||
|
||||
@@ -22,6 +22,8 @@ class ClicheWallet(Wallet):
|
||||
|
||||
def __init__(self):
|
||||
self.endpoint = settings.cliche_endpoint
|
||||
if not self.endpoint:
|
||||
raise Exception("cannot initialize cliche")
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
@@ -36,7 +38,7 @@ class ClicheWallet(Wallet):
|
||||
data = json.loads(r)
|
||||
except:
|
||||
return StatusResponse(
|
||||
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
|
||||
f"Failed to connect to {self.endpoint}, got: '{r[:200]}...'", 0
|
||||
)
|
||||
|
||||
return StatusResponse(None, data["result"]["wallets"][0]["balance"])
|
||||
@@ -89,6 +91,13 @@ class ClicheWallet(Wallet):
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
ws = create_connection(self.endpoint)
|
||||
ws.send(f"pay-invoice --invoice {bolt11}")
|
||||
checking_id, fee_msat, preimage, error_message, payment_ok = (
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
for _ in range(2):
|
||||
r = ws.recv()
|
||||
data = json.loads(r)
|
||||
@@ -151,9 +160,9 @@ class ClicheWallet(Wallet):
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
while True:
|
||||
try:
|
||||
ws = await create_connection(self.endpoint)
|
||||
ws = create_connection(self.endpoint)
|
||||
while True:
|
||||
r = await ws.recv()
|
||||
r = ws.recv()
|
||||
data = json.loads(r)
|
||||
print(data)
|
||||
try:
|
||||
|
||||
+14
-12
@@ -7,10 +7,7 @@ from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
# TODO: https://github.com/lnbits/lnbits/issues/764
|
||||
# mypy https://github.com/aaugustin/websockets/issues/940
|
||||
from websockets import connect # type: ignore
|
||||
from websockets.client import connect
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
@@ -34,11 +31,13 @@ class UnknownError(Exception):
|
||||
class EclairWallet(Wallet):
|
||||
def __init__(self):
|
||||
url = settings.eclair_url
|
||||
self.url = url[:-1] if url.endswith("/") else url
|
||||
passw = settings.eclair_pass
|
||||
if not url or not passw:
|
||||
raise Exception("cannot initialize eclair")
|
||||
|
||||
self.url = url[:-1] if url.endswith("/") else url
|
||||
self.ws_url = f"ws://{urllib.parse.urlsplit(self.url).netloc}/ws"
|
||||
|
||||
passw = settings.eclair_pass
|
||||
encodedAuth = base64.b64encode(f":{passw}".encode())
|
||||
auth = str(encodedAuth, "utf-8")
|
||||
self.auth = {"Authorization": f"Basic {auth}"}
|
||||
@@ -71,7 +70,11 @@ class EclairWallet(Wallet):
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
|
||||
data: Dict = {"amountMsat": amount * 1000}
|
||||
data: Dict = {
|
||||
"amountMsat": amount * 1000,
|
||||
"description_hash": b"",
|
||||
"description": memo,
|
||||
}
|
||||
if kwargs.get("expiry"):
|
||||
data["expireIn"] = kwargs["expiry"]
|
||||
|
||||
@@ -79,8 +82,6 @@ class EclairWallet(Wallet):
|
||||
data["descriptionHash"] = description_hash.hex()
|
||||
elif unhashed_description:
|
||||
data["descriptionHash"] = hashlib.sha256(unhashed_description).hexdigest()
|
||||
else:
|
||||
data["description"] = memo or ""
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
@@ -149,6 +150,7 @@ class EclairWallet(Wallet):
|
||||
}
|
||||
|
||||
data = r.json()[-1]
|
||||
fee_msat = 0
|
||||
if data["status"]["type"] == "sent":
|
||||
fee_msat = -data["status"]["feesPaid"]
|
||||
preimage = data["status"]["paymentPreimage"]
|
||||
@@ -223,10 +225,10 @@ class EclairWallet(Wallet):
|
||||
) as ws:
|
||||
while True:
|
||||
message = await ws.recv()
|
||||
message = json.loads(message)
|
||||
message_json = json.loads(message)
|
||||
|
||||
if message and message["type"] == "payment-received":
|
||||
yield message["paymentHash"]
|
||||
if message_json and message_json["type"] == "payment-received":
|
||||
yield message_json["paymentHash"]
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
|
||||
+12
-15
@@ -45,19 +45,18 @@ class FakeWallet(Wallet):
|
||||
) -> InvoiceResponse:
|
||||
data: Dict = {
|
||||
"out": False,
|
||||
"amount": amount,
|
||||
"amount": amount * 1000,
|
||||
"currency": "bc",
|
||||
"privkey": self.privkey,
|
||||
"memo": None,
|
||||
"description_hash": None,
|
||||
"memo": memo,
|
||||
"description_hash": b"",
|
||||
"description": "",
|
||||
"fallback": None,
|
||||
"expires": None,
|
||||
"expires": kwargs.get("expiry"),
|
||||
"timestamp": datetime.now().timestamp(),
|
||||
"route": None,
|
||||
"tags_set": [],
|
||||
}
|
||||
data["expires"] = kwargs.get("expiry")
|
||||
data["amount"] = amount * 1000
|
||||
data["timestamp"] = datetime.now().timestamp()
|
||||
if description_hash:
|
||||
data["tags_set"] = ["h"]
|
||||
data["description_hash"] = description_hash
|
||||
@@ -69,7 +68,7 @@ class FakeWallet(Wallet):
|
||||
data["memo"] = memo
|
||||
data["description"] = memo
|
||||
randomHash = (
|
||||
data["privkey"][:6]
|
||||
self.privkey[:6]
|
||||
+ hashlib.sha256(str(random.getrandbits(256)).encode()).hexdigest()[6:]
|
||||
)
|
||||
data["paymenthash"] = randomHash
|
||||
@@ -78,12 +77,10 @@ class FakeWallet(Wallet):
|
||||
|
||||
return InvoiceResponse(True, checking_id, payment_request)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
async def pay_invoice(self, bolt11: str, _: int) -> PaymentResponse:
|
||||
invoice = decode(bolt11)
|
||||
if (
|
||||
hasattr(invoice, "checking_id")
|
||||
and invoice.checking_id[:6] == self.privkey[:6] # type: ignore
|
||||
):
|
||||
|
||||
if invoice.payment_hash[:6] == self.privkey[:6]:
|
||||
await self.queue.put(invoice)
|
||||
return PaymentResponse(True, invoice.payment_hash, 0)
|
||||
else:
|
||||
@@ -91,10 +88,10 @@ class FakeWallet(Wallet):
|
||||
ok=False, error_message="Only internal invoices can be used!"
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
async def get_invoice_status(self, _: str) -> PaymentStatus:
|
||||
return PaymentStatus(None)
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
async def get_payment_status(self, _: str) -> PaymentStatus:
|
||||
return PaymentStatus(None)
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
|
||||
@@ -21,12 +21,13 @@ class LNbitsWallet(Wallet):
|
||||
|
||||
def __init__(self):
|
||||
self.endpoint = settings.lnbits_endpoint
|
||||
|
||||
key = (
|
||||
settings.lnbits_key
|
||||
or settings.lnbits_admin_key
|
||||
or settings.lnbits_invoice_key
|
||||
)
|
||||
if not self.endpoint or not key:
|
||||
raise Exception("cannot initialize lnbits wallet")
|
||||
self.key = {"X-Api-Key": key}
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
@@ -60,7 +61,7 @@ class LNbitsWallet(Wallet):
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: Dict = {"out": False, "amount": amount}
|
||||
data: Dict = {"out": False, "amount": amount, "memo": memo or ""}
|
||||
if kwargs.get("expiry"):
|
||||
data["expiry"] = kwargs["expiry"]
|
||||
if description_hash:
|
||||
@@ -68,8 +69,6 @@ class LNbitsWallet(Wallet):
|
||||
if unhashed_description:
|
||||
data["unhashed_description"] = unhashed_description.hex()
|
||||
|
||||
data["memo"] = memo or ""
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
url=f"{self.endpoint}/api/v1/payments", headers=self.key, json=data
|
||||
|
||||
+20
-15
@@ -105,9 +105,6 @@ class LndWallet(Wallet):
|
||||
)
|
||||
|
||||
endpoint = settings.lnd_grpc_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.port = int(settings.lnd_grpc_port)
|
||||
self.cert_path = settings.lnd_grpc_cert or settings.lnd_cert
|
||||
|
||||
macaroon = (
|
||||
settings.lnd_grpc_macaroon
|
||||
@@ -122,8 +119,17 @@ class LndWallet(Wallet):
|
||||
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
||||
encrypted_macaroon
|
||||
)
|
||||
self.macaroon = load_macaroon(macaroon)
|
||||
|
||||
cert_path = settings.lnd_grpc_cert or settings.lnd_cert
|
||||
if not endpoint or not macaroon or not cert_path or not settings.lnd_grpc_port:
|
||||
raise Exception("cannot initialize lndrest")
|
||||
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.port = int(settings.lnd_grpc_port)
|
||||
self.cert_path = settings.lnd_grpc_cert or settings.lnd_cert
|
||||
|
||||
self.macaroon = load_macaroon(macaroon)
|
||||
self.cert_path = cert_path
|
||||
cert = open(self.cert_path, "rb").read()
|
||||
creds = grpc.ssl_channel_credentials(cert)
|
||||
auth_creds = grpc.metadata_call_credentials(self.metadata_callback)
|
||||
@@ -140,8 +146,6 @@ class LndWallet(Wallet):
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
resp = await self.rpc.ChannelBalance(ln.ChannelBalanceRequest())
|
||||
except RpcError as exc:
|
||||
return StatusResponse(str(exc._details), 0)
|
||||
except Exception as exc:
|
||||
return StatusResponse(str(exc), 0)
|
||||
|
||||
@@ -155,20 +159,23 @@ class LndWallet(Wallet):
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
params: Dict = {"value": amount, "private": True}
|
||||
data: Dict = {
|
||||
"description_hash": b"",
|
||||
"value": amount,
|
||||
"private": True,
|
||||
"memo": memo or "",
|
||||
}
|
||||
if kwargs.get("expiry"):
|
||||
params["expiry"] = kwargs["expiry"]
|
||||
data["expiry"] = kwargs["expiry"]
|
||||
if description_hash:
|
||||
params["description_hash"] = description_hash
|
||||
data["description_hash"] = description_hash
|
||||
elif unhashed_description:
|
||||
params["description_hash"] = hashlib.sha256(
|
||||
data["description_hash"] = hashlib.sha256(
|
||||
unhashed_description
|
||||
).digest() # as bytes directly
|
||||
else:
|
||||
params["memo"] = memo or ""
|
||||
|
||||
try:
|
||||
req = ln.Invoice(**params)
|
||||
req = ln.Invoice(**data)
|
||||
resp = await self.rpc.AddInvoice(req)
|
||||
except Exception as exc:
|
||||
error_message = str(exc)
|
||||
@@ -188,8 +195,6 @@ class LndWallet(Wallet):
|
||||
)
|
||||
try:
|
||||
resp = await self.routerpc.SendPaymentV2(req).read()
|
||||
except RpcError as exc:
|
||||
return PaymentResponse(False, None, None, None, exc._details)
|
||||
except Exception as exc:
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
|
||||
|
||||
+29
-13
@@ -24,11 +24,7 @@ class LndRestWallet(Wallet):
|
||||
|
||||
def __init__(self):
|
||||
endpoint = settings.lnd_rest_endpoint
|
||||
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
endpoint = (
|
||||
"https://" + endpoint if not endpoint.startswith("http") else endpoint
|
||||
)
|
||||
self.endpoint = endpoint
|
||||
cert = settings.lnd_rest_cert
|
||||
|
||||
macaroon = (
|
||||
settings.lnd_rest_macaroon
|
||||
@@ -43,10 +39,31 @@ class LndRestWallet(Wallet):
|
||||
macaroon = AESCipher(description="macaroon decryption").decrypt(
|
||||
encrypted_macaroon
|
||||
)
|
||||
|
||||
if not endpoint:
|
||||
raise Exception("cannot initialize lndrest: no endpoint")
|
||||
|
||||
if not macaroon:
|
||||
raise Exception("cannot initialize lndrest: no macaroon")
|
||||
|
||||
if not cert:
|
||||
logger.warning(
|
||||
"no certificate for lndrest provided, this only works if you have a publicly issued certificate"
|
||||
)
|
||||
|
||||
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
endpoint = (
|
||||
f"https://{endpoint}" if not endpoint.startswith("http") else endpoint
|
||||
)
|
||||
self.endpoint = endpoint
|
||||
self.macaroon = load_macaroon(macaroon)
|
||||
|
||||
# if no cert provided it should be public so we set verify to True
|
||||
# and it will still check for validity of certificate and fail if its not valid
|
||||
# even on startup
|
||||
self.cert = cert or True
|
||||
|
||||
self.auth = {"Grpc-Metadata-macaroon": self.macaroon}
|
||||
self.cert = settings.lnd_rest_cert
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
@@ -54,8 +71,9 @@ class LndRestWallet(Wallet):
|
||||
r = await client.get(
|
||||
f"{self.endpoint}/v1/balance/channels", headers=self.auth
|
||||
)
|
||||
except (httpx.ConnectError, httpx.RequestError):
|
||||
return StatusResponse(f"Unable to connect to {self.endpoint}.", 0)
|
||||
r.raise_for_status()
|
||||
except (httpx.ConnectError, httpx.RequestError) as exc:
|
||||
return StatusResponse(f"Unable to connect to {self.endpoint}. {exc}", 0)
|
||||
|
||||
try:
|
||||
data = r.json()
|
||||
@@ -74,7 +92,7 @@ class LndRestWallet(Wallet):
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: Dict = {"value": amount, "private": True}
|
||||
data: Dict = {"value": amount, "private": True, "memo": memo or ""}
|
||||
if kwargs.get("expiry"):
|
||||
data["expiry"] = kwargs["expiry"]
|
||||
if description_hash:
|
||||
@@ -85,8 +103,6 @@ class LndRestWallet(Wallet):
|
||||
data["description_hash"] = base64.b64encode(
|
||||
hashlib.sha256(unhashed_description).digest()
|
||||
).decode("ascii")
|
||||
else:
|
||||
data["memo"] = memo or ""
|
||||
|
||||
async with httpx.AsyncClient(verify=self.cert) as client:
|
||||
r = await client.post(
|
||||
@@ -171,9 +187,9 @@ class LndRestWallet(Wallet):
|
||||
timeout=None, headers=self.auth, verify=self.cert
|
||||
) as client:
|
||||
async with client.stream("GET", url) as r:
|
||||
async for l in r.aiter_lines():
|
||||
async for json_line in r.aiter_lines():
|
||||
try:
|
||||
line = json.loads(l)
|
||||
line = json.loads(json_line)
|
||||
if line.get("error"):
|
||||
logger.error(
|
||||
line["error"]["message"]
|
||||
|
||||
+10
-3
@@ -5,7 +5,7 @@ from http import HTTPStatus
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
@@ -24,8 +24,13 @@ class LNPayWallet(Wallet):
|
||||
|
||||
def __init__(self):
|
||||
endpoint = settings.lnpay_api_endpoint
|
||||
wallet_key = settings.lnpay_wallet_key or settings.lnpay_admin_key
|
||||
|
||||
if not endpoint or not wallet_key or not settings.lnpay_api_key:
|
||||
raise Exception("cannot initialize lnpay")
|
||||
|
||||
self.wallet_key = wallet_key
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.wallet_key = settings.lnpay_wallet_key or settings.lnpay_admin_key
|
||||
self.auth = {"X-Api-Key": settings.lnpay_api_key}
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
@@ -134,7 +139,9 @@ class LNPayWallet(Wallet):
|
||||
yield value
|
||||
|
||||
async def webhook_listener(self):
|
||||
text: str = await request.get_data()
|
||||
# TODO: request.get_data is undefined, was it something with Flask or quart?
|
||||
# probably issue introduced when refactoring?
|
||||
text: str = await request.get_data() # type: ignore
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.decoder.JSONDecodeError:
|
||||
|
||||
@@ -21,13 +21,14 @@ from .base import (
|
||||
class LnTipsWallet(Wallet):
|
||||
def __init__(self):
|
||||
endpoint = settings.lntips_api_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
|
||||
key = (
|
||||
settings.lntips_api_key
|
||||
or settings.lntips_admin_key
|
||||
or settings.lntips_invoice_key
|
||||
)
|
||||
if not endpoint or not key:
|
||||
raise Exception("cannot initialize lntxbod")
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.auth = {"Authorization": f"Basic {key}"}
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
@@ -55,13 +56,11 @@ class LnTipsWallet(Wallet):
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: Dict = {"amount": amount}
|
||||
data: Dict = {"amount": amount, "description_hash": "", "memo": memo or ""}
|
||||
if description_hash:
|
||||
data["description_hash"] = description_hash.hex()
|
||||
elif unhashed_description:
|
||||
data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
|
||||
else:
|
||||
data["memo"] = memo or ""
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
|
||||
@@ -4,7 +4,7 @@ from http import HTTPStatus
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import httpx
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.settings import settings
|
||||
@@ -24,13 +24,15 @@ class OpenNodeWallet(Wallet):
|
||||
|
||||
def __init__(self):
|
||||
endpoint = settings.opennode_api_endpoint
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
|
||||
key = (
|
||||
settings.opennode_key
|
||||
or settings.opennode_admin_key
|
||||
or settings.opennode_invoice_key
|
||||
)
|
||||
if not endpoint or not key:
|
||||
raise Exception("cannot initialize opennode")
|
||||
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
self.auth = {"Authorization": key}
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
@@ -140,7 +142,9 @@ class OpenNodeWallet(Wallet):
|
||||
yield value
|
||||
|
||||
async def webhook_listener(self):
|
||||
data = await request.form
|
||||
# TODO: request.form is undefined, was it something with Flask or quart?
|
||||
# probably issue introduced when refactoring?
|
||||
data = await request.form # type: ignore
|
||||
if "status" not in data or data["status"] != "paid":
|
||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||
|
||||
|
||||
+40
-27
@@ -28,6 +28,7 @@ class UnknownError(Exception):
|
||||
|
||||
class SparkWallet(Wallet):
|
||||
def __init__(self):
|
||||
assert settings.spark_url, "spark url does not exist"
|
||||
self.url = settings.spark_url.replace("/rpc", "")
|
||||
self.token = settings.spark_token
|
||||
|
||||
@@ -46,6 +47,7 @@ class SparkWallet(Wallet):
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
assert self.token, "spark wallet token does not exist"
|
||||
r = await client.post(
|
||||
self.url + "/rpc",
|
||||
headers={"X-Access": self.token},
|
||||
@@ -60,7 +62,7 @@ class SparkWallet(Wallet):
|
||||
httpx.HTTPError,
|
||||
httpx.TimeoutException,
|
||||
) as exc:
|
||||
raise UnknownError("error connecting to spark: " + str(exc))
|
||||
raise UnknownError(f"error connecting to spark: {exc}")
|
||||
|
||||
try:
|
||||
data = r.json()
|
||||
@@ -133,38 +135,49 @@ class SparkWallet(Wallet):
|
||||
bolt11=bolt11,
|
||||
maxfee=fee_limit_msat,
|
||||
)
|
||||
fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"])
|
||||
preimage = r["payment_preimage"]
|
||||
return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None)
|
||||
|
||||
except (SparkError, UnknownError) as exc:
|
||||
listpays = await self.listpays(bolt11)
|
||||
if listpays:
|
||||
pays = listpays["pays"]
|
||||
if not listpays:
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
|
||||
if len(pays) == 0:
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
pays = listpays["pays"]
|
||||
|
||||
pay = pays[0]
|
||||
payment_hash = pay["payment_hash"]
|
||||
if len(pays) == 0:
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
|
||||
if len(pays) > 1:
|
||||
raise SparkError(
|
||||
f"listpays({payment_hash}) returned an unexpected response: {listpays}"
|
||||
)
|
||||
pay = pays[0]
|
||||
payment_hash = pay["payment_hash"]
|
||||
|
||||
if pay["status"] == "failed":
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
elif pay["status"] == "pending":
|
||||
return PaymentResponse(None, payment_hash, None, None, None)
|
||||
elif pay["status"] == "complete":
|
||||
r = pay
|
||||
r["payment_preimage"] = pay["preimage"]
|
||||
r["msatoshi"] = int(pay["amount_msat"][0:-4])
|
||||
r["msatoshi_sent"] = int(pay["amount_sent_msat"][0:-4])
|
||||
# this may result in an error if it was paid previously
|
||||
# our database won't allow the same payment_hash to be added twice
|
||||
# this is good
|
||||
if len(pays) > 1:
|
||||
raise SparkError(
|
||||
f"listpays({payment_hash}) returned an unexpected response: {listpays}"
|
||||
)
|
||||
|
||||
fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"])
|
||||
preimage = r["payment_preimage"]
|
||||
return PaymentResponse(True, r["payment_hash"], fee_msat, preimage, None)
|
||||
if pay["status"] == "failed":
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
|
||||
if pay["status"] == "pending":
|
||||
return PaymentResponse(None, payment_hash, None, None, None)
|
||||
|
||||
if pay["status"] == "complete":
|
||||
r = pay
|
||||
r["payment_preimage"] = pay["preimage"]
|
||||
r["msatoshi"] = int(pay["amount_msat"][0:-4])
|
||||
r["msatoshi_sent"] = int(pay["amount_sent_msat"][0:-4])
|
||||
# this may result in an error if it was paid previously
|
||||
# our database won't allow the same payment_hash to be added twice
|
||||
# this is good
|
||||
fee_msat = -int(r["msatoshi_sent"] - r["msatoshi"])
|
||||
preimage = r["payment_preimage"]
|
||||
return PaymentResponse(
|
||||
True, r["payment_hash"], fee_msat, preimage, None
|
||||
)
|
||||
else:
|
||||
return PaymentResponse(False, None, None, None, str(exc))
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
@@ -205,7 +218,7 @@ class SparkWallet(Wallet):
|
||||
- int(r["pays"][0]["amount_msat"][0:-4])
|
||||
)
|
||||
return PaymentStatus(True, fee_msat, r["pays"][0]["preimage"])
|
||||
elif status == "failed":
|
||||
if status == "failed":
|
||||
return PaymentStatus(False)
|
||||
return PaymentStatus(None)
|
||||
raise KeyError("supplied an invalid checking_id")
|
||||
|
||||
@@ -24,7 +24,10 @@ class VoidWallet(Wallet):
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
logger.warning(
|
||||
"This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits."
|
||||
(
|
||||
"This backend does nothing, it is here just as a placeholder, you must configure an "
|
||||
"actual backend before being able to do anything useful with LNbits."
|
||||
)
|
||||
)
|
||||
return StatusResponse(None, 0)
|
||||
|
||||
|
||||
Generated
+606
-15
@@ -1,19 +1,399 @@
|
||||
{
|
||||
"name": "main",
|
||||
"lockfileVersion": 2,
|
||||
"name": "lnbits",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lnbits",
|
||||
"dependencies": {
|
||||
"@chenfengyuan/vue-qrcode": "1.0.2",
|
||||
"axios": "^1.3.4",
|
||||
"chart.js": "2.9",
|
||||
"moment": "^2.29.4",
|
||||
"quasar": "1.13.2",
|
||||
"underscore": "^1.13.6",
|
||||
"vue": "2.6.12",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vue-qrcode-reader": "2.2",
|
||||
"vue-router": "3.4.3",
|
||||
"vuex": "3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "2.8.3",
|
||||
"pyright": "1.1.289"
|
||||
}
|
||||
},
|
||||
"node_modules/@chenfengyuan/vue-qrcode": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@chenfengyuan/vue-qrcode/-/vue-qrcode-1.0.2.tgz",
|
||||
"integrity": "sha512-hwy1d4YMJAyEh+V7dLPG8eAKACRvugzSB4ylwb6QNqo84KHTF50/5EJcBYdUhTRPfAqrxG0i6jDAXONWOGyQbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"qrcode": "^1.4.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-runtime": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||
"integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js": "^2.4.0",
|
||||
"regenerator-runtime": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/callforth": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/callforth/-/callforth-0.3.1.tgz",
|
||||
"integrity": "sha512-Q2zPfqnwoKsb1DTVCr4lmhe49wKNBsMmNlbudjleu3/co+Nw1pOqFHYJHrW3VZ253ou9AAr+xauQR0C55NPdzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "2.9.4",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz",
|
||||
"integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chartjs-color": "^2.1.0",
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-color": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
|
||||
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chartjs-color-string": "^0.6.0",
|
||||
"color-convert": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-color-string": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
|
||||
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
|
||||
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
|
||||
"integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encode-utf8": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
|
||||
"integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jsqr": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
|
||||
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
|
||||
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
@@ -24,11 +404,17 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/pyright": {
|
||||
"version": "1.1.289",
|
||||
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.289.tgz",
|
||||
"integrity": "sha512-fG3STxnwAt3i7bxbXUPJdYNFrcOWHLwCSEOySH2foUqtYdzWLcxDez0Kgl1X8LMQx0arMJ6HRkKghxfRD1/z6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"pyright": "index.js",
|
||||
"pyright-langserver": "langserver.index.js"
|
||||
@@ -36,20 +422,225 @@
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"prettier": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
|
||||
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
|
||||
"dev": true
|
||||
},
|
||||
"pyright": {
|
||||
"version": "1.1.289",
|
||||
"resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.289.tgz",
|
||||
"integrity": "sha512-fG3STxnwAt3i7bxbXUPJdYNFrcOWHLwCSEOySH2foUqtYdzWLcxDez0Kgl1X8LMQx0arMJ6HRkKghxfRD1/z6g==",
|
||||
"dev": true
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz",
|
||||
"integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"encode-utf8": "^1.0.3",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/quasar": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/quasar/-/quasar-1.13.2.tgz",
|
||||
"integrity": "sha512-HJtWz38rLXLvTJqZ5c9GIS6SSwLjN0t8iTwAwD2AQ5FOsUTji6sq3XzzoPll5degXFevnVJlyFH5YTqMYnawVg==",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0",
|
||||
"npm": ">= 5.6.0",
|
||||
"yarn": ">= 1.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://donate.quasar.dev"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/rtcpeerconnection-shim": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
|
||||
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"sdp": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
|
||||
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
|
||||
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "8.28.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.28.2.tgz",
|
||||
"integrity": "sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA=="
|
||||
},
|
||||
"node_modules/vue-qrcode-reader": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-qrcode-reader/-/vue-qrcode-reader-2.2.0.tgz",
|
||||
"integrity": "sha512-xsNptrJGTFFjwfksIK6KlbjsyIGBLTFgO9KH3JcmZzABY19c2d0+zG//LXkp8K2Jl+4APidktVtH/aTbmcgX7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"callforth": "^0.3.0",
|
||||
"jsqr": "^1.2.0",
|
||||
"webrtc-adapter": "^6.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.3.tgz",
|
||||
"integrity": "sha512-BADg1mjGWX18Dpmy6bOGzGNnk7B/ZA0RxuA6qedY/YJwirMfKXIDzcccmHbQI0A6k5PzMdMloc0ElHfyOoX35A=="
|
||||
},
|
||||
"node_modules/vuex": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
|
||||
"integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw==",
|
||||
"peerDependencies": {
|
||||
"vue": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "6.4.8",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-6.4.8.tgz",
|
||||
"integrity": "sha512-YM8yl545c/JhYcjGHgaCoA7jRK/KZuMwEDFeP2AcP0Auv5awEd+gZE0hXy9z7Ed3p9HvAXp8jdbe+4ESb1zxAw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"rtcpeerconnection-shim": "^1.2.14",
|
||||
"sdp": "^2.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
{
|
||||
"name": "lnbits",
|
||||
"devDependencies": {
|
||||
"prettier": "2.8.3",
|
||||
"pyright": "1.1.289"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chenfengyuan/vue-qrcode": "1.0.2",
|
||||
"axios": "^1.3.4",
|
||||
"chart.js": "2.9",
|
||||
"moment": "^2.29.4",
|
||||
"quasar": "1.13.2",
|
||||
"underscore": "^1.13.6",
|
||||
"vue": "2.6.12",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vue-qrcode-reader": "2.2",
|
||||
"vue-router": "3.4.3",
|
||||
"vuex": "3.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+160
-202
@@ -15,7 +15,6 @@ files = [
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
@@ -36,19 +35,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "astroid"
|
||||
version = "2.14.1"
|
||||
version = "2.15.2"
|
||||
description = "An abstract syntax tree for Python with inference support."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
{file = "astroid-2.14.1-py3-none-any.whl", hash = "sha256:23c718921acab5f08cbbbe9293967f1f8fec40c336d19cd75dc12a9ea31d2eb2"},
|
||||
{file = "astroid-2.14.1.tar.gz", hash = "sha256:bd1aa4f9915c98e8aaebcd4e71930154d4e8c9aaf05d35ac0a63d1956091ae3f"},
|
||||
{file = "astroid-2.15.2-py3-none-any.whl", hash = "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb"},
|
||||
{file = "astroid-2.15.2.tar.gz", hash = "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
lazy-object-proxy = ">=1.4.0"
|
||||
typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""}
|
||||
typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
|
||||
wrapt = [
|
||||
{version = ">=1.11,<2", markers = "python_version < \"3.11\""},
|
||||
@@ -67,9 +65,6 @@ files = [
|
||||
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "22.2.0"
|
||||
@@ -157,7 +152,6 @@ mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
@@ -378,7 +372,6 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "coincurve"
|
||||
@@ -442,63 +435,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.1.0"
|
||||
version = "7.2.3"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "coverage-7.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-win32.whl", hash = "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352"},
|
||||
{file = "coverage-7.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-win32.whl", hash = "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7"},
|
||||
{file = "coverage-7.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-win32.whl", hash = "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73"},
|
||||
{file = "coverage-7.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-win32.whl", hash = "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab"},
|
||||
{file = "coverage-7.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-win32.whl", hash = "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4"},
|
||||
{file = "coverage-7.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3"},
|
||||
{file = "coverage-7.1.0-pp37.pp38.pp39-none-any.whl", hash = "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda"},
|
||||
{file = "coverage-7.1.0.tar.gz", hash = "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"},
|
||||
{file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"},
|
||||
{file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -541,10 +534,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"]
|
||||
|
||||
@@ -823,7 +816,6 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
@@ -845,18 +837,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.11.5"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"},
|
||||
{file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"},
|
||||
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
colors = ["colorama (>=0.4.3)"]
|
||||
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
@@ -940,7 +932,6 @@ files = [
|
||||
[package.dependencies]
|
||||
bech32 = "*"
|
||||
pydantic = "*"
|
||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
@@ -1107,7 +1098,6 @@ files = [
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
|
||||
typing-extensions = ">=3.10"
|
||||
|
||||
[package.extras]
|
||||
@@ -1171,34 +1161,31 @@ six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
|
||||
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
|
||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.6.2"
|
||||
version = "3.2.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"},
|
||||
{file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"},
|
||||
{file = "platformdirs-3.2.0-py3-none-any.whl", hash = "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"},
|
||||
{file = "platformdirs-3.2.0.tar.gz", hash = "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
@@ -1212,9 +1199,6 @@ files = [
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
@@ -1416,18 +1400,18 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "2.16.1"
|
||||
version = "2.17.2"
|
||||
description = "python code static checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
{file = "pylint-2.16.1-py3-none-any.whl", hash = "sha256:bad9d7c36037f6043a1e848a43004dfd5ea5ceb05815d713ba56ca4503a9fe37"},
|
||||
{file = "pylint-2.16.1.tar.gz", hash = "sha256:ffe7fa536bb38ba35006a7c8a6d2efbfdd3d95bbf21199cad31f76b1c50aaf30"},
|
||||
{file = "pylint-2.17.2-py3-none-any.whl", hash = "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8"},
|
||||
{file = "pylint-2.17.2.tar.gz", hash = "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
astroid = ">=2.14.1,<=2.16.0-dev0"
|
||||
astroid = ">=2.15.2,<=2.17.0-dev0"
|
||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||
dill = [
|
||||
{version = ">=0.2", markers = "python_version < \"3.11\""},
|
||||
@@ -1551,7 +1535,6 @@ files = [
|
||||
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"
|
||||
@@ -1574,7 +1557,6 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=6.1.0"
|
||||
typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
||||
@@ -1738,14 +1720,14 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
|
||||
|
||||
[[package]]
|
||||
name = "shortuuid"
|
||||
version = "1.0.1"
|
||||
version = "1.0.11"
|
||||
description = "A generator library for concise, unambiguous and URL-safe UUIDs."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"},
|
||||
{file = "shortuuid-1.0.1.tar.gz", hash = "sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f"},
|
||||
{file = "shortuuid-1.0.11-py3-none-any.whl", hash = "sha256:27ea8f28b1bd0bf8f15057a3ece57275d2059d2b0bb02854f02189962c13b6aa"},
|
||||
{file = "shortuuid-1.0.11.tar.gz", hash = "sha256:fc75f2615914815a8e4cb1501b3a513745cb66ef0fd5fc6fb9f8c3fa3481f789"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1821,7 +1803,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"]
|
||||
@@ -1894,48 +1876,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
description = "Style preserving TOML library"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"},
|
||||
{file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.5.4"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
|
||||
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
|
||||
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
|
||||
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
|
||||
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
|
||||
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
|
||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||
{file = "tomlkit-0.11.7-py3-none-any.whl", hash = "sha256:5325463a7da2ef0c6bbfefb62a3dc883aebe679984709aee32a317907d0a8d3c"},
|
||||
{file = "tomlkit-0.11.7.tar.gz", hash = "sha256:f392ef70ad87a672f02519f99967d28a4d3047133e2d1df936511465fbb3791d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1994,7 +1942,6 @@ files = [
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
h11 = ">=0.8"
|
||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
|
||||
@@ -2114,76 +2061,87 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.14.1"
|
||||
version = "1.15.0"
|
||||
description = "Module for decorators, wrappers and monkey patching."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
files = [
|
||||
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
|
||||
{file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
|
||||
{file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
|
||||
{file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
|
||||
{file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
|
||||
{file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
|
||||
{file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
|
||||
{file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
|
||||
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"},
|
||||
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"},
|
||||
{file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"},
|
||||
{file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"},
|
||||
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"},
|
||||
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"},
|
||||
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"},
|
||||
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"},
|
||||
{file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"},
|
||||
{file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"},
|
||||
{file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"},
|
||||
{file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"},
|
||||
{file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"},
|
||||
{file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"},
|
||||
{file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"},
|
||||
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2204,5 +2162,5 @@ 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 = "9ade42d23e7541626f1d1be38c9af2313769c372bca8bb306a4e653812aea33c"
|
||||
python-versions = "^3.10 | ^3.9 | ^3.8.1"
|
||||
content-hash = "d8cae4bcda9fcc8db58c08e1027d1e735dfe87e77584d7763348a6183c0fbad8"
|
||||
|
||||
+12
-15
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "lnbits"
|
||||
version = "0.10.2"
|
||||
version = "0.10.3"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
||||
|
||||
@@ -9,7 +9,7 @@ generate-setup-file = false
|
||||
script = "tools/build.py"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||
python = "^3.10 | ^3.9 | ^3.8.1"
|
||||
bech32 = "1.2.0"
|
||||
bitstring = "3.1.9"
|
||||
click = "8.0.4"
|
||||
@@ -23,7 +23,7 @@ psycopg2-binary = "2.9.1"
|
||||
pydantic = "1.10.4"
|
||||
pyqrcode = "1.2.1"
|
||||
pyScss = "1.4.0"
|
||||
shortuuid = "1.0.1"
|
||||
shortuuid = "1.0.11"
|
||||
sqlalchemy = "1.3.24"
|
||||
sqlalchemy-aio = "0.17.0"
|
||||
sse-starlette = "0.6.2"
|
||||
@@ -40,8 +40,8 @@ pyln-client = "0.11.1"
|
||||
boltz-client = "0.1.3"
|
||||
cashu = "0.9.0"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
flake8 = "^6.0.0"
|
||||
isort = "^5.10.1"
|
||||
pytest = "^7.1.2"
|
||||
mock = "^4.0.3"
|
||||
@@ -50,11 +50,10 @@ pytest-asyncio = "^0.19.0"
|
||||
pytest-cov = "^3.0.0"
|
||||
mypy = "^0.971"
|
||||
types-protobuf = "^3.19.22"
|
||||
flake8 = { version = "^6.0.0", python = ">=3.8.1" }
|
||||
pylint = { version = "^2.15.10", python = ">=3.7.2" }
|
||||
pylint = "^2.17.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0", "pyScss"]
|
||||
requires = ["poetry-core>=1.0.0", "pyscss"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
@@ -69,18 +68,15 @@ include = [
|
||||
]
|
||||
exclude = [
|
||||
"lnbits/wallets/lnd_grpc_files",
|
||||
"lnbits/wallets",
|
||||
"lnbits/core",
|
||||
"lnbits/*.py",
|
||||
"lnbits/extensions",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
files = "lnbits"
|
||||
exclude = """(?x)(
|
||||
^lnbits/wallets/lnd_grpc_files.
|
||||
| ^lnbits/extensions.
|
||||
)"""
|
||||
exclude = [
|
||||
"^lnbits/wallets/lnd_grpc_files",
|
||||
"^lnbits/extensions",
|
||||
]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
@@ -134,6 +130,7 @@ enable = [
|
||||
|
||||
[tool.pylint.MASTER]
|
||||
ignore-paths = [
|
||||
"^lnbits/extensions/.*$",
|
||||
"^lnbits/wallets/lnd_grpc_files/.*$",
|
||||
]
|
||||
fail-under = 10.0
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
anyio==3.6.2 ; 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.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"
|
||||
boltz-client==0.1.3 ; python_version >= "3.7" and python_version < "4.0"
|
||||
cashu==0.9.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||
cerberus==1.3.4 ; 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.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.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"
|
||||
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.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.1.2 ; 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==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.12 ; python_version >= "3.7" and python_version < "4.0"
|
||||
psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0"
|
||||
pycparser==2.21 ; 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"
|
||||
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.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"
|
||||
represent==1.6.0.post0 ; python_version >= "3.7" and python_version < "4.0"
|
||||
requests==2.27.1 ; 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.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"
|
||||
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 < "3.11"
|
||||
typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||
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"
|
||||
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.11.0 ; python_version >= "3.7" and python_version < "4.0"
|
||||
@@ -1,28 +0,0 @@
|
||||
[flake8]
|
||||
max-line-length = 300
|
||||
exclude = lnbits/wallets/lnd_grpc_files/
|
||||
ignore =
|
||||
# E203 whitespace before ':'
|
||||
E203,
|
||||
# E221: multiple spaces before operator
|
||||
E221,
|
||||
# E241: multiple spaces after ':'
|
||||
E241,
|
||||
# E402: module level import not at top of file
|
||||
E402,
|
||||
# E501: line too long
|
||||
E501,
|
||||
# E741 ambiguous variable name
|
||||
E741,
|
||||
# W503: line break before binary operator
|
||||
W503,
|
||||
# F821: undefined name - should be addressed in future PR
|
||||
F821,
|
||||
# E265 block comment should start with '# ' - should be addressed in future PR
|
||||
E265,
|
||||
# E266 too many leading '#' for block comment - should be addressed in future PR
|
||||
E266,
|
||||
# E722 do not use bare 'except' - should be addressed in future PR
|
||||
E722,
|
||||
# flake8-requirements import checks
|
||||
I
|
||||
@@ -6,7 +6,7 @@ from lnbits import bolt11
|
||||
from lnbits.core.views.api import api_payment
|
||||
from lnbits.settings import get_wallet_class
|
||||
|
||||
from ...helpers import get_random_invoice_data, is_fake, is_regtest
|
||||
from ...helpers import get_random_invoice_data, is_fake
|
||||
|
||||
WALLET = get_wallet_class()
|
||||
|
||||
@@ -170,16 +170,15 @@ async def test_pay_invoice_invoicekey(client, invoice, inkey_headers_from):
|
||||
assert response.status_code >= 300 # should fail
|
||||
|
||||
|
||||
# check POST /api/v1/payments: payment with admin key [should pass]
|
||||
# check POST /api/v1/payments: payment with admin key, trying to pay twice [should fail]
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_regtest, reason="this only works in fakewallet")
|
||||
async def test_pay_invoice_adminkey(client, invoice, adminkey_headers_from):
|
||||
data = {"out": True, "bolt11": invoice["payment_request"]}
|
||||
# try payment with admin key
|
||||
response = await client.post(
|
||||
"/api/v1/payments", json=data, headers=adminkey_headers_from
|
||||
)
|
||||
assert response.status_code < 300 # should pass
|
||||
assert response.status_code > 300 # should fail
|
||||
|
||||
|
||||
# check POST /api/v1/payments/decode
|
||||
|
||||
@@ -4,27 +4,22 @@ import pytest
|
||||
@pytest.mark.asyncio
|
||||
async def test_core_views_generic(client):
|
||||
response = await client.get("/")
|
||||
assert response.status_code == 200, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /wallet: wallet info
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet(client):
|
||||
response = await client.get("wallet")
|
||||
assert response.status_code == 307, ( # redirect not modified
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
# redirect not modified
|
||||
assert response.status_code == 307, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /wallet: do not allow redirects, expect code 307
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet_no_redirect(client):
|
||||
response = await client.get("wallet", follow_redirects=False)
|
||||
assert response.status_code == 307, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 307, f"{response.url} {response.status_code}"
|
||||
|
||||
# determine the next redirect location
|
||||
request = client.build_request("GET", "wallet")
|
||||
@@ -33,13 +28,11 @@ async def test_get_wallet_no_redirect(client):
|
||||
response = await client.send(request)
|
||||
request = response.next_request
|
||||
if i == 0:
|
||||
assert response.status_code == 307, ( # first redirect
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
# first redirect
|
||||
assert response.status_code == 307, f"{response.url} {response.status_code}"
|
||||
elif i == 1:
|
||||
assert response.status_code == 200, ( # then get the actual page
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
# then get the actual page
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
i += 1
|
||||
|
||||
|
||||
@@ -47,18 +40,14 @@ async def test_get_wallet_no_redirect(client):
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet_with_nonexistent_user(client):
|
||||
response = await client.get("wallet", params={"usr": "1"})
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 400, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /wallet: with user
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet_with_user(client, to_user):
|
||||
response = await client.get("wallet", params={"usr": to_user.id})
|
||||
assert response.status_code == 307, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 307, f"{response.url} {response.status_code}"
|
||||
|
||||
# determine the next redirect location
|
||||
request = client.build_request("GET", "wallet", params={"usr": to_user.id})
|
||||
@@ -67,13 +56,11 @@ async def test_get_wallet_with_user(client, to_user):
|
||||
response = await client.send(request)
|
||||
request = response.next_request
|
||||
if i == 0:
|
||||
assert response.status_code == 307, ( # first redirect
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
# first redirect
|
||||
assert response.status_code == 307, f"{response.url} {response.status_code}"
|
||||
elif i == 1:
|
||||
assert response.status_code == 200, ( # then get the actual page
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
# then get the actual page
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
i += 1
|
||||
|
||||
|
||||
@@ -83,45 +70,36 @@ async def test_get_wallet_with_user_and_wallet(client, to_user, to_wallet):
|
||||
response = await client.get(
|
||||
"wallet", params={"usr": to_user.id, "wal": to_wallet.id}
|
||||
)
|
||||
assert response.status_code == 200, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /wallet: wrong wallet and user, expect 400
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_wallet_with_user_and_wrong_wallet(client, to_user):
|
||||
response = await client.get("wallet", params={"usr": to_user.id, "wal": "1"})
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 400, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /extensions: extensions list
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_extensions(client, to_user):
|
||||
response = await client.get("extensions", params={"usr": to_user.id})
|
||||
assert response.status_code == 200, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /extensions: extensions list wrong user, expect 400
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_extensions_wrong_user(client, to_user):
|
||||
response = await client.get("extensions", params={"usr": "1"})
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 400, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /extensions: no user given, expect code 400 bad request
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_extensions_no_user(client):
|
||||
response = await client.get("extensions")
|
||||
assert response.status_code == 400, ( # bad request
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
# bad request
|
||||
assert response.status_code == 400, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /extensions: enable extension
|
||||
@@ -131,9 +109,7 @@ async def test_get_extensions_no_user(client):
|
||||
# response = await client.get(
|
||||
# "extensions", params={"usr": to_user.id, "enable": "lnurlp"}
|
||||
# )
|
||||
# assert response.status_code == 200, (
|
||||
# str(response.url) + " " + str(response.status_code)
|
||||
# )
|
||||
# assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /extensions: enable and disable extensions, expect code 400 bad request
|
||||
@@ -143,9 +119,7 @@ async def test_get_extensions_no_user(client):
|
||||
# "extensions",
|
||||
# params={"usr": to_user.id, "enable": "lnurlp", "disable": "lnurlp"},
|
||||
# )
|
||||
# assert response.status_code == 400, (
|
||||
# str(response.url) + " " + str(response.status_code)
|
||||
# )
|
||||
# assert response.status_code == 400, f"{response.url} {response.status_code}"
|
||||
|
||||
|
||||
# check GET /extensions: enable nonexistent extension, expect code 400 bad request
|
||||
@@ -154,6 +128,4 @@ async def test_get_extensions_enable_nonexistent_extension(client, to_user):
|
||||
response = await client.get(
|
||||
"extensions", params={"usr": to_user.id, "enable": "12341234"}
|
||||
)
|
||||
assert response.status_code == 400, (
|
||||
str(response.url) + " " + str(response.status_code)
|
||||
)
|
||||
assert response.status_code == 400, f"{response.url} {response.status_code}"
|
||||
|
||||
+27
-67
@@ -1,74 +1,34 @@
|
||||
import glob
|
||||
import os
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
LNBITS_PATH = Path("lnbits").absolute()
|
||||
|
||||
# from ..lnbits.helpers import vendored_js, vendored_css
|
||||
vendored_js = [
|
||||
"/static/vendor/moment.js",
|
||||
"/static/vendor/underscore.js",
|
||||
"/static/vendor/axios.js",
|
||||
"/static/vendor/vue.js",
|
||||
"/static/vendor/vue-router.js",
|
||||
"/static/vendor/vue-qrcode-reader.browser.js",
|
||||
"/static/vendor/vue-qrcode.js",
|
||||
"/static/vendor/vue-i18n.js",
|
||||
"/static/vendor/vuex.js",
|
||||
"/static/vendor/quasar.ie.polyfills.umd.min.js",
|
||||
"/static/vendor/quasar.umd.js",
|
||||
"/static/vendor/Chart.bundle.js",
|
||||
]
|
||||
|
||||
def get_js_vendored(prefer_minified: bool = False) -> List[str]:
|
||||
paths = get_vendored(".js", prefer_minified)
|
||||
|
||||
def sorter(key: str):
|
||||
if "moment@" in key:
|
||||
return 1
|
||||
if "vue@" in key:
|
||||
return 2
|
||||
if "vue-router@" in key:
|
||||
return 3
|
||||
if "polyfills" in key:
|
||||
return 4
|
||||
return 9
|
||||
|
||||
return sorted(paths, key=sorter)
|
||||
|
||||
|
||||
def get_css_vendored(prefer_minified: bool = False) -> List[str]:
|
||||
paths = get_vendored(".css", prefer_minified)
|
||||
|
||||
def sorter(key: str):
|
||||
if "quasar@" in key:
|
||||
return 1
|
||||
if "vue@" in key:
|
||||
return 2
|
||||
if "chart.js@" in key:
|
||||
return 100
|
||||
return 9
|
||||
|
||||
return sorted(paths, key=sorter)
|
||||
|
||||
|
||||
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
|
||||
paths: List[str] = []
|
||||
for path in glob.glob(
|
||||
os.path.join(LNBITS_PATH, "static/vendor/**"), recursive=True
|
||||
):
|
||||
if path.endswith(".min" + ext):
|
||||
# path is minified
|
||||
unminified = path.replace(".min" + ext, ext)
|
||||
if prefer_minified:
|
||||
paths.append(path)
|
||||
if unminified in paths:
|
||||
paths.remove(unminified)
|
||||
elif unminified not in paths:
|
||||
paths.append(path)
|
||||
|
||||
elif path.endswith(ext):
|
||||
# path is not minified
|
||||
minified = path.replace(ext, ".min" + ext)
|
||||
if not prefer_minified:
|
||||
paths.append(path)
|
||||
if minified in paths:
|
||||
paths.remove(minified)
|
||||
elif minified not in paths:
|
||||
paths.append(path)
|
||||
|
||||
return sorted(paths)
|
||||
vendored_css = [
|
||||
"/static/vendor/quasar.css",
|
||||
"/static/vendor/Chart.css",
|
||||
"/static/vendor/vue-qrcode-reader.css",
|
||||
]
|
||||
|
||||
|
||||
def url_for_vendored(abspath: str) -> str:
|
||||
return "/" + os.path.relpath(abspath, LNBITS_PATH)
|
||||
return f"/{os.path.relpath(abspath, LNBITS_PATH)}"
|
||||
|
||||
|
||||
def transpile_scss():
|
||||
@@ -82,14 +42,14 @@ def transpile_scss():
|
||||
|
||||
|
||||
def bundle_vendored():
|
||||
for getfiles, outputpath in [
|
||||
(get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")),
|
||||
(get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")),
|
||||
for files, outputpath in [
|
||||
(vendored_js, os.path.join(LNBITS_PATH, "static/bundle.js")),
|
||||
(vendored_css, os.path.join(LNBITS_PATH, "static/bundle.css")),
|
||||
]:
|
||||
output = ""
|
||||
for path in getfiles():
|
||||
with open(path) as f:
|
||||
output += "/* " + url_for_vendored(path) + " */\n" + f.read() + ";\n"
|
||||
for path in files:
|
||||
with open(f"{LNBITS_PATH}{path}") as f:
|
||||
output += f.read() + ";\n"
|
||||
with open(outputpath, "w") as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user