Weixin (WeChat)
Hermes를 Tencent의 personal messaging platform인 WeChat(微信)에 연결합니다. adapter는 personal WeChat account용 Tencent iLink Bot API를 사용합니다. 이는 WeCom(Enterprise WeChat)과 별개입니다. message는 long-polling으로 전달되므로 public endpoint나 webhook이 필요 없습니다.
이 adapter는 personal WeChat account(微信)용입니다. enterprise/corporate WeChat이 필요하다면 WeCom adapter를 사용하세요.
QR login은 Hermes를 fully scriptable ordinary personal WeChat account가 아니라 iLink bot identity(예: a5ace6fd482e@im.bot)에 연결합니다. 결과:
- iLink bot identity는 보통 normal contact처럼 일반 WeChat group에 invite될 수 없습니다.
- iLink는 대부분의 bot-type account에서 ordinary WeChat group event(personal account에 대한
@mention 포함)를 gateway로 전달하지 않는 경우가 많습니다. - QR code를 scan한 personal WeChat account를
@mention하는 것은 iLink bot을@mention하는 것과 다릅니다. bot은 별도 identity입니다. - 아래
WEIXIN_GROUP_POLICY/WEIXIN_GROUP_ALLOWED_USERSsetting은 iLink가 실제로 해당 account type의 group event를 반환할 때만 효과가 있습니다. 반환하지 않는다면 policy와 관계없이 group message는 Hermes에 도달하지 않습니다.
실무에서는 대부분 DM to iLink bot만 안정적으로 동작합니다. 설정 후에도 group delivery가 되지 않는다면 제약은 Hermes가 아니라 iLink 쪽에 있습니다. gateway는 WEIXIN_GROUP_POLICY가 disabled가 아닌 값으로 설정되어 있으면 startup 시 WARNING을 log합니다.
prerequisites
- personal WeChat account
- Python package:
aiohttp,cryptography - terminal QR rendering은 Hermes를
messagingextra와 함께 설치하면 포함됩니다.
필요한 dependency 설치:
pip install aiohttp cryptography
# Optional: for terminal QR code display
pip install hermes-agent[messaging]
setup
1. setup wizard 실행
WeChat account를 연결하는 가장 쉬운 방법은 interactive setup입니다.
hermes gateway setup
prompt가 나오면 Weixin을 선택합니다. wizard는 다음을 수행합니다.
- iLink Bot API에서 QR code 요청
- terminal에 QR code 표시 또는 URL 제공
- WeChat mobile app으로 QR code를 scan할 때까지 대기
- phone에서 login confirm 요청
- account credential을
~/.hermes/weixin/accounts/에 자동 저장
confirm되면 다음과 비슷한 message가 표시됩니다.
微信连接成功,account_id=your-account-id
wizard는 account_id, token, base_url을 저장하므로 수동으로 설정할 필요가 없습니다.
2. environment variable 설정
초기 QR login 후 최소한 account 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=Home
3. gateway 시작
hermes gateway
adapter가 저장된 credential을 restore하고 iLink API에 연결한 뒤 message long-polling을 시작합니다.
features
- Long-poll transport - public endpoint, webhook, WebSocket 필요 없음
- QR code login -
hermes gateway setup을 통한 scan-to-connect setup - DM messaging - configurable access policy. group messaging은 연결된 identity에 대해 iLink가 실제 group event를 deliver하는지에 달려 있습니다. iLink bot account에서는 보통 그렇지 않습니다.
- Media support - image, video, file, voice message
- AES-128-ECB encrypted CDN - 모든 media transfer 자동 encryption/decryption
- Context token persistence - restart를 넘어 disk-backed reply continuity 유지
- Markdown formatting - header, table, code block을 포함한 Markdown 보존. Markdown을 지원하는 WeChat client에서는 native rendering 가능
- Smart message chunking - limit 아래에서는 single bubble 유지, 너무 큰 payload만 logical boundary에서 split
- Typing indicators - agent 처리 중 WeChat client에 "typing…" status 표시
- SSRF protection - outbound media URL download 전 validation
- Message deduplication - 5분 sliding window로 double-processing 방지
- Automatic retry with backoff - transient API error에서 recovery
configuration options
config.yaml의 platforms.weixin.extra 아래에 설정합니다.
| Key | Default | Description |
|---|---|---|
account_id | — | iLink Bot account ID(required) |
token | — | iLink Bot token(required, QR login에서 auto-saved) |
base_url | https://ilinkai.weixin.qq.com | iLink API base URL |
cdn_base_url | https://novac2c.cdn.weixin.qq.com/c2c | media transfer용 CDN base URL |
dm_policy | open | DM access: open, allowlist, disabled, pairing |
group_policy | disabled | Group access: open, allowlist, disabled |
allow_from | [] | DM 허용 user ID(dm_policy=allowlist일 때) |
group_allow_from | [] | 허용 group ID(group_policy=allowlist일 때) |
split_multiline_messages | false | true이면 multi-line reply를 여러 chat message로 split합니다(legacy behavior). false이면 length limit을 넘지 않는 한 multi-line reply를 하나의 message로 유지합니다. |
access policies
DM policy
bot에게 direct message를 보낼 수 있는 사람을 제어합니다.
| Value | Behavior |
|---|---|
open | 누구나 bot에게 DM 가능(기본값) |
allowlist | allow_from에 있는 user ID만 DM 가능 |
disabled | 모든 DM 무시 |
pairing | initial setup용 pairing mode |
WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
group policy
connected identity에 대해 iLink가 group event를 deliver하는 경우에만 bot이 어느 group에서 respond할지 제어합니다. QR-login iLink bot identity(예: ...@im.bot)에서는 group event가 보통 아예 deliver되지 않으므로, 이 policy가 효과가 없을 수 있습니다. 위 iLink bot limitation warning을 참고하세요.
| Value | Behavior |
|---|---|
open | event가 deliver되는 모든 group에서 bot이 respond |
allowlist | group_allow_from에 listed된 group ID에서만 respond(event가 deliver될 때) |
disabled | 모든 group message 무시(기본값) |
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
Weixin의 default group policy는 disabled입니다. WeCom의 기본값이 open인 것과 다릅니다. personal WeChat account는 많은 group에 들어가 있을 수 있고, iLink bot identity는 보통 ordinary WeChat group message를 받을 수 없기 때문입니다. WEIXIN_GROUP_POLICY를 disabled 외 값으로 설정하면 gateway가 startup 시 WARNING을 log합니다.
media support
inbound(receiving)
adapter는 user의 media attachment를 받고, WeChat CDN에서 download한 뒤 decrypt하여 agent processing을 위해 local에 cache합니다.
| Type | 처리 방식 |
|---|---|
| Images | download, AES decrypt 후 JPEG로 cache |
| Video | download, AES decrypt 후 MP4로 cache |
| Files | download, AES decrypt 후 cache. original filename 보존 |
| Voice | text transcription이 있으면 text로 추출. 없으면 audio(SILK format)를 download/cache |
Quoted messages: quoted(replied-to) message의 media도 추출되므로 agent는 사용자가 무엇에 reply하는지 context를 갖습니다.
AES-128-ECB encrypted CDN
WeChat media file은 encrypted CDN을 통해 transfer됩니다. adapter가 이를 transparent하게 처리합니다.
- Inbound: encrypted media를
encrypted_query_paramURL로 CDN에서 download한 뒤, message payload의 per-file key로 AES-128-ECB decrypt합니다. - Outbound: file을 random AES-128-ECB key로 local encrypt하고 CDN에 upload한 뒤 encrypted reference를 outbound message에 포함합니다.
- AES key는 16 bytes(128-bit)입니다. key는 raw base64 또는 hex-encoded로 올 수 있으며 adapter가 둘 다 처리합니다.
cryptographyPython package가 필요합니다.
별도 configuration은 필요 없습니다. encryption/decryption은 자동으로 수행됩니다.
outbound(sending)
| Method | What it sends |
|---|---|
send | Markdown formatting이 있는 text messages |
send_image / send_image_file | native image messages(CDN upload 사용) |
send_document | file attachments(CDN upload 사용) |
send_video | video messages(CDN upload 사용) |
모든 outbound media는 encrypted CDN upload flow를 거칩니다.
- random AES-128 key 생성
- AES-128-ECB + PKCS#7 padding으로 file encrypt
- iLink API(
getuploadurl)에서 upload URL 요청 - ciphertext를 CDN에 upload
- encrypted media reference로 message 전송
context token persistence
iLink Bot API는 given peer에 대한 outbound message마다 context_token을 되돌려 보내야 합니다. adapter는 disk-backed context token store를 유지합니다.
- token은 account+peer별로
~/.hermes/weixin/accounts/<account_id>.context-tokens.json에 저장됩니다. - startup 시 이전에 저장한 token을 restore합니다.
- inbound message가 올 때마다 sender의 stored token을 update합니다.
- outbound message에는 latest context token이 자동으로 포함됩니다.
이렇게 하면 gateway restart 후에도 reply continuity가 유지됩니다.
Markdown formatting
iLink Bot API로 연결된 WeChat client는 Markdown을 직접 render할 수 있으므로, adapter는 Markdown을 rewrite하지 않고 보존합니다.
- Headers는 Markdown heading(
#,##, ...) 그대로 유지 - Tables는 Markdown table 그대로 유지
- Code fences는 fenced code block 그대로 유지
- fenced code block 밖의 과도한 blank line은 double newline으로 collapse
message chunking
message는 platform limit 안에 들어가면 single chat message로 전달됩니다. oversized payload만 split됩니다.
- 최대 message length: 4000 characters
- limit 아래의 message는 여러 paragraph나 line break가 있어도 그대로 유지
- oversized message는 logical boundary(paragraph, blank line, code fence)에서 split
- code fence는 가능한 한 intact하게 유지. fence 자체가 limit을 넘는 경우가 아니면 mid-block split하지 않음
- oversized individual block은 base adapter의 truncation logic으로 fallback
- 여러 chunk를 보낼 때 WeChat rate-limit drop을 막기 위해 0.3초 inter-chunk delay 적용
typing indicators
adapter는 WeChat client에 typing status를 표시합니다.
- message가 오면 adapter가
getconfigAPI로typing_ticket을 가져옵니다. - typing ticket은 user별로 10분 cache됩니다.
send_typing은 typing-start signal을 보내고,stop_typing은 typing-stop signal을 보냅니다.- gateway는 agent가 message를 처리하는 동안 typing indicator를 자동 trigger합니다.
long-poll connection
adapter는 WebSocket이 아니라 HTTP long-polling으로 message를 받습니다.
동작 방식
- Connect: credential을 validate하고 poll loop를 시작합니다.
- Poll:
getupdates를 35초 timeout으로 호출합니다. server는 message가 오거나 timeout될 때까지 request를 hold합니다. - Dispatch: inbound message는
asyncio.create_task로 concurrently dispatch됩니다. - Sync buffer: persistent sync cursor(
get_updates_buf)가 disk에 저장되어 restart 후에도 올바른 위치부터 resume합니다.
retry behavior
API error가 나면 adapter는 단순한 retry strategy를 사용합니다.
| Condition | Behavior |
|---|---|
| Transient error(1st-2nd) | 2초 뒤 retry |
| Repeated errors(3+) | 30초 backoff 후 counter reset |
Session expired(errcode=-14) | 10분 pause(re-login 필요 가능) |
| Timeout | 즉시 re-poll(normal long-poll behavior) |
deduplication
inbound message는 message ID로 5분 window 안에서 deduplicate됩니다. network hiccup이나 overlapping poll response 중 double-processing을 막습니다.
token lock
하나의 Weixin gateway instance만 given token을 사용할 수 있습니다. adapter는 startup 시 scoped lock을 획득하고 shutdown 시 release합니다. 다른 gateway가 이미 같은 token을 사용 중이면 startup이 informative error message와 함께 실패합니다.
all environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
WEIXIN_ACCOUNT_ID | ✅ | — | iLink Bot account ID(QR login에서 얻음) |
WEIXIN_TOKEN | ✅ | — | iLink Bot token(QR login에서 auto-saved) |
WEIXIN_BASE_URL | — | https://ilinkai.weixin.qq.com | iLink API base URL |
WEIXIN_CDN_BASE_URL | — | https://novac2c.cdn.weixin.qq.com/c2c | media transfer용 CDN base URL |
WEIXIN_DM_POLICY | — | open | DM access policy: open, allowlist, disabled, pairing |
WEIXIN_GROUP_POLICY | — | disabled | Group access policy: open, allowlist, disabled |
WEIXIN_ALLOWED_USERS | — | (empty) | DM allowlist용 comma-separated user IDs |
WEIXIN_GROUP_ALLOWED_USERS | — | (empty) | group allowlist용 comma-separated group chat IDs(member user IDs 아님). variable name은 legacy입니다. |
WEIXIN_HOME_CHANNEL | — | — | cron/notification output용 Chat ID |
WEIXIN_HOME_CHANNEL_NAME | — | Home | home channel display name |
WEIXIN_ALLOW_ALL_USERS | — | — | setup wizard가 사용하는 gateway-level allow-all flag |
troubleshooting
| Problem | Fix |
|---|---|
Weixin startup failed: aiohttp and cryptography are required | 둘 다 설치: pip install aiohttp cryptography |
Weixin startup failed: WEIXIN_TOKEN is required | hermes gateway setup으로 QR login을 완료하거나 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 | 먼저 다른 gateway instance를 중지하세요. token당 poller 하나만 허용됩니다. |
Session expired(errcode=-14) | login session이 만료되었습니다. hermes gateway setup을 다시 실행해 새 QR code를 scan하세요. |
| QR code expired during setup | QR은 최대 3번 auto-refresh됩니다. 계속 만료되면 network connection을 확인하세요. |
| Bot doesn't respond to DMs | WEIXIN_DM_POLICY 확인. allowlist이면 sender가 WEIXIN_ALLOWED_USERS에 있어야 합니다. |
| Bot ignores group messages | group policy 기본값은 disabled입니다. WEIXIN_GROUP_POLICY=open 또는 allowlist로 설정하세요. 단, QR-login iLink bot identity(...@im.bot)는 보통 ordinary WeChat group message를 받을 수 없습니다. gateway log에 group raw inbound event가 없다면 제약은 Hermes가 아니라 iLink 쪽입니다. |
| Media download/upload fails | cryptography가 설치되어 있는지 확인하고 novac2c.cdn.weixin.qq.com network access를 확인하세요. |
Blocked unsafe URL (SSRF protection) | outbound media URL이 private/internal address를 가리킵니다. public URL만 허용됩니다. |
| Voice messages show as text | WeChat이 transcription을 제공하면 adapter는 text를 사용합니다. 정상 동작입니다. |
| Messages appear duplicated | adapter는 message ID로 deduplicate합니다. 중복이 보이면 여러 gateway instance가 실행 중인지 확인하세요. |
iLink POST ... HTTP 4xx/5xx | iLink service의 API error입니다. token validity와 network connectivity를 확인하세요. |
| Terminal QR code doesn't render | messaging extra로 재설치하세요. pip install hermes-agent[messaging] 또는 QR 위에 출력된 URL을 여세요. |