Files
LGBot/src/modules/0_karma_tracker.py

255 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
from bad_words import contains_bad_word
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
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)
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)
def register_handlers(bot: AsyncTeleBot):
"""Регистрирует обработчики для отслеживания благодарностей"""
logger.info("Регистрация обработчика благодарностей (karma_tracker)")
@bot.message_reaction_handler(func=lambda m: True)
async def handle_reaction(reaction: MessageReactionUpdated):
"""
Обрабатывает реакции на сообщения.
Реакции работают как переключатель:
- Поставил 👍 → +1 карма
- Убрал 👍 → -1 карма
- Поставил 👎 → -1 карма
- Убрал 👎 → +1 карма
"""
try:
logger.info(f"[KARMA] Получена реакция от {reaction.user.id}")
# Проверяем, что это групповой чат
if reaction.chat.type not in ['group', 'supergroup']:
logger.info(f"[KARMA] Пропуск реакции - не групповой чат")
return
from_user = reaction.user
chat_id = reaction.chat.id
# Получаем автора сообщения и топик из кэша
cached_data = _get_cached_message(chat_id, reaction.message_id)
if not cached_data:
logger.warning(f"[KARMA] Сообщение {reaction.message_id} не найдено в кэше")
return
to_user_id, message_thread_id = cached_data
# Защита от самооценки
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
# Проверяем старые реакции
old_thumbs_up = False
old_thumbs_down = False
if reaction.old_reaction:
for react in reaction.old_reaction:
if isinstance(react, ReactionTypeEmoji):
if react.emoji == "👍":
old_thumbs_up = True
elif react.emoji == "👎":
old_thumbs_down = True
# Проверяем новые реакции
new_thumbs_up = False
new_thumbs_down = False
if reaction.new_reaction:
for react in reaction.new_reaction:
if isinstance(react, ReactionTypeEmoji):
if react.emoji == "👍":
new_thumbs_up = True
elif react.emoji == "👎":
new_thumbs_down = True
# Определяем изменение кармы
karma_change = 0
action_emoji = ""
action_text = ""
# Логика изменения кармы
if new_thumbs_up and not old_thumbs_up:
# Добавили 👍
karma_change = 1
action_emoji = "👍"
action_text = "поставил 👍"
elif old_thumbs_up and not new_thumbs_up:
# Убрали 👍
karma_change = -1
action_emoji = "👍"
action_text = "убрал 👍"
elif new_thumbs_down and not old_thumbs_down:
# Добавили 👎
karma_change = -1
action_emoji = "👎"
action_text = "поставил 👎"
elif old_thumbs_down and not new_thumbs_down:
# Убрали 👎
karma_change = 1
action_emoji = "👎"
action_text = "убрал 👎"
# Если нет изменений - выходим
if karma_change == 0:
logger.info(f"[KARMA] Нет изменений в реакциях 👍/👎")
return
logger.info(f"[KARMA] {action_text} от {from_user.id} для {to_user_id}, изменение кармы: {karma_change}")
# Изменяем карму
db.add_karma(to_user_id, chat_id, karma_change)
# Получаем новую карму
new_karma = db.get_karma(to_user_id, chat_id)
# Формируем имя пользователя (из БД: id, nickname, tag)
to_user_display = f"@{to_user_info[2]}" if to_user_info[2] else to_user_info[1]
# Отправляем уведомление
karma_sign = f"+{karma_change}" if karma_change > 0 else str(karma_change)
change_word = "увеличена" if karma_change > 0 else "уменьшена"
response = f"{action_emoji} Карма пользователя {to_user_display} {change_word} ({karma_sign})! Текущая карма: {new_karma}"
logger.info(f"[KARMA] Отправка уведомления в чат {chat_id}")
try:
sent_message = await bot.send_message(
chat_id,
response,
message_thread_id=message_thread_id
)
logger.info(f"[KARMA] Уведомление отправлено успешно, message_id={sent_message.message_id}")
# Удаляем уведомление через 10 секунд
await asyncio.sleep(10)
try:
await bot.delete_message(chat_id, sent_message.message_id)
logger.info(f"[KARMA] Уведомление удалено")
except Exception as e:
logger.error(f"Не удалось удалить уведомление о карме: {e}")
except Exception as e:
logger.error(f"Ошибка отправки уведомления о карме: {e}", exc_info=True)
except Exception as e:
logger.error(f"Ошибка при обработке реакции: {e}", exc_info=True)
@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):
"""
Обрабатывает сообщения, которые являются ответами на другие сообщения.
Если сообщение содержит благодарность, начисляет карму автору оригинального сообщения.
"""
try:
logger.info(f"[KARMA] Получено reply-сообщение: {message.text[:50]}")
# Проверяем, что это групповой чат
if message.chat.type not in ['group', 'supergroup']:
logger.info(f"[KARMA] Пропуск - не групповой чат: {message.chat.type}")
return
# ВАЖНО: В топиках каждое сообщение технически является reply на первое сообщение топика
# Проверяем, что это реальный reply на сообщение пользователя, а не просто сообщение в топике
if message.is_topic_message and message.reply_to_message.message_id == message.message_thread_id:
logger.info(f"[KARMA] Пропуск - это сообщение в топике (не reply на пользователя)")
return
# Проверяем наличие благодарственных слов
if not contains_thank_word(message.text):
logger.info(f"[KARMA] Нет слов благодарности в: {message.text[:50]}")
return
logger.info(f"[KARMA] Обнаружена благодарность от {message.from_user.id}: {message.text[:50]}")
# Проверяем, что в сообщении нет мата (не начисляем карму за мат)
if contains_bad_word(message.text):
logger.info(f"Пользователь {message.from_user.id} написал благодарность с матом - карма не начислена")
return
from_user = message.from_user
to_user = message.reply_to_message.from_user
chat_id = message.chat.id
# Защита от самоблагодарности
if from_user.id == to_user.id:
logger.info(f"Пользователь {from_user.id} попытался поблагодарить сам себя")
return
# Проверяем, не является ли благодарность ботам
if to_user.is_bot:
logger.info(f"Пользователь {from_user.id} попытался поблагодарить бота")
return
# Атомарно проверяем кулдаун и записываем благодарность
# Это предотвращает race condition при параллельных запросах
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
# Определяем количество кармы: x2 если есть восклицательный знак
karma_amount = 2 if '!' in message.text else 1
# Начисляем карму (благодарность уже записана атомарно выше)
db.add_karma(to_user.id, chat_id, karma_amount)
# Получаем новую карму пользователя
new_karma = db.get_karma(to_user.id, chat_id)
# Формируем имя пользователя для отображения
to_user_name = to_user.first_name
if to_user.username:
to_user_display = f"@{to_user.username}"
else:
to_user_display = to_user_name
# Отправляем уведомление с указанием количества кармы
karma_emoji = "👍👍" if karma_amount == 2 else "👍"
karma_change = f"+{karma_amount}"
response = f"{karma_emoji} Карма пользователя {to_user_display} увеличена ({karma_change})! Текущая карма: {new_karma}"
sent_message = await bot.reply_to(message, response)
logger.info(f"Пользователь {from_user.id} поблагодарил {to_user.id}, карма: {new_karma}")
# Удаляем уведомление через 25 секунд
await asyncio.sleep(25)
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)