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

Миграция с v1

Для workflow, который шлёт в один чат, jtprogru/notiflow@v1 → jtprogru/notiflow@v2 — замена без правок. Все входы сохранили имена и смысл, все выходы сохранили имена, exit-коды 10–16 значат то же, что значили.

Ломающее изменение ровно одно, плюс набор починок, которые меняют поведение только там, где оно и так было сломано.

В v1.5.0 chat_id мог содержать список через запятую, а edit_message_id — парный список. В v2 отправка идёт ровно в один чат.

v1 v2
chat_id одно значение или CSV одно значение; запятая → exit 11
edit_message_id целое или CSV, длина обязана совпадать с chat_id одно целое
выход message_id CSV в порядке входа, пустой слот на упавший чат (42,,103) один id
выход error chat <id>: <причина>, склеенные через ; сырая причина
выход http_status 200 либо первый не-200 статус единственного запроса
exit 16 не совпало число элементов CSV значение не положительное целое

Агрегированные выходы теряли ровно то, что важно. message_id=42,,103 говорил, что что-то упало, но не что именно; http_status схлопывал несколько результатов в одно число; шаг показывал одну галочку или один крестик на то, что на деле было N независимых отправок. Восстанавливать эту точность внутри одного шага — значит переизобретать матрицу, которая уже делает это правильно.

Матрица, если чаты известны заранее:

jobs:
notify:
needs: [build]
if: always()
strategy:
fail-fast: false
matrix:
chat: ['-1001111111111', '-1002222222222']
runs-on: ubuntu-latest
steps:
- uses: jtprogru/notiflow@v2
with:
bot_token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
chat_id: ${{ matrix.chat }}
status: ${{ needs.build.result }}

Или несколько шагов подряд, если неизвестны:

- uses: jtprogru/notiflow@v2
id: team
with:
bot_token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
chat_id: '-1001111111111'
status: ${{ job.status }}
- uses: jtprogru/notiflow@v2
id: oncall
if: always()
with:
bot_token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
chat_id: '-1002222222222'
status: ${{ job.status }}

Оба варианта дают выходы и статус на каждый чат отдельно.

Если ты сочетал edit_message_id с CSV в chat_id, теперь у каждой ветки матрицы свой message_id — что для редактирования и требовалось: id всё равно никогда не были взаимозаменяемы между чатами.

Multi-chat fan-out оформлен как feature request, а не просто вычеркнут. Если ты на него опирался — это место, где об этом стоит сказать: issue заведён ровно затем, чтобы собрать случаи, которые матрица не покрывает.

Каждая записана в parity-корпус с выводами v1 и v2 рядом, так что разница задокументирована, а не обнаруживается на проде.

Плейсхолдеры подставляются за один проход

Заголовок раздела «Плейсхолдеры подставляются за один проход»

v1 подставлял ключ за ключом в цикле, поэтому значение, подставленное раньше, само просматривалось на плейсхолдеры каждой следующей итерацией. Workflow с именем {{.Actor}} действительно разворачивался в имя актора при parse_mode: none и HTML. При MarkdownV2 экранирование случайно это прятало. v2 читает шаблон ровно один раз.

Сообщения длиннее 4096 UTF-16 единиц обрезаются. v1 резал по сырой границе code unit и мог разорвать escape-пару MarkdownV2 (\ + .), HTML-сущность (&amp;) или тег — Telegram отвечал 400 на сообщение, вся вина которого была в длине. v2 режет между токенами разметки, а в HTML-режиме ещё и закрывает теги, оставшиеся открытыми.

У того же кода был второй отказ. iconv выходит с ненулевым кодом на неполном хвостовом символе даже с -c — BSD и GNU одинаково, — поэтому любое сообщение сверх лимита, у которого обрез пришёлся внутрь суррогатной пары, роняло отправку целиком на всех платформах. В v2 никакого iconv нет.

::add-mask:: закрывает лог workflow, но в CLI такого механизма нет, а ошибка HTTP может нести URL с bot<TOKEN>. v2 фильтрует всё, что печатает, и переписывает любой оставшийся сегмент пути /bot<token>/ — даже для токена, о котором ему не сообщали.

Аргументы видны любому процессу на машине. Флаг работает, но печатает предупреждение, а документация указывает на NOTIFLOW_BOT_TOKEN или конфиг с правами 0600.

В backoff появился джиттер, Retry-After учитывается

Заголовок раздела «В backoff появился джиттер, Retry-After учитывается»

v1 спал ровно 1, 2 и 4 секунды, поэтому флот job’ов, упёршихся в лимит одновременно, ретраил синхронно и снова вызывал лимит. v2 добавляет до 50% джиттера. Ещё он читает заголовок Retry-After, когда в теле нет parameters.retry_after, — именно так отвечают прокси и self-hosted Bot API.

Имя входа сохранено; в запросе теперь link_preview_options.is_disabled, потому что плоское поле Telegram задепрекейтил.

Внутри Actions allowlist прежний: api.telegram.org плюс loopback, всё остальное игнорируется с предупреждением. В CLI --api-base принимает любой http(s) URL, потому что self-hosted Bot API — легитимный сценарий, и это явный выбор пользователя.

  • Имена и дефолты всех входов, кроме семантики chat_id и edit_message_id выше.
  • Имена всех выходов.
  • Приоритет шаблонов: message > template_<status> > message_template > встроенный.
  • message по-прежнему дословный: без подстановки и без экранирования.
  • Набор плейсхолдеров, шаблон по умолчанию и правила экранирования каждого parse mode.
  • Exit-коды 10–16. Коды 20 (UNSUPPORTED_BASH) и 22 (MISSING_DEPENDENCY) принадлежали bash-рантайму; v2 их не выдаёт, и никто другой эти номера не займёт.
  • Семантика notify_on, включая сокращения any и all.
  • CLI: notiflow send | edit | render | whoami | completions, ставится из Homebrew, crates.io или релизного архива.
  • Поддержка Windows-раннеров.
  • verify_signature — проверка cosign при скачивании бинаря.
  • Exit-коды 17 (CONFIG_ERROR), 18 (INVALID_ARGUMENT) и 30 (IO_ERROR) для сценариев, до которых добирается CLI и почти не добирается Action.

Тег v1 продолжает указывать на bash-реализацию в ветке v1.x, которая получает только security-фиксы. Bash-исходники живут и в дереве v2, в tests/parity/v1/, где служат эталоном, с которым parity-набор сравнивается на каждом коммите.