[feat] User notifications backend (#3280)
Co-authored-by: Arc <33088785+arcbtc@users.noreply.github.com>
Co-authored-by: dni ⚡ <office@dnilabs.com>
This commit is contained in:
parent
a36ab2d408
commit
dce2bfb440
15 changed files with 306 additions and 207 deletions
|
|
@ -27,7 +27,7 @@ from lnbits.core.crud.extensions import create_installed_extension
|
||||||
from lnbits.core.helpers import migrate_extension_database
|
from lnbits.core.helpers import migrate_extension_database
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services.extensions import deactivate_extension, get_valid_extensions
|
from lnbits.core.services.extensions import deactivate_extension, get_valid_extensions
|
||||||
from lnbits.core.services.notifications import enqueue_notification
|
from lnbits.core.services.notifications import enqueue_admin_notification
|
||||||
from lnbits.core.services.payments import check_pending_payments
|
from lnbits.core.services.payments import check_pending_payments
|
||||||
from lnbits.core.tasks import (
|
from lnbits.core.tasks import (
|
||||||
audit_queue,
|
audit_queue,
|
||||||
|
|
@ -104,7 +104,7 @@ async def startup(app: FastAPI):
|
||||||
# initialize tasks
|
# initialize tasks
|
||||||
register_async_tasks()
|
register_async_tasks()
|
||||||
|
|
||||||
enqueue_notification(
|
enqueue_admin_notification(
|
||||||
NotificationType.server_start_stop,
|
NotificationType.server_start_stop,
|
||||||
{
|
{
|
||||||
"message": "LNbits server started.",
|
"message": "LNbits server started.",
|
||||||
|
|
@ -118,7 +118,7 @@ async def startup(app: FastAPI):
|
||||||
|
|
||||||
async def shutdown():
|
async def shutdown():
|
||||||
logger.warning("LNbits shutting down...")
|
logger.warning("LNbits shutting down...")
|
||||||
enqueue_notification(
|
enqueue_admin_notification(
|
||||||
NotificationType.server_start_stop,
|
NotificationType.server_start_stop,
|
||||||
{
|
{
|
||||||
"message": "LNbits server shutting down...",
|
"message": "LNbits server shutting down...",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ from enum import Enum
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from lnbits.core.models.users import UserNotifications
|
||||||
|
|
||||||
|
|
||||||
class NotificationType(Enum):
|
class NotificationType(Enum):
|
||||||
server_status = "server_status"
|
server_status = "server_status"
|
||||||
|
|
@ -18,6 +20,7 @@ class NotificationType(Enum):
|
||||||
class NotificationMessage(BaseModel):
|
class NotificationMessage(BaseModel):
|
||||||
message_type: NotificationType
|
message_type: NotificationType
|
||||||
values: dict
|
values: dict
|
||||||
|
user_notifications: UserNotifications | None = None
|
||||||
|
|
||||||
|
|
||||||
NOTIFICATION_TEMPLATES = {
|
NOTIFICATION_TEMPLATES = {
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,20 @@ from lnbits.settings import settings
|
||||||
from .wallets import Wallet
|
from .wallets import Wallet
|
||||||
|
|
||||||
|
|
||||||
|
class UserNotifications(BaseModel):
|
||||||
|
nostr_identifier: str | None = None
|
||||||
|
telegram_chat_id: str | None = None
|
||||||
|
email_address: str | None = None
|
||||||
|
excluded_wallets: list[str] = []
|
||||||
|
outgoing_payments_sats: int = 0
|
||||||
|
incoming_payments_sats: int = 0
|
||||||
|
|
||||||
|
|
||||||
class UserExtra(BaseModel):
|
class UserExtra(BaseModel):
|
||||||
email_verified: bool | None = False
|
email_verified: bool | None = False
|
||||||
first_name: str | None = None
|
first_name: str | None = None
|
||||||
last_name: str | None = None
|
last_name: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
nostr_notification_identifiers: list[str] = []
|
|
||||||
telegram_chat_id: str | None = None
|
|
||||||
notifications_excluded_wallets: list[str] = []
|
|
||||||
picture: str | None = None
|
picture: str | None = None
|
||||||
# Auth provider, possible values:
|
# Auth provider, possible values:
|
||||||
# - "env": the user was created automatically by the system
|
# - "env": the user was created automatically by the system
|
||||||
|
|
@ -38,6 +44,8 @@ class UserExtra(BaseModel):
|
||||||
# how many wallets are shown in the user interface
|
# how many wallets are shown in the user interface
|
||||||
visible_wallet_count: int | None = 10
|
visible_wallet_count: int | None = 10
|
||||||
|
|
||||||
|
notifications: UserNotifications = UserNotifications()
|
||||||
|
|
||||||
|
|
||||||
class EndpointAccess(BaseModel):
|
class EndpointAccess(BaseModel):
|
||||||
path: str
|
path: str
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from .funding_source import (
|
||||||
switch_to_voidwallet,
|
switch_to_voidwallet,
|
||||||
)
|
)
|
||||||
from .lnurl import fetch_lnurl_pay_request
|
from .lnurl import fetch_lnurl_pay_request
|
||||||
from .notifications import enqueue_notification, send_payment_notification
|
from .notifications import enqueue_admin_notification, send_payment_notification
|
||||||
from .payments import (
|
from .payments import (
|
||||||
calculate_fiat_amounts,
|
calculate_fiat_amounts,
|
||||||
cancel_hold_invoice,
|
cancel_hold_invoice,
|
||||||
|
|
@ -49,7 +49,7 @@ __all__ = [
|
||||||
"create_user_account",
|
"create_user_account",
|
||||||
"create_user_account_no_ckeck",
|
"create_user_account_no_ckeck",
|
||||||
"create_wallet_invoice",
|
"create_wallet_invoice",
|
||||||
"enqueue_notification",
|
"enqueue_admin_notification",
|
||||||
"fee_reserve",
|
"fee_reserve",
|
||||||
"fee_reserve_total",
|
"fee_reserve_total",
|
||||||
"fetch_lnurl_pay_request",
|
"fetch_lnurl_pay_request",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services.notifications import enqueue_notification
|
from lnbits.core.services.notifications import enqueue_admin_notification
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_funding_source, set_funding_source
|
from lnbits.wallets import get_funding_source, set_funding_source
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ async def check_server_balance_against_node():
|
||||||
f"Balance delta reached: {status.delta_sats} sats."
|
f"Balance delta reached: {status.delta_sats} sats."
|
||||||
f" Switch to void wallet: {use_voidwallet}."
|
f" Switch to void wallet: {use_voidwallet}."
|
||||||
)
|
)
|
||||||
enqueue_notification(
|
enqueue_admin_notification(
|
||||||
NotificationType.watchdog_check,
|
NotificationType.watchdog_check,
|
||||||
{
|
{
|
||||||
"delta_sats": status.delta_sats,
|
"delta_sats": status.delta_sats,
|
||||||
|
|
@ -71,7 +71,7 @@ async def check_balance_delta_changed():
|
||||||
settings.latest_balance_delta_sats = status.delta_sats
|
settings.latest_balance_delta_sats = status.delta_sats
|
||||||
return
|
return
|
||||||
if status.delta_sats != settings.latest_balance_delta_sats:
|
if status.delta_sats != settings.latest_balance_delta_sats:
|
||||||
enqueue_notification(
|
enqueue_admin_notification(
|
||||||
NotificationType.balance_delta,
|
NotificationType.balance_delta,
|
||||||
{
|
{
|
||||||
"delta_sats": status.delta_sats,
|
"delta_sats": status.delta_sats,
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,14 @@ from lnbits.core.crud import (
|
||||||
get_webpush_subscriptions_for_user,
|
get_webpush_subscriptions_for_user,
|
||||||
mark_webhook_sent,
|
mark_webhook_sent,
|
||||||
)
|
)
|
||||||
|
from lnbits.core.crud.users import get_user
|
||||||
from lnbits.core.models import Payment, Wallet
|
from lnbits.core.models import Payment, Wallet
|
||||||
from lnbits.core.models.notifications import (
|
from lnbits.core.models.notifications import (
|
||||||
NOTIFICATION_TEMPLATES,
|
NOTIFICATION_TEMPLATES,
|
||||||
NotificationMessage,
|
NotificationMessage,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
)
|
)
|
||||||
|
from lnbits.core.models.users import UserNotifications
|
||||||
from lnbits.core.services.nostr import fetch_nip5_details, send_nostr_dm
|
from lnbits.core.services.nostr import fetch_nip5_details, send_nostr_dm
|
||||||
from lnbits.core.services.websockets import websocket_manager
|
from lnbits.core.services.websockets import websocket_manager
|
||||||
from lnbits.helpers import check_callback_url, is_valid_email_address
|
from lnbits.helpers import check_callback_url, is_valid_email_address
|
||||||
|
|
@ -31,8 +33,8 @@ from lnbits.utils.nostr import normalize_private_key
|
||||||
notifications_queue: asyncio.Queue[NotificationMessage] = asyncio.Queue()
|
notifications_queue: asyncio.Queue[NotificationMessage] = asyncio.Queue()
|
||||||
|
|
||||||
|
|
||||||
def enqueue_notification(message_type: NotificationType, values: dict) -> None:
|
def enqueue_admin_notification(message_type: NotificationType, values: dict) -> None:
|
||||||
if not is_message_type_enabled(message_type):
|
if not _is_message_type_enabled(message_type):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
notifications_queue.put_nowait(
|
notifications_queue.put_nowait(
|
||||||
|
|
@ -42,62 +44,125 @@ def enqueue_notification(message_type: NotificationType, values: dict) -> None:
|
||||||
logger.error(f"Error enqueuing notification: {e}")
|
logger.error(f"Error enqueuing notification: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def enqueue_user_notification(
|
||||||
|
message_type: NotificationType, values: dict, user_notifications: UserNotifications
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
notifications_queue.put_nowait(
|
||||||
|
NotificationMessage(
|
||||||
|
message_type=message_type,
|
||||||
|
values=values,
|
||||||
|
user_notifications=user_notifications,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error enqueuing notification: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def process_next_notification() -> None:
|
async def process_next_notification() -> None:
|
||||||
notification_message = await notifications_queue.get()
|
notification_message = await notifications_queue.get()
|
||||||
message_type, text = _notification_message_to_text(notification_message)
|
message_type, text = _notification_message_to_text(notification_message)
|
||||||
await send_notification(text, message_type)
|
user_notifications = notification_message.user_notifications
|
||||||
|
if user_notifications:
|
||||||
|
await send_user_notification(user_notifications, text, message_type)
|
||||||
|
else:
|
||||||
|
await send_admin_notification(text, message_type)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_admin_notification(
|
||||||
|
message: str,
|
||||||
|
message_type: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
return await send_notification(
|
||||||
|
settings.lnbits_telegram_notifications_chat_id,
|
||||||
|
settings.lnbits_nostr_notifications_identifiers,
|
||||||
|
settings.lnbits_email_notifications_to_emails,
|
||||||
|
message,
|
||||||
|
message_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_user_notification(
|
||||||
|
user_notifications: UserNotifications,
|
||||||
|
message: str,
|
||||||
|
message_type: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
email_address = (
|
||||||
|
[user_notifications.email_address] if user_notifications.email_address else []
|
||||||
|
)
|
||||||
|
nostr_identifiers = (
|
||||||
|
[user_notifications.nostr_identifier]
|
||||||
|
if user_notifications.nostr_identifier
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
return await send_notification(
|
||||||
|
user_notifications.telegram_chat_id,
|
||||||
|
nostr_identifiers,
|
||||||
|
email_address,
|
||||||
|
message,
|
||||||
|
message_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def send_notification(
|
async def send_notification(
|
||||||
|
telegram_chat_id: str | None,
|
||||||
|
nostr_identifiers: list[str] | None,
|
||||||
|
email_addresses: list[str] | None,
|
||||||
message: str,
|
message: str,
|
||||||
message_type: Optional[str] = None,
|
message_type: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
if settings.lnbits_telegram_notifications_enabled:
|
if telegram_chat_id and settings.is_telegram_notifications_configured():
|
||||||
await send_telegram_notification(message)
|
await send_telegram_notification(telegram_chat_id, message)
|
||||||
logger.debug(f"Sent telegram notification: {message_type}")
|
logger.debug(f"Sent telegram notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending telegram notification {message_type}: {e}")
|
logger.error(f"Error sending telegram notification {message_type}: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if settings.lnbits_nostr_notifications_enabled:
|
if nostr_identifiers and settings.is_nostr_notifications_configured():
|
||||||
await send_nostr_notification(message)
|
await send_nostr_notifications(nostr_identifiers, message)
|
||||||
logger.debug(f"Sent nostr notification: {message_type}")
|
logger.debug(f"Sent nostr notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending nostr notification {message_type}: {e}")
|
logger.error(f"Error sending nostr notification {message_type}: {e}")
|
||||||
try:
|
try:
|
||||||
if settings.lnbits_email_notifications_enabled:
|
if email_addresses and settings.lnbits_email_notifications_enabled:
|
||||||
await send_email_notification(message)
|
await send_email_notification(email_addresses, message)
|
||||||
logger.debug(f"Sent email notification: {message_type}")
|
logger.debug(f"Sent email notification: {message_type}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending email notification {message_type}: {e}")
|
logger.error(f"Error sending email notification {message_type}: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def send_nostr_notification(message: str) -> dict:
|
async def send_nostr_notifications(identifiers: list[str], message: str) -> list[str]:
|
||||||
for i in settings.lnbits_nostr_notifications_identifiers:
|
success_sent: list[str] = []
|
||||||
|
for identifier in identifiers:
|
||||||
try:
|
try:
|
||||||
identifier = await fetch_nip5_details(i)
|
await send_nostr_notification(identifier, message)
|
||||||
user_pubkey = identifier[0]
|
success_sent.append(identifier)
|
||||||
relays = identifier[1]
|
|
||||||
server_private_key = normalize_private_key(
|
|
||||||
settings.lnbits_nostr_notifications_private_key
|
|
||||||
)
|
|
||||||
await send_nostr_dm(
|
|
||||||
server_private_key,
|
|
||||||
user_pubkey,
|
|
||||||
message,
|
|
||||||
relays,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error notifying identifier {i}: {e}")
|
logger.warning(f"Error notifying identifier {identifier}: {e}")
|
||||||
|
return success_sent
|
||||||
return {"status": "ok"}
|
|
||||||
|
|
||||||
|
|
||||||
async def send_telegram_notification(message: str) -> dict:
|
async def send_nostr_notification(identifier: str, message: str):
|
||||||
|
nip5 = await fetch_nip5_details(identifier)
|
||||||
|
user_pubkey = nip5[0]
|
||||||
|
relays = nip5[1]
|
||||||
|
server_private_key = normalize_private_key(
|
||||||
|
settings.lnbits_nostr_notifications_private_key
|
||||||
|
)
|
||||||
|
await send_nostr_dm(
|
||||||
|
server_private_key,
|
||||||
|
user_pubkey,
|
||||||
|
message,
|
||||||
|
relays,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_telegram_notification(chat_id: str, message: str) -> dict:
|
||||||
return await send_telegram_message(
|
return await send_telegram_message(
|
||||||
settings.lnbits_telegram_notifications_access_token,
|
settings.lnbits_telegram_notifications_access_token,
|
||||||
settings.lnbits_telegram_notifications_chat_id,
|
chat_id,
|
||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -112,7 +177,7 @@ async def send_telegram_message(token: str, chat_id: str, message: str) -> dict:
|
||||||
|
|
||||||
|
|
||||||
async def send_email_notification(
|
async def send_email_notification(
|
||||||
message: str, subject: str = "LNbits Notification"
|
to_emails: list[str], message: str, subject: str = "LNbits Notification"
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if not settings.lnbits_email_notifications_enabled:
|
if not settings.lnbits_email_notifications_enabled:
|
||||||
return {"status": "error", "message": "Email notifications are disabled"}
|
return {"status": "error", "message": "Email notifications are disabled"}
|
||||||
|
|
@ -123,7 +188,7 @@ async def send_email_notification(
|
||||||
settings.lnbits_email_notifications_username,
|
settings.lnbits_email_notifications_username,
|
||||||
settings.lnbits_email_notifications_password,
|
settings.lnbits_email_notifications_password,
|
||||||
settings.lnbits_email_notifications_email,
|
settings.lnbits_email_notifications_email,
|
||||||
settings.lnbits_email_notifications_to_emails,
|
to_emails,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
|
|
@ -163,41 +228,6 @@ async def send_email(
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def is_message_type_enabled(message_type: NotificationType) -> bool:
|
|
||||||
if message_type == NotificationType.balance_update:
|
|
||||||
return settings.lnbits_notification_credit_debit
|
|
||||||
if message_type == NotificationType.settings_update:
|
|
||||||
return settings.lnbits_notification_settings_update
|
|
||||||
if message_type == NotificationType.watchdog_check:
|
|
||||||
return settings.lnbits_notification_watchdog
|
|
||||||
if message_type == NotificationType.balance_delta:
|
|
||||||
return settings.notification_balance_delta_changed
|
|
||||||
if message_type == NotificationType.server_start_stop:
|
|
||||||
return settings.lnbits_notification_server_start_stop
|
|
||||||
if message_type == NotificationType.server_status:
|
|
||||||
return settings.lnbits_notification_server_status_hours > 0
|
|
||||||
if message_type == NotificationType.incoming_payment:
|
|
||||||
return settings.lnbits_notification_incoming_payment_amount_sats > 0
|
|
||||||
if message_type == NotificationType.outgoing_payment:
|
|
||||||
return settings.lnbits_notification_outgoing_payment_amount_sats > 0
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _notification_message_to_text(
|
|
||||||
notification_message: NotificationMessage,
|
|
||||||
) -> tuple[str, str]:
|
|
||||||
message_type = notification_message.message_type.value
|
|
||||||
meesage_value = NOTIFICATION_TEMPLATES.get(message_type, message_type)
|
|
||||||
try:
|
|
||||||
text = meesage_value.format(**notification_message.values)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Error formatting notification message: {e}")
|
|
||||||
text = meesage_value
|
|
||||||
text = f"""[{settings.lnbits_site_title}]\n{text}"""
|
|
||||||
return message_type, text
|
|
||||||
|
|
||||||
|
|
||||||
async def dispatch_webhook(payment: Payment):
|
async def dispatch_webhook(payment: Payment):
|
||||||
"""
|
"""
|
||||||
Dispatches the webhook to the webhook url.
|
Dispatches the webhook to the webhook url.
|
||||||
|
|
@ -231,7 +261,7 @@ async def send_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending websocket payment notification {e!s}")
|
logger.error(f"Error sending websocket payment notification {e!s}")
|
||||||
try:
|
try:
|
||||||
send_chat_payment_notification(wallet, payment)
|
await send_chat_payment_notification(wallet, payment)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending chat payment notification {e!s}")
|
logger.error(f"Error sending chat payment notification {e!s}")
|
||||||
try:
|
try:
|
||||||
|
|
@ -268,7 +298,7 @@ async def send_ws_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def send_chat_payment_notification(wallet: Wallet, payment: Payment):
|
async def send_chat_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
amount_sats = abs(payment.sat)
|
amount_sats = abs(payment.sat)
|
||||||
values: dict = {
|
values: dict = {
|
||||||
"wallet_id": wallet.id,
|
"wallet_id": wallet.id,
|
||||||
|
|
@ -283,10 +313,23 @@ def send_chat_payment_notification(wallet: Wallet, payment: Payment):
|
||||||
|
|
||||||
if payment.is_out:
|
if payment.is_out:
|
||||||
if amount_sats >= settings.lnbits_notification_outgoing_payment_amount_sats:
|
if amount_sats >= settings.lnbits_notification_outgoing_payment_amount_sats:
|
||||||
enqueue_notification(NotificationType.outgoing_payment, values)
|
enqueue_admin_notification(NotificationType.outgoing_payment, values)
|
||||||
else:
|
elif amount_sats >= settings.lnbits_notification_incoming_payment_amount_sats:
|
||||||
if amount_sats >= settings.lnbits_notification_incoming_payment_amount_sats:
|
enqueue_admin_notification(NotificationType.incoming_payment, values)
|
||||||
enqueue_notification(NotificationType.incoming_payment, values)
|
|
||||||
|
user = await get_user(wallet.user)
|
||||||
|
user_notifications = user.extra.notifications if user else None
|
||||||
|
if user_notifications and wallet.id not in user_notifications.excluded_wallets:
|
||||||
|
out_limit = user_notifications.outgoing_payments_sats
|
||||||
|
in_limit = user_notifications.incoming_payments_sats
|
||||||
|
if payment.is_out and (amount_sats >= out_limit):
|
||||||
|
enqueue_user_notification(
|
||||||
|
NotificationType.outgoing_payment, values, user_notifications
|
||||||
|
)
|
||||||
|
elif amount_sats >= in_limit:
|
||||||
|
enqueue_user_notification(
|
||||||
|
NotificationType.incoming_payment, values, user_notifications
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def send_payment_push_notification(wallet: Wallet, payment: Payment):
|
async def send_payment_push_notification(wallet: Wallet, payment: Payment):
|
||||||
|
|
@ -330,3 +373,38 @@ async def send_push_notification(subscription, title, body, url=""):
|
||||||
f"failed sending push notification: "
|
f"failed sending push notification: "
|
||||||
f"{e.response.text if e.response else e}"
|
f"{e.response.text if e.response else e}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_message_type_enabled(message_type: NotificationType) -> bool:
|
||||||
|
if message_type == NotificationType.balance_update:
|
||||||
|
return settings.lnbits_notification_credit_debit
|
||||||
|
if message_type == NotificationType.settings_update:
|
||||||
|
return settings.lnbits_notification_settings_update
|
||||||
|
if message_type == NotificationType.watchdog_check:
|
||||||
|
return settings.lnbits_notification_watchdog
|
||||||
|
if message_type == NotificationType.balance_delta:
|
||||||
|
return settings.notification_balance_delta_changed
|
||||||
|
if message_type == NotificationType.server_start_stop:
|
||||||
|
return settings.lnbits_notification_server_start_stop
|
||||||
|
if message_type == NotificationType.server_status:
|
||||||
|
return settings.lnbits_notification_server_status_hours > 0
|
||||||
|
if message_type == NotificationType.incoming_payment:
|
||||||
|
return settings.lnbits_notification_incoming_payment_amount_sats > 0
|
||||||
|
if message_type == NotificationType.outgoing_payment:
|
||||||
|
return settings.lnbits_notification_outgoing_payment_amount_sats > 0
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _notification_message_to_text(
|
||||||
|
notification_message: NotificationMessage,
|
||||||
|
) -> tuple[str, str]:
|
||||||
|
message_type = notification_message.message_type.value
|
||||||
|
meesage_value = NOTIFICATION_TEMPLATES.get(message_type, message_type)
|
||||||
|
try:
|
||||||
|
text = meesage_value.format(**notification_message.values)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error formatting notification message: {e}")
|
||||||
|
text = meesage_value
|
||||||
|
text = f"""[{settings.lnbits_site_title}]\n{text}"""
|
||||||
|
return message_type, text
|
||||||
|
|
|
||||||
|
|
@ -91,12 +91,8 @@ async def pay_invoice(
|
||||||
|
|
||||||
payment = await _pay_invoice(wallet.id, create_payment_model, conn)
|
payment = await _pay_invoice(wallet.id, create_payment_model, conn)
|
||||||
|
|
||||||
service_fee_memo = f"""
|
|
||||||
Service fee for payment of {abs(payment.sat)} sats.
|
|
||||||
Wallet: '{wallet.name}' ({wallet.id})."""
|
|
||||||
|
|
||||||
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
|
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
|
||||||
await _credit_service_fee_wallet(payment, service_fee_memo, new_conn)
|
await _credit_service_fee_wallet(wallet, payment, new_conn)
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
|
@ -905,12 +901,16 @@ def _validate_payment_request(
|
||||||
|
|
||||||
|
|
||||||
async def _credit_service_fee_wallet(
|
async def _credit_service_fee_wallet(
|
||||||
payment: Payment, memo: str, conn: Optional[Connection] = None
|
wallet: Wallet, payment: Payment, conn: Optional[Connection] = None
|
||||||
):
|
):
|
||||||
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
|
service_fee_msat = service_fee(payment.amount, internal=payment.is_internal)
|
||||||
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
|
if not settings.lnbits_service_fee_wallet or not service_fee_msat:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
memo = f"""
|
||||||
|
Service fee for payment of {abs(payment.sat)} sats.
|
||||||
|
Wallet: '{wallet.name}' ({wallet.id})."""
|
||||||
|
|
||||||
create_payment_model = CreatePayment(
|
create_payment_model = CreatePayment(
|
||||||
wallet_id=settings.lnbits_service_fee_wallet,
|
wallet_id=settings.lnbits_service_fee_wallet,
|
||||||
bolt11=payment.bolt11,
|
bolt11=payment.bolt11,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ from lnbits.core.services.funding_source import (
|
||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
)
|
)
|
||||||
from lnbits.core.services.notifications import (
|
from lnbits.core.services.notifications import (
|
||||||
enqueue_notification,
|
enqueue_admin_notification,
|
||||||
process_next_notification,
|
process_next_notification,
|
||||||
send_payment_notification,
|
send_payment_notification,
|
||||||
)
|
)
|
||||||
|
|
@ -87,7 +87,7 @@ async def _notify_server_status() -> None:
|
||||||
"lnbits_balance_sats": status.lnbits_balance_sats,
|
"lnbits_balance_sats": status.lnbits_balance_sats,
|
||||||
"node_balance_sats": status.node_balance_sats,
|
"node_balance_sats": status.node_balance_sats,
|
||||||
}
|
}
|
||||||
enqueue_notification(NotificationType.server_status, values)
|
enqueue_admin_notification(NotificationType.server_status, values)
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue) -> None:
|
async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue) -> None:
|
||||||
|
|
@ -123,7 +123,7 @@ async def wait_notification_messages() -> None:
|
||||||
try:
|
try:
|
||||||
await process_next_notification()
|
await process_next_notification()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.log("error", ex)
|
logger.warning("Payment notification error", ex)
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,43 +7,71 @@
|
||||||
<div class="row q-col-gutter-md q-mb-md">
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<q-card>
|
<q-card>
|
||||||
<div>
|
<div class="q-pa-sm">
|
||||||
<div class="q-gutter-y-md">
|
<div class="row items-center justify-between q-gutter-xs">
|
||||||
<q-tabs v-model="tab" align="left">
|
<div class="col">
|
||||||
<q-tab
|
<q-btn @click="updateAccount" unelevated color="primary">
|
||||||
name="user"
|
<span v-text="$t('update_account')"></span>
|
||||||
:label="$t('account_settings')"
|
</q-btn>
|
||||||
@update="val => tab = val.name"
|
</div>
|
||||||
></q-tab>
|
|
||||||
<q-tab
|
|
||||||
name="theme"
|
|
||||||
:label="$t('look_and_feel')"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
></q-tab>
|
|
||||||
<q-tab
|
|
||||||
name="notifications"
|
|
||||||
:label="$t('notifications')"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
></q-tab>
|
|
||||||
<q-tab
|
|
||||||
name="api_acls"
|
|
||||||
:label="$t('access_control_list')"
|
|
||||||
@update="val => tab = val.name"
|
|
||||||
></q-tab>
|
|
||||||
</q-tabs>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
<div v-if="user" class="col-md-12 col-lg-6 q-gutter-y-md">
|
<div class="col-12">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-splitter>
|
||||||
<div class="row">
|
<template v-slot:before>
|
||||||
<div class="col">
|
<q-tabs v-model="tab" vertical active-color="primary">
|
||||||
<q-tab-panels v-model="tab">
|
<q-tab
|
||||||
|
name="user"
|
||||||
|
icon="person"
|
||||||
|
:label="$q.screen.gt.sm ? $t('account_settings') : ''"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
>
|
||||||
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
||||||
|
><span v-text="$t('account_settings')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-tab>
|
||||||
|
|
||||||
|
<q-tab
|
||||||
|
name="notifications"
|
||||||
|
icon="notifications"
|
||||||
|
:label="$q.screen.gt.sm ? $t('notifications') : ''"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
>
|
||||||
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
||||||
|
><span v-text="$t('notifications')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-tab>
|
||||||
|
<q-tab
|
||||||
|
name="theme"
|
||||||
|
icon="palette"
|
||||||
|
:label="$q.screen.gt.sm ? $t('look_and_feel') : ''"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
>
|
||||||
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
||||||
|
><span v-text="$t('look_and_feel')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-tab>
|
||||||
|
<q-tab
|
||||||
|
name="api_acls"
|
||||||
|
icon="lock"
|
||||||
|
:label="$q.screen.gt.sm ? $t('access_control_list') : ''"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
>
|
||||||
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
||||||
|
><span v-text="$t('access_control_list')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-tab>
|
||||||
|
</q-tabs>
|
||||||
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<q-scroll-area style="height: 80vh">
|
||||||
|
<q-tab-panels v-if="user" v-model="tab">
|
||||||
<q-tab-panel name="user">
|
<q-tab-panel name="user">
|
||||||
<div v-if="credentialsData.show">
|
<div v-if="credentialsData.show">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
|
|
@ -298,9 +326,6 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-btn @click="updateAccount" unelevated color="primary">
|
|
||||||
<span v-text="$t('update_account')"></span>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="showUpdateCredentials()"
|
@click="showUpdateCredentials()"
|
||||||
:label="$t('change_password')"
|
:label="$t('change_password')"
|
||||||
|
|
@ -564,63 +589,80 @@
|
||||||
</q-select>
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-separator></q-separator>
|
|
||||||
<div class="row q-mb-md q-mt-md">
|
|
||||||
<q-btn @click="updateAccount" unelevated color="primary">
|
|
||||||
<span v-text="$t('update_account')"></span>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="notifications">
|
<q-tab-panel name="notifications">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<span
|
<span
|
||||||
v-text="$t('notifications_nostr_identifiers')"
|
v-text="$t('notifications_nostr_identifier')"
|
||||||
></span>
|
></span>
|
||||||
|
{%if not nostr_configured%}
|
||||||
|
<br />
|
||||||
|
<q-badge v-text="$t('not_connected')"></q-badge>
|
||||||
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model="notifications.nostr.identifier"
|
v-model="user.extra.notifications.nostr_identifier"
|
||||||
:hint="$t('notifications_nostr_identifiers_desc')"
|
:hint="$t('notifications_nostr_identifier_desc')"
|
||||||
@keydown.enter="addNostrNotificationIdentifier"
|
|
||||||
>
|
>
|
||||||
<q-btn
|
|
||||||
@click="addNostrNotificationIdentifier()"
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
icon="add"
|
|
||||||
></q-btn>
|
|
||||||
</q-input>
|
</q-input>
|
||||||
<div>
|
|
||||||
<q-chip
|
|
||||||
v-for="identifier in user.extra.nostr_notification_identifiers"
|
|
||||||
:key="identifier"
|
|
||||||
removable
|
|
||||||
@remove="removeNostrNotificationIdentifier(identifier)"
|
|
||||||
color="primary"
|
|
||||||
text-color="white"
|
|
||||||
><span class="ellipsis" v-text="identifier"></span
|
|
||||||
></q-chip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<span v-text="$t('notifications_chat_id')"></span>
|
<span v-text="$t('notifications_chat_id')"></span>
|
||||||
|
{%if not telegram_configured%}
|
||||||
|
<br />
|
||||||
|
<q-badge v-text="$t('not_connected')"></q-badge>
|
||||||
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model="user.extra.telegram_chat_id"
|
v-model="user.extra.notifications.telegram_chat_id"
|
||||||
:hint="$t('notifications_chat_id_desc')"
|
:hint="$t('notifications_chat_id_desc')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<q-separator class="q-mb-md"></q-separator>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<span v-text="$t('notification_outgoing_payment')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="user.extra.notifications.outgoing_payments_sats"
|
||||||
|
:hint="$t('notification_outgoing_payment_desc')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<span v-text="$t('notification_incoming_payment')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
v-model="user.extra.notifications.incoming_payments_sats"
|
||||||
|
:hint="$t('notification_incoming_payment_desc')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<span v-text="$t('exclude_wallets')"></span>
|
<span v-text="$t('exclude_wallets')"></span>
|
||||||
|
|
@ -632,7 +674,7 @@
|
||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
multiple
|
multiple
|
||||||
v-model="user.extra.notifications_excluded_wallets"
|
v-model="user.extra.notifications.excluded_wallets"
|
||||||
:options="g.user.walletOptions"
|
:options="g.user.walletOptions"
|
||||||
:label="$t('exclude_wallets')"
|
:label="$t('exclude_wallets')"
|
||||||
:hint="$t('notifications_excluded_wallets_desc')"
|
:hint="$t('notifications_excluded_wallets_desc')"
|
||||||
|
|
@ -642,14 +684,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section>
|
|
||||||
<q-separator></q-separator>
|
|
||||||
<div class="row q-mb-md q-mt-md">
|
|
||||||
<q-btn @click="updateAccount" unelevated color="primary">
|
|
||||||
<span v-text="$t('update_account')"></span>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="api_acls">
|
<q-tab-panel name="api_acls">
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
|
|
@ -876,16 +910,9 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</div>
|
</q-scroll-area>
|
||||||
</div>
|
</template>
|
||||||
</q-card-section>
|
</q-splitter>
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
<div v-else class="col-12 col-md-6 q-gutter-y-md">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<h4 class="q-my-none"><span v-text="$t('account')"></span></h4>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from lnbits.core.models import User
|
||||||
from lnbits.core.models.misc import Image, SimpleStatus
|
from lnbits.core.models.misc import Image, SimpleStatus
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services import (
|
from lnbits.core.services import (
|
||||||
enqueue_notification,
|
enqueue_admin_notification,
|
||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
update_cached_settings,
|
update_cached_settings,
|
||||||
)
|
)
|
||||||
|
|
@ -65,7 +65,9 @@ async def api_monitor():
|
||||||
)
|
)
|
||||||
async def api_test_email():
|
async def api_test_email():
|
||||||
return await send_email_notification(
|
return await send_email_notification(
|
||||||
"This is a LNbits test email.", "LNbits Test Email"
|
settings.lnbits_email_notifications_to_emails,
|
||||||
|
"This is a LNbits test email.",
|
||||||
|
"LNbits Test Email",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,7 +84,9 @@ async def api_get_settings(
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
)
|
)
|
||||||
async def api_update_settings(data: UpdateSettings, user: User = Depends(check_admin)):
|
async def api_update_settings(data: UpdateSettings, user: User = Depends(check_admin)):
|
||||||
enqueue_notification(NotificationType.settings_update, {"username": user.username})
|
enqueue_admin_notification(
|
||||||
|
NotificationType.settings_update, {"username": user.username}
|
||||||
|
)
|
||||||
await update_admin_settings(data)
|
await update_admin_settings(data)
|
||||||
admin_settings = await get_admin_settings(user.super_user)
|
admin_settings = await get_admin_settings(user.super_user)
|
||||||
if not admin_settings:
|
if not admin_settings:
|
||||||
|
|
@ -113,7 +117,9 @@ async def api_reset_settings(field_name: str):
|
||||||
|
|
||||||
@admin_router.delete("/api/v1/settings", status_code=HTTPStatus.OK)
|
@admin_router.delete("/api/v1/settings", status_code=HTTPStatus.OK)
|
||||||
async def api_delete_settings(user: User = Depends(check_super_user)) -> None:
|
async def api_delete_settings(user: User = Depends(check_super_user)) -> None:
|
||||||
enqueue_notification(NotificationType.settings_update, {"username": user.username})
|
enqueue_admin_notification(
|
||||||
|
NotificationType.settings_update, {"username": user.username}
|
||||||
|
)
|
||||||
await reset_core_settings()
|
await reset_core_settings()
|
||||||
server_restart.set()
|
server_restart.set()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -345,7 +345,6 @@ async def node(request: Request, user: User = Depends(check_admin)):
|
||||||
"node/index.html",
|
"node/index.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
"settings": settings.dict(),
|
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
"wallets": user.wallets[0].json(),
|
"wallets": user.wallets[0].json(),
|
||||||
"ajax": _is_ajax_request(request),
|
"ajax": _is_ajax_request(request),
|
||||||
|
|
@ -383,7 +382,6 @@ async def admin_index(request: Request, user: User = Depends(check_admin)):
|
||||||
"admin/index.html",
|
"admin/index.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
"settings": settings.dict(),
|
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
"currencies": list(currencies.keys()),
|
"currencies": list(currencies.keys()),
|
||||||
"ajax": _is_ajax_request(request),
|
"ajax": _is_ajax_request(request),
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.models.users import Account
|
from lnbits.core.models.users import Account
|
||||||
from lnbits.core.services import (
|
from lnbits.core.services import (
|
||||||
create_user_account_no_ckeck,
|
create_user_account_no_ckeck,
|
||||||
enqueue_notification,
|
enqueue_admin_notification,
|
||||||
update_user_account,
|
update_user_account,
|
||||||
update_user_extensions,
|
update_user_extensions,
|
||||||
update_wallet_balance,
|
update_wallet_balance,
|
||||||
|
|
@ -321,7 +321,7 @@ async def api_update_balance(data: UpdateBalance) -> SimpleStatus:
|
||||||
if not wallet:
|
if not wallet:
|
||||||
raise HTTPException(HTTPStatus.NOT_FOUND, "Wallet not found.")
|
raise HTTPException(HTTPStatus.NOT_FOUND, "Wallet not found.")
|
||||||
await update_wallet_balance(wallet=wallet, amount=int(data.amount))
|
await update_wallet_balance(wallet=wallet, amount=int(data.amount))
|
||||||
enqueue_notification(
|
enqueue_admin_notification(
|
||||||
NotificationType.balance_update,
|
NotificationType.balance_update,
|
||||||
{
|
{
|
||||||
"amount": int(data.amount),
|
"amount": int(data.amount),
|
||||||
|
|
|
||||||
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -230,6 +230,9 @@ window.localisation.en = {
|
||||||
notifications_nostr_private_key: 'Nostr Private Key',
|
notifications_nostr_private_key: 'Nostr Private Key',
|
||||||
notifications_nostr_private_key_desc:
|
notifications_nostr_private_key_desc:
|
||||||
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
||||||
|
notifications_nostr_identifier: 'Nostr Identifier',
|
||||||
|
notifications_nostr_identifier_desc:
|
||||||
|
'Nip5 identifier to send notifications to',
|
||||||
notifications_nostr_identifiers: 'Nostr Identifiers',
|
notifications_nostr_identifiers: 'Nostr Identifiers',
|
||||||
notifications_nostr_identifiers_desc:
|
notifications_nostr_identifiers_desc:
|
||||||
'List of identifiers to send notifications to',
|
'List of identifiers to send notifications to',
|
||||||
|
|
@ -646,5 +649,7 @@ window.localisation.en = {
|
||||||
'Signing secret for the webhook. Messages will be signed with this secret.',
|
'Signing secret for the webhook. Messages will be signed with this secret.',
|
||||||
callback_success_url: 'Callback Success URL',
|
callback_success_url: 'Callback Success URL',
|
||||||
callback_success_url_hint:
|
callback_success_url_hint:
|
||||||
'The user will be redirected to this URL after the payment is successful'
|
'The user will be redirected to this URL after the payment is successful',
|
||||||
|
connected: 'Connected',
|
||||||
|
not_connected: 'Not Connected'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -399,32 +399,6 @@ window.AccountPageLogic = {
|
||||||
} finally {
|
} finally {
|
||||||
this.apiAcl.password = ''
|
this.apiAcl.password = ''
|
||||||
}
|
}
|
||||||
},
|
|
||||||
addNostrNotificationIdentifier() {
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
||||||
if (!emailRegex.test(this.notifications.nostr.identifier)) {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Invalid email format.'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const identifier = this.user.extra.nostr_notification_identifiers.find(
|
|
||||||
i => i === this.notifications.nostr.identifier
|
|
||||||
)
|
|
||||||
if (identifier) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.user.extra.nostr_notification_identifiers.push(
|
|
||||||
this.notifications.nostr.identifier
|
|
||||||
)
|
|
||||||
this.notifications.nostr.identifier = ''
|
|
||||||
},
|
|
||||||
removeNostrNotificationIdentifier(identifier) {
|
|
||||||
this.user.extra.nostr_notification_identifiers =
|
|
||||||
this.user.extra.nostr_notification_identifiers.filter(
|
|
||||||
i => i !== identifier
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue