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=""
# Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
# 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_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.
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.
@ -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)!
[docs]: https://legend.lnbits.org/
[docs]: https://docs.lnbits.org/
[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-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
logo: "/logos/lnbits-full--inverse.png"
search_enabled: true
url: https://legend.lnbits.org
url: https://docs.lnbits.org
aux_links:
"LNbits on GitHub":
- "//github.com/lnbits/lnbits"

View File

@ -9,4 +9,4 @@ nav_order: 3
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
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:
invoice.description_hash = _trim_to_bytes(tagdata).hex()
elif tag == "p" and data_length == 52:
@ -260,7 +260,7 @@ class LnAddr(object):
def __str__(self):
return "LnAddr[{}, amount={}{} tags=[{}]]".format(
bytes.hex(self.pubkey.serialize()).decode("utf-8"),
bytes.hex(self.pubkey.serialize()).decode(),
self.amount,
self.currency,
", ".join([k + "=" + str(v) for k, v in self.tags]),

View File

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

View File

@ -23,14 +23,55 @@
>
<q-card>
<q-card-section>
<q-icon
:name="extension.icon"
color="grey-5"
style="font-size: 4rem"
></q-icon>
{% raw %}
<h5 class="q-mt-lg q-mb-xs">{{ extension.name }}</h5>
<small>{{ extension.shortDescription }} </small>{% endraw %}
<div class="row">
<div class="col-3">
<q-img
:src="extension.tile"
spinner-color="white"
style="max-width: 100%"
></q-img>
</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-separator></q-separator>
<q-card-actions>

View File

@ -23,7 +23,7 @@
<strong>{% raw %}{{ formattedBalance }} {% endraw %}</strong>
{{LNBITS_DENOMINATION}}
<q-btn
v-if="'{{user.admin}}' == 'True'"
v-if="'{{user.super_user}}' == 'True'"
flat
round
color="primary"
@ -36,27 +36,16 @@
v-model="credit"
>
<q-input
v-if="'{{LNBITS_DENOMINATION}}' != 'sats'"
label="Amount to credit account"
filled
label="{{LNBITS_DENOMINATION}} to credit"
hint="Press Enter to credit account"
v-model="scope.value"
dense
autofocus
mask="#.##"
:mask="'{{LNBITS_DENOMINATION}}' != 'sats' ? '#.##' : '#'"
fill-mask="0"
reverse-fill-mask
@keyup.enter="updateBalance(scope.value)"
>
<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
:step="'{{LNBITS_DENOMINATION}}' != 'sats' ? '0.01' : '1'"
@keyup.enter="updateBalance(scope.value)"
>
<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/")
async def api_get_settings(
user: User = Depends(check_admin), # type: ignore
user: User = Depends(check_admin),
) -> Optional[AdminSettings]:
admin_settings = await get_admin_settings(user.super_user)
return admin_settings

View File

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

View File

@ -2,9 +2,8 @@ import asyncio
from http import HTTPStatus
from typing import Optional
from fastapi import Request, status
from fastapi import Depends, Query, Request, status
from fastapi.exceptions import HTTPException
from fastapi.params import Depends, Query
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.routing import APIRouter
from loguru import logger
@ -49,9 +48,9 @@ async def home(request: Request, lightning: str = ""):
)
async def extensions(
request: Request,
user: User = Depends(check_user_exists), # type: ignore
enable: str = Query(None), # type: ignore
disable: str = Query(None), # type: ignore
user: User = Depends(check_user_exists),
enable: str = Query(None),
disable: str = Query(None),
):
extension_to_enable = enable
extension_to_disable = disable
@ -103,10 +102,10 @@ nothing: create everything<br>
""",
)
async def wallet(
request: Request = Query(None), # type: ignore
nme: Optional[str] = Query(None), # type: ignore
usr: Optional[UUID4] = Query(None), # type: ignore
wal: Optional[UUID4] = Query(None), # type: ignore
request: Request = Query(None),
nme: Optional[str] = Query(None),
usr: Optional[UUID4] = Query(None),
wal: Optional[UUID4] = Query(None),
):
user_id = usr.hex if usr 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:
user.admin = True
if user_id == settings.super_user:
user.super_user = True
if not wallet_id:
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)
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_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)
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()
_, balance = await WALLET.status()

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,6 +1,7 @@
import asyncio
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -15,6 +16,14 @@ def boltz_renderer():
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 .views import * # noqa
from .views_api import * # noqa

View File

@ -55,7 +55,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
raise
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(
"post",
@ -120,7 +120,7 @@ async def create_reverse_swap(
return False
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_hash = sha256(preimage).hexdigest()

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

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

View File

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

View File

@ -75,7 +75,7 @@ async def lnurl_callback(
memo=cp.lnurl_title,
unhashed_description=(
LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]]))
).encode("utf-8"),
).encode(),
extra={"tag": "copilot", "copilotid": cp.id, "comment": comment},
)
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 fastapi import Request, WebSocket, WebSocketDisconnect
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
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 . import copilot_ext, copilot_renderer
from .crud import get_copilot
templates = Jinja2Templates(directory="templates")
@copilot_ext.get("/", response_class=HTMLResponse)
async def index(
request: Request, user: User = Depends(check_user_exists) # type: ignore
):
async def index(request: Request, user: User = Depends(check_user_exists)):
return copilot_renderer().TemplateResponse(
"copilot/index.html", {"request": request, "user": user.dict()}
)

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

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

View File

@ -1,7 +1,6 @@
from http import HTTPStatus
from fastapi import Query
from fastapi.params import Depends
from fastapi import Depends, Query
from starlette.exceptions import HTTPException
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)
async def api_discordbot_users(
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
wallet: WalletTypeInfo = Depends(get_key_type),
):
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) # type: ignore
):
async def api_discordbot_user(user_id, wallet: WalletTypeInfo = Depends(get_key_type)):
user = await get_discordbot_user(user_id)
if user:
return user.dict()
@ -45,7 +42,7 @@ async def api_discordbot_user(
@discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED)
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)
full = user.dict()
@ -57,7 +54,7 @@ async def api_discordbot_users_create(
@discordbot_ext.delete("/api/v1/users/{user_id}")
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)
if not user:
@ -89,7 +86,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) # type: ignore
data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type)
):
user = await create_discordbot_wallet(
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")
async def api_discordbot_wallets(
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
wallet: WalletTypeInfo = Depends(get_key_type),
):
admin_id = wallet.wallet.user
return await get_discordbot_wallets(admin_id)
@ -107,21 +104,21 @@ async def api_discordbot_wallets(
@discordbot_ext.get("/api/v1/transactions/{wallet_id}")
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)
@discordbot_ext.get("/api/v1/wallets/{user_id}")
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)
@discordbot_ext.delete("/api/v1/wallets/{wallet_id}")
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)
if not get_wallet:

View File

@ -1,6 +1,7 @@
import asyncio
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -11,6 +12,14 @@ db = Database("ext_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():
return template_renderer(["lnbits/extensions/events/templates"])

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@ -1,15 +1,6 @@
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.services import pay_invoice
from lnbits.extensions.events.models import CreateTicket
from lnbits.helpers import get_current_extension_name
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:
# (avoid loops)
if (
"events" == payment.extra.get("tag")
payment.extra
and "events" == payment.extra.get("tag")
and payment.extra.get("name")
and payment.extra.get("email")
):
CreateTicket.name = str(payment.extra.get("name"))
CreateTicket.email = str(payment.extra.get("email"))
await api_ticket_send_ticket(payment.memo, payment.payment_hash, CreateTicket)
await api_ticket_send_ticket(
payment.memo,
payment.payment_hash,
CreateTicket(
name=str(payment.extra.get("name")),
email=str(payment.extra.get("email")),
),
)
return

View File

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

View File

@ -1,10 +1,7 @@
from http import HTTPStatus
from fastapi.param_functions import Query
from fastapi.params import Depends
from loguru import logger
from fastapi import Depends, Query
from starlette.exceptions import HTTPException
from starlette.requests import Request
from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice
@ -38,7 +35,8 @@ async def api_events(
wallet_ids = [wallet.wallet.id]
if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [event.dict() for event in await get_events(wallet_ids)]
@ -92,7 +90,8 @@ async def api_tickets(
wallet_ids = [wallet.wallet.id]
if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [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}")
async def api_ticket_send_ticket(event_id, payment_hash, data: CreateTicket):
event = await get_event(event_id)
try:
status = await api_payment(payment_hash)
if status["paid"]:
ticket = await create_ticket(
payment_hash=payment_hash,
wallet=event.wallet,
event=event_id,
name=data.name,
email=data.email,
if not event:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Event could not be fetched.",
)
status = await api_payment(payment_hash)
if status["paid"]:
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.",
)
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": True, "ticket_id": ticket.id}
return {"paid": False}

View File

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

View File

@ -1,5 +1,4 @@
from fastapi import FastAPI, Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse
@ -14,7 +13,7 @@ templates = Jinja2Templates(directory="templates")
@example_ext.get("/", response_class=HTMLResponse)
async def index(
request: Request,
user: User = Depends(check_user_exists), # type: ignore
user: User = Depends(check_user_exists),
):
return example_renderer().TemplateResponse(
"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.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
db = Database("ext_gerty")
gerty_static_files = [
{
"path": "/gerty/static",

View File

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

View File

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

View File

@ -3,15 +3,16 @@ import os
import random
import textwrap
from datetime import datetime, timedelta
from typing import List
import httpx
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.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 *
@ -24,8 +25,8 @@ def get_percent_difference(current, previous, precision=3):
def get_text_item_dict(
text: str,
font_size: int,
x_pos: int = None,
y_pos: int = None,
x_pos: int = -1,
y_pos: int = -1,
gerty_type: str = "Gerty",
):
# Get line size by font size
@ -63,13 +64,41 @@ def get_text_item_dict(
# logger.debug('multilineText')
# logger.debug(multilineText)
text = {"value": multilineText, "size": font_size}
if x_pos is None and y_pos is None:
text["position"] = "center"
data_text = {"value": multilineText, "size": font_size}
if x_pos == -1 and y_pos == -1:
data_text["position"] = "center"
else:
text["x"] = x_pos
text["y"] = y_pos
return text
data_text["x"] = x_pos if x_pos > 0 else 0
data_text["y"] = y_pos if x_pos > 0 else 0
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
@ -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):
utc_now = datetime.utcnow()
local_time = utc_now + timedelta(hours=utc_offset)
hours = local_time.strftime("%H")
hours = int(hours)
hours = int(local_time.strftime("%H"))
if hours >= 22 and hours <= 23:
return True
else:
@ -352,23 +380,17 @@ async def 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":
async with httpx.AsyncClient() as client:
r = await get_mempool_info("hashrate_1m", gerty)
data = r
stat = {}
stat["current"] = data["currentHashrate"]
stat["1w"] = data["hashrates"][len(data["hashrates"]) - 7]["avgHashrate"]
r = await get_mempool_info("hashrate_1m", gerty)
data = r
stat["current"] = data["currentHashrate"]
stat["1w"] = data["hashrates"][len(data["hashrates"]) - 7]["avgHashrate"]
elif stat_slug == "mining_current_difficulty":
async with httpx.AsyncClient() as client:
r = await get_mempool_info("hashrate_1m", gerty)
data = r
stat = {}
stat["current"] = data["currentDifficulty"]
stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2][
"difficulty"
]
r = await get_mempool_info("hashrate_1m", gerty)
data = r
stat["current"] = data["currentDifficulty"]
stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2]["difficulty"]
return stat
@ -384,7 +406,7 @@ async def get_satoshi():
quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
# logger.debug(quote.text)
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()
else:
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
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)
# first get the relevant slug from the display_preferences
areas = []
areas: List = []
title = ""
if screen_slug == "dashboard":
title = gerty.name
areas = await get_dashboard(gerty)
if screen_slug == "lnbits_wallets_balance":
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"
areas = await get_lightning_stats(gerty)
data = {}
data["title"] = title
data["areas"] = areas
data = {
"title": title,
"areas": areas,
}
return data
@ -570,7 +593,7 @@ async def get_dashboard(gerty):
text = []
text.append(
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,
gerty_type=gerty.type,
)
@ -602,7 +625,7 @@ async def get_lnbits_wallet_balances(gerty):
return wallets
async def get_placeholder_text():
async def get_placeholder_text(gerty):
return [
get_text_item_dict(
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)
stat = r["remainingTime"]
time = get_time_remaining(stat / 1000, 3)
return time
return time
async def get_mempool_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
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":
stat = round(r["count"])
text.append(
@ -921,31 +944,3 @@ async def get_mempool_stat(stat_slug: str, gerty):
)
)
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 typing import Optional
from fastapi import Query
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>
</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
class="q-pa-sm"
v-for="(wallet, t) in lnbits_wallets_balance"
@ -49,7 +52,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
<div
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
class="q-pa-sm"
@ -67,7 +70,7 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</q-card-section>
</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>
<div class="text-h6">Mining</div>
</q-card-section>
@ -78,7 +81,12 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</q-card-section>
</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>
<div class="text-h6">Lightning (Last 7 days)</div>
</q-card-section>
@ -88,7 +96,6 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
</p>
</q-card-section>
</q-card>
<q-card class="q-pa-sm" v-if="url_checker" unelevated class="q-pa-sm">
<q-card-section>
<div class="text-h6">Servers to check</div>
@ -153,7 +160,13 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
lnbits_wallets_balance: {},
dashboard_onchain: {},
fun_satoshi_quotes: {},
fun_exchange_market_rate: {},
fun_exchange_market_rate: {
unit: ''
},
dashboard_mining: {},
lightning_dashboard: {},
url_checker: {},
dashboard_mining: {},
gerty: [],
gerty_id: `{{gerty}}`,
gertyname: '',
@ -182,7 +195,6 @@ gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
LNbits.utils.notifyApiError(error)
}
}
console.log(this.gerty)
for (let i = 0; i < this.gerty.length; i++) {
if (this.gerty[i].screen.group == 'lnbits_wallets_balance') {
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 fastapi import Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from loguru import logger
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
@ -13,7 +10,6 @@ from lnbits.decorators import check_user_exists
from . import gerty_ext, gerty_renderer
from .crud import get_gerty
from .views_api import api_gerty_json
templates = Jinja2Templates(directory="templates")

View File

@ -1,24 +1,12 @@
import json
import math
import os
import random
import time
from datetime import datetime
from http import HTTPStatus
import httpx
from fastapi import Query
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from lnurl import decode as decode_lnurl
from fastapi import Depends, Query
from loguru import logger
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user, get_wallet_for_key
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment, api_wallet
from lnbits.core.crud import get_user
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 .crud import (
@ -29,8 +17,14 @@ from .crud import (
get_mempool_info,
update_gerty,
)
from .helpers import *
from .models import Gerty, MempoolEndpoint
from .helpers import (
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)
@ -39,7 +33,8 @@ async def api_gertys(
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [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),
gerty_id: str = Query(None),
):
logger.debug(data)
if gerty_id:
gerty = await get_gerty(gerty_id)
if not gerty:
@ -67,6 +61,9 @@ async def api_link_create_or_update(
data.wallet = wallet.wallet.id
gerty = await update_gerty(gerty_id, **data.dict())
assert gerty, HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist"
)
else:
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)
async def api_gerty_satoshi():
return await get_satoshi
return await get_satoshi()
@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)
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_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)
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.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -12,4 +13,12 @@ def hivemind_renderer():
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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

View File

@ -6,7 +6,6 @@ from . import db
from .models import (
CreateInvoiceData,
CreateInvoiceItemData,
CreatePaymentData,
Invoice,
InvoiceItem,
Payment,
@ -30,7 +29,7 @@ async def get_invoice_items(invoice_id: str) -> List[InvoiceItem]:
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(
"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]
async def get_invoice_payment(payment_id: str) -> Payment:
async def get_invoice_payment(payment_id: str) -> Optional[Payment]:
row = await db.fetchone(
"SELECT * FROM invoices.payments WHERE id = ?", (payment_id,)
)
@ -120,7 +119,9 @@ async def create_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(
"""
UPDATE invoices.invoices
@ -155,21 +156,21 @@ async def update_invoice_items(
updated_items.append(item.id)
await db.execute(
"""
UPDATE invoices.invoice_items
UPDATE invoices.invoice_items
SET description = ?, amount = ?
WHERE 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:
placeholders = "?"
updated_items = ("skip",)
updated_items = ["skip"]
await db.execute(
f"""
DELETE FROM invoices.invoice_items
DELETE FROM invoices.invoice_items
WHERE invoice_id = ?
AND id NOT IN ({placeholders})
""",
@ -180,8 +181,11 @@ async def update_invoice_items(
)
for item in data:
if not item.id:
await create_invoice_items(invoice_id=invoice_id, data=[item])
if not item:
await create_invoice_items(
invoice_id=invoice_id,
data=[CreateInvoiceItemData(description=item.description)],
)
invoice_items = await get_invoice_items(invoice_id)
return invoice_items

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

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

View File

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

View File

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

View File

@ -90,7 +90,7 @@ async def lnurl_callback(
wallet_id=ls.wallet,
amount=int(amount_received / 1000),
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},
)

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -1,6 +1,7 @@
import asyncio
from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -10,6 +11,14 @@ db = Database("ext_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():
return template_renderer(["lnbits/extensions/lnaddress/templates"])

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,4 +1,5 @@
from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -7,6 +8,14 @@ db = Database("ext_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():
return template_renderer(["lnbits/extensions/lndhub/templates"])

View File

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

View File

@ -23,7 +23,7 @@ async def check_wallet(
)
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import asyncio
import json
from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -11,6 +12,14 @@ db = Database("ext_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():
return template_renderer(["lnbits/extensions/lnticket/templates"])

View File

@ -1,6 +1,6 @@
{
"name": "Support Tickets",
"short_description": "LN support ticket system",
"icon": "contact_support",
"tile": "/lnticket/static/image/lntickets.png",
"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:
if payment.extra.get("tag") != "lnticket":
if not payment.extra or payment.extra.get("tag") != "lnticket":
# not a lnticket invoice
return

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import asyncio
from fastapi import APIRouter
from starlette.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@ -10,6 +11,14 @@ db = Database("ext_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():
return template_renderer(["lnbits/extensions/lnurldevice/templates"])

View File

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

View File

@ -1,5 +1,7 @@
from typing import List, Optional, Union
import shortuuid
from lnbits.helpers import urlsafe_short_hash
from . import db
@ -12,7 +14,7 @@ async def create_lnurldevice(
data: createLnurldevice,
) -> lnurldevices:
if data.device == "pos" or data.device == "atm":
lnurldevice_id = str(await get_lnurldeviceposcount())
lnurldevice_id = shortuuid.uuid()[:5]
else:
lnurldevice_id = 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
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:
row = await db.fetchone(
"SELECT * FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,)

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

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

View File

@ -1,18 +1,19 @@
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from lnbits.db import SQLITE
from . import db
from .models import CreatePayLinkData, 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"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
@ -28,11 +29,10 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
currency,
fiat_base_multiplier
)
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?)
{returning}
""",
(
link_id,
wallet_id,
data.description,
data.min,
@ -47,6 +47,10 @@ async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
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)
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,
amount=int(amount_received / 1000),
memo=link.description,
unhashed_description=link.lnurlpay_metadata.encode("utf-8"),
unhashed_description=link.lnurlpay_metadata.encode(),
extra={
"tag": "lnurlp",
"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_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):
id: str
id: int
wallet: str
description: str
min: float

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

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

View File

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

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