koishi-plugin-noah 2.0.6 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/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,
34
+ apply: () => apply14,
35
35
  inject: () => inject4,
36
- name: () => name16
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
  }
@@ -997,7 +1001,7 @@ async function ensureOfficialServerForUser(ctx, serverService, userService, uid,
997
1001
  const userServers = await serverService.getServersByUid(uid);
998
1002
  const existingOfficial = userServers.find((server3) => server3.type === "official");
999
1003
  if (existingOfficial) return existingOfficial.id;
1000
- const baseUrl = officialSupportUrl || ctx.config.official_support_url;
1004
+ const baseUrl = officialSupportUrl || ctx.globalConfig.official_support_url;
1001
1005
  const server2 = await serverService.createServerForUser(
1002
1006
  {
1003
1007
  type: "official",
@@ -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);
@@ -1055,7 +1059,7 @@ function bind(ctx, config) {
1055
1059
  serverService,
1056
1060
  userService,
1057
1061
  session.user.id,
1058
- config.official_support_url
1062
+ ctx.globalConfig.official_support_url
1059
1063
  );
1060
1064
  }
1061
1065
  const res = await cardService.createCard(
@@ -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(
@@ -1277,7 +1301,7 @@ async function showCardMenu(ctx, session, card2, cardService, serverService, use
1277
1301
  serverService,
1278
1302
  userService,
1279
1303
  uid,
1280
- ctx.config.official_support_url
1304
+ ctx.globalConfig.official_support_url
1281
1305
  );
1282
1306
  }
1283
1307
  await cardService.updateCard(card2.id, { code: cardCode, defaultServerId });
@@ -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,27 +1905,41 @@ 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") {
2638
- url = ctx.config.maoServerUrl;
1939
+ url = ctx.globalConfig.maoServerUrl;
2639
1940
  serverName = "MaoMaNi";
2640
1941
  } else if (serverType === "official") {
2641
- url = ctx.config.official_support_url;
1942
+ url = ctx.globalConfig.official_support_url;
2642
1943
  serverName = "Official";
2643
1944
  } else {
2644
1945
  let attempts = 0;
@@ -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", "globalConfig"];
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;
@@ -3245,16 +2585,7 @@ function getMaxScoreForVfInt(multiplier, vfInt) {
3245
2585
  const numerator = BigInt(vfInt + 1) * VOLFORCE_BASE_DENOMINATOR - 1n;
3246
2586
  return Number(numerator / multiplier);
3247
2587
  }
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";
3256
- }
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,
4135
+ apply: () => apply13,
4932
4136
  inject: () => inject3,
4933
- logger: () => logger5,
4934
- name: () => name15
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,27 @@ 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
+ inject: () => inject2,
4945
+ name: () => name9
5778
4946
  });
5779
4947
 
5780
4948
  // src/servers/Asphyxia/index.ts
5781
- var import_koishi12 = require("koishi");
4949
+ var import_koishi13 = require("koishi");
5782
4950
 
5783
4951
  // src/servers/Asphyxia/services/iidx-service.ts
5784
4952
  var IIDXService = class _IIDXService {
@@ -5787,12 +4955,12 @@ var IIDXService = class _IIDXService {
5787
4955
  }
5788
4956
  static instance;
5789
4957
  logger;
5790
- constructor(logger6) {
5791
- this.logger = logger6;
4958
+ constructor(logger5) {
4959
+ this.logger = logger5;
5792
4960
  }
5793
- static getInstance(logger6) {
4961
+ static getInstance(logger5) {
5794
4962
  if (!_IIDXService.instance) {
5795
- _IIDXService.instance = new _IIDXService(logger6);
4963
+ _IIDXService.instance = new _IIDXService(logger5);
5796
4964
  }
5797
4965
  return _IIDXService.instance;
5798
4966
  }
@@ -5801,19 +4969,6 @@ var IIDXService = class _IIDXService {
5801
4969
  };
5802
4970
 
5803
4971
  // 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
4972
  function getSDVXClearType(clearType) {
5818
4973
  return clearType === 6 ? "MC" : clearType === 5 ? "PUC" : clearType === 4 ? "UC" : clearType === 3 ? "HC" : clearType === 2 ? "NC" : clearType === 1 ? "PLAYED" : "NO PLAY";
5819
4974
  }
@@ -5840,12 +4995,12 @@ var SDVXService = class _SDVXService {
5840
4995
  }
5841
4996
  static instance;
5842
4997
  logger;
5843
- constructor(logger6) {
5844
- this.logger = logger6;
4998
+ constructor(logger5) {
4999
+ this.logger = logger5;
5845
5000
  }
5846
- static getInstance(logger6) {
5001
+ static getInstance(logger5) {
5847
5002
  if (!_SDVXService.instance) {
5848
- _SDVXService.instance = new _SDVXService(logger6);
5003
+ _SDVXService.instance = new _SDVXService(logger5);
5849
5004
  }
5850
5005
  return _SDVXService.instance;
5851
5006
  }
@@ -5882,9 +5037,9 @@ var SDVXService = class _SDVXService {
5882
5037
  const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
5883
5038
  const decodedResponse = typeof response === "string" ? response : new TextDecoder("utf-8").decode(response);
5884
5039
  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;
5040
+ const name15 = jsonResponse.response.game?.[0]?.name?.[0]._;
5041
+ if (!name15) throw new Error("User name not found");
5042
+ return name15;
5888
5043
  }
5889
5044
  /**
5890
5045
  * 获取所有分数
@@ -5943,7 +5098,7 @@ var SDVXService = class _SDVXService {
5943
5098
  score,
5944
5099
  exscore,
5945
5100
  clear_type: getSDVXClearType(clear_type),
5946
- score_grade: getSDVXGrade(score),
5101
+ score_grade: getGradeByScore(score),
5947
5102
  btn_rate: String(btn_rate),
5948
5103
  long_rate: String(long_rate),
5949
5104
  vol_rate: String(vol_rate)
@@ -5984,7 +5139,7 @@ var Asphyxia = class {
5984
5139
  name = "asphyxia";
5985
5140
  supportedGames = ["sdvx", "iidx"];
5986
5141
  gameServices = {};
5987
- logger = new import_koishi12.Logger("Noah-Asphyxia");
5142
+ logger = new import_koishi13.Logger("Noah-Asphyxia");
5988
5143
  /**
5989
5144
  * 初始化各个游戏服务实例
5990
5145
  */
@@ -5995,7 +5150,7 @@ var Asphyxia = class {
5995
5150
  };
5996
5151
 
5997
5152
  // src/servers/Mao/index.ts
5998
- var import_koishi13 = require("koishi");
5153
+ var import_koishi14 = require("koishi");
5999
5154
 
6000
5155
  // src/servers/Mao/services/sdvx-service.ts
6001
5156
  var SDVXService2 = class _SDVXService {
@@ -6004,12 +5159,12 @@ var SDVXService2 = class _SDVXService {
6004
5159
  }
6005
5160
  static instance;
6006
5161
  logger;
6007
- constructor(logger6) {
6008
- this.logger = logger6;
5162
+ constructor(logger5) {
5163
+ this.logger = logger5;
6009
5164
  }
6010
- static getInstance(logger6) {
5165
+ static getInstance(logger5) {
6011
5166
  if (!_SDVXService.instance) {
6012
- _SDVXService.instance = new _SDVXService(logger6);
5167
+ _SDVXService.instance = new _SDVXService(logger5);
6013
5168
  }
6014
5169
  return _SDVXService.instance;
6015
5170
  }
@@ -6038,7 +5193,7 @@ var SDVXService2 = class _SDVXService {
6038
5193
  */
6039
5194
  async getUserName(ctx, url, cardId) {
6040
5195
  const response = await ctx.http.get(`/my?card=${cardId}`, {
6041
- baseURL: url,
5196
+ baseURL: ctx.globalConfig.maoServerUrl,
6042
5197
  responseType: "text"
6043
5198
  });
6044
5199
  const match = response.match(/猫网玩家\[([^\]]+)\]/);
@@ -6056,16 +5211,19 @@ var SDVXService2 = class _SDVXService {
6056
5211
  * @returns 验证是否通过
6057
5212
  */
6058
5213
  async verifyPin(ctx, url, cardId, pin) {
6059
- const baseURL = "https://maomani.cn";
6060
- const resp = await ctx.http.post(
6061
- "/api/login/account",
5214
+ const apiKey = ctx.globalConfig.maoApiKey;
5215
+ if (!apiKey) {
5216
+ this.logger.warn("maoApiKey not configured");
5217
+ return false;
5218
+ }
5219
+ const resp = await ctx.http.get(
5220
+ `/bot/v2/player/card?card=${cardId}`,
6062
5221
  {
6063
- username: cardId,
6064
- password: pin
6065
- },
6066
- { baseURL }
5222
+ baseURL: ctx.globalConfig.maoServerUrl,
5223
+ headers: { "X-API-Key": apiKey }
5224
+ }
6067
5225
  );
6068
- return resp?.errorCode === 200;
5226
+ return resp?.code === 0 && resp?.data?.password === pin;
6069
5227
  }
6070
5228
  async uploadScore(ctx, url, cardId, scorePayload) {
6071
5229
  const baseURL = "https://maomani.cn";
@@ -6081,22 +5239,30 @@ var SDVXService2 = class _SDVXService {
6081
5239
  }
6082
5240
  async getAllScore(ctx, url, cardId, config) {
6083
5241
  try {
6084
- const data = await ctx.http.get(`/sdvx/scores?card=${cardId}`, {
6085
- baseURL: url,
6086
- responseType: "json"
5242
+ const apiKey = ctx.globalConfig.maoApiKey;
5243
+ if (!apiKey) {
5244
+ this.logger.warn("maoApiKey not configured");
5245
+ return [];
5246
+ }
5247
+ const resp = await ctx.http.get(`/bot/v2/sdvx/scores?card=${cardId}`, {
5248
+ baseURL: ctx.globalConfig.maoServerUrl,
5249
+ headers: { "X-API-Key": apiKey }
6087
5250
  });
6088
- const filteredData = data.filter(
6089
- (score) => score.music_id !== 244 && score.music_id !== 1759 && score.music_id !== 1761 && score.is_plus === 0
5251
+ if (resp?.code !== 0 || !resp?.data) {
5252
+ return [];
5253
+ }
5254
+ const filteredData = resp.data.filter(
5255
+ (score) => score.mid !== 244 && score.mid !== 1759 && score.mid !== 1761 && score.isPlus === 0
6090
5256
  );
6091
- const musicIds = filteredData.map((score) => score.music_id);
6092
- const diffTypes = filteredData.map((score) => parseInt(score.music_diff));
5257
+ const musicIds = filteredData.map((score) => score.mid);
5258
+ const diffTypes = filteredData.map((score) => score.musicType);
6093
5259
  const musicService = MusicService.getInstance(config);
6094
5260
  const musicData = await musicService.getMusic(ctx, musicIds);
6095
5261
  const musicDataMap = new Map(musicData.map((music) => [music.id, music]));
6096
5262
  const scores = filteredData.map((score, index) => {
6097
- const musicId = score.music_id;
5263
+ const musicId = score.mid;
6098
5264
  const music = musicDataMap.get(musicId);
6099
- const musicDiffType = parseInt(score.music_diff);
5265
+ const musicDiffType = score.musicType;
6100
5266
  const diffStr = this.getDifficultyString(musicDiffType);
6101
5267
  let difficultyData = null;
6102
5268
  if (music && music.difficulty && music.difficulty.length > 0) {
@@ -6111,15 +5277,15 @@ var SDVXService2 = class _SDVXService {
6111
5277
  music_diff_full_name: getDiffFullName(diffStr, infVer),
6112
5278
  score: score.score,
6113
5279
  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()
5280
+ clear_type: getSDVXClearType(score.clearType),
5281
+ score_grade: getGradeByScore(score.score),
5282
+ btn_rate: score.btnRate.toString(),
5283
+ long_rate: score.longRate.toString(),
5284
+ vol_rate: score.volRate.toString()
6119
5285
  },
6120
5286
  extra: {
6121
- play_count: score.play_count,
6122
- update_at: score.update_at,
5287
+ play_count: score.playCount,
5288
+ update_at: score.updateAt,
6123
5289
  volforce: 0
6124
5290
  },
6125
5291
  music_data: music || null,
@@ -6137,12 +5303,16 @@ var SDVXService2 = class _SDVXService {
6137
5303
  }
6138
5304
  }
6139
5305
  async getScore(ctx, url, cardId, musicId, config) {
6140
- const response = await ctx.http.get(`/sdvx/find?card=${cardId}&mid=${musicId}`, {
6141
- baseURL: url,
6142
- responseType: "text"
5306
+ const apiKey = ctx.globalConfig.maoApiKey;
5307
+ if (!apiKey) {
5308
+ this.logger.warn("maoApiKey not configured");
5309
+ throw new Error("maoApiKey not configured");
5310
+ }
5311
+ const resp = await ctx.http.get(`/bot/v2/sdvx/find?card=${cardId}&mid=${musicId}`, {
5312
+ baseURL: ctx.globalConfig.maoServerUrl,
5313
+ headers: { "X-API-Key": apiKey }
6143
5314
  });
6144
- const text = await response;
6145
- if (text === "没有找到相关记录") {
5315
+ if (resp?.code !== 0 || !resp?.data) {
6146
5316
  const musicService2 = MusicService.getInstance(config);
6147
5317
  const musicData2 = await musicService2.getMusic(ctx, [musicId]);
6148
5318
  const music2 = musicData2.length > 0 ? musicData2[0] : null;
@@ -6162,7 +5332,7 @@ var SDVXService2 = class _SDVXService {
6162
5332
  exscore: 0,
6163
5333
  clear_type: getSDVXClearType(0),
6164
5334
  // NO PLAY
6165
- score_grade: getSDVXGrade(0),
5335
+ score_grade: getGradeByScore(0),
6166
5336
  btn_rate: "0",
6167
5337
  long_rate: "0",
6168
5338
  vol_rate: "0"
@@ -6176,14 +5346,8 @@ var SDVXService2 = class _SDVXService {
6176
5346
  difficulty_data: difficultyData2
6177
5347
  };
6178
5348
  }
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);
5349
+ const data = resp.data;
5350
+ const musicTypeNum = data.musicType;
6187
5351
  const diffStr = this.getDifficultyString(musicTypeNum);
6188
5352
  const musicService = MusicService.getInstance(config);
6189
5353
  const musicData = await musicService.getMusic(ctx, [musicId]);
@@ -6199,19 +5363,16 @@ var SDVXService2 = class _SDVXService {
6199
5363
  music_diff: difficultyData ? difficultyData.difnum : 0,
6200
5364
  music_diff_name: getDiffName(diffStr, infVer),
6201
5365
  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)),
5366
+ score: data.score,
5367
+ exscore: data.exscore,
5368
+ clear_type: getSDVXClearType(data.clearType),
5369
+ score_grade: getGradeByScore(data.score),
6206
5370
  btn_rate: "0",
6207
- // Not provided in response
6208
5371
  long_rate: "0",
6209
- // Not provided in response
6210
5372
  vol_rate: "0"
6211
- // Not provided in response
6212
5373
  },
6213
5374
  extra: {
6214
- play_count: parseInt(playCount),
5375
+ play_count: data.playCount,
6215
5376
  update_at: "0",
6216
5377
  volforce: 0
6217
5378
  },
@@ -6222,49 +5383,25 @@ var SDVXService2 = class _SDVXService {
6222
5383
  return scoreObj;
6223
5384
  }
6224
5385
  async getRecentScores(ctx, url, cardId, config, count = 1) {
6225
- const text = await ctx.http.get(`/sdvx/recent?card=${cardId}&count=${count}`, {
6226
- baseURL: url,
6227
- responseType: "text"
6228
- });
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
- }
5386
+ const apiKey = ctx.globalConfig.maoApiKey;
5387
+ if (!apiKey) {
5388
+ this.logger.warn("maoApiKey not configured");
5389
+ return [];
6255
5390
  }
6256
- if (tempScores.length === 0) {
5391
+ const resp = await ctx.http.get(`/bot/v2/sdvx/recent?card=${cardId}&count=${count}`, {
5392
+ baseURL: ctx.globalConfig.maoServerUrl,
5393
+ headers: { "X-API-Key": apiKey }
5394
+ });
5395
+ if (resp?.code !== 0 || !resp?.data || resp.data.length === 0) {
6257
5396
  return [];
6258
5397
  }
5398
+ const musicIds = resp.data.map((item) => item.mid);
6259
5399
  const musicService = MusicService.getInstance(config);
6260
5400
  const musicDataList = await musicService.getMusic(ctx, musicIds);
6261
5401
  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);
5402
+ const scores = resp.data.map((item) => {
5403
+ const music = musicDataMap.get(item.mid);
5404
+ const diffStr = this.getDifficultyString(item.musicType);
6268
5405
  let difficultyData = null;
6269
5406
  if (music && music.difficulty && music.difficulty.length > 0) {
6270
5407
  difficultyData = music.difficulty.find((diff) => diff.difstr.toLowerCase() === diffStr) || null;
@@ -6272,31 +5409,28 @@ var SDVXService2 = class _SDVXService {
6272
5409
  const infVer = music ? music.inf_ver : 2;
6273
5410
  const scoreObj = {
6274
5411
  music: {
6275
- music_id: musicId,
5412
+ music_id: item.mid,
6276
5413
  music_diff: difficultyData ? difficultyData.difnum : 0,
6277
5414
  music_diff_name: getDiffName(diffStr, infVer),
6278
5415
  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),
5416
+ score: item.score,
5417
+ exscore: item.exscore,
5418
+ clear_type: getSDVXClearType(item.clearType),
5419
+ score_grade: getGradeByScore(item.score),
6283
5420
  btn_rate: "0",
6284
- // Not provided in API response
6285
5421
  long_rate: "0",
6286
- // Not provided in API response
6287
5422
  vol_rate: "0"
6288
- // Not provided in API response
6289
5423
  },
6290
5424
  extra: {
6291
5425
  volforce: 0,
6292
5426
  play_count: 0,
6293
- update_at: tempScore.time
5427
+ update_at: item.time
6294
5428
  },
6295
5429
  music_data: music || null,
6296
5430
  difficulty_data: difficultyData || null
6297
5431
  };
6298
5432
  scoreObj.extra.volforce = calculateVolforce(scoreObj);
6299
- scores.push(scoreObj);
5433
+ return scoreObj;
6300
5434
  });
6301
5435
  return scores;
6302
5436
  }
@@ -6310,7 +5444,7 @@ var Mao = class {
6310
5444
  name = "mao";
6311
5445
  supportedGames = ["sdvx"];
6312
5446
  gameServices = {};
6313
- logger = new import_koishi13.Logger("Noah-Mao");
5447
+ logger = new import_koishi14.Logger("Noah-Mao");
6314
5448
  /**
6315
5449
  * 初始化SDVX游戏服务实例
6316
5450
  */
@@ -6320,7 +5454,7 @@ var Mao = class {
6320
5454
  };
6321
5455
 
6322
5456
  // src/servers/Official/index.ts
6323
- var import_koishi14 = require("koishi");
5457
+ var import_koishi15 = require("koishi");
6324
5458
 
6325
5459
  // src/servers/Official/services/sdvx-service.ts
6326
5460
  var SDVXService3 = class _SDVXService {
@@ -6329,12 +5463,12 @@ var SDVXService3 = class _SDVXService {
6329
5463
  }
6330
5464
  static instance;
6331
5465
  logger;
6332
- constructor(logger6) {
6333
- this.logger = logger6;
5466
+ constructor(logger5) {
5467
+ this.logger = logger5;
6334
5468
  }
6335
- static getInstance(logger6) {
5469
+ static getInstance(logger5) {
6336
5470
  if (!_SDVXService.instance) {
6337
- _SDVXService.instance = new _SDVXService(logger6);
5471
+ _SDVXService.instance = new _SDVXService(logger5);
6338
5472
  }
6339
5473
  return _SDVXService.instance;
6340
5474
  }
@@ -6457,7 +5591,7 @@ var Official = class {
6457
5591
  name = "official";
6458
5592
  supportedGames = ["sdvx"];
6459
5593
  gameServices = {};
6460
- logger = new import_koishi14.Logger("Noah-Official");
5594
+ logger = new import_koishi15.Logger("Noah-Official");
6461
5595
  /**
6462
5596
  * 初始化SDVX游戏服务实例
6463
5597
  */
@@ -6501,51 +5635,239 @@ var ServerFactory = class {
6501
5635
  };
6502
5636
 
6503
5637
  // src/servers/index.ts
6504
- var name11 = "Noah-Server";
5638
+ var name9 = "Noah-Server";
5639
+ var inject2 = ["globalConfig"];
6505
5640
  var ServerManager = class _ServerManager {
6506
5641
  static {
6507
5642
  __name(this, "ServerManager");
6508
5643
  }
6509
- static instance;
6510
- factory;
6511
- constructor() {
6512
- this.factory = new ServerFactory();
5644
+ static instance;
5645
+ factory;
5646
+ constructor() {
5647
+ this.factory = new ServerFactory();
5648
+ }
5649
+ /**
5650
+ * 获取服务器管理器实例
5651
+ * @returns 服务器管理器的唯一实例
5652
+ */
5653
+ static getInstance() {
5654
+ if (!_ServerManager.instance) {
5655
+ _ServerManager.instance = new _ServerManager();
5656
+ }
5657
+ return _ServerManager.instance;
5658
+ }
5659
+ /**
5660
+ * 根据服务器类型获取对应的服务器实例
5661
+ * @param serverType - 服务器类型
5662
+ * @returns 对应的服务器实例
5663
+ */
5664
+ getServer(serverType) {
5665
+ return this.factory.getServer(serverType);
5666
+ }
5667
+ /**
5668
+ * 根据服务器类型和游戏类型获取对应的游戏服务实例
5669
+ * @param serverType - 服务器类型
5670
+ * @param gameType - 游戏类型
5671
+ * @returns 对应的游戏服务实例
5672
+ */
5673
+ getGameService(serverType, gameType) {
5674
+ const server2 = this.factory.getServer(serverType);
5675
+ return server2.gameServices[gameType];
5676
+ }
5677
+ };
5678
+ function apply9(ctx, config) {
5679
+ }
5680
+ __name(apply9, "apply");
5681
+
5682
+ // src/games/sdvx/services/score-service.ts
5683
+ var ScoreService = class _ScoreService {
5684
+ static {
5685
+ __name(this, "ScoreService");
5686
+ }
5687
+ static instance;
5688
+ constructor() {
5689
+ }
5690
+ /**
5691
+ * 获取 ScoreService 实例
5692
+ * @returns ScoreService 实例
5693
+ */
5694
+ static getInstance() {
5695
+ if (!_ScoreService.instance) {
5696
+ _ScoreService.instance = new _ScoreService();
5697
+ }
5698
+ return _ScoreService.instance;
5699
+ }
5700
+ /**
5701
+ * 按 Volforce 降序排序分数
5702
+ * @param scores - 待排序的 SDVX 分数数组
5703
+ * @returns 按 Volforce 降序排序的新数组
5704
+ */
5705
+ sortByVolforce(scores) {
5706
+ return [...scores].sort((a, b) => b.extra.volforce - a.extra.volforce);
5707
+ }
5708
+ /**
5709
+ * 获取前 50 名分数(包括并列)
5710
+ * @param scores - 待处理的 SDVX 分数数组
5711
+ * @returns 包含最高 Volforce 值的分数数组(如果存在并列,可能超过 50 个)
5712
+ */
5713
+ getBest50(scores) {
5714
+ const sortedScores = this.sortByVolforce(scores);
5715
+ if (sortedScores.length <= 50) {
5716
+ return sortedScores;
5717
+ }
5718
+ const fiftiethVF = sortedScores[49].extra.volforce;
5719
+ return sortedScores.filter((score) => score.extra.volforce >= fiftiethVF);
6513
5720
  }
6514
5721
  /**
6515
- * 获取服务器管理器实例
6516
- * @returns 服务器管理器的唯一实例
5722
+ * 过滤 SDVXScore 数组
5723
+ * @param scores - 待过滤的分数数组
5724
+ * @param options - 过滤选项
5725
+ * @returns 过滤后的分数数组
6517
5726
  */
6518
- static getInstance() {
6519
- if (!_ServerManager.instance) {
6520
- _ServerManager.instance = new _ServerManager();
5727
+ filterScores(scores, options) {
5728
+ let filtered = scores;
5729
+ if (options.grade) {
5730
+ const grades = Array.isArray(options.grade) ? options.grade : [options.grade];
5731
+ filtered = filtered.filter((score) => grades.includes(score.music.score_grade));
6521
5732
  }
6522
- return _ServerManager.instance;
5733
+ if (options.clearType) {
5734
+ const clearTypes = Array.isArray(options.clearType) ? options.clearType : [options.clearType];
5735
+ filtered = filtered.filter((score) => clearTypes.includes(score.music.clear_type));
5736
+ }
5737
+ if (options.musicDiff !== void 0) {
5738
+ const diffs = Array.isArray(options.musicDiff) ? options.musicDiff : [options.musicDiff];
5739
+ filtered = filtered.filter((score) => diffs.includes(score.music.music_diff));
5740
+ }
5741
+ if (options.radarFeature) {
5742
+ const featureKeys = Array.isArray(options.radarFeature) ? options.radarFeature : [options.radarFeature];
5743
+ filtered = filtered.filter((score) => {
5744
+ if (!score.difficulty_data) return false;
5745
+ const radar2 = score.difficulty_data.radar;
5746
+ let maxKey = "notes";
5747
+ let maxValue = radar2["notes"];
5748
+ for (const key of Object.keys(radar2)) {
5749
+ if (radar2[key] > maxValue) {
5750
+ maxKey = key;
5751
+ maxValue = radar2[key];
5752
+ }
5753
+ }
5754
+ return featureKeys.includes(maxKey);
5755
+ });
5756
+ }
5757
+ return filtered;
6523
5758
  }
6524
5759
  /**
6525
- * 根据服务器类型获取对应的服务器实例
6526
- * @param serverType - 服务器类型
6527
- * @returns 对应的服务器实例
5760
+ * 计算单曲在某个雷达分类下的贡献值 F
6528
5761
  */
6529
- getServer(serverType) {
6530
- return this.factory.getServer(serverType);
5762
+ calcSongRadarContribution(score, category) {
5763
+ const { difficulty_data } = score;
5764
+ if (!difficulty_data?.radar || !difficulty_data.max_exscore) return 0;
5765
+ const L = Math.floor(difficulty_data.difnum);
5766
+ const R = difficulty_data.radar[category];
5767
+ const S = score.music.score;
5768
+ const E = score.music.exscore;
5769
+ const Emax = difficulty_data.max_exscore;
5770
+ const P = Math.min(2 * (L + 31), 100);
5771
+ const B = Math.floor(3 * S * R / (4 * 1e7));
5772
+ const H = Math.floor((20 - L) / 2);
5773
+ const G = Math.max(1e3 + 2 * 2 ** H * (E - Emax), 0);
5774
+ const X = Math.floor(R * G / 4e3);
5775
+ return P * (B + X);
6531
5776
  }
6532
5777
  /**
6533
- * 根据服务器类型和游戏类型获取对应的游戏服务实例
6534
- * @param serverType - 服务器类型
6535
- * @param gameType - 游戏类型
6536
- * @returns 对应的游戏服务实例
5778
+ * 计算玩家六维雷达(显示值,如 200.00)
5779
+ * 算法:每个分类取每首歌最高贡献的难度,取 top50 求和,再除以 400000
6537
5780
  */
6538
- getGameService(serverType, gameType) {
6539
- const server2 = this.factory.getServer(serverType);
6540
- return server2.gameServices[gameType];
5781
+ getPlayerRadarByAllScore(scores) {
5782
+ const categories = ["notes", "peak", "tsumami", "tricky", "hand_trip", "one_hand"];
5783
+ const radar2 = { notes: 0, peak: 0, tsumami: 0, tricky: 0, hand_trip: 0, one_hand: 0 };
5784
+ for (const category of categories) {
5785
+ const bestPerSong = /* @__PURE__ */ new Map();
5786
+ for (const score of scores) {
5787
+ const F = this.calcSongRadarContribution(score, category);
5788
+ if (F <= 0) continue;
5789
+ const musicId = score.music.music_id;
5790
+ const existing = bestPerSong.get(musicId) ?? 0;
5791
+ if (F > existing) {
5792
+ bestPerSong.set(musicId, F);
5793
+ }
5794
+ }
5795
+ const top50 = [...bestPerSong.values()].sort((a, b) => b - a).slice(0, 50);
5796
+ const T = top50.reduce((sum, v) => sum + v, 0);
5797
+ radar2[category] = Math.floor(T / 40) / 100;
5798
+ }
5799
+ return radar2;
6541
5800
  }
6542
5801
  };
6543
- function apply11(ctx, config) {
5802
+
5803
+ // src/games/sdvx/commands/radar.ts
5804
+ function radar(ctx, config, logger5) {
5805
+ ctx.command("sdvx.radar").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).action(async ({ session }) => {
5806
+ const atGuild = session.guildId != null;
5807
+ const cardService = new CardService(ctx);
5808
+ const serverService = new ServerService(ctx);
5809
+ const userCards = await cardService.getCardsByUid(session.user.id);
5810
+ if (userCards.length === 0) return session.text(".card-not-found");
5811
+ const serverRes = await serverService.getSelectableServers(
5812
+ session.user.id,
5813
+ atGuild ? session.channel.id : null
5814
+ );
5815
+ if (serverRes.length === 0) return session.text(".server-not-found");
5816
+ const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
5817
+ if (!defaultCard) return session.text(".card-not-found");
5818
+ const card2 = await cardService.getCardByCode(defaultCard.code);
5819
+ let server2;
5820
+ if (card2.defaultServerId != void 0 && card2.defaultServerId != 0)
5821
+ server2 = await serverService.getServerById(card2.defaultServerId);
5822
+ else {
5823
+ const channelDefaultServer = atGuild ? await serverService.getDefaultServerByCid(
5824
+ session.platform,
5825
+ session.channel.id
5826
+ ) : null;
5827
+ if (channelDefaultServer) server2 = channelDefaultServer;
5828
+ else {
5829
+ const userDefaultServer = await serverService.getDefaultServerByUid(
5830
+ session.user.id
5831
+ );
5832
+ if (userDefaultServer) server2 = userDefaultServer;
5833
+ else return session.text(".server-not-found");
5834
+ }
5835
+ }
5836
+ const serverManager = ServerManager.getInstance();
5837
+ const sdvxService = serverManager.getGameService(server2.type, "sdvx");
5838
+ const scoreService = ScoreService.getInstance();
5839
+ try {
5840
+ const scoreList = await sdvxService.getAllScore(
5841
+ ctx,
5842
+ server2.baseUrl,
5843
+ card2.code,
5844
+ config
5845
+ );
5846
+ if (scoreList.length === 0) {
5847
+ return session.text(".no-scores-found");
5848
+ }
5849
+ const radarResult = scoreService.getPlayerRadarByAllScore(scoreList);
5850
+ const playerName = await sdvxService.getUserName(ctx, server2.baseUrl, card2.code);
5851
+ return session.text(".result", {
5852
+ name: playerName,
5853
+ notes: radarResult.notes.toFixed(2),
5854
+ peak: radarResult.peak.toFixed(2),
5855
+ tsumami: radarResult.tsumami.toFixed(2),
5856
+ tricky: radarResult.tricky.toFixed(2),
5857
+ hand_trip: radarResult.hand_trip.toFixed(2),
5858
+ one_hand: radarResult.one_hand.toFixed(2)
5859
+ });
5860
+ } catch (error) {
5861
+ logger5.warn(error);
5862
+ return session.text(".error");
5863
+ }
5864
+ });
6544
5865
  }
6545
- __name(apply11, "apply");
5866
+ __name(radar, "radar");
6546
5867
 
6547
5868
  // src/games/sdvx/commands/recent.ts
6548
- function recent(ctx, config, logger6) {
5869
+ var import_koishi16 = require("koishi");
5870
+ function recent(ctx, config, logger5) {
6549
5871
  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
5872
  const atGuild = session.guildId != null;
6551
5873
  if (!count) count = 1;
@@ -6573,7 +5895,7 @@ function recent(ctx, config, logger6) {
6573
5895
  cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
6574
5896
  }
6575
5897
  }
6576
- const msg = import_koishi15.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
5898
+ const msg = import_koishi16.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
6577
5899
  await session.send(msg);
6578
5900
  const select = await session.prompt();
6579
5901
  if (!select) return session.text("commands.timeout");
@@ -6630,11 +5952,11 @@ function recent(ctx, config, logger6) {
6630
5952
  }
6631
5953
  );
6632
5954
  if (options.lossless) {
6633
- return import_koishi15.h.image(imageBuffer, "image/png");
5955
+ return import_koishi16.h.image(imageBuffer, "image/png");
6634
5956
  }
6635
- return import_koishi15.h.image(imageBuffer, "image/jpg");
5957
+ return import_koishi16.h.image(imageBuffer, "image/jpg");
6636
5958
  } catch (error) {
6637
- logger6.warn(error);
5959
+ logger5.warn(error);
6638
5960
  return session.text(".error");
6639
5961
  }
6640
5962
  });
@@ -6642,7 +5964,7 @@ function recent(ctx, config, logger6) {
6642
5964
  __name(recent, "recent");
6643
5965
 
6644
5966
  // src/games/sdvx/commands/sync.ts
6645
- function sync(ctx, config, logger6) {
5967
+ function sync(ctx, config, logger5) {
6646
5968
  ctx.command("sdvx.sync").userFields(["id"]).channelFields(["id"]).action(async ({ session }) => {
6647
5969
  const serverService = new ServerService(ctx);
6648
5970
  const cardService = new CardService(ctx);
@@ -6717,7 +6039,7 @@ function sync(ctx, config, logger6) {
6717
6039
  const maoSdvxService = serverManager.getGameService("mao", "sdvx");
6718
6040
  const maoVerifyUrl = "https://maomani.cn";
6719
6041
  if (!maoSdvxService || typeof maoSdvxService.verifyPin !== "function") {
6720
- logger6.warn("Mao SDVX service does not support PIN verification");
6042
+ logger5.warn("Mao SDVX service does not support PIN verification");
6721
6043
  return session.text(".pin-verify-error");
6722
6044
  }
6723
6045
  const existingPin = await ctx.database.get("sdvx_pin_verified", {
@@ -6738,7 +6060,7 @@ function sync(ctx, config, logger6) {
6738
6060
  pinVerified = true;
6739
6061
  }
6740
6062
  } catch (error) {
6741
- logger6.warn(error);
6063
+ logger5.warn(error);
6742
6064
  }
6743
6065
  }
6744
6066
  if (!pinVerified) {
@@ -6774,7 +6096,7 @@ function sync(ctx, config, logger6) {
6774
6096
  })
6775
6097
  );
6776
6098
  } catch (error) {
6777
- logger6.warn(error);
6099
+ logger5.warn(error);
6778
6100
  await session.send(session.text(".pin-verify-error"));
6779
6101
  }
6780
6102
  }
@@ -6796,7 +6118,7 @@ function sync(ctx, config, logger6) {
6796
6118
  config
6797
6119
  );
6798
6120
  } catch (error) {
6799
- logger6.warn(error);
6121
+ logger5.warn(error);
6800
6122
  return session.text(".fetch-error");
6801
6123
  }
6802
6124
  if (!scoreList || scoreList.length === 0) {
@@ -6819,11 +6141,11 @@ function sync(ctx, config, logger6) {
6819
6141
  syncPayload
6820
6142
  );
6821
6143
  if (!ok) {
6822
- logger6.warn("Mao SDVX uploadScore returned falsy result");
6144
+ logger5.warn("Mao SDVX uploadScore returned falsy result");
6823
6145
  return session.text(".sync-failed");
6824
6146
  }
6825
6147
  } catch (error) {
6826
- logger6.warn(error);
6148
+ logger5.warn(error);
6827
6149
  return session.text(".sync-error");
6828
6150
  }
6829
6151
  return session.text(".selected-summary", {
@@ -6889,86 +6211,7 @@ __name(buildSyncPayload, "buildSyncPayload");
6889
6211
 
6890
6212
  // src/games/sdvx/commands/vf.ts
6891
6213
  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
- };
6214
+ var import_koishi17 = require("koishi");
6972
6215
 
6973
6216
  // src/games/sdvx/utils/filter.ts
6974
6217
  function parseFilterQuery(query) {
@@ -7021,7 +6264,7 @@ function parseFilterQuery(query) {
7021
6264
  __name(parseFilterQuery, "parseFilterQuery");
7022
6265
 
7023
6266
  // src/games/sdvx/commands/vf.ts
7024
- function vf(ctx, config, logger6) {
6267
+ function vf(ctx, config, logger5) {
7025
6268
  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
6269
  const atGuild = session.guildId != null;
7027
6270
  const cardService = new CardService(ctx);
@@ -7048,7 +6291,7 @@ function vf(ctx, config, logger6) {
7048
6291
  cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
7049
6292
  }
7050
6293
  }
7051
- const msg = import_koishi16.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
6294
+ const msg = import_koishi17.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
7052
6295
  await session.send(msg);
7053
6296
  const select = await session.prompt();
7054
6297
  if (!select) return session.text("commands.timeout");
@@ -7111,9 +6354,9 @@ function vf(ctx, config, logger6) {
7111
6354
  }
7112
6355
  );
7113
6356
  if (options.lossless) {
7114
- session.send(import_koishi16.h.file(imageBuffer, "image/png"));
6357
+ session.send(import_koishi17.h.file(imageBuffer, "image/png"));
7115
6358
  } else {
7116
- session.send(import_koishi16.h.image(imageBuffer, "image/jpg"));
6359
+ session.send(import_koishi17.h.image(imageBuffer, "image/jpg"));
7117
6360
  }
7118
6361
  const sortedScores = [...best50ScoreList].sort((a, b) => b.extra.volforce - a.extra.volforce).slice(0, 50);
7119
6362
  const total = sortedScores.reduce((sum, score) => sum + score.extra.volforce, 0);
@@ -7142,18 +6385,18 @@ function vf(ctx, config, logger6) {
7142
6385
  );
7143
6386
  if (fs4.existsSync(audioPath)) {
7144
6387
  const audioBuffer = fs4.readFileSync(audioPath);
7145
- session.send(import_koishi16.h.audio(audioBuffer, "audio/mpeg"));
6388
+ session.send(import_koishi17.h.audio(audioBuffer, "audio/mpeg"));
7146
6389
  }
7147
6390
  if (fs4.existsSync(imagePath)) {
7148
6391
  const imageBuffer2 = fs4.readFileSync(imagePath);
7149
- session.send(import_koishi16.h.image(imageBuffer2, "image/png"));
6392
+ session.send(import_koishi17.h.image(imageBuffer2, "image/png"));
7150
6393
  }
7151
6394
  } catch (error) {
7152
- logger6.warn(`Failed to send celebration files: ${error.message}`);
6395
+ logger5.warn(`Failed to send celebration files: ${error.message}`);
7153
6396
  }
7154
6397
  }
7155
6398
  } catch (error) {
7156
- logger6.warn(error);
6399
+ logger5.warn(error);
7157
6400
  return session.text(".error");
7158
6401
  }
7159
6402
  return;
@@ -7162,25 +6405,26 @@ function vf(ctx, config, logger6) {
7162
6405
  __name(vf, "vf");
7163
6406
 
7164
6407
  // src/games/sdvx/command.ts
7165
- var name12 = "command";
7166
- function apply12(ctx, config) {
6408
+ var name10 = "command";
6409
+ function apply10(ctx, config) {
7167
6410
  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);
6411
+ vf(ctx, config, logger4);
6412
+ recent(ctx, config, logger4);
6413
+ chart(ctx, config, logger4);
6414
+ calculate(ctx, config, logger4);
6415
+ sync(ctx, config, logger4);
6416
+ radar(ctx, config, logger4);
7173
6417
  }
7174
- __name(apply12, "apply");
6418
+ __name(apply10, "apply");
7175
6419
 
7176
6420
  // src/games/sdvx/database.ts
7177
- var database_exports3 = {};
7178
- __export(database_exports3, {
7179
- apply: () => apply13,
7180
- name: () => name13
6421
+ var database_exports2 = {};
6422
+ __export(database_exports2, {
6423
+ apply: () => apply11,
6424
+ name: () => name11
7181
6425
  });
7182
- var name13 = "database";
7183
- function apply13(ctx) {
6426
+ var name11 = "database";
6427
+ function apply11(ctx) {
7184
6428
  ctx.model.extend(
7185
6429
  "sdvx_pin_verified",
7186
6430
  {
@@ -7195,135 +6439,147 @@ function apply13(ctx) {
7195
6439
  }
7196
6440
  );
7197
6441
  }
7198
- __name(apply13, "apply");
6442
+ __name(apply11, "apply");
7199
6443
 
7200
6444
  // src/games/sdvx/event.ts
7201
6445
  var event_exports2 = {};
7202
6446
  __export(event_exports2, {
7203
- apply: () => apply14,
7204
- name: () => name14
6447
+ apply: () => apply12,
6448
+ name: () => name12
7205
6449
  });
7206
- var name14 = "event";
7207
- function apply14(ctx) {
6450
+ var name12 = "event";
6451
+ function apply12(ctx) {
7208
6452
  }
7209
- __name(apply14, "apply");
6453
+ __name(apply12, "apply");
7210
6454
 
7211
6455
  // 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>" } } } } };
6456
+ 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
6457
 
7214
6458
  // 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>" } } } } };
6459
+ 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
6460
 
7217
6461
  // 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) {
6462
+ var name13 = "Noah-SDVX";
6463
+ var inject3 = ["database", "globalConfig"];
6464
+ var logger4 = new import_koishi18.Logger("Noah-SDVX");
6465
+ async function apply13(ctx, config) {
7222
6466
  ;
7223
6467
  [
7224
- ["en-US", en_US_default5],
7225
- ["zh-CN", zh_CN_default5]
6468
+ ["en-US", en_US_default4],
6469
+ ["zh-CN", zh_CN_default4]
7226
6470
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
7227
- ctx.plugin(database_exports3, config.sdvx);
6471
+ ctx.plugin(database_exports2, config.sdvx);
7228
6472
  ctx.plugin(command_exports2, config.sdvx);
7229
6473
  ctx.plugin(event_exports2, config.sdvx);
7230
6474
  }
7231
- __name(apply15, "apply");
6475
+ __name(apply13, "apply");
7232
6476
 
7233
6477
  // src/config.ts
7234
- var import_koishi24 = require("koishi");
6478
+ var import_koishi26 = require("koishi");
7235
6479
 
7236
6480
  // 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),
6481
+ var import_koishi19 = require("koishi");
6482
+ var assetConfig = import_koishi19.Schema.object({
6483
+ data_path: import_koishi19.Schema.string().default("noah_assets"),
6484
+ auto_update: import_koishi19.Schema.boolean().default(true),
6485
+ update_url: import_koishi19.Schema.string().default("https://github.com/logthm/noah/releases/latest/download/"),
6486
+ update_interval: import_koishi19.Schema.number().default(24 * 60 * 60 * 1e3),
7243
6487
  // 24 hours in milliseconds
7244
- github_token: import_koishi18.Schema.string().description("GitHub token for accessing private repositories").role("secret")
6488
+ github_token: import_koishi19.Schema.string().description("GitHub token for accessing private repositories").role("secret")
7245
6489
  }).i18n({
7246
6490
  "en-US": en_US_default._config,
7247
6491
  "zh-CN": zh_CN_default._config
7248
6492
  });
7249
6493
 
7250
6494
  // 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")
6495
+ var import_koishi20 = require("koishi");
6496
+ var coreConfig = import_koishi20.Schema.object({
6497
+ adminUsers: import_koishi20.Schema.array(String).role("table"),
6498
+ guildNameCards: import_koishi20.Schema.array(String).role("table").default(["Noah | /help 获取食用指南"])
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()
7308
6549
  }).i18n({
7309
- "en-US": en_US_default5._config,
7310
- "zh-CN": zh_CN_default5._config
6550
+ "en-US": en_US_default4._config,
6551
+ "zh-CN": zh_CN_default4._config
6552
+ });
6553
+
6554
+ // src/global/config.ts
6555
+ var import_koishi24 = require("koishi");
6556
+ var globalConfig = import_koishi24.Schema.object({
6557
+ official_support_url: import_koishi24.Schema.string().default("https://noah.logthm.com"),
6558
+ maoServerUrl: import_koishi24.Schema.string().default("http://maomani.cn:577"),
6559
+ maoApiKey: import_koishi24.Schema.string().role("secret").default("")
7311
6560
  });
7312
6561
 
7313
6562
  // 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)
6563
+ var import_koishi25 = require("koishi");
6564
+
6565
+ // src/slash/locales/en-US.yml
6566
+ var en_US_default6 = { _config: { $desc: "Slash Module Settings", test_guilds: "**Guilds To Sync**", auto_sync_on_start: "**Auto Sync on Connect**" } };
6567
+
6568
+ // src/slash/locales/zh-CN.yml
6569
+ var zh_CN_default6 = { _config: { $desc: "Slash 模块设置", test_guilds: "**默认同步 Guild 列表**", auto_sync_on_start: "**连接后自动同步**" } };
6570
+
6571
+ // src/slash/config.ts
6572
+ var slashConfig = import_koishi25.Schema.object({
6573
+ test_guilds: import_koishi25.Schema.array(String).default([]),
6574
+ auto_sync_on_start: import_koishi25.Schema.boolean().default(true)
7320
6575
  }).i18n({
7321
- "en-US": en_US_default2._config,
7322
- "zh-CN": zh_CN_default2._config
6576
+ "en-US": en_US_default6._config,
6577
+ "zh-CN": zh_CN_default6._config
7323
6578
  });
7324
6579
 
7325
6580
  // src/config.ts
7326
- var Config = import_koishi24.Schema.object({
6581
+ var Config = import_koishi26.Schema.object({
6582
+ global: globalConfig,
7327
6583
  core: coreConfig,
7328
6584
  sdvx: sdvxConfig,
7329
6585
  poke: pokeConfig,
@@ -7333,10 +6589,11 @@ var Config = import_koishi24.Schema.object({
7333
6589
  });
7334
6590
 
7335
6591
  // src/index.ts
7336
- var name16 = "noah";
6592
+ var name14 = "noah";
7337
6593
  var inject4 = ["database", "skia"];
7338
- async function apply16(ctx, config) {
6594
+ async function apply14(ctx, config) {
7339
6595
  initConstants(ctx);
6596
+ ctx.set("globalConfig", config.global);
7340
6597
  ctx.plugin(core_exports, config);
7341
6598
  ctx.plugin(general_exports, config);
7342
6599
  ctx.plugin(sdvx_exports, config);
@@ -7344,9 +6601,8 @@ async function apply16(ctx, config) {
7344
6601
  ctx.plugin(drawer_exports, config);
7345
6602
  ctx.plugin(poke_exports, config);
7346
6603
  ctx.plugin(asset_exports, config);
7347
- ctx.plugin(slash_exports, config);
7348
6604
  }
7349
- __name(apply16, "apply");
6605
+ __name(apply14, "apply");
7350
6606
  // Annotate the CommonJS export names for ESM import in node:
7351
6607
  0 && (module.exports = {
7352
6608
  Config,