Spaces:
Running
Running
File size: 4,759 Bytes
8dd998f 516b9df 8dd998f 6805cfc 8dd998f 516b9df 8dd998f 516b9df 64931bf 516b9df eb4d318 37f6fb1 eb4d318 64931bf 8dd998f 516b9df 8dd998f 516b9df 8dd998f 516b9df 8dd998f 516b9df 8dd998f 516b9df 8dd998f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
"""
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")
|