Merge branch 'main' into Deezymain

This commit is contained in:
ben 2023-01-05 00:42:08 +00:00
commit 82eafe2900
205 changed files with 6180 additions and 1531 deletions

View file

@ -11,7 +11,11 @@ LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS="" LNBITS_ADMIN_USERS=""
# Extensions only admin can access # Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok, admin" LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
# Warning: Enabling this will make LNbits ignore this configuration file. Your settings will
# be stored in your database and you will be able to change them only through the Admin UI.
# Disable this to make LNbits use this config file again.
LNBITS_ADMIN_UI=false LNBITS_ADMIN_UI=false
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"

View file

@ -25,7 +25,7 @@ LNbits is a very simple Python server that sits on top of any funding source, an
LNbits can run on top of any lightning-network funding source, currently there is support for LND, c-lightning, Spark, LNpay, OpenNode, lntxbot, with more being added regularly. LNbits can run on top of any lightning-network funding source, currently there is support for LND, c-lightning, Spark, LNpay, OpenNode, lntxbot, with more being added regularly.
See [legend.lnbits.org](https://legend.lnbits.org) for more detailed documentation. See [docs.lnbits.org](https://docs.lnbits.org) for more detailed documentation.
Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series. Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series.
@ -70,7 +70,7 @@ Wallets can be easily generated and given out to people at events (one click mul
If you like this project and might even use or extend it, why not [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)! If you like this project and might even use or extend it, why not [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)!
[docs]: https://legend.lnbits.org/ [docs]: https://docs.lnbits.org/
[docs-badge]: https://img.shields.io/badge/docs-lnbits.org-673ab7.svg [docs-badge]: https://img.shields.io/badge/docs-lnbits.org-673ab7.svg
[github-mypy]: https://github.com/lnbits/lnbits/actions?query=workflow%3Amypy [github-mypy]: https://github.com/lnbits/lnbits/actions?query=workflow%3Amypy
[github-mypy-badge]: https://github.com/lnbits/lnbits/workflows/mypy/badge.svg [github-mypy-badge]: https://github.com/lnbits/lnbits/workflows/mypy/badge.svg

View file

@ -1 +1 @@
legend.lnbits.org docs.lnbits.org

View file

@ -3,7 +3,7 @@ remote_theme: pmarsceill/just-the-docs
color_scheme: dark color_scheme: dark
logo: "/logos/lnbits-full--inverse.png" logo: "/logos/lnbits-full--inverse.png"
search_enabled: true search_enabled: true
url: https://legend.lnbits.org url: https://docs.lnbits.org
aux_links: aux_links:
"LNbits on GitHub": "LNbits on GitHub":
- "//github.com/lnbits/lnbits" - "//github.com/lnbits/lnbits"

View file

@ -9,4 +9,4 @@ nav_order: 3
API reference API reference
============= =============
[Swagger Docs](https://legend.lnbits.org/devs/swagger.html) [Swagger Docs](https://docs.lnbits.org/devs/swagger.html)

View file

@ -74,7 +74,7 @@ def decode(pr: str) -> Invoice:
data_length = len(tagdata) / 5 data_length = len(tagdata) / 5
if tag == "d": if tag == "d":
invoice.description = _trim_to_bytes(tagdata).decode("utf-8") invoice.description = _trim_to_bytes(tagdata).decode()
elif tag == "h" and data_length == 52: elif tag == "h" and data_length == 52:
invoice.description_hash = _trim_to_bytes(tagdata).hex() invoice.description_hash = _trim_to_bytes(tagdata).hex()
elif tag == "p" and data_length == 52: elif tag == "p" and data_length == 52:
@ -260,7 +260,7 @@ class LnAddr(object):
def __str__(self): def __str__(self):
return "LnAddr[{}, amount={}{} tags=[{}]]".format( return "LnAddr[{}, amount={}{} tags=[{}]]".format(
bytes.hex(self.pubkey.serialize()).decode("utf-8"), bytes.hex(self.pubkey.serialize()).decode(),
self.amount, self.amount,
self.currency, self.currency,
", ".join([k + "=" + str(v) for k, v in self.tags]), ", ".join([k + "=" + str(v) for k, v in self.tags]),

View file

@ -46,8 +46,8 @@ class Wallet(BaseModel):
return "" return ""
def lnurlauth_key(self, domain: str) -> SigningKey: def lnurlauth_key(self, domain: str) -> SigningKey:
hashing_key = hashlib.sha256(self.id.encode("utf-8")).digest() hashing_key = hashlib.sha256(self.id.encode()).digest()
linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256") linking_key = hmac.digest(hashing_key, domain.encode(), "sha256")
return SigningKey.from_string( return SigningKey.from_string(
linking_key, curve=SECP256k1, hashfunc=hashlib.sha256 linking_key, curve=SECP256k1, hashfunc=hashlib.sha256

View file

@ -23,14 +23,55 @@
> >
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-icon <div class="row">
:name="extension.icon" <div class="col-3">
color="grey-5" <q-img
style="font-size: 4rem" :src="extension.tile"
></q-icon> spinner-color="white"
{% raw %} style="max-width: 100%"
<h5 class="q-mt-lg q-mb-xs">{{ extension.name }}</h5> ></q-img>
<small>{{ extension.shortDescription }} </small>{% endraw %} </div>
<div class="col-9 q-pl-sm">
{% raw %}
<div class="text-h5 gt-sm q-mt-sm q-mb-xs">
{{ extension.name }}
</div>
<div
class="text-subtitle2 gt-sm"
style="font-size: 11px; height: 34px"
>
{{ extension.shortDescription }}
</div>
<div class="text-subtitle1 lt-md q-mt-sm q-mb-xs">
{{ extension.name }}
</div>
<div
class="text-subtitle2 lt-md"
style="font-size: 9px; height: 34px"
>
{{ extension.shortDescription }}
</div>
{% endraw %}
</div>
</div>
</q-card-section>
<q-card-section>
<div>
<q-rating
class="gt-sm"
disable
size="2em"
:max="5"
color="primary"
></q-rating
><q-rating
class="lt-md"
size="1.5em"
:max="5"
color="primary"
></q-rating
><q-tooltip>Ratings coming soon</q-tooltip>
</div>
</q-card-section> </q-card-section>
<q-separator></q-separator> <q-separator></q-separator>
<q-card-actions> <q-card-actions>

View file

@ -23,7 +23,7 @@
<strong>{% raw %}{{ formattedBalance }} {% endraw %}</strong> <strong>{% raw %}{{ formattedBalance }} {% endraw %}</strong>
{{LNBITS_DENOMINATION}} {{LNBITS_DENOMINATION}}
<q-btn <q-btn
v-if="'{{user.admin}}' == 'True'" v-if="'{{user.super_user}}' == 'True'"
flat flat
round round
color="primary" color="primary"
@ -36,27 +36,16 @@
v-model="credit" v-model="credit"
> >
<q-input <q-input
v-if="'{{LNBITS_DENOMINATION}}' != 'sats'" filled
label="Amount to credit account" label="{{LNBITS_DENOMINATION}} to credit"
hint="Press Enter to credit account"
v-model="scope.value" v-model="scope.value"
dense dense
autofocus autofocus
mask="#.##" :mask="'{{LNBITS_DENOMINATION}}' != 'sats' ? '#.##' : '#'"
fill-mask="0" fill-mask="0"
reverse-fill-mask reverse-fill-mask
@keyup.enter="updateBalance(scope.value)" :step="'{{LNBITS_DENOMINATION}}' != 'sats' ? '0.01' : '1'"
>
<template v-slot:append>
<q-icon name="edit" />
</template>
</q-input>
<q-input
v-else
type="number"
label="Amount to credit account"
v-model="scope.value"
dense
autofocus
@keyup.enter="updateBalance(scope.value)" @keyup.enter="updateBalance(scope.value)"
> >
<template v-slot:append> <template v-slot:append>

View file

@ -17,7 +17,7 @@ from ..crud import delete_admin_settings, get_admin_settings, update_admin_setti
@core_app.get("/admin/api/v1/settings/") @core_app.get("/admin/api/v1/settings/")
async def api_get_settings( async def api_get_settings(
user: User = Depends(check_admin), # type: ignore user: User = Depends(check_admin),
) -> Optional[AdminSettings]: ) -> Optional[AdminSettings]:
admin_settings = await get_admin_settings(user.super_user) admin_settings = await get_admin_settings(user.super_user)
return admin_settings return admin_settings

View file

@ -12,6 +12,7 @@ import async_timeout
import httpx import httpx
import pyqrcode import pyqrcode
from fastapi import ( from fastapi import (
Body,
Depends, Depends,
Header, Header,
Query, Query,
@ -21,7 +22,6 @@ from fastapi import (
WebSocketDisconnect, WebSocketDisconnect,
) )
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger from loguru import logger
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.fields import Field from pydantic.fields import Field
@ -251,7 +251,7 @@ async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
) )
async def api_payments_create( async def api_payments_create(
wallet: WalletTypeInfo = Depends(require_invoice_key), wallet: WalletTypeInfo = Depends(require_invoice_key),
invoiceData: CreateInvoiceData = Body(...), # type: ignore invoiceData: CreateInvoiceData = Body(...),
): ):
if invoiceData.out is True and wallet.wallet_type == 0: if invoiceData.out is True and wallet.wallet_type == 0:
if not invoiceData.bolt11: if not invoiceData.bolt11:
@ -387,7 +387,7 @@ async def subscribe_wallet_invoices(request: Request, wallet: Wallet):
jdata = json.dumps(dict(data.dict(), pending=False)) jdata = json.dumps(dict(data.dict(), pending=False))
yield dict(data=jdata, event=typ) yield dict(data=jdata, event=typ)
except asyncio.CancelledError as e: except asyncio.CancelledError:
logger.debug(f"removing listener for wallet {uid}") logger.debug(f"removing listener for wallet {uid}")
api_invoice_listeners.pop(uid) api_invoice_listeners.pop(uid)
task.cancel() task.cancel()
@ -536,7 +536,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
params.update( params.update(
description_hash=hashlib.sha256( description_hash=hashlib.sha256(
data["metadata"].encode("utf-8") data["metadata"].encode()
).hexdigest() ).hexdigest()
) )
metadata = json.loads(data["metadata"]) metadata = json.loads(data["metadata"])

View file

@ -2,9 +2,8 @@ import asyncio
from http import HTTPStatus from http import HTTPStatus
from typing import Optional from typing import Optional
from fastapi import Request, status from fastapi import Depends, Query, Request, status
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.params import Depends, Query
from fastapi.responses import FileResponse, RedirectResponse from fastapi.responses import FileResponse, RedirectResponse
from fastapi.routing import APIRouter from fastapi.routing import APIRouter
from loguru import logger from loguru import logger
@ -49,9 +48,9 @@ async def home(request: Request, lightning: str = ""):
) )
async def extensions( async def extensions(
request: Request, request: Request,
user: User = Depends(check_user_exists), # type: ignore user: User = Depends(check_user_exists),
enable: str = Query(None), # type: ignore enable: str = Query(None),
disable: str = Query(None), # type: ignore disable: str = Query(None),
): ):
extension_to_enable = enable extension_to_enable = enable
extension_to_disable = disable extension_to_disable = disable
@ -103,10 +102,10 @@ nothing: create everything<br>
""", """,
) )
async def wallet( async def wallet(
request: Request = Query(None), # type: ignore request: Request = Query(None),
nme: Optional[str] = Query(None), # type: ignore nme: Optional[str] = Query(None),
usr: Optional[UUID4] = Query(None), # type: ignore usr: Optional[UUID4] = Query(None),
wal: Optional[UUID4] = Query(None), # type: ignore wal: Optional[UUID4] = Query(None),
): ):
user_id = usr.hex if usr else None user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None wallet_id = wal.hex if wal else None
@ -132,6 +131,8 @@ async def wallet(
) )
if user_id == settings.super_user or user_id in settings.lnbits_admin_users: if user_id == settings.super_user or user_id in settings.lnbits_admin_users:
user.admin = True user.admin = True
if user_id == settings.super_user:
user.super_user = True
if not wallet_id: if not wallet_id:
if user.wallets and not wallet_name: # type: ignore if user.wallets and not wallet_name: # type: ignore
@ -217,7 +218,7 @@ async def lnurl_full_withdraw_callback(request: Request):
@core_html_routes.get("/deletewallet", response_class=RedirectResponse) @core_html_routes.get("/deletewallet", response_class=RedirectResponse)
async def deletewallet(request: Request, wal: str = Query(...), usr: str = Query(...)): # type: ignore async def deletewallet(wal: str = Query(...), usr: str = Query(...)):
user = await get_user(usr) user = await get_user(usr)
user_wallet_ids = [u.id for u in user.wallets] # type: ignore user_wallet_ids = [u.id for u in user.wallets] # type: ignore
@ -313,7 +314,7 @@ async def manifest(usr: str):
@core_html_routes.get("/admin", response_class=HTMLResponse) @core_html_routes.get("/admin", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_admin)): # type: ignore async def index(request: Request, user: User = Depends(check_admin)):
WALLET = get_wallet_class() WALLET = get_wallet_class()
_, balance = await WALLET.status() _, balance = await WALLET.status()

View file

@ -1,11 +1,8 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Union
from cerberus import Validator # type: ignore from fastapi import Security, status
from fastapi import status
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.openapi.models import APIKey, APIKeyIn from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.params import Security
from fastapi.security.api_key import APIKeyHeader, APIKeyQuery from fastapi.security.api_key import APIKeyHeader, APIKeyQuery
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
from pydantic.types import UUID4 from pydantic.types import UUID4
@ -118,8 +115,8 @@ api_key_query = APIKeyQuery(
async def get_key_type( async def get_key_type(
r: Request, r: Request,
api_key_header: str = Security(api_key_header), # type: ignore api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query), # type: ignore api_key_query: str = Security(api_key_query),
) -> WalletTypeInfo: ) -> WalletTypeInfo:
# 0: admin # 0: admin
# 1: invoice # 1: invoice
@ -174,8 +171,8 @@ async def get_key_type(
async def require_admin_key( async def require_admin_key(
r: Request, r: Request,
api_key_header: str = Security(api_key_header), # type: ignore api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query), # type: ignore api_key_query: str = Security(api_key_query),
): ):
token = api_key_header or api_key_query token = api_key_header or api_key_query
@ -200,8 +197,8 @@ async def require_admin_key(
async def require_invoice_key( async def require_invoice_key(
r: Request, r: Request,
api_key_header: str = Security(api_key_header), # type: ignore api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query), # type: ignore api_key_query: str = Security(api_key_query),
): ):
token = api_key_header or api_key_query token = api_key_header or api_key_query

View file

@ -1,6 +1,6 @@
{ {
"name": "Bleskomat", "name": "Bleskomat",
"short_description": "Connect a Bleskomat ATM to an lnbits", "short_description": "Connect a Bleskomat ATM to an lnbits",
"icon": "money", "tile": "/bleskomat/static/image/bleskomat.png",
"contributors": ["chill117"] "contributors": ["chill117"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -1,6 +1,6 @@
{ {
"name": "Bolt Cards", "name": "Bolt Cards",
"short_description": "Self custody Bolt Cards with one time LNURLw", "short_description": "Self custody Bolt Cards with one time LNURLw",
"icon": "payment", "tile": "/boltcards/static/image/boltcard.png",
"contributors": ["iwarpbtc", "arcbtc", "leesalminen"] "contributors": ["iwarpbtc", "arcbtc", "leesalminen"]
} }

View file

@ -213,7 +213,7 @@ async def lnurlp_callback(
memo=f"Refund {hit_id}", memo=f"Refund {hit_id}",
unhashed_description=LnurlPayMetadata( unhashed_description=LnurlPayMetadata(
json.dumps([["text/plain", "Refund"]]) json.dumps([["text/plain", "Refund"]])
).encode("utf-8"), ).encode(),
extra={"refund": hit_id}, extra={"refund": hit_id},
) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -15,6 +16,14 @@ def boltz_renderer():
return template_renderer(["lnbits/extensions/boltz/templates"]) return template_renderer(["lnbits/extensions/boltz/templates"])
boltz_static_files = [
{
"path": "/boltz/static",
"app": StaticFiles(directory="lnbits/extensions/boltz/static"),
"name": "boltz_static",
}
]
from .tasks import check_for_pending_swaps, wait_for_paid_invoices from .tasks import check_for_pending_swaps, wait_for_paid_invoices
from .views import * # noqa from .views import * # noqa
from .views_api import * # noqa from .views_api import * # noqa

View file

@ -55,7 +55,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
raise raise
refund_privkey = ec.PrivateKey(os.urandom(32), True, net) refund_privkey = ec.PrivateKey(os.urandom(32), True, net)
refund_pubkey_hex = bytes.hex(refund_privkey.sec()).decode("UTF-8") refund_pubkey_hex = bytes.hex(refund_privkey.sec()).decode()
res = req_wrap( res = req_wrap(
"post", "post",
@ -120,7 +120,7 @@ async def create_reverse_swap(
return False return False
claim_privkey = ec.PrivateKey(os.urandom(32), True, net) claim_privkey = ec.PrivateKey(os.urandom(32), True, net)
claim_pubkey_hex = bytes.hex(claim_privkey.sec()).decode("UTF-8") claim_pubkey_hex = bytes.hex(claim_privkey.sec()).decode()
preimage = os.urandom(32) preimage = os.urandom(32)
preimage_hash = sha256(preimage).hexdigest() preimage_hash = sha256(preimage).hexdigest()

View file

@ -1,6 +1,6 @@
{ {
"name": "Boltz", "name": "Boltz",
"short_description": "Perform onchain/offchain swaps", "short_description": "Perform onchain/offchain swaps",
"icon": "swap_horiz", "tile": "/boltz/static/image/boltz.png",
"contributors": ["dni"] "contributors": ["dni"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -1,7 +1,7 @@
{ {
"name": "Cashu", "name": "Cashu",
"short_description": "Ecash mint and wallet", "short_description": "Ecash mint and wallet",
"icon": "account_balance", "tile": "/cashu/static/image/cashu.png",
"contributors": ["calle", "vlad", "arcbtc"], "contributors": ["calle", "vlad", "arcbtc"],
"hidden": false "hidden": false
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,7 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@ -18,7 +17,7 @@ templates = Jinja2Templates(directory="templates")
@cashu_ext.get("/", response_class=HTMLResponse) @cashu_ext.get("/", response_class=HTMLResponse)
async def index( async def index(
request: Request, request: Request,
user: User = Depends(check_user_exists), # type: ignore user: User = Depends(check_user_exists),
): ):
return cashu_renderer().TemplateResponse( return cashu_renderer().TemplateResponse(
"cashu/index.html", {"request": request, "user": user.dict()} "cashu/index.html", {"request": request, "user": user.dict()}

View file

@ -1,10 +1,7 @@
import json
import math import math
from http import HTTPStatus from http import HTTPStatus
from typing import Dict, List, Union from typing import Dict, List, Union
import httpx
# -------- cashu imports # -------- cashu imports
from cashu.core.base import ( from cashu.core.base import (
BlindedSignature, BlindedSignature,
@ -17,14 +14,10 @@ from cashu.core.base import (
MeltRequest, MeltRequest,
MintRequest, MintRequest,
PostSplitResponse, PostSplitResponse,
Proof,
SplitRequest, SplitRequest,
) )
from fastapi import Query from fastapi import Depends, Query
from fastapi.params import Depends
from lnurl import decode as decode_lnurl
from loguru import logger from loguru import logger
from secp256k1 import PublicKey
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits import bolt11 from lnbits import bolt11
@ -35,7 +28,6 @@ from lnbits.core.services import (
fee_reserve, fee_reserve,
pay_invoice, pay_invoice,
) )
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from lnbits.wallets.base import PaymentStatus from lnbits.wallets.base import PaymentStatus
@ -63,7 +55,7 @@ if not LIGHTNING:
@cashu_ext.get("/api/v1/mints", status_code=HTTPStatus.OK) @cashu_ext.get("/api/v1/mints", status_code=HTTPStatus.OK)
async def api_cashus( async def api_cashus(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
): ):
""" """
Get all mints of this wallet. Get all mints of this wallet.
@ -80,7 +72,7 @@ async def api_cashus(
@cashu_ext.post("/api/v1/mints", status_code=HTTPStatus.CREATED) @cashu_ext.post("/api/v1/mints", status_code=HTTPStatus.CREATED)
async def api_cashu_create( async def api_cashu_create(
data: Cashu, data: Cashu,
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore wallet: WalletTypeInfo = Depends(get_key_type),
): ):
""" """
Create a new mint for this wallet. Create a new mint for this wallet.
@ -98,7 +90,7 @@ async def api_cashu_create(
@cashu_ext.delete("/api/v1/mints/{cashu_id}") @cashu_ext.delete("/api/v1/mints/{cashu_id}")
async def api_cashu_delete( async def api_cashu_delete(
cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) # type: ignore cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
): ):
""" """
Delete an existing cashu mint. Delete an existing cashu mint.

View file

@ -1,7 +1,7 @@
{ {
"name": "Streamer Copilot", "name": "Streamer Copilot",
"short_description": "Video tips/animations/webhooks", "short_description": "Video tips/animations/webhooks",
"icon": "face", "tile": "/copilot/static/bitcoin-streaming.png",
"contributors": [ "contributors": [
"arcbtc" "arcbtc"
] ]

View file

@ -75,7 +75,7 @@ async def lnurl_callback(
memo=cp.lnurl_title, memo=cp.lnurl_title,
unhashed_description=( unhashed_description=(
LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]])) LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]]))
).encode("utf-8"), ).encode(),
extra={"tag": "copilot", "copilotid": cp.id, "comment": comment}, extra={"tag": "copilot", "copilotid": cp.id, "comment": comment},
) )
payResponse = {"pr": payment_request, "routes": []} payResponse = {"pr": payment_request, "routes": []}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,7 +1,6 @@
from typing import List from typing import List
from fastapi import Request, WebSocket, WebSocketDisconnect from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse # type: ignore from starlette.responses import HTMLResponse # type: ignore
@ -9,15 +8,12 @@ from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from . import copilot_ext, copilot_renderer from . import copilot_ext, copilot_renderer
from .crud import get_copilot
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@copilot_ext.get("/", response_class=HTMLResponse) @copilot_ext.get("/", response_class=HTMLResponse)
async def index( async def index(request: Request, user: User = Depends(check_user_exists)):
request: Request, user: User = Depends(check_user_exists) # type: ignore
):
return copilot_renderer().TemplateResponse( return copilot_renderer().TemplateResponse(
"copilot/index.html", {"request": request, "user": user.dict()} "copilot/index.html", {"request": request, "user": user.dict()}
) )

View file

@ -1,8 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Query, Request
from fastapi.param_functions import Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.services import websocketUpdater from lnbits.core.services import websocketUpdater
@ -22,9 +20,7 @@ from .models import CreateCopilotData
@copilot_ext.get("/api/v1/copilot") @copilot_ext.get("/api/v1/copilot")
async def api_copilots_retrieve( async def api_copilots_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
req: Request, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
wallet_user = wallet.wallet.user wallet_user = wallet.wallet.user
copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)] copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)]
try: try:
@ -37,7 +33,7 @@ async def api_copilots_retrieve(
async def api_copilot_retrieve( async def api_copilot_retrieve(
req: Request, req: Request,
copilot_id: str = Query(None), copilot_id: str = Query(None),
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore wallet: WalletTypeInfo = Depends(get_key_type),
): ):
copilot = await get_copilot(copilot_id) copilot = await get_copilot(copilot_id)
if not copilot: if not copilot:
@ -54,7 +50,7 @@ async def api_copilot_retrieve(
async def api_copilot_create_or_update( async def api_copilot_create_or_update(
data: CreateCopilotData, data: CreateCopilotData,
copilot_id: str = Query(None), copilot_id: str = Query(None),
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
data.user = wallet.wallet.user data.user = wallet.wallet.user
data.wallet = wallet.wallet.id data.wallet = wallet.wallet.id
@ -68,7 +64,7 @@ async def api_copilot_create_or_update(
@copilot_ext.delete("/api/v1/copilot/{copilot_id}") @copilot_ext.delete("/api/v1/copilot/{copilot_id}")
async def api_copilot_delete( async def api_copilot_delete(
copilot_id: str = Query(None), copilot_id: str = Query(None),
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
copilot = await get_copilot(copilot_id) copilot = await get_copilot(copilot_id)

View file

@ -1,6 +1,6 @@
{ {
"name": "Discord Bot", "name": "Discord Bot",
"short_description": "Generate users and wallets", "short_description": "Generate users and wallets",
"icon": "person_add", "tile": "/discordbot/static/image/discordbot.png",
"contributors": ["bitcoingamer21"] "contributors": ["bitcoingamer21"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -1,5 +1,4 @@
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
from lnbits.core.models import User from lnbits.core.models import User
@ -9,9 +8,7 @@ from . import discordbot_ext, discordbot_renderer
@discordbot_ext.get("/", response_class=HTMLResponse) @discordbot_ext.get("/", response_class=HTMLResponse)
async def index( async def index(request: Request, user: User = Depends(check_user_exists)):
request: Request, user: User = Depends(check_user_exists) # type: ignore
):
return discordbot_renderer().TemplateResponse( return discordbot_renderer().TemplateResponse(
"discordbot/index.html", {"request": request, "user": user.dict()} "discordbot/index.html", {"request": request, "user": user.dict()}
) )

View file

@ -1,7 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Query from fastapi import Depends, Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core import update_user_extension from lnbits.core import update_user_extension
@ -28,16 +27,14 @@ from .models import CreateUserData, CreateUserWallet
@discordbot_ext.get("/api/v1/users", status_code=HTTPStatus.OK) @discordbot_ext.get("/api/v1/users", status_code=HTTPStatus.OK)
async def api_discordbot_users( async def api_discordbot_users(
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore wallet: WalletTypeInfo = Depends(get_key_type),
): ):
user_id = wallet.wallet.user user_id = wallet.wallet.user
return [user.dict() for user in await get_discordbot_users(user_id)] return [user.dict() for user in await get_discordbot_users(user_id)]
@discordbot_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK) @discordbot_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK)
async def api_discordbot_user( async def api_discordbot_user(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) user = await get_discordbot_user(user_id)
if user: if user:
return user.dict() return user.dict()
@ -45,7 +42,7 @@ async def api_discordbot_user(
@discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED) @discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED)
async def api_discordbot_users_create( async def api_discordbot_users_create(
data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
user = await create_discordbot_user(data) user = await create_discordbot_user(data)
full = user.dict() full = user.dict()
@ -57,7 +54,7 @@ async def api_discordbot_users_create(
@discordbot_ext.delete("/api/v1/users/{user_id}") @discordbot_ext.delete("/api/v1/users/{user_id}")
async def api_discordbot_users_delete( async def api_discordbot_users_delete(
user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore user_id, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
user = await get_discordbot_user(user_id) user = await get_discordbot_user(user_id)
if not user: if not user:
@ -89,7 +86,7 @@ async def api_discordbot_activate_extension(
@discordbot_ext.post("/api/v1/wallets") @discordbot_ext.post("/api/v1/wallets")
async def api_discordbot_wallets_create( async def api_discordbot_wallets_create(
data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
user = await create_discordbot_wallet( user = await create_discordbot_wallet(
user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id
@ -99,7 +96,7 @@ async def api_discordbot_wallets_create(
@discordbot_ext.get("/api/v1/wallets") @discordbot_ext.get("/api/v1/wallets")
async def api_discordbot_wallets( async def api_discordbot_wallets(
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore wallet: WalletTypeInfo = Depends(get_key_type),
): ):
admin_id = wallet.wallet.user admin_id = wallet.wallet.user
return await get_discordbot_wallets(admin_id) return await get_discordbot_wallets(admin_id)
@ -107,21 +104,21 @@ async def api_discordbot_wallets(
@discordbot_ext.get("/api/v1/transactions/{wallet_id}") @discordbot_ext.get("/api/v1/transactions/{wallet_id}")
async def api_discordbot_wallet_transactions( async def api_discordbot_wallet_transactions(
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
return await get_discordbot_wallet_transactions(wallet_id) return await get_discordbot_wallet_transactions(wallet_id)
@discordbot_ext.get("/api/v1/wallets/{user_id}") @discordbot_ext.get("/api/v1/wallets/{user_id}")
async def api_discordbot_users_wallets( async def api_discordbot_users_wallets(
user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore user_id, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
return await get_discordbot_users_wallets(user_id) return await get_discordbot_users_wallets(user_id)
@discordbot_ext.delete("/api/v1/wallets/{wallet_id}") @discordbot_ext.delete("/api/v1/wallets/{wallet_id}")
async def api_discordbot_wallets_delete( async def api_discordbot_wallets_delete(
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
get_wallet = await get_discordbot_wallet(wallet_id) get_wallet = await get_discordbot_wallet(wallet_id)
if not get_wallet: if not get_wallet:

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -11,6 +12,14 @@ db = Database("ext_events")
events_ext: APIRouter = APIRouter(prefix="/events", tags=["Events"]) events_ext: APIRouter = APIRouter(prefix="/events", tags=["Events"])
events_static_files = [
{
"path": "/events/static",
"app": StaticFiles(packages=[("lnbits", "extensions/events/static")]),
"name": "events_static",
}
]
def events_renderer(): def events_renderer():
return template_renderer(["lnbits/extensions/events/templates"]) return template_renderer(["lnbits/extensions/events/templates"])

View file

@ -1,6 +1,6 @@
{ {
"name": "Events", "name": "Events",
"short_description": "Sell and register event tickets", "short_description": "Sell and register event tickets",
"icon": "local_activity", "tile": "/events/static/image/events.png",
"contributors": ["benarc"] "contributors": ["benarc"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View file

@ -1,15 +1,6 @@
import asyncio import asyncio
import json
from http import HTTPStatus
from urllib.parse import urlparse
import httpx
from fastapi import HTTPException
from loguru import logger
from lnbits import bolt11
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.core.services import pay_invoice
from lnbits.extensions.events.models import CreateTicket from lnbits.extensions.events.models import CreateTicket
from lnbits.helpers import get_current_extension_name from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
@ -29,11 +20,17 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment) -> None:
# (avoid loops) # (avoid loops)
if ( if (
"events" == payment.extra.get("tag") payment.extra
and "events" == payment.extra.get("tag")
and payment.extra.get("name") and payment.extra.get("name")
and payment.extra.get("email") and payment.extra.get("email")
): ):
CreateTicket.name = str(payment.extra.get("name")) await api_ticket_send_ticket(
CreateTicket.email = str(payment.extra.get("email")) payment.memo,
await api_ticket_send_ticket(payment.memo, payment.payment_hash, CreateTicket) payment.payment_hash,
CreateTicket(
name=str(payment.extra.get("name")),
email=str(payment.extra.get("email")),
),
)
return return

View file

@ -1,8 +1,7 @@
from datetime import date, datetime from datetime import date, datetime
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse

View file

@ -1,10 +1,7 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi.param_functions import Query from fastapi import Depends, Query
from fastapi.params import Depends
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.requests import Request
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
@ -38,7 +35,8 @@ async def api_events(
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [event.dict() for event in await get_events(wallet_ids)] return [event.dict() for event in await get_events(wallet_ids)]
@ -92,7 +90,8 @@ async def api_tickets(
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [ticket.dict() for ticket in await get_tickets(wallet_ids)] return [ticket.dict() for ticket in await get_tickets(wallet_ids)]
@ -119,26 +118,32 @@ async def api_ticket_make_ticket(event_id, name, email):
@events_ext.post("/api/v1/tickets/{event_id}/{payment_hash}") @events_ext.post("/api/v1/tickets/{event_id}/{payment_hash}")
async def api_ticket_send_ticket(event_id, payment_hash, data: CreateTicket): async def api_ticket_send_ticket(event_id, payment_hash, data: CreateTicket):
event = await get_event(event_id) event = await get_event(event_id)
try: if not event:
status = await api_payment(payment_hash) raise HTTPException(
if status["paid"]: status_code=HTTPStatus.NOT_FOUND,
ticket = await create_ticket( detail=f"Event could not be fetched.",
payment_hash=payment_hash, )
wallet=event.wallet,
event=event_id, status = await api_payment(payment_hash)
name=data.name, if status["paid"]:
email=data.email,
exists = await get_ticket(payment_hash)
if exists:
return {"paid": True, "ticket_id": exists.id}
ticket = await create_ticket(
payment_hash=payment_hash,
wallet=event.wallet,
event=event_id,
name=data.name,
email=data.email,
)
if not ticket:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Event could not be fetched.",
) )
return {"paid": True, "ticket_id": ticket.id}
if not ticket:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Event could not be fetched.",
)
return {"paid": True, "ticket_id": ticket.id}
except Exception:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Not paid")
return {"paid": False} return {"paid": False}

View file

@ -1,6 +1,6 @@
{ {
"name": "Build your own!!", "name": "Build your own!!",
"short_description": "Join us, make an extension", "short_description": "Join us, make an extension",
"icon": "info", "tile": "/cashu/static/image/tile.png",
"contributors": ["github_username"] "contributors": ["github_username"]
} }

View file

@ -1,5 +1,4 @@
from fastapi import FastAPI, Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@ -14,7 +13,7 @@ templates = Jinja2Templates(directory="templates")
@example_ext.get("/", response_class=HTMLResponse) @example_ext.get("/", response_class=HTMLResponse)
async def index( async def index(
request: Request, request: Request,
user: User = Depends(check_user_exists), # type: ignore user: User = Depends(check_user_exists),
): ):
return example_renderer().TemplateResponse( return example_renderer().TemplateResponse(
"example/index.html", {"request": request, "user": user.dict()} "example/index.html", {"request": request, "user": user.dict()}

View file

@ -5,11 +5,9 @@ from fastapi.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
db = Database("ext_gerty") db = Database("ext_gerty")
gerty_static_files = [ gerty_static_files = [
{ {
"path": "/gerty/static", "path": "/gerty/static",

View file

@ -1,6 +1,6 @@
{ {
"name": "Gerty", "name": "Gerty",
"short_description": "Desktop bitcoin Assistant", "short_description": "Desktop bitcoin Assistant",
"icon": "sentiment_satisfied", "tile": "/gerty/static/gerty.png",
"contributors": ["arcbtc", "blackcoffeebtc"] "contributors": ["arcbtc", "blackcoffeebtc"]
} }

View file

@ -50,11 +50,12 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
return gerty return gerty
async def update_gerty(gerty_id: str, **kwargs) -> Gerty: async def update_gerty(gerty_id: str, **kwargs) -> Optional[Gerty]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute( await db.execute(
f"UPDATE gerty.gertys SET {q} WHERE id = ?", (*kwargs.values(), gerty_id) f"UPDATE gerty.gertys SET {q} WHERE id = ?", (*kwargs.values(), gerty_id)
) )
return await get_gerty(gerty_id) return await get_gerty(gerty_id)
@ -82,7 +83,7 @@ async def delete_gerty(gerty_id: str) -> None:
#############MEMPOOL########### #############MEMPOOL###########
async def get_mempool_info(endPoint: str, gerty) -> Optional[Mempool]: async def get_mempool_info(endPoint: str, gerty) -> dict:
logger.debug(endPoint) logger.debug(endPoint)
endpoints = MempoolEndpoint() endpoints = MempoolEndpoint()
url = "" url = ""
@ -116,7 +117,7 @@ async def get_mempool_info(endPoint: str, gerty) -> Optional[Mempool]:
mempool_id, mempool_id,
json.dumps(response.json()), json.dumps(response.json()),
endPoint, endPoint,
int(time.time()), time.time(),
gerty.mempool_endpoint, gerty.mempool_endpoint,
), ),
) )
@ -128,7 +129,7 @@ async def get_mempool_info(endPoint: str, gerty) -> Optional[Mempool]:
"UPDATE gerty.mempool SET data = ?, time = ? WHERE endpoint = ? AND mempool_endpoint = ?", "UPDATE gerty.mempool SET data = ?, time = ? WHERE endpoint = ? AND mempool_endpoint = ?",
( (
json.dumps(response.json()), json.dumps(response.json()),
int(time.time()), time.time(),
endPoint, endPoint,
gerty.mempool_endpoint, gerty.mempool_endpoint,
), ),

View file

@ -3,15 +3,16 @@ import os
import random import random
import textwrap import textwrap
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List
import httpx import httpx
from loguru import logger from loguru import logger
from lnbits.core.crud import get_user, get_wallet_for_key from lnbits.core.crud import get_wallet_for_key
from lnbits.settings import settings from lnbits.settings import settings
from lnbits.utils.exchange_rates import satoshis_amount_as_fiat from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
from .crud import get_gerty, get_mempool_info from .crud import get_mempool_info
from .number_prefixer import * from .number_prefixer import *
@ -24,8 +25,8 @@ def get_percent_difference(current, previous, precision=3):
def get_text_item_dict( def get_text_item_dict(
text: str, text: str,
font_size: int, font_size: int,
x_pos: int = None, x_pos: int = -1,
y_pos: int = None, y_pos: int = -1,
gerty_type: str = "Gerty", gerty_type: str = "Gerty",
): ):
# Get line size by font size # Get line size by font size
@ -63,13 +64,41 @@ def get_text_item_dict(
# logger.debug('multilineText') # logger.debug('multilineText')
# logger.debug(multilineText) # logger.debug(multilineText)
text = {"value": multilineText, "size": font_size} data_text = {"value": multilineText, "size": font_size}
if x_pos is None and y_pos is None: if x_pos == -1 and y_pos == -1:
text["position"] = "center" data_text["position"] = "center"
else: else:
text["x"] = x_pos data_text["x"] = x_pos if x_pos > 0 else 0
text["y"] = y_pos data_text["y"] = y_pos if x_pos > 0 else 0
return text return data_text
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
else:
return ["st", "nd", "rd"][dayNumber % 10 - 1]
def get_time_remaining(seconds, granularity=2):
intervals = (
# ('weeks', 604800), # 60 * 60 * 24 * 7
("days", 86400), # 60 * 60 * 24
("hours", 3600), # 60 * 60
("minutes", 60),
("seconds", 1),
)
result = []
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip("s")
result.append("{} {}".format(round(value), name))
return ", ".join(result[:granularity])
# format a number for nice display output # format a number for nice display output
@ -293,8 +322,7 @@ def get_next_update_time(sleep_time_seconds: int = 0, utc_offset: int = 0):
def gerty_should_sleep(utc_offset: int = 0): def gerty_should_sleep(utc_offset: int = 0):
utc_now = datetime.utcnow() utc_now = datetime.utcnow()
local_time = utc_now + timedelta(hours=utc_offset) local_time = utc_now + timedelta(hours=utc_offset)
hours = local_time.strftime("%H") hours = int(local_time.strftime("%H"))
hours = int(hours)
if hours >= 22 and hours <= 23: if hours >= 22 and hours <= 23:
return True return True
else: else:
@ -352,23 +380,17 @@ async def get_mining_stat(stat_slug: str, gerty):
async def api_get_mining_stat(stat_slug: str, gerty): async def api_get_mining_stat(stat_slug: str, gerty):
stat = "" stat = {}
if stat_slug == "mining_current_hash_rate": if stat_slug == "mining_current_hash_rate":
async with httpx.AsyncClient() as client: r = await get_mempool_info("hashrate_1m", gerty)
r = await get_mempool_info("hashrate_1m", gerty) data = r
data = r stat["current"] = data["currentHashrate"]
stat = {} stat["1w"] = data["hashrates"][len(data["hashrates"]) - 7]["avgHashrate"]
stat["current"] = data["currentHashrate"]
stat["1w"] = data["hashrates"][len(data["hashrates"]) - 7]["avgHashrate"]
elif stat_slug == "mining_current_difficulty": elif stat_slug == "mining_current_difficulty":
async with httpx.AsyncClient() as client: r = await get_mempool_info("hashrate_1m", gerty)
r = await get_mempool_info("hashrate_1m", gerty) data = r
data = r stat["current"] = data["currentDifficulty"]
stat = {} stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2]["difficulty"]
stat["current"] = data["currentDifficulty"]
stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2][
"difficulty"
]
return stat return stat
@ -384,7 +406,7 @@ async def get_satoshi():
quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)] quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
# logger.debug(quote.text) # logger.debug(quote.text)
if len(quote["text"]) > maxQuoteLength: if len(quote["text"]) > maxQuoteLength:
logger.debug("Quote is too long, getting another") logger.trace("Quote is too long, getting another")
return await get_satoshi() return await get_satoshi()
else: else:
return quote return quote
@ -399,15 +421,16 @@ def get_screen_slug_by_index(index: int, screens_list):
# Get a list of text items for the screen number # Get a list of text items for the screen number
async def get_screen_data(screen_num: int, screens_list: dict, gerty): async def get_screen_data(screen_num: int, screens_list: list, gerty):
screen_slug = get_screen_slug_by_index(screen_num, screens_list) screen_slug = get_screen_slug_by_index(screen_num, screens_list)
# first get the relevant slug from the display_preferences # first get the relevant slug from the display_preferences
areas = [] areas: List = []
title = "" title = ""
if screen_slug == "dashboard": if screen_slug == "dashboard":
title = gerty.name title = gerty.name
areas = await get_dashboard(gerty) areas = await get_dashboard(gerty)
if screen_slug == "lnbits_wallets_balance": if screen_slug == "lnbits_wallets_balance":
wallets = await get_lnbits_wallet_balances(gerty) wallets = await get_lnbits_wallet_balances(gerty)
@ -505,10 +528,10 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
title = "Lightning Network" title = "Lightning Network"
areas = await get_lightning_stats(gerty) areas = await get_lightning_stats(gerty)
data = {} data = {
data["title"] = title "title": title,
data["areas"] = areas "areas": areas,
}
return data return data
@ -570,7 +593,7 @@ async def get_dashboard(gerty):
text = [] text = []
text.append( text.append(
get_text_item_dict( get_text_item_dict(
text=await get_time_remaining_next_difficulty_adjustment(gerty), text=await get_time_remaining_next_difficulty_adjustment(gerty) or "0",
font_size=15, font_size=15,
gerty_type=gerty.type, gerty_type=gerty.type,
) )
@ -602,7 +625,7 @@ async def get_lnbits_wallet_balances(gerty):
return wallets return wallets
async def get_placeholder_text(): async def get_placeholder_text(gerty):
return [ return [
get_text_item_dict( get_text_item_dict(
text="Some placeholder text", text="Some placeholder text",
@ -810,14 +833,14 @@ async def get_time_remaining_next_difficulty_adjustment(gerty):
r = await get_mempool_info("difficulty_adjustment", gerty) r = await get_mempool_info("difficulty_adjustment", gerty)
stat = r["remainingTime"] stat = r["remainingTime"]
time = get_time_remaining(stat / 1000, 3) time = get_time_remaining(stat / 1000, 3)
return time return time
async def get_mempool_stat(stat_slug: str, gerty): async def get_mempool_stat(stat_slug: str, gerty):
text = [] text = []
if isinstance(gerty.mempool_endpoint, str): if isinstance(gerty.mempool_endpoint, str):
if stat_slug == "mempool_tx_count": if stat_slug == "mempool_tx_count":
r = get_mempool_info("mempool", gerty) r = await get_mempool_info("mempool", gerty)
if stat_slug == "mempool_tx_count": if stat_slug == "mempool_tx_count":
stat = round(r["count"]) stat = round(r["count"])
text.append( text.append(
@ -921,31 +944,3 @@ async def get_mempool_stat(stat_slug: str, gerty):
) )
) )
return text return text
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
else:
return ["st", "nd", "rd"][dayNumber % 10 - 1]
def get_time_remaining(seconds, granularity=2):
intervals = (
# ('weeks', 604800), # 60 * 60 * 24 * 7
("days", 86400), # 60 * 60 * 24
("hours", 3600), # 60 * 60
("minutes", 60),
("seconds", 1),
)
result = []
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip("s")
result.append("{} {}".format(round(value), name))
return ", ".join(result[:granularity])

View file

@ -1,5 +1,4 @@
from sqlite3 import Row from sqlite3 import Row
from typing import Optional
from fastapi import Query from fastapi import Query
from pydantic import BaseModel from pydantic import BaseModel

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -32,7 +32,10 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</q-card> </q-card>
</div> </div>
<div class="q-pa-md row items-start q-gutter-md" v-if="lnbits_wallets_balance"> <div
class="q-pa-md row items-start q-gutter-md"
v-if="lnbits_wallets_balance[0]"
>
<q-card <q-card
class="q-pa-sm" class="q-pa-sm"
v-for="(wallet, t) in lnbits_wallets_balance" v-for="(wallet, t) in lnbits_wallets_balance"
@ -49,7 +52,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
<div <div
class="q-pa-md row items-start q-gutter-md" class="q-pa-md row items-start q-gutter-md"
v-if="dashboard_onchain || dashboard_mining || lightning_dashboard" v-if="dashboard_onchain[0] || dashboard_mining[0] || lightning_dashboard[0] || url_checker[0]"
> >
<q-card <q-card
class="q-pa-sm" class="q-pa-sm"
@ -67,7 +70,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</q-card-section> </q-card-section>
</q-card> </q-card>
<q-card class="q-pa-sm" v-if="dashboard_mining" unelevated class="q-pa-sm"> <q-card class="q-pa-sm" v-if="dashboard_mining[0]" unelevated class="q-pa-sm">
<q-card-section> <q-card-section>
<div class="text-h6">Mining</div> <div class="text-h6">Mining</div>
</q-card-section> </q-card-section>
@ -78,7 +81,12 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</q-card-section> </q-card-section>
</q-card> </q-card>
<q-card class="q-pa-sm" v-if="lightning_dashboard" unelevated class="q-pa-sm"> <q-card
class="q-pa-sm"
v-if="lightning_dashboard[0]"
unelevated
class="q-pa-sm"
>
<q-card-section> <q-card-section>
<div class="text-h6">Lightning (Last 7 days)</div> <div class="text-h6">Lightning (Last 7 days)</div>
</q-card-section> </q-card-section>
@ -88,7 +96,6 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</p> </p>
</q-card-section> </q-card-section>
</q-card> </q-card>
<q-card class="q-pa-sm" v-if="url_checker" unelevated class="q-pa-sm"> <q-card class="q-pa-sm" v-if="url_checker" unelevated class="q-pa-sm">
<q-card-section> <q-card-section>
<div class="text-h6">Servers to check</div> <div class="text-h6">Servers to check</div>
@ -153,7 +160,13 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
lnbits_wallets_balance: {}, lnbits_wallets_balance: {},
dashboard_onchain: {}, dashboard_onchain: {},
fun_satoshi_quotes: {}, fun_satoshi_quotes: {},
fun_exchange_market_rate: {}, fun_exchange_market_rate: {
unit: ''
},
dashboard_mining: {},
lightning_dashboard: {},
url_checker: {},
dashboard_mining: {},
gerty: [], gerty: [],
gerty_id: `{{gerty}}`, gerty_id: `{{gerty}}`,
gertyname: '', gertyname: '',
@ -182,7 +195,6 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
} }
} }
console.log(this.gerty)
for (let i = 0; i < this.gerty.length; i++) { for (let i = 0; i < this.gerty.length; i++) {
if (this.gerty[i].screen.group == 'lnbits_wallets_balance') { if (this.gerty[i].screen.group == 'lnbits_wallets_balance') {
for (let q = 0; q < this.gerty[i].screen.areas.length; q++) { for (let q = 0; q < this.gerty[i].screen.areas.length; q++) {

View file

@ -1,10 +1,7 @@
import json
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@ -13,7 +10,6 @@ from lnbits.decorators import check_user_exists
from . import gerty_ext, gerty_renderer from . import gerty_ext, gerty_renderer
from .crud import get_gerty from .crud import get_gerty
from .views_api import api_gerty_json
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")

View file

@ -1,24 +1,12 @@
import json import json
import math
import os
import random
import time
from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
import httpx from fastapi import Depends, Query
from fastapi import Query
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from lnurl import decode as decode_lnurl
from loguru import logger from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user, get_wallet_for_key from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment, api_wallet
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
from . import gerty_ext from . import gerty_ext
from .crud import ( from .crud import (
@ -29,8 +17,14 @@ from .crud import (
get_mempool_info, get_mempool_info,
update_gerty, update_gerty,
) )
from .helpers import * from .helpers import (
from .models import Gerty, MempoolEndpoint gerty_should_sleep,
get_next_update_time,
get_satoshi,
get_screen_data,
get_screen_slug_by_index,
)
from .models import Gerty
@gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK) @gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK)
@ -39,7 +33,8 @@ async def api_gertys(
): ):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [gerty.dict() for gerty in await get_gertys(wallet_ids)] return [gerty.dict() for gerty in await get_gertys(wallet_ids)]
@ -51,7 +46,6 @@ async def api_link_create_or_update(
wallet: WalletTypeInfo = Depends(get_key_type), wallet: WalletTypeInfo = Depends(get_key_type),
gerty_id: str = Query(None), gerty_id: str = Query(None),
): ):
logger.debug(data)
if gerty_id: if gerty_id:
gerty = await get_gerty(gerty_id) gerty = await get_gerty(gerty_id)
if not gerty: if not gerty:
@ -67,6 +61,9 @@ async def api_link_create_or_update(
data.wallet = wallet.wallet.id data.wallet = wallet.wallet.id
gerty = await update_gerty(gerty_id, **data.dict()) gerty = await update_gerty(gerty_id, **data.dict())
assert gerty, HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist"
)
else: else:
gerty = await create_gerty(wallet_id=wallet.wallet.id, data=data) gerty = await create_gerty(wallet_id=wallet.wallet.id, data=data)
@ -93,11 +90,11 @@ async def api_gerty_delete(
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK) @gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi(): async def api_gerty_satoshi():
return await get_satoshi return await get_satoshi()
@gerty_ext.get("/api/v1/gerty/pages/{gerty_id}/{p}") @gerty_ext.get("/api/v1/gerty/pages/{gerty_id}/{p}")
async def api_gerty_json(gerty_id: str, p: int = None): # page number async def api_gerty_json(gerty_id: str, p: int = 0): # page number
gerty = await get_gerty(gerty_id) gerty = await get_gerty(gerty_id)
if not gerty: if not gerty:
@ -117,7 +114,7 @@ async def api_gerty_json(gerty_id: str, p: int = None): # page number
enabled_screen_count += 1 enabled_screen_count += 1
enabled_screens.append(screen_slug) enabled_screens.append(screen_slug)
logger.debug("Screeens " + str(enabled_screens)) logger.debug("Screens " + str(enabled_screens))
data = await get_screen_data(p, enabled_screens, gerty) data = await get_screen_data(p, enabled_screens, gerty)
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1 next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1

View file

@ -1,4 +1,5 @@
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -12,4 +13,12 @@ def hivemind_renderer():
return template_renderer(["lnbits/extensions/hivemind/templates"]) return template_renderer(["lnbits/extensions/hivemind/templates"])
hivemind_static_files = [
{
"path": "/hivemind/static",
"app": StaticFiles(packages=[("lnbits", "extensions/hivemind/static")]),
"name": "hivemind_static",
}
]
from .views import * # noqa from .views import * # noqa

View file

@ -1,6 +1,6 @@
{ {
"name": "Hivemind", "name": "Hivemind",
"short_description": "Make cheap talk expensive!", "short_description": "Make cheap talk expensive!",
"icon": "batch_prediction", "tile": "/hivemind/static/image/hivemind.png",
"contributors": ["fiatjaf"] "contributors": ["fiatjaf"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,6 +1,6 @@
{ {
"name": "Invoices", "name": "Invoices",
"short_description": "Create invoices for your clients.", "short_description": "Create invoices for your clients.",
"icon": "request_quote", "tile": "/invoices/static/image/invoices.png",
"contributors": ["leesalminen"] "contributors": ["leesalminen"]
} }

View file

@ -6,7 +6,6 @@ from . import db
from .models import ( from .models import (
CreateInvoiceData, CreateInvoiceData,
CreateInvoiceItemData, CreateInvoiceItemData,
CreatePaymentData,
Invoice, Invoice,
InvoiceItem, InvoiceItem,
Payment, Payment,
@ -30,7 +29,7 @@ async def get_invoice_items(invoice_id: str) -> List[InvoiceItem]:
return [InvoiceItem.from_row(row) for row in rows] return [InvoiceItem.from_row(row) for row in rows]
async def get_invoice_item(item_id: str) -> InvoiceItem: async def get_invoice_item(item_id: str) -> Optional[InvoiceItem]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM invoices.invoice_items WHERE id = ?", (item_id,) "SELECT * FROM invoices.invoice_items WHERE id = ?", (item_id,)
) )
@ -61,7 +60,7 @@ async def get_invoice_payments(invoice_id: str) -> List[Payment]:
return [Payment.from_row(row) for row in rows] return [Payment.from_row(row) for row in rows]
async def get_invoice_payment(payment_id: str) -> Payment: async def get_invoice_payment(payment_id: str) -> Optional[Payment]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM invoices.payments WHERE id = ?", (payment_id,) "SELECT * FROM invoices.payments WHERE id = ?", (payment_id,)
) )
@ -120,7 +119,9 @@ async def create_invoice_items(
return invoice_items return invoice_items
async def update_invoice_internal(wallet_id: str, data: UpdateInvoiceData) -> Invoice: async def update_invoice_internal(
wallet_id: str, data: Union[UpdateInvoiceData, Invoice]
) -> Invoice:
await db.execute( await db.execute(
""" """
UPDATE invoices.invoices UPDATE invoices.invoices
@ -162,10 +163,10 @@ async def update_invoice_items(
(item.description, int(item.amount * 100), item.id), (item.description, int(item.amount * 100), item.id),
) )
placeholders = ",".join("?" for i in range(len(updated_items))) placeholders = ",".join("?" for _ in range(len(updated_items)))
if not placeholders: if not placeholders:
placeholders = "?" placeholders = "?"
updated_items = ("skip",) updated_items = ["skip"]
await db.execute( await db.execute(
f""" f"""
@ -180,8 +181,11 @@ async def update_invoice_items(
) )
for item in data: for item in data:
if not item.id: if not item:
await create_invoice_items(invoice_id=invoice_id, data=[item]) await create_invoice_items(
invoice_id=invoice_id,
data=[CreateInvoiceItemData(description=item.description)],
)
invoice_items = await get_invoice_items(invoice_id) invoice_items = await get_invoice_items(invoice_id)
return invoice_items return invoice_items

View file

@ -2,7 +2,7 @@ from enum import Enum
from sqlite3 import Row from sqlite3 import Row
from typing import List, Optional from typing import List, Optional
from fastapi.param_functions import Query from fastapi import Query
from pydantic import BaseModel from pydantic import BaseModel

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -1,9 +1,7 @@
import asyncio import asyncio
import json
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.helpers import urlsafe_short_hash from lnbits.tasks import register_invoice_listener
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import ( from .crud import (
create_invoice_payment, create_invoice_payment,
@ -14,6 +12,7 @@ from .crud import (
get_payments_total, get_payments_total,
update_invoice_internal, update_invoice_internal,
) )
from .models import InvoiceStatusEnum
async def wait_for_paid_invoices(): async def wait_for_paid_invoices():
@ -26,17 +25,22 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra:
return
if payment.extra.get("tag") != "invoices": if payment.extra.get("tag") != "invoices":
# not relevant
return return
invoice_id = payment.extra.get("invoice_id") invoice_id = payment.extra.get("invoice_id")
assert invoice_id
payment = await create_invoice_payment( amount = payment.extra.get("famount")
invoice_id=invoice_id, amount=payment.extra.get("famount") assert amount
)
await create_invoice_payment(invoice_id=invoice_id, amount=amount)
invoice = await get_invoice(invoice_id) invoice = await get_invoice(invoice_id)
assert invoice
invoice_items = await get_invoice_items(invoice_id) invoice_items = await get_invoice_items(invoice_id)
invoice_total = await get_invoice_total(invoice_items) invoice_total = await get_invoice_total(invoice_items)
@ -45,7 +49,7 @@ async def on_invoice_paid(payment: Payment) -> None:
payments_total = await get_payments_total(invoice_payments) payments_total = await get_payments_total(invoice_payments)
if payments_total >= invoice_total: if payments_total >= invoice_total:
invoice.status = "paid" invoice.status = InvoiceStatusEnum.paid
await update_invoice_internal(invoice.wallet, invoice) await update_invoice_internal(invoice.wallet, invoice)
return return

View file

@ -257,7 +257,7 @@ block page %}
> >
<q-responsive :ratio="1" class="q-mx-xs"> <q-responsive :ratio="1" class="q-mx-xs">
<qrcode <qrcode
:value="'lightning:' + qrCodeDialog.data.payment_request.toUpperCase()" :value="'lightning:' + qrCodeDialog.data.payment_request"
:options="{width: 400}" :options="{width: 400}"
class="rounded-borders" class="rounded-borders"
></qrcode> ></qrcode>

View file

@ -1,10 +1,8 @@
from datetime import datetime from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
from fastapi import FastAPI, Request from fastapi import Depends, HTTPException, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
from lnbits.core.models import User from lnbits.core.models import User

View file

@ -1,14 +1,12 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Query from fastapi import Depends, HTTPException, Query
from fastapi.params import Depends
from loguru import logger from loguru import logger
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.decorators import WalletTypeInfo, get_key_type
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
from . import invoices_ext from . import invoices_ext
@ -33,7 +31,8 @@ async def api_invoices(
): ):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [invoice.dict() for invoice in await get_invoices(wallet_ids)] return [invoice.dict() for invoice in await get_invoices(wallet_ids)]
@ -83,9 +82,7 @@ async def api_invoice_update(
@invoices_ext.post( @invoices_ext.post(
"/api/v1/invoice/{invoice_id}/payments", status_code=HTTPStatus.CREATED "/api/v1/invoice/{invoice_id}/payments", status_code=HTTPStatus.CREATED
) )
async def api_invoices_create_payment( async def api_invoices_create_payment(invoice_id: str, famount: int = Query(..., ge=1)):
famount: int = Query(..., ge=1), invoice_id: str = None
):
invoice = await get_invoice(invoice_id) invoice = await get_invoice(invoice_id)
invoice_items = await get_invoice_items(invoice_id) invoice_items = await get_invoice_items(invoice_id)
invoice_total = await get_invoice_total(invoice_items) invoice_total = await get_invoice_total(invoice_items)

View file

@ -1,6 +1,6 @@
{ {
"name": "Spotify Jukebox", "name": "Spotify Jukebox",
"short_description": "Spotify jukebox middleware", "short_description": "Spotify jukebox middleware",
"icon": "radio", "tile": "/jukebox/static/image/jukebox.png",
"contributors": ["benarc"] "contributors": ["benarc"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,7 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@ -17,9 +16,7 @@ templates = Jinja2Templates(directory="templates")
@jukebox_ext.get("/", response_class=HTMLResponse) @jukebox_ext.get("/", response_class=HTMLResponse)
async def index( async def index(request: Request, user: User = Depends(check_user_exists)):
request: Request, user: User = Depends(check_user_exists) # type: ignore
):
return jukebox_renderer().TemplateResponse( return jukebox_renderer().TemplateResponse(
"jukebox/index.html", {"request": request, "user": user.dict()} "jukebox/index.html", {"request": request, "user": user.dict()}
) )

View file

@ -3,10 +3,9 @@ import json
from http import HTTPStatus from http import HTTPStatus
import httpx import httpx
from fastapi.param_functions import Query from fastapi import Depends, Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse # type: ignore from starlette.responses import HTMLResponse
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment from lnbits.core.views.api import api_payment
@ -28,7 +27,7 @@ from .models import CreateJukeboxPayment, CreateJukeLinkData
@jukebox_ext.get("/api/v1/jukebox") @jukebox_ext.get("/api/v1/jukebox")
async def api_get_jukeboxs( async def api_get_jukeboxs(
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
wallet_user = wallet.wallet.user wallet_user = wallet.wallet.user

View file

@ -1,7 +1,7 @@
{ {
"name": "DJ Livestream", "name": "DJ Livestream",
"short_description": "Sell tracks and split revenue (lnurl-pay)", "short_description": "Sell tracks and split revenue (lnurl-pay)",
"icon": "speaker", "tile": "/livestream/static/image/livestream.png",
"contributors": [ "contributors": [
"fiatjaf", "fiatjaf",
"cryptograffiti" "cryptograffiti"

View file

@ -90,7 +90,7 @@ async def lnurl_callback(
wallet_id=ls.wallet, wallet_id=ls.wallet,
amount=int(amount_received / 1000), amount=int(amount_received / 1000),
memo=await track.fullname(), memo=await track.fullname(),
unhashed_description=(await track.lnurlpay_metadata()).encode("utf-8"), unhashed_description=(await track.lnurlpay_metadata()).encode(),
extra={"tag": "livestream", "track": track.id, "comment": comment}, extra={"tag": "livestream", "track": track.id, "comment": comment},
) )

View file

@ -1,12 +1,12 @@
import json import json
from typing import Optional from typing import Optional
from fastapi.params import Query from fastapi import Query
from lnurl import Lnurl from lnurl import Lnurl
from lnurl import encode as lnurl_encode # type: ignore from lnurl import encode as lnurl_encode # type: ignore
from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore from lnurl.types import LnurlPayMetadata # type: ignore
from pydantic.main import BaseModel from pydantic import BaseModel
from starlette.requests import Request from starlette.requests import Request

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
from fastapi import APIRouter from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -10,6 +11,14 @@ db = Database("ext_lnaddress")
lnaddress_ext: APIRouter = APIRouter(prefix="/lnaddress", tags=["lnaddress"]) lnaddress_ext: APIRouter = APIRouter(prefix="/lnaddress", tags=["lnaddress"])
lnaddress_static_files = [
{
"path": "/lnaddress/static",
"app": StaticFiles(directory="lnbits/extensions/lnaddress/static"),
"name": "lnaddress_static",
}
]
def lnaddress_renderer(): def lnaddress_renderer():
return template_renderer(["lnbits/extensions/lnaddress/templates"]) return template_renderer(["lnbits/extensions/lnaddress/templates"])

View file

@ -1,6 +1,6 @@
{ {
"name": "Lightning Address", "name": "Lightning Address",
"short_description": "Sell LN addresses for your domain", "short_description": "Sell LN addresses for your domain",
"icon": "alternate_email", "tile": "/lnaddress/static/image/lnaddress.png",
"contributors": ["talvasconcelos"] "contributors": ["talvasconcelos"]
} }

View file

@ -72,7 +72,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)):
"amount": int(amount_received / 1000), "amount": int(amount_received / 1000),
"description_hash": ( "description_hash": (
await address.lnurlpay_metadata(domain=domain.domain) await address.lnurlpay_metadata(domain=domain.domain)
).encode("utf-8"), ).encode(),
"extra": {"tag": f"Payment to {address.username}@{domain.domain}"}, "extra": {"tag": f"Payment to {address.username}@{domain.domain}"},
}, },
timeout=40, timeout=40,

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -1,4 +1,5 @@
from fastapi import APIRouter from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -7,6 +8,14 @@ db = Database("ext_lndhub")
lndhub_ext: APIRouter = APIRouter(prefix="/lndhub", tags=["lndhub"]) lndhub_ext: APIRouter = APIRouter(prefix="/lndhub", tags=["lndhub"])
lndhub_static_files = [
{
"path": "/lndhub/static",
"app": StaticFiles(directory="lnbits/extensions/lndhub/static"),
"name": "lndhub_static",
}
]
def lndhub_renderer(): def lndhub_renderer():
return template_renderer(["lnbits/extensions/lndhub/templates"]) return template_renderer(["lnbits/extensions/lndhub/templates"])

View file

@ -1,6 +1,6 @@
{ {
"name": "LndHub", "name": "LndHub",
"short_description": "Access lnbits from BlueWallet or Zeus", "short_description": "Access lnbits from BlueWallet or Zeus",
"icon": "navigation", "tile": "/lndhub/static/image/lndhub.png",
"contributors": ["fiatjaf"] "contributors": ["fiatjaf"]
} }

View file

@ -23,7 +23,7 @@ async def check_wallet(
) )
t = api_key_header_auth.split(" ")[1] t = api_key_header_auth.split(" ")[1]
_, token = b64decode(t).decode("utf-8").split(":") _, token = b64decode(t).decode().split(":")
return await get_key_type(r, api_key_header=token) return await get_key_type(r, api_key_header=token)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -1,5 +1,4 @@
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from lnbits.core.models import User from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists

View file

@ -1,15 +1,13 @@
import asyncio
import time import time
from base64 import urlsafe_b64encode from base64 import urlsafe_b64encode
from http import HTTPStatus from http import HTTPStatus
from fastapi.param_functions import Query from fastapi import Depends, Query
from fastapi.params import Depends
from pydantic import BaseModel from pydantic import BaseModel
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits import bolt11 from lnbits import bolt11
from lnbits.core.crud import delete_expired_invoices, get_payments from lnbits.core.crud import get_payments
from lnbits.core.services import create_invoice, pay_invoice from lnbits.core.services import create_invoice, pay_invoice
from lnbits.decorators import WalletTypeInfo from lnbits.decorators import WalletTypeInfo
from lnbits.settings import get_wallet_class, settings from lnbits.settings import get_wallet_class, settings
@ -35,9 +33,9 @@ async def lndhub_auth(data: AuthData):
token = ( token = (
data.refresh_token data.refresh_token
if data.refresh_token if data.refresh_token
else urlsafe_b64encode( else urlsafe_b64encode((data.login + ":" + data.password).encode()).decode(
(data.login + ":" + data.password).encode("utf-8") "ascii"
).decode("ascii") )
) )
return {"refresh_token": token, "access_token": token} return {"refresh_token": token, "access_token": token}
@ -73,13 +71,13 @@ async def lndhub_addinvoice(
} }
class Invoice(BaseModel): class CreateInvoice(BaseModel):
invoice: str = Query(...) invoice: str = Query(...)
@lndhub_ext.post("/ext/payinvoice") @lndhub_ext.post("/ext/payinvoice")
async def lndhub_payinvoice( async def lndhub_payinvoice(
r_invoice: Invoice, wallet: WalletTypeInfo = Depends(require_admin_key) r_invoice: CreateInvoice, wallet: WalletTypeInfo = Depends(require_admin_key)
): ):
try: try:
await pay_invoice( await pay_invoice(

View file

@ -2,6 +2,7 @@ import asyncio
import json import json
from fastapi import APIRouter from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -11,6 +12,14 @@ db = Database("ext_lnticket")
lnticket_ext: APIRouter = APIRouter(prefix="/lnticket", tags=["LNTicket"]) lnticket_ext: APIRouter = APIRouter(prefix="/lnticket", tags=["LNTicket"])
lnticket_static_files = [
{
"path": "/lnticket/static",
"app": StaticFiles(directory="lnbits/extensions/lnticket/static"),
"name": "lnticket_static",
}
]
def lnticket_renderer(): def lnticket_renderer():
return template_renderer(["lnbits/extensions/lnticket/templates"]) return template_renderer(["lnbits/extensions/lnticket/templates"])

View file

@ -1,6 +1,6 @@
{ {
"name": "Support Tickets", "name": "Support Tickets",
"short_description": "LN support ticket system", "short_description": "LN support ticket system",
"icon": "contact_support", "tile": "/lnticket/static/image/lntickets.png",
"contributors": ["benarc"] "contributors": ["benarc"]
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -19,7 +19,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") != "lnticket": if not payment.extra or payment.extra.get("tag") != "lnticket":
# not a lnticket invoice # not a lnticket invoice
return return

View file

@ -33,6 +33,7 @@ async def display(request: Request, form_id):
) )
wallet = await get_wallet(form.wallet) wallet = await get_wallet(form.wallet)
assert wallet
return lnticket_renderer().TemplateResponse( return lnticket_renderer().TemplateResponse(
"lnticket/display.html", "lnticket/display.html",

View file

@ -1,8 +1,7 @@
import re import re
from http import HTTPStatus from http import HTTPStatus
from fastapi import Query from fastapi import Depends, Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
@ -35,7 +34,8 @@ async def api_forms_get(
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [form.dict() for form in await get_forms(wallet_ids)] return [form.dict() for form in await get_forms(wallet_ids)]
@ -91,7 +91,8 @@ async def api_tickets(
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [form.dict() for form in await get_tickets(wallet_ids)] return [form.dict() for form in await get_tickets(wallet_ids)]

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
from fastapi import APIRouter from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -10,6 +11,14 @@ db = Database("ext_lnurldevice")
lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice"]) lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice"])
lnurldevice_static_files = [
{
"path": "/lnurldevice/static",
"app": StaticFiles(directory="lnbits/extensions/lnurldevice/static"),
"name": "lnurldevice_static",
}
]
def lnurldevice_renderer(): def lnurldevice_renderer():
return template_renderer(["lnbits/extensions/lnurldevice/templates"]) return template_renderer(["lnbits/extensions/lnurldevice/templates"])

View file

@ -1,6 +1,6 @@
{ {
"name": "LNURLDevice", "name": "LNURLDevice",
"short_description": "For offline LNURL devices", "short_description": "For offline LNURL devices",
"icon": "point_of_sale", "tile": "/lnurldevice/static/image/lnurldevice.png",
"contributors": ["arcbtc"] "contributors": ["arcbtc"]
} }

View file

@ -1,5 +1,7 @@
from typing import List, Optional, Union from typing import List, Optional, Union
import shortuuid
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
from . import db from . import db
@ -12,7 +14,7 @@ async def create_lnurldevice(
data: createLnurldevice, data: createLnurldevice,
) -> lnurldevices: ) -> lnurldevices:
if data.device == "pos" or data.device == "atm": if data.device == "pos" or data.device == "atm":
lnurldevice_id = str(await get_lnurldeviceposcount()) lnurldevice_id = shortuuid.uuid()[:5]
else: else:
lnurldevice_id = urlsafe_short_hash() lnurldevice_id = urlsafe_short_hash()
lnurldevice_key = urlsafe_short_hash() lnurldevice_key = urlsafe_short_hash()
@ -82,17 +84,6 @@ async def update_lnurldevice(lnurldevice_id: str, **kwargs) -> Optional[lnurldev
return lnurldevices(**row) if row else None return lnurldevices(**row) if row else None
async def get_lnurldeviceposcount() -> int:
row = await db.fetchall(
"SELECT * FROM lnurldevice.lnurldevices WHERE device = ? OR device = ?",
(
"pos",
"atm",
),
)
return len(row) + 1
async def get_lnurldevice(lnurldevice_id: str) -> lnurldevices: async def get_lnurldevice(lnurldevice_id: str) -> lnurldevices:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,) "SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,)

View file

@ -246,7 +246,7 @@ async def lnurl_callback(
wallet_id=device.wallet, wallet_id=device.wallet,
amount=int(lnurldevicepayment.sats / 1000), amount=int(lnurldevicepayment.sats / 1000),
memo=device.id + " PIN " + str(lnurldevicepayment.pin), memo=device.id + " PIN " + str(lnurldevicepayment.pin),
unhashed_description=device.lnurlpay_metadata.encode("utf-8"), unhashed_description=device.lnurlpay_metadata.encode(),
extra={ extra={
"tag": "Switch", "tag": "Switch",
"pin": str(lnurldevicepayment.pin), "pin": str(lnurldevicepayment.pin),
@ -267,7 +267,7 @@ async def lnurl_callback(
wallet_id=device.wallet, wallet_id=device.wallet,
amount=int(lnurldevicepayment.sats / 1000), amount=int(lnurldevicepayment.sats / 1000),
memo=device.title, memo=device.title,
unhashed_description=device.lnurlpay_metadata.encode("utf-8"), unhashed_description=device.lnurlpay_metadata.encode(),
extra={"tag": "PoS"}, extra={"tag": "PoS"},
) )
lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment = await update_lnurldevicepayment(

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,7 +1,7 @@
{ {
"name": "LNURLp", "name": "LNURLp",
"short_description": "Make reusable LNURL pay links", "short_description": "Make reusable LNURL pay links",
"icon": "receipt", "tile": "/lnurlp/static/image/lnurl-pay.png",
"contributors": [ "contributors": [
"arcbtc", "arcbtc",
"eillarra", "eillarra",

View file

@ -1,18 +1,19 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash from lnbits.db import SQLITE
from . import db from . import db
from .models import CreatePayLinkData, PayLink from .models import CreatePayLinkData, PayLink
async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink: async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
link_id = urlsafe_short_hash()
result = await db.execute( returning = "" if db.type == SQLITE else "RETURNING ID"
method = db.execute if db.type == SQLITE else db.fetchone
result = await (method)(
f""" f"""
INSERT INTO lnurlp.pay_links ( INSERT INTO lnurlp.pay_links (
id,
wallet, wallet,
description, description,
min, min,
@ -28,11 +29,10 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
currency, currency,
fiat_base_multiplier fiat_base_multiplier
) )
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?)
{returning} {returning}
""", """,
( (
link_id,
wallet_id, wallet_id,
data.description, data.description,
data.min, data.min,
@ -47,6 +47,10 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
data.fiat_base_multiplier, data.fiat_base_multiplier,
), ),
) )
if db.type == SQLITE:
link_id = result._result_proxy.lastrowid
else:
link_id = result[0]
link = await get_pay_link(link_id) link = await get_pay_link(link_id)
assert link, "Newly created link couldn't be retrieved" assert link, "Newly created link couldn't be retrieved"

View file

@ -87,7 +87,7 @@ async def api_lnurl_callback(request: Request, link_id):
wallet_id=link.wallet, wallet_id=link.wallet,
amount=int(amount_received / 1000), amount=int(amount_received / 1000),
memo=link.description, memo=link.description,
unhashed_description=link.lnurlpay_metadata.encode("utf-8"), unhashed_description=link.lnurlpay_metadata.encode(),
extra={ extra={
"tag": "lnurlp", "tag": "lnurlp",
"link": link.id, "link": link.id,

View file

@ -68,76 +68,3 @@ async def m005_webhook_headers_and_body(db):
""" """
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_headers TEXT;") await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_headers TEXT;")
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_body TEXT;") await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_body TEXT;")
async def m006_redux(db):
"""
Add UUID ID's to links and migrates existing data
"""
await db.execute("ALTER TABLE lnurlp.pay_links RENAME TO pay_links_old")
await db.execute(
f"""
CREATE TABLE lnurlp.pay_links (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
description TEXT NOT NULL,
min INTEGER NOT NULL,
max INTEGER,
currency TEXT,
fiat_base_multiplier INTEGER DEFAULT 1,
served_meta INTEGER NOT NULL,
served_pr INTEGER NOT NULL,
webhook_url TEXT,
success_text TEXT,
success_url TEXT,
comment_chars INTEGER DEFAULT 0,
webhook_headers TEXT,
webhook_body TEXT
);
"""
)
for row in [
list(row) for row in await db.fetchall("SELECT * FROM lnurlp.pay_links_old")
]:
await db.execute(
"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
served_meta,
served_pr,
webhook_url,
success_text,
success_url,
currency,
comment_chars,
max,
fiat_base_multiplier,
webhook_headers,
webhook_body
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
row[7],
row[8],
row[9],
row[10],
row[11],
row[12],
row[13],
row[14],
),
)
await db.execute("DROP TABLE lnurlp.pay_links_old")

View file

@ -26,7 +26,7 @@ class CreatePayLinkData(BaseModel):
class PayLink(BaseModel): class PayLink(BaseModel):
id: str id: int
wallet: str wallet: str
description: str description: str
min: float min: float

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -4,7 +4,6 @@ import json
import httpx import httpx
from loguru import logger from loguru import logger
from lnbits.core import db as core_db
from lnbits.core.crud import update_payment_extra from lnbits.core.crud import update_payment_extra
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name from lnbits.helpers import get_current_extension_name
@ -22,9 +21,8 @@ async def wait_for_paid_invoices():
await on_invoice_paid(payment) await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment):
if payment.extra.get("tag") != "lnurlp": if not payment.extra or payment.extra.get("tag") != "lnurlp":
# not an lnurlp invoice
return return
if payment.extra.get("wh_status"): if payment.extra.get("wh_status"):
@ -35,22 +33,23 @@ async def on_invoice_paid(payment: Payment) -> None:
if pay_link and pay_link.webhook_url: if pay_link and pay_link.webhook_url:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
kwargs = { r: httpx.Response = await client.post(
"json": { pay_link.webhook_url,
json={
"payment_hash": payment.payment_hash, "payment_hash": payment.payment_hash,
"payment_request": payment.bolt11, "payment_request": payment.bolt11,
"amount": payment.amount, "amount": payment.amount,
"comment": payment.extra.get("comment"), "comment": payment.extra.get("comment"),
"lnurlp": pay_link.id, "lnurlp": pay_link.id,
"body": json.loads(pay_link.webhook_body)
if pay_link.webhook_body
else "",
}, },
"timeout": 40, headers=json.loads(pay_link.webhook_headers)
} if pay_link.webhook_headers
if pay_link.webhook_body: else None,
kwargs["json"]["body"] = json.loads(pay_link.webhook_body) timeout=40,
if pay_link.webhook_headers: )
kwargs["headers"] = json.loads(pay_link.webhook_headers)
r: httpx.Response = await client.post(pay_link.webhook_url, **kwargs)
await mark_webhook_sent( await mark_webhook_sent(
payment.payment_hash, payment.payment_hash,
r.status_code, r.status_code,

View file

@ -1,7 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse

Some files were not shown because too many files have changed in this diff Show more