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:
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user