Шесть окон и одна мысль

Агентство вело блог собственника сразу на шести площадках: Telegram, MAX, ВК, vc.ru, Дзен и Хабр. Одну мысль приходилось переписывать шесть раз в нескольких окнах. К каждому посту ещё нужна обложка. Плановый ритм: 3-5 постов в неделю. При шести площадках это до 30 адаптаций в неделю руками.

Второй слой боли был в контроле. Черновики пишет ИИ, выпускать их без вычитки человеком нельзя. Показывать сырьё в чате и потом копипастить по редакторам площадок означало тот же ручной труд. Блог считается по заявкам и регистрациям, лайки тут ничего не решают.

  • Telegram понимает только узкое подмножество HTML
  • ВК не рендерит разметку и показывает служебные звёздочки читателю
  • MAX не знает подчёркивание и спойлер
  • у Дзена и Хабра публичного API для публикации нет в принципе
  • целевая длина расходится в разы: от 800 знаков в MAX до 20 000 на Хабре

Единицей работы стала идея

Одна тема: одна папка. Внутри отдельный markdown-файл на каждую площадку со своим фронтматтером: статус, дата публикации, обложка. Тексты и медиа лежат файлами в постоянном томе. Они переживают передеплой и читаются глазами без сервиса. SQLite держит только журнал публикаций и аналитику.

Реестр площадок собран в одном месте: 10 позиций. Шесть постовых и четыре сценарных под видео (YouTube, VK Видео, Rutube, Shorts). У каждой прописаны целевая длина и способ выпуска.

Для ИИ-агента сделали отдельный вход. Агент кладёт готовую идею одним POST-запросом: заголовок, описание, теги, словарь «площадка → текст» и при желании картинки. Сервис раскладывает это по файлам и рисует шаблонную обложку 1200×1200 на каждый постовый текст, подбирая кегль заголовка по его длине. Ошибка генерации обложки не роняет создание идеи, обложку всегда можно пересобрать.

!
Кнопка не должна врать

Копирование текста в буфер сервис намеренно не считает публикацией. Статус «опубликовано» для Дзена и Хабра ставит человек отдельным действием. Тот же урок вылез в истории с обложками: успешный статус без проверки результата опаснее честной ошибки.

Три площадки автоматом, три полуавтоматом

Публикатор выбирается по площадке. Каждый исход пишется в журнал: статус, ссылка на пост, внешний id или текст ошибки.

  • Telegram: markdown переводится в поддерживаемое подмножество HTML, длинный текст режется по абзацам под лимит 4096 знаков. Картинка идёт подписью, если текст влезает в 1024 знака, иначе фото и текст уходят раздельно
  • MAX: текст режется под 4000 знаков, обложка заливается тремя шагами и цепляется к первому сообщению, неподдерживаемые маркеры снимаются
  • ВК: разметка вычищается полностью, фото проходит через getWallUploadServer → upload → saveWallPhoto и цепляется вложением
  • Дзен и Хабр: публичного API для публикации не существует, поэтому кнопка отдаёт готовый текст с подсказкой, куда его вставить
  • vc.ru: площадка в реестре есть, публикатор пока отключён, кнопка в интерфейсе закрыта

Что видит редактор

Список идей с состояниями: разобрать, в очереди, выпущено. В редакторе поста есть превью, счётчик знаков против целевой длины площадки, тулбар разметки, эмодзи-пикер со вставкой по курсору и кнопка «Посмотреть пост» на уже опубликованное.

Обложку можно взять из трёх источников: шаблон, генерация нейросетью Kandinsky через FusionBrain, своё фото из галереи. В галерею идеи файлы грузятся пачкой до 30 штук по 15 МБ, там же просмотр, скачивание и обрезка по пресетам. Наружу медиа не светятся, файлы отдаются только залогиненным.

Планировщик раскладывает посты по календарю массово: галочками выбираются пары «идея и площадка», задаются дни недели, время старта и количество в день, слоты внутри дня идут через 4 часа. Предпросмотр раскладки показывается до применения. Фоновый воркер раз в 60 секунд обходит идеи и выпускает наступившие по времени. При неудаче он откатывает пост в состояние «готов» и шлёт сообщение о провале в Telegram. Туда же приходят новая идея и успешная автопубликация.

Грабли

Самая дорогая история: кеш-бастер. Интерфейс дописывал к пути обложки «?v=время», чтобы браузер показывал свежую картинку. Публикаторы читали обложку с диска по этому же пути. Файла с таким именем не существовало, и посты уходили без картинки. Внешне всё выглядело успешно, галочка «опубликовано» стояла. Починили срезанием query-части во всех трёх публикаторах сразу.

Одна грабля осталась незакрытой, говорим честно: папка с обложками пока не вынесена в постоянный том. При пересоздании контейнера старые обложки могут исчезнуть. Галерея медиа от этого защищена, она лежит в томе.

  • MAX: механику картинок вскрывали опытным путём на боевом токене. Сначала запрос за адресом загрузчика, потом заливка файла, потом объект цепляется вложением. Для публикации в канал обязателен флаг уведомления, а токен идёт в заголовке авторизации без слова Bearer
  • ВК: токен сообщества умеет постить и не умеет удалять. Тестовые посты на публичной стене чистили руками
  • Сборка падала на пустом месте. Next поднимает несколько воркеров, все разом лезли в свежесозданную SQLite и получали «база занята». Вылечили таймаутом ожидания блокировки в 5 секунд
  • Инцидент «пропал лендинг» оказался ложным. Корневая страница редиректила гостя сразу на форму входа мимо лендинга, файл всё это время лежал на месте
  • Продуктовая: первая версия генерации всегда делала версии под все площадки, и короткая заметка на три абзаца раздувалась в четыре поста. Набор версий привязали к формату материала

Сроки и стек

Первые файлы датированы 15 мая 2026 года. 15 июня проект переехал под git и на новый сервер, 17 июня встали контейнер и сборка в GitHub Actions. Публикаторы MAX и ВК появились 23 июня, боевые токены подключили 25 и 27 июня. Список правок редактора закрыли 13 июля. Около двух месяцев от первых файлов до закрытого списка, из них месяц ушёл на интеграции.

Под капотом Next.js 16 (App Router, standalone-сборка), React 19, TypeScript, Tailwind v4. SQLite через better-sqlite3 в режиме WAL держит журнал публикаций. Тексты в markdown с фронтматтером, разбор через gray-matter. Обложки рисует связка satori и resvg прямо на сервере. Деплой идёт push в main: GitHub Actions собирает образ, реестр ghcr отдаёт его серверу по таймеру. Данные вынесены в постоянные тома, снаружи nginx и HTTPS. Сервис крутится внутри студии, публичного входа у него нет, поэтому адрес мы не даём.

Частые вопросы

Как одна идея превращается в шесть постов?

Редактор заводит тему один раз. Одна тема это одна папка, внутри отдельный markdown-файл на каждую из шести постовых площадок со своим статусом, датой и обложкой. Набор версий привязан к формату материала, поэтому короткая заметка не раздувается в четыре поста. Всего в реестре 10 позиций: шесть постовых и четыре сценарных под видео.

Какие площадки выходят автоматом, а какие вручную?

Автоматом по API уходят три из шести: Telegram, MAX и ВК. У Дзена и Хабра публичного API для публикации нет, поэтому кнопка отдаёт готовый текст с подсказкой, куда его вставить. vc.ru в реестре есть, публикатор пока отключён. Копирование в буфер сервис намеренно не считает публикацией, статус «опубликовано» для Дзена и Хабра ставит человек отдельным действием.

Зачем переписывать текст под каждую площадку?

Разметка и лимиты у площадок разные. Telegram понимает узкое подмножество HTML и режется по абзацам под 4096 знаков, MAX под 4000, ВК разметку не рендерит и показывает читателю служебные звёздочки. Целевая длина расходится в разы: от 800 знаков в MAX до 20 000 на Хабре. При ритме 3-5 постов в неделю руками это давало до 30 адаптаций.

Откуда берутся обложки?

Сервис рисует шаблонную обложку 1200×1200 на каждый постовый текст и подбирает кегль заголовка под его длину. Можно взять генерацию нейросетью Kandinsky через FusionBrain или своё фото из галереи, куда файлы грузятся пачкой до 30 штук по 15 МБ. Ошибка генерации обложки не роняет создание идеи, обложку всегда можно пересобрать. Наружу медиа не светятся, файлы отдаются только залогиненным.

Как устроено расписание и что будет при сбое?

Планировщик раскладывает посты массово: галочками выбираются пары «идея и площадка», задаются дни недели, время старта и количество в день, слоты внутри дня идут через 4 часа. Раскладка показывается до применения. Фоновый воркер раз в 60 секунд обходит идеи и выпускает наступившие по времени. При неудаче он возвращает пост в состояние «готов» и шлёт сообщение о провале в Telegram.