qidao-openclaw-plugin 1.3.4 → 2.0.0-beta.2

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.
@@ -1,446 +0,0 @@
1
- import WebSocket from 'ws';
2
- import { getWebSocketUrl, QIDAO_CONFIG } from './config.js';
3
- import { exec } from 'child_process';
4
- import os from 'os';
5
-
6
- export class QidaoChannel {
7
- constructor(config = {}) {
8
- this.serverUrl = config.serverUrl || getWebSocketUrl(config.env);
9
- this.chatId = config.chatId || null;
10
- this.userId = config.userId || null;
11
- this.ws = null;
12
- this.connected = false;
13
- this.chatIdSet = false;
14
- this.reconnectInterval = config.reconnectInterval || QIDAO_CONFIG.DEFAULTS.RECONNECT_INTERVAL;
15
- this.reconnectTimer = null;
16
- this.messageHandlers = new Map();
17
- this.eventHandlers = {
18
- onConnect: null,
19
- onDisconnect: null,
20
- onMessage: null,
21
- onError: null
22
- };
23
-
24
- // 心跳机制
25
- this.heartbeatInterval = null;
26
- this.heartbeatIntervalMs = 30000; // 30秒发送一次心跳
27
- this.heartbeatTimeoutMs = 10000; // 10秒心跳超时
28
- this.lastPongTime = 0;
29
- }
30
-
31
- connect() {
32
- return new Promise((resolve, reject) => {
33
- try {
34
- console.log('🔗 正在连接栖岛中转服务器...');
35
-
36
- this.ws = new WebSocket(this.serverUrl);
37
-
38
- this.ws.on('open', async () => {
39
- console.log('✅ 已连接到栖岛服务');
40
- this.connected = true;
41
- this.lastPongTime = Date.now();
42
-
43
- // 启动心跳
44
- this.startHeartbeat();
45
-
46
- if (this.eventHandlers.onConnect) {
47
- this.eventHandlers.onConnect();
48
- }
49
-
50
- resolve();
51
-
52
- // 延迟自动设置chatId,确保连接稳定后再设置
53
- if (this.chatId && !this.chatIdSet) {
54
- setTimeout(async () => {
55
- if (this.isConnected()) {
56
- try {
57
- console.log('🔄 延迟设置chatId:', this.chatId);
58
- await this.setChatId(this.chatId, this.userId);
59
- this.chatIdSet = true;
60
- console.log('✅ 延迟设置chatId成功');
61
- } catch (error) {
62
- console.error('❌ 延迟设置chatId失败:', error.message);
63
- // 设置失败不影响连接,可以稍后重试
64
- }
65
- }
66
- }, 2000); // 延迟2秒,确保连接完全稳定
67
- }
68
- });
69
-
70
- this.ws.on('message', (data) => {
71
- this.handleMessage(data);
72
- });
73
-
74
- this.ws.on('close', () => {
75
- console.log('🔌 栖岛服务连接已断开');
76
- this.connected = false;
77
- this.chatIdSet = false;
78
-
79
- // 停止心跳
80
- this.stopHeartbeat();
81
-
82
- if (this.eventHandlers.onDisconnect) {
83
- this.eventHandlers.onDisconnect();
84
- }
85
-
86
- this.reconnect();
87
- });
88
-
89
- this.ws.on('error', (error) => {
90
- console.error('❌ WebSocket错误:', error.message);
91
- console.error('❌ 错误类型:', error.code);
92
- console.error('❌ 错误详情:', error);
93
- this.connected = false;
94
-
95
- if (this.eventHandlers.onError) {
96
- this.eventHandlers.onError(error);
97
- }
98
-
99
- reject(error);
100
- });
101
-
102
- } catch (error) {
103
- reject(error);
104
- }
105
- });
106
- }
107
-
108
- reconnect() {
109
- if (this.reconnectTimer) return;
110
-
111
- console.log(`⏳ ${this.reconnectInterval / 1000}秒后重连...`);
112
- this.reconnectTimer = setTimeout(() => {
113
- this.reconnectTimer = null;
114
- this.connect().catch(err => {
115
- console.error('❌ 重连失败:', err.message);
116
- });
117
- }, this.reconnectInterval);
118
- }
119
-
120
- disconnect() {
121
- if (this.reconnectTimer) {
122
- clearTimeout(this.reconnectTimer);
123
- this.reconnectTimer = null;
124
- }
125
-
126
- // 停止心跳
127
- this.stopHeartbeat();
128
-
129
- if (this.ws) {
130
- this.ws.close();
131
- this.ws = null;
132
- }
133
-
134
- this.connected = false;
135
- }
136
-
137
- isConnected() {
138
- return this.connected && this.ws && this.ws.readyState === WebSocket.OPEN;
139
- }
140
-
141
- on(event, handler) {
142
- if (this.eventHandlers.hasOwnProperty(`on${event.charAt(0).toUpperCase()}${event.slice(1)}`)) {
143
- this.eventHandlers[`on${event.charAt(0).toUpperCase()}${event.slice(1)}`] = handler;
144
- }
145
- }
146
-
147
- handleMessage(data) {
148
- try {
149
- const message = JSON.parse(data.toString());
150
-
151
- // 处理心跳响应
152
- if (message.type === 'pong') {
153
- this.lastPongTime = Date.now();
154
- console.log('💓 收到Server心跳响应');
155
- return;
156
- }
157
-
158
- // 先触发通用消息事件
159
- if (this.eventHandlers.onMessage) {
160
- this.eventHandlers.onMessage(message);
161
- }
162
-
163
- // 处理有ID的响应消息
164
- if (message.id && this.messageHandlers.has(message.id)) {
165
- const handler = this.messageHandlers.get(message.id);
166
- handler(message);
167
- this.messageHandlers.delete(message.id);
168
- return;
169
- }
170
-
171
- } catch (error) {
172
- console.error('❌ 处理消息失败:', error.message);
173
- }
174
- }
175
-
176
- // 启动心跳
177
- startHeartbeat() {
178
- this.stopHeartbeat();
179
-
180
- console.log('💓 启动插件心跳检测');
181
-
182
- this.heartbeatInterval = setInterval(() => {
183
- if (this.isConnected()) {
184
- this.sendHeartbeat();
185
-
186
- // 检查心跳超时
187
- const now = Date.now();
188
- if (now - this.lastPongTime > this.heartbeatIntervalMs + this.heartbeatTimeoutMs) {
189
- console.error('❌ 心跳超时,主动断开连接');
190
- this.ws.close();
191
- }
192
- }
193
- }, this.heartbeatIntervalMs);
194
- }
195
-
196
- // 停止心跳
197
- stopHeartbeat() {
198
- if (this.heartbeatInterval) {
199
- clearInterval(this.heartbeatInterval);
200
- this.heartbeatInterval = null;
201
- console.log('💓 停止插件心跳检测');
202
- }
203
- }
204
-
205
- // 发送心跳
206
- sendHeartbeat() {
207
- if (!this.isConnected()) {
208
- return;
209
- }
210
-
211
- try {
212
- const heartbeatMessage = {
213
- type: 'ping',
214
- timestamp: Date.now()
215
- };
216
-
217
- this.ws.send(JSON.stringify(heartbeatMessage));
218
- console.log('💓 发送插件心跳');
219
- } catch (error) {
220
- console.error('❌ 发送心跳失败:', error.message);
221
- }
222
- }
223
-
224
- sendRequest(request) {
225
- return new Promise((resolve, reject) => {
226
- if (!this.isConnected()) {
227
- reject(new Error('未连接到服务器'));
228
- return;
229
- }
230
-
231
- const messageId = Date.now() + Math.random();
232
- const message = {
233
- id: messageId,
234
- timestamp: Date.now(),
235
- ...request
236
- };
237
-
238
- const timeout = setTimeout(() => {
239
- this.messageHandlers.delete(messageId);
240
- reject(new Error('请求超时'));
241
- }, QIDAO_CONFIG.DEFAULTS.REQUEST_TIMEOUT);
242
-
243
- this.messageHandlers.set(messageId, (response) => {
244
- clearTimeout(timeout);
245
-
246
- if (response.code === 1) {
247
- resolve(response.data);
248
- } else {
249
- reject(new Error(response.msg || '请求失败'));
250
- }
251
- });
252
-
253
- this.ws.send(JSON.stringify(message));
254
- });
255
- }
256
-
257
- async sendMessage(chatId, message, messageType = QIDAO_CONFIG.MESSAGE_TYPES.TEXT, url = null) {
258
- return this.sendRequest({
259
- action: 'sendMessage',
260
- chatId,
261
- message,
262
- messageType,
263
- url
264
- });
265
- }
266
-
267
- async sendImageMessage(chatId, url) {
268
- return this.sendMessage(chatId, '', QIDAO_CONFIG.MESSAGE_TYPES.IMAGE, url);
269
- }
270
-
271
- async getChatRoomOnlineUsers(chatId) {
272
- return this.sendRequest({
273
- action: 'chatRoomOnlineUsers',
274
- chatId
275
- });
276
- }
277
-
278
- async getUserOnlineStatus(targetUserId) {
279
- return this.sendRequest({
280
- action: 'userOnlineStatus',
281
- targetUserId
282
- });
283
- }
284
-
285
- /**
286
- * 二维码认证流程 - 三步走
287
- * 1. 请求生成二维码
288
- * 2. 轮询扫码状态
289
- * 3. 完成认证获取chatId
290
- * @returns {Promise<{chatId: number, uid: number}>}
291
- */
292
- async qrAuth() {
293
- if (!this.isConnected()) {
294
- throw new Error('未连接到服务器');
295
- }
296
-
297
- console.log('🔐 开始二维码认证流程...');
298
-
299
- // 步骤1: 请求生成二维码
300
- console.log('📝 步骤1: 生成二维码...');
301
-
302
- // 使用sendRequest获取二维码数据
303
- const qrData = await this.sendRequest({
304
- action: 'startAuth'
305
- });
306
-
307
- console.log('✅ 二维码已生成:', qrData.qrUrl);
308
- console.log('📱 请使用栖岛APP扫描二维码');
309
-
310
- // 立即打开浏览器显示二维码
311
- console.log('🌐 正在打开浏览器...');
312
- this.openBrowser(qrData.qrUrl);
313
-
314
- // 步骤2: 轮询扫码状态
315
- console.log('⏳ 步骤2: 等待扫码...');
316
- let scanResult;
317
- let attempts = 0;
318
- const maxAttempts = 60; // 最多等待5分钟
319
-
320
- while (attempts < maxAttempts) {
321
- await new Promise(resolve => setTimeout(resolve, 5000)); // 等待5秒
322
- attempts++;
323
-
324
- try {
325
- const status = await this.sendRequest({
326
- action: 'checkAuthStatus',
327
- code: qrData.code
328
- });
329
-
330
- console.log(`🔍 检查状态 (${attempts}/${maxAttempts}):`, status.status);
331
-
332
- if (status.status === 'success') {
333
- console.log('✅ 扫码成功!');
334
- scanResult = status;
335
- break;
336
- } else if (status.status === 'expired') {
337
- throw new Error('二维码已过期,请重新生成');
338
- }
339
- // status === 'waiting' 继续等待
340
- } catch (error) {
341
- console.error('❌ 检查状态失败:', error.message);
342
- throw error;
343
- }
344
- }
345
-
346
- if (!scanResult || scanResult.status !== 'success') {
347
- throw new Error('扫码超时,请重试');
348
- }
349
-
350
- // 步骤3: 完成认证(添加好友+获取chatId)
351
- console.log('🔄 步骤3: 完成认证...');
352
- const authResult = await this.sendRequest({
353
- action: 'completeAuth',
354
- uid: scanResult.uid
355
- });
356
-
357
- console.log('✅ 认证完成!');
358
- console.log(`💬 ChatID: ${authResult.chatId}`);
359
- console.log(`👤 UID: ${authResult.uid}`);
360
-
361
- // 自动设置chatId
362
- this.chatId = authResult.chatId;
363
- this.userId = authResult.uid;
364
- this.chatIdSet = true;
365
-
366
- return {
367
- chatId: authResult.chatId,
368
- uid: authResult.uid
369
- };
370
- }
371
-
372
- // 打开浏览器
373
- openBrowser(url) {
374
- let command;
375
- const platform = os.platform();
376
-
377
- console.log('🌐 当前系统:', platform);
378
- console.log('🌐 打开URL:', url);
379
-
380
- if (platform === 'win32') {
381
- // Windows系统使用start命令
382
- // 注意:需要使用cmd /c来执行start命令
383
- command = `cmd /c start "" "${url}"`;
384
- } else if (platform === 'darwin') {
385
- // macOS系统使用open命令
386
- command = `open "${url}"`;
387
- } else {
388
- // Linux系统使用xdg-open命令
389
- command = `xdg-open "${url}"`;
390
- }
391
-
392
- console.log('🌐 执行命令:', command);
393
-
394
- exec(command, (error, stdout, stderr) => {
395
- if (error) {
396
- console.error('❌ 打开浏览器失败:', error.message);
397
- if (stderr) {
398
- console.error('❌ 错误详情:', stderr);
399
- }
400
- console.log('📱 请手动复制以下链接到浏览器中打开:');
401
- console.log(url);
402
- } else {
403
- console.log('✅ 浏览器已打开,请扫描二维码');
404
- }
405
- });
406
- }
407
-
408
- // 设置插件的chatId
409
- async setChatId(chatId, userId = null) {
410
- if (!this.isConnected()) {
411
- throw new Error('未连接到服务器');
412
- }
413
-
414
- const setChatIdMessage = {
415
- action: 'setChatId',
416
- chatId: chatId,
417
- userId: userId,
418
- timestamp: Date.now()
419
- };
420
-
421
- return new Promise((resolve, reject) => {
422
- const messageId = Date.now() + Math.random();
423
- setChatIdMessage.id = messageId;
424
-
425
- const timeout = setTimeout(() => {
426
- this.messageHandlers.delete(messageId);
427
- reject(new Error('设置chatId超时'));
428
- }, QIDAO_CONFIG.DEFAULTS.REQUEST_TIMEOUT);
429
-
430
- this.messageHandlers.set(messageId, (response) => {
431
- clearTimeout(timeout);
432
-
433
- if (response.code === 1) {
434
- console.log(`✅ 设置chatId成功: ${chatId}`);
435
- resolve(response.data);
436
- } else {
437
- reject(new Error(response.msg || '设置chatId失败'));
438
- }
439
- });
440
-
441
- this.ws.send(JSON.stringify(setChatIdMessage));
442
- });
443
- }
444
- }
445
-
446
- export default QidaoChannel;