koishi-plugin-noah 2.2.3 → 2.3.1

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