From aabc0e312d36fbba262b40e5a5a0945dc1691a70 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Tue, 28 Apr 2026 13:54:05 +0300 Subject: [PATCH] feat: log email and tg linking --- bot/app/web/subscription_webapp.py | 60 ++++++++++++++++++++++++ bot/services/notification_service.py | 69 ++++++++++++++++++++++++++++ locales/en.json | 2 + locales/ru.json | 2 + 4 files changed, 133 insertions(+) diff --git a/bot/app/web/subscription_webapp.py b/bot/app/web/subscription_webapp.py index 49032e4..0849b93 100644 --- a/bot/app/web/subscription_webapp.py +++ b/bot/app/web/subscription_webapp.py @@ -1074,7 +1074,11 @@ async def account_email_verify_route(request: web.Request) -> web.Response: source_panel_uuid: Optional[str] = None final_user_id = user_id final_email = email + final_telegram_id: Optional[int] = None + final_username: Optional[str] = None + final_first_name: Optional[str] = None final_panel_uuid: Optional[str] = None + should_notify_email_linked = False async with async_session_factory() as session: try: @@ -1102,6 +1106,10 @@ async def account_email_verify_route(request: web.Request) -> web.Response: if not current_user or current_user.is_banned: await session.rollback() return _json_error(403, "access_denied", "Access denied") + should_notify_email_linked = ( + bool(_telegram_id_for_user(current_user)) + and not current_user.email + ) existing_email_user = await user_dal.get_user_by_email(session, email) if existing_email_user and existing_email_user.user_id != current_user.user_id: @@ -1123,6 +1131,9 @@ async def account_email_verify_route(request: web.Request) -> web.Response: await _sync_panel_identity_for_user(request, current_user) await session.commit() final_user_id = int(current_user.user_id) + final_telegram_id = _telegram_id_for_user(current_user) + final_username = current_user.username + final_first_name = current_user.first_name final_panel_uuid = current_user.panel_user_uuid if merge_notice: @@ -1185,6 +1196,26 @@ async def account_email_verify_route(request: web.Request) -> web.Response: logger.exception("Email account link failed") return _json_error(500, "link_failed", "Link failed") + if should_notify_email_linked: + try: + from bot.services.notification_service import NotificationService + + bot: Bot = request.app["bot"] + notification_service = NotificationService( + bot, + settings, + request.app.get("i18n"), + ) + await notification_service.notify_account_email_linked( + user_id=int(final_user_id), + email=final_email, + telegram_id=final_telegram_id, + username=final_username, + first_name=final_first_name, + ) + except Exception: + logger.exception("Failed to send account email linked notification") + token = create_webapp_session_token(settings, int(final_user_id)) response_payload: Dict[str, Any] = {"ok": True} if merge_notice: @@ -1221,13 +1252,20 @@ async def account_telegram_link_route(request: web.Request) -> web.Response: final_user_id = user_id final_telegram_id: Optional[int] = None final_email: Optional[str] = None + final_username: Optional[str] = None + final_first_name: Optional[str] = None final_panel_uuid: Optional[str] = None + should_notify_telegram_linked = False async with async_session_factory() as session: try: current_user_before_link = await user_dal.get_user_by_id(session, user_id) if not current_user_before_link or current_user_before_link.is_banned: await session.rollback() return _json_error(403, "access_denied", "Access denied") + should_notify_telegram_linked = ( + bool(current_user_before_link.email) + and not _telegram_id_for_user(current_user_before_link) + ) source_panel_uuid = current_user_before_link.panel_user_uuid db_user = await _link_telegram_to_user( @@ -1244,6 +1282,8 @@ async def account_telegram_link_route(request: web.Request) -> web.Response: final_user_id = int(db_user.user_id) final_telegram_id = _telegram_id_for_user(db_user) final_email = db_user.email + final_username = db_user.username + final_first_name = db_user.first_name final_panel_uuid = db_user.panel_user_uuid if final_user_id != user_id: merge_notice = await _build_account_merge_notice( @@ -1315,6 +1355,26 @@ async def account_telegram_link_route(request: web.Request) -> web.Response: logger.exception("Telegram account link failed") return _json_error(500, "link_failed", "Link failed") + if should_notify_telegram_linked and final_telegram_id: + try: + from bot.services.notification_service import NotificationService + + bot: Bot = request.app["bot"] + notification_service = NotificationService( + bot, + settings, + request.app.get("i18n"), + ) + await notification_service.notify_account_telegram_linked( + user_id=int(final_user_id), + email=final_email, + telegram_id=int(final_telegram_id), + username=final_username, + first_name=final_first_name, + ) + except Exception: + logger.exception("Failed to send account Telegram linked notification") + token = create_webapp_session_token(settings, int(final_user_id)) response_payload: Dict[str, Any] = { "ok": True, diff --git a/bot/services/notification_service.py b/bot/services/notification_service.py index 40e51fc..212e238 100644 --- a/bot/services/notification_service.py +++ b/bot/services/notification_service.py @@ -255,6 +255,75 @@ class NotificationService: await self._send_to_log_channel(message, reply_markup=reply_markup) + async def notify_account_email_linked( + self, + user_id: int, + email: str, + telegram_id: Optional[int] = None, + username: Optional[str] = None, + first_name: Optional[str] = None, + ): + """Send notification when an email is linked to a Telegram-created account.""" + if not self.settings.LOG_NEW_USERS: + return + + admin_lang = self.settings.DEFAULT_LANGUAGE + _ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k + + user_display = self._format_user_display( + user_id=telegram_id or user_id, + username=username, + first_name=first_name, + ) + + message = _( + "log_account_email_linked", + user_id=user_id, + telegram_id=telegram_id or user_id, + user_display=user_display, + email=hd.quote(email), + timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ) + + reply_markup: Optional[InlineKeyboardMarkup] = None + if telegram_id and telegram_id > 0: + reply_markup = self._build_profile_keyboard(_, telegram_id) + + await self._send_to_log_channel(message, reply_markup=reply_markup) + + async def notify_account_telegram_linked( + self, + user_id: int, + email: Optional[str], + telegram_id: int, + username: Optional[str] = None, + first_name: Optional[str] = None, + ): + """Send notification when Telegram is linked to an email-created account.""" + if not self.settings.LOG_NEW_USERS: + return + + admin_lang = self.settings.DEFAULT_LANGUAGE + _ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k + + user_display = self._format_user_display( + user_id=telegram_id, + username=username, + first_name=first_name, + ) + + message = _( + "log_account_telegram_linked", + user_id=user_id, + telegram_id=telegram_id, + user_display=user_display, + email=hd.quote(email or ""), + timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ) + + profile_keyboard = self._build_profile_keyboard(_, telegram_id) + await self._send_to_log_channel(message, reply_markup=profile_keyboard) + async def notify_payment_received(self, user_id: int, amount: float, currency: str, months: int, payment_provider: str, username: Optional[str] = None, diff --git a/locales/en.json b/locales/en.json index 9c51cb9..9a5adb2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -335,6 +335,8 @@ "log_open_referrer_profile_button": "👤 Referrer profile", "log_new_user_registration": "👤 New User\n\n🆔 ID: {user_id}\n👤 Name: {user_display}{referral_text}\n📅 Time: {timestamp}", "log_new_email_user_registration": "📧 New User (email)\n\n🆔 ID: {user_id}\n📧 Email: {email}{referral_text}\n📅 Time: {timestamp}", + "log_account_email_linked": "📧 Email linked\n\n🆔 User ID: {user_id}\n📨 Telegram ID: {telegram_id}\n👤 User: {user_display}\n📧 Email: {email}\n🕐 Time: {timestamp}", + "log_account_telegram_linked": "📨 Telegram linked\n\n🆔 User ID: {user_id}\n📨 Telegram ID: {telegram_id}\n👤 User: {user_display}\n📧 Email: {email}\n🕐 Time: {timestamp}", "log_payment_received": "{provider_emoji} Payment Received\n\n👤 User: {user_display}\n💰 Amount: {amount} {currency}\n📅 Period: {months} mo.\n🏦 Provider: {payment_provider}\n🕐 Time: {timestamp}", "log_payment_received_traffic": "{provider_emoji} Payment Received\n\n👤 User: {user_display}\n💰 Amount: {amount} {currency}\n🗂 Traffic: {traffic_gb} GB\n🏦 Provider: {payment_provider}\n🕐 Time: {timestamp}", "log_promo_activation": "🎁 Promo Code Activated\n\n👤 User: {user_display}\n🏷 Code: {promo_code}\n🎯 Bonus: +{bonus_days}d\n🕐 Time: {timestamp}", diff --git a/locales/ru.json b/locales/ru.json index 5aeafaf..e2b1318 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -335,6 +335,8 @@ "log_open_referrer_profile_button": "👤 Профиль пригласившего", "log_new_user_registration": "👤 Новый пользователь\n\n🆔 ID: {user_id}\n👤 Имя: {user_display}{referral_text}\n📅 Время: {timestamp}", "log_new_email_user_registration": "📧 Новый пользователь (email)\n\n🆔 ID: {user_id}\n📧 Email: {email}{referral_text}\n📅 Время: {timestamp}", + "log_account_email_linked": "📧 Привязана почта\n\n🆔 ID пользователя: {user_id}\n📨 Telegram ID: {telegram_id}\n👤 Пользователь: {user_display}\n📧 Email: {email}\n🕐 Время: {timestamp}", + "log_account_telegram_linked": "📨 Привязан Telegram\n\n🆔 ID пользователя: {user_id}\n📨 Telegram ID: {telegram_id}\n👤 Пользователь: {user_display}\n📧 Email: {email}\n🕐 Время: {timestamp}", "log_payment_received": "{provider_emoji} Получен платеж\n\n👤 Пользователь: {user_display}\n💰 Сумма: {amount} {currency}\n📅 Период: {months} мес.\n🏦 Провайдер: {payment_provider}\n🕐 Время: {timestamp}", "log_payment_received_traffic": "{provider_emoji} Получен платеж\n\n👤 Пользователь: {user_display}\n💰 Сумма: {amount} {currency}\n🗂 Трафик: {traffic_gb} ГБ\n🏦 Провайдер: {payment_provider}\n🕐 Время: {timestamp}", "log_promo_activation": "🎁 Активирован промокод\n\n👤 Пользователь: {user_display}\n🏷 Код: {promo_code}\n🎯 Бонус: +{bonus_days} дн.\n🕐 Время: {timestamp}",