Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings

Commit 8ce13ef

Browse filesBrowse the repository at this point in the historyBrowse files
[Notifier] Fix escaping of MarkdownV2 markup in TelegramTransport
1 parent 19a9f0c commit 8ce13ef
Copy full SHA for 8ce13ef

2 files changed

+32-11Lines changed: 32 additions & 11 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Notifier/Bridge/Telegram/TelegramTransport.php
+5-10Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,14 @@ protected function doSend(MessageInterface $message): SentMessage
9090
if (!isset($options['parse_mode']) || TelegramOptions::PARSE_MODE_MARKDOWN_V2 === $options['parse_mode']) {
9191
$options['parse_mode'] = TelegramOptions::PARSE_MODE_MARKDOWN_V2;
9292
/*
93-
* Just replace the obvious chars according to Telegram documentation.
94-
* Do not try to find pairs or replace chars, that occur in pairs like
95-
* - *bold text*
96-
* - _italic text_
97-
* - __underlined text__
98-
* - various notations of images, f. ex. [title](url)
99-
* - `code samples`.
100-
*
101-
* These formats should be taken care of when the message is constructed.
93+
* Escape the reserved characters that Telegram would reject and leave the markup alone:
94+
* paired markers (*bold*, _italic_, `code`, ~strikethrough~, ||spoiler||, [link](url)),
95+
* block quotations (">" at the start of a line) and custom emojis ("![").
96+
* Characters that are already escaped are kept as they are.
10297
*
10398
* @see https://core.telegram.org/bots/api#markdownv2-style
10499
*/
105-
$text = preg_replace('/([.!#>+-=|{}~])/', '\\\\$1', $text);
100+
$text = preg_replace_callback('/\\\\[\x01-\x7E]|\|\||^(?:\*\*)?>|!\[|([.!#+\-=|{}>])/m', static fn (array $m) => isset($m[1]) ? '\\'.$m[1] : $m[0], $text);
106101
}
107102

108103
if (isset($options['upload'])) {
Collapse file

‎src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Component/Notifier/Bridge/Telegram/Tests/TelegramTransportTest.php
+27-1Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ public function testSendWithMarkdownShouldEscapeSpecialCharacters()
250250

251251
$expectedBody = [
252252
'chat_id' => 'testChannel',
253-
'text' => 'I contain special characters _ * [ ] ( ) \~ ` \> \# \+ \- \= \| \{ \} \. \! \ to send\.',
253+
'text' => 'I contain special characters _ * [ ] ( ) ~ ` \> \# \+ \- \= \| \{ \} \. \! \ to send\.',
254254
'parse_mode' => 'MarkdownV2',
255255
];
256256

@@ -265,6 +265,32 @@ public function testSendWithMarkdownShouldEscapeSpecialCharacters()
265265
$transport->send(new ChatMessage('I contain special characters _ * [ ] ( ) ~ ` > # + - = | { } . ! \\ to send.'));
266266
}
267267

268+
public function testSendWithMarkdownEscapesReservedCharactersButNotMarkup()
269+
{
270+
$cases = [
271+
'plain text' => ['Hello world. Cost: 10 EUR!', 'Hello world\. Cost: 10 EUR\!'],
272+
'digits and unreserved punctuation' => ['v1.2.3 at 10:30, a/b; x<y', 'v1\.2\.3 at 10:30, a/b; x<y'],
273+
'already escaped' => ['Version 1\.2 and \\\\.', 'Version 1\.2 and \\\\\.'],
274+
'paired markup' => ['*bold* _italic_ __underline__ `code` ~strike~ ||spoiler|| [link](https://symfony.com/)', '*bold* _italic_ __underline__ `code` ~strike~ ||spoiler|| [link](https://symfony\.com/)'],
275+
'block quotation' => [">Quote\n>More\n**>Expandable\n>Last line||", ">Quote\n>More\n**>Expandable\n>Last line||"],
276+
'reserved characters outside markup' => ['a > b, a | b, a ! b, **> c', 'a \> b, a \| b, a \! b, **\> c'],
277+
'custom emoji' => ['![👍](tg://emoji?id=5368324170671202286)', '![👍](tg://emoji?id\=5368324170671202286)'],
278+
];
279+
280+
foreach ($cases as $case => [$subject, $expectedText]) {
281+
$sentText = null;
282+
$client = new MockHttpClient(static function (string $method, string $url, array $options = []) use (&$sentText): ResponseInterface {
283+
$sentText = json_decode($options['body'], true)['text'];
284+
285+
return new JsonMockResponse(['ok' => true, 'result' => ['message_id' => 1]]);
286+
});
287+
288+
self::createTransport($client, 'testChannel')->send(new ChatMessage($subject));
289+
290+
$this->assertSame($expectedText, $sentText, $case);
291+
}
292+
}
293+
268294
/**
269295
* @return array<array<string, array{messageOptions: TelegramOptions, endpoint: string, expectedBody: array<mixed>, responseContent: array<mixed>}>>
270296
*/

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.