"""FastAPI app hosting the Telegram webhook on HuggingFace Spaces. Startup sequence: 1. The platform polling loop starts in a background thread (PlatformController). 2. The async Telegram client connects (through the Google Apps Script proxy when GOOGLE_APPS_SCRIPT_URL is set — required on HF Spaces). 3. Telegram pushes updates to POST /webhook/{WEBHOOK_SECRET}; register the URL once via POST /set_webhook after deploying. """ import asyncio import logging from contextlib import asynccontextmanager import uvicorn from fastapi import FastAPI, HTTPException, Request from .bot_service import TelegramBotService from .config import BotConfig from .controller import PlatformController from .telegram_client import TelegramClient from .tg_models import TelegramUpdate logger = logging.getLogger(__name__) controller = PlatformController() client = TelegramClient() bot_service = TelegramBotService(controller, client) @asynccontextmanager async def lifespan(app: FastAPI): logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s", ) BotConfig.validate() controller.start() await client.initialize() logger.info("telegram bot server started") yield controller.stop() await client.aclose() logger.info("telegram bot server stopped") app = FastAPI( title="Market Analyzing Platform Bot", description="Telegram control interface for the market-wide risk radar", version="0.1.0", lifespan=lifespan, ) @app.get("/") async def root(): """Health endpoint HuggingFace Spaces pings to keep the Space alive.""" return { "status": "running", "platform": controller.status(), "bot_configured": bool(BotConfig.BOT_TOKEN), "proxy_configured": bool(BotConfig.PROXY_URL), } @app.get("/health") async def health(): return {"status": "healthy", "platform_state": controller.status()["state"]} @app.post(f"/webhook/{BotConfig.WEBHOOK_SECRET}") async def webhook(request: Request): try: json_data = await request.json() update = TelegramUpdate(**json_data) except Exception as exc: logger.error("invalid webhook payload: %s", exc) raise HTTPException(status_code=400, detail="Invalid update format") # Ack Telegram immediately; process in the background so slow handlers # don't trigger webhook retries asyncio.create_task(bot_service.process_update(update)) return {"ok": True} @app.post("/set_webhook") async def set_webhook(url: str = ""): """Register the webhook with Telegram (via the proxy when configured). Uses https://{SPACE_HOST}/webhook/{WEBHOOK_SECRET} unless `url` is given. """ target = url or BotConfig.webhook_url() if not target: raise HTTPException( status_code=400, detail="No url given and SPACE_HOST is not set; pass ?url=https://.../webhook/", ) result = await client.call("setWebhook", url=target) return {"webhook_url": target, "telegram_response": result} @app.get("/webhook_info") async def webhook_info(): result = await client.call("getWebhookInfo") return {"expected_url": BotConfig.webhook_url() or "", "telegram_response": result} def main() -> None: uvicorn.run(app, host="0.0.0.0", port=BotConfig.PORT, log_level="info") if __name__ == "__main__": main()