Compare commits

...

21 Commits

Author SHA1 Message Date
0bd399f121 Исправление синтаксической ошибки 2025-10-20 09:23:50 +03:00
c257c6c1a2 Исправление логики работы кармы
Добавление дополнительных обработчиков ошибок в работе кармы
Убрано кэширование сообщений от ботов3
2025-10-20 09:19:04 +03:00
c7b2961ae1 Убран медленный режим 2025-10-20 01:33:17 +03:00
8af2f128a7 Исправление дополнительное логирование /karma 2025-10-19 19:58:37 +03:00
4aba68d242 Исправление с границами слов
Исправление команды карма с юзернеймом
2025-10-19 19:50:32 +03:00
8bf512e509 Изменение увеличение кэша до 7 дней 2025-10-19 19:29:36 +03:00
459ed66e9a Изменение кэширования сообщений 2025-10-19 19:22:07 +03:00
de1c82c267 Исправления зависания 2 2025-10-19 19:08:52 +03:00
4a2aa00eb7 Исправления зависания 2025-10-19 18:42:10 +03:00
c4400fc244 Изменение логики медленного режима 2025-10-19 17:37:52 +03:00
b7f09ae719 Добавление ещё нескольких эмодзи 2025-10-19 14:54:46 +03:00
1619e82df1 Исправление канала с отправкой уведомления о карме в правильный канал 2025-10-19 14:32:55 +03:00
0ee7cb3bd4 Изменение логики эмодзи кармы как переключатель 2025-10-19 14:28:01 +03:00
9b11f21bc1 Добавление уведомления за карму от эмодзи 2025-10-19 14:18:59 +03:00
2b9e819944 Добавление команды установки кармы 2025-10-19 14:14:35 +03:00
58daea0492 Добавление логирования кэширования 2025-10-19 14:05:21 +03:00
63ac924a3d Добавление медленного режима в зависимости от кармы 2025-10-19 13:56:21 +03:00
be64915e9b Добавление кармы за реакцию большого пальца 2025-10-19 13:34:56 +03:00
61e9d31a75 Исключение ложного срабатывания при благодарности без ответа 2025-10-19 13:27:44 +03:00
6bdf996ca4 Увеличение времени на отображения сообщения с результатом увеличения кармы
Увеличение очков кармы за благодарность с !
2025-10-19 13:21:19 +03:00
c07a082694 Увеличение времени на отображения сообщения с результатом увеличения кармы 2025-10-19 13:18:23 +03:00
14 changed files with 667 additions and 1275 deletions

File diff suppressed because it is too large Load Diff

29
.gitignore vendored
View File

@@ -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

View File

@@ -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 подряд)
# хууууууй → хуй, пииииздец → пиздец

View File

@@ -187,10 +187,23 @@ COMMAND_MESSAGES = {
"3. Ответ на сообщение:\n"
" Ответьте на сообщение: <code>/karma</code>\n\n"
"<b>💡 Как начислить карму?</b>\n"
"Ответьте на сообщение пользователя словами благодарности:\n"
"• спасибо\n"
"• благодарю\n"
"• спс, сенкс, thanks и др.\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': (
@@ -199,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>"
)
}

View File

@@ -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
View File

View File

@@ -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:

View File

@@ -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]}")
@@ -61,8 +290,11 @@ def register_handlers(bot: AsyncTeleBot):
# Молча игнорируем, чтобы не спамить
return
# Определяем количество кармы: x2 если есть восклицательный знак
karma_amount = 2 if '!' in message.text else 1
# Начисляем карму (благодарность уже записана атомарно выше)
db.add_karma(to_user.id, chat_id, 1)
db.add_karma(to_user.id, chat_id, karma_amount)
# Получаем новую карму пользователя
new_karma = db.get_karma(to_user.id, chat_id)
@@ -74,19 +306,27 @@ def register_handlers(bot: AsyncTeleBot):
else:
to_user_display = to_user_name
# Отправляем уведомление
response = f"👍 Карма пользователя {to_user_display} увеличена! Текущая карма: {new_karma}"
# Отправляем уведомление с указанием количества кармы
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}")
# Удаляем уведомление через 5 секунд
await asyncio.sleep(5)
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)

View File

@@ -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)

View File

@@ -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 = "😐"

View File

@@ -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
View 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)

View File

@@ -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

View File

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