diff --git a/bot/handlers/admin/broadcast.py b/bot/handlers/admin/broadcast.py index ce3821d..be425c9 100644 --- a/bot/handlers/admin/broadcast.py +++ b/bot/handlers/admin/broadcast.py @@ -334,11 +334,18 @@ async def confirm_broadcast_callback_handler( await session.rollback() logging.error(f"Error committing broadcast logs: {e_commit}") - # Get queue stats for detailed report + # Prepare queue stats presentation queue_stats = queue_manager.get_queue_stats() - - result_message = ( - _( + back_keyboard = get_back_to_admin_panel_keyboard(current_lang, i18n) + initial_user_failed = queue_stats.get("user_failed_messages", 0) + initial_group_failed = queue_stats.get("group_failed_messages", 0) + + def build_queue_status(stats: dict) -> str: + dynamic_failed = max( + 0, stats.get("user_failed_messages", 0) - initial_user_failed + ) + max(0, stats.get("group_failed_messages", 0) - initial_group_failed) + total_failed = failed_count + dynamic_failed + return _( "broadcast_queue_result", default=( "🚀 Рассылка поставлена в очередь!\n" @@ -350,16 +357,64 @@ async def confirm_broadcast_callback_handler( "ℹ️ Сообщения будут отправлены автоматически с соблюдением лимитов Telegram." ), sent_count=sent_count, - failed_count=failed_count, - user_queue_size=queue_stats["user_queue_size"], - group_queue_size=queue_stats["group_queue_size"], + failed_count=total_failed, + user_queue_size=stats["user_queue_size"], + group_queue_size=stats["group_queue_size"], ) - ) - await callback.message.answer( + + result_message = build_queue_status(queue_stats) + + status_message = await callback.message.answer( result_message, - reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n), + reply_markup=back_keyboard, ) + async def auto_update_queue_status() -> None: + """Refresh queue stats message twice per second via message edit.""" + last_text = result_message + # Update for up to 2 minutes (240 iterations at 0.5s intervals) + max_iterations = 240 + for _ in range(max_iterations): + await asyncio.sleep(0.5) + + stats = queue_manager.get_queue_stats() + new_text = build_queue_status(stats) + queues_drained = ( + stats["user_queue_size"] == 0 + and stats["group_queue_size"] == 0 + and not stats.get("user_queue_processing") + and not stats.get("group_queue_processing") + ) + + if new_text != last_text: + try: + await status_message.edit_text( + new_text, + reply_markup=back_keyboard, + ) + last_text = new_text + except TelegramBadRequest as e: + if "message is not modified" in str(e): + last_text = new_text + else: + logging.debug( + "Broadcast queue auto-update stopped: %s", e + ) + break + except Exception as e: + logging.debug( + "Broadcast queue auto-update unexpected error: %s", e + ) + break + + if queues_drained: + # Final refresh already attempted; exit loop. + break + else: + logging.debug("Broadcast queue auto-update reached time limit.") + + asyncio.create_task(auto_update_queue_status()) + elif action == "cancel": await callback.message.edit_text( _("admin_broadcast_cancelled"), diff --git a/bot/services/freekassa_service.py b/bot/services/freekassa_service.py index f31e825..662b379 100644 --- a/bot/services/freekassa_service.py +++ b/bot/services/freekassa_service.py @@ -390,7 +390,7 @@ class FreeKassaService: await notification_service.notify_payment_received( user_id=payment.user_id, amount=float(payment.amount), - currency=self.default_currency or self.settings.DEFAULT_CURRENCY_SYMBOL, + currency=self.default_currency, months=months, payment_provider="freekassa", username=db_user.username if db_user else None, diff --git a/bot/utils/message_queue.py b/bot/utils/message_queue.py index 40e3c0b..0f9314c 100644 --- a/bot/utils/message_queue.py +++ b/bot/utils/message_queue.py @@ -26,6 +26,8 @@ class MessageQueue: self.last_send_times: deque[datetime] = deque() self.is_processing = False self.delay_between_messages = 1.0 / messages_per_second + self.total_sent = 0 + self.total_failed = 0 async def add_message(self, message: QueuedMessage) -> None: """Add message to queue""" @@ -50,6 +52,7 @@ class MessageQueue: try: await self._send_message(message) self.last_send_times.append(datetime.now()) + self.total_sent += 1 # Keep only recent send times (last minute) cutoff_time = datetime.now() - timedelta(seconds=60) @@ -57,6 +60,7 @@ class MessageQueue: self.last_send_times.popleft() except Exception as e: + self.total_failed += 1 logging.error(f"Failed to send queued message to {message.chat_id}: {e}") finally: @@ -233,7 +237,11 @@ class MessageQueueManager: "group_queue_processing": self.group_queue.is_processing, "user_queue_processing": self.user_queue.is_processing, "group_recent_sends": len(self.group_queue.last_send_times), - "user_recent_sends": len(self.user_queue.last_send_times) + "user_recent_sends": len(self.user_queue.last_send_times), + "group_failed_messages": self.group_queue.total_failed, + "user_failed_messages": self.user_queue.total_failed, + "group_sent_messages": self.group_queue.total_sent, + "user_sent_messages": self.user_queue.total_sent, } @@ -250,4 +258,4 @@ def init_queue_manager(bot: Bot) -> MessageQueueManager: def get_queue_manager() -> Optional[MessageQueueManager]: """Get global queue manager instance""" - return _queue_manager \ No newline at end of file + return _queue_manager diff --git a/locales/en.json b/locales/en.json index a881d6c..8e0ccd2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -4,7 +4,7 @@ "menu_activate_trial_button": "🆓 Free Trial", "menu_subscribe_inline": "🚀 Purchase", "menu_my_subscription_inline": "🔐 My Subscription", - "no_subscription options available": "Subscription issuance is not configured by the bot administrator", + "no_subscription_options_available": "Subscription issuance is not configured by the bot administrator", "menu_referral_inline": "🎁 Referrals", "referral_no_bonuses_configured": "Sorry, the referral program is currently disabled", "menu_apply_promo_button": "🎟 Promo Code", diff --git a/locales/ru.json b/locales/ru.json index bcfe023..7907824 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -127,6 +127,7 @@ "admin_broadcast_cancelled": "Рассылка отменена.", "admin_broadcast_cancelled_alert": "Рассылка отменена!", "admin_broadcast_cancelled_nav_back": "Рассылка отменена. Вы возвращены в админ-панель.", + "broadcast_queue_result": "🚀 Рассылка поставлена в очередь!\n📤 В очередь добавлено: {sent_count}\n❌ Ошибок: {failed_count}\n\n📊 Статус очередей:\n👥 Очередь пользователей: {user_queue_size} сообщений\n📢 Очередь групп: {group_queue_size} сообщений\n\nℹ️ Сообщения будут отправлены автоматически с соблюдением лимитов Telegram.", "admin_promo_invalid_code_format": "Код должен быть от 3 до 30 символов и содержать только буквы и цифры.", "admin_promo_invalid_bonus_days": "Количество бонусных дней должно быть положительным числом.", "admin_promo_invalid_max_activations": "Максимальное количество активаций должно быть положительным числом.",