@onebots/adapter-icqq 1.0.0 → 1.0.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.
- package/lib/adapter.d.ts +12 -4
- package/lib/adapter.js +121 -16
- package/lib/bot.d.ts +4 -0
- package/lib/bot.js +8 -0
- package/lib/index.js +20 -0
- package/package.json +6 -2
package/lib/adapter.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ICQQ 适配器
|
|
3
|
-
* 继承 Adapter 基类,实现 ICQQ 平台功能
|
|
4
|
-
*/
|
|
5
1
|
import { Account } from "onebots";
|
|
6
2
|
import { Adapter } from "onebots";
|
|
7
3
|
import { BaseApp } from "onebots";
|
|
@@ -74,6 +70,18 @@ export declare class ICQQAdapter extends Adapter<ICQQBot, "icqq"> {
|
|
|
74
70
|
*/
|
|
75
71
|
getStatus(uin: string): Promise<Adapter.StatusInfo>;
|
|
76
72
|
createAccount(config: Account.Config<'icqq'>): Account<'icqq', ICQQBot>;
|
|
73
|
+
/**
|
|
74
|
+
* Web 验证提交:将前端提交的滑块 ticket 或短信验证码转交给 ICQQ Bot
|
|
75
|
+
* 支持 data.ticket / data.code(兼容)或通用 data.value
|
|
76
|
+
*/
|
|
77
|
+
submitVerification(accountId: string, type: string, data: Record<string, unknown>): void;
|
|
78
|
+
/** 请求向密保手机发送短信验证码(设备锁时用户选短信验证前调用) */
|
|
79
|
+
requestSmsCode(accountId: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* 处理 base64:// 前缀的文件数据
|
|
82
|
+
* 如果是 base64 格式,转换为 Buffer;否则返回原始数据
|
|
83
|
+
*/
|
|
84
|
+
private processFileData;
|
|
77
85
|
/**
|
|
78
86
|
* 构建 ICQQ 消息
|
|
79
87
|
*/
|
package/lib/adapter.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* ICQQ 适配器
|
|
3
3
|
* 继承 Adapter 基类,实现 ICQQ 平台功能
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import { Account, AdapterRegistry, AccountStatus, unixSecondsToEventMs } from "onebots";
|
|
6
7
|
import { Adapter } from "onebots";
|
|
7
8
|
import { ICQQBot, segment } from "./bot.js";
|
|
8
9
|
export class ICQQAdapter extends Adapter {
|
|
@@ -21,10 +22,11 @@ export class ICQQAdapter extends Adapter {
|
|
|
21
22
|
if (!account)
|
|
22
23
|
throw new Error(`Account ${uin} not found`);
|
|
23
24
|
const bot = account.client;
|
|
24
|
-
const {
|
|
25
|
+
const { scene_type, message } = params;
|
|
26
|
+
const sceneId = this.coerceId(params.scene_id);
|
|
25
27
|
// 转换消息格式
|
|
26
28
|
const icqqMessage = this.buildICQQMessage(message);
|
|
27
|
-
const targetId = parseInt(
|
|
29
|
+
const targetId = parseInt(sceneId.string);
|
|
28
30
|
let result;
|
|
29
31
|
if (scene_type === 'private') {
|
|
30
32
|
result = await bot.sendPrivateMessage(targetId, icqqMessage);
|
|
@@ -47,7 +49,7 @@ export class ICQQAdapter extends Adapter {
|
|
|
47
49
|
if (!account)
|
|
48
50
|
throw new Error(`Account ${uin} not found`);
|
|
49
51
|
const bot = account.client;
|
|
50
|
-
await bot.recallMessage(params.message_id.string);
|
|
52
|
+
await bot.recallMessage(this.coerceId(params.message_id).string);
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* 获取消息
|
|
@@ -57,11 +59,12 @@ export class ICQQAdapter extends Adapter {
|
|
|
57
59
|
if (!account)
|
|
58
60
|
throw new Error(`Account ${uin} not found`);
|
|
59
61
|
const bot = account.client;
|
|
60
|
-
const msg = await bot.getMessage(params.message_id.string);
|
|
62
|
+
const msg = await bot.getMessage(this.coerceId(params.message_id).string);
|
|
61
63
|
const isGroup = !!msg.group_id;
|
|
62
64
|
return {
|
|
63
65
|
message_id: this.createId(msg.message_id),
|
|
64
|
-
|
|
66
|
+
// MessageInfo.time 约定为 Unix 秒(与 OneBot get_msg 等一致)
|
|
67
|
+
time: msg.time,
|
|
65
68
|
sender: {
|
|
66
69
|
scene_type: isGroup ? 'group' : 'private',
|
|
67
70
|
sender_id: this.createId(msg.user_id.toString()),
|
|
@@ -303,18 +306,61 @@ export class ICQQAdapter extends Adapter {
|
|
|
303
306
|
});
|
|
304
307
|
bot.on('qrcode', (event) => {
|
|
305
308
|
this.logger.info(`ICQQ 请扫描二维码登录`);
|
|
306
|
-
// 可以通过事件通知前端显示二维码
|
|
307
309
|
this.emit('qrcode', { account_id: config.account_id, image: event.image });
|
|
310
|
+
const imageBase64 = event.image instanceof Buffer ? event.image.toString('base64') : event.image;
|
|
311
|
+
this.emit('verification:request', {
|
|
312
|
+
platform: 'icqq',
|
|
313
|
+
account_id: config.account_id,
|
|
314
|
+
type: 'qrcode',
|
|
315
|
+
hint: '请使用手机 QQ 扫描下方二维码登录',
|
|
316
|
+
options: { blocks: [{ type: 'image', base64: imageBase64, alt: '登录二维码' }] },
|
|
317
|
+
});
|
|
308
318
|
});
|
|
309
319
|
bot.on('slider', (event) => {
|
|
310
320
|
this.logger.info(`ICQQ 需要滑块验证: ${event.url}`);
|
|
311
|
-
// 可以通过事件通知前端进行滑块验证
|
|
312
321
|
this.emit('slider', { account_id: config.account_id, url: event.url });
|
|
322
|
+
this.emit('verification:request', {
|
|
323
|
+
platform: 'icqq',
|
|
324
|
+
account_id: config.account_id,
|
|
325
|
+
type: 'slider',
|
|
326
|
+
hint: '请在浏览器中打开下方链接完成滑块验证,完成后将获取的 ticket 填入并提交',
|
|
327
|
+
options: {
|
|
328
|
+
blocks: [
|
|
329
|
+
{ type: 'link', url: event.url, label: event.url },
|
|
330
|
+
{ type: 'input', key: 'ticket', placeholder: '粘贴 ticket' },
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
});
|
|
313
334
|
});
|
|
314
335
|
bot.on('device', (event) => {
|
|
315
336
|
this.logger.info(`ICQQ 需要设备锁验证: ${event.url}`);
|
|
316
|
-
// 可以通过事件通知前端进行设备锁验证
|
|
317
337
|
this.emit('device', { account_id: config.account_id, url: event.url, phone: event.phone });
|
|
338
|
+
const blocks = [
|
|
339
|
+
{ type: 'link', url: event.url, label: event.url },
|
|
340
|
+
];
|
|
341
|
+
if (event.phone)
|
|
342
|
+
blocks.push({ type: 'text', content: `手机号:${event.phone}` });
|
|
343
|
+
this.emit('verification:request', {
|
|
344
|
+
platform: 'icqq',
|
|
345
|
+
account_id: config.account_id,
|
|
346
|
+
type: 'device',
|
|
347
|
+
hint: '请在浏览器中打开下方链接完成设备锁验证',
|
|
348
|
+
options: { blocks },
|
|
349
|
+
});
|
|
350
|
+
if (event.phone) {
|
|
351
|
+
this.emit('verification:request', {
|
|
352
|
+
platform: 'icqq',
|
|
353
|
+
account_id: config.account_id,
|
|
354
|
+
type: 'sms',
|
|
355
|
+
hint: '使用短信验证:请先点击「发送验证码」,收到后填入 6 位验证码并提交',
|
|
356
|
+
requestSmsAvailable: true,
|
|
357
|
+
options: {
|
|
358
|
+
blocks: [
|
|
359
|
+
{ type: 'input', key: 'code', placeholder: '6 位短信验证码', maxLength: 6 },
|
|
360
|
+
],
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
}
|
|
318
364
|
});
|
|
319
365
|
bot.on('login_error', (event) => {
|
|
320
366
|
this.logger.error(`ICQQ 登录失败:`, event);
|
|
@@ -331,7 +377,7 @@ export class ICQQAdapter extends Adapter {
|
|
|
331
377
|
// 转换为 CommonEvent 格式
|
|
332
378
|
const commonEvent = {
|
|
333
379
|
id: this.createId(event.message_id),
|
|
334
|
-
timestamp: event.time
|
|
380
|
+
timestamp: unixSecondsToEventMs(event.time),
|
|
335
381
|
platform: 'icqq',
|
|
336
382
|
bot_id: this.createId(config.account_id),
|
|
337
383
|
type: 'message',
|
|
@@ -359,7 +405,7 @@ export class ICQQAdapter extends Adapter {
|
|
|
359
405
|
// 转换为 CommonEvent 格式
|
|
360
406
|
const commonEvent = {
|
|
361
407
|
id: this.createId(event.message_id),
|
|
362
|
-
timestamp: event.time
|
|
408
|
+
timestamp: unixSecondsToEventMs(event.time),
|
|
363
409
|
platform: 'icqq',
|
|
364
410
|
bot_id: this.createId(config.account_id),
|
|
365
411
|
type: 'message',
|
|
@@ -384,7 +430,7 @@ export class ICQQAdapter extends Adapter {
|
|
|
384
430
|
bot.on('group_increase', (event) => {
|
|
385
431
|
const noticeEvent = {
|
|
386
432
|
id: this.createId(`${event.group_id}_${event.user_id}_${event.time}`),
|
|
387
|
-
timestamp: event.time
|
|
433
|
+
timestamp: unixSecondsToEventMs(event.time),
|
|
388
434
|
platform: 'icqq',
|
|
389
435
|
bot_id: this.createId(config.account_id),
|
|
390
436
|
type: 'notice',
|
|
@@ -406,7 +452,7 @@ export class ICQQAdapter extends Adapter {
|
|
|
406
452
|
bot.on('group_decrease', (event) => {
|
|
407
453
|
const noticeEvent = {
|
|
408
454
|
id: this.createId(`${event.group_id}_${event.user_id}_${event.time}`),
|
|
409
|
-
timestamp: event.time
|
|
455
|
+
timestamp: unixSecondsToEventMs(event.time),
|
|
410
456
|
platform: 'icqq',
|
|
411
457
|
bot_id: this.createId(config.account_id),
|
|
412
458
|
type: 'notice',
|
|
@@ -440,9 +486,68 @@ export class ICQQAdapter extends Adapter {
|
|
|
440
486
|
});
|
|
441
487
|
return account;
|
|
442
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Web 验证提交:将前端提交的滑块 ticket 或短信验证码转交给 ICQQ Bot
|
|
491
|
+
* 支持 data.ticket / data.code(兼容)或通用 data.value
|
|
492
|
+
*/
|
|
493
|
+
submitVerification(accountId, type, data) {
|
|
494
|
+
const account = this.getAccount(accountId);
|
|
495
|
+
if (!account) {
|
|
496
|
+
this.logger.warn(`submitVerification: 账号不存在 ${accountId}`);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const bot = account.client;
|
|
500
|
+
const value = typeof data.value === 'string' ? data.value : undefined;
|
|
501
|
+
if (type === 'slider') {
|
|
502
|
+
const ticket = (data.ticket ?? value);
|
|
503
|
+
if (typeof ticket === 'string')
|
|
504
|
+
bot.submitSlider(ticket);
|
|
505
|
+
}
|
|
506
|
+
else if (type === 'sms') {
|
|
507
|
+
const code = (data.code ?? value);
|
|
508
|
+
if (typeof code === 'string')
|
|
509
|
+
bot.submitSmsCode(code);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
this.logger.debug(`submitVerification: 忽略类型 ${type} 或缺少参数`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/** 请求向密保手机发送短信验证码(设备锁时用户选短信验证前调用) */
|
|
516
|
+
requestSmsCode(accountId) {
|
|
517
|
+
const account = this.getAccount(accountId);
|
|
518
|
+
if (!account) {
|
|
519
|
+
this.logger.warn(`requestSmsCode: 账号不存在 ${accountId}`);
|
|
520
|
+
return Promise.resolve();
|
|
521
|
+
}
|
|
522
|
+
return account.client.sendSmsCode();
|
|
523
|
+
}
|
|
443
524
|
// ============================================
|
|
444
525
|
// 消息转换
|
|
445
526
|
// ============================================
|
|
527
|
+
/**
|
|
528
|
+
* 处理 base64:// 前缀的文件数据
|
|
529
|
+
* 如果是 base64 格式,转换为 Buffer;否则返回原始数据
|
|
530
|
+
*/
|
|
531
|
+
processFileData(file) {
|
|
532
|
+
if (typeof file === 'string' && file.startsWith('base64://')) {
|
|
533
|
+
const base64Data = file.replace(/^base64:\/\//, '');
|
|
534
|
+
// Strip whitespace (RFC 4648 allows whitespace in base64)
|
|
535
|
+
const cleanedData = base64Data.replace(/\s/g, '');
|
|
536
|
+
// Validate base64 format (basic validation)
|
|
537
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(cleanedData)) {
|
|
538
|
+
this.logger.warn(`Invalid base64 data format (length: ${cleanedData.length})`);
|
|
539
|
+
return file; // Return original if invalid
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
return Buffer.from(cleanedData, 'base64');
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
this.logger.error(`Failed to convert base64 to Buffer:`, error);
|
|
546
|
+
return file; // Return original on error
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return file;
|
|
550
|
+
}
|
|
446
551
|
/**
|
|
447
552
|
* 构建 ICQQ 消息
|
|
448
553
|
*/
|
|
@@ -467,7 +572,7 @@ export class ICQQAdapter extends Adapter {
|
|
|
467
572
|
else if (seg.type === 'image') {
|
|
468
573
|
const file = seg.data.url || seg.data.file;
|
|
469
574
|
if (file) {
|
|
470
|
-
result.push(segment.image(file));
|
|
575
|
+
result.push(segment.image(this.processFileData(file)));
|
|
471
576
|
}
|
|
472
577
|
}
|
|
473
578
|
else if (seg.type === 'face') {
|
|
@@ -479,13 +584,13 @@ export class ICQQAdapter extends Adapter {
|
|
|
479
584
|
else if (seg.type === 'record' || seg.type === 'audio') {
|
|
480
585
|
const file = seg.data.url || seg.data.file;
|
|
481
586
|
if (file) {
|
|
482
|
-
result.push(segment.record(file));
|
|
587
|
+
result.push(segment.record(this.processFileData(file)));
|
|
483
588
|
}
|
|
484
589
|
}
|
|
485
590
|
else if (seg.type === 'video') {
|
|
486
591
|
const file = seg.data.url || seg.data.file;
|
|
487
592
|
if (file) {
|
|
488
|
-
result.push(segment.video(file));
|
|
593
|
+
result.push(segment.video(this.processFileData(file)));
|
|
489
594
|
}
|
|
490
595
|
}
|
|
491
596
|
else if (seg.type === 'reply') {
|
package/lib/bot.d.ts
CHANGED
package/lib/bot.js
CHANGED
|
@@ -565,6 +565,14 @@ export class ICQQBot extends EventEmitter {
|
|
|
565
565
|
throw new Error('Bot not connected');
|
|
566
566
|
this.client.submitSlider(ticket);
|
|
567
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* 请求发送短信验证码(设备锁时可选,先调用此方法再提交验证码)
|
|
570
|
+
*/
|
|
571
|
+
sendSmsCode() {
|
|
572
|
+
if (!this.client)
|
|
573
|
+
throw new Error('Bot not connected');
|
|
574
|
+
return this.client.sendSmsCode();
|
|
575
|
+
}
|
|
568
576
|
/**
|
|
569
577
|
* 提交短信验证码
|
|
570
578
|
*/
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
import { AdapterRegistry } from 'onebots';
|
|
1
2
|
export * from './adapter.js';
|
|
2
3
|
export * from './bot.js';
|
|
4
|
+
const icqqSchema = {
|
|
5
|
+
account_id: { type: 'string', required: true, label: 'QQ 号' },
|
|
6
|
+
password: { type: 'string', label: '密码(可选/支持扫码)' },
|
|
7
|
+
protocol: {
|
|
8
|
+
platform: { type: 'number', enum: [1, 2, 3, 4, 5, 6], default: 2, label: '登录平台' },
|
|
9
|
+
ver: { type: 'string', label: 'APK 版本' },
|
|
10
|
+
sign_api_addr: { type: 'string', label: '签名服务器地址' },
|
|
11
|
+
data_dir: { type: 'string', label: '数据目录' },
|
|
12
|
+
log_config: { type: 'object', label: 'log4js 配置' },
|
|
13
|
+
ignore_self: { type: 'boolean', default: true, label: '过滤自己消息' },
|
|
14
|
+
resend: { type: 'boolean', default: true, label: '风控分片发送' },
|
|
15
|
+
reconn_interval: { type: 'number', default: 5, label: '重连间隔(秒)' },
|
|
16
|
+
cache_group_member: { type: 'boolean', default: true, label: '缓存群员列表' },
|
|
17
|
+
auto_server: { type: 'boolean', default: true, label: '自动选择服务器' },
|
|
18
|
+
ffmpeg_path: { type: 'string', label: 'ffmpeg 路径' },
|
|
19
|
+
ffprobe_path: { type: 'string', label: 'ffprobe 路径' },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
AdapterRegistry.registerSchema('icqq', icqqSchema);
|
|
3
23
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebots/adapter-icqq",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "onebots ICQQ 适配器 - 基于 ICQQ 协议的 QQ 机器人",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -27,11 +27,15 @@
|
|
|
27
27
|
"typescript": "latest"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"onebots": "1.0.
|
|
30
|
+
"onebots": "1.0.5"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@icqqjs/icqq": "^1.10.18"
|
|
34
34
|
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/lc-cn/onebots.git"
|
|
38
|
+
},
|
|
35
39
|
"scripts": {
|
|
36
40
|
"build": "rm -f *.tsbuildinfo && tsc --project tsconfig.json && tsc-alias -p tsconfig.json",
|
|
37
41
|
"clean": "rm -rf lib *.tsbuildinfo"
|