/
Shtirlitc
/
TickBot
Обзор
Документация
Войти
/
Shtirlitc
/
TickBot
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
main.py
269 строк
12 KB
Shtirlyts
Initial commit: TickBot attendance bot with stale-status fixes
31 июл 2026, 23:40
31 июл 2026, 23:40
30e955c
Код
Авторство
О чём код?
import asyncio from asyncio import subprocess import logging import os import sys import socket from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.client.session.aiohttp import AiohttpSession from aiogram.enums import ParseMode from config import settings, get_proxy_url, LOGS_DIR, ADMIN_ID, GROUPS from handlers import router as main_handlers_router from handlers.commands import init_commands from handlers.commands import router as commands_router from handlers.attendance import init_attendance, log_user_action from handlers.admin import init_admin from handlers.notifications import init_notifications from handlers.middlewares import setup_middlewares from database import init_db, migrate_all_students_from_gs from database.repositories import ( UserRepository, StudentRepository, AttendanceRepository, BlacklistRepository, WardenRepository, NotificationRepository ) from services.google_sheets import get_gs_client from services.notifications import send_notification_reminders from log_server.log_server import LogWebSocketServer # =========================== # PROXY CHECK FUNCTION # =========================== async def check_proxy_availability(host="127.0.0.1", port=1443, timeout=5): """Проверяет доступность прокси-сервера""" try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result = sock.connect_ex((host, port)) sock.close() return result == 0 except: return False # =========================== # LOGGING SETUP # =========================== logging.basicConfig( level=logging.INFO, format='[%(asctime)s] %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', handlers=[ logging.FileHandler("logs/bot.log", encoding="utf-8"), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) # Отключаем шумные логи logging.getLogger("aiohttp").setLevel(logging.WARNING) logging.getLogger("aiogram").setLevel(logging.WARNING) logging.getLogger("gspread").setLevel(logging.WARNING) # =========================== # BOT INITIALIZATION # =========================== async def create_bot() -> Bot: """Создает инстанс бота с нужным прокси""" proxy_url = get_proxy_url() # Проверяем доступность прокси, если он используется if proxy_url and "127.0.0.1:1443" in proxy_url: if not await check_proxy_availability(): logger.warning("Lokalnyi proksi nedostupen! Zapustite: docker-compose up -d") log_user_action( user_id=ADMIN_ID, username="system", action="PROXY_UNAVAILABLE", details="Lokalnyi proksi nedostupen", level="warning", log_type="system" ) # Если proxy_url есть (локальный или внешний), создаем сессию для aiohttp session = AiohttpSession(proxy=proxy_url) if proxy_url else None if proxy_url: log_user_action( user_id=ADMIN_ID, username="system", action="PROXY_USED", details=f"Внешний прокси: {proxy_url}", level="info", log_type="system" ) else: log_user_action( user_id=ADMIN_ID, username="system", action="PROXY_DIRECT", details="Бот работает напрямую", level="info", log_type="system" ) return Bot( token=settings.bot_token, session=session, default=DefaultBotProperties(parse_mode=ParseMode.HTML) ) # ============================================================ # ФОНОВАЯ ЗАДАЧА СИНХРОНИЗАЦИИ (НОВОЕ!) # ============================================================ async def sync_worker_task(attendance_repo, student_repo, gs_client, interval_seconds=45): """Фоновый процесс, работающий каждые N секунд""" while True: await asyncio.sleep(interval_seconds) try: await gs_client.sync_sqlite_to_gs(attendance_repo, student_repo) except Exception as e: logger.error(f"❌ Критическая ошибка в цикле воркера: {e}") # ============================================================ # MAIN # ============================================================ async def main(): """Точка входа""" log_user_action(user_id=ADMIN_ID, username="system", action="BOT_START", details="Запуск бота", level="info", log_type="system") # Проверяем конфигурацию прокси proxy_url = get_proxy_url() if proxy_url and "127.0.0.1:1443" in proxy_url: logger.info("Ispolzuetsya lokalnyi TeleProxy (SOCKS5://127.0.0.1:1443)") logger.info("Ubedites, chto konteiner zapushen: docker-compose up -d") # 1. Инициализируем БД await init_db() logger.debug("✅ База данных SQLite инициализирована") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_DB", details="SQLite инициализирована", level="info", log_type="system") # 1.1. Запускаем сервер логов log_server = LogWebSocketServer() asyncio.create_task(log_server.start()) logger.debug("✅ Сервер логов запущен") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_LOG_SERVER", details="Сервер логов запущен", level="info", log_type="system") # 2. Создаём репозитории user_repo = UserRepository() student_repo = StudentRepository() attendance_repo = AttendanceRepository() blacklist_repo = BlacklistRepository() warden_repo = WardenRepository() notification_repo = NotificationRepository() # 3. Создаём Google Sheets клиент gs_client = get_gs_client() logger.debug("✅ Google Sheets клиент инициализирован") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_GS", details="GS клиент создан", level="info", log_type="system") # 4. 🔄 МИГРАЦИЯ СТУДЕНТОВ ИЗ GS В SQLITE try: logger.debug("🔄 Начинаем миграцию студентов из Google Sheets...") log_user_action(user_id=ADMIN_ID, username="system", action="MIGRATION_START", details="Начало миграции из GS", level="info", log_type="system") migrated_count = await migrate_all_students_from_gs(gs_client) if migrated_count > 0: logger.debug(f"✅ Миграция завершена: {migrated_count} студентов") log_user_action(user_id=ADMIN_ID, username="system", action="MIGRATION_SKIP", details="Студенты уже в БД", level="info", log_type="system") else: logger.debug("ℹ️ Миграция не потребовалась (студенты уже в БД)") log_user_action( user_id=ADMIN_ID, username="system", action="MIGRATION_SKIP", details="Студенты уже в БД", level="info", log_type="system" ) except Exception as e: logger.error(f"❌ Ошибка миграции: {e}") log_user_action(user_id=ADMIN_ID, username="system", action="MIGRATION_ERROR", details=str(e), level="error", log_type="system") # 5. Инициализируем обработчики посещаемости init_attendance( gs_client, user_repo, student_repo, attendance_repo, blacklist_repo, warden_repo, notification_repo ) logger.debug("✅ Обработчики посещаемости инициализированы") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_ATTENDANCE", details="Обработчики посещаемости инициализированы", level="info", log_type="system") # 5.1. Инициализируем административные обработчики init_admin( gs_client, user_repo, student_repo, attendance_repo, blacklist_repo, warden_repo, notification_repo ) logger.debug("✅ Административные обработчики инициализированы") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_ADMIN", details="Административные обработчики инициализированы", level="info", log_type="system") # 5.2. Инициализируем обработчики уведомлений init_notifications(notification_repo) logger.debug("✅ Обработчики уведомлений инициализированы") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_NOTIFICATIONS", details="Обработчики уведомлений инициализированы", level="info", log_type="system") # 6. Создаём бота и диспетчер bot = await create_bot() dp = Dispatcher() # 7. Инициализируем commands.py init_commands(gs_client, user_repo, student_repo, warden_repo, bot) logger.debug("✅ Commands инициализированы") log_user_action(user_id=ADMIN_ID, username="system", action="INIT_COMMANDS", details="Commands инициализированы", level="info", log_type="system") # 8. Регистрируем middleware setup_middlewares(dp, blacklist_repo, user_repo, student_repo) # 9. Регистрируем роутеры dp.include_router(main_handlers_router) # 10. 🔄 ЗАПУСКАЕМ ФОНОВУЮ ЗАДАЧУ СИНХРОНИЗАЦИИ asyncio.create_task(sync_worker_task(attendance_repo, student_repo, gs_client, 45)) logger.debug("⏰ Быстрый фоновый воркер синхронизации запущен (45 сек)") log_user_action(user_id=ADMIN_ID, username="system", action="WORKER_STARTED", details="Синхронизация каждые 45 сек", level="info", log_type="system") # 11. Запуск фоновой задачи уведомлений asyncio.create_task(send_notification_reminders(bot, notification_repo, student_repo)) # 12. Запускаем polling logger.debug("🤖 Бот запущен в режиме polling...") log_user_action(user_id=ADMIN_ID, username="system", action="BOT_RUNNING", details="Polling запущен", level="info", log_type="system") logger.debug(f"👤 Admin ID: {ADMIN_ID}") log_user_action(user_id=ADMIN_ID, username="system", action="ADMIN_INFO", details=f"Admin ID: {ADMIN_ID}", level="info", log_type="system") logger.debug(f"📚 Групп: {len(GROUPS)}") log_user_action(user_id=ADMIN_ID, username="system", action="GROUP_COUNT", details=f"Количество групп: {len(GROUPS)}", level="info", log_type="system") await dp.start_polling(bot, allowed_updates=["message", "callback_query"]) if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: logger.info("👋 Бот остановлен пользователем") log_user_action( user_id=ADMIN_ID, username="system", action="BOT_STOPPED", details="Остановлен вручную", level="info", log_type="system" ) except Exception as e: error_msg = str(e) logger.critical(f"💥 Критическая ошибка: {error_msg}") try: log_user_action( user_id=ADMIN_ID, username="system", action="CRITICAL_ERROR", details=error_msg, level="critical", log_type="system" ) except: pass