qidao-openclaw-plugin 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qidao-openclaw-plugin",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "description": "OpenClaw 栖岛聊天 Channel 插件 - 连接到栖岛聊天服务",
package/src/auth-cli.js CHANGED
@@ -2,6 +2,9 @@
2
2
  * 栖岛聊天认证 CLI 命令
3
3
  */
4
4
 
5
+ import ws from 'ws';
6
+ import { spawn } from 'child_process';
7
+
5
8
  export function registerAuthCommand(api) {
6
9
  api.registerCli(
7
10
  ({ program }) => {
@@ -9,17 +12,162 @@ export function registerAuthCommand(api) {
9
12
  .command('qidao-auth')
10
13
  .description('栖岛聊天二维码认证')
11
14
  .action(async () => {
12
- console.log('🔐 开始栖岛聊天认证流程...');
13
15
  console.log('');
14
- console.log('请使用以下步骤完成认证:');
15
- console.log('1. 访问栖岛官方网站');
16
- console.log('2. 扫描二维码登录');
17
- console.log('3. 获取您的 chatId');
16
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
17
+ console.log('');
18
+ console.log(' 🔐 栖岛聊天二维码认证');
19
+ console.log('');
20
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
18
21
  console.log('');
19
- console.log('然后运行:');
20
- console.log(' openclaw config set channels.qidao.chatId <您的chatId>');
21
- console.log(' openclaw config set channels.qidao.enabled true');
22
- console.log(' openclaw gateway restart');
22
+
23
+ try {
24
+ // 创建临时连接到 Server 进行认证
25
+ const tempWs = new ws('wss://oc.qidao.chat/ws');
26
+
27
+ const chatId = await new Promise((resolve, reject) => {
28
+ const timeout = setTimeout(() => {
29
+ tempWs.close();
30
+ reject(new Error('连接超时'));
31
+ }, 10000);
32
+
33
+ tempWs.on('open', () => {
34
+ clearTimeout(timeout);
35
+ console.log('✅ 已连接到认证服务器');
36
+ console.log('');
37
+
38
+ // 请求生成二维码
39
+ tempWs.send(JSON.stringify({ action: 'startAuth' }));
40
+ });
41
+
42
+ tempWs.on('message', async (data) => {
43
+ try {
44
+ const message = JSON.parse(data.toString());
45
+
46
+ // 兼容Server的响应格式
47
+ // 格式1: { id, code: 1, msg, data: { code, qrUrl } }
48
+ // 格式2: { action: 'authStarted', code, qrUrl }
49
+ if ((message.code === 1 && message.data && message.data.qrUrl) ||
50
+ (message.action === 'authStarted')) {
51
+ const code = message.data?.code || message.code;
52
+ const qrUrl = message.data?.qrUrl || message.qrUrl;
53
+
54
+ console.log('📱 请使用栖岛 APP 扫描二维码:');
55
+ console.log('');
56
+ console.log(` ${qrUrl}`);
57
+ console.log('');
58
+ console.log('正在打开浏览器...');
59
+ console.log('');
60
+
61
+ // 打开浏览器显示二维码
62
+ const openCommand = process.platform === 'win32' ? 'start' :
63
+ process.platform === 'darwin' ? 'open' : 'xdg-open';
64
+ spawn(openCommand, [qrUrl], { shell: true, detached: true });
65
+
66
+ console.log('⏳ 等待扫码...');
67
+ console.log('');
68
+
69
+ // 开始轮询扫码状态
70
+ const pollInterval = setInterval(() => {
71
+ tempWs.send(JSON.stringify({ action: 'checkAuthStatus', code }));
72
+ }, 2000);
73
+
74
+ // 保存轮询定时器以便后续清理
75
+ tempWs._pollInterval = pollInterval;
76
+ tempWs._code = code;
77
+
78
+ } else if ((message.code === 1 && message.data && message.data.status) ||
79
+ (message.action === 'authStatus')) {
80
+ // 兼容两种格式的扫码状态响应
81
+ const status = message.data?.status || message.status;
82
+ const uid = message.data?.uid || message.uid;
83
+
84
+ if (status === 'success') {
85
+ console.log('✅ 扫码成功!');
86
+ console.log('');
87
+
88
+ // 请求完成认证获取 chatId
89
+ tempWs.send(JSON.stringify({
90
+ action: 'completeAuth',
91
+ uid,
92
+ code: tempWs._code
93
+ }));
94
+
95
+ } else if (status === 'waiting') {
96
+ // 继续等待
97
+ }
98
+
99
+ } else if ((message.code === 1 && message.data && message.data.chatId) ||
100
+ (message.action === 'authCompleted')) {
101
+ // 清理轮询定时器
102
+ if (tempWs._pollInterval) {
103
+ clearInterval(tempWs._pollInterval);
104
+ }
105
+
106
+ const receivedChatId = message.data?.chatId || message.chatId;
107
+ console.log(`🎉 认证完成!您的 chatId: ${receivedChatId}`);
108
+ console.log('');
109
+
110
+ tempWs.close();
111
+ resolve(receivedChatId);
112
+
113
+ } else if (message.code === 0 || message.action === 'error') {
114
+ if (tempWs._pollInterval) {
115
+ clearInterval(tempWs._pollInterval);
116
+ }
117
+ tempWs.close();
118
+ const errorMsg = message.msg || message.message || '认证失败';
119
+ reject(new Error(errorMsg));
120
+ }
121
+ } catch (err) {
122
+ console.error('处理消息失败:', err);
123
+ reject(err);
124
+ }
125
+ });
126
+
127
+ tempWs.on('error', (error) => {
128
+ clearTimeout(timeout);
129
+ if (tempWs._pollInterval) {
130
+ clearInterval(tempWs._pollInterval);
131
+ }
132
+ reject(error);
133
+ });
134
+ });
135
+
136
+ // 自动保存配置
137
+ console.log('正在保存配置...');
138
+ console.log('');
139
+
140
+ const { execSync } = await import('child_process');
141
+ execSync(`openclaw config set channels.qidao.chatId ${chatId}`, { stdio: 'inherit' });
142
+ execSync(`openclaw config set channels.qidao.enabled true`, { stdio: 'inherit' });
143
+
144
+ console.log('');
145
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
146
+ console.log('');
147
+ console.log(' ✅ 配置完成!');
148
+ console.log('');
149
+ console.log(' 请重启 Gateway 以应用配置:');
150
+ console.log('');
151
+ console.log(' openclaw gateway restart');
152
+ console.log('');
153
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
154
+ console.log('');
155
+
156
+ } catch (error) {
157
+ console.error('');
158
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
159
+ console.error('');
160
+ console.error(' ❌ 认证失败:', error.message);
161
+ console.error('');
162
+ console.error(' 请重试或手动配置:');
163
+ console.error('');
164
+ console.error(' openclaw config set channels.qidao.chatId <您的chatId>');
165
+ console.error(' openclaw config set channels.qidao.enabled true');
166
+ console.error('');
167
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
168
+ console.error('');
169
+ process.exit(1);
170
+ }
23
171
  });
24
172
  },
25
173
  { commands: ['qidao-auth'] }
package/src/index.js CHANGED
@@ -46,7 +46,7 @@ const qidaoChannel = {
46
46
  enabled: qidaoConfig.enabled !== false,
47
47
  chatId: qidaoConfig.chatId,
48
48
  userId: qidaoConfig.userId,
49
- serverUrl: qidaoConfig.serverUrl ?? 'wss://oc.qidao.chat/ws',
49
+ serverUrl: 'wss://oc.qidao.chat/ws', // 固定的服务器地址
50
50
  };
51
51
  },
52
52
  },
@@ -81,40 +81,56 @@ const qidaoChannel = {
81
81
 
82
82
  // 提示用户
83
83
  console.log('');
84
- console.log('栖岛聊天需要通过扫码获取 chatId');
85
- console.log('请访问栖岛官方网站完成认证后,输入获取到的 chatId');
84
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
85
+ console.log('');
86
+ console.log(' 📱 栖岛聊天配置');
87
+ console.log('');
88
+ console.log(' 推荐使用二维码认证(自动获取 chatId):');
89
+ console.log('');
90
+ console.log(' 请退出此向导(按 Ctrl+C),然后运行:');
91
+ console.log('');
92
+ console.log(' openclaw qidao-auth');
93
+ console.log('');
94
+ console.log(' 或者继续手动输入 chatId');
95
+ console.log('');
96
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
86
97
  console.log('');
87
98
 
88
99
  const chatIdInput = await prompter.text({
89
- message: '请输入栖岛 Chat ID:',
100
+ message: '请输入栖岛 Chat ID (或按 Ctrl+C 退出后使用 openclaw qidao-auth):',
90
101
  default: qidaoConfig.chatId?.toString() || '',
91
102
  });
92
103
 
93
104
  // 检查输入是否为空或只有空格
94
105
  if (!chatIdInput || chatIdInput.trim() === '') {
106
+ console.log('');
95
107
  console.log('未输入 chatId,取消配置');
108
+ console.log('');
109
+ console.log('提示:运行 openclaw qidao-auth 使用二维码认证');
110
+ console.log('');
96
111
  return { cfg };
97
112
  }
98
113
 
99
- const serverUrl = await prompter.text({
100
- message: '请输入服务器地址:',
101
- default: qidaoConfig.serverUrl || 'wss://oc.qidao.chat/ws',
102
- });
114
+ const chatId = parseInt(chatIdInput.trim());
103
115
 
104
- // 构建新配置 - 直接在 channels.qidao 下配置
116
+ // 构建新配置
105
117
  const newCfg = {
106
118
  ...cfg,
107
119
  channels: {
108
120
  ...cfg.channels,
109
121
  qidao: {
110
122
  enabled: true,
111
- chatId: parseInt(chatIdInput.trim()),
112
- serverUrl: (serverUrl && serverUrl.trim()) || 'wss://oc.qidao.chat/ws',
123
+ chatId,
113
124
  },
114
125
  },
115
126
  };
116
127
 
117
- console.log('配置已保存:', newCfg.channels.qidao);
128
+ console.log('');
129
+ console.log('✅ 配置已保存:', newCfg.channels.qidao);
130
+ console.log('');
131
+ console.log('请重启 Gateway 以应用配置:');
132
+ console.log(' openclaw gateway restart');
133
+ console.log('');
118
134
 
119
135
  return { cfg: newCfg, accountId: 'default' };
120
136
  },
@@ -154,8 +170,164 @@ const qidaoChannel = {
154
170
  }
155
171
 
156
172
  if (!account.chatId) {
157
- runtime.log?.('栖岛聊天缺少chatId配置');
158
- return;
173
+ runtime.log?.('');
174
+ runtime.log?.('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
175
+ runtime.log?.('');
176
+ runtime.log?.(' 📱 栖岛聊天需要配置');
177
+ runtime.log?.('');
178
+ runtime.log?.(' 正在启动二维码认证...');
179
+ runtime.log?.('');
180
+ runtime.log?.('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
181
+ runtime.log?.('');
182
+
183
+ try {
184
+ // 动态导入认证模块
185
+ const ws = (await import('ws')).default;
186
+ const { spawn } = await import('child_process');
187
+
188
+ // 创建临时连接到 Server 进行认证
189
+ const tempWs = new ws('wss://oc.qidao.chat/ws');
190
+
191
+ const chatId = await new Promise((resolve, reject) => {
192
+ const timeout = setTimeout(() => {
193
+ tempWs.close();
194
+ reject(new Error('连接超时'));
195
+ }, 10000);
196
+
197
+ tempWs.on('open', () => {
198
+ clearTimeout(timeout);
199
+ runtime.log?.('✅ 已连接到认证服务器');
200
+ runtime.log?.('');
201
+
202
+ // 请求生成二维码
203
+ tempWs.send(JSON.stringify({ action: 'startAuth' }));
204
+ });
205
+
206
+ tempWs.on('message', async (data) => {
207
+ try {
208
+ const message = JSON.parse(data.toString());
209
+
210
+ // 兼容Server的响应格式
211
+ if ((message.code === 1 && message.data && message.data.qrUrl) ||
212
+ (message.action === 'authStarted')) {
213
+ const code = message.data?.code || message.code;
214
+ const qrUrl = message.data?.qrUrl || message.qrUrl;
215
+
216
+ runtime.log?.('📱 请使用栖岛 APP 扫描二维码:');
217
+ runtime.log?.('');
218
+ runtime.log?.(` ${qrUrl}`);
219
+ runtime.log?.('');
220
+ runtime.log?.('正在打开浏览器...');
221
+ runtime.log?.('');
222
+
223
+ // 打开浏览器显示二维码
224
+ const openCommand = process.platform === 'win32' ? 'start' :
225
+ process.platform === 'darwin' ? 'open' : 'xdg-open';
226
+ spawn(openCommand, [qrUrl], { shell: true, detached: true });
227
+
228
+ runtime.log?.('⏳ 等待扫码...');
229
+ runtime.log?.('');
230
+
231
+ // 开始轮询扫码状态
232
+ const pollInterval = setInterval(() => {
233
+ tempWs.send(JSON.stringify({ action: 'checkAuthStatus', code }));
234
+ }, 2000);
235
+
236
+ // 保存轮询定时器以便后续清理
237
+ tempWs._pollInterval = pollInterval;
238
+ tempWs._code = code;
239
+
240
+ } else if ((message.code === 1 && message.data && message.data.status) ||
241
+ (message.action === 'authStatus')) {
242
+ const status = message.data?.status || message.status;
243
+ const uid = message.data?.uid || message.uid;
244
+
245
+ if (status === 'success') {
246
+ runtime.log?.('✅ 扫码成功!');
247
+ runtime.log?.('');
248
+
249
+ // 请求完成认证获取 chatId
250
+ tempWs.send(JSON.stringify({
251
+ action: 'completeAuth',
252
+ uid,
253
+ code: tempWs._code
254
+ }));
255
+
256
+ } else if (status === 'waiting') {
257
+ // 继续等待
258
+ }
259
+
260
+ } else if ((message.code === 1 && message.data && message.data.chatId) ||
261
+ (message.action === 'authCompleted')) {
262
+ // 清理轮询定时器
263
+ if (tempWs._pollInterval) {
264
+ clearInterval(tempWs._pollInterval);
265
+ }
266
+
267
+ const receivedChatId = message.data?.chatId || message.chatId;
268
+ runtime.log?.(`🎉 认证完成!您的 chatId: ${receivedChatId}`);
269
+ runtime.log?.('');
270
+
271
+ tempWs.close();
272
+ resolve(receivedChatId);
273
+
274
+ } else if (message.code === 0 || message.action === 'error') {
275
+ if (tempWs._pollInterval) {
276
+ clearInterval(tempWs._pollInterval);
277
+ }
278
+ tempWs.close();
279
+ const errorMsg = message.msg || message.message || '认证失败';
280
+ reject(new Error(errorMsg));
281
+ }
282
+ } catch (err) {
283
+ runtime.error?.('处理消息失败:', err);
284
+ reject(err);
285
+ }
286
+ });
287
+
288
+ tempWs.on('error', (error) => {
289
+ clearTimeout(timeout);
290
+ if (tempWs._pollInterval) {
291
+ clearInterval(tempWs._pollInterval);
292
+ }
293
+ reject(error);
294
+ });
295
+ });
296
+
297
+ // 自动保存配置
298
+ runtime.log?.('正在保存配置...');
299
+ runtime.log?.('');
300
+
301
+ const { execSync } = await import('child_process');
302
+ execSync(`openclaw config set channels.qidao.chatId ${chatId}`, { stdio: 'inherit' });
303
+
304
+ runtime.log?.('');
305
+ runtime.log?.('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
306
+ runtime.log?.('');
307
+ runtime.log?.(' ✅ 配置完成!Gateway 将自动重新连接...');
308
+ runtime.log?.('');
309
+ runtime.log?.('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
310
+ runtime.log?.('');
311
+
312
+ // 更新 account 对象
313
+ account.chatId = chatId;
314
+
315
+ } catch (error) {
316
+ runtime.error?.('');
317
+ runtime.error?.('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
318
+ runtime.error?.('');
319
+ runtime.error?.(` ❌ 认证失败: ${error.message}`);
320
+ runtime.error?.('');
321
+ runtime.error?.(' 请运行以下命令重新认证:');
322
+ runtime.error?.('');
323
+ runtime.error?.(' openclaw qidao-auth');
324
+ runtime.error?.('');
325
+ runtime.error?.('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
326
+ runtime.error?.('');
327
+
328
+ // 抛出错误以停止自动重试
329
+ throw new Error('栖岛聊天缺少chatId配置,请运行 openclaw qidao-auth 完成认证');
330
+ }
159
331
  }
160
332
 
161
333
  runtime.log?.(`连接栖岛聊天 (chatId: ${account.chatId})`);
@@ -182,7 +354,6 @@ const qidaoChannel = {
182
354
  if (message.type === 'new_message') {
183
355
  try {
184
356
  // 过滤掉机器人自己发送的消息,避免循环
185
- // 栖岛消息中,senderId=1 是畅小猪,otherUserId=175 是机器人
186
357
  if (message.senderId === 175 || message.otherUserId === 1) {
187
358
  runtime.log?.(`跳过机器人自己的消息`);
188
359
  return;
@@ -195,7 +366,6 @@ const qidaoChannel = {
195
366
  const chatId = message.chatId.toString();
196
367
  const chatType = message.chatType === 0 ? 'direct' : 'group';
197
368
 
198
- // 解析路由信息
199
369
  const route = core.channel.routing.resolveAgentRoute({
200
370
  cfg,
201
371
  channel: 'qidao',
@@ -206,7 +376,6 @@ const qidaoChannel = {
206
376
  },
207
377
  });
208
378
 
209
- // 构建标准消息上下文(参考企业微信插件)
210
379
  const ctxPayload = core.channel.reply.finalizeInboundContext({
211
380
  Body: message.messageText,
212
381
  RawBody: message.messageText,
@@ -227,7 +396,6 @@ const qidaoChannel = {
227
396
  CommandAuthorized: true,
228
397
  });
229
398
 
230
- // 使用 core.channel.reply.dispatchReplyWithBufferedBlockDispatcher 处理消息
231
399
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
232
400
  ctx: ctxPayload,
233
401
  cfg,
@@ -235,7 +403,6 @@ const qidaoChannel = {
235
403
  deliver: async (payload, info) => {
236
404
  runtime.log?.(`发送回复: ${payload.text.substring(0, 50)}...`);
237
405
 
238
- // 通过 connection 发送回复
239
406
  if (info.kind === 'final') {
240
407
  await connection.sendMessage(message.chatId, payload.text);
241
408
  }
@@ -253,7 +420,6 @@ const qidaoChannel = {
253
420
  }
254
421
  });
255
422
 
256
- // 处理中止信号
257
423
  if (abortSignal) {
258
424
  abortSignal.addEventListener('abort', () => {
259
425
  runtime.log?.('栖岛聊天连接被中止');
@@ -266,7 +432,6 @@ const qidaoChannel = {
266
432
 
267
433
  await connection.connect();
268
434
 
269
- // 返回 Promise,保持连接直到被中止
270
435
  return new Promise((resolve) => {
271
436
  if (abortSignal) {
272
437
  abortSignal.addEventListener('abort', resolve);