From 56f91381dd810376f6912af71accc99ba5193aa7 Mon Sep 17 00:00:00 2001 From: HenryXiaoYang Date: Tue, 10 Mar 2026 03:11:39 +0800 Subject: [PATCH] fix: rename plugin id to wechat-access-unqclawed, add login/logout commands, update README with enable instructions --- README.md | 16 +++++++-- common/message-context.ts | 10 +++--- http/message-handler.ts | 8 ++--- index.ts | 63 ++++++++++++++++++++++++++++++------ openclaw.plugin.json | 4 +-- package-lock.json | 4 +-- package.json | 8 ++--- websocket/message-handler.ts | 4 +-- 8 files changed, 85 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 9d45f35..2791f5e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,13 @@ OpenClaw 微信通路插件 — 通过 WeChat OAuth 扫码登录获取 token, openclaw plugins install @henryxiaoyang/wechat-access-unqclawed ``` -重启 Gateway 后生效。 +安装后启用渠道: + +```bash +openclaw config set channels.wechat-access-unqclawed.enabled true +``` + +重启 Gateway,首次启动会在终端显示微信扫码登录二维码。 ## 功能 @@ -17,15 +23,18 @@ openclaw plugins install @henryxiaoyang/wechat-access-unqclawed - AGP 协议 WebSocket 双向通信(流式文本、工具调用) - 邀请码验证(可配置跳过) - 支持生产/测试环境切换 +- `/wechat-login` 命令手动触发扫码登录 +- `/wechat-logout` 命令清除已保存的登录态 ## 配置 -在 OpenClaw 配置文件的 `channels.wechat-access` 下: +在 OpenClaw 配置文件的 `channels.wechat-access-unqclawed` 下: ```json { "channels": { - "wechat-access": { + "wechat-access-unqclawed": { + "enabled": true, "token": "", "wsUrl": "", "bypassInvite": false, @@ -37,6 +46,7 @@ openclaw plugins install @henryxiaoyang/wechat-access-unqclawed | 字段 | 类型 | 说明 | |------|------|------| +| `enabled` | boolean | 启用渠道(必须设为 `true`) | | `token` | string | 手动指定 channel token(留空则走扫码登录) | | `wsUrl` | string | WebSocket 网关地址(留空使用环境默认值) | | `bypassInvite` | boolean | 跳过邀请码验证 | diff --git a/common/message-context.ts b/common/message-context.ts index be8986c..9113e2f 100644 --- a/common/message-context.ts +++ b/common/message-context.ts @@ -82,7 +82,7 @@ export const buildMessageContext = (message: FuwuhaoMessage): MessageContext => // 根据频道、账号、对话类型等信息,决定使用哪个 Agent 处理消息 const frameworkRoute = runtime.channel.routing.resolveAgentRoute({ cfg, // 全局配置 - channel: "wechat-access", // 频道标识 + channel: "wechat-access-unqclawed", // 频道标识 accountId: "default", // 账号 ID(支持多账号场景) peer: { kind: "dm", // 对话类型:dm=私聊,group=群聊 @@ -132,7 +132,7 @@ export const buildMessageContext = (message: FuwuhaoMessage): MessageContext => // runtime.channel.reply.formatInboundEnvelope 将原始消息格式化为标准格式 // 添加时间戳、发送者信息、格式化选项等 const body = runtime.channel.reply.formatInboundEnvelope({ - channel: "wechat-access", // 频道标识 + channel: "wechat-access-unqclawed", // 频道标识 from: userId, // 发送者 ID timestamp, // 消息时间戳 body: content, // 消息内容 @@ -161,11 +161,11 @@ export const buildMessageContext = (message: FuwuhaoMessage): MessageContext => ChatType: "direct" as const, // 对话类型 ChannelSource: WECHAT_CHANNEL_LABELS.serviceAccount, // 渠道来源标识(用于 UI 侧区分消息来源) SenderId: userId, // 发送者 ID - Provider: "wechat-access", // 提供商标识 - Surface: "wechat-access", // 界面标识 + Provider: "wechat-access-unqclawed", // 提供商标识 + Surface: "wechat-access-unqclawed", // 界面标识 MessageSid: messageId, // 消息唯一标识 Timestamp: timestamp, // 时间戳 - OriginatingChannel: "wechat-access" as const, // 原始频道 + OriginatingChannel: "wechat-access-unqclawed" as const, // 原始频道 OriginatingTo: `wechat-access:${userId}`, // 原始接收者 }); // ctx 包含了 Agent 处理消息所需的所有信息 diff --git a/http/message-handler.ts b/http/message-handler.ts index 60031b4..e5f1701 100644 --- a/http/message-handler.ts +++ b/http/message-handler.ts @@ -103,7 +103,7 @@ export const handleMessage = async (message: FuwuhaoMessage): Promise { // 记录出站活动 runtime.channel.activity.record({ - channel: "wechat-access", + channel: "wechat-access-unqclawed", accountId: "default", direction: "outbound", }); diff --git a/index.ts b/index.ts index c948c9d..39e2f2f 100644 --- a/index.ts +++ b/index.ts @@ -13,14 +13,14 @@ const wsClients = new Map(); // 渠道元数据 const meta = { - id: "wechat-access", + id: "wechat-access-unqclawed", label: "腾讯通路", /** 选择时的显示文本 */ selectionLabel: "腾讯通路", detailLabel: "腾讯通路", /** 文档路径 */ docsPath: "/channels/wechat-access", - docsLabel: "wechat-access", + docsLabel: "wechat-access-unqclawed", /** 简介 */ blurb: "通用通路", /** 图标 */ @@ -31,7 +31,7 @@ const meta = { // 渠道插件 const tencentAccessPlugin = { - id: "wechat-access", + id: "wechat-access-unqclawed", meta, // 能力声明 @@ -46,13 +46,13 @@ const tencentAccessPlugin = { // 热重载:token 或 wsUrl 变更时触发 gateway 重启 reload: { - configPrefixes: ["channels.wechat-access.token", "channels.wechat-access.wsUrl"], + configPrefixes: ["channels.wechat-access-unqclawed.token", "channels.wechat-access-unqclawed.wsUrl"], }, // 配置适配器(必需) config: { listAccountIds: (cfg: any) => { - const accounts = cfg.channels?.["wechat-access"]?.accounts; + const accounts = cfg.channels?.["wechat-access-unqclawed"]?.accounts; if (accounts && typeof accounts === "object") { return Object.keys(accounts); } @@ -60,7 +60,7 @@ const tencentAccessPlugin = { return ["default"]; }, resolveAccount: (cfg: any, accountId: string) => { - const accounts = cfg.channels?.["wechat-access"]?.accounts; + const accounts = cfg.channels?.["wechat-access-unqclawed"]?.accounts; const account = accounts?.[accountId ?? "default"]; return account ?? { accountId: accountId ?? "default" }; }, @@ -86,7 +86,7 @@ const tencentAccessPlugin = { startAccount: async (ctx: any) => { const { cfg, accountId, abortSignal, log } = ctx; - const tencentAccessConfig = cfg?.channels?.["wechat-access"]; + const tencentAccessConfig = cfg?.channels?.["wechat-access-unqclawed"]; let token = tencentAccessConfig?.token ? String(tencentAccessConfig.token) : ""; const configWsUrl = tencentAccessConfig?.wsUrl ? String(tencentAccessConfig.wsUrl) : ""; const bypassInvite = tencentAccessConfig?.bypassInvite === true; @@ -211,7 +211,7 @@ const tencentAccessPlugin = { }; const index = { - id: "wechat-access", + id: "wechat-access-unqclawed", name: "通用通路插件", description: "腾讯通用通路插件", configSchema: emptyPluginConfigSchema(), @@ -226,8 +226,51 @@ const index = { // 2. 注册渠道插件 api.registerChannel({ plugin: tencentAccessPlugin as any }); - // 3. 注册 HTTP 处理器(如需要) - // api.registerHttpHandler(handleSimpleWecomWebhook); + // 3. 注册 /wechat-login 命令(手动触发扫码登录) + api.registerCommand?.({ + command: "wechat-login", + description: "手动执行微信扫码登录,获取 channel token", + handler: async ({ cfg, reply }) => { + const channelCfg = cfg?.channels?.["wechat-access-unqclawed"]; + const bypassInvite = channelCfg?.bypassInvite === true; + const authStatePath = channelCfg?.authStatePath + ? String(channelCfg.authStatePath) + : undefined; + const envName = channelCfg?.environment + ? String(channelCfg.environment) + : "production"; + + const env = getEnvironment(envName); + const guid = getDeviceGuid(); + + try { + reply("正在启动微信扫码登录,请查看终端..."); + const credentials = await performLogin({ + guid, + env, + bypassInvite, + authStatePath, + }); + reply(`登录成功! token: ${credentials.channelToken.substring(0, 6)}... (已保存,重启 Gateway 生效)`); + } catch (err) { + reply(`登录失败: ${err instanceof Error ? err.message : String(err)}`); + } + }, + }); + + // 4. 注册 /wechat-logout 命令(清除已保存的登录态) + api.registerCommand?.({ + command: "wechat-logout", + description: "清除已保存的微信登录态", + handler: async ({ cfg, reply }) => { + const channelCfg = cfg?.channels?.["wechat-access-unqclawed"]; + const authStatePath = channelCfg?.authStatePath + ? String(channelCfg.authStatePath) + : undefined; + clearState(authStatePath); + reply("已清除登录态,下次启动将重新扫码登录。"); + }, + }); console.log("[wechat-access] 腾讯通路插件已注册"); }, diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 498ffc3..818be2c 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,9 +1,9 @@ { - "id": "wechat-access", + "id": "wechat-access-unqclawed", "name": "WeChat Access", "description": "微信通路插件 — 扫码登录 + AGP WebSocket 双向通信", "version": "1.0.0", - "channels": ["wechat-access"], + "channels": ["wechat-access-unqclawed"], "configSchema": { "type": "object", "properties": { diff --git a/package-lock.json b/package-lock.json index 77625c7..5e4c62c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wechat-access", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wechat-access", - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "fast-xml-parser": "^5.4.1", "qrcode-terminal": "^0.12.0", diff --git a/package.json b/package.json index e5eb6a3..311bef3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@henryxiaoyang/wechat-access-unqclawed", - "version": "1.0.0", + "version": "1.0.1", "type": "module", "description": "OpenClaw 微信通路插件 — 扫码登录 + AGP WebSocket 双向通信", "author": "HenryXiaoYang", @@ -24,12 +24,12 @@ "./index.ts" ], "channel": { - "id": "wechat-access", - "label": "wechat-access", + "id": "wechat-access-unqclawed", + "label": "wechat-access-unqclawed", "selectionLabel": "WeCom (plugin)", "detailLabel": "WeCom Bot", "docsPath": "/channels/wechat-access", - "docsLabel": "wechat-access", + "docsLabel": "wechat-access-unqclawed", "blurb": "Enterprise WeCom intelligent bot (API mode) via encrypted webhooks + passive replies.", "aliases": [ "wechatwork", diff --git a/websocket/message-handler.ts b/websocket/message-handler.ts index b255a22..b7490da 100644 --- a/websocket/message-handler.ts +++ b/websocket/message-handler.ts @@ -210,7 +210,7 @@ export const handlePrompt = async ( * 这些统计数据用于 OpenClaw 控制台的活动监控面板。 */ runtime.channel.activity.record({ - channel: "wechat-access", + channel: "wechat-access-unqclawed", accountId: route.accountId ?? "default", direction: "inbound", }); @@ -449,7 +449,7 @@ export const handlePrompt = async ( // 记录出站活动统计(每次 deliver 都算一次出站) runtime.channel.activity.record({ - channel: "wechat-access", + channel: "wechat-access-unqclawed", accountId: route.accountId ?? "default", direction: "outbound", });