diff --git a/bot/handlers/admin/broadcast.py b/bot/handlers/admin/broadcast.py index bb2aa68..7c701b3 100644 --- a/bot/handlers/admin/broadcast.py +++ b/bot/handlers/admin/broadcast.py @@ -84,8 +84,7 @@ async def process_broadcast_message_handler( broadcast_entities=entities, ) - preview_snippet = (text[:200] + "...") if len(text) > 200 else text - confirmation_prompt = _("admin_broadcast_confirm_prompt", message_preview=preview_snippet) + confirmation_prompt = _("admin_broadcast_confirm_prompt", message_preview=text) await message.answer( confirmation_prompt, diff --git a/bot/handlers/admin/sync_admin.py b/bot/handlers/admin/sync_admin.py index 603ecab..c73139a 100644 --- a/bot/handlers/admin/sync_admin.py +++ b/bot/handlers/admin/sync_admin.py @@ -7,6 +7,7 @@ from datetime import datetime, timezone from config.settings import Settings from bot.services.panel_api_service import PanelApiService +from bot.services.notification_service import notify_admin_panel_sync from db.dal import user_dal, subscription_dal, panel_sync_dal @@ -222,7 +223,7 @@ async def perform_sync(panel_service: PanelApiService, session: AsyncSession, except Exception as e_sync_global: await session.rollback() logging.error(f"Global error during sync: {e_sync_global}", exc_info=True) - error_detail = f"Unexpected error during sync: {str(e_sync_global)[:200]}" + error_detail = f"Unexpected error during sync: {str(e_sync_global)}" await panel_sync_dal.update_panel_sync_status( session, "failed", error_detail, panel_records_checked, subscriptions_synced_count @@ -286,7 +287,7 @@ async def sync_command_handler( users_synced=sync_result.get("users_synced", 0), subs_synced=sync_result.get("subs_synced", 0), errors_count=len(errors), - error_details_preview=error_preview[:200] + "..." if len(error_preview) > 200 else error_preview + error_details_preview=error_preview ) await bot.send_message(target_chat_id, final_message) else: @@ -297,10 +298,28 @@ async def sync_command_handler( subs_synced=sync_result.get("subs_synced", 0) ) await bot.send_message(target_chat_id, _("sync_completed", status="Success", details=final_message)) + + # Send notification to log channel with proper thread handling + try: + await notify_admin_panel_sync( + bot, settings, i18n, status, details, + sync_result.get("users_processed", 0), + sync_result.get("subs_synced", 0) + ) + except Exception as e_notification: + logging.error(f"Failed to send sync notification: {e_notification}") except Exception as e_sync_global: logging.error(f"Global error during /sync command: {e_sync_global}", exc_info=True) await bot.send_message(target_chat_id, _("sync_failed", details=str(e_sync_global))) + + # Send notification to log channel about failure + try: + await notify_admin_panel_sync( + bot, settings, i18n, "failed", str(e_sync_global), 0, 0 + ) + except Exception as e_notification: + logging.error(f"Failed to send sync failure notification: {e_notification}") @router.message(Command("syncstatus")) @@ -323,11 +342,7 @@ async def sync_status_command_handler( ) details_val = status_record_model.details - details_str = ( - (details_val[:200] + "...") - if details_val and len(details_val) > 200 - else (details_val or "N/A") - ) + details_str = details_val or "N/A" response_text = ( f"{_('admin_stats_last_sync_header')}\n" diff --git a/bot/services/notification_service.py b/bot/services/notification_service.py index 8597597..64f02b9 100644 --- a/bot/services/notification_service.py +++ b/bot/services/notification_service.py @@ -221,6 +221,42 @@ class NotificationService: # Send to log channel await self._send_to_log_channel(message) + async def notify_panel_sync(self, status: str, details: str, + users_processed: int, subs_synced: int, + username: Optional[str] = None): + """Send notification about panel synchronization""" + if not getattr(self.settings, 'LOG_PANEL_SYNC', True): + return + + admin_lang = self.settings.DEFAULT_LANGUAGE + _ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k + + # Status emoji based on sync result + status_emoji = { + "completed": "✅", + "completed_with_errors": "⚠️", + "failed": "❌" + }.get(status, "🔄") + + message = _( + "log_panel_sync", + default="{status_emoji} Синхронизация с панелью\n\n" + "📊 Статус: {status}\n" + "👥 Обработано пользователей: {users_processed}\n" + "📋 Синхронизировано подписок: {subs_synced}\n" + "🕐 Время: {timestamp}\n\n" + "📝 Детали:\n{details}", + status_emoji=status_emoji, + status=status, + users_processed=users_processed, + subs_synced=subs_synced, + timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z"), + details=details + ) + + # Send to log channel + await self._send_to_log_channel(message) + async def notify_suspicious_promo_attempt( self, user_id: int, suspicious_input: str, username: Optional[str] = None, first_name: Optional[str] = None): @@ -297,4 +333,13 @@ async def notify_admin_promo_activation(bot: Bot, settings: Settings, user_id=user_id, code=code, bonus_days=bonus_days, - ) \ No newline at end of file + ) + + +async def notify_admin_panel_sync(bot: Bot, settings: Settings, + i18n: JsonI18n, status: str, + details: str, users_processed: int, + subs_synced: int) -> None: + """Send notification to admins about panel sync (legacy)""" + notification_service = NotificationService(bot, settings, i18n) + await notification_service.notify_panel_sync(status, details, users_processed, subs_synced) \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 738369e..0811460 100644 --- a/locales/en.json +++ b/locales/en.json @@ -131,7 +131,7 @@ "admin_sync_status_never_run": "Panel sync never run.", "admin_broadcast_enter_message": "Enter the broadcast message (HTML supported):", - "admin_broadcast_confirm_prompt": "You are about to send the following message (first 200 characters):\n\n{message_preview}\n\nConfirm sending?", + "admin_broadcast_confirm_prompt": "You are about to send the following message:\n\n{message_preview}\n\nConfirm sending?", "confirm_broadcast_send_button": "✅ Send", "cancel_broadcast_button": "❌ Cancel", "admin_broadcast_sending_started": "Starting broadcast...", @@ -145,6 +145,8 @@ "admin_promo_create_prompt": "Enter promo details in the format: CODE BONUS_DAYS MAX_USES [VALIDITY_DAYS]\nExample: {example_format}\n(Validity is optional; default is indefinite)", "admin_promo_invalid_format": "Invalid format. Please use: CODE BONUS_DAYS MAX_USES [VALIDITY_DAYS]", "admin_promo_invalid_code_format": "Code must be 3–30 alphanumeric characters.", + "admin_promo_invalid_bonus_days": "Bonus days must be a positive number.", + "admin_promo_invalid_max_activations": "Max activations must be a positive number.", "admin_promo_invalid_bonus_or_activations": "Bonus days and max uses must be positive numbers.", "admin_promo_invalid_validity_days": "Validity period (in days) must be a positive number.", "admin_promo_invalid_values": "Invalid values. {error}", @@ -314,6 +316,7 @@ "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_promo_activation": "🎁 Promo Code Activated\n\n👤 User: {user_display}\n🏷 Code: {promo_code}\n🎯 Bonus: +{bonus_days}d\n🕐 Time: {timestamp}", "log_trial_activation": "🆓 Trial Activated\n\n👤 User: {user_display}\n⏰ Valid until: {end_date}\n🕐 Time: {timestamp}", + "log_panel_sync": "{status_emoji} Panel Synchronization\n\n📊 Status: {status}\n👥 Users processed: {users_processed}\n📋 Subscriptions synced: {subs_synced}\n🕐 Time: {timestamp}\n\n📝 Details:\n{details}", "log_suspicious_promo": "⚠️ Suspicious Promo Code Attempt\n\n👤 User: {user_display}\n🆔 ID: {user_id}\n📝 Input:
{suspicious_input}
\n🕐 Time: {timestamp}", "admin_general_cancel_operation": "Operation cancelled ❌", diff --git a/locales/ru.json b/locales/ru.json index fd7177d..35598cc 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -131,7 +131,7 @@ "admin_sync_status_never_run": "Синхронизация с панелью еще не проводилась.", "admin_broadcast_enter_message": "Введите сообщение для рассылки (HTML поддерживается):", - "admin_broadcast_confirm_prompt": "Вы собираетесь отправить следующее сообщение (первые 200 символов):\n\n{message_preview}\n\nПодтверждаете отправку?", + "admin_broadcast_confirm_prompt": "Вы собираетесь отправить следующее сообщение:\n\n{message_preview}\n\nПодтверждаете отправку?", "confirm_broadcast_send_button": "✅ Отправить", "cancel_broadcast_button": "❌ Отмена", "admin_broadcast_sending_started": "Начинаю рассылку...", @@ -145,6 +145,8 @@ "admin_promo_create_prompt": "Введите детали промокода в формате: КОД ДНИ_БОНУСА МАКС_АКТИВАЦИЙ [СРОК_ДЕЙСТВИЯ_В_ДНЯХ_ОТ_СЕЙЧАС]\nПример: {example_format}\n(Срок действия необязателен, по умолчанию - бессрочный)", "admin_promo_invalid_format": "Неверный формат ввода. Пожалуйста, используйте: КОД ДНИ_БОНУСА МАКС_АКТИВАЦИЙ [ДНИ_ДЕЙСТВИЯ]", "admin_promo_invalid_code_format": "Код должен быть от 3 до 30 символов и содержать только буквы и цифры.", + "admin_promo_invalid_bonus_days": "Количество бонусных дней должно быть положительным числом.", + "admin_promo_invalid_max_activations": "Максимальное количество активаций должно быть положительным числом.", "admin_promo_invalid_bonus_or_activations": "Количество бонусных дней и максимальных активаций должны быть положительными числами.", "admin_promo_invalid_validity_days": "Срок действия промокода (в днях) должен быть положительным числом.", "admin_promo_invalid_values": "Неверные значения. {error}", @@ -323,6 +325,7 @@ "log_payment_received": "{provider_emoji} Получен платеж\n\n👤 Пользователь: {user_display}\n💰 Сумма: {amount} {currency}\n📅 Период: {months} мес.\n🏦 Провайдер: {payment_provider}\n🕐 Время: {timestamp}", "log_promo_activation": "🎁 Активирован промокод\n\n👤 Пользователь: {user_display}\n🏷 Код: {promo_code}\n🎯 Бонус: +{bonus_days} дн.\n🕐 Время: {timestamp}", "log_trial_activation": "🆓 Активирован триал\n\n👤 Пользователь: {user_display}\n⏰ Действует до: {end_date}\n🕐 Время: {timestamp}", + "log_panel_sync": "{status_emoji} Синхронизация с панелью\n\n📊 Статус: {status}\n👥 Обработано пользователей: {users_processed}\n📋 Синхронизировано подписок: {subs_synced}\n🕐 Время: {timestamp}\n\n📝 Детали:\n{details}", "log_suspicious_promo": "⚠️ Подозрительная попытка ввода промокода\n\n👤 Пользователь: {user_display}\n🆔 ID: {user_id}\n📝 Ввод:
{suspicious_input}
\n🕐 Время: {timestamp}", "admin_general_cancel_operation": "Операция отменена ❌",