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):
"""