diff --git a/src/database.py b/src/database.py index 3e6f287..4585c22 100644 --- a/src/database.py +++ b/src/database.py @@ -92,6 +92,24 @@ class Database: # Инициализация класса ON users(tag COLLATE NOCASE) ''') + # Таблица для кэша сообщений (для обработки реакций) + cursor.execute(''' + CREATE TABLE IF NOT EXISTS message_cache ( + chat_id INTEGER NOT NULL, + message_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + thread_id INTEGER, + timestamp INTEGER NOT NULL, + PRIMARY KEY (chat_id, message_id) + ) + ''') + + # Индекс для быстрой очистки старых записей + cursor.execute(''' + CREATE INDEX IF NOT EXISTS idx_message_cache_timestamp + ON message_cache(timestamp) + ''') + connect.commit() logger.info("База данных и индексы успешно инициализированы") @@ -395,5 +413,52 @@ class Database: # Инициализация класса connect.commit() logger.info(f"Пользователь {from_user_id} поблагодарил {to_user_id} в чате {chat_id}") + # Добавляет сообщение в кэш + def cache_message(self, chat_id: int, message_id: int, user_id: int, thread_id: Optional[int] = None): + with self._get_connection() as connect: + cursor = connect.cursor() + current_time = int(time.time()) + cursor.execute(''' + INSERT OR REPLACE INTO message_cache (chat_id, message_id, user_id, thread_id, timestamp) + VALUES (?, ?, ?, ?, ?) + ''', (chat_id, message_id, user_id, thread_id, current_time)) + connect.commit() + + # Получает информацию о сообщении из кэша + # Возвращает (user_id, thread_id) или None если не найдено + def get_cached_message(self, chat_id: int, message_id: int) -> Optional[Tuple[int, Optional[int]]]: + with self._get_connection() as connect: + cursor = connect.cursor() + cursor.execute(''' + SELECT user_id, thread_id + FROM message_cache + WHERE chat_id = ? AND message_id = ? + ''', (chat_id, message_id)) + result = cursor.fetchone() + return result if result else None + + # Очищает сообщения старше указанного времени (по умолчанию 24 часа) + def cleanup_old_messages(self, max_age_seconds: int = 86400): + with self._get_connection() as connect: + cursor = connect.cursor() + cutoff_time = int(time.time()) - max_age_seconds + cursor.execute(''' + DELETE FROM message_cache + WHERE timestamp < ? + ''', (cutoff_time,)) + deleted_count = cursor.rowcount + connect.commit() + if deleted_count > 0: + logger.info(f"Удалено {deleted_count} старых сообщений из кэша") + return deleted_count + + # Получает количество сообщений в кэше + def get_cache_size(self) -> int: + with self._get_connection() as connect: + cursor = connect.cursor() + cursor.execute('SELECT COUNT(*) FROM message_cache') + result = cursor.fetchone() + return result[0] if result else 0 + # Создаем экземпляр базы данных для импорта в других модулях db = Database() \ No newline at end of file diff --git a/src/modules/0_karma_tracker.py b/src/modules/0_karma_tracker.py index 268a0b2..50034bc 100644 --- a/src/modules/0_karma_tracker.py +++ b/src/modules/0_karma_tracker.py @@ -2,7 +2,6 @@ from telebot.async_telebot import AsyncTeleBot from telebot.types import Message, MessageReactionUpdated, ReactionTypeEmoji import asyncio import logging -from collections import OrderedDict from database import db from thank_words import contains_thank_word @@ -11,29 +10,39 @@ from config import THANK_COOLDOWN logger = logging.getLogger(__name__) -# Кэш для хранения message_id -> (user_id, message_thread_id) (последние 1000 сообщений) -# Используем OrderedDict для автоматического удаления старых записей -_message_cache = OrderedDict() -_MAX_CACHE_SIZE = 1000 +# Фоновая задача для автоочистки старых сообщений +_cleanup_task = None def _cache_message(chat_id: int, message_id: int, user_id: int, message_thread_id: int = None): - """Добавляет сообщение в кэш""" - key = f"{chat_id}:{message_id}" - _message_cache[key] = (user_id, message_thread_id) - - # Удаляем старые записи, если кэш переполнен - if len(_message_cache) > _MAX_CACHE_SIZE: - _message_cache.popitem(last=False) + """Добавляет сообщение в кэш БД""" + db.cache_message(chat_id, message_id, user_id, message_thread_id) def _get_cached_message(chat_id: int, message_id: int): - """Получает (user_id, message_thread_id) из кэша""" - key = f"{chat_id}:{message_id}" - return _message_cache.get(key) + """Получает (user_id, message_thread_id) из кэша БД""" + return db.get_cached_message(chat_id, message_id) + +async def _cleanup_old_cache(): + """Фоновая задача для очистки старых сообщений из кэша каждый час""" + while True: + try: + await asyncio.sleep(3600) # Ждём 1 час + deleted = db.cleanup_old_messages(max_age_seconds=86400) # Удаляем старше 24 часов + cache_size = db.get_cache_size() + logger.info(f"[CACHE CLEANUP] Размер кэша: {cache_size} сообщений") + except Exception as e: + logger.error(f"[CACHE CLEANUP] Ошибка очистки кэша: {e}", exc_info=True) def register_handlers(bot: AsyncTeleBot): """Регистрирует обработчики для отслеживания благодарностей""" logger.info("Регистрация обработчика благодарностей (karma_tracker)") + # Запускаем фоновую задачу очистки старых сообщений из кэша + global _cleanup_task + if _cleanup_task is None or _cleanup_task.done(): + _cleanup_task = asyncio.create_task(_cleanup_old_cache()) + cache_size = db.get_cache_size() + logger.info(f"[CACHE] Запущена автоочистка кэша. Текущий размер: {cache_size} сообщений") + @bot.message_reaction_handler(func=lambda m: True) async def handle_reaction(reaction: MessageReactionUpdated): """