koishi-plugin-bind-bot 2.1.5 → 2.1.7
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.
|
@@ -98,41 +98,25 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
98
98
|
*/
|
|
99
99
|
async handleNotice(session) {
|
|
100
100
|
try {
|
|
101
|
-
// 调试日志:记录所有notice事件
|
|
102
|
-
this.logger.info('入群审批', `[DEBUG] 收到notice事件 - type: ${session.type}, subtype: ${session.subtype}, guildId: ${session.guildId}`, true);
|
|
103
101
|
// 只处理群表情回应事件
|
|
104
102
|
if (session.subtype !== 'group-msg-emoji-like') {
|
|
105
|
-
this.logger.info('入群审批', `[DEBUG] 跳过: subtype不匹配 (${session.subtype})`, true);
|
|
106
103
|
return;
|
|
107
104
|
}
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const onebotSession = session;
|
|
115
|
-
const onebotData = onebotSession.onebot;
|
|
116
|
-
this.logger.info('入群审批', `[DEBUG] onebot数据: ${JSON.stringify(onebotData)}`, true);
|
|
117
|
-
if (!onebotData?.likes || onebotData.likes.length === 0) {
|
|
118
|
-
this.logger.info('入群审批', '[DEBUG] 跳过: 没有likes数据', true);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
// 从原始 OneBot 数据中读取(更可靠)
|
|
122
|
-
const msgId = onebotData.message_id?.toString() || session.messageId;
|
|
123
|
-
const userId = onebotData.user_id?.toString() || session.userId;
|
|
124
|
-
const emojiData = onebotData.likes;
|
|
125
|
-
if (!msgId || !userId) {
|
|
126
|
-
this.logger.warn('入群审批', '表情回应事件缺少必要数据: messageId 或 userId');
|
|
105
|
+
// 获取原始事件数据(直接访问 session.onebot,参考luckydraw实现)
|
|
106
|
+
const data = session.onebot;
|
|
107
|
+
const messageId = data?.message_id;
|
|
108
|
+
const userId = data?.user_id?.toString();
|
|
109
|
+
const likes = data?.likes || [];
|
|
110
|
+
if (!messageId || !userId || likes.length === 0) {
|
|
127
111
|
return;
|
|
128
112
|
}
|
|
113
|
+
const msgId = messageId.toString();
|
|
114
|
+
const emojiData = likes;
|
|
129
115
|
const operatorId = this.deps.normalizeQQId(userId);
|
|
130
|
-
this.logger.
|
|
116
|
+
this.logger.debug('入群审批', `收到表情回应 - 消息: ${msgId}, 操作者: ${operatorId}, 表情数: ${emojiData.length}`);
|
|
131
117
|
// 检查是否是待审批的消息
|
|
132
118
|
const pendingReq = this.pendingRequests.get(msgId);
|
|
133
119
|
if (!pendingReq) {
|
|
134
|
-
this.logger.info('入群审批', `[DEBUG] 跳过: 消息${msgId}不在待审批列表中`, true);
|
|
135
|
-
this.logger.info('入群审批', `[DEBUG] 当前待审批列表: ${Array.from(this.pendingRequests.keys()).join(', ')}`, true);
|
|
136
120
|
return;
|
|
137
121
|
}
|
|
138
122
|
// 检查是否已处理
|
|
@@ -185,7 +169,9 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
185
169
|
let buidUsername = null;
|
|
186
170
|
let medalInfo = null;
|
|
187
171
|
let bindStatus = '❌ UID 未提供';
|
|
172
|
+
this.logger.debug('入群审批', `[DEBUG] 准备解析UID - 原始answer: "${answer}"`);
|
|
188
173
|
const parsedUid = this.parseUID(answer);
|
|
174
|
+
this.logger.debug('入群审批', `[DEBUG] parseUID结果: ${parsedUid ? parsedUid : 'null'}`);
|
|
189
175
|
if (parsedUid) {
|
|
190
176
|
buidUid = parsedUid;
|
|
191
177
|
// 并行查询官方API和ZMINFO API
|
|
@@ -520,26 +506,59 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
520
506
|
}
|
|
521
507
|
}
|
|
522
508
|
/**
|
|
523
|
-
* 解析UID
|
|
509
|
+
* 解析UID(支持多种格式,参考BuidHandler.parseUidInput实现)
|
|
524
510
|
*/
|
|
525
511
|
parseUID(input) {
|
|
526
512
|
if (!input)
|
|
527
513
|
return null;
|
|
528
514
|
input = input.trim();
|
|
529
|
-
// 格式1:
|
|
515
|
+
// 格式1: 纯数字(整行都是数字)
|
|
530
516
|
if (/^\d+$/.test(input)) {
|
|
517
|
+
this.logger.debug('入群审批', `parseUID: 识别为纯数字 -> ${input}`);
|
|
531
518
|
return input;
|
|
532
519
|
}
|
|
533
|
-
// 格式2:
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
|
|
520
|
+
// 格式2: 包含"答案:"标记的多行文本(如:问题\n答案:123456789)
|
|
521
|
+
const answerMatch = input.match(/答案[::]\s*(\d+)/i);
|
|
522
|
+
if (answerMatch) {
|
|
523
|
+
this.logger.debug('入群审批', `parseUID: 从"答案:"提取 -> ${answerMatch[1]}`);
|
|
524
|
+
return answerMatch[1];
|
|
525
|
+
}
|
|
526
|
+
// 格式3: UID:123456789(参考BuidHandler)
|
|
527
|
+
if (input.toLowerCase().startsWith('uid:')) {
|
|
528
|
+
const uid = input.substring(4).trim();
|
|
529
|
+
if (/^\d+$/.test(uid)) {
|
|
530
|
+
this.logger.debug('入群审批', `parseUID: 从"UID:"前缀提取 -> ${uid}`);
|
|
531
|
+
return uid;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// 格式4: https://space.bilibili.com/123456789(参考BuidHandler的完善处理)
|
|
535
|
+
if (input.includes('space.bilibili.com/')) {
|
|
536
|
+
try {
|
|
537
|
+
let urlPart = input.replace(/^https?:\/\/space\.bilibili\.com\//, '');
|
|
538
|
+
// 移除查询参数
|
|
539
|
+
if (urlPart.includes('?')) {
|
|
540
|
+
urlPart = urlPart.split('?')[0];
|
|
541
|
+
}
|
|
542
|
+
// 移除路径
|
|
543
|
+
if (urlPart.includes('/')) {
|
|
544
|
+
urlPart = urlPart.split('/')[0];
|
|
545
|
+
}
|
|
546
|
+
if (/^\d+$/.test(urlPart)) {
|
|
547
|
+
this.logger.debug('入群审批', `parseUID: 从B站空间URL提取 -> ${urlPart}`);
|
|
548
|
+
return urlPart;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
this.logger.warn('入群审批', `parseUID: URL解析失败 - ${error.message}`);
|
|
553
|
+
}
|
|
537
554
|
}
|
|
538
|
-
// 格式
|
|
539
|
-
const
|
|
540
|
-
if (
|
|
541
|
-
|
|
555
|
+
// 格式5: 从文本中提取第一个长数字串(8-12位,B站UID的典型长度)
|
|
556
|
+
const numberMatch = input.match(/\b(\d{8,12})\b/);
|
|
557
|
+
if (numberMatch) {
|
|
558
|
+
this.logger.debug('入群审批', `parseUID: 从文本提取长数字串 -> ${numberMatch[1]}`);
|
|
559
|
+
return numberMatch[1];
|
|
542
560
|
}
|
|
561
|
+
this.logger.warn('入群审批', `parseUID: 无法解析 - "${input}"`);
|
|
543
562
|
return null;
|
|
544
563
|
}
|
|
545
564
|
/**
|
|
@@ -558,20 +577,35 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
558
577
|
* 执行自动绑定
|
|
559
578
|
*/
|
|
560
579
|
async performAutoBind(qq, uid, bot) {
|
|
561
|
-
const axios = require('axios');
|
|
562
580
|
try {
|
|
563
|
-
// 1.
|
|
564
|
-
this.logger.debug('入群审批',
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
581
|
+
// 1. 使用双API数据源获取最新用户信息(优先B站官方API)
|
|
582
|
+
this.logger.debug('入群审批', `开始获取 B站 UID ${uid} 的信息`);
|
|
583
|
+
// 尝试获取B站官方API的用户信息(最权威)
|
|
584
|
+
let officialUsername = null;
|
|
585
|
+
try {
|
|
586
|
+
this.logger.debug('入群审批', '正在查询B站官方API...');
|
|
587
|
+
const officialInfo = await this.deps.apiService.getBilibiliOfficialUserInfo(uid);
|
|
588
|
+
if (officialInfo && officialInfo.name) {
|
|
589
|
+
officialUsername = officialInfo.name;
|
|
590
|
+
this.logger.info('入群审批', `[官方API] ✅ 获取到用户名: "${officialUsername}"`, true);
|
|
569
591
|
}
|
|
570
|
-
|
|
571
|
-
|
|
592
|
+
else {
|
|
593
|
+
this.logger.warn('入群审批', '[官方API] ❌ 查询失败');
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (officialError) {
|
|
597
|
+
this.logger.warn('入群审批', `[官方API] ❌ 查询出错: ${officialError.message}`);
|
|
598
|
+
}
|
|
599
|
+
// 获取ZMINFO API的完整用户信息(包含粉丝牌、大航海等数据)
|
|
600
|
+
this.logger.debug('入群审批', '正在查询ZMINFO API...');
|
|
601
|
+
const zminfoUser = await this.deps.apiService.validateBUID(uid);
|
|
602
|
+
if (!zminfoUser) {
|
|
572
603
|
throw new Error(`无法验证B站UID: ${uid}`);
|
|
573
604
|
}
|
|
574
|
-
|
|
605
|
+
this.logger.debug('入群审批', `[ZMINFO] 获取到用户名: "${zminfoUser.username}"`);
|
|
606
|
+
// 使用官方API的用户名(如果可用),否则使用ZMINFO的
|
|
607
|
+
const finalUsername = officialUsername || zminfoUser.username;
|
|
608
|
+
this.logger.info('入群审批', `🎯 最终采用用户名: "${finalUsername}"`, true);
|
|
575
609
|
// 2. 检查是否已被其他人绑定
|
|
576
610
|
const existingBind = await this.repos.mcidbind.findByBuidUid(uid);
|
|
577
611
|
if (existingBind && existingBind.qqId !== qq) {
|
|
@@ -586,15 +620,15 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
586
620
|
qqId: qq,
|
|
587
621
|
mcUsername: tempMcUsername,
|
|
588
622
|
mcUuid: '',
|
|
589
|
-
buidUid:
|
|
590
|
-
buidUsername:
|
|
591
|
-
guardLevel:
|
|
592
|
-
guardLevelText:
|
|
593
|
-
maxGuardLevel:
|
|
594
|
-
maxGuardLevelText:
|
|
595
|
-
medalName:
|
|
596
|
-
medalLevel:
|
|
597
|
-
wealthMedalLevel:
|
|
623
|
+
buidUid: zminfoUser.uid,
|
|
624
|
+
buidUsername: finalUsername,
|
|
625
|
+
guardLevel: zminfoUser.guard_level || 0,
|
|
626
|
+
guardLevelText: zminfoUser.guard_level_text || '',
|
|
627
|
+
maxGuardLevel: zminfoUser.guard_level || 0,
|
|
628
|
+
maxGuardLevelText: zminfoUser.guard_level_text || '',
|
|
629
|
+
medalName: zminfoUser.medal?.name || '',
|
|
630
|
+
medalLevel: zminfoUser.medal?.level || 0,
|
|
631
|
+
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
598
632
|
lastActiveTime: new Date(),
|
|
599
633
|
lastModified: new Date()
|
|
600
634
|
});
|
|
@@ -603,26 +637,29 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
603
637
|
else {
|
|
604
638
|
// 更新现有绑定
|
|
605
639
|
await this.repos.mcidbind.update(qq, {
|
|
606
|
-
buidUid:
|
|
607
|
-
buidUsername:
|
|
608
|
-
guardLevel:
|
|
609
|
-
guardLevelText:
|
|
610
|
-
maxGuardLevel: Math.max(bind.maxGuardLevel || 0,
|
|
611
|
-
maxGuardLevelText:
|
|
612
|
-
?
|
|
640
|
+
buidUid: zminfoUser.uid,
|
|
641
|
+
buidUsername: finalUsername,
|
|
642
|
+
guardLevel: zminfoUser.guard_level || 0,
|
|
643
|
+
guardLevelText: zminfoUser.guard_level_text || '',
|
|
644
|
+
maxGuardLevel: Math.max(bind.maxGuardLevel || 0, zminfoUser.guard_level || 0),
|
|
645
|
+
maxGuardLevelText: zminfoUser.guard_level > (bind.maxGuardLevel || 0)
|
|
646
|
+
? zminfoUser.guard_level_text
|
|
613
647
|
: bind.maxGuardLevelText,
|
|
614
|
-
medalName:
|
|
615
|
-
medalLevel:
|
|
616
|
-
wealthMedalLevel:
|
|
648
|
+
medalName: zminfoUser.medal?.name || '',
|
|
649
|
+
medalLevel: zminfoUser.medal?.level || 0,
|
|
650
|
+
wealthMedalLevel: zminfoUser.wealthMedalLevel || 0,
|
|
617
651
|
lastActiveTime: new Date(),
|
|
618
652
|
lastModified: new Date()
|
|
619
653
|
});
|
|
620
654
|
this.logger.info('入群审批', `已更新绑定 - QQ: ${qq}, UID: ${uid}`, true);
|
|
621
655
|
}
|
|
622
|
-
// 4.
|
|
656
|
+
// 4. 更新群昵称(使用标准格式)
|
|
623
657
|
try {
|
|
624
658
|
const groupId = this.reviewConfig.targetGroupId;
|
|
625
|
-
const
|
|
659
|
+
const mcInfo = bind.mcUsername && !bind.mcUsername.startsWith('_temp_')
|
|
660
|
+
? bind.mcUsername
|
|
661
|
+
: '未绑定';
|
|
662
|
+
const nickname = `${finalUsername}(ID:${mcInfo})`;
|
|
626
663
|
await bot.internal.setGroupCard(groupId, qq, nickname);
|
|
627
664
|
this.logger.info('入群审批', `已更新群昵称 - QQ: ${qq}, 昵称: ${nickname}`);
|
|
628
665
|
}
|
|
@@ -630,7 +667,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
630
667
|
this.logger.warn('入群审批', `更新群昵称失败: ${error.message}`);
|
|
631
668
|
// 昵称更新失败不影响绑定
|
|
632
669
|
}
|
|
633
|
-
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${
|
|
670
|
+
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${finalUsername}`, true);
|
|
634
671
|
}
|
|
635
672
|
catch (error) {
|
|
636
673
|
this.logger.error('入群审批', `自动绑定失败: ${error.message}`, error);
|
|
@@ -7,9 +7,15 @@ import type { MojangProfile, ZminfoUser } from '../types';
|
|
|
7
7
|
export declare class ApiService {
|
|
8
8
|
private logger;
|
|
9
9
|
private config;
|
|
10
|
+
private cookieString;
|
|
10
11
|
constructor(logger: LoggerService, config: {
|
|
11
12
|
zminfoApiUrl: string;
|
|
13
|
+
SESSDATA?: string;
|
|
12
14
|
});
|
|
15
|
+
/**
|
|
16
|
+
* 处理cookie字符串,支持完整cookie或单独SESSDATA
|
|
17
|
+
*/
|
|
18
|
+
private processCookie;
|
|
13
19
|
/**
|
|
14
20
|
* 验证 Minecraft 用户名是否存在
|
|
15
21
|
* @param username MC 用户名
|
|
@@ -12,9 +12,30 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
12
12
|
class ApiService {
|
|
13
13
|
logger;
|
|
14
14
|
config;
|
|
15
|
+
cookieString;
|
|
15
16
|
constructor(logger, config) {
|
|
16
17
|
this.logger = logger;
|
|
17
18
|
this.config = config;
|
|
19
|
+
this.cookieString = this.processCookie(config.SESSDATA || '');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 处理cookie字符串,支持完整cookie或单独SESSDATA
|
|
23
|
+
*/
|
|
24
|
+
processCookie(input) {
|
|
25
|
+
if (!input || input.trim() === '') {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
const trimmedInput = input.trim();
|
|
29
|
+
// 如果输入包含多个cookie字段(包含分号),则认为是完整cookie
|
|
30
|
+
if (trimmedInput.includes(';')) {
|
|
31
|
+
return trimmedInput;
|
|
32
|
+
}
|
|
33
|
+
// 如果输入只是SESSDATA值(不包含"SESSDATA="前缀)
|
|
34
|
+
if (!trimmedInput.startsWith('SESSDATA=')) {
|
|
35
|
+
return `SESSDATA=${trimmedInput}`;
|
|
36
|
+
}
|
|
37
|
+
// 如果输入已经是"SESSDATA=xxx"格式
|
|
38
|
+
return trimmedInput;
|
|
18
39
|
}
|
|
19
40
|
// =========== Mojang API ===========
|
|
20
41
|
/**
|
|
@@ -197,14 +218,20 @@ class ApiService {
|
|
|
197
218
|
return null;
|
|
198
219
|
}
|
|
199
220
|
this.logger.debug('B站官方API', `开始查询UID ${uid} 的官方信息`);
|
|
221
|
+
const headers = {
|
|
222
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
223
|
+
Referer: 'https://space.bilibili.com/',
|
|
224
|
+
Origin: 'https://space.bilibili.com'
|
|
225
|
+
};
|
|
226
|
+
// 如果配置了Cookie,则添加到请求头(避免被风控)
|
|
227
|
+
if (this.cookieString) {
|
|
228
|
+
headers.Cookie = this.cookieString;
|
|
229
|
+
this.logger.debug('B站官方API', '使用Cookie进行请求');
|
|
230
|
+
}
|
|
200
231
|
const response = await axios_1.default.get('https://api.bilibili.com/x/space/acc/info', {
|
|
201
232
|
params: { mid: uid },
|
|
202
233
|
timeout: 10000,
|
|
203
|
-
headers
|
|
204
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
205
|
-
Referer: 'https://space.bilibili.com/',
|
|
206
|
-
Origin: 'https://space.bilibili.com'
|
|
207
|
-
}
|
|
234
|
+
headers
|
|
208
235
|
});
|
|
209
236
|
if (response.data.code === 0 && response.data.data) {
|
|
210
237
|
const userData = response.data.data;
|
|
@@ -14,7 +14,10 @@ class ServiceContainer {
|
|
|
14
14
|
nickname;
|
|
15
15
|
constructor(ctx, config, logger, mcidbindRepo, normalizeQQId) {
|
|
16
16
|
// 1. 实例化 API 服务(无依赖)
|
|
17
|
-
this.api = new api_service_1.ApiService(logger.createChild('API服务'), {
|
|
17
|
+
this.api = new api_service_1.ApiService(logger.createChild('API服务'), {
|
|
18
|
+
zminfoApiUrl: config.zminfoApiUrl,
|
|
19
|
+
SESSDATA: config.forceBindSessdata
|
|
20
|
+
});
|
|
18
21
|
// 2. 实例化数据库服务(依赖 API 服务)
|
|
19
22
|
this.database = new database_service_1.DatabaseService(ctx, logger.createChild('数据库服务'), mcidbindRepo, normalizeQQId, (uuid) => this.api.getUsernameByUuid(uuid));
|
|
20
23
|
// 3. 实例化群昵称服务(依赖 API 和数据库服务)
|