koishi-plugin-bind-bot 2.2.9 → 2.4.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.
- package/lib/export-utils.js +2 -1
- package/lib/force-bind-utils.d.ts +0 -4
- package/lib/force-bind-utils.js +0 -26
- package/lib/handlers/binding.handler.js +2 -1
- package/lib/handlers/buid.handler.d.ts +2 -5
- package/lib/handlers/buid.handler.js +12 -104
- package/lib/handlers/group-request-review.handler.js +12 -18
- package/lib/handlers/mcid.handler.js +41 -54
- package/lib/index.js +13 -311
- package/lib/repositories/mcidbind.repository.d.ts +8 -86
- package/lib/repositories/mcidbind.repository.js +74 -127
- package/lib/services/api.service.d.ts +2 -9
- package/lib/services/api.service.js +1 -39
- package/lib/services/database.service.d.ts +11 -4
- package/lib/services/database.service.js +7 -27
- package/lib/services/nickname.service.d.ts +7 -7
- package/lib/services/nickname.service.js +10 -41
- package/lib/services/service-container.js +1 -2
- package/lib/types/config.d.ts +4 -3
- package/lib/types/database.d.ts +0 -6
- package/lib/types/update-data.d.ts +0 -11
- package/lib/utils/helpers.d.ts +1 -9
- package/lib/utils/helpers.js +5 -0
- package/lib/utils/message-utils.d.ts +1 -5
- package/lib/utils/message-utils.js +2 -102
- package/lib/utils/supabase-client.d.ts +14 -0
- package/lib/utils/supabase-client.js +44 -0
- package/package.json +1 -1
|
@@ -2,171 +2,156 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MCIDBINDRepository = void 0;
|
|
4
4
|
const helpers_1 = require("../utils/helpers");
|
|
5
|
+
const TABLE = 'user';
|
|
6
|
+
const DATE_FIELDS = ['lastModified', 'usernameLastChecked', 'lastActiveTime'];
|
|
7
|
+
function deserialize(row) {
|
|
8
|
+
if (!row)
|
|
9
|
+
return row;
|
|
10
|
+
for (const field of DATE_FIELDS) {
|
|
11
|
+
if (row[field] && typeof row[field] === 'string') {
|
|
12
|
+
row[field] = new Date(row[field]);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return row;
|
|
16
|
+
}
|
|
17
|
+
function serializeDates(data) {
|
|
18
|
+
const out = { ...data };
|
|
19
|
+
for (const field of DATE_FIELDS) {
|
|
20
|
+
if (out[field] instanceof Date) {
|
|
21
|
+
out[field] = out[field].toISOString();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
5
26
|
/**
|
|
6
27
|
* MCIDBIND 数据仓储类
|
|
7
|
-
*
|
|
28
|
+
* 通过 Supabase REST API 操作 user 表
|
|
8
29
|
*/
|
|
9
30
|
class MCIDBINDRepository {
|
|
10
|
-
|
|
31
|
+
supabase;
|
|
11
32
|
logger;
|
|
12
|
-
constructor(
|
|
13
|
-
this.
|
|
33
|
+
constructor(supabase, logger) {
|
|
34
|
+
this.supabase = supabase;
|
|
14
35
|
this.logger = logger;
|
|
15
36
|
}
|
|
16
|
-
/**
|
|
17
|
-
* 根据 QQ 号查询绑定信息
|
|
18
|
-
* @param qqId QQ号(已规范化)
|
|
19
|
-
* @returns 绑定信息或 null
|
|
20
|
-
*/
|
|
21
37
|
async findByQQId(qqId) {
|
|
22
38
|
try {
|
|
23
39
|
this.logger.debug('数据库', `查询QQ(${qqId})的绑定信息`);
|
|
24
|
-
const
|
|
25
|
-
return
|
|
40
|
+
const rows = await this.supabase.get(TABLE, `qqId=eq.${qqId}&limit=1`);
|
|
41
|
+
return rows.length > 0 ? deserialize(rows[0]) : null;
|
|
26
42
|
}
|
|
27
43
|
catch (error) {
|
|
28
44
|
this.logger.error('数据库', `查询QQ(${qqId})绑定信息失败: ${error.message}`);
|
|
29
45
|
return null;
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
|
-
/**
|
|
33
|
-
* 根据 MC 用户名查询绑定信息(精确匹配)
|
|
34
|
-
* @param mcUsername MC用户名
|
|
35
|
-
* @returns 绑定信息或 null
|
|
36
|
-
*/
|
|
37
48
|
async findByMCUsername(mcUsername) {
|
|
38
49
|
try {
|
|
39
50
|
this.logger.debug('数据库', `查询MC用户名(${mcUsername})的绑定信息`);
|
|
40
|
-
const
|
|
41
|
-
return
|
|
51
|
+
const rows = await this.supabase.get(TABLE, `mcUsername=eq.${encodeURIComponent(mcUsername)}&limit=1`);
|
|
52
|
+
return rows.length > 0 ? deserialize(rows[0]) : null;
|
|
42
53
|
}
|
|
43
54
|
catch (error) {
|
|
44
55
|
this.logger.error('数据库', `查询MC用户名(${mcUsername})绑定信息失败: ${error.message}`);
|
|
45
56
|
return null;
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
|
-
/**
|
|
49
|
-
* 根据 MC 用户名查询绑定信息(不区分大小写)
|
|
50
|
-
* @param mcUsername MC用户名
|
|
51
|
-
* @returns 绑定信息或 null
|
|
52
|
-
*/
|
|
53
59
|
async findByUsernameIgnoreCase(mcUsername) {
|
|
54
60
|
try {
|
|
55
61
|
const normalizedInput = (0, helpers_1.normalizeUsername)(mcUsername, this.logger.getRawLogger());
|
|
56
62
|
this.logger.debug('数据库', `查询MC用户名(${mcUsername} -> ${normalizedInput})的绑定信息(不区分大小写)`);
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
return match || null;
|
|
63
|
+
const rows = await this.supabase.get(TABLE, `mcUsername=ilike.${encodeURIComponent(mcUsername)}`);
|
|
64
|
+
const match = rows.find(bind => bind.mcUsername && (0, helpers_1.normalizeUsername)(bind.mcUsername) === normalizedInput);
|
|
65
|
+
return match ? deserialize(match) : null;
|
|
61
66
|
}
|
|
62
67
|
catch (error) {
|
|
63
68
|
this.logger.error('数据库', `查询MC用户名(${mcUsername})绑定信息失败(不区分大小写): ${error.message}`);
|
|
64
69
|
return null;
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
|
-
/**
|
|
68
|
-
* 根据 MC UUID 查询绑定信息
|
|
69
|
-
* @param mcUuid MC UUID(可带或不带连字符)
|
|
70
|
-
* @returns 绑定信息或 null
|
|
71
|
-
*/
|
|
72
72
|
async findByUuid(mcUuid) {
|
|
73
73
|
try {
|
|
74
|
-
// 规范化 UUID(移除连字符)
|
|
75
74
|
const cleanUuid = mcUuid.replace(/-/g, '');
|
|
76
75
|
this.logger.debug('数据库', `查询MC UUID(${cleanUuid})的绑定信息`);
|
|
77
|
-
// 先尝试精确匹配
|
|
78
|
-
let binds = await this.ctx.database.get('mcidbind', { mcUuid: cleanUuid });
|
|
79
|
-
if (binds.length > 0)
|
|
80
|
-
return binds[0];
|
|
81
|
-
// 尝试带连字符的格式
|
|
82
76
|
const formattedUuid = `${cleanUuid.substring(0, 8)}-${cleanUuid.substring(8, 12)}-${cleanUuid.substring(12, 16)}-${cleanUuid.substring(16, 20)}-${cleanUuid.substring(20)}`;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
77
|
+
// 用 or 一次查两种格式
|
|
78
|
+
const rows = await this.supabase.get(TABLE, `or=(mcUuid.eq.${cleanUuid},mcUuid.eq.${formattedUuid})&limit=1`);
|
|
79
|
+
if (rows.length > 0)
|
|
80
|
+
return deserialize(rows[0]);
|
|
81
|
+
// 兜底:用 ilike 模糊匹配
|
|
82
|
+
const fallback = await this.supabase.get(TABLE, `mcUuid=ilike.*${cleanUuid}*&limit=1`);
|
|
83
|
+
if (fallback.length > 0)
|
|
84
|
+
return deserialize(fallback[0]);
|
|
85
|
+
return null;
|
|
90
86
|
}
|
|
91
87
|
catch (error) {
|
|
92
88
|
this.logger.error('数据库', `查询MC UUID(${mcUuid})绑定信息失败: ${error.message}`);
|
|
93
89
|
return null;
|
|
94
90
|
}
|
|
95
91
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
async validateBuidUid(uid) {
|
|
93
|
+
try {
|
|
94
|
+
this.logger.debug('数据库', `验证B站UID(${uid})是否存在`);
|
|
95
|
+
const rows = await this.supabase.get('events', `uid=eq.${uid}&uname=not.is.null&order=created_at.desc&limit=1&select=uid,uname`);
|
|
96
|
+
if (rows.length === 0)
|
|
97
|
+
return null;
|
|
98
|
+
return { uid: rows[0].uid.toString(), username: rows[0].uname };
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.logger.error('数据库', `验证B站UID(${uid})失败: ${error.message}`);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
101
105
|
async findByBuidUid(buidUid) {
|
|
102
106
|
try {
|
|
103
107
|
this.logger.debug('数据库', `查询B站UID(${buidUid})的绑定信息`);
|
|
104
|
-
const
|
|
105
|
-
return
|
|
108
|
+
const rows = await this.supabase.get(TABLE, `buidUid=eq.${buidUid}&limit=1`);
|
|
109
|
+
return rows.length > 0 ? deserialize(rows[0]) : null;
|
|
106
110
|
}
|
|
107
111
|
catch (error) {
|
|
108
112
|
this.logger.error('数据库', `查询B站UID(${buidUid})绑定信息失败: ${error.message}`);
|
|
109
113
|
return null;
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
|
-
/**
|
|
113
|
-
* 获取所有绑定记录
|
|
114
|
-
* @param options 查询选项
|
|
115
|
-
* @returns 绑定记录列表
|
|
116
|
-
*/
|
|
117
116
|
async findAll(options) {
|
|
118
117
|
try {
|
|
119
118
|
this.logger.debug('数据库', `获取所有绑定记录${options?.limit ? ` (limit: ${options.limit})` : ''}`);
|
|
120
|
-
const
|
|
121
|
-
|
|
119
|
+
const query = options?.limit ? `limit=${options.limit}` : '';
|
|
120
|
+
const rows = await this.supabase.get(TABLE, query);
|
|
121
|
+
return rows.map(deserialize);
|
|
122
122
|
}
|
|
123
123
|
catch (error) {
|
|
124
124
|
this.logger.error('数据库', `获取所有绑定记录失败: ${error.message}`);
|
|
125
125
|
return [];
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
/**
|
|
129
|
-
* 根据标签查询绑定记录
|
|
130
|
-
* @param tag 标签名称
|
|
131
|
-
* @returns 包含该标签的绑定记录列表
|
|
132
|
-
*/
|
|
133
128
|
async findByTag(tag) {
|
|
134
129
|
try {
|
|
135
130
|
this.logger.debug('数据库', `查询包含标签"${tag}"的绑定记录`);
|
|
136
|
-
const
|
|
137
|
-
return
|
|
131
|
+
const rows = await this.supabase.get(TABLE, `tags=cs.${encodeURIComponent(JSON.stringify([tag]))}`);
|
|
132
|
+
return rows.map(deserialize);
|
|
138
133
|
}
|
|
139
134
|
catch (error) {
|
|
140
135
|
this.logger.error('数据库', `查询标签"${tag}"的绑定记录失败: ${error.message}`);
|
|
141
136
|
return [];
|
|
142
137
|
}
|
|
143
138
|
}
|
|
144
|
-
/**
|
|
145
|
-
* 创建新的绑定记录
|
|
146
|
-
* @param data 绑定数据
|
|
147
|
-
* @returns 创建的记录
|
|
148
|
-
*/
|
|
149
139
|
async create(data) {
|
|
150
140
|
try {
|
|
151
141
|
this.logger.debug('数据库', `创建QQ(${data.qqId})的绑定记录`);
|
|
152
|
-
const created = await this.
|
|
142
|
+
const created = await this.supabase.post(TABLE, serializeDates(data));
|
|
153
143
|
this.logger.info('数据库', `成功创建QQ(${data.qqId})的绑定记录`, true);
|
|
154
|
-
return created;
|
|
144
|
+
return deserialize(created);
|
|
155
145
|
}
|
|
156
146
|
catch (error) {
|
|
157
147
|
this.logger.error('数据库', `创建QQ(${data.qqId})绑定记录失败: ${error.message}`);
|
|
158
148
|
throw error;
|
|
159
149
|
}
|
|
160
150
|
}
|
|
161
|
-
/**
|
|
162
|
-
* 更新绑定记录(部分字段)
|
|
163
|
-
* @param qqId QQ号
|
|
164
|
-
* @param data 要更新的字段
|
|
165
|
-
*/
|
|
166
151
|
async update(qqId, data) {
|
|
167
152
|
try {
|
|
168
153
|
this.logger.debug('数据库', `更新QQ(${qqId})的绑定记录`);
|
|
169
|
-
await this.
|
|
154
|
+
await this.supabase.patch(TABLE, `qqId=eq.${qqId}`, serializeDates(data));
|
|
170
155
|
this.logger.info('数据库', `成功更新QQ(${qqId})的绑定记录`, true);
|
|
171
156
|
}
|
|
172
157
|
catch (error) {
|
|
@@ -174,49 +159,35 @@ class MCIDBINDRepository {
|
|
|
174
159
|
throw error;
|
|
175
160
|
}
|
|
176
161
|
}
|
|
177
|
-
/**
|
|
178
|
-
* 删除绑定记录
|
|
179
|
-
* @param qqId QQ号
|
|
180
|
-
* @returns 删除的记录数
|
|
181
|
-
*/
|
|
182
162
|
async delete(qqId) {
|
|
183
163
|
try {
|
|
184
164
|
this.logger.debug('数据库', `删除QQ(${qqId})的绑定记录`);
|
|
185
|
-
const
|
|
186
|
-
this.logger.info('数据库', `成功删除QQ(${qqId})的绑定记录(删除${
|
|
187
|
-
return
|
|
165
|
+
const removed = await this.supabase.del(TABLE, `qqId=eq.${qqId}`);
|
|
166
|
+
this.logger.info('数据库', `成功删除QQ(${qqId})的绑定记录(删除${removed}条)`, true);
|
|
167
|
+
return removed;
|
|
188
168
|
}
|
|
189
169
|
catch (error) {
|
|
190
170
|
this.logger.error('数据库', `删除QQ(${qqId})绑定记录失败: ${error.message}`);
|
|
191
171
|
throw error;
|
|
192
172
|
}
|
|
193
173
|
}
|
|
194
|
-
/**
|
|
195
|
-
* 删除所有绑定记录
|
|
196
|
-
* @returns 删除的记录数
|
|
197
|
-
*/
|
|
198
174
|
async deleteAll() {
|
|
199
175
|
try {
|
|
200
176
|
this.logger.debug('数据库', '删除所有绑定记录');
|
|
201
|
-
const
|
|
202
|
-
this.logger.info('数据库', `成功删除所有绑定记录(删除${
|
|
203
|
-
return
|
|
177
|
+
const removed = await this.supabase.del(TABLE, 'qqId=not.is.null');
|
|
178
|
+
this.logger.info('数据库', `成功删除所有绑定记录(删除${removed}条)`, true);
|
|
179
|
+
return removed;
|
|
204
180
|
}
|
|
205
181
|
catch (error) {
|
|
206
182
|
this.logger.error('数据库', `删除所有绑定记录失败: ${error.message}`);
|
|
207
183
|
throw error;
|
|
208
184
|
}
|
|
209
185
|
}
|
|
210
|
-
/**
|
|
211
|
-
* 批量创建绑定记录
|
|
212
|
-
* @param records 绑定记录列表
|
|
213
|
-
*/
|
|
214
186
|
async batchCreate(records) {
|
|
215
187
|
try {
|
|
216
188
|
this.logger.debug('数据库', `批量创建${records.length}条绑定记录`);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
189
|
+
const serialized = records.map(r => serializeDates(r));
|
|
190
|
+
await this.supabase.postMany(TABLE, serialized);
|
|
220
191
|
this.logger.info('数据库', `成功批量创建${records.length}条绑定记录`, true);
|
|
221
192
|
}
|
|
222
193
|
catch (error) {
|
|
@@ -224,11 +195,6 @@ class MCIDBINDRepository {
|
|
|
224
195
|
throw error;
|
|
225
196
|
}
|
|
226
197
|
}
|
|
227
|
-
/**
|
|
228
|
-
* 为用户添加标签
|
|
229
|
-
* @param qqId QQ号
|
|
230
|
-
* @param tag 标签名称
|
|
231
|
-
*/
|
|
232
198
|
async addTag(qqId, tag) {
|
|
233
199
|
try {
|
|
234
200
|
const bind = await this.findByQQId(qqId);
|
|
@@ -247,11 +213,6 @@ class MCIDBINDRepository {
|
|
|
247
213
|
throw error;
|
|
248
214
|
}
|
|
249
215
|
}
|
|
250
|
-
/**
|
|
251
|
-
* 为用户移除标签
|
|
252
|
-
* @param qqId QQ号
|
|
253
|
-
* @param tag 标签名称
|
|
254
|
-
*/
|
|
255
216
|
async removeTag(qqId, tag) {
|
|
256
217
|
try {
|
|
257
218
|
const bind = await this.findByQQId(qqId);
|
|
@@ -271,11 +232,6 @@ class MCIDBINDRepository {
|
|
|
271
232
|
throw error;
|
|
272
233
|
}
|
|
273
234
|
}
|
|
274
|
-
/**
|
|
275
|
-
* 为用户添加白名单服务器
|
|
276
|
-
* @param qqId QQ号
|
|
277
|
-
* @param serverId 服务器ID
|
|
278
|
-
*/
|
|
279
235
|
async addWhitelist(qqId, serverId) {
|
|
280
236
|
try {
|
|
281
237
|
const bind = await this.findByQQId(qqId);
|
|
@@ -294,11 +250,6 @@ class MCIDBINDRepository {
|
|
|
294
250
|
throw error;
|
|
295
251
|
}
|
|
296
252
|
}
|
|
297
|
-
/**
|
|
298
|
-
* 为用户移除白名单服务器
|
|
299
|
-
* @param qqId QQ号
|
|
300
|
-
* @param serverId 服务器ID
|
|
301
|
-
*/
|
|
302
253
|
async removeWhitelist(qqId, serverId) {
|
|
303
254
|
try {
|
|
304
255
|
const bind = await this.findByQQId(qqId);
|
|
@@ -318,15 +269,11 @@ class MCIDBINDRepository {
|
|
|
318
269
|
throw error;
|
|
319
270
|
}
|
|
320
271
|
}
|
|
321
|
-
/**
|
|
322
|
-
* 获取所有管理员
|
|
323
|
-
* @returns 管理员列表
|
|
324
|
-
*/
|
|
325
272
|
async findAllAdmins() {
|
|
326
273
|
try {
|
|
327
274
|
this.logger.debug('数据库', '获取所有管理员');
|
|
328
|
-
const
|
|
329
|
-
return
|
|
275
|
+
const rows = await this.supabase.get(TABLE, 'isAdmin=eq.true');
|
|
276
|
+
return rows.map(deserialize);
|
|
330
277
|
}
|
|
331
278
|
catch (error) {
|
|
332
279
|
this.logger.error('数据库', `获取所有管理员失败: ${error.message}`);
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { LoggerService } from '../utils/logger';
|
|
2
|
-
import type { MojangProfile
|
|
2
|
+
import type { MojangProfile } from '../types';
|
|
3
3
|
/**
|
|
4
4
|
* API 服务层
|
|
5
|
-
* 统一管理外部 API 调用(Mojang API、
|
|
5
|
+
* 统一管理外部 API 调用(Mojang API、Bilibili 官方 API 等)
|
|
6
6
|
*/
|
|
7
7
|
export declare class ApiService {
|
|
8
8
|
private logger;
|
|
9
9
|
private config;
|
|
10
10
|
private cookieString;
|
|
11
11
|
constructor(logger: LoggerService, config: {
|
|
12
|
-
zminfoApiUrl: string;
|
|
13
12
|
SESSDATA?: string;
|
|
14
13
|
});
|
|
15
14
|
/**
|
|
@@ -49,12 +48,6 @@ export declare class ApiService {
|
|
|
49
48
|
name: string;
|
|
50
49
|
mid: number;
|
|
51
50
|
} | null>;
|
|
52
|
-
/**
|
|
53
|
-
* 验证 B 站 UID 是否存在
|
|
54
|
-
* @param buid B站UID
|
|
55
|
-
* @returns ZminfoUser 或 null
|
|
56
|
-
*/
|
|
57
|
-
validateBUID(buid: string): Promise<ZminfoUser | null>;
|
|
58
51
|
/**
|
|
59
52
|
* 获取 MC 头图 URL (Crafatar)
|
|
60
53
|
* @param uuid MC UUID
|
|
@@ -7,7 +7,7 @@ exports.ApiService = void 0;
|
|
|
7
7
|
const axios_1 = __importDefault(require("axios"));
|
|
8
8
|
/**
|
|
9
9
|
* API 服务层
|
|
10
|
-
* 统一管理外部 API 调用(Mojang API、
|
|
10
|
+
* 统一管理外部 API 调用(Mojang API、Bilibili 官方 API 等)
|
|
11
11
|
*/
|
|
12
12
|
class ApiService {
|
|
13
13
|
logger;
|
|
@@ -260,44 +260,6 @@ class ApiService {
|
|
|
260
260
|
return null;
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
-
// =========== ZMINFO API ===========
|
|
264
|
-
/**
|
|
265
|
-
* 验证 B 站 UID 是否存在
|
|
266
|
-
* @param buid B站UID
|
|
267
|
-
* @returns ZminfoUser 或 null
|
|
268
|
-
*/
|
|
269
|
-
async validateBUID(buid) {
|
|
270
|
-
try {
|
|
271
|
-
if (!buid || !/^\d+$/.test(buid)) {
|
|
272
|
-
this.logger.warn('B站账号验证', `无效的B站UID格式: ${buid}`);
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
this.logger.debug('B站账号验证', `验证B站UID: ${buid}`);
|
|
276
|
-
const response = await axios_1.default.get(`${this.config.zminfoApiUrl}/api/user/${buid}`, {
|
|
277
|
-
timeout: 10000,
|
|
278
|
-
headers: {
|
|
279
|
-
'User-Agent': 'Koishi-MCID-Bot/1.0'
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
if (response.data.success && response.data.data && response.data.data.user) {
|
|
283
|
-
const user = response.data.data.user;
|
|
284
|
-
this.logger.debug('B站账号验证', `B站UID ${buid} 验证成功: ${user.username}`);
|
|
285
|
-
return user;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
this.logger.warn('B站账号验证', `B站UID ${buid} 不存在或API返回失败: ${response.data.message}`);
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
if (error.response?.status === 404) {
|
|
294
|
-
this.logger.warn('B站账号验证', `B站UID ${buid} 不存在`);
|
|
295
|
-
return null;
|
|
296
|
-
}
|
|
297
|
-
this.logger.error('B站账号验证', `验证B站UID ${buid} 时出错: ${error.message}`);
|
|
298
|
-
throw new Error(`无法验证B站UID: ${error.message}`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
263
|
// =========== 工具方法 ===========
|
|
302
264
|
/**
|
|
303
265
|
* 获取 MC 头图 URL (Crafatar)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Context } from 'koishi';
|
|
2
2
|
import { LoggerService } from '../utils/logger';
|
|
3
3
|
import { MCIDBINDRepository } from '../repositories/mcidbind.repository';
|
|
4
|
-
import type { MCIDBIND
|
|
4
|
+
import type { MCIDBIND } from '../types';
|
|
5
5
|
/**
|
|
6
6
|
* 数据库服务层
|
|
7
7
|
* 统一管理数据库操作,包括 MC 绑定和 BUID 绑定的 CRUD
|
|
@@ -43,12 +43,19 @@ export declare class DatabaseService {
|
|
|
43
43
|
checkBuidExists(buid: string, currentUserId?: string): Promise<boolean>;
|
|
44
44
|
/**
|
|
45
45
|
* 创建或更新 B 站账号绑定
|
|
46
|
+
* 只写入 buidUid 和 buidUsername,详细字段由数据库触发器填充
|
|
46
47
|
*/
|
|
47
|
-
createOrUpdateBuidBind(userId: string, buidUser:
|
|
48
|
+
createOrUpdateBuidBind(userId: string, buidUser: {
|
|
49
|
+
uid: string;
|
|
50
|
+
username: string;
|
|
51
|
+
}): Promise<boolean>;
|
|
48
52
|
/**
|
|
49
|
-
* 仅更新 B
|
|
53
|
+
* 仅更新 B 站用户名,不更新绑定时间(用于昵称同步等场景)
|
|
54
|
+
* 详细字段(guardLevel, medal等)由数据库触发器自动维护
|
|
50
55
|
*/
|
|
51
|
-
updateBuidInfoOnly(userId: string, buidUser:
|
|
56
|
+
updateBuidInfoOnly(userId: string, buidUser: {
|
|
57
|
+
username: string;
|
|
58
|
+
}): Promise<boolean>;
|
|
52
59
|
/**
|
|
53
60
|
* 解绑 B 站账号(只清空 B 站字段,保留 MC 绑定)
|
|
54
61
|
*/
|
|
@@ -254,6 +254,7 @@ class DatabaseService {
|
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
256
|
* 创建或更新 B 站账号绑定
|
|
257
|
+
* 只写入 buidUid 和 buidUsername,详细字段由数据库触发器填充
|
|
257
258
|
*/
|
|
258
259
|
async createOrUpdateBuidBind(userId, buidUser) {
|
|
259
260
|
try {
|
|
@@ -271,18 +272,8 @@ class DatabaseService {
|
|
|
271
272
|
// 查询是否已存在绑定记录
|
|
272
273
|
let bind = await this.getMcBindByQQId(normalizedQQId);
|
|
273
274
|
const updateData = {
|
|
274
|
-
buidUid: buidUser.uid.toString(),
|
|
275
|
+
buidUid: buidUser.uid.toString(),
|
|
275
276
|
buidUsername: buidUser.username,
|
|
276
|
-
guardLevel: buidUser.guard_level || 0,
|
|
277
|
-
guardLevelText: buidUser.guard_level_text || '',
|
|
278
|
-
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
279
|
-
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
280
|
-
medalName: buidUser.medal?.name || '',
|
|
281
|
-
medalLevel: buidUser.medal?.level || 0,
|
|
282
|
-
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
283
|
-
lastActiveTime: buidUser.last_active_time
|
|
284
|
-
? new Date(buidUser.last_active_time)
|
|
285
|
-
: new Date(),
|
|
286
277
|
lastModified: new Date()
|
|
287
278
|
};
|
|
288
279
|
if (bind) {
|
|
@@ -315,7 +306,8 @@ class DatabaseService {
|
|
|
315
306
|
}
|
|
316
307
|
}
|
|
317
308
|
/**
|
|
318
|
-
* 仅更新 B
|
|
309
|
+
* 仅更新 B 站用户名,不更新绑定时间(用于昵称同步等场景)
|
|
310
|
+
* 详细字段(guardLevel, medal等)由数据库触发器自动维护
|
|
319
311
|
*/
|
|
320
312
|
async updateBuidInfoOnly(userId, buidUser) {
|
|
321
313
|
try {
|
|
@@ -330,19 +322,9 @@ class DatabaseService {
|
|
|
330
322
|
this.logger.warn('B站账号信息更新', `QQ(${normalizedQQId})没有绑定记录,无法更新B站信息`);
|
|
331
323
|
return false;
|
|
332
324
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
guardLevel: buidUser.guard_level || 0,
|
|
337
|
-
guardLevelText: buidUser.guard_level_text || '',
|
|
338
|
-
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
339
|
-
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
340
|
-
medalName: buidUser.medal?.name || '',
|
|
341
|
-
medalLevel: buidUser.medal?.level || 0,
|
|
342
|
-
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
343
|
-
lastActiveTime: buidUser.last_active_time ? new Date(buidUser.last_active_time) : new Date()
|
|
344
|
-
};
|
|
345
|
-
await this.mcidbindRepo.update(normalizedQQId, updateData);
|
|
325
|
+
await this.mcidbindRepo.update(normalizedQQId, {
|
|
326
|
+
buidUsername: buidUser.username
|
|
327
|
+
});
|
|
346
328
|
this.logger.info('B站账号信息更新', `刷新信息: QQ=${normalizedQQId}, B站UID=${bind.buidUid}, 用户名=${buidUser.username}`, true);
|
|
347
329
|
return true;
|
|
348
330
|
}
|
|
@@ -386,9 +368,7 @@ class DatabaseService {
|
|
|
386
368
|
buidUid: null,
|
|
387
369
|
buidUsername: null,
|
|
388
370
|
guardLevel: 0,
|
|
389
|
-
guardLevelText: '',
|
|
390
371
|
maxGuardLevel: 0,
|
|
391
|
-
maxGuardLevelText: '',
|
|
392
372
|
medalName: '',
|
|
393
373
|
medalLevel: 0,
|
|
394
374
|
wealthMedalLevel: 0,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Session } from 'koishi';
|
|
2
2
|
import { LoggerService } from '../utils/logger';
|
|
3
|
-
import type { ZminfoUser } from '../types';
|
|
4
3
|
/**
|
|
5
4
|
* 群昵称管理服务
|
|
6
5
|
* 负责自动设置和验证群昵称
|
|
@@ -9,26 +8,27 @@ export declare class NicknameService {
|
|
|
9
8
|
private logger;
|
|
10
9
|
private config;
|
|
11
10
|
private normalizeQQId;
|
|
12
|
-
private validateBUID;
|
|
13
11
|
private getBilibiliOfficialUserInfo;
|
|
14
12
|
private updateBuidInfoOnly;
|
|
15
13
|
constructor(logger: LoggerService, config: {
|
|
16
14
|
autoNicknameGroupId: string;
|
|
17
|
-
}, normalizeQQId: (userId: string) => string,
|
|
15
|
+
}, normalizeQQId: (userId: string) => string, getBilibiliOfficialUserInfo: (uid: string) => Promise<{
|
|
18
16
|
name: string;
|
|
19
17
|
mid: number;
|
|
20
|
-
} | null>, updateBuidInfoOnly: (userId: string, buidUser:
|
|
18
|
+
} | null>, updateBuidInfoOnly: (userId: string, buidUser: {
|
|
19
|
+
username: string;
|
|
20
|
+
}) => Promise<boolean>);
|
|
21
21
|
/**
|
|
22
22
|
* 检查群昵称格式是否正确
|
|
23
23
|
*/
|
|
24
24
|
checkNicknameFormat(nickname: string, buidUsername: string, mcUsername: string | null): boolean;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
* 优先级:官方API >
|
|
26
|
+
* 获取最准确的B站用户名
|
|
27
|
+
* 优先级:官方API > 数据库
|
|
28
28
|
*/
|
|
29
29
|
private getLatestBuidUsername;
|
|
30
30
|
/**
|
|
31
|
-
* 同步数据库中的B
|
|
31
|
+
* 同步数据库中的B站用户名
|
|
32
32
|
*/
|
|
33
33
|
private syncDatabaseIfNeeded;
|
|
34
34
|
/**
|