forked from Muzifs/LGBot
Compare commits
19 Commits
6bdf996ca4
...
master
Author | SHA1 | Date | |
---|---|---|---|
0bd399f121 | |||
c257c6c1a2 | |||
c7b2961ae1 | |||
8af2f128a7 | |||
4aba68d242 | |||
8bf512e509 | |||
459ed66e9a | |||
de1c82c267 | |||
4a2aa00eb7 | |||
c4400fc244 | |||
b7f09ae719 | |||
1619e82df1 | |||
0ee7cb3bd4 | |||
9b11f21bc1 | |||
2b9e819944 | |||
58daea0492 | |||
63ac924a3d | |||
be64915e9b | |||
61e9d31a75 |
File diff suppressed because it is too large
Load Diff
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,6 +1,33 @@
|
||||
# Python
|
||||
.venv/
|
||||
.env
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.gigaide/
|
||||
.claude/
|
||||
.vscode/
|
||||
*.iml
|
||||
|
||||
# Build
|
||||
target/
|
||||
build/
|
||||
dist/
|
||||
*.class
|
||||
|
||||
# Logs
|
||||
bot.log
|
||||
users.db
|
||||
*.log
|
||||
|
||||
# Databases
|
||||
users.db
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
@@ -126,7 +126,7 @@ def normalize_text(text: str) -> str:
|
||||
Убирает:
|
||||
- Звездочки, точки, подчеркивания между буквами (х*й, х.у.й, х_у_й → хуй)
|
||||
- Повторяющиеся символы (хууууууй → хуй)
|
||||
- Пробелы между буквами (х у й → хуй)
|
||||
- ОДИНОЧНЫЕ пробелы между ОДИНОЧНЫМИ буквами (х у й → хуй, но "не бу" остаётся "не бу")
|
||||
|
||||
Args:
|
||||
text: Исходный текст
|
||||
@@ -140,9 +140,22 @@ def normalize_text(text: str) -> str:
|
||||
# Приводим к нижнему регистру
|
||||
normalized = text.lower()
|
||||
|
||||
# Убираем распространенные символы обфускации между буквами
|
||||
# Заменяем последовательности: буква + [*._ ]+ + буква на буква+буква
|
||||
normalized = re.sub(r'([а-яё])[\*\.\-_\s]+([а-яё])', r'\1\2', normalized)
|
||||
# Циклически убираем обфускацию, пока что-то меняется
|
||||
max_iterations = 10
|
||||
for _ in range(max_iterations):
|
||||
before = normalized
|
||||
|
||||
# Убираем звёздочки, точки, дефисы, подчёркивания между буквами
|
||||
# х*й, х.у.й, х_у_й → хуй
|
||||
normalized = re.sub(r'([а-яё])[\*\.\-_]+([а-яё])', r'\1\2', normalized)
|
||||
|
||||
# Убираем ОДИНОЧНЫЕ пробелы между ОДИНОЧНЫМИ буквами (обфускация)
|
||||
# "х у й" → "хуй", но "не бу" → "не бу" (не склеиваем обычные слова)
|
||||
# Паттерн: одиночная буква + пробелы + одиночная буква
|
||||
normalized = re.sub(r'\b([а-яё])\s+([а-яё])\b', r'\1\2', normalized)
|
||||
|
||||
if before == normalized:
|
||||
break
|
||||
|
||||
# Убираем повторяющиеся буквы (более 2 подряд)
|
||||
# хууууууй → хуй, пииииздец → пиздец
|
||||
|
@@ -187,13 +187,23 @@ COMMAND_MESSAGES = {
|
||||
"3. Ответ на сообщение:\n"
|
||||
" Ответьте на сообщение: <code>/karma</code>\n\n"
|
||||
"<b>💡 Как начислить карму?</b>\n"
|
||||
"Ответьте на сообщение пользователя словами благодарности:\n"
|
||||
"<u>Способ 1: Ответить на сообщение</u>\n"
|
||||
"• спасибо → +1 карма\n"
|
||||
"• благодарю → +1 карма\n"
|
||||
"• спс, сенкс, thanks и др. → +1 карма\n\n"
|
||||
"<u>Способ 2: Поставить реакцию (работает как переключатель)</u>\n"
|
||||
"• Поставил 👍 → +1 карма | Убрал 👍 → -1 карма\n"
|
||||
"• Поставил 👎 → -1 карма | Убрал 👎 → +1 карма\n"
|
||||
"• Поставил 🔥 → +2 кармы | Убрал 🔥 → -2 кармы\n"
|
||||
"• Поставил ❤ → +5 кармы | Убрал ❤ → -5 кармы\n"
|
||||
"• Поставил ❤🔥 → +10 кармы | Убрал ❤🔥 → -10 кармы\n"
|
||||
"• Нет ограничений по времени для реакций!\n\n"
|
||||
"<b>🔥 БОНУС: Благодарность с восклицательным знаком даёт x2 кармы!</b>\n"
|
||||
"• спасибо! → +2 кармы 👍👍\n"
|
||||
"• thanks! → +2 кармы 👍👍\n\n"
|
||||
"<b>⚠️ Снятие кармы:</b>\n"
|
||||
"• Предупреждение (/warn): -5 кармы\n"
|
||||
"• Мут (/mute или автомут): -10 кармы\n\n"
|
||||
"<i>⏱ Одному пользователю можно давать карму раз в час</i>"
|
||||
),
|
||||
'top_karma_help': (
|
||||
@@ -202,6 +212,22 @@ COMMAND_MESSAGES = {
|
||||
"<u>🎯 Использование:</u>\n"
|
||||
" <code>/top</code>\n\n"
|
||||
"<i>💡 Система кармы поощряет активных и полезных участников чата!</i>"
|
||||
),
|
||||
'setkarma_help': (
|
||||
"<b>🎚 Команда /setkarma</b>\n\n"
|
||||
"<i>Устанавливает карму пользователя в указанное значение (только для администраторов)</i>\n\n"
|
||||
"<u>🎯 Способы использования:</u>\n"
|
||||
"1. Ответ на сообщение:\n"
|
||||
" <code>/setkarma 100</code>\n"
|
||||
"2. По тегу пользователя:\n"
|
||||
" <code>/setkarma @username 50</code>\n"
|
||||
"3. По ID пользователя:\n"
|
||||
" <code>/setkarma 123456789 -10</code>\n\n"
|
||||
"<b>💡 Примеры:</b>\n"
|
||||
"• Установить карму на 0: <code>/setkarma @user 0</code>\n"
|
||||
"• Установить отрицательную карму: <code>/setkarma @user -50</code>\n"
|
||||
"• Установить высокую карму: <code>/setkarma @user 1000</code>\n\n"
|
||||
"<i>⚠️ Команда доступна только администраторам с правами ограничения</i>"
|
||||
)
|
||||
|
||||
}
|
@@ -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("База данных и индексы успешно инициализированы")
|
||||
|
||||
@@ -287,6 +305,31 @@ class Database: # Инициализация класса
|
||||
connect.commit()
|
||||
logger.info(f"Пользователю {user_id} добавлено {amount} кармы в чате {chat_id}")
|
||||
|
||||
# Устанавливает карму пользователя в указанное значение
|
||||
def set_karma(self, user_id: int, chat_id: int, karma_value: int):
|
||||
with self._get_connection() as connect:
|
||||
cursor = connect.cursor()
|
||||
# Проверяем существование записи
|
||||
cursor.execute('SELECT karma_points FROM karma WHERE user_id = ? AND chat_id = ?', (user_id, chat_id))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
# Обновляем существующую карму
|
||||
cursor.execute('''
|
||||
UPDATE karma
|
||||
SET karma_points = ?
|
||||
WHERE user_id = ? AND chat_id = ?
|
||||
''', (karma_value, user_id, chat_id))
|
||||
else:
|
||||
# Создаем новую запись
|
||||
cursor.execute('''
|
||||
INSERT INTO karma (user_id, chat_id, karma_points)
|
||||
VALUES (?, ?, ?)
|
||||
''', (user_id, chat_id, karma_value))
|
||||
|
||||
connect.commit()
|
||||
logger.info(f"Карма пользователя {user_id} установлена на {karma_value} в чате {chat_id}")
|
||||
|
||||
# Получает карму пользователя
|
||||
def get_karma(self, user_id: int, chat_id: int) -> int:
|
||||
with self._get_connection() as connect:
|
||||
@@ -370,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()
|
0
src/lgbot.db
Normal file
0
src/lgbot.db
Normal file
25
src/main.py
25
src/main.py
@@ -65,6 +65,11 @@ class UserUpdateMiddleware(BaseMiddleware):
|
||||
|
||||
# Обработчик, вызываемый ДО обработки сообщения основными хэндлерами
|
||||
async def pre_process(self, message, data):
|
||||
# Проверяем, что это действительно сообщение (а не ChatMemberUpdated)
|
||||
if not hasattr(message, 'content_type'):
|
||||
# Это не Message объект (например ChatMemberUpdated), пропускаем
|
||||
return data
|
||||
|
||||
# Логируем ВСЕ входящие сообщения для отладки
|
||||
logger.info(f"[MIDDLEWARE] Получено сообщение от {message.from_user.id}, тип: {message.content_type}, текст: {message.text if hasattr(message, 'text') else 'N/A'}")
|
||||
|
||||
@@ -80,8 +85,20 @@ class UserUpdateMiddleware(BaseMiddleware):
|
||||
# Это позволяет auto_mute работать независимо от karma_tracker
|
||||
await self._check_profanity(message)
|
||||
|
||||
# ВАЖНО: Кэшируем ВСЕ сообщения для обработки реакций (не только текстовые!)
|
||||
# Пользователи могут ставить реакции на фото, видео, стикеры и т.д.
|
||||
try:
|
||||
karma_module = importlib.import_module("modules.0_karma_tracker")
|
||||
if message.chat.type in ['group', 'supergroup']:
|
||||
# Передаём message_thread_id для правильной отправки уведомлений в топики
|
||||
thread_id = getattr(message, 'message_thread_id', None)
|
||||
karma_module._cache_message(message.chat.id, message.message_id, message.from_user.id, thread_id)
|
||||
logger.info(f"[CACHE] Сообщение {message.message_id} от {message.from_user.id} добавлено в кэш, chat_id={message.chat.id}, thread_id={thread_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка кэширования сообщения: {e}", exc_info=True)
|
||||
|
||||
# Обработка новых участников группы
|
||||
elif message.content_type == 'new_chat_members':
|
||||
if message.content_type == 'new_chat_members':
|
||||
for new_member in message.new_chat_members:
|
||||
self.db.add_or_update_user(
|
||||
user_id=new_member.id,
|
||||
@@ -180,6 +197,7 @@ async def setup_bot_commands():
|
||||
BotCommand("botdata", "Получить данные бота (только для админов)"),
|
||||
BotCommand("karma", "Просмотр кармы пользователя"),
|
||||
BotCommand("top", "Топ-10 пользователей по карме"),
|
||||
BotCommand("setkarma", "Установить карму пользователя. Использование: /setkarma help"),
|
||||
]
|
||||
|
||||
await bot.set_my_commands(commands)
|
||||
@@ -200,8 +218,9 @@ async def main():
|
||||
# Устанавливаем команды бота
|
||||
await setup_bot_commands()
|
||||
|
||||
# Запускаем бота
|
||||
await bot.infinity_polling()
|
||||
# Запускаем бота с обработкой реакций
|
||||
logger.info("Запуск бота с allowed_updates: message, message_reaction, chat_member")
|
||||
await bot.infinity_polling(allowed_updates=['message', 'message_reaction', 'chat_member'])
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.types import Message
|
||||
from telebot.types import Message, MessageReactionUpdated, ReactionTypeEmoji
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
@@ -10,10 +10,233 @@ from config import THANK_COOLDOWN
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Фоновая задача для автоочистки старых сообщений
|
||||
_cleanup_task = None
|
||||
|
||||
def _cache_message(chat_id: int, message_id: int, user_id: int, message_thread_id: int = None):
|
||||
"""Добавляет сообщение в кэш БД"""
|
||||
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) из кэша БД"""
|
||||
return db.get_cached_message(chat_id, message_id)
|
||||
|
||||
async def _cleanup_old_cache():
|
||||
"""Фоновая задача для очистки старых сообщений из кэша каждые 6 часов"""
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(21600) # Ждём 6 часов
|
||||
deleted = db.cleanup_old_messages(max_age_seconds=604800) # Удаляем старше 7 дней
|
||||
cache_size = db.get_cache_size()
|
||||
logger.info(f"[CACHE CLEANUP] Удалено: {deleted}, размер кэша: {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):
|
||||
"""
|
||||
Обрабатывает реакции на сообщения.
|
||||
Реакции работают как переключатель:
|
||||
- Поставил 👍 → +1 карма | Убрал 👍 → -1 карма
|
||||
- Поставил 👎 → -1 карма | Убрал 👎 → +1 карма
|
||||
- Поставил 🔥 → +2 кармы | Убрал 🔥 → -2 кармы
|
||||
- Поставил ❤ → +5 кармы | Убрал ❤ → -5 кармы
|
||||
- Поставил ❤🔥 → +10 кармы | Убрал ❤🔥 → -10 кармы
|
||||
"""
|
||||
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
|
||||
|
||||
# Примечание: мы не проверяем является ли to_user_id ботом, т.к.:
|
||||
# 1. Сообщения ботов не кэшируются (только пользовательские)
|
||||
# 2. Если бот все же попал в кэш, это исключительный случай и не критично
|
||||
|
||||
# Проверяем старые реакции
|
||||
old_thumbs_up = False
|
||||
old_thumbs_down = False
|
||||
old_heart = False
|
||||
old_fire_heart = False
|
||||
old_fire = 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
|
||||
elif react.emoji == "❤":
|
||||
old_heart = True
|
||||
elif react.emoji == "❤🔥":
|
||||
old_fire_heart = True
|
||||
elif react.emoji == "🔥":
|
||||
old_fire = True
|
||||
|
||||
# Проверяем новые реакции
|
||||
new_thumbs_up = False
|
||||
new_thumbs_down = False
|
||||
new_heart = False
|
||||
new_fire_heart = False
|
||||
new_fire = 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
|
||||
elif react.emoji == "❤":
|
||||
new_heart = True
|
||||
elif react.emoji == "❤🔥":
|
||||
new_fire_heart = True
|
||||
elif react.emoji == "🔥":
|
||||
new_fire = True
|
||||
|
||||
# Определяем изменение кармы (накапливаем все изменения)
|
||||
karma_change = 0
|
||||
actions = [] # Список всех действий для логирования
|
||||
|
||||
# Логика изменения кармы - проверяем ВСЕ реакции (не elif!)
|
||||
# Это важно, т.к. пользователь может менять реакции (убрать 👍 и поставить 🔥)
|
||||
|
||||
# Проверяем 👍
|
||||
if new_thumbs_up and not old_thumbs_up:
|
||||
karma_change += 1
|
||||
actions.append("поставил 👍 (+1)")
|
||||
elif old_thumbs_up and not new_thumbs_up:
|
||||
karma_change -= 1
|
||||
actions.append("убрал 👍 (-1)")
|
||||
|
||||
# Проверяем 👎
|
||||
if new_thumbs_down and not old_thumbs_down:
|
||||
karma_change -= 1
|
||||
actions.append("поставил 👎 (-1)")
|
||||
elif old_thumbs_down and not new_thumbs_down:
|
||||
karma_change += 1
|
||||
actions.append("убрал 👎 (+1)")
|
||||
|
||||
# Проверяем ❤
|
||||
if new_heart and not old_heart:
|
||||
karma_change += 5
|
||||
actions.append("поставил ❤ (+5)")
|
||||
elif old_heart and not new_heart:
|
||||
karma_change -= 5
|
||||
actions.append("убрал ❤ (-5)")
|
||||
|
||||
# Проверяем ❤🔥
|
||||
if new_fire_heart and not old_fire_heart:
|
||||
karma_change += 10
|
||||
actions.append("поставил ❤🔥 (+10)")
|
||||
elif old_fire_heart and not new_fire_heart:
|
||||
karma_change -= 10
|
||||
actions.append("убрал ❤🔥 (-10)")
|
||||
|
||||
# Проверяем 🔥
|
||||
if new_fire and not old_fire:
|
||||
karma_change += 2
|
||||
actions.append("поставил 🔥 (+2)")
|
||||
elif old_fire and not new_fire:
|
||||
karma_change -= 2
|
||||
actions.append("убрал 🔥 (-2)")
|
||||
|
||||
# Если нет изменений - выходим
|
||||
if karma_change == 0:
|
||||
logger.info(f"[KARMA] Нет изменений в реакциях")
|
||||
return
|
||||
|
||||
# Формируем текст действий для логирования
|
||||
action_text = ", ".join(actions)
|
||||
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]
|
||||
|
||||
# Формируем эмодзи для уведомления (берем первое действие или дефолтное)
|
||||
notification_emoji = "⭐"
|
||||
if "👍" in action_text:
|
||||
notification_emoji = "👍"
|
||||
elif "👎" in action_text:
|
||||
notification_emoji = "👎"
|
||||
elif "🔥" in action_text and "❤🔥" not in action_text:
|
||||
notification_emoji = "🔥"
|
||||
elif "❤🔥" in action_text:
|
||||
notification_emoji = "❤🔥"
|
||||
elif "❤" in action_text:
|
||||
notification_emoji = "❤"
|
||||
|
||||
# Отправляем уведомление
|
||||
karma_sign = f"+{karma_change}" if karma_change > 0 else str(karma_change)
|
||||
change_word = "увеличена" if karma_change > 0 else "уменьшена"
|
||||
response = f"{notification_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 секунд В ФОНЕ (не блокируя обработку других реакций)
|
||||
async def delete_notification():
|
||||
try:
|
||||
await asyncio.sleep(10)
|
||||
await bot.delete_message(chat_id, sent_message.message_id)
|
||||
logger.info(f"[KARMA] Уведомление удалено")
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить уведомление о карме: {e}")
|
||||
|
||||
# Запускаем удаление в фоне
|
||||
asyncio.create_task(delete_notification())
|
||||
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):
|
||||
"""
|
||||
@@ -28,6 +251,12 @@ def register_handlers(bot: AsyncTeleBot):
|
||||
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]}")
|
||||
@@ -82,16 +311,22 @@ def register_handlers(bot: AsyncTeleBot):
|
||||
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)
|
||||
sent_message = await bot.reply_to(message, response)
|
||||
logger.info(f"Пользователь {from_user.id} поблагодарил {to_user.id}, карма: {new_karma}")
|
||||
|
||||
# Удаляем уведомление через 25 секунд В ФОНЕ
|
||||
async def delete_thank_notification():
|
||||
try:
|
||||
await asyncio.sleep(25)
|
||||
await bot.delete_message(chat_id, sent_message.message_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить уведомление о карме: {e}")
|
||||
|
||||
# Запускаем удаление в фоне
|
||||
asyncio.create_task(delete_thank_notification())
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось удалить уведомление о карме: {e}")
|
||||
logger.error(f"Ошибка отправки уведомления о благодарности: {e}", exc_info=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке благодарности: {e}", exc_info=True)
|
@@ -90,6 +90,10 @@ async def apply_mute(bot: AsyncTeleBot, message: Message, user_id: int, duration
|
||||
until_date=until_date
|
||||
)
|
||||
|
||||
# Снимаем карму за автомут
|
||||
db.add_karma(user_id, message.chat.id, -10)
|
||||
logger.info(f"Снято 10 кармы пользователю {user_id} за автомут")
|
||||
|
||||
# Удаляем сообщение с матом
|
||||
try:
|
||||
await bot.delete_message(chat_id=message.chat.id, message_id=message.message_id)
|
||||
|
@@ -52,6 +52,7 @@ def register_handlers(bot: AsyncTeleBot):
|
||||
user_data = db.get_user_by_username(username)
|
||||
if user_data:
|
||||
target_user_id = user_data[0]
|
||||
logger.info(f"[KARMA CMD] Найден пользователь по username '{username}': id={user_data[0]}, nickname={user_data[1]}, tag={user_data[2]}")
|
||||
target_user = type('User', (), {
|
||||
'id': user_data[0],
|
||||
'first_name': user_data[1],
|
||||
@@ -75,6 +76,8 @@ def register_handlers(bot: AsyncTeleBot):
|
||||
else:
|
||||
user_display = target_user.first_name
|
||||
|
||||
logger.info(f"[KARMA CMD] Показываем карму: user_id={target_user_id}, username={getattr(target_user, 'username', None)}, display={user_display}, karma={karma}")
|
||||
|
||||
# Определяем эмодзи в зависимости от кармы
|
||||
if karma == 0:
|
||||
emoji = "😐"
|
||||
|
@@ -279,6 +279,10 @@ async def mute_command(bot: AsyncTeleBot, message: Message, photo_path: str = No
|
||||
until_date=until_date
|
||||
)
|
||||
|
||||
# Снимаем карму за мут
|
||||
db.add_karma(target_user.id, message.chat.id, -10)
|
||||
logger.info(f"Снято 10 кармы пользователю {target_user.id} за мут")
|
||||
|
||||
# Форматируем время в удобный формат
|
||||
time_display = format_mute_time(mute_seconds)
|
||||
|
||||
|
198
src/modules/setkarma.py
Normal file
198
src/modules/setkarma.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from telebot.async_telebot import AsyncTeleBot
|
||||
from telebot.types import Message, User
|
||||
import logging
|
||||
|
||||
from database import db
|
||||
from action_reporter import action_reporter
|
||||
from utils import delete_messages, check_admin_status
|
||||
|
||||
from config import COMMAND_MESSAGES
|
||||
|
||||
# Получаем логгер для текущего модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Регистрирует обработчик команды
|
||||
def register_handlers(bot: AsyncTeleBot):
|
||||
|
||||
# Обработчик команды /setkarma
|
||||
@bot.message_handler(commands=['setkarma'])
|
||||
async def _setkarma_command_wrapper(message: Message):
|
||||
await setkarma_command(bot, message)
|
||||
|
||||
# Основная функция команды /setkarma
|
||||
async def setkarma_command(bot: AsyncTeleBot, message: Message):
|
||||
"""Устанавливает карму пользователя в указанное значение"""
|
||||
|
||||
# Определяем целевого пользователя
|
||||
target_user = None
|
||||
|
||||
# Определяем новое значение кармы
|
||||
new_karma_value = None
|
||||
|
||||
# Разбиваем текст сообщения на части
|
||||
parts_msg = message.text.split()
|
||||
|
||||
# Команда /setkarma help
|
||||
if len(parts_msg) == 2 and parts_msg[1].strip() in ('help', 'помощь'):
|
||||
# Отправляем инструкцию
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text=COMMAND_MESSAGES['setkarma_help'],
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
|
||||
# Удаляем сообщения через 30 секунд
|
||||
await delete_messages(bot, message, time_sleep=30, number_message=2)
|
||||
return
|
||||
|
||||
try:
|
||||
# Проверяем, является ли отправитель администратором
|
||||
if await check_admin_status(bot, message) == 1:
|
||||
return
|
||||
|
||||
# Проверяем, что это групповой чат
|
||||
if message.chat.type not in ['group', 'supergroup']:
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text="❌ Эта команда работает только в групповых чатах.",
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
||||
return
|
||||
|
||||
# Если недостаточно аргументов
|
||||
if len(parts_msg) < 2:
|
||||
await delete_messages(bot, message, time_sleep=3, number_message=1)
|
||||
return
|
||||
|
||||
# Команда через ответ на сообщение: /setkarma 100
|
||||
if message.reply_to_message and (not message.is_topic_message or message.message_thread_id != message.reply_to_message.message_id):
|
||||
if len(parts_msg) >= 2:
|
||||
target_user = message.reply_to_message.from_user
|
||||
try:
|
||||
new_karma_value = int(parts_msg[1])
|
||||
except ValueError:
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text="❌ Неверный формат кармы. Укажите целое число.",
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
||||
return
|
||||
|
||||
# Команда с указанием пользователя: /setkarma @username 100 или /setkarma 123456789 100
|
||||
elif len(parts_msg) >= 3 and (parts_msg[1].strip().isdigit() or parts_msg[1].startswith('@')):
|
||||
identifier = parts_msg[1].strip()
|
||||
try:
|
||||
new_karma_value = int(parts_msg[2])
|
||||
except ValueError:
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text="❌ Неверный формат кармы. Укажите целое число.",
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
||||
return
|
||||
|
||||
# Поиск по ID
|
||||
if identifier.isdigit():
|
||||
user_info = db.get_user(int(identifier))
|
||||
if user_info:
|
||||
target_user = User(
|
||||
id=user_info[0],
|
||||
first_name=user_info[1],
|
||||
username=user_info[2],
|
||||
is_bot=False
|
||||
)
|
||||
|
||||
# Поиск по тегу
|
||||
elif identifier.startswith('@'):
|
||||
user_info = db.get_user_by_username(identifier[1:])
|
||||
if user_info:
|
||||
target_user = User(
|
||||
id=user_info[0],
|
||||
first_name=user_info[1],
|
||||
username=user_info[2],
|
||||
is_bot=False
|
||||
)
|
||||
|
||||
# Если команда неправильная
|
||||
else:
|
||||
await delete_messages(bot, message, time_sleep=3, number_message=1)
|
||||
return
|
||||
|
||||
# Если пользователь не найден
|
||||
if not target_user:
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text=COMMAND_MESSAGES['user_not_found'],
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
||||
return
|
||||
|
||||
# Проверяем, не пытается ли установить карму себе
|
||||
if message.from_user.id == target_user.id:
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text="❌ Нельзя устанавливать карму самому себе.",
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
||||
return
|
||||
|
||||
# Получаем текущую карму
|
||||
old_karma = db.get_karma(target_user.id, message.chat.id)
|
||||
|
||||
# Устанавливаем новую карму
|
||||
db.set_karma(target_user.id, message.chat.id, new_karma_value)
|
||||
|
||||
# Формируем имя пользователя для отображения
|
||||
target_user_display = f"@{target_user.username}" if target_user.username else target_user.first_name
|
||||
|
||||
# Вычисляем разницу
|
||||
karma_diff = new_karma_value - old_karma
|
||||
diff_sign = "+" if karma_diff > 0 else ""
|
||||
|
||||
# Отправляем сообщение-лог в админ-чат
|
||||
await action_reporter.log_action(
|
||||
action="УСТАНОВКА КАРМЫ",
|
||||
user_id=target_user.id,
|
||||
admin_id=message.from_user.id,
|
||||
reason=f"Карма изменена: {old_karma} → {new_karma_value} ({diff_sign}{karma_diff})",
|
||||
duration=None,
|
||||
)
|
||||
|
||||
# Отправляем сообщение в чат
|
||||
response = (
|
||||
f"✅ Карма пользователя {target_user_display} установлена на <b>{new_karma_value}</b>\n"
|
||||
f"Было: {old_karma} → Стало: {new_karma_value} ({diff_sign}{karma_diff})"
|
||||
)
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text=response,
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
|
||||
# Записываем действие в логи
|
||||
logger.info(
|
||||
f"Администратор {message.from_user.id} установил карму пользователя {target_user.id} "
|
||||
f"на {new_karma_value} (было {old_karma})"
|
||||
)
|
||||
|
||||
# Удаляем сообщения через 5 секунд
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
||||
|
||||
except Exception as e:
|
||||
# Отправляем ошибку
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text=COMMAND_MESSAGES['general_error'],
|
||||
message_thread_id=message.message_thread_id,
|
||||
)
|
||||
|
||||
# Записываем ошибку в логи
|
||||
logger.error(f"Общая ошибка в setkarma_command: {str(e)}")
|
||||
|
||||
# Удаляем сообщения через 5 секунд
|
||||
await delete_messages(bot, message, time_sleep=5, number_message=2)
|
@@ -144,6 +144,10 @@ async def warn_command(bot: AsyncTeleBot, message: Message):
|
||||
admin_id=message.from_user.id
|
||||
)
|
||||
|
||||
# Снимаем карму за предупреждение
|
||||
db.add_karma(target_user.id, message.chat.id, -5)
|
||||
logger.info(f"Снято 5 кармы пользователю {target_user.id} за предупреждение")
|
||||
|
||||
# Импортируем константы времени
|
||||
from config import ONE_WEEK, TWO_WEEKS
|
||||
|
||||
|
17
src/utils.py
17
src/utils.py
@@ -8,14 +8,21 @@ from config import COMMAND_MESSAGES
|
||||
# Получаем логгер для текущего модуля
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Удаляет определённое количество сообщения
|
||||
# Удаляет определённое количество сообщения В ФОНЕ (не блокирует обработку других событий)
|
||||
async def delete_messages(bot: AsyncTeleBot, message: Message, time_sleep: int, number_message: int):
|
||||
await asyncio.sleep(time_sleep)
|
||||
for i in range(number_message):
|
||||
async def _delete_task():
|
||||
try:
|
||||
await bot.delete_message(message.chat.id, message.message_id+i)
|
||||
await asyncio.sleep(time_sleep)
|
||||
for i in range(number_message):
|
||||
try:
|
||||
await bot.delete_message(message.chat.id, message.message_id+i)
|
||||
except Exception as e:
|
||||
logger.debug(f"Не удалось удалить сообщение {message.message_id+i}: {e}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Не удалось удалить сообщение {message.message_id+i}: {e}")
|
||||
logger.error(f"Ошибка в задаче удаления сообщений: {e}")
|
||||
|
||||
# Запускаем удаление в фоне
|
||||
asyncio.create_task(_delete_task())
|
||||
|
||||
# Проверяет, является ли отправитель администратором
|
||||
async def check_admin_status(bot: AsyncTeleBot, message: Message, check_restrict_rights: bool = True):
|
||||
|
Reference in New Issue
Block a user