""" Reusable Telegram‑bot preview router. Exposes: • GET / – HTML preview card • GET /avatar.jpg – bot avatar (streamed, in‑memory) To use: `from telegram_preview import router` and include it in your FastAPI app. """ import io import os import requests from pathlib import Path from typing import Optional from fastapi import APIRouter, Response, Request from fastapi.responses import HTMLResponse, StreamingResponse from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles # NEW # ─── Locate templates folder absolute path ──────────────────────────────────── BASE_DIR = Path(__file__).resolve().parent templates = Jinja2Templates(directory=str(BASE_DIR / "templates")) def include_in_app(app): """ Helper so the main application can pull in both router *and* static mount. """ app.include_router(router) app.mount( "/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static", ) # ─── Environment ─────────────────────────────────────────────────────────────── TOKEN = os.getenv("BOT_TOKEN") FALLBACK_IMG = "https://telegram.org/img/t_logo.png" if not TOKEN: raise RuntimeError("BOT_TOKEN environment variable not set") API_ROOT = f"https://api.telegram.org/bot{TOKEN}" router = APIRouter() # ─── Helpers ─────────────────────────────────────────────────────────────────── def _get_description() -> str: """Try short → full → empty string.""" try: r = requests.get(f"{API_ROOT}/getMyShortDescription", timeout=5).json() if (desc := r["result"].get("short_description")): return desc except Exception: pass try: r = requests.get(f"{API_ROOT}/getMyDescription", timeout=5).json() return r["result"].get("description", "") except Exception: return "" def _fetch_avatar_bytes() -> Optional[bytes]: """Return the newest avatar as bytes, or None if missing/error.""" try: me = requests.get(f"{API_ROOT}/getMe", timeout=5).json() user_id = me["result"]["id"] photos = requests.get( f"{API_ROOT}/getUserProfilePhotos", params={"user_id": user_id, "limit": 1}, timeout=5, ).json() if photos["result"]["total_count"] == 0: return None file_id = photos["result"]["photos"][0][-1]["file_id"] file_obj = requests.get( f"{API_ROOT}/getFile", params={"file_id": file_id}, timeout=5 ).json() file_path = file_obj["result"]["file_path"] resp = requests.get( f"https://api.telegram.org/file/bot{TOKEN}/{file_path}", timeout=10 ) if resp.status_code == 200: return resp.content except Exception as exc: print("Avatar fetch error:", exc) return None def _get_bot_identity() -> dict: """Return {'first_name': ..., 'username': ...} from getMe""" try: r = requests.get(f"{API_ROOT}/getMe", timeout=5).json() return { "first_name": r["result"].get("first_name", "Telegram Bot"), "username": r["result"].get("username", "unknown_bot"), } except Exception: return {"first_name": "Telegram Bot", "username": "unknown_bot"} # ─── Routes ──────────────────────────────────────────────────────────────────── @router.get("/", include_in_schema=False, response_class=HTMLResponse) def bot_preview(request: Request): description = _get_description() identity = _get_bot_identity() return templates.TemplateResponse( "preview.html", { "request": request, "title": f"@{identity['username']}", "username": identity["username"], "first_name": identity["first_name"], "description": description, }, ) @router.get("/avatar.jpg", include_in_schema=False) def serve_avatar(): """Streams avatar JPEG or Telegram logo (PNG) as fallback.""" data = _fetch_avatar_bytes() if data: return StreamingResponse(io.BytesIO(data), media_type="image/jpeg") fallback = requests.get(FALLBACK_IMG, timeout=10) return Response(content=fallback.content, media_type="image/png")