Как я настроил автопостинг из WordPress в MAX и почему это оказалось проще, чем кажется

Я давно привык жить по правилам DevOps: один узел это хорошо, два узла это спокойный сон. С контентом та же история. Если весь трафик и подписчики завязаны на одну площадку, любая нестабильность превращается в головную боль. Поэтому я решил сделать нормальный запасной канал и автоматизировать публикации.

В качестве второй площадки я выбрал MAX. Дальше цель простая: пост вышел на сайте, пост улетел в канал. Без копипаста, без «сейчас, минутку, я руками перекину».

В итоге я собрал рабочую схему и дописал свой плагин под WordPress. Ниже показываю путь от создания канала и получения токена до очереди отправки и загрузки картинок через API.

Что я хотел получить по факту

Мне нужна была не «интеграция ради интеграции», а практичный автопостинг.

1) Публикация новых записей из WordPress в канал MAX.

2) Нормальный внешний вид: заголовок, короткий текст, ссылка, кнопка «Читать».

3) Картинка к посту, чтобы лента выглядела как медиа, а не как лог с сервера.

4) Очередь, повторы и логи. Чтобы редактор нажал «Опубликовать», а дальше уже работала автоматика, даже если сеть моргнула.

Этап 1. Канал и доступ к партнёрской панели

С MAX всё начинается с партнёрской панели. Там создаётся канал и там же выдаётся токен для интеграции. Без токена дальше делать нечего.

Стартовая страница партнёрской панели MAX
Стартовая страница партнёрской панели.

Важно: я сразу завёл отдельного бота именно под автопостинг. Не смешиваю в одну кучу «бот отвечает людям» и «бот постит новости». Так проще поддерживать и безопаснее.

Этап 2. Токен, права и главный нюанс с админством

После создания бота мне понадобились две вещи: токен и идентификатор канала (Chat ID). Токен это ключ от двери. Chat ID это адрес, куда стучаться.

Самый частый затык на старте выглядит так: токен есть, запросы уходят, а посты не публикуются. Причина банальная. Бот должен быть администратором канала. Если он просто добавлен «как участник», права на публикацию могут не сработать.

Окно выдачи токена интеграции MAX
Окно, где выдаётся токен интеграции.

Я это проверял просто: отправлял тестовое сообщение из админки плагина. Если тест уходит, значит токен рабочий, Chat ID правильный, права выставлены.

Этап 3. Админка WordPress и настройки плагина

Я сделал отдельную страницу настроек в админке WordPress. Минимум полей, максимум пользы: Token, Chat ID, галка «картинка», галка «кнопка», плюс кнопка «Отправить тест».

Настройки плагина автопостинга в WordPress
Настройки плагина в WordPress.

Токен я храню в настройках сайта и не свечу нигде на фронте. Доступ к странице настроек только у админов. На сохранение настроек повесил nonce, потому что я люблю, когда чужие руки не трогают мои ключи.

Как устроена отправка постов и почему я сделал очередь

Сначала я пробовал отправлять пост «в момент публикации». Работает, пока всё идеально. Потом ловишь ситуацию: сеть зависла, API ответило медленно, редактор висит, WordPress думает, что жизнь боль, и ты начинаешь ненавидеть всё живое.

Поэтому я сделал очередь.

При публикации поста я не отправляю его сразу. Я ставлю метку «в очереди» (post meta) и складываю ID в список. Дальше по расписанию это разгребает фоновая задача (WP Cron или системный cron, как настроишь).

Плюсы простые: админка не тормозит, посты уходят стабильно, при ошибке можно повторить попытку, логи сохраняются.

Создание канала MAX в приложении
Момент создания канала, чтобы было видно живьём.

Картинка к посту: почему просто URL это не всегда надёжно

Самая «весёлая» часть была с картинками. Хотелось сделать по-взрослому: брать featured image из WordPress и прикреплять к сообщению так, чтобы в MAX она отображалась нормально.

Ссылкой на картинку можно жить, но это не всегда красиво и не всегда стабильно. Поэтому я пошёл по пути загрузки файла через multipart-запрос. Беру физический файл с сервера, отправляю в API загрузки, получаю токен, дальше прикрепляю токен к сообщению.

Да, это чуть больше кода. Зато результат предсказуемый, и внешний вид постов одинаковый.

Пост с картинкой в канале MAX

Форматирование: один пост или два сообщения

Я быстро понял, что у формата «картинка плюс длинная простыня» есть побочные эффекты. Где-то подпись обрезалась, где-то выглядело не так, как я ожидал на телефоне.

Поэтому я сделал два режима.

Режим 1: одно сообщение, где картинка и подпись вместе.

Режим 2: сначала улетает картинка, следом улетает текст с заголовком, анонсом и кнопкой «Читать». На практике второй вариант выглядит аккуратнее и стабильнее.

Пример логики отправки (упрощённо)

Ниже упрощённый пример того, как выглядит «ядро» отправки: собрал текст, загрузил картинку, сформировал payload, отправил запрос. В реальном плагине вокруг этого ещё очередь, повторы и логирование.

public static function send_post_to_max(int $post_id): bool {
    $s = self::get_settings();

    $title = get_the_title($post_id);
    $url   = get_permalink($post_id);

    $raw = get_post_field('post_content', $post_id);
    $text = wp_trim_words(wp_strip_all_tags($raw), 45);

    // 1) Загружаю featured image и получаю токен (если есть картинка)
    $img_token = null;
    if (!empty($s['with_image'])) {
        $img_token = self::upload_featured_image_and_get_token($post_id);
    }

    // 2) Формирую сообщение
    $msg = "<b>" . esc_html($title) . "</b>\n\n"
         . esc_html($text) . "\n\n"
         . esc_html($url);

    $payload = [
        'chat_id' => $s['chat_id'],
        'text'    => $msg,
        'attachments' => []
    ];

    // 3) Прикрепляю картинку по токену
    if ($img_token) {
        $payload['attachments'][] = [
            'type' => 'image',
            'payload' => ['token' => $img_token]
        ];
    }

    // 4) Кнопка Читать
    if (!empty($s['with_button'])) {
        $payload['attachments'][] = [
            'type' => 'inline_keyboard',
            'payload' => [
                'buttons' => [
                    [
                        [
                            'type' => 'link',
                            'text' => 'Читать',
                            'url'  => $url
                        ]
                    ]
                ]
            ]
        ];
    }

    // 5) Отправляю
    $resp = wp_remote_post(self::api_url('/messages.send'), [
        'timeout' => 15,
        'headers' => [
            'Authorization' => 'Bearer ' . $s['token'],
            'Content-Type'  => 'application/json; charset=utf-8',
        ],
        'body' => wp_json_encode($payload, JSON_UNESCAPED_UNICODE),
    ]);

    return !is_wp_error($resp) && wp_remote_retrieve_response_code($resp) >= 200
           && wp_remote_retrieve_response_code($resp) < 300;
}

Да, API endpoints и названия методов могут отличаться в зависимости от текущей версии. Суть тут в архитектуре: не блокировать публикацию, грузить картинку файлом, логировать ответ, уметь повторить.

Итог

В итоге я получил нормальную связку: WordPress публикует, плагин ставит задачу в очередь, фоновая отправка пушит пост в MAX, а подписчики видят красивый пост с кнопкой. Это ровно тот случай, когда один вечер разработки экономит кучу времени дальше.

Дальше я планирую допилить массовую публикацию старых записей с выбором «с новых к старым» и сделать более удобный экран очереди. Чтобы канал можно было наполнить за час, а не за неделю.

Источники и ссылки


Комментарии загружаются…