koishi-plugin-bind-bot 2.1.0 → 2.1.2

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.
@@ -4,27 +4,77 @@
4
4
  */
5
5
  /**
6
6
  * MCIDBIND表结构 - MC和B站账号绑定主表
7
+ *
8
+ * @remarks
9
+ * 该表存储用户的 Minecraft 账号和 B站账号绑定信息
10
+ * - 使用 qqId 作为主键
11
+ * - mcUsername 具有唯一性约束
12
+ * - 支持单独绑定 MC 或 B站账号
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * {
17
+ * qqId: '12345678',
18
+ * mcUsername: 'Notch',
19
+ * mcUuid: '069a79f4-44e9-4726-a5be-fca90e38aaf5',
20
+ * lastModified: new Date(),
21
+ * isAdmin: false,
22
+ * whitelist: ['survival', 'creative'],
23
+ * tags: ['VIP', '建筑师'],
24
+ * buidUid: '87654321',
25
+ * buidUsername: 'B站用户名',
26
+ * guardLevel: 3,
27
+ * guardLevelText: '舰长',
28
+ * maxGuardLevel: 3,
29
+ * maxGuardLevelText: '舰长',
30
+ * medalName: '粉丝牌',
31
+ * medalLevel: 20,
32
+ * wealthMedalLevel: 15,
33
+ * lastActiveTime: new Date(),
34
+ * reminderCount: 0
35
+ * }
36
+ * ```
7
37
  */
8
38
  export interface MCIDBIND {
39
+ /** 纯QQ号 (作为主键, 不包含 'qq:' 前缀) */
9
40
  qqId: string;
41
+ /** MC用户名 (具有唯一性约束) */
10
42
  mcUsername: string;
43
+ /** MC UUID (格式: 带连字符的标准UUID) */
11
44
  mcUuid: string;
45
+ /** MC用户名上次检查时间 (用于改名检测缓存) */
12
46
  usernameLastChecked?: Date;
47
+ /** 用户名检查失败次数 (连续失败计数, 失败≥3次延长冷却期至72小时) */
13
48
  usernameCheckFailCount?: number;
49
+ /** 上次修改时间 (绑定/解绑操作的时间戳) */
14
50
  lastModified: Date;
51
+ /** 是否为MC绑定管理员 (拥有管理员权限) */
15
52
  isAdmin: boolean;
53
+ /** 已添加白名单的服务器ID列表 */
16
54
  whitelist: string[];
55
+ /** 用户标签列表 (用于分类管理) */
17
56
  tags: string[];
57
+ /** B站UID */
18
58
  buidUid: string;
59
+ /** B站用户名 */
19
60
  buidUsername: string;
61
+ /** 当前舰长等级 (0=无, 1=总督, 2=提督, 3=舰长) */
20
62
  guardLevel: number;
63
+ /** 当前舰长等级文本 (例: '舰长', '提督', '总督') */
21
64
  guardLevelText: string;
65
+ /** 历史最高舰长等级 */
22
66
  maxGuardLevel: number;
67
+ /** 历史最高舰长等级文本 */
23
68
  maxGuardLevelText: string;
69
+ /** 粉丝牌名称 */
24
70
  medalName: string;
71
+ /** 粉丝牌等级 */
25
72
  medalLevel: number;
73
+ /** 荣耀等级 (财富勋章等级) */
26
74
  wealthMedalLevel: number;
75
+ /** 最后活跃时间 (B站活跃时间) */
27
76
  lastActiveTime: Date;
77
+ /** 随机提醒次数 (用于天选时刻等功能) */
28
78
  reminderCount: number;
29
79
  }
30
80
  /**
@@ -3,43 +3,126 @@
3
3
  */
4
4
  /**
5
5
  * MC绑定更新数据接口
6
+ *
7
+ * @remarks
8
+ * 用于更新 MCIDBIND 表中的 MC 相关字段
9
+ * 所有字段都是可选的,只更新提供的字段
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // 仅更新用户名
14
+ * {
15
+ * mcUsername: 'NewUsername'
16
+ * }
17
+ *
18
+ * // 更新用户名和UUID
19
+ * {
20
+ * mcUsername: 'NewUsername',
21
+ * mcUuid: '069a79f4-44e9-4726-a5be-fca90e38aaf5',
22
+ * lastModified: new Date()
23
+ * }
24
+ * ```
6
25
  */
7
26
  export interface UpdateMcBindData {
27
+ /** MC用户名 */
8
28
  mcUsername?: string;
29
+ /** MC UUID */
9
30
  mcUuid?: string;
31
+ /** 上次修改时间 */
10
32
  lastModified?: Date;
33
+ /** 是否为管理员 */
11
34
  isAdmin?: boolean;
35
+ /** 用户名上次检查时间 */
12
36
  usernameLastChecked?: Date;
37
+ /** 用户名检查失败次数 */
13
38
  usernameCheckFailCount?: number;
14
39
  }
15
40
  /**
16
41
  * BUID绑定更新数据接口(完整更新)
42
+ *
43
+ * @remarks
44
+ * 用于更新 MCIDBIND 表中的 B站相关字段
45
+ * 该接口会同时更新 lastModified 字段,表示绑定操作时间
46
+ *
47
+ * @see {@link UpdateBuidInfoData} 仅刷新信息不更新 lastModified
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * {
52
+ * buidUid: '87654321',
53
+ * buidUsername: 'B站用户名',
54
+ * guardLevel: 3,
55
+ * guardLevelText: '舰长',
56
+ * maxGuardLevel: 3,
57
+ * maxGuardLevelText: '舰长',
58
+ * medalName: '粉丝牌',
59
+ * medalLevel: 20,
60
+ * wealthMedalLevel: 15,
61
+ * lastActiveTime: new Date(),
62
+ * lastModified: new Date()
63
+ * }
64
+ * ```
17
65
  */
18
66
  export interface UpdateBuidBindData {
67
+ /** B站UID (数据库中存储为字符串) */
19
68
  buidUid?: string;
69
+ /** B站用户名 */
20
70
  buidUsername?: string;
71
+ /** 当前舰长等级 */
21
72
  guardLevel?: number;
73
+ /** 当前舰长等级文本 */
22
74
  guardLevelText?: string;
75
+ /** 历史最高舰长等级 */
23
76
  maxGuardLevel?: number;
77
+ /** 历史最高舰长等级文本 */
24
78
  maxGuardLevelText?: string;
79
+ /** 粉丝牌名称 */
25
80
  medalName?: string;
81
+ /** 粉丝牌等级 */
26
82
  medalLevel?: number;
83
+ /** 荣耀等级 */
27
84
  wealthMedalLevel?: number;
85
+ /** 最后活跃时间 */
28
86
  lastActiveTime?: Date;
87
+ /** 上次修改时间 (绑定操作时间) */
29
88
  lastModified?: Date;
30
89
  }
31
90
  /**
32
91
  * BUID信息更新数据接口(仅更新信息,不更新lastModified)
92
+ *
93
+ * @remarks
94
+ * 用于查询时刷新 B站信息,不影响绑定时间
95
+ * 与 {@link UpdateBuidBindData} 的区别是不包含 lastModified 字段
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // 查询时刷新B站信息
100
+ * {
101
+ * buidUsername: '新用户名',
102
+ * guardLevel: 3,
103
+ * guardLevelText: '舰长',
104
+ * medalLevel: 21
105
+ * }
106
+ * ```
33
107
  */
34
108
  export interface UpdateBuidInfoData {
109
+ /** B站用户名 */
35
110
  buidUsername?: string;
111
+ /** 当前舰长等级 */
36
112
  guardLevel?: number;
113
+ /** 当前舰长等级文本 */
37
114
  guardLevelText?: string;
115
+ /** 历史最高舰长等级 */
38
116
  maxGuardLevel?: number;
117
+ /** 历史最高舰长等级文本 */
39
118
  maxGuardLevelText?: string;
119
+ /** 粉丝牌名称 */
40
120
  medalName?: string;
121
+ /** 粉丝牌等级 */
41
122
  medalLevel?: number;
123
+ /** 荣耀等级 */
42
124
  wealthMedalLevel?: number;
125
+ /** 最后活跃时间 */
43
126
  lastActiveTime?: Date;
44
127
  }
45
128
  /**
@@ -51,10 +51,14 @@ function getUserFacingErrorMessage(errorMsg) {
51
51
  }
52
52
  // RCON相关错误
53
53
  if (errorMsg.includes('RCON') || errorMsg.includes('服务器')) {
54
- if (errorMsg.includes('authentication') || errorMsg.includes('auth') || errorMsg.includes('认证')) {
54
+ if (errorMsg.includes('authentication') ||
55
+ errorMsg.includes('auth') ||
56
+ errorMsg.includes('认证')) {
55
57
  return 'RCON认证失败,服务器拒绝访问,请联系管理员检查密码';
56
58
  }
57
- if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('ETIMEDOUT') || errorMsg.includes('无法连接')) {
59
+ if (errorMsg.includes('ECONNREFUSED') ||
60
+ errorMsg.includes('ETIMEDOUT') ||
61
+ errorMsg.includes('无法连接')) {
58
62
  return '无法连接到游戏服务器,请确认服务器是否在线或联系管理员';
59
63
  }
60
64
  if (errorMsg.includes('command') || errorMsg.includes('执行命令')) {
@@ -101,11 +105,6 @@ function isWarningError(errorMsg) {
101
105
  * @returns 是否为严重错误
102
106
  */
103
107
  function isCriticalError(errorMsg) {
104
- const criticalPatterns = [
105
- '无法连接',
106
- 'RCON认证失败',
107
- '服务器通信失败',
108
- '数据库操作出错'
109
- ];
108
+ const criticalPatterns = ['无法连接', 'RCON认证失败', '服务器通信失败', '数据库操作出错'];
110
109
  return criticalPatterns.some(pattern => errorMsg.includes(pattern));
111
110
  }
@@ -32,7 +32,7 @@ exports.extractBuidUsernameFromNickname = extractBuidUsernameFromNickname;
32
32
  function normalizeQQId(userId, logger) {
33
33
  // 处理空值情况
34
34
  if (!userId) {
35
- logger?.warn(`[用户ID] 收到空用户ID`);
35
+ logger?.warn('[用户ID] 收到空用户ID');
36
36
  return '';
37
37
  }
38
38
  let extractedId = '';
@@ -165,7 +165,7 @@ function getStarlightSkinUrl(username, logger) {
165
165
  'dungeons', // 地下城风格
166
166
  'facepalm', // 捂脸
167
167
  'mojavatar', // Mojave姿态
168
- 'head', // 头部特写
168
+ 'head' // 头部特写
169
169
  ];
170
170
  // 随机选择一个动作
171
171
  const randomPose = poses[Math.floor(Math.random() * poses.length)];
@@ -186,7 +186,39 @@ function checkIrrelevantInput(state, content) {
186
186
  if (!content)
187
187
  return false;
188
188
  // 常见的聊天用语或明显无关的内容
189
- const chatKeywords = ['你好', 'hello', 'hi', '在吗', '在不在', '怎么样', '什么', '为什么', '好的', '谢谢', '哈哈', '呵呵', '早上好', '晚上好', '晚安', '再见', '拜拜', '666', '牛', '厉害', '真的吗', '不是吧', '哇', '哦', '嗯', '好吧', '行', '可以', '没事', '没问题', '没关系'];
189
+ const chatKeywords = [
190
+ '你好',
191
+ 'hello',
192
+ 'hi',
193
+ '在吗',
194
+ '在不在',
195
+ '怎么样',
196
+ '什么',
197
+ '为什么',
198
+ '好的',
199
+ '谢谢',
200
+ '哈哈',
201
+ '呵呵',
202
+ '早上好',
203
+ '晚上好',
204
+ '晚安',
205
+ '再见',
206
+ '拜拜',
207
+ '666',
208
+ '牛',
209
+ '厉害',
210
+ '真的吗',
211
+ '不是吧',
212
+ '哇',
213
+ '哦',
214
+ '嗯',
215
+ '好吧',
216
+ '行',
217
+ '可以',
218
+ '没事',
219
+ '没问题',
220
+ '没关系'
221
+ ];
190
222
  const lowercaseContent = content.toLowerCase();
191
223
  // 检查是否包含明显的聊天用语
192
224
  if (chatKeywords.some(keyword => lowercaseContent.includes(keyword))) {
@@ -212,7 +244,10 @@ function checkIrrelevantInput(state, content) {
212
244
  return true;
213
245
  }
214
246
  // 如果是明显的指令格式
215
- if (content.startsWith('.') || content.startsWith('/') || content.startsWith('mcid') || content.startsWith('buid')) {
247
+ if (content.startsWith('.') ||
248
+ content.startsWith('/') ||
249
+ content.startsWith('mcid') ||
250
+ content.startsWith('buid')) {
216
251
  return true;
217
252
  }
218
253
  }
@@ -230,7 +265,10 @@ function checkIrrelevantInput(state, content) {
230
265
  return true;
231
266
  }
232
267
  // 如果是明显的指令格式
233
- if (content.startsWith('.') || content.startsWith('/') || content.startsWith('mcid') || content.startsWith('buid')) {
268
+ if (content.startsWith('.') ||
269
+ content.startsWith('/') ||
270
+ content.startsWith('mcid') ||
271
+ content.startsWith('buid')) {
234
272
  return true;
235
273
  }
236
274
  }
@@ -265,7 +303,7 @@ function cleanUserInput(content, session, botNickname, logger) {
265
303
  // @Bot昵称 格式(如果配置了botNickname)
266
304
  botNickname ? new RegExp(`^@${escapeRegExp(botNickname)}\\s+`, 'i') : null,
267
305
  // @botUserId 格式
268
- new RegExp(`^@${escapeRegExp(botUserId)}\\s+`, 'i'),
306
+ new RegExp(`^@${escapeRegExp(botUserId)}\\s+`, 'i')
269
307
  ].filter(Boolean);
270
308
  let cleanedContent = content.trim();
271
309
  // 尝试匹配并移除@Bot前缀
@@ -333,7 +371,7 @@ function calculateSimilarity(str1, str2) {
333
371
  */
334
372
  function normalizeUsername(username, logger) {
335
373
  if (!username) {
336
- logger?.warn(`[用户名规范化] 收到空用户名`);
374
+ logger?.warn('[用户名规范化] 收到空用户名');
337
375
  return '';
338
376
  }
339
377
  // 移除首尾空格
@@ -47,8 +47,12 @@ class MessageUtils {
47
47
  // 处理私聊和群聊的消息格式
48
48
  // 主动消息不引用原消息
49
49
  const promptMessage = session.channelId?.startsWith('private:')
50
- ? (isProactiveMessage ? content : [koishi_1.h.quote(session.messageId), ...content])
51
- : (isProactiveMessage ? [koishi_1.h.at(normalizedQQId), '\n', ...content] : [koishi_1.h.quote(session.messageId), koishi_1.h.at(normalizedQQId), '\n', ...content]);
50
+ ? isProactiveMessage
51
+ ? content
52
+ : [koishi_1.h.quote(session.messageId), ...content]
53
+ : isProactiveMessage
54
+ ? [koishi_1.h.at(normalizedQQId), '\n', ...content]
55
+ : [koishi_1.h.quote(session.messageId), koishi_1.h.at(normalizedQQId), '\n', ...content];
52
56
  // 发送消息并获取返回的消息ID
53
57
  const messageResult = await session.send(promptMessage);
54
58
  this.logger.debug('消息', `成功向QQ(${normalizedQQId})发送消息,频道: ${session.channelId}`);
@@ -58,11 +62,18 @@ class MessageUtils {
58
62
  // 但如果用户在绑定会话中发送聊天消息(不包括指令),不撤回
59
63
  // 主动消息不撤回用户消息
60
64
  const bindingSession = this.getBindingSessionFn(session.userId, session.channelId);
61
- const isBindingCommand = session.content && (session.content.trim() === '绑定' ||
62
- session.content.includes('@') && session.content.includes('绑定'));
63
- const shouldNotRecallUserMessage = bindingSession && session.content &&
64
- !isBindingCommand && (0, helpers_1.checkIrrelevantInput)(bindingSession.state, session.content.trim());
65
- if (this.config.recallUserMessage && isGroupMessage && session.messageId && !shouldNotRecallUserMessage && !isProactiveMessage) {
65
+ const isBindingCommand = session.content &&
66
+ (session.content.trim() === '绑定' ||
67
+ (session.content.includes('@') && session.content.includes('绑定')));
68
+ const shouldNotRecallUserMessage = bindingSession &&
69
+ session.content &&
70
+ !isBindingCommand &&
71
+ (0, helpers_1.checkIrrelevantInput)(bindingSession.state, session.content.trim());
72
+ if (this.config.recallUserMessage &&
73
+ isGroupMessage &&
74
+ session.messageId &&
75
+ !shouldNotRecallUserMessage &&
76
+ !isProactiveMessage) {
66
77
  setTimeout(async () => {
67
78
  try {
68
79
  await session.bot.deleteMessage(session.channelId, session.messageId);
@@ -78,7 +89,7 @@ class MessageUtils {
78
89
  this.logger.debug('消息', `QQ(${normalizedQQId})在绑定会话中发送聊天消息,跳过撤回用户消息`);
79
90
  }
80
91
  else if (isProactiveMessage) {
81
- this.logger.debug('消息', `主动发送的消息,跳过撤回用户消息`);
92
+ this.logger.debug('消息', '主动发送的消息,跳过撤回用户消息');
82
93
  }
83
94
  // 处理撤回机器人消息 - 只在群聊中撤回机器人自己的消息
84
95
  // 检查是否为不应撤回的重要提示消息(只有绑定会话超时提醒)
@@ -105,9 +116,10 @@ class MessageUtils {
105
116
  }
106
117
  else if (messageResult && typeof messageResult === 'object') {
107
118
  // 尝试提取各种可能的消息ID格式
108
- messageId = messageResult.messageId ||
109
- messageResult.id ||
110
- messageResult.message_id;
119
+ messageId =
120
+ messageResult.messageId ||
121
+ messageResult.id ||
122
+ messageResult.message_id;
111
123
  }
112
124
  if (messageId) {
113
125
  // 设置定时器延迟撤回
@@ -123,11 +135,11 @@ class MessageUtils {
123
135
  this.logger.debug('消息', `已设置 ${this.config.autoRecallTime} 秒后自动撤回机器人消息 ${messageId}`);
124
136
  }
125
137
  else {
126
- this.logger.warn('消息', `无法获取消息ID,自动撤回功能无法生效`);
138
+ this.logger.warn('消息', '无法获取消息ID,自动撤回功能无法生效');
127
139
  }
128
140
  }
129
141
  else {
130
- this.logger.debug('消息', `检测到私聊消息,不撤回机器人回复`);
142
+ this.logger.debug('消息', '检测到私聊消息,不撤回机器人回复');
131
143
  }
132
144
  }
133
145
  }
@@ -151,7 +163,7 @@ class MessageUtils {
151
163
  const actualUserId = targetUserId || session.userId;
152
164
  const normalizedUserId = (0, helpers_1.normalizeQQId)(actualUserId);
153
165
  // 根据MC绑定状态设置不同的格式(临时用户名视为未绑定)
154
- const mcInfo = (mcUsername && !mcUsername.startsWith('_temp_')) ? mcUsername : "未绑定";
166
+ const mcInfo = mcUsername && !mcUsername.startsWith('_temp_') ? mcUsername : '未绑定';
155
167
  let currentBuidUsername = buidUsername;
156
168
  const newNickname = `${currentBuidUsername}(ID:${mcInfo})`;
157
169
  // 使用指定的群ID,如果没有指定则使用配置的默认群ID
@@ -184,30 +196,31 @@ class MessageUtils {
184
196
  this.logger.debug('群昵称设置', `API返回B站用户名: "${latestBuidUser.username}"`);
185
197
  // 2. 智能三层判断
186
198
  // 情况A: API返回 == 当前群昵称中的名称
187
- if (currentNicknameUsername && latestBuidUser.username === currentNicknameUsername) {
199
+ if (currentNicknameUsername &&
200
+ latestBuidUser.username === currentNicknameUsername) {
188
201
  this.logger.info('群昵称设置', `✅ 当前群昵称"${currentNickname}"已包含正确的B站名称"${currentNicknameUsername}"`);
189
202
  // 群昵称已经正确,仅需更新数据库(如果数据库是旧的)
190
203
  if (latestBuidUser.username !== buidUsername) {
191
204
  this.logger.info('群昵称设置', `数据库中的B站名称需要更新: "${buidUsername}" → "${latestBuidUser.username}"`);
192
205
  try {
193
206
  await this.updateBuidInfoOnly(normalizedUserId, latestBuidUser);
194
- this.logger.info('群昵称设置', `已更新数据库中的B站用户名`);
207
+ this.logger.info('群昵称设置', '已更新数据库中的B站用户名');
195
208
  }
196
209
  catch (updateError) {
197
210
  this.logger.warn('群昵称设置', `更新数据库失败: ${updateError.message}`);
198
211
  }
199
212
  }
200
213
  else {
201
- this.logger.debug('群昵称设置', `数据库中的B站名称已是最新,无需更新`);
214
+ this.logger.debug('群昵称设置', '数据库中的B站名称已是最新,无需更新');
202
215
  }
203
216
  // 跳过群昵称修改
204
- this.logger.info('群昵称设置', `群昵称格式正确且名称最新,跳过修改`);
217
+ this.logger.info('群昵称设置', '群昵称格式正确且名称最新,跳过修改');
205
218
  return;
206
219
  }
207
220
  // 情况B: API返回 == 数据库(都是旧的)
208
221
  if (latestBuidUser.username === buidUsername) {
209
222
  this.logger.warn('群昵称设置', `⚠️ API返回的B站名称"${latestBuidUser.username}"与数据库一致,但与群昵称不符`);
210
- this.logger.warn('群昵称设置', `可能是API缓存未刷新,采用保守策略:不修改群昵称`);
223
+ this.logger.warn('群昵称设置', '可能是API缓存未刷新,采用保守策略:不修改群昵称');
211
224
  return;
212
225
  }
213
226
  // 情况C: API返回新数据(!= 群昵称 且 != 数据库)
@@ -258,7 +271,7 @@ class MessageUtils {
258
271
  // 如果获取当前昵称失败,直接尝试设置新昵称
259
272
  this.logger.warn('群昵称设置', `获取QQ(${normalizedUserId})当前群昵称失败: ${getInfoError.message}`);
260
273
  this.logger.warn('群昵称设置', `错误详情: ${JSON.stringify(getInfoError)}`);
261
- this.logger.debug('群昵称设置', `将直接尝试设置新昵称...`);
274
+ this.logger.debug('群昵称设置', '将直接尝试设置新昵称...');
262
275
  // 如果传入了 buidUid,尝试获取最新的B站用户信息
263
276
  if (buidUid && this.validateBUID && this.updateBuidInfoOnly) {
264
277
  this.logger.debug('群昵称设置', `尝试获取B站UID ${buidUid} 的最新信息...`);
@@ -269,7 +282,7 @@ class MessageUtils {
269
282
  // 智能判断:API返回 == 数据库(都是旧的)
270
283
  if (latestBuidUser.username === buidUsername) {
271
284
  this.logger.warn('群昵称设置', `⚠️ API返回的B站名称"${latestBuidUser.username}"与数据库一致`);
272
- this.logger.warn('群昵称设置', `可能是API缓存未刷新,且无法获取当前群昵称,采用保守策略:跳过修改`);
285
+ this.logger.warn('群昵称设置', '可能是API缓存未刷新,且无法获取当前群昵称,采用保守策略:跳过修改');
273
286
  return;
274
287
  }
275
288
  // API返回新数据(!= 数据库)
@@ -285,7 +298,7 @@ class MessageUtils {
285
298
  }
286
299
  }
287
300
  else {
288
- this.logger.warn('群昵称设置', `获取最新B站用户信息失败`);
301
+ this.logger.warn('群昵称设置', '获取最新B站用户信息失败');
289
302
  }
290
303
  }
291
304
  catch (validateError) {
@@ -307,7 +320,7 @@ class MessageUtils {
307
320
  }
308
321
  else {
309
322
  this.logger.warn('群昵称设置', `⚠️ 验证失败,期望"${nicknameToSet}",实际"${verifyNickname}",可能是权限不足`);
310
- this.logger.warn('群昵称设置', `建议检查: 1.机器人是否为群管理员 2.群设置是否允许管理员修改昵称 3.OneBot实现是否支持该功能`);
323
+ this.logger.warn('群昵称设置', '建议检查: 1.机器人是否为群管理员 2.群设置是否允许管理员修改昵称 3.OneBot实现是否支持该功能');
311
324
  }
312
325
  }
313
326
  catch (verifyError) {
@@ -48,7 +48,12 @@ class SessionManager {
48
48
  // 发送超时消息,@用户
49
49
  const normalizedUser = (0, helpers_1.normalizeQQId)(userId);
50
50
  this.ctx.bots.forEach(bot => {
51
- bot.sendMessage(channelId, [koishi_1.h.at(normalizedUser), koishi_1.h.text(' 绑定会话已超时,请重新开始绑定流程\n\n⚠️ 温馨提醒:若在管理员多次提醒后仍不配合绑定账号信息,将按群规进行相应处理。')]).catch(() => { });
51
+ bot
52
+ .sendMessage(channelId, [
53
+ koishi_1.h.at(normalizedUser),
54
+ koishi_1.h.text(' 绑定会话已超时,请重新开始绑定流程\n\n⚠️ 温馨提醒:若在管理员多次提醒后仍不配合绑定账号信息,将按群规进行相应处理。')
55
+ ])
56
+ .catch(() => { });
52
57
  });
53
58
  this.logger.info('交互绑定', `QQ(${normalizedUser})的绑定会话因超时被清理`, true);
54
59
  }, this.sessionTimeout);
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.0",
4
+ "version": "2.1.2",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -9,7 +9,8 @@
9
9
  ],
10
10
  "license": "CC-BY-NC-SA-4.0",
11
11
  "scripts": {
12
- "build": "tsc"
12
+ "build": "tsc",
13
+ "lint:fix": "eslint src --ext .ts --fix && prettier --write \"src/**/*.ts\""
13
14
  },
14
15
  "keywords": [
15
16
  "chatbot",
@@ -25,7 +26,16 @@
25
26
  }
26
27
  },
27
28
  "devDependencies": {
29
+ "@types/jest": "^30.0.0",
28
30
  "@types/node": "^20.10.0",
31
+ "@typescript-eslint/eslint-plugin": "^8.46.2",
32
+ "@typescript-eslint/parser": "^8.46.2",
33
+ "eslint": "^9.38.0",
34
+ "eslint-config-prettier": "^10.1.8",
35
+ "eslint-plugin-prettier": "^5.5.4",
36
+ "jest": "^30.2.0",
37
+ "prettier": "^3.6.2",
38
+ "ts-jest": "^29.4.5",
29
39
  "typescript": "^5.5.4"
30
40
  },
31
41
  "peerDependencies": {