mirai-js 2.8.4 → 2.8.5

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.
Files changed (79) hide show
  1. package/README.md +2 -0
  2. package/dist/browser/mirai-js.js +1 -1
  3. package/dist/node/borwserEntry.js +21 -0
  4. package/dist/node/lib/index.ts +0 -0
  5. package/index.ts +36 -3
  6. package/package.json +1 -1
  7. package/src/borwserEntry.js +11 -0
  8. package/src/lib/index.ts +0 -0
  9. package/srcold/BaseType.d.ts +419 -0
  10. package/srcold/Bot.d.ts +567 -0
  11. package/srcold/Bot.js +1208 -0
  12. package/srcold/FileManager.js +270 -0
  13. package/srcold/Message.d.ts +66 -0
  14. package/srcold/Message.js +314 -0
  15. package/srcold/Middleware.d.ts +170 -0
  16. package/srcold/Middleware.js +657 -0
  17. package/srcold/Waiter.d.ts +13 -0
  18. package/srcold/Waiter.js +24 -0
  19. package/srcold/core/anno/deleteAnno.js +43 -0
  20. package/srcold/core/anno/getAnno.js +44 -0
  21. package/srcold/core/anno/publishAnno.js +44 -0
  22. package/srcold/core/auth.js +40 -0
  23. package/srcold/core/fs/deleteGroupFile.js +45 -0
  24. package/srcold/core/fs/getGroupFileInfo.js +46 -0
  25. package/srcold/core/fs/getGroupFileList.js +47 -0
  26. package/srcold/core/fs/makeGroupDir.js +45 -0
  27. package/srcold/core/fs/moveGroupFile.js +47 -0
  28. package/srcold/core/fs/renameGroupFile.js +44 -0
  29. package/srcold/core/fs/uploadGroupFIle.js +58 -0
  30. package/srcold/core/getFriendList.js +37 -0
  31. package/srcold/core/getGroupConfig.js +37 -0
  32. package/srcold/core/getGroupList.js +37 -0
  33. package/srcold/core/getMemberInfo.js +41 -0
  34. package/srcold/core/getMemberList.js +49 -0
  35. package/srcold/core/getSessionConfig.js +39 -0
  36. package/srcold/core/getUserProfile.js +40 -0
  37. package/srcold/core/messageFromId.js +40 -0
  38. package/srcold/core/mute.js +41 -0
  39. package/srcold/core/muteAll.js +39 -0
  40. package/srcold/core/quitGroup.js +40 -0
  41. package/srcold/core/recall.js +39 -0
  42. package/srcold/core/releaseSession.js +41 -0
  43. package/srcold/core/removeFriend.js +40 -0
  44. package/srcold/core/removeMember.js +42 -0
  45. package/srcold/core/responseBotInvitedJoinGroupRequest.js +46 -0
  46. package/srcold/core/responseFirendRequest.js +45 -0
  47. package/srcold/core/responseMemberJoinRequest.js +47 -0
  48. package/srcold/core/sendCommand.js +41 -0
  49. package/srcold/core/sendFriendMessage.js +43 -0
  50. package/srcold/core/sendGroupMessage.js +43 -0
  51. package/srcold/core/sendImageMessage.js +4 -0
  52. package/srcold/core/sendNudge.js +43 -0
  53. package/srcold/core/sendTempMessage.js +55 -0
  54. package/srcold/core/setEssence.js +44 -0
  55. package/srcold/core/setGroupConfig.js +58 -0
  56. package/srcold/core/setMemberAdmin.js +44 -0
  57. package/srcold/core/setMemberInfo.js +48 -0
  58. package/srcold/core/setSessionConfig.js +41 -0
  59. package/srcold/core/startListeningBrowser.js +62 -0
  60. package/srcold/core/startListeningNode.js +74 -0
  61. package/srcold/core/stopListeningBrowser.js +34 -0
  62. package/srcold/core/stopListeningNode.js +34 -0
  63. package/srcold/core/unmute.js +40 -0
  64. package/srcold/core/unmuteAll.js +39 -0
  65. package/srcold/core/uploadImage.js +55 -0
  66. package/srcold/core/uploadVoice.js +54 -0
  67. package/srcold/core/verify.js +41 -0
  68. package/srcold/index.d.ts +10 -0
  69. package/srcold/index.js +21 -0
  70. package/srcold/interface.js +20 -0
  71. package/srcold/polyfill/URL.js +5 -0
  72. package/srcold/polyfill/wsListener.js +6 -0
  73. package/srcold/typeHelpers.d.ts +2 -0
  74. package/srcold/util/errCode.js +23 -0
  75. package/srcold/util/errorHandler.js +24 -0
  76. package/srcold/util/getInvalidParamsString.js +12 -0
  77. package/srcold/util/isBrowserEnv.js +3 -0
  78. package/srcold/util/random.js +6 -0
  79. package/webpack.config.js +3 -2
package/srcold/Bot.js ADDED
@@ -0,0 +1,1208 @@
1
+ // 引入核心功能,前缀下划线时为了与方法名区别 (视觉上的区别)
2
+ const _releaseSession = require('./core/releaseSession');
3
+ const _verify = require('./core/auth');
4
+ const _bind = require('./core/verify');
5
+ const _sendCommand = require('./core/sendCommand');
6
+ const _sendFriendMessage = require('./core/sendFriendMessage');
7
+ const _sendGroupMessage = require('./core/sendGroupMessage');
8
+ const _sendTempMessage = require('./core/sendTempMessage');
9
+ const _sendNudge = require('./core/sendNudge');
10
+ const _getSessionConfig = require('./core/getSessionConfig');
11
+ const _setSessionConfig = require('./core/setSessionConfig');
12
+ const _uploadImage = require('./core/uploadImage');
13
+ const _uploadVoice = require('./core/uploadVoice');
14
+ const _getFriendList = require('./core/getFriendList');
15
+ const _getGroupList = require('./core/getGroupList');
16
+ const _getMemberList = require('./core/getMemberList');
17
+ const _getMemberInfo = require('./core/getMemberInfo');
18
+ const _getUserProfile = require('./core/getUserProfile');
19
+ const _setMemberInfo = require('./core/setMemberInfo');
20
+ const _setMemberAdmin = require('./core/setMemberAdmin');
21
+ const _getAnnoList = require('./core/anno/getAnno');
22
+ const _publishAnno = require('./core/anno/publishAnno');
23
+ const _deleteAnno = require('./core/anno/deleteAnno');
24
+ const _recall = require('./core/recall');
25
+ const _mute = require('./core/mute');
26
+ const _muteAll = require('./core/muteAll');
27
+ const _unmute = require('./core/unmute');
28
+ const _unmuteAll = require('./core/unmuteAll');
29
+ const _removeMember = require('./core/removeMember');
30
+ const _removeFriend = require('./core/removeFriend');
31
+ const _quitGroup = require('./core/quitGroup');
32
+ const _getGroupConfig = require('./core/getGroupConfig');
33
+ const _setGroupConfig = require('./core/setGroupConfig');
34
+ const _setEssence = require('./core/setEssence');
35
+ const _messageFromId = require('./core/messageFromId');
36
+ const { wsStartListening: _startListening, wsStopListening: _stopListening } = require('./polyfill/wsListener');
37
+
38
+ // 其他
39
+ const random = require('./util/random')(0, 2E16);
40
+ const getInvalidParamsString = require('./util/getInvalidParamsString');
41
+ const { Waiter } = require('./Waiter');
42
+ const { FileManager } = require('./FileManager');
43
+ const { errCodeEnum } = require('./util/errCode');
44
+ const { isBrowserEnv } = require('./util/isBrowserEnv');
45
+
46
+ const fs = isBrowserEnv() ? null : require('fs');
47
+ const { promisify } = isBrowserEnv() ? { promisify: null } : require('util');
48
+
49
+ // 扩展接口
50
+ const { MessageChainGetable, BotConfigGetable } = require('./interface');
51
+
52
+ /**
53
+ * @field config 包含 baseUrl verifyKey qq
54
+ * @field eventProcessorMap 事件处理器 map
55
+ * @field wsConnection 建立连接的 WebSocket 实例
56
+ * @field waiter 内部类单例,提供同步 io 机制
57
+ */
58
+ class Bot extends BotConfigGetable {
59
+ constructor() {
60
+ super();
61
+ this.waiter = new Waiter(this);
62
+ this.config = undefined;
63
+ this.eventProcessorMap = {};
64
+ this.wsConnection = undefined;
65
+ }
66
+
67
+ /**
68
+ * 实现 BotConfigGetable 接口
69
+ */
70
+ getBaseUrl() { return this.config.baseUrl; }
71
+ getQQ() { return this.config.qq; }
72
+ getVerifyKey() { return this.config.verifyKey; }
73
+ getSessionKey() { return this.config.sessionKey; }
74
+
75
+ /**
76
+ * @description 连接到 mirai-api-http,并开启一个会话,重复调用意为重建会话
77
+ * open 方法 1. 建立会话 2. 绑定 qq 3. 与服务端建立 WebSocket 连接
78
+ * @param {string} baseUrl 必选,mirai-api-http server 的地址
79
+ * @param {string} verifyKey 必选,mirai-api-http server 设置的 verifyKey
80
+ * @param {number} qq 必选,欲绑定的 qq 号,需要确保该 qq 号已在 mirai-console 登陆
81
+ * @param {boolean} singleMode 可选,mirai-api-http server 是否启用了 singleMode
82
+ * @returns {void}
83
+ */
84
+ async open({ baseUrl, qq, verifyKey, singleMode } = {}) {
85
+ // 若 config 存在,则认为该对象已经 open 过
86
+ // ,此处应该先令对象回到初始状态,然后重建会话
87
+ if (this.config) {
88
+ await this.close({ keepProcessor: true, keepConfig: true });
89
+ }
90
+
91
+ // 设置对象状态
92
+ // 若开发者重复调用 open,仅更新已提供的值
93
+ this.config = {
94
+ baseUrl: this.config?.baseUrl ?? baseUrl,
95
+ qq: this.config?.qq ?? qq,
96
+ verifyKey: this.config?.verifyKey ?? verifyKey,
97
+ sessionKey: this.config?.sessionKey ?? '',
98
+ };
99
+
100
+ // 事件处理器 map
101
+ // 如果重复调用 open 则保留事件处理器
102
+ this.eventProcessorMap = this.eventProcessorMap ?? {};
103
+
104
+ // 需要使用的参数
105
+ ({ baseUrl, qq, verifyKey } = this.config);
106
+
107
+ // 检查参数
108
+ if (!this.config.baseUrl || !this.config.qq || !this.config.verifyKey) {
109
+ throw new Error(`open 缺少必要的 ${getInvalidParamsString({
110
+ baseUrl, qq, verifyKey,
111
+ })} 参数`);
112
+ }
113
+
114
+ // 创建会话
115
+ const sessionKey = this.config.sessionKey = await _verify({ baseUrl, verifyKey });
116
+
117
+ // 绑定到一个 qq, 若开启了 singleMode 则需要跳过绑定
118
+ !singleMode && await _bind({ baseUrl, sessionKey, qq });
119
+
120
+ // 配置服务端 websocket 状态
121
+ // await _setSessionConfig({ baseUrl, sessionKey, enableWebsocket: true });
122
+
123
+ // 开始监听事件
124
+ await this.__wsListen();
125
+
126
+ }
127
+
128
+ /**
129
+ * @private
130
+ * @description 监听 ws 消息
131
+ */
132
+ async __wsListen() {
133
+ const { baseUrl, sessionKey, verifyKey } = this.config;
134
+ this.wsConnection = await _startListening({
135
+ baseUrl,
136
+ sessionKey,
137
+ verifyKey,
138
+ message: data => {
139
+ // 如果当前到达的事件拥有处理器,则依次调用所有该事件的处理器
140
+ if (data.type in this.eventProcessorMap) {
141
+ data.bot = this;
142
+ return Object.values(this.eventProcessorMap[data.type])
143
+ .forEach(processor => processor(data));
144
+ }
145
+ },
146
+ error: err => {
147
+ const type = 'error';
148
+ if (type in this.eventProcessorMap) {
149
+ err.bot = this;
150
+ return Object.values(this.eventProcessorMap[type])
151
+ .forEach(processor => processor(err));
152
+ }
153
+ try {
154
+ console.log(`ws error\n${JSON.stringify(err)}`);
155
+ } catch (error) { } // eslint-disable-line no-empty
156
+ },
157
+ close: (obj) => {
158
+ const type = 'close';
159
+ if (type in this.eventProcessorMap) {
160
+ obj.bot = this;
161
+ return Object.values(this.eventProcessorMap[type])
162
+ .forEach(processor => processor(obj));
163
+ }
164
+ try {
165
+ console.log(`ws close\n${JSON.stringify(obj)}`);
166
+ } catch (error) { }// eslint-disable-line no-empty
167
+ },
168
+ unexpectedResponse: (obj) => {
169
+ const type = 'unexpected-response';
170
+ if (type in this.eventProcessorMap) {
171
+ obj.bot = this;
172
+ return Object.values(this.eventProcessorMap[type])
173
+ .forEach(processor => processor(obj));
174
+ }
175
+ try {
176
+ console.log(`ws unexpectedResponse\n${JSON.stringify(obj)}`);
177
+ } catch (error) { }// eslint-disable-line no-empty
178
+ }
179
+ });
180
+ }
181
+
182
+
183
+ /**
184
+ * @description 关闭会话
185
+ * @param {boolean} keepProcessor 可选,是否保留事件处理器,默认值为 false,不保留
186
+ * @param {boolean} keepConfig 可选,是否保留 session baseUrl qq verifyKey,默认值为 false,不保留
187
+ * @returns {void}
188
+ */
189
+ async close({ keepProcessor = false, keepConfig = false } = {}) {
190
+ // 检查对象状态
191
+ if (!this.config) {
192
+ throw new Error('close 请先调用 open,建立一个会话');
193
+ }
194
+
195
+ // 需要使用的参数
196
+ const { baseUrl, sessionKey, qq } = this.config;
197
+
198
+ // 关闭 ws 连接
199
+ await _stopListening(this.wsConnection);
200
+
201
+ // 释放会话
202
+ await _releaseSession({ baseUrl, sessionKey, qq });
203
+
204
+ // 初始化对象状态
205
+ if (!keepProcessor) {
206
+ this.eventProcessorMap = {};
207
+ }
208
+ if (!keepConfig) {
209
+ this.config = undefined;
210
+ }
211
+ this.wsConnection = undefined;
212
+ }
213
+
214
+ /**
215
+ * ! messageChain 将在未来被移除
216
+ * @description 向 qq 好友 或 qq 群发送消息,若同时提供,则优先向好友发送消息
217
+ * @param {boolean} temp 可选,是否是临时会话,默认为 false
218
+ * @param {number} friend 二选一,好友 qq 号
219
+ * @param {number} group 二选一,群号
220
+ * @param {number} quote 可选,消息引用,使用发送时返回的 messageId
221
+ * @param {Message} message 必选,Message 实例或 MessageType 数组
222
+ * @returns {number} messageId
223
+ */
224
+ async sendMessage({ temp = false, friend, group, quote, message, messageChain }) {
225
+ // 检查对象状态
226
+ if (!this.config) {
227
+ throw new Error('sendMessage 请先调用 open,建立一个会话');
228
+ }
229
+
230
+ // 检查参数
231
+ if (!friend && !group | !message && !messageChain) {
232
+ throw new Error(`sendMessage 缺少必要的 ${getInvalidParamsString({
233
+ 'friend 或 group': friend || group,
234
+ 'message 或 messageChain': message || messageChain,
235
+ })} 参数`);
236
+ }
237
+
238
+ if (messageChain) {
239
+ console.log('warning: 现在 sendMessage 方法的 message 参数可以同时接收 Message 实例或 messageChain,messageChain 参数将在未来被移除');
240
+ }
241
+
242
+ // 需要使用的参数
243
+ const { baseUrl, sessionKey } = this.config;
244
+
245
+ // 处理 message,兼容存在 messageChain 参数的版本
246
+ messageChain = messageChain ?? message;
247
+
248
+ if (messageChain instanceof MessageChainGetable) {
249
+ messageChain = messageChain.getMessageChain();
250
+ } else if (typeof messageChain === 'string') {
251
+ messageChain = [{
252
+ type: 'Plain',
253
+ text: messageChain,
254
+ }];
255
+ }
256
+
257
+ // 根据 temp、friend、group 参数的情况依次调用
258
+ if (temp) {
259
+ // 临时会话的接口,好友和群是在一起的,在内部做了参数判断并抛出异常
260
+ // 而正常的好友和群的发送消息接口是分开的,所以在外面做了参数判断并抛出异常,格式相同
261
+ return await _sendTempMessage({
262
+ baseUrl, sessionKey, qq: friend, group, quote, messageChain
263
+ });
264
+ } else {
265
+ if (friend) {
266
+ return await _sendFriendMessage({
267
+ baseUrl, sessionKey, target: friend, quote, messageChain
268
+ });
269
+ } else if (group) {
270
+ return await _sendGroupMessage({
271
+ baseUrl, sessionKey, target: group, quote, messageChain
272
+ });
273
+ } else {
274
+ throw { message: 'sendGroupMessage 缺少必要的 qq 或 group 参数' };
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * @description 向好友或群成员发送戳一戳
281
+ * 如果提供了 group 参数则忽略 friend
282
+ * mirai-api-http-v1.10.1 feature
283
+ * @param {number} friend 二选一,好友 qq 号
284
+ * @param {number} group 二选一,群成员所在群
285
+ * @param {number} target 必选,目标 qq 号
286
+ */
287
+ async sendNudge({ friend, group, target }) {
288
+ // 检查对象状态
289
+ if (!this.config) {
290
+ throw new Error('sendNudge 请先调用 open,建立一个会话');
291
+ }
292
+
293
+ // 检查参数
294
+ if (!((group || friend) && target)) {
295
+ throw new Error(`sendNudge 缺少必要的 ${getInvalidParamsString({
296
+ 'group 或 friend': group || friend,
297
+ 'target': target,
298
+ })} 参数`);
299
+ }
300
+
301
+ // 需要使用的参数
302
+ const { baseUrl, sessionKey } = this.config;
303
+
304
+ // 发给群成员
305
+ if (group) {
306
+ await _sendNudge({
307
+ baseUrl, sessionKey,
308
+ target,
309
+ subject: group,
310
+ kind: 'Group',
311
+ });
312
+ }
313
+ // 发给好友
314
+ else if (friend) {
315
+ await _sendNudge({
316
+ baseUrl, sessionKey,
317
+ target,
318
+ subject: friend,
319
+ kind: 'Friend',
320
+ });
321
+ }
322
+ }
323
+
324
+ /**
325
+ * @description 添加一个事件处理器
326
+ * 框架维护的 WebSocket 实例会在 ws 的事件 message 下分发 Mirai http server 的消息
327
+ * 回调函数 (data) => void,data 的结构取决于消息类型,详见 mirai-api-http 的文档
328
+ * 而对于 ws 的其他事件 error, close, unexpectedResponse,其回调函数分别为
329
+ * - 'error': (err: Error) => void
330
+ * - 'close': (code: number, message: string) => void
331
+ * - 'unexpected-response': (request: http.ClientRequest, response: http.IncomingMessage) => void
332
+ * @param {string | string[]} eventType 必选,事件类型
333
+ * @param {function} callback 必选,回调函数
334
+ * @returns {number | string[]} 事件处理器的标识,用于移除该处理器
335
+ */
336
+ on(eventType, callback) {
337
+ // 检查对象状态
338
+ if (!this.config) {
339
+ throw new Error('on 请先调用 open,建立一个会话');
340
+ }
341
+
342
+ // 检查参数
343
+ if (!eventType || !callback) {
344
+ throw new Error(`on 缺少必要的 ${getInvalidParamsString({ eventType, callback })} 参数`);
345
+
346
+ }
347
+
348
+ // 适配 eventType 数组
349
+ if (Array.isArray(eventType)) {
350
+ return eventType.map(event => this.on(event, callback));
351
+ }
352
+
353
+ // 为没有任何事件处理器的事件生成一个空对象 (空对象 {},而不是 null)
354
+ if (!(eventType in this.eventProcessorMap)) {
355
+ this.eventProcessorMap[eventType] = {};
356
+ }
357
+
358
+ // 生成一个唯一的 handle,作为当前
359
+ // processor 的标识,用于移除该处理器
360
+ let handle = random();
361
+ while (handle in this.eventProcessorMap[eventType]) {
362
+ handle = random();
363
+ }
364
+
365
+ // processor
366
+ // 每个事件对应多个 processor,这些 processor 和
367
+ // handle 分别作为 value 和 key 包含在一个大对象中
368
+ let processor = callback;
369
+
370
+ // 添加事件处理器
371
+ this.eventProcessorMap[eventType][handle] = processor;
372
+ return handle;
373
+ }
374
+
375
+ /**
376
+ * @description 添加一个一次性事件处理器,回调一次后自动移除
377
+ * @param {string | string[]} eventType 必选,事件类型
378
+ * @param {function} callback 必选,回调函数
379
+ * @param {boolean} strict 可选,是否严格检测调用,由于消息可能会被中间件拦截
380
+ * 当为 true 时,只有开发者的处理器结束后才会移除该处理器
381
+ * 当为 false 时,即使消息被拦截,也会移除该处理器
382
+ * @returns {void}
383
+ */
384
+ one(eventType, callback, strict = false) {
385
+ // 检查对象状态
386
+ if (!this.config) {
387
+ throw new Error('one 请先调用 open,建立一个会话');
388
+ }
389
+
390
+ // 检查参数
391
+ if (!eventType || !callback) {
392
+ throw new Error(`one 缺少必要的 ${getInvalidParamsString({ eventType, callback })} 参数`);
393
+
394
+ }
395
+
396
+ // 适配 eventType 数组
397
+ if (Array.isArray(eventType)) {
398
+ eventType.map(event => this.one(event, callback));
399
+ return;
400
+ }
401
+
402
+ // 为没有任何事件处理器的事件生成一个空对象 (空对象 {},而不是 null)
403
+ if (!(eventType in this.eventProcessorMap)) {
404
+ this.eventProcessorMap[eventType] = {};
405
+ }
406
+
407
+ // 生成一个唯一的 handle,作为当前
408
+ // processor 的标识,用于移除该处理器
409
+ let handle = random();
410
+ while (handle in this.eventProcessorMap[eventType]) {
411
+ handle = random();
412
+ }
413
+
414
+ // processor
415
+ // 每个事件对应多个 processor,这些 processor 和 h
416
+ // andle 分别作为 value 和 key 包含在一个大对象中
417
+ const processor = async (data) => {
418
+ if (strict) {
419
+ // 严格检测回调
420
+ // 当开发者的处理器结束后才移除该处理器,这里等待异步回调
421
+ await callback(data);
422
+ if (handle in this.eventProcessorMap[eventType]) {
423
+ delete this.eventProcessorMap[eventType][handle];
424
+ }
425
+ } else {
426
+ // 不严格检测,直接移除处理器
427
+ // 从 field eventProcessorMap 中移除 handle 指定的事件处理器
428
+ if (handle in this.eventProcessorMap[eventType]) {
429
+ delete this.eventProcessorMap[eventType][handle];
430
+ }
431
+
432
+ // 调用开发者提供的回调
433
+ callback(data);
434
+ }
435
+ };
436
+
437
+ // 添加事件处理器
438
+ this.eventProcessorMap[eventType][handle] = processor;
439
+ }
440
+
441
+ /**
442
+ * @description 移除一个事件处理器
443
+ * @param {string} eventType 必选,事件类型
444
+ * @param {number | number[]} handle
445
+ * 可选,事件处理器标识(或数组),由 on 方法返回,未提供时将移除该事件下的所有处理器
446
+ * @returns {void}
447
+ */
448
+ off(eventType, handle) {
449
+ // 检查对象状态
450
+ if (!this.config) {
451
+ throw new Error('off 请先调用 open,建立一个会话');
452
+ }
453
+
454
+ // 检查参数
455
+ if (!eventType) {
456
+ throw new Error('off 缺少必要的 eventType 参数');
457
+ }
458
+
459
+
460
+ if (handle) {
461
+ // 从 field eventProcessorMap 中移除 handle 指定的事件处理器
462
+ if (handle.forEach) {
463
+ // 可迭代
464
+ handle.forEach(hd => {
465
+ if (hd in this.eventProcessorMap[eventType]) {
466
+ delete this.eventProcessorMap[eventType][hd];
467
+ }
468
+ });
469
+ } else {
470
+ // 不可迭代,认为是单个标识
471
+ if (handle in this.eventProcessorMap[eventType]) {
472
+ delete this.eventProcessorMap[eventType][handle];
473
+ }
474
+ }
475
+ } else {
476
+ // 未提供 handle,移除所有
477
+ if (eventType in this.eventProcessorMap) {
478
+ delete this.eventProcessorMap[eventType];
479
+ }
480
+ }
481
+
482
+ }
483
+
484
+ /**
485
+ * @description 移除所有事件处理器
486
+ * @param {string | string[]} eventType 可选,事件类型(或数组)
487
+ * @returns {void}
488
+ */
489
+ offAll(eventType) {
490
+ // 检查对象状态
491
+ if (!this.config) {
492
+ throw new Error('offAll 请先调用 open,建立一个会话');
493
+ }
494
+
495
+ if (eventType) {
496
+ // 提供了特定的 eventType 参数
497
+ if (eventType.forEach) {
498
+ // 可迭代
499
+ eventType.forEach(evtType => {
500
+ if (evtType in this.eventProcessorMap) {
501
+ delete this.eventProcessorMap[evtType];
502
+ }
503
+ });
504
+ } else {
505
+ // 不可迭代
506
+ if (eventType in this.eventProcessorMap) {
507
+ delete this.eventProcessorMap[eventType];
508
+ }
509
+ }
510
+ } else {
511
+ // 未提供参数,全部移除
512
+ this.eventProcessorMap = {};
513
+ }
514
+ }
515
+
516
+ /**
517
+ * @description 获取 config
518
+ * @returns {Object} 结构 { cacheSize, enableWebsocket }
519
+ */
520
+ async getSessionConfig() {
521
+ // 检查对象状态
522
+ if (!this.config) {
523
+ throw new Error('getConfig 请先调用 open,建立一个会话');
524
+ }
525
+
526
+ const { baseUrl, sessionKey } = this.config;
527
+ return await _getSessionConfig({ baseUrl, sessionKey });
528
+ }
529
+
530
+ /**
531
+ * @description 设置 config
532
+ * @param {number} cacheSize 可选,插件缓存大小
533
+ * @param {boolean} enableWebsocket 可选,websocket 状态
534
+ * @returns {void}
535
+ */
536
+ async setSessionConfig({ cacheSize, enableWebsocket }) {
537
+ // 检查对象状态
538
+ if (!this.config) {
539
+ throw new Error('setConfig 请先调用 open,建立一个会话');
540
+ }
541
+
542
+ const { baseUrl, sessionKey } = this.config;
543
+ await _setSessionConfig({ baseUrl, sessionKey, cacheSize, enableWebsocket });
544
+ }
545
+
546
+ /**
547
+ * @description 撤回由 messageId 确定的消息
548
+ * @param {number} messageId 欲撤回消息的 messageId
549
+ * @returns {void}
550
+ */
551
+ async recall({ messageId }) {
552
+ // 检查对象状态
553
+ if (!this.config) {
554
+ throw new Error('recall 请先调用 open,建立一个会话');
555
+ }
556
+
557
+ // 检查参数
558
+ if (!messageId) {
559
+ throw new Error('recall 缺少必要的 messageId 参数');
560
+ }
561
+
562
+ const { baseUrl, sessionKey } = this.config;
563
+ // 撤回消息
564
+ await _recall({ baseUrl, sessionKey, target: messageId });
565
+ }
566
+
567
+ /**
568
+ * FIXME: type 指定为 'friend' 或 'temp' 时发送的图片显示红色感叹号,无法加载,group 则正常
569
+ * @description 上传图片至服务器,返回指定 type 的 imageId,url,及 path
570
+ * @param {string} type 可选,"friend" 或 "group" 或 "temp",默认为 "group"
571
+ * @param {Buffer} img 二选一,图片二进制数据
572
+ * @param {string} filename 二选一,图片文件路径
573
+ * @returns {Object} 结构 { imageId, url, path }
574
+ */
575
+ async uploadImage({ type = 'group', img, filename }) {
576
+ if (isBrowserEnv()) { throw new Error('uploadImage 在浏览器环境下不可用'); }
577
+ // 检查对象状态
578
+ if (!this.config) {
579
+ throw new Error('uploadImage 请先调用 open,建立一个会话');
580
+ }
581
+
582
+ // 检查参数
583
+ if (isBrowserEnv() && filename) {
584
+ throw new Error('uploadImage 浏览器端不支持 filename 参数');
585
+ }
586
+ if (!img && !filename) {
587
+ throw new Error('uploadImage 缺少必要的 img 或 filename 参数');
588
+ }
589
+
590
+ // 若传入 filename 则统一转换为 Buffer
591
+ if (filename) {
592
+ // 优先使用 img 的原值
593
+ img = img ?? await promisify(fs.readFile)(filename);
594
+ }
595
+
596
+ const { baseUrl, sessionKey } = this.config;
597
+ return await _uploadImage({ baseUrl, sessionKey, type, img });
598
+ }
599
+
600
+ /**
601
+ * FIXME: 目前该功能返回的 voiceId 无法正常使用,无法
602
+ * 发送给好友,提示 message is empty,发到群里则是 1s 的无声语音
603
+ * @description 上传语音至服务器,返回 voiceId, url 及 path
604
+ * @param {string} type TODO: 目前仅支持 "group",请忽略该参数
605
+ * @param {Buffer} voice 二选一,语音二进制数据
606
+ * @param {string} filename 二选一,语音文件路径
607
+ * @returns {Object} 结构 { voiceId, url, path }
608
+ */
609
+ async uploadVoice({ type = 'group', voice, filename }) {
610
+ if (isBrowserEnv()) { throw new Error('uploadVoice 在浏览器环境下不可用'); }
611
+ // 检查对象状态
612
+ if (!this.config) {
613
+ throw new Error('uploadVoice 请先调用 open,建立一个会话');
614
+ }
615
+
616
+ // 检查参数
617
+ if (isBrowserEnv() && filename) {
618
+ throw new Error('uploadVoice 浏览器端不支持 filename 参数');
619
+ }
620
+ if (!voice && !filename) {
621
+ throw new Error('uploadVoice 缺少必要的 voice 或 filename 参数');
622
+ }
623
+
624
+ // 若传入 filename 则统一转换为 Buffer
625
+ if (filename) {
626
+ // 优先使用 img 的原值
627
+ voice = voice ?? await promisify(fs.readFile)(filename);
628
+ }
629
+
630
+ const { baseUrl, sessionKey } = this.config;
631
+ return await _uploadVoice({ baseUrl, sessionKey, type, voice });
632
+ }
633
+
634
+ /**
635
+ * @description 获取好友列表
636
+ * @returns {Object[]} 结构 array[...{ id, name, remark }]
637
+ */
638
+ async getFriendList() {
639
+ // 检查对象状态
640
+ if (!this.config) {
641
+ throw new Error('getFriendList 请先调用 open,建立一个会话');
642
+ }
643
+
644
+ const { baseUrl, sessionKey } = this.config;
645
+
646
+ // 获取列表
647
+ const friendList = await _getFriendList({ baseUrl, sessionKey });
648
+
649
+ // 这里希望所有列表应该具有相同的属性名
650
+ friendList.map((value) => {
651
+ value.name = value.nickname;
652
+ delete value.nickname;
653
+ });
654
+ return friendList;
655
+ }
656
+
657
+ /**
658
+ * @description 获取群列表
659
+ * @returns {Object[]} 结构 array[...{ id, name, permission }]
660
+ */
661
+ async getGroupList() {
662
+ // 检查对象状态
663
+ if (!this.config) {
664
+ throw new Error('getGroupList 请先调用 open,建立一个会话');
665
+ }
666
+
667
+ const { baseUrl, sessionKey } = this.config;
668
+ return await _getGroupList({ baseUrl, sessionKey });
669
+ }
670
+
671
+ /**
672
+ * @description 获取指定群的成员列表
673
+ * @param {number} group 必选,欲获取成员列表的群号
674
+ * @returns {Object[]} 结构 array[...{ id, name, permission }]
675
+ */
676
+ async getMemberList({ group }) {
677
+ // 检查对象状态
678
+ if (!this.config) {
679
+ throw new Error('getMemberList 请先调用 open,建立一个会话');
680
+ }
681
+
682
+ // 检查参数
683
+ if (!group) {
684
+ throw new Error('getMemberList 缺少必要的 group 参数');
685
+ }
686
+
687
+ // 获取列表
688
+ const { baseUrl, sessionKey } = this.config;
689
+ const memberList = await _getMemberList({ baseUrl, sessionKey, target: group });
690
+
691
+ // 这里希望所有列表应该具有相同的属性名
692
+ memberList.map((value) => {
693
+ value.name = value.memberName;
694
+ delete value.group;
695
+ delete value.memberName;
696
+ });
697
+ return memberList;
698
+ }
699
+
700
+ /**
701
+ * @description 获取群成员信息
702
+ * @param {number} group 必选,群成员所在群号
703
+ * @param {number} qq 必选,群成员的 qq 号
704
+ * @returns {Object}
705
+ */
706
+ async getMemberInfo({ group, qq }) {
707
+ // 检查对象状态
708
+ if (!this.config) {
709
+ throw new Error('getMemberInfo 请先调用 open,建立一个会话');
710
+ }
711
+
712
+ // 检查参数
713
+ if (!group || !qq) {
714
+ throw new Error(`getMemberInfo 缺少必要的 ${getInvalidParamsString({
715
+ group, qq
716
+ })} 参数`);
717
+ }
718
+
719
+ // 获取列表
720
+ const { baseUrl, sessionKey } = this.config;
721
+ const memberInfo = await _getMemberInfo({
722
+ baseUrl, sessionKey, target: group, memberId: qq
723
+ });
724
+
725
+ // 将 specialTitle 改为 title,在 setMemberInfo 也保持一致
726
+ memberInfo.title = memberInfo.specialTitle;
727
+ delete memberInfo.specialTitle;
728
+
729
+ return memberInfo;
730
+ }
731
+
732
+ /**
733
+ * @description 获取群成员信息
734
+ * @param {number} qq 必选,用户的 qq 号
735
+ * @returns {Object} 结构 { nickname, email, age, level, sign, sex }
736
+ */
737
+ async getUserProfile({ qq }) {
738
+ // 检查对象状态
739
+ if (!this.config) {
740
+ throw new Error('getUserProfile 请先调用 open,建立一个会话');
741
+ }
742
+
743
+ // 检查参数
744
+ if (!qq) {
745
+ throw new Error('getUserProfile 缺少必要的 qq 参数');
746
+ }
747
+
748
+ const { baseUrl, sessionKey } = this.config;
749
+ return await _getUserProfile({ baseUrl, sessionKey, target: qq });
750
+ }
751
+
752
+ /**
753
+ * @description 设置群成员信息
754
+ * @param {number} group 必选,群成员所在群号
755
+ * @param {number} qq 必选,群成员的 qq 号
756
+ * @param {string} name 可选,要设置的群名片
757
+ * @param {string} title 可选,要设置的群头衔
758
+ * @param {boolean} permission 可选,要设置的权限,
759
+ * 使用枚举值:Bot.Permission.Admin, Bot.Permission.Member
760
+ * @returns {void}
761
+ */
762
+ async setMemberInfo({ group, qq, name, title, permission }) {
763
+ // 检查对象状态
764
+ if (!this.config) {
765
+ throw new Error('setMemberInfo 请先调用 open,建立一个会话');
766
+ }
767
+
768
+ // 检查参数
769
+ if (!group || !qq) {
770
+ throw new Error(`setMemberInfo 缺少必要的 ${getInvalidParamsString({
771
+ group, qq
772
+ })} 参数`);
773
+ }
774
+ if (permission != undefined
775
+ && permission != Bot.groupPermission.ADMINISTRATOR
776
+ && permission != Bot.groupPermission.MEMBER) {
777
+ throw new Error('setMemberInfo admin 参数只能是 Bot.groupPermission.ADMINISTRATOR 或 Bot.groupPermission.Member');
778
+ }
779
+
780
+ // setMemberInfo
781
+ const { baseUrl, sessionKey } = this.config;
782
+ if (name != undefined || title != undefined) {
783
+ await _setMemberInfo({
784
+ baseUrl, sessionKey, target: group, memberId: qq,
785
+ name, specialTitle: title,
786
+ });
787
+ }
788
+
789
+ // setPermission
790
+ if (permission != undefined) {
791
+ await _setMemberAdmin({
792
+ baseUrl, sessionKey, target: group, memberId: qq,
793
+ assign: permission == Bot.groupPermission.ADMINISTRATOR ? true : false,
794
+ });
795
+ }
796
+ }
797
+
798
+ /**
799
+ * @description 获取群公告列表迭代器
800
+ * @param {number} group 必选,群号
801
+ * @returns 迭代器
802
+ */
803
+ async *getAnnoIter({ group }) {
804
+ // 检查对象状态
805
+ if (!this.config) {
806
+ throw new Error('getAnno 请先调用 open,建立一个会话');
807
+ }
808
+
809
+ // 检查参数
810
+ if (!group) {
811
+ throw new Error('getAnno 缺少必要的 group 参数');
812
+ }
813
+
814
+ // 获取列表
815
+ const { baseUrl, sessionKey } = this.config;
816
+ let offset = 0;
817
+ let annoList = await _getAnnoList({ baseUrl, sessionKey, id: group, offset, size: 10 });
818
+ while (annoList.length > 0) {
819
+ for (const anno of annoList) {
820
+ yield anno;
821
+ }
822
+
823
+ // 获取下一页
824
+ offset += 10;
825
+ annoList = await _getAnnoList({ baseUrl, sessionKey, id: group, offset, size: 10 });
826
+ }
827
+
828
+ return;
829
+ }
830
+
831
+ /**
832
+ * @description 发布群公告
833
+ * @param {number} group 必选,群号
834
+ * @param {string} content 必选,公告内容
835
+ * @returns {void}
836
+ */
837
+ async publishAnno({ group, content, pinned }) {
838
+ // 检查对象状态
839
+ if (!this.config) {
840
+ throw new Error('publishAllo 请先调用 open,建立一个会话');
841
+ }
842
+
843
+ // 检查参数
844
+ if (!group || !content) {
845
+ throw new Error(`publishAllo 缺少必要的 ${getInvalidParamsString({
846
+ group, content
847
+ })} 参数`);
848
+ }
849
+
850
+ // 发布公告
851
+ const { baseUrl, sessionKey } = this.config;
852
+ await _publishAnno({ baseUrl, sessionKey, target: group, content, pinned });
853
+ }
854
+
855
+ /**
856
+ * @description 删除群公告
857
+ * @param {number} group 必选,群号
858
+ * @param {string} fid 必选,公告 id
859
+ * @reaturns {void}
860
+ */
861
+ async deleteAnno({ group, fid }) {
862
+ // 检查对象状态
863
+ if (!this.config) {
864
+ throw new Error('deleteAnno 请先调用 open,建立一个会话');
865
+ }
866
+
867
+ // 检查参数
868
+ if (!group || !fid) {
869
+ throw new Error(`deleteAnno 缺少必要的 ${getInvalidParamsString({
870
+ group, fid
871
+ })} 参数`);
872
+ }
873
+
874
+ // 发布公告
875
+ const { baseUrl, sessionKey } = this.config;
876
+ await _deleteAnno({ baseUrl, sessionKey, id: group, fid });
877
+ }
878
+
879
+ /**
880
+ * @description 禁言群成员
881
+ * @param {number} group 必选,欲禁言成员所在群号
882
+ * @param {number} qq 必选,欲禁言成员 qq 号
883
+ * @param {number} time 必选,禁言时长,单位: s (秒)
884
+ * @returns {void}
885
+ */
886
+ async mute({ group, qq, time }) {
887
+ // 检查对象状态
888
+ if (!this.config) {
889
+ throw new Error('mute 请先调用 open,建立一个会话');
890
+ }
891
+
892
+ // 检查参数
893
+ if (!group || !qq || !time) {
894
+ throw new Error(`mute 缺少必要的 ${getInvalidParamsString({ group, qq, time })} 参数`);
895
+ }
896
+
897
+ const { baseUrl, sessionKey } = this.config;
898
+ // 禁言
899
+ await _mute({ baseUrl, sessionKey, target: group, memberId: qq, time });
900
+ }
901
+
902
+ /**
903
+ * @description 全员禁言
904
+ * @param {number} group 必选,欲全员禁言的群号
905
+ * @returns {void}
906
+ */
907
+ async muteAll({ group }) {
908
+ // 检查对象状态
909
+ if (!this.config) {
910
+ throw new Error('muteAll 请先调用 open,建立一个会话');
911
+ }
912
+
913
+ // 检查参数
914
+ if (!group) {
915
+ throw new Error('muteAll 缺少必要的 group 参数');
916
+ }
917
+
918
+ const { baseUrl, sessionKey } = this.config;
919
+ // 禁言
920
+ await _muteAll({ baseUrl, sessionKey, target: group });
921
+ }
922
+
923
+ /**
924
+ * @description 解除禁言
925
+ * @param {number} group 必选,欲解除禁言的成员所在群号
926
+ * @param {number} qq 必选,欲解除禁言的成员 qq 号
927
+ * @returns {void}
928
+ */
929
+ async unmute({ group, qq }) {
930
+ // 检查对象状态
931
+ if (!this.config) {
932
+ throw new Error('unmute 请先调用 open,建立一个会话');
933
+ }
934
+
935
+ // 检查参数
936
+ if (!group || !qq) {
937
+ throw new Error(`unmute 缺少必要的 ${getInvalidParamsString({ group, qq })} 参数`);
938
+ }
939
+
940
+ const { baseUrl, sessionKey } = this.config;
941
+ // 禁言
942
+ await _unmute({ baseUrl, sessionKey, target: group, memberId: qq });
943
+ }
944
+
945
+ /**
946
+ * @description 解除全员禁言
947
+ * @param {number} group 必选,欲解除全员禁言的群号
948
+ * @returns {void}
949
+ */
950
+ async unmuteAll({ group }) {
951
+ // 检查对象状态
952
+ if (!this.config) {
953
+ throw new Error('unmute 请先调用 open,建立一个会话');
954
+ }
955
+
956
+ // 检查参数
957
+ if (!group) {
958
+ throw new Error('unmute 缺少必要的 group 参数');
959
+ }
960
+
961
+ const { baseUrl, sessionKey } = this.config;
962
+ // 禁言
963
+ await _unmuteAll({ baseUrl, sessionKey, target: group });
964
+ }
965
+
966
+ /**
967
+ * @description 移除群成员
968
+ * @param {number} group 必选,欲移除的成员所在群号
969
+ * @param {number} qq 必选,欲移除的成员 qq 号
970
+ * @param {string} message 可选,默认为空串 "",信息
971
+ * @returns {void}
972
+ */
973
+ async removeMember({ group, qq, message = '' }) {
974
+ // 检查对象状态
975
+ if (!this.config) {
976
+ throw new Error('removeMember 请先调用 open,建立一个会话');
977
+ }
978
+
979
+ // 检查参数
980
+ if (!group || !qq) {
981
+ throw new Error(`removeMember 缺少必要的 ${getInvalidParamsString({ group, qq })} 参数`);
982
+ }
983
+
984
+ const { baseUrl, sessionKey } = this.config;
985
+ // 禁言
986
+ await _removeMember({ baseUrl, sessionKey, target: group, memberId: qq, msg: message });
987
+ }
988
+
989
+ /**
990
+ * @description 删除好友
991
+ * @param {*} qq 欲删除的好友 qq 号
992
+ * @returns {void}
993
+ */
994
+ async removeFriend({ qq }) {
995
+ // 检查对象状态
996
+ if (!this.config) {
997
+ throw new Error('removeFriend 请先调用 open,建立一个会话');
998
+ }
999
+
1000
+ // 检查参数
1001
+ if (!qq) {
1002
+ throw new Error('removeFriend 缺少必要的 qq 参数');
1003
+ }
1004
+
1005
+ const { baseUrl, sessionKey } = this.config;
1006
+
1007
+ // 删除好友
1008
+ await _removeFriend({ baseUrl, sessionKey, target: qq });
1009
+ }
1010
+
1011
+ /**
1012
+ * @description 移除群成员
1013
+ * @param {number} group 必选,欲移除的成员所在群号
1014
+ * @returns {void}
1015
+ */
1016
+ async quitGroup({ group }) {
1017
+ // 检查对象状态
1018
+ if (!this.config) {
1019
+ throw new Error('quitGroup 请先调用 open,建立一个会话');
1020
+ }
1021
+
1022
+ // 检查参数
1023
+ if (!group) {
1024
+ throw new Error('quitGroup 缺少必要的 group 参数');
1025
+ }
1026
+
1027
+ const { baseUrl, sessionKey } = this.config;
1028
+ // 禁言
1029
+ await _quitGroup({ baseUrl, sessionKey, target: group });
1030
+ }
1031
+
1032
+ /**
1033
+ * @description 获取群配置
1034
+ * @param {number} group 必选,群号
1035
+ * @returns {Object}
1036
+ */
1037
+ async getGroupConfig({ group }) {
1038
+ // 检查对象状态
1039
+ if (!this.config) {
1040
+ throw new Error('getGroupConfig 请先调用 open,建立一个会话');
1041
+ }
1042
+
1043
+ // 检查参数
1044
+ if (!group) {
1045
+ throw new Error('getGroupConfig 缺少必要的 group 参数');
1046
+ }
1047
+
1048
+ const { baseUrl, sessionKey } = this.config;
1049
+ return await _getGroupConfig({ baseUrl, sessionKey, target: group });
1050
+ }
1051
+
1052
+ /**
1053
+ * @description 设置群配置
1054
+ * @param {number} group 必选,群号
1055
+ * @param {string} name 可选,群名
1056
+ * @param {string} announcement 可选,群公告
1057
+ * @param {boolean} confessTalk 可选,是否开启坦白说
1058
+ * @param {boolean} allowMemberInvite 可选,是否允许群员邀请
1059
+ * @param {boolean} autoApprove 可选,是否开启自动审批入群
1060
+ * @param {boolean} anonymousChat 可选,是否允许匿名聊天
1061
+ * @returns {void}
1062
+ */
1063
+ async setGroupConfig({
1064
+ group,
1065
+ name, announcement, confessTalk, allowMemberInvite, autoApprove, anonymousChat,
1066
+ }) {
1067
+ // 检查对象状态
1068
+ if (!this.config) {
1069
+ throw new Error('setGroupConfig 请先调用 open,建立一个会话');
1070
+ }
1071
+
1072
+ // 检查参数
1073
+ if (!group) {
1074
+ throw new Error('setGroupConfig 缺少必要的 group 参数');
1075
+ }
1076
+
1077
+
1078
+ const { baseUrl, sessionKey } = this.config;
1079
+ await _setGroupConfig({
1080
+ baseUrl, sessionKey, target: group,
1081
+ name, announcement,
1082
+ confessTalk,
1083
+ allowMemberInvite,
1084
+ autoApprove,
1085
+ anonymousChat,
1086
+ });
1087
+ }
1088
+
1089
+ /**
1090
+ * @description 文件管理器的工厂方法
1091
+ * @param {number} group 群号
1092
+ * @returns {FileManager} 文件管理器实例
1093
+ */
1094
+ fileManager({ group }) {
1095
+ return new FileManager({ bot: this, group });
1096
+ }
1097
+
1098
+ /**
1099
+ * @description 设置群精华消息
1100
+ * @param {number} messageId 必选,消息 id
1101
+ * @returns {void}
1102
+ */
1103
+ async setEssence({ messageId }) {
1104
+ // 检查对象状态
1105
+ if (!this.config) {
1106
+ throw new Error('setEssence 请先调用 open,建立一个会话');
1107
+ }
1108
+
1109
+ // 检查参数
1110
+ if (!messageId) {
1111
+ throw new Error('setEssence 缺少必要的 messageId 参数');
1112
+ }
1113
+
1114
+ const { baseUrl, sessionKey } = this.config;
1115
+ await _setEssence({ baseUrl, sessionKey, target: messageId });
1116
+ }
1117
+
1118
+ /**
1119
+ * @description 向 mirai-console 发送指令
1120
+ * @param {string[]} command 必选,指令和参数
1121
+ * @returns {Object} 结构 { message },注意查看 message 的内容,已知的问题:
1122
+ * 'Login failed: Mirai 无法完成滑块验证. 使用协议 ANDROID_PHONE 强制要求滑块验证,
1123
+ * 请更换协议后重试. 另请参阅: https://github.com/project-mirai/mirai-login-solver-selenium'
1124
+ */
1125
+ async sendCommand({ command }) {
1126
+ // 检查对象状态
1127
+ if (!this.config) {
1128
+ throw new Error('setEssence 请先调用 open,建立一个会话');
1129
+ }
1130
+
1131
+ // 检查参数
1132
+ if (!command) {
1133
+ throw new Error(`sendCommand 缺少必要的 ${getInvalidParamsString({ command })} 参数`);
1134
+ }
1135
+
1136
+ const { Message } = require('./Message');
1137
+ const { baseUrl, sessionKey } = this.config;
1138
+ return await _sendCommand({
1139
+ baseUrl, sessionKey,
1140
+ command: command
1141
+ .map((v) => v?.toString() ?? '')
1142
+ .reduce((acc, cur) => acc.addText(cur), new Message)
1143
+ .messageChain
1144
+ });
1145
+ }
1146
+
1147
+ /**
1148
+ * @description 通过 messageId 获取消息
1149
+ * @param {number} target 可选, 目标 qq 号/群号, mah v2.6.0+ 新增该参数
1150
+ * @param {number} messageId 必选, 消息 id
1151
+ * @returns {Object} 结构 { type, messageChain, sender }
1152
+ */
1153
+ async getMessageById({ messageId, target }) {
1154
+ // 检查对象状态
1155
+ if (!this.config) {
1156
+ throw new Error('getMessageById 请先调用 open,建立一个会话');
1157
+ }
1158
+
1159
+ // 检查参数
1160
+ if (!messageId) {
1161
+ throw new Error('getMessageById 缺少必要的 messageId 参数');
1162
+ }
1163
+
1164
+ const { baseUrl, sessionKey } = this.config;
1165
+ return await _messageFromId({ baseUrl, sessionKey, target, messageId });
1166
+ }
1167
+
1168
+ /**
1169
+ * @description 检测该账号是否已经在 mirai-console 登录
1170
+ * @param {string} baseUrl 必选,mirai-api-http server 的地址
1171
+ * @param {string} verifyKey 必选,mirai-api-http server 设置的 verifyKey
1172
+ * @param {number} qq 必选,qq 号
1173
+ * @returns
1174
+ */
1175
+ static async isBotLoggedIn({ baseUrl, verifyKey, qq }) {
1176
+ // 检查参数
1177
+ if (!baseUrl || !verifyKey || !qq) {
1178
+ throw new Error(`isBotLoggedIn 缺少必要的 ${getInvalidParamsString({ baseUrl, verifyKey, qq })} 参数`);
1179
+ }
1180
+
1181
+ const sessionKey = await _verify({ baseUrl, verifyKey });
1182
+ const { code } = await _bind({ baseUrl, sessionKey, qq, throwable: false });
1183
+
1184
+ if (code == errCodeEnum.BOT_NOT_FOUND) {
1185
+ return false;
1186
+ } else {
1187
+ _releaseSession({ baseUrl, sessionKey, qq });
1188
+ return true;
1189
+ }
1190
+ }
1191
+
1192
+ }
1193
+
1194
+
1195
+ // 静态属性: 群成员的权限
1196
+ Bot.groupPermission = {
1197
+ get OWNER() {
1198
+ return 'OWNER';
1199
+ },
1200
+ get ADMINISTRATOR() {
1201
+ return 'ADMINISTRATOR';
1202
+ },
1203
+ get MEMBER() {
1204
+ return 'MEMBER';
1205
+ },
1206
+ };
1207
+
1208
+ module.exports = { Bot };