Esta es una ampliación opcional de mensajería. No necesitas Telegram para aprender el núcleo ni para completar los talleres locales. El adaptador incluido lee updates, filtra un chat privado y prepara una intención; no convierte el bot en un servicio de producción.
Objetivo: leer el adaptador de Telegram, practicar la inspección sin envío y separar polling, allowlist y envío autorizado.
Requisitos y seguridad
Necesitas el kit extraído, Python 3.10+, una cuenta de Telegram y un chat privado de prueba si decides continuar. Los pasos reales requieren red y un token propio; no están comprobados por el curso. No uses chats de clientes.
En el flujo de Telegram:
- Polling pregunta a
getUpdatespor mensajes pendientes. - Allowlist compara
TELEGRAM_ALLOWED_CHAT_ID; chats no autorizados se ignoran. - Dry-run (
TELEGRAM_ALLOW_SEND=0, sin--send) crea una intención pendiente local. - Envío exige dos señales:
--sendyTELEGRAM_ALLOW_SEND=1.
Crear el bot y encontrar el chat (opcional)
Abre @BotFather, usa /newbot, guarda el token en un gestor privado y escribe /start desde tu chat de prueba. Nunca lo pegues en Git, capturas, URLs o comandos que queden en historial.
En una terminal Bash, carga el token sin escribirlo en el historial. Mantén abierta esta terminal para los siguientes pasos: una variable creada dentro de Python no se exportaría a tu shell.
read -r -s -p 'Token del bot (oculto): ' TELEGRAM_BOT_TOKEN
printf '\n'
export TELEGRAM_BOT_TOKEN
export TELEGRAM_ALLOW_SEND=0
No ejecutes esto dentro de una grabación ni actives set -x, que mostraría valores sensibles. Son pasos reales opcionales, no una prueba ejecutada contra Telegram.
Para obtener chat_id, este ejemplo usa el token desde el entorno y solo muestra update_id, chat_id y tipo; nunca imprime el payload completo. Ejecútalo únicamente con tu bot de prueba:
python3 - <<'PY'
import json, os, urllib.request
token = os.environ["TELEGRAM_BOT_TOKEN"]
request = urllib.request.Request(
"https://api.telegram.org/bot" + token + "/getUpdates",
data=json.dumps({"allowed_updates": ["message"], "limit": 100}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=20) as response:
payload = json.load(response)
except (OSError, ValueError):
raise SystemExit("No se pudo consultar Telegram. Revisa conexión y token sin imprimirlo.") from None
if not isinstance(payload, dict) or payload.get("ok") is not True:
raise SystemExit("Telegram no confirmó la consulta; no imprimas el cuerpo completo.")
for update in payload.get("result", []):
message = update.get("message", {})
chat = message.get("chat", {})
if chat.get("type") == "private":
print({"update_id": update.get("update_id"), "chat_id": chat.get("id"), "type": chat.get("type")})
PY
Si no aparece ningún update, envía /start y revisa que otro proceso no lo haya consumido.
Leer primero, enviar después
Copia tu chat_id privado de la salida anterior. En la misma terminal, autorízalo e introduce en tu chat el comando stock A-100:
read -r -p 'chat_id privado autorizado: ' TELEGRAM_ALLOWED_CHAT_ID
export TELEGRAM_ALLOWED_CHAT_ID
export TELEGRAM_ALLOW_SEND=0
python3 telegram.py poll --once
Esto sí podría leer red si ejecutas el comando real. El curso no lo ejecuta. Para revisar el estado local sin red usa el valor por defecto seguro:
python3 telegram.py inspect
También puedes indicar una ruta privada explícita, por ejemplo --db "$HOME/.local/share/agente-catalogo/telegram.sqlite3"; no pases una variable vacía. inspect no garantiza que exista una respuesta pendiente: si todavía no hubo un update aceptado, pending estará vacío. «Inspeccionar» no significa que haya una previsualización disponible.
Solo tras revisar el chat y la respuesta, y con permiso explícito, se podría ejecutar:
export TELEGRAM_ALLOW_SEND=1
python3 telegram.py poll --once --send
--once significa una vuelta de lectura, no un único mensaje: puede haber varias intenciones pendientes. Mantén el bot limitado a tu chat de prueba. Un timeout de sendMessage queda unknown; no repitas el POST a ciegas. Resuelve manualmente después de obtener confirmación externa.
Al terminar, elimina las variables de esta sesión con unset TELEGRAM_BOT_TOKEN TELEGRAM_ALLOWED_CHAT_ID TELEGRAM_ALLOW_SEND. SQLite conserva estado y respuestas; trata esa base como información privada.
Ejercicio: inspección sin Telegram
Comprueba qué contiene una base nueva antes de recibir mensajes:
DB="$(mktemp -d)/telegram.sqlite3"
python3 telegram.py inspect --db "$DB" > inspect.json
python3 - <<'PY'
import json
state=json.load(open("inspect.json", encoding="utf-8"))
assert state["offset"] == 0
assert state["updates"] == []
assert state["pending"] == []
print("Telegram inspect offline OK: sin update no hay preview pendiente")
PY
Resultado observable
El snippet solo crea una SQLite local privada y comprueba el estado vacío. No prueba BotFather, Telegram, red, entrega exactly-once ni un mensaje real. El núcleo sigue siendo la fuente de los hechos de Tienda Luna; el canal solo transporta texto.
Error habitual y recuperación
Si aparece pending: [], no significa que se haya enviado nada: no hay una respuesta pendiente en esa base. Comprueba el chat_id, envía stock A-100 desde el chat autorizado y repite la lectura solo si decidiste hacer el ensayo real. Si cambiaste de terminal, vuelve a cargar sus variables; no copies el token dentro del código para arreglarlo.