diff --git a/.gigaide/gigaide.properties b/.gigaide/gigaide.properties index ca124ef..3adf633 100644 --- a/.gigaide/gigaide.properties +++ b/.gigaide/gigaide.properties @@ -1,5 +1,5 @@ -## changed at Sun Oct 19 13:23:17 MSK 2025 -#Sun Oct 19 13:23:17 MSK 2025 +## changed at Sun Oct 19 17:30:33 MSK 2025 +#Sun Oct 19 17:30:33 MSK 2025 com.gigaide.elements.ext.marker.solution.BeanMarkedPsi.shouldMark=true com.gigaide.elements.ext.marker.solution.ConfigMarkedPsi.shouldMark=true com.gigaide.elements.ext.marker.solution.DataMarkedPsi.shouldMark=true diff --git a/src/main.py b/src/main.py index 8fa75ed..a14917a 100644 --- a/src/main.py +++ b/src/main.py @@ -16,6 +16,8 @@ from action_reporter import init_action_reporter from config import MODULES_DIR +from message_queue import init_message_queue, add_to_queue + # Загружаем токен бота из .env load_dotenv() @@ -143,7 +145,24 @@ class UserUpdateMiddleware(BaseMiddleware): can_send, remaining_time = check_slow_mode(message.from_user.id, message.chat.id, user_karma) if not can_send: - # Удаляем сообщение + # Сохраняем текст сообщения перед удалением + message_text = message.text + + # Добавляем сообщение в очередь для отправки после задержки + try: + add_to_queue( + chat_id=message.chat.id, + user_id=message.from_user.id, + user_name=message.from_user.first_name, + text=message_text, + delay_seconds=remaining_time, + thread_id=message.message_thread_id + ) + logger.info(f"[SLOW MODE] Сообщение от {message.from_user.id} добавлено в очередь на {remaining_time}с") + except Exception as e: + logger.error(f"Ошибка добавления сообщения в очередь: {e}") + + # Удаляем оригинальное сообщение try: await self.bot.delete_message(message.chat.id, message.message_id) logger.info(f"[SLOW MODE] Удалено сообщение от {message.from_user.id} (карма: {user_karma}, осталось ждать: {remaining_time}с)") @@ -154,7 +173,7 @@ class UserUpdateMiddleware(BaseMiddleware): try: notification = await self.bot.send_message( chat_id=message.chat.id, - text=f"⏱ {message.from_user.first_name}, подождите ещё {format_time(remaining_time)} перед отправкой следующего сообщения.\n\n" + text=f"⏱ {message.from_user.first_name}, ваше сообщение будет отправлено через {format_time(remaining_time)}.\n\n" f"💡 Ваша карма: {user_karma}. Повышайте карму, чтобы уменьшить задержку!", message_thread_id=message.message_thread_id, ) @@ -283,6 +302,10 @@ async def main(): # Устанавливаем команды бота await setup_bot_commands() + # Инициализируем систему очереди сообщений + init_message_queue(bot) + logger.info("Система очереди сообщений инициализирована") + # Запускаем бота с обработкой реакций logger.info("Запуск бота с allowed_updates: message, message_reaction, chat_member") await bot.infinity_polling(allowed_updates=['message', 'message_reaction', 'chat_member']) diff --git a/src/message_queue.py b/src/message_queue.py new file mode 100644 index 0000000..7ebdd6c --- /dev/null +++ b/src/message_queue.py @@ -0,0 +1,150 @@ +# Очередь отложенных сообщений для медленного режима +# Сохраняет сообщения и отправляет их после истечения задержки + +import asyncio +import time +import logging +from typing import Dict, List, Optional +from dataclasses import dataclass +from collections import deque + +logger = logging.getLogger(__name__) + +@dataclass +class QueuedMessage: + """Сообщение в очереди""" + chat_id: int + user_id: int + user_name: str + text: str + send_time: float # Unix timestamp когда нужно отправить + thread_id: Optional[int] = None + +# Очередь сообщений для каждого чата +_message_queues: Dict[int, deque[QueuedMessage]] = {} +_processing_task: Optional[asyncio.Task] = None +_bot_instance = None + +def init_message_queue(bot): + """ + Инициализирует систему очереди сообщений. + + Args: + bot: Экземпляр бота для отправки сообщений + """ + global _bot_instance, _processing_task + _bot_instance = bot + + # Запускаем фоновую задачу обработки очереди, если она ещё не запущена + if _processing_task is None or _processing_task.done(): + _processing_task = asyncio.create_task(_process_queue()) + logger.info("Система очереди сообщений инициализирована") + +def add_to_queue(chat_id: int, user_id: int, user_name: str, text: str, delay_seconds: int, thread_id: Optional[int] = None): + """ + Добавляет сообщение в очередь для отправки после задержки. + + Args: + chat_id: ID чата + user_id: ID пользователя + user_name: Имя пользователя + text: Текст сообщения + delay_seconds: Задержка в секундах перед отправкой + thread_id: ID топика (для супергрупп с топиками) + """ + send_time = time.time() + delay_seconds + + message = QueuedMessage( + chat_id=chat_id, + user_id=user_id, + user_name=user_name, + text=text, + send_time=send_time, + thread_id=thread_id + ) + + # Добавляем очередь для чата, если её ещё нет + if chat_id not in _message_queues: + _message_queues[chat_id] = deque() + + _message_queues[chat_id].append(message) + logger.info(f"[QUEUE] Сообщение от {user_name} ({user_id}) добавлено в очередь чата {chat_id}, отправка через {delay_seconds}с") + +async def _process_queue(): + """ + Фоновая задача для обработки очереди сообщений. + Проверяет каждую секунду, есть ли сообщения готовые к отправке. + """ + global _bot_instance + logger.info("[QUEUE] Запущена фоновая задача обработки очереди") + + while True: + try: + current_time = time.time() + + # Проходим по всем чатам + for chat_id in list(_message_queues.keys()): + queue = _message_queues[chat_id] + + # Обрабатываем сообщения, которые готовы к отправке + while queue and queue[0].send_time <= current_time: + message = queue.popleft() + + try: + # Отправляем сообщение + formatted_text = f"💬 {message.user_name}:\n{message.text}" + + await _bot_instance.send_message( + chat_id=message.chat_id, + text=formatted_text, + message_thread_id=message.thread_id + ) + + logger.info(f"[QUEUE] Сообщение от {message.user_name} ({message.user_id}) отправлено в чат {message.chat_id}") + + except Exception as e: + logger.error(f"[QUEUE] Ошибка отправки сообщения из очереди: {e}", exc_info=True) + + # Удаляем пустые очереди + if not queue: + del _message_queues[chat_id] + + # Ждём 1 секунду перед следующей проверкой + await asyncio.sleep(1) + + except Exception as e: + logger.error(f"[QUEUE] Ошибка в обработке очереди: {e}", exc_info=True) + await asyncio.sleep(1) + +def get_queue_size(chat_id: int) -> int: + """ + Возвращает количество сообщений в очереди для чата. + + Args: + chat_id: ID чата + + Returns: + Количество сообщений в очереди + """ + return len(_message_queues.get(chat_id, [])) + +def get_user_queue_position(chat_id: int, user_id: int) -> Optional[int]: + """ + Возвращает позицию пользователя в очереди (1-based). + + Args: + chat_id: ID чата + user_id: ID пользователя + + Returns: + Позиция в очереди или None если нет сообщений + """ + if chat_id not in _message_queues: + return None + + queue = _message_queues[chat_id] + for i, msg in enumerate(queue): + if msg.user_id == user_id: + return i + 1 + + return None \ No newline at end of file