Weixin (WeChat)

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

Этот адаптер предназначен для **личных аккаунтов WeChat** (微信). Если вам нужен корпоративный WeChat, воспользуйтесь [адаптером WeCom](./wecom.md).

warning iLink bot identity — обычные группы WeChat могут не работать QR-логин подключает Hermes к идентификатору iLink бота (например, a5ace6fd482e@im.bot), а не к полноценному скриптируемому личному аккаунту WeChat. Последствия:

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

Предварительные требования

Установите необходимые зависимости:

pip install aiohttp cryptography
# Опционально: для отображения QR-кода в терминале
pip install hermes-agent[messaging]

Настройка

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

Самый простой способ подключить ваш аккаунт WeChat — интерактивная настройка:

hermes gateway setup

Выберите Weixin при появлении запроса. Мастер выполнит следующее:

  1. Запросит QR-код у iLink Bot API

  2. Отобразит QR-код в вашем терминале (или предоставит URL)

  3. Дождётся, пока вы отсканируете QR-код мобильным приложением 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. Запустите gateway

hermes gateway

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

Возможности

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

Установите их в config.yaml в разделе platforms.weixin.extra:

Ключ По умолчанию Описание
account_id ID аккаунта iLink Bot (обязательно)
token Токен iLink Bot (обязательно, авто-сохраняется из QR-логина)
base_url https://ilinkai.weixin.qq.com Базовый URL iLink API
cdn_base_url https://novac2c.cdn.weixin.qq.com/c2c Базовый URL CDN для передачи медиа
dm_policy open Доступ к DM: open, allowlist, disabled, pairing
group_policy disabled Доступ к группам: open, allowlist, disabled
allow_from [] ID пользователей, разрешённых для DM (при dm_policy=allowlist)
group_allow_from [] ID групп, разрешённых (при group_policy=allowlist)
split_multiline_messages false Если true, разбивает многострочные ответы на несколько сообщений чата (устаревшее поведение). Если false, сохраняет многострочные ответы как одно сообщение, если они не превышают лимит длины.

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

Политика DM

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

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

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

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

Значение Поведение
open Бот отвечает во всех группах (если события доставляются)
allowlist Бот отвечает только в группах из списка group_allow_from (если события доставляются)
disabled Все групповые сообщения игнорируются (по умолчанию)
WEIXIN_GROUP_POLICY=allowlist
# ПРИМЕЧАНИЕ: это список ID групповых чатов, разделённых запятыми, а НЕ ID пользователей,
# несмотря на то, что имя переменной содержит "USERS". Учтите это при настройке.
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2
Политика групп по умолчанию — `disabled` для Weixin (в отличие от WeCom, где по умолчанию `open`). Это сделано намеренно — личные аккаунты WeChat могут быть во многих группах, а iLink бот-идентификаторы обычно вообще не могут получать обычные групповые сообщения WeChat. Gateway логирует `WARNING` при запуске, если вы устанавливаете `WEIXIN_GROUP_POLICY` в любое значение, отличное от `disabled`.

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

Входящие (получение)

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

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

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

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

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

Никакой настройки не требуется — шифрование и дешифрование происходят автоматически.

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

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

Все исходящие медиа проходят через процесс шифрованной загрузки на CDN:

  1. Генерация случайного ключа AES-128

  2. Шифрование файла с помощью AES-128-ECB + PKCS#7 padding

  3. Запрос URL для загрузки у iLink API (getuploadurl)

  4. Загрузка шифротекста на CDN

  5. Отправка сообщения со ссылкой на зашифрованное медиа

Персистентность контекстных токенов

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

Это обеспечивает непрерывность ответов даже после перезапусков gateway.

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

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

Разбивка сообщений

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

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

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

  1. При получении сообщения адаптер запрашивает typing_ticket через API getconfig

  2. Тикет набора текста кешируется на 10 минут для каждого пользователя

  3. send_typing отправляет сигнал начала набора; stop_typing отправляет сигнал остановки набора

  4. Gateway автоматически запускает индикаторы набора текста, пока агент обрабатывает сообщение

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 gateway может использовать данный токен одновременно. Адаптер получает блокировку с областью действия при запуске и освобождает её при остановке. Если другой gateway уже использует тот же токен, запуск завершается с информативным сообщением об ошибке.

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

Переменная Обязательная По умолчанию Описание
WEIXIN_ACCOUNT_ID ID аккаунта iLink Bot (из QR-логина)
WEIXIN_TOKEN Токен iLink Bot (авто-сохраняется из 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 Политика доступа к DM: open, allowlist, disabled, pairing
WEIXIN_GROUP_POLICY disabled Политика доступа к группам: open, allowlist, disabled
WEIXIN_ALLOWED_USERS (пусто) ID пользователей через запятую для DM allowlist
WEIXIN_GROUP_ALLOWED_USERS (пусто) Разделённые запятыми ID групповых чатов (не ID участников) для группового allowlist. Имя переменной устаревшее — ожидаются ID групп, а не ID пользователей.
WEIXIN_HOME_CHANNEL ID чата для вывода cron/уведомлений
WEIXIN_HOME_CHANNEL_NAME Home Отображаемое имя домашнего канала
WEIXIN_ALLOW_ALL_USERS Флаг уровня gateway для разрешения всем пользователям (используется мастером настройки)

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

Проблема Решение
Weixin startup failed: aiohttp and cryptography are required Установите оба: pip install aiohttp cryptography
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 Сначала остановите другой экземпляр gateway — разрешён только один poller на токен
Сессия истекла (errcode=-14) Ваша сессия входа истекла. Повторно запустите hermes gateway setup для сканирования нового QR-кода
QR-код истёк во время настройки QR-код автоматически обновляется до 3 раз. Если он продолжает истекать, проверьте сетевое подключение
Бот не отвечает на DM Проверьте WEIXIN_DM_POLICY — если установлен allowlist, отправитель должен быть в WEIXIN_ALLOWED_USERS
Бот игнорирует групповые сообщения Политика групп по умолчанию — disabled. Установите WEIXIN_GROUP_POLICY=open или allowlist — но учтите, что iLink бот-идентификаторы, подключённые через QR-логин (...@im.bot), обычно не могут получать обычные групповые сообщения WeChat. Если в логах gateway нет сырых входящих событий для групповых сообщений, ограничение на стороне iLink, а не Hermes.
Не удаётся загрузить/скачать медиа Убедитесь, что установлен cryptography. Проверьте сетевой доступ к novac2c.cdn.weixin.qq.com
Blocked unsafe URL (SSRF protection) Исходящий медиа-URL указывает на частный/внутренний адрес. Разрешены только публичные URL
Голосовые сообщения отображаются как текст Если WeChat предоставляет расшифровку, адаптер использует текст. Это ожидаемое поведение
Сообщения появляются дублированными Адаптер дедуплицирует по ID сообщения. Если вы видите дубликаты, проверьте, не запущено ли несколько экземпляров gateway
iLink POST ... HTTP 4xx/5xx Ошибка API от сервиса iLink. Проверьте действительность токена и сетевое подключение
QR-код в терминале не отображается Переустановите с дополнением messaging: pip install hermes-agent[messaging]. Альтернативно, откройте URL, напечатанный над QR-кодом