Weixin(微信)
目次
Hermes を、テンセントの個人向けメッセージングサービス WeChat(微信)につなぎます。このアダプターは個人の WeChat アカウント向けにテンセントの iLink Bot API を使います。WeCom(企業向け WeChat)とは別物です。メッセージはロングポーリングで届くので、公開エンドポイントや Webhook を用意する必要はありません。
事前に必要なもの
- 個人の WeChat アカウント
- Python パッケージ:
aiohttpとcryptography - Hermes を
messagingエクストラ付きで入れると、ターミナルへの QR コード表示も一緒に入ります
必要な依存関係をインストールします。
pip install aiohttp cryptography
# Optional: for terminal QR code display
cd ~/.hermes/hermes-agent && uv pip install -e ".[messaging]"設定
1. セットアップウィザードを実行する
WeChat アカウントをつなぐいちばん簡単な方法は、対話式のセットアップです。
hermes gateway setup選択肢が出たら Weixin を選びます。ウィザードは次の流れで進みます。
- iLink Bot API に QR コードを要求する
- ターミナルに QR コードを表示する(または URL を出す)
- WeChat のスマホアプリで QR コードを読み取るのを待つ
- スマホ側でログインを承認するよう促す
- アカウントの認証情報を
~/.hermes/weixin/accounts/へ自動保存する
承認が終わると、次のようなメッセージが出ます。
微信连接成功,account_id=your-account-idウィザードが account_id、token、base_url を保存するので、手作業で設定する必要はありません。
2. 環境変数を設定する
最初の QR ログインが済んだら、少なくともアカウント ID を ~/.hermes/.env に書きます。
WEIXIN_ACCOUNT_ID=your-account-id
# Optional: override the token (normally auto-saved from QR login)
# WEIXIN_TOKEN=your-bot-token
# Optional: restrict access
WEIXIN_DM_POLICY=open
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
# Optional: restore legacy multiline splitting behavior
# WEIXIN_SPLIT_MULTILINE_MESSAGES=true
# Optional: home channel for cron/notifications
WEIXIN_HOME_CHANNEL=chat_id
WEIXIN_HOME_CHANNEL_NAME=Home3. ゲートウェイを起動する
hermes gatewayアダプターが保存済みの認証情報を読み戻し、iLink API に接続して、メッセージのロングポーリングを始めます。
できること
- ロングポーリング通信 — 公開エンドポイント、Webhook、WebSocket のいずれも不要
- QR コードログイン —
hermes gateway setupで読み取るだけの接続手順 - DM のやり取り — アクセス方針を設定可能。グループでのやり取りは、つないだ人格に対して iLink が実際にグループのイベントを届けるかどうか次第です(iLink ボットのアカウントでは届かないことが多い。上の警告を参照)
- メディア対応 — 画像、動画、ファイル、音声メッセージ
- AES-128-ECB 暗号化 CDN — すべてのメディア転送を自動で暗号化・復号
- コンテキストトークンの永続化 — ディスクに保存し、再起動をまたいで返信のつながりを保つ
- Markdown の書式 — 見出し、表、コードブロックを含めて Markdown をそのまま残すので、Markdown に対応した WeChat クライアントならそのまま表示できます
- 賢いメッセージ分割 — 上限内なら 1 つの吹き出しのまま。大きすぎるものだけを区切りのよい位置で分けます
- 入力中の表示 — エージェントが処理している間、WeChat クライアントに「入力中…」を表示します
- SSRF 対策 — 送信するメディアの URL をダウンロード前に検証します
- メッセージの重複排除 — 5 分間のスライディングウィンドウで二重処理を防ぎます
- バックオフ付きの自動再試行 — 一時的な API エラーから復帰します
設定できる項目
config.yaml の platforms.weixin.extra の下に書きます。
| キー | 既定値 | 説明 |
|---|---|---|
account_id |
— | iLink Bot のアカウント ID(必須) |
token |
— | iLink Bot のトークン(必須。QR ログイン時に自動保存) |
base_url |
https://ilinkai.weixin.qq.com |
iLink API のベース URL |
cdn_base_url |
https://novac2c.cdn.weixin.qq.com/c2c |
メディア転送用 CDN のベース URL |
dm_policy |
open |
DM のアクセス: open、allowlist、disabled、pairing |
group_policy |
disabled |
グループのアクセス: open、allowlist、disabled |
allow_from |
[] |
DM を許可するユーザー ID(dm_policy=allowlist のとき) |
group_allow_from |
[] |
許可するグループ ID(group_policy=allowlist のとき) |
split_multiline_messages |
false |
true にすると、複数行の返信を複数のメッセージに分けて送ります(従来の動き)。false なら、長さの上限を超えないかぎり複数行の返信を 1 通のままにします。 |
text_batch_delay_seconds |
3.0 |
短時間に届いたテキストをまとめて 1 回のリクエストとして流すまでの待ち時間(秒)。iLink はメッセージを 1 通ずつ届けるため、この待ち時間があると断片ごとにエージェントが動くのを避けられます。0 にすると 1 通ごとにすぐ処理します。 |
text_batch_split_delay_seconds |
5.0 |
直近の断片が分割のしきい値に近いとき(iLink が長文を分けた可能性があるとき)に使う、長めの待ち時間。 |
アクセス方針
DM の方針
だれがボットにダイレクトメッセージを送れるかを決めます。
| 値 | 動き |
|---|---|
open |
だれでもボットに DM を送れます(既定) |
allowlist |
allow_from にあるユーザー ID だけが DM を送れます |
disabled |
DM をすべて無視します |
pairing |
ペアリングモード(初回の設定用) |
WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2WEIXIN_ALLOWED_USERS は 受信側のフィルター であって、招待の仕組みではありません。QR ログインで Hermes につながるのは 1 つの iLink ボット人格です。ほかの人が自分のアカウントで Hermes の QR コードを読み取るわけではありません。つながっている iLink のボット/連絡先へ WeChat から話しかけてもらう形になり、Hermes が DM を処理するのは送信者の Weixin ユーザー ID が WEIXIN_ALLOWED_USERS にある場合だけです。
実際の手順は次のようになります。
hermes gateway setupで Hermes を一度ペアリングし、つながった iLink ボットの
アカウントを控えます。
- 許可したい人それぞれに、そのボット/連絡先へダイレクトメッセージを送ってもらいます。
- ゲートウェイのログか、受信したイベントの中身から送信者のユーザー ID を読み取ります。
- その ID を
WEIXIN_ALLOWED_USERSに追加し、ゲートウェイを再起動します。
QR コードを読み取ったアカウントとしか会話できない場合は、ほかの人が QR ログインに使った個人の WeChat アカウントではなく iLink のボット人格そのものへ話しかけているかを確認してください。iLink ボットは独立した人格で、普通の WeChat の連絡先やグループの経路はテンセントの iLink の挙動によって 制限されることがあります。
グループの方針
つないだ人格に対して iLink がグループのイベントを届ける場合に、ボットがどのグループで反応するかを決めます。QR ログインの iLink ボット人格(例: ...@im.bot)では、そもそもグループのイベントが届かないのが普通なので、この方針が効かないことがあります。ページ冒頭の iLink ボットの制約に関する警告を参照してください。
| 値 | 動き |
|---|---|
open |
すべてのグループで反応します(イベントが届く場合) |
allowlist |
group_allow_from に並べたグループ ID でだけ反応します(イベントが届く場合) |
disabled |
グループのメッセージをすべて無視します(既定) |
WEIXIN_GROUP_POLICY=allowlist
# NOTE: this is a comma-separated list of group chat IDs, NOT member user IDs,
# despite the variable name containing "USERS". Keep this in mind when configuring.
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2メディアへの対応
受信
アダプターはユーザーからのメディア添付を受け取り、WeChat の CDN からダウンロードして復号し、エージェントが扱えるようローカルにキャッシュします。
| 種類 | 扱い方 |
|---|---|
| 画像 | ダウンロードして AES で復号し、JPEG としてキャッシュします。 |
| 動画 | ダウンロードして AES で復号し、MP4 としてキャッシュします。 |
| ファイル | ダウンロードして AES で復号し、キャッシュします。元のファイル名は保たれます。 |
| 音声 | テキストの書き起こしがあればそれをテキストとして取り出します。なければ音声(SILK 形式)をダウンロードしてキャッシュします。 |
引用されたメッセージ: 引用(返信元)のメッセージに含まれるメディアも取り出すので、ユーザーが何に返信しているかをエージェントが把握できます。
AES-128-ECB で暗号化された CDN
WeChat のメディアファイルは暗号化された CDN を通ってやり取りされます。アダプターはこれを裏側で処理します。
- 受信: 暗号化されたメディアを
encrypted_query_paramの URL で CDN からダウンロードし、メッセージに入っているファイルごとの鍵で AES-128-ECB を使って復号します。 - 送信: ファイルをローカルでランダムな AES-128-ECB 鍵を使って暗号化し、CDN へアップロードして、暗号化された参照を送信メッセージに含めます。
- AES の鍵は 16 バイト(128 ビット)です。鍵は生の base64 で来ることも 16 進数で来ることもあり、アダプターはどちらの形式にも対応します。
- これには Python の
cryptographyパッケージが必要です。
設定は要りません。暗号化と復号は自動で行われます。
送信
| メソッド | 送るもの |
|---|---|
send |
Markdown 書式のテキストメッセージ |
send_image / send_image_file |
画像そのものとして送るメッセージ(CDN へアップロード) |
send_document |
添付ファイル(CDN へアップロード) |
send_video |
動画メッセージ(CDN へアップロード) |
送信するメディアはすべて、暗号化された CDN へのアップロードの流れを通ります。
- ランダムな AES-128 の鍵を作る
- AES-128-ECB と PKCS#7 パディングでファイルを暗号化する
- iLink API(
getuploadurl)にアップロード先の URL を要求する - 暗号文を CDN へアップロードする
- 暗号化されたメディアの参照を付けてメッセージを送る
コンテキストトークンの永続化
iLink Bot API は、相手ごとに送信メッセージへ context_token を付け返すことを求めます。アダプターはコンテキストトークンをディスクに保存して管理します。
- トークンはアカウントと相手の組ごとに
~/.hermes/weixin/accounts/<account_id>.context-tokens.jsonへ保存されます - 起動時に、保存済みのトークンを読み戻します
- メッセージを受け取るたびに、その送信者のトークンを更新します
- 送信メッセージには最新のコンテキストトークンが自動で付きます
これにより、ゲートウェイを再起動しても返信のつながりが保たれます。
Markdown の書式
iLink Bot API を通してつながる WeChat クライアントは Markdown をそのまま表示できるので、アダプターは Markdown を書き換えずに残します。
- 見出し は Markdown の見出し(
#、##、…)のまま - 表 は Markdown の表のまま
- コードフェンス はフェンス付きコードブロックのまま
- 余分な空行 は、フェンス付きコードブロックの外では 2 つの改行にまとめられます
メッセージの分割
プラットフォームの上限に収まるかぎり、メッセージは 1 通として届きます。大きすぎるものだけが分割されます。
- メッセージの最大長: 4000 文字
- 上限内なら、段落や改行が複数あっても 1 通のままです
- 大きすぎるメッセージは、区切りのよい位置(段落、空行、コードフェンス)で分かれます
- コードフェンスはできるかぎりそのまま保たれます(フェンス自体が上限を超えないかぎり、途中で分けません)
- 大きすぎる 1 ブロックは、基底アダプターの切り詰め処理に回ります
- 複数に分けて送るときは、WeChat のレート制限による取りこぼしを防ぐため、断片の間に 0.3 秒の間隔を置きます
入力中の表示
アダプターは WeChat クライアントに入力中の状態を出します。
- メッセージが届くと、アダプターは
getconfigAPI でtyping_ticketを取得します - 入力中チケットはユーザーごとに 10 分間キャッシュされます
send_typingが入力開始の合図を、stop_typingが入力終了の合図を送ります- エージェントがメッセージを処理している間、ゲートウェイが自動で入力中の表示を出します
ロングポーリング接続
このアダプターは、メッセージの受信に WebSocket ではなく HTTP のロングポーリングを使います。
仕組み
- 接続: 認証情報を検証してポーリングのループを始めます
- ポーリング: タイムアウト 35 秒で
getupdatesを呼びます。サーバーはメッセージが届くかタイムアウトするまでリクエストを保持します - 振り分け: 受信したメッセージは
asyncio.create_taskで並行して処理されます - 同期バッファー: 同期位置(
get_updates_buf)をディスクへ保存するので、再起動しても正しい位置から再開できます
再試行の動き
API エラーが起きたとき、アダプターは単純な再試行を行います。
| 状況 | 動き |
|---|---|
| 一時的なエラー(1〜2 回目) | 2 秒後に再試行 |
| エラーが続く(3 回目以降) | 30 秒待ってからカウンターをリセット |
セッション切れ(errcode=-14) |
10 分間停止(再ログインが必要な場合あり) |
| タイムアウト | すぐに再ポーリング(ロングポーリングの通常の動き) |
重複排除
受信メッセージはメッセージ ID を使って 5 分間の窓で重複排除されます。これにより、ネットワークの乱れやポーリング応答の重なりによる二重処理を防ぎます。
トークンのロック
1 つのトークンを同時に使える Weixin ゲートウェイは 1 つだけです。アダプターは起動時にスコープ付きのロックを取り、終了時に解放します。同じトークンを別のゲートウェイがすでに使っている場合、起動は失敗し、その旨のエラーメッセージが出ます。
環境変数の一覧
| 変数 | 必須 | 既定値 | 説明 |
|---|---|---|---|
WEIXIN_ACCOUNT_ID |
✅ | — | iLink Bot のアカウント ID(QR ログインで取得) |
WEIXIN_TOKEN |
✅ | — | iLink Bot のトークン(QR ログイン時に自動保存) |
WEIXIN_BASE_URL |
— | https://ilinkai.weixin.qq.com |
iLink API のベース URL |
WEIXIN_CDN_BASE_URL |
— | https://novac2c.cdn.weixin.qq.com/c2c |
メディア転送用 CDN のベース URL |
WEIXIN_DM_POLICY |
— | open |
DM のアクセス方針: open、allowlist、disabled、pairing |
WEIXIN_GROUP_POLICY |
— | disabled |
グループのアクセス方針: open、allowlist、disabled |
WEIXIN_ALLOWED_USERS |
— | _(空)_ | DM の許可リストに入れるユーザー ID をカンマ区切りで |
WEIXIN_GROUP_ALLOWED_USERS |
— | _(空)_ | グループの許可リストに入れる グループチャットの ID(メンバーのユーザー ID ではありません)をカンマ区切りで。変数名は昔の名残で、期待されるのはグループ ID であってユーザー ID ではありません。 |
WEIXIN_HOME_CHANNEL |
— | — | 定期実行や通知の出力先チャット ID |
WEIXIN_HOME_CHANNEL_NAME |
— | Home |
ホームチャンネルの表示名 |
WEIXIN_ALLOW_ALL_USERS |
— | — | すべてのユーザーを許可するゲートウェイ側のフラグ(セットアップウィザードが使います) |
困ったときは
| 症状 | 対処 |
|---|---|
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 |
.env に WEIXIN_ACCOUNT_ID を設定するか、hermes gateway setup を実行します |
Another local Hermes gateway is already using this Weixin token |
先に別のゲートウェイを止めてください。1 つのトークンにつきポーリングは 1 つだけです |
セッション切れ(errcode=-14) |
ログインのセッションが切れています。hermes gateway setup をもう一度実行して、新しい QR コードを読み取ってください |
| 設定中に QR コードの期限が切れた | QR コードは最大 3 回まで自動で更新されます。それでも切れ続ける場合はネットワーク接続を確認してください |
| ボットが DM に反応しない | WEIXIN_DM_POLICY を確認します。allowlist なら、送信者が WEIXIN_ALLOWED_USERS に入っている必要があります |
| ボットがグループのメッセージを無視する | グループの方針は既定で disabled です。WEIXIN_GROUP_POLICY=open か allowlist にします。ただし QR ログインの iLink ボット人格(...@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 |
iLink 側の API エラーです。トークンが有効か、ネットワークがつながっているかを確認してください |
| ターミナルに QR コードが表示されない | messaging エクストラ付きで入れ直します: cd ~/.hermes/hermes-agent && uv pip install -e ".[messaging]"。あるいは QR コードの上に出力された URL を開いてください |