"""Single entry point: build Application, run HTTP server + polling. Migrations run in Docker entrypoint.""" import asyncio import json import logging import threading import urllib.request import config from telegram.ext import ApplicationBuilder from handlers import register_handlers logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO, ) logger = logging.getLogger(__name__) def _set_default_menu_button_webapp() -> None: """Set the bot's default menu button to Web App so Telegram sends tgWebAppData when users open the app from the menu.""" if not (config.MINI_APP_BASE_URL and config.BOT_TOKEN): return menu_url = (config.MINI_APP_BASE_URL.rstrip("/") + "/app/").strip() if not menu_url.startswith("https://"): return payload = { "menu_button": { "type": "web_app", "text": "Календарь", "web_app": {"url": menu_url}, } } req = urllib.request.Request( f"https://api.telegram.org/bot{config.BOT_TOKEN}/setChatMenuButton", data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=10) as resp: if resp.status == 200: logger.info("Default menu button set to Web App: %s", menu_url) else: logger.warning("setChatMenuButton returned %s", resp.status) except Exception as e: logger.warning("Could not set menu button: %s", e) def _run_uvicorn(web_app, port: int) -> None: """Run uvicorn in a dedicated thread with its own event loop.""" import uvicorn loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) server = uvicorn.Server( uvicorn.Config(web_app, host="0.0.0.0", port=port, log_level="info"), ) loop.run_until_complete(server.serve()) def main() -> None: # Menu button (Календарь) and inline Calendar button are disabled; users open the app by link if needed. # _set_default_menu_button_webapp() app = ApplicationBuilder().token(config.BOT_TOKEN).build() register_handlers(app) from api.app import app as web_app t = threading.Thread( target=_run_uvicorn, args=(web_app, config.HTTP_PORT), daemon=True, ) t.start() logger.info("Bot starting (polling)... HTTP API on port %s", config.HTTP_PORT) app.run_polling(allowed_updates=["message"]) if __name__ == "__main__": main()