mirror of
https://github.com/lnbits/lnbits.git
synced 2025-04-08 03:48:30 +02:00
Merge remote-tracking branch 'origin/FinalAdminUI' into FinalAdminUI
This commit is contained in:
commit
e186422439
15
.env.example
15
.env.example
@ -19,7 +19,7 @@ LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
||||
LNBITS_AD_SPACE=""
|
||||
|
||||
# Hides wallet api, extensions can choose to honor
|
||||
LNBITS_HIDE_API=false
|
||||
LNBITS_HIDE_API=false
|
||||
|
||||
# Disable extensions for all users, use "all" to disable all extensions
|
||||
LNBITS_DISABLED_EXTENSIONS="amilk"
|
||||
@ -43,11 +43,11 @@ LNBITS_RESERVE_FEE_PERCENT=1.0
|
||||
LNBITS_SITE_TITLE="LNbits"
|
||||
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
|
||||
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
|
||||
# Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic
|
||||
LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salvador"
|
||||
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic
|
||||
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador"
|
||||
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
||||
|
||||
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
|
||||
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet
|
||||
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
|
||||
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
|
||||
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
|
||||
@ -73,7 +73,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY
|
||||
LND_REST_ENDPOINT=https://127.0.0.1:8080/
|
||||
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
|
||||
LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
|
||||
# To use an AES-encrypted macaroon, set
|
||||
# To use an AES-encrypted macaroon, set
|
||||
# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
|
||||
|
||||
# LNPayWallet
|
||||
@ -98,3 +98,8 @@ LNBITS_DENOMINATION=sats
|
||||
# EclairWallet
|
||||
ECLAIR_URL=http://127.0.0.1:8283
|
||||
ECLAIR_PASS=eclairpw
|
||||
|
||||
# LnTipsWallet
|
||||
# Enter /api in LightningTipBot to get your key
|
||||
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
|
||||
LNTIPS_API_ENDPOINT=https://ln.tips
|
||||
|
@ -79,3 +79,8 @@ For the invoice to work you must have a publicly accessible URL in your LNbits.
|
||||
- `LNBITS_BACKEND_WALLET_CLASS`: **OpenNodeWallet**
|
||||
- `OPENNODE_API_ENDPOINT`: https://api.opennode.com/
|
||||
- `OPENNODE_KEY`: opennodeAdminApiKey
|
||||
|
||||
|
||||
### Cliche Wallet
|
||||
|
||||
- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000
|
||||
|
@ -333,7 +333,7 @@ async def delete_expired_invoices(
|
||||
"""
|
||||
)
|
||||
logger.debug(f"Checking expiry of {len(rows)} invoices")
|
||||
for (payment_request,) in rows:
|
||||
for i, (payment_request,) in enumerate(rows):
|
||||
try:
|
||||
invoice = bolt11.decode(payment_request)
|
||||
except:
|
||||
@ -343,7 +343,7 @@ async def delete_expired_invoices(
|
||||
if expiration_date > datetime.datetime.utcnow():
|
||||
continue
|
||||
logger.debug(
|
||||
f"Deleting expired invoice: {invoice.payment_hash} (expired: {expiration_date})"
|
||||
f"Deleting expired invoice {i}/{len(rows)}: {invoice.payment_hash} (expired: {expiration_date})"
|
||||
)
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
|
@ -361,6 +361,35 @@ new Vue({
|
||||
this.receive.status = 'pending'
|
||||
})
|
||||
},
|
||||
onInitQR: async function (promise) {
|
||||
try {
|
||||
await promise
|
||||
} catch (error) {
|
||||
let mapping = {
|
||||
NotAllowedError: 'ERROR: you need to grant camera access permission',
|
||||
NotFoundError: 'ERROR: no camera on this device',
|
||||
NotSupportedError:
|
||||
'ERROR: secure context required (HTTPS, localhost)',
|
||||
NotReadableError: 'ERROR: is the camera already in use?',
|
||||
OverconstrainedError: 'ERROR: installed cameras are not suitable',
|
||||
StreamApiNotSupportedError:
|
||||
'ERROR: Stream API is not supported in this browser',
|
||||
InsecureContextError:
|
||||
'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
|
||||
}
|
||||
let valid_error = Object.keys(mapping).filter(key => {
|
||||
return error.name === key
|
||||
})
|
||||
let camera_error = valid_error
|
||||
? mapping[valid_error]
|
||||
: `ERROR: Camera error (${error.name})`
|
||||
this.parse.camera.show = false
|
||||
this.$q.notify({
|
||||
message: camera_error,
|
||||
type: 'negative'
|
||||
})
|
||||
}
|
||||
},
|
||||
decodeQR: function (res) {
|
||||
this.parse.data.request = res
|
||||
this.decodeRequest()
|
||||
|
@ -649,6 +649,7 @@
|
||||
<q-responsive :ratio="1">
|
||||
<qrcode-stream
|
||||
@decode="decodeQR"
|
||||
@init="onInitQR"
|
||||
class="rounded-borders"
|
||||
></qrcode-stream>
|
||||
</q-responsive>
|
||||
@ -667,6 +668,7 @@
|
||||
<div class="text-center q-mb-lg">
|
||||
<qrcode-stream
|
||||
@decode="decodeQR"
|
||||
@init="onInitQR"
|
||||
class="rounded-borders"
|
||||
></qrcode-stream>
|
||||
</div>
|
||||
|
@ -476,7 +476,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
|
||||
except:
|
||||
# parse internet identifier (user@domain.com)
|
||||
name_domain = code.split("@")
|
||||
if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2:
|
||||
if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2:
|
||||
name, domain = name_domain
|
||||
url = (
|
||||
("http://" if domain.endswith(".onion") else "https://")
|
||||
|
@ -52,6 +52,12 @@ class Compat:
|
||||
return ""
|
||||
return "<nothing>"
|
||||
|
||||
@property
|
||||
def big_int(self) -> str:
|
||||
if self.type in {POSTGRES}:
|
||||
return "BIGINT"
|
||||
return "INT"
|
||||
|
||||
|
||||
class Connection(Compat):
|
||||
def __init__(self, conn: AsyncConnection, txn, typ, name, schema):
|
||||
|
@ -29,7 +29,7 @@ async def m001_initial(db):
|
||||
)
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
f"""
|
||||
CREATE TABLE boltcards.hits (
|
||||
id TEXT PRIMARY KEY UNIQUE,
|
||||
card_id TEXT NOT NULL,
|
||||
@ -38,7 +38,7 @@ async def m001_initial(db):
|
||||
useragent TEXT,
|
||||
old_ctr INT NOT NULL DEFAULT 0,
|
||||
new_ctr INT NOT NULL DEFAULT 0,
|
||||
amount INT NOT NULL,
|
||||
amount {db.big_int} NOT NULL,
|
||||
time TIMESTAMP NOT NULL DEFAULT """
|
||||
+ db.timestamp_now
|
||||
+ """
|
||||
@ -47,11 +47,11 @@ async def m001_initial(db):
|
||||
)
|
||||
|
||||
await db.execute(
|
||||
"""
|
||||
f"""
|
||||
CREATE TABLE boltcards.refunds (
|
||||
id TEXT PRIMARY KEY UNIQUE,
|
||||
hit_id TEXT NOT NULL,
|
||||
refund_amount INT NOT NULL,
|
||||
refund_amount {db.big_int} NOT NULL,
|
||||
time TIMESTAMP NOT NULL DEFAULT """
|
||||
+ db.timestamp_now
|
||||
+ """
|
||||
|
@ -1,16 +1,16 @@
|
||||
async def m001_initial(db):
|
||||
await db.execute(
|
||||
"""
|
||||
f"""
|
||||
CREATE TABLE boltz.submarineswap (
|
||||
id TEXT PRIMARY KEY,
|
||||
wallet TEXT NOT NULL,
|
||||
payment_hash TEXT NOT NULL,
|
||||
amount INT NOT NULL,
|
||||
amount {db.big_int} NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
boltz_id TEXT NOT NULL,
|
||||
refund_address TEXT NOT NULL,
|
||||
refund_privkey TEXT NOT NULL,
|
||||
expected_amount INT NOT NULL,
|
||||
expected_amount {db.big_int} NOT NULL,
|
||||
timeout_block_height INT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
bip21 TEXT NOT NULL,
|
||||
@ -22,12 +22,12 @@ async def m001_initial(db):
|
||||
"""
|
||||
)
|
||||
await db.execute(
|
||||
"""
|
||||
f"""
|
||||
CREATE TABLE boltz.reverse_submarineswap (
|
||||
id TEXT PRIMARY KEY,
|
||||
wallet TEXT NOT NULL,
|
||||
onchain_address TEXT NOT NULL,
|
||||
amount INT NOT NULL,
|
||||
amount {db.big_int} NOT NULL,
|
||||
instant_settlement BOOLEAN NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
boltz_id TEXT NOT NULL,
|
||||
@ -37,7 +37,7 @@ async def m001_initial(db):
|
||||
claim_privkey TEXT NOT NULL,
|
||||
lockup_address TEXT NOT NULL,
|
||||
invoice TEXT NOT NULL,
|
||||
onchain_amount INT NOT NULL,
|
||||
onchain_amount {db.big_int} NOT NULL,
|
||||
time TIMESTAMP NOT NULL DEFAULT """
|
||||
+ db.timestamp_now
|
||||
+ """
|
||||
|
@ -57,7 +57,7 @@ async def check_for_pending_swaps():
|
||||
swap_status = get_swap_status(swap)
|
||||
# should only happen while development when regtest is reset
|
||||
if swap_status.exists is False:
|
||||
logger.warning(f"Boltz - swap: {swap.boltz_id} does not exist.")
|
||||
logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.")
|
||||
await update_swap_status(swap.id, "failed")
|
||||
continue
|
||||
|
||||
@ -73,7 +73,7 @@ async def check_for_pending_swaps():
|
||||
else:
|
||||
if swap_status.hit_timeout:
|
||||
if not swap_status.has_lockup:
|
||||
logger.warning(
|
||||
logger.debug(
|
||||
f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..."
|
||||
)
|
||||
await update_swap_status(swap.id, "timeout")
|
||||
|
@ -10,7 +10,7 @@ from .models import Copilots, CreateCopilotData
|
||||
|
||||
async def create_copilot(
|
||||
data: CreateCopilotData, inkey: Optional[str] = ""
|
||||
) -> Copilots:
|
||||
) -> Optional[Copilots]:
|
||||
copilot_id = urlsafe_short_hash()
|
||||
await db.execute(
|
||||
"""
|
||||
@ -67,19 +67,19 @@ async def create_copilot(
|
||||
|
||||
|
||||
async def update_copilot(
|
||||
data: CreateCopilotData, copilot_id: Optional[str] = ""
|
||||
data: CreateCopilotData, copilot_id: str
|
||||
) -> Optional[Copilots]:
|
||||
q = ", ".join([f"{field[0]} = ?" for field in data])
|
||||
items = [f"{field[1]}" for field in data]
|
||||
items.append(copilot_id)
|
||||
await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items))
|
||||
await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items,))
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
|
||||
)
|
||||
return Copilots(**row) if row else None
|
||||
|
||||
|
||||
async def get_copilot(copilot_id: str) -> Copilots:
|
||||
async def get_copilot(copilot_id: str) -> Optional[Copilots]:
|
||||
row = await db.fetchone(
|
||||
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
|
||||
)
|
||||
|
@ -26,7 +26,7 @@ async def wait_for_paid_invoices():
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
webhook = None
|
||||
data = None
|
||||
if payment.extra.get("tag") != "copilot":
|
||||
if not payment.extra or payment.extra.get("tag") != "copilot":
|
||||
# not an copilot invoice
|
||||
return
|
||||
|
||||
@ -71,12 +71,12 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||
|
||||
|
||||
async def mark_webhook_sent(payment: Payment, status: int) -> None:
|
||||
payment.extra["wh_status"] = status
|
||||
|
||||
await core_db.execute(
|
||||
"""
|
||||
UPDATE apipayments SET extra = ?
|
||||
WHERE hash = ?
|
||||
""",
|
||||
(json.dumps(payment.extra), payment.payment_hash),
|
||||
)
|
||||
if payment.extra:
|
||||
payment.extra["wh_status"] = status
|
||||
await core_db.execute(
|
||||
"""
|
||||
UPDATE apipayments SET extra = ?
|
||||
WHERE hash = ?
|
||||
""",
|
||||
(json.dumps(payment.extra), payment.payment_hash),
|
||||
)
|
||||
|
@ -15,7 +15,9 @@ templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@copilot_ext.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
async def index(
|
||||
request: Request, user: User = Depends(check_user_exists) # type: ignore
|
||||
):
|
||||
return copilot_renderer().TemplateResponse(
|
||||
"copilot/index.html", {"request": request, "user": user.dict()}
|
||||
)
|
||||
@ -44,7 +46,7 @@ class ConnectionManager:
|
||||
|
||||
async def connect(self, websocket: WebSocket, copilot_id: str):
|
||||
await websocket.accept()
|
||||
websocket.id = copilot_id
|
||||
websocket.id = copilot_id # type: ignore
|
||||
self.active_connections.append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket):
|
||||
@ -52,7 +54,7 @@ class ConnectionManager:
|
||||
|
||||
async def send_personal_message(self, message: str, copilot_id: str):
|
||||
for connection in self.active_connections:
|
||||
if connection.id == copilot_id:
|
||||
if connection.id == copilot_id: # type: ignore
|
||||
await connection.send_text(message)
|
||||
|
||||
async def broadcast(self, message: str):
|
||||
|
@ -23,7 +23,7 @@ from .views import updater
|
||||
|
||||
@copilot_ext.get("/api/v1/copilot")
|
||||
async def api_copilots_retrieve(
|
||||
req: Request, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
req: Request, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
wallet_user = wallet.wallet.user
|
||||
copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)]
|
||||
@ -37,7 +37,7 @@ async def api_copilots_retrieve(
|
||||
async def api_copilot_retrieve(
|
||||
req: Request,
|
||||
copilot_id: str = Query(None),
|
||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
|
||||
):
|
||||
copilot = await get_copilot(copilot_id)
|
||||
if not copilot:
|
||||
@ -54,7 +54,7 @@ async def api_copilot_retrieve(
|
||||
async def api_copilot_create_or_update(
|
||||
data: CreateCopilotData,
|
||||
copilot_id: str = Query(None),
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
|
||||
):
|
||||
data.user = wallet.wallet.user
|
||||
data.wallet = wallet.wallet.id
|
||||
@ -67,7 +67,8 @@ async def api_copilot_create_or_update(
|
||||
|
||||
@copilot_ext.delete("/api/v1/copilot/{copilot_id}")
|
||||
async def api_copilot_delete(
|
||||
copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
copilot_id: str = Query(None),
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
|
||||
):
|
||||
copilot = await get_copilot(copilot_id)
|
||||
|
||||
|
@ -98,21 +98,21 @@ async def get_discordbot_wallet(wallet_id: str) -> Optional[Wallets]:
|
||||
return Wallets(**row) if row else None
|
||||
|
||||
|
||||
async def get_discordbot_wallets(admin_id: str) -> Optional[Wallets]:
|
||||
async def get_discordbot_wallets(admin_id: str) -> List[Wallets]:
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM discordbot.wallets WHERE admin = ?", (admin_id,)
|
||||
)
|
||||
return [Wallets(**row) for row in rows]
|
||||
|
||||
|
||||
async def get_discordbot_users_wallets(user_id: str) -> Optional[Wallets]:
|
||||
async def get_discordbot_users_wallets(user_id: str) -> List[Wallets]:
|
||||
rows = await db.fetchall(
|
||||
"""SELECT * FROM discordbot.wallets WHERE "user" = ?""", (user_id,)
|
||||
)
|
||||
return [Wallets(**row) for row in rows]
|
||||
|
||||
|
||||
async def get_discordbot_wallet_transactions(wallet_id: str) -> Optional[Payment]:
|
||||
async def get_discordbot_wallet_transactions(wallet_id: str) -> List[Payment]:
|
||||
return await get_payments(
|
||||
wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True
|
||||
)
|
||||
|
@ -9,7 +9,9 @@ from . import discordbot_ext, discordbot_renderer
|
||||
|
||||
|
||||
@discordbot_ext.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
async def index(
|
||||
request: Request, user: User = Depends(check_user_exists) # type: ignore
|
||||
):
|
||||
return discordbot_renderer().TemplateResponse(
|
||||
"discordbot/index.html", {"request": request, "user": user.dict()}
|
||||
)
|
||||
|
@ -27,32 +27,37 @@ from .models import CreateUserData, CreateUserWallet
|
||||
|
||||
|
||||
@discordbot_ext.get("/api/v1/users", status_code=HTTPStatus.OK)
|
||||
async def api_discordbot_users(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_discordbot_users(
|
||||
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
|
||||
):
|
||||
user_id = wallet.wallet.user
|
||||
return [user.dict() for user in await get_discordbot_users(user_id)]
|
||||
|
||||
|
||||
@discordbot_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK)
|
||||
async def api_discordbot_user(user_id, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_discordbot_user(
|
||||
user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
user = await get_discordbot_user(user_id)
|
||||
return user.dict()
|
||||
if user:
|
||||
return user.dict()
|
||||
|
||||
|
||||
@discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED)
|
||||
async def api_discordbot_users_create(
|
||||
data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
user = await create_discordbot_user(data)
|
||||
full = user.dict()
|
||||
full["wallets"] = [
|
||||
wallet.dict() for wallet in await get_discordbot_users_wallets(user.id)
|
||||
]
|
||||
wallets = await get_discordbot_users_wallets(user.id)
|
||||
if wallets:
|
||||
full["wallets"] = [wallet for wallet in wallets]
|
||||
return full
|
||||
|
||||
|
||||
@discordbot_ext.delete("/api/v1/users/{user_id}")
|
||||
async def api_discordbot_users_delete(
|
||||
user_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
user = await get_discordbot_user(user_id)
|
||||
if not user:
|
||||
@ -75,7 +80,7 @@ async def api_discordbot_activate_extension(
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
|
||||
)
|
||||
update_user_extension(user_id=userid, extension=extension, active=active)
|
||||
await update_user_extension(user_id=userid, extension=extension, active=active)
|
||||
return {"extension": "updated"}
|
||||
|
||||
|
||||
@ -84,7 +89,7 @@ async def api_discordbot_activate_extension(
|
||||
|
||||
@discordbot_ext.post("/api/v1/wallets")
|
||||
async def api_discordbot_wallets_create(
|
||||
data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
user = await create_discordbot_wallet(
|
||||
user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id
|
||||
@ -93,28 +98,30 @@ async def api_discordbot_wallets_create(
|
||||
|
||||
|
||||
@discordbot_ext.get("/api/v1/wallets")
|
||||
async def api_discordbot_wallets(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_discordbot_wallets(
|
||||
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
|
||||
):
|
||||
admin_id = wallet.wallet.user
|
||||
return [wallet.dict() for wallet in await get_discordbot_wallets(admin_id)]
|
||||
return await get_discordbot_wallets(admin_id)
|
||||
|
||||
|
||||
@discordbot_ext.get("/api/v1/transactions/{wallet_id}")
|
||||
async def api_discordbot_wallet_transactions(
|
||||
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
return await get_discordbot_wallet_transactions(wallet_id)
|
||||
|
||||
|
||||
@discordbot_ext.get("/api/v1/wallets/{user_id}")
|
||||
async def api_discordbot_users_wallets(
|
||||
user_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
return [s_wallet.dict() for s_wallet in await get_discordbot_users_wallets(user_id)]
|
||||
return await get_discordbot_users_wallets(user_id)
|
||||
|
||||
|
||||
@discordbot_ext.delete("/api/v1/wallets/{wallet_id}")
|
||||
async def api_discordbot_wallets_delete(
|
||||
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
get_wallet = await get_discordbot_wallet(wallet_id)
|
||||
if not get_wallet:
|
||||
|
@ -12,7 +12,10 @@ templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@example_ext.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
async def index(
|
||||
request: Request,
|
||||
user: User = Depends(check_user_exists), # type: ignore
|
||||
):
|
||||
return example_renderer().TemplateResponse(
|
||||
"example/index.html", {"request": request, "user": user.dict()}
|
||||
)
|
||||
|
@ -45,7 +45,7 @@ async def m001_initial_invoices(db):
|
||||
id TEXT PRIMARY KEY,
|
||||
invoice_id TEXT NOT NULL,
|
||||
|
||||
amount INT NOT NULL,
|
||||
amount {db.big_int} NOT NULL,
|
||||
|
||||
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
|
||||
|
@ -4,10 +4,10 @@ import json
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core import db as core_db
|
||||
from lnbits.core.crud import create_payment
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.helpers import get_current_extension_name, urlsafe_short_hash
|
||||
from lnbits.tasks import internal_invoice_listener, register_invoice_listener
|
||||
from lnbits.core.services import create_invoice, pay_invoice
|
||||
from lnbits.helpers import get_current_extension_name
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
|
||||
from .crud import get_livestream_by_track, get_producer, get_track
|
||||
|
||||
@ -44,44 +44,20 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||
# now we make a special kind of internal transfer
|
||||
amount = int(payment.amount * (100 - ls.fee_pct) / 100)
|
||||
|
||||
# mark the original payment with two extra keys, "shared_with" and "received"
|
||||
# (this prevents us from doing this process again and it's informative)
|
||||
# and reduce it by the amount we're going to send to the producer
|
||||
await core_db.execute(
|
||||
"""
|
||||
UPDATE apipayments
|
||||
SET extra = ?, amount = ?
|
||||
WHERE hash = ?
|
||||
AND checking_id NOT LIKE 'internal_%'
|
||||
""",
|
||||
(
|
||||
json.dumps(
|
||||
dict(
|
||||
**payment.extra,
|
||||
shared_with=[producer.name, producer.id],
|
||||
received=payment.amount,
|
||||
)
|
||||
),
|
||||
payment.amount - amount,
|
||||
payment.payment_hash,
|
||||
),
|
||||
)
|
||||
|
||||
# perform an internal transfer using the same payment_hash to the producer wallet
|
||||
internal_checking_id = f"internal_{urlsafe_short_hash()}"
|
||||
await create_payment(
|
||||
wallet_id=producer.wallet,
|
||||
checking_id=internal_checking_id,
|
||||
payment_request="",
|
||||
payment_hash=payment.payment_hash,
|
||||
amount=amount,
|
||||
payment_hash, payment_request = await create_invoice(
|
||||
wallet_id=tpos.tip_wallet,
|
||||
amount=amount, # sats
|
||||
internal=True,
|
||||
memo=f"Revenue from '{track.name}'.",
|
||||
pending=False,
|
||||
)
|
||||
logger.debug(f"livestream: producer invoice created: {payment_hash}")
|
||||
|
||||
# manually send this for now
|
||||
# await internal_invoice_paid.send(internal_checking_id)
|
||||
await internal_invoice_listener.put(internal_checking_id)
|
||||
checking_id = await pay_invoice(
|
||||
payment_request=payment_request,
|
||||
wallet_id=payment.wallet_id,
|
||||
extra={"tag": "livestream"},
|
||||
)
|
||||
logger.debug(f"livestream: producer invoice paid: {checking_id}")
|
||||
|
||||
# so the flow is the following:
|
||||
# - we receive, say, 1000 satoshis
|
||||
|
@ -1,7 +1,10 @@
|
||||
import asyncio
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from lnbits.db import Database
|
||||
from lnbits.helpers import template_renderer
|
||||
from lnbits.tasks import catch_everything_and_restart
|
||||
|
||||
db = Database("ext_lnurldevice")
|
||||
|
||||
@ -13,5 +16,11 @@ def lnurldevice_renderer():
|
||||
|
||||
|
||||
from .lnurl import * # noqa
|
||||
from .tasks import wait_for_paid_invoices
|
||||
from .views import * # noqa
|
||||
from .views_api import * # noqa
|
||||
|
||||
|
||||
def lnurldevice_start():
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
||||
|
@ -22,9 +22,10 @@ async def create_lnurldevice(
|
||||
wallet,
|
||||
currency,
|
||||
device,
|
||||
profit
|
||||
profit,
|
||||
amount
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
lnurldevice_id,
|
||||
@ -34,6 +35,7 @@ async def create_lnurldevice(
|
||||
data.currency,
|
||||
data.device,
|
||||
data.profit,
|
||||
data.amount,
|
||||
),
|
||||
)
|
||||
return await get_lnurldevice(lnurldevice_id)
|
||||
|
@ -102,7 +102,32 @@ async def lnurl_v1_params(
|
||||
if device.device == "atm":
|
||||
if paymentcheck:
|
||||
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
||||
if device.device == "switch":
|
||||
|
||||
price_msat = (
|
||||
await fiat_amount_as_satoshis(float(device.profit), device.currency)
|
||||
if device.currency != "sat"
|
||||
else amount_in_cent
|
||||
) * 1000
|
||||
|
||||
lnurldevicepayment = await create_lnurldevicepayment(
|
||||
deviceid=device.id,
|
||||
payload="bla",
|
||||
sats=price_msat,
|
||||
pin=1,
|
||||
payhash="bla",
|
||||
)
|
||||
if not lnurldevicepayment:
|
||||
return {"status": "ERROR", "reason": "Could not create payment."}
|
||||
return {
|
||||
"tag": "payRequest",
|
||||
"callback": request.url_for(
|
||||
"lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id
|
||||
),
|
||||
"minSendable": price_msat,
|
||||
"maxSendable": price_msat,
|
||||
"metadata": await device.lnurlpay_metadata(),
|
||||
}
|
||||
if len(p) % 4 > 0:
|
||||
p += "=" * (4 - (len(p) % 4))
|
||||
|
||||
@ -184,22 +209,42 @@ async def lnurl_callback(
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found."
|
||||
)
|
||||
if pr:
|
||||
if lnurldevicepayment.id != k1:
|
||||
return {"status": "ERROR", "reason": "Bad K1"}
|
||||
if lnurldevicepayment.payhash != "payment_hash":
|
||||
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
||||
if device.device == "atm":
|
||||
if not pr:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="No payment request"
|
||||
)
|
||||
else:
|
||||
if lnurldevicepayment.id != k1:
|
||||
return {"status": "ERROR", "reason": "Bad K1"}
|
||||
if lnurldevicepayment.payhash != "payment_hash":
|
||||
return {"status": "ERROR", "reason": f"Payment already claimed"}
|
||||
lnurldevicepayment = await update_lnurldevicepayment(
|
||||
lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload
|
||||
)
|
||||
|
||||
await pay_invoice(
|
||||
await pay_invoice(
|
||||
wallet_id=device.wallet,
|
||||
payment_request=pr,
|
||||
max_sat=lnurldevicepayment.sats / 1000,
|
||||
extra={"tag": "withdraw"},
|
||||
)
|
||||
return {"status": "OK"}
|
||||
if device.device == "switch":
|
||||
payment_hash, payment_request = await create_invoice(
|
||||
wallet_id=device.wallet,
|
||||
payment_request=pr,
|
||||
max_sat=lnurldevicepayment.sats / 1000,
|
||||
extra={"tag": "withdraw"},
|
||||
amount=lnurldevicepayment.sats / 1000,
|
||||
memo=device.title + "-" + lnurldevicepayment.id,
|
||||
unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
|
||||
extra={"tag": "Switch", "id": paymentid, "time": device.amount},
|
||||
)
|
||||
return {"status": "OK"}
|
||||
lnurldevicepayment = await update_lnurldevicepayment(
|
||||
lnurldevicepayment_id=paymentid, payhash=payment_hash
|
||||
)
|
||||
return {
|
||||
"pr": payment_request,
|
||||
"routes": [],
|
||||
}
|
||||
|
||||
payment_hash, payment_request = await create_invoice(
|
||||
wallet_id=device.wallet,
|
||||
@ -221,5 +266,3 @@ async def lnurl_callback(
|
||||
},
|
||||
"routes": [],
|
||||
}
|
||||
|
||||
return resp.dict()
|
||||
|
@ -29,7 +29,7 @@ async def m001_initial(db):
|
||||
payhash TEXT,
|
||||
payload TEXT NOT NULL,
|
||||
pin INT,
|
||||
sats INT,
|
||||
sats {db.big_int},
|
||||
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
|
||||
);
|
||||
"""
|
||||
@ -79,3 +79,12 @@ async def m002_redux(db):
|
||||
)
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
async def m003_redux(db):
|
||||
"""
|
||||
Add 'meta' for storing various metadata about the wallet
|
||||
"""
|
||||
await db.execute(
|
||||
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;"
|
||||
)
|
||||
|
@ -17,6 +17,7 @@ class createLnurldevice(BaseModel):
|
||||
currency: str
|
||||
device: str
|
||||
profit: float
|
||||
amount: int
|
||||
|
||||
|
||||
class lnurldevices(BaseModel):
|
||||
@ -27,15 +28,14 @@ class lnurldevices(BaseModel):
|
||||
currency: str
|
||||
device: str
|
||||
profit: float
|
||||
amount: int
|
||||
timestamp: str
|
||||
|
||||
def from_row(cls, row: Row) -> "lnurldevices":
|
||||
return cls(**dict(row))
|
||||
|
||||
def lnurl(self, req: Request) -> Lnurl:
|
||||
url = req.url_for(
|
||||
"lnurldevice.lnurl_response", device_id=self.id, _external=True
|
||||
)
|
||||
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||
return lnurl_encode(url)
|
||||
|
||||
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
||||
|
40
lnbits/extensions/lnurldevice/tasks.py
Normal file
40
lnbits/extensions/lnurldevice/tasks.py
Normal file
@ -0,0 +1,40 @@
|
||||
import asyncio
|
||||
import json
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
|
||||
from lnbits import bolt11
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.core.services import pay_invoice
|
||||
from lnbits.helpers import get_current_extension_name
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
|
||||
from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment
|
||||
from .views import updater
|
||||
|
||||
|
||||
async def wait_for_paid_invoices():
|
||||
invoice_queue = asyncio.Queue()
|
||||
register_invoice_listener(invoice_queue, get_current_extension_name())
|
||||
|
||||
while True:
|
||||
payment = await invoice_queue.get()
|
||||
await on_invoice_paid(payment)
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
# (avoid loops)
|
||||
if "Switch" == payment.extra.get("tag"):
|
||||
lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id"))
|
||||
if not lnurldevicepayment:
|
||||
return
|
||||
if lnurldevicepayment.payhash == "used":
|
||||
return
|
||||
lnurldevicepayment = await update_lnurldevicepayment(
|
||||
lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
|
||||
)
|
||||
return await updater(lnurldevicepayment.deviceid)
|
||||
return
|
@ -1,13 +1,24 @@
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<p>
|
||||
Register LNURLDevice devices to receive payments in your LNbits wallet.<br />
|
||||
Build your own here
|
||||
<a href="https://github.com/arcbtc/bitcoinpos"
|
||||
>https://github.com/arcbtc/bitcoinpos</a
|
||||
For LNURL based Points of Sale, ATMs, and relay devices<br />
|
||||
Use with: <br />
|
||||
LNPoS
|
||||
<a href="https://lnbits.github.io/lnpos">
|
||||
https://lnbits.github.io/lnpos</a
|
||||
><br />
|
||||
bitcoinSwitch
|
||||
<a href="https://github.com/lnbits/bitcoinSwitch">
|
||||
https://github.com/lnbits/bitcoinSwitch</a
|
||||
><br />
|
||||
FOSSA
|
||||
<a href="https://github.com/lnbits/fossa">
|
||||
https://github.com/lnbits/fossa</a
|
||||
><br />
|
||||
<small>
|
||||
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
||||
Created by, <a href="https://github.com/benarc">Ben Arc</a>,
|
||||
<a href="https://github.com/blackcoffeexbt">BC</a>,
|
||||
<a href="https://github.com/motorina0">Vlad Stan</a></small
|
||||
>
|
||||
</p>
|
||||
</q-card-section>
|
||||
|
@ -51,6 +51,7 @@
|
||||
<q-tr :props="props">
|
||||
<q-th style="width: 5%"></q-th>
|
||||
<q-th style="width: 5%"></q-th>
|
||||
<q-th style="width: 5%"></q-th>
|
||||
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
@ -91,6 +92,22 @@
|
||||
<q-tooltip> LNURLDevice Settings </q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-btn
|
||||
v-if="props.row.device == 'switch'"
|
||||
:disable="protocol == 'http:'"
|
||||
flat
|
||||
unelevated
|
||||
dense
|
||||
size="xs"
|
||||
icon="visibility"
|
||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||
@click="openQrCodeDialog(props.row.id)"
|
||||
><q-tooltip v-if="protocol == 'http:'">
|
||||
LNURLs only work over HTTPS </q-tooltip
|
||||
><q-tooltip v-else> view LNURL </q-tooltip></q-btn
|
||||
>
|
||||
</q-td>
|
||||
<q-td
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
@ -132,20 +149,33 @@
|
||||
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
||||
>
|
||||
<div class="text-h6">LNURLDevice device string</div>
|
||||
<q-btn
|
||||
dense
|
||||
outline
|
||||
unelevated
|
||||
color="primary"
|
||||
size="md"
|
||||
@click="copyText(location + '/lnurldevice/api/v1/lnurl/' + settingsDialog.data.id + ',' +
|
||||
<center>
|
||||
<q-btn
|
||||
v-if="settingsDialog.data.device == 'switch'"
|
||||
dense
|
||||
outline
|
||||
unelevated
|
||||
color="primary"
|
||||
size="md"
|
||||
@click="copyText(wslocation + '/lnurldevice/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')"
|
||||
>{% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{%
|
||||
endraw %}<q-tooltip> Click to copy URL </q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
v-else
|
||||
dense
|
||||
outline
|
||||
unelevated
|
||||
color="primary"
|
||||
size="md"
|
||||
@click="copyText(location + '/lnurldevice/api/v1/lnurl/' + settingsDialog.data.id + ',' +
|
||||
settingsDialog.data.key + ',' + settingsDialog.data.currency, 'Link copied to clipboard!')"
|
||||
>{% raw
|
||||
%}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
|
||||
{{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
|
||||
%}<q-tooltip> Click to copy URL </q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
>{% raw
|
||||
%}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
|
||||
{{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
|
||||
%}<q-tooltip> Click to copy URL </q-tooltip>
|
||||
</q-btn>
|
||||
</center>
|
||||
<div class="text-subtitle2">
|
||||
<small> </small>
|
||||
</div>
|
||||
@ -191,6 +221,7 @@
|
||||
label="Type of device"
|
||||
></q-option-group>
|
||||
<q-input
|
||||
v-if="formDialoglnurldevice.data.device != 'switch'"
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialoglnurldevice.data.profit"
|
||||
@ -198,6 +229,29 @@
|
||||
max="90"
|
||||
label="Profit margin (% added to invoices/deducted from faucets)"
|
||||
></q-input>
|
||||
<div v-else>
|
||||
<q-input
|
||||
ref="setAmount"
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialoglnurldevice.data.profit"
|
||||
class="q-pb-md"
|
||||
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||
:mask="'#.##'"
|
||||
fill-mask="0"
|
||||
reverse-fill-mask
|
||||
:step="'0.01'"
|
||||
value="0.00"
|
||||
></q-input>
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialoglnurldevice.data.amount"
|
||||
type="number"
|
||||
value="1000"
|
||||
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||
></q-input>
|
||||
</div>
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn
|
||||
@ -225,6 +279,33 @@
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="qrCodeDialog.show" position="top">
|
||||
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||
<qrcode
|
||||
:value="qrCodeDialog.data.url + '/?lightning=' + qrCodeDialog.data.lnurl"
|
||||
:options="{width: 800}"
|
||||
class="rounded-borders"
|
||||
></qrcode>
|
||||
{% raw %}
|
||||
</q-responsive>
|
||||
<p style="word-break: break-all">
|
||||
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
|
||||
</p>
|
||||
{% endraw %}
|
||||
<div class="row q-mt-lg q-gutter-sm">
|
||||
<q-btn
|
||||
outline
|
||||
color="grey"
|
||||
@click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')"
|
||||
class="q-ml-sm"
|
||||
>Copy LNURL</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
|
||||
@ -252,7 +333,9 @@
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
protocol: window.location.protocol,
|
||||
location: window.location.hostname,
|
||||
wslocation: window.location.hostname,
|
||||
filter: '',
|
||||
currency: 'USD',
|
||||
lnurldeviceLinks: [],
|
||||
@ -265,6 +348,10 @@
|
||||
{
|
||||
label: 'ATM',
|
||||
value: 'atm'
|
||||
},
|
||||
{
|
||||
label: 'Switch',
|
||||
value: 'switch'
|
||||
}
|
||||
],
|
||||
lnurldevicesTable: {
|
||||
@ -333,7 +420,8 @@
|
||||
show_ack: false,
|
||||
show_price: 'None',
|
||||
device: 'pos',
|
||||
profit: 2,
|
||||
profit: 0,
|
||||
amount: 1,
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
@ -344,6 +432,16 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openQrCodeDialog: function (lnurldevice_id) {
|
||||
var lnurldevice = _.findWhere(this.lnurldeviceLinks, {
|
||||
id: lnurldevice_id
|
||||
})
|
||||
console.log(lnurldevice)
|
||||
this.qrCodeDialog.data = _.clone(lnurldevice)
|
||||
this.qrCodeDialog.data.url =
|
||||
window.location.protocol + '//' + window.location.host
|
||||
this.qrCodeDialog.show = true
|
||||
},
|
||||
cancellnurldevice: function (data) {
|
||||
var self = this
|
||||
self.formDialoglnurldevice.show = false
|
||||
@ -400,6 +498,7 @@
|
||||
.then(function (response) {
|
||||
if (response.data) {
|
||||
self.lnurldeviceLinks = response.data.map(maplnurldevice)
|
||||
console.log(response.data)
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
@ -519,6 +618,7 @@
|
||||
'//',
|
||||
window.location.host
|
||||
].join('')
|
||||
self.wslocation = ['ws://', window.location.host].join('')
|
||||
LNbits.api
|
||||
.request('GET', '/api/v1/currencies')
|
||||
.then(response => {
|
||||
|
@ -1,11 +1,13 @@
|
||||
from http import HTTPStatus
|
||||
from io import BytesIO
|
||||
|
||||
from fastapi import Request
|
||||
import pyqrcode
|
||||
from fastapi import Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi.param_functions import Query
|
||||
from fastapi.params import Depends
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.responses import HTMLResponse
|
||||
from starlette.responses import HTMLResponse, StreamingResponse
|
||||
|
||||
from lnbits.core.crud import update_payment_status
|
||||
from lnbits.core.models import User
|
||||
@ -51,3 +53,58 @@ async def displaypin(request: Request, paymentid: str = Query(None)):
|
||||
"lnurldevice/error.html",
|
||||
{"request": request, "pin": "filler", "not_paid": True},
|
||||
)
|
||||
|
||||
|
||||
@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse)
|
||||
async def img(request: Request, lnurldevice_id):
|
||||
lnurldevice = await get_lnurldevice(lnurldevice_id)
|
||||
if not lnurldevice:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist."
|
||||
)
|
||||
return lnurldevice.lnurl(request)
|
||||
|
||||
|
||||
##################WEBSOCKET ROUTES########################
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: List[WebSocket] = []
|
||||
|
||||
async def connect(self, websocket: WebSocket, lnurldevice_id: str):
|
||||
await websocket.accept()
|
||||
websocket.id = lnurldevice_id
|
||||
self.active_connections.append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket):
|
||||
self.active_connections.remove(websocket)
|
||||
|
||||
async def send_personal_message(self, message: str, lnurldevice_id: str):
|
||||
for connection in self.active_connections:
|
||||
if connection.id == lnurldevice_id:
|
||||
await connection.send_text(message)
|
||||
|
||||
async def broadcast(self, message: str):
|
||||
for connection in self.active_connections:
|
||||
await connection.send_text(message)
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
@lnurldevice_ext.websocket("/ws/{lnurldevice_id}", name="lnurldevice.lnurldevice_by_id")
|
||||
async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str):
|
||||
await manager.connect(websocket, lnurldevice_id)
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket)
|
||||
|
||||
|
||||
async def updater(lnurldevice_id):
|
||||
lnurldevice = await get_lnurldevice(lnurldevice_id)
|
||||
if not lnurldevice:
|
||||
return
|
||||
await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
|
||||
|
@ -32,32 +32,42 @@ async def api_list_currencies_available():
|
||||
@lnurldevice_ext.post("/api/v1/lnurlpos")
|
||||
@lnurldevice_ext.put("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||
async def api_lnurldevice_create_or_update(
|
||||
req: Request,
|
||||
data: createLnurldevice,
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
lnurldevice_id: str = Query(None),
|
||||
):
|
||||
if not lnurldevice_id:
|
||||
lnurldevice = await create_lnurldevice(data)
|
||||
return lnurldevice.dict()
|
||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||
else:
|
||||
lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id)
|
||||
return lnurldevice.dict()
|
||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||
|
||||
|
||||
@lnurldevice_ext.get("/api/v1/lnurlpos")
|
||||
async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_lnurldevices_retrieve(
|
||||
req: Request, wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
):
|
||||
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
||||
try:
|
||||
return [
|
||||
{**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||
{**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||
for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||
]
|
||||
except:
|
||||
return ""
|
||||
try:
|
||||
return [
|
||||
{**lnurldevice.dict()}
|
||||
for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||
]
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
@lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||
async def api_lnurldevice_retrieve(
|
||||
request: Request,
|
||||
req: Request,
|
||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||
lnurldevice_id: str = Query(None),
|
||||
):
|
||||
@ -68,7 +78,7 @@ async def api_lnurldevice_retrieve(
|
||||
)
|
||||
if not lnurldevice.lnurl_toggle:
|
||||
return {**lnurldevice.dict()}
|
||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(request=request)}}
|
||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
||||
|
||||
|
||||
@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||
|
@ -3,14 +3,14 @@ async def m001_initial(db):
|
||||
Initial lnurlpayouts table.
|
||||
"""
|
||||
await db.execute(
|
||||
"""
|
||||
f"""
|
||||
CREATE TABLE lnurlpayout.lnurlpayouts (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
wallet TEXT NOT NULL,
|
||||
admin_key TEXT NOT NULL,
|
||||
lnurlpay TEXT NOT NULL,
|
||||
threshold INT NOT NULL
|
||||
threshold {db.big_int} NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
@ -102,7 +102,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
|
||||
charge = await get_charge(charge_id)
|
||||
if not charge.paid:
|
||||
if charge.onchainaddress:
|
||||
config = await get_config(charge.user)
|
||||
config = await get_charge_config(charge_id)
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(
|
||||
@ -122,3 +122,10 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
|
||||
return await update_charge(charge_id=charge_id, balance=charge.amount)
|
||||
row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
|
||||
return Charges.from_row(row) if row else None
|
||||
|
||||
|
||||
async def get_charge_config(charge_id: str):
|
||||
row = await db.fetchone(
|
||||
"""SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,)
|
||||
)
|
||||
return await get_config(row.user)
|
||||
|
17
lnbits/extensions/satspay/helpers.py
Normal file
17
lnbits/extensions/satspay/helpers.py
Normal file
@ -0,0 +1,17 @@
|
||||
from .models import Charges
|
||||
|
||||
|
||||
def compact_charge(charge: Charges):
|
||||
return {
|
||||
"id": charge.id,
|
||||
"description": charge.description,
|
||||
"onchainaddress": charge.onchainaddress,
|
||||
"payment_request": charge.payment_request,
|
||||
"payment_hash": charge.payment_hash,
|
||||
"time": charge.time,
|
||||
"amount": charge.amount,
|
||||
"balance": charge.balance,
|
||||
"paid": charge.paid,
|
||||
"timestamp": charge.timestamp,
|
||||
"completelink": charge.completelink, # should be secret?
|
||||
}
|
@ -19,7 +19,6 @@ class CreateCharge(BaseModel):
|
||||
|
||||
class Charges(BaseModel):
|
||||
id: str
|
||||
user: str
|
||||
description: Optional[str]
|
||||
onchainwallet: Optional[str]
|
||||
onchainaddress: Optional[str]
|
||||
|
@ -328,7 +328,7 @@
|
||||
)
|
||||
},
|
||||
checkBalances: async function () {
|
||||
if (!this.charge.hasStaleBalance) await this.refreshCharge()
|
||||
if (this.charge.hasStaleBalance) return
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
@ -339,18 +339,9 @@
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
refreshCharge: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
`/satspay/api/v1/charge/${this.charge.id}`
|
||||
)
|
||||
this.charge = mapCharge(data, this.charge)
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
checkPendingOnchain: async function () {
|
||||
if (!this.charge.onchainaddress) return
|
||||
|
||||
const {
|
||||
bitcoin: {addresses: addressesAPI}
|
||||
} = mempoolJS({
|
||||
|
@ -9,10 +9,9 @@ from starlette.responses import HTMLResponse
|
||||
from lnbits.core.crud import get_wallet
|
||||
from lnbits.core.models import User
|
||||
from lnbits.decorators import check_user_exists
|
||||
from lnbits.extensions.watchonly.crud import get_config
|
||||
|
||||
from . import satspay_ext, satspay_renderer
|
||||
from .crud import get_charge
|
||||
from .crud import get_charge, get_charge_config
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
@ -32,7 +31,7 @@ async def display(request: Request, charge_id: str):
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
|
||||
)
|
||||
wallet = await get_wallet(charge.lnbitswallet)
|
||||
onchainwallet_config = await get_config(charge.user)
|
||||
onchainwallet_config = await get_charge_config(charge_id)
|
||||
inkey = wallet.inkey if wallet else None
|
||||
mempool_endpoint = (
|
||||
onchainwallet_config.mempool_endpoint if onchainwallet_config else None
|
||||
|
@ -20,6 +20,7 @@ from .crud import (
|
||||
get_charges,
|
||||
update_charge,
|
||||
)
|
||||
from .helpers import compact_charge
|
||||
from .models import CreateCharge
|
||||
|
||||
#############################CHARGES##########################
|
||||
@ -123,25 +124,13 @@ async def api_charge_balance(charge_id):
|
||||
try:
|
||||
r = await client.post(
|
||||
charge.webhook,
|
||||
json={
|
||||
"id": charge.id,
|
||||
"description": charge.description,
|
||||
"onchainaddress": charge.onchainaddress,
|
||||
"payment_request": charge.payment_request,
|
||||
"payment_hash": charge.payment_hash,
|
||||
"time": charge.time,
|
||||
"amount": charge.amount,
|
||||
"balance": charge.balance,
|
||||
"paid": charge.paid,
|
||||
"timestamp": charge.timestamp,
|
||||
"completelink": charge.completelink,
|
||||
},
|
||||
json=compact_charge(charge),
|
||||
timeout=40,
|
||||
)
|
||||
except AssertionError:
|
||||
charge.webhook = None
|
||||
return {
|
||||
**charge.dict(),
|
||||
**compact_charge(charge),
|
||||
**{"time_elapsed": charge.time_elapsed},
|
||||
**{"time_left": charge.time_left},
|
||||
**{"paid": charge.paid},
|
||||
|
@ -14,7 +14,7 @@ class Target(BaseModel):
|
||||
class TargetPutList(BaseModel):
|
||||
wallet: str = Query(...)
|
||||
alias: str = Query("")
|
||||
percent: float = Query(..., ge=0.01)
|
||||
percent: float = Query(..., ge=0.01, lt=100)
|
||||
|
||||
|
||||
class TargetPut(BaseModel):
|
||||
|
@ -1,13 +1,11 @@
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core import db as core_db
|
||||
from lnbits.core.crud import create_payment
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.helpers import get_current_extension_name, urlsafe_short_hash
|
||||
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
||||
from lnbits.core.services import create_invoice, pay_invoice
|
||||
from lnbits.helpers import get_current_extension_name
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
|
||||
from .crud import get_targets
|
||||
|
||||
@ -22,60 +20,36 @@ async def wait_for_paid_invoices():
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
if payment.extra.get("tag") == "splitpayments" or payment.extra.get("splitted"):
|
||||
# already splitted, ignore
|
||||
if payment.extra.get("tag") == "splitpayments":
|
||||
# already a splitted payment, ignore
|
||||
return
|
||||
|
||||
# now we make some special internal transfers (from no one to the receiver)
|
||||
targets = await get_targets(payment.wallet_id)
|
||||
|
||||
if not targets:
|
||||
return
|
||||
|
||||
transfers = [
|
||||
(target.wallet, int(target.percent * payment.amount / 100))
|
||||
for target in targets
|
||||
]
|
||||
transfers = [(wallet, amount) for wallet, amount in transfers if amount > 0]
|
||||
amount_left = payment.amount - sum([amount for _, amount in transfers])
|
||||
total_percent = sum([target.percent for target in targets])
|
||||
|
||||
if amount_left < 0:
|
||||
logger.error(
|
||||
"splitpayments failure: amount_left is negative.", payment.payment_hash
|
||||
)
|
||||
if total_percent > 100:
|
||||
logger.error("splitpayment failure: total percent adds up to more than 100%")
|
||||
return
|
||||
|
||||
# mark the original payment with one extra key, "splitted"
|
||||
# (this prevents us from doing this process again and it's informative)
|
||||
# and reduce it by the amount we're going to send to the producer
|
||||
await core_db.execute(
|
||||
"""
|
||||
UPDATE apipayments
|
||||
SET extra = ?, amount = ?
|
||||
WHERE hash = ?
|
||||
AND checking_id NOT LIKE 'internal_%'
|
||||
""",
|
||||
(
|
||||
json.dumps(dict(**payment.extra, splitted=True)),
|
||||
amount_left,
|
||||
payment.payment_hash,
|
||||
),
|
||||
)
|
||||
|
||||
# perform the internal transfer using the same payment_hash
|
||||
for wallet, amount in transfers:
|
||||
internal_checking_id = f"internal_{urlsafe_short_hash()}"
|
||||
await create_payment(
|
||||
wallet_id=wallet,
|
||||
checking_id=internal_checking_id,
|
||||
payment_request="",
|
||||
payment_hash=payment.payment_hash,
|
||||
amount=amount,
|
||||
memo=payment.memo,
|
||||
pending=False,
|
||||
logger.debug(f"performing split payments to {len(targets)} targets")
|
||||
for target in targets:
|
||||
amount = int(payment.amount * target.percent / 100) # msats
|
||||
payment_hash, payment_request = await create_invoice(
|
||||
wallet_id=target.wallet,
|
||||
amount=int(amount / 1000), # sats
|
||||
internal=True,
|
||||
memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
|
||||
extra={"tag": "splitpayments"},
|
||||
)
|
||||
logger.debug(f"created split invoice: {payment_hash}")
|
||||
|
||||
# manually send this for now
|
||||
await internal_invoice_queue.put(internal_checking_id)
|
||||
return
|
||||
checking_id = await pay_invoice(
|
||||
payment_request=payment_request,
|
||||
wallet_id=payment.wallet_id,
|
||||
extra={"tag": "splitpayments"},
|
||||
)
|
||||
logger.debug(f"paid split invoice: {checking_id}")
|
||||
|
@ -25,7 +25,7 @@ async def m001_initial(db):
|
||||
name TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
cur_code TEXT NOT NULL,
|
||||
sats INT NOT NULL,
|
||||
sats {db.big_int} NOT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
service INTEGER NOT NULL,
|
||||
posted BOOLEAN NOT NULL,
|
||||
|
@ -3,10 +3,10 @@ from typing import List, Optional, Union
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
|
||||
from . import db
|
||||
from .models import CreateDomain, Domains, Subdomains
|
||||
from .models import CreateDomain, CreateSubdomain, Domains, Subdomains
|
||||
|
||||
|
||||
async def create_subdomain(payment_hash, wallet, data: CreateDomain) -> Subdomains:
|
||||
async def create_subdomain(payment_hash, wallet, data: CreateSubdomain) -> Subdomains:
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO subdomains.subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid, record_type)
|
||||
|
@ -3,24 +3,24 @@ from pydantic.main import BaseModel
|
||||
|
||||
|
||||
class CreateDomain(BaseModel):
|
||||
wallet: str = Query(...)
|
||||
domain: str = Query(...)
|
||||
cf_token: str = Query(...)
|
||||
cf_zone_id: str = Query(...)
|
||||
webhook: str = Query("")
|
||||
description: str = Query(..., min_length=0)
|
||||
cost: int = Query(..., ge=0)
|
||||
allowed_record_types: str = Query(...)
|
||||
wallet: str = Query(...) # type: ignore
|
||||
domain: str = Query(...) # type: ignore
|
||||
cf_token: str = Query(...) # type: ignore
|
||||
cf_zone_id: str = Query(...) # type: ignore
|
||||
webhook: str = Query("") # type: ignore
|
||||
description: str = Query(..., min_length=0) # type: ignore
|
||||
cost: int = Query(..., ge=0) # type: ignore
|
||||
allowed_record_types: str = Query(...) # type: ignore
|
||||
|
||||
|
||||
class CreateSubdomain(BaseModel):
|
||||
domain: str = Query(...)
|
||||
subdomain: str = Query(...)
|
||||
email: str = Query(...)
|
||||
ip: str = Query(...)
|
||||
sats: int = Query(..., ge=0)
|
||||
duration: int = Query(...)
|
||||
record_type: str = Query(...)
|
||||
domain: str = Query(...) # type: ignore
|
||||
subdomain: str = Query(...) # type: ignore
|
||||
email: str = Query(...) # type: ignore
|
||||
ip: str = Query(...) # type: ignore
|
||||
sats: int = Query(..., ge=0) # type: ignore
|
||||
duration: int = Query(...) # type: ignore
|
||||
record_type: str = Query(...) # type: ignore
|
||||
|
||||
|
||||
class Domains(BaseModel):
|
||||
|
@ -20,7 +20,7 @@ async def wait_for_paid_invoices():
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
if payment.extra.get("tag") != "lnsubdomain":
|
||||
if not payment.extra or payment.extra.get("tag") != "lnsubdomain":
|
||||
# not an lnurlp invoice
|
||||
return
|
||||
|
||||
@ -37,7 +37,7 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||
)
|
||||
|
||||
### Use webhook to notify about cloudflare registration
|
||||
if domain.webhook:
|
||||
if domain and domain.webhook:
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
r = await client.post(
|
||||
|
@ -16,7 +16,9 @@ templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@subdomains_ext.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||
async def index(
|
||||
request: Request, user: User = Depends(check_user_exists) # type:ignore
|
||||
):
|
||||
return subdomains_renderer().TemplateResponse(
|
||||
"subdomains/index.html", {"request": request, "user": user.dict()}
|
||||
)
|
||||
|
@ -29,12 +29,15 @@ from .crud import (
|
||||
|
||||
@subdomains_ext.get("/api/v1/domains")
|
||||
async def api_domains(
|
||||
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
|
||||
g: WalletTypeInfo = Depends(get_key_type), # type: ignore
|
||||
all_wallets: bool = Query(False),
|
||||
):
|
||||
wallet_ids = [g.wallet.id]
|
||||
|
||||
if all_wallets:
|
||||
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
|
||||
user = await get_user(g.wallet.user)
|
||||
if user is not None:
|
||||
wallet_ids = user.wallet_ids
|
||||
|
||||
return [domain.dict() for domain in await get_domains(wallet_ids)]
|
||||
|
||||
@ -42,7 +45,9 @@ async def api_domains(
|
||||
@subdomains_ext.post("/api/v1/domains")
|
||||
@subdomains_ext.put("/api/v1/domains/{domain_id}")
|
||||
async def api_domain_create(
|
||||
data: CreateDomain, domain_id=None, g: WalletTypeInfo = Depends(get_key_type)
|
||||
data: CreateDomain,
|
||||
domain_id=None,
|
||||
g: WalletTypeInfo = Depends(get_key_type), # type: ignore
|
||||
):
|
||||
if domain_id:
|
||||
domain = await get_domain(domain_id)
|
||||
@ -63,7 +68,9 @@ async def api_domain_create(
|
||||
|
||||
|
||||
@subdomains_ext.delete("/api/v1/domains/{domain_id}")
|
||||
async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_domain_delete(
|
||||
domain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
domain = await get_domain(domain_id)
|
||||
|
||||
if not domain:
|
||||
@ -82,12 +89,14 @@ async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type)
|
||||
|
||||
@subdomains_ext.get("/api/v1/subdomains")
|
||||
async def api_subdomains(
|
||||
all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type)
|
||||
all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
wallet_ids = [g.wallet.id]
|
||||
|
||||
if all_wallets:
|
||||
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
|
||||
user = await get_user(g.wallet.user)
|
||||
if user is not None:
|
||||
wallet_ids = user.wallet_ids
|
||||
|
||||
return [domain.dict() for domain in await get_subdomains(wallet_ids)]
|
||||
|
||||
@ -173,7 +182,9 @@ async def api_subdomain_send_subdomain(payment_hash):
|
||||
|
||||
|
||||
@subdomains_ext.delete("/api/v1/subdomains/{subdomain_id}")
|
||||
async def api_subdomain_delete(subdomain_id, g: WalletTypeInfo = Depends(get_key_type)):
|
||||
async def api_subdomain_delete(
|
||||
subdomain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore
|
||||
):
|
||||
subdomain = await get_subdomain(subdomain_id)
|
||||
|
||||
if not subdomain:
|
||||
|
@ -19,8 +19,8 @@ async def m001_initial(db):
|
||||
wallet TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
sats INT NOT NULL,
|
||||
tipjar INT NOT NULL,
|
||||
sats {db.big_int} NOT NULL,
|
||||
tipjar {db.big_int} NOT NULL,
|
||||
FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id)
|
||||
);
|
||||
"""
|
||||
|
@ -1,11 +1,11 @@
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from lnbits.core import db as core_db
|
||||
from lnbits.core.crud import create_payment
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.helpers import get_current_extension_name, urlsafe_short_hash
|
||||
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
||||
from lnbits.core.services import create_invoice, pay_invoice
|
||||
from lnbits.helpers import get_current_extension_name
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
|
||||
from .crud import get_tpos
|
||||
|
||||
@ -20,11 +20,9 @@ async def wait_for_paid_invoices():
|
||||
|
||||
|
||||
async def on_invoice_paid(payment: Payment) -> None:
|
||||
if payment.extra.get("tag") == "tpos" and payment.extra.get("tipSplitted"):
|
||||
# already splitted, ignore
|
||||
if payment.extra.get("tag") != "tpos":
|
||||
return
|
||||
|
||||
# now we make some special internal transfers (from no one to the receiver)
|
||||
tpos = await get_tpos(payment.extra.get("tposId"))
|
||||
tipAmount = payment.extra.get("tipAmount")
|
||||
|
||||
@ -32,39 +30,17 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||
# no tip amount
|
||||
return
|
||||
|
||||
tipAmount = tipAmount * 1000
|
||||
amount = payment.amount - tipAmount
|
||||
|
||||
# mark the original payment with one extra key, "splitted"
|
||||
# (this prevents us from doing this process again and it's informative)
|
||||
# and reduce it by the amount we're going to send to the producer
|
||||
await core_db.execute(
|
||||
"""
|
||||
UPDATE apipayments
|
||||
SET extra = ?, amount = ?
|
||||
WHERE hash = ?
|
||||
AND checking_id NOT LIKE 'internal_%'
|
||||
""",
|
||||
(
|
||||
json.dumps(dict(**payment.extra, tipSplitted=True)),
|
||||
amount,
|
||||
payment.payment_hash,
|
||||
),
|
||||
)
|
||||
|
||||
# perform the internal transfer using the same payment_hash
|
||||
internal_checking_id = f"internal_{urlsafe_short_hash()}"
|
||||
await create_payment(
|
||||
payment_hash, payment_request = await create_invoice(
|
||||
wallet_id=tpos.tip_wallet,
|
||||
checking_id=internal_checking_id,
|
||||
payment_request="",
|
||||
payment_hash=payment.payment_hash,
|
||||
amount=tipAmount,
|
||||
memo=f"Tip for {payment.memo}",
|
||||
pending=False,
|
||||
extra={"tipSplitted": True},
|
||||
amount=int(tipAmount), # sats
|
||||
internal=True,
|
||||
memo=f"tpos tip",
|
||||
)
|
||||
logger.debug(f"tpos: tip invoice created: {payment_hash}")
|
||||
|
||||
# manually send this for now
|
||||
await internal_invoice_queue.put(internal_checking_id)
|
||||
return
|
||||
checking_id = await pay_invoice(
|
||||
payment_request=payment_request,
|
||||
wallet_id=payment.wallet_id,
|
||||
extra={"tag": "tpos"},
|
||||
)
|
||||
logger.debug(f"tpos: tip invoice paid: {checking_id}")
|
||||
|
@ -10,7 +10,7 @@ from .models import Address, Config, WalletAccount
|
||||
##########################WALLETS####################
|
||||
|
||||
|
||||
async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
|
||||
async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount:
|
||||
wallet_id = urlsafe_short_hash()
|
||||
await db.execute(
|
||||
"""
|
||||
@ -30,7 +30,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
|
||||
""",
|
||||
(
|
||||
wallet_id,
|
||||
w.user,
|
||||
user,
|
||||
w.masterpub,
|
||||
w.fingerprint,
|
||||
w.title,
|
||||
|
@ -14,7 +14,6 @@ class CreateWallet(BaseModel):
|
||||
|
||||
class WalletAccount(BaseModel):
|
||||
id: str
|
||||
user: str
|
||||
masterpub: str
|
||||
fingerprint: str
|
||||
title: str
|
||||
|
@ -86,7 +86,6 @@ async def api_wallet_create_or_update(
|
||||
|
||||
new_wallet = WalletAccount(
|
||||
id="none",
|
||||
user=w.wallet.user,
|
||||
masterpub=data.masterpub,
|
||||
fingerprint=descriptor.keys[0].fingerprint.hex(),
|
||||
type=descriptor.scriptpubkey_type(),
|
||||
@ -115,7 +114,7 @@ async def api_wallet_create_or_update(
|
||||
)
|
||||
)
|
||||
|
||||
wallet = await create_watch_wallet(new_wallet)
|
||||
wallet = await create_watch_wallet(w.wallet.user, new_wallet)
|
||||
|
||||
await api_get_addresses(wallet.id, w)
|
||||
except Exception as e:
|
||||
|
@ -1,95 +0,0 @@
|
||||
from functools import partial
|
||||
from typing import Callable, List, Optional
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import parse_http_list as _parse_list_header
|
||||
|
||||
from quart import Request
|
||||
from quart_trio.asgi import TrioASGIHTTPConnection
|
||||
from werkzeug.datastructures import Headers
|
||||
|
||||
|
||||
class ASGIProxyFix(TrioASGIHTTPConnection):
|
||||
def _create_request_from_scope(self, send: Callable) -> Request:
|
||||
headers = Headers()
|
||||
headers["Remote-Addr"] = (self.scope.get("client") or ["<local>"])[0]
|
||||
for name, value in self.scope["headers"]:
|
||||
headers.add(name.decode("latin1").title(), value.decode("latin1"))
|
||||
if self.scope["http_version"] < "1.1":
|
||||
headers.setdefault("Host", self.app.config["SERVER_NAME"] or "")
|
||||
|
||||
path = self.scope["path"]
|
||||
path = path if path[0] == "/" else urlparse(path).path
|
||||
|
||||
x_proto = self._get_real_value(1, headers.get("X-Forwarded-Proto"))
|
||||
if x_proto:
|
||||
self.scope["scheme"] = x_proto
|
||||
|
||||
x_host = self._get_real_value(1, headers.get("X-Forwarded-Host"))
|
||||
if x_host:
|
||||
headers["host"] = x_host.lower()
|
||||
|
||||
return self.app.request_class(
|
||||
self.scope["method"],
|
||||
self.scope["scheme"],
|
||||
path,
|
||||
self.scope["query_string"],
|
||||
headers,
|
||||
self.scope.get("root_path", ""),
|
||||
self.scope["http_version"],
|
||||
max_content_length=self.app.config["MAX_CONTENT_LENGTH"],
|
||||
body_timeout=self.app.config["BODY_TIMEOUT"],
|
||||
send_push_promise=partial(self._send_push_promise, send),
|
||||
scope=self.scope,
|
||||
)
|
||||
|
||||
def _get_real_value(self, trusted: int, value: Optional[str]) -> Optional[str]:
|
||||
"""Get the real value from a list header based on the configured
|
||||
number of trusted proxies.
|
||||
:param trusted: Number of values to trust in the header.
|
||||
:param value: Comma separated list header value to parse.
|
||||
:return: The real value, or ``None`` if there are fewer values
|
||||
than the number of trusted proxies.
|
||||
.. versionchanged:: 1.0
|
||||
Renamed from ``_get_trusted_comma``.
|
||||
.. versionadded:: 0.15
|
||||
"""
|
||||
if not (trusted and value):
|
||||
return None
|
||||
|
||||
values = self.parse_list_header(value)
|
||||
if len(values) >= trusted:
|
||||
return values[-trusted]
|
||||
|
||||
return None
|
||||
|
||||
def parse_list_header(self, value: str) -> List[str]:
|
||||
result = []
|
||||
for item in _parse_list_header(value):
|
||||
if item[:1] == item[-1:] == '"':
|
||||
item = self.unquote_header_value(item[1:-1])
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
def unquote_header_value(self, value: str, is_filename: bool = False) -> str:
|
||||
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
|
||||
This does not use the real unquoting but what browsers are actually
|
||||
using for quoting.
|
||||
.. versionadded:: 0.5
|
||||
:param value: the header value to unquote.
|
||||
:param is_filename: The value represents a filename or path.
|
||||
"""
|
||||
if value and value[0] == value[-1] == '"':
|
||||
# this is not the real unquoting, but fixing this so that the
|
||||
# RFC is met will result in bugs with internet explorer and
|
||||
# probably some other browsers as well. IE for example is
|
||||
# uploading files with "C:\foo\bar.txt" as filename
|
||||
value = value[1:-1]
|
||||
|
||||
# if this is a filename and the starting characters look like
|
||||
# a UNC path, then just return the value without quotes. Using the
|
||||
# replace sequence below on a UNC path has the effect of turning
|
||||
# the leading double slash into a single slash and then
|
||||
# _fix_ie_filename() doesn't work correctly. See #458.
|
||||
if not is_filename or value[:2] != "\\\\":
|
||||
return value.replace("\\\\", "\\").replace('\\"', '"')
|
||||
return value
|
@ -1,5 +1,6 @@
|
||||
# flake8: noqa
|
||||
|
||||
|
||||
from .cliche import ClicheWallet
|
||||
from .cln import CoreLightningWallet # legacy .env support
|
||||
from .cln import CoreLightningWallet as CLightningWallet
|
||||
@ -9,6 +10,7 @@ from .lnbits import LNbitsWallet
|
||||
from .lndgrpc import LndWallet
|
||||
from .lndrest import LndRestWallet
|
||||
from .lnpay import LNPayWallet
|
||||
from .lntips import LnTipsWallet
|
||||
from .lntxbot import LntxbotWallet
|
||||
from .opennode import OpenNodeWallet
|
||||
from .spark import SparkWallet
|
||||
|
170
lnbits/wallets/lntips.py
Normal file
170
lnbits/wallets/lntips.py
Normal file
@ -0,0 +1,170 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
from os import getenv
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
PaymentStatus,
|
||||
StatusResponse,
|
||||
Wallet,
|
||||
)
|
||||
|
||||
|
||||
class LnTipsWallet(Wallet):
|
||||
def __init__(self):
|
||||
endpoint = getenv("LNTIPS_API_ENDPOINT")
|
||||
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
|
||||
key = (
|
||||
getenv("LNTIPS_API_KEY")
|
||||
or getenv("LNTIPS_ADMIN_KEY")
|
||||
or getenv("LNTIPS_INVOICE_KEY")
|
||||
)
|
||||
self.auth = {"Authorization": f"Basic {key}"}
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.get(
|
||||
f"{self.endpoint}/api/v1/balance", headers=self.auth, timeout=40
|
||||
)
|
||||
try:
|
||||
data = r.json()
|
||||
except:
|
||||
return StatusResponse(
|
||||
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
|
||||
)
|
||||
|
||||
if data.get("error"):
|
||||
return StatusResponse(data["error"], 0)
|
||||
|
||||
return StatusResponse(None, data["balance"] * 1000)
|
||||
|
||||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
data: Dict = {"amount": amount}
|
||||
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(
|
||||
f"{self.endpoint}/api/v1/createinvoice",
|
||||
headers=self.auth,
|
||||
json=data,
|
||||
timeout=40,
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
try:
|
||||
data = r.json()
|
||||
error_message = data["message"]
|
||||
except:
|
||||
error_message = r.text
|
||||
pass
|
||||
|
||||
return InvoiceResponse(False, None, None, error_message)
|
||||
|
||||
data = r.json()
|
||||
return InvoiceResponse(
|
||||
True, data["payment_hash"], data["payment_request"], None
|
||||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
f"{self.endpoint}/api/v1/payinvoice",
|
||||
headers=self.auth,
|
||||
json={"pay_req": bolt11},
|
||||
timeout=None,
|
||||
)
|
||||
if r.is_error:
|
||||
return PaymentResponse(False, None, 0, None, r.text)
|
||||
|
||||
if "error" in r.json():
|
||||
try:
|
||||
data = r.json()
|
||||
error_message = data["error"]
|
||||
except:
|
||||
error_message = r.text
|
||||
pass
|
||||
return PaymentResponse(False, None, 0, None, error_message)
|
||||
|
||||
data = r.json()["details"]
|
||||
checking_id = data["payment_hash"]
|
||||
fee_msat = -data["fee"]
|
||||
preimage = data["preimage"]
|
||||
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
f"{self.endpoint}/api/v1/invoicestatus/{checking_id}",
|
||||
headers=self.auth,
|
||||
)
|
||||
|
||||
if r.is_error or len(r.text) == 0:
|
||||
return PaymentStatus(None)
|
||||
|
||||
data = r.json()
|
||||
return PaymentStatus(data["paid"])
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
async with httpx.AsyncClient() as client:
|
||||
r = await client.post(
|
||||
url=f"{self.endpoint}/api/v1/paymentstatus/{checking_id}",
|
||||
headers=self.auth,
|
||||
)
|
||||
|
||||
if r.is_error:
|
||||
return PaymentStatus(None)
|
||||
data = r.json()
|
||||
|
||||
paid_to_status = {False: None, True: True}
|
||||
return PaymentStatus(paid_to_status[data.get("paid")])
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
last_connected = None
|
||||
while True:
|
||||
url = f"{self.endpoint}/api/v1/invoicestream"
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=None, headers=self.auth) as client:
|
||||
last_connected = time.time()
|
||||
async with client.stream("GET", url) as r:
|
||||
async for line in r.aiter_lines():
|
||||
try:
|
||||
prefix = "data: "
|
||||
if not line.startswith(prefix):
|
||||
continue
|
||||
data = line[len(prefix) :] # sse parsing
|
||||
inv = json.loads(data)
|
||||
if not inv.get("payment_hash"):
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
yield inv["payment_hash"]
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# do not sleep if the connection was active for more than 10s
|
||||
# since the backend is expected to drop the connection after 90s
|
||||
if last_connected is None or time.time() - last_connected < 10:
|
||||
logger.error(
|
||||
f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying in 5 seconds"
|
||||
)
|
||||
await asyncio.sleep(5)
|
280
poetry.lock
generated
280
poetry.lock
generated
@ -8,7 +8,7 @@ python-versions = ">=3.6,<4.0"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.1"
|
||||
version = "3.6.2"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -22,7 +22,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
trio = ["trio (>=0.16,<0.22)"]
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
@ -69,7 +69,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"]
|
||||
tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"]
|
||||
|
||||
[[package]]
|
||||
name = "base58"
|
||||
@ -100,11 +100,11 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.8.0"
|
||||
version = "22.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
@ -122,7 +122,7 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "Cerberus"
|
||||
name = "cerberus"
|
||||
version = "1.3.4"
|
||||
description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
|
||||
category = "main"
|
||||
@ -160,7 +160,7 @@ optional = false
|
||||
python-versions = ">=3.5.0"
|
||||
|
||||
[package.extras]
|
||||
unicode_backport = ["unicodedata2"]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
@ -188,11 +188,11 @@ cffi = ">=1.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
@ -258,24 +258,6 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "environs"
|
||||
version = "9.3.3"
|
||||
description = "simplified environment variable parsing"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
marshmallow = ">=3.0.0"
|
||||
python-dotenv = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"]
|
||||
django = ["dj-database-url", "dj-email-url", "django-cache-url"]
|
||||
lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"]
|
||||
tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.78.0"
|
||||
@ -296,7 +278,7 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (
|
||||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "1.49.1"
|
||||
version = "1.50.0"
|
||||
description = "HTTP/2-based RPC framework"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -306,7 +288,7 @@ python-versions = ">=3.7"
|
||||
six = ">=1.5.2"
|
||||
|
||||
[package.extras]
|
||||
protobuf = ["grpcio-tools (>=1.49.1)"]
|
||||
protobuf = ["grpcio-tools (>=1.50.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
@ -408,12 +390,12 @@ python-versions = ">=3.6.1,<4.0"
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||
pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements_deprecated_finder = ["pip-api", "pipreqs"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "Jinja2"
|
||||
name = "jinja2"
|
||||
version = "3.0.1"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
@ -455,7 +437,7 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
|
||||
dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "MarkupSafe"
|
||||
name = "markupsafe"
|
||||
version = "2.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
@ -589,7 +571,7 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "4.21.7"
|
||||
version = "4.21.8"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -697,7 +679,7 @@ optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "PyQRCode"
|
||||
name = "pyqrcode"
|
||||
version = "1.2.1"
|
||||
description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output."
|
||||
category = "main"
|
||||
@ -705,10 +687,10 @@ optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
PNG = ["pypng (>=0.0.13)"]
|
||||
png = ["pypng (>=0.0.13)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyScss"
|
||||
name = "pyscss"
|
||||
version = "1.4.0"
|
||||
description = "pyScss, a Scss compiler for Python"
|
||||
category = "main"
|
||||
@ -721,7 +703,7 @@ pathlib2 = "*"
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "PySocks"
|
||||
name = "pysocks"
|
||||
version = "1.7.1"
|
||||
description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
|
||||
category = "main"
|
||||
@ -791,7 +773,7 @@ python-versions = ">=3.5"
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "PyYAML"
|
||||
name = "pyyaml"
|
||||
version = "5.4.1"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
@ -799,7 +781,7 @@ optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[[package]]
|
||||
name = "Represent"
|
||||
name = "represent"
|
||||
version = "1.6.0.post0"
|
||||
description = "Create __repr__ automatically or declaratively."
|
||||
category = "main"
|
||||
@ -839,7 +821,7 @@ cffi = ">=1.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "65.4.1"
|
||||
version = "65.5.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -875,7 +857,7 @@ optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "SQLAlchemy"
|
||||
name = "sqlalchemy"
|
||||
version = "1.3.23"
|
||||
description = "Database Abstraction Library"
|
||||
category = "main"
|
||||
@ -884,14 +866,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.extras]
|
||||
mssql = ["pyodbc"]
|
||||
mssql_pymssql = ["pymssql"]
|
||||
mssql_pyodbc = ["pyodbc"]
|
||||
mssql-pymssql = ["pymssql"]
|
||||
mssql-pyodbc = ["pyodbc"]
|
||||
mysql = ["mysqlclient"]
|
||||
oracle = ["cx_oracle"]
|
||||
postgresql = ["psycopg2"]
|
||||
postgresql_pg8000 = ["pg8000 (<1.16.6)"]
|
||||
postgresql_psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql_psycopg2cffi = ["psycopg2cffi"]
|
||||
postgresql-pg8000 = ["pg8000 (<1.16.6)"]
|
||||
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
||||
pymysql = ["pymysql", "pymysql (<1)"]
|
||||
|
||||
[[package]]
|
||||
@ -953,7 +935,7 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "types-protobuf"
|
||||
version = "3.20.4"
|
||||
version = "3.20.4.1"
|
||||
description = "Typing stubs for protobuf"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@ -1051,7 +1033,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||
content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42"
|
||||
content-hash = "e798b36b5941b43ee249bc196fcfb28d8ee712947336d21467651c672ba0106b"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
@ -1059,8 +1041,8 @@ aiofiles = [
|
||||
{file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"},
|
||||
]
|
||||
anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
{file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
|
||||
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
|
||||
]
|
||||
asgiref = [
|
||||
{file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
|
||||
@ -1092,31 +1074,29 @@ bitstring = [
|
||||
{file = "bitstring-3.1.9.tar.gz", hash = "sha256:a5848a3f63111785224dca8bb4c0a75b62ecdef56a042c8d6be74b16f7e860e7"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"},
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"},
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"},
|
||||
{file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"},
|
||||
{file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"},
|
||||
{file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"},
|
||||
{file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"},
|
||||
{file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"},
|
||||
{file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"},
|
||||
{file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"},
|
||||
{file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"},
|
||||
{file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"},
|
||||
{file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"},
|
||||
{file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"},
|
||||
{file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"},
|
||||
{file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
|
||||
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
|
||||
{file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
|
||||
{file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
|
||||
{file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
|
||||
{file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
|
||||
{file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
|
||||
{file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
|
||||
{file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
|
||||
{file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
|
||||
{file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
|
||||
{file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
|
||||
{file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
|
||||
{file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
|
||||
{file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
|
||||
{file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
|
||||
{file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
|
||||
{file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
|
||||
{file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
|
||||
{file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
|
||||
{file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
|
||||
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
|
||||
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
|
||||
]
|
||||
Cerberus = [
|
||||
cerberus = [
|
||||
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
|
||||
]
|
||||
certifi = [
|
||||
@ -1220,8 +1200,8 @@ coincurve = [
|
||||
{file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
coverage = [
|
||||
{file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
|
||||
@ -1309,60 +1289,56 @@ enum34 = [
|
||||
{file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"},
|
||||
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
|
||||
]
|
||||
environs = [
|
||||
{file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"},
|
||||
{file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"},
|
||||
]
|
||||
fastapi = [
|
||||
{file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
|
||||
{file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
|
||||
]
|
||||
grpcio = [
|
||||
{file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"},
|
||||
{file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"},
|
||||
{file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"},
|
||||
{file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"},
|
||||
{file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"},
|
||||
{file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"},
|
||||
{file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:2d9fd6e38b16c4d286a01e1776fdf6c7a4123d99ae8d6b3f0b4a03a34bf6ce45"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:4b123fbb7a777a2fedec684ca0b723d85e1d2379b6032a9a9b7851829ed3ca9a"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2f77a90ba7b85bfb31329f8eab9d9540da2cf8a302128fb1241d7ea239a5469"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eea18a878cffc804506d39c6682d71f6b42ec1c151d21865a95fae743fda500"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b71916fa8f9eb2abd93151fafe12e18cebb302686b924bd4ec39266211da525"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:95ce51f7a09491fb3da8cf3935005bff19983b77c4e9437ef77235d787b06842"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-win32.whl", hash = "sha256:f7025930039a011ed7d7e7ef95a1cb5f516e23c5a6ecc7947259b67bea8e06ca"},
|
||||
{file = "grpcio-1.50.0-cp310-cp310-win_amd64.whl", hash = "sha256:05f7c248e440f538aaad13eee78ef35f0541e73498dd6f832fe284542ac4b298"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:ca8a2254ab88482936ce941485c1c20cdeaef0efa71a61dbad171ab6758ec998"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3b611b3de3dfd2c47549ca01abfa9bbb95937eb0ea546ea1d762a335739887be"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a4cd8cb09d1bc70b3ea37802be484c5ae5a576108bad14728f2516279165dd7"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:156f8009e36780fab48c979c5605eda646065d4695deea4cfcbcfdd06627ddb6"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de411d2b030134b642c092e986d21aefb9d26a28bf5a18c47dd08ded411a3bc5"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d144ad10eeca4c1d1ce930faa105899f86f5d99cecfe0d7224f3c4c76265c15e"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-win32.whl", hash = "sha256:92d7635d1059d40d2ec29c8bf5ec58900120b3ce5150ef7414119430a4b2dd5c"},
|
||||
{file = "grpcio-1.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:ce8513aee0af9c159319692bfbf488b718d1793d764798c3d5cff827a09e25ef"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:8e8999a097ad89b30d584c034929f7c0be280cd7851ac23e9067111167dcbf55"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:a50a1be449b9e238b9bd43d3857d40edf65df9416dea988929891d92a9f8a778"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:cf151f97f5f381163912e8952eb5b3afe89dec9ed723d1561d59cabf1e219a35"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a23d47f2fc7111869f0ff547f771733661ff2818562b04b9ed674fa208e261f4"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84d04dec64cc4ed726d07c5d17b73c343c8ddcd6b59c7199c801d6bbb9d9ed1"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:67dd41a31f6fc5c7db097a5c14a3fa588af54736ffc174af4411d34c4f306f68"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8d4c8e73bf20fb53fe5a7318e768b9734cf122fe671fcce75654b98ba12dfb75"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-win32.whl", hash = "sha256:7489dbb901f4fdf7aec8d3753eadd40839c9085967737606d2c35b43074eea24"},
|
||||
{file = "grpcio-1.50.0-cp37-cp37m-win_amd64.whl", hash = "sha256:531f8b46f3d3db91d9ef285191825d108090856b3bc86a75b7c3930f16ce432f"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:d534d169673dd5e6e12fb57cc67664c2641361e1a0885545495e65a7b761b0f4"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:1d8d02dbb616c0a9260ce587eb751c9c7dc689bc39efa6a88cc4fa3e9c138a7b"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:baab51dcc4f2aecabf4ed1e2f57bceab240987c8b03533f1cef90890e6502067"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40838061e24f960b853d7bce85086c8e1b81c6342b1f4c47ff0edd44bbae2722"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:931e746d0f75b2a5cff0a1197d21827a3a2f400c06bace036762110f19d3d507"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15f9e6d7f564e8f0776770e6ef32dac172c6f9960c478616c366862933fa08b4"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a4c23e54f58e016761b576976da6a34d876420b993f45f66a2bfb00363ecc1f9"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-win32.whl", hash = "sha256:3e4244c09cc1b65c286d709658c061f12c61c814be0b7030a2d9966ff02611e0"},
|
||||
{file = "grpcio-1.50.0-cp38-cp38-win_amd64.whl", hash = "sha256:8e69aa4e9b7f065f01d3fdcecbe0397895a772d99954bb82eefbb1682d274518"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:af98d49e56605a2912cf330b4627e5286243242706c3a9fa0bcec6e6f68646fc"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:080b66253f29e1646ac53ef288c12944b131a2829488ac3bac8f52abb4413c0d"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:ab5d0e3590f0a16cb88de4a3fa78d10eb66a84ca80901eb2c17c1d2c308c230f"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb11464f480e6103c59d558a3875bd84eed6723f0921290325ebe97262ae1347"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e07fe0d7ae395897981d16be61f0db9791f482f03fee7d1851fe20ddb4f69c03"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d75061367a69808ab2e84c960e9dce54749bcc1e44ad3f85deee3a6c75b4ede9"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ae23daa7eda93c1c49a9ecc316e027ceb99adbad750fbd3a56fa9e4a2ffd5ae0"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-win32.whl", hash = "sha256:177afaa7dba3ab5bfc211a71b90da1b887d441df33732e94e26860b3321434d9"},
|
||||
{file = "grpcio-1.50.0-cp39-cp39-win_amd64.whl", hash = "sha256:ea8ccf95e4c7e20419b7827aa5b6da6f02720270686ac63bd3493a651830235c"},
|
||||
{file = "grpcio-1.50.0.tar.gz", hash = "sha256:12b479839a5e753580b5e6053571de14006157f2ef9b71f38c56dc9b23b95ad6"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
@ -1428,7 +1404,7 @@ isort = [
|
||||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
Jinja2 = [
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
||||
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
||||
]
|
||||
@ -1440,7 +1416,7 @@ loguru = [
|
||||
{file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
|
||||
{file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
|
||||
]
|
||||
MarkupSafe = [
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||
@ -1573,20 +1549,20 @@ pluggy = [
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
protobuf = [
|
||||
{file = "protobuf-4.21.7-cp310-abi3-win32.whl", hash = "sha256:c7cb105d69a87416bd9023e64324e1c089593e6dae64d2536f06bcbe49cd97d8"},
|
||||
{file = "protobuf-4.21.7-cp310-abi3-win_amd64.whl", hash = "sha256:3ec85328a35a16463c6f419dbce3c0fc42b3e904d966f17f48bae39597c7a543"},
|
||||
{file = "protobuf-4.21.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:db9056b6a11cb5131036d734bcbf91ef3ef9235d6b681b2fc431cbfe5a7f2e56"},
|
||||
{file = "protobuf-4.21.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ca200645d6235ce0df3ccfdff1567acbab35c4db222a97357806e015f85b5744"},
|
||||
{file = "protobuf-4.21.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b019c79e23a80735cc8a71b95f76a49a262f579d6b84fd20a0b82279f40e2cc1"},
|
||||
{file = "protobuf-4.21.7-cp37-cp37m-win32.whl", hash = "sha256:d3f89ccf7182293feba2de2739c8bf34fed1ed7c65a5cf987be00311acac57c1"},
|
||||
{file = "protobuf-4.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:a74d96cd960b87b4b712797c741bb3ea3a913f5c2dc4b6cbe9c0f8360b75297d"},
|
||||
{file = "protobuf-4.21.7-cp38-cp38-win32.whl", hash = "sha256:8e09d1916386eca1ef1353767b6efcebc0a6859ed7f73cb7fb974feba3184830"},
|
||||
{file = "protobuf-4.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:9e355f2a839d9930d83971b9f562395e13493f0e9211520f8913bd11efa53c02"},
|
||||
{file = "protobuf-4.21.7-cp39-cp39-win32.whl", hash = "sha256:f370c0a71712f8965023dd5b13277444d3cdfecc96b2c778b0e19acbfd60df6e"},
|
||||
{file = "protobuf-4.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:9643684232b6b340b5e63bb69c9b4904cdd39e4303d498d1a92abddc7e895b7f"},
|
||||
{file = "protobuf-4.21.7-py2.py3-none-any.whl", hash = "sha256:8066322588d4b499869bf9f665ebe448e793036b552f68c585a9b28f1e393f66"},
|
||||
{file = "protobuf-4.21.7-py3-none-any.whl", hash = "sha256:58b81358ec6c0b5d50df761460ae2db58405c063fd415e1101209221a0a810e1"},
|
||||
{file = "protobuf-4.21.7.tar.gz", hash = "sha256:71d9dba03ed3432c878a801e2ea51e034b0ea01cf3a4344fb60166cb5f6c8757"},
|
||||
{file = "protobuf-4.21.8-cp310-abi3-win32.whl", hash = "sha256:c252c55ee15175aa1b21b7b9896e6add5162d066d5202e75c39f96136f08cce3"},
|
||||
{file = "protobuf-4.21.8-cp310-abi3-win_amd64.whl", hash = "sha256:809ca0b225d3df42655a12f311dd0f4148a943c51f1ad63c38343e457492b689"},
|
||||
{file = "protobuf-4.21.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bbececaf3cfea9ea65ebb7974e6242d310d2a7772a6f015477e0d79993af4511"},
|
||||
{file = "protobuf-4.21.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:b02eabb9ebb1a089ed20626a90ad7a69cee6bcd62c227692466054b19c38dd1f"},
|
||||
{file = "protobuf-4.21.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:4761201b93e024bb70ee3a6a6425d61f3152ca851f403ba946fb0cde88872661"},
|
||||
{file = "protobuf-4.21.8-cp37-cp37m-win32.whl", hash = "sha256:f2d55ff22ec300c4d954d3b0d1eeb185681ec8ad4fbecff8a5aee6a1cdd345ba"},
|
||||
{file = "protobuf-4.21.8-cp37-cp37m-win_amd64.whl", hash = "sha256:c5f94911dd8feb3cd3786fc90f7565c9aba7ce45d0f254afd625b9628f578c3f"},
|
||||
{file = "protobuf-4.21.8-cp38-cp38-win32.whl", hash = "sha256:b37b76efe84d539f16cba55ee0036a11ad91300333abd213849cbbbb284b878e"},
|
||||
{file = "protobuf-4.21.8-cp38-cp38-win_amd64.whl", hash = "sha256:2c92a7bfcf4ae76a8ac72e545e99a7407e96ffe52934d690eb29a8809ee44d7b"},
|
||||
{file = "protobuf-4.21.8-cp39-cp39-win32.whl", hash = "sha256:89d641be4b5061823fa0e463c50a2607a97833e9f8cfb36c2f91ef5ccfcc3861"},
|
||||
{file = "protobuf-4.21.8-cp39-cp39-win_amd64.whl", hash = "sha256:bc471cf70a0f53892fdd62f8cd4215f0af8b3f132eeee002c34302dff9edd9b6"},
|
||||
{file = "protobuf-4.21.8-py2.py3-none-any.whl", hash = "sha256:a55545ce9eec4030cf100fcb93e861c622d927ef94070c1a3c01922902464278"},
|
||||
{file = "protobuf-4.21.8-py3-none-any.whl", hash = "sha256:0f236ce5016becd989bf39bd20761593e6d8298eccd2d878eda33012645dc369"},
|
||||
{file = "protobuf-4.21.8.tar.gz", hash = "sha256:427426593b55ff106c84e4a88cac855175330cb6eb7e889e85aaa7b5652b686d"},
|
||||
]
|
||||
psycopg2-binary = [
|
||||
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
|
||||
@ -1706,14 +1682,14 @@ pyparsing = [
|
||||
pypng = [
|
||||
{file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"},
|
||||
]
|
||||
PyQRCode = [
|
||||
pyqrcode = [
|
||||
{file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"},
|
||||
{file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"},
|
||||
]
|
||||
pyScss = [
|
||||
pyscss = [
|
||||
{file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"},
|
||||
]
|
||||
PySocks = [
|
||||
pysocks = [
|
||||
{file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"},
|
||||
{file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"},
|
||||
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
|
||||
@ -1734,7 +1710,7 @@ python-dotenv = [
|
||||
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
|
||||
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
|
||||
]
|
||||
PyYAML = [
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
||||
@ -1765,7 +1741,7 @@ PyYAML = [
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
||||
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
||||
]
|
||||
Represent = [
|
||||
represent = [
|
||||
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
|
||||
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
|
||||
]
|
||||
@ -1799,8 +1775,8 @@ secp256k1 = [
|
||||
{file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"},
|
||||
]
|
||||
setuptools = [
|
||||
{file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
|
||||
{file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
|
||||
{file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"},
|
||||
{file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"},
|
||||
]
|
||||
shortuuid = [
|
||||
{file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"},
|
||||
@ -1814,7 +1790,7 @@ sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
]
|
||||
SQLAlchemy = [
|
||||
sqlalchemy = [
|
||||
{file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"},
|
||||
{file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"},
|
||||
{file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"},
|
||||
@ -1896,8 +1872,8 @@ typed-ast = [
|
||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||
]
|
||||
types-protobuf = [
|
||||
{file = "types-protobuf-3.20.4.tar.gz", hash = "sha256:0dad3a5009895c985a56e2837f61902bad9594151265ac0ee907bb16d0b01eb7"},
|
||||
{file = "types_protobuf-3.20.4-py3-none-any.whl", hash = "sha256:5082437afe64ce3b31c8db109eae86e02fda11e4d5f9ac59cb8578a8a138aa70"},
|
||||
{file = "types-protobuf-3.20.4.1.tar.gz", hash = "sha256:67df7cc7ec85d114db2664a8ae8905543e75fb5edbed437dabc9e9fb8f8fcf9e"},
|
||||
{file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||
|
@ -88,8 +88,34 @@ profile = "black"
|
||||
ignore_missing_imports = "True"
|
||||
files = "lnbits"
|
||||
exclude = """(?x)(
|
||||
^lnbits/extensions.
|
||||
| ^lnbits/wallets/lnd_grpc_files.
|
||||
^lnbits/extensions/bleskomat.
|
||||
| ^lnbits/extensions/boltz.
|
||||
| ^lnbits/extensions/boltcards.
|
||||
| ^lnbits/extensions/events.
|
||||
| ^lnbits/extensions/hivemind.
|
||||
| ^lnbits/extensions/invoices.
|
||||
| ^lnbits/extensions/jukebox.
|
||||
| ^lnbits/extensions/livestream.
|
||||
| ^lnbits/extensions/lnaddress.
|
||||
| ^lnbits/extensions/lndhub.
|
||||
| ^lnbits/extensions/lnticket.
|
||||
| ^lnbits/extensions/lnurldevice.
|
||||
| ^lnbits/extensions/lnurlp.
|
||||
| ^lnbits/extensions/lnurlpayout.
|
||||
| ^lnbits/extensions/ngrok.
|
||||
| ^lnbits/extensions/offlineshop.
|
||||
| ^lnbits/extensions/paywall.
|
||||
| ^lnbits/extensions/satsdice.
|
||||
| ^lnbits/extensions/satspay.
|
||||
| ^lnbits/extensions/scrub.
|
||||
| ^lnbits/extensions/splitpayments.
|
||||
| ^lnbits/extensions/streamalerts.
|
||||
| ^lnbits/extensions/tipjar.
|
||||
| ^lnbits/extensions/tpos.
|
||||
| ^lnbits/extensions/usermanager.
|
||||
| ^lnbits/extensions/watchonly.
|
||||
| ^lnbits/extensions/withdraw.
|
||||
| ^lnbits/wallets/lnd_grpc_files.
|
||||
)"""
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
Loading…
x
Reference in New Issue
Block a user