@zhin.js/adapter-onebot11 1.0.55 → 1.0.57
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/CHANGELOG.md +16 -0
- package/README.md +35 -40
- package/lib/adapter.d.ts +30 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +120 -0
- package/lib/adapter.js.map +1 -0
- package/lib/bot-ws-client.d.ts +39 -0
- package/lib/bot-ws-client.d.ts.map +1 -0
- package/lib/bot-ws-client.js +370 -0
- package/lib/bot-ws-client.js.map +1 -0
- package/lib/bot-ws-server.d.ts +41 -0
- package/lib/bot-ws-server.d.ts.map +1 -0
- package/lib/bot-ws-server.js +308 -0
- package/lib/bot-ws-server.js.map +1 -0
- package/lib/index.d.ts +11 -172
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +12 -827
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +54 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +5 -0
- package/lib/types.js.map +1 -0
- package/package.json +8 -5
- package/skills/onebot11/SKILL.md +18 -0
- package/src/adapter.ts +139 -0
- package/src/bot-ws-client.ts +399 -0
- package/src/bot-ws-server.ts +350 -0
- package/src/index.ts +21 -968
- package/src/types.ts +58 -0
package/lib/index.js
CHANGED
|
@@ -1,817 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
adapter;
|
|
12
|
-
$config;
|
|
13
|
-
$connected;
|
|
14
|
-
ws;
|
|
15
|
-
reconnectTimer;
|
|
16
|
-
heartbeatTimer;
|
|
17
|
-
requestId = 0;
|
|
18
|
-
pendingRequests = new Map();
|
|
19
|
-
constructor(adapter, $config) {
|
|
20
|
-
super();
|
|
21
|
-
this.adapter = adapter;
|
|
22
|
-
this.$config = $config;
|
|
23
|
-
this.$connected = false;
|
|
24
|
-
}
|
|
25
|
-
get $id() {
|
|
26
|
-
return this.$config.name;
|
|
27
|
-
}
|
|
28
|
-
async $connect() {
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
let wsUrl = this.$config.url;
|
|
31
|
-
const headers = {};
|
|
32
|
-
if (this.$config.access_token) {
|
|
33
|
-
headers['Authorization'] = `Bearer ${this.$config.access_token}`;
|
|
34
|
-
}
|
|
35
|
-
this.ws = new WebSocket(wsUrl, { headers });
|
|
36
|
-
this.ws.on('open', () => {
|
|
37
|
-
this.$connected = true;
|
|
38
|
-
if (!this.$config.access_token)
|
|
39
|
-
plugin.logger.warn(`missing 'access_token', your OneBot protocol is not safely`);
|
|
40
|
-
this.startHeartbeat();
|
|
41
|
-
resolve();
|
|
42
|
-
});
|
|
43
|
-
this.ws.on('message', (data) => {
|
|
44
|
-
try {
|
|
45
|
-
const message = JSON.parse(data.toString());
|
|
46
|
-
this.handleWebSocketMessage(message);
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
this.emit('error', error);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
this.ws.on('close', (code, reason) => {
|
|
53
|
-
this.$connected = false;
|
|
54
|
-
reject({ code, reason });
|
|
55
|
-
this.scheduleReconnect();
|
|
56
|
-
});
|
|
57
|
-
this.ws.on('error', (error) => {
|
|
58
|
-
reject(error);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
async $disconnect() {
|
|
63
|
-
if (this.reconnectTimer) {
|
|
64
|
-
clearTimeout(this.reconnectTimer);
|
|
65
|
-
this.reconnectTimer = undefined;
|
|
66
|
-
}
|
|
67
|
-
if (this.heartbeatTimer) {
|
|
68
|
-
clearInterval(this.heartbeatTimer);
|
|
69
|
-
this.heartbeatTimer = undefined;
|
|
70
|
-
}
|
|
71
|
-
// 清理所有待处理的请求
|
|
72
|
-
for (const [id, request] of this.pendingRequests) {
|
|
73
|
-
clearTimeout(request.timeout);
|
|
74
|
-
request.reject(new Error('Connection closed'));
|
|
75
|
-
}
|
|
76
|
-
this.pendingRequests.clear();
|
|
77
|
-
if (this.ws) {
|
|
78
|
-
this.ws.close();
|
|
79
|
-
this.ws = undefined;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
$formatMessage(onebotMsg) {
|
|
83
|
-
const message = Message.from(onebotMsg, {
|
|
84
|
-
$id: onebotMsg.message_id.toString(),
|
|
85
|
-
$adapter: 'onebot11',
|
|
86
|
-
$bot: `${this.$config.name}`,
|
|
87
|
-
$sender: {
|
|
88
|
-
id: onebotMsg.user_id.toString(),
|
|
89
|
-
name: onebotMsg.user_id.toString()
|
|
90
|
-
},
|
|
91
|
-
$channel: {
|
|
92
|
-
id: (onebotMsg.group_id || onebotMsg.user_id).toString(),
|
|
93
|
-
type: onebotMsg.group_id ? 'group' : 'private'
|
|
94
|
-
},
|
|
95
|
-
$content: onebotMsg.message,
|
|
96
|
-
$raw: onebotMsg.raw_message,
|
|
97
|
-
$timestamp: onebotMsg.time,
|
|
98
|
-
$recall: async () => {
|
|
99
|
-
await this.$recallMessage(message.$id);
|
|
100
|
-
},
|
|
101
|
-
$reply: async (content, quote) => {
|
|
102
|
-
if (quote)
|
|
103
|
-
content.unshift({ type: 'reply', data: { message_id: message.$id } });
|
|
104
|
-
return await this.adapter.sendMessage({
|
|
105
|
-
...message.$channel,
|
|
106
|
-
context: 'onebot11',
|
|
107
|
-
bot: `${this.$config.name}`,
|
|
108
|
-
content
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
return message;
|
|
113
|
-
}
|
|
114
|
-
async $sendMessage(options) {
|
|
115
|
-
const messageData = {
|
|
116
|
-
message: options.content
|
|
117
|
-
};
|
|
118
|
-
if (options.type === 'group') {
|
|
119
|
-
const result = await this.callApi('send_group_msg', {
|
|
120
|
-
group_id: parseInt(options.id),
|
|
121
|
-
...messageData
|
|
122
|
-
});
|
|
123
|
-
plugin.logger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
|
|
124
|
-
return result.message_id.toString();
|
|
125
|
-
}
|
|
126
|
-
else if (options.type === 'private') {
|
|
127
|
-
const result = await this.callApi('send_private_msg', {
|
|
128
|
-
user_id: parseInt(options.id),
|
|
129
|
-
...messageData
|
|
130
|
-
});
|
|
131
|
-
plugin.logger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
|
|
132
|
-
return result.message_id.toString();
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
throw new Error('Either group_id or user_id must be provided');
|
|
136
|
-
}
|
|
137
|
-
return '';
|
|
138
|
-
}
|
|
139
|
-
async $recallMessage(id) {
|
|
140
|
-
await this.callApi('delete_msg', {
|
|
141
|
-
message_id: parseInt(id)
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
// ==================== 群管理 API ====================
|
|
145
|
-
/**
|
|
146
|
-
* 踢出群成员
|
|
147
|
-
*/
|
|
148
|
-
async kickMember(groupId, userId, rejectAddRequest = false) {
|
|
149
|
-
try {
|
|
150
|
-
await this.callApi('set_group_kick', {
|
|
151
|
-
group_id: groupId,
|
|
152
|
-
user_id: userId,
|
|
153
|
-
reject_add_request: rejectAddRequest,
|
|
154
|
-
});
|
|
155
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} 踢出成员 ${userId}(群 ${groupId})`);
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 踢出成员失败:`, error);
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* 禁言群成员
|
|
165
|
-
*/
|
|
166
|
-
async muteMember(groupId, userId, duration = 600) {
|
|
167
|
-
try {
|
|
168
|
-
await this.callApi('set_group_ban', {
|
|
169
|
-
group_id: groupId,
|
|
170
|
-
user_id: userId,
|
|
171
|
-
duration,
|
|
172
|
-
});
|
|
173
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} ${duration > 0 ? `禁言成员 ${userId} ${duration}秒` : `解除成员 ${userId} 禁言`}(群 ${groupId})`);
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 禁言操作失败:`, error);
|
|
178
|
-
throw error;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* 全员禁言
|
|
183
|
-
*/
|
|
184
|
-
async muteAll(groupId, enable = true) {
|
|
185
|
-
try {
|
|
186
|
-
await this.callApi('set_group_whole_ban', {
|
|
187
|
-
group_id: groupId,
|
|
188
|
-
enable,
|
|
189
|
-
});
|
|
190
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} ${enable ? '开启' : '关闭'}全员禁言(群 ${groupId})`);
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 全员禁言操作失败:`, error);
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* 设置管理员
|
|
200
|
-
*/
|
|
201
|
-
async setAdmin(groupId, userId, enable = true) {
|
|
202
|
-
try {
|
|
203
|
-
await this.callApi('set_group_admin', {
|
|
204
|
-
group_id: groupId,
|
|
205
|
-
user_id: userId,
|
|
206
|
-
enable,
|
|
207
|
-
});
|
|
208
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} ${enable ? '设置' : '取消'}管理员 ${userId}(群 ${groupId})`);
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 设置管理员失败:`, error);
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* 设置群名片
|
|
218
|
-
*/
|
|
219
|
-
async setCard(groupId, userId, card) {
|
|
220
|
-
try {
|
|
221
|
-
await this.callApi('set_group_card', {
|
|
222
|
-
group_id: groupId,
|
|
223
|
-
user_id: userId,
|
|
224
|
-
card,
|
|
225
|
-
});
|
|
226
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} 设置成员 ${userId} 群名片为 "${card}"(群 ${groupId})`);
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 设置群名片失败:`, error);
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* 设置群头衔
|
|
236
|
-
*/
|
|
237
|
-
async setTitle(groupId, userId, title, duration = -1) {
|
|
238
|
-
try {
|
|
239
|
-
await this.callApi('set_group_special_title', {
|
|
240
|
-
group_id: groupId,
|
|
241
|
-
user_id: userId,
|
|
242
|
-
special_title: title,
|
|
243
|
-
duration,
|
|
244
|
-
});
|
|
245
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} 设置成员 ${userId} 头衔为 "${title}"(群 ${groupId})`);
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 设置头衔失败:`, error);
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* 设置群名
|
|
255
|
-
*/
|
|
256
|
-
async setGroupName(groupId, name) {
|
|
257
|
-
try {
|
|
258
|
-
await this.callApi('set_group_name', {
|
|
259
|
-
group_id: groupId,
|
|
260
|
-
group_name: name,
|
|
261
|
-
});
|
|
262
|
-
plugin.logger.info(`OneBot11 Bot ${this.$id} 设置群名为 "${name}"(群 ${groupId})`);
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 设置群名失败:`, error);
|
|
267
|
-
throw error;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* 获取群成员列表
|
|
272
|
-
*/
|
|
273
|
-
async getMemberList(groupId) {
|
|
274
|
-
try {
|
|
275
|
-
return await this.callApi('get_group_member_list', { group_id: groupId });
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 获取群成员列表失败:`, error);
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* 获取群信息
|
|
284
|
-
*/
|
|
285
|
-
async getGroupInfo(groupId) {
|
|
286
|
-
try {
|
|
287
|
-
return await this.callApi('get_group_info', { group_id: groupId });
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
plugin.logger.error(`OneBot11 Bot ${this.$id} 获取群信息失败:`, error);
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
async callApi(action, params = {}) {
|
|
295
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
296
|
-
throw new Error('WebSocket is not connected');
|
|
297
|
-
}
|
|
298
|
-
const echo = `req_${++this.requestId}`;
|
|
299
|
-
const message = {
|
|
300
|
-
action,
|
|
301
|
-
params,
|
|
302
|
-
echo
|
|
303
|
-
};
|
|
304
|
-
return new Promise((resolve, reject) => {
|
|
305
|
-
const timeout = setTimeout(() => {
|
|
306
|
-
this.pendingRequests.delete(echo);
|
|
307
|
-
reject(new Error(`API call timeout: ${action}`));
|
|
308
|
-
}, 30000); // 30秒超时
|
|
309
|
-
this.pendingRequests.set(echo, { resolve, reject, timeout });
|
|
310
|
-
this.ws.send(JSON.stringify(message));
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
handleWebSocketMessage(message) {
|
|
314
|
-
// 处理API响应
|
|
315
|
-
if (message.echo && this.pendingRequests.has(message.echo)) {
|
|
316
|
-
const request = this.pendingRequests.get(message.echo);
|
|
317
|
-
this.pendingRequests.delete(message.echo);
|
|
318
|
-
clearTimeout(request.timeout);
|
|
319
|
-
const response = message;
|
|
320
|
-
if (response.status === 'ok') {
|
|
321
|
-
return request.resolve(response.data);
|
|
322
|
-
}
|
|
323
|
-
return request.reject(new Error(`API error: ${response.retcode}`));
|
|
324
|
-
}
|
|
325
|
-
// 处理事件消息
|
|
326
|
-
if (message.post_type === 'message') {
|
|
327
|
-
this.handleOneBot11Message(message);
|
|
328
|
-
}
|
|
329
|
-
else if (message.post_type === 'notice') {
|
|
330
|
-
this.handleOneBot11Notice(message);
|
|
331
|
-
}
|
|
332
|
-
else if (message.post_type === 'request') {
|
|
333
|
-
this.handleOneBot11Request(message);
|
|
334
|
-
}
|
|
335
|
-
else if (message.post_type === 'meta_event' && message.meta_event_type === 'heartbeat') {
|
|
336
|
-
// 心跳消息,暂时忽略
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
handleOneBot11Notice(event) {
|
|
340
|
-
const noticeTypeMap = {
|
|
341
|
-
group_increase: 'group_member_increase',
|
|
342
|
-
group_decrease: 'group_member_decrease',
|
|
343
|
-
group_admin: 'group_admin_change',
|
|
344
|
-
group_ban: 'group_ban',
|
|
345
|
-
group_recall: 'group_recall',
|
|
346
|
-
friend_recall: 'friend_recall',
|
|
347
|
-
friend_add: 'friend_add',
|
|
348
|
-
notify: event.sub_type === 'poke'
|
|
349
|
-
? (event.group_id ? 'group_poke' : 'friend_poke')
|
|
350
|
-
: `notify_${event.sub_type}`,
|
|
351
|
-
group_upload: 'group_upload',
|
|
352
|
-
};
|
|
353
|
-
const $type = noticeTypeMap[event.notice_type] || event.notice_type;
|
|
354
|
-
const isGroup = !!event.group_id;
|
|
355
|
-
const notice = Notice.from(event, {
|
|
356
|
-
$id: `${event.time}_${event.notice_type}_${event.group_id || event.user_id}`,
|
|
357
|
-
$adapter: 'onebot11',
|
|
358
|
-
$bot: this.$config.name,
|
|
359
|
-
$type,
|
|
360
|
-
$subType: event.sub_type,
|
|
361
|
-
$channel: {
|
|
362
|
-
id: (event.group_id || event.user_id)?.toString() || '',
|
|
363
|
-
type: isGroup ? 'group' : 'private',
|
|
364
|
-
},
|
|
365
|
-
$operator: event.operator_id ? { id: event.operator_id.toString(), name: event.operator_id.toString() } : undefined,
|
|
366
|
-
$target: event.user_id ? { id: event.user_id.toString(), name: event.user_id.toString() } : undefined,
|
|
367
|
-
$timestamp: event.time || Math.floor(Date.now() / 1000),
|
|
368
|
-
});
|
|
369
|
-
this.adapter.emit('notice.receive', notice);
|
|
370
|
-
}
|
|
371
|
-
handleOneBot11Request(event) {
|
|
372
|
-
const typeMap = {
|
|
373
|
-
friend: 'friend_add',
|
|
374
|
-
group: event.sub_type === 'invite' ? 'group_invite' : 'group_add',
|
|
375
|
-
};
|
|
376
|
-
const $type = typeMap[event.request_type] || event.request_type;
|
|
377
|
-
const request = Request.from(event, {
|
|
378
|
-
$id: event.flag || `${event.time}_${event.request_type}_${event.user_id}`,
|
|
379
|
-
$adapter: 'onebot11',
|
|
380
|
-
$bot: this.$config.name,
|
|
381
|
-
$type,
|
|
382
|
-
$subType: event.sub_type,
|
|
383
|
-
$channel: {
|
|
384
|
-
id: (event.group_id || event.user_id)?.toString() || '',
|
|
385
|
-
type: event.group_id ? 'group' : 'private',
|
|
386
|
-
},
|
|
387
|
-
$sender: { id: event.user_id?.toString() || '', name: event.user_id?.toString() || '' },
|
|
388
|
-
$comment: event.comment,
|
|
389
|
-
$timestamp: event.time || Math.floor(Date.now() / 1000),
|
|
390
|
-
$approve: async (remark) => {
|
|
391
|
-
await this.callApi(event.request_type === 'friend' ? 'set_friend_add_request' : 'set_group_add_request', { flag: event.flag, approve: true, remark });
|
|
392
|
-
},
|
|
393
|
-
$reject: async (reason) => {
|
|
394
|
-
await this.callApi(event.request_type === 'friend' ? 'set_friend_add_request' : 'set_group_add_request', { flag: event.flag, approve: false, reason });
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
this.adapter.emit('request.receive', request);
|
|
398
|
-
}
|
|
399
|
-
handleOneBot11Message(onebotMsg) {
|
|
400
|
-
const message = this.$formatMessage(onebotMsg);
|
|
401
|
-
this.adapter.emit('message.receive', message);
|
|
402
|
-
plugin.logger.debug(`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`);
|
|
403
|
-
}
|
|
404
|
-
startHeartbeat() {
|
|
405
|
-
const interval = this.$config.heartbeat_interval || 30000;
|
|
406
|
-
this.heartbeatTimer = setInterval(() => {
|
|
407
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
408
|
-
this.ws.ping();
|
|
409
|
-
}
|
|
410
|
-
}, interval);
|
|
411
|
-
}
|
|
412
|
-
scheduleReconnect() {
|
|
413
|
-
if (this.reconnectTimer) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
const interval = this.$config.reconnect_interval || 5000;
|
|
417
|
-
this.reconnectTimer = setTimeout(async () => {
|
|
418
|
-
this.reconnectTimer = undefined;
|
|
419
|
-
try {
|
|
420
|
-
await this.$connect();
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
this.emit('error', new Error(`Reconnection failed: ${error}`));
|
|
424
|
-
this.scheduleReconnect();
|
|
425
|
-
}
|
|
426
|
-
}, interval);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
export class OneBot11WsServer extends EventEmitter {
|
|
430
|
-
adapter;
|
|
431
|
-
router;
|
|
432
|
-
$config;
|
|
433
|
-
$connected;
|
|
434
|
-
#wss;
|
|
435
|
-
#clientMap = new Map();
|
|
436
|
-
heartbeatTimer;
|
|
437
|
-
requestId = 0;
|
|
438
|
-
pendingRequests = new Map();
|
|
439
|
-
get $id() {
|
|
440
|
-
return this.$config.name;
|
|
441
|
-
}
|
|
442
|
-
constructor(adapter, router, $config) {
|
|
443
|
-
super();
|
|
444
|
-
this.adapter = adapter;
|
|
445
|
-
this.router = router;
|
|
446
|
-
this.$config = $config;
|
|
447
|
-
this.$connected = false;
|
|
448
|
-
}
|
|
449
|
-
async $connect() {
|
|
450
|
-
if (!this.$config.access_token)
|
|
451
|
-
plugin.logger.warn(`missing 'access_token', your OneBot protocol is not safely`);
|
|
452
|
-
this.#wss = this.router.ws(this.$config.path, {
|
|
453
|
-
verifyClient: (info) => {
|
|
454
|
-
const { req: { headers }, } = info;
|
|
455
|
-
const authorization = headers['authorization'] || '';
|
|
456
|
-
if (this.$config.access_token && authorization !== `Bearer ${this.$config.access_token}`) {
|
|
457
|
-
plugin.logger.error('鉴权失败');
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
return true;
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
this.$connected = true;
|
|
464
|
-
plugin.logger.info(`ws server start at path:${this.$config.path}`);
|
|
465
|
-
this.#wss.on('connection', (client, req) => {
|
|
466
|
-
this.startHeartbeat();
|
|
467
|
-
plugin.logger.info(`已连接到协议端:${req.socket.remoteAddress}`);
|
|
468
|
-
client.on('error', err => {
|
|
469
|
-
plugin.logger.error('连接出错:', err);
|
|
470
|
-
});
|
|
471
|
-
client.on('close', code => {
|
|
472
|
-
plugin.logger.error(`与连接端(${req.socket.remoteAddress})断开,错误码:${code}`);
|
|
473
|
-
for (const [key, value] of this.#clientMap) {
|
|
474
|
-
if (client === value)
|
|
475
|
-
this.#clientMap.delete(key);
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
client.on('message', (data) => {
|
|
479
|
-
try {
|
|
480
|
-
const message = JSON.parse(data.toString());
|
|
481
|
-
this.handleWebSocketMessage(client, message);
|
|
482
|
-
}
|
|
483
|
-
catch (error) {
|
|
484
|
-
this.emit('error', error);
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
async $disconnect() {
|
|
490
|
-
this.#wss?.close();
|
|
491
|
-
if (this.heartbeatTimer) {
|
|
492
|
-
clearInterval(this.heartbeatTimer);
|
|
493
|
-
delete this.heartbeatTimer;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
$formatMessage(onebotMsg) {
|
|
497
|
-
const message = Message.from(onebotMsg, {
|
|
498
|
-
$id: onebotMsg.message_id.toString(),
|
|
499
|
-
$adapter: 'onebot11',
|
|
500
|
-
$bot: `${this.$config.name}`,
|
|
501
|
-
$sender: {
|
|
502
|
-
id: onebotMsg.user_id.toString(),
|
|
503
|
-
name: onebotMsg.user_id.toString()
|
|
504
|
-
},
|
|
505
|
-
$channel: {
|
|
506
|
-
id: [onebotMsg.self_id, (onebotMsg.group_id || onebotMsg.user_id)].join(':'),
|
|
507
|
-
type: onebotMsg.group_id ? 'group' : 'private'
|
|
508
|
-
},
|
|
509
|
-
$content: onebotMsg.message,
|
|
510
|
-
$raw: onebotMsg.raw_message,
|
|
511
|
-
$timestamp: onebotMsg.time,
|
|
512
|
-
$recall: async () => {
|
|
513
|
-
await this.$recallMessage(message.$id);
|
|
514
|
-
},
|
|
515
|
-
$reply: async (content, quote) => {
|
|
516
|
-
if (!Array.isArray(content))
|
|
517
|
-
content = [content];
|
|
518
|
-
if (quote)
|
|
519
|
-
content.unshift({ type: 'reply', data: { message_id: message.$id } });
|
|
520
|
-
return await this.$sendMessage({
|
|
521
|
-
...message.$channel,
|
|
522
|
-
context: 'onebot11',
|
|
523
|
-
bot: `${this.$config.name}`,
|
|
524
|
-
content
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
return message;
|
|
529
|
-
}
|
|
530
|
-
async $sendMessage(options) {
|
|
531
|
-
const messageData = {
|
|
532
|
-
message: options.content
|
|
533
|
-
};
|
|
534
|
-
if (options.type === 'group') {
|
|
535
|
-
const [self_id, id] = options.id.split(':');
|
|
536
|
-
const result = await this.callApi(self_id, 'send_group_msg', {
|
|
537
|
-
group_id: parseInt(id),
|
|
538
|
-
...messageData
|
|
539
|
-
});
|
|
540
|
-
plugin.logger.debug(`${this.$config.name} send ${options.type}(${id}):${segment.raw(options.content)}`);
|
|
541
|
-
return result.message_id.toString();
|
|
542
|
-
}
|
|
543
|
-
else if (options.type === 'private') {
|
|
544
|
-
const [self_id, id] = options.id.split(':');
|
|
545
|
-
const result = await this.callApi(self_id, 'send_private_msg', {
|
|
546
|
-
user_id: parseInt(id),
|
|
547
|
-
...messageData
|
|
548
|
-
});
|
|
549
|
-
plugin.logger.debug(`${this.$config.name} send ${options.type}(${id}):${segment.raw(options.content)}`);
|
|
550
|
-
return result.message_id.toString();
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
throw new Error('Either group_id or user_id must be provided');
|
|
554
|
-
}
|
|
555
|
-
return '';
|
|
556
|
-
}
|
|
557
|
-
async $recallMessage(id) {
|
|
558
|
-
const [self_id, message_id] = id.split(':');
|
|
559
|
-
await this.callApi(self_id, 'delete_msg', {
|
|
560
|
-
message_id: parseInt(message_id)
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
async callApi(self_id, action, params = {}) {
|
|
564
|
-
const client = this.#clientMap.get(self_id);
|
|
565
|
-
if (!client || client.readyState !== WebSocket.OPEN) {
|
|
566
|
-
throw new Error('WebSocket is not connected');
|
|
567
|
-
}
|
|
568
|
-
const echo = `req_${++this.requestId}`;
|
|
569
|
-
const message = {
|
|
570
|
-
action,
|
|
571
|
-
params,
|
|
572
|
-
echo
|
|
573
|
-
};
|
|
574
|
-
return new Promise((resolve, reject) => {
|
|
575
|
-
const timeout = setTimeout(() => {
|
|
576
|
-
this.pendingRequests.delete(echo);
|
|
577
|
-
reject(new Error(`API call timeout: ${action}`));
|
|
578
|
-
}, 30000); // 30秒超时
|
|
579
|
-
this.pendingRequests.set(echo, { resolve, reject, timeout });
|
|
580
|
-
client.send(JSON.stringify(message));
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
handleWebSocketMessage(client, message) {
|
|
584
|
-
// 处理API响应
|
|
585
|
-
if (message.echo && this.pendingRequests.has(message.echo)) {
|
|
586
|
-
const request = this.pendingRequests.get(message.echo);
|
|
587
|
-
this.pendingRequests.delete(message.echo);
|
|
588
|
-
clearTimeout(request.timeout);
|
|
589
|
-
const response = message;
|
|
590
|
-
if (response.status === 'ok') {
|
|
591
|
-
return request.resolve(response.data);
|
|
592
|
-
}
|
|
593
|
-
return request.reject(new Error(`API error: ${response.retcode}`));
|
|
594
|
-
}
|
|
595
|
-
switch (message.post_type) {
|
|
596
|
-
case 'message':
|
|
597
|
-
return this.handleMessage(message);
|
|
598
|
-
case 'notice':
|
|
599
|
-
return this.handleNotice(message);
|
|
600
|
-
case 'request':
|
|
601
|
-
return this.handleRequest(message);
|
|
602
|
-
case 'meta_event':
|
|
603
|
-
return this.handleMetaEvent(client, message);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
handleMetaEvent(client, message) {
|
|
607
|
-
switch (message.sub_type) {
|
|
608
|
-
case 'heartbeat':
|
|
609
|
-
break;
|
|
610
|
-
case 'connect':
|
|
611
|
-
this.#clientMap.set(message.self_id, client);
|
|
612
|
-
plugin.logger.info(`client ${message.self_id} of ${this.$config.name} by ${this.$config.context} connected`);
|
|
613
|
-
break;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
handleMessage(onebotMsg) {
|
|
617
|
-
const message = this.$formatMessage(onebotMsg);
|
|
618
|
-
this.adapter.emit('message.receive', message);
|
|
619
|
-
plugin.logger.debug(`${this.$config.name} recv ${message.$channel.type}(${onebotMsg.group_id || onebotMsg.user_id}):${segment.raw(message.$content)}`);
|
|
620
|
-
}
|
|
621
|
-
handleNotice(event) {
|
|
622
|
-
const noticeTypeMap = {
|
|
623
|
-
group_increase: 'group_member_increase',
|
|
624
|
-
group_decrease: 'group_member_decrease',
|
|
625
|
-
group_admin: 'group_admin_change',
|
|
626
|
-
group_ban: 'group_ban',
|
|
627
|
-
group_recall: 'group_recall',
|
|
628
|
-
friend_recall: 'friend_recall',
|
|
629
|
-
friend_add: 'friend_add',
|
|
630
|
-
notify: event.sub_type === 'poke'
|
|
631
|
-
? (event.group_id ? 'group_poke' : 'friend_poke')
|
|
632
|
-
: `notify_${event.sub_type}`,
|
|
633
|
-
group_upload: 'group_upload',
|
|
634
|
-
};
|
|
635
|
-
const $type = noticeTypeMap[event.notice_type] || event.notice_type;
|
|
636
|
-
const isGroup = !!event.group_id;
|
|
637
|
-
const notice = Notice.from(event, {
|
|
638
|
-
$id: `${event.self_id}:${event.time}_${event.notice_type}_${event.group_id || event.user_id}`,
|
|
639
|
-
$adapter: 'onebot11',
|
|
640
|
-
$bot: this.$config.name,
|
|
641
|
-
$type,
|
|
642
|
-
$subType: event.sub_type,
|
|
643
|
-
$channel: {
|
|
644
|
-
id: [event.self_id, (event.group_id || event.user_id)].join(':'),
|
|
645
|
-
type: isGroup ? 'group' : 'private',
|
|
646
|
-
},
|
|
647
|
-
$operator: event.operator_id ? { id: event.operator_id.toString(), name: event.operator_id.toString() } : undefined,
|
|
648
|
-
$target: event.user_id ? { id: event.user_id.toString(), name: event.user_id.toString() } : undefined,
|
|
649
|
-
$timestamp: event.time || Math.floor(Date.now() / 1000),
|
|
650
|
-
});
|
|
651
|
-
this.adapter.emit('notice.receive', notice);
|
|
652
|
-
}
|
|
653
|
-
handleRequest(event) {
|
|
654
|
-
const self_id = event.self_id?.toString() || '';
|
|
655
|
-
const typeMap = {
|
|
656
|
-
friend: 'friend_add',
|
|
657
|
-
group: event.sub_type === 'invite' ? 'group_invite' : 'group_add',
|
|
658
|
-
};
|
|
659
|
-
const $type = typeMap[event.request_type] || event.request_type;
|
|
660
|
-
const request = Request.from(event, {
|
|
661
|
-
$id: event.flag || `${self_id}:${event.time}_${event.request_type}_${event.user_id}`,
|
|
662
|
-
$adapter: 'onebot11',
|
|
663
|
-
$bot: this.$config.name,
|
|
664
|
-
$type,
|
|
665
|
-
$subType: event.sub_type,
|
|
666
|
-
$channel: {
|
|
667
|
-
id: [self_id, (event.group_id || event.user_id)].join(':'),
|
|
668
|
-
type: event.group_id ? 'group' : 'private',
|
|
669
|
-
},
|
|
670
|
-
$sender: { id: event.user_id?.toString() || '', name: event.user_id?.toString() || '' },
|
|
671
|
-
$comment: event.comment,
|
|
672
|
-
$timestamp: event.time || Math.floor(Date.now() / 1000),
|
|
673
|
-
$approve: async (remark) => {
|
|
674
|
-
await this.callApi(self_id, event.request_type === 'friend' ? 'set_friend_add_request' : 'set_group_add_request', { flag: event.flag, approve: true, remark });
|
|
675
|
-
},
|
|
676
|
-
$reject: async (reason) => {
|
|
677
|
-
await this.callApi(self_id, event.request_type === 'friend' ? 'set_friend_add_request' : 'set_group_add_request', { flag: event.flag, approve: false, reason });
|
|
678
|
-
},
|
|
679
|
-
});
|
|
680
|
-
this.adapter.emit('request.receive', request);
|
|
681
|
-
}
|
|
682
|
-
startHeartbeat() {
|
|
683
|
-
const interval = this.$config.heartbeat_interval || 30000;
|
|
684
|
-
this.heartbeatTimer = setInterval(() => {
|
|
685
|
-
for (const client of this.#wss?.clients || []) {
|
|
686
|
-
if (client && client.readyState === WebSocket.OPEN) {
|
|
687
|
-
client.ping();
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}, interval);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
// 定义 Adapter 类
|
|
694
|
-
class OneBot11Adapter extends Adapter {
|
|
695
|
-
constructor(plugin) {
|
|
696
|
-
super(plugin, 'onebot11', []);
|
|
697
|
-
}
|
|
698
|
-
createBot(config) {
|
|
699
|
-
return new OneBot11WsClient(this, config);
|
|
700
|
-
}
|
|
701
|
-
// ── IGroupManagement 标准群管方法 ──────────────────────────────────
|
|
702
|
-
async kickMember(botId, sceneId, userId) {
|
|
703
|
-
const bot = this.bots.get(botId);
|
|
704
|
-
if (!bot)
|
|
705
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
706
|
-
return bot.kickMember(Number(sceneId), Number(userId), false);
|
|
707
|
-
}
|
|
708
|
-
async muteMember(botId, sceneId, userId, duration = 600) {
|
|
709
|
-
const bot = this.bots.get(botId);
|
|
710
|
-
if (!bot)
|
|
711
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
712
|
-
return bot.muteMember(Number(sceneId), Number(userId), duration);
|
|
713
|
-
}
|
|
714
|
-
async muteAll(botId, sceneId, enable = true) {
|
|
715
|
-
const bot = this.bots.get(botId);
|
|
716
|
-
if (!bot)
|
|
717
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
718
|
-
return bot.muteAll(Number(sceneId), enable);
|
|
719
|
-
}
|
|
720
|
-
async setAdmin(botId, sceneId, userId, enable = true) {
|
|
721
|
-
const bot = this.bots.get(botId);
|
|
722
|
-
if (!bot)
|
|
723
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
724
|
-
return bot.setAdmin(Number(sceneId), Number(userId), enable);
|
|
725
|
-
}
|
|
726
|
-
async setMemberNickname(botId, sceneId, userId, nickname) {
|
|
727
|
-
const bot = this.bots.get(botId);
|
|
728
|
-
if (!bot)
|
|
729
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
730
|
-
return bot.setCard(Number(sceneId), Number(userId), nickname);
|
|
731
|
-
}
|
|
732
|
-
async setGroupName(botId, sceneId, name) {
|
|
733
|
-
const bot = this.bots.get(botId);
|
|
734
|
-
if (!bot)
|
|
735
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
736
|
-
return bot.setGroupName(Number(sceneId), name);
|
|
737
|
-
}
|
|
738
|
-
async listMembers(botId, sceneId) {
|
|
739
|
-
const bot = this.bots.get(botId);
|
|
740
|
-
if (!bot)
|
|
741
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
742
|
-
const members = await bot.getMemberList(Number(sceneId));
|
|
743
|
-
return {
|
|
744
|
-
members: members.map((m) => ({
|
|
745
|
-
user_id: m.user_id, nickname: m.nickname, card: m.card,
|
|
746
|
-
role: m.role, title: m.title,
|
|
747
|
-
})),
|
|
748
|
-
count: members.length,
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
async getGroupInfo(botId, sceneId) {
|
|
752
|
-
const bot = this.bots.get(botId);
|
|
753
|
-
if (!bot)
|
|
754
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
755
|
-
return bot.getGroupInfo(Number(sceneId));
|
|
756
|
-
}
|
|
757
|
-
// ── 生命周期 ───────────────────────────────────────────────────────
|
|
758
|
-
async start() {
|
|
759
|
-
this.registerOneBot11PlatformTools();
|
|
760
|
-
const groupTools = createGroupManagementTools(this, this.name);
|
|
761
|
-
groupTools.forEach((t) => this.addTool(t));
|
|
762
|
-
this.declareSkill({
|
|
763
|
-
description: 'OneBot11 协议群管理:踢人、禁言、封禁、设管理员、改群名、查成员等。仅有昵称时请先 list_members 获取 user_id 再执行操作。',
|
|
764
|
-
keywords: GROUP_MANAGEMENT_SKILL_KEYWORDS,
|
|
765
|
-
tags: GROUP_MANAGEMENT_SKILL_TAGS,
|
|
766
|
-
});
|
|
767
|
-
await super.start();
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* 注册 OneBot11 平台特有工具(头衔等)
|
|
771
|
-
*/
|
|
772
|
-
registerOneBot11PlatformTools() {
|
|
773
|
-
// 设置头衔工具(OneBot11 平台特有)
|
|
774
|
-
this.addTool({
|
|
775
|
-
name: 'onebot11_set_title',
|
|
776
|
-
description: '设置群成员的专属头衔(需要群主权限)',
|
|
777
|
-
parameters: {
|
|
778
|
-
type: 'object',
|
|
779
|
-
properties: {
|
|
780
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
781
|
-
group_id: { type: 'number', description: '群号' },
|
|
782
|
-
user_id: { type: 'number', description: '成员 QQ 号' },
|
|
783
|
-
title: { type: 'string', description: '头衔名称' },
|
|
784
|
-
},
|
|
785
|
-
required: ['bot', 'group_id', 'user_id', 'title'],
|
|
786
|
-
},
|
|
787
|
-
platforms: ['onebot11'],
|
|
788
|
-
scopes: ['group'],
|
|
789
|
-
permissionLevel: 'group_owner',
|
|
790
|
-
execute: async (args) => {
|
|
791
|
-
const { bot: botId, group_id, user_id, title } = args;
|
|
792
|
-
const bot = this.bots.get(botId);
|
|
793
|
-
if (!bot)
|
|
794
|
-
throw new Error(`Bot ${botId} 不存在`);
|
|
795
|
-
const success = await bot.setTitle(group_id, user_id, title);
|
|
796
|
-
return { success, message: success ? `已将 ${user_id} 的头衔设为 "${title}"` : '操作失败' };
|
|
797
|
-
},
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
class OneBot11WssAdapter extends Adapter {
|
|
802
|
-
#router;
|
|
803
|
-
constructor(plugin, router) {
|
|
804
|
-
super(plugin, 'onebot11.wss', []);
|
|
805
|
-
this.#router = router;
|
|
806
|
-
}
|
|
807
|
-
createBot(config) {
|
|
808
|
-
return new OneBot11WsServer(this, this.#router, config);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
// 使用新的 provide() API 注册适配器
|
|
1
|
+
/**
|
|
2
|
+
* OneBot11 适配器入口:单一适配器,支持正向 WS / 反向 WS(connection: ws | wss)
|
|
3
|
+
*/
|
|
4
|
+
import { usePlugin } from 'zhin.js';
|
|
5
|
+
import { OneBot11Adapter } from './adapter.js';
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
export { OneBot11WsClient } from './bot-ws-client.js';
|
|
8
|
+
export { OneBot11WsServer } from './bot-ws-server.js';
|
|
9
|
+
export { OneBot11Adapter } from './adapter.js';
|
|
10
|
+
const { provide } = usePlugin();
|
|
812
11
|
provide({
|
|
813
|
-
name:
|
|
814
|
-
description:
|
|
12
|
+
name: 'onebot11',
|
|
13
|
+
description: 'OneBot11 协议适配器(正向 WS / 反向 WS)',
|
|
815
14
|
mounted: async (p) => {
|
|
816
15
|
const adapter = new OneBot11Adapter(p);
|
|
817
16
|
await adapter.start();
|
|
@@ -821,18 +20,4 @@ provide({
|
|
|
821
20
|
await adapter.stop();
|
|
822
21
|
},
|
|
823
22
|
});
|
|
824
|
-
useContext('router', (router) => {
|
|
825
|
-
provide({
|
|
826
|
-
name: "onebot11.wss",
|
|
827
|
-
description: "OneBot11 WebSocket Server Adapter",
|
|
828
|
-
mounted: async (p) => {
|
|
829
|
-
const adapter = new OneBot11WssAdapter(p, router);
|
|
830
|
-
await adapter.start();
|
|
831
|
-
return adapter;
|
|
832
|
-
},
|
|
833
|
-
dispose: async (adapter) => {
|
|
834
|
-
await adapter.stop();
|
|
835
|
-
},
|
|
836
|
-
});
|
|
837
|
-
});
|
|
838
23
|
//# sourceMappingURL=index.js.map
|