diff --git a/gateway/platforms/weixin.py b/gateway/platforms/weixin.py index 3084ba8122..6f6b56caa4 100644 --- a/gateway/platforms/weixin.py +++ b/gateway/platforms/weixin.py @@ -1222,6 +1222,17 @@ class WeixinAdapter(BasePlatformAdapter): self._mark_connected() _LIVE_ADAPTERS[self._token] = self logger.info("[%s] Connected account=%s base=%s", self.name, _safe_id(self._account_id), self._base_url) + if self._group_policy != "disabled": + logger.warning( + "[%s] WEIXIN_GROUP_POLICY=%s is set, but QR-login connects an iLink bot " + "identity (e.g. ...@im.bot) which typically cannot be invited into ordinary " + "WeChat groups. iLink usually does not deliver ordinary-group events for " + "these accounts, so group messages may never reach Hermes regardless of this " + "policy. If group delivery doesn't work, the limitation is on the iLink side, " + "not in Hermes.", + self.name, + self._group_policy, + ) return True async def disconnect(self) -> None: diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index cb47533d80..e05410a788 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -3293,6 +3293,12 @@ def _setup_weixin(): print_warning(" Direct messages disabled.") print() + print_info(" Note: QR login connects an iLink bot identity (e.g. ...@im.bot), not a") + print_info(" scriptable personal WeChat account. Ordinary WeChat groups typically cannot") + print_info(" invite an @im.bot identity, and iLink does not deliver ordinary-group events") + print_info(" to most bot accounts. The settings below only apply when iLink actually") + print_info(" delivers group events for your account type — otherwise DM remains the only") + print_info(" working channel regardless of this choice.") group_choices = [ "Disable group chats (recommended)", "Allow all group chats", @@ -3306,12 +3312,12 @@ def _setup_weixin(): elif group_idx == 1: save_env_value("WEIXIN_GROUP_POLICY", "open") save_env_value("WEIXIN_GROUP_ALLOWED_USERS", "") - print_warning(" All group chats enabled.") + print_warning(" All group chats enabled (only takes effect if iLink delivers group events).") else: - allow_groups = prompt(" Allowed group chat IDs (comma-separated)", "", password=False).replace(" ", "") + allow_groups = prompt(" Allowed group chat IDs (comma-separated, not member user IDs)", "", password=False).replace(" ", "") save_env_value("WEIXIN_GROUP_POLICY", "allowlist") save_env_value("WEIXIN_GROUP_ALLOWED_USERS", allow_groups) - print_success(" Group allowlist saved.") + print_success(" Group allowlist saved (only takes effect if iLink delivers group events).") if user_id: print() diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index f42272b277..f5da509bcf 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -312,7 +312,7 @@ For cloud sandbox backends, persistence is filesystem-oriented. `TERMINAL_LIFETI | `WEIXIN_DM_POLICY` | Direct message policy: `open`, `allowlist`, `pairing`, `disabled` (default: `open`) | | `WEIXIN_GROUP_POLICY` | Group message policy: `open`, `allowlist`, `disabled` (default: `disabled`) | | `WEIXIN_ALLOWED_USERS` | Comma-separated Weixin user IDs allowed to DM the bot | -| `WEIXIN_GROUP_ALLOWED_USERS` | Comma-separated Weixin group IDs allowed to interact with the bot | +| `WEIXIN_GROUP_ALLOWED_USERS` | Comma-separated Weixin **group chat IDs** (not member user IDs) allowed to interact with the bot. The variable name is legacy — it expects group IDs. Only takes effect when iLink actually delivers group events; QR-login iLink bot identities (`...@im.bot`) typically don't receive ordinary WeChat group messages. | | `WEIXIN_HOME_CHANNEL` | Weixin chat ID for cron delivery and notifications | | `WEIXIN_HOME_CHANNEL_NAME` | Display name for the Weixin home channel | | `WEIXIN_ALLOW_ALL_USERS` | Allow all Weixin users without an allowlist (`true`/`false`) | diff --git a/website/docs/user-guide/messaging/weixin.md b/website/docs/user-guide/messaging/weixin.md index 57977b0c7f..c2932a39a7 100644 --- a/website/docs/user-guide/messaging/weixin.md +++ b/website/docs/user-guide/messaging/weixin.md @@ -12,6 +12,17 @@ Connect Hermes to [WeChat](https://weixin.qq.com/) (微信), Tencent's personal This adapter is for **personal WeChat accounts** (微信). If you need enterprise/corporate WeChat, see the [WeCom adapter](./wecom.md) instead. ::: +:::warning iLink bot identity — ordinary WeChat groups may not work +QR login connects Hermes to an **iLink bot identity** (e.g. `a5ace6fd482e@im.bot`), **not** a fully scriptable ordinary personal WeChat account. Consequences: + +- The iLink bot identity generally **cannot be invited into ordinary WeChat groups** the way a normal contact can. +- iLink typically **does not deliver ordinary WeChat group events** (including `@`-mentions of the personal account used for QR login) to the gateway for most bot-type accounts. +- `@`-mentioning the personal WeChat account used to scan the QR code is **not** the same as `@`-mentioning the iLink bot — the bot is a separate identity. +- The `WEIXIN_GROUP_POLICY` / `WEIXIN_GROUP_ALLOWED_USERS` settings below only take effect when iLink actually returns group events for your account type. If it doesn't, group messages will never reach Hermes regardless of policy. + +In practice, most deployments only get DMs to the iLink bot working reliably. If group delivery doesn't work after configuration, the limitation is on the iLink side, not in Hermes. The gateway logs a `WARNING` at startup whenever `WEIXIN_GROUP_POLICY` is set to anything other than `disabled`. +::: + ## Prerequisites - A personal WeChat account @@ -86,7 +97,7 @@ The adapter will restore saved credentials, connect to the iLink API, and begin - **Long-poll transport** — no public endpoint, webhook, or WebSocket needed - **QR code login** — scan-to-connect setup via `hermes gateway setup` -- **DM and group messaging** — configurable access policies +- **DM messaging** — configurable access policies; group messaging depends on iLink actually delivering group events for the connected identity (often not the case for iLink bot accounts — see the warning above) - **Media support** — images, video, files, and voice messages - **AES-128-ECB encrypted CDN** — automatic encryption/decryption for all media transfers - **Context token persistence** — disk-backed reply continuity across restarts @@ -133,21 +144,23 @@ WEIXIN_ALLOWED_USERS=user_id_1,user_id_2 ### Group Policy -Controls which groups the bot responds in: +Controls which groups the bot responds in **when iLink delivers group events for the connected identity**. For QR-login iLink bot identities (e.g. `...@im.bot`), group events are typically not delivered at all, so this policy may have no effect — see the iLink bot limitation warning at the top of the page. | Value | Behavior | |-------|----------| -| `open` | Bot responds in all groups | -| `allowlist` | Bot only responds in group IDs listed in `group_allow_from` | +| `open` | Bot responds in all groups (if events are delivered) | +| `allowlist` | Bot only responds in group IDs listed in `group_allow_from` (if events are delivered) | | `disabled` | All group messages are ignored (default) | ```bash 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 ``` :::note -The default group policy is `disabled` for Weixin (unlike WeCom where it defaults to `open`). This is intentional since personal WeChat accounts may be in many groups. +The default group policy is `disabled` for Weixin (unlike WeCom where it defaults to `open`). This is intentional — personal WeChat accounts may be in many groups, and iLink bot identities typically can't receive ordinary WeChat group messages at all. The gateway logs a `WARNING` at startup if you set `WEIXIN_GROUP_POLICY` to anything other than `disabled`. ::: ## Media Support @@ -274,7 +287,7 @@ Only one Weixin gateway instance can use a given token at a time. The adapter ac | `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)_ | Comma-separated user IDs for DM allowlist | -| `WEIXIN_GROUP_ALLOWED_USERS` | — | _(empty)_ | Comma-separated group IDs for group allowlist | +| `WEIXIN_GROUP_ALLOWED_USERS` | — | _(empty)_ | Comma-separated **group chat IDs** (not member user IDs) for group allowlist. The variable name is legacy — it expects group IDs, not user IDs. | | `WEIXIN_HOME_CHANNEL` | — | — | Chat ID for cron/notification output | | `WEIXIN_HOME_CHANNEL_NAME` | — | `Home` | Display name for the home channel | | `WEIXIN_ALLOW_ALL_USERS` | — | — | Gateway-level flag to allow all users (used by setup wizard) | @@ -290,7 +303,7 @@ Only one Weixin gateway instance can use a given token at a time. The adapter ac | Session expired (`errcode=-14`) | Your login session has expired. Re-run `hermes gateway setup` to scan a new QR code | | QR code expired during setup | The QR auto-refreshes up to 3 times. If it keeps expiring, check your network connection | | Bot doesn't respond to DMs | Check `WEIXIN_DM_POLICY` — if set to `allowlist`, the sender must be in `WEIXIN_ALLOWED_USERS` | -| Bot ignores group messages | Group policy defaults to `disabled`. Set `WEIXIN_GROUP_POLICY=open` or `allowlist` | +| Bot ignores group messages | Group policy defaults to `disabled`. Set `WEIXIN_GROUP_POLICY=open` or `allowlist` — but note that QR-login iLink bot identities (`...@im.bot`) typically cannot receive ordinary WeChat group messages at all. If the gateway logs show no raw inbound events for group messages, the limitation is on the iLink side, not in Hermes. | | Media download/upload fails | Ensure `cryptography` is installed. Check network access to `novac2c.cdn.weixin.qq.com` | | `Blocked unsafe URL (SSRF protection)` | The outbound media URL points to a private/internal address. Only public URLs are allowed | | Voice messages show as text | If WeChat provides a transcription, the adapter uses the text. This is expected behavior |