Parse modes и экранирование
Три режима
Заголовок раздела «Три режима»| Режим | Уходит как | Значения экранируются |
|---|---|---|
MarkdownV2 (по умолчанию) |
parse_mode: MarkdownV2 |
обратным слэшем перед каждым метасимволом |
HTML |
parse_mode: HTML |
& → &, < → <, > → > |
none |
поле не отправляется | никак |
Markdown принимается как алиас MarkdownV2 и печатает предупреждение. notiflow
экранирует значения по правилам V2; отдавать такое легаси-парсеру Telegram — получить битое
или отклонённое сообщение, поэтому молчаливый апгрейд — единственное поведение, которое
даёт то сообщение, которое человек имел в виду.
MarkdownV2
Заголовок раздела «MarkdownV2»Эти 18 символов — разметка, и внутри подставляемых значений они экранируются:
_ * [ ] ( ) ~ ` > # + - = | { } . !Сам обратный слэш экранируется первым, поэтому значение с \. превращается в \\\. — один
экранированный слэш и одна экранированная точка, а не схлопывается в один escape.
notiflow render -T 'ветка {{.Branch}}' --set Branch='fix/a.b-c_d[e](f)'# ветка fix/a\.b\-c\_d\[e\]\(f\)Обычный текст не трогается: буквы, цифры, пробелы, /, :, кириллица и эмодзи проходят
как есть.
Разметка, написанная в шаблоне, работает — экранируются только значения:
message_template: | *{{.Workflow}}* закончился на `{{.Branch}}` [открыть запуск]({{.RunUrl}})Telegram принимает небольшой набор тегов: b, strong, i, em, u, ins, s,
strike, del, span class="tg-spoiler", tg-spoiler, a href, code, pre,
blockquote.
В подставляемых значениях экранируются &, < и >, причём амперсанд первым, чтобы
< не превратился в &lt;.
parse_mode: HTMLmessage_template: | <b>{{.Workflow}}</b> на <code>{{.Repo}}</code> <a href="{{.RunUrl}}">открыть запуск</a>Нюанс, на который легко налететь: {{.RunUrl}} внутри атрибута href экранируется как
HTML-текст, а не как URL. Для URL, которые формирует GitHub, это правильно. Если ты
подставляешь собственный URL с &, он приедет как & — что тоже корректный HTML, и
Telegram его разэкранирует.
Не экранируется ничего, parse_mode не отправляется. Сообщение приходит ровно таким, каким
собрано. Правильный выбор, когда текст приходит откуда-то, что ты не контролируешь:
сообщение коммита, хвост лога, вывод упавшего теста, — там ломать нечего.
tail -50 build.log | notiflow send --parse-mode none --stdinГде дословный текст опасен
Заголовок раздела «Где дословный текст опасен»message (и его аналоги --message-file / --stdin) проходит нетронутым: ни подстановки,
ни экранирования. В этом и смысл — это лазейка для текста, который ты уже отформатировал.
И это же делает вот такое гранатой с выдернутой чекой:
# так не надоmessage: "Build failed: ${{ github.event.head_commit.message }}"parse_mode: MarkdownV2Сообщение коммита с _ или [ даёт 400 Bad Request: can't parse entities, а с
[x](https://evil.example) — ссылку, которую ты не писал. Два выхода:
# либо: ломать нечегоmessage: "Build failed: ${{ github.event.head_commit.message }}"parse_mode: none# либо: пусть экранирует notiflow — шаблоны экранируют каждое подставленное значениеmessage_template: "❌ {{.Workflow}} сломался на {{.Branch}} ({{.ShortSha}})"Как проверить
Заголовок раздела «Как проверить»notiflow render --parse-mode MarkdownV2 --template '…' --explainnotiflow send --parse-mode HTML --template '…' --dry-runrender показывает текст, --dry-run — целиком JSON-тело, включая то, попал ли
parse_mode на провод вообще.