koishi-plugin-noah 2.0.5 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -6,8 +6,8 @@ var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
7
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
8
  var __export = (target, all) => {
9
- for (var name17 in all)
10
- __defProp(target, name17, { get: all[name17], enumerable: true });
9
+ for (var name15 in all)
10
+ __defProp(target, name15, { get: all[name15], enumerable: true });
11
11
  };
12
12
  var __copyProps = (to, from, except, desc) => {
13
13
  if (from && typeof from === "object" || typeof from === "function") {
@@ -28,14 +28,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
33
  Config: () => Config,
34
- apply: () => apply16,
35
- inject: () => inject4,
36
- name: () => name16
34
+ apply: () => apply14,
35
+ inject: () => inject3,
36
+ name: () => name14
37
37
  });
38
- module.exports = __toCommonJS(src_exports);
38
+ module.exports = __toCommonJS(index_exports);
39
39
 
40
40
  // src/asset/index.ts
41
41
  var asset_exports = {};
@@ -64,6 +64,7 @@ var AssetService = class {
64
64
  this.config = config;
65
65
  this.basePath = import_path.default.resolve(this.ctx.baseDir, this.config.data_path);
66
66
  }
67
+ ctx;
67
68
  static {
68
69
  __name(this, "AssetService");
69
70
  }
@@ -329,18 +330,18 @@ __name(getAssetPath, "getAssetPath");
329
330
  // src/core/index.ts
330
331
  var core_exports = {};
331
332
  __export(core_exports, {
332
- apply: () => apply7,
333
- inject: () => inject2,
334
- logger: () => logger3,
335
- name: () => name7
333
+ apply: () => apply5,
334
+ inject: () => inject,
335
+ logger: () => logger2,
336
+ name: () => name5
336
337
  });
337
- var import_koishi5 = require("koishi");
338
+ var import_koishi6 = require("koishi");
338
339
 
339
340
  // src/core/command.ts
340
341
  var command_exports = {};
341
342
  __export(command_exports, {
342
- apply: () => apply4,
343
- name: () => name4
343
+ apply: () => apply2,
344
+ name: () => name2
344
345
  });
345
346
 
346
347
  // src/core/services/card-service.ts
@@ -348,6 +349,7 @@ var CardService = class {
348
349
  constructor(ctx) {
349
350
  this.ctx = ctx;
350
351
  }
352
+ ctx;
351
353
  static {
352
354
  __name(this, "CardService");
353
355
  }
@@ -410,8 +412,8 @@ var CardService = class {
410
412
  * @param name - 卡片名称
411
413
  * @returns 卡片信息,不存在则返回null
412
414
  */
413
- async getCardByName(name17) {
414
- const rows = await this.ctx.database.get(this.tableName, { name: name17 });
415
+ async getCardByName(name15) {
416
+ const rows = await this.ctx.database.get(this.tableName, { name: name15 });
415
417
  if (!rows[0]) return null;
416
418
  return {
417
419
  ...rows[0],
@@ -426,10 +428,10 @@ var CardService = class {
426
428
  * @param defaultServerId - 默认服务器ID(若没有默认服务器则为0)
427
429
  * @returns 创建成功后包含自动填充ID等字段的完整对象
428
430
  */
429
- async createCard(uid, code, name17, defaultServerId = 0) {
431
+ async createCard(uid, code, name15, defaultServerId = 0) {
430
432
  const data = {
431
433
  code,
432
- name: name17,
434
+ name: name15,
433
435
  defaultServerId
434
436
  };
435
437
  const res = await this.ctx.database.create(this.tableName, data);
@@ -492,6 +494,7 @@ var ServerService = class {
492
494
  constructor(ctx) {
493
495
  this.ctx = ctx;
494
496
  }
497
+ ctx;
495
498
  static {
496
499
  __name(this, "ServerService");
497
500
  }
@@ -567,8 +570,8 @@ var ServerService = class {
567
570
  * @param name - 服务器名称
568
571
  * @returns 服务器信息,不存在则返回null
569
572
  */
570
- async getServerByName(name17) {
571
- const rows = await this.ctx.database.get(this.tableName, { name: name17 });
573
+ async getServerByName(name15) {
574
+ const rows = await this.ctx.database.get(this.tableName, { name: name15 });
572
575
  return rows[0] ?? null;
573
576
  }
574
577
  /**
@@ -685,6 +688,7 @@ var UserService = class {
685
688
  constructor(ctx) {
686
689
  this.ctx = ctx;
687
690
  }
691
+ ctx;
688
692
  static {
689
693
  __name(this, "UserService");
690
694
  }
@@ -1017,7 +1021,7 @@ __name(ensureOfficialServerForUser, "ensureOfficialServerForUser");
1017
1021
 
1018
1022
  // src/core/commands/bind.ts
1019
1023
  function bind(ctx, config) {
1020
- ctx.command("bind [cardCode:text]").alias("绑卡").userFields(["defaultCardId", "id"]).action(async ({ session }, cardCode) => {
1024
+ ctx.command("bind [cardCode:text]", { slash: true }).alias("绑卡").userFields(["defaultCardId", "id"]).action(async ({ session }, cardCode) => {
1021
1025
  const cardService = new CardService(ctx);
1022
1026
  const serverService = new ServerService(ctx);
1023
1027
  const userService = new UserService(ctx);
@@ -1076,7 +1080,7 @@ __name(bind, "bind");
1076
1080
  // src/core/commands/card.ts
1077
1081
  var import_koishi2 = require("koishi");
1078
1082
  function card(ctx, config) {
1079
- ctx.command("card").alias("卡片管理").userFields(["id"]).channelFields(["id"]).option("detail", "-d <cardCode:text>").action(async ({ session, options }) => {
1083
+ ctx.command("card", { slash: true }).alias("卡片管理").userFields(["id"]).channelFields(["id"]).option("detail", "-d <cardCode:text>").action(async ({ session, options }) => {
1080
1084
  const cardService = new CardService(ctx);
1081
1085
  const atGuild = session.guildId != null;
1082
1086
  if (options.detail) {
@@ -1198,23 +1202,43 @@ async function showCardSelectMenu(ctx, session, cardService, serverService, user
1198
1202
  }
1199
1203
  __name(showCardSelectMenu, "showCardSelectMenu");
1200
1204
  async function showCardMenu(ctx, session, card2, cardService, serverService, userService, uid, userDefaultCardId, userDefaultServerId, cid, channelDefaultServerId) {
1201
- if (card2.defaultServerId != 0) {
1202
- const server2 = await serverService.getServerById(card2.defaultServerId);
1203
- await session.send(
1204
- session.text(".menu-has-bound-server", {
1205
- name: card2.name,
1206
- defaultServerName: server2.name
1207
- })
1208
- );
1205
+ let selectNum;
1206
+ if (session.platform === "discord") {
1207
+ const serverInfo = card2.defaultServerId != 0 ? `
1208
+ > 🔗 ${(await serverService.getServerById(card2.defaultServerId)).name}` : "";
1209
+ const buttons = [
1210
+ (0, import_koishi2.h)("button", { id: "card.action/1", type: "primary" }, "⭐ 设为默认"),
1211
+ (0, import_koishi2.h)("button", { id: "card.action/2", type: "secondary" }, "✏️ 修改卡号"),
1212
+ (0, import_koishi2.h)("button", { id: "card.action/3", type: "danger" }, "🗑️ 删除"),
1213
+ (0, import_koishi2.h)("button", { id: "card.action/4", type: "secondary" }, "📝 重命名"),
1214
+ (0, import_koishi2.h)("button", { id: "card.action/5", type: "secondary" }, "🖥️ 绑定服务器"),
1215
+ (0, import_koishi2.h)("button", { id: "card.action/0", type: "secondary" }, "↩️ 返回")
1216
+ ];
1217
+ await session.send((0, import_koishi2.h)("message", [`**[${card2.name}]**${serverInfo}`, ...buttons]));
1218
+ const click = await session.prompt();
1219
+ if (!click) return session.text("commands.timeout");
1220
+ const match = click.match(/^card\.action\/(\d+)$/);
1221
+ if (!match) return session.text(".invalid-select");
1222
+ selectNum = Number(match[1]);
1209
1223
  } else {
1210
- await session.send(session.text(".menu", card2));
1211
- }
1212
- const select = await session.prompt();
1213
- if (!select) return session.text("commands.timeout");
1214
- if (select === "q") return session.text(".quit");
1215
- const selectNum = parseInt(select, 10);
1216
- if (isNaN(selectNum) || selectNum < 0 || selectNum > 5) {
1217
- return await session.text(".invalid-select");
1224
+ if (card2.defaultServerId != 0) {
1225
+ const server2 = await serverService.getServerById(card2.defaultServerId);
1226
+ await session.send(
1227
+ session.text(".menu-has-bound-server", {
1228
+ name: card2.name,
1229
+ defaultServerName: server2.name
1230
+ })
1231
+ );
1232
+ } else {
1233
+ await session.send(session.text(".menu", card2));
1234
+ }
1235
+ const select = await session.prompt();
1236
+ if (!select) return session.text("commands.timeout");
1237
+ if (select === "q") return session.text(".quit");
1238
+ selectNum = parseInt(select, 10);
1239
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > 5) {
1240
+ return await session.text(".invalid-select");
1241
+ }
1218
1242
  }
1219
1243
  if (selectNum === 0) {
1220
1244
  return await showCardSelectMenu(
@@ -1309,6 +1333,33 @@ async function showCardMenu(ctx, session, card2, cardService, serverService, use
1309
1333
  }
1310
1334
  if (selectNum === 5) {
1311
1335
  const res = await serverService.getSelectableServers(uid, cid);
1336
+ if (session.platform === "discord") {
1337
+ const buttons = res.map((srv, index) => {
1338
+ let label = srv.name;
1339
+ if (srv.id === userDefaultServerId) label += " ⭐";
1340
+ else if (srv.id === channelDefaultServerId) label += " 📌";
1341
+ return (0, import_koishi2.h)(
1342
+ "button",
1343
+ {
1344
+ id: `card.srv/${index + 1}`,
1345
+ type: srv.id === card2.defaultServerId ? "primary" : "secondary"
1346
+ },
1347
+ label
1348
+ );
1349
+ });
1350
+ await session.send((0, import_koishi2.h)("message", ["请选择一个服务器:", ...buttons]));
1351
+ const click = await session.prompt();
1352
+ if (!click) return session.text("commands.timeout");
1353
+ const match = click.match(/^card\.srv\/(\d+)$/);
1354
+ if (!match) return session.text(".invalid-select");
1355
+ const selected = Number(match[1]);
1356
+ if (isNaN(selected) || selected < 1 || selected > res.length)
1357
+ return session.text(".invalid-select");
1358
+ if (card2.defaultServerId === res[selected - 1].id)
1359
+ return session.text(".menu-5-error-duplicate");
1360
+ await cardService.updateCard(card2.id, { defaultServerId: res[selected - 1].id });
1361
+ return session.text(".menu-5-success");
1362
+ }
1312
1363
  let serverListMsg = "";
1313
1364
  for (let i = 0; i < res.length; i++) {
1314
1365
  if (res[i].id === userDefaultServerId) {
@@ -1321,10 +1372,10 @@ async function showCardMenu(ctx, session, card2, cardService, serverService, use
1321
1372
  }
1322
1373
  const msg = import_koishi2.h.unescape(session.text(".menu-5", { server_list: serverListMsg })).replace("< 你的默认服务器", "&lt; 你的默认服务器").replace("< 群组默认服务器", "&lt; 群组默认服务器");
1323
1374
  await session.send(msg);
1324
- const select2 = await session.prompt();
1325
- if (!select2) return session.text("commands.timeout");
1326
- if (select2 === "q") return session.text(".quit");
1327
- const selectNum2 = parseInt(select2, 10);
1375
+ const select = await session.prompt();
1376
+ if (!select) return session.text("commands.timeout");
1377
+ if (select === "q") return session.text(".quit");
1378
+ const selectNum2 = parseInt(select, 10);
1328
1379
  if (isNaN(selectNum2) || selectNum2 < 1 || selectNum2 > res.length) {
1329
1380
  return session.text(".invalid-select");
1330
1381
  }
@@ -1336,996 +1387,198 @@ async function showCardMenu(ctx, session, card2, cardService, serverService, use
1336
1387
  }
1337
1388
  __name(showCardMenu, "showCardMenu");
1338
1389
 
1339
- // src/slash/index.ts
1340
- var slash_exports = {};
1341
- __export(slash_exports, {
1342
- apply: () => apply3,
1343
- inject: () => inject,
1344
- name: () => name3,
1345
- registerSlashCommand: () => registerSlashCommand
1346
- });
1347
- var import_koishi3 = require("koishi");
1348
-
1349
- // src/slash/database.ts
1350
- var database_exports = {};
1351
- __export(database_exports, {
1352
- apply: () => apply2,
1353
- name: () => name2
1354
- });
1355
- var name2 = "database";
1356
- function apply2(ctx) {
1357
- ctx.model.extend(
1358
- "slash_command",
1359
- {
1360
- key: "string",
1361
- json: "text",
1362
- version: "string",
1363
- lastDeclaredAt: "timestamp"
1364
- },
1365
- {
1366
- primary: "key"
1367
- }
1368
- );
1369
- ctx.model.extend(
1370
- "slash_registered_command",
1371
- {
1372
- id: "unsigned",
1373
- platform: "string",
1374
- scope: "string",
1375
- key: "string",
1376
- platformCommandId: "string",
1377
- hash: "string",
1378
- rawPayload: "text",
1379
- lastSyncedAt: "timestamp"
1380
- },
1381
- {
1382
- autoInc: true
1383
- }
1384
- );
1385
- ctx.model.extend(
1386
- "slash_sync_log",
1387
- {
1388
- id: "unsigned",
1389
- platform: "string",
1390
- scope: "string",
1391
- action: "string",
1392
- key: "string",
1393
- details: "text",
1394
- createdAt: "timestamp"
1395
- },
1396
- {
1397
- autoInc: true
1398
- }
1399
- );
1400
- ctx.model.extend(
1401
- "slash_lock",
1402
- {
1403
- name: "string",
1404
- owner: "string",
1405
- acquiredAt: "timestamp"
1406
- },
1407
- {
1408
- primary: "name"
1409
- }
1410
- );
1390
+ // src/core/commands/help.ts
1391
+ function help(ctx, config) {
1392
+ ctx.command("help", { slash: true }).alias("帮助").action(({ session }) => {
1393
+ console.log(session);
1394
+ return session.text(".content");
1395
+ });
1411
1396
  }
1412
- __name(apply2, "apply");
1397
+ __name(help, "help");
1413
1398
 
1414
- // src/slash/registry.ts
1415
- var declarations = /* @__PURE__ */ new Map();
1416
- var listeners = /* @__PURE__ */ new Set();
1417
- function cloneDefinition(def) {
1418
- return JSON.parse(JSON.stringify(def));
1419
- }
1420
- __name(cloneDefinition, "cloneDefinition");
1421
- function notifyListeners(def) {
1422
- for (const cb of listeners) {
1423
- try {
1424
- const result = cb(cloneDefinition(def));
1425
- if (result && typeof result.catch === "function") {
1426
- ;
1427
- result.catch(
1428
- (err) => console.error("[slash] registry listener failed", err)
1429
- );
1399
+ // src/core/commands/link.ts
1400
+ var import_koishi3 = require("koishi");
1401
+ function link(ctx, _config) {
1402
+ const tokens = /* @__PURE__ */ Object.create(null);
1403
+ function generate(platform, userId, phase) {
1404
+ const token = "noah/" + import_koishi3.Random.id(6, 10);
1405
+ tokens[token] = [platform, userId, phase];
1406
+ ctx.setTimeout(() => delete tokens[token], 5 * import_koishi3.Time.minute);
1407
+ return token;
1408
+ }
1409
+ __name(generate, "generate");
1410
+ async function bindAccount(aid, platform, pid) {
1411
+ await ctx.database.set("binding", { platform, pid }, { aid });
1412
+ }
1413
+ __name(bindAccount, "bindAccount");
1414
+ ctx.command("link", { slash: true }).alias("关联").userFields(["id"]).option("remove", "-r").action(async ({ session, options }) => {
1415
+ if (options.remove) {
1416
+ const { platform, userId: pid } = session;
1417
+ const bindings = await ctx.database.get("binding", { aid: session.user.id });
1418
+ const binding = bindings.find((b) => b.platform === platform && b.pid === pid);
1419
+ if (binding.aid !== binding.bid) {
1420
+ await bindAccount(binding.bid, platform, pid);
1421
+ return session.text(".remove-success");
1422
+ } else if (bindings.filter((b) => b.aid === b.bid).length === 1) {
1423
+ return session.text(".remove-original");
1424
+ } else {
1425
+ const authority = await session.resolve(ctx.root.config.autoAuthorize);
1426
+ const user = await ctx.database.create("user", { authority });
1427
+ await bindAccount(user.id, platform, pid);
1428
+ return session.text(".remove-success");
1430
1429
  }
1431
- } catch (err) {
1432
- console.error("[slash] registry listener failed", err);
1433
1430
  }
1434
- }
1431
+ const token = generate(session.platform, session.userId, +!session.isDirect);
1432
+ return session.text(".generated-1", [token]);
1433
+ });
1434
+ ctx.middleware(async (session, next) => {
1435
+ const token = session.stripped.content;
1436
+ const data = tokens[token];
1437
+ if (!data) return next();
1438
+ if (data[0] === session.platform && data[1] === session.userId) {
1439
+ return session.text("commands.link.messages.self-" + (data[2] < 0 ? "2" : "1"));
1440
+ }
1441
+ delete tokens[token];
1442
+ if (data[2] < 0) {
1443
+ const [binding] = await ctx.database.get(
1444
+ "binding",
1445
+ { platform: data[0], pid: data[1] },
1446
+ ["aid"]
1447
+ );
1448
+ await bindAccount(binding.aid, session.platform, session.userId);
1449
+ return session.text("commands.link.messages.success");
1450
+ } else {
1451
+ const user = await ctx.database.getUser(session.platform, session.userId, [
1452
+ "id",
1453
+ "authority"
1454
+ ]);
1455
+ if (!user.authority) return session.text("internal.low-authority");
1456
+ if (data[2]) {
1457
+ const token2 = generate(session.platform, session.userId, -1);
1458
+ return session.text("commands.link.messages.generated-2", [token2]);
1459
+ } else {
1460
+ await bindAccount(user.id, data[0], data[1]);
1461
+ return session.text("commands.link.messages.success");
1462
+ }
1463
+ }
1464
+ }, true);
1435
1465
  }
1436
- __name(notifyListeners, "notifyListeners");
1437
- function registerSlashCommand(def) {
1438
- const normalized = cloneDefinition(def);
1439
- declarations.set(normalized.key, normalized);
1440
- notifyListeners(normalized);
1466
+ __name(link, "link");
1467
+
1468
+ // src/core/commands/locale.ts
1469
+ var import_koishi4 = require("koishi");
1470
+
1471
+ // src/core/utils/role.ts
1472
+ function hasPermission(...perms) {
1473
+ return perms.some((perm) => perm === true);
1441
1474
  }
1442
- __name(registerSlashCommand, "registerSlashCommand");
1443
- function getRegisteredSlashCommands() {
1444
- return Array.from(declarations.values()).map((def) => cloneDefinition(def));
1475
+ __name(hasPermission, "hasPermission");
1476
+ function isPluginAdmin(session, config) {
1477
+ return config.adminUsers?.includes(session.userId) ?? false;
1445
1478
  }
1446
- __name(getRegisteredSlashCommands, "getRegisteredSlashCommands");
1447
- function onSlashCommandRegistered(callback) {
1448
- listeners.add(callback);
1449
- return () => listeners.delete(callback);
1479
+ __name(isPluginAdmin, "isPluginAdmin");
1480
+ function isGuildAdmin(session) {
1481
+ const platform = session.event.platform;
1482
+ if (platform === "onebot") {
1483
+ if (session.guildId == null) return true;
1484
+ const role = session.event.member.roles[0];
1485
+ const guildMember = typeof role === "string" ? role : role?.name || "";
1486
+ return guildMember === "owner" || guildMember === "admin" || guildMember === "SUBCHANNEL_ADMIN" || guildMember === "OWNER" || guildMember === "ADMIN";
1487
+ } else if (platform === "qq") {
1488
+ return true;
1489
+ } else {
1490
+ return true;
1491
+ }
1450
1492
  }
1451
- __name(onSlashCommandRegistered, "onSlashCommandRegistered");
1493
+ __name(isGuildAdmin, "isGuildAdmin");
1452
1494
 
1453
- // src/slash/service.ts
1454
- var import_crypto = __toESM(require("crypto"), 1);
1455
- var OPTION_TYPE_MAP = {
1456
- SUB_COMMAND: 1,
1457
- SUB_COMMAND_GROUP: 2,
1458
- STRING: 3,
1459
- INTEGER: 4,
1460
- BOOLEAN: 5,
1461
- USER: 6,
1462
- CHANNEL: 7,
1463
- ROLE: 8,
1464
- MENTIONABLE: 9,
1465
- NUMBER: 10,
1466
- ATTACHMENT: 11
1467
- };
1468
- var sleep = /* @__PURE__ */ __name((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)), "sleep");
1469
- function hashDefinition(def) {
1470
- const serialized = JSON.stringify(def) + "::" + (def.version ?? "");
1471
- return import_crypto.default.createHash("sha256").update(serialized).digest("hex");
1472
- }
1473
- __name(hashDefinition, "hashDefinition");
1474
- var PersistentDiscordSlashCommandService = class {
1475
- constructor(ctx, adapter, logger6) {
1476
- this.ctx = ctx;
1477
- this.adapter = adapter;
1478
- this.logger = logger6;
1479
- }
1480
- static {
1481
- __name(this, "PersistentDiscordSlashCommandService");
1482
- }
1483
- platform = "discord";
1484
- lockOwner = `noah-slash-${process.pid}-${Math.random().toString(36).slice(2)}`;
1485
- async registerDeclaration(def) {
1486
- const payload = {
1487
- key: def.key,
1488
- json: JSON.stringify(def),
1489
- version: def.version ?? null,
1490
- lastDeclaredAt: /* @__PURE__ */ new Date()
1491
- };
1492
- const existing = await this.ctx.database.get("slash_command", { key: def.key });
1493
- if (existing.length) {
1494
- await this.ctx.database.set(
1495
- "slash_command",
1496
- { key: def.key },
1497
- {
1498
- json: payload.json,
1499
- version: payload.version,
1500
- lastDeclaredAt: payload.lastDeclaredAt
1501
- }
1502
- );
1503
- } else {
1504
- await this.ctx.database.create("slash_command", payload);
1505
- }
1506
- }
1507
- async listDeclarations() {
1508
- const rows = await this.ctx.database.get("slash_command", {});
1509
- return rows.map((row) => JSON.parse(row.json));
1510
- }
1511
- async previewGuild(guildId) {
1512
- return this.previewScope({ type: "guild", id: guildId });
1513
- }
1514
- async previewScope(target) {
1515
- const plan = await this.buildPlan(target);
1516
- return {
1517
- create: plan.toCreate.map((item) => item.def.key),
1518
- update: plan.toUpdate.map((item) => item.def.key),
1519
- delete: plan.toDelete.map((item) => item.persisted.key)
1520
- };
1521
- }
1522
- async syncGuild(guildId, opts) {
1523
- return this.syncScope({ type: "guild", id: guildId }, opts);
1524
- }
1525
- async syncScope(target, opts) {
1526
- const lockKey = target.type === "global" ? "sync:global" : `sync:guild:${target.id}`;
1527
- if (!await this.acquireLock(lockKey, opts?.force)) {
1528
- throw new Error(
1529
- `Another sync is currently running for ${target.type === "global" ? "global" : target.id}`
1530
- );
1495
+ // src/core/commands/locale.ts
1496
+ function locale(ctx, config) {
1497
+ ctx.command("locale", { slash: true }).alias("language").option("channel", "-c").option("reset", "-r").userFields(["locales"]).channelFields(["locales"]).action(async ({ session, options }) => {
1498
+ if (options.channel && !hasPermission(isPluginAdmin(session, config), isGuildAdmin(session))) {
1499
+ return session.text(".noAuth");
1531
1500
  }
1532
- try {
1533
- const plan = await this.buildPlan(target);
1534
- const summary = { created: 0, updated: 0, deleted: 0 };
1535
- for (const entry of plan.toCreate) {
1536
- const result = await this.retryWrapper(
1537
- () => this.createOnScope(target, entry.payload)
1538
- );
1539
- const commandId = this.resolveCommandId(result);
1540
- await this.persistRegisteredCommand(target, entry.def, commandId, entry.payload);
1541
- await this.logSync("create", target, entry.def.key, { response: result });
1542
- summary.created++;
1543
- }
1544
- for (const entry of plan.toUpdate) {
1545
- const commandId = entry.persisted.platformCommandId;
1546
- if (!commandId) {
1547
- const created = await this.retryWrapper(
1548
- () => this.createOnScope(target, entry.payload)
1549
- );
1550
- const commandId2 = this.resolveCommandId(created);
1551
- await this.persistRegisteredCommand(target, entry.def, commandId2, entry.payload);
1552
- await this.logSync("update-create", target, entry.def.key, {
1553
- response: created
1554
- });
1555
- summary.updated++;
1556
- continue;
1557
- }
1558
- try {
1559
- await this.retryWrapper(
1560
- () => this.updateOnScope(target, commandId, entry.payload)
1561
- );
1562
- await this.persistRegisteredCommand(target, entry.def, commandId, entry.payload);
1563
- await this.logSync("update", target, entry.def.key, { ok: true });
1564
- summary.updated++;
1565
- } catch (err) {
1566
- await this.logSync("update-error", target, entry.def.key, {
1567
- error: err instanceof Error ? err.message : String(err)
1568
- });
1569
- await this.retryWrapper(() => this.deleteOnScope(target, commandId2)).catch().catch(() => null);
1570
- const recreated = await this.retryWrapper(
1571
- () => this.createOnScope(target, entry.payload)
1572
- );
1573
- const commandId2 = this.resolveCommandId(recreated);
1574
- await this.persistRegisteredCommand(target, entry.def, commandId2, entry.payload);
1575
- await this.logSync("update-recreate", target, entry.def.key, {
1576
- response: recreated
1577
- });
1578
- summary.updated++;
1579
- }
1580
- }
1581
- for (const entry of plan.toDelete) {
1582
- if (entry.persisted.platformCommandId) {
1583
- await this.retryWrapper(
1584
- () => this.deleteOnScope(target, entry.persisted.platformCommandId)
1585
- ).catch((err) => {
1586
- this.logger.warn(
1587
- `[slash] failed to delete command ${entry.persisted.key}: ${err}`
1588
- );
1589
- if (err?.status !== 404) throw err;
1590
- });
1591
- }
1592
- await this.removeRegisteredCommand(entry.persisted.id);
1593
- await this.logSync("delete", target, entry.persisted.key, { ok: true });
1594
- summary.deleted++;
1595
- }
1596
- return summary;
1597
- } finally {
1598
- await this.releaseLock(lockKey);
1599
- }
1600
- }
1601
- async buildPlan(target) {
1602
- const allDecls = await this.listDeclarations();
1603
- const scopeLabel = target.type === "global" ? "global" : target.id;
1604
- const localDefs = allDecls.filter((def) => this.shouldInclude(def, target));
1605
- const remote = target.type === "global" ? await this.fetchGlobalCommands() : await this.fetchRemoteCommands(target.id);
1606
- const scopeKey = target.type === "global" ? "global" : `guild:${target.id}`;
1607
- const persistedRows = await this.ctx.database.get("slash_registered_command", {
1608
- platform: this.platform,
1609
- scope: scopeKey
1610
- });
1611
- const persistedByKey = /* @__PURE__ */ new Map();
1612
- for (const row of persistedRows) persistedByKey.set(row.key, row);
1613
- const remoteByName = new Map(remote.map((cmd) => [cmd.name, cmd]));
1614
- const toCreate = [];
1615
- const toUpdate = [];
1616
- const toDelete = [];
1617
- for (const def of localDefs) {
1618
- const payload = this.mapToDiscordPayload(def);
1619
- const hash = hashDefinition(def);
1620
- const persisted = persistedByKey.get(def.key);
1621
- if (!persisted) {
1622
- const remoteSameName = remoteByName.get(def.name);
1623
- if (!remoteSameName) {
1624
- toCreate.push({ def, payload });
1625
- } else {
1626
- toUpdate.push({
1627
- def,
1628
- payload,
1629
- persisted: {
1630
- platform: this.platform,
1631
- scope: scopeKey,
1632
- key: def.key,
1633
- platformCommandId: remoteSameName.id,
1634
- hash: null,
1635
- rawPayload: null,
1636
- lastSyncedAt: /* @__PURE__ */ new Date()
1637
- }
1638
- });
1639
- }
1640
- } else if (persisted.hash !== hash) {
1641
- toUpdate.push({ def, payload, persisted });
1642
- }
1643
- }
1644
- for (const persisted of persistedRows) {
1645
- const exists = localDefs.some((def) => def.key === persisted.key);
1646
- if (!exists) {
1647
- toDelete.push({ persisted });
1648
- }
1649
- }
1650
- for (const remoteCmd of remote) {
1651
- const name17 = remoteCmd?.name;
1652
- const id = remoteCmd?.id;
1653
- const matchedLocal = localDefs.some((def) => def.name === name17);
1654
- const matchedPersisted = persistedRows.some((row) => row.platformCommandId === id);
1655
- if (!matchedLocal && !matchedPersisted && id) {
1656
- toDelete.push({
1657
- persisted: {
1658
- platform: this.platform,
1659
- scope: scopeKey,
1660
- key: name17 ?? `remote:${id}`,
1661
- platformCommandId: id,
1662
- hash: null,
1663
- rawPayload: JSON.stringify(remoteCmd),
1664
- lastSyncedAt: /* @__PURE__ */ new Date()
1665
- }
1666
- });
1501
+ if (options.reset) {
1502
+ if (options.channel) {
1503
+ session.channel.locales = [];
1504
+ await session.channel.$update();
1505
+ return session.text(".reset-channel");
1506
+ } else {
1507
+ session.user.locales = [];
1508
+ await session.user.$update();
1509
+ return session.text(".reset-user");
1667
1510
  }
1668
1511
  }
1669
- this.logger.debug?.(
1670
- `[slash] plan for ${scopeLabel}: create=${toCreate.length}, update=${toUpdate.length}, delete=${toDelete.length}`
1671
- );
1672
- return { toCreate, toUpdate, toDelete };
1673
- }
1674
- async persistRegisteredCommand(target, def, commandId, payload) {
1675
- const existing = await this.ctx.database.get("slash_registered_command", {
1676
- platform: this.platform,
1677
- scope: target.type === "global" ? "global" : `guild:${target.id}`,
1678
- key: def.key
1679
- });
1680
- const now = /* @__PURE__ */ new Date();
1681
- const record = {
1682
- platform: this.platform,
1683
- scope: target.type === "global" ? "global" : `guild:${target.id}`,
1684
- key: def.key,
1685
- platformCommandId: commandId,
1686
- hash: hashDefinition(def),
1687
- rawPayload: JSON.stringify(payload),
1688
- lastSyncedAt: now
1689
- };
1690
- if (existing.length) {
1691
- await this.ctx.database.set("slash_registered_command", { id: existing[0].id }, record);
1512
+ const target = options.channel ? "channel" : "user";
1513
+ let lang;
1514
+ if (session.platform === "discord") {
1515
+ const buttons = [
1516
+ (0, import_koishi4.h)("button", { id: "locale/zh-CN", type: "primary" }, "简体中文"),
1517
+ (0, import_koishi4.h)("button", { id: "locale/en-US", type: "secondary" }, "English")
1518
+ ];
1519
+ const label = target === "channel" ? "群聊" : "你";
1520
+ await session.send((0, import_koishi4.h)("message", [`选择${label}使用的语言:`, ...buttons]));
1521
+ const click = await session.prompt();
1522
+ if (!click) return session.text("commands.timeout");
1523
+ const match = click.match(/^locale\/(zh-CN|en-US)$/);
1524
+ if (!match) return session.text(".invalid-select");
1525
+ lang = match[1];
1692
1526
  } else {
1693
- await this.ctx.database.create("slash_registered_command", record);
1694
- }
1695
- }
1696
- async removeRegisteredCommand(id) {
1697
- if (!id) return;
1698
- await this.ctx.database.remove("slash_registered_command", { id });
1699
- }
1700
- shouldInclude(def, target) {
1701
- if (!def.scopes || def.scopes.length === 0) {
1702
- return target.type === "global";
1527
+ await session.send(session.text(`.set-${target}`));
1528
+ const select = await session.prompt();
1529
+ if (!select) return session.text("commands.timeout");
1530
+ if (select === "q") return session.text(".quit");
1531
+ const selectNum = parseInt(select, 10);
1532
+ if (isNaN(selectNum) || selectNum < 1 || selectNum > 2) {
1533
+ return session.text(".invalid-select");
1534
+ }
1535
+ lang = selectNum === 1 ? "zh-CN" : "en-US";
1703
1536
  }
1704
- if (target.type === "global") {
1705
- return def.scopes.some((scope) => !scope.guildId);
1537
+ if (options.channel) {
1538
+ updateLocales(session.channel, lang);
1539
+ await session.channel.$update();
1540
+ } else {
1541
+ updateLocales(session.user, lang);
1542
+ await session.user.$update();
1706
1543
  }
1707
- return def.scopes.some((scope) => scope.guildId === target.id);
1708
- }
1709
- mapToDiscordPayload(def) {
1710
- const description = this.normalizeLocalized(def.description, def.description_localizations);
1711
- return {
1712
- name: def.name,
1713
- name_localizations: def.name_localizations,
1714
- description: description.text,
1715
- description_localizations: description.locales,
1716
- options: def.options?.map((opt) => this.mapOption(opt)),
1717
- default_member_permissions: def.default_member_permissions === void 0 ? null : def.default_member_permissions,
1718
- dm_permission: def.dm_permission,
1719
- integration_types: def.integration_types,
1720
- contexts: def.contexts,
1721
- nsfw: def.nsfw
1722
- };
1723
- }
1724
- mapOption(option) {
1725
- const description = this.normalizeLocalized(
1726
- option.description,
1727
- option.description_localizations,
1728
- "Configure option"
1729
- );
1730
- return {
1731
- type: OPTION_TYPE_MAP[option.type] ?? OPTION_TYPE_MAP.STRING,
1732
- name: option.name,
1733
- name_localizations: option.name_localizations,
1734
- description: description.text,
1735
- description_localizations: description.locales,
1736
- required: option.required,
1737
- options: option.options?.map((sub) => this.mapOption(sub)),
1738
- choices: option.choices?.map((choice) => {
1739
- const localized = this.normalizeLocalized(choice.name, void 0, "choice");
1740
- return {
1741
- name: localized.text,
1742
- name_localizations: localized.locales,
1743
- value: choice.value
1744
- };
1745
- }),
1746
- channel_types: option.channel_types,
1747
- min_value: option.min_value,
1748
- max_value: option.max_value,
1749
- min_length: option.min_length,
1750
- max_length: option.max_length,
1751
- autocomplete: option.autocomplete
1752
- };
1544
+ return session.text(".success");
1545
+ });
1546
+ }
1547
+ __name(locale, "locale");
1548
+ function updateLocales(target, lang) {
1549
+ const index = target.locales.indexOf(lang);
1550
+ if (index !== -1) {
1551
+ target.locales.splice(index, 1);
1753
1552
  }
1754
- normalizeLocalized(value, explicit, fallback = "...") {
1755
- if (!value && !explicit) {
1756
- return { text: fallback, locales: void 0 };
1757
- }
1758
- if (typeof value === "string") {
1759
- return { text: value, locales: explicit };
1760
- }
1761
- if (value && typeof value === "object") {
1762
- const text = value["en-US"] ?? value["en"] ?? Object.values(value)[0] ?? fallback;
1763
- return { text, locales: { ...value, ...explicit ?? {} } };
1553
+ target.locales.unshift(lang);
1554
+ }
1555
+ __name(updateLocales, "updateLocales");
1556
+
1557
+ // src/core/commands/maintain.ts
1558
+ function maintain(ctx, config) {
1559
+ ctx.command("maintain", { slash: true }).alias("维护").option("stop", "-s").action(async ({ session, options }) => {
1560
+ if (!hasPermission(isPluginAdmin(session, config))) {
1561
+ return session.text(".no-auth");
1764
1562
  }
1765
- return { text: fallback, locales: explicit };
1766
- }
1767
- async acquireLock(name17, force = false) {
1768
- const existing = await this.ctx.database.get("slash_lock", { name: name17 });
1769
- if (existing.length) {
1770
- if (existing[0].owner === this.lockOwner) return true;
1771
- if (!force) return false;
1772
- await this.ctx.database.remove("slash_lock", { name: name17 });
1563
+ let card2 = "";
1564
+ let text = "";
1565
+ if (options.stop) {
1566
+ session.send(session.text(".stop"));
1567
+ card2 = config.guildNameCards[0];
1568
+ text = ".success-stop";
1569
+ } else {
1570
+ session.send(session.text(".start"));
1571
+ card2 = "Noah | 🚧维护中";
1572
+ text = ".success-start";
1773
1573
  }
1774
- try {
1775
- await this.ctx.database.create("slash_lock", {
1776
- name: name17,
1777
- owner: this.lockOwner,
1778
- acquiredAt: /* @__PURE__ */ new Date()
1779
- });
1780
- return true;
1781
- } catch (err) {
1782
- this.logger.debug(`[slash] failed to acquire lock ${name17}: ${err}`);
1783
- return false;
1784
- }
1785
- }
1786
- async releaseLock(name17) {
1787
- await this.ctx.database.remove("slash_lock", { name: name17, owner: this.lockOwner });
1788
- }
1789
- async releaseAllLocks() {
1790
- await this.ctx.database.remove("slash_lock", { owner: this.lockOwner });
1791
- }
1792
- async logSync(action, target, key, details) {
1793
- await this.ctx.database.create("slash_sync_log", {
1794
- platform: this.platform,
1795
- scope: target.type === "global" ? "global" : `guild:${target.id}`,
1796
- action,
1797
- key,
1798
- details: JSON.stringify(details ?? {}),
1799
- createdAt: /* @__PURE__ */ new Date()
1800
- });
1801
- }
1802
- async retryWrapper(fn, retries = 5) {
1803
- let attempt = 0;
1804
- while (true) {
1805
- try {
1806
- return await fn();
1807
- } catch (err) {
1808
- attempt++;
1809
- const status = err?.status;
1810
- if (status === 429 && attempt <= retries) {
1811
- const retryAfter = this.parseRetryAfter(err);
1812
- await sleep(retryAfter ?? Math.min(2e3 * attempt, 1e4));
1813
- continue;
1814
- }
1815
- if (status && status >= 500 && attempt < retries) {
1816
- await sleep(500 * attempt);
1817
- continue;
1818
- }
1819
- throw err;
1820
- }
1821
- }
1822
- }
1823
- parseRetryAfter(err) {
1824
- if (typeof err?.retryAfter === "number") {
1825
- return err.retryAfter;
1826
- }
1827
- return null;
1828
- }
1829
- async fetchRemoteCommands(guildId) {
1830
- const raw = await this.adapter.listGuild(guildId);
1831
- return this.normalizeRemoteCommands(raw, `guild:${guildId}`);
1832
- }
1833
- async fetchGlobalCommands() {
1834
- const raw = await this.adapter.listGlobal();
1835
- return this.normalizeRemoteCommands(raw, "global");
1836
- }
1837
- async createOnScope(target, payload) {
1838
- return target.type === "global" ? this.adapter.createGlobal(payload) : this.adapter.createGuild(target.id, payload);
1839
- }
1840
- async updateOnScope(target, commandId, payload) {
1841
- return target.type === "global" ? this.adapter.updateGlobal(commandId, payload) : this.adapter.updateGuild(target.id, commandId, payload);
1842
- }
1843
- async deleteOnScope(target, commandId) {
1844
- return target.type === "global" ? this.adapter.deleteGlobal(commandId) : this.adapter.deleteGuild(target.id, commandId);
1845
- }
1846
- async normalizeRemoteCommands(raw, scopeLabel) {
1847
- if (Array.isArray(raw)) return raw;
1848
- const rawData = raw?.data;
1849
- if (Array.isArray(rawData)) return rawData;
1850
- const jsonFn = raw && typeof raw.json === "function" ? raw.json : null;
1851
- if (jsonFn) {
1852
- const data = await jsonFn.call(raw);
1853
- if (Array.isArray(data)) return data;
1854
- }
1855
- this.logger.warn(`[slash] unexpected command payload for ${scopeLabel}, defaulting to []`);
1856
- return [];
1857
- }
1858
- resolveCommandId(result) {
1859
- if (result && typeof result === "object") {
1860
- if (typeof result.id === "string") return result.id;
1861
- if (result.data && typeof result.data.id === "string") return result.data.id;
1862
- }
1863
- throw new Error("Unable to resolve command id from Discord response.");
1864
- }
1865
- async importRemoteCommands(target) {
1866
- const remote = target.type === "global" ? await this.fetchGlobalCommands() : await this.fetchRemoteCommands(target.id);
1867
- console.log(
1868
- `[slash] fetched ${remote.length} commands from ${target.type === "global" ? "global" : target.id}`,
1869
- remote.map((c) => ({ id: c?.id, name: c?.name }))
1870
- );
1871
- const scope = target.type === "global" ? "global" : `guild:${target.id}`;
1872
- const inserted = [];
1873
- for (const command of remote) {
1874
- const platformCommandId = typeof command?.id === "string" ? command.id : typeof command?.data?.id === "string" ? command.data.id : void 0;
1875
- const key = platformCommandId ? `remote:${platformCommandId}` : `remote:${scope}:${command?.name ?? Math.random().toString(36).slice(2)}`;
1876
- const exists = await this.ctx.database.get("slash_registered_command", {
1877
- platform: this.platform,
1878
- scope,
1879
- key
1880
- });
1881
- if (exists.length) continue;
1882
- await this.ctx.database.create("slash_registered_command", {
1883
- platform: this.platform,
1884
- scope,
1885
- key,
1886
- platformCommandId,
1887
- hash: null,
1888
- rawPayload: JSON.stringify(command),
1889
- lastSyncedAt: /* @__PURE__ */ new Date()
1890
- });
1891
- inserted.push(key);
1892
- }
1893
- return inserted;
1894
- }
1895
- };
1896
-
1897
- // src/slash/discord/adapter.ts
1898
- var DiscordAdapter = class {
1899
- constructor(ctx, appId, token) {
1900
- this.ctx = ctx;
1901
- this.appId = appId;
1902
- this.token = token;
1903
- if (!appId || !token) {
1904
- throw new Error("Discord application id and bot token are required for slash sync");
1905
- }
1906
- }
1907
- static {
1908
- __name(this, "DiscordAdapter");
1909
- }
1910
- baseURL = "https://discord.com/api/v10";
1911
- headers() {
1912
- return {
1913
- Authorization: `Bot ${this.token}`,
1914
- "Content-Type": "application/json"
1915
- };
1916
- }
1917
- async listGuild(guildId) {
1918
- return this.request(`/applications/${this.appId}/guilds/${guildId}/commands`, "GET");
1919
- }
1920
- async listGlobal() {
1921
- return this.request(`/applications/${this.appId}/commands`, "GET");
1922
- }
1923
- async createGlobal(payload) {
1924
- return this.request(`/applications/${this.appId}/commands`, "POST", payload);
1925
- }
1926
- async updateGlobal(commandId, payload) {
1927
- return this.request(`/applications/${this.appId}/commands/${commandId}`, "PATCH", payload);
1928
- }
1929
- async deleteGlobal(commandId) {
1930
- await this.request(
1931
- `/applications/${this.appId}/commands/${commandId}`,
1932
- "DELETE",
1933
- void 0,
1934
- false
1935
- );
1936
- return null;
1937
- }
1938
- async createGuild(guildId, payload) {
1939
- return this.request(
1940
- `/applications/${this.appId}/guilds/${guildId}/commands`,
1941
- "POST",
1942
- payload
1943
- );
1944
- }
1945
- async updateGuild(guildId, commandId, payload) {
1946
- return this.request(
1947
- `/applications/${this.appId}/guilds/${guildId}/commands/${commandId}`,
1948
- "PATCH",
1949
- payload
1950
- );
1951
- }
1952
- async deleteGuild(guildId, commandId) {
1953
- await this.request(
1954
- `/applications/${this.appId}/guilds/${guildId}/commands/${commandId}`,
1955
- "DELETE",
1956
- void 0,
1957
- false
1958
- );
1959
- return null;
1960
- }
1961
- async bulkOverwriteGuild(guildId, payloads) {
1962
- return this.request(
1963
- `/applications/${this.appId}/guilds/${guildId}/commands`,
1964
- "PUT",
1965
- payloads
1966
- );
1967
- }
1968
- async request(path3, method, data, expectJson = true) {
1969
- try {
1970
- return await this.ctx.http(method, `${this.baseURL}${path3}`, {
1971
- headers: this.headers(),
1972
- data,
1973
- ...expectJson ? { responseType: "json" } : {}
1974
- });
1975
- } catch (error) {
1976
- throw this.makeError(error);
1977
- }
1978
- }
1979
- makeError(err) {
1980
- const status = err?.response?.status ?? err?.status;
1981
- const statusText = err?.response?.statusText ?? "";
1982
- const data = err?.response?.data;
1983
- const body = typeof data === "string" ? data : data ? JSON.stringify(data) : err?.message ?? "";
1984
- const wrapped = new Error(`Discord API ${status ?? "ERR"} ${statusText}: ${body}`);
1985
- const retryAfterHeader = err?.response?.headers?.["retry-after"] ?? err?.response?.headers?.["Retry-After"];
1986
- if (status) wrapped.status = status;
1987
- if (body) wrapped.body = body;
1988
- if (retryAfterHeader) {
1989
- const retryAfterSeconds = Number(retryAfterHeader);
1990
- if (Number.isFinite(retryAfterSeconds)) {
1991
- ;
1992
- wrapped.retryAfter = retryAfterSeconds * 1e3;
1993
- }
1994
- }
1995
- return wrapped;
1996
- }
1997
- };
1998
-
1999
- // src/slash/discord/index.ts
2000
- var DiscordSlashService = class {
2001
- constructor(ctx, config, logger6) {
2002
- this.ctx = ctx;
2003
- this.config = config;
2004
- this.logger = logger6;
2005
- const adapter = new DiscordAdapter(ctx, config.discord_application_id, config.discord_token);
2006
- this.slashService = new PersistentDiscordSlashCommandService(ctx, adapter, this.logger);
2007
- this.ready = this.seedDeclarations().catch((err) => {
2008
- this.logger.error(
2009
- `[slash] failed to initialize declarations: ${err instanceof Error ? err.message : String(err)}`
2010
- );
2011
- throw err;
2012
- });
2013
- this.stopRegistryListener = onSlashCommandRegistered(
2014
- (def) => this.handleNewDeclaration(def)
2015
- );
2016
- this.registerCli();
2017
- this.setupAutoSync();
2018
- this.ctx.on("dispose", () => this.slashService.releaseAllLocks());
2019
- this.ctx.on("dispose", () => this.dispose());
2020
- }
2021
- static {
2022
- __name(this, "DiscordSlashService");
2023
- }
2024
- slashService;
2025
- stopRegistryListener;
2026
- ready;
2027
- async seedDeclarations() {
2028
- for (const def of getRegisteredSlashCommands()) {
2029
- await this.slashService.registerDeclaration(def);
2030
- }
2031
- }
2032
- async handleNewDeclaration(def) {
2033
- try {
2034
- await this.slashService.registerDeclaration(def);
2035
- } catch (err) {
2036
- this.logger.error(
2037
- `[slash] failed to persist declaration ${def.key}: ${err instanceof Error ? err.message : String(err)}`
2038
- );
2039
- }
2040
- }
2041
- setupAutoSync() {
2042
- const enableAutoSync = this.config.auto_sync_on_start ?? true;
2043
- if (!enableAutoSync) return;
2044
- this.ctx.on("ready", async () => {
2045
- await this.ready;
2046
- const guilds = this.resolveGuilds();
2047
- if (!guilds.length) {
2048
- this.logger.warn("[slash] no guilds configured for Discord sync; skip auto sync");
2049
- return;
2050
- }
2051
- for (const guild of guilds) {
2052
- try {
2053
- const summary = await this.slashService.syncScope({
2054
- type: "guild",
2055
- id: guild
2056
- });
2057
- this.logger.info(this.formatSummary(guild, summary));
2058
- } catch (err) {
2059
- this.logger.error(
2060
- `[slash] failed to sync guild ${guild}: ${err instanceof Error ? err.message : String(err)}`
2061
- );
2062
- }
2063
- }
2064
- });
2065
- }
2066
- registerCli() {
2067
- this.ctx.command("slash.sync [targets:text]").option("preview", "-p, --preview").option("force", "-f, --force").action(async ({ options }, targetsArg) => {
2068
- await this.ready;
2069
- const targets = this.resolveTargets(targetsArg);
2070
- if (!targets.length) return "No targets configured for slash sync.";
2071
- const lines = [];
2072
- for (const target of targets) {
2073
- const label = target.type === "global" ? "global" : target.id;
2074
- if (options?.preview) {
2075
- const diff = await this.slashService.previewScope(target);
2076
- lines.push(this.formatPreview(label, diff));
2077
- } else {
2078
- const summary = await this.slashService.syncScope(target, {
2079
- force: options?.force
2080
- });
2081
- lines.push(this.formatSummary(label, summary));
2082
- }
2083
- }
2084
- return lines.join("\n");
2085
- });
2086
- this.ctx.command("slash.preview [targets:text]").action(async (_, targetsArg) => {
2087
- await this.ready;
2088
- const targets = this.resolveTargets(targetsArg);
2089
- if (!targets.length) return "No targets configured for slash sync.";
2090
- const lines = [];
2091
- for (const target of targets) {
2092
- const label = target.type === "global" ? "global" : target.id;
2093
- const diff = await this.slashService.previewScope(target);
2094
- lines.push(this.formatPreview(label, diff));
2095
- }
2096
- return lines.join("\n");
2097
- });
2098
- this.ctx.command("slash.import-remote [targets:text]").action(async (_, targetsArg) => {
2099
- await this.ready;
2100
- const targets = this.resolveImportTargets(targetsArg);
2101
- if (!targets.length) return "No targets configured for slash import.";
2102
- const lines = [];
2103
- for (const target of targets) {
2104
- const inserted = await this.slashService.importRemoteCommands(target);
2105
- const label = target.type === "global" ? "global" : target.id;
2106
- lines.push(`[${label}] imported ${inserted.length} commands`);
2107
- }
2108
- return lines.join("\n");
2109
- });
2110
- }
2111
- resolveTargets(overrides) {
2112
- const tokens = overrides ? overrides.split(/[,;\s]+/).map((t) => t.trim()).filter(Boolean) : [];
2113
- const targets = [];
2114
- const seenGuilds = /* @__PURE__ */ new Set();
2115
- let globalAdded = false;
2116
- const baseGuilds = tokens.length > 0 ? [] : Array.from(
2117
- /* @__PURE__ */ new Set([
2118
- ...this.config.test_guilds ?? [],
2119
- ...process.env.DEV_GUILD_ID ? [process.env.DEV_GUILD_ID] : []
2120
- ])
2121
- ).filter(Boolean);
2122
- const source = tokens.length > 0 ? tokens : ["global", ...baseGuilds];
2123
- for (const token of source) {
2124
- if (token.toLowerCase() === "global") {
2125
- if (!globalAdded) {
2126
- targets.push({ type: "global" });
2127
- globalAdded = true;
2128
- }
2129
- continue;
2130
- }
2131
- if (seenGuilds.has(token)) continue;
2132
- seenGuilds.add(token);
2133
- targets.push({ type: "guild", id: token });
2134
- }
2135
- return targets;
2136
- }
2137
- resolveImportTargets(overrides) {
2138
- const tokens = overrides ? overrides.split(/[,;\s]+/).map((token) => token.trim()).filter(Boolean) : [];
2139
- const targets = [];
2140
- const guilds = tokens.length > 0 ? [] : Array.from(
2141
- /* @__PURE__ */ new Set([
2142
- ...this.config.test_guilds ?? [],
2143
- ...process.env.DEV_GUILD_ID ? [process.env.DEV_GUILD_ID] : []
2144
- ])
2145
- ).filter(Boolean);
2146
- const seenGuilds = /* @__PURE__ */ new Set();
2147
- let globalAdded = false;
2148
- const processToken = /* @__PURE__ */ __name((token) => {
2149
- if (token.toLowerCase() === "global") {
2150
- if (!globalAdded) {
2151
- targets.push({ type: "global" });
2152
- globalAdded = true;
2153
- }
2154
- return;
2155
- }
2156
- if (!seenGuilds.has(token)) {
2157
- seenGuilds.add(token);
2158
- targets.push({ type: "guild", id: token });
2159
- }
2160
- }, "processToken");
2161
- if (tokens.length) {
2162
- tokens.forEach(processToken);
2163
- } else {
2164
- processToken("global");
2165
- guilds.forEach(processToken);
2166
- }
2167
- return targets;
2168
- }
2169
- formatPreview(guildId, diff) {
2170
- const parts = [];
2171
- if (diff.create.length) parts.push(`create(${diff.create.join(", ")})`);
2172
- if (diff.update.length) parts.push(`update(${diff.update.join(", ")})`);
2173
- if (diff.delete.length) parts.push(`delete(${diff.delete.join(", ")})`);
2174
- const detail = parts.length ? parts.join(" | ") : "up-to-date";
2175
- return `[${guildId}] ${detail}`;
2176
- }
2177
- formatSummary(guildId, summary) {
2178
- return `[${guildId}] +${summary.created} / ~${summary.updated} / -${summary.deleted}`;
2179
- }
2180
- dispose() {
2181
- this.stopRegistryListener();
2182
- }
2183
- resolveGuilds() {
2184
- const configured = this.config.test_guilds ?? [];
2185
- const envGuild = process.env.DEV_GUILD_ID ? [process.env.DEV_GUILD_ID] : [];
2186
- return Array.from(/* @__PURE__ */ new Set([...configured, ...envGuild])).filter(Boolean);
2187
- }
2188
- };
2189
-
2190
- // src/slash/locales/en-US.yml
2191
- var en_US_default2 = { _config: { $desc: "Slash Module Settings", discord_token: "**Discord Token**", discord_application_id: "**Discord Application ID**", test_guilds: "**Guilds To Sync**", auto_sync_on_start: "**Auto Sync on Connect**" } };
2192
-
2193
- // src/slash/locales/zh-CN.yml
2194
- var zh_CN_default2 = { _config: { $desc: "Slash 模块设置", discord_token: "**Discord Token**", discord_application_id: "**Discord Application ID**", test_guilds: "**默认同步 Guild 列表**", auto_sync_on_start: "**连接后自动同步**" } };
2195
-
2196
- // src/slash/index.ts
2197
- var name3 = "Noah-Slash";
2198
- var inject = ["database"];
2199
- var logger2 = new import_koishi3.Logger("Noah-Slash");
2200
- async function apply3(ctx, config) {
2201
- ;
2202
- [
2203
- ["en-US", en_US_default2],
2204
- ["zh-CN", zh_CN_default2]
2205
- ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
2206
- ctx.plugin(database_exports, config.core);
2207
- if (!config.slash.discord_token || !config.slash.discord_application_id) {
2208
- logger2.warn("Discord token or application id missing, slash sync disabled.");
2209
- return;
2210
- }
2211
- new DiscordSlashService(ctx, config.slash, logger2);
2212
- }
2213
- __name(apply3, "apply");
2214
-
2215
- // src/core/commands/help.ts
2216
- var helpSlashCommand = {
2217
- key: "core.help",
2218
- name: "help",
2219
- description: "Display Noah help menu.",
2220
- description_localizations: {
2221
- "zh-CN": "显示 Noah 帮助菜单"
2222
- },
2223
- version: "1.0.0"
2224
- };
2225
- registerSlashCommand(helpSlashCommand);
2226
- function help(ctx, config) {
2227
- ctx.command("noah.help").alias("帮助").action(({ session }) => {
2228
- return session.text(".content");
2229
- });
2230
- }
2231
- __name(help, "help");
2232
-
2233
- // src/core/utils/role.ts
2234
- function hasPermission(...perms) {
2235
- return perms.some((perm) => perm === true);
2236
- }
2237
- __name(hasPermission, "hasPermission");
2238
- function isPluginAdmin(session, config) {
2239
- return config.adminUsers?.includes(session.userId) ?? false;
2240
- }
2241
- __name(isPluginAdmin, "isPluginAdmin");
2242
- function isGuildAdmin(session) {
2243
- const platform = session.event.platform;
2244
- let guildMember;
2245
- if (platform === "onebot") {
2246
- if (session.guildId == null) return true;
2247
- guildMember = session.event.member.roles[0];
2248
- return guildMember === "owner" || guildMember === "admin" || guildMember === "SUBCHANNEL_ADMIN" || guildMember === "OWNER" || guildMember === "ADMIN";
2249
- } else if (platform === "qq") {
2250
- return true;
2251
- } else {
2252
- return true;
2253
- }
2254
- }
2255
- __name(isGuildAdmin, "isGuildAdmin");
2256
-
2257
- // src/core/commands/locale.ts
2258
- function locale(ctx, config) {
2259
- ctx.command("locale").alias("language").option("channel", "-c").option("reset", "-r").userFields(["locales"]).channelFields(["locales"]).action(async ({ session, options }) => {
2260
- if (options.channel && !hasPermission(isPluginAdmin(session, config), isGuildAdmin(session))) {
2261
- return session.text(".noAuth");
2262
- }
2263
- if (options.reset) {
2264
- if (options.channel) {
2265
- session.channel.locales = [];
2266
- await session.channel.$update();
2267
- return session.text(".reset-channel");
2268
- } else {
2269
- session.user.locales = [];
2270
- await session.user.$update();
2271
- return session.text(".reset-user");
2272
- }
2273
- }
2274
- const target = options.channel ? "channel" : "user";
2275
- await session.send(session.text(`.set-${target}`));
2276
- const select = await session.prompt();
2277
- if (!select) return session.text("commands.timeout");
2278
- if (select === "q") return session.text(".quit");
2279
- const selectNum = parseInt(select, 10);
2280
- if (isNaN(selectNum) || selectNum < 1 || selectNum > 2) {
2281
- return session.text(".invalid-select");
2282
- }
2283
- const lang = selectNum === 1 ? "zh-CN" : "en-US";
2284
- if (options.channel) {
2285
- updateLocales(session.channel, lang);
2286
- await session.channel.$update();
2287
- } else {
2288
- updateLocales(session.user, lang);
2289
- await session.user.$update();
2290
- }
2291
- return session.text(".success");
2292
- });
2293
- }
2294
- __name(locale, "locale");
2295
- function updateLocales(target, lang) {
2296
- const index = target.locales.indexOf(lang);
2297
- if (index !== -1) {
2298
- target.locales.splice(index, 1);
2299
- }
2300
- target.locales.unshift(lang);
2301
- }
2302
- __name(updateLocales, "updateLocales");
2303
-
2304
- // src/core/commands/maintain.ts
2305
- function maintain(ctx, config) {
2306
- ctx.command("maintain").alias("维护").option("stop", "-s").action(async ({ session, options }) => {
2307
- if (!hasPermission(isPluginAdmin(session, config))) {
2308
- return session.text(".no-auth");
2309
- }
2310
- let card2 = "";
2311
- let text = "";
2312
- if (options.stop) {
2313
- session.send(session.text(".stop"));
2314
- card2 = config.guildNameCards[0];
2315
- text = ".success-stop";
2316
- } else {
2317
- session.send(session.text(".start"));
2318
- card2 = "Noah | 🚧维护中";
2319
- text = ".success-start";
2320
- }
2321
- const bots = ctx.bots;
2322
- for (const bot of bots) {
2323
- if (bot.platform !== "onebot") continue;
2324
- const guilds = bot.getGuildIter();
2325
- for await (const guild of guilds) {
2326
- await bot.internal.setGroupCard(guild.id, bot.user.id, card2);
2327
- }
2328
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
1574
+ const bots = ctx.bots;
1575
+ for (const bot of bots) {
1576
+ if (bot.platform !== "onebot") continue;
1577
+ const guilds = bot.getGuildIter();
1578
+ for await (const guild of guilds) {
1579
+ await bot.internal.setGroupCard(guild.id, bot.user.id, card2);
1580
+ }
1581
+ await new Promise((resolve3) => setTimeout(resolve3, 3e3));
2329
1582
  }
2330
1583
  return session.text(text);
2331
1584
  });
@@ -2333,7 +1586,7 @@ function maintain(ctx, config) {
2333
1586
  __name(maintain, "maintain");
2334
1587
 
2335
1588
  // src/core/commands/server.ts
2336
- var import_koishi4 = require("koishi");
1589
+ var import_koishi5 = require("koishi");
2337
1590
 
2338
1591
  // src/types/index.ts
2339
1592
  var serverValues = ["asphyxia", "mao", "official"];
@@ -2354,8 +1607,50 @@ function normalizeUrl(url) {
2354
1607
  }
2355
1608
  }
2356
1609
  __name(normalizeUrl, "normalizeUrl");
1610
+ async function promptServerSelect(session, servers, defaultId, prefix) {
1611
+ if (session.platform === "discord") {
1612
+ const buttons = [
1613
+ (0, import_koishi5.h)("button", { id: `${prefix}/0`, type: "secondary" }, "➕ 添加新服务器"),
1614
+ ...servers.map(
1615
+ (srv, i) => (0, import_koishi5.h)(
1616
+ "button",
1617
+ {
1618
+ id: `${prefix}/${i + 1}`,
1619
+ type: srv.id === defaultId ? "primary" : "secondary"
1620
+ },
1621
+ srv.name
1622
+ )
1623
+ )
1624
+ ];
1625
+ await session.send((0, import_koishi5.h)("message", ["请选择一个服务器:", ...buttons]));
1626
+ const click = await session.prompt();
1627
+ if (!click) return null;
1628
+ const match = click.match(new RegExp(`^${prefix.replace(".", "\\.")}\\/(\\d+)$`));
1629
+ if (!match) return -1;
1630
+ const n = Number(match[1]);
1631
+ if (n > servers.length) return -1;
1632
+ return n;
1633
+ }
1634
+ let serverListMsg = "";
1635
+ for (let i = 0; i < servers.length; i++) {
1636
+ if (servers[i].id === defaultId) {
1637
+ serverListMsg += `<p>${i + 1}. ${servers[i].name} < 默认服务器</p>`;
1638
+ } else {
1639
+ serverListMsg += `<p>${i + 1}. ${servers[i].name}</p>`;
1640
+ }
1641
+ }
1642
+ const msg = import_koishi5.h.unescape(session.text(".menu-select", { server_list: serverListMsg })).replace("< 默认服务器", "&lt; 默认服务器");
1643
+ await session.send(msg);
1644
+ const select = await session.prompt();
1645
+ if (!select) return null;
1646
+ if (select === "q") return -2;
1647
+ const selectNum = parseInt(select, 10);
1648
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > servers.length) return -1;
1649
+ return selectNum;
1650
+ }
1651
+ __name(promptServerSelect, "promptServerSelect");
2357
1652
  function server(ctx, config) {
2358
- ctx.command("server").alias("服务器管理").option("channel", "-c").userFields(["id"]).channelFields(["id"]).action(async ({ session, options }) => {
1653
+ ctx.command("server", { slash: true }).alias("服务器管理").option("channel", "-c").userFields(["id"]).channelFields(["id"]).action(async ({ session, options }) => {
2359
1654
  const serverService = new ServerService(ctx);
2360
1655
  const userService = new UserService(ctx);
2361
1656
  const atGuild = session.guildId != null;
@@ -2401,24 +1696,11 @@ function server(ctx, config) {
2401
1696
  __name(server, "server");
2402
1697
  async function showChannelServerSelectMenu(ctx, session, serverService, userService, uid, userDefaultServerId, cid, channelDefaultServerId) {
2403
1698
  const res = await serverService.getServersByCid(cid);
2404
- let serverListMsg = "";
2405
- for (let i = 0; i < res.length; i++) {
2406
- if (res[i].id === channelDefaultServerId) {
2407
- serverListMsg += `<p>${i + 1}. ${res[i].name} < 群聊默认服务器</p>`;
2408
- } else {
2409
- serverListMsg += `<p>${i + 1}. ${res[i].name}</p>`;
2410
- }
2411
- }
2412
- const msg = import_koishi4.h.unescape(session.text(".menu-select", { server_list: serverListMsg })).replace("< 群聊默认服务器", "&lt; 群聊默认服务器");
2413
- await session.send(msg);
2414
- const select = await session.prompt();
2415
- if (!select) return session.text("commands.timeout");
2416
- if (select === "q") return session.text(".quit");
2417
- const selectNum = parseInt(select, 10);
2418
- if (isNaN(selectNum) || selectNum < 0 || selectNum > res.length) {
2419
- return session.text(".invalid-select");
2420
- }
2421
- if (selectNum === 0) {
1699
+ const selected = await promptServerSelect(session, res, channelDefaultServerId, "srv.ch");
1700
+ if (selected === null) return session.text("commands.timeout");
1701
+ if (selected === -1) return session.text(".invalid-select");
1702
+ if (selected === -2) return session.text(".quit");
1703
+ if (selected === 0) {
2422
1704
  return addServer(
2423
1705
  ctx,
2424
1706
  session,
@@ -2430,42 +1712,28 @@ async function showChannelServerSelectMenu(ctx, session, serverService, userServ
2430
1712
  cid,
2431
1713
  channelDefaultServerId
2432
1714
  );
2433
- } else {
2434
- return showServerMenu(
2435
- ctx,
2436
- session,
2437
- serverService,
2438
- userService,
2439
- res[selectNum - 1],
2440
- "channel",
2441
- uid,
2442
- userDefaultServerId,
2443
- cid,
2444
- channelDefaultServerId
2445
- );
2446
1715
  }
1716
+ return showServerMenu(
1717
+ ctx,
1718
+ session,
1719
+ serverService,
1720
+ userService,
1721
+ res[selected - 1],
1722
+ "channel",
1723
+ uid,
1724
+ userDefaultServerId,
1725
+ cid,
1726
+ channelDefaultServerId
1727
+ );
2447
1728
  }
2448
1729
  __name(showChannelServerSelectMenu, "showChannelServerSelectMenu");
2449
1730
  async function showUserServerSelectMenu(ctx, session, serverService, userService, uid, userDefaultServerId, cid, channelDefaultServerId) {
2450
1731
  const res = await serverService.getServersByUid(uid);
2451
- let serverListMsg = "";
2452
- for (let i = 0; i < res.length; i++) {
2453
- if (res[i].id === userDefaultServerId) {
2454
- serverListMsg += `<p>${i + 1}. ${res[i].name} < 默认服务器</p>`;
2455
- } else {
2456
- serverListMsg += `<p>${i + 1}. ${res[i].name}</p>`;
2457
- }
2458
- }
2459
- const msg = import_koishi4.h.unescape(session.text(".menu-select", { server_list: serverListMsg })).replace("< 默认服务器", "&lt; 默认服务器");
2460
- await session.send(msg);
2461
- const select = await session.prompt();
2462
- if (!select) return session.text("commands.timeout");
2463
- if (select === "q") return session.text(".quit");
2464
- const selectNum = parseInt(select, 10);
2465
- if (isNaN(selectNum) || selectNum < 0 || selectNum > res.length) {
2466
- return session.text(".invalid-select");
2467
- }
2468
- if (selectNum === 0) {
1732
+ const selected = await promptServerSelect(session, res, userDefaultServerId, "srv.usr");
1733
+ if (selected === null) return session.text("commands.timeout");
1734
+ if (selected === -1) return session.text(".invalid-select");
1735
+ if (selected === -2) return session.text(".quit");
1736
+ if (selected === 0) {
2469
1737
  return addServer(
2470
1738
  ctx,
2471
1739
  session,
@@ -2477,30 +1745,49 @@ async function showUserServerSelectMenu(ctx, session, serverService, userService
2477
1745
  cid,
2478
1746
  channelDefaultServerId
2479
1747
  );
2480
- } else {
2481
- return showServerMenu(
2482
- ctx,
2483
- session,
2484
- serverService,
2485
- userService,
2486
- res[selectNum - 1],
2487
- "user",
2488
- uid,
2489
- userDefaultServerId,
2490
- cid,
2491
- channelDefaultServerId
2492
- );
2493
1748
  }
1749
+ return showServerMenu(
1750
+ ctx,
1751
+ session,
1752
+ serverService,
1753
+ userService,
1754
+ res[selected - 1],
1755
+ "user",
1756
+ uid,
1757
+ userDefaultServerId,
1758
+ cid,
1759
+ channelDefaultServerId
1760
+ );
2494
1761
  }
2495
1762
  __name(showUserServerSelectMenu, "showUserServerSelectMenu");
2496
1763
  async function showServerMenu(ctx, session, serverService, userService, server2, from, uid, userDefaultServerId, cid, channelDefaultServerId) {
2497
- await session.send(session.text(".menu", server2));
2498
- const select = await session.prompt();
2499
- if (!select) return session.text("commands.timeout");
2500
- if (select === "q") return session.text(".quit");
2501
- const selectNum = parseInt(select, 10);
2502
- if (isNaN(selectNum) || selectNum < 0 || selectNum > 4) {
2503
- return session.text(".invalid-select");
1764
+ let selectNum;
1765
+ if (session.platform === "discord") {
1766
+ const buttons = [
1767
+ (0, import_koishi5.h)("button", { id: "srv.act/1", type: "primary" }, "⭐ 设为默认"),
1768
+ (0, import_koishi5.h)("button", { id: "srv.act/2", type: "secondary" }, "🔗 修改URL"),
1769
+ (0, import_koishi5.h)("button", { id: "srv.act/3", type: "danger" }, "🗑️ 删除"),
1770
+ (0, import_koishi5.h)("button", { id: "srv.act/4", type: "secondary" }, "📝 重命名"),
1771
+ (0, import_koishi5.h)("button", { id: "srv.act/0", type: "secondary" }, "↩️ 返回")
1772
+ ];
1773
+ await session.send(
1774
+ (0, import_koishi5.h)("message", [`**[${server2.name}]**
1775
+ > 类型: ${server2.type}`, ...buttons])
1776
+ );
1777
+ const click = await session.prompt();
1778
+ if (!click) return session.text("commands.timeout");
1779
+ const match = click.match(/^srv\.act\/(\d+)$/);
1780
+ if (!match) return session.text(".invalid-select");
1781
+ selectNum = Number(match[1]);
1782
+ } else {
1783
+ await session.send(session.text(".menu", server2));
1784
+ const select = await session.prompt();
1785
+ if (!select) return session.text("commands.timeout");
1786
+ if (select === "q") return session.text(".quit");
1787
+ selectNum = parseInt(select, 10);
1788
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > 4) {
1789
+ return session.text(".invalid-select");
1790
+ }
2504
1791
  }
2505
1792
  if (selectNum === 0) {
2506
1793
  if (from === "user")
@@ -2618,20 +1905,34 @@ async function showServerMenu(ctx, session, serverService, userService, server2,
2618
1905
  }
2619
1906
  __name(showServerMenu, "showServerMenu");
2620
1907
  async function addServer(ctx, session, serverService, userService, from, uid, userDefaultServerId, cid, channelDefaultServerId) {
2621
- let serverTypeListMsg = "";
2622
- for (let i = 0; i < serverValues.length; i++) {
2623
- serverTypeListMsg += `<p>${i + 1}. ${serverValues[i]}</p>`;
2624
- }
2625
- const msg = import_koishi4.h.unescape(session.text(".add-type", { server_type_list: serverTypeListMsg }));
2626
- await session.send(msg);
2627
- const select = await session.prompt();
2628
- if (!select) return session.text("commands.timeout");
2629
- if (select === "q") return session.text(".quit");
2630
- const selectNum = parseInt(select, 10);
2631
- if (isNaN(selectNum) || selectNum < 1 || selectNum > serverValues.length) {
2632
- return session.text(".invalid-select");
1908
+ let serverType;
1909
+ if (session.platform === "discord") {
1910
+ const buttons = serverValues.map(
1911
+ (type) => (0, import_koishi5.h)("button", { id: `srv.type/${type}`, type: "secondary" }, type)
1912
+ );
1913
+ await session.send((0, import_koishi5.h)("message", ["请选择服务器类型:", ...buttons]));
1914
+ const click = await session.prompt();
1915
+ if (!click) return session.text("commands.timeout");
1916
+ const match = click.match(/^srv\.type\/(.+)$/);
1917
+ if (!match || !serverValues.includes(match[1]))
1918
+ return session.text(".invalid-select");
1919
+ serverType = match[1];
1920
+ } else {
1921
+ let serverTypeListMsg = "";
1922
+ for (let i = 0; i < serverValues.length; i++) {
1923
+ serverTypeListMsg += `<p>${i + 1}. ${serverValues[i]}</p>`;
1924
+ }
1925
+ const msg = import_koishi5.h.unescape(session.text(".add-type", { server_type_list: serverTypeListMsg }));
1926
+ await session.send(msg);
1927
+ const select = await session.prompt();
1928
+ if (!select) return session.text("commands.timeout");
1929
+ if (select === "q") return session.text(".quit");
1930
+ const selectNum = parseInt(select, 10);
1931
+ if (isNaN(selectNum) || selectNum < 1 || selectNum > serverValues.length) {
1932
+ return session.text(".invalid-select");
1933
+ }
1934
+ serverType = serverValues[selectNum - 1];
2633
1935
  }
2634
- const serverType = serverValues[selectNum - 1];
2635
1936
  let url;
2636
1937
  let serverName;
2637
1938
  if (serverType === "mao") {
@@ -2698,25 +1999,26 @@ async function addServer(ctx, session, serverService, userService, from, uid, us
2698
1999
  __name(addServer, "addServer");
2699
2000
 
2700
2001
  // src/core/command.ts
2701
- var name4 = "command";
2702
- function apply4(ctx, config) {
2002
+ var name2 = "command";
2003
+ function apply2(ctx, config) {
2703
2004
  help(ctx, config);
2704
2005
  bind(ctx, config);
2705
2006
  card(ctx, config);
2706
2007
  server(ctx, config);
2707
2008
  locale(ctx, config);
2009
+ link(ctx, config);
2708
2010
  maintain(ctx, config);
2709
2011
  }
2710
- __name(apply4, "apply");
2012
+ __name(apply2, "apply");
2711
2013
 
2712
2014
  // src/core/database.ts
2713
- var database_exports2 = {};
2714
- __export(database_exports2, {
2715
- apply: () => apply5,
2716
- name: () => name5
2015
+ var database_exports = {};
2016
+ __export(database_exports, {
2017
+ apply: () => apply3,
2018
+ name: () => name3
2717
2019
  });
2718
- var name5 = "database";
2719
- function apply5(ctx) {
2020
+ var name3 = "database";
2021
+ function apply3(ctx) {
2720
2022
  ctx.model.extend("user", {
2721
2023
  defaultCardId: "unsigned",
2722
2024
  defaultServerId: "unsigned"
@@ -2784,13 +2086,13 @@ function apply5(ctx) {
2784
2086
  }
2785
2087
  );
2786
2088
  }
2787
- __name(apply5, "apply");
2089
+ __name(apply3, "apply");
2788
2090
 
2789
2091
  // src/core/event.ts
2790
2092
  var event_exports = {};
2791
2093
  __export(event_exports, {
2792
- apply: () => apply6,
2793
- name: () => name6
2094
+ apply: () => apply4,
2095
+ name: () => name4
2794
2096
  });
2795
2097
 
2796
2098
  // src/core/events/friend.ts
@@ -2941,44 +2243,47 @@ function guildNameCard(ctx, config) {
2941
2243
  __name(guildNameCard, "guildNameCard");
2942
2244
 
2943
2245
  // src/core/event.ts
2944
- var name6 = "event";
2945
- function apply6(ctx, config) {
2246
+ var name4 = "event";
2247
+ function apply4(ctx, config) {
2946
2248
  friendRequest(ctx);
2947
2249
  guildRequest(ctx, config);
2948
2250
  guildNameCard(ctx, config);
2949
2251
  }
2950
- __name(apply6, "apply");
2252
+ __name(apply4, "apply");
2951
2253
 
2952
2254
  // src/core/locales/en-US.yml
2953
- var en_US_default3 = { _config: { $desc: "Core Module Settings", adminUsers: "**Plugin Admin** User ID (use inspect command to get)", guildNameCards: "**Group Card** Name List", maoServerUrl: "**Mao Server** URL address", official_support_url: "**Official Support** URL address" }, commands: { maintain: { description: "Switch to maintenance mode", messages: { "no-auth": "<p>You don't have permission to use this feature~</p>", start: "<p>Entering maintenance mode</p>", "success-start": "<p>Successfully switched to maintenance mode</p>", stop: "<p>Exiting maintenance mode</p>", "success-stop": "<p>Successfully exited maintenance mode</p>" } }, timeout: "Noah didn't wait for your reply, please try again!", noah: { help: { description: "Show Noah help information", messages: { content: "<p>Guide:</p>\nhttps://docs.logthm.cn/noah" } } }, locale: { description: "Set language", messages: { "no-auth": "<p>Only group admins can use this feature~</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Quit!</p>", "reset-channel": "<p>Reset successfully!</p>", "reset-user": "<p>Reset successfully!</p>", success: "<p>Set successfully!</p>", "set-channel": "<p>Select the default language for group chats:</p>\n<p>1. 简体中文</p>\n<p>2. English</p>\n<p>q. Quit</p>", "set-user": "<p>Select the language you use:</p>\n<p>1. 中文</p>\n<p>2. English</p>\n<p>q. Quit</p>" } }, bind: { description: "Bind card", messages: { prompt: "<p>Please enter your card number:</p>", "convert-access-failed": "<p>Failed to convert Access Code, please use 16-digit card code instead.</p>", "invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", name: "<p>Received! Please give this card a name, no spaces allowed:</p>", "invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", success: "<p>Bound successfully, your card information is as follows:</p>\n<p>[{cardName}]</p>\n<p>{cardCode}</p>" } }, card: { description: "Manage cards", options: { detail: "Show detailed card information" }, messages: { "invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", "invalid-select": "<p>No such option!</p>", "lookup-error": "Lookup failed: {message}", "lookup-error-unknown": "Lookup failed: unknown error", "menu-select": "<p>[Card Management]</p>\n{card_list}\n<p>0. Add new card</p>\n<p>Please enter the serial number:</p>", menu: "<p>[{name}]</p>\n<p>1. Set as default card</p>\n<p>2. View or modify card number</p>\n<p>3. Delete the card</p>\n<p>4. Rename the card</p>\n<p>5. Bind the card to a specified server</p>\n<p>0. Return to card selection</p>\n<p>Please enter the serial number:</p>", "menu-has-bound-server": "<p>[{name}]</p>\n<p>Bound server: {defaultServerName}</p>\n<p>1. Set as default card</p>\n<p>2. View or modify card number</p>\n<p>3. Delete the card</p>\n<p>4. Rename the card</p>\n<p>5. Bind the card to a specified server</p>\n<p>0. Return to card selection</p>\n<p>Please enter the serial number:</p>", "menu-1-success": "<p>Set successfully!</p>", "menu-1-error-duplicate": "<p>The selected card is the same as the old default card!</p>", "menu-2-prompt": "<p>Card number: {code}</p>\n<p>Please enter the new card number:</p>\n<p>Enter 0 to return</p>", "menu-2-success": "<p>Modified successfully!</p>", "menu-2-error-invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", "menu-3-success": "<p>Deleted successfully!</p>", "menu-4-prompt": "<p>Enter a new name, no spaces allowed:</p>", "menu-4-error-invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", "menu-4-success": "<p>Modified successfully!</p>", "menu-5": "{server_list}\n<p>Please enter the serial number:</p>", "menu-5-success": "<p>Set successfully!</p>", "menu-5-error-duplicate": "<p>The selected server is the same as the old default server!</p>" } }, server: { description: "Manage servers", messages: { "no-channel": "<p>Please use this feature in group chats~</p>", "invalid-select": "<p>No such option!</p>", "no-auth": "<p>Only group admins can use this feature~</p>", "admin-menu-select": "<p>[Group server management]</p>\n{server_list}\n<p>0. Add new server</p>\n<p>Please enter the serial number:</p>", "menu-select": "<p>[Server management]</p>\n{server_list}\n<p>0. Add new server</p>\n<p>Please enter the serial number:</p>", menu: "<p>[{name}]</p>\n<p>Type: {type}</p>\n<p>1. Set as default server</p>\n<p>2. View or modify url</p>\n<p>3. Delete the server</p>\n<p>4. Rename the server</p>\n<p>0. Return to server selection</p>\n<p>Please enter the serial number:</p>", "menu-1-success": "<p>Set successfully!</p>", "menu-1-error-duplicate": "<p>The selected server is the same as the old default server!</p>", "menu-2-prompt": "<p>The server's url: {baseUrl}</p>\n<p>Please enter the new server url:</p>\n<p>Enter 0 to return</p>", "menu-2-success": "<p>Modified successfully!</p>", "menu-2-invalid-url": "<p>Invalid URL format! Please enter a valid url address.</p>", "menu-2-too-many-attempts": "<p>Too many attempts. Operation cancelled.</p>", "menu-3-success": "<p>Deleted successfully!</p>", "menu-4-prompt": "<p>Enter a new name, no spaces allowed:</p>", "menu-4-error-invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", "menu-4-success": "<p>Modified successfully!</p>", "add-type": "<p>Please select the server type:</p>\n{server_type_list}", "add-url": "<p>Please enter the server url:</p>", "add-invalid-url": "<p>Invalid URL format! Please enter a valid url address.</p>", "add-too-many-attempts": "<p>Too many attempts. Operation cancelled.</p>", "add-name": "<p>Received! Please give the server a name, no spaces allowed:</p>", "add-invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", "add-success": "<p>Added successfully, the server information is as follows:</p>\n<p>[{serverName}]</p>\n<p>Type: {serverType}</p>" } } } };
2255
+ var en_US_default2 = { _config: { $desc: "Core Module Settings", adminUsers: "**Plugin Admin** User ID (use inspect command to get)", guildNameCards: "**Group Card** Name List", maoServerUrl: "**Mao Server** URL address", official_support_url: "**Official Support** URL address" }, commands: { maintain: { description: "Switch to maintenance mode", messages: { "no-auth": "<p>You don't have permission to use this feature~</p>", start: "<p>Entering maintenance mode</p>", "success-start": "<p>Successfully switched to maintenance mode</p>", stop: "<p>Exiting maintenance mode</p>", "success-stop": "<p>Successfully exited maintenance mode</p>" } }, timeout: "Noah didn't wait for your reply, please try again!", noah: { help: { description: "Show Noah help information", messages: { content: "<p>Guide:</p>\nhttps://docs.logthm.cn/noah" } } }, locale: { description: "Set language", messages: { "no-auth": "<p>Only group admins can use this feature~</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Quit!</p>", "reset-channel": "<p>Reset successfully!</p>", "reset-user": "<p>Reset successfully!</p>", success: "<p>Set successfully!</p>", "set-channel": "<p>Select the default language for group chats:</p>\n<p>1. 简体中文</p>\n<p>2. English</p>\n<p>q. Quit</p>", "set-user": "<p>Select the language you use:</p>\n<p>1. 中文</p>\n<p>2. English</p>\n<p>q. Quit</p>" } }, link: { description: "Link account to another platform", options: { remove: "Unlink" }, messages: { "generated-1": `<p>The Link command can be used to link user data across multiple platforms. During the linking process, the original platform's user data is fully preserved, while the target platform's user data is overwritten.</p>
2256
+ <p>Please make sure the current platform is your target platform and send the following text to the bot on the original platform within 5 minutes:</p>
2257
+ <p>{0}</p>
2258
+ <p>Once linked, you can use "link -r" to unlink at any time.</p>`, "generated-2": "<p>Token verified! Now proceeding to the second step.</p>\n<p>Please send the following text to the bot on the target platform within 5 minutes:</p>\n<p>{0}</p>\n<p>Note: The current platform is your original platform. User data here will overwrite the target platform's data.</p>", "self-1": "<p>Please enter this on the original platform.</p>", "self-2": "<p>Please enter this on the target platform.</p>", success: "<p>Account linked successfully!</p>", "remove-success": "<p>Account unlinked successfully!</p>", "remove-original": "<p>Cannot unlink: this is your original account.</p>" } }, bind: { description: "Bind card", messages: { prompt: "<p>Please enter your card number:</p>", "convert-access-failed": "<p>Failed to convert Access Code, please use 16-digit card code instead.</p>", "invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", name: "<p>Received! Please give this card a name, no spaces allowed:</p>", "invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", success: "<p>Bound successfully, your card information is as follows:</p>\n<p>[{cardName}]</p>\n<p>{cardCode}</p>" } }, card: { description: "Manage cards", options: { detail: "Show detailed card information" }, messages: { "invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", "invalid-select": "<p>No such option!</p>", "lookup-error": "Lookup failed: {message}", "lookup-error-unknown": "Lookup failed: unknown error", "menu-select": "<p>[Card Management]</p>\n{card_list}\n<p>0. Add new card</p>\n<p>Please enter the serial number:</p>", menu: "<p>[{name}]</p>\n<p>1. Set as default card</p>\n<p>2. View or modify card number</p>\n<p>3. Delete the card</p>\n<p>4. Rename the card</p>\n<p>5. Bind the card to a specified server</p>\n<p>0. Return to card selection</p>\n<p>Please enter the serial number:</p>", "menu-has-bound-server": "<p>[{name}]</p>\n<p>Bound server: {defaultServerName}</p>\n<p>1. Set as default card</p>\n<p>2. View or modify card number</p>\n<p>3. Delete the card</p>\n<p>4. Rename the card</p>\n<p>5. Bind the card to a specified server</p>\n<p>0. Return to card selection</p>\n<p>Please enter the serial number:</p>", "menu-1-success": "<p>Set successfully!</p>", "menu-1-error-duplicate": "<p>The selected card is the same as the old default card!</p>", "menu-2-prompt": "<p>Card number: {code}</p>\n<p>Please enter the new card number:</p>\n<p>Enter 0 to return</p>", "menu-2-success": "<p>Modified successfully!</p>", "menu-2-error-invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", "menu-3-success": "<p>Deleted successfully!</p>", "menu-4-prompt": "<p>Enter a new name, no spaces allowed:</p>", "menu-4-error-invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", "menu-4-success": "<p>Modified successfully!</p>", "menu-5": "{server_list}\n<p>Please enter the serial number:</p>", "menu-5-success": "<p>Set successfully!</p>", "menu-5-error-duplicate": "<p>The selected server is the same as the old default server!</p>" } }, server: { description: "Manage servers", messages: { "no-channel": "<p>Please use this feature in group chats~</p>", "invalid-select": "<p>No such option!</p>", "no-auth": "<p>Only group admins can use this feature~</p>", "admin-menu-select": "<p>[Group server management]</p>\n{server_list}\n<p>0. Add new server</p>\n<p>Please enter the serial number:</p>", "menu-select": "<p>[Server management]</p>\n{server_list}\n<p>0. Add new server</p>\n<p>Please enter the serial number:</p>", menu: "<p>[{name}]</p>\n<p>Type: {type}</p>\n<p>1. Set as default server</p>\n<p>2. View or modify url</p>\n<p>3. Delete the server</p>\n<p>4. Rename the server</p>\n<p>0. Return to server selection</p>\n<p>Please enter the serial number:</p>", "menu-1-success": "<p>Set successfully!</p>", "menu-1-error-duplicate": "<p>The selected server is the same as the old default server!</p>", "menu-2-prompt": "<p>The server's url: {baseUrl}</p>\n<p>Please enter the new server url:</p>\n<p>Enter 0 to return</p>", "menu-2-success": "<p>Modified successfully!</p>", "menu-2-invalid-url": "<p>Invalid URL format! Please enter a valid url address.</p>", "menu-2-too-many-attempts": "<p>Too many attempts. Operation cancelled.</p>", "menu-3-success": "<p>Deleted successfully!</p>", "menu-4-prompt": "<p>Enter a new name, no spaces allowed:</p>", "menu-4-error-invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", "menu-4-success": "<p>Modified successfully!</p>", "add-type": "<p>Please select the server type:</p>\n{server_type_list}", "add-url": "<p>Please enter the server url:</p>", "add-invalid-url": "<p>Invalid URL format! Please enter a valid url address.</p>", "add-too-many-attempts": "<p>Too many attempts. Operation cancelled.</p>", "add-name": "<p>Received! Please give the server a name, no spaces allowed:</p>", "add-invalid-name": "<p>No spaces allowed! Please try again o(一︿一+)o</p>", "add-success": "<p>Added successfully, the server information is as follows:</p>\n<p>[{serverName}]</p>\n<p>Type: {serverType}</p>" } } } };
2954
2259
 
2955
2260
  // src/core/locales/zh-CN.yml
2956
- var zh_CN_default3 = { _config: { $desc: "Core 模块设置", adminUsers: "**插件管理员** 的用户id (使用 inspect 指令获取)", guildNameCards: "**群聊卡片** 的名称列表", maoServerUrl: "**猫网服务器** 的 URL 地址", official_support_url: "**官方支持** 的 URL 地址" }, commands: { maintain: { description: "切换到维护模式", messages: { "no-auth": "<p>你没有权限使用本功能哦~</p>", start: "<p>正在进入维护模式</p>", "success-start": "<p>成功切换到维护模式</p>", stop: "<p>正在退出维护模式</p>", "success-stop": "<p>成功退出维护模式</p>" } }, timeout: "Noah 没等到你的回复,请重试!", noah: { help: { description: "显示 Noah 帮助信息", messages: { content: "<p>使用文档:</p>\nhttps://docs.logthm.cn/noah" } } }, locale: { description: "设置语言", messages: { "no-auth": "<p>只有群管理员能使用本功能哦~</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>", "reset-channel": "<p>重置成功!</p>", "reset-user": "<p>重置成功!</p>", success: "<p>设置成功!</p>", "set-channel": "<p>选择群聊默认使用的语言:</p>\n<p>1. 简体中文</p>\n<p>2. English</p>\n<p>q. 退出</p>", "set-user": "<p>选择你使用的语言:</p>\n<p>1. 简体中文</p>\n<p>2. English</p>\n<p>q. 退出</p>" } }, bind: { description: "绑定卡片", messages: { prompt: "<p>请输入你的卡号:</p>", "convert-access-failed": "<p>转换 Access Code 失败,请使用 16 位卡号进行绑定。</p>", "invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", name: "<p>收到!为这张卡取一个名字吧,不要带空格哦:</p>", "invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", success: "<p>绑好啦,你的卡片信息如下:</p>\n<p>[{cardName}]</p>\n<p>{cardCode}</p>" } }, card: { description: "管理卡片", options: { detail: "显示卡片详细信息" }, messages: { "invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>", "lookup-error": "查询失败: {message}", "lookup-error-unknown": "查询失败: 未知错误", "menu-select": "<p>[卡片管理]</p>\n{card_list}\n<p>0. 添加新卡片</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", menu: "<p>[{name}]</p>\n<p>1. 设为默认卡片</p>\n<p>2. 查看或修改卡号</p>\n<p>3. 删除该卡</p>\n<p>4. 重命名该卡片</p>\n<p>5. 将卡片与指定服务器绑定</p>\n<p>0. 返回卡片选择</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-has-bound-server": "<p>[{name}]</p>\n<p>已绑定服务器:{defaultServerName}</p>\n<p>1. 设为默认卡片</p>\n<p>2. 查看或修改卡号</p>\n<p>3. 删除该卡</p>\n<p>4. 重命名该卡片</p>\n<p>5. 将卡片与指定服务器绑定</p>\n<p>0. 返回卡片选择</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-1-success": "<p>设置成功!</p>", "menu-1-error-duplicate": "<p>选择的卡片与旧的默认卡片相同!</p>", "menu-2-prompt": "<p>卡号:{code}</p>\n<p>请输入新的卡号:</p>\n<p>0. 返回</p>\n<p>q. 退出</p>", "menu-2-success": "<p>修改成功!</p>", "menu-2-error-invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", "menu-3-success": "<p>已删除!</p>", "menu-4-prompt": "<p>输入一个新名字,不要带空格哦:</p>\n<p>q. 退出</p>", "menu-4-error-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "menu-4-success": "<p>修改成功!</p>", "menu-5": "<p>请选择一个服务器:</p>\n{server_list}\n<p>q. 退出</p>", "menu-5-success": "<p>设置成功!</p>", "menu-5-error-duplicate": "<p>选择的服务器与旧的默认服务器相同!</p>" } }, server: { description: "管理服务器", messages: { "no-channel": "<p>请在群聊中使用本功能哦~</p>", "invalid-select": "<p>没有该选项!</p>", "no-auth": "<p>只有群管理员能使用本功能哦~</p>", quit: "<p>已退出~</p>", "admin-menu-select": "<p>[群聊服务器管理]</p>\n{server_list}\n<p>0. 添加新服务器</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-select": "<p>[服务器管理]</p>\n{server_list}\n<p>0. 添加新服务器</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", menu: "<p>[{name}]</p>\n<p>类型:{type}</p>\n<p>1. 设为默认服务器</p>\n<p>2. 查看或修改 url</p>\n<p>3. 删除该服务器</p>\n<p>4. 重命名该服务器</p>\n<p>0. 返回服务器选择</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-1-success": "<p>设置成功!</p>", "menu-1-error-duplicate": "<p>选择的服务器与旧的默认服务器相同!</p>", "menu-2-prompt": "<p>该服务器的 url:{baseUrl}</p>\n<p>请输入新的服务器 url:</p>\n<p>0. 返回</p>\n<p>q. 退出</p>", "menu-2-success": "<p>修改成功!</p>", "menu-2-invalid-url": "<p>URL 格式不正确!请输入有效的 url 地址。</p>", "menu-2-too-many-attempts": "<p>尝试次数过多,操作已取消。</p>", "menu-3-success": "<p>已删除!</p>", "menu-4-prompt": "<p>输入一个新名字,不要带空格哦:</p>\n<p>0. 返回</p>\n<p>q. 退出</p>", "menu-4-error-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "menu-4-success": "<p>修改成功!</p>", "add-type": "<p>请选择服务器的类型:</p>\n{server_type_list}\n<p>q. 退出</p>", "add-url": "<p>请输入服务器的 url:</p>\n<p>q. 退出</p>", "add-invalid-url": "<p>URL 格式不正确!请输入有效的 url 地址。</p>", "add-too-many-attempts": "<p>尝试次数过多,操作已取消。</p>", "add-name": "<p>收到!为服务器取一个名字吧,不要带空格哦:</p>\n<p>q. 退出</p>", "add-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "add-success": "<p>添加成功啦,服务器信息如下:</p>\n<p>[{serverName}]</p>\n<p>类型:{serverType}</p>" } } } };
2261
+ var zh_CN_default2 = { _config: { $desc: "Core 模块设置", adminUsers: "**插件管理员** 的用户id (使用 inspect 指令获取)", guildNameCards: "**群聊卡片** 的名称列表", maoServerUrl: "**猫网服务器** 的 URL 地址", official_support_url: "**官方支持** 的 URL 地址" }, commands: { maintain: { description: "切换到维护模式", messages: { "no-auth": "<p>你没有权限使用本功能哦~</p>", start: "<p>正在进入维护模式</p>", "success-start": "<p>成功切换到维护模式</p>", stop: "<p>正在退出维护模式</p>", "success-stop": "<p>成功退出维护模式</p>" } }, timeout: "Noah 没等到你的回复,请重试!", noah: { help: { description: "显示 Noah 帮助信息", messages: { content: "<p>使用文档:</p>\nhttps://docs.logthm.cn/noah" } } }, locale: { description: "设置语言", messages: { "no-auth": "<p>只有群管理员能使用本功能哦~</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>", "reset-channel": "<p>重置成功!</p>", "reset-user": "<p>重置成功!</p>", success: "<p>设置成功!</p>", "set-channel": "<p>选择群聊默认使用的语言:</p>\n<p>1. 简体中文</p>\n<p>2. English</p>\n<p>q. 退出</p>", "set-user": "<p>选择你使用的语言:</p>\n<p>1. 简体中文</p>\n<p>2. English</p>\n<p>q. 退出</p>" } }, link: { description: "关联账号到其他平台", options: { remove: "解除关联" }, messages: { "generated-1": "<p>Link 指令可用于在多个平台间关联用户数据。关联过程中,原始平台的用户数据将完全保留,而目标平台的用户数据将被原始平台的数据所覆盖。</p>\n<p>请确认当前平台是你的目标平台,并在 5 分钟内使用你的账号在原始平台内向机器人发送以下文本:</p>\n<p>{0}</p>\n<p>关联完成后,你可以随时使用「link -r」来解除关联。</p>", "generated-2": "<p>令牌核验成功!下面将进行第二步操作。</p>\n<p>请在 5 分钟内使用你的账号在目标平台内向机器人发送以下文本:</p>\n<p>{0}</p>\n<p>注意:当前平台是你的原始平台,这里的用户数据将覆盖目标平台的数据。</p>", "self-1": "<p>请前往原始平台输入。</p>", "self-2": "<p>请前往目标平台输入。</p>", success: "<p>账号关联成功!</p>", "remove-success": "<p>账号解除关联成功!</p>", "remove-original": "<p>无法解除关联:这是你的原始账号。</p>" } }, bind: { description: "绑定卡片", messages: { prompt: "<p>请输入你的卡号:</p>", "convert-access-failed": "<p>转换 Access Code 失败,请使用 16 位卡号进行绑定。</p>", "invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", name: "<p>收到!为这张卡取一个名字吧,不要带空格哦:</p>", "invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", success: "<p>绑好啦,你的卡片信息如下:</p>\n<p>[{cardName}]</p>\n<p>{cardCode}</p>" } }, card: { description: "管理卡片", options: { detail: "显示卡片详细信息" }, messages: { "invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>", "lookup-error": "查询失败: {message}", "lookup-error-unknown": "查询失败: 未知错误", "menu-select": "<p>[卡片管理]</p>\n{card_list}\n<p>0. 添加新卡片</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", menu: "<p>[{name}]</p>\n<p>1. 设为默认卡片</p>\n<p>2. 查看或修改卡号</p>\n<p>3. 删除该卡</p>\n<p>4. 重命名该卡片</p>\n<p>5. 将卡片与指定服务器绑定</p>\n<p>0. 返回卡片选择</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-has-bound-server": "<p>[{name}]</p>\n<p>已绑定服务器:{defaultServerName}</p>\n<p>1. 设为默认卡片</p>\n<p>2. 查看或修改卡号</p>\n<p>3. 删除该卡</p>\n<p>4. 重命名该卡片</p>\n<p>5. 将卡片与指定服务器绑定</p>\n<p>0. 返回卡片选择</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-1-success": "<p>设置成功!</p>", "menu-1-error-duplicate": "<p>选择的卡片与旧的默认卡片相同!</p>", "menu-2-prompt": "<p>卡号:{code}</p>\n<p>请输入新的卡号:</p>\n<p>0. 返回</p>\n<p>q. 退出</p>", "menu-2-success": "<p>修改成功!</p>", "menu-2-error-invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", "menu-3-success": "<p>已删除!</p>", "menu-4-prompt": "<p>输入一个新名字,不要带空格哦:</p>\n<p>q. 退出</p>", "menu-4-error-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "menu-4-success": "<p>修改成功!</p>", "menu-5": "<p>请选择一个服务器:</p>\n{server_list}\n<p>q. 退出</p>", "menu-5-success": "<p>设置成功!</p>", "menu-5-error-duplicate": "<p>选择的服务器与旧的默认服务器相同!</p>" } }, server: { description: "管理服务器", messages: { "no-channel": "<p>请在群聊中使用本功能哦~</p>", "invalid-select": "<p>没有该选项!</p>", "no-auth": "<p>只有群管理员能使用本功能哦~</p>", quit: "<p>已退出~</p>", "admin-menu-select": "<p>[群聊服务器管理]</p>\n{server_list}\n<p>0. 添加新服务器</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-select": "<p>[服务器管理]</p>\n{server_list}\n<p>0. 添加新服务器</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", menu: "<p>[{name}]</p>\n<p>类型:{type}</p>\n<p>1. 设为默认服务器</p>\n<p>2. 查看或修改 url</p>\n<p>3. 删除该服务器</p>\n<p>4. 重命名该服务器</p>\n<p>0. 返回服务器选择</p>\n<p>q. 退出菜单</p>\n<p>请输入序号:</p>", "menu-1-success": "<p>设置成功!</p>", "menu-1-error-duplicate": "<p>选择的服务器与旧的默认服务器相同!</p>", "menu-2-prompt": "<p>该服务器的 url:{baseUrl}</p>\n<p>请输入新的服务器 url:</p>\n<p>0. 返回</p>\n<p>q. 退出</p>", "menu-2-success": "<p>修改成功!</p>", "menu-2-invalid-url": "<p>URL 格式不正确!请输入有效的 url 地址。</p>", "menu-2-too-many-attempts": "<p>尝试次数过多,操作已取消。</p>", "menu-3-success": "<p>已删除!</p>", "menu-4-prompt": "<p>输入一个新名字,不要带空格哦:</p>\n<p>0. 返回</p>\n<p>q. 退出</p>", "menu-4-error-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "menu-4-success": "<p>修改成功!</p>", "add-type": "<p>请选择服务器的类型:</p>\n{server_type_list}\n<p>q. 退出</p>", "add-url": "<p>请输入服务器的 url:</p>\n<p>q. 退出</p>", "add-invalid-url": "<p>URL 格式不正确!请输入有效的 url 地址。</p>", "add-too-many-attempts": "<p>尝试次数过多,操作已取消。</p>", "add-name": "<p>收到!为服务器取一个名字吧,不要带空格哦:</p>\n<p>q. 退出</p>", "add-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "add-success": "<p>添加成功啦,服务器信息如下:</p>\n<p>[{serverName}]</p>\n<p>类型:{serverType}</p>" } } } };
2957
2262
 
2958
2263
  // src/core/index.ts
2959
- var name7 = "Noah-Core";
2960
- var inject2 = ["database"];
2961
- var logger3 = new import_koishi5.Logger("Noah-Core");
2962
- function apply7(ctx, config) {
2264
+ var name5 = "Noah-Core";
2265
+ var inject = ["database"];
2266
+ var logger2 = new import_koishi6.Logger("Noah-Core");
2267
+ function apply5(ctx, config) {
2963
2268
  ;
2964
2269
  [
2965
- ["en-US", en_US_default3],
2966
- ["zh-CN", zh_CN_default3]
2270
+ ["en-US", en_US_default2],
2271
+ ["zh-CN", zh_CN_default2]
2967
2272
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
2968
- ctx.plugin(database_exports2, config.core);
2273
+ ctx.plugin(database_exports, config.core);
2969
2274
  ctx.plugin(command_exports, config.core);
2970
2275
  ctx.plugin(event_exports, config.core);
2971
2276
  }
2972
- __name(apply7, "apply");
2277
+ __name(apply5, "apply");
2973
2278
 
2974
2279
  // src/drawer/index.ts
2975
2280
  var drawer_exports = {};
2976
2281
  __export(drawer_exports, {
2977
2282
  DrawerManager: () => DrawerManager,
2978
- apply: () => apply8,
2979
- name: () => name8
2283
+ apply: () => apply6,
2284
+ name: () => name6
2980
2285
  });
2981
- var import_koishi6 = require("koishi");
2286
+ var import_koishi7 = require("koishi");
2982
2287
 
2983
2288
  // src/drawer/BaseDrawer.ts
2984
2289
  var import_sharp = __toESM(require("sharp"), 1);
@@ -2986,6 +2291,7 @@ var BaseDrawer = class {
2986
2291
  constructor(ctx) {
2987
2292
  this.ctx = ctx;
2988
2293
  }
2294
+ ctx;
2989
2295
  static {
2990
2296
  __name(this, "BaseDrawer");
2991
2297
  }
@@ -3025,6 +2331,7 @@ var CoreDrawer = class extends BaseDrawer {
3025
2331
  super(ctx);
3026
2332
  this.ctx = ctx;
3027
2333
  }
2334
+ ctx;
3028
2335
  static {
3029
2336
  __name(this, "CoreDrawer");
3030
2337
  }
@@ -3060,6 +2367,7 @@ var IIDXDrawer = class extends BaseDrawer {
3060
2367
  super(ctx);
3061
2368
  this.ctx = ctx;
3062
2369
  }
2370
+ ctx;
3063
2371
  static {
3064
2372
  __name(this, "IIDXDrawer");
3065
2373
  }
@@ -3125,6 +2433,50 @@ var IIDXDrawer = class extends BaseDrawer {
3125
2433
  var fs2 = __toESM(require("fs"), 1);
3126
2434
  var import_bwip_js = __toESM(require("bwip-js"), 1);
3127
2435
 
2436
+ // src/games/sdvx/constants.ts
2437
+ var ALL_GRADES = ["S", "AAA+", "AAA", "AA+", "AA", "A+", "A", "B", "C", "D"];
2438
+ var ALL_CLEAR_TYPES = [
2439
+ "S-PUC",
2440
+ "PUC",
2441
+ "UC",
2442
+ "MC",
2443
+ "HC",
2444
+ "NC",
2445
+ "PLAYED",
2446
+ "NO PLAY"
2447
+ ];
2448
+ var SCORE_GRADE_MAP = [
2449
+ { min: 99e5, max: 1e7, grade: "S" },
2450
+ { min: 98e5, max: 9899999, grade: "AAA+" },
2451
+ { min: 97e5, max: 9799999, grade: "AAA" },
2452
+ { min: 95e5, max: 9699999, grade: "AA+" },
2453
+ { min: 93e5, max: 9499999, grade: "AA" },
2454
+ { min: 9e6, max: 9299999, grade: "A+" },
2455
+ { min: 87e5, max: 8999999, grade: "A" },
2456
+ { min: 75e5, max: 8699999, grade: "B" },
2457
+ { min: 65e5, max: 7499999, grade: "C" },
2458
+ { min: 0, max: 6499999, grade: "D" }
2459
+ ];
2460
+ var CLEAR_TYPE_ABBR_MAP = {
2461
+ spuc: "S-PUC",
2462
+ puc: "PUC",
2463
+ uc: "UC",
2464
+ mc: "MC",
2465
+ hc: "HC",
2466
+ nc: "NC",
2467
+ played: "PLAYED",
2468
+ noplay: "NO PLAY"
2469
+ };
2470
+ function getGradeByScore(score) {
2471
+ for (const mapping of SCORE_GRADE_MAP) {
2472
+ if (score >= mapping.min && score <= mapping.max) {
2473
+ return mapping.grade;
2474
+ }
2475
+ }
2476
+ return "D";
2477
+ }
2478
+ __name(getGradeByScore, "getGradeByScore");
2479
+
3128
2480
  // src/games/sdvx/utils/vf.ts
3129
2481
  var FACTOR_SCALE = 100;
3130
2482
  var GRADE_FACTOR_SCALES = {
@@ -3180,18 +2532,6 @@ __name(calculateVolforceInt, "calculateVolforceInt");
3180
2532
 
3181
2533
  // src/games/sdvx/utils/calculator.ts
3182
2534
  var cachedLookupTable = null;
3183
- var SCORE_GRADE_MAP = [
3184
- { min: 99e5, max: 1e7, grade: "S" },
3185
- { min: 98e5, max: 9899999, grade: "AAA+" },
3186
- { min: 97e5, max: 9799999, grade: "AAA" },
3187
- { min: 95e5, max: 9699999, grade: "AA+" },
3188
- { min: 93e5, max: 9499999, grade: "AA" },
3189
- { min: 9e6, max: 9299999, grade: "A+" },
3190
- { min: 87e5, max: 8999999, grade: "A" },
3191
- { min: 75e5, max: 8699999, grade: "B" },
3192
- { min: 65e5, max: 7499999, grade: "C" },
3193
- { min: 0, max: 6499999, grade: "D" }
3194
- ];
3195
2535
  var VF_INT_MULTIPLIER = 20;
3196
2536
  var VF_INPUT_PRECISION = 6;
3197
2537
  var LEVEL_SCALE = 10;
@@ -3242,19 +2582,10 @@ function getMinScoreForVfInt(multiplier, vfInt) {
3242
2582
  }
3243
2583
  __name(getMinScoreForVfInt, "getMinScoreForVfInt");
3244
2584
  function getMaxScoreForVfInt(multiplier, vfInt) {
3245
- const numerator = BigInt(vfInt + 1) * VOLFORCE_BASE_DENOMINATOR - 1n;
3246
- return Number(numerator / multiplier);
3247
- }
3248
- __name(getMaxScoreForVfInt, "getMaxScoreForVfInt");
3249
- function getGradeByScore(score) {
3250
- for (const mapping of SCORE_GRADE_MAP) {
3251
- if (score >= mapping.min && score <= mapping.max) {
3252
- return mapping.grade;
3253
- }
3254
- }
3255
- return "D";
2585
+ const numerator = BigInt(vfInt + 1) * VOLFORCE_BASE_DENOMINATOR - 1n;
2586
+ return Number(numerator / multiplier);
3256
2587
  }
3257
- __name(getGradeByScore, "getGradeByScore");
2588
+ __name(getMaxScoreForVfInt, "getMaxScoreForVfInt");
3258
2589
  function getPossibleClearTypes(score) {
3259
2590
  if (score === 1e7) {
3260
2591
  return ["PUC", "S-PUC"];
@@ -3321,23 +2652,12 @@ function getNextVFIncrease(level, currentScore, clearType) {
3321
2652
  }
3322
2653
  __name(getNextVFIncrease, "getNextVFIncrease");
3323
2654
  function getGradeRange(grade) {
3324
- const allGrades = ["S", "AAA+", "AAA", "AA+", "AA", "A+", "A", "B", "C", "D"];
3325
- if (!grade) return allGrades;
2655
+ if (!grade) return ALL_GRADES;
3326
2656
  return Array.isArray(grade) ? grade : [grade];
3327
2657
  }
3328
2658
  __name(getGradeRange, "getGradeRange");
3329
2659
  function getClearTypeRange(clearType) {
3330
- const allClearTypes = [
3331
- "S-PUC",
3332
- "PUC",
3333
- "UC",
3334
- "MC",
3335
- "HC",
3336
- "NC",
3337
- "PLAYED",
3338
- "NO PLAY"
3339
- ];
3340
- if (!clearType) return allClearTypes;
2660
+ if (!clearType) return ALL_CLEAR_TYPES;
3341
2661
  return Array.isArray(clearType) ? clearType : [clearType];
3342
2662
  }
3343
2663
  __name(getClearTypeRange, "getClearTypeRange");
@@ -3349,27 +2669,16 @@ function getLevelRange(level) {
3349
2669
  __name(getLevelRange, "getLevelRange");
3350
2670
  function generateVFLookupTable() {
3351
2671
  const lookupTable = /* @__PURE__ */ new Map();
3352
- const allGrades = ["S", "AAA+", "AAA", "AA+", "AA", "A+", "A", "B", "C", "D"];
3353
- const allClearTypes = [
3354
- "S-PUC",
3355
- "PUC",
3356
- "UC",
3357
- "MC",
3358
- "HC",
3359
- "NC",
3360
- "PLAYED",
3361
- "NO PLAY"
3362
- ];
3363
2672
  for (const level of getAllValidLevels()) {
3364
2673
  const levelScaled = scaleLevel(level);
3365
- for (const grade of allGrades) {
2674
+ for (const grade of ALL_GRADES) {
3366
2675
  const gradeMapping = SCORE_GRADE_MAP.find((m) => m.grade === grade);
3367
2676
  if (!gradeMapping) {
3368
2677
  continue;
3369
2678
  }
3370
2679
  const gradeMin = gradeMapping.min;
3371
2680
  const gradeMax = gradeMapping.max;
3372
- for (const clearType of allClearTypes) {
2681
+ for (const clearType of ALL_CLEAR_TYPES) {
3373
2682
  const scoresToCheck = /* @__PURE__ */ new Set();
3374
2683
  scoresToCheck.add(gradeMin);
3375
2684
  scoresToCheck.add(gradeMax);
@@ -3575,9 +2884,85 @@ var SDVXDrawer = class extends BaseDrawer {
3575
2884
  super(ctx);
3576
2885
  this.ctx = ctx;
3577
2886
  }
2887
+ ctx;
3578
2888
  static {
3579
2889
  __name(this, "SDVXDrawer");
3580
2890
  }
2891
+ fontsLoaded = false;
2892
+ recentAssets = null;
2893
+ vfAssets = null;
2894
+ ensureFontsLoaded() {
2895
+ if (this.fontsLoaded) return;
2896
+ const { FontLibrary } = this.ctx.skia;
2897
+ const fonts = [
2898
+ ["Noto Sans", "fonts/NotoSans-VariableFont_wdth,wght.ttf"],
2899
+ ["Noto Sans JP", "fonts/NotoSansJP-VariableFont_wght.ttf"],
2900
+ ["Slant", "fonts/Slant.ttf"],
2901
+ ["Fredoka One", "fonts/FredokaOne.ttf"]
2902
+ ];
2903
+ for (const [name15, file] of fonts) {
2904
+ const path3 = getAssetPath(this.ctx, file);
2905
+ if (fs2.existsSync(path3)) FontLibrary.use(name15, path3);
2906
+ }
2907
+ this.fontsLoaded = true;
2908
+ }
2909
+ async ensureRecentAssets() {
2910
+ if (this.recentAssets) return this.recentAssets;
2911
+ const { loadImage } = this.ctx.skia;
2912
+ const gradeList = ["C", "D", "S", "B", "AAA", "AAA+", "AA+", "A", "A+", "AA"];
2913
+ const playTypeList = ["UC", "PLAYED", "SPUC", "PUC", "NOPLAY", "NC", "MC", "HC"];
2914
+ const [gradeImages, playTypeImages] = await Promise.all([
2915
+ this.loadImageMap(loadImage, gradeList, (g) => `sdvx/recent/grade/Type=${g}.png`),
2916
+ this.loadImageMap(
2917
+ loadImage,
2918
+ playTypeList,
2919
+ (t) => `sdvx/recent/play_type/Type=${t}.png`
2920
+ )
2921
+ ]);
2922
+ this.recentAssets = { gradeImages, playTypeImages };
2923
+ return this.recentAssets;
2924
+ }
2925
+ async ensureVFAssets() {
2926
+ if (this.vfAssets) return this.vfAssets;
2927
+ const { loadImage } = this.ctx.skia;
2928
+ const grades = ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S"];
2929
+ const circles = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
2930
+ const diffs = ["NOV", "ADV", "EXH", "INF", "MXM", "GRV", "HVN", "VVD", "XCD", "ULT"];
2931
+ const clears = ["PUC", "SPUC", "UC", "MC", "HC", "NC", "PLAYED", "NOPLAY"];
2932
+ const [gradeImages, circleImages, difficultyBadges, clearTypeBadges] = await Promise.all([
2933
+ this.loadImageMap(loadImage, grades, (g) => `sdvx/vf/grade/${g}.png`),
2934
+ this.loadImageMap(loadImage, circles, (i) => `sdvx/vf/circle/Class=${i}.png`),
2935
+ this.loadImageMap(loadImage, diffs, (d) => `sdvx/vf/difficulty/${d}.png`),
2936
+ this.loadImageMap(loadImage, clears, (c) => `sdvx/vf/clear_type/${c}.png`)
2937
+ ]);
2938
+ this.vfAssets = { gradeImages, circleImages, difficultyBadges, clearTypeBadges };
2939
+ return this.vfAssets;
2940
+ }
2941
+ async loadImageMap(loadImage, keys, pathFn) {
2942
+ const entries = await Promise.all(
2943
+ keys.map(async (key) => {
2944
+ try {
2945
+ return [
2946
+ String(key),
2947
+ await loadImage(getAssetPath(this.ctx, pathFn(key)))
2948
+ ];
2949
+ } catch {
2950
+ return [String(key), null];
2951
+ }
2952
+ })
2953
+ );
2954
+ return Object.fromEntries(entries.filter(([, v]) => v !== null));
2955
+ }
2956
+ fitText(ctx, text, maxWidth) {
2957
+ if (ctx.measureText(text).width <= maxWidth) return text;
2958
+ let lo = 0, hi = text.length;
2959
+ while (lo < hi) {
2960
+ const mid = lo + hi + 1 >> 1;
2961
+ if (ctx.measureText(text.substring(0, mid)).width <= maxWidth) lo = mid;
2962
+ else hi = mid - 1;
2963
+ }
2964
+ return text.substring(0, lo);
2965
+ }
3581
2966
  /**
3582
2967
  * Format difficulty level text.
3583
2968
  * If it's an integer like 17 / 20, keep trailing ".0" (e.g. "17.0").
@@ -3593,46 +2978,16 @@ var SDVXDrawer = class extends BaseDrawer {
3593
2978
  * @returns 包含图像数据的Buffer
3594
2979
  */
3595
2980
  async generateRecentImage(options, compression) {
3596
- const { Canvas, loadImage, FontLibrary } = this.ctx.skia;
2981
+ const { Canvas, loadImage } = this.ctx.skia;
3597
2982
  if (!compression) {
3598
2983
  compression = { lossless: true };
3599
2984
  }
3600
- const notoSansPath = getAssetPath(this.ctx, "fonts/NotoSans-VariableFont_wdth,wght.ttf");
3601
- const notoSansJPPath = getAssetPath(this.ctx, "fonts/NotoSansJP-VariableFont_wght.ttf");
3602
- if (fs2.existsSync(notoSansPath)) {
3603
- FontLibrary.use("Noto Sans", notoSansPath);
3604
- this.ctx.logger("SDVX-Drawer").debug("Loaded Noto Sans font successfully");
3605
- } else {
3606
- this.ctx.logger("SDVX-Drawer").warn(`Noto Sans font file not found at: ${notoSansPath}`);
3607
- }
3608
- if (fs2.existsSync(notoSansJPPath)) {
3609
- FontLibrary.use("Noto Sans JP", notoSansJPPath);
3610
- this.ctx.logger("SDVX-Drawer").debug("Loaded Noto Sans JP font successfully");
3611
- } else {
3612
- this.ctx.logger("SDVX-Drawer").warn(`Noto Sans JP font file not found at: ${notoSansJPPath}`);
3613
- }
3614
- const bgImage = await loadImage(getAssetPath(this.ctx, "sdvx/recent/main_bg.png"));
3615
- const ticketBgImage = await loadImage(getAssetPath(this.ctx, "sdvx/recent/ticket_bg.png"));
3616
- const gradeImages = {};
3617
- const gradeList = ["C", "D", "S", "B", "AAA", "AAA+", "AA+", "A", "A+", "AA"];
3618
- for (const grade of gradeList) {
3619
- try {
3620
- const gradePath = getAssetPath(this.ctx, `sdvx/recent/grade/Type=${grade}.png`);
3621
- gradeImages[grade] = await loadImage(gradePath);
3622
- } catch (error) {
3623
- this.ctx.logger("SDVX-Drawer").warn(`Failed to load grade image for ${grade}: ${error.message}`);
3624
- }
3625
- }
3626
- const playTypeImages = {};
3627
- const playTypeList = ["UC", "PLAYED", "SPUC", "PUC", "NOPLAY", "NC", "MC", "HC"];
3628
- for (const type of playTypeList) {
3629
- try {
3630
- const typePath = getAssetPath(this.ctx, `sdvx/recent/play_type/Type=${type}.png`);
3631
- playTypeImages[type] = await loadImage(typePath);
3632
- } catch (error) {
3633
- this.ctx.logger("SDVX-Drawer").warn(`Failed to load play_type image for ${type}: ${error.message}`);
3634
- }
3635
- }
2985
+ this.ensureFontsLoaded();
2986
+ const { gradeImages, playTypeImages } = await this.ensureRecentAssets();
2987
+ const [bgImage, ticketBgImage] = await Promise.all([
2988
+ loadImage(getAssetPath(this.ctx, "sdvx/recent/main_bg.png")),
2989
+ loadImage(getAssetPath(this.ctx, "sdvx/recent/ticket_bg.png"))
2990
+ ]);
3636
2991
  let jacketImage;
3637
2992
  try {
3638
2993
  if (options.score.difficulty_data?.cover_url) {
@@ -3656,20 +3011,20 @@ var SDVXDrawer = class extends BaseDrawer {
3656
3011
  ctx.save();
3657
3012
  ctx.translate(556, 17);
3658
3013
  ctx.beginPath();
3659
- const r = 16, w = 100, h9 = 200, x0 = 0, y0 = 0;
3014
+ const r = 16, w = 100, h10 = 200, x0 = 0, y0 = 0;
3660
3015
  ctx.moveTo(x0 + r, y0);
3661
3016
  ctx.lineTo(x0 + w - r, y0);
3662
3017
  ctx.arcTo(x0 + w, y0, x0 + w, y0 + r, r);
3663
- ctx.lineTo(x0 + w, y0 + h9 - r);
3664
- ctx.arcTo(x0 + w, y0 + h9, x0 + w - r, y0 + h9, r);
3665
- ctx.lineTo(x0 + r, y0 + h9);
3666
- ctx.arcTo(x0, y0 + h9, x0, y0 + h9 - r, r);
3018
+ ctx.lineTo(x0 + w, y0 + h10 - r);
3019
+ ctx.arcTo(x0 + w, y0 + h10, x0 + w - r, y0 + h10, r);
3020
+ ctx.lineTo(x0 + r, y0 + h10);
3021
+ ctx.arcTo(x0, y0 + h10, x0, y0 + h10 - r, r);
3667
3022
  ctx.lineTo(x0, y0 + r);
3668
3023
  ctx.arcTo(x0, y0, x0 + r, y0, r);
3669
3024
  ctx.closePath();
3670
3025
  const circleRadius = 14;
3671
3026
  const circleX = x0 + w;
3672
- const circleY = y0 + h9 / 2;
3027
+ const circleY = y0 + h10 / 2;
3673
3028
  ctx.moveTo(circleX + circleRadius, circleY);
3674
3029
  ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2, true);
3675
3030
  ctx.clip();
@@ -3694,42 +3049,23 @@ var SDVXDrawer = class extends BaseDrawer {
3694
3049
  ctx.textAlign = "left";
3695
3050
  ctx.fillStyle = "#0230A5";
3696
3051
  const maxWidth = 360;
3052
+ const gap = 8;
3053
+ let mainTitleWidth = ctx.measureText(mainTitle).width;
3697
3054
  let displayMainTitle = mainTitle;
3698
3055
  let displaySubTitle = subTitle;
3699
- const gap = 8;
3700
- let mainTitleWidth = ctx.measureText(displayMainTitle).width;
3701
- let subTitleWidth = displaySubTitle ? ctx.measureText(displaySubTitle).width : 0;
3702
- if (mainTitleWidth + (displaySubTitle ? gap + subTitleWidth : 0) > maxWidth) {
3703
- if (displaySubTitle) {
3704
- let fitLength = 0;
3705
- for (let i = 1; i <= displaySubTitle.length; i++) {
3706
- if (ctx.measureText(displaySubTitle.substring(0, i)).width + mainTitleWidth + gap > maxWidth) {
3707
- fitLength = i - 1;
3708
- break;
3709
- }
3710
- }
3711
- if (fitLength > 0) {
3712
- displaySubTitle = displaySubTitle.substring(0, fitLength);
3713
- } else {
3714
- displaySubTitle = "";
3715
- }
3716
- subTitleWidth = ctx.measureText(displaySubTitle).width;
3056
+ if (mainTitleWidth + (subTitle ? gap + ctx.measureText(subTitle).width : 0) > maxWidth) {
3057
+ if (subTitle) {
3058
+ displaySubTitle = this.fitText(ctx, subTitle, maxWidth - mainTitleWidth - gap);
3717
3059
  }
3060
+ const subTitleWidth = displaySubTitle ? ctx.measureText(displaySubTitle).width : 0;
3718
3061
  if (mainTitleWidth + (displaySubTitle ? gap + subTitleWidth : 0) > maxWidth) {
3719
- let fitLength = 0;
3720
- for (let i = 1; i <= displayMainTitle.length; i++) {
3721
- if (ctx.measureText(displayMainTitle.substring(0, i)).width + (displaySubTitle ? gap + subTitleWidth : 0) > maxWidth) {
3722
- fitLength = i - 1;
3723
- break;
3724
- }
3725
- }
3726
- if (fitLength > 0) {
3727
- displayMainTitle = displayMainTitle.substring(0, fitLength);
3728
- } else {
3729
- displayMainTitle = "";
3730
- }
3731
- mainTitleWidth = ctx.measureText(displayMainTitle).width;
3062
+ displayMainTitle = this.fitText(
3063
+ ctx,
3064
+ mainTitle,
3065
+ maxWidth - (displaySubTitle ? gap + subTitleWidth : 0)
3066
+ );
3732
3067
  }
3068
+ mainTitleWidth = ctx.measureText(displayMainTitle).width;
3733
3069
  }
3734
3070
  ctx.fillStyle = "#0230A5";
3735
3071
  ctx.globalAlpha = 1;
@@ -3836,42 +3172,11 @@ var SDVXDrawer = class extends BaseDrawer {
3836
3172
  * @returns 包含图像数据的Buffer
3837
3173
  */
3838
3174
  async generateVFImage(options, compression) {
3839
- const { Canvas, loadImage, FontLibrary } = this.ctx.skia;
3175
+ const { Canvas, loadImage } = this.ctx.skia;
3840
3176
  if (!compression) {
3841
3177
  compression = { lossless: true };
3842
3178
  }
3843
- try {
3844
- const notoSansPath = getAssetPath(this.ctx, "fonts/NotoSans-VariableFont_wdth,wght.ttf");
3845
- const slantPath = getAssetPath(this.ctx, "fonts/Slant.ttf");
3846
- const fredokaPath = getAssetPath(this.ctx, "fonts/FredokaOne.ttf");
3847
- const notoSansJPPath = getAssetPath(this.ctx, "fonts/NotoSansJP-VariableFont_wght.ttf");
3848
- if (fs2.existsSync(notoSansPath)) {
3849
- FontLibrary.use("Noto Sans", notoSansPath);
3850
- this.ctx.logger("SDVX-Drawer").debug("Loaded Noto Sans font successfully");
3851
- } else {
3852
- this.ctx.logger("SDVX-Drawer").warn(`Noto Sans font file not found at: ${notoSansPath}`);
3853
- }
3854
- if (fs2.existsSync(slantPath)) {
3855
- FontLibrary.use("Slant", slantPath);
3856
- this.ctx.logger("SDVX-Drawer").debug("Loaded Slant font successfully");
3857
- } else {
3858
- this.ctx.logger("SDVX-Drawer").warn(`Slant font file not found at: ${slantPath}`);
3859
- }
3860
- if (fs2.existsSync(fredokaPath)) {
3861
- FontLibrary.use("Fredoka One", fredokaPath);
3862
- this.ctx.logger("SDVX-Drawer").debug("Loaded Fredoka One font successfully");
3863
- } else {
3864
- this.ctx.logger("SDVX-Drawer").warn(`Fredoka One font file not found at: ${fredokaPath}`);
3865
- }
3866
- if (fs2.existsSync(notoSansJPPath)) {
3867
- FontLibrary.use("Noto Sans JP", notoSansJPPath);
3868
- this.ctx.logger("SDVX-Drawer").debug("Loaded Noto Sans JP font successfully");
3869
- } else {
3870
- this.ctx.logger("SDVX-Drawer").warn(`Noto Sans JP font file not found at: ${notoSansJPPath}`);
3871
- }
3872
- } catch (error) {
3873
- this.ctx.logger("SDVX-Drawer").warn(`Error loading fonts: ${error.message}`);
3874
- }
3179
+ this.ensureFontsLoaded();
3875
3180
  const bgImage = await loadImage(getAssetPath(this.ctx, "sdvx/vf/main_bg.png"));
3876
3181
  const canvas = new Canvas(bgImage.width, bgImage.height);
3877
3182
  const ctx = canvas.getContext("2d");
@@ -3909,85 +3214,29 @@ var SDVXDrawer = class extends BaseDrawer {
3909
3214
  const cardWidth = cardBgImage.width;
3910
3215
  const cardHeight = cardBgImage.height;
3911
3216
  const effectiveCardWidth = cardWidth + cardHorizontalSpacing;
3912
- const gradeImages = {};
3913
- const circleImages = {};
3217
+ const { gradeImages, circleImages, difficultyBadges, clearTypeBadges } = await this.ensureVFAssets();
3914
3218
  const jacketImages = {};
3915
- const difficultyBadges = {};
3916
- const clearTypeBadges = {};
3917
- try {
3918
- const grades = ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S"];
3919
- for (const grade of grades) {
3920
- try {
3921
- const gradePath = getAssetPath(this.ctx, `sdvx/vf/grade/${grade}.png`);
3922
- gradeImages[grade] = await loadImage(gradePath);
3923
- } catch (error) {
3924
- this.ctx.logger("SDVX-Drawer").warn(`Failed to load grade image for ${grade}: ${error.message}`);
3925
- }
3926
- }
3927
- for (let i = 1; i <= 10; i++) {
3928
- try {
3929
- const circlePath = getAssetPath(this.ctx, `sdvx/vf/circle/Class=${i}.png`);
3930
- circleImages[i] = await loadImage(circlePath);
3931
- } catch (error) {
3932
- this.ctx.logger("SDVX-Drawer").warn(`Failed to load circle image for class ${i}: ${error.message}`);
3933
- }
3934
- }
3935
- const difficultyBadgeKeys = [
3936
- "NOV",
3937
- "ADV",
3938
- "EXH",
3939
- "INF",
3940
- "MXM",
3941
- "GRV",
3942
- "HVN",
3943
- "VVD",
3944
- "XCD",
3945
- "ULT"
3946
- ];
3947
- for (const difficulty of difficultyBadgeKeys) {
3948
- try {
3949
- const difficultyBadgePath = getAssetPath(
3950
- this.ctx,
3951
- `sdvx/vf/difficulty/${difficulty}.png`
3952
- );
3953
- difficultyBadges[difficulty] = await loadImage(difficultyBadgePath);
3954
- } catch (error) {
3955
- this.ctx.logger("SDVX-Drawer").warn(
3956
- `Failed to load difficulty badge image for ${difficulty}: ${error.message}`
3957
- );
3958
- }
3959
- }
3960
- const clearTypeBadgeKeys = ["PUC", "SPUC", "UC", "MC", "HC", "NC", "PLAYED", "NOPLAY"];
3961
- for (const clearType of clearTypeBadgeKeys) {
3962
- try {
3963
- const clearTypeBadgePath = getAssetPath(
3964
- this.ctx,
3965
- `sdvx/vf/clear_type/${clearType}.png`
3966
- );
3967
- clearTypeBadges[clearType] = await loadImage(clearTypeBadgePath);
3968
- } catch (error) {
3969
- this.ctx.logger("SDVX-Drawer").warn(
3970
- `Failed to load clear type badge image for ${clearType}: ${error.message}`
3971
- );
3972
- }
3973
- }
3974
- for (const score of scores) {
3219
+ const uniqueUrls = [
3220
+ ...new Set(
3221
+ scores.filter((s) => s.difficulty_data?.cover_url).map(
3222
+ (s) => `${config.sdvx_data_url}${s.difficulty_data.cover_url}`.replace(
3223
+ ".webp",
3224
+ ".png"
3225
+ )
3226
+ )
3227
+ )
3228
+ ];
3229
+ const jacketEntries = await Promise.all(
3230
+ uniqueUrls.map(async (url) => {
3975
3231
  try {
3976
- if (score.difficulty_data?.cover_url) {
3977
- const coverUrl = `${config.sdvx_data_url}${score.difficulty_data.cover_url}`.replace(
3978
- ".webp",
3979
- ".png"
3980
- );
3981
- if (!jacketImages[coverUrl]) {
3982
- jacketImages[coverUrl] = await loadImage(coverUrl);
3983
- }
3984
- }
3985
- } catch (error) {
3986
- this.ctx.logger("SDVX-Drawer").warn(`Failed to load jacket image: ${error.message}`);
3232
+ return [url, await loadImage(url)];
3233
+ } catch {
3234
+ return [url, null];
3987
3235
  }
3988
- }
3989
- } catch (error) {
3990
- this.ctx.logger("SDVX-Drawer").warn(`Failed to load images: ${error.message}`);
3236
+ })
3237
+ );
3238
+ for (const [url, img] of jacketEntries) {
3239
+ if (img) jacketImages[url] = img;
3991
3240
  }
3992
3241
  for (let i = 0; i < scores.length; i++) {
3993
3242
  const score = scores[i];
@@ -3996,17 +3245,7 @@ var SDVXDrawer = class extends BaseDrawer {
3996
3245
  const x = startX + col * effectiveCardWidth;
3997
3246
  const y = startY + row * (cardHeight + cardVerticalSpacing);
3998
3247
  const vf2 = score.extra.volforce;
3999
- let vfClass = 1;
4000
- if (vf2 >= 20) vfClass = 10;
4001
- else if (vf2 >= 19) vfClass = 9;
4002
- else if (vf2 >= 18) vfClass = 8;
4003
- else if (vf2 >= 17) vfClass = 7;
4004
- else if (vf2 >= 16) vfClass = 6;
4005
- else if (vf2 >= 15) vfClass = 5;
4006
- else if (vf2 >= 14) vfClass = 4;
4007
- else if (vf2 >= 12) vfClass = 3;
4008
- else if (vf2 >= 10) vfClass = 2;
4009
- else vfClass = 1;
3248
+ const { class: vfClass } = this.getVFClassColor(vf2);
4010
3249
  if (circleImages[vfClass]) {
4011
3250
  ctx.drawImage(circleImages[vfClass], x + 628, y - 42);
4012
3251
  }
@@ -4199,48 +3438,19 @@ var SDVXDrawer = class extends BaseDrawer {
4199
3438
  }
4200
3439
  const totalAvailableWidth = 413;
4201
3440
  ctx.font = '400 24px "Noto Sans JP"';
4202
- const mainTitleWidth = ctx.measureText(mainTitle).width;
4203
3441
  let displayMainTitle = mainTitle;
4204
3442
  if (subTitle) {
4205
3443
  const mainTitleMaxWidth = totalAvailableWidth * 0.7;
4206
- if (mainTitleWidth > mainTitleMaxWidth) {
4207
- let fitLength = 0;
4208
- for (let i2 = 1; i2 <= mainTitle.length; i2++) {
4209
- if (ctx.measureText(mainTitle.substring(0, i2)).width > mainTitleMaxWidth) {
4210
- fitLength = i2 - 1;
4211
- break;
4212
- }
4213
- }
4214
- displayMainTitle = mainTitle.substring(0, fitLength);
4215
- }
3444
+ displayMainTitle = this.fitText(ctx, mainTitle, mainTitleMaxWidth);
4216
3445
  ctx.fillStyle = "#FFFFFF";
4217
3446
  ctx.fillText(displayMainTitle, x + 388, y + 115);
4218
3447
  ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
4219
3448
  const actualMainTitleWidth = ctx.measureText(displayMainTitle).width;
4220
3449
  const remainingWidth = totalAvailableWidth - actualMainTitleWidth - 10;
4221
- let displaySubTitle = subTitle;
4222
- if (ctx.measureText(subTitle).width > remainingWidth) {
4223
- let fitLength = 0;
4224
- for (let i2 = 1; i2 <= subTitle.length; i2++) {
4225
- if (ctx.measureText(subTitle.substring(0, i2)).width > remainingWidth) {
4226
- fitLength = i2 - 1;
4227
- break;
4228
- }
4229
- }
4230
- displaySubTitle = subTitle.substring(0, fitLength);
4231
- }
3450
+ const displaySubTitle = this.fitText(ctx, subTitle, remainingWidth);
4232
3451
  ctx.fillText(displaySubTitle, x + 388 + actualMainTitleWidth + 10, y + 115);
4233
3452
  } else {
4234
- if (mainTitleWidth > totalAvailableWidth) {
4235
- let fitLength = 0;
4236
- for (let i2 = 1; i2 <= mainTitle.length; i2++) {
4237
- if (ctx.measureText(mainTitle.substring(0, i2)).width > totalAvailableWidth) {
4238
- fitLength = i2 - 1;
4239
- break;
4240
- }
4241
- }
4242
- displayMainTitle = mainTitle.substring(0, fitLength);
4243
- }
3453
+ displayMainTitle = this.fitText(ctx, mainTitle, totalAvailableWidth);
4244
3454
  ctx.fillStyle = "#FFFFFF";
4245
3455
  ctx.fillText(displayMainTitle, x + 388, y + 115);
4246
3456
  }
@@ -4365,18 +3575,11 @@ var SDVXDrawer = class extends BaseDrawer {
4365
3575
  return { class: vfClass, color };
4366
3576
  }
4367
3577
  async generateVFTableImage(options, compression) {
4368
- const { Canvas, FontLibrary } = this.ctx.skia;
3578
+ const { Canvas } = this.ctx.skia;
4369
3579
  if (!compression) {
4370
3580
  compression = { lossless: true };
4371
3581
  }
4372
- const notoSansPath = getAssetPath(this.ctx, "fonts/NotoSans-VariableFont_wdth,wght.ttf");
4373
- const notoSansJPPath = getAssetPath(this.ctx, "fonts/NotoSansJP-VariableFont_wght.ttf");
4374
- if (fs2.existsSync(notoSansPath)) {
4375
- FontLibrary.use("Noto Sans", notoSansPath);
4376
- }
4377
- if (fs2.existsSync(notoSansJPPath)) {
4378
- FontLibrary.use("Noto Sans JP", notoSansJPPath);
4379
- }
3582
+ this.ensureFontsLoaded();
4380
3583
  const baseCellWidth = 160;
4381
3584
  const baseCellHeight = 60;
4382
3585
  const headerHeight = 80;
@@ -4568,6 +3771,7 @@ var DrawerFactory = class {
4568
3771
  constructor(ctx) {
4569
3772
  this.ctx = ctx;
4570
3773
  }
3774
+ ctx;
4571
3775
  static {
4572
3776
  __name(this, "DrawerFactory");
4573
3777
  }
@@ -4601,7 +3805,7 @@ var DrawerFactory = class {
4601
3805
  };
4602
3806
 
4603
3807
  // src/drawer/index.ts
4604
- var name8 = "Noah-Drawer";
3808
+ var name6 = "Noah-Drawer";
4605
3809
  var DrawerManager = class _DrawerManager {
4606
3810
  static {
4607
3811
  __name(this, "DrawerManager");
@@ -4611,7 +3815,7 @@ var DrawerManager = class _DrawerManager {
4611
3815
  logger;
4612
3816
  constructor(ctx) {
4613
3817
  this.factory = new DrawerFactory(ctx);
4614
- this.logger = new import_koishi6.Logger("Noah-Drawer");
3818
+ this.logger = new import_koishi7.Logger("Noah-Drawer");
4615
3819
  }
4616
3820
  /**
4617
3821
  * 获取绘图管理器实例
@@ -4634,35 +3838,35 @@ var DrawerManager = class _DrawerManager {
4634
3838
  return this.factory.getDrawer(pluginType);
4635
3839
  }
4636
3840
  };
4637
- function apply8(ctx, config) {
3841
+ function apply6(ctx, config) {
4638
3842
  ctx.logger("Noah-Drawer").info("Initializing drawer manager");
4639
3843
  const drawerManager = DrawerManager.getInstance(ctx);
4640
3844
  }
4641
- __name(apply8, "apply");
3845
+ __name(apply6, "apply");
4642
3846
 
4643
3847
  // src/fun/poke/index.ts
4644
3848
  var poke_exports = {};
4645
3849
  __export(poke_exports, {
4646
- apply: () => apply9,
4647
- name: () => name9
3850
+ apply: () => apply7,
3851
+ name: () => name7
4648
3852
  });
4649
3853
  var fs3 = __toESM(require("fs"), 1);
4650
3854
  var path2 = __toESM(require("path"), 1);
4651
- var import_koishi7 = require("koishi");
3855
+ var import_koishi8 = require("koishi");
4652
3856
 
4653
3857
  // src/fun/poke/locales/en-US.yml
4654
- var en_US_default4 = { _config: { $desc: "Poke Module Settings", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: "警告内容", messages: { $desc: "消息内容", content: "消息内容", weight: "权重" } }, commands: { poke: { description: "poke" } } };
3858
+ var en_US_default3 = { _config: { $desc: "Poke Module Settings", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: "警告内容", messages: { $desc: "消息内容", content: "消息内容", weight: "权重" } }, commands: { poke: { description: "poke" } } };
4655
3859
 
4656
3860
  // src/fun/poke/locales/zh-CN.yml
4657
- var zh_CN_default4 = { _config: { $desc: "Poke 模块设置", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: { $desc: "警告内容", content: "消息", weight: "权重" }, messages: { $desc: "消息内容", content: "消息", weight: "权重" } }, commands: { poke: { description: "戳一戳" } } };
3861
+ var zh_CN_default3 = { _config: { $desc: "Poke 模块设置", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: { $desc: "警告内容", content: "消息", weight: "权重" }, messages: { $desc: "消息内容", content: "消息", weight: "权重" } }, commands: { poke: { description: "戳一戳" } } };
4658
3862
 
4659
3863
  // src/fun/poke/index.ts
4660
- var name9 = "Noah-Poke";
4661
- function apply9(ctx, config) {
3864
+ var name7 = "Noah-Poke";
3865
+ function apply7(ctx, config) {
4662
3866
  ;
4663
3867
  [
4664
- ["en-US", en_US_default4],
4665
- ["zh-CN", zh_CN_default4]
3868
+ ["en-US", en_US_default3],
3869
+ ["zh-CN", zh_CN_default3]
4666
3870
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
4667
3871
  const cache = /* @__PURE__ */ new Map();
4668
3872
  const pokeConfig2 = config.poke;
@@ -4751,11 +3955,11 @@ function apply9(ctx, config) {
4751
3955
  }
4752
3956
  const imageBuffer = fs3.readFileSync(randomStamp.path);
4753
3957
  const audioPath = findMatchingAudio(randomStamp.path);
4754
- await session.sendQueued(import_koishi7.h.image(imageBuffer, getMimeType(randomStamp.path)));
3958
+ await session.sendQueued(import_koishi8.h.image(imageBuffer, getMimeType(randomStamp.path)));
4755
3959
  if (audioPath && fs3.existsSync(audioPath)) {
4756
3960
  ctx2.logger("Noah-Poke").debug(`Found matching audio: ${path2.basename(audioPath)}`);
4757
3961
  const audioBuffer = fs3.readFileSync(audioPath);
4758
- await session.sendQueued(import_koishi7.h.audio(audioBuffer, "audio/mpeg"));
3962
+ await session.sendQueued(import_koishi8.h.audio(audioBuffer, "audio/mpeg"));
4759
3963
  }
4760
3964
  } catch (error) {
4761
3965
  ctx2.logger("Noah-Poke").error(`Error sending noah stamp: ${error.message}`);
@@ -4776,7 +3980,7 @@ function apply9(ctx, config) {
4776
3980
  return "Selected stamp not found!";
4777
3981
  }
4778
3982
  const imageBuffer = fs3.readFileSync(stampPath);
4779
- return import_koishi7.h.image(imageBuffer, getMimeType(stampPath));
3983
+ return import_koishi8.h.image(imageBuffer, getMimeType(stampPath));
4780
3984
  } catch (error) {
4781
3985
  ctx2.logger("Noah-Poke").error(`Error sending chat stamp: ${error.message}`);
4782
3986
  return "Failed to send chat stamp.";
@@ -4823,7 +4027,7 @@ function apply9(ctx, config) {
4823
4027
  if (session.timestamp - ts < pokeConfig2.interval) {
4824
4028
  if (pokeConfig2.warning) {
4825
4029
  const msg = randomMessage(pokeConfig2.prompt);
4826
- const content = import_koishi7.h.parse(msg, session);
4030
+ const content = import_koishi8.h.parse(msg, session);
4827
4031
  session.sendQueued(content);
4828
4032
  }
4829
4033
  return;
@@ -4832,12 +4036,12 @@ function apply9(ctx, config) {
4832
4036
  cache.set(session.userId, session.timestamp);
4833
4037
  if (pokeConfig2.messages.length > 0) {
4834
4038
  const msg = randomMessage(pokeConfig2.messages);
4835
- const content = import_koishi7.h.parse(msg, session);
4039
+ const content = import_koishi8.h.parse(msg, session);
4836
4040
  await session.sendQueued(content);
4837
4041
  }
4838
4042
  });
4839
4043
  }
4840
- __name(apply9, "apply");
4044
+ __name(apply7, "apply");
4841
4045
  function randomMessage(messages) {
4842
4046
  const totalWeight = messages.reduce((sum2, cur) => sum2 + cur.weight, 0);
4843
4047
  const random = Math.random() * totalWeight;
@@ -4859,14 +4063,14 @@ __name(parsePlatform, "parsePlatform");
4859
4063
  // src/games/general/index.ts
4860
4064
  var general_exports = {};
4861
4065
  __export(general_exports, {
4862
- apply: () => apply10,
4863
- logger: () => logger4,
4864
- name: () => name10
4066
+ apply: () => apply8,
4067
+ logger: () => logger3,
4068
+ name: () => name8
4865
4069
  });
4866
- var import_koishi9 = require("koishi");
4070
+ var import_koishi10 = require("koishi");
4867
4071
 
4868
4072
  // src/games/general/events/quote.ts
4869
- var import_koishi8 = require("koishi");
4073
+ var import_koishi9 = require("koishi");
4870
4074
 
4871
4075
  // src/games/general/utils/codeReader.ts
4872
4076
  async function readCode128(ctx, barcodeApiUrl, imageUrl) {
@@ -4892,8 +4096,8 @@ __name(readCode128, "readCode128");
4892
4096
  function quote(ctx, config) {
4893
4097
  ctx.on("message", async (session) => {
4894
4098
  if (session.quote && session.quote.user.id === session.selfId) {
4895
- const images = await import_koishi8.h.select(session.quote.elements, "img");
4896
- const textElements = await import_koishi8.h.select(session.elements, "text");
4099
+ const images = await import_koishi9.h.select(session.quote.elements, "img");
4100
+ const textElements = await import_koishi9.h.select(session.elements, "text");
4897
4101
  const allText = textElements.map((el) => el.attrs?.content || "").join(" ");
4898
4102
  if (images.length === 1) {
4899
4103
  const imageUrl = images[0].attrs.src;
@@ -4918,32 +4122,32 @@ function quote(ctx, config) {
4918
4122
  __name(quote, "quote");
4919
4123
 
4920
4124
  // src/games/general/index.ts
4921
- var name10 = "Noah-General";
4922
- var logger4 = new import_koishi9.Logger("Noah-General");
4923
- async function apply10(ctx, config) {
4125
+ var name8 = "Noah-General";
4126
+ var logger3 = new import_koishi10.Logger("Noah-General");
4127
+ async function apply8(ctx, config) {
4924
4128
  quote(ctx, config.general);
4925
4129
  }
4926
- __name(apply10, "apply");
4130
+ __name(apply8, "apply");
4927
4131
 
4928
4132
  // src/games/sdvx/index.ts
4929
4133
  var sdvx_exports = {};
4930
4134
  __export(sdvx_exports, {
4931
- apply: () => apply15,
4932
- inject: () => inject3,
4933
- logger: () => logger5,
4934
- name: () => name15
4135
+ apply: () => apply13,
4136
+ inject: () => inject2,
4137
+ logger: () => logger4,
4138
+ name: () => name13
4935
4139
  });
4936
- var import_koishi17 = require("koishi");
4140
+ var import_koishi18 = require("koishi");
4937
4141
 
4938
4142
  // src/games/sdvx/command.ts
4939
4143
  var command_exports2 = {};
4940
4144
  __export(command_exports2, {
4941
- apply: () => apply12,
4942
- name: () => name12
4145
+ apply: () => apply10,
4146
+ name: () => name10
4943
4147
  });
4944
4148
 
4945
4149
  // src/games/sdvx/commands/calculate.ts
4946
- var import_koishi10 = require("koishi");
4150
+ var import_koishi11 = require("koishi");
4947
4151
 
4948
4152
  // src/games/sdvx/utils/param-parser.ts
4949
4153
  function parseNumberWithSuffix(str) {
@@ -5064,37 +4268,17 @@ function parseVfValue(item) {
5064
4268
  }
5065
4269
  __name(parseVfValue, "parseVfValue");
5066
4270
  function parseClearTypeRange(item) {
5067
- const clearTypeMap = {
5068
- spuc: "S-PUC",
5069
- puc: "PUC",
5070
- uc: "UC",
5071
- mc: "MC",
5072
- hc: "HC",
5073
- nc: "NC",
5074
- played: "PLAYED",
5075
- noplay: "NO PLAY"
5076
- };
5077
- const clearTypeList = [
5078
- "S-PUC",
5079
- "PUC",
5080
- "UC",
5081
- "MC",
5082
- "HC",
5083
- "NC",
5084
- "PLAYED",
5085
- "NO PLAY"
5086
- ];
5087
4271
  if (item.includes("-")) {
5088
4272
  const [startRaw, endRaw] = item.split("-").filter(Boolean);
5089
- if (startRaw && endRaw && clearTypeMap[startRaw] && clearTypeMap[endRaw]) {
5090
- const start = clearTypeList.findIndex((t) => t === clearTypeMap[startRaw]);
5091
- const end = clearTypeList.findIndex((t) => t === clearTypeMap[endRaw]);
4273
+ if (startRaw && endRaw && CLEAR_TYPE_ABBR_MAP[startRaw] && CLEAR_TYPE_ABBR_MAP[endRaw]) {
4274
+ const start = ALL_CLEAR_TYPES.findIndex((t) => t === CLEAR_TYPE_ABBR_MAP[startRaw]);
4275
+ const end = ALL_CLEAR_TYPES.findIndex((t) => t === CLEAR_TYPE_ABBR_MAP[endRaw]);
5092
4276
  if (start !== -1 && end !== -1) {
5093
4277
  const from = Math.min(start, end);
5094
4278
  const to = Math.max(start, end);
5095
4279
  const clearTypes = [];
5096
4280
  for (let i = from; i <= to; i++) {
5097
- clearTypes.push(clearTypeList[i]);
4281
+ clearTypes.push(ALL_CLEAR_TYPES[i]);
5098
4282
  }
5099
4283
  if (clearTypes.includes("PUC") && !clearTypes.includes("S-PUC")) {
5100
4284
  clearTypes.push("S-PUC");
@@ -5107,18 +4291,8 @@ function parseClearTypeRange(item) {
5107
4291
  }
5108
4292
  __name(parseClearTypeRange, "parseClearTypeRange");
5109
4293
  function parseSingleClearType(item) {
5110
- const clearTypeMap = {
5111
- spuc: "S-PUC",
5112
- puc: "PUC",
5113
- uc: "UC",
5114
- mc: "MC",
5115
- hc: "HC",
5116
- nc: "NC",
5117
- played: "PLAYED",
5118
- noplay: "NO PLAY"
5119
- };
5120
- if (clearTypeMap[item.toLowerCase()]) {
5121
- const clearType = clearTypeMap[item.toLowerCase()];
4294
+ if (CLEAR_TYPE_ABBR_MAP[item.toLowerCase()]) {
4295
+ const clearType = CLEAR_TYPE_ABBR_MAP[item.toLowerCase()];
5122
4296
  if (clearType === "PUC") {
5123
4297
  return ["PUC", "S-PUC"];
5124
4298
  }
@@ -5128,17 +4302,16 @@ function parseSingleClearType(item) {
5128
4302
  }
5129
4303
  __name(parseSingleClearType, "parseSingleClearType");
5130
4304
  function parseGradeRange(item) {
5131
- const gradeList = ["S", "AAA+", "AAA", "AA+", "AA", "A+", "A", "B", "C", "D"];
5132
4305
  if (/^[a-ds][a-z+]*-[a-ds][a-z+]*$/.test(item)) {
5133
4306
  const [startRaw, endRaw] = item.split("-");
5134
- const start = gradeList.findIndex((g) => g.toLowerCase() === startRaw);
5135
- const end = gradeList.findIndex((g) => g.toLowerCase() === endRaw);
4307
+ const start = ALL_GRADES.findIndex((g) => g.toLowerCase() === startRaw);
4308
+ const end = ALL_GRADES.findIndex((g) => g.toLowerCase() === endRaw);
5136
4309
  if (start !== -1 && end !== -1) {
5137
4310
  const from = Math.min(start, end);
5138
4311
  const to = Math.max(start, end);
5139
4312
  const grades = [];
5140
4313
  for (let i = from; i <= to; i++) {
5141
- grades.push(gradeList[i]);
4314
+ grades.push(ALL_GRADES[i]);
5142
4315
  }
5143
4316
  return grades;
5144
4317
  }
@@ -5147,8 +4320,7 @@ function parseGradeRange(item) {
5147
4320
  }
5148
4321
  __name(parseGradeRange, "parseGradeRange");
5149
4322
  function parseSingleGrade(item) {
5150
- const gradeList = ["S", "AAA+", "AAA", "AA+", "AA", "A+", "A", "B", "C", "D"];
5151
- const grade = gradeList.find((g) => g.toLowerCase() === item.toLowerCase());
4323
+ const grade = ALL_GRADES.find((g) => g.toLowerCase() === item.toLowerCase());
5152
4324
  if (grade) {
5153
4325
  return [grade];
5154
4326
  }
@@ -5247,7 +4419,7 @@ function parseQueryInput(input) {
5247
4419
  __name(parseQueryInput, "parseQueryInput");
5248
4420
 
5249
4421
  // src/games/sdvx/commands/calculate.ts
5250
- function calculate(ctx, config, logger6) {
4422
+ function calculate(ctx, config, logger5) {
5251
4423
  ctx.command("sdvx.calculate <query:text>").alias("sdvx.cal").action(async ({ session, options }, query) => {
5252
4424
  try {
5253
4425
  let queryInput = query;
@@ -5281,7 +4453,7 @@ function calculate(ctx, config, logger6) {
5281
4453
  lossless: true
5282
4454
  }
5283
4455
  );
5284
- return import_koishi10.h.image(imageBuffer, "image/png");
4456
+ return import_koishi11.h.image(imageBuffer, "image/png");
5285
4457
  } catch (error) {
5286
4458
  ctx.logger("SDVX-Calculate").warn(error);
5287
4459
  return session.text(".error");
@@ -5291,7 +4463,7 @@ function calculate(ctx, config, logger6) {
5291
4463
  __name(calculate, "calculate");
5292
4464
 
5293
4465
  // src/games/sdvx/commands/chart.ts
5294
- var import_koishi11 = require("koishi");
4466
+ var import_koishi12 = require("koishi");
5295
4467
 
5296
4468
  // src/servers/utils/difficulty.ts
5297
4469
  function getDiffName(diffStr, infVer) {
@@ -5480,7 +4652,6 @@ var MusicService = class _MusicService {
5480
4652
  const music = typeof id === "number" ? numericMap.get(id) : stringMap.get(id);
5481
4653
  if (music) results.push(music);
5482
4654
  }
5483
- console.log("results", results.slice(0, 3));
5484
4655
  return results;
5485
4656
  }
5486
4657
  /**
@@ -5528,7 +4699,6 @@ var MusicService = class _MusicService {
5528
4699
  if (musicIds.length === 0) {
5529
4700
  return null;
5530
4701
  }
5531
- console.log("musicIds", musicIds);
5532
4702
  return await this.getMusic(ctx, musicIds);
5533
4703
  } catch {
5534
4704
  return null;
@@ -5643,7 +4813,7 @@ function getHighestDifstr(diffs) {
5643
4813
  return diffs[diffs.length - 1].difstr;
5644
4814
  }
5645
4815
  __name(getHighestDifstr, "getHighestDifstr");
5646
- function chart(ctx, config, logger6) {
4816
+ function chart(ctx, config, logger5) {
5647
4817
  ctx.command("sdvx.chart [query:text]").alias("sdvx.c").action(async ({ session }, query) => {
5648
4818
  if (!query) {
5649
4819
  await session.send(session.text(".prompt"));
@@ -5706,7 +4876,7 @@ function chart(ctx, config, logger6) {
5706
4876
  const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
5707
4877
  headers: { "Content-Type": "application/json" }
5708
4878
  });
5709
- return import_koishi11.h.image(res, "image/png");
4879
+ return import_koishi12.h.image(res, "image/png");
5710
4880
  } else {
5711
4881
  const {
5712
4882
  diffStr: parsedDiff,
@@ -5722,7 +4892,7 @@ function chart(ctx, config, logger6) {
5722
4892
  const picked = musicInfo[0];
5723
4893
  const music_id = Number(picked?.id);
5724
4894
  if (!Number.isFinite(music_id)) {
5725
- logger6.warn("search result missing id", picked);
4895
+ logger5.warn("search result missing id", picked);
5726
4896
  return session.text(".error");
5727
4897
  }
5728
4898
  let difstr = null;
@@ -5756,29 +4926,26 @@ function chart(ctx, config, logger6) {
5756
4926
  const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
5757
4927
  headers: { "Content-Type": "application/json" }
5758
4928
  });
5759
- return import_koishi11.h.image(res, "image/png");
4929
+ return import_koishi12.h.image(res, "image/png");
5760
4930
  }
5761
4931
  } catch (err) {
5762
- logger6.warn(err);
4932
+ logger5.warn(err);
5763
4933
  return session.text(".error");
5764
4934
  }
5765
4935
  });
5766
4936
  }
5767
4937
  __name(chart, "chart");
5768
4938
 
5769
- // src/games/sdvx/commands/recent.ts
5770
- var import_koishi15 = require("koishi");
5771
-
5772
4939
  // src/servers/index.ts
5773
4940
  var servers_exports = {};
5774
4941
  __export(servers_exports, {
5775
4942
  ServerManager: () => ServerManager,
5776
- apply: () => apply11,
5777
- name: () => name11
4943
+ apply: () => apply9,
4944
+ name: () => name9
5778
4945
  });
5779
4946
 
5780
4947
  // src/servers/Asphyxia/index.ts
5781
- var import_koishi12 = require("koishi");
4948
+ var import_koishi13 = require("koishi");
5782
4949
 
5783
4950
  // src/servers/Asphyxia/services/iidx-service.ts
5784
4951
  var IIDXService = class _IIDXService {
@@ -5787,12 +4954,12 @@ var IIDXService = class _IIDXService {
5787
4954
  }
5788
4955
  static instance;
5789
4956
  logger;
5790
- constructor(logger6) {
5791
- this.logger = logger6;
4957
+ constructor(logger5) {
4958
+ this.logger = logger5;
5792
4959
  }
5793
- static getInstance(logger6) {
4960
+ static getInstance(logger5) {
5794
4961
  if (!_IIDXService.instance) {
5795
- _IIDXService.instance = new _IIDXService(logger6);
4962
+ _IIDXService.instance = new _IIDXService(logger5);
5796
4963
  }
5797
4964
  return _IIDXService.instance;
5798
4965
  }
@@ -5801,19 +4968,6 @@ var IIDXService = class _IIDXService {
5801
4968
  };
5802
4969
 
5803
4970
  // src/servers/utils/grade.ts
5804
- function getSDVXGrade(score) {
5805
- if (score >= 99e5) return "S";
5806
- if (score >= 98e5) return "AAA+";
5807
- if (score >= 97e5) return "AAA";
5808
- if (score >= 95e5) return "AA+";
5809
- if (score >= 93e5) return "AA";
5810
- if (score >= 9e6) return "A+";
5811
- if (score >= 87e5) return "A";
5812
- if (score >= 75e5) return "B";
5813
- if (score >= 65e5) return "C";
5814
- return "D";
5815
- }
5816
- __name(getSDVXGrade, "getSDVXGrade");
5817
4971
  function getSDVXClearType(clearType) {
5818
4972
  return clearType === 6 ? "MC" : clearType === 5 ? "PUC" : clearType === 4 ? "UC" : clearType === 3 ? "HC" : clearType === 2 ? "NC" : clearType === 1 ? "PLAYED" : "NO PLAY";
5819
4973
  }
@@ -5840,12 +4994,12 @@ var SDVXService = class _SDVXService {
5840
4994
  }
5841
4995
  static instance;
5842
4996
  logger;
5843
- constructor(logger6) {
5844
- this.logger = logger6;
4997
+ constructor(logger5) {
4998
+ this.logger = logger5;
5845
4999
  }
5846
- static getInstance(logger6) {
5000
+ static getInstance(logger5) {
5847
5001
  if (!_SDVXService.instance) {
5848
- _SDVXService.instance = new _SDVXService(logger6);
5002
+ _SDVXService.instance = new _SDVXService(logger5);
5849
5003
  }
5850
5004
  return _SDVXService.instance;
5851
5005
  }
@@ -5882,9 +5036,9 @@ var SDVXService = class _SDVXService {
5882
5036
  const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
5883
5037
  const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
5884
5038
  const jsonResponse = await xmlToJson(decodedResponse);
5885
- const name17 = jsonResponse.response.game?.[0]?.name?.[0]._;
5886
- if (!name17) throw new Error("User name not found");
5887
- return name17;
5039
+ const name15 = jsonResponse.response.game?.[0]?.name?.[0]._;
5040
+ if (!name15) throw new Error("User name not found");
5041
+ return name15;
5888
5042
  }
5889
5043
  /**
5890
5044
  * 获取所有分数
@@ -5943,7 +5097,7 @@ var SDVXService = class _SDVXService {
5943
5097
  score,
5944
5098
  exscore,
5945
5099
  clear_type: getSDVXClearType(clear_type),
5946
- score_grade: getSDVXGrade(score),
5100
+ score_grade: getGradeByScore(score),
5947
5101
  btn_rate: String(btn_rate),
5948
5102
  long_rate: String(long_rate),
5949
5103
  vol_rate: String(vol_rate)
@@ -5984,7 +5138,7 @@ var Asphyxia = class {
5984
5138
  name = "asphyxia";
5985
5139
  supportedGames = ["sdvx", "iidx"];
5986
5140
  gameServices = {};
5987
- logger = new import_koishi12.Logger("Noah-Asphyxia");
5141
+ logger = new import_koishi13.Logger("Noah-Asphyxia");
5988
5142
  /**
5989
5143
  * 初始化各个游戏服务实例
5990
5144
  */
@@ -5995,7 +5149,7 @@ var Asphyxia = class {
5995
5149
  };
5996
5150
 
5997
5151
  // src/servers/Mao/index.ts
5998
- var import_koishi13 = require("koishi");
5152
+ var import_koishi14 = require("koishi");
5999
5153
 
6000
5154
  // src/servers/Mao/services/sdvx-service.ts
6001
5155
  var SDVXService2 = class _SDVXService {
@@ -6004,12 +5158,12 @@ var SDVXService2 = class _SDVXService {
6004
5158
  }
6005
5159
  static instance;
6006
5160
  logger;
6007
- constructor(logger6) {
6008
- this.logger = logger6;
5161
+ constructor(logger5) {
5162
+ this.logger = logger5;
6009
5163
  }
6010
- static getInstance(logger6) {
5164
+ static getInstance(logger5) {
6011
5165
  if (!_SDVXService.instance) {
6012
- _SDVXService.instance = new _SDVXService(logger6);
5166
+ _SDVXService.instance = new _SDVXService(logger5);
6013
5167
  }
6014
5168
  return _SDVXService.instance;
6015
5169
  }
@@ -6056,16 +5210,19 @@ var SDVXService2 = class _SDVXService {
6056
5210
  * @returns 验证是否通过
6057
5211
  */
6058
5212
  async verifyPin(ctx, url, cardId, pin) {
6059
- const baseURL = "https://maomani.cn";
6060
- const resp = await ctx.http.post(
6061
- "/api/login/account",
5213
+ const apiKey = ctx.config.maoApiKey;
5214
+ if (!apiKey) {
5215
+ this.logger.warn("maoApiKey not configured");
5216
+ return false;
5217
+ }
5218
+ const resp = await ctx.http.get(
5219
+ `/bot/v2/player/card?card=${cardId}`,
6062
5220
  {
6063
- username: cardId,
6064
- password: pin
6065
- },
6066
- { baseURL }
5221
+ baseURL: url,
5222
+ headers: { "X-API-Key": apiKey }
5223
+ }
6067
5224
  );
6068
- return resp?.errorCode === 200;
5225
+ return resp?.code === 0 && resp?.data?.password === pin;
6069
5226
  }
6070
5227
  async uploadScore(ctx, url, cardId, scorePayload) {
6071
5228
  const baseURL = "https://maomani.cn";
@@ -6081,22 +5238,30 @@ var SDVXService2 = class _SDVXService {
6081
5238
  }
6082
5239
  async getAllScore(ctx, url, cardId, config) {
6083
5240
  try {
6084
- const data = await ctx.http.get(`/sdvx/scores?card=${cardId}`, {
5241
+ const apiKey = ctx.config.maoApiKey;
5242
+ if (!apiKey) {
5243
+ this.logger.warn("maoApiKey not configured");
5244
+ return [];
5245
+ }
5246
+ const resp = await ctx.http.get(`/bot/v2/sdvx/scores?card=${cardId}`, {
6085
5247
  baseURL: url,
6086
- responseType: "json"
5248
+ headers: { "X-API-Key": apiKey }
6087
5249
  });
6088
- const filteredData = data.filter(
6089
- (score) => score.music_id !== 244 && score.music_id !== 1759 && score.music_id !== 1761
5250
+ if (resp?.code !== 0 || !resp?.data) {
5251
+ return [];
5252
+ }
5253
+ const filteredData = resp.data.filter(
5254
+ (score) => score.mid !== 244 && score.mid !== 1759 && score.mid !== 1761 && score.isPlus === 0
6090
5255
  );
6091
- const musicIds = filteredData.map((score) => score.music_id);
6092
- const diffTypes = filteredData.map((score) => parseInt(score.music_diff));
5256
+ const musicIds = filteredData.map((score) => score.mid);
5257
+ const diffTypes = filteredData.map((score) => score.musicType);
6093
5258
  const musicService = MusicService.getInstance(config);
6094
5259
  const musicData = await musicService.getMusic(ctx, musicIds);
6095
5260
  const musicDataMap = new Map(musicData.map((music) => [music.id, music]));
6096
5261
  const scores = filteredData.map((score, index) => {
6097
- const musicId = score.music_id;
5262
+ const musicId = score.mid;
6098
5263
  const music = musicDataMap.get(musicId);
6099
- const musicDiffType = parseInt(score.music_diff);
5264
+ const musicDiffType = score.musicType;
6100
5265
  const diffStr = this.getDifficultyString(musicDiffType);
6101
5266
  let difficultyData = null;
6102
5267
  if (music && music.difficulty && music.difficulty.length > 0) {
@@ -6111,15 +5276,15 @@ var SDVXService2 = class _SDVXService {
6111
5276
  music_diff_full_name: getDiffFullName(diffStr, infVer),
6112
5277
  score: score.score,
6113
5278
  exscore: score.exscore,
6114
- clear_type: getSDVXClearType(score.clear_type),
6115
- score_grade: getSDVXGrade(score.score),
6116
- btn_rate: score.btn_rate.toString(),
6117
- long_rate: score.long_rate.toString(),
6118
- vol_rate: score.vol_rate.toString()
5279
+ clear_type: getSDVXClearType(score.clearType),
5280
+ score_grade: getGradeByScore(score.score),
5281
+ btn_rate: score.btnRate.toString(),
5282
+ long_rate: score.longRate.toString(),
5283
+ vol_rate: score.volRate.toString()
6119
5284
  },
6120
5285
  extra: {
6121
- play_count: score.play_count,
6122
- update_at: score.update_at,
5286
+ play_count: score.playCount,
5287
+ update_at: score.updateAt,
6123
5288
  volforce: 0
6124
5289
  },
6125
5290
  music_data: music || null,
@@ -6137,12 +5302,16 @@ var SDVXService2 = class _SDVXService {
6137
5302
  }
6138
5303
  }
6139
5304
  async getScore(ctx, url, cardId, musicId, config) {
6140
- const response = await ctx.http.get(`/sdvx/find?card=${cardId}&mid=${musicId}`, {
5305
+ const apiKey = ctx.config.maoApiKey;
5306
+ if (!apiKey) {
5307
+ this.logger.warn("maoApiKey not configured");
5308
+ throw new Error("maoApiKey not configured");
5309
+ }
5310
+ const resp = await ctx.http.get(`/bot/v2/sdvx/find?card=${cardId}&mid=${musicId}`, {
6141
5311
  baseURL: url,
6142
- responseType: "text"
5312
+ headers: { "X-API-Key": apiKey }
6143
5313
  });
6144
- const text = await response;
6145
- if (text === "没有找到相关记录") {
5314
+ if (resp?.code !== 0 || !resp?.data) {
6146
5315
  const musicService2 = MusicService.getInstance(config);
6147
5316
  const musicData2 = await musicService2.getMusic(ctx, [musicId]);
6148
5317
  const music2 = musicData2.length > 0 ? musicData2[0] : null;
@@ -6162,7 +5331,7 @@ var SDVXService2 = class _SDVXService {
6162
5331
  exscore: 0,
6163
5332
  clear_type: getSDVXClearType(0),
6164
5333
  // NO PLAY
6165
- score_grade: getSDVXGrade(0),
5334
+ score_grade: getGradeByScore(0),
6166
5335
  btn_rate: "0",
6167
5336
  long_rate: "0",
6168
5337
  vol_rate: "0"
@@ -6176,14 +5345,8 @@ var SDVXService2 = class _SDVXService {
6176
5345
  difficulty_data: difficultyData2
6177
5346
  };
6178
5347
  }
6179
- const matches = text.match(
6180
- /MusicType: (\d+), Score: (\d+), Exscore: (\d+), ClearType: (\d+), PlayCount: (\d+)/
6181
- );
6182
- if (!matches) {
6183
- throw new Error(`Invalid score format received: ${text}`);
6184
- }
6185
- const [, musicType, score, exscore, clearType, playCount] = matches;
6186
- const musicTypeNum = parseInt(musicType);
5348
+ const data = resp.data;
5349
+ const musicTypeNum = data.musicType;
6187
5350
  const diffStr = this.getDifficultyString(musicTypeNum);
6188
5351
  const musicService = MusicService.getInstance(config);
6189
5352
  const musicData = await musicService.getMusic(ctx, [musicId]);
@@ -6199,19 +5362,16 @@ var SDVXService2 = class _SDVXService {
6199
5362
  music_diff: difficultyData ? difficultyData.difnum : 0,
6200
5363
  music_diff_name: getDiffName(diffStr, infVer),
6201
5364
  music_diff_full_name: getDiffFullName(diffStr, infVer),
6202
- score: parseInt(score),
6203
- exscore: parseInt(exscore),
6204
- clear_type: getSDVXClearType(parseInt(clearType)),
6205
- score_grade: getSDVXGrade(parseInt(score)),
5365
+ score: data.score,
5366
+ exscore: data.exscore,
5367
+ clear_type: getSDVXClearType(data.clearType),
5368
+ score_grade: getGradeByScore(data.score),
6206
5369
  btn_rate: "0",
6207
- // Not provided in response
6208
5370
  long_rate: "0",
6209
- // Not provided in response
6210
5371
  vol_rate: "0"
6211
- // Not provided in response
6212
5372
  },
6213
5373
  extra: {
6214
- play_count: parseInt(playCount),
5374
+ play_count: data.playCount,
6215
5375
  update_at: "0",
6216
5376
  volforce: 0
6217
5377
  },
@@ -6222,49 +5382,25 @@ var SDVXService2 = class _SDVXService {
6222
5382
  return scoreObj;
6223
5383
  }
6224
5384
  async getRecentScores(ctx, url, cardId, config, count = 1) {
6225
- const text = await ctx.http.get(`/sdvx/recent?card=${cardId}&count=${count}`, {
5385
+ const apiKey = ctx.config.maoApiKey;
5386
+ if (!apiKey) {
5387
+ this.logger.warn("maoApiKey not configured");
5388
+ return [];
5389
+ }
5390
+ const resp = await ctx.http.get(`/bot/v2/sdvx/recent?card=${cardId}&count=${count}`, {
6226
5391
  baseURL: url,
6227
- responseType: "text"
5392
+ headers: { "X-API-Key": apiKey }
6228
5393
  });
6229
- const scores = [];
6230
- const musicIds = [];
6231
- const tempScores = [];
6232
- const lines = text.split("\n");
6233
- for (const line of lines) {
6234
- const match = line.match(
6235
- /lscore:(\d+)\s+lexscore:(\d+)\s+lctype:(\d+)\s+mid:(\d+)\s+mtype:(\d+)\s+ctype:(\d+)\s+score:(\d+)\s+exscore:(\d+)\s+time:([^,]+)(?:,\s+retryCnt:(\d+)(?:,\s+judge:([^,]*)(?:,\s+critical:(\d+)(?:,\s+near:(\d+)(?:,\s+error:(\d+)(?:,\s+combo:(\d+))?)?)?)?)?)?/
6236
- );
6237
- if (match) {
6238
- const musicId = match[4];
6239
- const musicType = match[5];
6240
- const clearType = match[6];
6241
- const score = match[7];
6242
- const exscore = match[8];
6243
- const time = match[9];
6244
- const parsedMusicId = parseInt(musicId);
6245
- tempScores.push({
6246
- musicId: parsedMusicId,
6247
- musicType: parseInt(musicType),
6248
- clearType: parseInt(clearType),
6249
- score: parseInt(score),
6250
- exscore: parseInt(exscore),
6251
- time: time.trim()
6252
- });
6253
- musicIds.push(parsedMusicId);
6254
- }
6255
- }
6256
- if (tempScores.length === 0) {
5394
+ if (resp?.code !== 0 || !resp?.data || resp.data.length === 0) {
6257
5395
  return [];
6258
5396
  }
5397
+ const musicIds = resp.data.map((item) => item.mid);
6259
5398
  const musicService = MusicService.getInstance(config);
6260
5399
  const musicDataList = await musicService.getMusic(ctx, musicIds);
6261
5400
  const musicDataMap = new Map(musicDataList.map((music) => [music.id, music]));
6262
- tempScores.forEach((tempScore) => {
6263
- const scoreNum = tempScore.score;
6264
- const musicId = tempScore.musicId;
6265
- const musicTypeNum = tempScore.musicType;
6266
- const music = musicDataMap.get(musicId);
6267
- const diffStr = this.getDifficultyString(musicTypeNum);
5401
+ const scores = resp.data.map((item) => {
5402
+ const music = musicDataMap.get(item.mid);
5403
+ const diffStr = this.getDifficultyString(item.musicType);
6268
5404
  let difficultyData = null;
6269
5405
  if (music && music.difficulty && music.difficulty.length > 0) {
6270
5406
  difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
@@ -6272,31 +5408,28 @@ var SDVXService2 = class _SDVXService {
6272
5408
  const infVer = music ? music.inf_ver : 2;
6273
5409
  const scoreObj = {
6274
5410
  music: {
6275
- music_id: musicId,
5411
+ music_id: item.mid,
6276
5412
  music_diff: difficultyData ? difficultyData.difnum : 0,
6277
5413
  music_diff_name: getDiffName(diffStr, infVer),
6278
5414
  music_diff_full_name: getDiffFullName(diffStr, infVer),
6279
- score: scoreNum,
6280
- exscore: tempScore.exscore,
6281
- clear_type: getSDVXClearType(tempScore.clearType),
6282
- score_grade: getSDVXGrade(scoreNum),
5415
+ score: item.score,
5416
+ exscore: item.exscore,
5417
+ clear_type: getSDVXClearType(item.clearType),
5418
+ score_grade: getGradeByScore(item.score),
6283
5419
  btn_rate: "0",
6284
- // Not provided in API response
6285
5420
  long_rate: "0",
6286
- // Not provided in API response
6287
5421
  vol_rate: "0"
6288
- // Not provided in API response
6289
5422
  },
6290
5423
  extra: {
6291
5424
  volforce: 0,
6292
5425
  play_count: 0,
6293
- update_at: tempScore.time
5426
+ update_at: item.time
6294
5427
  },
6295
5428
  music_data: music || null,
6296
5429
  difficulty_data: difficultyData || null
6297
5430
  };
6298
5431
  scoreObj.extra.volforce = calculateVolforce(scoreObj);
6299
- scores.push(scoreObj);
5432
+ return scoreObj;
6300
5433
  });
6301
5434
  return scores;
6302
5435
  }
@@ -6310,7 +5443,7 @@ var Mao = class {
6310
5443
  name = "mao";
6311
5444
  supportedGames = ["sdvx"];
6312
5445
  gameServices = {};
6313
- logger = new import_koishi13.Logger("Noah-Mao");
5446
+ logger = new import_koishi14.Logger("Noah-Mao");
6314
5447
  /**
6315
5448
  * 初始化SDVX游戏服务实例
6316
5449
  */
@@ -6320,7 +5453,7 @@ var Mao = class {
6320
5453
  };
6321
5454
 
6322
5455
  // src/servers/Official/index.ts
6323
- var import_koishi14 = require("koishi");
5456
+ var import_koishi15 = require("koishi");
6324
5457
 
6325
5458
  // src/servers/Official/services/sdvx-service.ts
6326
5459
  var SDVXService3 = class _SDVXService {
@@ -6329,12 +5462,12 @@ var SDVXService3 = class _SDVXService {
6329
5462
  }
6330
5463
  static instance;
6331
5464
  logger;
6332
- constructor(logger6) {
6333
- this.logger = logger6;
5465
+ constructor(logger5) {
5466
+ this.logger = logger5;
6334
5467
  }
6335
- static getInstance(logger6) {
5468
+ static getInstance(logger5) {
6336
5469
  if (!_SDVXService.instance) {
6337
- _SDVXService.instance = new _SDVXService(logger6);
5470
+ _SDVXService.instance = new _SDVXService(logger5);
6338
5471
  }
6339
5472
  return _SDVXService.instance;
6340
5473
  }
@@ -6457,7 +5590,7 @@ var Official = class {
6457
5590
  name = "official";
6458
5591
  supportedGames = ["sdvx"];
6459
5592
  gameServices = {};
6460
- logger = new import_koishi14.Logger("Noah-Official");
5593
+ logger = new import_koishi15.Logger("Noah-Official");
6461
5594
  /**
6462
5595
  * 初始化SDVX游戏服务实例
6463
5596
  */
@@ -6501,51 +5634,238 @@ var ServerFactory = class {
6501
5634
  };
6502
5635
 
6503
5636
  // src/servers/index.ts
6504
- var name11 = "Noah-Server";
5637
+ var name9 = "Noah-Server";
6505
5638
  var ServerManager = class _ServerManager {
6506
5639
  static {
6507
5640
  __name(this, "ServerManager");
6508
5641
  }
6509
- static instance;
6510
- factory;
6511
- constructor() {
6512
- this.factory = new ServerFactory();
5642
+ static instance;
5643
+ factory;
5644
+ constructor() {
5645
+ this.factory = new ServerFactory();
5646
+ }
5647
+ /**
5648
+ * 获取服务器管理器实例
5649
+ * @returns 服务器管理器的唯一实例
5650
+ */
5651
+ static getInstance() {
5652
+ if (!_ServerManager.instance) {
5653
+ _ServerManager.instance = new _ServerManager();
5654
+ }
5655
+ return _ServerManager.instance;
5656
+ }
5657
+ /**
5658
+ * 根据服务器类型获取对应的服务器实例
5659
+ * @param serverType - 服务器类型
5660
+ * @returns 对应的服务器实例
5661
+ */
5662
+ getServer(serverType) {
5663
+ return this.factory.getServer(serverType);
5664
+ }
5665
+ /**
5666
+ * 根据服务器类型和游戏类型获取对应的游戏服务实例
5667
+ * @param serverType - 服务器类型
5668
+ * @param gameType - 游戏类型
5669
+ * @returns 对应的游戏服务实例
5670
+ */
5671
+ getGameService(serverType, gameType) {
5672
+ const server2 = this.factory.getServer(serverType);
5673
+ return server2.gameServices[gameType];
5674
+ }
5675
+ };
5676
+ function apply9(ctx, config) {
5677
+ }
5678
+ __name(apply9, "apply");
5679
+
5680
+ // src/games/sdvx/services/score-service.ts
5681
+ var ScoreService = class _ScoreService {
5682
+ static {
5683
+ __name(this, "ScoreService");
5684
+ }
5685
+ static instance;
5686
+ constructor() {
5687
+ }
5688
+ /**
5689
+ * 获取 ScoreService 实例
5690
+ * @returns ScoreService 实例
5691
+ */
5692
+ static getInstance() {
5693
+ if (!_ScoreService.instance) {
5694
+ _ScoreService.instance = new _ScoreService();
5695
+ }
5696
+ return _ScoreService.instance;
5697
+ }
5698
+ /**
5699
+ * 按 Volforce 降序排序分数
5700
+ * @param scores - 待排序的 SDVX 分数数组
5701
+ * @returns 按 Volforce 降序排序的新数组
5702
+ */
5703
+ sortByVolforce(scores) {
5704
+ return [...scores].sort((a, b) => b.extra.volforce - a.extra.volforce);
5705
+ }
5706
+ /**
5707
+ * 获取前 50 名分数(包括并列)
5708
+ * @param scores - 待处理的 SDVX 分数数组
5709
+ * @returns 包含最高 Volforce 值的分数数组(如果存在并列,可能超过 50 个)
5710
+ */
5711
+ getBest50(scores) {
5712
+ const sortedScores = this.sortByVolforce(scores);
5713
+ if (sortedScores.length <= 50) {
5714
+ return sortedScores;
5715
+ }
5716
+ const fiftiethVF = sortedScores[49].extra.volforce;
5717
+ return sortedScores.filter((score) => score.extra.volforce >= fiftiethVF);
6513
5718
  }
6514
5719
  /**
6515
- * 获取服务器管理器实例
6516
- * @returns 服务器管理器的唯一实例
5720
+ * 过滤 SDVXScore 数组
5721
+ * @param scores - 待过滤的分数数组
5722
+ * @param options - 过滤选项
5723
+ * @returns 过滤后的分数数组
6517
5724
  */
6518
- static getInstance() {
6519
- if (!_ServerManager.instance) {
6520
- _ServerManager.instance = new _ServerManager();
5725
+ filterScores(scores, options) {
5726
+ let filtered = scores;
5727
+ if (options.grade) {
5728
+ const grades = Array.isArray(options.grade) ? options.grade : [options.grade];
5729
+ filtered = filtered.filter((score) => grades.includes(score.music.score_grade));
6521
5730
  }
6522
- return _ServerManager.instance;
5731
+ if (options.clearType) {
5732
+ const clearTypes = Array.isArray(options.clearType) ? options.clearType : [options.clearType];
5733
+ filtered = filtered.filter((score) => clearTypes.includes(score.music.clear_type));
5734
+ }
5735
+ if (options.musicDiff !== void 0) {
5736
+ const diffs = Array.isArray(options.musicDiff) ? options.musicDiff : [options.musicDiff];
5737
+ filtered = filtered.filter((score) => diffs.includes(score.music.music_diff));
5738
+ }
5739
+ if (options.radarFeature) {
5740
+ const featureKeys = Array.isArray(options.radarFeature) ? options.radarFeature : [options.radarFeature];
5741
+ filtered = filtered.filter((score) => {
5742
+ if (!score.difficulty_data) return false;
5743
+ const radar2 = score.difficulty_data.radar;
5744
+ let maxKey = "notes";
5745
+ let maxValue = radar2["notes"];
5746
+ for (const key of Object.keys(radar2)) {
5747
+ if (radar2[key] > maxValue) {
5748
+ maxKey = key;
5749
+ maxValue = radar2[key];
5750
+ }
5751
+ }
5752
+ return featureKeys.includes(maxKey);
5753
+ });
5754
+ }
5755
+ return filtered;
6523
5756
  }
6524
5757
  /**
6525
- * 根据服务器类型获取对应的服务器实例
6526
- * @param serverType - 服务器类型
6527
- * @returns 对应的服务器实例
5758
+ * 计算单曲在某个雷达分类下的贡献值 F
6528
5759
  */
6529
- getServer(serverType) {
6530
- return this.factory.getServer(serverType);
5760
+ calcSongRadarContribution(score, category) {
5761
+ const { difficulty_data } = score;
5762
+ if (!difficulty_data?.radar || !difficulty_data.max_exscore) return 0;
5763
+ const L = Math.floor(difficulty_data.difnum);
5764
+ const R = difficulty_data.radar[category];
5765
+ const S = score.music.score;
5766
+ const E = score.music.exscore;
5767
+ const Emax = difficulty_data.max_exscore;
5768
+ const P = Math.min(2 * (L + 31), 100);
5769
+ const B = Math.floor(3 * S * R / (4 * 1e7));
5770
+ const H = Math.floor((20 - L) / 2);
5771
+ const G = Math.max(1e3 + 2 * 2 ** H * (E - Emax), 0);
5772
+ const X = Math.floor(R * G / 4e3);
5773
+ return P * (B + X);
6531
5774
  }
6532
5775
  /**
6533
- * 根据服务器类型和游戏类型获取对应的游戏服务实例
6534
- * @param serverType - 服务器类型
6535
- * @param gameType - 游戏类型
6536
- * @returns 对应的游戏服务实例
5776
+ * 计算玩家六维雷达(显示值,如 200.00)
5777
+ * 算法:每个分类取每首歌最高贡献的难度,取 top50 求和,再除以 400000
6537
5778
  */
6538
- getGameService(serverType, gameType) {
6539
- const server2 = this.factory.getServer(serverType);
6540
- return server2.gameServices[gameType];
5779
+ getPlayerRadarByAllScore(scores) {
5780
+ const categories = ["notes", "peak", "tsumami", "tricky", "hand_trip", "one_hand"];
5781
+ const radar2 = { notes: 0, peak: 0, tsumami: 0, tricky: 0, hand_trip: 0, one_hand: 0 };
5782
+ for (const category of categories) {
5783
+ const bestPerSong = /* @__PURE__ */ new Map();
5784
+ for (const score of scores) {
5785
+ const F = this.calcSongRadarContribution(score, category);
5786
+ if (F <= 0) continue;
5787
+ const musicId = score.music.music_id;
5788
+ const existing = bestPerSong.get(musicId) ?? 0;
5789
+ if (F > existing) {
5790
+ bestPerSong.set(musicId, F);
5791
+ }
5792
+ }
5793
+ const top50 = [...bestPerSong.values()].sort((a, b) => b - a).slice(0, 50);
5794
+ const T = top50.reduce((sum, v) => sum + v, 0);
5795
+ radar2[category] = Math.floor(T / 40) / 100;
5796
+ }
5797
+ return radar2;
6541
5798
  }
6542
5799
  };
6543
- function apply11(ctx, config) {
5800
+
5801
+ // src/games/sdvx/commands/radar.ts
5802
+ function radar(ctx, config, logger5) {
5803
+ ctx.command("sdvx.radar").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).action(async ({ session }) => {
5804
+ const atGuild = session.guildId != null;
5805
+ const cardService = new CardService(ctx);
5806
+ const serverService = new ServerService(ctx);
5807
+ const userCards = await cardService.getCardsByUid(session.user.id);
5808
+ if (userCards.length === 0) return session.text(".card-not-found");
5809
+ const serverRes = await serverService.getSelectableServers(
5810
+ session.user.id,
5811
+ atGuild ? session.channel.id : null
5812
+ );
5813
+ if (serverRes.length === 0) return session.text(".server-not-found");
5814
+ const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
5815
+ if (!defaultCard) return session.text(".card-not-found");
5816
+ const card2 = await cardService.getCardByCode(defaultCard.code);
5817
+ let server2;
5818
+ if (card2.defaultServerId != void 0 && card2.defaultServerId != 0)
5819
+ server2 = await serverService.getServerById(card2.defaultServerId);
5820
+ else {
5821
+ const channelDefaultServer = atGuild ? await serverService.getDefaultServerByCid(
5822
+ session.platform,
5823
+ session.channel.id
5824
+ ) : null;
5825
+ if (channelDefaultServer) server2 = channelDefaultServer;
5826
+ else {
5827
+ const userDefaultServer = await serverService.getDefaultServerByUid(
5828
+ session.user.id
5829
+ );
5830
+ if (userDefaultServer) server2 = userDefaultServer;
5831
+ else return session.text(".server-not-found");
5832
+ }
5833
+ }
5834
+ const serverManager = ServerManager.getInstance();
5835
+ const sdvxService = serverManager.getGameService(server2.type, "sdvx");
5836
+ const scoreService = ScoreService.getInstance();
5837
+ try {
5838
+ const scoreList = await sdvxService.getAllScore(
5839
+ ctx,
5840
+ server2.baseUrl,
5841
+ card2.code,
5842
+ config
5843
+ );
5844
+ if (scoreList.length === 0) {
5845
+ return session.text(".no-scores-found");
5846
+ }
5847
+ const radarResult = scoreService.getPlayerRadarByAllScore(scoreList);
5848
+ const playerName = await sdvxService.getUserName(ctx, server2.baseUrl, card2.code);
5849
+ return session.text(".result", {
5850
+ name: playerName,
5851
+ notes: radarResult.notes.toFixed(2),
5852
+ peak: radarResult.peak.toFixed(2),
5853
+ tsumami: radarResult.tsumami.toFixed(2),
5854
+ tricky: radarResult.tricky.toFixed(2),
5855
+ hand_trip: radarResult.hand_trip.toFixed(2),
5856
+ one_hand: radarResult.one_hand.toFixed(2)
5857
+ });
5858
+ } catch (error) {
5859
+ logger5.warn(error);
5860
+ return session.text(".error");
5861
+ }
5862
+ });
6544
5863
  }
6545
- __name(apply11, "apply");
5864
+ __name(radar, "radar");
6546
5865
 
6547
5866
  // src/games/sdvx/commands/recent.ts
6548
- function recent(ctx, config, logger6) {
5867
+ var import_koishi16 = require("koishi");
5868
+ function recent(ctx, config, logger5) {
6549
5869
  ctx.command("sdvx.recent [count:number]").alias("sdvx.r").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).option("lossless", "-l").option("card", "-c").action(async ({ session, options }, count) => {
6550
5870
  const atGuild = session.guildId != null;
6551
5871
  if (!count) count = 1;
@@ -6573,7 +5893,7 @@ function recent(ctx, config, logger6) {
6573
5893
  cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
6574
5894
  }
6575
5895
  }
6576
- const msg = import_koishi15.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
5896
+ const msg = import_koishi16.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
6577
5897
  await session.send(msg);
6578
5898
  const select = await session.prompt();
6579
5899
  if (!select) return session.text("commands.timeout");
@@ -6630,11 +5950,11 @@ function recent(ctx, config, logger6) {
6630
5950
  }
6631
5951
  );
6632
5952
  if (options.lossless) {
6633
- return import_koishi15.h.image(imageBuffer, "image/png");
5953
+ return import_koishi16.h.image(imageBuffer, "image/png");
6634
5954
  }
6635
- return import_koishi15.h.image(imageBuffer, "image/jpg");
5955
+ return import_koishi16.h.image(imageBuffer, "image/jpg");
6636
5956
  } catch (error) {
6637
- logger6.warn(error);
5957
+ logger5.warn(error);
6638
5958
  return session.text(".error");
6639
5959
  }
6640
5960
  });
@@ -6642,7 +5962,7 @@ function recent(ctx, config, logger6) {
6642
5962
  __name(recent, "recent");
6643
5963
 
6644
5964
  // src/games/sdvx/commands/sync.ts
6645
- function sync(ctx, config, logger6) {
5965
+ function sync(ctx, config, logger5) {
6646
5966
  ctx.command("sdvx.sync").userFields(["id"]).channelFields(["id"]).action(async ({ session }) => {
6647
5967
  const serverService = new ServerService(ctx);
6648
5968
  const cardService = new CardService(ctx);
@@ -6717,7 +6037,7 @@ function sync(ctx, config, logger6) {
6717
6037
  const maoSdvxService = serverManager.getGameService("mao", "sdvx");
6718
6038
  const maoVerifyUrl = "https://maomani.cn";
6719
6039
  if (!maoSdvxService || typeof maoSdvxService.verifyPin !== "function") {
6720
- logger6.warn("Mao SDVX service does not support PIN verification");
6040
+ logger5.warn("Mao SDVX service does not support PIN verification");
6721
6041
  return session.text(".pin-verify-error");
6722
6042
  }
6723
6043
  const existingPin = await ctx.database.get("sdvx_pin_verified", {
@@ -6738,7 +6058,7 @@ function sync(ctx, config, logger6) {
6738
6058
  pinVerified = true;
6739
6059
  }
6740
6060
  } catch (error) {
6741
- logger6.warn(error);
6061
+ logger5.warn(error);
6742
6062
  }
6743
6063
  }
6744
6064
  if (!pinVerified) {
@@ -6774,7 +6094,7 @@ function sync(ctx, config, logger6) {
6774
6094
  })
6775
6095
  );
6776
6096
  } catch (error) {
6777
- logger6.warn(error);
6097
+ logger5.warn(error);
6778
6098
  await session.send(session.text(".pin-verify-error"));
6779
6099
  }
6780
6100
  }
@@ -6796,7 +6116,7 @@ function sync(ctx, config, logger6) {
6796
6116
  config
6797
6117
  );
6798
6118
  } catch (error) {
6799
- logger6.warn(error);
6119
+ logger5.warn(error);
6800
6120
  return session.text(".fetch-error");
6801
6121
  }
6802
6122
  if (!scoreList || scoreList.length === 0) {
@@ -6819,11 +6139,11 @@ function sync(ctx, config, logger6) {
6819
6139
  syncPayload
6820
6140
  );
6821
6141
  if (!ok) {
6822
- logger6.warn("Mao SDVX uploadScore returned falsy result");
6142
+ logger5.warn("Mao SDVX uploadScore returned falsy result");
6823
6143
  return session.text(".sync-failed");
6824
6144
  }
6825
6145
  } catch (error) {
6826
- logger6.warn(error);
6146
+ logger5.warn(error);
6827
6147
  return session.text(".sync-error");
6828
6148
  }
6829
6149
  return session.text(".selected-summary", {
@@ -6889,86 +6209,7 @@ __name(buildSyncPayload, "buildSyncPayload");
6889
6209
 
6890
6210
  // src/games/sdvx/commands/vf.ts
6891
6211
  var fs4 = __toESM(require("fs"), 1);
6892
- var import_koishi16 = require("koishi");
6893
-
6894
- // src/games/sdvx/services/score-service.ts
6895
- var ScoreService = class _ScoreService {
6896
- static {
6897
- __name(this, "ScoreService");
6898
- }
6899
- static instance;
6900
- constructor() {
6901
- }
6902
- /**
6903
- * 获取 ScoreService 实例
6904
- * @returns ScoreService 实例
6905
- */
6906
- static getInstance() {
6907
- if (!_ScoreService.instance) {
6908
- _ScoreService.instance = new _ScoreService();
6909
- }
6910
- return _ScoreService.instance;
6911
- }
6912
- /**
6913
- * 按 Volforce 降序排序分数
6914
- * @param scores - 待排序的 SDVX 分数数组
6915
- * @returns 按 Volforce 降序排序的新数组
6916
- */
6917
- sortByVolforce(scores) {
6918
- return [...scores].sort((a, b) => b.extra.volforce - a.extra.volforce);
6919
- }
6920
- /**
6921
- * 获取前 50 名分数(包括并列)
6922
- * @param scores - 待处理的 SDVX 分数数组
6923
- * @returns 包含最高 Volforce 值的分数数组(如果存在并列,可能超过 50 个)
6924
- */
6925
- getBest50(scores) {
6926
- const sortedScores = this.sortByVolforce(scores);
6927
- if (sortedScores.length <= 50) {
6928
- return sortedScores;
6929
- }
6930
- const fiftiethVF = sortedScores[49].extra.volforce;
6931
- return sortedScores.filter((score) => score.extra.volforce >= fiftiethVF);
6932
- }
6933
- /**
6934
- * 过滤 SDVXScore 数组
6935
- * @param scores - 待过滤的分数数组
6936
- * @param options - 过滤选项
6937
- * @returns 过滤后的分数数组
6938
- */
6939
- filterScores(scores, options) {
6940
- let filtered = scores;
6941
- if (options.grade) {
6942
- const grades = Array.isArray(options.grade) ? options.grade : [options.grade];
6943
- filtered = filtered.filter((score) => grades.includes(score.music.score_grade));
6944
- }
6945
- if (options.clearType) {
6946
- const clearTypes = Array.isArray(options.clearType) ? options.clearType : [options.clearType];
6947
- filtered = filtered.filter((score) => clearTypes.includes(score.music.clear_type));
6948
- }
6949
- if (options.musicDiff !== void 0) {
6950
- const diffs = Array.isArray(options.musicDiff) ? options.musicDiff : [options.musicDiff];
6951
- filtered = filtered.filter((score) => diffs.includes(score.music.music_diff));
6952
- }
6953
- if (options.radarFeature) {
6954
- const featureKeys = Array.isArray(options.radarFeature) ? options.radarFeature : [options.radarFeature];
6955
- filtered = filtered.filter((score) => {
6956
- if (!score.difficulty_data) return false;
6957
- const radar = score.difficulty_data.radar;
6958
- let maxKey = "notes";
6959
- let maxValue = radar["notes"];
6960
- for (const key of Object.keys(radar)) {
6961
- if (radar[key] > maxValue) {
6962
- maxKey = key;
6963
- maxValue = radar[key];
6964
- }
6965
- }
6966
- return featureKeys.includes(maxKey);
6967
- });
6968
- }
6969
- return filtered;
6970
- }
6971
- };
6212
+ var import_koishi17 = require("koishi");
6972
6213
 
6973
6214
  // src/games/sdvx/utils/filter.ts
6974
6215
  function parseFilterQuery(query) {
@@ -7021,7 +6262,7 @@ function parseFilterQuery(query) {
7021
6262
  __name(parseFilterQuery, "parseFilterQuery");
7022
6263
 
7023
6264
  // src/games/sdvx/commands/vf.ts
7024
- function vf(ctx, config, logger6) {
6265
+ function vf(ctx, config, logger5) {
7025
6266
  ctx.command("vf").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).option("lossless", "-l").option("card", "-c").option("filter", "-f <query:text>").action(async ({ session, options }) => {
7026
6267
  const atGuild = session.guildId != null;
7027
6268
  const cardService = new CardService(ctx);
@@ -7048,7 +6289,7 @@ function vf(ctx, config, logger6) {
7048
6289
  cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
7049
6290
  }
7050
6291
  }
7051
- const msg = import_koishi16.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
6292
+ const msg = import_koishi17.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
7052
6293
  await session.send(msg);
7053
6294
  const select = await session.prompt();
7054
6295
  if (!select) return session.text("commands.timeout");
@@ -7111,9 +6352,9 @@ function vf(ctx, config, logger6) {
7111
6352
  }
7112
6353
  );
7113
6354
  if (options.lossless) {
7114
- session.send(import_koishi16.h.file(imageBuffer, "image/png"));
6355
+ session.send(import_koishi17.h.file(imageBuffer, "image/png"));
7115
6356
  } else {
7116
- session.send(import_koishi16.h.image(imageBuffer, "image/jpg"));
6357
+ session.send(import_koishi17.h.image(imageBuffer, "image/jpg"));
7117
6358
  }
7118
6359
  const sortedScores = [...best50ScoreList].sort((a, b) => b.extra.volforce - a.extra.volforce).slice(0, 50);
7119
6360
  const total = sortedScores.reduce((sum, score) => sum + score.extra.volforce, 0);
@@ -7142,18 +6383,18 @@ function vf(ctx, config, logger6) {
7142
6383
  );
7143
6384
  if (fs4.existsSync(audioPath)) {
7144
6385
  const audioBuffer = fs4.readFileSync(audioPath);
7145
- session.send(import_koishi16.h.audio(audioBuffer, "audio/mpeg"));
6386
+ session.send(import_koishi17.h.audio(audioBuffer, "audio/mpeg"));
7146
6387
  }
7147
6388
  if (fs4.existsSync(imagePath)) {
7148
6389
  const imageBuffer2 = fs4.readFileSync(imagePath);
7149
- session.send(import_koishi16.h.image(imageBuffer2, "image/png"));
6390
+ session.send(import_koishi17.h.image(imageBuffer2, "image/png"));
7150
6391
  }
7151
6392
  } catch (error) {
7152
- logger6.warn(`Failed to send celebration files: ${error.message}`);
6393
+ logger5.warn(`Failed to send celebration files: ${error.message}`);
7153
6394
  }
7154
6395
  }
7155
6396
  } catch (error) {
7156
- logger6.warn(error);
6397
+ logger5.warn(error);
7157
6398
  return session.text(".error");
7158
6399
  }
7159
6400
  return;
@@ -7162,25 +6403,26 @@ function vf(ctx, config, logger6) {
7162
6403
  __name(vf, "vf");
7163
6404
 
7164
6405
  // src/games/sdvx/command.ts
7165
- var name12 = "command";
7166
- function apply12(ctx, config) {
6406
+ var name10 = "command";
6407
+ function apply10(ctx, config) {
7167
6408
  ctx.command("sdvx").alias("s");
7168
- vf(ctx, config, logger5);
7169
- recent(ctx, config, logger5);
7170
- chart(ctx, config, logger5);
7171
- calculate(ctx, config, logger5);
7172
- sync(ctx, config, logger5);
6409
+ vf(ctx, config, logger4);
6410
+ recent(ctx, config, logger4);
6411
+ chart(ctx, config, logger4);
6412
+ calculate(ctx, config, logger4);
6413
+ sync(ctx, config, logger4);
6414
+ radar(ctx, config, logger4);
7173
6415
  }
7174
- __name(apply12, "apply");
6416
+ __name(apply10, "apply");
7175
6417
 
7176
6418
  // src/games/sdvx/database.ts
7177
- var database_exports3 = {};
7178
- __export(database_exports3, {
7179
- apply: () => apply13,
7180
- name: () => name13
6419
+ var database_exports2 = {};
6420
+ __export(database_exports2, {
6421
+ apply: () => apply11,
6422
+ name: () => name11
7181
6423
  });
7182
- var name13 = "database";
7183
- function apply13(ctx) {
6424
+ var name11 = "database";
6425
+ function apply11(ctx) {
7184
6426
  ctx.model.extend(
7185
6427
  "sdvx_pin_verified",
7186
6428
  {
@@ -7195,135 +6437,141 @@ function apply13(ctx) {
7195
6437
  }
7196
6438
  );
7197
6439
  }
7198
- __name(apply13, "apply");
6440
+ __name(apply11, "apply");
7199
6441
 
7200
6442
  // src/games/sdvx/event.ts
7201
6443
  var event_exports2 = {};
7202
6444
  __export(event_exports2, {
7203
- apply: () => apply14,
7204
- name: () => name14
6445
+ apply: () => apply12,
6446
+ name: () => name12
7205
6447
  });
7206
- var name14 = "event";
7207
- function apply14(ctx) {
6448
+ var name12 = "event";
6449
+ function apply12(ctx) {
7208
6450
  }
7209
- __name(apply14, "apply");
6451
+ __name(apply12, "apply");
7210
6452
 
7211
6453
  // src/games/sdvx/locales/en-US.yml
7212
- var en_US_default5 = { _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: { "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>" } }, 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>" } } } } };
6454
+ 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>" } } } } };
7213
6455
 
7214
6456
  // src/games/sdvx/locales/zh-CN.yml
7215
- var zh_CN_default5 = { _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: { "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>" } }, 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>" } } } } };
6457
+ 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>" } } } } };
7216
6458
 
7217
6459
  // src/games/sdvx/index.ts
7218
- var name15 = "Noah-SDVX";
7219
- var inject3 = ["database"];
7220
- var logger5 = new import_koishi17.Logger("Noah-SDVX");
7221
- async function apply15(ctx, config) {
6460
+ var name13 = "Noah-SDVX";
6461
+ var inject2 = ["database"];
6462
+ var logger4 = new import_koishi18.Logger("Noah-SDVX");
6463
+ async function apply13(ctx, config) {
7222
6464
  ;
7223
6465
  [
7224
- ["en-US", en_US_default5],
7225
- ["zh-CN", zh_CN_default5]
6466
+ ["en-US", en_US_default4],
6467
+ ["zh-CN", zh_CN_default4]
7226
6468
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
7227
- ctx.plugin(database_exports3, config.sdvx);
6469
+ ctx.plugin(database_exports2, config.sdvx);
7228
6470
  ctx.plugin(command_exports2, config.sdvx);
7229
6471
  ctx.plugin(event_exports2, config.sdvx);
7230
6472
  }
7231
- __name(apply15, "apply");
6473
+ __name(apply13, "apply");
7232
6474
 
7233
6475
  // src/config.ts
7234
- var import_koishi24 = require("koishi");
6476
+ var import_koishi25 = require("koishi");
7235
6477
 
7236
6478
  // src/asset/config.ts
7237
- var import_koishi18 = require("koishi");
7238
- var assetConfig = import_koishi18.Schema.object({
7239
- data_path: import_koishi18.Schema.string().default("noah_assets"),
7240
- auto_update: import_koishi18.Schema.boolean().default(true),
7241
- update_url: import_koishi18.Schema.string().default("https://github.com/logthm/noah/releases/latest/download/"),
7242
- update_interval: import_koishi18.Schema.number().default(24 * 60 * 60 * 1e3),
6479
+ var import_koishi19 = require("koishi");
6480
+ var assetConfig = import_koishi19.Schema.object({
6481
+ data_path: import_koishi19.Schema.string().default("noah_assets"),
6482
+ auto_update: import_koishi19.Schema.boolean().default(true),
6483
+ update_url: import_koishi19.Schema.string().default("https://github.com/logthm/noah/releases/latest/download/"),
6484
+ update_interval: import_koishi19.Schema.number().default(24 * 60 * 60 * 1e3),
7243
6485
  // 24 hours in milliseconds
7244
- github_token: import_koishi18.Schema.string().description("GitHub token for accessing private repositories").role("secret")
6486
+ github_token: import_koishi19.Schema.string().description("GitHub token for accessing private repositories").role("secret")
7245
6487
  }).i18n({
7246
6488
  "en-US": en_US_default._config,
7247
6489
  "zh-CN": zh_CN_default._config
7248
6490
  });
7249
6491
 
7250
6492
  // src/core/config.ts
7251
- var import_koishi19 = require("koishi");
7252
- var coreConfig = import_koishi19.Schema.object({
7253
- adminUsers: import_koishi19.Schema.array(String).role("table"),
7254
- guildNameCards: import_koishi19.Schema.array(String).role("table").default(["Noah | /help 获取食用指南"]),
7255
- maoServerUrl: import_koishi19.Schema.string().default("http://maomani.cn:573"),
7256
- official_support_url: import_koishi19.Schema.string().default("https://noah.logthm.com")
6493
+ var import_koishi20 = require("koishi");
6494
+ var coreConfig = import_koishi20.Schema.object({
6495
+ adminUsers: import_koishi20.Schema.array(String).role("table"),
6496
+ guildNameCards: import_koishi20.Schema.array(String).role("table").default(["Noah | /help 获取食用指南"]),
6497
+ maoServerUrl: import_koishi20.Schema.string().default("http://maomani.cn:573"),
6498
+ official_support_url: import_koishi20.Schema.string().default("https://noah.logthm.com")
7257
6499
  }).i18n({
7258
- "en-US": en_US_default3._config,
7259
- "zh-CN": zh_CN_default3._config
6500
+ "en-US": en_US_default2._config,
6501
+ "zh-CN": zh_CN_default2._config
7260
6502
  });
7261
6503
 
7262
6504
  // src/fun/poke/config.ts
7263
- var import_koishi20 = require("koishi");
7264
- var pokeConfig = import_koishi20.Schema.object({
7265
- interval: import_koishi20.Schema.number().default(1e3).step(100),
7266
- warning: import_koishi20.Schema.boolean().default(false),
7267
- prompt: import_koishi20.Schema.array(
7268
- import_koishi20.Schema.object({
7269
- content: import_koishi20.Schema.string().required(),
7270
- weight: import_koishi20.Schema.number().min(0).max(100).default(50)
6505
+ var import_koishi21 = require("koishi");
6506
+ var pokeConfig = import_koishi21.Schema.object({
6507
+ interval: import_koishi21.Schema.number().default(1e3).step(100),
6508
+ warning: import_koishi21.Schema.boolean().default(false),
6509
+ prompt: import_koishi21.Schema.array(
6510
+ import_koishi21.Schema.object({
6511
+ content: import_koishi21.Schema.string().required(),
6512
+ weight: import_koishi21.Schema.number().min(0).max(100).default(50)
7271
6513
  })
7272
6514
  ).role("table"),
7273
- messages: import_koishi20.Schema.array(
7274
- import_koishi20.Schema.object({
7275
- content: import_koishi20.Schema.string().required(),
7276
- weight: import_koishi20.Schema.number().min(0).max(100).default(50)
6515
+ messages: import_koishi21.Schema.array(
6516
+ import_koishi21.Schema.object({
6517
+ content: import_koishi21.Schema.string().required(),
6518
+ weight: import_koishi21.Schema.number().min(0).max(100).default(50)
7277
6519
  })
7278
6520
  ).role("table")
7279
6521
  }).i18n({
7280
- "en-US": en_US_default4._config,
7281
- "zh-CN": zh_CN_default4._config
6522
+ "en-US": en_US_default3._config,
6523
+ "zh-CN": zh_CN_default3._config
7282
6524
  });
7283
6525
 
7284
6526
  // src/games/general/config.ts
7285
- var import_koishi21 = require("koishi");
6527
+ var import_koishi22 = require("koishi");
7286
6528
 
7287
6529
  // src/games/general/locales/en-US.yml
7288
- var en_US_default6 = { _config: { $desc: "General Module Settings", barcode_api_url: "<p>The URL of the barcode API</p>" } };
6530
+ var en_US_default5 = { _config: { $desc: "General Module Settings", barcode_api_url: "<p>The URL of the barcode API</p>" } };
7289
6531
 
7290
6532
  // src/games/general/locales/zh-CN.yml
7291
- var zh_CN_default6 = { _config: { $desc: "通用工具模块设置", barcode_api_url: "<p>条形码 API 地址</p>" } };
6533
+ var zh_CN_default5 = { _config: { $desc: "通用工具模块设置", barcode_api_url: "<p>条形码 API 地址</p>" } };
7292
6534
 
7293
6535
  // src/games/general/config.ts
7294
- var generalConfig = import_koishi21.Schema.object({
7295
- barcode_api_url: import_koishi21.Schema.string().required()
6536
+ var generalConfig = import_koishi22.Schema.object({
6537
+ barcode_api_url: import_koishi22.Schema.string().required()
7296
6538
  }).i18n({
7297
- "en-US": en_US_default6._config,
7298
- "zh-CN": zh_CN_default6._config
6539
+ "en-US": en_US_default5._config,
6540
+ "zh-CN": zh_CN_default5._config
7299
6541
  });
7300
6542
 
7301
6543
  // src/games/sdvx/config.ts
7302
- var import_koishi22 = require("koishi");
7303
- var sdvxConfig = import_koishi22.Schema.object({
7304
- default_model: import_koishi22.Schema.string().default("2025100700"),
7305
- sdvx_data_url: import_koishi22.Schema.string().required(),
7306
- sdvx_search_url: import_koishi22.Schema.string().required(),
7307
- official_support_url: import_koishi22.Schema.string().required()
6544
+ var import_koishi23 = require("koishi");
6545
+ var sdvxConfig = import_koishi23.Schema.object({
6546
+ default_model: import_koishi23.Schema.string().default("2025100700"),
6547
+ sdvx_data_url: import_koishi23.Schema.string().required(),
6548
+ sdvx_search_url: import_koishi23.Schema.string().required(),
6549
+ official_support_url: import_koishi23.Schema.string().required()
7308
6550
  }).i18n({
7309
- "en-US": en_US_default5._config,
7310
- "zh-CN": zh_CN_default5._config
6551
+ "en-US": en_US_default4._config,
6552
+ "zh-CN": zh_CN_default4._config
7311
6553
  });
7312
6554
 
7313
6555
  // src/slash/config.ts
7314
- var import_koishi23 = require("koishi");
7315
- var slashConfig = import_koishi23.Schema.object({
7316
- discord_token: import_koishi23.Schema.string(),
7317
- discord_application_id: import_koishi23.Schema.string(),
7318
- test_guilds: import_koishi23.Schema.array(String).default([]),
7319
- auto_sync_on_start: import_koishi23.Schema.boolean().default(true)
6556
+ var import_koishi24 = require("koishi");
6557
+
6558
+ // src/slash/locales/en-US.yml
6559
+ var en_US_default6 = { _config: { $desc: "Slash Module Settings", test_guilds: "**Guilds To Sync**", auto_sync_on_start: "**Auto Sync on Connect**" } };
6560
+
6561
+ // src/slash/locales/zh-CN.yml
6562
+ var zh_CN_default6 = { _config: { $desc: "Slash 模块设置", test_guilds: "**默认同步 Guild 列表**", auto_sync_on_start: "**连接后自动同步**" } };
6563
+
6564
+ // src/slash/config.ts
6565
+ var slashConfig = import_koishi24.Schema.object({
6566
+ test_guilds: import_koishi24.Schema.array(String).default([]),
6567
+ auto_sync_on_start: import_koishi24.Schema.boolean().default(true)
7320
6568
  }).i18n({
7321
- "en-US": en_US_default2._config,
7322
- "zh-CN": zh_CN_default2._config
6569
+ "en-US": en_US_default6._config,
6570
+ "zh-CN": zh_CN_default6._config
7323
6571
  });
7324
6572
 
7325
6573
  // src/config.ts
7326
- var Config = import_koishi24.Schema.object({
6574
+ var Config = import_koishi25.Schema.object({
7327
6575
  core: coreConfig,
7328
6576
  sdvx: sdvxConfig,
7329
6577
  poke: pokeConfig,
@@ -7333,9 +6581,9 @@ var Config = import_koishi24.Schema.object({
7333
6581
  });
7334
6582
 
7335
6583
  // src/index.ts
7336
- var name16 = "noah";
7337
- var inject4 = ["database", "skia"];
7338
- async function apply16(ctx, config) {
6584
+ var name14 = "noah";
6585
+ var inject3 = ["database", "skia"];
6586
+ async function apply14(ctx, config) {
7339
6587
  initConstants(ctx);
7340
6588
  ctx.plugin(core_exports, config);
7341
6589
  ctx.plugin(general_exports, config);
@@ -7344,9 +6592,8 @@ async function apply16(ctx, config) {
7344
6592
  ctx.plugin(drawer_exports, config);
7345
6593
  ctx.plugin(poke_exports, config);
7346
6594
  ctx.plugin(asset_exports, config);
7347
- ctx.plugin(slash_exports, config);
7348
6595
  }
7349
- __name(apply16, "apply");
6596
+ __name(apply14, "apply");
7350
6597
  // Annotate the CommonJS export names for ESM import in node:
7351
6598
  0 && (module.exports = {
7352
6599
  Config,