Skip to content

Telegram

Telegram funciona en ambos modos:

ModoQué hace
NotificaciónsendMessage unidireccional desde el pipeline de alertas.
IM bridgeChat bidireccional con el agente vía long-poll getUpdates.

El mismo bot token (8…:AA…, de @BotFather) alimenta ambos — Telegram autentica por token-en-path. No hay un esquema de firma separado.

Diseñado para usuarios fuera del territorio Feishu / DingTalk

Telegram es el canal IM que Ongrid recomienda para equipos no-China y para despliegues híbridos donde los operadores residen en Telegram. El provider se añadió en ADR-031.

Modo notificación

Un notify.Sender POSTea {chat_id, text} a https://api.telegram.org/bot<TOKEN>/sendMessage. La credencial es el bot token (en el path); el chat_id (numérico) es el chat de destino.

go
// internal/pkg/notify/webhook.go
NewTelegramSender(name, endpoint, chatID, client)

Donde endpoint es la URL literal …/bot<TOKEN>/sendMessage y chatID es el chat target (el ID numérico de un usuario para un DM, o el número negativo que Telegram te da para un grupo).

Setup:

  1. DM @BotFather/newbot → elige un username → copia el token.
  2. Añade el bot al chat target (grupo / canal) o hazle DM una vez para que sepa de ti.
  3. Toma el chat ID. La forma más rápida: envía cualquier mensaje en el chat, luego curl https://api.telegram.org/bot<TOKEN>/getUpdates y lee result[0].message.chat.id.
  4. En Ongrid: Settings → Channels → New → Provider = telegram → Endpoint = https://api.telegram.org/bot<TOKEN>/sendMessage, chat_id = el número del paso 3.

Modo IM bridge (bidireccional)

El inbound se hace long-poll desde la conexión HTTPS saliente del manager. Como la llamada es saliente, Telegram funciona detrás de NAT, firewalls y proxies HTTPS. setWebhook (la alternativa) requeriría que Telegram alcanzara al manager — eso es incompatible con la mayoría de los despliegues private-cloud y poco fiable desde China continental.

stream es el único modo

El validator rechaza cualquier otra cosa:

telegram only supports stream mode

El modo webhook no se expone en la UI para Telegram.

Mapeo de credenciales

La fila im_apps reutiliza columnas existentes — sin cambios de schema:

Columna im_appsSignificado en Telegram
provider"telegram"
mode"stream" (el validator lo fija)
app_idUsername del bot (p. ej. ongridbot). Display + dedupe.
app_secretToken de BotFather (8…:AA…). Encriptado en reposo.
allow_fromRequerido comma-separated IDs numéricos de usuario.
verify_tokenNo usado.
encrypt_keyNo usado.

allow_from

Una lista no vacía de IDs numéricos de usuario de Telegram que pueden conversar con el bot. El validator descarta los tokens no numéricos (así un alice tipeado mal aterriza como un error de required limpio, no como una allowlist silenciosamente vacía) y rechaza los tokens negativos (esos son IDs de chat de grupo / supergrupo y no pertenecen a una allowlist de remitentes).

telegram requires allow_from — at least one numeric Telegram user ID (the bot is publicly reachable; an empty allowlist would let anyone command the agent)

Cómo encontrar un ID numérico de usuario:

  • Hazle DM a @userinfobot y te responde con tu ID numérico.
  • O envía un mensaje en el chat después de registrarte, corre curl https://api.telegram.org/bot<TOKEN>/getUpdates, y lee result[0].message.from.id.

Los prefijos telegram: / tg: se eliminan silenciosamente (compatibilidad con OpenClaw), así que telegram:123456789 y 123456789 son la misma entrada.

Drop silencioso al fallar

Los remitentes no allowlisted se descartan con un log WARN:

text
telegram inbound from non-allowlisted sender — ignored
  user_id=42  user_name=alice  chat_id=42

No hay respuesta. No hay placeholder. El bot ni siquiera confirma que existe. Esto espeja allowFrom de OpenClaw — una respuesta filtraría que un bot respaldado por agente vive en este username y tentaría a la gente a probarlo.

Setup

  1. DM @BotFather/newbot → elige un nombre y un username único terminando en bot. Copia el token.
  2. (Opcional) @BotFather → /setprivacy → Disable si quieres que el bot vea mensajes en grupos (el default es solo-mención).
  3. Encuentra tu ID numérico de usuario vía @userinfobot.
  4. En Ongrid: Settings → IM bridge → New → Provider = telegram → Mode = stream → App ID = el username del bot → App secret = el token de BotFather → allow_from = tu ID numérico (y los de tus teammates). Save y Enable.
  5. DM al bot desde una cuenta allowlisted. El bot responde con un placeholder, luego lo edita in place mientras el agente razona.

La historia del proxy

El host de la API de Telegram (api.telegram.org) es alcanzable en la mayoría de redes pero está bloqueado desde China continental. El provider usa un http.Client de valor cero para que honre HTTPS_PROXY / HTTP_PROXY / NO_PROXY del entorno del manager — el mismo proxy que lleva getUpdates lleva sendMessage.

En despliegues docker-compose, persiste el proxy en docker-compose.override.yml:

yaml
services:
  manager:
    environment:
      HTTPS_PROXY: http://your-proxy:8080
      NO_PROXY: localhost,127.0.0.1,manager,mysql,prometheus,loki,tempo,grafana,qdrant

No pongas el proxy en el docker-compose.yml principal

El docker-compose.yml principal viene con cada release. Un archivo de override es por-entorno y sobrevive a reruns de make package / install script.

Quirks que vale la pena conocer

message is not modified es inocuo

Telegram devuelve HTTP 400 (message is not modified) cuando un payload de editMessageText coincide exactamente con el texto actual del mensaje. El streaming progresivo puede repetir el mismo chunk en un tick throttled o en el flush final. El cliente traga exactamente este string de error y devuelve nil — cualquier otra cosa (400, 403, 5xx) sigue propagándose. Ver EditMessageText.

Solo un poller por bot

Telegram rechaza llamadas concurrentes a getUpdates. El StreamSupervisor refuerza un cliente por fila im_app. Si duplicas una fila de app de Telegram, solo una pollearía con éxito — la otra verá 409 Conflict y hará backoff.

Retries por rate-limit

429 Too Many Requests se reintenta hasta 3 veces. La espera honra el parameters.retry_after de Telegram, topado en 60s. Los errores 5xx reintentan con backoff 1s / 2s / 4s. Los 4xx duros (400/401/403) no reintentan — burbujean al supervisor y probablemente indican un problema de token / chat-id. Ver maxCallRetries.

Timeout del long-poll

El long poll del lado del servidor espera 25s (pollTimeoutSec) con un buffer de 10s del lado del cliente. Las redes lentas solo ven ciclos de poll más largos; el supervisor no está haciendo reconnects busy-wait.

Solo texto

Stickers, fotos, voice notes y archivos se descartan silenciosamente. Solo los eventos message.text mueven un turno del agente. El bridge es intencionalmente S1 (texto in / texto out); el rich media es una expansión futura.