Render Bot correct image
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ekaropolus 2025-09-16 21:03:06 -06:00
parent 4394fa1b7b
commit f656f04a5f
2 changed files with 52 additions and 27 deletions

View File

@ -14,9 +14,9 @@ def _build_keyboard(buttons: Optional[List[dict]]) -> Optional[InlineKeyboardMar
Build an InlineKeyboardMarkup from a list of button specs.
Supported kinds:
- open_url: {"label":"...", "kind":"open_url", "url":"https://..."}
- callback_api:{"label":"...", "kind":"callback_api", "action":"rerun",
"params": {...}, "state_token":"..."}
- open_url: {"label":"...", "kind":"open_url", "url":"https://..."}
- callback_api: {"label":"...", "kind":"callback_api", "action":"rerun",
"params": {...}, "state_token":"..."}
"""
if not buttons:
return None
@ -45,7 +45,7 @@ def _build_keyboard(buttons: Optional[List[dict]]) -> Optional[InlineKeyboardMar
payload = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
except Exception:
payload = '{"e":"bad"}'
# Telegram doc: 164 bytes recommended
# Telegram recommends <=64 bytes for callback_data
if len(payload.encode("utf-8")) > 64:
logger.warning("renderer: callback_data too long (%sB); trimming",
len(payload.encode("utf-8")))
@ -103,9 +103,34 @@ async def render_spec(*, bot: Bot, chat_id: int, spec: Dict) -> List[Message]:
if not (file_id or media_url):
logger.warning("renderer: photo without file_id/media_url; skipping")
continue
msg = await bot.send_photo(chat_id=chat_id, photo=file_id or media_url,
caption=caption, reply_markup=kb)
sent.append(msg)
try:
msg = await bot.send_photo(chat_id=chat_id,
photo=file_id or media_url,
caption=caption,
reply_markup=kb)
sent.append(msg)
except TelegramError as te:
# Typical: "BadRequest: Wrong type of the web page content"
logger.exception("renderer.photo_error send_photo err=%s url=%s", te, media_url)
# Fallback 1: try as document (Telegram is more permissive)
try:
msg = await bot.send_document(chat_id=chat_id,
document=file_id or media_url,
caption=caption,
reply_markup=kb)
sent.append(msg)
except TelegramError as te2:
logger.exception("renderer.photo_fallback_doc_error err=%s url=%s", te2, media_url)
# Fallback 2: plain text with link
fallback_text = (caption + "\n" if caption else "") + (media_url or "")
if fallback_text.strip():
try:
msg = await bot.send_message(chat_id=chat_id,
text=fallback_text,
reply_markup=kb)
sent.append(msg)
except TelegramError as te3:
logger.exception("renderer.photo_fallback_text_error err=%s url=%s", te3, media_url)
elif mtype == "document":
file_id = m.get("file_id")

View File

@ -8,6 +8,7 @@ import openai
from telegram import Update, Bot
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.core.cache import cache
from asgiref.sync import sync_to_async
from .models import TelegramBot
@ -18,15 +19,11 @@ from .handlers import (
next_route, complete_stop, missed_stop, city_eco_score,
available_jobs, accept_job, next_pickup, complete_pickup, private_eco_score
)
from .renderer import render_spec
logger = logging.getLogger(__name__)
openai.api_key = os.getenv("OPENAI_API_KEY")
# at top with other imports
from .renderer import render_spec
# top imports
from django.core.cache import cache
# ---------------------------
# Canonical req.v1 builder
@ -152,6 +149,7 @@ def build_req_v1(update: Dict[str, Any], bot_name: str) -> Dict[str, Any]:
}
return env
# ---------------------------
# Existing helper flows
# ---------------------------
@ -231,6 +229,7 @@ async def transcribe_with_whisper(update: Update, bot: Bot) -> Optional[str]:
)
return transcript_str.strip() if transcript_str else None
# ---------------------------
# Webhook
# ---------------------------
@ -259,28 +258,30 @@ async def telegram_webhook(request, bot_name: str):
payload = json.loads(request.body.decode("utf-8") or "{}")
except json.JSONDecodeError:
return JsonResponse({"ok": False, "error": "invalid_json"}, status=400)
# ----- Idempotency / retry guard (drops duplicates for ~90s) -----
# ----- Idempotency / retry guard (drops duplicates for ~90s) -----
upd_id = payload.get("update_id")
# Fallback if no update_id: use message_id + user_id
fallback_msg = (payload.get("message") or {}).get("message_id")
fallback_user = ((payload.get("message") or {}).get("from") or {}).get("id")
cbq = payload.get("callback_query") or {}
cbq_id = cbq.get("id")
msg = payload.get("message") or {}
fallback_msg_id = msg.get("message_id")
fallback_user = (msg.get("from") or {}).get("id")
dedupe_key = None
if upd_id is not None:
dedupe_key = f"tg:update:{upd_id}"
elif fallback_msg and fallback_user:
dedupe_key = f"tg:msg:{fallback_msg}:{fallback_user}"
elif cbq_id:
dedupe_key = f"tg:cbq:{cbq_id}"
elif fallback_msg_id and fallback_user:
dedupe_key = f"tg:msg:{fallback_msg_id}:{fallback_user}"
if dedupe_key:
# cache.add returns True if the key did not exist (first time), False otherwise
if not cache.add(dedupe_key, "1", timeout=90):
logger.info("tg.idempotent.skip key=%s", dedupe_key)
return JsonResponse({"status": "duplicate_skipped"})
# -----------------------------------------------------------------
# Build canonical req.v1 (LOG ONLY for now)
# Build canonical req.v1 (log only for now)
try:
canon = build_req_v1(payload, bot_name)
logger.info("tg.canonical env=%s", json.dumps(canon, ensure_ascii=False))
@ -290,8 +291,7 @@ async def telegram_webhook(request, bot_name: str):
# Convert to telegram.Update
update = Update.de_json(payload, Bot(token=bot_instance.token))
# --- TEMP: demo renderer (safe to delete later) ----------------------
# If user sends "/_render_demo", send a text + photo + buttons
# --- TEMP demo: send a text + photo + buttons ----------------------
if update.message and (update.message.text or "").strip() == "/_render_demo":
bot = Bot(token=bot_instance.token)
spec = {
@ -300,7 +300,8 @@ async def telegram_webhook(request, bot_name: str):
{"type": "text", "text": "Demo: render_spec text ✅"},
{
"type": "photo",
"media_url": "https://upload.wikimedia.org/wikipedia/commons/5/5f/Alameda_Central_CDMX.jpg",
# Use a known-good image URL (or host your own under /static/)
"media_url": "https://picsum.photos/seed/polisplexity/800/480.jpg",
"caption": "Demo: render_spec photo ✅"
}
],
@ -312,8 +313,7 @@ async def telegram_webhook(request, bot_name: str):
}
await render_spec(bot=bot, chat_id=update.effective_chat.id, spec=spec)
return JsonResponse({"status": "ok", "render_demo": True})
# --------------------------------------------------------------------
# -------------------------------------------------------------------
if not update.message:
# No message (e.g., callback handled elsewhere in legacy); ack anyway