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.
- package/auth-cli.encrypted.js +8 -0
- package/auth-cli.js +1 -0
- package/config.js +1 -0
- package/device-fingerprint.encrypted.js +8 -0
- package/device-fingerprint.js +1 -0
- package/index.js +1 -0
- package/integrity.json +42 -0
- package/package.json +48 -61
- package/plugin.js +1 -0
- package/qidao-channel.js +1 -0
- package/qr-server.js +259 -0
- package/src/auth-cli.js +0 -175
- package/src/config.js +0 -116
- package/src/index.js +0 -458
- package/src/plugin.js +0 -205
- package/src/qidao-channel.js +0 -446
package/src/index.js
DELETED
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 栖岛聊天 OpenClaw Channel 插件
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { QidaoChannel } from './qidao-channel.js';
|
|
6
|
-
import { registerAuthCommand } from './auth-cli.js';
|
|
7
|
-
|
|
8
|
-
let connection = null;
|
|
9
|
-
let pluginApi = null;
|
|
10
|
-
let qidaoRuntime = null;
|
|
11
|
-
|
|
12
|
-
function setQidaoRuntime(r) {
|
|
13
|
-
qidaoRuntime = r;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function getQidaoRuntime() {
|
|
17
|
-
if (!qidaoRuntime) {
|
|
18
|
-
throw new Error('Qidao runtime not initialized - plugin not registered');
|
|
19
|
-
}
|
|
20
|
-
return qidaoRuntime;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const qidaoChannel = {
|
|
24
|
-
id: 'qidao',
|
|
25
|
-
|
|
26
|
-
meta: {
|
|
27
|
-
id: 'qidao',
|
|
28
|
-
label: '栖岛聊天',
|
|
29
|
-
selectionLabel: '栖岛聊天 (Qidao Chat)',
|
|
30
|
-
blurb: '连接到栖岛聊天服务',
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
capabilities: {
|
|
34
|
-
chatTypes: ['direct', 'group'],
|
|
35
|
-
hasGateway: true,
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
config: {
|
|
39
|
-
listAccountIds: () => ['default'],
|
|
40
|
-
|
|
41
|
-
resolveAccount: (cfg) => {
|
|
42
|
-
const qidaoConfig = cfg.channels?.qidao ?? {};
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
accountId: 'default',
|
|
46
|
-
enabled: qidaoConfig.enabled !== false,
|
|
47
|
-
chatId: qidaoConfig.chatId,
|
|
48
|
-
userId: qidaoConfig.userId,
|
|
49
|
-
serverUrl: 'wss://oc.qidao.chat/ws', // 固定的服务器地址
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
onboarding: {
|
|
55
|
-
getStatus: async ({ cfg }) => {
|
|
56
|
-
const qidaoConfig = cfg.channels?.qidao ?? {};
|
|
57
|
-
const configured = Boolean(qidaoConfig.chatId);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
channel: 'qidao',
|
|
61
|
-
configured,
|
|
62
|
-
statusLines: [`栖岛聊天: ${configured ? `已配置 (chatId: ${qidaoConfig.chatId})` : '未配置'}`],
|
|
63
|
-
selectionHint: configured ? '已配置' : '需要配置',
|
|
64
|
-
};
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
configure: async ({ cfg, prompter }) => {
|
|
68
|
-
const qidaoConfig = cfg.channels?.qidao ?? {};
|
|
69
|
-
|
|
70
|
-
// 如果已经有 chatId,询问是否重新认证
|
|
71
|
-
if (qidaoConfig.chatId) {
|
|
72
|
-
const reauth = await prompter.confirm({
|
|
73
|
-
message: `已配置 chatId: ${qidaoConfig.chatId},是否重新配置?`,
|
|
74
|
-
default: false,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (!reauth) {
|
|
78
|
-
return { cfg, accountId: 'default' };
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 提示用户
|
|
83
|
-
console.log('');
|
|
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('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
97
|
-
console.log('');
|
|
98
|
-
|
|
99
|
-
const chatIdInput = await prompter.text({
|
|
100
|
-
message: '请输入栖岛 Chat ID (或按 Ctrl+C 退出后使用 openclaw qidao-auth):',
|
|
101
|
-
default: qidaoConfig.chatId?.toString() || '',
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// 检查输入是否为空或只有空格
|
|
105
|
-
if (!chatIdInput || chatIdInput.trim() === '') {
|
|
106
|
-
console.log('');
|
|
107
|
-
console.log('未输入 chatId,取消配置');
|
|
108
|
-
console.log('');
|
|
109
|
-
console.log('提示:运行 openclaw qidao-auth 使用二维码认证');
|
|
110
|
-
console.log('');
|
|
111
|
-
return { cfg };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const chatId = parseInt(chatIdInput.trim());
|
|
115
|
-
|
|
116
|
-
// 构建新配置
|
|
117
|
-
const newCfg = {
|
|
118
|
-
...cfg,
|
|
119
|
-
channels: {
|
|
120
|
-
...cfg.channels,
|
|
121
|
-
qidao: {
|
|
122
|
-
enabled: true,
|
|
123
|
-
chatId,
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
|
|
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('');
|
|
134
|
-
|
|
135
|
-
return { cfg: newCfg, accountId: 'default' };
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
outbound: {
|
|
140
|
-
deliveryMode: 'direct',
|
|
141
|
-
|
|
142
|
-
sendText: async ({ text, chatId }) => {
|
|
143
|
-
if (!connection || !connection.isConnected()) {
|
|
144
|
-
return { ok: false, error: '未连接到栖岛服务' };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!chatId) {
|
|
148
|
-
return { ok: false, error: '未指定chatId' };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
await connection.sendMessage(parseInt(chatId), text);
|
|
153
|
-
return { ok: true };
|
|
154
|
-
} catch (error) {
|
|
155
|
-
return { ok: false, error: error.message };
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
gateway: {
|
|
161
|
-
startAccount: async (ctx) => {
|
|
162
|
-
const { account, cfg, runtime, abortSignal } = ctx;
|
|
163
|
-
|
|
164
|
-
runtime.log?.('🚀 栖岛聊天 gateway.startAccount 被调用!');
|
|
165
|
-
runtime.log?.(`账户信息: ${JSON.stringify(account)}`);
|
|
166
|
-
|
|
167
|
-
if (!account.enabled) {
|
|
168
|
-
runtime.log?.('栖岛聊天未启用');
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (!account.chatId) {
|
|
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
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
runtime.log?.(`连接栖岛聊天 (chatId: ${account.chatId})`);
|
|
334
|
-
|
|
335
|
-
connection = new QidaoChannel({
|
|
336
|
-
serverUrl: account.serverUrl,
|
|
337
|
-
chatId: account.chatId,
|
|
338
|
-
userId: account.userId,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
connection.on('connect', () => {
|
|
342
|
-
runtime.log?.('栖岛聊天已连接');
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
connection.on('disconnect', () => {
|
|
346
|
-
runtime.log?.('栖岛聊天已断开');
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
connection.on('error', (error) => {
|
|
350
|
-
runtime.error?.(`栖岛聊天错误: ${error.message}`);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
connection.on('message', async (message) => {
|
|
354
|
-
if (message.type === 'new_message') {
|
|
355
|
-
try {
|
|
356
|
-
// 过滤掉机器人自己发送的消息,避免循环
|
|
357
|
-
if (message.senderId === 175 || message.otherUserId === 1) {
|
|
358
|
-
runtime.log?.(`跳过机器人自己的消息`);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
runtime.log?.(`收到栖岛消息: ${message.messageText}`);
|
|
363
|
-
|
|
364
|
-
const core = getQidaoRuntime();
|
|
365
|
-
|
|
366
|
-
const chatId = message.chatId.toString();
|
|
367
|
-
const chatType = message.chatType === 0 ? 'direct' : 'group';
|
|
368
|
-
|
|
369
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
370
|
-
cfg,
|
|
371
|
-
channel: 'qidao',
|
|
372
|
-
accountId: account.accountId,
|
|
373
|
-
peer: {
|
|
374
|
-
kind: chatType,
|
|
375
|
-
id: chatId,
|
|
376
|
-
},
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
380
|
-
Body: message.messageText,
|
|
381
|
-
RawBody: message.messageText,
|
|
382
|
-
CommandBody: message.messageText,
|
|
383
|
-
MessageSid: message.messageId?.toString() || `${Date.now()}`,
|
|
384
|
-
From: chatType === 'group' ? `qidao:group:${chatId}` : `qidao:${message.senderId}`,
|
|
385
|
-
To: `qidao:${chatId}`,
|
|
386
|
-
SenderId: message.senderId.toString(),
|
|
387
|
-
SessionKey: route.sessionKey,
|
|
388
|
-
AccountId: account.accountId,
|
|
389
|
-
ChatType: chatType,
|
|
390
|
-
ConversationLabel: chatType === 'group' ? `group:${chatId}` : `user:${message.senderId}`,
|
|
391
|
-
Timestamp: message.timestamp || Date.now(),
|
|
392
|
-
Provider: 'qidao',
|
|
393
|
-
Surface: 'qidao',
|
|
394
|
-
OriginatingChannel: 'qidao',
|
|
395
|
-
OriginatingTo: `qidao:${chatId}`,
|
|
396
|
-
CommandAuthorized: true,
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
400
|
-
ctx: ctxPayload,
|
|
401
|
-
cfg,
|
|
402
|
-
dispatcherOptions: {
|
|
403
|
-
deliver: async (payload, info) => {
|
|
404
|
-
runtime.log?.(`发送回复: ${payload.text.substring(0, 50)}...`);
|
|
405
|
-
|
|
406
|
-
if (info.kind === 'final') {
|
|
407
|
-
await connection.sendMessage(message.chatId, payload.text);
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
onError: (err, info) => {
|
|
411
|
-
runtime.error?.(`回复失败 (${info.kind}): ${err.message}`);
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
runtime.log?.('✅ 消息处理完成');
|
|
417
|
-
} catch (error) {
|
|
418
|
-
runtime.error?.(`处理消息失败: ${error.message}`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
if (abortSignal) {
|
|
424
|
-
abortSignal.addEventListener('abort', () => {
|
|
425
|
-
runtime.log?.('栖岛聊天连接被中止');
|
|
426
|
-
if (connection) {
|
|
427
|
-
connection.disconnect();
|
|
428
|
-
connection = null;
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
await connection.connect();
|
|
434
|
-
|
|
435
|
-
return new Promise((resolve) => {
|
|
436
|
-
if (abortSignal) {
|
|
437
|
-
abortSignal.addEventListener('abort', resolve);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
},
|
|
441
|
-
},
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
export default function register(api) {
|
|
445
|
-
pluginApi = api;
|
|
446
|
-
|
|
447
|
-
// 设置 runtime(参考企业微信插件)
|
|
448
|
-
setQidaoRuntime(api.runtime);
|
|
449
|
-
|
|
450
|
-
api.logger.info('栖岛聊天插件加载中...');
|
|
451
|
-
|
|
452
|
-
api.registerChannel({ plugin: qidaoChannel });
|
|
453
|
-
|
|
454
|
-
// 注册认证命令
|
|
455
|
-
registerAuthCommand(api);
|
|
456
|
-
|
|
457
|
-
api.logger.info('栖岛聊天插件加载完成');
|
|
458
|
-
}
|
package/src/plugin.js
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 栖岛聊天 OpenClaw Channel 插件
|
|
3
|
-
*
|
|
4
|
-
* 这是OpenClaw的入口文件,导出register函数供OpenClaw加载
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { QidaoChannel } from './qidao-channel.js';
|
|
8
|
-
|
|
9
|
-
// 存储所有账户的连接实例
|
|
10
|
-
const accountConnections = new Map();
|
|
11
|
-
|
|
12
|
-
const qidaoChannel = {
|
|
13
|
-
id: 'qidao',
|
|
14
|
-
|
|
15
|
-
meta: {
|
|
16
|
-
id: 'qidao',
|
|
17
|
-
label: '栖岛聊天',
|
|
18
|
-
selectionLabel: '栖岛聊天 (Qidao Chat)',
|
|
19
|
-
docsPath: '/channels/qidao',
|
|
20
|
-
blurb: '连接到栖岛聊天服务,支持实时消息收发',
|
|
21
|
-
aliases: ['qidao', 'qidao-chat'],
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
capabilities: {
|
|
25
|
-
chatTypes: ['direct', 'group'],
|
|
26
|
-
supportsMedia: true,
|
|
27
|
-
supportsReactions: false,
|
|
28
|
-
supportsThreads: false,
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
config: {
|
|
32
|
-
// 列出所有配置的账户ID
|
|
33
|
-
listAccountIds: (cfg) => {
|
|
34
|
-
const accounts = cfg.channels?.qidao?.accounts ?? {};
|
|
35
|
-
return Object.keys(accounts);
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
// 解析指定账户的配置
|
|
39
|
-
resolveAccount: (cfg, accountId) => {
|
|
40
|
-
const accounts = cfg.channels?.qidao?.accounts ?? {};
|
|
41
|
-
const account = accounts[accountId ?? 'default'];
|
|
42
|
-
|
|
43
|
-
if (!account) {
|
|
44
|
-
return { accountId };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
accountId: accountId ?? 'default',
|
|
49
|
-
enabled: account.enabled ?? true,
|
|
50
|
-
chatId: account.chatId,
|
|
51
|
-
userId: account.userId,
|
|
52
|
-
serverUrl: account.serverUrl ?? 'wss://oc.qidao.chat/ws',
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
outbound: {
|
|
58
|
-
deliveryMode: 'direct',
|
|
59
|
-
|
|
60
|
-
// 发送文本消息
|
|
61
|
-
sendText: async ({ text, accountId, chatId }) => {
|
|
62
|
-
const connection = accountConnections.get(accountId ?? 'default');
|
|
63
|
-
|
|
64
|
-
if (!connection || !connection.isConnected()) {
|
|
65
|
-
return {
|
|
66
|
-
ok: false,
|
|
67
|
-
error: '未连接到栖岛服务'
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// chatId必须由调用方传入
|
|
72
|
-
if (!chatId) {
|
|
73
|
-
return {
|
|
74
|
-
ok: false,
|
|
75
|
-
error: '未指定chatId'
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
// 发送消息到栖岛
|
|
81
|
-
await connection.sendMessage(chatId, text);
|
|
82
|
-
|
|
83
|
-
return { ok: true };
|
|
84
|
-
} catch (error) {
|
|
85
|
-
return {
|
|
86
|
-
ok: false,
|
|
87
|
-
error: error.message
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
// 发送媒体消息
|
|
93
|
-
sendMedia: async ({ url, accountId, chatId }) => {
|
|
94
|
-
const connection = accountConnections.get(accountId ?? 'default');
|
|
95
|
-
|
|
96
|
-
if (!connection || !connection.isConnected()) {
|
|
97
|
-
return {
|
|
98
|
-
ok: false,
|
|
99
|
-
error: '未连接到栖岛服务'
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// chatId必须由调用方传入
|
|
104
|
-
if (!chatId) {
|
|
105
|
-
return {
|
|
106
|
-
ok: false,
|
|
107
|
-
error: '未指定chatId'
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
// 发送图片消息到栖岛
|
|
113
|
-
await connection.sendImageMessage(chatId, url);
|
|
114
|
-
|
|
115
|
-
return { ok: true };
|
|
116
|
-
} catch (error) {
|
|
117
|
-
return {
|
|
118
|
-
ok: false,
|
|
119
|
-
error: error.message
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* OpenClaw插件注册函数
|
|
128
|
-
* @param {Object} api - OpenClaw Plugin API
|
|
129
|
-
*/
|
|
130
|
-
export default function register(api) {
|
|
131
|
-
api.logger.info('栖岛聊天插件加载中...');
|
|
132
|
-
|
|
133
|
-
// 注册Channel
|
|
134
|
-
api.registerChannel({ plugin: qidaoChannel });
|
|
135
|
-
|
|
136
|
-
// 获取配置
|
|
137
|
-
const config = api.getConfig();
|
|
138
|
-
const accounts = config.channels?.qidao?.accounts ?? {};
|
|
139
|
-
|
|
140
|
-
// 为每个启用的账户创建连接
|
|
141
|
-
Object.entries(accounts).forEach(([accountId, accountConfig]) => {
|
|
142
|
-
if (accountConfig.enabled === false) {
|
|
143
|
-
api.logger.info(`栖岛账户 ${accountId} 已禁用`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!accountConfig.chatId) {
|
|
148
|
-
api.logger.warn(`栖岛账户 ${accountId} 缺少chatId配置`);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const serverUrl = accountConfig.serverUrl ?? 'wss://oc.qidao.chat/ws';
|
|
153
|
-
|
|
154
|
-
api.logger.info(`连接栖岛账户: ${accountId} (chatId: ${accountConfig.chatId})`);
|
|
155
|
-
|
|
156
|
-
// 创建连接
|
|
157
|
-
const connection = new QidaoChannel({
|
|
158
|
-
serverUrl,
|
|
159
|
-
chatId: accountConfig.chatId,
|
|
160
|
-
userId: accountConfig.userId,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// 监听连接事件
|
|
164
|
-
connection.on('connect', () => {
|
|
165
|
-
api.logger.info(`栖岛账户 ${accountId} 已连接`);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
connection.on('disconnect', () => {
|
|
169
|
-
api.logger.warn(`栖岛账户 ${accountId} 已断开`);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
connection.on('error', (error) => {
|
|
173
|
-
api.logger.error(`栖岛账户 ${accountId} 错误: ${error.message}`);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// 监听栖岛消息,转发给OpenClaw
|
|
177
|
-
connection.on('message', (message) => {
|
|
178
|
-
api.logger.debug(`栖岛账户 ${accountId} 收到消息:`, message);
|
|
179
|
-
|
|
180
|
-
// 将栖岛消息转换为OpenClaw消息格式
|
|
181
|
-
if (message.type === 'new_message') {
|
|
182
|
-
api.ingestMessage({
|
|
183
|
-
channelId: 'qidao',
|
|
184
|
-
accountId: accountId,
|
|
185
|
-
senderId: message.senderId.toString(),
|
|
186
|
-
senderName: message.senderName,
|
|
187
|
-
text: message.messageText,
|
|
188
|
-
timestamp: message.timestamp,
|
|
189
|
-
chatType: message.chatType === 0 ? 'direct' : 'group',
|
|
190
|
-
chatId: message.chatId.toString(),
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// 连接到服务器
|
|
196
|
-
connection.connect().catch((error) => {
|
|
197
|
-
api.logger.error(`栖岛账户 ${accountId} 连接失败: ${error.message}`);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// 保存连接实例
|
|
201
|
-
accountConnections.set(accountId, connection);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
api.logger.info('栖岛聊天插件加载完成');
|
|
205
|
-
}
|