"""Telegram bot entrypoint powered by aiogram 3.x.""" from __future__ import annotations import asyncio import logging import sys from contextlib import suppress from aiohttp import web from aiogram import Bot, Dispatcher from aiogram.client.default import DefaultBotProperties from aiogram.enums import ParseMode from pydantic import ValidationError from app.config import Settings, format_config_error, get_settings from app.handlers import router from app.llm import LLMChat from app.memory import SQLiteMemory async def health(request: web.Request) -> web.Response: """HTTP health endpoint for platform checks.""" settings: Settings = request.app["settings"] return web.json_response( { "ok": True, "service": "telegram-bot", "framework": "aiogram-3.x", "designapi_base_url": settings.designapi_base_url, "designapi_model": settings.designapi_model, } ) async def start_health_server(settings: Settings) -> web.AppRunner | None: """Run a lightweight aiohttp health server. In this shared pod port 8080 can already be occupied by the platform preview sidecar. Health is useful but must not prevent Telegram polling from starting, so an address-in-use error is logged and treated as non-fatal. """ app = web.Application() app["settings"] = settings app.router.add_get("/health", health) app.router.add_get("/", health) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, host=settings.health_host, port=settings.health_port) try: await site.start() except OSError as exc: await runner.cleanup() if exc.errno == 98: logging.warning( "Health server port %s:%s is already in use; continuing without HTTP health endpoint", settings.health_host, settings.health_port, ) return None raise logging.info("Health server started on %s:%s", settings.health_host, settings.health_port) return runner async def main() -> None: """Start optional health server and Telegram long polling.""" logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s [%(name)s] %(message)s", ) try: settings = get_settings() except ValidationError as exc: logging.error(format_config_error(exc)) raise SystemExit(2) from exc health_runner = await start_health_server(settings) bot = Bot( token=settings.bot_token, default=DefaultBotProperties(parse_mode=ParseMode.HTML), ) memory = SQLiteMemory(settings.db_path) llm_chat = LLMChat(settings, memory) dp = Dispatcher(settings=settings, memory=memory, llm_chat=llm_chat) dp.include_router(router) logging.info("Starting aiogram polling") try: await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()) finally: if health_runner is not None: with suppress(Exception): await health_runner.cleanup() await bot.session.close() if __name__ == "__main__": try: asyncio.run(main()) except (KeyboardInterrupt, SystemExit) as exc: code = exc.code if isinstance(exc, SystemExit) and isinstance(exc.code, int) else 0 sys.exit(code)