fix: rename plugin id to wechat-access-unqclawed, add login/logout commands, update README with enable instructions
This commit is contained in:
16
README.md
16
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 | 跳过邀请码验证 |
|
||||
|
||||
@@ -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 处理消息所需的所有信息
|
||||
|
||||
@@ -103,7 +103,7 @@ export const handleMessage = async (message: FuwuhaoMessage): Promise<string | n
|
||||
// runtime.channel.activity.record 记录频道的活动统计
|
||||
// 用于监控、分析、计费等场景
|
||||
runtime.channel.activity.record({
|
||||
channel: "wechat-access", // 频道标识
|
||||
channel: "wechat-access-unqclawed", // 频道标识
|
||||
accountId: "default", // 账号 ID
|
||||
direction: "inbound", // 方向:inbound=入站(用户发送),outbound=出站(Bot 回复)
|
||||
});
|
||||
@@ -196,7 +196,7 @@ export const handleMessage = async (message: FuwuhaoMessage): Promise<string | n
|
||||
|
||||
// 记录出站活动统计(Bot 回复)
|
||||
runtime.channel.activity.record({
|
||||
channel: "wechat-access",
|
||||
channel: "wechat-access-unqclawed",
|
||||
accountId: "default",
|
||||
direction: "outbound", // 出站:Bot 发送给用户
|
||||
});
|
||||
@@ -322,7 +322,7 @@ export const handleMessageStream = async (
|
||||
// 4. 记录频道活动统计
|
||||
// ============================================
|
||||
runtime.channel.activity.record({
|
||||
channel: "wechat-access",
|
||||
channel: "wechat-access-unqclawed",
|
||||
accountId: "default",
|
||||
direction: "inbound",
|
||||
});
|
||||
@@ -519,7 +519,7 @@ const unsubscribeAgentEvents = onAgentEvent((evt: AgentEventPayload) => {
|
||||
|
||||
// 记录出站活动
|
||||
runtime.channel.activity.record({
|
||||
channel: "wechat-access",
|
||||
channel: "wechat-access-unqclawed",
|
||||
accountId: "default",
|
||||
direction: "outbound",
|
||||
});
|
||||
|
||||
63
index.ts
63
index.ts
@@ -13,14 +13,14 @@ const wsClients = new Map<string, WechatAccessWebSocketClient>();
|
||||
|
||||
// 渠道元数据
|
||||
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] 腾讯通路插件已注册");
|
||||
},
|
||||
|
||||
@@ -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": {
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user