Исходная ситуация
Текст готов, картинка готова, ссылка готова. Дальше начинается ручной круг по каналам.
У каждого канала свои требования. Telegram — своя разметка и своё поведение с превью ссылки. VK — своя разметка и свой способ приложить изображение. LinkedIn обрезает пост после первых строк и прячет остальное под «ещё», поэтому первый абзац переписывают под удар. Короткие форматы жёстко ограничены по длине, и текст приходится резать. Сайт и рассылка — ещё два варианта.
Один текст переписывается столько раз, сколько каналов. Картинка загружается столько же раз. Ссылка вставляется руками — и часть публикаций уходит без метки источника, потому что метку набирали по памяти.
Вторая потеря — рассинхрон. Опечатку нашли после публикации, исправили в двух каналах из четырёх, в остальных живёт старая версия. Через месяц никто не помнит, где какая.
Третья — время выхода. Ручная публикация случается тогда, когда у человека дошли руки. Обычно это не то время, когда канал читают.
Четвёртая — сбои. Сеть отвалилась на середине, ответ API не дошёл, человек нажал «отправить» ещё раз. В канале два одинаковых поста, и удалять их приходится тоже руками.
Фоном идёт переключение между вкладками, у каждой своя авторизация и свой каприз.
Что смотрели
Масштаб задачи меряют фактами, а не ощущением «долго».
- Время от готового текста до последней публикации. Засечь по часам на трёх-пяти материалах подряд.
- Фактическое покрытие каналов. Выгрузить историю публикаций за месяц по каждому каналу и сверить по тексту: сколько материалов дошло везде, сколько застряло в одном-двух. Намерение публиковать везде и факт расходятся сильно.
- Доля ссылок без меток. Считается по той же выгрузке: ссылка есть, параметров нет.
- Разброс времени публикации одного материала — от первого канала до последнего.
- Число дублей и снятых постов.
Отдельная скучная работа на пару часов — таблица требований каналов: лимит символов, поддерживаемая разметка, обязательна ли картинка, как ведёт себя превью ссылки, что происходит с несколькими изображениями, есть ли API и что он умеет, как устроена авторизация и на сколько живёт токен. Эта таблица определяет весь дальнейший дизайн, и без неё конвейер собирается вслепую.
Признак, что задача вообще решается автоматизацией: ядро текста совпадает между каналами больше чем наполовину, а различия сводятся к формату. Если под каждый канал пишется свой материал по существу — нужен редактор, а не конвейер.
Что поменяли
1. Один исходник. Файл в репозитории или строка в таблице. Поля: ядро текста, картинки, ссылка без параметров, желаемое окно публикации, список каналов и — только там, где без этого никак — переопределение текста под конкретный канал. Переопределение допускается, но не по умолчанию, и лежит рядом с ядром в том же файле.
2. Адаптер на канал. Небольшая функция: на входе исходник, на выходе готовая полезная нагрузка для API. Внутри живёт всё капризное — обрезка до лимита по границе предложения, конвертация разметки, решение «прикладывать картинку или отдать превью ссылке», порядок загрузки медиа. Появился новый канал — добавляется один файл, остальные не трогают.
3. Очередь. Между «материал готов» и «пост опубликован» ставится хранилище состояния: JSON-файл, таблица, что угодно с надёжной записью. Одна запись — один материал в один канал. Поля: статус (queued / sending / sent / failed), число попыток, время следующей, внешний идентификатор публикации, текст ошибки.
4. Ключ идемпотентности. Составляется из идентификатора материала и имени канала. Отправка проверяет ключ перед запросом и пишет результат сразу после ответа. Повтор после сбоя не создаёт вторую публикацию — на записи уже стоит sent и внешний id.
5. Планировщик. cron, launchd, systemd timer — любой. Раз в несколько минут забирает записи, у которых наступило время. Выход материала перестаёт зависеть от того, когда человек добрался до вкладки.
6. Ссылки собирает код. Одна функция строит адрес с метками из полей исходника и имени канала. Руками ссылку в текст не вписывают вообще — тогда доля публикаций без меток падает до нуля не усилием воли, а конструкцией.
7. Двухфазные API. Часть платформ публикует в два шага: создать контейнер с содержимым, дождаться обработки, опубликовать по его идентификатору. Ожидание живёт внутри адаптера, с таймаутом и внятной ошибкой вместо бесконечного цикла.
8. Пишущее действие отделено от читающего. Команда, которая реально публикует, вызывается явно и отдельно. Просмотр очереди, сухой прогон, проверка форматов безопасны и вызываются сколько угодно.
9. Отчёт после. Раз в неделю по сохранённым внешним идентификаторам собирается, что вышло и как разошлось. Метки в ссылках дают срез по каналам в общей аналитике без отдельного дашборда.
Что не трогали
Написание текста. Конвейер доставляет, но не сочиняет. Автопостинг, склеенный с автогенерацией, довольно быстро превращает канал в шум, а подписчиков — в бывших.
Комментарии и ответы читателям. Остаются в родных приложениях. Ответ на комментарий — разговор, доставлять его по расписанию нечего.
Аналитику каналов. Сводить статистику всех площадок в один дашборд первым шагом не нужно: меток в ссылках хватает, чтобы понять, откуда пришли.
Рендер изображений. Если шаблон или скрипт для картинок уже собран, он остаётся как есть, конвейер берёт готовый файл.
Ручной режим. Возможность опубликовать один пост в один канал руками мимо очереди сохраняется — с пометкой в исходнике, чтобы недельный отчёт не врал.
Обратимость
- Сухой прогон. Флаг, при котором адаптеры отрабатывают полностью, результат пишется в лог, запрос к API не уходит. Первый прогон нового канала — всегда так.
- Пауза очереди. Поле в конфиге или файл-флаг: планировщик просыпается, видит паузу и уходит. Останавливает всё за секунду, ничего не ломая.
- Снятие расписания. Возвращает прежний ручной режим целиком: исходники остаются обычными файлами, публиковать можно копипастом, как раньше.
- Удаление опубликованного автоматика не умеет. Ошибочный пост снимает человек в интерфейсе канала, запись в очереди помечается вручную. Право удалять чужие публикации скрипту не выдают.
- Токены и ключи лежат отдельно от кода и отзываются в кабинете платформы за минуту. Отозванный токен останавливает один канал, не задевая остальные.
Границы
Каналы без API. Часть платформ либо не даёт публиковать программно, либо открывает доступ только бизнес-аккаунтам после проверки. Обход через управление браузером ломается при первом же редизайне и ловит блокировки.
Согласование. Если пост проходит юриста, заказчика или комплаенс, узкое место — согласование, а не публикация. Конвейер тут ускоряет последний шаг из пяти.
Форматы с ручным трудом. Видео под каждый канал со своим соотношением сторон и субтитрами, карусели, сторис — автоматизируется доставка файла, но не его подготовка.
Малые объёмы. Пара материалов в месяц: сборка и поддержка конвейера не окупится, а токены протухнут между публикациями.
Высокая цена ошибки. Официальный канал компании, где неверный пост стоит дорого: очередь остаётся, но последний шаг делает человек по кнопке.
Разные аудитории. Если каналы читают разные люди с разными задачами, единый исходник даёт усреднённый текст, который не работает нигде.
Правило одной строкой: публикует очередь, а не человек и не модель — один исходник, адаптер на канал, идемпотентный ключ на каждую попытку.
