koishi-plugin-bind-bot 2.0.4 → 2.1.0

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.
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NicknameService = void 0;
4
+ const helpers_1 = require("../utils/helpers");
5
+ /**
6
+ * 群昵称管理服务
7
+ * 负责自动设置和验证群昵称
8
+ */
9
+ class NicknameService {
10
+ logger;
11
+ config;
12
+ normalizeQQId;
13
+ validateBUID;
14
+ getBilibiliOfficialUserInfo;
15
+ updateBuidInfoOnly;
16
+ constructor(logger, config, normalizeQQId, validateBUID, getBilibiliOfficialUserInfo, updateBuidInfoOnly) {
17
+ this.logger = logger;
18
+ this.config = config;
19
+ this.normalizeQQId = normalizeQQId;
20
+ this.validateBUID = validateBUID;
21
+ this.getBilibiliOfficialUserInfo = getBilibiliOfficialUserInfo;
22
+ this.updateBuidInfoOnly = updateBuidInfoOnly;
23
+ }
24
+ /**
25
+ * 检查群昵称格式是否正确
26
+ */
27
+ checkNicknameFormat(nickname, buidUsername, mcUsername) {
28
+ if (!nickname || !buidUsername)
29
+ return false;
30
+ // 期望格式:B站名称(ID:MC用户名)或 B站名称(ID:未绑定)
31
+ const mcInfo = mcUsername && !mcUsername.startsWith('_temp_') ? mcUsername : "未绑定";
32
+ const expectedFormat = `${buidUsername}(ID:${mcInfo})`;
33
+ return nickname === expectedFormat;
34
+ }
35
+ /**
36
+ * 使用四层判断逻辑获取最准确的B站用户名
37
+ * 优先级:官方API > ZMINFO > 数据库
38
+ */
39
+ async getLatestBuidUsername(buidUid, currentDbUsername) {
40
+ // 1. 尝试获取B站官方API的用户信息(最权威)
41
+ let officialUsername = null;
42
+ try {
43
+ this.logger.debug('群昵称设置', `正在查询B站官方API...`);
44
+ const officialInfo = await this.getBilibiliOfficialUserInfo(buidUid);
45
+ if (officialInfo && officialInfo.name) {
46
+ officialUsername = officialInfo.name;
47
+ this.logger.info('群昵称设置', `[层1-官方API] ✅ "${officialUsername}"`, true);
48
+ }
49
+ else {
50
+ this.logger.warn('群昵称设置', `[层1-官方API] ❌ 查询失败`);
51
+ }
52
+ }
53
+ catch (officialError) {
54
+ this.logger.warn('群昵称设置', `[层1-官方API] ❌ 查询出错: ${officialError.message}`);
55
+ }
56
+ // 2. 尝试获取ZMINFO API的用户信息(可能有缓存)
57
+ let zminfoUserData = null;
58
+ try {
59
+ this.logger.debug('群昵称设置', `正在查询ZMINFO API...`);
60
+ zminfoUserData = await this.validateBUID(buidUid);
61
+ if (zminfoUserData && zminfoUserData.username) {
62
+ this.logger.debug('群昵称设置', `[层2-ZMINFO] "${zminfoUserData.username}"`);
63
+ }
64
+ else {
65
+ this.logger.warn('群昵称设置', `[层2-ZMINFO] 查询失败`);
66
+ }
67
+ }
68
+ catch (zminfoError) {
69
+ this.logger.warn('群昵称设置', `[层2-ZMINFO] 查询出错: ${zminfoError.message}`);
70
+ }
71
+ // 3. 根据优先级返回结果
72
+ if (officialUsername) {
73
+ this.logger.info('群昵称设置', `🎯 采用官方API结果: "${officialUsername}"`, true);
74
+ return {
75
+ username: officialUsername,
76
+ source: 'official',
77
+ zminfoData: zminfoUserData || undefined
78
+ };
79
+ }
80
+ else if (zminfoUserData && zminfoUserData.username) {
81
+ this.logger.info('群昵称设置', `⚠️ 官方API不可用,降级使用ZMINFO: "${zminfoUserData.username}"`, true);
82
+ return {
83
+ username: zminfoUserData.username,
84
+ source: 'zminfo',
85
+ zminfoData: zminfoUserData
86
+ };
87
+ }
88
+ else {
89
+ this.logger.warn('群昵称设置', `⚠️ 官方API和ZMINFO都不可用,使用数据库名称: "${currentDbUsername}"`);
90
+ return {
91
+ username: currentDbUsername,
92
+ source: 'database'
93
+ };
94
+ }
95
+ }
96
+ /**
97
+ * 同步数据库中的B站用户信息
98
+ */
99
+ async syncDatabaseIfNeeded(normalizedUserId, latestUsername, currentDbUsername, zminfoData) {
100
+ if (latestUsername === currentDbUsername) {
101
+ return; // 无需更新
102
+ }
103
+ if (!zminfoData) {
104
+ this.logger.debug('群昵称设置', `无ZMINFO数据,跳过数据库同步`);
105
+ return;
106
+ }
107
+ try {
108
+ const updatedData = { ...zminfoData, username: latestUsername };
109
+ await this.updateBuidInfoOnly(normalizedUserId, updatedData);
110
+ this.logger.info('群昵称设置', `已同步数据库: "${currentDbUsername}" → "${latestUsername}"`, true);
111
+ }
112
+ catch (updateError) {
113
+ this.logger.warn('群昵称设置', `数据库同步失败: ${updateError.message}`);
114
+ }
115
+ }
116
+ /**
117
+ * 设置群昵称并验证
118
+ */
119
+ async setAndVerifyNickname(session, targetGroupId, normalizedUserId, nickname, currentNickname) {
120
+ try {
121
+ await session.bot.internal.setGroupCard(targetGroupId, normalizedUserId, nickname);
122
+ if (currentNickname) {
123
+ this.logger.info('群昵称设置', `成功在群${targetGroupId}中将QQ(${normalizedUserId})群昵称从"${currentNickname}"修改为"${nickname}"`, true);
124
+ }
125
+ else {
126
+ this.logger.info('群昵称设置', `成功在群${targetGroupId}中将QQ(${normalizedUserId})群昵称设置为: ${nickname}`, true);
127
+ }
128
+ // 验证设置是否生效
129
+ try {
130
+ await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
131
+ const verifyGroupInfo = await session.bot.internal.getGroupMemberInfo(targetGroupId, normalizedUserId);
132
+ const verifyNickname = verifyGroupInfo.card || verifyGroupInfo.nickname || '';
133
+ if (verifyNickname === nickname) {
134
+ this.logger.info('群昵称设置', `✅ 验证成功,群昵称已生效: "${verifyNickname}"`, true);
135
+ }
136
+ else {
137
+ this.logger.warn('群昵称设置', `⚠️ 验证失败,期望"${nickname}",实际"${verifyNickname}",可能是权限不足或API延迟`);
138
+ if (!currentNickname) {
139
+ this.logger.warn('群昵称设置', `建议检查: 1.机器人是否为群管理员 2.群设置是否允许管理员修改昵称 3.OneBot实现是否支持该功能`);
140
+ }
141
+ }
142
+ }
143
+ catch (verifyError) {
144
+ this.logger.warn('群昵称设置', `无法验证群昵称设置结果: ${verifyError.message}`);
145
+ }
146
+ }
147
+ catch (setCardError) {
148
+ this.logger.error('群昵称设置', `设置群昵称失败: ${setCardError.message}`);
149
+ this.logger.error('群昵称设置', `错误详情: ${JSON.stringify(setCardError)}`);
150
+ throw setCardError;
151
+ }
152
+ }
153
+ /**
154
+ * 自动群昵称设置功能(重构版)
155
+ */
156
+ async autoSetGroupNickname(session, mcUsername, buidUsername, buidUid, targetUserId, specifiedGroupId) {
157
+ try {
158
+ // 准备基本参数
159
+ const actualUserId = targetUserId || session.userId;
160
+ const normalizedUserId = this.normalizeQQId(actualUserId);
161
+ const targetGroupId = specifiedGroupId || this.config.autoNicknameGroupId;
162
+ const mcInfo = (mcUsername && !mcUsername.startsWith('_temp_')) ? mcUsername : "未绑定";
163
+ this.logger.debug('群昵称设置', `开始处理QQ(${normalizedUserId})的群昵称设置,目标群: ${targetGroupId}`);
164
+ // 检查前置条件
165
+ if (!session.bot.internal) {
166
+ this.logger.debug('群昵称设置', `QQ(${normalizedUserId})bot不支持OneBot内部API,跳过自动群昵称设置`);
167
+ return;
168
+ }
169
+ if (!targetGroupId) {
170
+ this.logger.debug('群昵称设置', `QQ(${normalizedUserId})未配置自动群昵称设置目标群,跳过群昵称设置`);
171
+ return;
172
+ }
173
+ // 获取最新的B站用户名
174
+ let latestBuidUsername = buidUsername;
175
+ if (buidUid) {
176
+ this.logger.debug('群昵称设置', `开始四层判断获取最新B站用户名...`);
177
+ this.logger.debug('群昵称设置', `[层3-数据库] "${buidUsername}"`);
178
+ const result = await this.getLatestBuidUsername(buidUid, buidUsername);
179
+ latestBuidUsername = result.username;
180
+ // 尝试同步数据库
181
+ await this.syncDatabaseIfNeeded(normalizedUserId, latestBuidUsername, buidUsername, result.zminfoData);
182
+ }
183
+ // 生成目标昵称
184
+ const targetNickname = `${latestBuidUsername}(ID:${mcInfo})`;
185
+ this.logger.debug('群昵称设置', `目标昵称: "${targetNickname}"`);
186
+ // 尝试获取当前昵称并比对
187
+ try {
188
+ this.logger.debug('群昵称设置', `正在获取QQ(${normalizedUserId})在群${targetGroupId}的当前昵称...`);
189
+ const currentGroupInfo = await session.bot.internal.getGroupMemberInfo(targetGroupId, normalizedUserId);
190
+ const currentNickname = currentGroupInfo.card || currentGroupInfo.nickname || '';
191
+ this.logger.debug('群昵称设置', `当前昵称: "${currentNickname}"`);
192
+ // 智能判断:如果当前昵称已包含最新用户名,跳过修改
193
+ if (buidUid && currentNickname) {
194
+ const currentNicknameUsername = (0, helpers_1.extractBuidUsernameFromNickname)(currentNickname);
195
+ this.logger.debug('群昵称设置', `[层4-群昵称] "${currentNicknameUsername || '(无法提取)'}"`);
196
+ if (currentNicknameUsername && currentNicknameUsername === latestBuidUsername) {
197
+ this.logger.info('群昵称设置', `✅ 群昵称已包含最新名称"${latestBuidUsername}",跳过修改`, true);
198
+ return;
199
+ }
200
+ }
201
+ // 如果昵称完全一致,也跳过
202
+ if (currentNickname === targetNickname) {
203
+ this.logger.info('群昵称设置', `QQ(${normalizedUserId})群昵称已经是"${targetNickname}",跳过修改`, true);
204
+ return;
205
+ }
206
+ // 昵称需要更新
207
+ this.logger.debug('群昵称设置', `昵称不一致,正在修改群昵称为: "${targetNickname}"`);
208
+ await this.setAndVerifyNickname(session, targetGroupId, normalizedUserId, targetNickname, currentNickname);
209
+ }
210
+ catch (getInfoError) {
211
+ // 无法获取当前昵称,直接设置新昵称
212
+ this.logger.warn('群昵称设置', `获取QQ(${normalizedUserId})当前群昵称失败: ${getInfoError.message}`);
213
+ this.logger.debug('群昵称设置', `将直接尝试设置新昵称...`);
214
+ await this.setAndVerifyNickname(session, targetGroupId, normalizedUserId, targetNickname);
215
+ }
216
+ }
217
+ catch (error) {
218
+ const actualUserId = targetUserId || session.userId;
219
+ const normalizedUserId = this.normalizeQQId(actualUserId);
220
+ this.logger.error('群昵称设置', `QQ(${normalizedUserId})自动群昵称设置失败: ${error.message}`);
221
+ this.logger.error('群昵称设置', `完整错误信息: ${JSON.stringify(error)}`);
222
+ }
223
+ }
224
+ }
225
+ exports.NicknameService = NicknameService;
@@ -0,0 +1,17 @@
1
+ import { Context } from 'koishi';
2
+ import { LoggerService } from '../utils/logger';
3
+ import { MCIDBINDRepository } from '../repositories/mcidbind.repository';
4
+ import { ApiService } from './api.service';
5
+ import { DatabaseService } from './database.service';
6
+ import { NicknameService } from './nickname.service';
7
+ import { Config } from '../types/config';
8
+ /**
9
+ * 服务容器类
10
+ * 统一管理所有服务的实例化,解决服务初始化分散的问题
11
+ */
12
+ export declare class ServiceContainer {
13
+ readonly api: ApiService;
14
+ readonly database: DatabaseService;
15
+ readonly nickname: NicknameService;
16
+ constructor(ctx: Context, config: Config, logger: LoggerService, mcidbindRepo: MCIDBINDRepository, normalizeQQId: (userId: string) => string);
17
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServiceContainer = void 0;
4
+ const api_service_1 = require("./api.service");
5
+ const database_service_1 = require("./database.service");
6
+ const nickname_service_1 = require("./nickname.service");
7
+ /**
8
+ * 服务容器类
9
+ * 统一管理所有服务的实例化,解决服务初始化分散的问题
10
+ */
11
+ class ServiceContainer {
12
+ api;
13
+ database;
14
+ nickname;
15
+ constructor(ctx, config, logger, mcidbindRepo, normalizeQQId) {
16
+ // 1. 实例化 API 服务(无依赖)
17
+ this.api = new api_service_1.ApiService(logger.createChild('API服务'), { zminfoApiUrl: config.zminfoApiUrl });
18
+ // 2. 实例化数据库服务(依赖 API 服务)
19
+ this.database = new database_service_1.DatabaseService(ctx, logger.createChild('数据库服务'), mcidbindRepo, normalizeQQId, (uuid) => this.api.getUsernameByUuid(uuid));
20
+ // 3. 实例化群昵称服务(依赖 API 和数据库服务)
21
+ this.nickname = new nickname_service_1.NicknameService(logger.createChild('群昵称服务'), { autoNicknameGroupId: config.autoNicknameGroupId }, normalizeQQId, (buid) => this.api.validateBUID(buid), (uid) => this.api.getBilibiliOfficialUserInfo(uid), (userId, buidUser) => this.database.updateBuidInfoOnly(userId, buidUser));
22
+ }
23
+ }
24
+ exports.ServiceContainer = ServiceContainer;
@@ -18,6 +18,8 @@ export interface Config {
18
18
  showMcSkin: boolean;
19
19
  zminfoApiUrl: string;
20
20
  enableLotteryBroadcast: boolean;
21
+ lotteryTargetGroupId: string;
22
+ lotteryTargetPrivateId: string;
21
23
  autoNicknameGroupId: string;
22
24
  forceBindSessdata: string;
23
25
  forceBindTargetUpUid: number;
@@ -6,3 +6,4 @@ export * from './config';
6
6
  export * from './database';
7
7
  export * from './api';
8
8
  export * from './common';
9
+ export * from './update-data';
@@ -26,3 +26,5 @@ __exportStar(require("./database"), exports);
26
26
  __exportStar(require("./api"), exports);
27
27
  // 通用类型
28
28
  __exportStar(require("./common"), exports);
29
+ // 更新数据类型
30
+ __exportStar(require("./update-data"), exports);
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 数据库更新数据类型定义
3
+ */
4
+ /**
5
+ * MC绑定更新数据接口
6
+ */
7
+ export interface UpdateMcBindData {
8
+ mcUsername?: string;
9
+ mcUuid?: string;
10
+ lastModified?: Date;
11
+ isAdmin?: boolean;
12
+ usernameLastChecked?: Date;
13
+ usernameCheckFailCount?: number;
14
+ }
15
+ /**
16
+ * BUID绑定更新数据接口(完整更新)
17
+ */
18
+ export interface UpdateBuidBindData {
19
+ buidUid?: string;
20
+ buidUsername?: string;
21
+ guardLevel?: number;
22
+ guardLevelText?: string;
23
+ maxGuardLevel?: number;
24
+ maxGuardLevelText?: string;
25
+ medalName?: string;
26
+ medalLevel?: number;
27
+ wealthMedalLevel?: number;
28
+ lastActiveTime?: Date;
29
+ lastModified?: Date;
30
+ }
31
+ /**
32
+ * BUID信息更新数据接口(仅更新信息,不更新lastModified)
33
+ */
34
+ export interface UpdateBuidInfoData {
35
+ buidUsername?: string;
36
+ guardLevel?: number;
37
+ guardLevelText?: string;
38
+ maxGuardLevel?: number;
39
+ maxGuardLevelText?: string;
40
+ medalName?: string;
41
+ medalLevel?: number;
42
+ wealthMedalLevel?: number;
43
+ lastActiveTime?: Date;
44
+ }
45
+ /**
46
+ * 创建新绑定时的完整数据接口
47
+ */
48
+ export interface CreateBindData {
49
+ qqId: string;
50
+ mcUsername: string;
51
+ mcUuid: string;
52
+ isAdmin: boolean;
53
+ whitelist: string[];
54
+ tags: string[];
55
+ [key: string]: any;
56
+ }
57
+ /**
58
+ * 标签更新数据接口
59
+ */
60
+ export interface UpdateTagsData {
61
+ tags?: string[];
62
+ }
63
+ /**
64
+ * 白名单更新数据接口
65
+ */
66
+ export interface UpdateWhitelistData {
67
+ whitelist?: string[];
68
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * 数据库更新数据类型定义
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -107,3 +107,16 @@ export declare function normalizeUsername(username: string, logger?: Logger): st
107
107
  * @returns 是否相同
108
108
  */
109
109
  export declare function isSameUsername(username1: string, username2: string): boolean;
110
+ /**
111
+ * 从群昵称中提取B站用户名
112
+ * 用于解析标准格式的群昵称:B站名称(ID:MC名称)
113
+ *
114
+ * @param nickname 群昵称字符串
115
+ * @returns B站用户名,如果格式不匹配返回 null
116
+ *
117
+ * @example
118
+ * extractBuidUsernameFromNickname("张三(ID:Steve)") // => "张三"
119
+ * extractBuidUsernameFromNickname("李四(ID:未绑定)") // => "李四"
120
+ * extractBuidUsernameFromNickname("无效格式") // => null
121
+ */
122
+ export declare function extractBuidUsernameFromNickname(nickname: string): string | null;
@@ -13,6 +13,7 @@ exports.levenshteinDistance = levenshteinDistance;
13
13
  exports.calculateSimilarity = calculateSimilarity;
14
14
  exports.normalizeUsername = normalizeUsername;
15
15
  exports.isSameUsername = isSameUsername;
16
+ exports.extractBuidUsernameFromNickname = extractBuidUsernameFromNickname;
16
17
  /**
17
18
  * 通用工具函数集合
18
19
  */
@@ -360,3 +361,23 @@ function isSameUsername(username1, username2) {
360
361
  return false;
361
362
  return normalizeUsername(username1) === normalizeUsername(username2);
362
363
  }
364
+ /**
365
+ * 从群昵称中提取B站用户名
366
+ * 用于解析标准格式的群昵称:B站名称(ID:MC名称)
367
+ *
368
+ * @param nickname 群昵称字符串
369
+ * @returns B站用户名,如果格式不匹配返回 null
370
+ *
371
+ * @example
372
+ * extractBuidUsernameFromNickname("张三(ID:Steve)") // => "张三"
373
+ * extractBuidUsernameFromNickname("李四(ID:未绑定)") // => "李四"
374
+ * extractBuidUsernameFromNickname("无效格式") // => null
375
+ */
376
+ function extractBuidUsernameFromNickname(nickname) {
377
+ if (!nickname)
378
+ return null;
379
+ // 匹配格式:任意内容(ID:任意内容)
380
+ // 使用非贪婪匹配 .+? 确保正确提取第一个括号前的内容
381
+ const match = nickname.match(/^(.+?)(ID:.+)$/);
382
+ return match ? match[1].trim() : null;
383
+ }
@@ -18,13 +18,17 @@ export declare class MessageUtils {
18
18
  private config;
19
19
  private logger;
20
20
  private getBindingSessionFn;
21
+ private validateBUID?;
22
+ private updateBuidInfoOnly?;
21
23
  /**
22
24
  * 创建消息工具实例
23
25
  * @param config 消息工具配置
24
26
  * @param logger 日志服务
25
27
  * @param getBindingSessionFn 获取绑定会话的函数
28
+ * @param validateBUID 验证B站UID的函数(可选)
29
+ * @param updateBuidInfoOnly 更新B站信息的函数(可选)
26
30
  */
27
- constructor(config: MessageUtilsConfig, logger: LoggerService, getBindingSessionFn: (userId: string, channelId: string) => BindingSession | null);
31
+ constructor(config: MessageUtilsConfig, logger: LoggerService, getBindingSessionFn: (userId: string, channelId: string) => BindingSession | null, validateBUID?: (uid: string) => Promise<any>, updateBuidInfoOnly?: (qqId: string, buidUser: any) => Promise<boolean>);
28
32
  /**
29
33
  * 发送消息并处理自动撤回
30
34
  * @param session Koishi Session对象
@@ -39,8 +43,9 @@ export declare class MessageUtils {
39
43
  * @param session Koishi Session对象
40
44
  * @param mcUsername MC用户名
41
45
  * @param buidUsername B站用户名
46
+ * @param buidUid B站UID(可选,用于实时验证)
42
47
  * @param targetUserId 目标用户ID(可选,用于管理员为他人设置)
43
48
  * @param specifiedGroupId 指定的群ID(可选,默认使用配置的群ID)
44
49
  */
45
- autoSetGroupNickname(session: Session, mcUsername: string | null, buidUsername: string, targetUserId?: string, specifiedGroupId?: string): Promise<void>;
50
+ autoSetGroupNickname(session: Session, mcUsername: string | null, buidUsername: string, buidUid?: string, targetUserId?: string, specifiedGroupId?: string): Promise<void>;
46
51
  }
@@ -11,16 +11,22 @@ class MessageUtils {
11
11
  config;
12
12
  logger;
13
13
  getBindingSessionFn;
14
+ validateBUID;
15
+ updateBuidInfoOnly;
14
16
  /**
15
17
  * 创建消息工具实例
16
18
  * @param config 消息工具配置
17
19
  * @param logger 日志服务
18
20
  * @param getBindingSessionFn 获取绑定会话的函数
21
+ * @param validateBUID 验证B站UID的函数(可选)
22
+ * @param updateBuidInfoOnly 更新B站信息的函数(可选)
19
23
  */
20
- constructor(config, logger, getBindingSessionFn) {
24
+ constructor(config, logger, getBindingSessionFn, validateBUID, updateBuidInfoOnly) {
21
25
  this.config = config;
22
26
  this.logger = logger;
23
27
  this.getBindingSessionFn = getBindingSessionFn;
28
+ this.validateBUID = validateBUID;
29
+ this.updateBuidInfoOnly = updateBuidInfoOnly;
24
30
  }
25
31
  /**
26
32
  * 发送消息并处理自动撤回
@@ -135,17 +141,19 @@ class MessageUtils {
135
141
  * @param session Koishi Session对象
136
142
  * @param mcUsername MC用户名
137
143
  * @param buidUsername B站用户名
144
+ * @param buidUid B站UID(可选,用于实时验证)
138
145
  * @param targetUserId 目标用户ID(可选,用于管理员为他人设置)
139
146
  * @param specifiedGroupId 指定的群ID(可选,默认使用配置的群ID)
140
147
  */
141
- async autoSetGroupNickname(session, mcUsername, buidUsername, targetUserId, specifiedGroupId) {
148
+ async autoSetGroupNickname(session, mcUsername, buidUsername, buidUid, targetUserId, specifiedGroupId) {
142
149
  try {
143
150
  // 如果指定了目标用户ID,使用目标用户ID,否则使用session的用户ID
144
151
  const actualUserId = targetUserId || session.userId;
145
152
  const normalizedUserId = (0, helpers_1.normalizeQQId)(actualUserId);
146
153
  // 根据MC绑定状态设置不同的格式(临时用户名视为未绑定)
147
154
  const mcInfo = (mcUsername && !mcUsername.startsWith('_temp_')) ? mcUsername : "未绑定";
148
- const newNickname = `${buidUsername}(ID:${mcInfo})`;
155
+ let currentBuidUsername = buidUsername;
156
+ const newNickname = `${currentBuidUsername}(ID:${mcInfo})`;
149
157
  // 使用指定的群ID,如果没有指定则使用配置的默认群ID
150
158
  const targetGroupId = specifiedGroupId || this.config.autoNicknameGroupId;
151
159
  this.logger.debug('群昵称设置', `开始处理QQ(${normalizedUserId})的群昵称设置,目标群: ${targetGroupId}`);
@@ -164,20 +172,82 @@ class MessageUtils {
164
172
  this.logger.info('群昵称设置', `QQ(${normalizedUserId})群昵称已经是"${newNickname}",跳过修改`);
165
173
  return;
166
174
  }
175
+ // 昵称不一致,先尝试获取最新的B站用户信息
176
+ if (buidUid && this.validateBUID && this.updateBuidInfoOnly) {
177
+ this.logger.debug('群昵称设置', `检测到昵称不一致,尝试获取B站UID ${buidUid} 的最新信息...`);
178
+ // 1. 提取当前群昵称中的B站用户名
179
+ const currentNicknameUsername = (0, helpers_1.extractBuidUsernameFromNickname)(currentNickname);
180
+ this.logger.debug('群昵称设置', `从当前群昵称"${currentNickname}"中提取到B站名称: ${currentNicknameUsername || '(无法提取)'}`);
181
+ try {
182
+ const latestBuidUser = await this.validateBUID(buidUid);
183
+ if (latestBuidUser && latestBuidUser.username) {
184
+ this.logger.debug('群昵称设置', `API返回B站用户名: "${latestBuidUser.username}"`);
185
+ // 2. 智能三层判断
186
+ // 情况A: API返回 == 当前群昵称中的名称
187
+ if (currentNicknameUsername && latestBuidUser.username === currentNicknameUsername) {
188
+ this.logger.info('群昵称设置', `✅ 当前群昵称"${currentNickname}"已包含正确的B站名称"${currentNicknameUsername}"`);
189
+ // 群昵称已经正确,仅需更新数据库(如果数据库是旧的)
190
+ if (latestBuidUser.username !== buidUsername) {
191
+ this.logger.info('群昵称设置', `数据库中的B站名称需要更新: "${buidUsername}" → "${latestBuidUser.username}"`);
192
+ try {
193
+ await this.updateBuidInfoOnly(normalizedUserId, latestBuidUser);
194
+ this.logger.info('群昵称设置', `已更新数据库中的B站用户名`);
195
+ }
196
+ catch (updateError) {
197
+ this.logger.warn('群昵称设置', `更新数据库失败: ${updateError.message}`);
198
+ }
199
+ }
200
+ else {
201
+ this.logger.debug('群昵称设置', `数据库中的B站名称已是最新,无需更新`);
202
+ }
203
+ // 跳过群昵称修改
204
+ this.logger.info('群昵称设置', `群昵称格式正确且名称最新,跳过修改`);
205
+ return;
206
+ }
207
+ // 情况B: API返回 == 数据库(都是旧的)
208
+ if (latestBuidUser.username === buidUsername) {
209
+ this.logger.warn('群昵称设置', `⚠️ API返回的B站名称"${latestBuidUser.username}"与数据库一致,但与群昵称不符`);
210
+ this.logger.warn('群昵称设置', `可能是API缓存未刷新,采用保守策略:不修改群昵称`);
211
+ return;
212
+ }
213
+ // 情况C: API返回新数据(!= 群昵称 且 != 数据库)
214
+ this.logger.info('群昵称设置', `检测到B站用户名已更新: "${buidUsername}" → "${latestBuidUser.username}"`);
215
+ currentBuidUsername = latestBuidUser.username;
216
+ // 更新数据库中的B站信息
217
+ try {
218
+ await this.updateBuidInfoOnly(normalizedUserId, latestBuidUser);
219
+ this.logger.info('群昵称设置', `已更新数据库中的B站用户名为: ${currentBuidUsername}`);
220
+ }
221
+ catch (updateError) {
222
+ this.logger.warn('群昵称设置', `更新数据库中的B站用户名失败: ${updateError.message}`);
223
+ // 即使更新数据库失败,也继续使用最新的用户名设置昵称
224
+ }
225
+ }
226
+ else {
227
+ this.logger.warn('群昵称设置', `获取最新B站用户信息失败,使用数据库中的用户名: ${currentBuidUsername}`);
228
+ }
229
+ }
230
+ catch (validateError) {
231
+ this.logger.warn('群昵称设置', `获取最新B站用户信息时出错: ${validateError.message},使用数据库中的用户名: ${currentBuidUsername}`);
232
+ // API调用失败时降级处理,使用数据库中的旧名称
233
+ }
234
+ }
235
+ // 使用(可能已更新的)用户名重新生成昵称
236
+ const finalNickname = `${currentBuidUsername}(ID:${mcInfo})`;
167
237
  // 昵称不一致,执行修改
168
- this.logger.debug('群昵称设置', `昵称不一致,正在修改群昵称...`);
169
- await session.bot.internal.setGroupCard(targetGroupId, normalizedUserId, newNickname);
170
- this.logger.info('群昵称设置', `成功在群${targetGroupId}中将QQ(${normalizedUserId})群昵称从"${currentNickname}"修改为"${newNickname}"`, true);
238
+ this.logger.debug('群昵称设置', `昵称不一致,正在修改群昵称为: "${finalNickname}"`);
239
+ await session.bot.internal.setGroupCard(targetGroupId, normalizedUserId, finalNickname);
240
+ this.logger.info('群昵称设置', `成功在群${targetGroupId}中将QQ(${normalizedUserId})群昵称从"${currentNickname}"修改为"${finalNickname}"`, true);
171
241
  // 验证设置是否生效 - 再次获取群昵称确认
172
242
  try {
173
243
  await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
174
244
  const verifyGroupInfo = await session.bot.internal.getGroupMemberInfo(targetGroupId, normalizedUserId);
175
245
  const verifyNickname = verifyGroupInfo.card || verifyGroupInfo.nickname || '';
176
- if (verifyNickname === newNickname) {
246
+ if (verifyNickname === finalNickname) {
177
247
  this.logger.info('群昵称设置', `✅ 验证成功,群昵称已生效: "${verifyNickname}"`, true);
178
248
  }
179
249
  else {
180
- this.logger.warn('群昵称设置', `⚠️ 验证失败,期望"${newNickname}",实际"${verifyNickname}",可能是权限不足或API延迟`);
250
+ this.logger.warn('群昵称设置', `⚠️ 验证失败,期望"${finalNickname}",实际"${verifyNickname}",可能是权限不足或API延迟`);
181
251
  }
182
252
  }
183
253
  catch (verifyError) {
@@ -189,19 +259,54 @@ class MessageUtils {
189
259
  this.logger.warn('群昵称设置', `获取QQ(${normalizedUserId})当前群昵称失败: ${getInfoError.message}`);
190
260
  this.logger.warn('群昵称设置', `错误详情: ${JSON.stringify(getInfoError)}`);
191
261
  this.logger.debug('群昵称设置', `将直接尝试设置新昵称...`);
262
+ // 如果传入了 buidUid,尝试获取最新的B站用户信息
263
+ if (buidUid && this.validateBUID && this.updateBuidInfoOnly) {
264
+ this.logger.debug('群昵称设置', `尝试获取B站UID ${buidUid} 的最新信息...`);
265
+ try {
266
+ const latestBuidUser = await this.validateBUID(buidUid);
267
+ if (latestBuidUser && latestBuidUser.username) {
268
+ this.logger.debug('群昵称设置', `API返回B站用户名: "${latestBuidUser.username}"`);
269
+ // 智能判断:API返回 == 数据库(都是旧的)
270
+ if (latestBuidUser.username === buidUsername) {
271
+ this.logger.warn('群昵称设置', `⚠️ API返回的B站名称"${latestBuidUser.username}"与数据库一致`);
272
+ this.logger.warn('群昵称设置', `可能是API缓存未刷新,且无法获取当前群昵称,采用保守策略:跳过修改`);
273
+ return;
274
+ }
275
+ // API返回新数据(!= 数据库)
276
+ this.logger.info('群昵称设置', `检测到B站用户名已更新: "${buidUsername}" → "${latestBuidUser.username}"`);
277
+ currentBuidUsername = latestBuidUser.username;
278
+ // 更新数据库
279
+ try {
280
+ await this.updateBuidInfoOnly(normalizedUserId, latestBuidUser);
281
+ this.logger.info('群昵称设置', `已更新数据库中的B站用户名为: ${currentBuidUsername}`);
282
+ }
283
+ catch (updateError) {
284
+ this.logger.warn('群昵称设置', `更新数据库失败: ${updateError.message}`);
285
+ }
286
+ }
287
+ else {
288
+ this.logger.warn('群昵称设置', `获取最新B站用户信息失败`);
289
+ }
290
+ }
291
+ catch (validateError) {
292
+ this.logger.warn('群昵称设置', `获取最新B站用户信息失败: ${validateError.message}`);
293
+ }
294
+ }
192
295
  try {
193
- await session.bot.internal.setGroupCard(targetGroupId, normalizedUserId, newNickname);
194
- this.logger.info('群昵称设置', `成功在群${targetGroupId}中将QQ(${normalizedUserId})群昵称设置为: ${newNickname}`, true);
296
+ // 使用(可能已更新的)用户名生成昵称
297
+ const nicknameToSet = `${currentBuidUsername}(ID:${mcInfo})`;
298
+ await session.bot.internal.setGroupCard(targetGroupId, normalizedUserId, nicknameToSet);
299
+ this.logger.info('群昵称设置', `成功在群${targetGroupId}中将QQ(${normalizedUserId})群昵称设置为: ${nicknameToSet}`, true);
195
300
  // 验证设置是否生效
196
301
  try {
197
302
  await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
198
303
  const verifyGroupInfo = await session.bot.internal.getGroupMemberInfo(targetGroupId, normalizedUserId);
199
304
  const verifyNickname = verifyGroupInfo.card || verifyGroupInfo.nickname || '';
200
- if (verifyNickname === newNickname) {
305
+ if (verifyNickname === nicknameToSet) {
201
306
  this.logger.info('群昵称设置', `✅ 验证成功,群昵称已生效: "${verifyNickname}"`, true);
202
307
  }
203
308
  else {
204
- this.logger.warn('群昵称设置', `⚠️ 验证失败,期望"${newNickname}",实际"${verifyNickname}",可能是权限不足`);
309
+ this.logger.warn('群昵称设置', `⚠️ 验证失败,期望"${nicknameToSet}",实际"${verifyNickname}",可能是权限不足`);
205
310
  this.logger.warn('群昵称设置', `建议检查: 1.机器人是否为群管理员 2.群设置是否允许管理员修改昵称 3.OneBot实现是否支持该功能`);
206
311
  }
207
312
  }
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.0.4",
4
+ "version": "2.1.0",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [