forked from Muzifs/LGBot
215 lines
9.4 KiB
Python
215 lines
9.4 KiB
Python
from telebot.async_telebot import AsyncTeleBot
|
||
from telebot.asyncio_handler_backends import BaseMiddleware
|
||
import asyncio
|
||
|
||
import os
|
||
import sys
|
||
import importlib
|
||
from dotenv import load_dotenv
|
||
|
||
import logging
|
||
from logger import setup_logging
|
||
|
||
from database import db
|
||
|
||
from action_reporter import init_action_reporter
|
||
|
||
from config import MODULES_DIR
|
||
|
||
# Загружаем токен бота из .env
|
||
load_dotenv()
|
||
|
||
# Валидация переменных окружения
|
||
def validate_env_vars():
|
||
"""Проверяет наличие всех необходимых переменных окружения"""
|
||
required_vars = {
|
||
"BOT_TOKEN": "Токен бота",
|
||
"ADMIN_CHAT_ID": "ID админ-чата",
|
||
"LOG_THREAD_ID": "ID топика для логов"
|
||
}
|
||
|
||
missing_vars = []
|
||
for var_name, description in required_vars.items():
|
||
value = os.getenv(var_name)
|
||
if not value or value.strip() == "" or value == "...":
|
||
missing_vars.append(f"{var_name} ({description})")
|
||
|
||
if missing_vars:
|
||
print("\n❌ ОШИБКА: Не заполнены необходимые переменные окружения в файле .env:")
|
||
for var in missing_vars:
|
||
print(f" • {var}")
|
||
print("\nПожалуйста, заполните файл .env на основе .env.example")
|
||
sys.exit(1)
|
||
|
||
# Проверяем переменные окружения
|
||
validate_env_vars()
|
||
|
||
bot = AsyncTeleBot(os.getenv("BOT_TOKEN"), parse_mode="html")
|
||
|
||
# Загружаем ID админ-чата из .env и инициализируемся для логов в чат
|
||
init_action_reporter(bot, int(os.getenv("ADMIN_CHAT_ID")), int(os.getenv("LOG_THREAD_ID")))
|
||
|
||
# Получаем логгер для текущего модуля
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Middleware для автоматического обновления информации о пользователях в базе данных
|
||
# И проверки на нецензурную лексику (выполняется ДО всех обработчиков)
|
||
class UserUpdateMiddleware(BaseMiddleware):
|
||
def __init__(self, db, bot):
|
||
super().__init__()
|
||
# message - все обычные сообщения
|
||
# chat_member - события изменения статуса участников чата
|
||
self.update_types = ['message', 'chat_member']
|
||
self.db = db
|
||
self.bot = bot
|
||
|
||
# Обработчик, вызываемый ДО обработки сообщения основными хэндлерами
|
||
async def pre_process(self, message, data):
|
||
# Логируем ВСЕ входящие сообщения для отладки
|
||
logger.info(f"[MIDDLEWARE] Получено сообщение от {message.from_user.id}, тип: {message.content_type}, текст: {message.text if hasattr(message, 'text') else 'N/A'}")
|
||
|
||
# Обработка пользователей, отправившие сообщение
|
||
if message.content_type == 'text':
|
||
self.db.add_or_update_user(
|
||
user_id=message.from_user.id,
|
||
nickname=message.from_user.first_name,
|
||
tag=message.from_user.username
|
||
)
|
||
|
||
# ВАЖНО: Проверяем на мат ДО передачи другим обработчикам
|
||
# Это позволяет auto_mute работать независимо от karma_tracker
|
||
await self._check_profanity(message)
|
||
|
||
# Обработка новых участников группы
|
||
elif 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,
|
||
nickname=new_member.first_name,
|
||
tag=new_member.username
|
||
)
|
||
return data
|
||
|
||
# Проверка на нецензурную лексику (вызывается в middleware)
|
||
async def _check_profanity(self, message):
|
||
"""Проверяет сообщение на мат и применяет мут если нужно"""
|
||
from bad_words import contains_bad_word
|
||
|
||
# Только для групповых чатов и не команды
|
||
if message.chat.type not in ['group', 'supergroup']:
|
||
return
|
||
|
||
if not message.text or message.text.startswith('/'):
|
||
return
|
||
|
||
# Проверяем наличие мата
|
||
if not contains_bad_word(message.text):
|
||
return
|
||
|
||
# Импортируем функцию проверки из auto_mute (если модуль загружен)
|
||
try:
|
||
from modules.auto_mute import check_message_for_profanity
|
||
await check_message_for_profanity(self.bot, message)
|
||
except ImportError:
|
||
logger.warning("Модуль auto_mute не загружен, пропускаем проверку мата")
|
||
|
||
# Обработчик, вызываемый ПОСЛЕ обработки сообщения основными хэндлерами
|
||
async def post_process(self, message, data, exception):
|
||
pass
|
||
|
||
# Регистрируем middleware
|
||
bot.setup_middleware(UserUpdateMiddleware(db, bot))
|
||
|
||
# Загружает все модули из директории /modules
|
||
async def load_modules():
|
||
# Переменная для подсчёта модулей
|
||
loaded_count = 0
|
||
|
||
# Импортируем относительный путь проекта
|
||
modules_path = os.path.join(os.path.dirname(__file__), MODULES_DIR)
|
||
|
||
for filename in os.listdir(modules_path):
|
||
|
||
# Если файл содержит в конце .py (кроме __init__.py)
|
||
if filename.endswith(".py") and filename != "__init__.py":
|
||
|
||
# Убираем расширение .py
|
||
module_name = filename[:-3]
|
||
|
||
try:
|
||
|
||
# Импортируем модуль (modules.start)
|
||
module = importlib.import_module(f"{MODULES_DIR}.{module_name}")
|
||
|
||
# Если присутствует register_handlers
|
||
if hasattr(module, "register_handlers"):
|
||
|
||
module.register_handlers(bot)
|
||
loaded_count += 1
|
||
logger.info(f"Модуль {module_name} успешно загружен.")
|
||
|
||
# Если нет register_handlers
|
||
else:
|
||
|
||
# Записываем действие в логи
|
||
logger.warning(f"Модуль {module_name} не содержит функцию register_handlers.")
|
||
|
||
except Exception as e:
|
||
|
||
# Записываем ошибку в логи
|
||
logger.error(f"Ошибка при загрузке модуля {module_name}: {str(e)}")
|
||
|
||
# Записываем отчет о модулях в логи
|
||
logger.info(f"Загружено модулей: {loaded_count} шт. Бот запущен.")
|
||
|
||
# Устанавливаем меню команд бота
|
||
async def setup_bot_commands():
|
||
from telebot.types import BotCommand
|
||
|
||
commands = [
|
||
BotCommand("start", "Начало работы с ботом"),
|
||
BotCommand("help", "Справка по всем командам"),
|
||
BotCommand("log", "Инструкция по созданию лога ошибки"),
|
||
BotCommand("warn", "Выдать предупреждение. Использование: /warn help"),
|
||
BotCommand("ban", "Забанить пользователя. Использование: /ban help"),
|
||
BotCommand("unban", "Разбанить пользователя. Использование: /unban help"),
|
||
BotCommand("mute", "Замутить пользователя. Использование: /mute help"),
|
||
BotCommand("unmute", "Размутить пользователя. Использование: /unmute help"),
|
||
BotCommand("badwords", "Управление списком бранных слов. /badwords help"),
|
||
BotCommand("reset_violations", "Сбросить счётчик нарушений пользователя"),
|
||
BotCommand("botdata", "Получить данные бота (только для админов)"),
|
||
BotCommand("karma", "Просмотр кармы пользователя"),
|
||
BotCommand("top", "Топ-10 пользователей по карме"),
|
||
]
|
||
|
||
await bot.set_my_commands(commands)
|
||
logger.info("Команды бота успешно установлены.")
|
||
|
||
async def main():
|
||
# Инициализация логирования (должна быть первой)
|
||
setup_logging()
|
||
|
||
# Очищаем терминал
|
||
os.system('clear')
|
||
|
||
try:
|
||
|
||
# Проверяем и загружаем модули
|
||
await load_modules()
|
||
|
||
# Устанавливаем команды бота
|
||
await setup_bot_commands()
|
||
|
||
# Запускаем бота
|
||
await bot.infinity_polling()
|
||
|
||
except Exception as e:
|
||
|
||
# Записываем критическую ошибку в логи
|
||
logger.critical(f"Критическая ошибка: {str(e)}")
|
||
|
||
# Завершаем скрипт с критической ошибкой
|
||
sys.exit(1)
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main()) |