koishi-plugin-noah 2.4.4 → 2.4.6

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
@@ -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
34
  apply: () => apply14,
35
35
  inject: () => inject4,
36
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
  }
@@ -336,18 +337,61 @@ __export(core_exports, {
336
337
  });
337
338
  var import_koishi6 = require("koishi");
338
339
 
339
- // src/core/command.ts
340
- var command_exports = {};
341
- __export(command_exports, {
342
- apply: () => apply2,
343
- name: () => name2
344
- });
340
+ // src/core/api/auth.ts
341
+ var crypto = __toESM(require("crypto"), 1);
342
+ function resolveApiContext(config) {
343
+ return {
344
+ secret: crypto.randomBytes(32).toString("hex"),
345
+ tokenTtl: config.tokenTtl ?? 1800,
346
+ frontendUrl: config.frontendUrl ?? "",
347
+ corsOrigin: config.corsOrigin ?? "*"
348
+ };
349
+ }
350
+ __name(resolveApiContext, "resolveApiContext");
351
+ var ALG = "sha256";
352
+ function base64url(data) {
353
+ const buf = typeof data === "string" ? Buffer.from(data) : data;
354
+ return buf.toString("base64url");
355
+ }
356
+ __name(base64url, "base64url");
357
+ function sign(payload, secret) {
358
+ const header = base64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
359
+ const body = base64url(payload);
360
+ const signature = crypto.createHmac(ALG, secret).update(`${header}.${body}`).digest("base64url");
361
+ return `${header}.${body}.${signature}`;
362
+ }
363
+ __name(sign, "sign");
364
+ function createToken(uid, secret, ttlSeconds = 1800) {
365
+ const now = Math.floor(Date.now() / 1e3);
366
+ const payload = { uid, iat: now, exp: now + ttlSeconds };
367
+ return sign(JSON.stringify(payload), secret);
368
+ }
369
+ __name(createToken, "createToken");
370
+ function verifyToken(token, secret) {
371
+ const parts = token.split(".");
372
+ if (parts.length !== 3) return null;
373
+ const [header, body, signature] = parts;
374
+ const expected = crypto.createHmac(ALG, secret).update(`${header}.${body}`).digest("base64url");
375
+ if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) return null;
376
+ try {
377
+ const payload = JSON.parse(Buffer.from(body, "base64url").toString());
378
+ if (payload.exp < Math.floor(Date.now() / 1e3)) return null;
379
+ return payload;
380
+ } catch {
381
+ return null;
382
+ }
383
+ }
384
+ __name(verifyToken, "verifyToken");
385
+
386
+ // src/types/index.ts
387
+ var serverValues = ["asphyxia", "mao", "official"];
345
388
 
346
389
  // src/core/services/card-service.ts
347
390
  var CardService = class {
348
391
  constructor(ctx) {
349
392
  this.ctx = ctx;
350
393
  }
394
+ ctx;
351
395
  static {
352
396
  __name(this, "CardService");
353
397
  }
@@ -447,9 +491,6 @@ var CardService = class {
447
491
  * @returns 无返回值的Promise
448
492
  */
449
493
  async updateCard(id, partial) {
450
- if (partial.defaultServerId === void 0) {
451
- partial.defaultServerId = 0;
452
- }
453
494
  await this.ctx.database.set(this.tableName, id, partial);
454
495
  }
455
496
  /**
@@ -492,6 +533,7 @@ var ServerService = class {
492
533
  constructor(ctx) {
493
534
  this.ctx = ctx;
494
535
  }
536
+ ctx;
495
537
  static {
496
538
  __name(this, "ServerService");
497
539
  }
@@ -641,18 +683,20 @@ var ServerService = class {
641
683
  if (channelServerRes.length > 0) {
642
684
  await this.ctx.database.set(
643
685
  "channel",
644
- { id: channel.id },
686
+ { id: channel.id, platform: channel.platform },
645
687
  { defaultServerId: channelServerRes[0].sid }
646
688
  );
647
689
  } else {
648
- await this.ctx.database.set("channel", { id: channel.id }, { defaultServerId: 0 });
690
+ await this.ctx.database.set(
691
+ "channel",
692
+ { id: channel.id, platform: channel.platform },
693
+ { defaultServerId: 0 }
694
+ );
649
695
  }
650
696
  }
651
697
  const cardRes = await this.ctx.database.get("card", { defaultServerId: id });
652
698
  for (const card2 of cardRes) {
653
- if (card2.defaultServerId !== void 0 && card2.defaultServerId !== 0) {
654
- await this.ctx.database.set("card", { id: card2.id }, { defaultServerId: 0 });
655
- }
699
+ await this.ctx.database.set("card", { id: card2.id }, { defaultServerId: 0 });
656
700
  }
657
701
  }
658
702
  /**
@@ -685,6 +729,7 @@ var UserService = class {
685
729
  constructor(ctx) {
686
730
  this.ctx = ctx;
687
731
  }
732
+ ctx;
688
733
  static {
689
734
  __name(this, "UserService");
690
735
  }
@@ -719,7 +764,7 @@ var UserService = class {
719
764
  };
720
765
 
721
766
  // src/core/utils/card.ts
722
- var crypto = __toESM(require("crypto"), 1);
767
+ var crypto2 = __toESM(require("crypto"), 1);
723
768
  var KEY_STRING = "?I'llB2c.YouXXXeMeHaYpy!";
724
769
  var KEY_BUFFER = Buffer.from(KEY_STRING, "ascii");
725
770
  var DES3_KEY = Buffer.from(KEY_BUFFER.map((b) => b * 2 & 255));
@@ -729,7 +774,7 @@ function encDes(data) {
729
774
  if (data.length !== 8) {
730
775
  throw new Error("encDes: data must be 8 bytes");
731
776
  }
732
- const cipher = crypto.createCipheriv("des-ede3-cbc", DES3_KEY, IV);
777
+ const cipher = crypto2.createCipheriv("des-ede3-cbc", DES3_KEY, IV);
733
778
  cipher.setAutoPadding(false);
734
779
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
735
780
  return encrypted;
@@ -739,7 +784,7 @@ function decDes(data) {
739
784
  if (data.length !== 8) {
740
785
  throw new Error("decDes: data must be 8 bytes");
741
786
  }
742
- const decipher = crypto.createDecipheriv("des-ede3-cbc", DES3_KEY, IV);
787
+ const decipher = crypto2.createDecipheriv("des-ede3-cbc", DES3_KEY, IV);
743
788
  decipher.setAutoPadding(false);
744
789
  const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
745
790
  return decrypted;
@@ -993,6 +1038,19 @@ function formatCardInfo(data) {
993
1038
  __name(formatCardInfo, "formatCardInfo");
994
1039
 
995
1040
  // src/core/utils/server.ts
1041
+ async function resolveServerForCard(card2, cardService, serverService, uid, platform, channelId) {
1042
+ if (card2.defaultServerId != void 0 && card2.defaultServerId != 0) {
1043
+ const cardServer = await serverService.getServerById(card2.defaultServerId);
1044
+ if (cardServer) return cardServer;
1045
+ await cardService.updateCard(card2.id, { defaultServerId: 0 });
1046
+ }
1047
+ if (channelId) {
1048
+ const channelDefault = await serverService.getDefaultServerByCid(platform, channelId);
1049
+ if (channelDefault) return channelDefault;
1050
+ }
1051
+ return await serverService.getDefaultServerByUid(uid);
1052
+ }
1053
+ __name(resolveServerForCard, "resolveServerForCard");
996
1054
  async function ensureOfficialServerForUser(ctx, serverService, userService, uid, officialSupportUrl) {
997
1055
  const userServers = await serverService.getServersByUid(uid);
998
1056
  const existingOfficial = userServers.find((server3) => server3.type === "official");
@@ -1015,12 +1073,355 @@ async function ensureOfficialServerForUser(ctx, serverService, userService, uid,
1015
1073
  }
1016
1074
  __name(ensureOfficialServerForUser, "ensureOfficialServerForUser");
1017
1075
 
1018
- // src/core/commands/bind.ts
1019
- function bind(ctx, config) {
1020
- ctx.command("bind [cardCode:text]", { slash: true }).alias("绑卡").userFields(["defaultCardId", "id"]).action(async ({ session }, cardCode) => {
1076
+ // src/core/api/index.ts
1077
+ function registerApiRoutes(ctx, apiCtx) {
1078
+ const { secret, corsOrigin } = apiCtx;
1079
+ function setCors(kctx) {
1080
+ kctx.set("Access-Control-Allow-Origin", corsOrigin);
1081
+ kctx.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
1082
+ kctx.set("Access-Control-Allow-Headers", "Authorization, Content-Type");
1083
+ }
1084
+ __name(setCors, "setCors");
1085
+ function authenticate(kctx) {
1086
+ const auth = kctx.get("Authorization");
1087
+ if (!auth?.startsWith("Bearer ")) return null;
1088
+ return verifyToken(auth.slice(7), secret);
1089
+ }
1090
+ __name(authenticate, "authenticate");
1091
+ function unauthorized(kctx) {
1092
+ kctx.status = 401;
1093
+ kctx.body = { error: "Unauthorized" };
1094
+ }
1095
+ __name(unauthorized, "unauthorized");
1096
+ function badRequest(kctx, message) {
1097
+ kctx.status = 400;
1098
+ kctx.body = { error: message };
1099
+ }
1100
+ __name(badRequest, "badRequest");
1101
+ ctx.server.get("/noah/api/health", async (kctx) => {
1102
+ setCors(kctx);
1103
+ kctx.body = { status: "ok" };
1104
+ });
1105
+ const apiPaths = [
1106
+ "/noah/api/me",
1107
+ "/noah/api/cards",
1108
+ "/noah/api/cards/:id",
1109
+ "/noah/api/servers",
1110
+ "/noah/api/servers/:id",
1111
+ "/noah/api/defaults",
1112
+ "/noah/api/resolve",
1113
+ "/noah/api/health"
1114
+ ];
1115
+ for (const path4 of apiPaths) {
1116
+ ctx.server.all(path4, async (kctx, next) => {
1117
+ setCors(kctx);
1118
+ if (kctx.method === "OPTIONS") {
1119
+ kctx.status = 204;
1120
+ return;
1121
+ }
1122
+ await next();
1123
+ });
1124
+ }
1125
+ ctx.server.get("/noah/api/me", async (kctx) => {
1126
+ setCors(kctx);
1127
+ const payload = authenticate(kctx);
1128
+ if (!payload) return unauthorized(kctx);
1129
+ const cardService = new CardService(ctx);
1130
+ const serverService = new ServerService(ctx);
1131
+ const defaultCard = await cardService.getDefaultCardByUid(payload.uid);
1132
+ const defaultServer = await serverService.getDefaultServerByUid(payload.uid);
1133
+ kctx.body = {
1134
+ uid: payload.uid,
1135
+ defaultCardId: defaultCard?.id ?? null,
1136
+ defaultServerId: defaultServer?.id ?? null
1137
+ };
1138
+ });
1139
+ ctx.server.get("/noah/api/cards", async (kctx) => {
1140
+ setCors(kctx);
1141
+ const payload = authenticate(kctx);
1142
+ if (!payload) return unauthorized(kctx);
1143
+ const cardService = new CardService(ctx);
1144
+ const defaultCard = await cardService.getDefaultCardByUid(payload.uid);
1145
+ const cards = await cardService.getCardsByUid(payload.uid);
1146
+ kctx.body = cards.map((card2) => ({
1147
+ id: card2.id,
1148
+ code: card2.code,
1149
+ name: card2.name,
1150
+ defaultServerId: card2.defaultServerId || null,
1151
+ isDefault: defaultCard?.id === card2.id
1152
+ }));
1153
+ });
1154
+ ctx.server.post("/noah/api/cards", async (kctx) => {
1155
+ setCors(kctx);
1156
+ const payload = authenticate(kctx);
1157
+ if (!payload) return unauthorized(kctx);
1158
+ const body = kctx.request.body;
1159
+ if (!body?.code || !body?.name) return badRequest(kctx, "code and name are required");
1021
1160
  const cardService = new CardService(ctx);
1022
1161
  const serverService = new ServerService(ctx);
1023
1162
  const userService = new UserService(ctx);
1163
+ const processed = processInputCardCode(body.code);
1164
+ const cardType = classifyCardId(ctx, processed);
1165
+ if (cardType === "invalid") return badRequest(kctx, "Invalid card code");
1166
+ let finalCode = processed;
1167
+ if (cardType === "access") {
1168
+ try {
1169
+ finalCode = await accessToUid(ctx, processed);
1170
+ } catch (e) {
1171
+ return badRequest(kctx, `Failed to convert access code: ${e.message}`);
1172
+ }
1173
+ } else if (cardType === "konamiid") {
1174
+ try {
1175
+ finalCode = konamiIdToUid(processed);
1176
+ } catch (e) {
1177
+ return badRequest(kctx, `Failed to convert Konami ID: ${e.message}`);
1178
+ }
1179
+ }
1180
+ const existing = await cardService.getCardByCode(finalCode);
1181
+ if (existing) return badRequest(kctx, "Card already exists");
1182
+ const card2 = await cardService.createCard(payload.uid, finalCode, body.name);
1183
+ if (cardType === "official") {
1184
+ await ensureOfficialServerForUser(ctx, serverService, userService, payload.uid);
1185
+ }
1186
+ const defaultCard = await cardService.getDefaultCardByUid(payload.uid);
1187
+ if (!defaultCard) {
1188
+ await userService.updateUserDefaultCard(payload.uid, card2.id);
1189
+ }
1190
+ kctx.status = 201;
1191
+ kctx.body = { id: card2.id, code: card2.code, name: card2.name, defaultServerId: null };
1192
+ });
1193
+ ctx.server.patch("/noah/api/cards/:id", async (kctx) => {
1194
+ setCors(kctx);
1195
+ const payload = authenticate(kctx);
1196
+ if (!payload) return unauthorized(kctx);
1197
+ const cardId = Number(kctx.params.id);
1198
+ if (isNaN(cardId)) return badRequest(kctx, "Invalid card id");
1199
+ const cardService = new CardService(ctx);
1200
+ const cards = await cardService.getCardsByUid(payload.uid);
1201
+ if (!cards.find((c) => c.id === cardId)) {
1202
+ kctx.status = 404;
1203
+ kctx.body = { error: "Card not found" };
1204
+ return;
1205
+ }
1206
+ const body = kctx.request.body ?? {};
1207
+ const update = {};
1208
+ if (body.name !== void 0) update.name = body.name;
1209
+ if (body.defaultServerId !== void 0) update.defaultServerId = body.defaultServerId ?? 0;
1210
+ if (Object.keys(update).length > 0) {
1211
+ await cardService.updateCard(cardId, update);
1212
+ }
1213
+ const updated = await cardService.getCardById(cardId);
1214
+ kctx.body = updated;
1215
+ });
1216
+ ctx.server.delete("/noah/api/cards/:id", async (kctx) => {
1217
+ setCors(kctx);
1218
+ const payload = authenticate(kctx);
1219
+ if (!payload) return unauthorized(kctx);
1220
+ const cardId = Number(kctx.params.id);
1221
+ if (isNaN(cardId)) return badRequest(kctx, "Invalid card id");
1222
+ const cardService = new CardService(ctx);
1223
+ const cards = await cardService.getCardsByUid(payload.uid);
1224
+ if (!cards.find((c) => c.id === cardId)) {
1225
+ kctx.status = 404;
1226
+ kctx.body = { error: "Card not found" };
1227
+ return;
1228
+ }
1229
+ await cardService.removeCard(cardId);
1230
+ kctx.status = 204;
1231
+ });
1232
+ ctx.server.get("/noah/api/servers", async (kctx) => {
1233
+ setCors(kctx);
1234
+ const payload = authenticate(kctx);
1235
+ if (!payload) return unauthorized(kctx);
1236
+ const serverService = new ServerService(ctx);
1237
+ const defaultServer = await serverService.getDefaultServerByUid(payload.uid);
1238
+ const servers = await serverService.getServersByUid(payload.uid);
1239
+ kctx.body = servers.map((s) => ({
1240
+ id: s.id,
1241
+ type: s.type,
1242
+ name: s.name,
1243
+ baseUrl: s.baseUrl,
1244
+ pcbid: s.pcbid ?? null,
1245
+ isDefault: defaultServer?.id === s.id
1246
+ }));
1247
+ });
1248
+ ctx.server.post("/noah/api/servers", async (kctx) => {
1249
+ setCors(kctx);
1250
+ const payload = authenticate(kctx);
1251
+ if (!payload) return unauthorized(kctx);
1252
+ const body = kctx.request.body;
1253
+ if (!body?.type) return badRequest(kctx, "type is required");
1254
+ if (!serverValues.includes(body.type)) {
1255
+ return badRequest(
1256
+ kctx,
1257
+ `Invalid server type. Must be one of: ${serverValues.join(", ")}`
1258
+ );
1259
+ }
1260
+ const serverType = body.type;
1261
+ let baseUrl;
1262
+ let serverName;
1263
+ if (serverType === "mao") {
1264
+ baseUrl = ctx.globalConfig.maoServerUrl;
1265
+ serverName = body.name || "MaoMaNi";
1266
+ } else if (serverType === "official") {
1267
+ baseUrl = ctx.globalConfig.official_support_url;
1268
+ serverName = body.name || "Official";
1269
+ } else {
1270
+ if (!body.baseUrl) return badRequest(kctx, "baseUrl is required for asphyxia servers");
1271
+ if (!body.name) return badRequest(kctx, "name is required for asphyxia servers");
1272
+ baseUrl = body.baseUrl;
1273
+ serverName = body.name;
1274
+ }
1275
+ const serverService = new ServerService(ctx);
1276
+ const userService = new UserService(ctx);
1277
+ const server2 = await serverService.createServerForUser(
1278
+ {
1279
+ type: serverType,
1280
+ name: serverName,
1281
+ baseUrl,
1282
+ pcbid: body.pcbid,
1283
+ supportedGames: []
1284
+ },
1285
+ payload.uid
1286
+ );
1287
+ const defaultServer = await serverService.getDefaultServerByUid(payload.uid);
1288
+ if (!defaultServer) {
1289
+ await userService.updateUserDefaultServer(payload.uid, server2.id);
1290
+ }
1291
+ kctx.status = 201;
1292
+ kctx.body = {
1293
+ id: server2.id,
1294
+ type: server2.type,
1295
+ name: server2.name,
1296
+ baseUrl: server2.baseUrl,
1297
+ pcbid: server2.pcbid ?? null
1298
+ };
1299
+ });
1300
+ ctx.server.patch("/noah/api/servers/:id", async (kctx) => {
1301
+ setCors(kctx);
1302
+ const payload = authenticate(kctx);
1303
+ if (!payload) return unauthorized(kctx);
1304
+ const serverId = Number(kctx.params.id);
1305
+ if (isNaN(serverId)) return badRequest(kctx, "Invalid server id");
1306
+ const serverService = new ServerService(ctx);
1307
+ const servers = await serverService.getServersByUid(payload.uid);
1308
+ if (!servers.find((s) => s.id === serverId)) {
1309
+ kctx.status = 404;
1310
+ kctx.body = { error: "Server not found" };
1311
+ return;
1312
+ }
1313
+ const body = kctx.request.body ?? {};
1314
+ const update = {};
1315
+ if (body.name !== void 0) update.name = body.name;
1316
+ if (body.baseUrl !== void 0) update.baseUrl = body.baseUrl;
1317
+ if (body.pcbid !== void 0) update.pcbid = body.pcbid;
1318
+ if (Object.keys(update).length > 0) {
1319
+ await serverService.updateServer(serverId, update);
1320
+ }
1321
+ const updated = await serverService.getServerById(serverId);
1322
+ kctx.body = {
1323
+ id: updated.id,
1324
+ type: updated.type,
1325
+ name: updated.name,
1326
+ baseUrl: updated.baseUrl,
1327
+ pcbid: updated.pcbid ?? null
1328
+ };
1329
+ });
1330
+ ctx.server.delete("/noah/api/servers/:id", async (kctx) => {
1331
+ setCors(kctx);
1332
+ const payload = authenticate(kctx);
1333
+ if (!payload) return unauthorized(kctx);
1334
+ const serverId = Number(kctx.params.id);
1335
+ if (isNaN(serverId)) return badRequest(kctx, "Invalid server id");
1336
+ const serverService = new ServerService(ctx);
1337
+ const servers = await serverService.getServersByUid(payload.uid);
1338
+ if (!servers.find((s) => s.id === serverId)) {
1339
+ kctx.status = 404;
1340
+ kctx.body = { error: "Server not found" };
1341
+ return;
1342
+ }
1343
+ await serverService.removeServer(serverId);
1344
+ kctx.status = 204;
1345
+ });
1346
+ ctx.server.put("/noah/api/defaults", async (kctx) => {
1347
+ setCors(kctx);
1348
+ const payload = authenticate(kctx);
1349
+ if (!payload) return unauthorized(kctx);
1350
+ const body = kctx.request.body ?? {};
1351
+ const userService = new UserService(ctx);
1352
+ const cardService = new CardService(ctx);
1353
+ const serverService = new ServerService(ctx);
1354
+ if (body.defaultCardId !== void 0) {
1355
+ if (body.defaultCardId === null || body.defaultCardId === 0) {
1356
+ await userService.updateUserDefaultCard(payload.uid, 0);
1357
+ } else {
1358
+ const cards = await cardService.getCardsByUid(payload.uid);
1359
+ if (!cards.find((c) => c.id === body.defaultCardId)) {
1360
+ return badRequest(kctx, "Card not found");
1361
+ }
1362
+ await userService.updateUserDefaultCard(payload.uid, body.defaultCardId);
1363
+ }
1364
+ }
1365
+ if (body.defaultServerId !== void 0) {
1366
+ if (body.defaultServerId === null || body.defaultServerId === 0) {
1367
+ await userService.updateUserDefaultServer(payload.uid, 0);
1368
+ } else {
1369
+ const servers = await serverService.getServersByUid(payload.uid);
1370
+ if (!servers.find((s) => s.id === body.defaultServerId)) {
1371
+ return badRequest(kctx, "Server not found");
1372
+ }
1373
+ await userService.updateUserDefaultServer(payload.uid, body.defaultServerId);
1374
+ }
1375
+ }
1376
+ const defaultCard = await cardService.getDefaultCardByUid(payload.uid);
1377
+ const defaultServer = await serverService.getDefaultServerByUid(payload.uid);
1378
+ kctx.body = {
1379
+ defaultCardId: defaultCard?.id ?? null,
1380
+ defaultServerId: defaultServer?.id ?? null
1381
+ };
1382
+ });
1383
+ ctx.server.get("/noah/api/resolve", async (kctx) => {
1384
+ setCors(kctx);
1385
+ const payload = authenticate(kctx);
1386
+ if (!payload) return unauthorized(kctx);
1387
+ const cardService = new CardService(ctx);
1388
+ const serverService = new ServerService(ctx);
1389
+ const cards = await cardService.getCardsByUid(payload.uid);
1390
+ const defaultCard = await cardService.getDefaultCardByUid(payload.uid);
1391
+ const result = await Promise.all(
1392
+ cards.map(async (card2) => {
1393
+ let resolvedServer = null;
1394
+ if (card2.defaultServerId && card2.defaultServerId !== 0) {
1395
+ resolvedServer = await serverService.getServerById(card2.defaultServerId);
1396
+ }
1397
+ if (!resolvedServer) {
1398
+ resolvedServer = await serverService.getDefaultServerByUid(payload.uid);
1399
+ }
1400
+ return {
1401
+ cardId: card2.id,
1402
+ cardName: card2.name,
1403
+ isDefaultCard: defaultCard?.id === card2.id,
1404
+ resolvedServerId: resolvedServer?.id ?? null,
1405
+ resolvedServerName: resolvedServer?.name ?? null,
1406
+ source: card2.defaultServerId && card2.defaultServerId !== 0 ? "card" : "user-default"
1407
+ };
1408
+ })
1409
+ );
1410
+ kctx.body = result;
1411
+ });
1412
+ }
1413
+ __name(registerApiRoutes, "registerApiRoutes");
1414
+
1415
+ // src/core/command.ts
1416
+ var command_exports = {};
1417
+ __export(command_exports, {
1418
+ apply: () => apply2,
1419
+ name: () => name2
1420
+ });
1421
+
1422
+ // src/core/commands/bind.ts
1423
+ function bind(ctx, config, cardService, serverService, userService) {
1424
+ ctx.command("bind [cardCode:text]", { slash: true }).alias("绑卡").userFields(["id"]).action(async ({ session }, cardCode) => {
1024
1425
  if (!cardCode) {
1025
1426
  await session.send(session.text(".prompt"));
1026
1427
  cardCode = await session.prompt();
@@ -1075,9 +1476,8 @@ __name(bind, "bind");
1075
1476
 
1076
1477
  // src/core/commands/card.ts
1077
1478
  var import_koishi2 = require("koishi");
1078
- function card(ctx, config) {
1479
+ function card(ctx, config, cardService, serverService, userService) {
1079
1480
  ctx.command("card", { slash: true }).alias("卡片管理").userFields(["id"]).channelFields(["id"]).option("detail", "-d <cardCode:text>").action(async ({ session, options }) => {
1080
- const cardService = new CardService(ctx);
1081
1481
  const atGuild = session.guildId != null;
1082
1482
  if (options.detail) {
1083
1483
  const cardCode = processInputCardCode(options.detail);
@@ -1094,8 +1494,6 @@ function card(ctx, config) {
1094
1494
  return session.text(".lookup-error-unknown");
1095
1495
  }
1096
1496
  }
1097
- const serverService = new ServerService(ctx);
1098
- const userService = new UserService(ctx);
1099
1497
  const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
1100
1498
  const defaultCardId = defaultCard ? defaultCard.id : 0;
1101
1499
  const userDefaultServer = await serverService.getDefaultServerByUid(session.user.id);
@@ -1305,14 +1703,6 @@ async function showCardMenu(ctx, session, card2, cardService, serverService, use
1305
1703
  }
1306
1704
  if (selectNum === 3) {
1307
1705
  await cardService.removeCard(card2.id);
1308
- if (userDefaultCardId === card2.id) {
1309
- const res = await cardService.getCardsByUid(uid);
1310
- if (res.length === 0) {
1311
- await userService.updateUserDefaultCard(uid, 0);
1312
- } else {
1313
- await userService.updateUserDefaultCard(uid, res[0].id);
1314
- }
1315
- }
1316
1706
  return session.text(".menu-3-success");
1317
1707
  }
1318
1708
  if (selectNum === 4) {
@@ -1321,10 +1711,7 @@ async function showCardMenu(ctx, session, card2, cardService, serverService, use
1321
1711
  if (!cardName) return session.text("commands.timeout");
1322
1712
  if (cardName === "q") return session.text(".quit");
1323
1713
  if (cardName.includes(" ")) return session.text(".menu-4-error-invalid-name");
1324
- await cardService.updateCard(card2.id, {
1325
- name: cardName,
1326
- defaultServerId: card2.defaultServerId
1327
- });
1714
+ await cardService.updateCard(card2.id, { name: cardName });
1328
1715
  return session.text(".menu-4-success");
1329
1716
  }
1330
1717
  if (selectNum === 5) {
@@ -1386,8 +1773,9 @@ __name(showCardMenu, "showCardMenu");
1386
1773
  // src/core/commands/help.ts
1387
1774
  function help(ctx, config) {
1388
1775
  ctx.command("help", { slash: true }).alias("帮助").action(({ session }) => {
1389
- console.log(session);
1390
- return session.text(".content");
1776
+ let msg = session.text(".content");
1777
+ if (session.platform == "onebot") msg = msg.concat(session.text(".qq-extra"));
1778
+ return msg;
1391
1779
  });
1392
1780
  }
1393
1781
  __name(help, "help");
@@ -1473,13 +1861,12 @@ function isPluginAdmin(session, config) {
1473
1861
  return config.adminUsers?.includes(session.userId) ?? false;
1474
1862
  }
1475
1863
  __name(isPluginAdmin, "isPluginAdmin");
1476
- function isGuildAdmin(session) {
1864
+ async function isGuildAdmin(session) {
1477
1865
  const platform = session.event.platform;
1478
1866
  if (platform === "onebot") {
1479
- if (session.guildId == null) return true;
1480
- const role = session.event.member.roles[0];
1481
- const guildMember = typeof role === "string" ? role : role?.name || "";
1482
- return guildMember === "owner" || guildMember === "admin" || guildMember === "SUBCHANNEL_ADMIN" || guildMember === "OWNER" || guildMember === "ADMIN";
1867
+ if (!session.guildId) return true;
1868
+ const member = await session.onebot.getGroupMemberInfo(session.guildId, session.userId);
1869
+ return member.role === "owner" || member.role === "admin";
1483
1870
  } else if (platform === "qq") {
1484
1871
  return true;
1485
1872
  } else {
@@ -1491,7 +1878,7 @@ __name(isGuildAdmin, "isGuildAdmin");
1491
1878
  // src/core/commands/locale.ts
1492
1879
  function locale(ctx, config) {
1493
1880
  ctx.command("locale", { slash: true }).alias("language").option("channel", "-c").option("reset", "-r").userFields(["locales"]).channelFields(["locales"]).action(async ({ session, options }) => {
1494
- if (options.channel && !hasPermission(isPluginAdmin(session, config), isGuildAdmin(session))) {
1881
+ if (options.channel && !hasPermission(isPluginAdmin(session, config), await isGuildAdmin(session))) {
1495
1882
  return session.text(".noAuth");
1496
1883
  }
1497
1884
  if (options.reset) {
@@ -1583,11 +1970,6 @@ __name(maintain, "maintain");
1583
1970
 
1584
1971
  // src/core/commands/server.ts
1585
1972
  var import_koishi5 = require("koishi");
1586
-
1587
- // src/types/index.ts
1588
- var serverValues = ["asphyxia", "mao", "official"];
1589
-
1590
- // src/core/commands/server.ts
1591
1973
  function normalizeUrl(url) {
1592
1974
  try {
1593
1975
  if (!url.match(/^https?:\/\//i)) {
@@ -1645,14 +2027,12 @@ async function promptServerSelect(session, servers, defaultId, prefix) {
1645
2027
  return selectNum;
1646
2028
  }
1647
2029
  __name(promptServerSelect, "promptServerSelect");
1648
- function server(ctx, config) {
2030
+ function server(ctx, config, serverService, userService) {
1649
2031
  ctx.command("server", { slash: true }).alias("服务器管理").option("channel", "-c").userFields(["id"]).channelFields(["id"]).action(async ({ session, options }) => {
1650
- const serverService = new ServerService(ctx);
1651
- const userService = new UserService(ctx);
1652
2032
  const atGuild = session.guildId != null;
1653
2033
  if (options.channel) {
1654
2034
  if (!atGuild) return session.text(".no-channel");
1655
- if (!hasPermission(isGuildAdmin(session), isPluginAdmin(session, config)))
2035
+ if (!hasPermission(await isGuildAdmin(session), isPluginAdmin(session, config)))
1656
2036
  return session.text(".no-auth");
1657
2037
  const userDefaultServer2 = await serverService.getDefaultServerByUid(session.user.id);
1658
2038
  const userDefaultServerId2 = userDefaultServer2 ? userDefaultServer2.id : 0;
@@ -1855,25 +2235,6 @@ async function showServerMenu(ctx, session, serverService, userService, server2,
1855
2235
  }
1856
2236
  if (selectNum === 3) {
1857
2237
  await serverService.removeServer(server2.id);
1858
- if (from === "user") {
1859
- if (userDefaultServerId === server2.id) {
1860
- const res = await serverService.getServersByUid(uid);
1861
- if (res.length === 0) {
1862
- await userService.updateUserDefaultServer(uid, 0);
1863
- } else {
1864
- await userService.updateUserDefaultServer(uid, res[0].id);
1865
- }
1866
- }
1867
- } else {
1868
- if (channelDefaultServerId === server2.id) {
1869
- const res = await serverService.getServersByCid(cid);
1870
- if (res.length === 0) {
1871
- await userService.updateChannelDefaultServer(session.platform, cid, 0);
1872
- } else {
1873
- await userService.updateChannelDefaultServer(session.platform, cid, res[0].id);
1874
- }
1875
- }
1876
- }
1877
2238
  return session.text(".menu-3-success");
1878
2239
  }
1879
2240
  if (selectNum === 4) {
@@ -1994,16 +2355,35 @@ async function addServer(ctx, session, serverService, userService, from, uid, us
1994
2355
  }
1995
2356
  __name(addServer, "addServer");
1996
2357
 
2358
+ // src/core/commands/settings.ts
2359
+ function settings(ctx, apiCtx) {
2360
+ ctx.command("settings", { slash: true }).alias("设置", "setting").userFields(["id"]).channelFields(["id"]).action(async ({ session }) => {
2361
+ const atGuild = session.guildId != null;
2362
+ if (atGuild) return session.text(".dm-only");
2363
+ const { secret, tokenTtl, frontendUrl } = apiCtx;
2364
+ const token = createToken(session.user.id, secret, tokenTtl);
2365
+ const url = frontendUrl ? `${frontendUrl}?token=${token}` : `Token: ${token}`;
2366
+ return session.text(".success", { url, minutes: Math.floor(tokenTtl / 60) });
2367
+ });
2368
+ }
2369
+ __name(settings, "settings");
2370
+
1997
2371
  // src/core/command.ts
1998
2372
  var name2 = "command";
1999
2373
  function apply2(ctx, config) {
2374
+ const cardService = new CardService(ctx);
2375
+ const serverService = new ServerService(ctx);
2376
+ const userService = new UserService(ctx);
2000
2377
  help(ctx, config);
2001
- bind(ctx, config);
2002
- card(ctx, config);
2003
- server(ctx, config);
2378
+ bind(ctx, config, cardService, serverService, userService);
2379
+ card(ctx, config, cardService, serverService, userService);
2380
+ server(ctx, config, serverService, userService);
2004
2381
  locale(ctx, config);
2005
2382
  link(ctx, config);
2006
2383
  maintain(ctx, config);
2384
+ if (config._apiCtx) {
2385
+ settings(ctx, config._apiCtx);
2386
+ }
2007
2387
  }
2008
2388
  __name(apply2, "apply");
2009
2389
 
@@ -2178,7 +2558,7 @@ function guildNameCard(ctx, config) {
2178
2558
  bot.selfId,
2179
2559
  nextNameCard
2180
2560
  );
2181
- ctx.logger("guild-namecard").info(
2561
+ ctx.logger("guild-namecard").debug(
2182
2562
  `更新 ${bot.selfId} 在 ${guild.name} 的群名片: ${currentNameCard} -> ${nextNameCard}`
2183
2563
  );
2184
2564
  try {
@@ -2248,17 +2628,17 @@ function apply4(ctx, config) {
2248
2628
  __name(apply4, "apply");
2249
2629
 
2250
2630
  // src/core/locales/en-US.yml
2251
- 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!", 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>
2631
+ 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", tokenTtl: "**Settings Panel** Link expiry time (seconds)", frontendUrl: "**Settings Panel** Frontend URL (/settings will generate a full link when set)", corsOrigin: "**Settings Panel** CORS allowed origin (set to frontend domain in production)" }, selector: { "card-not-found": "<p>You haven't bound a card yet, go bind one first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Quit</p>", "server-menu-select": "<p>Please select the server you want to use:</p>\n{server_list}\n<p>q. Quit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Quit!</p>", "default-card": "Default", "default-server": "Default" }, 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!", help: { description: "Show Noah help information", messages: { content: "<p>Guide:</p>\n<p>https://docs.logthm.cn/noah</p>", "qq-extra": "<p>Having issues? Join group 723977027 to give feedback~</p>" } }, 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>
2252
2632
  <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>
2253
2633
  <p>{0}</p>
2254
- <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>" } } } };
2634
+ <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>" } }, settings: { description: "Get settings panel link", messages: { "dm-only": "Please use this command in a direct message.", success: "<p>Open the settings panel via the link below:</p>\n<p>{url}</p>\n<p>This link expires in {minutes} minutes. Do not share it.</p>" } } } };
2255
2635
 
2256
2636
  // src/core/locales/zh-CN.yml
2257
- 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 没等到你的回复,请重试!", 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>" } } } };
2637
+ var zh_CN_default2 = { _config: { $desc: "Core 模块设置", adminUsers: "**插件管理员** 的用户id (使用 inspect 指令获取)", guildNameCards: "**群聊卡片** 的名称列表", tokenTtl: "**设置面板** 链接有效期(秒)", frontendUrl: "**设置面板** 前端地址(配置后 /settings 会生成完整链接)", corsOrigin: "**设置面板** CORS 允许来源(生产环境请设为前端域名)", maoServerUrl: "**猫网服务器** 的 URL 地址", official_support_url: "**官方支持** 的 URL 地址" }, selector: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "server-menu-select": "<p>请选择你要使用的服务器:</p>\n{server_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>", "default-card": "默认卡片", "default-server": "默认服务器" }, commands: { maintain: { description: "切换到维护模式", messages: { "no-auth": "<p>你没有权限使用本功能哦~</p>", start: "<p>正在进入维护模式</p>", "success-start": "<p>成功切换到维护模式</p>", stop: "<p>正在退出维护模式</p>", "success-stop": "<p>成功退出维护模式</p>" } }, timeout: "Noah 没等到你的回复,请重试!", help: { description: "显示 Noah 帮助信息", messages: { content: "<p>使用文档:</p>\n<p>https://docs.logthm.cn/noah</p>", "qq-extra": "<p>遇到问题了?加群 723977027 反馈~</p>" } }, 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>" } }, settings: { description: "获取设置面板链接", messages: { "dm-only": "请在私聊中使用此命令。", success: "<p>点击下方链接打开设置面板:</p>\n<p>{url}</p>\n<p>链接有效期 {minutes} 分钟,请勿分享给他人。</p>" } } } };
2258
2638
 
2259
2639
  // src/core/index.ts
2260
2640
  var name5 = "Noah-Core";
2261
- var inject = ["database", "globalConfig"];
2641
+ var inject = ["database", "globalConfig", "server"];
2262
2642
  var logger2 = new import_koishi6.Logger("Noah-Core");
2263
2643
  function apply5(ctx, config) {
2264
2644
  ;
@@ -2266,9 +2646,11 @@ function apply5(ctx, config) {
2266
2646
  ["en-US", en_US_default2],
2267
2647
  ["zh-CN", zh_CN_default2]
2268
2648
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
2649
+ const apiCtx = resolveApiContext(config.core);
2269
2650
  ctx.plugin(database_exports, config.core);
2270
- ctx.plugin(command_exports, config.core);
2651
+ ctx.plugin(command_exports, { ...config.core, _apiCtx: apiCtx });
2271
2652
  ctx.plugin(event_exports, config.core);
2653
+ registerApiRoutes(ctx, apiCtx);
2272
2654
  }
2273
2655
  __name(apply5, "apply");
2274
2656
 
@@ -2287,6 +2669,7 @@ var BaseDrawer = class {
2287
2669
  constructor(ctx) {
2288
2670
  this.ctx = ctx;
2289
2671
  }
2672
+ ctx;
2290
2673
  static {
2291
2674
  __name(this, "BaseDrawer");
2292
2675
  }
@@ -2326,6 +2709,7 @@ var CoreDrawer = class extends BaseDrawer {
2326
2709
  super(ctx);
2327
2710
  this.ctx = ctx;
2328
2711
  }
2712
+ ctx;
2329
2713
  static {
2330
2714
  __name(this, "CoreDrawer");
2331
2715
  }
@@ -2361,6 +2745,7 @@ var IIDXDrawer = class extends BaseDrawer {
2361
2745
  super(ctx);
2362
2746
  this.ctx = ctx;
2363
2747
  }
2748
+ ctx;
2364
2749
  static {
2365
2750
  __name(this, "IIDXDrawer");
2366
2751
  }
@@ -2645,12 +3030,12 @@ function getNextVFIncrease(level, currentScore, clearType) {
2645
3030
  }
2646
3031
  __name(getNextVFIncrease, "getNextVFIncrease");
2647
3032
  function getGradeRange(grade) {
2648
- if (!grade) return ALL_GRADES;
3033
+ if (!grade) return ALL_GRADES.slice(0, ALL_GRADES.indexOf("A") + 1);
2649
3034
  return Array.isArray(grade) ? grade : [grade];
2650
3035
  }
2651
3036
  __name(getGradeRange, "getGradeRange");
2652
3037
  function getClearTypeRange(clearType) {
2653
- if (!clearType) return ALL_CLEAR_TYPES;
3038
+ if (!clearType) return ALL_CLEAR_TYPES.slice(0, ALL_CLEAR_TYPES.indexOf("NC") + 1);
2654
3039
  return Array.isArray(clearType) ? clearType : [clearType];
2655
3040
  }
2656
3041
  __name(getClearTypeRange, "getClearTypeRange");
@@ -2866,7 +3251,20 @@ function generateQueryResults(params) {
2866
3251
  uniqueResults.push(result);
2867
3252
  }
2868
3253
  }
2869
- const filteredResults = uniqueResults.filter((result) => result.clearType !== "S-PUC");
3254
+ const excludeLevelSet = params.excludeLevel ? new Set(params.excludeLevel.map((l) => Math.round(l * 10))) : null;
3255
+ const excludeScoreSet = params.excludeScore ? new Set(params.excludeScore) : null;
3256
+ const excludeGradeSet = params.excludeGrade ? new Set(params.excludeGrade) : null;
3257
+ const excludeClearTypeSet = params.excludeClearType ? new Set(params.excludeClearType) : null;
3258
+ const excludeVfSet = params.excludeVf ? new Set(params.excludeVf.map((v) => Math.round(v * 20))) : null;
3259
+ const afterExclusion = uniqueResults.filter((result) => {
3260
+ if (excludeLevelSet && excludeLevelSet.has(Math.round(result.level * 10))) return false;
3261
+ if (excludeScoreSet && excludeScoreSet.has(result.score)) return false;
3262
+ if (excludeGradeSet && excludeGradeSet.has(result.grade)) return false;
3263
+ if (excludeClearTypeSet && excludeClearTypeSet.has(result.clearType)) return false;
3264
+ if (excludeVfSet && excludeVfSet.has(Math.round(result.vf * 20))) return false;
3265
+ return true;
3266
+ });
3267
+ const filteredResults = afterExclusion.filter((result) => result.clearType !== "S-PUC");
2870
3268
  return filteredResults.sort((a, b) => b.vf - a.vf);
2871
3269
  }
2872
3270
  __name(generateQueryResults, "generateQueryResults");
@@ -2877,6 +3275,7 @@ var SDVXDrawer = class extends BaseDrawer {
2877
3275
  super(ctx);
2878
3276
  this.ctx = ctx;
2879
3277
  }
3278
+ ctx;
2880
3279
  static {
2881
3280
  __name(this, "SDVXDrawer");
2882
3281
  }
@@ -2893,8 +3292,8 @@ var SDVXDrawer = class extends BaseDrawer {
2893
3292
  ["Fredoka One", "fonts/FredokaOne.ttf"]
2894
3293
  ];
2895
3294
  for (const [name15, file] of fonts) {
2896
- const path3 = getAssetPath(this.ctx, file);
2897
- if (fs2.existsSync(path3)) FontLibrary.use(name15, path3);
3295
+ const path4 = getAssetPath(this.ctx, file);
3296
+ if (fs2.existsSync(path4)) FontLibrary.use(name15, path4);
2898
3297
  }
2899
3298
  this.fontsLoaded = true;
2900
3299
  }
@@ -3000,20 +3399,20 @@ var SDVXDrawer = class extends BaseDrawer {
3000
3399
  ctx.save();
3001
3400
  ctx.translate(556, 17);
3002
3401
  ctx.beginPath();
3003
- const r = 16, w = 100, h10 = 200, x0 = 0, y0 = 0;
3402
+ const r = 16, w = 100, h12 = 200, x0 = 0, y0 = 0;
3004
3403
  ctx.moveTo(x0 + r, y0);
3005
3404
  ctx.lineTo(x0 + w - r, y0);
3006
3405
  ctx.arcTo(x0 + w, y0, x0 + w, y0 + r, r);
3007
- ctx.lineTo(x0 + w, y0 + h10 - r);
3008
- ctx.arcTo(x0 + w, y0 + h10, x0 + w - r, y0 + h10, r);
3009
- ctx.lineTo(x0 + r, y0 + h10);
3010
- ctx.arcTo(x0, y0 + h10, x0, y0 + h10 - r, r);
3406
+ ctx.lineTo(x0 + w, y0 + h12 - r);
3407
+ ctx.arcTo(x0 + w, y0 + h12, x0 + w - r, y0 + h12, r);
3408
+ ctx.lineTo(x0 + r, y0 + h12);
3409
+ ctx.arcTo(x0, y0 + h12, x0, y0 + h12 - r, r);
3011
3410
  ctx.lineTo(x0, y0 + r);
3012
3411
  ctx.arcTo(x0, y0, x0 + r, y0, r);
3013
3412
  ctx.closePath();
3014
3413
  const circleRadius = 14;
3015
3414
  const circleX = x0 + w;
3016
- const circleY = y0 + h10 / 2;
3415
+ const circleY = y0 + h12 / 2;
3017
3416
  ctx.moveTo(circleX + circleRadius, circleY);
3018
3417
  ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2, true);
3019
3418
  ctx.clip();
@@ -3765,6 +4164,7 @@ var DrawerFactory = class {
3765
4164
  constructor(ctx) {
3766
4165
  this.ctx = ctx;
3767
4166
  }
4167
+ ctx;
3768
4168
  static {
3769
4169
  __name(this, "DrawerFactory");
3770
4170
  }
@@ -3841,154 +4241,20 @@ __name(apply6, "apply");
3841
4241
  var poke_exports = {};
3842
4242
  __export(poke_exports, {
3843
4243
  apply: () => apply7,
4244
+ logger: () => logger3,
3844
4245
  name: () => name7
3845
4246
  });
3846
- var fs3 = __toESM(require("fs"), 1);
3847
- var path2 = __toESM(require("path"), 1);
3848
- var import_koishi8 = require("koishi");
3849
-
3850
- // src/fun/poke/locales/en-US.yml
3851
- var en_US_default3 = { _config: { $desc: "Poke Module Settings", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: "警告内容", messages: { $desc: "消息内容", content: "消息内容", weight: "权重" } }, commands: { poke: { description: "poke" } } };
3852
-
3853
- // src/fun/poke/locales/zh-CN.yml
3854
- var zh_CN_default3 = { _config: { $desc: "Poke 模块设置", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: { $desc: "警告内容", content: "消息", weight: "权重" }, messages: { $desc: "消息内容", content: "消息", weight: "权重" } }, commands: { poke: { description: "戳一戳" } } };
4247
+ var import_koishi10 = require("koishi");
3855
4248
 
3856
- // src/fun/poke/index.ts
3857
- var name7 = "Noah-Poke";
3858
- function apply7(ctx, config) {
3859
- ;
3860
- [
3861
- ["en-US", en_US_default3],
3862
- ["zh-CN", zh_CN_default3]
3863
- ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
3864
- const cache = /* @__PURE__ */ new Map();
3865
- const pokeConfig2 = config.poke;
3866
- const IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
3867
- function isImageFile(filename) {
3868
- const ext = path2.extname(filename).toLowerCase();
3869
- return IMAGE_EXTENSIONS.includes(ext);
3870
- }
3871
- __name(isImageFile, "isImageFile");
3872
- function getMimeType(filename) {
3873
- const ext = path2.extname(filename).toLowerCase();
3874
- switch (ext) {
3875
- case ".png":
3876
- return "image/png";
3877
- case ".jpg":
3878
- case ".jpeg":
3879
- return "image/jpeg";
3880
- case ".gif":
3881
- return "image/gif";
3882
- case ".webp":
3883
- return "image/webp";
3884
- default:
3885
- return "image/png";
3886
- }
3887
- }
3888
- __name(getMimeType, "getMimeType");
3889
- function findMatchingAudio(imagePath) {
3890
- try {
3891
- const dir = path2.dirname(imagePath);
3892
- const filename = path2.basename(imagePath);
3893
- const match = filename.match(/_(\d+)\.(png|jpg|jpeg|gif|webp)$/i);
3894
- if (!match) {
3895
- return null;
3896
- }
3897
- const imageNumber = match[1];
3898
- const audioNumber = parseInt(imageNumber, 10).toString();
3899
- const files = fs3.readdirSync(dir);
3900
- const audioFile = files.find((file) => {
3901
- return file.endsWith(`-${audioNumber}.mp3`);
3902
- });
3903
- return audioFile ? path2.join(dir, audioFile) : null;
3904
- } catch {
3905
- return null;
3906
- }
3907
- }
3908
- __name(findMatchingAudio, "findMatchingAudio");
3909
- async function sendRandomNoahStamp(ctx2, session, voiceOnly = false) {
3910
- try {
3911
- const stampsPath = getAssetPath(ctx2, "stamps/noah_stamp");
3912
- const stampDirs = fs3.readdirSync(stampsPath).filter((dir) => dir.startsWith("stamp_"));
3913
- if (stampDirs.length === 0) {
3914
- await session.sendQueued("No stamps found!");
3915
- return;
3916
- }
3917
- const allStamps = [];
3918
- for (const dir of stampDirs) {
3919
- const stampDirPath = path2.join(stampsPath, dir);
3920
- const stampFiles = fs3.readdirSync(stampDirPath).filter((file) => file.startsWith(dir) && isImageFile(file));
3921
- stampFiles.forEach((file) => {
3922
- allStamps.push({
3923
- path: path2.join(stampDirPath, file),
3924
- dirName: dir
3925
- });
3926
- });
3927
- }
3928
- if (allStamps.length === 0) {
3929
- await session.sendQueued("No stamps found in any directory!");
3930
- return;
3931
- }
3932
- let availableStamps = allStamps;
3933
- if (voiceOnly) {
3934
- availableStamps = allStamps.filter((stamp) => {
3935
- const audioPath2 = findMatchingAudio(stamp.path);
3936
- return audioPath2 && fs3.existsSync(audioPath2);
3937
- });
3938
- if (availableStamps.length === 0) {
3939
- await session.sendQueued("No stamps with voice found!");
3940
- return;
3941
- }
3942
- }
3943
- const randomStamp = availableStamps[Math.floor(Math.random() * availableStamps.length)];
3944
- ctx2.logger("Noah-Poke").debug(`Selected stamp from ${randomStamp.dirName}`);
3945
- if (!fs3.existsSync(randomStamp.path)) {
3946
- await session.sendQueued("Selected stamp not found!");
3947
- return;
3948
- }
3949
- const imageBuffer = fs3.readFileSync(randomStamp.path);
3950
- const audioPath = findMatchingAudio(randomStamp.path);
3951
- await session.sendQueued(import_koishi8.h.image(imageBuffer, getMimeType(randomStamp.path)));
3952
- if (audioPath && fs3.existsSync(audioPath)) {
3953
- ctx2.logger("Noah-Poke").debug(`Found matching audio: ${path2.basename(audioPath)}`);
3954
- const audioBuffer = fs3.readFileSync(audioPath);
3955
- await session.sendQueued(import_koishi8.h.audio(audioBuffer, "audio/mpeg"));
3956
- }
3957
- } catch (error) {
3958
- ctx2.logger("Noah-Poke").error(`Error sending noah stamp: ${error.message}`);
3959
- await session.sendQueued("Failed to send noah stamp.");
3960
- }
3961
- }
3962
- __name(sendRandomNoahStamp, "sendRandomNoahStamp");
3963
- async function sendRandomChatStamp(ctx2) {
3964
- try {
3965
- const stampsPath = getAssetPath(ctx2, "stamps/chat_stamp");
3966
- const stampFiles = fs3.readdirSync(stampsPath).filter((file) => isImageFile(file));
3967
- if (stampFiles.length === 0) {
3968
- return "No chat stamps found!";
3969
- }
3970
- const randomStamp = stampFiles[Math.floor(Math.random() * stampFiles.length)];
3971
- const stampPath = path2.join(stampsPath, randomStamp);
3972
- if (!fs3.existsSync(stampPath)) {
3973
- return "Selected stamp not found!";
3974
- }
3975
- const imageBuffer = fs3.readFileSync(stampPath);
3976
- return import_koishi8.h.image(imageBuffer, getMimeType(stampPath));
3977
- } catch (error) {
3978
- ctx2.logger("Noah-Poke").error(`Error sending chat stamp: ${error.message}`);
3979
- return "Failed to send chat stamp.";
3980
- }
3981
- }
3982
- __name(sendRandomChatStamp, "sendRandomChatStamp");
3983
- ctx.command("stamp").option("type", "-t [type]").option("voice", "-v", { fallback: false }).action(async ({ session, options }) => {
3984
- if (options.type == "noah") {
3985
- await sendRandomNoahStamp(ctx, session, options.voice);
3986
- } else if (options.type == "chat") {
3987
- return sendRandomChatStamp(ctx);
3988
- } else {
3989
- return sendRandomChatStamp(ctx);
3990
- }
3991
- });
4249
+ // src/fun/poke/commands/poke.ts
4250
+ function parsePlatform(target) {
4251
+ const index = target.indexOf(":");
4252
+ const platform = target.slice(0, index);
4253
+ const id = target.slice(index + 1);
4254
+ return [platform, id];
4255
+ }
4256
+ __name(parsePlatform, "parsePlatform");
4257
+ function registerPokeCommand(ctx) {
3992
4258
  ctx.platform("onebot").command("poke [target:user]").action(async ({ session }, target) => {
3993
4259
  if (!session.onebot) {
3994
4260
  return;
@@ -3996,7 +4262,7 @@ function apply7(ctx, config) {
3996
4262
  const params = { user_id: session.userId };
3997
4263
  if (target) {
3998
4264
  const [platform, id] = parsePlatform(target);
3999
- if (platform != "onebot") {
4265
+ if (platform !== "onebot") {
4000
4266
  return;
4001
4267
  }
4002
4268
  params.user_id = id;
@@ -4004,66 +4270,292 @@ function apply7(ctx, config) {
4004
4270
  if (session.isDirect) {
4005
4271
  await session.onebot._request("friend_poke", params);
4006
4272
  } else {
4007
- params["group_id"] = session.guildId;
4273
+ params.group_id = session.guildId;
4008
4274
  await session.onebot._request("group_poke", params);
4009
4275
  }
4010
4276
  });
4011
- ctx.platform("onebot").on("notice", async (session) => {
4012
- if (session.subtype != "poke") {
4277
+ }
4278
+ __name(registerPokeCommand, "registerPokeCommand");
4279
+
4280
+ // src/fun/poke/utils/stamp.ts
4281
+ var fs4 = __toESM(require("fs"), 1);
4282
+ var path3 = __toESM(require("path"), 1);
4283
+ var import_koishi8 = require("koishi");
4284
+
4285
+ // src/fun/poke/utils/file.ts
4286
+ var fs3 = __toESM(require("fs"), 1);
4287
+ var path2 = __toESM(require("path"), 1);
4288
+
4289
+ // src/fun/poke/constants.ts
4290
+ var IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
4291
+ var MIME_MAP = {
4292
+ ".png": "image/png",
4293
+ ".jpg": "image/jpeg",
4294
+ ".jpeg": "image/jpeg",
4295
+ ".gif": "image/gif",
4296
+ ".webp": "image/webp"
4297
+ };
4298
+ var DEFAULT_MIME = "image/png";
4299
+ var DEFAULT_LOCALE = "zh-CN";
4300
+ var TIP_POOL = {
4301
+ "zh-CN": [
4302
+ "你知道吗: 《FIN4LE ~終止線の彼方へ~》在发布 337 天后才出现首个 PUC。",
4303
+ "你知道吗: Near 是双子中的姐姐哟~",
4304
+ "你知道吗: Noah 是双子中的妹妹哟~",
4305
+ "你知道吗: 可以看刘海方向来区分 Near 和 Noah。",
4306
+ "你知道吗: Near 和 Noah 就读于ボルテ学園初等部。",
4307
+ "你知道吗: 元气满满的是姐姐 Near,文静的是妹妹 Noah。",
4308
+ "你知道吗: Near 和 Noah 的兔子鞋是烈风刀送给她们的。",
4309
+ "你知道吗: Near 和 Noah 都会飞哟~",
4310
+ "你知道吗: Near 和 Noah 初登场于 BOOTH 代歌曲《freaky freak》。",
4311
+ "你知道吗: Near 和 Noah 的 CV 为 日高里菜。",
4312
+ "你知道吗: Near 和 Noah 的身高是 140 cm。",
4313
+ "你知道吗: Near 和 Noah 的生日是 6 月 10 日。"
4314
+ ],
4315
+ "en-US": [
4316
+ "Do you know: FIN4LE ~終止線の彼方へ~ took 337 days to get its first PUC.",
4317
+ "Do you know: Near is the older sister of the twin.",
4318
+ "Do you know: Noah is the younger sister of the twin.",
4319
+ "Do you know: You can tell Near and Noah apart by their hair direction.",
4320
+ "Do you know: Near and Noah attend ボルテ学園初等部.",
4321
+ "Do you know: The energetic Near is the older sister, while the quiet Noah is the younger sister.",
4322
+ "Do you know: Near and Noah received their rabbit shoes from 烈风刀.",
4323
+ "Do you know: Near and Noah can fly.",
4324
+ 'Do you know: Near and Noah made their debut in the BOOTH song "freaky freak".',
4325
+ "Do you know: Near and Noah's CV is 日高里菜.",
4326
+ "Do you know: Near and Noah's height is 140 cm.",
4327
+ "Do you know: Near and Noah's birthday is June 10th."
4328
+ ]
4329
+ };
4330
+
4331
+ // src/fun/poke/utils/file.ts
4332
+ function isImageFile(filename) {
4333
+ const ext = path2.extname(filename).toLowerCase();
4334
+ return IMAGE_EXTENSIONS.includes(ext);
4335
+ }
4336
+ __name(isImageFile, "isImageFile");
4337
+ function getMimeType(filename) {
4338
+ const ext = path2.extname(filename).toLowerCase();
4339
+ return MIME_MAP[ext] ?? DEFAULT_MIME;
4340
+ }
4341
+ __name(getMimeType, "getMimeType");
4342
+ function findMatchingAudio(imagePath) {
4343
+ try {
4344
+ const dir = path2.dirname(imagePath);
4345
+ const filename = path2.basename(imagePath);
4346
+ const match = filename.match(/_(\d+)\.(png|jpg|jpeg|gif|webp)$/i);
4347
+ if (!match) {
4348
+ return null;
4349
+ }
4350
+ const audioNumber = parseInt(match[1], 10).toString();
4351
+ const files = fs3.readdirSync(dir);
4352
+ const audioFile = files.find((file) => file.endsWith(`-${audioNumber}.mp3`));
4353
+ return audioFile ? path2.join(dir, audioFile) : null;
4354
+ } catch {
4355
+ return null;
4356
+ }
4357
+ }
4358
+ __name(findMatchingAudio, "findMatchingAudio");
4359
+
4360
+ // src/fun/poke/utils/random.ts
4361
+ function randomMessage(items) {
4362
+ if (items.length === 0) {
4363
+ return void 0;
4364
+ }
4365
+ const totalWeight = items.reduce((sum2, cur) => sum2 + cur.weight, 0);
4366
+ const random = Math.random() * totalWeight;
4367
+ let sum = 0;
4368
+ for (const item of items) {
4369
+ sum += item.weight;
4370
+ if (random < sum) return item;
4371
+ }
4372
+ return items[items.length - 1];
4373
+ }
4374
+ __name(randomMessage, "randomMessage");
4375
+ function pickRandom(items) {
4376
+ if (items.length === 0) {
4377
+ return void 0;
4378
+ }
4379
+ return items[Math.floor(Math.random() * items.length)];
4380
+ }
4381
+ __name(pickRandom, "pickRandom");
4382
+
4383
+ // src/fun/poke/utils/stamp.ts
4384
+ var LOGGER_NAME = "Noah-Poke";
4385
+ function collectNoahStamps(stampsPath) {
4386
+ const stampDirs = fs4.readdirSync(stampsPath).filter((dir) => dir.startsWith("stamp_"));
4387
+ const allStamps = [];
4388
+ for (const dir of stampDirs) {
4389
+ const stampDirPath = path3.join(stampsPath, dir);
4390
+ const stampFiles = fs4.readdirSync(stampDirPath).filter((file) => file.startsWith(dir) && isImageFile(file));
4391
+ stampFiles.forEach((file) => {
4392
+ allStamps.push({ path: path3.join(stampDirPath, file), dirName: dir });
4393
+ });
4394
+ }
4395
+ return allStamps;
4396
+ }
4397
+ __name(collectNoahStamps, "collectNoahStamps");
4398
+ async function sendRandomNoahStamp(ctx, session, voiceOnly = false) {
4399
+ const logger6 = ctx.logger(LOGGER_NAME);
4400
+ try {
4401
+ const stampsPath = getAssetPath(ctx, "stamps/noah_stamp");
4402
+ const allStamps = collectNoahStamps(stampsPath);
4403
+ if (allStamps.length === 0) {
4404
+ await session.sendQueued("No stamps found!");
4013
4405
  return;
4014
4406
  }
4015
- if (session.targetId != session.selfId) {
4407
+ const availableStamps = voiceOnly ? allStamps.filter((stamp) => {
4408
+ const audioPath2 = findMatchingAudio(stamp.path);
4409
+ return audioPath2 && fs4.existsSync(audioPath2);
4410
+ }) : allStamps;
4411
+ if (availableStamps.length === 0) {
4412
+ await session.sendQueued("No stamps with voice found!");
4016
4413
  return;
4017
4414
  }
4018
- if (pokeConfig2.interval > 0 && cache.has(session.userId)) {
4415
+ const randomStamp = pickRandom(availableStamps);
4416
+ logger6.debug(`Selected stamp from ${randomStamp.dirName}`);
4417
+ if (!fs4.existsSync(randomStamp.path)) {
4418
+ await session.sendQueued("Selected stamp not found!");
4419
+ return;
4420
+ }
4421
+ const imageBuffer = fs4.readFileSync(randomStamp.path);
4422
+ await session.sendQueued(import_koishi8.h.image(imageBuffer, getMimeType(randomStamp.path)));
4423
+ const audioPath = findMatchingAudio(randomStamp.path);
4424
+ if (audioPath && fs4.existsSync(audioPath)) {
4425
+ logger6.debug(`Found matching audio: ${path3.basename(audioPath)}`);
4426
+ const audioBuffer = fs4.readFileSync(audioPath);
4427
+ await session.sendQueued(import_koishi8.h.audio(audioBuffer, "audio/mpeg"));
4428
+ }
4429
+ } catch (error) {
4430
+ logger6.error(`Error sending noah stamp: ${error.message}`);
4431
+ await session.sendQueued("Failed to send noah stamp.");
4432
+ }
4433
+ }
4434
+ __name(sendRandomNoahStamp, "sendRandomNoahStamp");
4435
+ function getRandomChatStamp(ctx) {
4436
+ const logger6 = ctx.logger(LOGGER_NAME);
4437
+ try {
4438
+ const stampsPath = getAssetPath(ctx, "stamps/chat_stamp");
4439
+ const stampFiles = fs4.readdirSync(stampsPath).filter((file) => isImageFile(file));
4440
+ if (stampFiles.length === 0) {
4441
+ return "No chat stamps found!";
4442
+ }
4443
+ const randomStamp = pickRandom(stampFiles);
4444
+ const stampPath = path3.join(stampsPath, randomStamp);
4445
+ if (!fs4.existsSync(stampPath)) {
4446
+ return "Selected stamp not found!";
4447
+ }
4448
+ const imageBuffer = fs4.readFileSync(stampPath);
4449
+ return import_koishi8.h.image(imageBuffer, getMimeType(stampPath));
4450
+ } catch (error) {
4451
+ logger6.error(`Error sending chat stamp: ${error.message}`);
4452
+ return "Failed to send chat stamp.";
4453
+ }
4454
+ }
4455
+ __name(getRandomChatStamp, "getRandomChatStamp");
4456
+
4457
+ // src/fun/poke/commands/stamp.ts
4458
+ function registerStampCommand(ctx) {
4459
+ ctx.command("stamp").option("type", "-t [type]").option("voice", "-v", { fallback: false }).action(async ({ session, options }) => {
4460
+ if (options.type === "noah") {
4461
+ await sendRandomNoahStamp(ctx, session, options.voice);
4462
+ return;
4463
+ }
4464
+ return getRandomChatStamp(ctx);
4465
+ });
4466
+ }
4467
+ __name(registerStampCommand, "registerStampCommand");
4468
+
4469
+ // src/fun/poke/events/notice.ts
4470
+ var import_koishi9 = require("koishi");
4471
+
4472
+ // src/fun/poke/utils/tip.ts
4473
+ async function pickTip(session) {
4474
+ const user = await session.observeUser(["locales"]);
4475
+ const locales = user.locales ?? [];
4476
+ for (const locale2 of locales) {
4477
+ const tips = TIP_POOL[locale2];
4478
+ if (tips && tips.length > 0) {
4479
+ return pickRandom(tips);
4480
+ }
4481
+ }
4482
+ return pickRandom(TIP_POOL[DEFAULT_LOCALE] ?? []);
4483
+ }
4484
+ __name(pickTip, "pickTip");
4485
+
4486
+ // src/fun/poke/events/notice.ts
4487
+ function registerPokeNotice(ctx, config, cache) {
4488
+ ctx.platform("onebot").on("notice", async (session) => {
4489
+ if (session.subtype !== "poke" || session.targetId !== session.selfId) {
4490
+ return;
4491
+ }
4492
+ if (config.interval > 0 && cache.has(session.userId)) {
4019
4493
  const ts = cache.get(session.userId);
4020
- if (session.timestamp - ts < pokeConfig2.interval) {
4021
- if (pokeConfig2.warning) {
4022
- const msg = randomMessage(pokeConfig2.prompt);
4023
- const content = import_koishi8.h.parse(msg, session);
4024
- session.sendQueued(content);
4494
+ if (session.timestamp - ts < config.interval) {
4495
+ if (config.warning) {
4496
+ const msg2 = randomMessage(config.prompt);
4497
+ if (msg2) {
4498
+ await session.sendQueued(import_koishi9.h.parse(msg2.content, session));
4499
+ }
4025
4500
  }
4026
4501
  return;
4027
4502
  }
4028
4503
  }
4029
4504
  cache.set(session.userId, session.timestamp);
4030
- if (pokeConfig2.messages.length > 0) {
4031
- const msg = randomMessage(pokeConfig2.messages);
4032
- const content = import_koishi8.h.parse(msg, session);
4033
- await session.sendQueued(content);
4505
+ if (config.messages.length === 0) {
4506
+ return;
4507
+ }
4508
+ const msg = randomMessage(config.messages);
4509
+ if (!msg) {
4510
+ return;
4511
+ }
4512
+ if (msg.type === "noah") {
4513
+ await sendRandomNoahStamp(ctx, session);
4514
+ } else if (msg.type === "chat") {
4515
+ await session.sendQueued(getRandomChatStamp(ctx));
4516
+ } else {
4517
+ const tip = await pickTip(session);
4518
+ if (tip) {
4519
+ await session.sendQueued(import_koishi9.h.parse(tip, session));
4520
+ }
4034
4521
  }
4035
4522
  });
4036
4523
  }
4037
- __name(apply7, "apply");
4038
- function randomMessage(messages) {
4039
- const totalWeight = messages.reduce((sum2, cur) => sum2 + cur.weight, 0);
4040
- const random = Math.random() * totalWeight;
4041
- let sum = 0;
4042
- for (const message of messages) {
4043
- sum += message.weight;
4044
- if (random < sum) return message.content;
4045
- }
4046
- }
4047
- __name(randomMessage, "randomMessage");
4048
- function parsePlatform(target) {
4049
- const index = target.indexOf(":");
4050
- const platform = target.slice(0, index);
4051
- const id = target.slice(index + 1);
4052
- return [platform, id];
4524
+ __name(registerPokeNotice, "registerPokeNotice");
4525
+
4526
+ // src/fun/poke/locales/en-US.yml
4527
+ var en_US_default3 = { _config: { $desc: "Poke Module Settings", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: "警告内容", messages: { $desc: "消息内容", type: "Type (text=random tip / noah=voice stamp / chat=chat stamp)", weight: "权重" } }, commands: { poke: { description: "poke" } } };
4528
+
4529
+ // src/fun/poke/locales/zh-CN.yml
4530
+ var zh_CN_default3 = { _config: { $desc: "Poke 模块设置", interval: "最小触发间隔(毫秒)", warning: "频繁触发是否发送警告", prompt: { $desc: "警告内容", content: "消息", weight: "权重" }, messages: { $desc: "消息内容", type: "类型(text=随机提示 / noah=语音表情 / chat=聊天表情)", weight: "权重" } }, commands: { poke: { description: "戳一戳" } } };
4531
+
4532
+ // src/fun/poke/index.ts
4533
+ var name7 = "Noah-Poke";
4534
+ var logger3 = new import_koishi10.Logger("Noah-Poke");
4535
+ function apply7(ctx, config) {
4536
+ ;
4537
+ [
4538
+ ["en-US", en_US_default3],
4539
+ ["zh-CN", zh_CN_default3]
4540
+ ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
4541
+ const cache = /* @__PURE__ */ new Map();
4542
+ registerStampCommand(ctx);
4543
+ registerPokeCommand(ctx);
4544
+ registerPokeNotice(ctx, config.poke, cache);
4053
4545
  }
4054
- __name(parsePlatform, "parsePlatform");
4546
+ __name(apply7, "apply");
4055
4547
 
4056
4548
  // src/games/general/index.ts
4057
4549
  var general_exports = {};
4058
4550
  __export(general_exports, {
4059
4551
  apply: () => apply8,
4060
- logger: () => logger3,
4552
+ logger: () => logger4,
4061
4553
  name: () => name8
4062
4554
  });
4063
- var import_koishi10 = require("koishi");
4555
+ var import_koishi12 = require("koishi");
4064
4556
 
4065
4557
  // src/games/general/events/quote.ts
4066
- var import_koishi9 = require("koishi");
4558
+ var import_koishi11 = require("koishi");
4067
4559
 
4068
4560
  // src/games/general/utils/codeReader.ts
4069
4561
  async function readCode128(ctx, barcodeApiUrl, imageUrl) {
@@ -4089,8 +4581,8 @@ __name(readCode128, "readCode128");
4089
4581
  function quote(ctx, config) {
4090
4582
  ctx.on("message", async (session) => {
4091
4583
  if (session.quote && session.quote.user.id === session.selfId) {
4092
- const images = await import_koishi9.h.select(session.quote.elements, "img");
4093
- const textElements = await import_koishi9.h.select(session.elements, "text");
4584
+ const images = await import_koishi11.h.select(session.quote.elements, "img");
4585
+ const textElements = await import_koishi11.h.select(session.elements, "text");
4094
4586
  const allText = textElements.map((el) => el.attrs?.content || "").join(" ");
4095
4587
  if (images.length === 1) {
4096
4588
  const imageUrl = images[0].attrs.src;
@@ -4116,7 +4608,7 @@ __name(quote, "quote");
4116
4608
 
4117
4609
  // src/games/general/index.ts
4118
4610
  var name8 = "Noah-General";
4119
- var logger3 = new import_koishi10.Logger("Noah-General");
4611
+ var logger4 = new import_koishi12.Logger("Noah-General");
4120
4612
  async function apply8(ctx, config) {
4121
4613
  quote(ctx, config.general);
4122
4614
  }
@@ -4127,10 +4619,10 @@ var sdvx_exports = {};
4127
4619
  __export(sdvx_exports, {
4128
4620
  apply: () => apply13,
4129
4621
  inject: () => inject3,
4130
- logger: () => logger4,
4622
+ logger: () => logger5,
4131
4623
  name: () => name13
4132
4624
  });
4133
- var import_koishi18 = require("koishi");
4625
+ var import_koishi21 = require("koishi");
4134
4626
 
4135
4627
  // src/servers/index.ts
4136
4628
  var servers_exports = {};
@@ -4142,7 +4634,7 @@ __export(servers_exports, {
4142
4634
  });
4143
4635
 
4144
4636
  // src/servers/Asphyxia/index.ts
4145
- var import_koishi11 = require("koishi");
4637
+ var import_koishi13 = require("koishi");
4146
4638
  var Asphyxia = class {
4147
4639
  static {
4148
4640
  __name(this, "Asphyxia");
@@ -4150,11 +4642,11 @@ var Asphyxia = class {
4150
4642
  name = "asphyxia";
4151
4643
  supportedGames = ["sdvx", "iidx"];
4152
4644
  gameServices = {};
4153
- logger = new import_koishi11.Logger("Noah-Asphyxia");
4645
+ logger = new import_koishi13.Logger("Noah-Asphyxia");
4154
4646
  };
4155
4647
 
4156
4648
  // src/servers/Mao/index.ts
4157
- var import_koishi12 = require("koishi");
4649
+ var import_koishi14 = require("koishi");
4158
4650
  var Mao = class {
4159
4651
  static {
4160
4652
  __name(this, "Mao");
@@ -4162,11 +4654,11 @@ var Mao = class {
4162
4654
  name = "mao";
4163
4655
  supportedGames = ["sdvx"];
4164
4656
  gameServices = {};
4165
- logger = new import_koishi12.Logger("Noah-Mao");
4657
+ logger = new import_koishi14.Logger("Noah-Mao");
4166
4658
  };
4167
4659
 
4168
4660
  // src/servers/Official/index.ts
4169
- var import_koishi13 = require("koishi");
4661
+ var import_koishi15 = require("koishi");
4170
4662
  var Official = class {
4171
4663
  static {
4172
4664
  __name(this, "Official");
@@ -4174,7 +4666,7 @@ var Official = class {
4174
4666
  name = "official";
4175
4667
  supportedGames = ["sdvx"];
4176
4668
  gameServices = {};
4177
- logger = new import_koishi13.Logger("Noah-Official");
4669
+ logger = new import_koishi15.Logger("Noah-Official");
4178
4670
  };
4179
4671
 
4180
4672
  // src/servers/ServerFactory.ts
@@ -4542,13 +5034,13 @@ var AsphyxiaSDVXService = class _AsphyxiaSDVXService {
4542
5034
  logger;
4543
5035
  config;
4544
5036
  cachedModel = null;
4545
- constructor(logger5, config) {
4546
- this.logger = logger5;
5037
+ constructor(logger6, config) {
5038
+ this.logger = logger6;
4547
5039
  this.config = config;
4548
5040
  }
4549
- static getInstance(logger5, config) {
5041
+ static getInstance(logger6, config) {
4550
5042
  if (!_AsphyxiaSDVXService.instance) {
4551
- _AsphyxiaSDVXService.instance = new _AsphyxiaSDVXService(logger5, config);
5043
+ _AsphyxiaSDVXService.instance = new _AsphyxiaSDVXService(logger6, config);
4552
5044
  }
4553
5045
  return _AsphyxiaSDVXService.instance;
4554
5046
  }
@@ -4695,12 +5187,12 @@ var MaoSDVXService = class _MaoSDVXService {
4695
5187
  }
4696
5188
  static instance;
4697
5189
  logger;
4698
- constructor(logger5) {
4699
- this.logger = logger5;
5190
+ constructor(logger6) {
5191
+ this.logger = logger6;
4700
5192
  }
4701
- static getInstance(logger5) {
5193
+ static getInstance(logger6) {
4702
5194
  if (!_MaoSDVXService.instance) {
4703
- _MaoSDVXService.instance = new _MaoSDVXService(logger5);
5195
+ _MaoSDVXService.instance = new _MaoSDVXService(logger6);
4704
5196
  }
4705
5197
  return _MaoSDVXService.instance;
4706
5198
  }
@@ -4994,7 +5486,7 @@ var MaoSDVXService = class _MaoSDVXService {
4994
5486
  baseURL: ctx.globalConfig.maoServerUrl,
4995
5487
  headers: { "X-API-Key": apiKey }
4996
5488
  });
4997
- if (resp?.code !== 0 || !resp?.data || resp.data.length === 0) {
5489
+ if (resp?.code !== 0 || !Array.isArray(resp?.data) || resp.data.length === 0) {
4998
5490
  return [];
4999
5491
  }
5000
5492
  const musicIds = resp.data.map((item) => item.mid);
@@ -5045,12 +5537,12 @@ var OfficialSDVXService = class _OfficialSDVXService {
5045
5537
  }
5046
5538
  static instance;
5047
5539
  logger;
5048
- constructor(logger5) {
5049
- this.logger = logger5;
5540
+ constructor(logger6) {
5541
+ this.logger = logger6;
5050
5542
  }
5051
- static getInstance(logger5) {
5543
+ static getInstance(logger6) {
5052
5544
  if (!_OfficialSDVXService.instance) {
5053
- _OfficialSDVXService.instance = new _OfficialSDVXService(logger5);
5545
+ _OfficialSDVXService.instance = new _OfficialSDVXService(logger6);
5054
5546
  }
5055
5547
  return _OfficialSDVXService.instance;
5056
5548
  }
@@ -5155,15 +5647,15 @@ var OfficialSDVXService = class _OfficialSDVXService {
5155
5647
  };
5156
5648
 
5157
5649
  // src/games/sdvx/adapters/index.ts
5158
- function registerSDVXAdapters(logger5, config) {
5650
+ function registerSDVXAdapters(logger6, config) {
5159
5651
  const serverManager = ServerManager.getInstance();
5160
5652
  serverManager.registerGameService(
5161
5653
  "asphyxia",
5162
5654
  "sdvx",
5163
- AsphyxiaSDVXService.getInstance(logger5, config)
5655
+ AsphyxiaSDVXService.getInstance(logger6, config)
5164
5656
  );
5165
- serverManager.registerGameService("mao", "sdvx", MaoSDVXService.getInstance(logger5));
5166
- serverManager.registerGameService("official", "sdvx", OfficialSDVXService.getInstance(logger5));
5657
+ serverManager.registerGameService("mao", "sdvx", MaoSDVXService.getInstance(logger6));
5658
+ serverManager.registerGameService("official", "sdvx", OfficialSDVXService.getInstance(logger6));
5167
5659
  }
5168
5660
  __name(registerSDVXAdapters, "registerSDVXAdapters");
5169
5661
 
@@ -5175,7 +5667,7 @@ __export(command_exports2, {
5175
5667
  });
5176
5668
 
5177
5669
  // src/games/sdvx/commands/calculate.ts
5178
- var import_koishi14 = require("koishi");
5670
+ var import_koishi16 = require("koishi");
5179
5671
 
5180
5672
  // src/games/sdvx/utils/param-parser.ts
5181
5673
  function parseNumberWithSuffix(str) {
@@ -5363,6 +5855,249 @@ function parseRadarFeature(item) {
5363
5855
  return null;
5364
5856
  }
5365
5857
  __name(parseRadarFeature, "parseRadarFeature");
5858
+ function extractOperator(item) {
5859
+ if (item.startsWith(">=")) return { op: ">=", value: item.slice(2) };
5860
+ if (item.startsWith("<=")) return { op: "<=", value: item.slice(2) };
5861
+ if (item.startsWith("!=")) return { op: "!=", value: item.slice(2) };
5862
+ if (item.startsWith(">")) return { op: ">", value: item.slice(1) };
5863
+ if (item.startsWith("<")) return { op: "<", value: item.slice(1) };
5864
+ if (item.startsWith("=")) return { op: "=", value: item.slice(1) };
5865
+ if (item.startsWith("~")) return { op: "~", value: item.slice(1) };
5866
+ return { op: null, value: item };
5867
+ }
5868
+ __name(extractOperator, "extractOperator");
5869
+ function getAllValidLevels2() {
5870
+ const levels = [];
5871
+ for (let i = 1; i <= 16; i++) levels.push(i);
5872
+ levels.push(17, 17.5);
5873
+ for (let scaled = 180; scaled <= 209; scaled++) levels.push(scaled / 10);
5874
+ return levels;
5875
+ }
5876
+ __name(getAllValidLevels2, "getAllValidLevels");
5877
+ function applyLevelOperator(op, levels) {
5878
+ const allLevels = getAllValidLevels2();
5879
+ const value = levels[0];
5880
+ if (op === "=") return { include: levels, exclude: [] };
5881
+ if (op === "!=") return { include: [], exclude: levels };
5882
+ if (op === "~") {
5883
+ const result = /* @__PURE__ */ new Set();
5884
+ for (const lv of levels) {
5885
+ const idx2 = allLevels.indexOf(lv);
5886
+ if (idx2 === -1) continue;
5887
+ if (idx2 > 0) result.add(allLevels[idx2 - 1]);
5888
+ result.add(allLevels[idx2]);
5889
+ if (idx2 < allLevels.length - 1) result.add(allLevels[idx2 + 1]);
5890
+ }
5891
+ return { include: [...result], exclude: [] };
5892
+ }
5893
+ const idx = allLevels.indexOf(value);
5894
+ if (idx === -1) return { include: levels, exclude: [] };
5895
+ switch (op) {
5896
+ case ">":
5897
+ return { include: allLevels.slice(idx + 1), exclude: [] };
5898
+ case ">=":
5899
+ return { include: allLevels.slice(idx), exclude: [] };
5900
+ case "<":
5901
+ return { include: allLevels.slice(0, idx), exclude: [] };
5902
+ case "<=":
5903
+ return { include: allLevels.slice(0, idx + 1), exclude: [] };
5904
+ }
5905
+ }
5906
+ __name(applyLevelOperator, "applyLevelOperator");
5907
+ function applyScoreOperator(op, scores) {
5908
+ const value = scores[0];
5909
+ if (op === "=") return { include: [value], exclude: [] };
5910
+ if (op === "!=") return { include: [], exclude: [value] };
5911
+ if (op === "~") {
5912
+ return { include: [value - 5e4, value + 5e4], exclude: [] };
5913
+ }
5914
+ switch (op) {
5915
+ case ">":
5916
+ return { include: [value + 1, 1e7], exclude: [] };
5917
+ case ">=":
5918
+ return { include: [value, 1e7], exclude: [] };
5919
+ case "<":
5920
+ return { include: [0, value - 1], exclude: [] };
5921
+ case "<=":
5922
+ return { include: [0, value], exclude: [] };
5923
+ }
5924
+ }
5925
+ __name(applyScoreOperator, "applyScoreOperator");
5926
+ function applyVfOperator(op, vfValue) {
5927
+ if (op === "=") return { include: vfValue, exclude: [] };
5928
+ if (op === "!=") return { include: [], exclude: [vfValue] };
5929
+ if (op === "~") {
5930
+ return { include: [vfValue - 0.25, vfValue + 0.25], exclude: [] };
5931
+ }
5932
+ switch (op) {
5933
+ case ">":
5934
+ return { include: [vfValue + 0.05, 99], exclude: [] };
5935
+ case ">=":
5936
+ return { include: [vfValue, 99], exclude: [] };
5937
+ case "<":
5938
+ return { include: [0, vfValue - 0.05], exclude: [] };
5939
+ case "<=":
5940
+ return { include: [0, vfValue], exclude: [] };
5941
+ }
5942
+ }
5943
+ __name(applyVfOperator, "applyVfOperator");
5944
+ function applyGradeOperator(op, grade) {
5945
+ const idx = ALL_GRADES.indexOf(grade);
5946
+ if (idx === -1) return { include: [grade], exclude: [] };
5947
+ if (op === "=") return { include: [grade], exclude: [] };
5948
+ if (op === "!=") return { include: [], exclude: [grade] };
5949
+ if (op === "~") {
5950
+ const result = [];
5951
+ if (idx > 0) result.push(ALL_GRADES[idx - 1]);
5952
+ result.push(ALL_GRADES[idx]);
5953
+ if (idx < ALL_GRADES.length - 1) result.push(ALL_GRADES[idx + 1]);
5954
+ return { include: result, exclude: [] };
5955
+ }
5956
+ switch (op) {
5957
+ case ">":
5958
+ return { include: ALL_GRADES.slice(0, idx), exclude: [] };
5959
+ case ">=":
5960
+ return { include: ALL_GRADES.slice(0, idx + 1), exclude: [] };
5961
+ case "<":
5962
+ return { include: ALL_GRADES.slice(idx + 1), exclude: [] };
5963
+ case "<=":
5964
+ return { include: ALL_GRADES.slice(idx), exclude: [] };
5965
+ }
5966
+ }
5967
+ __name(applyGradeOperator, "applyGradeOperator");
5968
+ function applyClearTypeOperator(op, clearType) {
5969
+ const idx = ALL_CLEAR_TYPES.indexOf(clearType);
5970
+ if (idx === -1) return { include: [clearType], exclude: [] };
5971
+ if (op === "=") return { include: [clearType], exclude: [] };
5972
+ if (op === "!=") return { include: [], exclude: [clearType] };
5973
+ if (op === "~") {
5974
+ const result = [];
5975
+ if (idx > 0) result.push(ALL_CLEAR_TYPES[idx - 1]);
5976
+ result.push(ALL_CLEAR_TYPES[idx]);
5977
+ if (idx < ALL_CLEAR_TYPES.length - 1) result.push(ALL_CLEAR_TYPES[idx + 1]);
5978
+ return { include: result, exclude: [] };
5979
+ }
5980
+ switch (op) {
5981
+ case ">":
5982
+ return { include: ALL_CLEAR_TYPES.slice(0, idx), exclude: [] };
5983
+ case ">=":
5984
+ return { include: ALL_CLEAR_TYPES.slice(0, idx + 1), exclude: [] };
5985
+ case "<":
5986
+ return { include: ALL_CLEAR_TYPES.slice(idx + 1), exclude: [] };
5987
+ case "<=":
5988
+ return { include: ALL_CLEAR_TYPES.slice(idx), exclude: [] };
5989
+ }
5990
+ }
5991
+ __name(applyClearTypeOperator, "applyClearTypeOperator");
5992
+ function mergeLevels(params, levels) {
5993
+ if (params.level !== void 0) {
5994
+ const existing = Array.isArray(params.level) ? params.level : [params.level];
5995
+ params.level = [.../* @__PURE__ */ new Set([...existing, ...levels])];
5996
+ } else {
5997
+ params.level = levels.length === 1 ? levels[0] : levels;
5998
+ }
5999
+ }
6000
+ __name(mergeLevels, "mergeLevels");
6001
+ function mergeScores(params, scores) {
6002
+ if (params.score !== void 0) {
6003
+ const existing = Array.isArray(params.score) ? params.score : [params.score];
6004
+ params.score = [.../* @__PURE__ */ new Set([...existing, ...scores])];
6005
+ } else {
6006
+ params.score = scores.length === 1 ? scores[0] : scores;
6007
+ }
6008
+ }
6009
+ __name(mergeScores, "mergeScores");
6010
+ function mergeVf(params, vf2) {
6011
+ if (params.vf !== void 0) {
6012
+ const existing = Array.isArray(params.vf) ? params.vf : [params.vf];
6013
+ const newVf = Array.isArray(vf2) ? vf2 : [vf2];
6014
+ params.vf = [...existing, ...newVf];
6015
+ } else {
6016
+ params.vf = vf2;
6017
+ }
6018
+ }
6019
+ __name(mergeVf, "mergeVf");
6020
+ function mergeGrades(params, grades) {
6021
+ if (params.grade) {
6022
+ const existing = Array.isArray(params.grade) ? params.grade : [params.grade];
6023
+ params.grade = [.../* @__PURE__ */ new Set([...existing, ...grades])];
6024
+ } else {
6025
+ params.grade = grades.length === 1 ? grades[0] : grades;
6026
+ }
6027
+ }
6028
+ __name(mergeGrades, "mergeGrades");
6029
+ function mergeClearTypes(params, clearTypes) {
6030
+ if (params.clearType) {
6031
+ const existing = Array.isArray(params.clearType) ? params.clearType : [params.clearType];
6032
+ params.clearType = [.../* @__PURE__ */ new Set([...existing, ...clearTypes])];
6033
+ } else {
6034
+ params.clearType = clearTypes;
6035
+ }
6036
+ }
6037
+ __name(mergeClearTypes, "mergeClearTypes");
6038
+ function parseWithOperator(params, op, value) {
6039
+ const vf2 = parseVfValue(value);
6040
+ if (vf2 !== null) {
6041
+ const vfNum = Array.isArray(vf2) ? vf2[0] : vf2;
6042
+ const result = applyVfOperator(op, vfNum);
6043
+ if (result.exclude.length > 0) {
6044
+ params.excludeVf = [...params.excludeVf || [], ...result.exclude];
6045
+ }
6046
+ if (Array.isArray(result.include) && result.include.length > 0) {
6047
+ mergeVf(params, result.include);
6048
+ } else if (typeof result.include === "number") {
6049
+ mergeVf(params, result.include);
6050
+ }
6051
+ return true;
6052
+ }
6053
+ const levels = parseLevelRange(value);
6054
+ if (levels !== null && levels.length === 1) {
6055
+ const result = applyLevelOperator(op, levels);
6056
+ if (result.exclude.length > 0) {
6057
+ params.excludeLevel = [...params.excludeLevel || [], ...result.exclude];
6058
+ }
6059
+ if (result.include.length > 0) {
6060
+ mergeLevels(params, result.include);
6061
+ }
6062
+ return true;
6063
+ }
6064
+ const scores = parseScoreRange(value);
6065
+ if (scores !== null && scores.length === 1) {
6066
+ const result = applyScoreOperator(op, scores);
6067
+ if (result.exclude.length > 0) {
6068
+ params.excludeScore = [...params.excludeScore || [], ...result.exclude];
6069
+ }
6070
+ if (result.include.length > 0) {
6071
+ mergeScores(params, result.include);
6072
+ }
6073
+ return true;
6074
+ }
6075
+ const singleClearTypes = parseSingleClearType(value);
6076
+ if (singleClearTypes !== null) {
6077
+ const ct = singleClearTypes[0];
6078
+ const result = applyClearTypeOperator(op, ct);
6079
+ if (result.exclude.length > 0) {
6080
+ params.excludeClearType = [...params.excludeClearType || [], ...result.exclude];
6081
+ }
6082
+ if (result.include.length > 0) {
6083
+ mergeClearTypes(params, result.include);
6084
+ }
6085
+ return true;
6086
+ }
6087
+ const singleGrades = parseSingleGrade(value);
6088
+ if (singleGrades !== null) {
6089
+ const result = applyGradeOperator(op, singleGrades[0]);
6090
+ if (result.exclude.length > 0) {
6091
+ params.excludeGrade = [...params.excludeGrade || [], ...result.exclude];
6092
+ }
6093
+ if (result.include.length > 0) {
6094
+ mergeGrades(params, result.include);
6095
+ }
6096
+ return true;
6097
+ }
6098
+ return false;
6099
+ }
6100
+ __name(parseWithOperator, "parseWithOperator");
5366
6101
  function parseQueryInput(input) {
5367
6102
  const params = {};
5368
6103
  if (!input || input.trim() === "") {
@@ -5370,6 +6105,10 @@ function parseQueryInput(input) {
5370
6105
  }
5371
6106
  const items = input.split(/\s+/).map((s) => s.trim()).filter(Boolean);
5372
6107
  for (const item of items) {
6108
+ const { op, value } = extractOperator(item);
6109
+ if (op) {
6110
+ if (parseWithOperator(params, op, value)) continue;
6111
+ }
5373
6112
  const vf2 = parseVfValue(item);
5374
6113
  if (vf2 !== null) {
5375
6114
  if (params.vf !== void 0) {
@@ -5447,7 +6186,7 @@ function parseQueryInput(input) {
5447
6186
  __name(parseQueryInput, "parseQueryInput");
5448
6187
 
5449
6188
  // src/games/sdvx/commands/calculate.ts
5450
- function calculate(ctx, config, logger5) {
6189
+ function calculate(ctx, config, logger6) {
5451
6190
  ctx.command("sdvx.calculate <query:text>").alias("sdvx.cal").action(async ({ session, options }, query) => {
5452
6191
  try {
5453
6192
  let queryInput = query;
@@ -5481,7 +6220,7 @@ function calculate(ctx, config, logger5) {
5481
6220
  lossless: true
5482
6221
  }
5483
6222
  );
5484
- return import_koishi14.h.image(imageBuffer, "image/png");
6223
+ return import_koishi16.h.image(imageBuffer, "image/png");
5485
6224
  } catch (error) {
5486
6225
  ctx.logger("SDVX-Calculate").warn(error);
5487
6226
  return session.text(".error");
@@ -5491,7 +6230,7 @@ function calculate(ctx, config, logger5) {
5491
6230
  __name(calculate, "calculate");
5492
6231
 
5493
6232
  // src/games/sdvx/commands/chart.ts
5494
- var import_koishi15 = require("koishi");
6233
+ var import_koishi17 = require("koishi");
5495
6234
  function mapArrangementToken(tok) {
5496
6235
  const t = tok.toLowerCase();
5497
6236
  if (t === "ran" || t === "random") return "random";
@@ -5574,7 +6313,7 @@ function getHighestDifstr(diffs) {
5574
6313
  return diffs[diffs.length - 1].difstr;
5575
6314
  }
5576
6315
  __name(getHighestDifstr, "getHighestDifstr");
5577
- function chart(ctx, config, logger5) {
6316
+ function chart(ctx, config, logger6) {
5578
6317
  ctx.command("sdvx.chart [query:text]").alias("sdvx.c").action(async ({ session }, query) => {
5579
6318
  if (!query) {
5580
6319
  await session.send(session.text(".prompt"));
@@ -5637,7 +6376,7 @@ function chart(ctx, config, logger5) {
5637
6376
  const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
5638
6377
  headers: { "Content-Type": "application/json" }
5639
6378
  });
5640
- return import_koishi15.h.image(res, "image/png");
6379
+ return import_koishi17.h.image(res, "image/png");
5641
6380
  } else {
5642
6381
  const {
5643
6382
  diffStr: parsedDiff,
@@ -5653,7 +6392,7 @@ function chart(ctx, config, logger5) {
5653
6392
  const picked = musicInfo[0];
5654
6393
  const music_id = Number(picked?.id);
5655
6394
  if (!Number.isFinite(music_id)) {
5656
- logger5.warn("search result missing id", picked);
6395
+ logger6.warn("search result missing id", picked);
5657
6396
  return session.text(".error");
5658
6397
  }
5659
6398
  let difstr = null;
@@ -5687,16 +6426,76 @@ function chart(ctx, config, logger5) {
5687
6426
  const res = await ctx.http.post(`${config.sdvx_data_url}/chart`, payload, {
5688
6427
  headers: { "Content-Type": "application/json" }
5689
6428
  });
5690
- return import_koishi15.h.image(res, "image/png");
6429
+ return import_koishi17.h.image(res, "image/png");
5691
6430
  }
5692
6431
  } catch (err) {
5693
- logger5.warn(err);
6432
+ logger6.warn(err);
5694
6433
  return session.text(".error");
5695
6434
  }
5696
6435
  });
5697
6436
  }
5698
6437
  __name(chart, "chart");
5699
6438
 
6439
+ // src/core/utils/selector.ts
6440
+ var import_koishi18 = require("koishi");
6441
+ async function selectCard(session, cardService, uid) {
6442
+ const userCards = await cardService.getCardsByUid(uid);
6443
+ if (userCards.length === 0) {
6444
+ return { ok: false, message: session.text("selector.card-not-found") };
6445
+ }
6446
+ const defaultCard = await cardService.getDefaultCardByUid(uid);
6447
+ const defaultLabel = session.text("selector.default-card");
6448
+ let cardListMsg = "";
6449
+ for (let i = 0; i < userCards.length; i++) {
6450
+ if (defaultCard && userCards[i].id === defaultCard.id) {
6451
+ cardListMsg += `<p>${i + 1}. ${userCards[i].name} &lt; ${defaultLabel}</p>`;
6452
+ } else {
6453
+ cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
6454
+ }
6455
+ }
6456
+ const msg = import_koishi18.h.unescape(session.text("selector.menu-select", { card_list: cardListMsg }));
6457
+ await session.send(msg);
6458
+ const select = await session.prompt();
6459
+ if (!select) return { ok: false, message: session.text("commands.timeout") };
6460
+ if (select === "q") return { ok: false, message: session.text("selector.quit") };
6461
+ const selectNum = parseInt(select, 10);
6462
+ if (isNaN(selectNum) || selectNum < 1 || selectNum > userCards.length) {
6463
+ return { ok: false, message: session.text("selector.invalid-select") };
6464
+ }
6465
+ const card2 = await cardService.getCardByCode(userCards[selectNum - 1].code);
6466
+ return { ok: true, value: card2 };
6467
+ }
6468
+ __name(selectCard, "selectCard");
6469
+ async function selectServer(session, serverService, uid, channelId) {
6470
+ const servers = await serverService.getSelectableServers(uid, channelId);
6471
+ if (servers.length === 0) {
6472
+ return { ok: false, message: session.text("selector.server-not-found") };
6473
+ }
6474
+ const defaultServer = await serverService.getDefaultServerByUid(uid);
6475
+ const defaultLabel = session.text("selector.default-server");
6476
+ let serverListMsg = "";
6477
+ for (let i = 0; i < servers.length; i++) {
6478
+ if (defaultServer && servers[i].id === defaultServer.id) {
6479
+ serverListMsg += `<p>${i + 1}. ${servers[i].name} (${servers[i].type}) &lt; ${defaultLabel}</p>`;
6480
+ } else {
6481
+ serverListMsg += `<p>${i + 1}. ${servers[i].name} (${servers[i].type})</p>`;
6482
+ }
6483
+ }
6484
+ const msg = import_koishi18.h.unescape(
6485
+ session.text("selector.server-menu-select", { server_list: serverListMsg })
6486
+ );
6487
+ await session.send(msg);
6488
+ const select = await session.prompt();
6489
+ if (!select) return { ok: false, message: session.text("commands.timeout") };
6490
+ if (select === "q") return { ok: false, message: session.text("selector.quit") };
6491
+ const selectNum = parseInt(select, 10);
6492
+ if (isNaN(selectNum) || selectNum < 1 || selectNum > servers.length) {
6493
+ return { ok: false, message: session.text("selector.invalid-select") };
6494
+ }
6495
+ return { ok: true, value: servers[selectNum - 1] };
6496
+ }
6497
+ __name(selectServer, "selectServer");
6498
+
5700
6499
  // src/games/sdvx/services/score-service.ts
5701
6500
  var ScoreService = class _ScoreService {
5702
6501
  static {
@@ -5780,17 +6579,18 @@ var ScoreService = class _ScoreService {
5780
6579
  calcSongRadarContribution(score, category) {
5781
6580
  const { difficulty_data } = score;
5782
6581
  if (!difficulty_data?.radar || !difficulty_data.max_exscore) return 0;
5783
- const L = Math.floor(difficulty_data.difnum);
6582
+ const L = Math.max(1, Math.min(20, Math.floor(difficulty_data.difnum)));
5784
6583
  const R = difficulty_data.radar[category];
5785
6584
  const S = score.music.score;
5786
6585
  const E = score.music.exscore;
5787
6586
  const Emax = difficulty_data.max_exscore;
5788
- const P = Math.min(2 * (L + 31), 100);
6587
+ const P = Math.min(100, Math.max(0, 60 + 2 * L));
5789
6588
  const B = Math.floor(3 * S * R / (4 * 1e7));
5790
- const H = Math.floor((20 - L) / 2);
5791
- const G = Math.max(1e3 + 2 * 2 ** H * (E - Emax), 0);
6589
+ const H = Math.floor(Math.max(1, 21 - L) / 2);
6590
+ const G = Emax > 0 && E <= Emax ? Math.max(0, 1e3 + 2 * (E - Emax) * (1 << H)) : 0;
5792
6591
  const X = Math.floor(R * G / 4e3);
5793
- return P * (B + X);
6592
+ const completedAxis = Math.min(R, B + X);
6593
+ return P * completedAxis;
5794
6594
  }
5795
6595
  /**
5796
6596
  * 计算玩家六维雷达(显示值,如 200.00)
@@ -5819,37 +6619,46 @@ var ScoreService = class _ScoreService {
5819
6619
  };
5820
6620
 
5821
6621
  // src/games/sdvx/commands/radar.ts
5822
- function radar(ctx, config, logger5) {
5823
- ctx.command("sdvx.radar").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).action(async ({ session }) => {
6622
+ function radar(ctx, config, logger6) {
6623
+ ctx.command("sdvx.radar").userFields(["id"]).channelFields(["id"]).option("card", "-c").option("server", "-s").action(async ({ session, options }) => {
5824
6624
  const atGuild = session.guildId != null;
5825
6625
  const cardService = new CardService(ctx);
5826
6626
  const serverService = new ServerService(ctx);
6627
+ const channelId = atGuild ? session.channel.id : null;
5827
6628
  const userCards = await cardService.getCardsByUid(session.user.id);
5828
6629
  if (userCards.length === 0) return session.text(".card-not-found");
5829
- const serverRes = await serverService.getSelectableServers(
5830
- session.user.id,
5831
- atGuild ? session.channel.id : null
5832
- );
6630
+ const serverRes = await serverService.getSelectableServers(session.user.id, channelId);
5833
6631
  if (serverRes.length === 0) return session.text(".server-not-found");
5834
- const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
5835
- if (!defaultCard) return session.text(".card-not-found");
5836
- const card2 = await cardService.getCardByCode(defaultCard.code);
6632
+ let card2;
6633
+ if (options.card) {
6634
+ const result = await selectCard(session, cardService, session.user.id);
6635
+ if (result.ok === false) return result.message;
6636
+ card2 = result.value;
6637
+ } else {
6638
+ const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
6639
+ if (!defaultCard) return session.text(".card-not-found");
6640
+ card2 = await cardService.getCardByCode(defaultCard.code);
6641
+ }
5837
6642
  let server2;
5838
- if (card2.defaultServerId != void 0 && card2.defaultServerId != 0)
5839
- server2 = await serverService.getServerById(card2.defaultServerId);
5840
- else {
5841
- const channelDefaultServer = atGuild ? await serverService.getDefaultServerByCid(
6643
+ if (options.server) {
6644
+ const result = await selectServer(
6645
+ session,
6646
+ serverService,
6647
+ session.user.id,
6648
+ channelId
6649
+ );
6650
+ if (result.ok === false) return result.message;
6651
+ server2 = result.value;
6652
+ } else {
6653
+ server2 = await resolveServerForCard(
6654
+ card2,
6655
+ cardService,
6656
+ serverService,
6657
+ session.user.id,
5842
6658
  session.platform,
5843
- session.channel.id
5844
- ) : null;
5845
- if (channelDefaultServer) server2 = channelDefaultServer;
5846
- else {
5847
- const userDefaultServer = await serverService.getDefaultServerByUid(
5848
- session.user.id
5849
- );
5850
- if (userDefaultServer) server2 = userDefaultServer;
5851
- else return session.text(".server-not-found");
5852
- }
6659
+ channelId
6660
+ );
6661
+ if (!server2) return session.text(".server-not-found");
5853
6662
  }
5854
6663
  const serverManager = ServerManager.getInstance();
5855
6664
  const sdvxService = serverManager.getGameService(server2.type, "sdvx");
@@ -5876,71 +6685,56 @@ function radar(ctx, config, logger5) {
5876
6685
  one_hand: radarResult.one_hand.toFixed(2)
5877
6686
  });
5878
6687
  } catch (error) {
5879
- logger5.warn(error);
5880
- return session.text(".error");
6688
+ logger6.warn(error);
6689
+ return session.text(".error", { card: card2.name, server: server2.name });
5881
6690
  }
5882
6691
  });
5883
6692
  }
5884
6693
  __name(radar, "radar");
5885
6694
 
5886
6695
  // src/games/sdvx/commands/recent.ts
5887
- var import_koishi16 = require("koishi");
5888
- function recent(ctx, config, logger5) {
5889
- 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) => {
6696
+ var import_koishi19 = require("koishi");
6697
+ function recent(ctx, config, logger6) {
6698
+ ctx.command("sdvx.recent [count:number]").alias("sdvx.r").userFields(["id"]).channelFields(["id"]).option("lossless", "-l").option("card", "-c").option("server", "-s").action(async ({ session, options }, count) => {
5890
6699
  const atGuild = session.guildId != null;
5891
6700
  if (!count) count = 1;
5892
6701
  const cardService = new CardService(ctx);
5893
6702
  const serverService = new ServerService(ctx);
6703
+ const channelId = atGuild ? session.channel.id : null;
5894
6704
  const userCards = await cardService.getCardsByUid(session.user.id);
5895
6705
  if (userCards.length === 0) return session.text(".card-not-found");
5896
- const serverRes = await serverService.getSelectableServers(
5897
- session.user.id,
5898
- atGuild ? session.channel.id : null
5899
- );
6706
+ const serverRes = await serverService.getSelectableServers(session.user.id, channelId);
5900
6707
  if (serverRes.length === 0) return session.text(".server-not-found");
5901
- let cardCode = "";
5902
- if (!options.card) {
6708
+ let card2;
6709
+ if (options.card) {
6710
+ const result = await selectCard(session, cardService, session.user.id);
6711
+ if (result.ok === false) return result.message;
6712
+ card2 = result.value;
6713
+ } else {
5903
6714
  const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
5904
6715
  if (!defaultCard) return session.text(".card-not-found");
5905
- cardCode = defaultCard.code;
5906
- } else {
5907
- let cardListMsg = "";
5908
- for (let i = 0; i < userCards.length; i++) {
5909
- const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
5910
- if (defaultCard && userCards[i].id === defaultCard.id) {
5911
- cardListMsg += `<p>${i + 1}. ${userCards[i].name} < 默认卡片</p>`;
5912
- } else {
5913
- cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
5914
- }
5915
- }
5916
- const msg = import_koishi16.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
5917
- await session.send(msg);
5918
- const select = await session.prompt();
5919
- if (!select) return session.text("commands.timeout");
5920
- if (select === "q") return session.text(".quit");
5921
- const selectNum = parseInt(select, 10);
5922
- if (isNaN(selectNum) || selectNum < 1 || selectNum > userCards.length) {
5923
- return session.text(".invalid-select");
5924
- }
5925
- cardCode = userCards[selectNum - 1].code;
6716
+ card2 = await cardService.getCardByCode(defaultCard.code);
5926
6717
  }
5927
- const card2 = await cardService.getCardByCode(cardCode);
5928
6718
  let server2;
5929
- if (card2.defaultServerId != void 0 && card2.defaultServerId != 0)
5930
- server2 = await serverService.getServerById(card2.defaultServerId);
5931
- else {
5932
- const channelDefaultServer = atGuild ? await serverService.getDefaultServerByCid(
6719
+ if (options.server) {
6720
+ const result = await selectServer(
6721
+ session,
6722
+ serverService,
6723
+ session.user.id,
6724
+ channelId
6725
+ );
6726
+ if (result.ok === false) return result.message;
6727
+ server2 = result.value;
6728
+ } else {
6729
+ server2 = await resolveServerForCard(
6730
+ card2,
6731
+ cardService,
6732
+ serverService,
6733
+ session.user.id,
5933
6734
  session.platform,
5934
- session.channel.id
5935
- ) : null;
5936
- if (channelDefaultServer) server2 = channelDefaultServer;
5937
- else {
5938
- const userDefaultServer = await serverService.getDefaultServerByUid(
5939
- session.user.id
5940
- );
5941
- if (userDefaultServer) server2 = userDefaultServer;
5942
- else return session.text(".server-not-found");
5943
- }
6735
+ channelId
6736
+ );
6737
+ if (!server2) return session.text(".server-not-found");
5944
6738
  }
5945
6739
  const serverManager = ServerManager.getInstance();
5946
6740
  const sdvxService = serverManager.getGameService(server2.type, "sdvx");
@@ -5970,19 +6764,19 @@ function recent(ctx, config, logger5) {
5970
6764
  }
5971
6765
  );
5972
6766
  if (options.lossless) {
5973
- return import_koishi16.h.image(imageBuffer, "image/png");
6767
+ return import_koishi19.h.image(imageBuffer, "image/png");
5974
6768
  }
5975
- return import_koishi16.h.image(imageBuffer, "image/jpg");
6769
+ return import_koishi19.h.image(imageBuffer, "image/jpg");
5976
6770
  } catch (error) {
5977
- logger5.warn(error);
5978
- return session.text(".error");
6771
+ logger6.warn(error);
6772
+ return session.text(".error", { card: card2.name, server: server2.name });
5979
6773
  }
5980
6774
  });
5981
6775
  }
5982
6776
  __name(recent, "recent");
5983
6777
 
5984
6778
  // src/games/sdvx/commands/sync.ts
5985
- function sync(ctx, config, logger5) {
6779
+ function sync(ctx, config, logger6) {
5986
6780
  ctx.command("sdvx.sync").userFields(["id"]).channelFields(["id"]).action(async ({ session }) => {
5987
6781
  const serverService = new ServerService(ctx);
5988
6782
  const cardService = new CardService(ctx);
@@ -6057,7 +6851,7 @@ function sync(ctx, config, logger5) {
6057
6851
  const maoSdvxService = serverManager.getGameService("mao", "sdvx");
6058
6852
  const maoVerifyUrl = "https://maomani.cn";
6059
6853
  if (!maoSdvxService || typeof maoSdvxService.verifyPin !== "function") {
6060
- logger5.warn("Mao SDVX service does not support PIN verification");
6854
+ logger6.warn("Mao SDVX service does not support PIN verification");
6061
6855
  return session.text(".pin-verify-error");
6062
6856
  }
6063
6857
  const existingPin = await ctx.database.get("sdvx_pin_verified", {
@@ -6078,7 +6872,7 @@ function sync(ctx, config, logger5) {
6078
6872
  pinVerified = true;
6079
6873
  }
6080
6874
  } catch (error) {
6081
- logger5.warn(error);
6875
+ logger6.warn(error);
6082
6876
  }
6083
6877
  }
6084
6878
  if (!pinVerified) {
@@ -6114,7 +6908,7 @@ function sync(ctx, config, logger5) {
6114
6908
  })
6115
6909
  );
6116
6910
  } catch (error) {
6117
- logger5.warn(error);
6911
+ logger6.warn(error);
6118
6912
  await session.send(session.text(".pin-verify-error"));
6119
6913
  }
6120
6914
  }
@@ -6136,8 +6930,11 @@ function sync(ctx, config, logger5) {
6136
6930
  config
6137
6931
  );
6138
6932
  } catch (error) {
6139
- logger5.warn(error);
6140
- return session.text(".fetch-error");
6933
+ logger6.warn(error);
6934
+ return session.text(".fetch-error", {
6935
+ card: sourceCard.name,
6936
+ server: sourceServer.name
6937
+ });
6141
6938
  }
6142
6939
  if (!scoreList || scoreList.length === 0) {
6143
6940
  return session.text(".no-scores");
@@ -6158,12 +6955,15 @@ function sync(ctx, config, logger5) {
6158
6955
  scoreList
6159
6956
  );
6160
6957
  if (!ok) {
6161
- logger5.warn("Mao SDVX uploadScore returned falsy result");
6958
+ logger6.warn("Mao SDVX uploadScore returned falsy result");
6162
6959
  return session.text(".sync-failed");
6163
6960
  }
6164
6961
  } catch (error) {
6165
- logger5.warn(error);
6166
- return session.text(".sync-error");
6962
+ logger6.warn(error);
6963
+ return session.text(".sync-error", {
6964
+ card: targetCard.name,
6965
+ server: maoServer.name
6966
+ });
6167
6967
  }
6168
6968
  return session.text(".selected-summary", {
6169
6969
  source_server_name: sourceServer.name,
@@ -6177,8 +6977,8 @@ function sync(ctx, config, logger5) {
6177
6977
  __name(sync, "sync");
6178
6978
 
6179
6979
  // src/games/sdvx/commands/vf.ts
6180
- var fs4 = __toESM(require("fs"), 1);
6181
- var import_koishi17 = require("koishi");
6980
+ var fs5 = __toESM(require("fs"), 1);
6981
+ var import_koishi20 = require("koishi");
6182
6982
 
6183
6983
  // src/games/sdvx/utils/filter.ts
6184
6984
  function parseFilterQuery(query) {
@@ -6231,61 +7031,46 @@ function parseFilterQuery(query) {
6231
7031
  __name(parseFilterQuery, "parseFilterQuery");
6232
7032
 
6233
7033
  // src/games/sdvx/commands/vf.ts
6234
- function vf(ctx, config, logger5) {
6235
- 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 }) => {
7034
+ function vf(ctx, config, logger6) {
7035
+ ctx.command("vf").userFields(["id"]).channelFields(["id"]).option("lossless", "-l").option("card", "-c").option("server", "-s").option("filter", "-f <query:text>").action(async ({ session, options }) => {
6236
7036
  const atGuild = session.guildId != null;
6237
7037
  const cardService = new CardService(ctx);
6238
7038
  const serverService = new ServerService(ctx);
7039
+ const channelId = atGuild ? session.channel.id : null;
6239
7040
  const userCards = await cardService.getCardsByUid(session.user.id);
6240
7041
  if (userCards.length === 0) return session.text(".card-not-found");
6241
- const serverRes = await serverService.getSelectableServers(
6242
- session.user.id,
6243
- atGuild ? session.channel.id : null
6244
- );
7042
+ const serverRes = await serverService.getSelectableServers(session.user.id, channelId);
6245
7043
  if (serverRes.length === 0) return session.text(".server-not-found");
6246
- let cardCode = "";
6247
- if (!options.card) {
7044
+ let card2;
7045
+ if (options.card) {
7046
+ const result = await selectCard(session, cardService, session.user.id);
7047
+ if (result.ok === false) return result.message;
7048
+ card2 = result.value;
7049
+ } else {
6248
7050
  const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
6249
7051
  if (!defaultCard) return session.text(".card-not-found");
6250
- cardCode = defaultCard.code;
6251
- } else {
6252
- let cardListMsg = "";
6253
- for (let i = 0; i < userCards.length; i++) {
6254
- const defaultCard = await cardService.getDefaultCardByUid(session.user.id);
6255
- if (defaultCard && userCards[i].id === defaultCard.id) {
6256
- cardListMsg += `<p>${i + 1}. ${userCards[i].name} < 默认卡片</p>`;
6257
- } else {
6258
- cardListMsg += `<p>${i + 1}. ${userCards[i].name}</p>`;
6259
- }
6260
- }
6261
- const msg = import_koishi17.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
6262
- await session.send(msg);
6263
- const select = await session.prompt();
6264
- if (!select) return session.text("commands.timeout");
6265
- if (select === "q") return session.text(".quit");
6266
- const selectNum = parseInt(select, 10);
6267
- if (isNaN(selectNum) || selectNum < 1 || selectNum > userCards.length) {
6268
- return session.text(".invalid-select");
6269
- }
6270
- cardCode = userCards[selectNum - 1].code;
7052
+ card2 = await cardService.getCardByCode(defaultCard.code);
6271
7053
  }
6272
- const card2 = await cardService.getCardByCode(cardCode);
6273
7054
  let server2;
6274
- if (card2.defaultServerId != void 0 && card2.defaultServerId != 0)
6275
- server2 = await serverService.getServerById(card2.defaultServerId);
6276
- else {
6277
- const channelDefaultServer = atGuild ? await serverService.getDefaultServerByCid(
7055
+ if (options.server) {
7056
+ const result = await selectServer(
7057
+ session,
7058
+ serverService,
7059
+ session.user.id,
7060
+ channelId
7061
+ );
7062
+ if (result.ok === false) return result.message;
7063
+ server2 = result.value;
7064
+ } else {
7065
+ server2 = await resolveServerForCard(
7066
+ card2,
7067
+ cardService,
7068
+ serverService,
7069
+ session.user.id,
6278
7070
  session.platform,
6279
- session.channel.id
6280
- ) : null;
6281
- if (channelDefaultServer) server2 = channelDefaultServer;
6282
- else {
6283
- const userDefaultServer = await serverService.getDefaultServerByUid(
6284
- session.user.id
6285
- );
6286
- if (userDefaultServer) server2 = userDefaultServer;
6287
- else return session.text(".server-not-found");
6288
- }
7071
+ channelId
7072
+ );
7073
+ if (!server2) return session.text(".server-not-found");
6289
7074
  }
6290
7075
  const serverManager = ServerManager.getInstance();
6291
7076
  const sdvxService = serverManager.getGameService(server2.type, "sdvx");
@@ -6321,9 +7106,9 @@ function vf(ctx, config, logger5) {
6321
7106
  }
6322
7107
  );
6323
7108
  if (options.lossless) {
6324
- session.send(import_koishi17.h.file(imageBuffer, "image/png"));
7109
+ session.send(import_koishi20.h.file(imageBuffer, "image/png"));
6325
7110
  } else {
6326
- session.send(import_koishi17.h.image(imageBuffer, "image/jpg"));
7111
+ session.send(import_koishi20.h.image(imageBuffer, "image/jpg"));
6327
7112
  }
6328
7113
  const sortedScores = [...best50ScoreList].sort((a, b) => b.extra.volforce - a.extra.volforce).slice(0, 50);
6329
7114
  const total = sortedScores.reduce((sum, score) => sum + score.extra.volforce, 0);
@@ -6350,21 +7135,21 @@ function vf(ctx, config, logger5) {
6350
7135
  ctx,
6351
7136
  "stamps/noah_stamp/stamp_0385/stamp_0385_02.png"
6352
7137
  );
6353
- if (fs4.existsSync(audioPath)) {
6354
- const audioBuffer = fs4.readFileSync(audioPath);
6355
- session.send(import_koishi17.h.audio(audioBuffer, "audio/mpeg"));
7138
+ if (fs5.existsSync(audioPath)) {
7139
+ const audioBuffer = fs5.readFileSync(audioPath);
7140
+ session.send(import_koishi20.h.audio(audioBuffer, "audio/mpeg"));
6356
7141
  }
6357
- if (fs4.existsSync(imagePath)) {
6358
- const imageBuffer2 = fs4.readFileSync(imagePath);
6359
- session.send(import_koishi17.h.image(imageBuffer2, "image/png"));
7142
+ if (fs5.existsSync(imagePath)) {
7143
+ const imageBuffer2 = fs5.readFileSync(imagePath);
7144
+ session.send(import_koishi20.h.image(imageBuffer2, "image/png"));
6360
7145
  }
6361
7146
  } catch (error) {
6362
- logger5.warn(`Failed to send celebration files: ${error.message}`);
7147
+ logger6.warn(`Failed to send celebration files: ${error.message}`);
6363
7148
  }
6364
7149
  }
6365
7150
  } catch (error) {
6366
- logger5.warn(error);
6367
- return session.text(".error");
7151
+ logger6.warn(error);
7152
+ return session.text(".error", { card: card2.name, server: server2.name });
6368
7153
  }
6369
7154
  return;
6370
7155
  });
@@ -6375,12 +7160,12 @@ __name(vf, "vf");
6375
7160
  var name10 = "command";
6376
7161
  function apply10(ctx, config) {
6377
7162
  ctx.command("sdvx").alias("s");
6378
- vf(ctx, config, logger4);
6379
- recent(ctx, config, logger4);
6380
- chart(ctx, config, logger4);
6381
- calculate(ctx, config, logger4);
6382
- sync(ctx, config, logger4);
6383
- radar(ctx, config, logger4);
7163
+ vf(ctx, config, logger5);
7164
+ recent(ctx, config, logger5);
7165
+ chart(ctx, config, logger5);
7166
+ calculate(ctx, config, logger5);
7167
+ sync(ctx, config, logger5);
7168
+ radar(ctx, config, logger5);
6384
7169
  }
6385
7170
  __name(apply10, "apply");
6386
7171
 
@@ -6420,22 +7205,22 @@ function apply12(ctx) {
6420
7205
  __name(apply12, "apply");
6421
7206
 
6422
7207
  // src/games/sdvx/locales/en-US.yml
6423
- var en_US_default4 = { _config: { $desc: "SDVX Module Settings", sdvx_data_url: "<p>The URL of the SDVX data service</p>", sdvx_search_url: "<p>The URL of the SDVX search service</p>", official_support_url: "<p>The URL of the SDVX official support service</p>" }, commands: { vf: { description: "Show Noah help information", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing {name} [{difstr}], please wait patiently~</p>", "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Exited~</p>" } }, sdvx: { recent: { description: "Show recent scores", messages: { "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>" } }, chart: { description: "Show SDVX chart", messages: { prompt: "<p>Which song's chart would you like to view?</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", "no-result": "<p>Aww, Noah couldn’t find that song~ try another keyword, okay?</p>" } }, calculate: { description: "Calculate volforce value or score", messages: { "invalid-query": "<p>Invalid query parameters, please check your input~</p>", "no-results": "<p>No results found~</p>", "too-many-results": "<p>Too many results! Found {0} results, please narrow down your query (current limit: 500 results)</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", error: "<p>An error occurred(っ °Д °;)っ</p>" } }, radar: { description: "Show player radar stats", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred(っ °Д °;)っ</p>", result: "<p>{name}'s Radar</p>\n<p>NOTES: {notes}</p>\n<p>PEAK: {peak}</p>\n<p>TSUMAMI: {tsumami}</p>\n<p>TRICKY: {tricky}</p>\n<p>HAND-TRIP: {hand_trip}</p>\n<p>ONE-HAND: {one_hand}</p>" } }, sync: { description: "Sync scores to Mao", messages: { "mao-not-found": "<p>Mao server not found. Please add a Mao server first, then try syncing again.</p>", "source-server-not-found": "<p>No available source servers. Please add a server first.</p>", "source-server-select": "<p>Please choose the source server:</p>\n{server_list}\n<p>q. Exit</p>", "card-not-found": "<p>You haven't bound any card yet. Please bind a card first.</p>", "source-card-select": "<p>Please choose the source card:</p>\n{card_list}\n<p>q. Exit</p>", "target-card-select": "<p>Please choose the target card (sync to Mao):</p>\n{card_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Sync cancelled.</p>", "same-card-error": "<p>When the source server is Mao, the source card and target card cannot be the same. Please choose again.</p>", "dm-only": "<p>For security, please use this command in a private chat.</p>", "pin-prompt": "<p>Please enter the Mao PIN (4 digits):</p>\n<p>q. Exit</p>", "pin-invalid": "<p>Invalid PIN format. Please enter 4 digits.</p>", "pin-verify-failed": "<p>PIN verification failed: {message}</p>", "pin-verify-error": "<p>PIN verification error. Please try again later.</p>", "pin-too-many": "<p>Too many PIN attempts. Please try again later.</p>", "fetch-error": "<p>Failed to fetch scores from the source server. Please try again later.</p>", "no-scores": "<p>No scores available for sync.</p>", "confirm-sync": "<p>Found {score_count} scores. Start sync?</p>\n<p>Type y to confirm, any other key to cancel.</p>", "sync-error": "<p>Error occurred during sync(っ °Д °;)っ</p>", "sync-failed": "<p>Sync failed(っ °Д °;)っ</p>", "selected-summary": "<p>Sync completedヾ(≧▽≦*)o</p>\n<p>Source server: {source_server_name} ({source_server_type})</p>\n<p>Source card: {source_card_name}</p>\n<p>Target card: {target_card_name}</p>\n<p>Tracks synced: {score_count}</p>" } } } } };
7208
+ var en_US_default4 = { _config: { $desc: "SDVX Module Settings", sdvx_data_url: "<p>The URL of the SDVX data service</p>", sdvx_search_url: "<p>The URL of the SDVX search service</p>", official_support_url: "<p>The URL of the SDVX official support service</p>" }, commands: { vf: { description: "Show Noah help information", messages: { "card-not-found": "<p>You haven't bound a card yet, go bind a card first~</p>", "server-not-found": "<p>No available servers, add one yourself~</p>", "no-scores-found": "<p>No scores found, please check your data.</p>", error: "<p>An error occurred while using card: {card}, server: {server} (っ °Д °;)っ</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>", "server-menu-select": "<p>Please select the server you want to use:</p>\n{server_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>", "server-menu-select": "<p>Please select the server you want to use:</p>\n{server_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 while using card: {card}, server: {server} (っ °Д °;)っ</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>", quit: "<p>Exited~</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 while using card: {card}, server: {server} (っ °Д °;)っ</p>", "menu-select": "<p>Please select the card you want to use:</p>\n{card_list}\n<p>q. Exit</p>", "server-menu-select": "<p>Please select the server you want to use:</p>\n{server_list}\n<p>q. Exit</p>", "invalid-select": "<p>No such option!</p>", quit: "<p>Exited~</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>An error occurred while using card: {card}, server: {server} (っ °Д °;)っ</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>An error occurred while using card: {card}, server: {server} (っ °Д °;)っ</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>" } } } } };
6424
7209
 
6425
7210
  // src/games/sdvx/locales/zh-CN.yml
6426
- var zh_CN_default4 = { _config: { $desc: "SDVX 模块设置", sdvx_data_url: "<p>SDVX 数据服务的 URL</p>", sdvx_search_url: "<p>SDVX 搜索服务的 URL</p>", official_support_url: "<p>SDVX 官方支持服务的 URL</p>" }, commands: { vf: { description: "查询 SDVX VOLFORCE", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</p>" } }, sdvx: { recent: { description: "查询最近分数", messages: { "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>" } }, chart: { description: "查询 SDVX 谱面", messages: { prompt: "<p>要查哪首歌的铺面呢?</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在绘制 {name} [{difstr}],请耐心等待~</p>", "no-result": "<p>Noah 没有找到这首歌,换个关键词试试吧~</p>" } }, calculate: { description: "计算 volforce 值或分数", messages: { "invalid-query": "<p>查询参数无效,请检查你的输入~</p>", "no-results": "<p>没有找到符合条件的结果~</p>", "too-many-results": "<p>结果太多啦!共找到 {0} 条结果,请缩小查询范围(当前限制:500 条)</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>" } }, radar: { description: "查询玩家六维雷达", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 遇到了错误(っ °Д °;)っ</p>", result: "<p>{name} 的雷达</p>\n<p>NOTES: {notes}</p>\n<p>PEAK: {peak}</p>\n<p>TSUMAMI: {tsumami}</p>\n<p>TRICKY: {tricky}</p>\n<p>HAND-TRIP: {hand_trip}</p>\n<p>ONE-HAND: {one_hand}</p>" } }, sync: { description: "同步成绩到猫网", messages: { "mao-not-found": "<p>没有找到猫网服务器,请先添加猫网服务器后再尝试同步。</p>", "source-server-not-found": "<p>没有可作为来源的服务器,请先添加服务器。</p>", "source-server-select": "<p>请选择来源服务器:</p>\n{server_list}\n<p>q. 退出</p>", "card-not-found": "<p>你还没有绑定任何卡片哦~</p>", "source-card-select": "<p>请选择来源卡片:</p>\n{card_list}\n<p>q. 退出</p>", "target-card-select": "<p>请选择目标卡片(同步到猫网):</p>\n{card_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出同步。</p>", "same-card-error": "<p>来源服务器为猫网时,来源卡片和目标卡片不能相同,请重新选择。</p>", "dm-only": "<p>出于安全考虑,请在私聊中使用该指令。</p>", "pin-prompt": "<p>请输入猫网 PIN 码(4 位数字):</p>\n<p>q. 退出</p>", "pin-invalid": "<p>PIN 码格式不正确,请输入 4 位数字。</p>", "pin-verify-failed": "<p>PIN 验证失败:{message}</p>", "pin-verify-error": "<p>PIN 验证时出现错误,请稍后重试。</p>", "pin-too-many": "<p>PIN 验证次数过多,请稍后再试。</p>", "fetch-error": "<p>获取来源服务器成绩失败,请稍后再试。</p>", "no-scores": "<p>没有可同步的成绩。</p>", "confirm-sync": "<p>共找到 {score_count} 条成绩,是否开始同步?</p>\n<p>输入 y 确认,其他任意键取消。</p>", "sync-error": "<p>同步过程中出现了错误(っ °Д °;)っ</p>", "sync-failed": "<p>同步失败(っ °Д °;)っ</p>", "selected-summary": "<p>同步完成ヾ(≧▽≦*)o</p>\n<p>来源服务器:{source_server_name} ({source_server_type})</p>\n<p>来源卡片:{source_card_name}</p>\n<p>目标卡片:{target_card_name}</p>\n<p>同步曲目数量:{score_count}</p>" } } } } };
7211
+ var zh_CN_default4 = { _config: { $desc: "SDVX 模块设置", sdvx_data_url: "<p>SDVX 数据服务的 URL</p>", sdvx_search_url: "<p>SDVX 搜索服务的 URL</p>", official_support_url: "<p>SDVX 官方支持服务的 URL</p>" }, commands: { vf: { description: "查询 SDVX VOLFORCE", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>Noah 在使用卡片:{card},服务器:{server} 时遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "server-menu-select": "<p>请选择你要使用的服务器:</p>\n{server_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>", "server-menu-select": "<p>请选择你要使用的服务器:</p>\n{server_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 在使用卡片:{card},服务器:{server} 时遇到了错误(っ °Д °;)っ</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>", quit: "<p>已退出~</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 在使用卡片:{card},服务器:{server} 时遇到了错误(っ °Д °;)っ</p>", "menu-select": "<p>请选择你要使用的卡片:</p>\n{card_list}\n<p>q. 退出</p>", "server-menu-select": "<p>请选择你要使用的服务器:</p>\n{server_list}\n<p>q. 退出</p>", "invalid-select": "<p>没有该选项!</p>", quit: "<p>已退出~</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>Noah 在使用卡片:{card},服务器:{server} 时遇到了错误(っ °Д °;)っ</p>", "no-scores": "<p>没有可同步的成绩。</p>", "confirm-sync": "<p>共找到 {score_count} 条成绩,是否开始同步?</p>\n<p>输入 y 确认,其他任意键取消。</p>", "sync-error": "<p>Noah 在使用卡片:{card},服务器:{server} 时遇到了错误(っ °Д °;)っ</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>" } } } } };
6427
7212
 
6428
7213
  // src/games/sdvx/index.ts
6429
7214
  var name13 = "Noah-SDVX";
6430
7215
  var inject3 = ["database", "globalConfig"];
6431
- var logger4 = new import_koishi18.Logger("Noah-SDVX");
7216
+ var logger5 = new import_koishi21.Logger("Noah-SDVX");
6432
7217
  async function apply13(ctx, config) {
6433
7218
  ;
6434
7219
  [
6435
7220
  ["en-US", en_US_default4],
6436
7221
  ["zh-CN", zh_CN_default4]
6437
7222
  ].forEach(([lang, file]) => ctx.i18n.define(lang, file));
6438
- registerSDVXAdapters(logger4, config.sdvx);
7223
+ registerSDVXAdapters(logger5, config.sdvx);
6439
7224
  ctx.plugin(database_exports2, config.sdvx);
6440
7225
  ctx.plugin(command_exports2, config.sdvx);
6441
7226
  ctx.plugin(event_exports2, config.sdvx);
@@ -6443,47 +7228,50 @@ async function apply13(ctx, config) {
6443
7228
  __name(apply13, "apply");
6444
7229
 
6445
7230
  // src/config.ts
6446
- var import_koishi26 = require("koishi");
7231
+ var import_koishi29 = require("koishi");
6447
7232
 
6448
7233
  // src/asset/config.ts
6449
- var import_koishi19 = require("koishi");
6450
- var assetConfig = import_koishi19.Schema.object({
6451
- data_path: import_koishi19.Schema.string().default("noah_assets"),
6452
- auto_update: import_koishi19.Schema.boolean().default(true),
6453
- update_url: import_koishi19.Schema.string().default("https://github.com/logthm/noah/releases/latest/download/"),
6454
- update_interval: import_koishi19.Schema.number().default(24 * 60 * 60 * 1e3),
7234
+ var import_koishi22 = require("koishi");
7235
+ var assetConfig = import_koishi22.Schema.object({
7236
+ data_path: import_koishi22.Schema.string().default("noah_assets"),
7237
+ auto_update: import_koishi22.Schema.boolean().default(true),
7238
+ update_url: import_koishi22.Schema.string().default("https://github.com/logthm/noah/releases/latest/download/"),
7239
+ update_interval: import_koishi22.Schema.number().default(24 * 60 * 60 * 1e3),
6455
7240
  // 24 hours in milliseconds
6456
- github_token: import_koishi19.Schema.string().description("GitHub token for accessing private repositories").role("secret")
7241
+ github_token: import_koishi22.Schema.string().description("GitHub token for accessing private repositories").role("secret")
6457
7242
  }).i18n({
6458
7243
  "en-US": en_US_default._config,
6459
7244
  "zh-CN": zh_CN_default._config
6460
7245
  });
6461
7246
 
6462
7247
  // src/core/config.ts
6463
- var import_koishi20 = require("koishi");
6464
- var coreConfig = import_koishi20.Schema.object({
6465
- adminUsers: import_koishi20.Schema.array(String).role("table"),
6466
- guildNameCards: import_koishi20.Schema.array(String).role("table").default(["Noah | /help 获取食用指南"])
7248
+ var import_koishi23 = require("koishi");
7249
+ var coreConfig = import_koishi23.Schema.object({
7250
+ adminUsers: import_koishi23.Schema.array(String).role("table"),
7251
+ guildNameCards: import_koishi23.Schema.array(String).role("table").default(["Noah | /help 获取食用指南"]),
7252
+ tokenTtl: import_koishi23.Schema.number().default(1800),
7253
+ frontendUrl: import_koishi23.Schema.string().default(""),
7254
+ corsOrigin: import_koishi23.Schema.string().default("*")
6467
7255
  }).i18n({
6468
7256
  "en-US": en_US_default2._config,
6469
7257
  "zh-CN": zh_CN_default2._config
6470
7258
  });
6471
7259
 
6472
7260
  // src/fun/poke/config.ts
6473
- var import_koishi21 = require("koishi");
6474
- var pokeConfig = import_koishi21.Schema.object({
6475
- interval: import_koishi21.Schema.number().default(1e3).step(100),
6476
- warning: import_koishi21.Schema.boolean().default(false),
6477
- prompt: import_koishi21.Schema.array(
6478
- import_koishi21.Schema.object({
6479
- content: import_koishi21.Schema.string().required(),
6480
- weight: import_koishi21.Schema.number().min(0).max(100).default(50)
7261
+ var import_koishi24 = require("koishi");
7262
+ var pokeConfig = import_koishi24.Schema.object({
7263
+ interval: import_koishi24.Schema.number().default(1e3).step(100),
7264
+ warning: import_koishi24.Schema.boolean().default(false),
7265
+ prompt: import_koishi24.Schema.array(
7266
+ import_koishi24.Schema.object({
7267
+ content: import_koishi24.Schema.string().required(),
7268
+ weight: import_koishi24.Schema.number().min(0).max(100).default(50)
6481
7269
  })
6482
7270
  ).role("table"),
6483
- messages: import_koishi21.Schema.array(
6484
- import_koishi21.Schema.object({
6485
- content: import_koishi21.Schema.string().required(),
6486
- weight: import_koishi21.Schema.number().min(0).max(100).default(50)
7271
+ messages: import_koishi24.Schema.array(
7272
+ import_koishi24.Schema.object({
7273
+ type: import_koishi24.Schema.union(["text", "noah", "chat"]).default("text"),
7274
+ weight: import_koishi24.Schema.number().min(0).max(100).default(50)
6487
7275
  })
6488
7276
  ).role("table")
6489
7277
  }).i18n({
@@ -6492,7 +7280,7 @@ var pokeConfig = import_koishi21.Schema.object({
6492
7280
  });
6493
7281
 
6494
7282
  // src/games/general/config.ts
6495
- var import_koishi22 = require("koishi");
7283
+ var import_koishi25 = require("koishi");
6496
7284
 
6497
7285
  // src/games/general/locales/en-US.yml
6498
7286
  var en_US_default5 = { _config: { $desc: "General Module Settings", barcode_api_url: "<p>The URL of the barcode API</p>" } };
@@ -6501,33 +7289,33 @@ var en_US_default5 = { _config: { $desc: "General Module Settings", barcode_api_
6501
7289
  var zh_CN_default5 = { _config: { $desc: "通用工具模块设置", barcode_api_url: "<p>条形码 API 地址</p>" } };
6502
7290
 
6503
7291
  // src/games/general/config.ts
6504
- var generalConfig = import_koishi22.Schema.object({
6505
- barcode_api_url: import_koishi22.Schema.string().required()
7292
+ var generalConfig = import_koishi25.Schema.object({
7293
+ barcode_api_url: import_koishi25.Schema.string().required()
6506
7294
  }).i18n({
6507
7295
  "en-US": en_US_default5._config,
6508
7296
  "zh-CN": zh_CN_default5._config
6509
7297
  });
6510
7298
 
6511
7299
  // src/games/sdvx/config.ts
6512
- var import_koishi23 = require("koishi");
6513
- var sdvxConfig = import_koishi23.Schema.object({
6514
- sdvx_data_url: import_koishi23.Schema.string().required(),
6515
- sdvx_search_url: import_koishi23.Schema.string().required()
7300
+ var import_koishi26 = require("koishi");
7301
+ var sdvxConfig = import_koishi26.Schema.object({
7302
+ sdvx_data_url: import_koishi26.Schema.string().required(),
7303
+ sdvx_search_url: import_koishi26.Schema.string().required()
6516
7304
  }).i18n({
6517
7305
  "en-US": en_US_default4._config,
6518
7306
  "zh-CN": zh_CN_default4._config
6519
7307
  });
6520
7308
 
6521
7309
  // src/global/config.ts
6522
- var import_koishi24 = require("koishi");
6523
- var globalConfig = import_koishi24.Schema.object({
6524
- official_support_url: import_koishi24.Schema.string().default("https://noah.logthm.com"),
6525
- maoServerUrl: import_koishi24.Schema.string().default("http://maomani.cn:577"),
6526
- maoApiKey: import_koishi24.Schema.string().role("secret").default("")
7310
+ var import_koishi27 = require("koishi");
7311
+ var globalConfig = import_koishi27.Schema.object({
7312
+ official_support_url: import_koishi27.Schema.string().default("https://noah.logthm.com"),
7313
+ maoServerUrl: import_koishi27.Schema.string().default("http://maomani.cn:577"),
7314
+ maoApiKey: import_koishi27.Schema.string().role("secret").default("")
6527
7315
  });
6528
7316
 
6529
7317
  // src/slash/config.ts
6530
- var import_koishi25 = require("koishi");
7318
+ var import_koishi28 = require("koishi");
6531
7319
 
6532
7320
  // src/slash/locales/en-US.yml
6533
7321
  var en_US_default6 = { _config: { $desc: "Slash Module Settings", test_guilds: "**Guilds To Sync**", auto_sync_on_start: "**Auto Sync on Connect**" } };
@@ -6536,16 +7324,16 @@ var en_US_default6 = { _config: { $desc: "Slash Module Settings", test_guilds: "
6536
7324
  var zh_CN_default6 = { _config: { $desc: "Slash 模块设置", test_guilds: "**默认同步 Guild 列表**", auto_sync_on_start: "**连接后自动同步**" } };
6537
7325
 
6538
7326
  // src/slash/config.ts
6539
- var slashConfig = import_koishi25.Schema.object({
6540
- test_guilds: import_koishi25.Schema.array(String).default([]),
6541
- auto_sync_on_start: import_koishi25.Schema.boolean().default(true)
7327
+ var slashConfig = import_koishi28.Schema.object({
7328
+ test_guilds: import_koishi28.Schema.array(String).default([]),
7329
+ auto_sync_on_start: import_koishi28.Schema.boolean().default(true)
6542
7330
  }).i18n({
6543
7331
  "en-US": en_US_default6._config,
6544
7332
  "zh-CN": zh_CN_default6._config
6545
7333
  });
6546
7334
 
6547
7335
  // src/config.ts
6548
- var Config = import_koishi26.Schema.object({
7336
+ var Config = import_koishi29.Schema.object({
6549
7337
  global: globalConfig,
6550
7338
  core: coreConfig,
6551
7339
  sdvx: sdvxConfig,
@@ -6557,7 +7345,7 @@ var Config = import_koishi26.Schema.object({
6557
7345
 
6558
7346
  // src/index.ts
6559
7347
  var name14 = "noah";
6560
- var inject4 = ["database", "skia"];
7348
+ var inject4 = ["database", "skia", "server"];
6561
7349
  async function apply14(ctx, config) {
6562
7350
  initConstants(ctx);
6563
7351
  ctx.set("globalConfig", config.global);