diff --git a/src/config.py b/src/config.py index 37025a5..3b8fbfc 100644 --- a/src/config.py +++ b/src/config.py @@ -187,10 +187,12 @@ COMMAND_MESSAGES = { "3. Ответ на сообщение:\n" " Ответьте на сообщение: /karma\n\n" "💡 Как начислить карму?\n" - "Ответьте на сообщение пользователя словами благодарности:\n" + "Способ 1: Ответить на сообщение\n" "• спасибо → +1 карма\n" "• благодарю → +1 карма\n" "• спс, сенкс, thanks и др. → +1 карма\n\n" + "Способ 2: Поставить реакцию 👍\n" + "• Нажмите на сообщение и выберите 👍 → +1 карма\n\n" "🔥 БОНУС: Благодарность с восклицательным знаком даёт x2 кармы!\n" "• спасибо! → +2 кармы 👍👍\n" "• thanks! → +2 кармы 👍👍\n\n" diff --git a/src/main.py b/src/main.py index 3273084..ebca443 100644 --- a/src/main.py +++ b/src/main.py @@ -200,8 +200,8 @@ async def main(): # Устанавливаем команды бота await setup_bot_commands() - # Запускаем бота - await bot.infinity_polling() + # Запускаем бота с обработкой реакций + await bot.infinity_polling(allowed_updates=['message', 'message_reaction', 'chat_member']) except Exception as e: diff --git a/src/modules/0_karma_tracker.py b/src/modules/0_karma_tracker.py index d828fda..c165bb3 100644 --- a/src/modules/0_karma_tracker.py +++ b/src/modules/0_karma_tracker.py @@ -1,7 +1,8 @@ from telebot.async_telebot import AsyncTeleBot -from telebot.types import Message +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 @@ -10,10 +11,126 @@ from config import THANK_COOLDOWN logger = logging.getLogger(__name__) +# Кэш для хранения message_id -> user_id (последние 1000 сообщений) +# Используем OrderedDict для автоматического удаления старых записей +_message_cache = OrderedDict() +_MAX_CACHE_SIZE = 1000 + +def _cache_message(chat_id: int, message_id: int, user_id: int): + """Добавляет сообщение в кэш""" + key = f"{chat_id}:{message_id}" + _message_cache[key] = user_id + + # Удаляем старые записи, если кэш переполнен + if len(_message_cache) > _MAX_CACHE_SIZE: + _message_cache.popitem(last=False) + +def _get_cached_user(chat_id: int, message_id: int) -> int: + """Получает user_id из кэша""" + key = f"{chat_id}:{message_id}" + return _message_cache.get(key) + def register_handlers(bot: AsyncTeleBot): """Регистрирует обработчики для отслеживания благодарностей""" logger.info("Регистрация обработчика благодарностей (karma_tracker)") + @bot.message_reaction_handler(func=lambda reaction: True) + async def handle_reaction(reaction: MessageReactionUpdated): + """ + Обрабатывает реакции на сообщения. + Если пользователь поставил 👍, начисляет карму автору сообщения. + """ + try: + logger.info(f"[KARMA] Получена реакция от {reaction.user.id}") + + # Проверяем, что это групповой чат + if reaction.chat.type not in ['group', 'supergroup']: + logger.info(f"[KARMA] Пропуск реакции - не групповой чат") + return + + # Проверяем, что есть новые реакции (не удаление) + if not reaction.new_reaction: + logger.info(f"[KARMA] Пропуск - удаление реакции") + return + + # Ищем реакцию 👍 среди новых реакций + thumbs_up_found = False + for react in reaction.new_reaction: + if isinstance(react, ReactionTypeEmoji) and react.emoji == "👍": + thumbs_up_found = True + break + + if not thumbs_up_found: + logger.info(f"[KARMA] Нет реакции 👍") + return + + logger.info(f"[KARMA] Обнаружена реакция 👍 от {reaction.user.id}") + + from_user = reaction.user + chat_id = reaction.chat.id + + # Получаем автора сообщения из кэша + to_user_id = _get_cached_user(chat_id, reaction.message_id) + if not to_user_id: + logger.warning(f"[KARMA] Сообщение {reaction.message_id} не найдено в кэше") + return + + # Защита от самоблагодарности + if from_user.id == to_user_id: + logger.info(f"Пользователь {from_user.id} попытался поблагодарить сам себя реакцией") + return + + # Получаем информацию о пользователе из БД + to_user_info = db.get_user(to_user_id) + if not to_user_info: + logger.warning(f"[KARMA] Пользователь {to_user_id} не найден в БД") + return + + # Атомарно проверяем кулдаун и записываем благодарность + if not db.try_add_karma_thank(from_user.id, to_user_id, chat_id, THANK_COOLDOWN): + logger.info(f"Пользователь {from_user.id} уже благодарил {to_user_id} недавно (реакция)") + return + + # Начисляем +1 карму за реакцию + db.add_karma(to_user_id, chat_id, 1) + + # Получаем новую карму + new_karma = db.get_karma(to_user_id, chat_id) + + logger.info(f"Пользователь {from_user.id} поблагодарил {to_user_id} реакцией 👍, карма: {new_karma}") + + # Формируем имя пользователя (из БД: id, nickname, tag) + to_user_display = f"@{to_user_info[2]}" if to_user_info[2] else to_user_info[1] + + response = f"👍 Карма пользователя {to_user_display} увеличена (+1)! Текущая карма: {new_karma}" + sent_message = await bot.send_message( + chat_id, + response, + message_thread_id=getattr(reaction, 'message_thread_id', None) + ) + + # Удаляем уведомление через 15 секунд + await asyncio.sleep(15) + try: + await bot.delete_message(chat_id, sent_message.message_id) + except Exception as e: + logger.error(f"Не удалось удалить уведомление о карме: {e}") + + except Exception as e: + logger.error(f"Ошибка при обработке реакции: {e}", exc_info=True) + + # Обработчик всех текстовых сообщений для кэширования + @bot.message_handler(content_types=['text']) + async def cache_messages(message: Message): + """Кэширует все текстовые сообщения для возможности обработки реакций""" + try: + # Кэшируем только сообщения в групповых чатах + if message.chat.type in ['group', 'supergroup']: + _cache_message(message.chat.id, message.message_id, message.from_user.id) + logger.debug(f"[CACHE] Сообщение {message.message_id} от {message.from_user.id} добавлено в кэш") + except Exception as e: + logger.error(f"Ошибка кэширования сообщения: {e}") + @bot.message_handler(func=lambda message: message.reply_to_message is not None and message.text and not message.text.startswith('/')) async def handle_thank_message(message: Message): """