diff --git a/src/config.py b/src/config.py
index a3c27ef..b46f6ec 100644
--- a/src/config.py
+++ b/src/config.py
@@ -27,6 +27,9 @@ COMMAND_MESSAGES = {
"• /unmute help
- Снятие мута\n"
"• /ban help
- Инструкция по бану\n"
"• /unban help
- Снятие бана\n\n"
+ "⭐ Система кармы:\n"
+ "• /karma
- Просмотр кармы\n"
+ "• /top
- Топ пользователей по карме\n\n"
"ℹ️ Для подробностей по конкретной команде используйте: /команда help"
),
'manual_mute': (
@@ -155,6 +158,30 @@ COMMAND_MESSAGES = {
),
'warned': '⚠️ Пользователь получил предупреждение.',
'warned_auto_mute_day': '⚠️ Пользователь получил предупреждение и автомут на 1 день (повторное нарушение за неделю).',
- 'warned_auto_mute_week': '⚠️ Пользователь получил предупреждение и автомут на 7 дней (множественные нарушения).'
+ 'warned_auto_mute_week': '⚠️ Пользователь получил предупреждение и автомут на 7 дней (множественные нарушения).',
+ 'karma_help': (
+ "⭐ Команда /karma\n\n"
+ "Показывает карму пользователя в этом чате\n\n"
+ "🎯 Способы использования:\n"
+ "1. Показать свою карму:\n"
+ " /karma
\n"
+ "2. По тегу пользователя:\n"
+ " /karma @username
\n"
+ "3. Ответ на сообщение:\n"
+ " Ответьте на сообщение: /karma
\n\n"
+ "💡 Как начислить карму?\n"
+ "Ответьте на сообщение пользователя словами благодарности:\n"
+ "• спасибо\n"
+ "• благодарю\n"
+ "• спс, сенкс, thanks и др.\n\n"
+ "⏱ Одному пользователю можно давать карму раз в час"
+ ),
+ 'top_karma_help': (
+ "🏆 Команда /top\n\n"
+ "Показывает топ-10 пользователей по карме в этом чате\n\n"
+ "🎯 Использование:\n"
+ " /top
\n\n"
+ "💡 Система кармы поощряет активных и полезных участников чата!"
+ )
}
\ No newline at end of file
diff --git a/src/data/thank_words.json b/src/data/thank_words.json
new file mode 100644
index 0000000..0a00c5c
--- /dev/null
+++ b/src/data/thank_words.json
@@ -0,0 +1,25 @@
+{
+ "thank_words": [
+ "спасибо",
+ "благодарю",
+ "спс",
+ "сенкс",
+ "сенкью",
+ "thanks",
+ "thank you",
+ "thx",
+ "ty",
+ "дякую",
+ "дзякуй",
+ "рахмет",
+ "пасиб",
+ "пасибо",
+ "спасибочки",
+ "благодарочка",
+ "мерси",
+ "merci",
+ "danke",
+ "gracias",
+ "grazie"
+ ]
+}
\ No newline at end of file
diff --git a/src/database.py b/src/database.py
index 95822ae..b6ed952 100644
--- a/src/database.py
+++ b/src/database.py
@@ -45,6 +45,26 @@ class Database: # Инициализация класса
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS karma (
+ user_id INTEGER NOT NULL,
+ chat_id INTEGER NOT NULL,
+ karma_points INTEGER DEFAULT 0,
+ PRIMARY KEY (user_id, chat_id),
+ FOREIGN KEY (user_id) REFERENCES users (id)
+ )
+ ''')
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS karma_history (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ from_user_id INTEGER NOT NULL,
+ to_user_id INTEGER NOT NULL,
+ chat_id INTEGER NOT NULL,
+ timestamp INTEGER NOT NULL,
+ FOREIGN KEY (from_user_id) REFERENCES users (id),
+ FOREIGN KEY (to_user_id) REFERENCES users (id)
+ )
+ ''')
connect.commit()
# Возвращает соединение с базой данных
@@ -218,5 +238,83 @@ class Database: # Инициализация класса
logger.info(f"Сброшено {deleted_count} предупреждений пользователя {user_id} в чате {chat_id}")
return deleted_count
+ # Добавляет карму пользователю
+ def add_karma(self, user_id: int, chat_id: int, amount: int = 1):
+ 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:
+ # Обновляем существующую карму
+ new_karma = result[0] + amount
+ cursor.execute('''
+ UPDATE karma
+ SET karma_points = ?
+ WHERE user_id = ? AND chat_id = ?
+ ''', (new_karma, user_id, chat_id))
+ else:
+ # Создаем новую запись
+ cursor.execute('''
+ INSERT INTO karma (user_id, chat_id, karma_points)
+ VALUES (?, ?, ?)
+ ''', (user_id, chat_id, amount))
+
+ connect.commit()
+ logger.info(f"Пользователю {user_id} добавлено {amount} кармы в чате {chat_id}")
+
+ # Получает карму пользователя
+ def get_karma(self, user_id: int, chat_id: int) -> 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()
+ return result[0] if result else 0
+
+ # Получает топ пользователей по карме
+ def get_top_karma(self, chat_id: int, limit: int = 10):
+ with self._get_connection() as connect:
+ cursor = connect.cursor()
+ cursor.execute('''
+ SELECT k.user_id, u.nickname, u.tag, k.karma_points
+ FROM karma k
+ JOIN users u ON k.user_id = u.id
+ WHERE k.chat_id = ? AND k.karma_points > 0
+ ORDER BY k.karma_points DESC
+ LIMIT ?
+ ''', (chat_id, limit))
+ return cursor.fetchall()
+
+ # Проверяет, может ли пользователь поблагодарить другого (проверка кулдауна)
+ def can_thank(self, from_user_id: int, to_user_id: int, chat_id: int, cooldown_seconds: int = 3600) -> bool:
+ import time
+ with self._get_connection() as connect:
+ cursor = connect.cursor()
+ cutoff_time = int(time.time()) - cooldown_seconds
+ cursor.execute('''
+ SELECT COUNT(*)
+ FROM karma_history
+ WHERE from_user_id = ? AND to_user_id = ? AND chat_id = ? AND timestamp > ?
+ ''', (from_user_id, to_user_id, chat_id, cutoff_time))
+ result = cursor.fetchone()
+ return result[0] == 0 if result else True
+
+ # Добавляет запись в историю кармы
+ def add_karma_history(self, from_user_id: int, to_user_id: int, chat_id: int):
+ import time
+ with self._get_connection() as connect:
+ cursor = connect.cursor()
+ cursor.execute('''
+ INSERT INTO karma_history (from_user_id, to_user_id, chat_id, timestamp)
+ VALUES (?, ?, ?, ?)
+ ''', (from_user_id, to_user_id, chat_id, int(time.time())))
+ connect.commit()
+ logger.info(f"Пользователь {from_user_id} поблагодарил {to_user_id} в чате {chat_id}")
+
# Создаем экземпляр базы данных для импорта в других модулях
db = Database()
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index b891dfe..01ba25c 100644
--- a/src/main.py
+++ b/src/main.py
@@ -149,6 +149,8 @@ async def setup_bot_commands():
BotCommand("badwords", "Управление списком бранных слов. /badwords help"),
BotCommand("reset_violations", "Сбросить счётчик нарушений пользователя"),
BotCommand("botdata", "Получить данные бота (только для админов)"),
+ BotCommand("karma", "Просмотр кармы пользователя"),
+ BotCommand("top", "Топ-10 пользователей по карме"),
]
await bot.set_my_commands(commands)
diff --git a/src/modules/karma_commands.py b/src/modules/karma_commands.py
new file mode 100644
index 0000000..0299860
--- /dev/null
+++ b/src/modules/karma_commands.py
@@ -0,0 +1,153 @@
+from telebot.async_telebot import AsyncTeleBot
+from telebot.types import Message
+import logging
+
+from database import db
+
+logger = logging.getLogger(__name__)
+
+def register_handlers(bot: AsyncTeleBot):
+ """Регистрирует обработчики команд для системы кармы"""
+
+ @bot.message_handler(commands=['karma', 'rating'])
+ async def handle_karma_command(message: Message):
+ """
+ Команда /karma - показывает карму пользователя
+ Использование:
+ /karma - показать свою карму
+ /karma @username - показать карму пользователя
+ /karma (в ответ на сообщение) - показать карму автора сообщения
+ """
+ try:
+ # Проверяем, что это групповой чат
+ if message.chat.type not in ['group', 'supergroup']:
+ await bot.reply_to(message, "❌ Эта команда работает только в групповых чатах")
+ return
+
+ chat_id = message.chat.id
+ target_user = None
+ target_user_id = None
+
+ # Если команда - ответ на сообщение
+ if message.reply_to_message:
+ target_user = message.reply_to_message.from_user
+ target_user_id = target_user.id
+
+ # Если указан username в команде
+ elif len(message.text.split()) > 1:
+ username_arg = message.text.split()[1]
+ # Убираем @ если есть
+ username = username_arg.lstrip('@')
+
+ # Ищем пользователя в БД
+ user_data = db.get_user_by_username(username)
+ if user_data:
+ target_user_id = user_data[0]
+ target_user = type('User', (), {
+ 'id': user_data[0],
+ 'first_name': user_data[1],
+ 'username': user_data[2]
+ })()
+ else:
+ await bot.reply_to(message, f"❌ Пользователь @{username} не найден в базе данных")
+ return
+
+ # Иначе показываем карму отправителя команды
+ else:
+ target_user = message.from_user
+ target_user_id = target_user.id
+
+ # Получаем карму
+ karma = db.get_karma(target_user_id, chat_id)
+
+ # Формируем имя пользователя
+ if hasattr(target_user, 'username') and target_user.username:
+ user_display = f"@{target_user.username}"
+ else:
+ user_display = target_user.first_name
+
+ # Определяем эмодзи в зависимости от кармы
+ if karma == 0:
+ emoji = "😐"
+ elif karma < 5:
+ emoji = "🙂"
+ elif karma < 10:
+ emoji = "😊"
+ elif karma < 20:
+ emoji = "😄"
+ elif karma < 50:
+ emoji = "🌟"
+ else:
+ emoji = "⭐"
+
+ response = f"{emoji} Карма пользователя {user_display}: {karma}"
+
+ sent_message = await bot.reply_to(message, response)
+
+ # Удаляем команду и ответ через 10 секунд
+ import asyncio
+ await asyncio.sleep(10)
+ try:
+ await bot.delete_message(chat_id, message.message_id)
+ 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"Ошибка при обработке команды /karma: {e}", exc_info=True)
+ await bot.reply_to(message, "❌ Произошла ошибка при получении кармы")
+
+ @bot.message_handler(commands=['top', 'leaderboard', 'topkarma'])
+ async def handle_top_command(message: Message):
+ """
+ Команда /top - показывает топ пользователей по карме
+ """
+ try:
+ # Проверяем, что это групповой чат
+ if message.chat.type not in ['group', 'supergroup']:
+ await bot.reply_to(message, "❌ Эта команда работает только в групповых чатах")
+ return
+
+ chat_id = message.chat.id
+
+ # Получаем топ 10 пользователей
+ top_users = db.get_top_karma(chat_id, limit=10)
+
+ if not top_users:
+ await bot.reply_to(message, "📊 В этом чате пока нет пользователей с кармой")
+ return
+
+ # Формируем сообщение
+ response = "🏆 Топ-10 пользователей по карме:\n\n"
+
+ medals = ["🥇", "🥈", "🥉"]
+
+ for idx, (user_id, nickname, tag, karma_points) in enumerate(top_users, 1):
+ # Определяем медаль для топ-3
+ if idx <= 3:
+ medal = medals[idx - 1]
+ else:
+ medal = f"{idx}."
+
+ # Формируем отображение пользователя
+ if tag:
+ user_display = f"@{tag}"
+ else:
+ user_display = nickname or f"ID: {user_id}"
+
+ response += f"{medal} {user_display} — {karma_points} кармы\n"
+
+ sent_message = await bot.reply_to(message, response, parse_mode='HTML')
+
+ # Удаляем команду и ответ через 30 секунд
+ import asyncio
+ await asyncio.sleep(30)
+ try:
+ await bot.delete_message(chat_id, message.message_id)
+ 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"Ошибка при обработке команды /top: {e}", exc_info=True)
+ await bot.reply_to(message, "❌ Произошла ошибка при получении топа")
\ No newline at end of file
diff --git a/src/modules/karma_tracker.py b/src/modules/karma_tracker.py
new file mode 100644
index 0000000..a237766
--- /dev/null
+++ b/src/modules/karma_tracker.py
@@ -0,0 +1,81 @@
+from telebot.async_telebot import AsyncTeleBot
+from telebot.types import Message
+import logging
+
+from database import db
+from thank_words import contains_thank_word
+
+logger = logging.getLogger(__name__)
+
+# Кулдаун для благодарностей (в секундах) - 1 час
+THANK_COOLDOWN = 3600
+
+def register_handlers(bot: AsyncTeleBot):
+ """Регистрирует обработчики для отслеживания благодарностей"""
+
+ @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:
+ # Проверяем, что это групповой чат
+ if message.chat.type not in ['group', 'supergroup']:
+ return
+
+ # Проверяем наличие благодарственных слов
+ if not contains_thank_word(message.text):
+ 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
+
+ # Проверяем кулдаун (можно благодарить одного пользователя раз в час)
+ if not db.can_thank(from_user.id, to_user.id, chat_id, THANK_COOLDOWN):
+ logger.info(f"Пользователь {from_user.id} уже благодарил {to_user.id} недавно")
+ # Молча игнорируем, чтобы не спамить
+ return
+
+ # Начисляем карму
+ db.add_karma(to_user.id, chat_id, 1)
+ db.add_karma_history(from_user.id, to_user.id, chat_id)
+
+ # Получаем новую карму пользователя
+ 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
+
+ # Отправляем уведомление
+ response = f"👍 Карма пользователя {to_user_display} увеличена! Текущая карма: {new_karma}"
+
+ sent_message = await bot.reply_to(message, response)
+
+ logger.info(f"Пользователь {from_user.id} поблагодарил {to_user.id}, карма: {new_karma}")
+
+ # Удаляем уведомление через 5 секунд
+ import asyncio
+ await asyncio.sleep(5)
+ 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)
\ No newline at end of file
diff --git a/src/thank_words.py b/src/thank_words.py
new file mode 100644
index 0000000..a2363ab
--- /dev/null
+++ b/src/thank_words.py
@@ -0,0 +1,109 @@
+import json
+import os
+import logging
+
+logger = logging.getLogger(__name__)
+
+# Путь к файлу с благодарственными словами
+THANK_WORDS_FILE = os.path.join(os.path.dirname(__file__), 'data', 'thank_words.json')
+
+# Кэш для быстрой проверки
+_thank_words_cache = None
+
+def _load_thank_words():
+ """Загружает список благодарственных слов из файла"""
+ global _thank_words_cache
+ try:
+ with open(THANK_WORDS_FILE, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+ _thank_words_cache = [word.lower() for word in data.get('thank_words', [])]
+ logger.info(f"Загружено {len(_thank_words_cache)} благодарственных слов")
+ return _thank_words_cache
+ except FileNotFoundError:
+ logger.error(f"Файл {THANK_WORDS_FILE} не найден")
+ _thank_words_cache = []
+ return []
+ except json.JSONDecodeError as e:
+ logger.error(f"Ошибка разбора JSON: {e}")
+ _thank_words_cache = []
+ return []
+
+def get_thank_words():
+ """Возвращает список благодарственных слов (с кэшированием)"""
+ global _thank_words_cache
+ if _thank_words_cache is None:
+ _load_thank_words()
+ return _thank_words_cache
+
+def contains_thank_word(text: str) -> bool:
+ """
+ Проверяет, содержит ли текст благодарственные слова
+
+ Args:
+ text: Текст для проверки
+
+ Returns:
+ True если найдено хотя бы одно благодарственное слово
+ """
+ if not text:
+ return False
+
+ text_lower = text.lower()
+ thank_words = get_thank_words()
+
+ # Разбиваем текст на слова для проверки
+ words = text_lower.split()
+
+ # Проверяем каждое слово и фразы из 2 слов
+ for i, word in enumerate(words):
+ # Очищаем от знаков препинания
+ clean_word = ''.join(c for c in word if c.isalnum())
+ if clean_word in thank_words:
+ return True
+
+ # Проверяем фразы из двух слов (например, "thank you")
+ if i < len(words) - 1:
+ two_word_phrase = clean_word + ' ' + ''.join(c for c in words[i+1] if c.isalnum())
+ if two_word_phrase in thank_words:
+ return True
+
+ return False
+
+def get_thank_words_from_text(text: str) -> list:
+ """
+ Возвращает список найденных благодарственных слов в тексте
+
+ Args:
+ text: Текст для анализа
+
+ Returns:
+ Список найденных благодарственных слов
+ """
+ if not text:
+ return []
+
+ text_lower = text.lower()
+ thank_words = get_thank_words()
+ found_words = []
+
+ words = text_lower.split()
+
+ for i, word in enumerate(words):
+ clean_word = ''.join(c for c in word if c.isalnum())
+ if clean_word in thank_words and clean_word not in found_words:
+ found_words.append(clean_word)
+
+ # Проверяем фразы из двух слов
+ if i < len(words) - 1:
+ clean_next = ''.join(c for c in words[i+1] if c.isalnum())
+ two_word_phrase = clean_word + ' ' + clean_next
+ if two_word_phrase in thank_words and two_word_phrase not in found_words:
+ found_words.append(two_word_phrase)
+
+ return found_words
+
+def reload_thank_words():
+ """Перезагружает список благодарственных слов из файла"""
+ global _thank_words_cache
+ _thank_words_cache = None
+ return _load_thank_words()
\ No newline at end of file