📄 polling.py 2,526 bytes Apr 30, 2026 📋 Raw

"""True long-polling for Telegram updates.

Uses timeout parameter to keep connection hung open.
Inbound messages process instantly, no 30s latency.

Uses TELEGRAM_BOT_TOKEN from staging config — which is the test bot
in staging environment (8469114191:...), NOT production.
"""
import asyncio
import logging

import httpx

from icarus.core.config.staging import TELEGRAM_BOT_TOKEN
from icarus.core.telegram.handler import process_update

BASE_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"
OFFSET = 0

async def polling_loop():
"""Long-polling loop running alongside FastAPI."""
global OFFSET

# Clear any existing webhook first
async with httpx.AsyncClient() as client:
    try:
        resp = await client.post(f"{BASE_URL}/deleteWebhook")
        result = resp.json()
        if result.get("ok"):
            logging.info("[polling] Webhook cleared, starting long-polling...")
        else:
            logging.warning("[polling] deleteWebhook: %s", result)
    except Exception as e:
        logging.warning("[polling] Failed to clear webhook: %s", e)

logging.info("[polling] Long-polling loop started")

while True:
    try:
        async with httpx.AsyncClient(timeout=65.0) as client:
            # timeout=60 keeps connection hung open (+5s buffer)
            resp = await client.post(
                f"{BASE_URL}/getUpdates",
                json={"offset": OFFSET, "limit": 100, "timeout": 60}
            )
            resp.raise_for_status()
            data = resp.json()

            if data.get("ok") and data.get("result"):
                for update in data["result"]:
                    OFFSET = update["update_id"] + 1
                    logging.info("[polling] Processing update %s", update["update_id"])
                    try:
                        await process_update(update)
                    except Exception as e:
                        logging.exception("[polling] Error processing update: %s", e)

    except asyncio.CancelledError:
        logging.info("[polling] Loop cancelled, exiting")
        break
    except Exception as e:
        logging.error("[polling] Polling error: %s", e)
        await asyncio.sleep(5)  # Backoff on error

async def start_polling():
"""Entry point to start background task."""
logging.info("[polling] Starting background polling task")
asyncio.create_task(polling_loop())