import asyncio import logging import os from logging.handlers import RotatingFileHandler from aiogram import Bot, Dispatcher, types from aiogram.filters import Command from aiogram.types import BotCommand from config import BOT_TOKEN, ALLOWED_CHAT_ID from prompts import SYSTEM_PROMPT from llm import ask_llm from chat_history import load_history, add_message from scheduler import BytikScheduler LOG_DIR = "logs" os.makedirs(LOG_DIR, exist_ok=True) logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ RotatingFileHandler( os.path.join(LOG_DIR, "bytik.log"), maxBytes=5*1024*1024, backupCount=3, encoding="utf-8", ), logging.StreamHandler(), ], ) logger = logging.getLogger(__name__) bot = Bot(token=BOT_TOKEN) dp = Dispatcher() scheduler = None def is_allowed_chat(chat_id: int) -> bool: return chat_id == ALLOWED_CHAT_ID @dp.message(Command("start")) async def cmd_start(message: types.Message): if not is_allowed_chat(message.chat.id): logger.warning(f"Попытка /start из неразрешённого чата: {message.chat.id}") return await message.answer("Привет, Егор! 👋 Я Байтик, твой робот-помощник! Спрашивай меня о чём угодно! 🤖") @dp.message(Command("clear")) async def cmd_clear(message: types.Message): if not is_allowed_chat(message.chat.id): return from chat_history import clear_history clear_history(message.from_user.id) await message.answer("История наших разговоров очищена! 🧹") @dp.message() async def handle_message(message: types.Message): if not message.text: return if not is_allowed_chat(message.chat.id): logger.debug(f"Игнорирую сообщение из чата {message.chat.id}") return user_message = message.text.strip() logger.info(f"Сообщение от user {message.from_user.id}: {user_message[:100]}") typing_msg = await message.answer("Думаю... 🤔") history = load_history(message.from_user.id) messages_for_llm = [{"role": "system", "content": SYSTEM_PROMPT}] + history messages_for_llm.append({"role": "user", "content": user_message}) add_message(message.from_user.id, "user", user_message) answer = await ask_llm(messages_for_llm) add_message(message.from_user.id, "assistant", answer) try: await typing_msg.edit_text(answer) except Exception: await message.answer(answer) @dp.my_chat_member() async def handle_bot_status_change(update: types.ChatMemberUpdated): chat_id = update.chat.id new_status = update.new_chat_member.status if new_status == "member" and chat_id != ALLOWED_CHAT_ID: logger.warning(f"Бот добавлен в неразрешённый чат {chat_id}. Покидаю.") await bot.leave_chat(chat_id) elif new_status == "left": logger.info(f"Бот удалён из чата {chat_id}") async def set_commands(): commands = [ BotCommand(command="start", description="Начать общение"), BotCommand(command="clear", description="Очистить историю диалога"), ] await bot.set_my_commands(commands) async def main(): global scheduler await set_commands() scheduler = BytikScheduler(bot) scheduler.start() logger.info(f"Бот запущен. Whitelist chat_id: {ALLOWED_CHAT_ID}") await dp.start_polling(bot) async def on_shutdown(): if scheduler: scheduler.shutdown() await bot.session.close() if __name__ == "__main__": try: asyncio.run(main()) except (KeyboardInterrupt, SystemExit): logger.info("Бот остановлен")