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": "Операция отменена ❌",