feat: unified notification delivery for webhook events (email + WS support)

- Replace direct bot.send_message with notification_delivery_service
- Email-only and OAuth users now receive webhook notifications via email/WS
- Add 10 new NotificationType enum values for webhook subscription events
- Map all webhook text_keys to NotificationType for unified routing
This commit is contained in:
Fringg
2026-02-10 05:16:59 +03:00
parent 6d67cad3e7
commit 26637f0ae5
2 changed files with 51 additions and 15 deletions
@@ -57,6 +57,18 @@ class NotificationType(Enum):
EMAIL_VERIFICATION = 'email_verification'
PASSWORD_RESET = 'password_reset'
# Webhook subscription events
WEBHOOK_SUB_EXPIRED = 'webhook_sub_expired'
WEBHOOK_SUB_DISABLED = 'webhook_sub_disabled'
WEBHOOK_SUB_ENABLED = 'webhook_sub_enabled'
WEBHOOK_SUB_LIMITED = 'webhook_sub_limited'
WEBHOOK_SUB_TRAFFIC_RESET = 'webhook_sub_traffic_reset'
WEBHOOK_SUB_DELETED = 'webhook_sub_deleted'
WEBHOOK_SUB_REVOKED = 'webhook_sub_revoked'
WEBHOOK_SUB_EXPIRING = 'webhook_sub_expiring'
WEBHOOK_SUB_FIRST_CONNECTED = 'webhook_sub_first_connected'
WEBHOOK_SUB_BANDWIDTH_THRESHOLD = 'webhook_sub_bandwidth_threshold'
# Other
BROADCAST = 'broadcast'
PAYMENT_RECEIVED = 'payment_received'
+39 -15
View File
@@ -13,7 +13,6 @@ from datetime import UTC, datetime
from typing import Any
from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError
from aiogram.types import InlineKeyboardMarkup
from sqlalchemy.ext.asyncio import AsyncSession
@@ -27,12 +26,31 @@ from app.database.crud.subscription import (
from app.database.crud.user import get_user_by_remnawave_uuid, get_user_by_telegram_id
from app.database.models import Subscription, SubscriptionStatus, User
from app.localization.texts import get_texts
from app.services.notification_delivery_service import NotificationType, notification_delivery_service
from app.utils.miniapp_buttons import build_miniapp_or_callback_button
logger = logging.getLogger(__name__)
# Mapping from locale text_key to NotificationType for unified delivery
_TEXT_KEY_TO_NOTIFICATION_TYPE: dict[str, NotificationType] = {
'WEBHOOK_SUB_EXPIRED': NotificationType.WEBHOOK_SUB_EXPIRED,
'WEBHOOK_SUB_DISABLED': NotificationType.WEBHOOK_SUB_DISABLED,
'WEBHOOK_SUB_ENABLED': NotificationType.WEBHOOK_SUB_ENABLED,
'WEBHOOK_SUB_LIMITED': NotificationType.WEBHOOK_SUB_LIMITED,
'WEBHOOK_SUB_TRAFFIC_RESET': NotificationType.WEBHOOK_SUB_TRAFFIC_RESET,
'WEBHOOK_SUB_DELETED': NotificationType.WEBHOOK_SUB_DELETED,
'WEBHOOK_SUB_REVOKED': NotificationType.WEBHOOK_SUB_REVOKED,
'WEBHOOK_SUB_EXPIRES_72H': NotificationType.WEBHOOK_SUB_EXPIRING,
'WEBHOOK_SUB_EXPIRES_48H': NotificationType.WEBHOOK_SUB_EXPIRING,
'WEBHOOK_SUB_EXPIRES_24H': NotificationType.WEBHOOK_SUB_EXPIRING,
'WEBHOOK_SUB_EXPIRED_24H_AGO': NotificationType.WEBHOOK_SUB_EXPIRED,
'WEBHOOK_SUB_FIRST_CONNECTED': NotificationType.WEBHOOK_SUB_FIRST_CONNECTED,
'WEBHOOK_SUB_BANDWIDTH_THRESHOLD': NotificationType.WEBHOOK_SUB_BANDWIDTH_THRESHOLD,
}
class RemnaWaveWebhookService:
"""Processes incoming webhooks from RemnaWave backend."""
@@ -143,10 +161,12 @@ class RemnaWaveWebhookService:
reply_markup: InlineKeyboardMarkup | None = None,
format_kwargs: dict[str, Any] | None = None,
) -> None:
"""Send a notification to user via Telegram."""
if not user.telegram_id:
return
"""Send a notification to user via appropriate channel.
Telegram users receive a bot message; email-only users receive
an email and/or WebSocket notification through the unified
notification delivery service.
"""
texts = get_texts(user.language)
message = texts.get(text_key)
if not message:
@@ -160,17 +180,21 @@ class RemnaWaveWebhookService:
logger.warning('Failed to format message %s with kwargs %s', text_key, format_kwargs)
return
try:
await self.bot.send_message(
chat_id=user.telegram_id,
text=message,
reply_markup=reply_markup,
parse_mode='HTML',
)
except TelegramForbiddenError:
logger.warning('User %s blocked the bot, cannot send webhook notification', user.telegram_id)
except TelegramBadRequest as exc:
logger.warning('Failed to send webhook notification to %s: %s', user.telegram_id, exc)
notification_type = _TEXT_KEY_TO_NOTIFICATION_TYPE.get(text_key)
if not notification_type:
logger.warning('No NotificationType mapping for text_key %s', text_key)
return
context = {'text_key': text_key, **(format_kwargs or {})}
await notification_delivery_service.send_notification(
user=user,
notification_type=notification_type,
context=context,
bot=self.bot,
telegram_message=message,
telegram_markup=reply_markup,
)
# ------------------------------------------------------------------
# Event handlers