Module: AgentsControl::Channels::Telegram::Markdown
- Defined in:
- lib/agents_control/channels/telegram/markdown.rb
Overview
Turns plain GFM-like text — the shape Claude Code writes — into Telegram MarkdownV2.
Escaping rules:
- outside code, the 18 characters `_*[]()~`>#+-=|{}.!` must be
backslash-escaped, or sendMessage answers with a 400: Bad
Request: can't parse entities: Character '.' is reserved…
- inside `inline code` and ```blocks``` those same characters
must NOT be escaped — there they're just text;
- a ```lang tag before a block is accepted and syntax-highlighted.
This is Telegram's strict mode: one incorrectly escaped period and the whole message fails to send. So the converter is written conservatively (it never touches code, and escapes prose in full), and the Api side still carries its own separate safety net — falling back to plain text if Telegram rejects it anyway.
Constant Summary collapse
- RESERVED =
Escaped outside code. Order matters:
\goes first, or the escaping slashes would themselves get escaped again. %w[\\ _ * [ ] ( ) ~ ` > # + - = | { } . !].freeze
- CODE =
Code is what must never be touched: even the opening triple quote can carry a language tag (```ruby) that has to be kept literal, not escaped.
/(```.*?```|`[^`\n]+`)/m- BOLD =
One of the few GFM constructs worth preserving as actual formatting rather than turning into escaped asterisks: Claude Code often puts important text in headings and summaries.
/\*\*(.+?)\*\*/m
Class Method Summary collapse
Class Method Details
.convert(text) ⇒ Object
40 41 42 43 44 |
# File 'lib/agents_control/channels/telegram/markdown.rb', line 40 def convert(text) text.to_s.split(CODE).each_with_index.map do |chunk, index| index.odd? ? chunk : escape_prose(chunk) end.join end |
.escape_literal(text) ⇒ Object
52 53 54 55 |
# File 'lib/agents_control/channels/telegram/markdown.rb', line 52 def escape_literal(text) pattern = Regexp.union(RESERVED) text.gsub(pattern) { |char| "\\#{char}" } end |
.escape_prose(text) ⇒ Object
46 47 48 49 50 |
# File 'lib/agents_control/channels/telegram/markdown.rb', line 46 def escape_prose(text) text.split(BOLD).each_with_index.map do |chunk, index| index.odd? ? "*#{escape_literal(chunk)}*" : escape_literal(chunk) end.join end |