koishi-plugin-noah 2.2.2 → 2.3.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.
Files changed (32) hide show
  1. package/lib/drawer/sdvx/index.d.ts +1 -1
  2. package/lib/games/sdvx/adapters/asphyxia.d.ts +18 -0
  3. package/lib/games/sdvx/adapters/index.d.ts +6 -0
  4. package/lib/games/sdvx/adapters/mao.d.ts +19 -0
  5. package/lib/games/sdvx/adapters/official.d.ts +14 -0
  6. package/lib/games/sdvx/command.d.ts +1 -1
  7. package/lib/games/sdvx/commands/calculate.d.ts +1 -1
  8. package/lib/games/sdvx/commands/chart.d.ts +1 -1
  9. package/lib/games/sdvx/commands/info.d.ts +1 -1
  10. package/lib/games/sdvx/commands/radar.d.ts +1 -1
  11. package/lib/games/sdvx/commands/recent.d.ts +1 -1
  12. package/lib/games/sdvx/commands/sync.d.ts +1 -1
  13. package/lib/games/sdvx/commands/vf.d.ts +1 -1
  14. package/lib/games/sdvx/config.d.ts +1 -2
  15. package/lib/games/sdvx/services/music-service.d.ts +1 -1
  16. package/lib/games/sdvx/types/config.d.ts +10 -0
  17. package/lib/games/sdvx/types/index.d.ts +16 -0
  18. package/lib/index.cjs +1294 -1360
  19. package/lib/servers/Asphyxia/index.d.ts +0 -4
  20. package/lib/servers/Asphyxia/utils/xml.d.ts +2 -0
  21. package/lib/servers/Mao/index.d.ts +0 -4
  22. package/lib/servers/Official/index.d.ts +0 -4
  23. package/lib/servers/index.d.ts +4 -17
  24. package/lib/types/config.d.ts +2 -12
  25. package/lib/types/index.d.ts +0 -67
  26. package/package.json +1 -1
  27. package/lib/servers/Asphyxia/services/sdvx-service.d.ts +0 -26
  28. package/lib/servers/Mao/services/sdvx-service.d.ts +0 -39
  29. package/lib/servers/Official/services/sdvx-service.d.ts +0 -25
  30. package/lib/servers/utils/grade.d.ts +0 -8
  31. package/lib/servers/utils/xml.d.ts +0 -13
  32. /package/lib/{servers → games/sdvx}/utils/difficulty.d.ts +0 -0
package/lib/index.cjs CHANGED
@@ -4140,333 +4140,308 @@ __export(sdvx_exports, {
4140
4140
  });
4141
4141
  var import_koishi18 = require("koishi");
4142
4142
 
4143
- // src/games/sdvx/command.ts
4144
- var command_exports2 = {};
4145
- __export(command_exports2, {
4146
- apply: () => apply10,
4147
- name: () => name10
4143
+ // src/servers/index.ts
4144
+ var servers_exports = {};
4145
+ __export(servers_exports, {
4146
+ ServerManager: () => ServerManager,
4147
+ apply: () => apply9,
4148
+ inject: () => inject2,
4149
+ name: () => name9
4148
4150
  });
4149
4151
 
4150
- // src/games/sdvx/commands/calculate.ts
4152
+ // src/servers/Asphyxia/index.ts
4151
4153
  var import_koishi11 = require("koishi");
4152
-
4153
- // src/games/sdvx/utils/param-parser.ts
4154
- function parseNumberWithSuffix(str) {
4155
- const num = parseInt(str, 10);
4156
- if (str.endsWith("w")) {
4157
- return num * 1e4;
4158
- } else if (str.endsWith("k")) {
4159
- return num * 1e3;
4160
- }
4161
- return num;
4162
- }
4163
- __name(parseNumberWithSuffix, "parseNumberWithSuffix");
4164
- function isValidLevel(level) {
4165
- if (level < 1 || level > 20.9) {
4166
- return false;
4154
+ var Asphyxia = class {
4155
+ static {
4156
+ __name(this, "Asphyxia");
4167
4157
  }
4168
- const integerPart = Math.floor(level);
4169
- const decimalPart = level - integerPart;
4170
- if (integerPart >= 1 && integerPart <= 16) {
4171
- return decimalPart === 0;
4158
+ name = "asphyxia";
4159
+ supportedGames = ["sdvx", "iidx"];
4160
+ gameServices = {};
4161
+ logger = new import_koishi11.Logger("Noah-Asphyxia");
4162
+ };
4163
+
4164
+ // src/servers/Mao/index.ts
4165
+ var import_koishi12 = require("koishi");
4166
+ var Mao = class {
4167
+ static {
4168
+ __name(this, "Mao");
4172
4169
  }
4173
- if (integerPart === 17) {
4174
- return decimalPart === 0 || decimalPart === 0.5;
4170
+ name = "mao";
4171
+ supportedGames = ["sdvx"];
4172
+ gameServices = {};
4173
+ logger = new import_koishi12.Logger("Noah-Mao");
4174
+ };
4175
+
4176
+ // src/servers/Official/index.ts
4177
+ var import_koishi13 = require("koishi");
4178
+ var Official = class {
4179
+ static {
4180
+ __name(this, "Official");
4175
4181
  }
4176
- if (integerPart >= 18 && integerPart <= 20) {
4177
- const tenths = Math.round(decimalPart * 10);
4178
- return Math.abs(decimalPart * 10 - tenths) < 1e-4 && tenths >= 0 && tenths <= 9;
4182
+ name = "official";
4183
+ supportedGames = ["sdvx"];
4184
+ gameServices = {};
4185
+ logger = new import_koishi13.Logger("Noah-Official");
4186
+ };
4187
+
4188
+ // src/servers/ServerFactory.ts
4189
+ var ServerFactory = class {
4190
+ static {
4191
+ __name(this, "ServerFactory");
4179
4192
  }
4180
- return false;
4181
- }
4182
- __name(isValidLevel, "isValidLevel");
4183
- function generateValidLevelRange(start, end) {
4184
- const levels = [];
4185
- const startInt = Math.floor(start);
4186
- const endInt = Math.floor(end);
4187
- const startTenth = Math.round((start - startInt) * 10);
4188
- const endTenth = Math.round((end - endInt) * 10);
4189
- for (let intPart = startInt; intPart <= endInt; intPart++) {
4190
- if (intPart >= 1 && intPart <= 16) {
4191
- if (intPart === startInt && startTenth > 0 || intPart === endInt && endTenth > 0) {
4192
- continue;
4193
- }
4194
- levels.push(intPart);
4195
- } else if (intPart === 17) {
4196
- const currentStartTenth = intPart === startInt ? startTenth : 0;
4197
- const currentEndTenth = intPart === endInt ? endTenth : 5;
4198
- if (currentStartTenth <= 0 && currentEndTenth >= 0) {
4199
- levels.push(17);
4200
- }
4201
- if (currentStartTenth <= 5 && currentEndTenth >= 5) {
4202
- levels.push(17.5);
4203
- }
4204
- } else if (intPart >= 18 && intPart <= 20) {
4205
- const currentStartTenth = intPart === startInt ? startTenth : 0;
4206
- const currentEndTenth = intPart === endInt ? endTenth : 9;
4207
- for (let tenth = currentStartTenth; tenth <= currentEndTenth && tenth <= 9; tenth++) {
4208
- levels.push(intPart + tenth / 10);
4193
+ serverInstances = /* @__PURE__ */ new Map();
4194
+ /**
4195
+ * 根据服务器类型获取服务器实例,如果不存在则创建新实例
4196
+ * @param serverType - 服务器类型
4197
+ * @returns 对应的服务器实例
4198
+ * @throws 当请求不支持的服务器类型时抛出异常
4199
+ */
4200
+ getServer(serverType) {
4201
+ if (!this.serverInstances.has(serverType)) {
4202
+ let serverInstance;
4203
+ switch (serverType) {
4204
+ case "asphyxia":
4205
+ serverInstance = new Asphyxia();
4206
+ break;
4207
+ case "mao":
4208
+ serverInstance = new Mao();
4209
+ break;
4210
+ case "official":
4211
+ serverInstance = new Official();
4212
+ break;
4213
+ default:
4214
+ throw new Error(`Unsupported server type: ${serverType}`);
4209
4215
  }
4216
+ this.serverInstances.set(serverType, serverInstance);
4210
4217
  }
4218
+ return this.serverInstances.get(serverType);
4211
4219
  }
4212
- return levels;
4213
- }
4214
- __name(generateValidLevelRange, "generateValidLevelRange");
4215
- function parseLevelRange(item) {
4216
- if (/^\d{1,2}(\.\d)?-\d{1,2}(\.\d)?$/.test(item)) {
4217
- const [startRaw, endRaw] = item.split("-");
4218
- const start = parseFloat(startRaw.includes(".") ? startRaw : startRaw + ".0");
4219
- const end = parseFloat(endRaw.includes(".") ? endRaw : endRaw + ".0");
4220
- if (start >= 1 && end <= 20.9 && start <= end) {
4221
- if (!isValidLevel(start) || !isValidLevel(end)) {
4222
- return null;
4223
- }
4224
- return generateValidLevelRange(start, end);
4225
- }
4220
+ };
4221
+
4222
+ // src/servers/index.ts
4223
+ var name9 = "Noah-Server";
4224
+ var inject2 = ["globalConfig"];
4225
+ var ServerManager = class _ServerManager {
4226
+ static {
4227
+ __name(this, "ServerManager");
4226
4228
  }
4227
- if (/^\d{1,2}(\.\d)?$/.test(item)) {
4228
- const level = parseFloat(item.includes(".") ? item : item + ".0");
4229
- if (isValidLevel(level)) {
4230
- return [level];
4231
- }
4229
+ static instance;
4230
+ factory;
4231
+ externalServices = /* @__PURE__ */ new Map();
4232
+ constructor() {
4233
+ this.factory = new ServerFactory();
4232
4234
  }
4233
- return null;
4234
- }
4235
- __name(parseLevelRange, "parseLevelRange");
4236
- function parseScoreRange(item) {
4237
- if (/^\d+[wk]?-\d+[wk]?$/.test(item)) {
4238
- const [startRaw, endRaw] = item.split("-");
4239
- const start = parseNumberWithSuffix(startRaw);
4240
- const end = parseNumberWithSuffix(endRaw);
4241
- if (start >= 0 && end <= 1e7 && start <= end) {
4242
- return [start, end];
4235
+ static getInstance() {
4236
+ if (!_ServerManager.instance) {
4237
+ _ServerManager.instance = new _ServerManager();
4243
4238
  }
4239
+ return _ServerManager.instance;
4244
4240
  }
4245
- if (/^\d+[wk]?$/.test(item)) {
4246
- const score = parseNumberWithSuffix(item);
4247
- const hasSuffix = /[wk]$/.test(item);
4248
- if (score >= 0 && score <= 1e7 && (score >= 100 || hasSuffix)) {
4249
- return [score];
4250
- }
4241
+ getServer(serverType) {
4242
+ return this.factory.getServer(serverType);
4251
4243
  }
4252
- return null;
4253
- }
4254
- __name(parseScoreRange, "parseScoreRange");
4255
- function parseVfValue(item) {
4256
- if (/^\d{1,2}\.\d{2}-\d{1,2}\.\d{2}$/.test(item)) {
4257
- const [start, end] = item.split("-").map(parseFloat);
4258
- if (start >= 0 && end <= 99 && start <= end) {
4259
- return [start, end];
4260
- }
4244
+ registerGameService(serverType, gameType, service) {
4245
+ const key = `${serverType}:${gameType}`;
4246
+ this.externalServices.set(key, service);
4261
4247
  }
4262
- if (/^\d{1,2}\.\d{2}$/.test(item)) {
4263
- const vf2 = parseFloat(item);
4264
- if (vf2 >= 0 && vf2 <= 99) {
4265
- return vf2;
4266
- }
4248
+ getGameService(serverType, gameType) {
4249
+ const key = `${serverType}:${gameType}`;
4250
+ return this.externalServices.get(key);
4267
4251
  }
4268
- return null;
4252
+ };
4253
+ function apply9(ctx, config) {
4269
4254
  }
4270
- __name(parseVfValue, "parseVfValue");
4271
- function parseClearTypeRange(item) {
4272
- if (item.includes("-")) {
4273
- const [startRaw, endRaw] = item.split("-").filter(Boolean);
4274
- if (startRaw && endRaw && CLEAR_TYPE_ABBR_MAP[startRaw] && CLEAR_TYPE_ABBR_MAP[endRaw]) {
4275
- const start = ALL_CLEAR_TYPES.findIndex((t) => t === CLEAR_TYPE_ABBR_MAP[startRaw]);
4276
- const end = ALL_CLEAR_TYPES.findIndex((t) => t === CLEAR_TYPE_ABBR_MAP[endRaw]);
4277
- if (start !== -1 && end !== -1) {
4278
- const from = Math.min(start, end);
4279
- const to = Math.max(start, end);
4280
- const clearTypes = [];
4281
- for (let i = from; i <= to; i++) {
4282
- clearTypes.push(ALL_CLEAR_TYPES[i]);
4283
- }
4284
- if (clearTypes.includes("PUC") && !clearTypes.includes("S-PUC")) {
4285
- clearTypes.push("S-PUC");
4286
- }
4287
- return clearTypes;
4255
+ __name(apply9, "apply");
4256
+
4257
+ // src/servers/Asphyxia/utils/xml.ts
4258
+ var xml2js = __toESM(require("xml2js"), 1);
4259
+ var xmlToJson = /* @__PURE__ */ __name((xml) => {
4260
+ return new Promise((resolve3, reject) => {
4261
+ xml2js.parseString(xml, (err, result) => {
4262
+ if (err) {
4263
+ reject(new Error("Error parsing XML: " + err));
4264
+ } else {
4265
+ resolve3(result);
4288
4266
  }
4289
- }
4267
+ });
4268
+ });
4269
+ }, "xmlToJson");
4270
+
4271
+ // src/games/sdvx/services/music-service.ts
4272
+ var MusicService = class _MusicService {
4273
+ static {
4274
+ __name(this, "MusicService");
4290
4275
  }
4291
- return null;
4292
- }
4293
- __name(parseClearTypeRange, "parseClearTypeRange");
4294
- function parseSingleClearType(item) {
4295
- if (CLEAR_TYPE_ABBR_MAP[item.toLowerCase()]) {
4296
- const clearType = CLEAR_TYPE_ABBR_MAP[item.toLowerCase()];
4297
- if (clearType === "PUC") {
4298
- return ["PUC", "S-PUC"];
4276
+ static instance;
4277
+ sdvx_data_url;
4278
+ sdvx_search_url;
4279
+ constructor(config) {
4280
+ this.sdvx_data_url = config.sdvx_data_url;
4281
+ this.sdvx_search_url = config.sdvx_search_url;
4282
+ }
4283
+ parseDifnum(value) {
4284
+ if (typeof value === "number") {
4285
+ return Number.isFinite(value) ? value : 0;
4299
4286
  }
4300
- return [clearType];
4287
+ if (typeof value === "string") {
4288
+ const parsed = Number.parseFloat(value.trim());
4289
+ return Number.isFinite(parsed) ? parsed : 0;
4290
+ }
4291
+ return 0;
4301
4292
  }
4302
- return null;
4303
- }
4304
- __name(parseSingleClearType, "parseSingleClearType");
4305
- function parseGradeRange(item) {
4306
- if (/^[a-ds][a-z+]*-[a-ds][a-z+]*$/.test(item)) {
4307
- const [startRaw, endRaw] = item.split("-");
4308
- const start = ALL_GRADES.findIndex((g) => g.toLowerCase() === startRaw);
4309
- const end = ALL_GRADES.findIndex((g) => g.toLowerCase() === endRaw);
4310
- if (start !== -1 && end !== -1) {
4311
- const from = Math.min(start, end);
4312
- const to = Math.max(start, end);
4313
- const grades = [];
4314
- for (let i = from; i <= to; i++) {
4315
- grades.push(ALL_GRADES[i]);
4316
- }
4317
- return grades;
4318
- }
4319
- }
4320
- return null;
4321
- }
4322
- __name(parseGradeRange, "parseGradeRange");
4323
- function parseSingleGrade(item) {
4324
- const grade = ALL_GRADES.find((g) => g.toLowerCase() === item.toLowerCase());
4325
- if (grade) {
4326
- return [grade];
4327
- }
4328
- return null;
4329
- }
4330
- __name(parseSingleGrade, "parseSingleGrade");
4331
- function parseRadarFeature(item) {
4332
- const radarList = ["notes", "peak", "tsumami", "tricky", "hand_trip", "one_hand"];
4333
- if (radarList.includes(item)) {
4334
- return item;
4293
+ normalizeMusicDifnum(music) {
4294
+ if (!music?.difficulty?.length) return music;
4295
+ return {
4296
+ ...music,
4297
+ difficulty: music.difficulty.map((d) => ({
4298
+ ...d,
4299
+ difnum: this.parseDifnum(d.difnum)
4300
+ }))
4301
+ };
4335
4302
  }
4336
- return null;
4337
- }
4338
- __name(parseRadarFeature, "parseRadarFeature");
4339
- function parseQueryInput(input) {
4340
- const params = {};
4341
- if (!input || input.trim() === "") {
4342
- return params;
4303
+ /**
4304
+ * 获取 MusicService 实例
4305
+ * @param config - SDVX 配置对象
4306
+ * @returns MusicService 实例
4307
+ */
4308
+ static getInstance(config) {
4309
+ if (!_MusicService.instance) {
4310
+ _MusicService.instance = new _MusicService(config);
4311
+ }
4312
+ return _MusicService.instance;
4343
4313
  }
4344
- const items = input.split(/\s+/).map((s) => s.trim()).filter(Boolean);
4345
- for (const item of items) {
4346
- const vf2 = parseVfValue(item);
4347
- if (vf2 !== null) {
4348
- if (params.vf !== void 0) {
4349
- const existing = Array.isArray(params.vf) ? params.vf : [params.vf];
4350
- const newVf = Array.isArray(vf2) ? vf2 : [vf2];
4351
- params.vf = [...existing, ...newVf];
4352
- } else {
4353
- params.vf = vf2;
4314
+ /**
4315
+ * 获取音乐信息(支持数字和字符串 ID)
4316
+ * @param ctx - Koishi 上下文对象
4317
+ * @param musicIds - 音乐 ID 数组(数字或字符串)
4318
+ * @returns 音乐信息数组
4319
+ */
4320
+ async getMusic(ctx, musicIds) {
4321
+ const numericIds = musicIds.filter((id) => typeof id === "number");
4322
+ const stringTitles = musicIds.filter((id) => typeof id === "string");
4323
+ const numericMap = /* @__PURE__ */ new Map();
4324
+ const stringMap = /* @__PURE__ */ new Map();
4325
+ if (numericIds.length > 0) {
4326
+ try {
4327
+ const response = await ctx.http.post(
4328
+ `/music`,
4329
+ { ids: numericIds },
4330
+ { baseURL: this.sdvx_data_url }
4331
+ );
4332
+ if (Array.isArray(response)) {
4333
+ for (const m of response) {
4334
+ const normalized = this.normalizeMusicDifnum(m);
4335
+ numericMap.set(Number(normalized.id), normalized);
4336
+ }
4337
+ }
4338
+ } catch (error) {
4339
+ console.warn("Failed to fetch music data for numeric IDs:", error);
4354
4340
  }
4355
- continue;
4356
4341
  }
4357
- const levels = parseLevelRange(item);
4358
- if (levels !== null) {
4359
- if (params.level !== void 0) {
4360
- const existing = Array.isArray(params.level) ? params.level : [params.level];
4361
- params.level = [.../* @__PURE__ */ new Set([...existing, ...levels])];
4362
- } else {
4363
- params.level = levels.length === 1 ? levels[0] : levels;
4342
+ if (stringTitles.length > 0) {
4343
+ try {
4344
+ const response = await ctx.http.post(
4345
+ `/external/music`,
4346
+ { titles: stringTitles },
4347
+ { baseURL: this.sdvx_data_url }
4348
+ );
4349
+ const converted = this.convertExternalToStandard(response);
4350
+ for (const m of converted) {
4351
+ const normalized = this.normalizeMusicDifnum(m);
4352
+ stringMap.set(String(normalized.id), normalized);
4353
+ stringMap.set(normalized.title_name, normalized);
4354
+ }
4355
+ } catch (error) {
4356
+ console.warn("Failed to fetch music data for string IDs:", error);
4364
4357
  }
4365
- continue;
4366
4358
  }
4367
- const scores = parseScoreRange(item);
4368
- if (scores !== null) {
4369
- if (params.score !== void 0) {
4370
- const existing = Array.isArray(params.score) ? params.score : [params.score];
4371
- params.score = [.../* @__PURE__ */ new Set([...existing, ...scores])];
4372
- } else {
4373
- params.score = scores.length === 1 ? scores[0] : scores;
4374
- }
4375
- continue;
4359
+ const results = [];
4360
+ for (const id of musicIds) {
4361
+ const music = typeof id === "number" ? numericMap.get(id) : stringMap.get(id);
4362
+ if (music) results.push(music);
4376
4363
  }
4377
- const clearTypes = parseClearTypeRange(item);
4378
- if (clearTypes !== null) {
4379
- if (params.clearType) {
4380
- const existing = Array.isArray(params.clearType) ? params.clearType : [params.clearType];
4381
- params.clearType = [.../* @__PURE__ */ new Set([...existing, ...clearTypes])];
4382
- } else {
4383
- params.clearType = clearTypes;
4364
+ return results;
4365
+ }
4366
+ /**
4367
+ * 转换外部音乐数据到标准格式
4368
+ * @param externalData - 外部音乐数据数组
4369
+ * @returns 标准格式音乐数据数组
4370
+ */
4371
+ convertExternalToStandard(externalData) {
4372
+ return externalData.map((data) => ({
4373
+ id: data.slug,
4374
+ title_name: data.ext_title_name,
4375
+ artist_name: data.ext_artist_name,
4376
+ genre: data.genre,
4377
+ genre_name: data.genre_name,
4378
+ inf_ver: data.difficulty[0]?.inf_ver || 0,
4379
+ difficulty: data.difficulty.map((diff) => ({
4380
+ difstr: diff.difstr,
4381
+ difnum: this.parseDifnum(diff.difnum),
4382
+ illustrator: diff.ext_illustrator,
4383
+ effected_by: diff.ext_effected_by,
4384
+ cover_url: diff.cover_url,
4385
+ inf_ver: diff.inf_ver
4386
+ }))
4387
+ }));
4388
+ }
4389
+ /**
4390
+ * 根据曲名查询歌曲
4391
+ * @param ctx - Koishi 上下文对象
4392
+ * @param query - 查询字符串
4393
+ * @returns 音乐信息数组
4394
+ */
4395
+ async searchMusic(ctx, query) {
4396
+ try {
4397
+ const response = await ctx.http.get(
4398
+ `/search?query=${encodeURIComponent(query)}&size=50`,
4399
+ {
4400
+ baseURL: this.sdvx_search_url
4401
+ }
4402
+ );
4403
+ const results = response?.results;
4404
+ if (!Array.isArray(results) || results.length === 0) {
4405
+ return null;
4384
4406
  }
4385
- continue;
4386
- }
4387
- const singleClearTypes = parseSingleClearType(item);
4388
- if (singleClearTypes !== null) {
4389
- if (params.clearType) {
4390
- const existing = Array.isArray(params.clearType) ? params.clearType : [params.clearType];
4391
- params.clearType = [.../* @__PURE__ */ new Set([...existing, ...singleClearTypes])];
4392
- } else {
4393
- params.clearType = singleClearTypes;
4407
+ const musicIds = results.map((item) => Number.isInteger(item.id) ? item.id : item.external_key).filter((v) => v != null);
4408
+ if (musicIds.length === 0) {
4409
+ return null;
4394
4410
  }
4395
- continue;
4411
+ return await this.getMusic(ctx, musicIds);
4412
+ } catch {
4413
+ return null;
4396
4414
  }
4397
- const grades = parseGradeRange(item);
4398
- if (grades !== null) {
4399
- if (params.grade) {
4400
- const existing = Array.isArray(params.grade) ? params.grade : [params.grade];
4401
- params.grade = [.../* @__PURE__ */ new Set([...existing, ...grades])];
4402
- } else {
4403
- params.grade = grades;
4415
+ }
4416
+ async extTitleToMid(ctx, titles) {
4417
+ try {
4418
+ const response = await ctx.http.post(
4419
+ `/external/title-mapping`,
4420
+ { titles },
4421
+ { baseURL: this.sdvx_data_url }
4422
+ );
4423
+ if (!Array.isArray(response)) {
4424
+ return [];
4404
4425
  }
4405
- continue;
4406
- }
4407
- const singleGrades = parseSingleGrade(item);
4408
- if (singleGrades !== null) {
4409
- if (params.grade) {
4410
- const existing = Array.isArray(params.grade) ? params.grade : [params.grade];
4411
- params.grade = [.../* @__PURE__ */ new Set([...existing, ...singleGrades])];
4412
- } else {
4413
- params.grade = singleGrades[0];
4426
+ const mapping = new Map(
4427
+ response.map((item) => [
4428
+ item?.title?.toLowerCase(),
4429
+ Number.isInteger(item?.music_id) ? String(item.music_id) : null
4430
+ ])
4431
+ );
4432
+ const mids = [];
4433
+ for (const title of titles) {
4434
+ const mid = mapping.get(title.toLowerCase()) ?? null;
4435
+ mids.push({ title, mid });
4414
4436
  }
4415
- continue;
4437
+ return mids;
4438
+ } catch {
4439
+ return [];
4416
4440
  }
4417
4441
  }
4418
- return params;
4419
- }
4420
- __name(parseQueryInput, "parseQueryInput");
4442
+ };
4421
4443
 
4422
- // src/games/sdvx/commands/calculate.ts
4423
- function calculate(ctx, config, logger5) {
4424
- ctx.command("sdvx.calculate <query:text>").alias("sdvx.cal").action(async ({ session, options }, query) => {
4425
- try {
4426
- let queryInput = query;
4427
- if (!queryInput) {
4428
- await session.send(session.text(".prompt"));
4429
- queryInput = await session.prompt();
4430
- if (!queryInput) return session.text("commands.timeout");
4431
- if (queryInput === "q") return session.text(".quit");
4432
- }
4433
- const params = parseQueryInput(queryInput);
4434
- if (Object.keys(params).length === 0) {
4435
- return session.text(".invalid-query");
4436
- }
4437
- const results = generateQueryResults(params);
4438
- if (results.length === 0) {
4439
- return session.text(".no-results");
4440
- }
4441
- const maxResults = 500;
4442
- if (results.length > maxResults) {
4443
- return session.text(".too-many-results", [results.length]);
4444
- }
4445
- session.send(session.text(".drawing"));
4446
- const drawerManager = DrawerManager.getInstance(ctx);
4447
- const sdvxDrawer = drawerManager.getDrawer("sdvx");
4448
- const imageBuffer = await sdvxDrawer.generateVFTableImage(
4449
- {
4450
- results,
4451
- config
4452
- },
4453
- {
4454
- lossless: true
4455
- }
4456
- );
4457
- return import_koishi11.h.image(imageBuffer, "image/png");
4458
- } catch (error) {
4459
- ctx.logger("SDVX-Calculate").warn(error);
4460
- return session.text(".error");
4461
- }
4462
- });
4463
- }
4464
- __name(calculate, "calculate");
4465
-
4466
- // src/games/sdvx/commands/chart.ts
4467
- var import_koishi12 = require("koishi");
4468
-
4469
- // src/servers/utils/difficulty.ts
4444
+ // src/games/sdvx/utils/difficulty.ts
4470
4445
  function getDiffName(diffStr, infVer) {
4471
4446
  switch (diffStr) {
4472
4447
  case "novice":
@@ -4560,531 +4535,450 @@ function getDiffStringFromAbbr(diffAbbr) {
4560
4535
  }
4561
4536
  __name(getDiffStringFromAbbr, "getDiffStringFromAbbr");
4562
4537
 
4563
- // src/games/sdvx/services/music-service.ts
4564
- var MusicService = class _MusicService {
4538
+ // src/games/sdvx/adapters/asphyxia.ts
4539
+ var AsphyxiaSDVXService = class _AsphyxiaSDVXService {
4565
4540
  static {
4566
- __name(this, "MusicService");
4541
+ __name(this, "AsphyxiaSDVXService");
4567
4542
  }
4568
4543
  static instance;
4569
- sdvx_data_url;
4570
- sdvx_search_url;
4571
- constructor(config) {
4572
- this.sdvx_data_url = config.sdvx_data_url;
4573
- this.sdvx_search_url = config.sdvx_search_url;
4544
+ logger;
4545
+ config;
4546
+ cachedModel = null;
4547
+ constructor(logger5, config) {
4548
+ this.logger = logger5;
4549
+ this.config = config;
4574
4550
  }
4575
- parseDifnum(value) {
4576
- if (typeof value === "number") {
4577
- return Number.isFinite(value) ? value : 0;
4551
+ static getInstance(logger5, config) {
4552
+ if (!_AsphyxiaSDVXService.instance) {
4553
+ _AsphyxiaSDVXService.instance = new _AsphyxiaSDVXService(logger5, config);
4578
4554
  }
4579
- if (typeof value === "string") {
4580
- const parsed = Number.parseFloat(value.trim());
4581
- return Number.isFinite(parsed) ? parsed : 0;
4555
+ return _AsphyxiaSDVXService.instance;
4556
+ }
4557
+ async getModel(ctx) {
4558
+ if (this.cachedModel) return this.cachedModel;
4559
+ const resp = await ctx.http.get(
4560
+ `${this.config.sdvx_data_url}/model`
4561
+ );
4562
+ this.cachedModel = String(resp.model);
4563
+ return this.cachedModel;
4564
+ }
4565
+ async getRefid(ctx, url, model, cardId) {
4566
+ const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
4567
+ const xmlRequestBody = `<?xml version="1.0" encoding="UTF-8"?>
4568
+ <call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
4569
+ <cardmng cardid="${cardId}" cardtype="2" method="inquire" update="0" />
4570
+ </call>`;
4571
+ const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
4572
+ const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
4573
+ const jsonResponse = await xmlToJson(decodedResponse);
4574
+ const cardmng = jsonResponse.response.cardmng?.[0]?.$;
4575
+ if (!cardmng || !cardmng.refid) throw new Error("refid not found");
4576
+ return cardmng.refid;
4577
+ }
4578
+ async getUserName(ctx, url, cardId) {
4579
+ const model = await this.getModel(ctx);
4580
+ const refid = await this.getRefid(ctx, url, model, cardId);
4581
+ const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
4582
+ const xmlRequestBody = `<call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
4583
+ <game method="sv7_load" ver="0">
4584
+ <dataid __type="str">${refid}</dataid>
4585
+ <cardid __type="str">${cardId}</cardid>
4586
+ <refid __type="str">${refid}</refid>
4587
+ <locid __type="str">ea</locid>
4588
+ </game>
4589
+ </call>`;
4590
+ const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
4591
+ const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
4592
+ const jsonResponse = await xmlToJson(decodedResponse);
4593
+ const name15 = jsonResponse.response.game?.[0]?.name?.[0]._;
4594
+ if (!name15) throw new Error("User name not found");
4595
+ return name15;
4596
+ }
4597
+ async getAllScore(ctx, url, cardId, config) {
4598
+ const model = await this.getModel(ctx);
4599
+ const refid = await this.getRefid(ctx, url, model, cardId);
4600
+ const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
4601
+ const xmlRequestBody = `<call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
4602
+ <game method="sv7_load_m" ver="0">
4603
+ <dataid __type="str">${refid}</dataid>
4604
+ <cardid __type="str">${cardId}</cardid>
4605
+ <refid __type="str">${refid}</refid>
4606
+ <locid __type="str">ea</locid>
4607
+ </game>
4608
+ </call>`;
4609
+ const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
4610
+ const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
4611
+ const jsonResponse = await xmlToJson(decodedResponse);
4612
+ const infoList = jsonResponse.response.game?.[0]?.music?.[0]?.info || [];
4613
+ if (!Array.isArray(infoList)) return [];
4614
+ const scoresRaw = infoList.map((item) => {
4615
+ const paramStr = item.param?.[0]?._ || item.param?.[0];
4616
+ return paramStr ? paramStr.split(" ").map(Number) : [];
4617
+ }).filter((arr) => arr.length >= 11);
4618
+ const musicIds = scoresRaw.map((arr) => arr[0]);
4619
+ const musicService = MusicService.getInstance(config);
4620
+ const musicDataList = await musicService.getMusic(ctx, musicIds);
4621
+ const musicDataMap = new Map(musicDataList.map((m) => [m.id, m]));
4622
+ return scoresRaw.map((arr) => {
4623
+ const [
4624
+ music_id,
4625
+ music_diff,
4626
+ score,
4627
+ exscore,
4628
+ clear_type,
4629
+ score_grade,
4630
+ ,
4631
+ ,
4632
+ btn_rate,
4633
+ long_rate,
4634
+ vol_rate
4635
+ ] = arr;
4636
+ const music = musicDataMap.get(music_id) || null;
4637
+ const diffStr = this.getDifficultyString(music_diff);
4638
+ let difficultyData = null;
4639
+ if (music && music.difficulty && music.difficulty.length > 0) {
4640
+ difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
4641
+ }
4642
+ const infVer = music ? music.inf_ver : 2;
4643
+ const scoreObj = {
4644
+ music: {
4645
+ music_id,
4646
+ music_diff: difficultyData ? difficultyData.difnum : 0,
4647
+ music_diff_name: getDiffName(diffStr, infVer),
4648
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
4649
+ score,
4650
+ exscore,
4651
+ clear_type: this.getClearType(clear_type),
4652
+ score_grade: getGradeByScore(score),
4653
+ btn_rate: String(btn_rate),
4654
+ long_rate: String(long_rate),
4655
+ vol_rate: String(vol_rate)
4656
+ },
4657
+ extra: {
4658
+ play_count: 0,
4659
+ update_at: "",
4660
+ volforce: 0
4661
+ },
4662
+ music_data: music,
4663
+ difficulty_data: difficultyData
4664
+ };
4665
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
4666
+ return scoreObj;
4667
+ });
4668
+ }
4669
+ getClearType(clearType) {
4670
+ const map = {
4671
+ 0: "NO PLAY",
4672
+ 1: "PLAYED",
4673
+ 2: "NC",
4674
+ 3: "HC",
4675
+ 4: "MC",
4676
+ 5: "UC",
4677
+ 6: "PUC"
4678
+ };
4679
+ return map[clearType] || "NO PLAY";
4680
+ }
4681
+ getDifficultyString(diffType) {
4682
+ const diffStrMap = {
4683
+ 0: "novice",
4684
+ 1: "advanced",
4685
+ 2: "exhaust",
4686
+ 3: "infinite",
4687
+ 4: "maximum",
4688
+ 5: "ultimate"
4689
+ };
4690
+ return diffStrMap[diffType] || "novice";
4691
+ }
4692
+ };
4693
+
4694
+ // src/games/sdvx/adapters/mao.ts
4695
+ var MaoSDVXService = class _MaoSDVXService {
4696
+ static {
4697
+ __name(this, "MaoSDVXService");
4698
+ }
4699
+ static instance;
4700
+ logger;
4701
+ constructor(logger5) {
4702
+ this.logger = logger5;
4703
+ }
4704
+ static getInstance(logger5) {
4705
+ if (!_MaoSDVXService.instance) {
4706
+ _MaoSDVXService.instance = new _MaoSDVXService(logger5);
4582
4707
  }
4583
- return 0;
4708
+ return _MaoSDVXService.instance;
4584
4709
  }
4585
- normalizeMusicDifnum(music) {
4586
- if (!music?.difficulty?.length) return music;
4587
- return {
4588
- ...music,
4589
- difficulty: music.difficulty.map((d) => ({
4590
- ...d,
4591
- difnum: this.parseDifnum(d.difnum)
4592
- }))
4710
+ getDifficultyString(diffType) {
4711
+ const diffStrMap = {
4712
+ 0: "novice",
4713
+ 1: "advanced",
4714
+ 2: "exhaust",
4715
+ 3: "infinite",
4716
+ 4: "maximum",
4717
+ 5: "ultimate"
4593
4718
  };
4719
+ return diffStrMap[diffType] || "novice";
4594
4720
  }
4595
- /**
4596
- * 获取 MusicService 实例
4597
- * @param config - SDVX 配置对象
4598
- * @returns MusicService 实例
4599
- */
4600
- static getInstance(config) {
4601
- if (!_MusicService.instance) {
4602
- _MusicService.instance = new _MusicService(config);
4721
+ getClearType(clearType) {
4722
+ return clearType === 6 ? "MC" : clearType === 5 ? "PUC" : clearType === 4 ? "UC" : clearType === 3 ? "HC" : clearType === 2 ? "NC" : clearType === 1 ? "PLAYED" : "NO PLAY";
4723
+ }
4724
+ async getUserName(ctx, url, cardId) {
4725
+ const response = await ctx.http.get(`/my?card=${cardId}`, {
4726
+ baseURL: ctx.globalConfig.maoServerUrl,
4727
+ responseType: "text"
4728
+ });
4729
+ const match = response.match(/猫网玩家\[([^\]]+)\]/);
4730
+ if (!match || !match[1]) {
4731
+ throw new Error("Failed to extract player ID from response");
4603
4732
  }
4604
- return _MusicService.instance;
4733
+ return match[1];
4605
4734
  }
4606
- /**
4607
- * 获取音乐信息(支持数字和字符串 ID)
4608
- * @param ctx - Koishi 上下文对象
4609
- * @param musicIds - 音乐 ID 数组(数字或字符串)
4610
- * @returns 音乐信息数组
4611
- */
4612
- async getMusic(ctx, musicIds) {
4613
- const numericIds = musicIds.filter((id) => typeof id === "number");
4614
- const stringTitles = musicIds.filter((id) => typeof id === "string");
4615
- const numericMap = /* @__PURE__ */ new Map();
4616
- const stringMap = /* @__PURE__ */ new Map();
4617
- if (numericIds.length > 0) {
4618
- try {
4619
- const response = await ctx.http.post(
4620
- `/music`,
4621
- { ids: numericIds },
4622
- { baseURL: this.sdvx_data_url }
4623
- );
4624
- if (Array.isArray(response)) {
4625
- for (const m of response) {
4626
- const normalized = this.normalizeMusicDifnum(m);
4627
- numericMap.set(Number(normalized.id), normalized);
4628
- }
4629
- }
4630
- } catch (error) {
4631
- console.warn("Failed to fetch music data for numeric IDs:", error);
4632
- }
4735
+ async verifyPin(ctx, url, cardId, pin) {
4736
+ const apiKey = ctx.globalConfig.maoApiKey;
4737
+ if (!apiKey) {
4738
+ this.logger.warn("maoApiKey not configured");
4739
+ return false;
4633
4740
  }
4634
- if (stringTitles.length > 0) {
4635
- try {
4636
- const response = await ctx.http.post(
4637
- `/external/music`,
4638
- { titles: stringTitles },
4639
- { baseURL: this.sdvx_data_url }
4640
- );
4641
- const converted = this.convertExternalToStandard(response);
4642
- for (const m of converted) {
4643
- const normalized = this.normalizeMusicDifnum(m);
4644
- stringMap.set(String(normalized.id), normalized);
4645
- stringMap.set(normalized.title_name, normalized);
4646
- }
4647
- } catch (error) {
4648
- console.warn("Failed to fetch music data for string IDs:", error);
4741
+ const resp = await ctx.http.get(
4742
+ `/bot/v2/player/card?card=${cardId}`,
4743
+ {
4744
+ baseURL: ctx.globalConfig.maoServerUrl,
4745
+ headers: { "X-API-Key": apiKey }
4649
4746
  }
4747
+ );
4748
+ return resp?.code === 0 && resp?.data?.password === pin;
4749
+ }
4750
+ clearTypeToNum(clearType) {
4751
+ switch (clearType) {
4752
+ case "MC":
4753
+ return 6;
4754
+ case "PUC":
4755
+ case "S-PUC":
4756
+ return 5;
4757
+ case "UC":
4758
+ return 4;
4759
+ case "HC":
4760
+ return 3;
4761
+ case "NC":
4762
+ return 2;
4763
+ case "PLAYED":
4764
+ return 1;
4765
+ default:
4766
+ return 0;
4650
4767
  }
4651
- const results = [];
4652
- for (const id of musicIds) {
4653
- const music = typeof id === "number" ? numericMap.get(id) : stringMap.get(id);
4654
- if (music) results.push(music);
4768
+ }
4769
+ diffNameToMusicType(diffName) {
4770
+ switch (diffName.toUpperCase()) {
4771
+ case "NOV":
4772
+ return 0;
4773
+ case "ADV":
4774
+ return 1;
4775
+ case "EXH":
4776
+ return 2;
4777
+ case "INF":
4778
+ case "GRV":
4779
+ case "HVN":
4780
+ case "VVD":
4781
+ case "XCD":
4782
+ return 3;
4783
+ case "MXM":
4784
+ return 4;
4785
+ case "ULT":
4786
+ return 5;
4787
+ default:
4788
+ return 0;
4655
4789
  }
4656
- return results;
4657
4790
  }
4658
- /**
4659
- * 转换外部音乐数据到标准格式
4660
- * @param externalData - 外部音乐数据数组
4661
- * @returns 标准格式音乐数据数组
4662
- */
4663
- convertExternalToStandard(externalData) {
4664
- return externalData.map((data) => ({
4665
- id: data.slug,
4666
- title_name: data.ext_title_name,
4667
- artist_name: data.ext_artist_name,
4668
- genre: data.genre,
4669
- genre_name: data.genre_name,
4670
- inf_ver: data.difficulty[0]?.inf_ver || 0,
4671
- difficulty: data.difficulty.map((diff) => ({
4672
- difstr: diff.difstr,
4673
- difnum: this.parseDifnum(diff.difnum),
4674
- illustrator: diff.ext_illustrator,
4675
- effected_by: diff.ext_effected_by,
4676
- cover_url: diff.cover_url,
4677
- inf_ver: diff.inf_ver
4678
- }))
4791
+ async uploadScore(ctx, url, cardId, scores) {
4792
+ const apiKey = ctx.globalConfig.maoApiKey;
4793
+ if (!apiKey) {
4794
+ this.logger.warn("maoApiKey not configured");
4795
+ return false;
4796
+ }
4797
+ const payload = scores.filter((s) => {
4798
+ const musicId = Number(s.music.music_id);
4799
+ return musicId > 0 && s.music.music_diff_name;
4800
+ }).map((s) => ({
4801
+ music_id: Number(s.music.music_id),
4802
+ music_type: this.diffNameToMusicType(s.music.music_diff_name),
4803
+ score: s.music.score,
4804
+ exscore: s.music.exscore,
4805
+ clear_type: this.clearTypeToNum(s.music.clear_type)
4679
4806
  }));
4680
- }
4681
- /**
4682
- * 根据曲名查询歌曲
4683
- * @param ctx - Koishi 上下文对象
4684
- * @param query - 查询字符串
4685
- * @returns 音乐信息数组
4686
- */
4687
- async searchMusic(ctx, query) {
4688
- try {
4689
- const response = await ctx.http.get(
4690
- `/search?query=${encodeURIComponent(query)}&size=50`,
4691
- {
4692
- baseURL: this.sdvx_search_url
4693
- }
4694
- );
4695
- const results = response?.results;
4696
- if (!Array.isArray(results) || results.length === 0) {
4697
- return null;
4698
- }
4699
- const musicIds = results.map((item) => Number.isInteger(item.id) ? item.id : item.external_key).filter((v) => v != null);
4700
- if (musicIds.length === 0) {
4701
- return null;
4807
+ if (payload.length === 0) return false;
4808
+ const resp = await ctx.http.post(
4809
+ "/bot/v2/sdvx/upload",
4810
+ { card: cardId, scores: payload },
4811
+ {
4812
+ baseURL: ctx.globalConfig.maoServerUrl,
4813
+ headers: { "X-API-Key": apiKey }
4702
4814
  }
4703
- return await this.getMusic(ctx, musicIds);
4704
- } catch {
4705
- return null;
4706
- }
4815
+ );
4816
+ return resp?.code === 0;
4707
4817
  }
4708
- async extTitleToMid(ctx, titles) {
4818
+ async getAllScore(ctx, url, cardId, config) {
4709
4819
  try {
4710
- const response = await ctx.http.post(
4711
- `/external/title-mapping`,
4712
- { titles },
4713
- { baseURL: this.sdvx_data_url }
4714
- );
4715
- if (!Array.isArray(response)) {
4820
+ const apiKey = ctx.globalConfig.maoApiKey;
4821
+ if (!apiKey) {
4822
+ this.logger.warn("maoApiKey not configured");
4716
4823
  return [];
4717
4824
  }
4718
- const mapping = new Map(
4719
- response.map((item) => [
4720
- item?.title?.toLowerCase(),
4721
- Number.isInteger(item?.music_id) ? String(item.music_id) : null
4722
- ])
4825
+ const resp = await ctx.http.get(`/bot/v2/sdvx/scores?card=${cardId}`, {
4826
+ baseURL: ctx.globalConfig.maoServerUrl,
4827
+ headers: { "X-API-Key": apiKey }
4828
+ });
4829
+ if (resp?.code !== 0 || !resp?.data) {
4830
+ return [];
4831
+ }
4832
+ const filteredData = resp.data.filter(
4833
+ (score) => score.mid !== 244 && score.mid !== 1759 && score.mid !== 1761 && score.isPlus === 0
4723
4834
  );
4724
- const mids = [];
4725
- for (const title of titles) {
4726
- const mid = mapping.get(title.toLowerCase()) ?? null;
4727
- mids.push({ title, mid });
4835
+ const musicIds = filteredData.map((score) => score.mid);
4836
+ const diffTypes = filteredData.map((score) => score.musicType);
4837
+ const musicService = MusicService.getInstance(config);
4838
+ const musicData = await musicService.getMusic(ctx, musicIds);
4839
+ const musicDataMap = new Map(musicData.map((music) => [music.id, music]));
4840
+ const scores = filteredData.map((score, index) => {
4841
+ const musicId = score.mid;
4842
+ const music = musicDataMap.get(musicId);
4843
+ const musicDiffType = score.musicType;
4844
+ const diffStr = this.getDifficultyString(musicDiffType);
4845
+ let difficultyData = null;
4846
+ if (music && music.difficulty && music.difficulty.length > 0) {
4847
+ difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
4848
+ }
4849
+ const infVer = music ? music.inf_ver : 2;
4850
+ const scoreObj = {
4851
+ music: {
4852
+ music_id: musicId,
4853
+ music_diff: difficultyData ? difficultyData.difnum : 0,
4854
+ music_diff_name: getDiffName(diffStr, infVer),
4855
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
4856
+ score: score.score,
4857
+ exscore: score.exscore,
4858
+ clear_type: this.getClearType(score.clearType),
4859
+ score_grade: getGradeByScore(score.score),
4860
+ btn_rate: score.btnRate.toString(),
4861
+ long_rate: score.longRate.toString(),
4862
+ vol_rate: score.volRate.toString()
4863
+ },
4864
+ extra: {
4865
+ play_count: score.playCount,
4866
+ update_at: score.updateAt,
4867
+ volforce: 0
4868
+ },
4869
+ music_data: music || null,
4870
+ difficulty_data: difficultyData || null
4871
+ };
4872
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
4873
+ return scoreObj;
4874
+ });
4875
+ return scores;
4876
+ } catch (error) {
4877
+ if (error.response?.status === 404) {
4878
+ return [];
4728
4879
  }
4729
- return mids;
4730
- } catch {
4731
- return [];
4880
+ throw error;
4732
4881
  }
4733
4882
  }
4734
- };
4735
-
4736
- // src/games/sdvx/commands/chart.ts
4737
- function mapArrangementToken(tok) {
4738
- const t = tok.toLowerCase();
4739
- if (t === "ran" || t === "random") return "random";
4740
- if (t === "m" || t === "mirror" || t === "mir") return "mirror";
4741
- if (t === "sran" || t === "srandom") return "s-random";
4742
- if (t === "fran" || t === "wtf") return "f-random";
4743
- return null;
4744
- }
4745
- __name(mapArrangementToken, "mapArrangementToken");
4746
- function mapLeftColorToken(tok) {
4747
- const m = /^l(blue|red|yellow|green)$/i.exec(tok);
4748
- return m ? m[1].toUpperCase() : null;
4749
- }
4750
- __name(mapLeftColorToken, "mapLeftColorToken");
4751
- function mapRightColorToken(tok) {
4752
- const m = /^r(blue|red|yellow|green)$/i.exec(tok);
4753
- return m ? m[1].toUpperCase() : null;
4754
- }
4755
- __name(mapRightColorToken, "mapRightColorToken");
4756
- function isBarcode(s) {
4757
- return /^\d+\.[A-Za-z]+$/.test(s);
4758
- }
4759
- __name(isBarcode, "isBarcode");
4760
- function parseDiffAndTitle(rawTokens) {
4761
- let diffStr = null;
4762
- let diffIndex = null;
4763
- const titleParts = [];
4764
- const fullMap = {
4765
- novice: "novice",
4766
- nov: "novice",
4767
- advanced: "advanced",
4768
- adv: "advanced",
4769
- exhaust: "exhaust",
4770
- exh: "exhaust",
4771
- infinite: "infinite",
4772
- inf: "infinite",
4773
- gravity: "infinite",
4774
- grv: "infinite",
4775
- heavenly: "infinite",
4776
- hvn: "infinite",
4777
- vivid: "infinite",
4778
- vvd: "infinite",
4779
- exceed: "infinite",
4780
- xcd: "infinite",
4781
- maximum: "maximum",
4782
- mxm: "maximum"
4783
- };
4784
- for (const tok of rawTokens) {
4785
- const trimmed = tok.trim();
4786
- if (!trimmed) continue;
4787
- if (diffIndex === null && /^[1-5]$/.test(trimmed)) {
4788
- diffIndex = Number(trimmed) - 1;
4789
- continue;
4883
+ async getScore(ctx, url, cardId, musicId, config) {
4884
+ const apiKey = ctx.globalConfig.maoApiKey;
4885
+ if (!apiKey) {
4886
+ this.logger.warn("maoApiKey not configured");
4887
+ throw new Error("maoApiKey not configured");
4790
4888
  }
4791
- const lettersOnly = trimmed.replace(/[^A-Za-z]/g, "");
4792
- if (lettersOnly) {
4793
- const byAbbr = getDiffStringFromAbbr(lettersOnly);
4794
- if (byAbbr) {
4795
- diffStr = byAbbr.diffStr;
4796
- continue;
4797
- }
4798
- const lower = lettersOnly.toLowerCase();
4799
- if (fullMap[lower]) {
4800
- diffStr = fullMap[lower];
4801
- continue;
4889
+ const resp = await ctx.http.get(`/bot/v2/sdvx/find?card=${cardId}&mid=${musicId}`, {
4890
+ baseURL: ctx.globalConfig.maoServerUrl,
4891
+ headers: { "X-API-Key": apiKey }
4892
+ });
4893
+ if (resp?.code !== 0 || !resp?.data) {
4894
+ const musicService2 = MusicService.getInstance(config);
4895
+ const musicData2 = await musicService2.getMusic(ctx, [musicId]);
4896
+ const music2 = musicData2.length > 0 ? musicData2[0] : null;
4897
+ const diffStr2 = "novice";
4898
+ const infVer2 = music2 ? music2.inf_ver : 2;
4899
+ let difficultyData2 = null;
4900
+ if (music2 && music2.difficulty && music2.difficulty.length > 0) {
4901
+ difficultyData2 = music2.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr2) || music2.difficulty[0];
4802
4902
  }
4903
+ return {
4904
+ music: {
4905
+ music_id: musicId,
4906
+ music_diff: difficultyData2 ? difficultyData2.difnum : 0,
4907
+ music_diff_name: getDiffName(diffStr2, infVer2),
4908
+ music_diff_full_name: getDiffFullName(diffStr2, infVer2),
4909
+ score: 0,
4910
+ exscore: 0,
4911
+ clear_type: this.getClearType(0),
4912
+ score_grade: getGradeByScore(0),
4913
+ btn_rate: "0",
4914
+ long_rate: "0",
4915
+ vol_rate: "0"
4916
+ },
4917
+ extra: {
4918
+ play_count: 0,
4919
+ update_at: "0",
4920
+ volforce: 0
4921
+ },
4922
+ music_data: music2,
4923
+ difficulty_data: difficultyData2
4924
+ };
4803
4925
  }
4804
- titleParts.push(trimmed);
4805
- }
4806
- return {
4807
- diffStr,
4808
- diffIndex,
4809
- titleQuery: titleParts.join(" ").trim()
4810
- };
4811
- }
4812
- __name(parseDiffAndTitle, "parseDiffAndTitle");
4813
- function getHighestDifstr(diffs) {
4814
- return diffs[diffs.length - 1].difstr;
4815
- }
4816
- __name(getHighestDifstr, "getHighestDifstr");
4817
- function chart(ctx, config, logger5) {
4818
- ctx.command("sdvx.chart [query:text]").alias("sdvx.c").action(async ({ session }, query) => {
4819
- if (!query) {
4820
- await session.send(session.text(".prompt"));
4821
- query = await session.prompt();
4822
- if (!query) return session.text("commands.timeout");
4823
- }
4926
+ const data = resp.data;
4927
+ const musicTypeNum = data.musicType;
4928
+ const diffStr = this.getDifficultyString(musicTypeNum);
4824
4929
  const musicService = MusicService.getInstance(config);
4825
- try {
4826
- let arrangement_mode = "normal";
4827
- let laser_l_color = "BLUE";
4828
- let laser_r_color = "RED";
4829
- const tokens = query.split(/\s+/).filter(Boolean);
4830
- const remaining = [];
4831
- for (const tok of tokens) {
4832
- const arr = mapArrangementToken(tok);
4833
- if (arr) {
4834
- arrangement_mode = arr;
4835
- continue;
4836
- }
4837
- const lc = mapLeftColorToken(tok);
4838
- if (lc) {
4839
- laser_l_color = lc;
4840
- continue;
4841
- }
4842
- const rc = mapRightColorToken(tok);
4843
- if (rc) {
4844
- laser_r_color = rc;
4845
- continue;
4846
- }
4847
- remaining.push(tok);
4848
- }
4849
- const rest = remaining.join(" ").trim();
4850
- if (!rest) {
4851
- return;
4852
- }
4853
- if (isBarcode(rest)) {
4854
- const [midRaw, diffAbbrRaw] = rest.split(".");
4855
- const music_id = Number(midRaw);
4856
- const { diffStr } = getDiffStringFromAbbr(diffAbbrRaw);
4857
- const difstr = diffStr;
4858
- const output_format = "PNG";
4859
- const payload = {
4860
- music_id,
4861
- difstr,
4862
- arrangement_mode,
4863
- rng_seed: null,
4864
- output_format,
4865
- laser_l_color,
4866
- laser_r_color,
4867
- px_per_second: 600,
4868
- column_height: 2800
4869
- };
4870
- const musicList = await musicService.getMusic(ctx, [music_id]);
4871
- session.send(
4872
- session.text(".drawing", {
4873
- name: musicList[0].title_name,
4874
- difstr
4875
- })
4876
- );
4877
- const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
4878
- headers: { "Content-Type": "application/json" }
4879
- });
4880
- return import_koishi12.h.image(res, "image/png");
4881
- } else {
4882
- const {
4883
- diffStr: parsedDiff,
4884
- diffIndex,
4885
- titleQuery
4886
- } = parseDiffAndTitle(remaining);
4887
- if (!titleQuery) return session.text(".no-result");
4888
- const musicInfo = await musicService.searchMusic(
4889
- ctx,
4890
- titleQuery
4891
- );
4892
- if (!musicInfo || musicInfo.length === 0) return session.text(".no-result");
4893
- const picked = musicInfo[0];
4894
- const music_id = Number(picked?.id);
4895
- if (!Number.isFinite(music_id)) {
4896
- logger5.warn("search result missing id", picked);
4897
- return session.text(".error");
4898
- }
4899
- let difstr = null;
4900
- if (parsedDiff) {
4901
- difstr = parsedDiff;
4902
- } else if (diffIndex !== null && Array.isArray(picked?.difficulty) && diffIndex >= 0 && diffIndex < picked.difficulty.length) {
4903
- const d = picked.difficulty[diffIndex];
4904
- difstr = d?.difstr ?? null;
4905
- }
4906
- if (!difstr) {
4907
- difstr = getHighestDifstr(picked?.difficulty);
4908
- }
4909
- const output_format = "PNG";
4910
- const payload = {
4911
- music_id,
4912
- difstr,
4913
- arrangement_mode,
4914
- rng_seed: null,
4915
- output_format,
4916
- laser_l_color,
4917
- laser_r_color,
4918
- px_per_second: 600,
4919
- column_height: 2800
4920
- };
4921
- session.send(
4922
- session.text(".drawing", {
4923
- name: picked.title_name,
4924
- difstr
4925
- })
4926
- );
4927
- const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
4928
- headers: { "Content-Type": "application/json" }
4929
- });
4930
- return import_koishi12.h.image(res, "image/png");
4931
- }
4932
- } catch (err) {
4933
- logger5.warn(err);
4934
- return session.text(".error");
4930
+ const musicData = await musicService.getMusic(ctx, [musicId]);
4931
+ const music = musicData.length > 0 ? musicData[0] : null;
4932
+ let difficultyData = null;
4933
+ if (music && music.difficulty && music.difficulty.length > 0) {
4934
+ difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
4935
4935
  }
4936
- });
4937
- }
4938
- __name(chart, "chart");
4939
-
4940
- // src/servers/index.ts
4941
- var servers_exports = {};
4942
- __export(servers_exports, {
4943
- ServerManager: () => ServerManager,
4944
- apply: () => apply9,
4945
- inject: () => inject2,
4946
- name: () => name9
4947
- });
4948
-
4949
- // src/servers/Asphyxia/index.ts
4950
- var import_koishi13 = require("koishi");
4951
-
4952
- // src/servers/Asphyxia/services/iidx-service.ts
4953
- var IIDXService = class _IIDXService {
4954
- static {
4955
- __name(this, "IIDXService");
4956
- }
4957
- static instance;
4958
- logger;
4959
- constructor(logger5) {
4960
- this.logger = logger5;
4936
+ const infVer = music ? music.inf_ver : 2;
4937
+ const scoreObj = {
4938
+ music: {
4939
+ music_id: musicId,
4940
+ music_diff: difficultyData ? difficultyData.difnum : 0,
4941
+ music_diff_name: getDiffName(diffStr, infVer),
4942
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
4943
+ score: data.score,
4944
+ exscore: data.exscore,
4945
+ clear_type: this.getClearType(data.clearType),
4946
+ score_grade: getGradeByScore(data.score),
4947
+ btn_rate: "0",
4948
+ long_rate: "0",
4949
+ vol_rate: "0"
4950
+ },
4951
+ extra: {
4952
+ play_count: data.playCount,
4953
+ update_at: "0",
4954
+ volforce: 0
4955
+ },
4956
+ music_data: music,
4957
+ difficulty_data: difficultyData
4958
+ };
4959
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
4960
+ return scoreObj;
4961
4961
  }
4962
- static getInstance(logger5) {
4963
- if (!_IIDXService.instance) {
4964
- _IIDXService.instance = new _IIDXService(logger5);
4962
+ async getRecentScores(ctx, url, cardId, config, count = 1) {
4963
+ const apiKey = ctx.globalConfig.maoApiKey;
4964
+ if (!apiKey) {
4965
+ this.logger.warn("maoApiKey not configured");
4966
+ return [];
4965
4967
  }
4966
- return _IIDXService.instance;
4967
- }
4968
- async fetchData(url) {
4969
- }
4970
- };
4971
-
4972
- // src/servers/utils/grade.ts
4973
- function getSDVXClearType(clearType) {
4974
- return clearType === 6 ? "MC" : clearType === 5 ? "PUC" : clearType === 4 ? "UC" : clearType === 3 ? "HC" : clearType === 2 ? "NC" : clearType === 1 ? "PLAYED" : "NO PLAY";
4975
- }
4976
- __name(getSDVXClearType, "getSDVXClearType");
4977
-
4978
- // src/servers/utils/xml.ts
4979
- var xml2js = __toESM(require("xml2js"), 1);
4980
- var xmlToJson = /* @__PURE__ */ __name((xml) => {
4981
- return new Promise((resolve3, reject) => {
4982
- xml2js.parseString(xml, (err, result) => {
4983
- if (err) {
4984
- reject(new Error("Error parsing XML: " + err));
4985
- } else {
4986
- resolve3(result);
4987
- }
4968
+ const resp = await ctx.http.get(`/bot/v2/sdvx/recent?card=${cardId}&count=${count}`, {
4969
+ baseURL: ctx.globalConfig.maoServerUrl,
4970
+ headers: { "X-API-Key": apiKey }
4988
4971
  });
4989
- });
4990
- }, "xmlToJson");
4991
-
4992
- // src/servers/Asphyxia/services/sdvx-service.ts
4993
- var SDVXService = class _SDVXService {
4994
- static {
4995
- __name(this, "SDVXService");
4996
- }
4997
- static instance;
4998
- logger;
4999
- constructor(logger5) {
5000
- this.logger = logger5;
5001
- }
5002
- static getInstance(logger5) {
5003
- if (!_SDVXService.instance) {
5004
- _SDVXService.instance = new _SDVXService(logger5);
4972
+ if (resp?.code !== 0 || !resp?.data || resp.data.length === 0) {
4973
+ return [];
5005
4974
  }
5006
- return _SDVXService.instance;
5007
- }
5008
- /**
5009
- * 获取 refid
5010
- */
5011
- async getRefid(ctx, url, model, cardId) {
5012
- const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
5013
- const xmlRequestBody = `<?xml version="1.0" encoding="UTF-8"?>
5014
- <call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
5015
- <cardmng cardid="${cardId}" cardtype="2" method="inquire" update="0" />
5016
- </call>`;
5017
- const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
5018
- const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
5019
- const jsonResponse = await xmlToJson(decodedResponse);
5020
- const cardmng = jsonResponse.response.cardmng?.[0]?.$;
5021
- if (!cardmng || !cardmng.refid) throw new Error("refid not found");
5022
- return cardmng.refid;
5023
- }
5024
- /**
5025
- * 获取用户名
5026
- */
5027
- async getUserName(ctx, url, cardId, model = "model") {
5028
- const refid = await this.getRefid(ctx, url, model, cardId);
5029
- const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
5030
- const xmlRequestBody = `<call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
5031
- <game method="sv6_load" ver="0">
5032
- <dataid __type="str">${refid}</dataid>
5033
- <cardid __type="str">${cardId}</cardid>
5034
- <refid __type="str">${refid}</refid>
5035
- <locid __type="str">ea</locid>
5036
- </game>
5037
- </call>`;
5038
- const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
5039
- const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
5040
- const jsonResponse = await xmlToJson(decodedResponse);
5041
- const name15 = jsonResponse.response.game?.[0]?.name?.[0]._;
5042
- if (!name15) throw new Error("User name not found");
5043
- return name15;
5044
- }
5045
- /**
5046
- * 获取所有分数
5047
- */
5048
- async getAllScore(ctx, url, cardId, config, model = "model") {
5049
- const refid = await this.getRefid(ctx, url, model, cardId);
5050
- const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
5051
- const xmlRequestBody = `<call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
5052
- <game method="sv6_load_m" ver="0">
5053
- <dataid __type="str">${refid}</dataid>
5054
- <cardid __type="str">${cardId}</cardid>
5055
- <refid __type="str">${refid}</refid>
5056
- <locid __type="str">ea</locid>
5057
- </game>
5058
- </call>`;
5059
- const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
5060
- const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
5061
- const jsonResponse = await xmlToJson(decodedResponse);
5062
- const infoList = jsonResponse.response.game?.[0]?.music?.[0]?.info || [];
5063
- if (!Array.isArray(infoList)) return [];
5064
- const scoresRaw = infoList.map((item) => {
5065
- const paramStr = item.param?.[0]?._ || item.param?.[0];
5066
- return paramStr ? paramStr.split(" ").map(Number) : [];
5067
- }).filter((arr) => arr.length >= 11);
5068
- const musicIds = scoresRaw.map((arr) => arr[0]);
4975
+ const musicIds = resp.data.map((item) => item.mid);
5069
4976
  const musicService = MusicService.getInstance(config);
5070
4977
  const musicDataList = await musicService.getMusic(ctx, musicIds);
5071
- const musicDataMap = new Map(musicDataList.map((m) => [m.id, m]));
5072
- return scoresRaw.map((arr) => {
5073
- const [
5074
- music_id,
5075
- music_diff,
5076
- score,
5077
- exscore,
5078
- clear_type,
5079
- score_grade,
5080
- ,
5081
- ,
5082
- btn_rate,
5083
- long_rate,
5084
- vol_rate
5085
- ] = arr;
5086
- const music = musicDataMap.get(music_id) || null;
5087
- const diffStr = this.getDifficultyString(music_diff);
4978
+ const musicDataMap = new Map(musicDataList.map((music) => [music.id, music]));
4979
+ const scores = resp.data.map((item) => {
4980
+ const music = musicDataMap.get(item.mid);
4981
+ const diffStr = this.getDifficultyString(item.musicType);
5088
4982
  let difficultyData = null;
5089
4983
  if (music && music.difficulty && music.difficulty.length > 0) {
5090
4984
  difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
@@ -5092,71 +4986,37 @@ var SDVXService = class _SDVXService {
5092
4986
  const infVer = music ? music.inf_ver : 2;
5093
4987
  const scoreObj = {
5094
4988
  music: {
5095
- music_id,
4989
+ music_id: item.mid,
5096
4990
  music_diff: difficultyData ? difficultyData.difnum : 0,
5097
4991
  music_diff_name: getDiffName(diffStr, infVer),
5098
4992
  music_diff_full_name: getDiffFullName(diffStr, infVer),
5099
- score,
5100
- exscore,
5101
- clear_type: getSDVXClearType(clear_type),
5102
- score_grade: getGradeByScore(score),
5103
- btn_rate: String(btn_rate),
5104
- long_rate: String(long_rate),
5105
- vol_rate: String(vol_rate)
4993
+ score: item.score,
4994
+ exscore: item.exscore,
4995
+ clear_type: this.getClearType(item.clearType),
4996
+ score_grade: getGradeByScore(item.score),
4997
+ btn_rate: "0",
4998
+ long_rate: "0",
4999
+ vol_rate: "0"
5106
5000
  },
5107
5001
  extra: {
5002
+ volforce: 0,
5108
5003
  play_count: 0,
5109
- update_at: "",
5110
- volforce: 0
5004
+ update_at: item.time
5111
5005
  },
5112
- music_data: music,
5113
- difficulty_data: difficultyData
5006
+ music_data: music || null,
5007
+ difficulty_data: difficultyData || null
5114
5008
  };
5115
5009
  scoreObj.extra.volforce = calculateVolforce(scoreObj);
5116
5010
  return scoreObj;
5117
5011
  });
5118
- }
5119
- /**
5120
- * 难度字符串映射
5121
- */
5122
- getDifficultyString(diffType) {
5123
- const diffStrMap = {
5124
- 0: "novice",
5125
- 1: "advanced",
5126
- 2: "exhaust",
5127
- 3: "infinite",
5128
- 4: "maximum",
5129
- 5: "ultimate"
5130
- };
5131
- return diffStrMap[diffType] || "novice";
5132
- }
5133
- };
5134
-
5135
- // src/servers/Asphyxia/index.ts
5136
- var Asphyxia = class {
5137
- static {
5138
- __name(this, "Asphyxia");
5139
- }
5140
- name = "asphyxia";
5141
- supportedGames = ["sdvx", "iidx"];
5142
- gameServices = {};
5143
- logger = new import_koishi13.Logger("Noah-Asphyxia");
5144
- /**
5145
- * 初始化各个游戏服务实例
5146
- */
5147
- constructor() {
5148
- this.gameServices["sdvx"] = SDVXService.getInstance(this.logger);
5149
- this.gameServices["iidx"] = IIDXService.getInstance(this.logger);
5012
+ return scores;
5150
5013
  }
5151
5014
  };
5152
5015
 
5153
- // src/servers/Mao/index.ts
5154
- var import_koishi14 = require("koishi");
5155
-
5156
- // src/servers/Mao/services/sdvx-service.ts
5157
- var SDVXService2 = class _SDVXService {
5016
+ // src/games/sdvx/adapters/official.ts
5017
+ var OfficialSDVXService = class _OfficialSDVXService {
5158
5018
  static {
5159
- __name(this, "SDVXService");
5019
+ __name(this, "OfficialSDVXService");
5160
5020
  }
5161
5021
  static instance;
5162
5022
  logger;
@@ -5164,577 +5024,651 @@ var SDVXService2 = class _SDVXService {
5164
5024
  this.logger = logger5;
5165
5025
  }
5166
5026
  static getInstance(logger5) {
5167
- if (!_SDVXService.instance) {
5168
- _SDVXService.instance = new _SDVXService(logger5);
5027
+ if (!_OfficialSDVXService.instance) {
5028
+ _OfficialSDVXService.instance = new _OfficialSDVXService(logger5);
5169
5029
  }
5170
- return _SDVXService.instance;
5030
+ return _OfficialSDVXService.instance;
5171
5031
  }
5172
- /**
5173
- * Get the difficulty string based on the difficulty type number
5174
- * @param diffType Difficulty type
5175
- * @returns The difficulty string (novice, advanced, exhaust, infinite, maximum)
5176
- */
5177
- getDifficultyString(diffType) {
5178
- const diffStrMap = {
5179
- 0: "novice",
5180
- 1: "advanced",
5181
- 2: "exhaust",
5182
- 3: "infinite",
5183
- 4: "maximum",
5184
- 5: "ultimate"
5032
+ mapClearType(mark) {
5033
+ const markMap = {
5034
+ "s-puc": "S-PUC",
5035
+ spuc: "S-PUC",
5036
+ puc: "PUC",
5037
+ uc: "UC",
5038
+ mc: "MC",
5039
+ hc: "HC",
5040
+ nc: "NC",
5041
+ none: "PLAYED"
5185
5042
  };
5186
- return diffStrMap[diffType] || "novice";
5043
+ return markMap[mark.toLowerCase()] || "PLAYED";
5187
5044
  }
5188
- /**
5189
- * Get the user name/ID from the server response
5190
- * @param ctx Context object
5191
- * @param url Base URL for the API
5192
- * @param cardId Card ID of the player
5193
- * @returns The player ID (e.g., "ED*")
5194
- */
5195
- async getUserName(ctx, url, cardId) {
5196
- const response = await ctx.http.get(`/my?card=${cardId}`, {
5197
- baseURL: ctx.globalConfig.maoServerUrl,
5198
- responseType: "text"
5045
+ async getUserName(ctx, url, player_id) {
5046
+ const response = await ctx.http.get(`/api/sdvx/profile?id=${player_id}`, {
5047
+ baseURL: url,
5048
+ responseType: "json"
5199
5049
  });
5200
- const match = response.match(/猫网玩家\[([^\]]+)\]/);
5201
- if (!match || !match[1]) {
5202
- throw new Error("Failed to extract player ID from response");
5203
- }
5204
- return match[1];
5050
+ return response.player_name || player_id;
5205
5051
  }
5206
- /**
5207
- * 验证猫网 PIN 码
5208
- * @param ctx - Koishi 上下文对象
5209
- * @param url - 猫网基础 URL
5210
- * @param cardId - 卡号
5211
- * @param pin - 四位数字 PIN
5212
- * @returns 验证是否通过
5213
- */
5214
- async verifyPin(ctx, url, cardId, pin) {
5215
- const apiKey = ctx.globalConfig.maoApiKey;
5216
- if (!apiKey) {
5217
- this.logger.warn("maoApiKey not configured");
5218
- return false;
5219
- }
5220
- const resp = await ctx.http.get(
5221
- `/bot/v2/player/card?card=${cardId}`,
5222
- {
5223
- baseURL: ctx.globalConfig.maoServerUrl,
5224
- headers: { "X-API-Key": apiKey }
5052
+ async getAllScore(ctx, url, player_id, config) {
5053
+ try {
5054
+ const response = await ctx.http.get(`/api/sdvx/scores?id=${player_id}`, {
5055
+ baseURL: url,
5056
+ responseType: "json"
5057
+ });
5058
+ const scoresData = response;
5059
+ const stringMusicTitles = [...new Set(scoresData.map((score) => score.title))];
5060
+ const musicService = MusicService.getInstance(config);
5061
+ const [musicDataList, titleMidPairs] = await Promise.all([
5062
+ musicService.getMusic(ctx, stringMusicTitles),
5063
+ musicService.extTitleToMid(ctx, stringMusicTitles)
5064
+ ]);
5065
+ const titleMidMap = new Map(
5066
+ titleMidPairs.filter(({ mid }) => !!mid).map(({ title, mid }) => [title.toLowerCase(), mid])
5067
+ );
5068
+ const musicDataMap = new Map(musicDataList.map((music) => [music.title_name, music]));
5069
+ const sdvxScores = [];
5070
+ for (const scoreData of scoresData) {
5071
+ const musicData = musicDataMap.get(scoreData.title);
5072
+ const mappedMid = titleMidMap.get(scoreData.title.toLowerCase());
5073
+ const musicId = mappedMid && /^\d+$/.test(mappedMid) ? Number(mappedMid) : scoreData.music_id;
5074
+ for (const [diffKey, diffScore] of Object.entries(scoreData.difficulties)) {
5075
+ const diffAbbr = diffKey.toUpperCase();
5076
+ const diffInfo = getDiffStringFromAbbr(diffAbbr);
5077
+ const diffStr = diffInfo?.diffStr || diffKey.toLowerCase();
5078
+ const infVer = diffInfo?.infVer ?? musicData?.inf_ver ?? 0;
5079
+ let difficultyData = null;
5080
+ if (musicData && musicData.difficulty) {
5081
+ difficultyData = musicData.difficulty.find(
5082
+ (d) => d.difstr.toLowerCase() === diffStr.toLowerCase()
5083
+ ) || null;
5084
+ }
5085
+ const scoreObj = {
5086
+ music: {
5087
+ music_id: musicId,
5088
+ music_diff: difficultyData?.difnum || 0,
5089
+ music_diff_name: getDiffName(diffStr, infVer),
5090
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
5091
+ score: diffScore.score,
5092
+ exscore: 0,
5093
+ clear_type: this.mapClearType(diffScore.mark),
5094
+ score_grade: diffScore.grade,
5095
+ btn_rate: "0",
5096
+ long_rate: "0",
5097
+ vol_rate: "0"
5098
+ },
5099
+ extra: {
5100
+ volforce: 0,
5101
+ play_count: 0,
5102
+ update_at: ""
5103
+ },
5104
+ music_data: musicData || null,
5105
+ difficulty_data: difficultyData || null
5106
+ };
5107
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
5108
+ sdvxScores.push(scoreObj);
5109
+ }
5225
5110
  }
5226
- );
5227
- return resp?.code === 0 && resp?.data?.password === pin;
5228
- }
5229
- clearTypeToNum(clearType) {
5230
- switch (clearType) {
5231
- case "MC":
5232
- return 6;
5233
- case "PUC":
5234
- case "S-PUC":
5235
- return 5;
5236
- case "UC":
5237
- return 4;
5238
- case "HC":
5239
- return 3;
5240
- case "NC":
5241
- return 2;
5242
- case "PLAYED":
5243
- return 1;
5244
- default:
5245
- return 0;
5111
+ return sdvxScores;
5112
+ } catch (error) {
5113
+ if (error.response?.status === 404) {
5114
+ return [];
5115
+ }
5116
+ throw error;
5246
5117
  }
5247
5118
  }
5248
- diffNameToMusicType(diffName) {
5249
- switch (diffName.toUpperCase()) {
5250
- case "NOV":
5251
- return 0;
5252
- case "ADV":
5253
- return 1;
5254
- case "EXH":
5255
- return 2;
5256
- case "INF":
5257
- case "GRV":
5258
- case "HVN":
5259
- case "VVD":
5260
- case "XCD":
5261
- return 3;
5262
- case "MXM":
5263
- return 4;
5264
- case "ULT":
5265
- return 5;
5266
- default:
5267
- return 0;
5119
+ async getScore(ctx, url, player_id, musicId, config) {
5120
+ const allScores = await this.getAllScore(ctx, url, player_id, config);
5121
+ const score = allScores.find((s) => s.music.music_id === musicId);
5122
+ if (!score) {
5123
+ throw new Error(`Score not found for music ID: ${musicId}`);
5268
5124
  }
5125
+ return score;
5269
5126
  }
5270
- async uploadScore(ctx, url, cardId, scores) {
5271
- const apiKey = ctx.globalConfig.maoApiKey;
5272
- if (!apiKey) {
5273
- this.logger.warn("maoApiKey not configured");
5274
- return false;
5275
- }
5276
- const payload = scores.filter((s) => {
5277
- const musicId = Number(s.music.music_id);
5278
- return musicId > 0 && s.music.music_diff_name;
5279
- }).map((s) => ({
5280
- music_id: Number(s.music.music_id),
5281
- music_type: this.diffNameToMusicType(s.music.music_diff_name),
5282
- score: s.music.score,
5283
- exscore: s.music.exscore,
5284
- clear_type: this.clearTypeToNum(s.music.clear_type)
5285
- }));
5286
- if (payload.length === 0) return false;
5287
- const resp = await ctx.http.post(
5288
- "/bot/v2/sdvx/upload",
5289
- { card: cardId, scores: payload },
5290
- {
5291
- baseURL: ctx.globalConfig.maoServerUrl,
5292
- headers: { "X-API-Key": apiKey }
5293
- }
5294
- );
5295
- return resp?.code === 0;
5127
+ async getRecentScores(ctx, url, player_id, config, count = 1) {
5128
+ return [];
5296
5129
  }
5297
- async getAllScore(ctx, url, cardId, config) {
5298
- try {
5299
- const apiKey = ctx.globalConfig.maoApiKey;
5300
- if (!apiKey) {
5301
- this.logger.warn("maoApiKey not configured");
5302
- return [];
5130
+ };
5131
+
5132
+ // src/games/sdvx/adapters/index.ts
5133
+ function registerSDVXAdapters(logger5, config) {
5134
+ const serverManager = ServerManager.getInstance();
5135
+ serverManager.registerGameService(
5136
+ "asphyxia",
5137
+ "sdvx",
5138
+ AsphyxiaSDVXService.getInstance(logger5, config)
5139
+ );
5140
+ serverManager.registerGameService("mao", "sdvx", MaoSDVXService.getInstance(logger5));
5141
+ serverManager.registerGameService("official", "sdvx", OfficialSDVXService.getInstance(logger5));
5142
+ }
5143
+ __name(registerSDVXAdapters, "registerSDVXAdapters");
5144
+
5145
+ // src/games/sdvx/command.ts
5146
+ var command_exports2 = {};
5147
+ __export(command_exports2, {
5148
+ apply: () => apply10,
5149
+ name: () => name10
5150
+ });
5151
+
5152
+ // src/games/sdvx/commands/calculate.ts
5153
+ var import_koishi14 = require("koishi");
5154
+
5155
+ // src/games/sdvx/utils/param-parser.ts
5156
+ function parseNumberWithSuffix(str) {
5157
+ const num = parseInt(str, 10);
5158
+ if (str.endsWith("w")) {
5159
+ return num * 1e4;
5160
+ } else if (str.endsWith("k")) {
5161
+ return num * 1e3;
5162
+ }
5163
+ return num;
5164
+ }
5165
+ __name(parseNumberWithSuffix, "parseNumberWithSuffix");
5166
+ function isValidLevel(level) {
5167
+ if (level < 1 || level > 20.9) {
5168
+ return false;
5169
+ }
5170
+ const integerPart = Math.floor(level);
5171
+ const decimalPart = level - integerPart;
5172
+ if (integerPart >= 1 && integerPart <= 16) {
5173
+ return decimalPart === 0;
5174
+ }
5175
+ if (integerPart === 17) {
5176
+ return decimalPart === 0 || decimalPart === 0.5;
5177
+ }
5178
+ if (integerPart >= 18 && integerPart <= 20) {
5179
+ const tenths = Math.round(decimalPart * 10);
5180
+ return Math.abs(decimalPart * 10 - tenths) < 1e-4 && tenths >= 0 && tenths <= 9;
5181
+ }
5182
+ return false;
5183
+ }
5184
+ __name(isValidLevel, "isValidLevel");
5185
+ function generateValidLevelRange(start, end) {
5186
+ const levels = [];
5187
+ const startInt = Math.floor(start);
5188
+ const endInt = Math.floor(end);
5189
+ const startTenth = Math.round((start - startInt) * 10);
5190
+ const endTenth = Math.round((end - endInt) * 10);
5191
+ for (let intPart = startInt; intPart <= endInt; intPart++) {
5192
+ if (intPart >= 1 && intPart <= 16) {
5193
+ if (intPart === startInt && startTenth > 0 || intPart === endInt && endTenth > 0) {
5194
+ continue;
5303
5195
  }
5304
- const resp = await ctx.http.get(`/bot/v2/sdvx/scores?card=${cardId}`, {
5305
- baseURL: ctx.globalConfig.maoServerUrl,
5306
- headers: { "X-API-Key": apiKey }
5307
- });
5308
- if (resp?.code !== 0 || !resp?.data) {
5309
- return [];
5196
+ levels.push(intPart);
5197
+ } else if (intPart === 17) {
5198
+ const currentStartTenth = intPart === startInt ? startTenth : 0;
5199
+ const currentEndTenth = intPart === endInt ? endTenth : 5;
5200
+ if (currentStartTenth <= 0 && currentEndTenth >= 0) {
5201
+ levels.push(17);
5310
5202
  }
5311
- const filteredData = resp.data.filter(
5312
- (score) => score.mid !== 244 && score.mid !== 1759 && score.mid !== 1761 && score.isPlus === 0
5313
- );
5314
- const musicIds = filteredData.map((score) => score.mid);
5315
- const diffTypes = filteredData.map((score) => score.musicType);
5316
- const musicService = MusicService.getInstance(config);
5317
- const musicData = await musicService.getMusic(ctx, musicIds);
5318
- const musicDataMap = new Map(musicData.map((music) => [music.id, music]));
5319
- const scores = filteredData.map((score, index) => {
5320
- const musicId = score.mid;
5321
- const music = musicDataMap.get(musicId);
5322
- const musicDiffType = score.musicType;
5323
- const diffStr = this.getDifficultyString(musicDiffType);
5324
- let difficultyData = null;
5325
- if (music && music.difficulty && music.difficulty.length > 0) {
5326
- difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
5327
- }
5328
- const infVer = music ? music.inf_ver : 2;
5329
- const scoreObj = {
5330
- music: {
5331
- music_id: musicId,
5332
- music_diff: difficultyData ? difficultyData.difnum : 0,
5333
- music_diff_name: getDiffName(diffStr, infVer),
5334
- music_diff_full_name: getDiffFullName(diffStr, infVer),
5335
- score: score.score,
5336
- exscore: score.exscore,
5337
- clear_type: getSDVXClearType(score.clearType),
5338
- score_grade: getGradeByScore(score.score),
5339
- btn_rate: score.btnRate.toString(),
5340
- long_rate: score.longRate.toString(),
5341
- vol_rate: score.volRate.toString()
5342
- },
5343
- extra: {
5344
- play_count: score.playCount,
5345
- update_at: score.updateAt,
5346
- volforce: 0
5347
- },
5348
- music_data: music || null,
5349
- difficulty_data: difficultyData || null
5350
- };
5351
- scoreObj.extra.volforce = calculateVolforce(scoreObj);
5352
- return scoreObj;
5353
- });
5354
- return scores;
5355
- } catch (error) {
5356
- if (error.response?.status === 404) {
5357
- return [];
5203
+ if (currentStartTenth <= 5 && currentEndTenth >= 5) {
5204
+ levels.push(17.5);
5205
+ }
5206
+ } else if (intPart >= 18 && intPart <= 20) {
5207
+ const currentStartTenth = intPart === startInt ? startTenth : 0;
5208
+ const currentEndTenth = intPart === endInt ? endTenth : 9;
5209
+ for (let tenth = currentStartTenth; tenth <= currentEndTenth && tenth <= 9; tenth++) {
5210
+ levels.push(intPart + tenth / 10);
5358
5211
  }
5359
- throw error;
5360
5212
  }
5361
5213
  }
5362
- async getScore(ctx, url, cardId, musicId, config) {
5363
- const apiKey = ctx.globalConfig.maoApiKey;
5364
- if (!apiKey) {
5365
- this.logger.warn("maoApiKey not configured");
5366
- throw new Error("maoApiKey not configured");
5367
- }
5368
- const resp = await ctx.http.get(`/bot/v2/sdvx/find?card=${cardId}&mid=${musicId}`, {
5369
- baseURL: ctx.globalConfig.maoServerUrl,
5370
- headers: { "X-API-Key": apiKey }
5371
- });
5372
- if (resp?.code !== 0 || !resp?.data) {
5373
- const musicService2 = MusicService.getInstance(config);
5374
- const musicData2 = await musicService2.getMusic(ctx, [musicId]);
5375
- const music2 = musicData2.length > 0 ? musicData2[0] : null;
5376
- const diffStr2 = "novice";
5377
- const infVer2 = music2 ? music2.inf_ver : 2;
5378
- let difficultyData2 = null;
5379
- if (music2 && music2.difficulty && music2.difficulty.length > 0) {
5380
- difficultyData2 = music2.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr2) || music2.difficulty[0];
5214
+ return levels;
5215
+ }
5216
+ __name(generateValidLevelRange, "generateValidLevelRange");
5217
+ function parseLevelRange(item) {
5218
+ if (/^\d{1,2}(\.\d)?-\d{1,2}(\.\d)?$/.test(item)) {
5219
+ const [startRaw, endRaw] = item.split("-");
5220
+ const start = parseFloat(startRaw.includes(".") ? startRaw : startRaw + ".0");
5221
+ const end = parseFloat(endRaw.includes(".") ? endRaw : endRaw + ".0");
5222
+ if (start >= 1 && end <= 20.9 && start <= end) {
5223
+ if (!isValidLevel(start) || !isValidLevel(end)) {
5224
+ return null;
5381
5225
  }
5382
- return {
5383
- music: {
5384
- music_id: musicId,
5385
- music_diff: difficultyData2 ? difficultyData2.difnum : 0,
5386
- music_diff_name: getDiffName(diffStr2, infVer2),
5387
- music_diff_full_name: getDiffFullName(diffStr2, infVer2),
5388
- score: 0,
5389
- exscore: 0,
5390
- clear_type: getSDVXClearType(0),
5391
- // NO PLAY
5392
- score_grade: getGradeByScore(0),
5393
- btn_rate: "0",
5394
- long_rate: "0",
5395
- vol_rate: "0"
5396
- },
5397
- extra: {
5398
- play_count: 0,
5399
- update_at: "0",
5400
- volforce: 0
5401
- },
5402
- music_data: music2,
5403
- difficulty_data: difficultyData2
5404
- };
5226
+ return generateValidLevelRange(start, end);
5405
5227
  }
5406
- const data = resp.data;
5407
- const musicTypeNum = data.musicType;
5408
- const diffStr = this.getDifficultyString(musicTypeNum);
5409
- const musicService = MusicService.getInstance(config);
5410
- const musicData = await musicService.getMusic(ctx, [musicId]);
5411
- const music = musicData.length > 0 ? musicData[0] : null;
5412
- let difficultyData = null;
5413
- if (music && music.difficulty && music.difficulty.length > 0) {
5414
- difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
5228
+ }
5229
+ if (/^\d{1,2}(\.\d)?$/.test(item)) {
5230
+ const level = parseFloat(item.includes(".") ? item : item + ".0");
5231
+ if (isValidLevel(level)) {
5232
+ return [level];
5415
5233
  }
5416
- const infVer = music ? music.inf_ver : 2;
5417
- const scoreObj = {
5418
- music: {
5419
- music_id: musicId,
5420
- music_diff: difficultyData ? difficultyData.difnum : 0,
5421
- music_diff_name: getDiffName(diffStr, infVer),
5422
- music_diff_full_name: getDiffFullName(diffStr, infVer),
5423
- score: data.score,
5424
- exscore: data.exscore,
5425
- clear_type: getSDVXClearType(data.clearType),
5426
- score_grade: getGradeByScore(data.score),
5427
- btn_rate: "0",
5428
- long_rate: "0",
5429
- vol_rate: "0"
5430
- },
5431
- extra: {
5432
- play_count: data.playCount,
5433
- update_at: "0",
5434
- volforce: 0
5435
- },
5436
- music_data: music,
5437
- difficulty_data: difficultyData
5438
- };
5439
- scoreObj.extra.volforce = calculateVolforce(scoreObj);
5440
- return scoreObj;
5441
5234
  }
5442
- async getRecentScores(ctx, url, cardId, config, count = 1) {
5443
- const apiKey = ctx.globalConfig.maoApiKey;
5444
- if (!apiKey) {
5445
- this.logger.warn("maoApiKey not configured");
5446
- return [];
5235
+ return null;
5236
+ }
5237
+ __name(parseLevelRange, "parseLevelRange");
5238
+ function parseScoreRange(item) {
5239
+ if (/^\d+[wk]?-\d+[wk]?$/.test(item)) {
5240
+ const [startRaw, endRaw] = item.split("-");
5241
+ const start = parseNumberWithSuffix(startRaw);
5242
+ const end = parseNumberWithSuffix(endRaw);
5243
+ if (start >= 0 && end <= 1e7 && start <= end) {
5244
+ return [start, end];
5447
5245
  }
5448
- const resp = await ctx.http.get(`/bot/v2/sdvx/recent?card=${cardId}&count=${count}`, {
5449
- baseURL: ctx.globalConfig.maoServerUrl,
5450
- headers: { "X-API-Key": apiKey }
5451
- });
5452
- if (resp?.code !== 0 || !resp?.data || resp.data.length === 0) {
5453
- return [];
5246
+ }
5247
+ if (/^\d+[wk]?$/.test(item)) {
5248
+ const score = parseNumberWithSuffix(item);
5249
+ const hasSuffix = /[wk]$/.test(item);
5250
+ if (score >= 0 && score <= 1e7 && (score >= 100 || hasSuffix)) {
5251
+ return [score];
5454
5252
  }
5455
- const musicIds = resp.data.map((item) => item.mid);
5456
- const musicService = MusicService.getInstance(config);
5457
- const musicDataList = await musicService.getMusic(ctx, musicIds);
5458
- const musicDataMap = new Map(musicDataList.map((music) => [music.id, music]));
5459
- const scores = resp.data.map((item) => {
5460
- const music = musicDataMap.get(item.mid);
5461
- const diffStr = this.getDifficultyString(item.musicType);
5462
- let difficultyData = null;
5463
- if (music && music.difficulty && music.difficulty.length > 0) {
5464
- difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
5465
- }
5466
- const infVer = music ? music.inf_ver : 2;
5467
- const scoreObj = {
5468
- music: {
5469
- music_id: item.mid,
5470
- music_diff: difficultyData ? difficultyData.difnum : 0,
5471
- music_diff_name: getDiffName(diffStr, infVer),
5472
- music_diff_full_name: getDiffFullName(diffStr, infVer),
5473
- score: item.score,
5474
- exscore: item.exscore,
5475
- clear_type: getSDVXClearType(item.clearType),
5476
- score_grade: getGradeByScore(item.score),
5477
- btn_rate: "0",
5478
- long_rate: "0",
5479
- vol_rate: "0"
5480
- },
5481
- extra: {
5482
- volforce: 0,
5483
- play_count: 0,
5484
- update_at: item.time
5485
- },
5486
- music_data: music || null,
5487
- difficulty_data: difficultyData || null
5488
- };
5489
- scoreObj.extra.volforce = calculateVolforce(scoreObj);
5490
- return scoreObj;
5491
- });
5492
- return scores;
5493
5253
  }
5494
- };
5495
-
5496
- // src/servers/Mao/index.ts
5497
- var Mao = class {
5498
- static {
5499
- __name(this, "Mao");
5254
+ return null;
5255
+ }
5256
+ __name(parseScoreRange, "parseScoreRange");
5257
+ function parseVfValue(item) {
5258
+ if (/^\d{1,2}\.\d{2}-\d{1,2}\.\d{2}$/.test(item)) {
5259
+ const [start, end] = item.split("-").map(parseFloat);
5260
+ if (start >= 0 && end <= 99 && start <= end) {
5261
+ return [start, end];
5262
+ }
5500
5263
  }
5501
- name = "mao";
5502
- supportedGames = ["sdvx"];
5503
- gameServices = {};
5504
- logger = new import_koishi14.Logger("Noah-Mao");
5505
- /**
5506
- * 初始化SDVX游戏服务实例
5507
- */
5508
- constructor() {
5509
- this.gameServices["sdvx"] = SDVXService2.getInstance(this.logger);
5264
+ if (/^\d{1,2}\.\d{2}$/.test(item)) {
5265
+ const vf2 = parseFloat(item);
5266
+ if (vf2 >= 0 && vf2 <= 99) {
5267
+ return vf2;
5268
+ }
5510
5269
  }
5511
- };
5512
-
5513
- // src/servers/Official/index.ts
5514
- var import_koishi15 = require("koishi");
5515
-
5516
- // src/servers/Official/services/sdvx-service.ts
5517
- var SDVXService3 = class _SDVXService {
5518
- static {
5519
- __name(this, "SDVXService");
5270
+ return null;
5271
+ }
5272
+ __name(parseVfValue, "parseVfValue");
5273
+ function parseClearTypeRange(item) {
5274
+ if (item.includes("-")) {
5275
+ const [startRaw, endRaw] = item.split("-").filter(Boolean);
5276
+ if (startRaw && endRaw && CLEAR_TYPE_ABBR_MAP[startRaw] && CLEAR_TYPE_ABBR_MAP[endRaw]) {
5277
+ const start = ALL_CLEAR_TYPES.findIndex((t) => t === CLEAR_TYPE_ABBR_MAP[startRaw]);
5278
+ const end = ALL_CLEAR_TYPES.findIndex((t) => t === CLEAR_TYPE_ABBR_MAP[endRaw]);
5279
+ if (start !== -1 && end !== -1) {
5280
+ const from = Math.min(start, end);
5281
+ const to = Math.max(start, end);
5282
+ const clearTypes = [];
5283
+ for (let i = from; i <= to; i++) {
5284
+ clearTypes.push(ALL_CLEAR_TYPES[i]);
5285
+ }
5286
+ if (clearTypes.includes("PUC") && !clearTypes.includes("S-PUC")) {
5287
+ clearTypes.push("S-PUC");
5288
+ }
5289
+ return clearTypes;
5290
+ }
5291
+ }
5520
5292
  }
5521
- static instance;
5522
- logger;
5523
- constructor(logger5) {
5524
- this.logger = logger5;
5293
+ return null;
5294
+ }
5295
+ __name(parseClearTypeRange, "parseClearTypeRange");
5296
+ function parseSingleClearType(item) {
5297
+ if (CLEAR_TYPE_ABBR_MAP[item.toLowerCase()]) {
5298
+ const clearType = CLEAR_TYPE_ABBR_MAP[item.toLowerCase()];
5299
+ if (clearType === "PUC") {
5300
+ return ["PUC", "S-PUC"];
5301
+ }
5302
+ return [clearType];
5525
5303
  }
5526
- static getInstance(logger5) {
5527
- if (!_SDVXService.instance) {
5528
- _SDVXService.instance = new _SDVXService(logger5);
5304
+ return null;
5305
+ }
5306
+ __name(parseSingleClearType, "parseSingleClearType");
5307
+ function parseGradeRange(item) {
5308
+ if (/^[a-ds][a-z+]*-[a-ds][a-z+]*$/.test(item)) {
5309
+ const [startRaw, endRaw] = item.split("-");
5310
+ const start = ALL_GRADES.findIndex((g) => g.toLowerCase() === startRaw);
5311
+ const end = ALL_GRADES.findIndex((g) => g.toLowerCase() === endRaw);
5312
+ if (start !== -1 && end !== -1) {
5313
+ const from = Math.min(start, end);
5314
+ const to = Math.max(start, end);
5315
+ const grades = [];
5316
+ for (let i = from; i <= to; i++) {
5317
+ grades.push(ALL_GRADES[i]);
5318
+ }
5319
+ return grades;
5529
5320
  }
5530
- return _SDVXService.instance;
5531
5321
  }
5532
- /**
5533
- * 映射 mark 到 SDVXClearType
5534
- */
5535
- mapClearType(mark) {
5536
- const markMap = {
5537
- "s-puc": "S-PUC",
5538
- spuc: "S-PUC",
5539
- puc: "PUC",
5540
- uc: "UC",
5541
- mc: "MC",
5542
- hc: "HC",
5543
- nc: "NC",
5544
- none: "PLAYED"
5545
- };
5546
- return markMap[mark.toLowerCase()] || "PLAYED";
5322
+ return null;
5323
+ }
5324
+ __name(parseGradeRange, "parseGradeRange");
5325
+ function parseSingleGrade(item) {
5326
+ const grade = ALL_GRADES.find((g) => g.toLowerCase() === item.toLowerCase());
5327
+ if (grade) {
5328
+ return [grade];
5547
5329
  }
5548
- /**
5549
- * Get the user name/ID from the server response
5550
- * @param ctx Context object
5551
- * @param url Base Official Support URL for the API
5552
- * @param player_id Player ID of the player (SV-xxxx-xxxx format)
5553
- * @returns The player name
5554
- */
5555
- async getUserName(ctx, url, player_id) {
5556
- const response = await ctx.http.get(`/api/sdvx/profile?id=${player_id}`, {
5557
- baseURL: url,
5558
- responseType: "json"
5559
- });
5560
- return response.player_name || player_id;
5330
+ return null;
5331
+ }
5332
+ __name(parseSingleGrade, "parseSingleGrade");
5333
+ function parseRadarFeature(item) {
5334
+ const radarList = ["notes", "peak", "tsumami", "tricky", "hand_trip", "one_hand"];
5335
+ if (radarList.includes(item)) {
5336
+ return item;
5561
5337
  }
5562
- async getAllScore(ctx, url, player_id, config) {
5338
+ return null;
5339
+ }
5340
+ __name(parseRadarFeature, "parseRadarFeature");
5341
+ function parseQueryInput(input) {
5342
+ const params = {};
5343
+ if (!input || input.trim() === "") {
5344
+ return params;
5345
+ }
5346
+ const items = input.split(/\s+/).map((s) => s.trim()).filter(Boolean);
5347
+ for (const item of items) {
5348
+ const vf2 = parseVfValue(item);
5349
+ if (vf2 !== null) {
5350
+ if (params.vf !== void 0) {
5351
+ const existing = Array.isArray(params.vf) ? params.vf : [params.vf];
5352
+ const newVf = Array.isArray(vf2) ? vf2 : [vf2];
5353
+ params.vf = [...existing, ...newVf];
5354
+ } else {
5355
+ params.vf = vf2;
5356
+ }
5357
+ continue;
5358
+ }
5359
+ const levels = parseLevelRange(item);
5360
+ if (levels !== null) {
5361
+ if (params.level !== void 0) {
5362
+ const existing = Array.isArray(params.level) ? params.level : [params.level];
5363
+ params.level = [.../* @__PURE__ */ new Set([...existing, ...levels])];
5364
+ } else {
5365
+ params.level = levels.length === 1 ? levels[0] : levels;
5366
+ }
5367
+ continue;
5368
+ }
5369
+ const scores = parseScoreRange(item);
5370
+ if (scores !== null) {
5371
+ if (params.score !== void 0) {
5372
+ const existing = Array.isArray(params.score) ? params.score : [params.score];
5373
+ params.score = [.../* @__PURE__ */ new Set([...existing, ...scores])];
5374
+ } else {
5375
+ params.score = scores.length === 1 ? scores[0] : scores;
5376
+ }
5377
+ continue;
5378
+ }
5379
+ const clearTypes = parseClearTypeRange(item);
5380
+ if (clearTypes !== null) {
5381
+ if (params.clearType) {
5382
+ const existing = Array.isArray(params.clearType) ? params.clearType : [params.clearType];
5383
+ params.clearType = [.../* @__PURE__ */ new Set([...existing, ...clearTypes])];
5384
+ } else {
5385
+ params.clearType = clearTypes;
5386
+ }
5387
+ continue;
5388
+ }
5389
+ const singleClearTypes = parseSingleClearType(item);
5390
+ if (singleClearTypes !== null) {
5391
+ if (params.clearType) {
5392
+ const existing = Array.isArray(params.clearType) ? params.clearType : [params.clearType];
5393
+ params.clearType = [.../* @__PURE__ */ new Set([...existing, ...singleClearTypes])];
5394
+ } else {
5395
+ params.clearType = singleClearTypes;
5396
+ }
5397
+ continue;
5398
+ }
5399
+ const grades = parseGradeRange(item);
5400
+ if (grades !== null) {
5401
+ if (params.grade) {
5402
+ const existing = Array.isArray(params.grade) ? params.grade : [params.grade];
5403
+ params.grade = [.../* @__PURE__ */ new Set([...existing, ...grades])];
5404
+ } else {
5405
+ params.grade = grades;
5406
+ }
5407
+ continue;
5408
+ }
5409
+ const singleGrades = parseSingleGrade(item);
5410
+ if (singleGrades !== null) {
5411
+ if (params.grade) {
5412
+ const existing = Array.isArray(params.grade) ? params.grade : [params.grade];
5413
+ params.grade = [.../* @__PURE__ */ new Set([...existing, ...singleGrades])];
5414
+ } else {
5415
+ params.grade = singleGrades[0];
5416
+ }
5417
+ continue;
5418
+ }
5419
+ }
5420
+ return params;
5421
+ }
5422
+ __name(parseQueryInput, "parseQueryInput");
5423
+
5424
+ // src/games/sdvx/commands/calculate.ts
5425
+ function calculate(ctx, config, logger5) {
5426
+ ctx.command("sdvx.calculate <query:text>").alias("sdvx.cal").action(async ({ session, options }, query) => {
5563
5427
  try {
5564
- const response = await ctx.http.get(`/api/sdvx/scores?id=${player_id}`, {
5565
- baseURL: url,
5566
- responseType: "json"
5567
- });
5568
- const scoresData = response;
5569
- const stringMusicTitles = [...new Set(scoresData.map((score) => score.title))];
5570
- const musicService = MusicService.getInstance(config);
5571
- const [musicDataList, titleMidPairs] = await Promise.all([
5572
- musicService.getMusic(ctx, stringMusicTitles),
5573
- musicService.extTitleToMid(ctx, stringMusicTitles)
5574
- ]);
5575
- const titleMidMap = new Map(
5576
- titleMidPairs.filter(({ mid }) => !!mid).map(({ title, mid }) => [title.toLowerCase(), mid])
5577
- );
5578
- const musicDataMap = new Map(musicDataList.map((music) => [music.title_name, music]));
5579
- const sdvxScores = [];
5580
- for (const scoreData of scoresData) {
5581
- const musicData = musicDataMap.get(scoreData.title);
5582
- const mappedMid = titleMidMap.get(scoreData.title.toLowerCase());
5583
- const musicId = mappedMid && /^\d+$/.test(mappedMid) ? Number(mappedMid) : scoreData.music_id;
5584
- for (const [diffKey, diffScore] of Object.entries(scoreData.difficulties)) {
5585
- const diffAbbr = diffKey.toUpperCase();
5586
- const diffInfo = getDiffStringFromAbbr(diffAbbr);
5587
- const diffStr = diffInfo?.diffStr || diffKey.toLowerCase();
5588
- const infVer = diffInfo?.infVer ?? musicData?.inf_ver ?? 0;
5589
- let difficultyData = null;
5590
- if (musicData && musicData.difficulty) {
5591
- difficultyData = musicData.difficulty.find(
5592
- (d) => d.difstr.toLowerCase() === diffStr.toLowerCase()
5593
- ) || null;
5594
- }
5595
- const scoreObj = {
5596
- music: {
5597
- music_id: musicId,
5598
- music_diff: difficultyData?.difnum || 0,
5599
- music_diff_name: getDiffName(diffStr, infVer),
5600
- music_diff_full_name: getDiffFullName(diffStr, infVer),
5601
- score: diffScore.score,
5602
- exscore: 0,
5603
- // Official 服务器不提供
5604
- clear_type: this.mapClearType(diffScore.mark),
5605
- score_grade: diffScore.grade,
5606
- btn_rate: "0",
5607
- long_rate: "0",
5608
- vol_rate: "0"
5609
- },
5610
- extra: {
5611
- volforce: 0,
5612
- play_count: 0,
5613
- update_at: ""
5614
- },
5615
- music_data: musicData || null,
5616
- difficulty_data: difficultyData || null
5617
- };
5618
- scoreObj.extra.volforce = calculateVolforce(scoreObj);
5619
- sdvxScores.push(scoreObj);
5428
+ let queryInput = query;
5429
+ if (!queryInput) {
5430
+ await session.send(session.text(".prompt"));
5431
+ queryInput = await session.prompt();
5432
+ if (!queryInput) return session.text("commands.timeout");
5433
+ if (queryInput === "q") return session.text(".quit");
5434
+ }
5435
+ const params = parseQueryInput(queryInput);
5436
+ if (Object.keys(params).length === 0) {
5437
+ return session.text(".invalid-query");
5438
+ }
5439
+ const results = generateQueryResults(params);
5440
+ if (results.length === 0) {
5441
+ return session.text(".no-results");
5442
+ }
5443
+ const maxResults = 500;
5444
+ if (results.length > maxResults) {
5445
+ return session.text(".too-many-results", [results.length]);
5446
+ }
5447
+ session.send(session.text(".drawing"));
5448
+ const drawerManager = DrawerManager.getInstance(ctx);
5449
+ const sdvxDrawer = drawerManager.getDrawer("sdvx");
5450
+ const imageBuffer = await sdvxDrawer.generateVFTableImage(
5451
+ {
5452
+ results,
5453
+ config
5454
+ },
5455
+ {
5456
+ lossless: true
5620
5457
  }
5458
+ );
5459
+ return import_koishi14.h.image(imageBuffer, "image/png");
5460
+ } catch (error) {
5461
+ ctx.logger("SDVX-Calculate").warn(error);
5462
+ return session.text(".error");
5463
+ }
5464
+ });
5465
+ }
5466
+ __name(calculate, "calculate");
5467
+
5468
+ // src/games/sdvx/commands/chart.ts
5469
+ var import_koishi15 = require("koishi");
5470
+ function mapArrangementToken(tok) {
5471
+ const t = tok.toLowerCase();
5472
+ if (t === "ran" || t === "random") return "random";
5473
+ if (t === "m" || t === "mirror" || t === "mir") return "mirror";
5474
+ if (t === "sran" || t === "srandom") return "s-random";
5475
+ if (t === "fran" || t === "wtf") return "f-random";
5476
+ return null;
5477
+ }
5478
+ __name(mapArrangementToken, "mapArrangementToken");
5479
+ function mapLeftColorToken(tok) {
5480
+ const m = /^l(blue|red|yellow|green)$/i.exec(tok);
5481
+ return m ? m[1].toUpperCase() : null;
5482
+ }
5483
+ __name(mapLeftColorToken, "mapLeftColorToken");
5484
+ function mapRightColorToken(tok) {
5485
+ const m = /^r(blue|red|yellow|green)$/i.exec(tok);
5486
+ return m ? m[1].toUpperCase() : null;
5487
+ }
5488
+ __name(mapRightColorToken, "mapRightColorToken");
5489
+ function isBarcode(s) {
5490
+ return /^\d+\.[A-Za-z]+$/.test(s);
5491
+ }
5492
+ __name(isBarcode, "isBarcode");
5493
+ function parseDiffAndTitle(rawTokens) {
5494
+ let diffStr = null;
5495
+ let diffIndex = null;
5496
+ const titleParts = [];
5497
+ const fullMap = {
5498
+ novice: "novice",
5499
+ nov: "novice",
5500
+ advanced: "advanced",
5501
+ adv: "advanced",
5502
+ exhaust: "exhaust",
5503
+ exh: "exhaust",
5504
+ infinite: "infinite",
5505
+ inf: "infinite",
5506
+ gravity: "infinite",
5507
+ grv: "infinite",
5508
+ heavenly: "infinite",
5509
+ hvn: "infinite",
5510
+ vivid: "infinite",
5511
+ vvd: "infinite",
5512
+ exceed: "infinite",
5513
+ xcd: "infinite",
5514
+ maximum: "maximum",
5515
+ mxm: "maximum"
5516
+ };
5517
+ for (const tok of rawTokens) {
5518
+ const trimmed = tok.trim();
5519
+ if (!trimmed) continue;
5520
+ if (diffIndex === null && /^[1-5]$/.test(trimmed)) {
5521
+ diffIndex = Number(trimmed) - 1;
5522
+ continue;
5523
+ }
5524
+ const lettersOnly = trimmed.replace(/[^A-Za-z]/g, "");
5525
+ if (lettersOnly) {
5526
+ const byAbbr = getDiffStringFromAbbr(lettersOnly);
5527
+ if (byAbbr) {
5528
+ diffStr = byAbbr.diffStr;
5529
+ continue;
5621
5530
  }
5622
- return sdvxScores;
5623
- } catch (error) {
5624
- if (error.response?.status === 404) {
5625
- return [];
5531
+ const lower = lettersOnly.toLowerCase();
5532
+ if (fullMap[lower]) {
5533
+ diffStr = fullMap[lower];
5534
+ continue;
5626
5535
  }
5627
- throw error;
5628
5536
  }
5537
+ titleParts.push(trimmed);
5629
5538
  }
5630
- async getScore(ctx, url, player_id, musicId, config) {
5631
- const allScores = await this.getAllScore(ctx, url, player_id, config);
5632
- const score = allScores.find((s) => s.music.music_id === musicId);
5633
- if (!score) {
5634
- throw new Error(`Score not found for music ID: ${musicId}`);
5539
+ return {
5540
+ diffStr,
5541
+ diffIndex,
5542
+ titleQuery: titleParts.join(" ").trim()
5543
+ };
5544
+ }
5545
+ __name(parseDiffAndTitle, "parseDiffAndTitle");
5546
+ function getHighestDifstr(diffs) {
5547
+ return diffs[diffs.length - 1].difstr;
5548
+ }
5549
+ __name(getHighestDifstr, "getHighestDifstr");
5550
+ function chart(ctx, config, logger5) {
5551
+ ctx.command("sdvx.chart [query:text]").alias("sdvx.c").action(async ({ session }, query) => {
5552
+ if (!query) {
5553
+ await session.send(session.text(".prompt"));
5554
+ query = await session.prompt();
5555
+ if (!query) return session.text("commands.timeout");
5635
5556
  }
5636
- return score;
5637
- }
5638
- async getRecentScores(ctx, url, player_id, config, count = 1) {
5639
- return [];
5640
- }
5641
- };
5642
-
5643
- // src/servers/Official/index.ts
5644
- var Official = class {
5645
- static {
5646
- __name(this, "Official");
5647
- }
5648
- name = "official";
5649
- supportedGames = ["sdvx"];
5650
- gameServices = {};
5651
- logger = new import_koishi15.Logger("Noah-Official");
5652
- /**
5653
- * 初始化SDVX游戏服务实例
5654
- */
5655
- constructor() {
5656
- this.gameServices["sdvx"] = SDVXService3.getInstance(this.logger);
5657
- }
5658
- };
5659
-
5660
- // src/servers/ServerFactory.ts
5661
- var ServerFactory = class {
5662
- static {
5663
- __name(this, "ServerFactory");
5664
- }
5665
- serverInstances = /* @__PURE__ */ new Map();
5666
- /**
5667
- * 根据服务器类型获取服务器实例,如果不存在则创建新实例
5668
- * @param serverType - 服务器类型
5669
- * @returns 对应的服务器实例
5670
- * @throws 当请求不支持的服务器类型时抛出异常
5671
- */
5672
- getServer(serverType) {
5673
- if (!this.serverInstances.has(serverType)) {
5674
- let serverInstance;
5675
- switch (serverType) {
5676
- case "asphyxia":
5677
- serverInstance = new Asphyxia();
5678
- break;
5679
- case "mao":
5680
- serverInstance = new Mao();
5681
- break;
5682
- case "official":
5683
- serverInstance = new Official();
5684
- break;
5685
- default:
5686
- throw new Error(`Unsupported server type: ${serverType}`);
5557
+ const musicService = MusicService.getInstance(config);
5558
+ try {
5559
+ let arrangement_mode = "normal";
5560
+ let laser_l_color = "BLUE";
5561
+ let laser_r_color = "RED";
5562
+ const tokens = query.split(/\s+/).filter(Boolean);
5563
+ const remaining = [];
5564
+ for (const tok of tokens) {
5565
+ const arr = mapArrangementToken(tok);
5566
+ if (arr) {
5567
+ arrangement_mode = arr;
5568
+ continue;
5569
+ }
5570
+ const lc = mapLeftColorToken(tok);
5571
+ if (lc) {
5572
+ laser_l_color = lc;
5573
+ continue;
5574
+ }
5575
+ const rc = mapRightColorToken(tok);
5576
+ if (rc) {
5577
+ laser_r_color = rc;
5578
+ continue;
5579
+ }
5580
+ remaining.push(tok);
5687
5581
  }
5688
- this.serverInstances.set(serverType, serverInstance);
5689
- }
5690
- return this.serverInstances.get(serverType);
5691
- }
5692
- };
5693
-
5694
- // src/servers/index.ts
5695
- var name9 = "Noah-Server";
5696
- var inject2 = ["globalConfig"];
5697
- var ServerManager = class _ServerManager {
5698
- static {
5699
- __name(this, "ServerManager");
5700
- }
5701
- static instance;
5702
- factory;
5703
- constructor() {
5704
- this.factory = new ServerFactory();
5705
- }
5706
- /**
5707
- * 获取服务器管理器实例
5708
- * @returns 服务器管理器的唯一实例
5709
- */
5710
- static getInstance() {
5711
- if (!_ServerManager.instance) {
5712
- _ServerManager.instance = new _ServerManager();
5582
+ const rest = remaining.join(" ").trim();
5583
+ if (!rest) {
5584
+ return;
5585
+ }
5586
+ if (isBarcode(rest)) {
5587
+ const [midRaw, diffAbbrRaw] = rest.split(".");
5588
+ const music_id = Number(midRaw);
5589
+ const { diffStr } = getDiffStringFromAbbr(diffAbbrRaw);
5590
+ const difstr = diffStr;
5591
+ const output_format = "PNG";
5592
+ const payload = {
5593
+ music_id,
5594
+ difstr,
5595
+ arrangement_mode,
5596
+ rng_seed: null,
5597
+ output_format,
5598
+ laser_l_color,
5599
+ laser_r_color,
5600
+ px_per_second: 600,
5601
+ column_height: 2800
5602
+ };
5603
+ const musicList = await musicService.getMusic(ctx, [music_id]);
5604
+ session.send(
5605
+ session.text(".drawing", {
5606
+ name: musicList[0].title_name,
5607
+ difstr
5608
+ })
5609
+ );
5610
+ const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
5611
+ headers: { "Content-Type": "application/json" }
5612
+ });
5613
+ return import_koishi15.h.image(res, "image/png");
5614
+ } else {
5615
+ const {
5616
+ diffStr: parsedDiff,
5617
+ diffIndex,
5618
+ titleQuery
5619
+ } = parseDiffAndTitle(remaining);
5620
+ if (!titleQuery) return session.text(".no-result");
5621
+ const musicInfo = await musicService.searchMusic(
5622
+ ctx,
5623
+ titleQuery
5624
+ );
5625
+ if (!musicInfo || musicInfo.length === 0) return session.text(".no-result");
5626
+ const picked = musicInfo[0];
5627
+ const music_id = Number(picked?.id);
5628
+ if (!Number.isFinite(music_id)) {
5629
+ logger5.warn("search result missing id", picked);
5630
+ return session.text(".error");
5631
+ }
5632
+ let difstr = null;
5633
+ if (parsedDiff) {
5634
+ difstr = parsedDiff;
5635
+ } else if (diffIndex !== null && Array.isArray(picked?.difficulty) && diffIndex >= 0 && diffIndex < picked.difficulty.length) {
5636
+ const d = picked.difficulty[diffIndex];
5637
+ difstr = d?.difstr ?? null;
5638
+ }
5639
+ if (!difstr) {
5640
+ difstr = getHighestDifstr(picked?.difficulty);
5641
+ }
5642
+ const output_format = "PNG";
5643
+ const payload = {
5644
+ music_id,
5645
+ difstr,
5646
+ arrangement_mode,
5647
+ rng_seed: null,
5648
+ output_format,
5649
+ laser_l_color,
5650
+ laser_r_color,
5651
+ px_per_second: 600,
5652
+ column_height: 2800
5653
+ };
5654
+ session.send(
5655
+ session.text(".drawing", {
5656
+ name: picked.title_name,
5657
+ difstr
5658
+ })
5659
+ );
5660
+ const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
5661
+ headers: { "Content-Type": "application/json" }
5662
+ });
5663
+ return import_koishi15.h.image(res, "image/png");
5664
+ }
5665
+ } catch (err) {
5666
+ logger5.warn(err);
5667
+ return session.text(".error");
5713
5668
  }
5714
- return _ServerManager.instance;
5715
- }
5716
- /**
5717
- * 根据服务器类型获取对应的服务器实例
5718
- * @param serverType - 服务器类型
5719
- * @returns 对应的服务器实例
5720
- */
5721
- getServer(serverType) {
5722
- return this.factory.getServer(serverType);
5723
- }
5724
- /**
5725
- * 根据服务器类型和游戏类型获取对应的游戏服务实例
5726
- * @param serverType - 服务器类型
5727
- * @param gameType - 游戏类型
5728
- * @returns 对应的游戏服务实例
5729
- */
5730
- getGameService(serverType, gameType) {
5731
- const server2 = this.factory.getServer(serverType);
5732
- return server2.gameServices[gameType];
5733
- }
5734
- };
5735
- function apply9(ctx, config) {
5669
+ });
5736
5670
  }
5737
- __name(apply9, "apply");
5671
+ __name(chart, "chart");
5738
5672
 
5739
5673
  // src/games/sdvx/services/score-service.ts
5740
5674
  var ScoreService = class _ScoreService {
@@ -6459,10 +6393,10 @@ function apply12(ctx) {
6459
6393
  __name(apply12, "apply");
6460
6394
 
6461
6395
  // src/games/sdvx/locales/en-US.yml
6462
- var en_US_default4 = { _config: { $desc: "SDVX Module Settings", default_model: "<p>Default model value (e.g. `2024110700`)</p>", sdvx_data_url: "<p>The URL of the SDVX data service</p>", sdvx_search_url: "<p>The URL of the SDVX search service</p>", official_support_url: "<p>The URL of the SDVX official support service</p>" }, commands: { vf: { description: "Show Noah help information", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing {name} [{difstr}], please wait patiently~</p>", "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Exited~</p>" } }, sdvx: { recent: { description: "Show recent scores", messages: { "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>" } }, chart: { description: "Show SDVX chart", messages: { prompt: "<p>Which song's chart would you like to view?</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", "no-result": "<p>Aww, Noah couldn’t find that song~ try another keyword, okay?</p>" } }, calculate: { description: "Calculate volforce value or score", messages: { "invalid-query": "<p>Invalid query parameters, please check your input~</p>", "no-results": "<p>No results found~</p>", "too-many-results": "<p>Too many results! Found {0} results, please narrow down your query (current limit: 500 results)</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", error: "<p>An error occurred(っ °Д °;)っ</p>" } }, radar: { description: "Show player radar stats", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", result: "<p>{name}'s Radar</p>\n<p>NOTES: {notes}</p>\n<p>PEAK: {peak}</p>\n<p>TSUMAMI: {tsumami}</p>\n<p>TRICKY: {tricky}</p>\n<p>HAND-TRIP: {hand_trip}</p>\n<p>ONE-HAND: {one_hand}</p>" } }, sync: { description: "Sync scores to Mao", messages: { "mao-not-found": "<p>Mao server not found. Please add a Mao server first, then try syncing again.</p>", "source-server-not-found": "<p>No available source servers. Please add a server first.</p>", "source-server-select": "<p>Please choose the source server:</p>\n{server_list}\n<p>q. Exit</p>", "card-not-found": "<p>You haven't bound any card yet. Please bind a card first.</p>", "source-card-select": "<p>Please choose the source card:</p>\n{card_list}\n<p>q. Exit</p>", "target-card-select": "<p>Please choose the target card (sync to Mao):</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Sync cancelled.</p>", "same-card-error": "<p>When the source server is Mao, the source card and target card cannot be the same. Please choose again.</p>", "dm-only": "<p>For security, please use this command in a private chat.</p>", "pin-prompt": "<p>Please enter the Mao PIN (4 digits):</p>\n<p>q. Exit</p>", "pin-invalid": "<p>Invalid PIN format. Please enter 4 digits.</p>", "pin-verify-failed": "<p>PIN verification failed: {message}</p>", "pin-verify-error": "<p>PIN verification error. Please try again later.</p>", "pin-too-many": "<p>Too many PIN attempts. Please try again later.</p>", "fetch-error": "<p>Failed to fetch scores from the source server. Please try again later.</p>", "no-scores": "<p>No scores available for sync.</p>", "confirm-sync": "<p>Found {score_count} scores. Start sync?</p>\n<p>Type y to confirm, any other key to cancel.</p>", "sync-error": "<p>Error occurred during sync(っ °Д °;)っ</p>", "sync-failed": "<p>Sync failed(っ °Д °;)っ</p>", "selected-summary": "<p>Sync completedヾ(≧▽≦*)o</p>\n<p>Source server: {source_server_name} ({source_server_type})</p>\n<p>Source card: {source_card_name}</p>\n<p>Target card: {target_card_name}</p>\n<p>Tracks synced: {score_count}</p>" } } } } };
6396
+ var en_US_default4 = { _config: { $desc: "SDVX Module Settings", sdvx_data_url: "<p>The URL of the SDVX data service</p>", sdvx_search_url: "<p>The URL of the SDVX search service</p>", official_support_url: "<p>The URL of the SDVX official support service</p>" }, commands: { vf: { description: "Show Noah help information", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing {name} [{difstr}], please wait patiently~</p>", "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Exited~</p>" } }, sdvx: { recent: { description: "Show recent scores", messages: { "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>" } }, chart: { description: "Show SDVX chart", messages: { prompt: "<p>Which song's chart would you like to view?</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", "no-result": "<p>Aww, Noah couldn’t find that song~ try another keyword, okay?</p>" } }, calculate: { description: "Calculate volforce value or score", messages: { "invalid-query": "<p>Invalid query parameters, please check your input~</p>", "no-results": "<p>No results found~</p>", "too-many-results": "<p>Too many results! Found {0} results, please narrow down your query (current limit: 500 results)</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", error: "<p>An error occurred(っ °Д °;)っ</p>" } }, radar: { description: "Show player radar stats", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", result: "<p>{name}'s Radar</p>\n<p>NOTES: {notes}</p>\n<p>PEAK: {peak}</p>\n<p>TSUMAMI: {tsumami}</p>\n<p>TRICKY: {tricky}</p>\n<p>HAND-TRIP: {hand_trip}</p>\n<p>ONE-HAND: {one_hand}</p>" } }, sync: { description: "Sync scores to Mao", messages: { "mao-not-found": "<p>Mao server not found. Please add a Mao server first, then try syncing again.</p>", "source-server-not-found": "<p>No available source servers. Please add a server first.</p>", "source-server-select": "<p>Please choose the source server:</p>\n{server_list}\n<p>q. Exit</p>", "card-not-found": "<p>You haven't bound any card yet. Please bind a card first.</p>", "source-card-select": "<p>Please choose the source card:</p>\n{card_list}\n<p>q. Exit</p>", "target-card-select": "<p>Please choose the target card (sync to Mao):</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Sync cancelled.</p>", "same-card-error": "<p>When the source server is Mao, the source card and target card cannot be the same. Please choose again.</p>", "dm-only": "<p>For security, please use this command in a private chat.</p>", "pin-prompt": "<p>Please enter the Mao PIN (4 digits):</p>\n<p>q. Exit</p>", "pin-invalid": "<p>Invalid PIN format. Please enter 4 digits.</p>", "pin-verify-failed": "<p>PIN verification failed: {message}</p>", "pin-verify-error": "<p>PIN verification error. Please try again later.</p>", "pin-too-many": "<p>Too many PIN attempts. Please try again later.</p>", "fetch-error": "<p>Failed to fetch scores from the source server. Please try again later.</p>", "no-scores": "<p>No scores available for sync.</p>", "confirm-sync": "<p>Found {score_count} scores. Start sync?</p>\n<p>Type y to confirm, any other key to cancel.</p>", "sync-error": "<p>Error occurred during sync(っ °Д °;)っ</p>", "sync-failed": "<p>Sync failed(っ °Д °;)っ</p>", "selected-summary": "<p>Sync completedヾ(≧▽≦*)o</p>\n<p>Source server: {source_server_name} ({source_server_type})</p>\n<p>Source card: {source_card_name}</p>\n<p>Target card: {target_card_name}</p>\n<p>Tracks synced: {score_count}</p>" } } } } };
6463
6397
 
6464
6398
  // src/games/sdvx/locales/zh-CN.yml
6465
- var zh_CN_default4 = { _config: { $desc: "SDVX 模块设置", default_model: "<p>默认的 model 值(如 `2024110700`)</p>", sdvx_data_url: "<p>SDVX 数据服务的 URL</p>", sdvx_search_url: "<p>SDVX 搜索服务的 URL</p>", official_support_url: "<p>SDVX 官方支持服务的 URL</p>" }, commands: { vf: { description: "查询 SDVX VOLFORCE", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>" } }, sdvx: { recent: { description: "查询最近分数", messages: { "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>" } }, chart: { description: "查询 SDVX 谱面", messages: { prompt: "<p>要查哪首歌的铺面呢?</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在绘制 {name} [{difstr}],请耐心等待~</p>", "no-result": "<p>Noah 没有找到这首歌,换个关键词试试吧~</p>" } }, calculate: { description: "计算 volforce 值或分数", messages: { "invalid-query": "<p>查询参数无效,请检查你的输入~</p>", "no-results": "<p>没有找到符合条件的结果~</p>", "too-many-results": "<p>结果太多啦!共找到 {0} 条结果,请缩小查询范围(当前限制:500 条)</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>" } }, radar: { description: "查询玩家六维雷达", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", result: "<p>{name} 的雷达</p>\n<p>NOTES: {notes}</p>\n<p>PEAK: {peak}</p>\n<p>TSUMAMI: {tsumami}</p>\n<p>TRICKY: {tricky}</p>\n<p>HAND-TRIP: {hand_trip}</p>\n<p>ONE-HAND: {one_hand}</p>" } }, sync: { description: "同步成绩到猫网", messages: { "mao-not-found": "<p>没有找到猫网服务器,请先添加猫网服务器后再尝试同步。</p>", "source-server-not-found": "<p>没有可作为来源的服务器,请先添加服务器。</p>", "source-server-select": "<p>请选择来源服务器:</p>\n{server_list}\n<p>q. 退出</p>", "card-not-found": "<p>你还没有绑定任何卡片哦~</p>", "source-card-select": "<p>请选择来源卡片:</p>\n{card_list}\n<p>q. 退出</p>", "target-card-select": "<p>请选择目标卡片(同步到猫网):</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出同步。</p>", "same-card-error": "<p>来源服务器为猫网时,来源卡片和目标卡片不能相同,请重新选择。</p>", "dm-only": "<p>出于安全考虑,请在私聊中使用该指令。</p>", "pin-prompt": "<p>请输入猫网 PIN 码(4 位数字):</p>\n<p>q. 退出</p>", "pin-invalid": "<p>PIN 码格式不正确,请输入 4 位数字。</p>", "pin-verify-failed": "<p>PIN 验证失败:{message}</p>", "pin-verify-error": "<p>PIN 验证时出现错误,请稍后重试。</p>", "pin-too-many": "<p>PIN 验证次数过多,请稍后再试。</p>", "fetch-error": "<p>获取来源服务器成绩失败,请稍后再试。</p>", "no-scores": "<p>没有可同步的成绩。</p>", "confirm-sync": "<p>共找到 {score_count} 条成绩,是否开始同步?</p>\n<p>输入 y 确认,其他任意键取消。</p>", "sync-error": "<p>同步过程中出现了错误(っ °Д °;)っ</p>", "sync-failed": "<p>同步失败(っ °Д °;)っ</p>", "selected-summary": "<p>同步完成ヾ(≧▽≦*)o</p>\n<p>来源服务器:{source_server_name} ({source_server_type})</p>\n<p>来源卡片:{source_card_name}</p>\n<p>目标卡片:{target_card_name}</p>\n<p>同步曲目数量:{score_count}</p>" } } } } };
6399
+ var zh_CN_default4 = { _config: { $desc: "SDVX 模块设置", sdvx_data_url: "<p>SDVX 数据服务的 URL</p>", sdvx_search_url: "<p>SDVX 搜索服务的 URL</p>", official_support_url: "<p>SDVX 官方支持服务的 URL</p>" }, commands: { vf: { description: "查询 SDVX VOLFORCE", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>" } }, sdvx: { recent: { description: "查询最近分数", messages: { "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>" } }, chart: { description: "查询 SDVX 谱面", messages: { prompt: "<p>要查哪首歌的铺面呢?</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在绘制 {name} [{difstr}],请耐心等待~</p>", "no-result": "<p>Noah 没有找到这首歌,换个关键词试试吧~</p>" } }, calculate: { description: "计算 volforce 值或分数", messages: { "invalid-query": "<p>查询参数无效,请检查你的输入~</p>", "no-results": "<p>没有找到符合条件的结果~</p>", "too-many-results": "<p>结果太多啦!共找到 {0} 条结果,请缩小查询范围(当前限制:500 条)</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>" } }, radar: { description: "查询玩家六维雷达", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", result: "<p>{name} 的雷达</p>\n<p>NOTES: {notes}</p>\n<p>PEAK: {peak}</p>\n<p>TSUMAMI: {tsumami}</p>\n<p>TRICKY: {tricky}</p>\n<p>HAND-TRIP: {hand_trip}</p>\n<p>ONE-HAND: {one_hand}</p>" } }, sync: { description: "同步成绩到猫网", messages: { "mao-not-found": "<p>没有找到猫网服务器,请先添加猫网服务器后再尝试同步。</p>", "source-server-not-found": "<p>没有可作为来源的服务器,请先添加服务器。</p>", "source-server-select": "<p>请选择来源服务器:</p>\n{server_list}\n<p>q. 退出</p>", "card-not-found": "<p>你还没有绑定任何卡片哦~</p>", "source-card-select": "<p>请选择来源卡片:</p>\n{card_list}\n<p>q. 退出</p>", "target-card-select": "<p>请选择目标卡片(同步到猫网):</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出同步。</p>", "same-card-error": "<p>来源服务器为猫网时,来源卡片和目标卡片不能相同,请重新选择。</p>", "dm-only": "<p>出于安全考虑,请在私聊中使用该指令。</p>", "pin-prompt": "<p>请输入猫网 PIN 码(4 位数字):</p>\n<p>q. 退出</p>", "pin-invalid": "<p>PIN 码格式不正确,请输入 4 位数字。</p>", "pin-verify-failed": "<p>PIN 验证失败:{message}</p>", "pin-verify-error": "<p>PIN 验证时出现错误,请稍后重试。</p>", "pin-too-many": "<p>PIN 验证次数过多,请稍后再试。</p>", "fetch-error": "<p>获取来源服务器成绩失败,请稍后再试。</p>", "no-scores": "<p>没有可同步的成绩。</p>", "confirm-sync": "<p>共找到 {score_count} 条成绩,是否开始同步?</p>\n<p>输入 y 确认,其他任意键取消。</p>", "sync-error": "<p>同步过程中出现了错误(っ °Д °;)っ</p>", "sync-failed": "<p>同步失败(っ °Д °;)っ</p>", "selected-summary": "<p>同步完成ヾ(≧▽≦*)o</p>\n<p>来源服务器:{source_server_name} ({source_server_type})</p>\n<p>来源卡片:{source_card_name}</p>\n<p>目标卡片:{target_card_name}</p>\n<p>同步曲目数量:{score_count}</p>" } } } } };
6466
6400
 
6467
6401
  // src/games/sdvx/index.ts
6468
6402
  var name13 = "Noah-SDVX";
@@ -6474,6 +6408,7 @@ async function apply13(ctx, config) {
6474
6408
  ["en-US", en_US_default4],
6475
6409
  ["zh-CN", zh_CN_default4]
6476
6410
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
6411
+ registerSDVXAdapters(logger4, config.sdvx);
6477
6412
  ctx.plugin(database_exports2, config.sdvx);
6478
6413
  ctx.plugin(command_exports2, config.sdvx);
6479
6414
  ctx.plugin(event_exports2, config.sdvx);
@@ -6549,7 +6484,6 @@ var generalConfig = import_koishi22.Schema.object({
6549
6484
  // src/games/sdvx/config.ts
6550
6485
  var import_koishi23 = require("koishi");
6551
6486
  var sdvxConfig = import_koishi23.Schema.object({
6552
- default_model: import_koishi23.Schema.string().default("2025100700"),
6553
6487
  sdvx_data_url: import_koishi23.Schema.string().required(),
6554
6488
  sdvx_search_url: import_koishi23.Schema.string().required()
6555
6489
  }).i18n({