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.
@@ -79,7 +79,7 @@ export declare class GroupRequestReviewHandler extends BaseHandler {
79
79
  */
80
80
  private checkAdminPermission;
81
81
  /**
82
- * 解析UID(支持多种格式)
82
+ * 解析UID(支持多种格式,参考BuidHandler.parseUidInput实现)
83
83
  */
84
84
  private parseUID;
85
85
  /**
@@ -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
- if (session.guildId !== this.reviewConfig.reviewGroupId) {
110
- this.logger.info('入群审批', `[DEBUG] 跳过: guildId不匹配 (收到: ${session.guildId}, 需要: ${this.reviewConfig.reviewGroupId})`, true);
111
- return;
112
- }
113
- // 获取原始事件数据(使用类型断言访问 onebot 扩展属性)
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.info('入群审批', `收到表情回应 - 消息: ${msgId}, 操作者: ${operatorId}, 表情数: ${emojiData.length}`, true);
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: UID:123456789
534
- const uidMatch = input.match(/^UID:(\d+)$/i);
535
- if (uidMatch) {
536
- return uidMatch[1];
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
- // 格式3: https://space.bilibili.com/123456789
539
- const urlMatch = input.match(/space\.bilibili\.com\/(\d+)/);
540
- if (urlMatch) {
541
- return urlMatch[1];
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. 验证 UID
564
- this.logger.debug('入群审批', `验证 B站 UID: ${uid}`);
565
- const response = await axios.get(`${this.config.zminfoApiUrl}/api/user/${uid}`, {
566
- timeout: 10000,
567
- headers: {
568
- 'User-Agent': 'Koishi-MCID-Bot/1.0'
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
- if (response.status !== 200 || !response.data || !response.data.uid) {
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
- const buidUser = response.data;
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: buidUser.uid,
590
- buidUsername: buidUser.username,
591
- guardLevel: buidUser.guard_level || 0,
592
- guardLevelText: buidUser.guard_level_text || '',
593
- maxGuardLevel: buidUser.guard_level || 0,
594
- maxGuardLevelText: buidUser.guard_level_text || '',
595
- medalName: buidUser.medal?.name || '',
596
- medalLevel: buidUser.medal?.level || 0,
597
- wealthMedalLevel: buidUser.wealth_medal_level || 0,
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: buidUser.uid,
607
- buidUsername: buidUser.username,
608
- guardLevel: buidUser.guard_level || 0,
609
- guardLevelText: buidUser.guard_level_text || '',
610
- maxGuardLevel: Math.max(bind.maxGuardLevel || 0, buidUser.guard_level || 0),
611
- maxGuardLevelText: buidUser.guard_level > (bind.maxGuardLevel || 0)
612
- ? buidUser.guard_level_text
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: buidUser.medal?.name || '',
615
- medalLevel: buidUser.medal?.level || 0,
616
- wealthMedalLevel: buidUser.wealth_medal_level || 0,
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 nickname = `${buidUser.username}_${bind.mcUsername || 'MCID'}`;
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}, 用户名: ${buidUser.username}`, true);
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服务'), { zminfoApiUrl: config.zminfoApiUrl });
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 和数据库服务)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-bind-bot",
3
3
  "description": "[WittF自用] BIND-BOT - 账号绑定管理机器人,支持Minecraft账号和B站账号绑定与管理。",
4
- "version": "2.1.5",
4
+ "version": "2.1.7",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [