Weixin (WeChat)

Команды установки Python-зависимостей на этой странице рассчитаны на чекаут исходников, подготовленный через PM. После изменения зависимостей заново активируйте чекаут и перезапустите Hermes.

Подключите Hermes к WeChat (微信) — личному мессенджеру от Tencent. Адаптер работает через iLink Bot API Tencent для персональных аккаунтов WeChat; это не то же самое, что WeCom (корпоративный WeChat). Сообщения приходят по long-polling, поэтому публичный эндпоинт или вебхук не нужны.

инфо

Этот адаптер рассчитан на личные аккаунты WeChat (微信). Если вам нужен корпоративный WeChat, смотрите адаптер WeCom.

внимание
Идентичность iLink-бота — обычные группы WeChat могут не работать

Вход по QR-коду связывает Hermes с идентичностью iLink-бота (например, a5ace6fd482e@im.bot), а не с полноценным управляемым из скриптов обычным личным аккаунтом WeChat. Из этого вытекает следующее:

  • Идентичность iLink-бота, как правило, нельзя пригласить в обычные группы WeChat так, как это делается с обычным контактом.
  • iLink обычно не доставляет события обычных групп WeChat (включая @-упоминания личного аккаунта, использованного для входа по QR) в шлюз для большинства аккаунтов бот-типа.
  • @-упоминание личного аккаунта WeChat, которым вы сканировали QR-код, — это не то же самое, что @-упоминание iLink-бота: бот живёт как отдельная идентичность.
  • Настройки WEIXIN_GROUP_POLICY / WEIXIN_GROUP_ALLOWED_USERS (см. ниже) срабатывают только тогда, когда iLink действительно возвращает групповые события для вашего типа аккаунта. Если этого не происходит, групповые сообщения никогда не дойдут до Hermes, какую бы политику вы ни задали.

На практике у большинства установок стабильно работают только личные сообщения к iLink-боту. Если доставка из групп не заработала после настройки, ограничение лежит на стороне iLink, а не в Hermes. При запуске шлюз пишет WARNING всякий раз, когда WEIXIN_GROUP_POLICY выставлен в любое значение, отличное от disabled.

Требования

  • Личный аккаунт WeChat
  • Python-пакеты: aiohttp и cryptography
  • Отрисовка QR в терминале идёт в комплекте, если Hermes установлен с extra-пакетом messaging

Установите нужные зависимости:

# Включает aiohttp и поддержку QR-кода в терминале
python -c "import pm; pm.sync_venv(['messaging'], explicit=True)"

Установка

1. Запустите мастер настройки

Проще всего подключить аккаунт WeChat через интерактивную настройку:

hermes gateway setup

Когда появится выбор, укажите Weixin. Мастер сделает следующее:

  1. Запросит QR-код у iLink Bot API
  2. Покажет QR-код в терминале (или даст URL)
  3. Подождёт, пока вы отсканируете код мобильным приложением WeChat
  4. Попросит подтвердить вход на телефоне
  5. Автоматически сохранит учётные данные аккаунта в ~/.hermes/weixin/accounts/

После подтверждения вы увидите сообщение вроде такого:

微信连接成功,account_id=your-account-id

Мастер сохранит account_id, token и base_url, так что прописывать их вручную не придётся.

2. Задайте переменные окружения

После первого входа по QR пропишите как минимум ID аккаунта в ~/.hermes/.env:

WEIXIN_ACCOUNT_ID=your-account-id

# Опционально: переопределить токен (обычно сохраняется сам после входа по QR)
# WEIXIN_TOKEN=your-bot-token

# Опционально: ограничить доступ
WEIXIN_DM_POLICY=open
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2

# Опционально: вернуть прежнее поведение с разбиением многострочных сообщений
# WEIXIN_SPLIT_MULTILINE_MESSAGES=true

# Опционально: домашний канал для cron и уведомлений
WEIXIN_HOME_CHANNEL=chat_id
WEIXIN_HOME_CHANNEL_NAME=Home

3. Запустите шлюз

hermes gateway

Адаптер восстановит сохранённые учётные данные, подключится к iLink API и начнёт long-polling за сообщениями.

Возможности

  • Транспорт на long-poll — публичный эндпоинт, вебхук или WebSocket не требуются
  • Вход по QR-коду — подключение сканированием через hermes gateway setup
  • Личные сообщения — настраиваемые политики доступа; групповая переписка зависит от того, доставляет ли iLink групповые события для подключённой идентичности (для аккаунтов iLink-бота это часто не так — см. предупреждение выше)
  • Поддержка медиа — картинки, видео, файлы и голосовые сообщения
  • Шифрованный CDN на AES-128-ECB — автоматическое шифрование и расшифровка при любой передаче медиа
  • Хранение контекстных токенов — преемственность ответов сохраняется на диске и переживает перезапуски
  • Форматирование Markdown — Markdown сохраняется (заголовки, таблицы, блоки кода), поэтому клиенты WeChat с поддержкой Markdown отрисовывают его как родной
  • Умная нарезка сообщений — пока текст укладывается в лимит, он остаётся одним пузырём; разбиваются по логическим границам только слишком длинные сообщения
  • Индикаторы набора текста — пока агент обрабатывает запрос, в клиенте WeChat виден статус «печатает…»
  • Защита от SSRF — исходящие URL медиа проверяются перед скачиванием
  • Дедупликация сообщений — скользящее окно в 5 минут не даёт обработать сообщение дважды
  • Автоповтор с задержкой — восстанавливается после временных ошибок API

Параметры конфигурации

Задаются в config.yaml в секции platforms.weixin.extra:

КлючПо умолчаниюОписание
account_id—ID аккаунта iLink-бота (обязательно)
token—Токен iLink-бота (обязательно, сохраняется сам после входа по QR)
base_urlhttps://ilinkai.weixin.qq.comБазовый URL iLink API
cdn_base_urlhttps://novac2c.cdn.weixin.qq.com/c2cБазовый URL CDN для передачи медиа
dm_policyopenДоступ к личным сообщениям: open, allowlist, disabled, pairing
group_policydisabledДоступ к группам: open, allowlist, disabled
allow_from[]ID пользователей с доступом к личным сообщениям (при dm_policy=allowlist)
group_allow_from[]ID групп с доступом (при group_policy=allowlist)
split_multiline_messagesfalseЕсли true, многострочные ответы разбиваются на несколько сообщений (прежнее поведение). Если false, многострочный ответ остаётся одним сообщением, пока не превысит лимит длины.
text_batch_delay_seconds0.3Период тишины (в секундах, максимум 2.0) перед тем, как накопленная очередь быстрых текстовых сообщений уйдёт одним объединённым запросом. iLink доставляет сообщения по отдельности, и этот дебаунс избавляет от запуска агента на каждый фрагмент. Поставьте 0, чтобы отправлять каждое сообщение сразу.
text_batch_split_delay_seconds1.0Увеличенная задержка отправки (максимум 4.0; не меньше text_batch_delay_seconds) для случая, когда последний фрагмент близок к порогу разбиения (длинные сообщения, которые iLink мог нарезать).

Политики доступа

Политика личных сообщений

Определяет, кто может писать боту в личку:

ЗначениеПоведение
openНаписать боту может кто угодно (по умолчанию)
allowlistПисать могут только пользователи из allow_from
disabledВсе личные сообщения игнорируются
pairingРежим сопряжения (для первичной настройки)
WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2

WEIXIN_ALLOWED_USERS — это входящий фильтр, а не система приглашений. Вход по QR связывает с Hermes одну идентичность iLink-бота. Другие люди не сканируют QR-код Hermes собственными аккаунтами — им нужно написать подключённому iLink-боту или контакту через WeChat, и Hermes обработает личное сообщение только тогда, когда Weixin-ID отправителя есть в WEIXIN_ALLOWED_USERS.

Рабочий порядок настройки такой:

  1. Один раз свяжите Hermes командой hermes gateway setup и запишите подключённый аккаунт iLink-бота.
  2. Попросите каждого разрешённого пользователя отправить личное сообщение этому боту/контакту.
  3. Возьмите ID отправителя из логов шлюза или из полезной нагрузки входящего события.
  4. Добавьте эти ID в WEIXIN_ALLOWED_USERS и перезапустите шлюз.

Если с Hermes общается только тот аккаунт, что сканировал QR-код, проверьте, что остальные пишут именно идентичности iLink-бота, а не личному аккаунту WeChat, которым выполнялся вход по QR. iLink-бот — отдельная идентичность, а маршрутизация обычных контактов и групп WeChat может быть ограничена поведением iLink со стороны Tencent.

Политика групп

Определяет, в каких группах бот отвечает, когда iLink доставляет групповые события для подключённой идентичности. Для идентичностей iLink-бота, полученных входом по QR (например, ...@im.bot), групповые события обычно не доставляются вовсе, поэтому эта политика может ни на что не влиять — см. предупреждение об ограничении iLink-бота в начале страницы.

ЗначениеПоведение
openБот отвечает во всех группах (если события доставляются)
allowlistБот отвечает только в группах, чьи ID перечислены в group_allow_from (если события доставляются)
disabledВсе групповые сообщения игнорируются (по умолчанию)
WEIXIN_GROUP_POLICY=allowlist
# ВНИМАНИЕ: это список ID групповых чатов через запятую, а НЕ ID участников,
# хотя в имени переменной и есть слово "USERS". Учитывайте это при настройке.
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2
заметка

По умолчанию для Weixin политика групп — disabled (в отличие от WeCom, где по умолчанию open). Так задумано: личный аккаунт WeChat может состоять во множестве групп, а идентичности iLink-бота, как правило, вообще не получают обычные групповые сообщения WeChat. При запуске шлюз пишет WARNING, если вы выставите WEIXIN_GROUP_POLICY в любое значение, кроме disabled.

Поддержка медиа

Входящие (приём)

Адаптер принимает медиавложения от пользователей, скачивает их с CDN WeChat, расшифровывает и кэширует локально для обработки агентом:

ТипКак обрабатывается
ИзображенияСкачиваются, расшифровываются AES и кэшируются как JPEG.
ВидеоСкачивается, расшифровывается AES и кэшируется как MP4.
ФайлыСкачиваются, расшифровываются AES и кэшируются. Исходное имя файла сохраняется.
ГолосЕсли есть текстовая расшифровка, она извлекается как текст. Иначе скачивается и кэшируется само аудио (формат SILK).

Цитируемые сообщения: медиа из цитируемых сообщений (на которые отвечают) тоже извлекается, чтобы у агента был контекст того, на что отвечает пользователь.

Шифрованный CDN на AES-128-ECB

Медиафайлы WeChat передаются через шифрованный CDN. Адаптер берёт это на себя незаметно для пользователя:

  • Входящие: шифрованное медиа скачивается с CDN по URL с encrypted_query_param, а затем расшифровывается алгоритмом AES-128-ECB по ключу для конкретного файла, который приходит в полезной нагрузке сообщения.
  • Исходящие: файлы шифруются локально случайным ключом AES-128-ECB, выгружаются на CDN, а в исходящее сообщение подставляется шифрованная ссылка.
  • Ключ AES — это 16 байт (128 бит). Ключи могут приходить как сырой base64 или в hex-кодировке; адаптер понимает оба формата.
  • Для этого нужен Python-пакет cryptography.

Настраивать ничего не надо — шифрование и расшифровка идут автоматически.

Исходящие (отправка)

МетодЧто отправляет
sendТекстовые сообщения с форматированием Markdown
send_image / send_image_fileНативные сообщения с картинкой (через выгрузку на CDN)
send_documentВложения-файлы (через выгрузку на CDN)
send_videoВидеосообщения (через выгрузку на CDN)

Любое исходящее медиа проходит через шифрованную выгрузку на CDN:

  1. Сгенерировать случайный ключ AES-128
  2. Зашифровать файл алгоритмом AES-128-ECB с дополнением PKCS#7
  3. Запросить URL для выгрузки у iLink API (getuploadurl)
  4. Выгрузить шифротекст на CDN
  5. Отправить сообщение с шифрованной ссылкой на медиа

Хранение контекстных токенов

iLink Bot API требует, чтобы вместе с каждым исходящим сообщением для конкретного собеседника возвращался context_token. Адаптер держит дисковое хранилище контекстных токенов:

  • Токены сохраняются по паре аккаунт+собеседник в ~/.hermes/weixin/accounts/<account_id>.context-tokens.json
  • При запуске ранее сохранённые токены восстанавливаются
  • Каждое входящее сообщение обновляет сохранённый токен для этого отправителя
  • В исходящие сообщения автоматически подставляется свежий контекстный токен

Благодаря этому ответы сохраняют преемственность даже после перезапусков шлюза.

Форматирование Markdown

Клиенты WeChat, подключённые через iLink Bot API, отрисовывают Markdown напрямую, поэтому адаптер сохраняет разметку, а не переписывает её:

  • Заголовки остаются заголовками Markdown (#, ##, …)
  • Таблицы остаются таблицами Markdown
  • Блоки кода остаются ограждёнными блоками кода
  • Лишние пустые строки вне блоков кода схлопываются до двойного перевода строки

Нарезка сообщений

Сообщение уходит одним чатом всякий раз, когда укладывается в лимит платформы. На части разбивается только слишком длинная полезная нагрузка:

  • Максимальная длина сообщения — 4000 символов
  • Сообщения в пределах лимита остаются целыми, даже если в них несколько абзацев или переносов строк
  • Слишком длинные сообщения разбиваются по логическим границам (абзацы, пустые строки, блоки кода)
  • Блоки кода по возможности сохраняются целиком (не режутся посередине, пока сам блок не превысит лимит)
  • Отдельные блоки сверх лимита уходят на логику усечения базового адаптера
  • Задержка в 0,3 с между фрагментами не даёт WeChat сбрасывать сообщения по rate-limit, когда отправляется сразу несколько частей

Индикаторы набора текста

Адаптер показывает в клиенте WeChat статус набора:

  1. Когда приходит сообщение, адаптер получает typing_ticket через API getconfig
  2. Тикеты набора кэшируются на 10 минут для каждого пользователя
  3. send_typing шлёт сигнал начала набора; stop_typing — сигнал остановки
  4. Пока агент обрабатывает сообщение, шлюз сам включает индикаторы набора

Соединение по long-poll

Для приёма сообщений адаптер использует HTTP long-polling (а не WebSocket):

Как это работает

  1. Подключение: проверка учётных данных и старт цикла опроса
  2. Опрос: вызов getupdates с таймаутом 35 секунд; сервер удерживает запрос, пока не придут сообщения или не истечёт таймаут
  3. Диспетчеризация: входящие сообщения раздаются параллельно через asyncio.create_task
  4. Буфер синхронизации: постоянный курсор синхронизации (get_updates_buf) сохраняется на диск, чтобы после перезапуска адаптер продолжил с нужной позиции

Поведение при повторах

При ошибках API адаптер действует по простой схеме повторов:

УсловиеПоведение
Временная ошибка (1-я–2-я)Повтор через 2 секунды
Повторяющиеся ошибки (3 и больше)Пауза 30 секунд, затем сброс счётчика
Сессия истекла (errcode=-14)Пауза 10 минут (может понадобиться повторный вход)
ТаймаутСразу новый опрос (нормальное поведение long-poll)

Дедупликация

Входящие сообщения дедуплицируются по ID сообщений с окном в 5 минут. Так двойная обработка не происходит при сетевых сбоях или перекрывающихся ответах опроса.

Блокировка токена

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

Все переменные окружения

ПеременнаяОбязательнаПо умолчаниюОписание
WEIXIN_ACCOUNT_ID✅—ID аккаунта iLink-бота (из входа по QR)
WEIXIN_TOKEN✅—Токен iLink-бота (сохраняется сам после входа по QR)
WEIXIN_BASE_URL—https://ilinkai.weixin.qq.comБазовый URL iLink API
WEIXIN_CDN_BASE_URL—https://novac2c.cdn.weixin.qq.com/c2cБазовый URL CDN для передачи медиа
WEIXIN_DM_POLICY—openПолитика доступа к личным сообщениям: open, allowlist, disabled, pairing
WEIXIN_GROUP_POLICY—disabledПолитика доступа к группам: open, allowlist, disabled
WEIXIN_ALLOWED_USERS—(пусто)ID пользователей через запятую для списка разрешений личных сообщений
WEIXIN_GROUP_ALLOWED_USERS—(пусто)ID групповых чатов через запятую (не ID участников) для списка разрешений групп. Имя переменной досталось от прежней версии — оно ждёт ID групп, а не пользователей.
WEIXIN_HOME_CHANNEL——ID чата для вывода cron и уведомлений
WEIXIN_HOME_CHANNEL_NAME—HomeОтображаемое имя домашнего канала
WEIXIN_ALLOW_ALL_USERS——Флаг уровня шлюза для разрешения всех пользователей (используется мастером настройки)

Устранение неполадок

ПроблемаРешение
Weixin startup failed: aiohttp and cryptography are requiredУстановите оба пакета: python -c "import pm; pm.sync_venv(['messaging'], explicit=True)"
Weixin startup failed: WEIXIN_TOKEN is requiredЗапустите hermes gateway setup, чтобы завершить вход по QR, или задайте WEIXIN_TOKEN вручную
Weixin startup failed: WEIXIN_ACCOUNT_ID is requiredЗадайте WEIXIN_ACCOUNT_ID в .env или запустите hermes gateway setup
Another local Hermes gateway is already using this Weixin tokenСначала остановите другой экземпляр шлюза — на один токен допускается лишь один поллер
Сессия истекла (errcode=-14)Срок действия вашей сессии вышел. Запустите hermes gateway setup заново и отсканируйте новый QR-код
Проактивная отправка (cron / уведомление) падает с ret=-2 errmsg=prepare failed или unknown errorУ собеседника протух context_token (от него давно не было входящих сообщений). Адаптер считает это устаревшей сессией, а не лимитом запросов, и один раз переотправляет без токена, так что сообщение всё равно доходит. Если iLink и после этого отвечает prepare failed (или сбрасывать было нечего — бот только что сопряжён), отправка (текста или медиа) падает с iLink sendmessage session not ready … the user must send the bot a message first (or re-pair); кулдаун лимита запросов для неё не включается. Только другие ответы -2 запускают backoff/кулдаун лимита запросов, и ошибка кулдауна содержит сырые ret/errcode/errmsg
QR-код истёк во время настройкиQR обновляется автоматически до 3 раз. Если он продолжает истекать, проверьте сетевое соединение
Бот не отвечает на личные сообщенияПроверьте WEIXIN_DM_POLICY — если стоит allowlist, отправитель должен быть в WEIXIN_ALLOWED_USERS
Бот игнорирует групповые сообщенияПо умолчанию политика групп — disabled. Поставьте WEIXIN_GROUP_POLICY=open или allowlist, но учтите: идентичности iLink-бота из входа по QR (...@im.bot), как правило, вообще не получают обычные групповые сообщения WeChat. Если в логах шлюза нет сырых входящих событий по группам, ограничение лежит на стороне iLink, а не в Hermes.
Скачивание/выгрузка медиа не работаетУбедитесь, что установлен cryptography. Проверьте сетевой доступ к novac2c.cdn.weixin.qq.com
Blocked unsafe URL (SSRF protection)Исходящий URL медиа указывает на приватный/внутренний адрес. Разрешены только публичные URL
Голосовые сообщения приходят как текстЕсли WeChat даёт расшифровку, адаптер берёт текст. Это ожидаемое поведение
Сообщения дублируютсяАдаптер дедуплицирует по ID сообщения. Если видите дубли, проверьте, не запущено ли несколько экземпляров шлюза
iLink POST ... HTTP 4xx/5xxОшибка API от сервиса iLink. Проверьте действительность токена и сетевую связность
QR-код не отрисовывается в терминалеПереустановите с extra-пакетом messaging: cd ~/.hermes/hermes-agent && python -c "import pm; pm.sync_venv(['messaging'], explicit=True)". Либо откройте URL, напечатанный над QR-кодом
перевод и редактура:

обновлено

⌕ ESC