Skip to content

feat(channels): add Telegram as a first-class chat channel (WeChat peer)#54

Merged
Germey merged 1 commit into
mainfrom
feat/channel-telegram
Jul 3, 2026
Merged

feat(channels): add Telegram as a first-class chat channel (WeChat peer)#54
Germey merged 1 commit into
mainfrom
feat/channel-telegram

Conversation

@acedatacloud-dev

Copy link
Copy Markdown
Member

What & why

The channels portal/pipeline was WeChat-only — there was no peer entry point for other messengers. This adds Telegram as a first-class chat channel, running side-by-side with WeChat through the same PolicyGate → SessionDispatcher pipeline. One coding-bridge channels start now drives both at once.

Telegram needs no gateway: the daemon long-polls the official Bot API (getUpdates, outbound-only, no webhook, no open ports). The bot token lives only in the client URL path (/bot<token>/…) and is never logged or surfaced in any error string.

Changes

  • channels/telegram/client.py — async Bot API client: getMe / getUpdates / sendMessage / deleteWebhook. Bounded, token-free errors; sendMessage caps text at 4096 chars; TelegramError carries error_code + retry_after.
  • channels/telegram/adapter.py — long-poll loop: offset tracking (update_id + 1), backoff 0.5→30s (reset on success), 409deleteWebhook+continue, 401→stop, 429→honor retry_after. Handler exceptions never kill the loop. aclose() closes the owned client to interrupt an in-flight poll.
  • channels/config.py — new TelegramInstanceConfig + [[channels.telegram]] parsing/validation (unknown-key rejection, token_env XOR token_file, api_base scheme + no embedded-credentials, allowlist/rate/dedup bounds, default_providerKNOWN_PROVIDERS). Extracted a shared _resolve_token_source() helper and refactored WeChatInstanceConfig.resolve_token to delegate to it — WeChat behavior/messages unchanged.
  • channels_cli.pychannels start builds WeChat and Telegram adapters on one shared stop event; channels doctor adds a Telegram getMe probe; channels init template + --help mention Telegram.
  • pyproject.tomltelegram = [] marker extra (parity with wechat).
  • README.md — channels section now documents WeChat + Telegram as peers.
  • tests/test_channels_telegram.py — 45 new tests (update parsing, client via httpx.MockTransport, adapter loop edge cases, config validation, WeChat+Telegram coexistence).

Security

  • Bot token never logged: transport errors surface only exc.__class__.__name__; adapter logs error_code ints only; doctor prints bot @username only. Tests assert the token string never appears in any error.
  • Off by default (enabled = false); same gated inbound (trigger prefix / sender + group allowlist / rate limit / dedup) as WeChat; provider turns still run locally.

Testing

  • ruff check clean.
  • New Telegram suite: 45 passed.
  • Full suite: 471 passed, 1 skipped, 3 pre-existing Windows-only failures (test_capabilities, test_fs, test_locking — filesystem/PID assertions that pass on CI Linux; unrelated to this change).

🤖 对抗评审

Reviewer: Claude general-purpose subagent (Codex not available on this host), read-only, one round.

VERDICT: CLEAN — no real defects found. Classes of bug explicitly checked and confirmed handled:

  1. Token leakage — all error paths (client _call transport error, send_message, adapter logs, doctor probe) surface only exception class names / error_code ints / @username; never the token or base URL. Tests assert the token never appears in SendResult.error.
  2. Long-poll offset — offset advances to update_id + 1 for every update including skipped/unparseable ones, so a permanently-bad update cannot wedge the loop. Verified by tests.
  3. Stop responsivenessaclose() sets the stop event and closes the owned client, which fails the in-flight getUpdates; the TelegramError/stop check then returns immediately. Injected clients are intentionally not closed (caller-owned) — covered by a test.
  4. 409 / 401 / 429deleteWebhook+continue (same offset), stop, and retry_after respectively — all verified.
  5. Config refactor_resolve_token_source is byte-for-byte equivalent to the old inline WeChat logic; error substrings unchanged; existing WeChat tests still pass.
  6. Config parity — Telegram validates every field WeChat does (plus api_base), including bool/int/NaN/Inf rejection and provider allowlist.
  7. channels start wiring — both adapters share one stop event; all adapters/dispatchers closed in finally.
  8. _parse_update — drops non-message/edited/non-text/bad-chat updates; sender_id falls back to chat_id; supergroup/channel → "group" so allowed_groups applies.

No RISK items were raised, so none required fix-or-rebuttal.

Adds a Telegram bot channel that runs side-by-side with WeChat through the same PolicyGate -> SessionDispatcher pipeline. It long-polls getUpdates (outbound only, no webhook or open ports); the bot token lives only in the client URL path and is never logged.

- telegram/client.py: async Bot API client (getMe/getUpdates/sendMessage/deleteWebhook) with bounded, token-free errors and a 4096-char send cap
- telegram/adapter.py: long-poll loop with offset tracking, backoff, 409->deleteWebhook, 401->stop, 429->retry_after; handler errors never kill the loop
- config.py: TelegramInstanceConfig + [[channels.telegram]] parsing; extracted a shared _resolve_token_source helper (WeChat behavior unchanged)
- channels start/doctor/init now drive WeChat + Telegram together
- 45 new tests; README + init template document Telegram as a WeChat peer
@Germey Germey merged commit 873323d into main Jul 3, 2026
3 checks passed
@Germey Germey deleted the feat/channel-telegram branch July 3, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants