Перейти к содержимому

Parse modes и экранирование

Режим Уходит как Значения экранируются
MarkdownV2 (по умолчанию) parse_mode: MarkdownV2 обратным слэшем перед каждым метасимволом
HTML parse_mode: HTML & → &amp;, < → &lt;, > → &gt;
none поле не отправляется никак

Markdown принимается как алиас MarkdownV2 и печатает предупреждение. notiflow экранирует значения по правилам V2; отдавать такое легаси-парсеру Telegram — получить битое или отклонённое сообщение, поэтому молчаливый апгрейд — единственное поведение, которое даёт то сообщение, которое человек имел в виду.

Эти 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; не превратился в &amp;lt;.

parse_mode: HTML
message_template: |
<b>{{.Workflow}}</b> на <code>{{.Repo}}</code>
<a href="{{.RunUrl}}">открыть запуск</a>

Нюанс, на который легко налететь: {{.RunUrl}} внутри атрибута href экранируется как HTML-текст, а не как URL. Для URL, которые формирует GitHub, это правильно. Если ты подставляешь собственный URL с &, он приедет как &amp; — что тоже корректный 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 '…' --explain
notiflow send --parse-mode HTML --template '…' --dry-run

render показывает текст, --dry-run — целиком JSON-тело, включая то, попал ли parse_mode на провод вообще.