Skip to content

feat(telegram): inbound stickers/animations/non-file text, media identity, mention regex, allowlist, early typing (#225) - #269

Merged
patrick-chinchill merged 6 commits into
mainfrom
sync/4.41-tg1
Sep 30, 2026
Merged

patrick-chinchill merged 6 commits into
mainfrom
sync/4.41-tg1

Conversation

@patrick-chinchill

@patrick-chinchill patrick-chinchill commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Brings Telegram inbound parsing up to chat@4.41.1.

  • Stickers, venues, locations, contacts, polls, dice, games, invoices and stories no longer arrive with text == "".
  • Stickers and animations now become attachments. The document copy Telegram sends alongside an animation is no longer reported a second time.
  • Photos report image/jpeg. Every attachment's fetch_metadata includes fileUniqueId when Telegram sends one.
  • @mybot-dev no longer counts as a mention of @mybot. The mention pattern is compiled once per username.
  • New user allowlist: allowed_user_ids / TELEGRAM_ALLOWED_USER_IDS.
  • Private, non-bot messages and slash commands start typing when they arrive.
  • File downloads are capped at 25 MB with a 30 s total timeout.

Upstream commits mapped

Upstream What Python
4ee187ac (#612, chat@4.32.0) typing on receipt _start_typing_for_private_message, called from handle_incoming_message_update and handle_slash_command_update
2531a422 (#621, Telegram half, chat@4.34.0) @{user}(?![\w-]) _get_mention_regex (re.IGNORECASE | re.ASCII)
0701679e (#706, chat@4.35.0) mention regex cache _mention_regex / _mention_regex_username (the abortable-sleep half is N/A)
54eea715 (#742, chat@4.35.0) user allowlist TelegramAdapterConfig.allowed_user_ids, process_update gate, _update_user_id
53bf73db (#752, chat@4.36.0) fileUniqueId, photo image/jpeg create_attachment(file_unique_id=...), top-level and rich.py photo MIME
a0ba9868 (#835, chat@4.39.0) stickers and animations text fallback in parse_telegram_message, _sticker_attachment_format, animation video attachment with its document copy suppressed
a18e7922 (#836, chat@4.39.0) non-file content text _describe_non_file_content, _format_invoice_amount
b6fa24c6 (#865, Telegram half, chat@4.39.0) 25 MB / 30 s download guard download_file + _read_telegram_file

Tests ported (packages/adapter-telegram/src/index.test.ts → tests/test_telegram_webhook.py)

  • "should resolve allowedUserIds from TELEGRAM_ALLOWED_USER_IDS env var", "should allow all users when TELEGRAM_ALLOWED_USER_IDS is empty", "rejects disallowed and identityless updates before dispatch"
  • "starts typing before processing private message updates", "starts typing before processing private slash command updates". The ordering is asserted with a process_* stand-in that schedules a task, the way the real Chat does.
  • "does not mark a mention when a hyphen-suffixed name is mentioned", "matches with the cached regex and recompiles when the username changes". The cache is checked by counting re.compile calls.
  • "preserves stable photo identity and JPEG metadata across resends and serialization", "preserves stable identity for voice attachments"
  • sticker messages (3), sticker and animation attachments (5), non-file content (10)

Python-specific tests:

  • Downloads: a Content-Length over the cap is rejected before any read. A body with no length is stopped by the running count, and the chunk after the cap is never read. A body exactly at the cap is accepted. A malformed Content-Length falls back to the running count. A timeout raises NetworkError. The request passes ClientTimeout(total=30).
  • Mentions: re.ASCII cases (@mybotж mentions, @mybot_x does not).
  • Typing: a failing typing task is logged and does not block dispatch. Group chats and bot senders do not trigger typing.
  • Allowlist: an explicit config list overrides the env var and normalizes ids. Allowlisted callback and reaction users are dispatched.
  • Text and identity: _js_number_str matches JS String(). Integral and tiny float coordinates render as in JS. An empty caption still takes precedence over the non-file description. An empty file_unique_id is omitted.

Existing expectations updated: fetch_metadata now includes fileUniqueId, and rich photos report mime_type="image/jpeg". Both match the upstream 4.41 tests. Two test helpers now mock telegram_fetch, because private messages fire a typing request. A local net-guard run confirmed that no Telegram test reaches the real HTTP session.

Mutation-checked: removing re.ASCII, reverting to \b, moving typing after dispatch, dropping the regex cache, reporting the animation's document copy again, formatting floats with repr, and always adding fileUniqueId each fail at least one test.

Fidelity

adapter-telegram/src/index.test.ts is not fidelity-mapped (#78), so the target report is unchanged:

Delta vs committed report (HEAD): missing 242 -> 242 (+0)

(scripts/fidelity_target.json was regenerated and came out byte-identical, so the PR does not include it.) Strict: 733/733 at chat@4.31.0.

Divergences

None. The Python-specific adaptations are recorded in a new parity row in docs/UPSTREAM_SYNC.md:

  • _js_number_str formats coordinates, dice values and allowlist ids the way JS String() does.
  • The mention pattern uses re.ASCII.
  • The typing call runs as an asyncio task that is scheduled before the handler task.
  • The download uses aiohttp's ClientTimeout(total=30) and iter_chunked.

The download deliberately does not use chat_sdk.shared.download (#204), for two reasons. Its HTTPS and public-address checks would break self-hosted Bot API servers, which upstream also leaves unchecked. And read_attachment_body decodes Content-Encoding itself, while the shared aiohttp session already decompresses. The PR reuses only DEFAULT_LIMIT and DEFAULT_TIMEOUT_MS.

Consumer impact (Telegram only; none for Slack or Teams)

  • message.text is now non-empty for the kinds listed above.
  • Photo mime_type is now image/jpeg (it was None). fetch_metadata gains fileUniqueId.
  • New sticker and animation attachments. An animation is now one video attachment instead of one file attachment.
  • One extra sendChatAction request per private message or slash command.
  • New allowed_user_ids option. When it is unset, behavior is unchanged.
  • Downloads over 25 MB, or taking longer than 30 s, now raise NetworkError.

Out of scope, each with its own issue:

Closes #225
Part of #184

Merge gate

Independent review findings (4 reported; 4 fixed, 0 declined), all in commit bfbaa2b:

  • Mention regex case folding (raised by two reviewers). re.ASCII turned off non-ASCII case folding. The pattern is now (?![A-Za-z0-9_-]) with re.IGNORECASE, so @БОТИК mentions ботик, as JS /i does. The lookahead still accepts only ASCII characters, like JS \w. The docstring and the docs/UPSTREAM_SYNC.md row are corrected, and the row now records the remaining Kelvin sign / long s folding difference. Test: test_mention_regex_case_folds_non_ascii_usernames_like_js_i, which fails on the old pattern.
  • Allowlist test gap. The test could not catch the allowlist gate being moved below slash-command dispatch. It now also sends a private /ping and a DM from a disallowed user. It asserts that process_slash_command is not called and that no sendChatAction is sent. Mutation check: moving the gate after handle_slash_command_update makes the test fail.
  • allowed_user_ids as a bare string. A string was iterated character by character. A value that is not a list, tuple or set now raises ValidationError. This guard is Python-only, since upstream's .map already throws on a string. Test: test_non_list_allowed_user_ids_raise.

gpt-6-astra: 4 rounds. Final verdict on fdf7d52: clean.

  • R1 (bfbaa2b), [P2]: receipt-typing tasks still pending at disconnect() could reopen the aiohttp session after it was closed. This was a real bug (Python session lifecycle). Fixed in 103b21c: disconnect() now cancels and awaits pending typing tasks. The new test test_disconnect_cancels_pending_typing_so_the_session_is_not_reopened fails without the fix.
  • R2 (103b21c): clean.
  • R3 (b89102a, after merging origin/main), [P2]: a re-posted GIF animation is sent through sendVideo. Rebutted as upstream parity. chat@4.41.1 adapter-telegram/src/index.ts:2798-2809 maps animation to a "video" attachment, and ATTACHMENT_UPLOADS.video (index.ts:154) uses sendVideo. There is no Python-specific hazard, so fdf7d52 adds a comment noting the parity and leaves behavior unchanged.
  • R4 (fdf7d52): clean.

Bots: CodeRabbit was rate-limited and posted no review. The PR has no inline or review comments.

CI: all checks green on fdf7d52 (Lint & Type Check, test 3.12 and 3.13, CodeQL).

Local validation: full run green:

  • ruff check and format
  • test-quality audit
  • fidelity --check-docs and --strict (4.31.0 pin)
  • pytest: 6384 passed
  • pyrefly: 0 errors

Fidelity target delta: missing 242 -> 242 (+0).

…tity, mention regex, allowlist, early typing (#225)

Ports vercel/chat 4ee187ac (#612), 2531a422 (#621, Telegram half),
0701679e (#706, regex cache), 54eea715 (#742), 53bf73db (#752),
a0ba9868 (#835), a18e7922 (#836) and the Telegram half of b6fa24c6 (#865).

- message.text is non-empty for stickers, venues, locations, contacts,
  polls, dice, games, invoices and stories
- stickers and animations become attachments; the animation's document
  twin is suppressed; photos report image/jpeg; fetch_metadata carries
  fileUniqueId when present
- @mybot-dev no longer mentions @Mybot; the pattern is cached per username
- allowed_user_ids / TELEGRAM_ALLOWED_USER_IDS gate updates before dispatch
- private, non-bot messages and slash commands start typing on receipt
- file downloads are capped at 25 MB with a 30 s total timeout
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 29 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: b9fe3a5a-a74b-49f6-aae9-620f69a21f16

📥 Commits

Reviewing files that changed from the base of the PR and between 4b1e7c5 and b57ecfa.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • docs/UPSTREAM_SYNC.md
  • src/chat_sdk/adapters/telegram/adapter.py
  • src/chat_sdk/adapters/telegram/rich.py
  • src/chat_sdk/adapters/telegram/types.py
  • tests/test_telegram_api.py
  • tests/test_telegram_rich.py
  • tests/test_telegram_webhook.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… allowed_user_ids, pin allowlist gate before slash/typing (#225)
@patrick-chinchill
patrick-chinchill marked this pull request as ready for review September 30, 2026 18:57
@patrick-chinchill

Copy link
Copy Markdown
Collaborator Author

Merge gate: CI green (Lint & Type Check, test (3.12), test (3.13), Analyze (python), Analyze (actions), CodeQL) on b57ecfa; local Codex review (gpt-6-astra, xhigh, --base origin/main) on fdf7d52: "No actionable regressions were found relative to the supplied merge base. All 476 Telegram tests passed, and targeted Ruff checks and diff whitespace checks passed."; 4 astra rounds. b57ecfa only merges origin/main (#270, #220, #212, #266); the merge was clean with disjoint files, and the PR diff under src/ tests/ scripts/ is byte-identical to what astra reviewed, so no re-review was needed. Local full validation: 6546 passed, strict fidelity 730/730 + 3 absorbers, pyrefly 0 errors, fidelity target delta +0. CodeRabbit is rate-limited (no review). Merging with --admin (Protect Main requires a code-owner approval).

@patrick-chinchill
patrick-chinchill merged commit 9c7b5b7 into main Sep 30, 2026
7 checks passed
@patrick-chinchill
patrick-chinchill deleted the sync/4.41-tg1 branch September 30, 2026 19:27
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.

[4.41/TG1] Telegram inbound: stickers/animations/locations/polls, media identity, mention regex, allowlist, early typing

1 participant