Spaces:
Paused
Paused
| """ | |
| 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 ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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, | |
| }, | |
| ) | |
| 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") | |