Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,14 @@ protected function doSend(MessageInterface $message): SentMessage
if (!isset($options['parse_mode']) || TelegramOptions::PARSE_MODE_MARKDOWN_V2 === $options['parse_mode']) {
$options['parse_mode'] = TelegramOptions::PARSE_MODE_MARKDOWN_V2;
/*
* Just replace the obvious chars according to Telegram documentation.
* Do not try to find pairs or replace chars, that occur in pairs like
* - *bold text*
* - _italic text_
* - __underlined text__
* - various notations of images, f. ex. [title](url)
* - `code samples`.
*
* These formats should be taken care of when the message is constructed.
* Escape the reserved characters that Telegram would reject and leave the markup alone:
* paired markers (*bold*, _italic_, `code`, ~strikethrough~, ||spoiler||, [link](url)),
* block quotations (">" at the start of a line) and custom emojis ("![").
* Characters that are already escaped are kept as they are.
*
* @see https://core.telegram.org/bots/api#markdownv2-style
*/
$text = preg_replace('/([.!#>+-=|{}~])/', '\\\\$1', $text);
$text = preg_replace_callback('/\\\\[\x01-\x7E]|\|\||^(?:\*\*)?>|!\[|([.!#+\-=|{}>])/m', static fn (array $m) => isset($m[1]) ? '\\'.$m[1] : $m[0], $text);
}

if (isset($options['upload'])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public function testSendWithMarkdownShouldEscapeSpecialCharacters()

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

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

public function testSendWithMarkdownEscapesReservedCharactersButNotMarkup()
{
$cases = [
'plain text' => ['Hello world. Cost: 10 EUR!', 'Hello world\. Cost: 10 EUR\!'],
'digits and unreserved punctuation' => ['v1.2.3 at 10:30, a/b; x<y', 'v1\.2\.3 at 10:30, a/b; x<y'],
'already escaped' => ['Version 1\.2 and \\\\.', 'Version 1\.2 and \\\\\.'],
'paired markup' => ['*bold* _italic_ __underline__ `code` ~strike~ ||spoiler|| [link](https://symfony.com/)', '*bold* _italic_ __underline__ `code` ~strike~ ||spoiler|| [link](https://symfony\.com/)'],
'block quotation' => [">Quote\n>More\n**>Expandable\n>Last line||", ">Quote\n>More\n**>Expandable\n>Last line||"],
'reserved characters outside markup' => ['a > b, a | b, a ! b, **> c', 'a \> b, a \| b, a \! b, **\> c'],
'custom emoji' => ['![👍](tg://emoji?id=5368324170671202286)', '![👍](tg://emoji?id\=5368324170671202286)'],
];

foreach ($cases as $case => [$subject, $expectedText]) {
$sentText = null;
$client = new MockHttpClient(static function (string $method, string $url, array $options = []) use (&$sentText): ResponseInterface {
$sentText = json_decode($options['body'], true)['text'];

return new JsonMockResponse(['ok' => true, 'result' => ['message_id' => 1]]);
});

self::createTransport($client, 'testChannel')->send(new ChatMessage($subject));

$this->assertSame($expectedText, $sentText, $case);
}
}

/**
* @return array<array<string, array{messageOptions: TelegramOptions, endpoint: string, expectedBody: array<mixed>, responseContent: array<mixed>}>>
*/
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.