koishi-plugin-bind-bot 2.0.5 → 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.
- package/lib/handlers/base.handler.d.ts +12 -18
- package/lib/handlers/binding.handler.js +3 -3
- package/lib/handlers/buid.handler.js +4 -4
- package/lib/handlers/index.d.ts +1 -0
- package/lib/handlers/index.js +1 -0
- package/lib/handlers/lottery.handler.d.ts +42 -0
- package/lib/handlers/lottery.handler.js +225 -0
- package/lib/handlers/mcid.handler.js +56 -56
- package/lib/handlers/tag.handler.js +8 -8
- package/lib/handlers/whitelist.handler.js +14 -14
- package/lib/index.js +73 -1159
- package/lib/services/api.service.d.ts +70 -0
- package/lib/services/api.service.js +344 -0
- package/lib/services/database.service.d.ts +64 -0
- package/lib/services/database.service.js +451 -0
- package/lib/services/index.d.ts +6 -0
- package/lib/services/index.js +22 -0
- package/lib/services/nickname.service.d.ts +42 -0
- package/lib/services/nickname.service.js +225 -0
- package/lib/services/service-container.d.ts +17 -0
- package/lib/services/service-container.js +24 -0
- package/lib/types/config.d.ts +2 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +2 -0
- package/lib/types/update-data.d.ts +68 -0
- package/lib/types/update-data.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { LoggerService } from '../utils/logger';
|
|
2
|
+
import type { MojangProfile, ZminfoUser } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* API 服务层
|
|
5
|
+
* 统一管理外部 API 调用(Mojang API、ZMINFO API 等)
|
|
6
|
+
*/
|
|
7
|
+
export declare class ApiService {
|
|
8
|
+
private logger;
|
|
9
|
+
private config;
|
|
10
|
+
constructor(logger: LoggerService, config: {
|
|
11
|
+
zminfoApiUrl: string;
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* 验证 Minecraft 用户名是否存在
|
|
15
|
+
* @param username MC 用户名
|
|
16
|
+
* @returns Mojang Profile 或 null
|
|
17
|
+
*/
|
|
18
|
+
validateUsername(username: string): Promise<MojangProfile | null>;
|
|
19
|
+
/**
|
|
20
|
+
* 使用备用 API 验证用户名
|
|
21
|
+
* @param username MC 用户名
|
|
22
|
+
* @returns Mojang Profile 或 null
|
|
23
|
+
*/
|
|
24
|
+
private tryBackupAPI;
|
|
25
|
+
/**
|
|
26
|
+
* 通过 UUID 查询用户名
|
|
27
|
+
* @param uuid MC UUID
|
|
28
|
+
* @returns 用户名或 null
|
|
29
|
+
*/
|
|
30
|
+
getUsernameByUuid(uuid: string): Promise<string | null>;
|
|
31
|
+
/**
|
|
32
|
+
* 使用备用 API 通过 UUID 查询用户名
|
|
33
|
+
* @param uuid MC UUID
|
|
34
|
+
* @returns 用户名或 null
|
|
35
|
+
*/
|
|
36
|
+
private getUsernameByUuidBackupAPI;
|
|
37
|
+
/**
|
|
38
|
+
* 通过B站官方API获取用户基本信息(最权威的数据源)
|
|
39
|
+
* @param uid B站UID
|
|
40
|
+
* @returns 用户基本信息或null
|
|
41
|
+
*/
|
|
42
|
+
getBilibiliOfficialUserInfo(uid: string): Promise<{
|
|
43
|
+
name: string;
|
|
44
|
+
mid: number;
|
|
45
|
+
} | null>;
|
|
46
|
+
/**
|
|
47
|
+
* 验证 B 站 UID 是否存在
|
|
48
|
+
* @param buid B站UID
|
|
49
|
+
* @returns ZminfoUser 或 null
|
|
50
|
+
*/
|
|
51
|
+
validateBUID(buid: string): Promise<ZminfoUser | null>;
|
|
52
|
+
/**
|
|
53
|
+
* 获取 MC 头图 URL (Crafatar)
|
|
54
|
+
* @param uuid MC UUID
|
|
55
|
+
* @returns 头图 URL 或 null
|
|
56
|
+
*/
|
|
57
|
+
getCrafatarUrl(uuid: string): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* 使用 Starlight SkinAPI 获取皮肤渲染
|
|
60
|
+
* @param username MC 用户名
|
|
61
|
+
* @returns 皮肤渲染 URL 或 null
|
|
62
|
+
*/
|
|
63
|
+
getStarlightSkinUrl(username: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* 格式化 UUID (添加连字符,使其符合标准格式)
|
|
66
|
+
* @param uuid 原始 UUID
|
|
67
|
+
* @returns 格式化后的 UUID
|
|
68
|
+
*/
|
|
69
|
+
formatUuid(uuid: string): string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ApiService = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
/**
|
|
9
|
+
* API 服务层
|
|
10
|
+
* 统一管理外部 API 调用(Mojang API、ZMINFO API 等)
|
|
11
|
+
*/
|
|
12
|
+
class ApiService {
|
|
13
|
+
logger;
|
|
14
|
+
config;
|
|
15
|
+
constructor(logger, config) {
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
// =========== Mojang API ===========
|
|
20
|
+
/**
|
|
21
|
+
* 验证 Minecraft 用户名是否存在
|
|
22
|
+
* @param username MC 用户名
|
|
23
|
+
* @returns Mojang Profile 或 null
|
|
24
|
+
*/
|
|
25
|
+
async validateUsername(username) {
|
|
26
|
+
try {
|
|
27
|
+
this.logger.debug('Mojang API', `开始验证用户名: ${username}`);
|
|
28
|
+
const response = await axios_1.default.get(`https://api.mojang.com/users/profiles/minecraft/${username}`, {
|
|
29
|
+
timeout: 10000, // 添加10秒超时
|
|
30
|
+
headers: {
|
|
31
|
+
'User-Agent': 'KoishiMCVerifier/1.0', // 添加User-Agent头
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (response.status === 200 && response.data) {
|
|
35
|
+
this.logger.debug('Mojang API', `用户名"${username}"验证成功,UUID: ${response.data.id},标准名称: ${response.data.name}`);
|
|
36
|
+
return {
|
|
37
|
+
id: response.data.id,
|
|
38
|
+
name: response.data.name // 使用Mojang返回的正确大小写
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (axios_1.default.isAxiosError(error) && error.response?.status === 404) {
|
|
45
|
+
this.logger.warn('Mojang API', `用户名"${username}"不存在`);
|
|
46
|
+
}
|
|
47
|
+
else if (axios_1.default.isAxiosError(error) && error.code === 'ECONNABORTED') {
|
|
48
|
+
this.logger.error('Mojang API', `验证用户名"${username}"时请求超时: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// 记录更详细的错误信息
|
|
52
|
+
const errorMessage = axios_1.default.isAxiosError(error)
|
|
53
|
+
? `${error.message},响应状态: ${error.response?.status || '未知'}\n响应数据: ${JSON.stringify(error.response?.data || '无数据')}`
|
|
54
|
+
: error.message || '未知错误';
|
|
55
|
+
this.logger.error('Mojang API', `验证用户名"${username}"时发生错误: ${errorMessage}`);
|
|
56
|
+
// 如果是网络相关错误,尝试使用备用API检查
|
|
57
|
+
if (axios_1.default.isAxiosError(error) && (error.code === 'ENOTFOUND' ||
|
|
58
|
+
error.code === 'ETIMEDOUT' ||
|
|
59
|
+
error.code === 'ECONNRESET' ||
|
|
60
|
+
error.code === 'ECONNREFUSED' ||
|
|
61
|
+
error.code === 'ECONNABORTED' ||
|
|
62
|
+
error.response?.status === 429 || // 添加429 (Too Many Requests)
|
|
63
|
+
error.response?.status === 403)) { // 添加403 (Forbidden)
|
|
64
|
+
// 尝试使用playerdb.co作为备用API
|
|
65
|
+
this.logger.info('Mojang API', `遇到错误(${error.code || error.response?.status}),将尝试使用备用API`);
|
|
66
|
+
return this.tryBackupAPI(username);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 使用备用 API 验证用户名
|
|
74
|
+
* @param username MC 用户名
|
|
75
|
+
* @returns Mojang Profile 或 null
|
|
76
|
+
*/
|
|
77
|
+
async tryBackupAPI(username) {
|
|
78
|
+
this.logger.info('备用API', `尝试使用备用API验证用户名"${username}"`);
|
|
79
|
+
try {
|
|
80
|
+
// 使用playerdb.co作为备用API
|
|
81
|
+
const backupResponse = await axios_1.default.get(`https://playerdb.co/api/player/minecraft/${username}`, {
|
|
82
|
+
timeout: 10000,
|
|
83
|
+
headers: {
|
|
84
|
+
'User-Agent': 'KoishiMCVerifier/1.0'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (backupResponse.status === 200 && backupResponse.data?.code === "player.found") {
|
|
88
|
+
const playerData = backupResponse.data.data.player;
|
|
89
|
+
const rawId = playerData.raw_id || playerData.id.replace(/-/g, ''); // 确保使用不带连字符的UUID
|
|
90
|
+
this.logger.info('备用API', `用户名"${username}"验证成功,UUID: ${rawId},标准名称: ${playerData.username}`);
|
|
91
|
+
return {
|
|
92
|
+
id: rawId, // 确保使用不带连字符的UUID
|
|
93
|
+
name: playerData.username
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
this.logger.warn('备用API', `用户名"${username}"验证失败: ${JSON.stringify(backupResponse.data)}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
catch (backupError) {
|
|
100
|
+
const errorMsg = axios_1.default.isAxiosError(backupError)
|
|
101
|
+
? `${backupError.message}, 状态码: ${backupError.response?.status || '未知'}`
|
|
102
|
+
: backupError.message || '未知错误';
|
|
103
|
+
this.logger.error('备用API', `验证用户名"${username}"失败: ${errorMsg}`);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 通过 UUID 查询用户名
|
|
109
|
+
* @param uuid MC UUID
|
|
110
|
+
* @returns 用户名或 null
|
|
111
|
+
*/
|
|
112
|
+
async getUsernameByUuid(uuid) {
|
|
113
|
+
try {
|
|
114
|
+
// 确保UUID格式正确(去除连字符)
|
|
115
|
+
const cleanUuid = uuid.replace(/-/g, '');
|
|
116
|
+
this.logger.debug('Mojang API', `通过UUID "${cleanUuid}" 查询用户名`);
|
|
117
|
+
const response = await axios_1.default.get(`https://api.mojang.com/user/profile/${cleanUuid}`, {
|
|
118
|
+
timeout: 10000,
|
|
119
|
+
headers: {
|
|
120
|
+
'User-Agent': 'KoishiMCVerifier/1.0',
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
if (response.status === 200 && response.data) {
|
|
124
|
+
// 从返回数据中提取用户名
|
|
125
|
+
const username = response.data.name;
|
|
126
|
+
this.logger.debug('Mojang API', `UUID "${cleanUuid}" 当前用户名: ${username}`);
|
|
127
|
+
return username;
|
|
128
|
+
}
|
|
129
|
+
this.logger.warn('Mojang API', `UUID "${cleanUuid}" 查询不到用户名`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
// 如果是网络相关错误,尝试使用备用API
|
|
134
|
+
if (axios_1.default.isAxiosError(error) && (error.code === 'ENOTFOUND' ||
|
|
135
|
+
error.code === 'ETIMEDOUT' ||
|
|
136
|
+
error.code === 'ECONNRESET' ||
|
|
137
|
+
error.code === 'ECONNREFUSED' ||
|
|
138
|
+
error.code === 'ECONNABORTED' ||
|
|
139
|
+
error.response?.status === 429 || // 添加429 (Too Many Requests)
|
|
140
|
+
error.response?.status === 403)) { // 添加403 (Forbidden)
|
|
141
|
+
this.logger.info('Mojang API', `通过UUID查询用户名时遇到错误(${error.code || error.response?.status}),将尝试使用备用API`);
|
|
142
|
+
return this.getUsernameByUuidBackupAPI(uuid);
|
|
143
|
+
}
|
|
144
|
+
const errorMessage = axios_1.default.isAxiosError(error)
|
|
145
|
+
? `${error.message},响应状态: ${error.response?.status || '未知'}\n响应数据: ${JSON.stringify(error.response?.data || '无数据')}`
|
|
146
|
+
: error.message || '未知错误';
|
|
147
|
+
this.logger.error('Mojang API', `通过UUID "${uuid}" 查询用户名失败: ${errorMessage}`);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 使用备用 API 通过 UUID 查询用户名
|
|
153
|
+
* @param uuid MC UUID
|
|
154
|
+
* @returns 用户名或 null
|
|
155
|
+
*/
|
|
156
|
+
async getUsernameByUuidBackupAPI(uuid) {
|
|
157
|
+
try {
|
|
158
|
+
// 确保UUID格式正确,备用API支持带连字符的UUID
|
|
159
|
+
const formattedUuid = uuid.includes('-') ? uuid : this.formatUuid(uuid);
|
|
160
|
+
this.logger.debug('备用API', `通过UUID "${formattedUuid}" 查询用户名`);
|
|
161
|
+
const response = await axios_1.default.get(`https://playerdb.co/api/player/minecraft/${formattedUuid}`, {
|
|
162
|
+
timeout: 10000,
|
|
163
|
+
headers: {
|
|
164
|
+
'User-Agent': 'KoishiMCVerifier/1.0',
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
if (response.status === 200 && response.data?.code === "player.found") {
|
|
168
|
+
const playerData = response.data.data.player;
|
|
169
|
+
this.logger.debug('备用API', `UUID "${formattedUuid}" 当前用户名: ${playerData.username}`);
|
|
170
|
+
return playerData.username;
|
|
171
|
+
}
|
|
172
|
+
this.logger.warn('备用API', `UUID "${formattedUuid}" 查询不到用户名: ${JSON.stringify(response.data)}`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const errorMessage = axios_1.default.isAxiosError(error)
|
|
177
|
+
? `${error.message},响应状态: ${error.response?.status || '未知'}\n响应数据: ${JSON.stringify(error.response?.data || '无数据')}`
|
|
178
|
+
: error.message || '未知错误';
|
|
179
|
+
this.logger.error('备用API', `通过UUID "${uuid}" 查询用户名失败: ${errorMessage}`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// =========== B站官方API ===========
|
|
184
|
+
/**
|
|
185
|
+
* 通过B站官方API获取用户基本信息(最权威的数据源)
|
|
186
|
+
* @param uid B站UID
|
|
187
|
+
* @returns 用户基本信息或null
|
|
188
|
+
*/
|
|
189
|
+
async getBilibiliOfficialUserInfo(uid) {
|
|
190
|
+
try {
|
|
191
|
+
if (!uid || !/^\d+$/.test(uid)) {
|
|
192
|
+
this.logger.warn('B站官方API', `无效的B站UID格式: ${uid}`);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
this.logger.debug('B站官方API', `开始查询UID ${uid} 的官方信息`);
|
|
196
|
+
const response = await axios_1.default.get(`https://api.bilibili.com/x/space/acc/info`, {
|
|
197
|
+
params: { mid: uid },
|
|
198
|
+
timeout: 10000,
|
|
199
|
+
headers: {
|
|
200
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
if (response.data.code === 0 && response.data.data) {
|
|
204
|
+
const userData = response.data.data;
|
|
205
|
+
this.logger.debug('B站官方API', `UID ${uid} 的官方用户名: "${userData.name}"`);
|
|
206
|
+
return {
|
|
207
|
+
name: userData.name,
|
|
208
|
+
mid: userData.mid
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
this.logger.warn('B站官方API', `UID ${uid} 查询失败: ${response.data.message || '未知错误'}`);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
218
|
+
if (error.response?.status === 404) {
|
|
219
|
+
this.logger.warn('B站官方API', `UID ${uid} 不存在`);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
this.logger.error('B站官方API', `查询UID ${uid} 时出错: ${error.message}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.logger.error('B站官方API', `查询UID ${uid} 时出错: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// =========== ZMINFO API ===========
|
|
231
|
+
/**
|
|
232
|
+
* 验证 B 站 UID 是否存在
|
|
233
|
+
* @param buid B站UID
|
|
234
|
+
* @returns ZminfoUser 或 null
|
|
235
|
+
*/
|
|
236
|
+
async validateBUID(buid) {
|
|
237
|
+
try {
|
|
238
|
+
if (!buid || !/^\d+$/.test(buid)) {
|
|
239
|
+
this.logger.warn('B站账号验证', `无效的B站UID格式: ${buid}`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
this.logger.debug('B站账号验证', `验证B站UID: ${buid}`);
|
|
243
|
+
const response = await axios_1.default.get(`${this.config.zminfoApiUrl}/api/user/${buid}`, {
|
|
244
|
+
timeout: 10000,
|
|
245
|
+
headers: {
|
|
246
|
+
'User-Agent': 'Koishi-MCID-Bot/1.0'
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
if (response.data.success && response.data.data && response.data.data.user) {
|
|
250
|
+
const user = response.data.data.user;
|
|
251
|
+
this.logger.debug('B站账号验证', `B站UID ${buid} 验证成功: ${user.username}`);
|
|
252
|
+
return user;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
this.logger.warn('B站账号验证', `B站UID ${buid} 不存在或API返回失败: ${response.data.message}`);
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (error.response?.status === 404) {
|
|
261
|
+
this.logger.warn('B站账号验证', `B站UID ${buid} 不存在`);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
this.logger.error('B站账号验证', `验证B站UID ${buid} 时出错: ${error.message}`);
|
|
265
|
+
throw new Error(`无法验证B站UID: ${error.message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// =========== 工具方法 ===========
|
|
269
|
+
/**
|
|
270
|
+
* 获取 MC 头图 URL (Crafatar)
|
|
271
|
+
* @param uuid MC UUID
|
|
272
|
+
* @returns 头图 URL 或 null
|
|
273
|
+
*/
|
|
274
|
+
getCrafatarUrl(uuid) {
|
|
275
|
+
if (!uuid)
|
|
276
|
+
return null;
|
|
277
|
+
// 检查UUID格式 (不带连字符应为32位,带连字符应为36位)
|
|
278
|
+
const uuidRegex = /^[0-9a-f]{32}$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
279
|
+
if (!uuidRegex.test(uuid)) {
|
|
280
|
+
this.logger.warn('MC头图', `UUID "${uuid}" 格式无效,无法生成头图URL`);
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
// 移除任何连字符,Crafatar接受不带连字符的UUID
|
|
284
|
+
const cleanUuid = uuid.replace(/-/g, '');
|
|
285
|
+
// 直接生成URL
|
|
286
|
+
const url = `https://crafatar.com/avatars/${cleanUuid}`;
|
|
287
|
+
this.logger.debug('MC头图', `为UUID "${cleanUuid}" 生成头图URL`);
|
|
288
|
+
return url;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 使用 Starlight SkinAPI 获取皮肤渲染
|
|
292
|
+
* @param username MC 用户名
|
|
293
|
+
* @returns 皮肤渲染 URL 或 null
|
|
294
|
+
*/
|
|
295
|
+
getStarlightSkinUrl(username) {
|
|
296
|
+
if (!username)
|
|
297
|
+
return null;
|
|
298
|
+
// 可用的动作列表 (共16种)
|
|
299
|
+
const poses = [
|
|
300
|
+
'default', // 默认站立
|
|
301
|
+
'marching', // 行军
|
|
302
|
+
'walking', // 行走
|
|
303
|
+
'crouching', // 下蹲
|
|
304
|
+
'crossed', // 交叉手臂
|
|
305
|
+
'crisscross', // 交叉腿
|
|
306
|
+
'cheering', // 欢呼
|
|
307
|
+
'relaxing', // 放松
|
|
308
|
+
'trudging', // 艰难行走
|
|
309
|
+
'cowering', // 退缩
|
|
310
|
+
'pointing', // 指向
|
|
311
|
+
'lunging', // 前冲
|
|
312
|
+
'dungeons', // 地下城风格
|
|
313
|
+
'facepalm', // 捂脸
|
|
314
|
+
'mojavatar', // Mojave姿态
|
|
315
|
+
'head', // 头部特写
|
|
316
|
+
];
|
|
317
|
+
// 随机选择一个动作
|
|
318
|
+
const randomPose = poses[Math.floor(Math.random() * poses.length)];
|
|
319
|
+
// 视图类型(full为全身图)
|
|
320
|
+
const viewType = 'full';
|
|
321
|
+
// 生成URL
|
|
322
|
+
const url = `https://starlightskins.lunareclipse.studio/render/${randomPose}/${username}/${viewType}`;
|
|
323
|
+
this.logger.debug('Starlight皮肤', `为用户名"${username}"生成动作"${randomPose}"的渲染URL`);
|
|
324
|
+
return url;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* 格式化 UUID (添加连字符,使其符合标准格式)
|
|
328
|
+
* @param uuid 原始 UUID
|
|
329
|
+
* @returns 格式化后的 UUID
|
|
330
|
+
*/
|
|
331
|
+
formatUuid(uuid) {
|
|
332
|
+
if (!uuid)
|
|
333
|
+
return '未知';
|
|
334
|
+
if (uuid.includes('-'))
|
|
335
|
+
return uuid; // 已经是带连字符的格式
|
|
336
|
+
// 确保UUID长度正确
|
|
337
|
+
if (uuid.length !== 32) {
|
|
338
|
+
this.logger.warn('UUID', `UUID "${uuid}" 长度异常,无法格式化`);
|
|
339
|
+
return uuid;
|
|
340
|
+
}
|
|
341
|
+
return `${uuid.substring(0, 8)}-${uuid.substring(8, 12)}-${uuid.substring(12, 16)}-${uuid.substring(16, 20)}-${uuid.substring(20)}`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
exports.ApiService = ApiService;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { LoggerService } from '../utils/logger';
|
|
3
|
+
import { MCIDBINDRepository } from '../repositories/mcidbind.repository';
|
|
4
|
+
import type { MCIDBIND, ZminfoUser } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* 数据库服务层
|
|
7
|
+
* 统一管理数据库操作,包括 MC 绑定和 BUID 绑定的 CRUD
|
|
8
|
+
*/
|
|
9
|
+
export declare class DatabaseService {
|
|
10
|
+
private ctx;
|
|
11
|
+
private logger;
|
|
12
|
+
private mcidbindRepo;
|
|
13
|
+
private normalizeQQId;
|
|
14
|
+
private getUsernameByUuid;
|
|
15
|
+
constructor(ctx: Context, logger: LoggerService, mcidbindRepo: MCIDBINDRepository, normalizeQQId: (userId: string) => string, getUsernameByUuid: (uuid: string) => Promise<string | null>);
|
|
16
|
+
/**
|
|
17
|
+
* 根据 QQ 号查询 MC 绑定信息
|
|
18
|
+
*/
|
|
19
|
+
getMcBindByQQId(qqId: string): Promise<MCIDBIND | null>;
|
|
20
|
+
/**
|
|
21
|
+
* 根据 MC 用户名查询绑定信息
|
|
22
|
+
*/
|
|
23
|
+
getMcBindByUsername(mcUsername: string): Promise<MCIDBIND | null>;
|
|
24
|
+
/**
|
|
25
|
+
* 创建或更新 MC 绑定
|
|
26
|
+
*/
|
|
27
|
+
createOrUpdateMcBind(userId: string, mcUsername: string, mcUuid: string, isAdmin?: boolean): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* 删除 MC 绑定(同时解绑 MC 和 B 站账号)
|
|
30
|
+
*/
|
|
31
|
+
deleteMcBind(userId: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* 检查 MC 用户名是否已被其他 QQ 号绑定(支持不区分大小写和 UUID 检查)
|
|
34
|
+
*/
|
|
35
|
+
checkUsernameExists(username: string, currentUserId?: string, uuid?: string): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* 根据 B 站 UID 查询绑定信息
|
|
38
|
+
*/
|
|
39
|
+
getBuidBindByBuid(buid: string): Promise<MCIDBIND | null>;
|
|
40
|
+
/**
|
|
41
|
+
* 检查 B 站 UID 是否已被绑定
|
|
42
|
+
*/
|
|
43
|
+
checkBuidExists(buid: string, currentUserId?: string): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* 创建或更新 B 站账号绑定
|
|
46
|
+
*/
|
|
47
|
+
createOrUpdateBuidBind(userId: string, buidUser: ZminfoUser): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* 仅更新 B 站信息,不更新绑定时间(用于查询时刷新数据)
|
|
50
|
+
*/
|
|
51
|
+
updateBuidInfoOnly(userId: string, buidUser: ZminfoUser): Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* 检查并更新用户名(如果与当前数据库中的不同)
|
|
54
|
+
*/
|
|
55
|
+
checkAndUpdateUsername(bind: MCIDBIND): Promise<MCIDBIND>;
|
|
56
|
+
/**
|
|
57
|
+
* 智能缓存版本的改名检测函数
|
|
58
|
+
* 特性:
|
|
59
|
+
* - 24小时冷却期(失败>=3次时延长到72小时)
|
|
60
|
+
* - 失败计数追踪
|
|
61
|
+
* - 成功时重置失败计数
|
|
62
|
+
*/
|
|
63
|
+
checkAndUpdateUsernameWithCache(bind: MCIDBIND): Promise<MCIDBIND>;
|
|
64
|
+
}
|