Render Bot correct image
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
4394fa1b7b
commit
f656f04a5f
@ -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: 1–64 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")
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user