Repository navigation
fix(custom_emojis): reject non-emoji signs as fallback (№, ™, lone keycap) - #315
Merged
Merged
Conversation
…ycap) _looks_like_emoji accepted any non-alnum character >= U+2100, so text signs like № (U+2116), ™ (U+2122) and math operators passed validation and reached the API as MessageEntityCustomEmoji fallbacks. It also let a lone combining keycap (U+20E3) through via the "no keycap in text" floor bypass. Replace the U+2100 floor with an emoji-block check (_is_emoji_char): arrows, misc technical, enclosed alphanumerics, geometric shapes, misc symbols, dingbats, misc symbols and arrows, plus the astral emoji blocks, and the VS16/ZWJ sequence components — with the RGI emoji that live between the blocks (ℹ U+2139, ⤴/⤵ U+2934–U+2935, 〰 U+3030, 〽 U+303D, ㊗/㊙ U+3297/U+3299) listed explicitly. The keycap mark now counts only through its keycap base. U+2139 additionally needs an exemption in the letter/digit check: it is the only letter-form emoji (General_Category Ll, not So), so isalnum() is True for it. Text-presentation signs (™, ☀) remain valid with an explicit U+FE0F, so no real emoji sequence is rejected. Co-Authored-By: Claude Code <[email protected]>
chigwell
approved these changes
Oct 16, 2026
chigwell
requested changes
Oct 16, 2026
chigwell
left a comment
Owner
There was a problem hiding this comment.
Run black --check .
would reformat /home/runner/work/telegram-mcp/telegram-mcp/telegram_mcp/tools/messages.py
Oh no! 💥 💔 💥
1 file would be reformatted, 91 files would be left unchanged.
Error: Process completed with exit code 1.
Same reformat as landed on main in b5149a1: this file was written under a pre-24.x black that kept hand-wrapped call sites; current black collapses them. No behavior change. Co-Authored-By: Claude Code <[email protected]>
chigwell
approved these changes
Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
custom_emojisfallback validation (_looks_like_emoji, from #280) accepted any non-alnum character ≥ U+2100. Text signs — № (U+2116), ™ (U+2122), math operators (∉) — therefore passed validation and reached the Telegram API asMessageEntityCustomEmojifallbacks attached to plain text. A lone combining keycap (U+20E3) also slipped through via the "no keycap in text" floor bypass.The U+2100 floor is replaced by an emoji-block check (
_is_emoji_char): arrows, misc technical, enclosed alphanumerics, geometric shapes, misc symbols, dingbats, misc symbols and arrows, the astral emoji blocks, plus the VS16/ZWJ sequence components — with the RGI emoji that live between the blocks (ℹ U+2139, ⤴/⤵ U+2934–U+2935, 〰 U+3030, 〽 U+303D, ㊗/㊙ U+3297/U+3299) listed explicitly. The keycap mark now counts only through its keycap base. U+2139 additionally needs an exemption in the letter/digit check: it is the only letter-form emoji (General_Category Ll, not So), soisalnum()is True for it.Fixes #299
Before
_looks_like_emojiclassified signs and emoji by one threshold:_looks_like_emoji('№')→ True (wrong)_looks_like_emoji('™')→ True (wrong)_looks_like_emoji('⃣')→ True (wrong, lone keycap mark)_looks_like_emoji('#')→ False (correct)_looks_like_emoji('😀')→ True (correct)A structurally identical request with only the fallback character changed either sent an invalid entity or produced a validation error — the validator could not tell a sign from an emoji.
After
Non-emoji signs are refused with the standard validation error before anything reaches the API:
_looks_like_emoji('№')→ False_looks_like_emoji('™')→ False_looks_like_emoji('⃣')→ False (a keycap mark now counts only through its base)_looks_like_emoji('∉')→ False (math operators no longer pass)_looks_like_emoji('#')→ False (unchanged)_looks_like_emoji('😀')→ True (unchanged)_looks_like_emoji('™️'),('☀️'),('ℹ️'),('⤴️'),('〽️'),('㊙️')→ True (text-presentation signs and RGI emoji between the blocks stay valid with an explicit U+FE0F)Tests
tests/test_send_custom_emojis.py::test_non_emoji_signs_are_refused— №, ™, lone U+20E3, ∉, # are refused.tests/test_send_custom_emojis.py::test_text_presentation_signs_with_vs16_are_accepted— ™️, ☀️, ℹ️,pytest -q→ 1226 passed.Risks
🤖 Generated with Claude Code