koishi-plugin-noah 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/lib/config.d.ts +2 -0
  2. package/lib/core/command.d.ts +4 -0
  3. package/lib/core/commands/bind.d.ts +3 -0
  4. package/lib/core/commands/card.d.ts +3 -0
  5. package/lib/core/commands/help.d.ts +3 -0
  6. package/lib/core/commands/server.d.ts +3 -0
  7. package/lib/core/config.d.ts +2 -0
  8. package/lib/core/database.d.ts +49 -0
  9. package/lib/core/index.d.ts +6 -0
  10. package/lib/core/services/card-service.d.ts +46 -0
  11. package/lib/core/services/drawer-service.d.ts +0 -0
  12. package/lib/core/services/server-service.d.ts +61 -0
  13. package/lib/core/types/index.d.ts +16 -0
  14. package/lib/core/utils/card.d.ts +24 -0
  15. package/lib/core/utils/role.d.ts +6 -0
  16. package/lib/drawer/BaseDrawer.d.ts +19 -0
  17. package/lib/drawer/Core/index.d.ts +22 -0
  18. package/lib/drawer/DrawerFactory.d.ts +8 -0
  19. package/lib/drawer/IIDX/index.d.ts +29 -0
  20. package/lib/drawer/SDVX/index.d.ts +35 -0
  21. package/lib/drawer/index.d.ts +13 -0
  22. package/lib/games/iidx/services/drawer-service.d.ts +0 -0
  23. package/lib/games/sdvx/command.d.ts +4 -0
  24. package/lib/games/sdvx/commands/vf.d.ts +3 -0
  25. package/lib/games/sdvx/config.d.ts +2 -0
  26. package/lib/games/sdvx/database.d.ts +3 -0
  27. package/lib/games/sdvx/event.d.ts +3 -0
  28. package/lib/games/sdvx/index.d.ts +6 -0
  29. package/lib/games/sdvx/services/drawer-service.d.ts +0 -0
  30. package/lib/games/sdvx/services/music-service.d.ts +10 -0
  31. package/lib/games/sdvx/services/score-service.d.ts +18 -0
  32. package/lib/games/sdvx/types/index.d.ts +82 -0
  33. package/lib/games/sdvx/utils/vf.d.ts +19 -0
  34. package/lib/index.d.ts +6 -0
  35. package/lib/index.js +2389 -0
  36. package/lib/servers/Asphyxia/index.d.ts +11 -0
  37. package/lib/servers/Asphyxia/services/iidx-service.d.ts +9 -0
  38. package/lib/servers/Asphyxia/services/sdvx-service.d.ts +12 -0
  39. package/lib/servers/Mao/index.d.ts +11 -0
  40. package/lib/servers/Mao/services/sdvx-service.d.ts +19 -0
  41. package/lib/servers/ServerFactory.d.ts +5 -0
  42. package/lib/servers/config.d.ts +0 -0
  43. package/lib/servers/index.d.ts +13 -0
  44. package/lib/servers/utils/difficulty.d.ts +14 -0
  45. package/lib/servers/utils/grade.d.ts +3 -0
  46. package/lib/servers/utils/xml.d.ts +14 -0
  47. package/lib/types/config.d.ts +13 -0
  48. package/lib/types/index.d.ts +66 -0
  49. package/package.json +47 -0
  50. package/readme.md +5 -0
package/lib/index.js ADDED
@@ -0,0 +1,2389 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name11 in all)
10
+ __defProp(target, name11, { get: all[name11], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ Config: () => Config,
34
+ apply: () => apply10,
35
+ inject: () => inject3,
36
+ name: () => name10
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+
40
+ // src/core/index.ts
41
+ var core_exports = {};
42
+ __export(core_exports, {
43
+ apply: () => apply3,
44
+ inject: () => inject,
45
+ logger: () => logger,
46
+ name: () => name3
47
+ });
48
+ var import_koishi4 = require("koishi");
49
+
50
+ // src/core/locales/en-US.yml
51
+ var en_US_default = { _config: { $desc: "Core Module Settings", adminUsers: "**Plugin Admin** User ID (use inspect command to get)" }, commands: { timeout: "Noah didn't wait for your reply, please try again!", noah: { help: { description: "Show Noah help information", messages: { content: "<p>Help document:</p>\n<a>https://docs.logthm.com/noah</a>" } } }, bind: { description: "Bind card", messages: { prompt: "<p>Please enter your card number:</p>", "invalid-code": "<p>The card number is incorrect, if you don't remember it, go to the arcade to check it~</p>", duplicate: "<p>This card has already been bound (︶^︶)</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>" } }, "add-server": { description: "Add server" }, card: { description: "Manage cards", 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>", "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: { "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-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-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>" } } } };
52
+
53
+ // src/core/locales/zh-CN.yml
54
+ var zh_CN_default = { _config: { $desc: "Core 模块设置", adminUsers: "**插件管理员** 的用户id (使用 inspect 指令获取)" }, commands: { timeout: "Noah 没等到你的回复,请重试!", noah: { help: { description: "显示 Noah 帮助信息", messages: { content: "<p>帮助文档:</p>\n<a>https://docs.logthm.com/noah</a>" } } }, bind: { description: "绑定卡片", messages: { prompt: "<p>请输入你的卡号:</p>", "invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", duplicate: "<p>这张卡已经被绑定啦 (︶^︶)</p>", name: "<p>收到!为这张卡取一个名字吧,不要带空格哦:</p>", invalid_name: "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", success: "<p>绑好啦,你的卡片信息如下:</p>\n<p>[{cardName}]</p>\n<p>{cardCode}</p>" } }, "add-server": { description: "添加服务器" }, card: { description: "管理卡片", messages: { "invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", "invalid-select": "<p>没有该选项!</p>", "menu-select": "<p>[卡片管理]</p>\n{card_list}\n<p>0. 添加新卡片</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>请输入序号:</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>请输入序号:</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>", "menu-2-success": "<p>修改成功!</p>", "menu-2-error-invalid-code": "<p>卡号不对哟,不记得的话去机台刷一下吧~</p>", "menu-3-success": "<p>已删除!</p>", "menu-4-prompt": "<p>输入一个新名字,不要带空格哦:</p>", "menu-4-error-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "menu-4-success": "<p>修改成功!</p>", "menu-5": "{server_list}\n<p>请输入序号:</p>", "menu-5-success": "<p>设置成功!</p>", "menu-5-error-duplicate": "<p>选择的服务器与旧的默认服务器相同!</p>" } }, server: { description: "管理服务器", messages: { "invalid-select": "<p>没有该选项!</p>", "no-auth": "<p>只有群管理员能使用本功能哦~</p>", "admin-menu-select": "<p>[群聊服务器管理]</p>\n{server_list}\n<p>0. 添加新服务器</p>\n<p>请输入序号:</p>", "menu-select": "<p>[服务器管理]</p>\n{server_list}\n<p>0. 添加新服务器</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>请输入序号:</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>", "menu-2-success": "<p>修改成功!</p>", "menu-3-success": "<p>已删除!</p>", "menu-4-prompt": "<p>输入一个新名字,不要带空格哦:</p>", "menu-4-error-invalid-name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "menu-4-success": "<p>修改成功!</p>", "add-type": "<p>请选择服务器的类型:</p>\n{server_type_list}", "add-url": "<p>请输入服务器的 url:</p>", "add-name": "<p>收到!为服务器取一个名字吧,不要带空格哦:</p>", "add-invalid_name": "<p>名字不要带空格!重来重来 o(一︿一+)o</p>", "add-success": "<p>添加成功啦,服务器信息如下:</p>\n<p>[{serverName}]</p>\n<p>类型:{serverType}</p>" } } } };
55
+
56
+ // src/core/database.ts
57
+ var database_exports = {};
58
+ __export(database_exports, {
59
+ apply: () => apply,
60
+ name: () => name
61
+ });
62
+ var name = "database";
63
+ function apply(ctx) {
64
+ ctx.model.extend("user", {
65
+ defaultCardId: "unsigned",
66
+ defaultServerId: "unsigned"
67
+ });
68
+ ctx.model.extend("channel", {
69
+ defaultServerId: "unsigned"
70
+ });
71
+ ctx.model.extend("card", {
72
+ id: "unsigned",
73
+ code: "string",
74
+ name: "string",
75
+ defaultServerId: "unsigned"
76
+ }, {
77
+ autoInc: true
78
+ });
79
+ ctx.model.extend("server", {
80
+ id: "unsigned",
81
+ type: "string",
82
+ name: "string",
83
+ baseUrl: "string",
84
+ pcbid: "string",
85
+ supportedGames: "list"
86
+ }, {
87
+ autoInc: true
88
+ });
89
+ ctx.model.extend("user_card", {
90
+ id: "unsigned",
91
+ uid: "unsigned",
92
+ cid: "unsigned"
93
+ }, {
94
+ autoInc: true
95
+ });
96
+ ctx.model.extend("user_server", {
97
+ id: "unsigned",
98
+ uid: "unsigned",
99
+ sid: "unsigned"
100
+ }, {
101
+ autoInc: true
102
+ });
103
+ ctx.model.extend("channel_server", {
104
+ id: "unsigned",
105
+ cid: "string",
106
+ sid: "unsigned"
107
+ }, {
108
+ autoInc: true
109
+ });
110
+ }
111
+ __name(apply, "apply");
112
+
113
+ // src/core/command.ts
114
+ var command_exports = {};
115
+ __export(command_exports, {
116
+ apply: () => apply2,
117
+ name: () => name2
118
+ });
119
+
120
+ // src/core/commands/help.ts
121
+ function help(ctx, config) {
122
+ ctx.command("noah.help").alias("帮助").action(({ session }) => {
123
+ return session.text(".content");
124
+ });
125
+ }
126
+ __name(help, "help");
127
+
128
+ // src/core/utils/card.ts
129
+ var crypto = __toESM(require("crypto"));
130
+ var KEY_STRING = "?I'llB2c.YouXXXeMeHaYpy!";
131
+ var KEY_BUFFER = Buffer.from(KEY_STRING, "ascii");
132
+ var DES3_KEY = Buffer.from(KEY_BUFFER.map((b) => b * 2 & 255));
133
+ var IV = Buffer.alloc(8, 0);
134
+ var ALPHABET = "0123456789ABCDEFGHJKLMNPRSTUWXYZ";
135
+ function encDes(data) {
136
+ if (data.length !== 8) {
137
+ throw new Error("encDes: data must be 8 bytes");
138
+ }
139
+ const cipher = crypto.createCipheriv("des-ede3-cbc", DES3_KEY, IV);
140
+ cipher.setAutoPadding(false);
141
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
142
+ return encrypted;
143
+ }
144
+ __name(encDes, "encDes");
145
+ function decDes(data) {
146
+ if (data.length !== 8) {
147
+ throw new Error("decDes: data must be 8 bytes");
148
+ }
149
+ const decipher = crypto.createDecipheriv("des-ede3-cbc", DES3_KEY, IV);
150
+ decipher.setAutoPadding(false);
151
+ const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
152
+ return decrypted;
153
+ }
154
+ __name(decDes, "decDes");
155
+ function pack5(data) {
156
+ let bits = data.map((n) => n.toString(2).padStart(5, "0")).join("");
157
+ const padLength = (8 - bits.length % 8) % 8;
158
+ bits += "0".repeat(padLength);
159
+ const bytes = [];
160
+ for (let i = 0; i < bits.length; i += 8) {
161
+ const byteStr = bits.slice(i, i + 8);
162
+ bytes.push(parseInt(byteStr, 2));
163
+ }
164
+ return Buffer.from(bytes);
165
+ }
166
+ __name(pack5, "pack5");
167
+ function unpack5(data) {
168
+ let bits = Array.from(data).map((b) => b.toString(2).padStart(8, "0")).join("");
169
+ const padLength = (5 - bits.length % 5) % 5;
170
+ bits += "0".repeat(padLength);
171
+ const numbers = [];
172
+ for (let i = 0; i < bits.length; i += 5) {
173
+ const chunk = bits.slice(i, i + 5);
174
+ numbers.push(parseInt(chunk, 2));
175
+ }
176
+ return numbers;
177
+ }
178
+ __name(unpack5, "unpack5");
179
+ function checksum(data) {
180
+ if (data.length < 15) {
181
+ throw new Error("checksum: data must have at least 15 elements");
182
+ }
183
+ let chk = 0;
184
+ for (let i = 0; i < 15; i++) {
185
+ chk += data[i] * (i % 3 + 1);
186
+ }
187
+ while (chk > 31) {
188
+ chk = (chk >> 5) + (chk & 31);
189
+ }
190
+ return chk;
191
+ }
192
+ __name(checksum, "checksum");
193
+ function toKonamiId(uid) {
194
+ const upperUid = uid.toUpperCase();
195
+ let cardType;
196
+ if (upperUid.startsWith("E004")) {
197
+ cardType = 1;
198
+ } else if (upperUid.startsWith("0")) {
199
+ cardType = 2;
200
+ } else {
201
+ throw new Error("Invalid UID prefix");
202
+ }
203
+ if (uid.length !== 16) {
204
+ throw new Error("UID must be 16 hex digits");
205
+ }
206
+ const kid = Buffer.from(uid, "hex");
207
+ if (kid.length !== 8) {
208
+ throw new Error("Parsed UID must be 8 bytes");
209
+ }
210
+ const reversedKid = Buffer.from(kid).reverse();
211
+ const encrypted = encDes(reversedKid);
212
+ const unpacked = unpack5(encrypted);
213
+ const out = unpacked.slice(0, 13).concat([0, 0, 0]);
214
+ out[0] ^= cardType;
215
+ out[13] = 1;
216
+ for (let i = 1; i < 14; i++) {
217
+ out[i] ^= out[i - 1];
218
+ }
219
+ out[14] = cardType;
220
+ out[15] = checksum(out);
221
+ let konamiId = "";
222
+ for (const n of out) {
223
+ if (n < 0 || n >= ALPHABET.length) {
224
+ throw new Error(`Invalid index ${n} when mapping to alphabet`);
225
+ }
226
+ konamiId += ALPHABET[n];
227
+ }
228
+ return konamiId;
229
+ }
230
+ __name(toKonamiId, "toKonamiId");
231
+ function toUid(konamiId) {
232
+ if (konamiId.length !== 16) {
233
+ throw new Error("KONAMI ID must be 16 characters");
234
+ }
235
+ const cardTypeChar = konamiId[14];
236
+ let cardType;
237
+ if (cardTypeChar === "1") {
238
+ cardType = 1;
239
+ } else if (cardTypeChar === "2") {
240
+ cardType = 2;
241
+ } else {
242
+ throw new Error("Invalid KONAMI ID card type");
243
+ }
244
+ const card2 = [];
245
+ for (const ch of konamiId) {
246
+ const index = ALPHABET.indexOf(ch);
247
+ if (index === -1) {
248
+ throw new Error(`Invalid character in KONAMI ID: ${ch}`);
249
+ }
250
+ card2.push(index);
251
+ }
252
+ if ((card2[11] & 1) !== (card2[12] & 1)) {
253
+ throw new Error("Parity check failed (positions 11 and 12)");
254
+ }
255
+ if (card2[13] !== (card2[12] ^ 1)) {
256
+ throw new Error("Encoding check failed (position 13)");
257
+ }
258
+ if (card2[15] !== checksum(card2)) {
259
+ throw new Error("Checksum failed");
260
+ }
261
+ for (let i = 13; i >= 1; i--) {
262
+ card2[i] ^= card2[i - 1];
263
+ }
264
+ card2[0] ^= cardType;
265
+ const packed = pack5(card2.slice(0, 13));
266
+ const dataForDec = packed.slice(0, 8);
267
+ const decrypted = decDes(dataForDec);
268
+ const uidBuffer = Buffer.from(decrypted).reverse();
269
+ const uidHex = uidBuffer.toString("hex").toUpperCase();
270
+ if (cardType === 1) {
271
+ if (!uidHex.startsWith("E004")) {
272
+ throw new Error("Invalid card type: expected magnetic stripe (E004 prefix)");
273
+ }
274
+ } else if (cardType === 2) {
275
+ if (!uidHex.startsWith("0")) {
276
+ throw new Error("Invalid card type: expected FeliCa (leading 0)");
277
+ }
278
+ }
279
+ return uidHex;
280
+ }
281
+ __name(toUid, "toUid");
282
+ function classifyCardId(input) {
283
+ if (input.length !== 16) {
284
+ return "invalid";
285
+ }
286
+ const uidRegex = /^[0-9A-Fa-f]{16}$/;
287
+ if (uidRegex.test(input)) {
288
+ try {
289
+ toKonamiId(input);
290
+ } catch (e) {
291
+ return "invalid";
292
+ }
293
+ return "uid";
294
+ }
295
+ const ALPHABET2 = "0123456789ABCDEFGHJKLMNPRSTUWXYZ";
296
+ const isKonami = input.split("").every((ch) => ALPHABET2.includes(ch));
297
+ if (isKonami) {
298
+ try {
299
+ toUid(input);
300
+ } catch (e) {
301
+ return "invalid";
302
+ }
303
+ return "konamiid";
304
+ }
305
+ return "invalid";
306
+ }
307
+ __name(classifyCardId, "classifyCardId");
308
+ function processInputCardCode(input) {
309
+ return input.toUpperCase().replace(/[IOQV]/gi, (match) => {
310
+ switch (match.toUpperCase()) {
311
+ case "I":
312
+ return "1";
313
+ case "O":
314
+ return "0";
315
+ case "Q":
316
+ return "0";
317
+ case "V":
318
+ return "U";
319
+ default:
320
+ return match;
321
+ }
322
+ });
323
+ }
324
+ __name(processInputCardCode, "processInputCardCode");
325
+
326
+ // src/core/services/card-service.ts
327
+ var CardService = class {
328
+ constructor(ctx) {
329
+ this.ctx = ctx;
330
+ }
331
+ static {
332
+ __name(this, "CardService");
333
+ }
334
+ tableName = "card";
335
+ /**
336
+ * 获取所有卡片列表
337
+ */
338
+ async getAllCards() {
339
+ const cards = await this.ctx.database.get(this.tableName, {});
340
+ return cards.map((card2) => ({
341
+ ...card2,
342
+ defaultServerId: card2.defaultServerId ?? 0
343
+ }));
344
+ }
345
+ /**
346
+ * 根据 user id 获取卡片列表
347
+ * @param uid 用户的 user id
348
+ * @returns 该用户绑定的卡片的完整信息列表
349
+ */
350
+ async getCardsByUid(uid) {
351
+ const rows = await this.ctx.database.get("user_card", { uid }, ["cid"]);
352
+ const cards = await this.ctx.database.get(this.tableName, rows.map((obj) => obj.cid));
353
+ return cards.map((card2) => ({
354
+ ...card2,
355
+ defaultServerId: card2.defaultServerId ?? 0
356
+ }));
357
+ }
358
+ /**
359
+ * 根据 id 获取卡片信息
360
+ */
361
+ async getCardById(id) {
362
+ const rows = await this.ctx.database.get(this.tableName, id);
363
+ if (!rows[0])
364
+ return null;
365
+ return {
366
+ ...rows[0],
367
+ defaultServerId: rows[0].defaultServerId ?? 0
368
+ };
369
+ }
370
+ /**
371
+ * 根据 code 获取卡片信息
372
+ */
373
+ async getCardByCode(code) {
374
+ const rows = await this.ctx.database.get(this.tableName, { code });
375
+ if (!rows[0])
376
+ return null;
377
+ return {
378
+ ...rows[0],
379
+ defaultServerId: rows[0].defaultServerId ?? 0
380
+ };
381
+ }
382
+ /**
383
+ * 创建一张新的卡
384
+ * @param uid 用户的 uid
385
+ * @param code 卡片的卡号
386
+ * @param name 卡片的名称
387
+ * @param defaultServerId 默认服务器 ID(若没有默认服务器则为 0)
388
+ * @returns 创建成功后包含自动填充 id 等字段的完整对象
389
+ */
390
+ async createCard(uid, code, name11, defaultServerId = 0) {
391
+ const data = {
392
+ code,
393
+ name: name11,
394
+ defaultServerId
395
+ };
396
+ const res = await this.ctx.database.create(this.tableName, data);
397
+ await this.ctx.database.create("user_card", { uid, cid: res.id });
398
+ return {
399
+ ...res,
400
+ defaultServerId: res.defaultServerId ?? 0
401
+ };
402
+ }
403
+ /**
404
+ * 更新卡片的信息
405
+ * - 可更新 code、name、defaultServerId
406
+ * @param id 卡片的 id
407
+ * @param partial 更新字段
408
+ */
409
+ async updateCard(id, partial) {
410
+ if (partial.defaultServerId === void 0) {
411
+ partial.defaultServerId = 0;
412
+ }
413
+ await this.ctx.database.set(this.tableName, id, partial);
414
+ }
415
+ /**
416
+ * 删除卡片
417
+ * @param id 卡片 id
418
+ */
419
+ async removeCard(id) {
420
+ await this.ctx.database.remove(this.tableName, id);
421
+ }
422
+ };
423
+
424
+ // src/core/commands/bind.ts
425
+ function bind(ctx, config) {
426
+ ctx.command("bind [cardCode]").alias("绑卡").userFields(["defaultCardId", "id"]).action(async ({ session }, cardCode) => {
427
+ const cardService = new CardService(ctx);
428
+ if (!cardCode) {
429
+ await session.send(session.text(".prompt"));
430
+ cardCode = await session.prompt();
431
+ if (!cardCode)
432
+ return session.text("commands.timeout");
433
+ }
434
+ cardCode = processInputCardCode(cardCode);
435
+ switch (classifyCardId(cardCode)) {
436
+ case "invalid":
437
+ return session.text(".invalid-code");
438
+ case "konamiid":
439
+ cardCode = toUid(cardCode);
440
+ }
441
+ if (await cardService.getCardByCode(cardCode) != null)
442
+ return session.text(".duplicate");
443
+ await session.send(session.text(".name"));
444
+ const cardName = await session.prompt();
445
+ if (!cardName)
446
+ return session.text("commands.timeout");
447
+ if (cardName.includes(" "))
448
+ return session.text(".invalid-name");
449
+ const res = await cardService.createCard(session.user.id, cardCode, cardName);
450
+ if (session.user.defaultCardId === 0)
451
+ session.user.defaultCardId = res.id;
452
+ return session.text(".success", { cardName, cardCode });
453
+ });
454
+ }
455
+ __name(bind, "bind");
456
+
457
+ // src/core/commands/card.ts
458
+ var import_koishi = require("koishi");
459
+
460
+ // src/core/services/server-service.ts
461
+ var ServerService = class {
462
+ constructor(ctx) {
463
+ this.ctx = ctx;
464
+ }
465
+ static {
466
+ __name(this, "ServerService");
467
+ }
468
+ tableName = "server";
469
+ /**
470
+ * 获取所有服务器列表
471
+ */
472
+ async getAllServers() {
473
+ return await this.ctx.database.get(this.tableName, {});
474
+ }
475
+ /**
476
+ * 根据 user id 获取服务器列表
477
+ * @param uid 用户的 user id
478
+ * @returns 该用户绑定的服务器的完整信息列表
479
+ */
480
+ async getServersByUid(uid) {
481
+ const rows = await this.ctx.database.get("user_server", { uid }, ["sid"]);
482
+ return await this.ctx.database.get("server", rows.map((obj) => obj.sid));
483
+ }
484
+ /**
485
+ * 根据 channel id 获取服务器列表
486
+ * @param cid 频道的 channel id
487
+ * @returns 该频道绑定的服务器的完整信息列表
488
+ */
489
+ async getServersByCid(cid) {
490
+ const rows = await this.ctx.database.get("channel_server", { cid }, ["sid"]);
491
+ return await this.ctx.database.get("server", rows.map((obj) => obj.sid));
492
+ }
493
+ /**
494
+ * 根据 id 获取服务器信息
495
+ */
496
+ async getServerById(id) {
497
+ const rows = await this.ctx.database.get(this.tableName, id);
498
+ return rows[0] ?? null;
499
+ }
500
+ /**
501
+ * 根据名称查找服务器
502
+ */
503
+ async getServerByName(name11) {
504
+ const rows = await this.ctx.database.get(this.tableName, { name: name11 });
505
+ return rows[0] ?? null;
506
+ }
507
+ /**
508
+ * 根据类型筛选服务器
509
+ */
510
+ async getServersByType(type) {
511
+ const servers = await this.ctx.database.get(this.tableName, { type });
512
+ return servers;
513
+ }
514
+ /**
515
+ * 创建一个新的服务器记录并关联到用户
516
+ * @param serverData 要插入的数据 (不含 id)
517
+ * @param uid 用户的 ID
518
+ * @returns 插入后的完整数据
519
+ */
520
+ async createServerForUser(serverData, uid) {
521
+ const res = await this.ctx.database.create(this.tableName, serverData);
522
+ await this.ctx.database.create("user_server", { uid, sid: res.id });
523
+ return res;
524
+ }
525
+ /**
526
+ * 创建一个新的服务器记录并关联到频道
527
+ * @param serverData 要插入的数据 (不含 id)
528
+ * @param cid 频道的 ID
529
+ * @returns 插入后的完整数据
530
+ */
531
+ async createServerForChannel(serverData, cid) {
532
+ const res = await this.ctx.database.create(this.tableName, serverData);
533
+ await this.ctx.database.create("channel_server", { cid, sid: res.id });
534
+ return res;
535
+ }
536
+ /**
537
+ * 更新服务器记录
538
+ * @param id 服务器 id
539
+ * @param partial 要更新的字段
540
+ */
541
+ async updateServer(id, partial) {
542
+ await this.ctx.database.set(this.tableName, id, partial);
543
+ }
544
+ /**
545
+ * 删除服务器记录
546
+ * @param id 服务器 id
547
+ */
548
+ async removeServer(id) {
549
+ await this.ctx.database.remove(this.tableName, id);
550
+ }
551
+ };
552
+
553
+ // src/core/commands/card.ts
554
+ function card(ctx, config) {
555
+ ctx.command("card").alias("卡片管理").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).action(async ({ session }) => {
556
+ const cardService = new CardService(ctx);
557
+ const serverService = new ServerService(ctx);
558
+ return await showCardSelectMenu(
559
+ ctx,
560
+ session,
561
+ cardService,
562
+ serverService,
563
+ session.user.id,
564
+ session.user.defaultCardId,
565
+ session.user.defaultServerId,
566
+ session.channel.id,
567
+ session.channel.defaultServerId
568
+ );
569
+ });
570
+ }
571
+ __name(card, "card");
572
+ async function showCardSelectMenu(ctx, session, cardService, serverService, uid, userDefaultCardId, userDefaultServerId, cid, channelDefaultServerId) {
573
+ const res = await cardService.getCardsByUid(uid);
574
+ let cardListMsg = "";
575
+ for (let i = 0; i < res.length; i++) {
576
+ if (res[i].id === userDefaultCardId) {
577
+ cardListMsg += `<p>${i + 1}. ${res[i].name} < 默认卡片</p>`;
578
+ } else {
579
+ cardListMsg += `<p>${i + 1}. ${res[i].name}</p>`;
580
+ }
581
+ }
582
+ const msg = import_koishi.h.unescape(session.text(".menu-select", { card_list: cardListMsg })).replace("< 默认卡片", "&lt; 默认卡片");
583
+ await session.send(msg);
584
+ const select = await session.prompt();
585
+ if (!select)
586
+ return session.text("commands.timeout");
587
+ const selectNum = parseInt(select, 10);
588
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > res.length) {
589
+ return session.text(".invalid-select");
590
+ }
591
+ if (selectNum === 0) {
592
+ return session.execute("bind");
593
+ } else {
594
+ return showCardMenu(ctx, session, res[selectNum - 1], cardService, serverService, uid, userDefaultCardId, userDefaultServerId, cid, channelDefaultServerId);
595
+ }
596
+ }
597
+ __name(showCardSelectMenu, "showCardSelectMenu");
598
+ async function showCardMenu(ctx, session, card2, cardService, serverService, uid, userDefaultCardId, userDefaultServerId, cid, channelDefaultServerId) {
599
+ if (card2.defaultServerId != 0) {
600
+ const server2 = await serverService.getServerById(card2.defaultServerId);
601
+ await session.send(session.text(".menu-has-bound-server", { name: card2.name, defaultServerName: server2.name }));
602
+ } else {
603
+ await session.send(session.text(".menu", card2));
604
+ }
605
+ const select = await session.prompt();
606
+ if (!select)
607
+ return session.text("commands.timeout");
608
+ const selectNum = parseInt(select, 10);
609
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > 5) {
610
+ return await session.text(".invalid-select");
611
+ }
612
+ if (selectNum === 0) {
613
+ return await showCardSelectMenu(ctx, session, cardService, serverService, uid, userDefaultCardId, userDefaultServerId, cid, channelDefaultServerId);
614
+ }
615
+ if (selectNum === 1) {
616
+ if (userDefaultCardId === card2.id)
617
+ return session.text(".menu-1-error-duplicate");
618
+ userDefaultCardId = card2.id;
619
+ return session.text(".menu-1-success");
620
+ }
621
+ if (selectNum === 2) {
622
+ await session.send(session.text(".menu-2-prompt", card2));
623
+ let cardCode = await session.prompt();
624
+ if (!cardCode)
625
+ return session.text("commands.timeout");
626
+ if (cardCode === "0")
627
+ return showCardMenu(ctx, session, card2, cardService, serverService, uid, userDefaultCardId, userDefaultServerId, cid, channelDefaultServerId);
628
+ cardCode = processInputCardCode(cardCode);
629
+ switch (classifyCardId(cardCode)) {
630
+ case "invalid":
631
+ return session.text(".menu-2-error-invalid-code");
632
+ case "konamiid":
633
+ cardCode = toUid(cardCode);
634
+ }
635
+ if (await cardService.getCardByCode(cardCode) != null)
636
+ return session.text(".duplicate");
637
+ await cardService.updateCard(card2.id, { code: cardCode });
638
+ return session.text(".menu-2-success");
639
+ }
640
+ if (selectNum === 3) {
641
+ await cardService.removeCard(card2.id);
642
+ if (userDefaultCardId === card2.id) {
643
+ const res = await cardService.getCardsByUid(uid);
644
+ if (res.length === 0)
645
+ userDefaultCardId = 0;
646
+ else
647
+ userDefaultCardId = res[0].id;
648
+ }
649
+ return session.text(".menu-3-success");
650
+ }
651
+ if (selectNum === 4) {
652
+ await session.send(session.text(".menu-4-prompt"));
653
+ const cardName = await session.prompt();
654
+ if (!cardName)
655
+ return session.text("commands.timeout");
656
+ if (cardName.includes(" "))
657
+ return session.text(".menu-4-error-invalid-name");
658
+ await cardService.updateCard(card2.id, { name: cardName });
659
+ return session.text(".menu-4-success");
660
+ }
661
+ if (selectNum === 5) {
662
+ const userRes = await serverService.getServersByUid(uid);
663
+ const channelRes = await serverService.getServersByCid(cid);
664
+ const res = channelRes.concat(userRes);
665
+ let serverListMsg = "";
666
+ for (let i = 0; i < res.length; i++) {
667
+ if (res[i].id === userDefaultServerId) {
668
+ serverListMsg += `<p>${i + 1}. ${res[i].name} < 你的默认服务器</p>`;
669
+ } else if (res[i].id === channelDefaultServerId) {
670
+ serverListMsg += `<p>${i + 1}. ${res[i].name} < 群组默认服务器</p>`;
671
+ } else {
672
+ serverListMsg += `<p>${i + 1}. ${res[i].name}</p>`;
673
+ }
674
+ }
675
+ const msg = import_koishi.h.unescape(session.text(".menu-5", { server_list: serverListMsg })).replace("< 你的默认服务器", "&lt; 你的默认服务器").replace("< 群组默认服务器", "&lt; 群组默认服务器");
676
+ await session.send(msg);
677
+ const select2 = await session.prompt();
678
+ if (!select2)
679
+ return session.text("commands.timeout");
680
+ const selectNum2 = parseInt(select2, 10);
681
+ if (isNaN(selectNum2) || selectNum2 < 1 || selectNum2 > res.length) {
682
+ return session.text(".invalid-select");
683
+ }
684
+ if (card2.defaultServerId === res[selectNum2 - 1].id)
685
+ return session.text(".menu-5-error-duplicate");
686
+ await cardService.updateCard(card2.id, { defaultServerId: res[selectNum2 - 1].id });
687
+ return session.text(".menu-5-success");
688
+ }
689
+ }
690
+ __name(showCardMenu, "showCardMenu");
691
+
692
+ // src/core/commands/server.ts
693
+ var import_koishi3 = require("koishi");
694
+
695
+ // src/core/config.ts
696
+ var import_koishi2 = require("koishi");
697
+ var coreConfig = import_koishi2.Schema.object({
698
+ adminUsers: import_koishi2.Schema.union([
699
+ import_koishi2.Schema.array(String),
700
+ import_koishi2.Schema.transform(String, (adminUsers) => [adminUsers])
701
+ ])
702
+ }).i18n({
703
+ "en-US": en_US_default._config,
704
+ "zh-CN": zh_CN_default._config
705
+ });
706
+
707
+ // src/core/utils/role.ts
708
+ function hasPermission(...perms) {
709
+ return perms.some((perm) => perm === true);
710
+ }
711
+ __name(hasPermission, "hasPermission");
712
+ function isPluginAdmin(session, config) {
713
+ console.log(config.adminUsers, session.userId);
714
+ return config.adminUsers?.includes(session.userId) ?? false;
715
+ }
716
+ __name(isPluginAdmin, "isPluginAdmin");
717
+ function isGuildAdmin(session) {
718
+ const platform = session.event.platform;
719
+ let guildMember;
720
+ if (platform === "onebot") {
721
+ guildMember = session.event.member.roles[0];
722
+ return guildMember === "owner" || guildMember === "admin" || guildMember === "SUBCHANNEL_ADMIN" || guildMember === "OWNER" || guildMember === "ADMIN";
723
+ } else if (platform === "qq") {
724
+ return true;
725
+ } else {
726
+ return true;
727
+ }
728
+ }
729
+ __name(isGuildAdmin, "isGuildAdmin");
730
+
731
+ // src/types/index.ts
732
+ var serverValues = ["asphyxia", "mao"];
733
+
734
+ // src/core/commands/server.ts
735
+ function server(ctx, config) {
736
+ ctx.command("server").alias("服务器管理").option("channel", "-c").userFields(["defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).action(async ({ session, options }) => {
737
+ const serverService = new ServerService(ctx);
738
+ if (options.channel) {
739
+ if (!hasPermission(isGuildAdmin(session), isPluginAdmin(session, config)))
740
+ return session.text(".no-auth");
741
+ return await showChannelServerSelectMenu(
742
+ ctx,
743
+ session,
744
+ serverService,
745
+ session.user.id,
746
+ session.user.defaultServerId,
747
+ session.channel.id,
748
+ session.channel.defaultServerId
749
+ );
750
+ }
751
+ return await showUserServerSelectMenu(
752
+ ctx,
753
+ session,
754
+ serverService,
755
+ session.user.id,
756
+ session.user.defaultServerId,
757
+ session.channel.id,
758
+ session.channel.defaultServerId
759
+ );
760
+ });
761
+ }
762
+ __name(server, "server");
763
+ async function showChannelServerSelectMenu(ctx, session, serverService, uid, userDefaultServerId, cid, channelDefaultServerId) {
764
+ const res = await serverService.getServersByCid(cid);
765
+ let serverListMsg = "";
766
+ for (let i = 0; i < res.length; i++) {
767
+ if (res[i].id === channelDefaultServerId) {
768
+ serverListMsg += `<p>${i + 1}. ${res[i].name} < 群聊默认服务器</p>`;
769
+ } else {
770
+ serverListMsg += `<p>${i + 1}. ${res[i].name}</p>`;
771
+ }
772
+ }
773
+ const msg = import_koishi3.h.unescape(session.text(".menu-select", { server_list: serverListMsg })).replace("< 群聊默认服务器", "&lt; 群聊默认服务器");
774
+ await session.send(msg);
775
+ const select = await session.prompt();
776
+ if (!select)
777
+ return session.text("commands.timeout");
778
+ const selectNum = parseInt(select, 10);
779
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > res.length) {
780
+ return session.text(".invalid-select");
781
+ }
782
+ if (selectNum === 0) {
783
+ return addServer(ctx, session, serverService, "channel", uid, userDefaultServerId, cid, channelDefaultServerId);
784
+ } else {
785
+ return showServerMenu(ctx, session, serverService, res[selectNum - 1], "channel", uid, userDefaultServerId, cid, channelDefaultServerId);
786
+ }
787
+ }
788
+ __name(showChannelServerSelectMenu, "showChannelServerSelectMenu");
789
+ async function showUserServerSelectMenu(ctx, session, serverService, uid, userDefaultServerId, cid, channelDefaultServerId) {
790
+ const res = await serverService.getServersByUid(uid);
791
+ let serverListMsg = "";
792
+ for (let i = 0; i < res.length; i++) {
793
+ if (res[i].id === userDefaultServerId) {
794
+ serverListMsg += `<p>${i + 1}. ${res[i].name} < 默认服务器</p>`;
795
+ } else {
796
+ serverListMsg += `<p>${i + 1}. ${res[i].name}</p>`;
797
+ }
798
+ }
799
+ const msg = import_koishi3.h.unescape(session.text(".menu-select", { server_list: serverListMsg })).replace("< 默认服务器", "&lt; 默认服务器");
800
+ await session.send(msg);
801
+ const select = await session.prompt();
802
+ if (!select)
803
+ return session.text("commands.timeout");
804
+ const selectNum = parseInt(select, 10);
805
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > res.length) {
806
+ return session.text(".invalid-select");
807
+ }
808
+ if (selectNum === 0) {
809
+ return addServer(ctx, session, serverService, "user", uid, userDefaultServerId, cid, channelDefaultServerId);
810
+ } else {
811
+ return showServerMenu(ctx, session, serverService, res[selectNum - 1], "user", uid, userDefaultServerId, cid, channelDefaultServerId);
812
+ }
813
+ }
814
+ __name(showUserServerSelectMenu, "showUserServerSelectMenu");
815
+ async function showServerMenu(ctx, session, serverService, server2, from, uid, userDefaultServerId, cid, channelDefaultServerId) {
816
+ await session.send(session.text(".menu", server2));
817
+ const select = await session.prompt();
818
+ if (!select)
819
+ return session.text("commands.timeout");
820
+ const selectNum = parseInt(select, 10);
821
+ if (isNaN(selectNum) || selectNum < 0 || selectNum > 4) {
822
+ return await session.text(".invalid-select");
823
+ }
824
+ if (selectNum === 0) {
825
+ if (from === "user")
826
+ return await showUserServerSelectMenu(ctx, session, serverService, uid, userDefaultServerId, cid, channelDefaultServerId);
827
+ else
828
+ return await showChannelServerSelectMenu(ctx, session, serverService, uid, userDefaultServerId, cid, channelDefaultServerId);
829
+ }
830
+ if (selectNum === 1) {
831
+ if (from === "user") {
832
+ if (userDefaultServerId === server2.id)
833
+ return session.text(".menu-1-error-duplicate");
834
+ userDefaultServerId = server2.id;
835
+ } else {
836
+ if (channelDefaultServerId === server2.id)
837
+ return session.text(".menu-1-error-duplicate");
838
+ channelDefaultServerId = server2.id;
839
+ }
840
+ return session.text(".menu-1-success");
841
+ }
842
+ if (selectNum === 2) {
843
+ await session.send(session.text(".menu-2-prompt", server2));
844
+ let url = await session.prompt();
845
+ if (!url)
846
+ return session.text("commands.timeout");
847
+ if (url === "0")
848
+ return showServerMenu(ctx, session, serverService, server2, from, uid, userDefaultServerId, cid, channelDefaultServerId);
849
+ await serverService.updateServer(server2.id, { baseUrl: url });
850
+ return session.text(".menu-2-success");
851
+ }
852
+ if (selectNum === 3) {
853
+ await serverService.removeServer(server2.id);
854
+ if (from === "user") {
855
+ if (userDefaultServerId === server2.id) {
856
+ const res = await serverService.getServersByUid(uid);
857
+ if (res.length === 0)
858
+ userDefaultServerId = 0;
859
+ else
860
+ userDefaultServerId = res[0].id;
861
+ }
862
+ } else {
863
+ if (channelDefaultServerId === server2.id) {
864
+ const res = await serverService.getServersByCid(cid);
865
+ if (res.length === 0)
866
+ channelDefaultServerId = 0;
867
+ else
868
+ channelDefaultServerId = res[0].id;
869
+ }
870
+ }
871
+ return session.text(".menu-3-success");
872
+ }
873
+ if (selectNum === 4) {
874
+ await session.send(session.text(".menu-4-prompt"));
875
+ const serverName = await session.prompt();
876
+ if (!serverName)
877
+ return session.text("commands.timeout");
878
+ if (serverName.includes(" "))
879
+ return session.text(".menu-4-error-invalid-name");
880
+ await serverService.updateServer(server2.id, { name: serverName });
881
+ return session.text(".menu-4-success");
882
+ }
883
+ }
884
+ __name(showServerMenu, "showServerMenu");
885
+ async function addServer(ctx, session, serverService, from, uid, userDefaultServerId, cid, channelDefaultServerId) {
886
+ let serverTypeListMsg = "";
887
+ for (let i = 0; i < serverValues.length; i++) {
888
+ serverTypeListMsg += `<p>${i + 1}. ${serverValues[i]}</p>`;
889
+ }
890
+ const msg = import_koishi3.h.unescape(session.text(".add-type", { server_type_list: serverTypeListMsg }));
891
+ await session.send(msg);
892
+ const select = await session.prompt();
893
+ if (!select)
894
+ return session.text("commands.timeout");
895
+ const selectNum = parseInt(select, 10);
896
+ if (isNaN(selectNum) || selectNum < 1 || selectNum > serverValues.length) {
897
+ return await session.text(".invalid-select");
898
+ }
899
+ const serverType = serverValues[selectNum - 1];
900
+ await session.send(session.text(".add-url"));
901
+ const url = await session.prompt();
902
+ if (!url)
903
+ return session.text("commands.timeout");
904
+ await session.send(session.text(".add-name"));
905
+ const serverName = await session.prompt();
906
+ if (!serverName)
907
+ return session.text("commands.timeout");
908
+ if (serverName.includes(" "))
909
+ return session.text(".add-invalid-name");
910
+ const res = from === "user" ? await serverService.createServerForUser({
911
+ type: serverType,
912
+ name: serverName,
913
+ baseUrl: url,
914
+ supportedGames: []
915
+ }, uid) : await serverService.createServerForChannel({
916
+ type: serverType,
917
+ name: serverName,
918
+ baseUrl: url,
919
+ supportedGames: []
920
+ }, cid);
921
+ if (from === "user") {
922
+ if (userDefaultServerId === 0) {
923
+ userDefaultServerId = res.id;
924
+ }
925
+ } else {
926
+ if (channelDefaultServerId === 0) {
927
+ channelDefaultServerId = res.id;
928
+ }
929
+ }
930
+ return session.text(".add-success", { serverName, serverType });
931
+ }
932
+ __name(addServer, "addServer");
933
+
934
+ // src/core/command.ts
935
+ var name2 = "command";
936
+ function apply2(ctx, config) {
937
+ help(ctx, config);
938
+ bind(ctx, config);
939
+ card(ctx, config);
940
+ server(ctx, config);
941
+ }
942
+ __name(apply2, "apply");
943
+
944
+ // src/core/index.ts
945
+ var name3 = "Noah-Core";
946
+ var inject = ["database"];
947
+ var logger = new import_koishi4.Logger("Noah-Core");
948
+ function apply3(ctx, config) {
949
+ [["en-US", en_US_default], ["zh-CN", zh_CN_default]].forEach(([lang, file]) => ctx.i18n.define(lang, file));
950
+ ctx.plugin(database_exports, config.core);
951
+ ctx.plugin(command_exports, config.core);
952
+ }
953
+ __name(apply3, "apply");
954
+
955
+ // src/games/sdvx/index.ts
956
+ var sdvx_exports = {};
957
+ __export(sdvx_exports, {
958
+ apply: () => apply9,
959
+ inject: () => inject2,
960
+ logger: () => logger2,
961
+ name: () => name9
962
+ });
963
+ var import_koishi9 = require("koishi");
964
+
965
+ // src/games/sdvx/locales/en-US.yml
966
+ var en_US_default2 = { _config: { $desc: "SDVX Module Settings", default_model: "<p>Default model value (e.g. `2024110700`)</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>\n<p>{message}</p>", drawing: "<p>Noah is drawing, please wait patiently~</p>" } } } };
967
+
968
+ // src/games/sdvx/locales/zh-CN.yml
969
+ var zh_CN_default2 = { _config: { $desc: "SDVX 模块设置", default_model: "<p>默认的 model 值(如 `2024110700`)</p>" }, commands: { vf: { description: "显示 Noah 帮助信息", messages: { "card-not-found": "<p>你还没绑卡,去绑个卡再来吧~</p>", "server-not-found": "<p>没有可用的服务器哦,自己添加一个吧~</p>", "no-scores-found": "<p>没有找到你的分数数据哦~</p>", error: "<p>发生了错误:</p>\n<p>{message}</p>", drawing: "<p>Noah 正在画啦,请耐心等待~</p>" } } } };
970
+
971
+ // src/games/sdvx/database.ts
972
+ var database_exports2 = {};
973
+ __export(database_exports2, {
974
+ apply: () => apply4,
975
+ name: () => name4
976
+ });
977
+ var name4 = "database";
978
+ function apply4(ctx) {
979
+ }
980
+ __name(apply4, "apply");
981
+
982
+ // src/games/sdvx/command.ts
983
+ var command_exports2 = {};
984
+ __export(command_exports2, {
985
+ apply: () => apply7,
986
+ name: () => name7
987
+ });
988
+
989
+ // src/games/sdvx/commands/vf.ts
990
+ var import_koishi8 = require("koishi");
991
+
992
+ // src/servers/index.ts
993
+ var servers_exports = {};
994
+ __export(servers_exports, {
995
+ ServerManager: () => ServerManager,
996
+ apply: () => apply5,
997
+ name: () => name5
998
+ });
999
+
1000
+ // src/servers/Asphyxia/index.ts
1001
+ var import_koishi5 = require("koishi");
1002
+
1003
+ // src/servers/utils/xml.ts
1004
+ var xml2js = __toESM(require("xml2js"));
1005
+ var xmlToJson = /* @__PURE__ */ __name((xml) => {
1006
+ return new Promise((resolve2, reject) => {
1007
+ xml2js.parseString(xml, (err, result) => {
1008
+ if (err) {
1009
+ reject(new Error("Error parsing XML: " + err));
1010
+ } else {
1011
+ resolve2(result);
1012
+ }
1013
+ });
1014
+ });
1015
+ }, "xmlToJson");
1016
+
1017
+ // src/servers/Asphyxia/services/sdvx-service.ts
1018
+ var SDVXService = class _SDVXService {
1019
+ static {
1020
+ __name(this, "SDVXService");
1021
+ }
1022
+ static instance;
1023
+ logger;
1024
+ constructor(logger3) {
1025
+ this.logger = logger3;
1026
+ }
1027
+ static getInstance(logger3) {
1028
+ if (!_SDVXService.instance) {
1029
+ _SDVXService.instance = new _SDVXService(logger3);
1030
+ }
1031
+ return _SDVXService.instance;
1032
+ }
1033
+ async fetchRefid(ctx, url, model, card2) {
1034
+ const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
1035
+ const xmlRequestBody = `
1036
+ <call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
1037
+ <cardmng cardid="${card2}" cardtype="2" method="inquire" update="0" />
1038
+ </call>
1039
+ `;
1040
+ const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
1041
+ const decodedResponse = new TextDecoder("utf-8").decode(response);
1042
+ const jsonResponse = await xmlToJson(decodedResponse);
1043
+ return jsonResponse.response.cardmng[0].$.refid;
1044
+ }
1045
+ async fetchProfile(ctx, url, model, card2) {
1046
+ const refid = await this.fetchRefid(ctx, url, model, card2);
1047
+ const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
1048
+ const xmlRequestBody = `
1049
+ <call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
1050
+ <game method="sv6_load" ver="0">
1051
+ <dataid __type="str">${refid}</dataid>
1052
+ <cardid __type="str">${card2}</cardid>
1053
+ <refid __type="str">${refid}</refid>
1054
+ <locid __type="str">ea</locid>
1055
+ </game>
1056
+ </call>
1057
+ `;
1058
+ const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
1059
+ const decodedResponse = new TextDecoder("utf-8").decode(response);
1060
+ const jsonResponse = await xmlToJson(decodedResponse);
1061
+ return jsonResponse.response;
1062
+ }
1063
+ async fetchScore(ctx, url, model, card2) {
1064
+ const refid = await this.fetchRefid(ctx, url, model, card2);
1065
+ const requestUrl = `?model=KFC:J:G:A:${model}&f=message.get`;
1066
+ const xmlRequestBody = `
1067
+ <call model="KFC:J:G:A:${model}" srcid="00010203040506070809">
1068
+ <game method="sv6_load_m" ver="0">
1069
+ <dataid __type="str">${refid}</dataid>
1070
+ <cardid __type="str">${card2}</cardid>
1071
+ <refid __type="str">${refid}</refid>
1072
+ <locid __type="str">ea</locid>
1073
+ </game>
1074
+ </call>
1075
+ `;
1076
+ const response = await ctx.http.post(requestUrl, xmlRequestBody, { baseURL: url });
1077
+ const decodedResponse = new TextDecoder("utf-8").decode(response);
1078
+ const jsonResponse = await xmlToJson(decodedResponse);
1079
+ const rawScoreData = jsonResponse.response.game[0].music[0].info;
1080
+ return rawScoreData.map((item) => {
1081
+ const params = item.param[0]._.split(" ");
1082
+ return {
1083
+ music: {
1084
+ music_id: params[0],
1085
+ // music_id
1086
+ music_type: params[1],
1087
+ // music_type
1088
+ score: parseInt(params[2]),
1089
+ // score
1090
+ exscore: parseInt(params[3]),
1091
+ // exscore
1092
+ clear_type: params[4],
1093
+ // clear_type
1094
+ score_grade: params[5],
1095
+ // score_grade
1096
+ btn_rate: params[8],
1097
+ // btn_rate
1098
+ long_rate: params[9],
1099
+ // long_rate
1100
+ vol_rate: params[10]
1101
+ // vol_rate
1102
+ }
1103
+ };
1104
+ });
1105
+ }
1106
+ };
1107
+
1108
+ // src/servers/Asphyxia/services/iidx-service.ts
1109
+ var IIDXService = class _IIDXService {
1110
+ static {
1111
+ __name(this, "IIDXService");
1112
+ }
1113
+ static instance;
1114
+ logger;
1115
+ constructor(logger3) {
1116
+ this.logger = logger3;
1117
+ }
1118
+ static getInstance(logger3) {
1119
+ if (!_IIDXService.instance) {
1120
+ _IIDXService.instance = new _IIDXService(logger3);
1121
+ }
1122
+ return _IIDXService.instance;
1123
+ }
1124
+ async fetchData(url) {
1125
+ }
1126
+ };
1127
+
1128
+ // src/servers/Asphyxia/index.ts
1129
+ var Asphyxia = class {
1130
+ static {
1131
+ __name(this, "Asphyxia");
1132
+ }
1133
+ name = "asphyxia";
1134
+ supportedGames = ["sdvx", "iidx"];
1135
+ gameServices = {};
1136
+ logger = new import_koishi5.Logger("Noah-Asphyxia");
1137
+ constructor() {
1138
+ this.gameServices["sdvx"] = SDVXService.getInstance(this.logger);
1139
+ this.gameServices["iidx"] = IIDXService.getInstance(this.logger);
1140
+ }
1141
+ };
1142
+
1143
+ // src/servers/Mao/index.ts
1144
+ var import_koishi6 = require("koishi");
1145
+
1146
+ // src/servers/utils/grade.ts
1147
+ function getSDVXGrade(score) {
1148
+ if (score > 99e5)
1149
+ return "S";
1150
+ if (score >= 98e5)
1151
+ return "AAA+";
1152
+ if (score >= 97e5)
1153
+ return "AAA";
1154
+ if (score >= 95e5)
1155
+ return "AA+";
1156
+ if (score >= 93e5)
1157
+ return "AA";
1158
+ if (score >= 9e6)
1159
+ return "A+";
1160
+ if (score >= 87e5)
1161
+ return "A";
1162
+ if (score >= 75e5)
1163
+ return "B";
1164
+ if (score >= 65e5)
1165
+ return "C";
1166
+ return "D";
1167
+ }
1168
+ __name(getSDVXGrade, "getSDVXGrade");
1169
+ function getSDVXClearType(clearType) {
1170
+ return clearType === 5 ? "PUC" : clearType === 4 ? "UC" : clearType === 3 ? "HC" : clearType === 2 ? "NC" : clearType === 1 ? "PLAYED" : "NO PLAY";
1171
+ }
1172
+ __name(getSDVXClearType, "getSDVXClearType");
1173
+
1174
+ // src/servers/utils/difficulty.ts
1175
+ function getDiffName(diffStr, infVer) {
1176
+ switch (diffStr) {
1177
+ case "novice":
1178
+ return "NOV";
1179
+ case "advanced":
1180
+ return "ADV";
1181
+ case "exhaust":
1182
+ return "EXH";
1183
+ case "infinite": {
1184
+ const infVerStr = String(infVer);
1185
+ switch (infVerStr) {
1186
+ case "2":
1187
+ return "INF";
1188
+ case "3":
1189
+ return "GRV";
1190
+ case "4":
1191
+ return "HVN";
1192
+ case "5":
1193
+ return "VVD";
1194
+ case "6":
1195
+ return "XCD";
1196
+ }
1197
+ }
1198
+ case "maximum":
1199
+ return "MXM";
1200
+ }
1201
+ }
1202
+ __name(getDiffName, "getDiffName");
1203
+ function getDiffFullName(diffStr, infVer) {
1204
+ switch (diffStr) {
1205
+ case "novice":
1206
+ return "NOVICE";
1207
+ case "advanced":
1208
+ return "ADVANCED";
1209
+ case "exhaust":
1210
+ return "EXHAUST";
1211
+ case "infinite": {
1212
+ const infVerStr = String(infVer);
1213
+ switch (infVerStr) {
1214
+ case "2":
1215
+ return "INFINITE";
1216
+ case "3":
1217
+ return "GRAVITY";
1218
+ case "4":
1219
+ return "HEAVENLY";
1220
+ case "5":
1221
+ return "VIVID";
1222
+ case "6":
1223
+ return "EXCEED";
1224
+ }
1225
+ }
1226
+ case "maximum":
1227
+ return "MAXIMUM";
1228
+ }
1229
+ }
1230
+ __name(getDiffFullName, "getDiffFullName");
1231
+
1232
+ // src/games/sdvx/utils/vf.ts
1233
+ var GRADE_FACTORS = {
1234
+ "S": 1.05,
1235
+ "AAA+": 1.02,
1236
+ "AAA": 1,
1237
+ "AA+": 0.97,
1238
+ "AA": 0.94,
1239
+ "A+": 0.91,
1240
+ "A": 0.88,
1241
+ "B": 0.85,
1242
+ "C": 0.82,
1243
+ "D": 0.8
1244
+ };
1245
+ var CLEAR_FACTORS = {
1246
+ "S-PUC": 1.1,
1247
+ "PUC": 1.1,
1248
+ "UC": 1.05,
1249
+ "HC": 1.02,
1250
+ "NC": 1,
1251
+ "PLAYED": 0.5,
1252
+ "NO PLAY": 0.5
1253
+ };
1254
+ function calculateVolforce(raw_score) {
1255
+ const score = raw_score.music.score;
1256
+ const level = raw_score.music.music_diff;
1257
+ const grade = raw_score.music.score_grade;
1258
+ const clearType = raw_score.music.clear_type;
1259
+ const gradeFactor = GRADE_FACTORS[grade];
1260
+ const clearFactor = CLEAR_FACTORS[clearType];
1261
+ const volforce = level * score * gradeFactor * clearFactor * 2e-6;
1262
+ return Math.round(volforce) / 20;
1263
+ }
1264
+ __name(calculateVolforce, "calculateVolforce");
1265
+
1266
+ // src/games/sdvx/services/music-service.ts
1267
+ var MusicService = class _MusicService {
1268
+ static {
1269
+ __name(this, "MusicService");
1270
+ }
1271
+ static instance;
1272
+ sdvx_data_url;
1273
+ constructor(config) {
1274
+ this.sdvx_data_url = config.sdvx_data_url;
1275
+ }
1276
+ static getInstance(config) {
1277
+ if (!_MusicService.instance) {
1278
+ _MusicService.instance = new _MusicService(config);
1279
+ }
1280
+ return _MusicService.instance;
1281
+ }
1282
+ async getMusic(ctx, musicIds) {
1283
+ try {
1284
+ const response = await ctx.http.post(`/music`, {
1285
+ ids: musicIds
1286
+ }, {
1287
+ baseURL: this.sdvx_data_url
1288
+ });
1289
+ const musicInfo = await response;
1290
+ return musicInfo;
1291
+ } catch (error) {
1292
+ throw error;
1293
+ }
1294
+ }
1295
+ };
1296
+
1297
+ // src/servers/Mao/services/sdvx-service.ts
1298
+ var SDVXService2 = class _SDVXService {
1299
+ static {
1300
+ __name(this, "SDVXService");
1301
+ }
1302
+ static instance;
1303
+ logger;
1304
+ constructor(logger3) {
1305
+ this.logger = logger3;
1306
+ }
1307
+ static getInstance(logger3) {
1308
+ if (!_SDVXService.instance) {
1309
+ _SDVXService.instance = new _SDVXService(logger3);
1310
+ }
1311
+ return _SDVXService.instance;
1312
+ }
1313
+ /**
1314
+ * Get the difficulty string based on the difficulty type number
1315
+ * @param diffType Difficulty type (0-4)
1316
+ * @returns The difficulty string (novice, advanced, exhaust, infinite, maximum)
1317
+ */
1318
+ getDifficultyString(diffType) {
1319
+ const diffStrMap = {
1320
+ 0: "novice",
1321
+ 1: "advanced",
1322
+ 2: "exhaust",
1323
+ 3: "infinite",
1324
+ 4: "maximum"
1325
+ };
1326
+ return diffStrMap[diffType] || "novice";
1327
+ }
1328
+ async getAllScore(ctx, url, cardId, config) {
1329
+ try {
1330
+ const data = await ctx.http.get(`/sdvx/scores?card=${cardId}`, { baseURL: url, responseType: "json" });
1331
+ const filteredData = data.filter((score) => score.music_id !== 244 && score.music_id !== 1759 && score.music_id !== 1761);
1332
+ const musicIds = filteredData.map((score) => score.music_id);
1333
+ const diffTypes = filteredData.map((score) => parseInt(score.music_diff));
1334
+ const musicService = MusicService.getInstance(config);
1335
+ const musicData = await musicService.getMusic(ctx, musicIds);
1336
+ const musicDataMap = new Map(musicData.map((music) => [music.id, music]));
1337
+ const scores = filteredData.map((score, index) => {
1338
+ const musicId = score.music_id;
1339
+ const music = musicDataMap.get(musicId);
1340
+ const musicDiffType = parseInt(score.music_diff);
1341
+ const diffStr = this.getDifficultyString(musicDiffType);
1342
+ let difficultyData = null;
1343
+ if (music && music.difficulty && music.difficulty.length > 0) {
1344
+ difficultyData = music.difficulty.find(
1345
+ (diff) => diff.difstr.toLowerCase() === diffStr
1346
+ ) || null;
1347
+ }
1348
+ const infVer = music ? music.inf_ver : 2;
1349
+ const scoreObj = {
1350
+ music: {
1351
+ music_id: musicId,
1352
+ music_diff: difficultyData ? difficultyData.difnum : 0,
1353
+ music_diff_name: getDiffName(diffStr, infVer),
1354
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
1355
+ score: score.score,
1356
+ exscore: score.exscore,
1357
+ clear_type: getSDVXClearType(score.clear_type),
1358
+ score_grade: getSDVXGrade(score.score),
1359
+ btn_rate: score.btn_rate.toString(),
1360
+ long_rate: score.long_rate.toString(),
1361
+ vol_rate: score.vol_rate.toString()
1362
+ },
1363
+ extra: {
1364
+ play_count: score.play_count,
1365
+ update_at: score.update_at,
1366
+ volforce: 0
1367
+ },
1368
+ music_data: music || null,
1369
+ difficulty_data: difficultyData || null
1370
+ };
1371
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
1372
+ return scoreObj;
1373
+ });
1374
+ return scores;
1375
+ } catch (error) {
1376
+ throw error;
1377
+ }
1378
+ }
1379
+ async getScore(ctx, url, cardId, musicId, config) {
1380
+ try {
1381
+ const response = await ctx.http.get(`/sdvx/find?card=${cardId}&mid=${musicId}`, { baseURL: url, responseType: "text" });
1382
+ const text = await response;
1383
+ if (text === "没有找到相关记录") {
1384
+ const musicService2 = MusicService.getInstance(config);
1385
+ const musicData2 = await musicService2.getMusic(ctx, [musicId]);
1386
+ const music2 = musicData2.length > 0 ? musicData2[0] : null;
1387
+ const diffStr2 = "novice";
1388
+ const infVer2 = music2 ? music2.inf_ver : 2;
1389
+ let difficultyData2 = null;
1390
+ if (music2 && music2.difficulty && music2.difficulty.length > 0) {
1391
+ difficultyData2 = music2.difficulty.find(
1392
+ (diff) => diff.difstr.toLowerCase() === diffStr2
1393
+ ) || music2.difficulty[0];
1394
+ }
1395
+ return {
1396
+ music: {
1397
+ music_id: musicId,
1398
+ music_diff: difficultyData2 ? difficultyData2.difnum : 0,
1399
+ music_diff_name: getDiffName(diffStr2, infVer2),
1400
+ music_diff_full_name: getDiffFullName(diffStr2, infVer2),
1401
+ score: 0,
1402
+ exscore: 0,
1403
+ clear_type: getSDVXClearType(0),
1404
+ // NO PLAY
1405
+ score_grade: getSDVXGrade(0),
1406
+ btn_rate: "0",
1407
+ long_rate: "0",
1408
+ vol_rate: "0"
1409
+ },
1410
+ extra: {
1411
+ play_count: 0,
1412
+ update_at: "0",
1413
+ volforce: 0
1414
+ },
1415
+ music_data: music2,
1416
+ difficulty_data: difficultyData2
1417
+ };
1418
+ }
1419
+ const matches = text.match(/MusicType: (\d+), Score: (\d+), Exscore: (\d+), ClearType: (\d+), PlayCount: (\d+)/);
1420
+ if (!matches) {
1421
+ throw new Error(`Invalid score format received: ${text}`);
1422
+ }
1423
+ const [, musicType, score, exscore, clearType, playCount] = matches;
1424
+ const musicTypeNum = parseInt(musicType);
1425
+ const diffStr = this.getDifficultyString(musicTypeNum);
1426
+ const musicService = MusicService.getInstance(config);
1427
+ const musicData = await musicService.getMusic(ctx, [musicId]);
1428
+ const music = musicData.length > 0 ? musicData[0] : null;
1429
+ let difficultyData = null;
1430
+ if (music && music.difficulty && music.difficulty.length > 0) {
1431
+ difficultyData = music.difficulty.find(
1432
+ (diff) => diff.difstr.toLowerCase() === diffStr
1433
+ ) || null;
1434
+ }
1435
+ const infVer = music ? music.inf_ver : 2;
1436
+ const scoreObj = {
1437
+ music: {
1438
+ music_id: musicId,
1439
+ music_diff: difficultyData ? difficultyData.difnum : 0,
1440
+ music_diff_name: getDiffName(diffStr, infVer),
1441
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
1442
+ score: parseInt(score),
1443
+ exscore: parseInt(exscore),
1444
+ clear_type: getSDVXClearType(parseInt(clearType)),
1445
+ score_grade: getSDVXGrade(parseInt(score)),
1446
+ btn_rate: "0",
1447
+ // Not provided in response
1448
+ long_rate: "0",
1449
+ // Not provided in response
1450
+ vol_rate: "0"
1451
+ // Not provided in response
1452
+ },
1453
+ extra: {
1454
+ play_count: parseInt(playCount),
1455
+ update_at: "0",
1456
+ volforce: 0
1457
+ },
1458
+ music_data: music,
1459
+ difficulty_data: difficultyData
1460
+ };
1461
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
1462
+ return scoreObj;
1463
+ } catch (error) {
1464
+ throw error;
1465
+ }
1466
+ }
1467
+ async getRecentScores(ctx, url, cardId, count = 1, config) {
1468
+ try {
1469
+ const text = await ctx.http.get(`/sdvx/recent?card=${cardId}&count=${count}`, { baseURL: url, responseType: "text" });
1470
+ const scores = [];
1471
+ const musicIds = [];
1472
+ const tempScores = [];
1473
+ const lines = text.split("\n");
1474
+ for (const line of lines) {
1475
+ if (line.startsWith("mid:")) {
1476
+ const match = line.match(/mid:(\d+)\s+mtype:(\d+)\s+ctype:(\d+)\s+score:(\d+)\s+exscore(\d+)\s+time:(.+)/);
1477
+ if (match) {
1478
+ const [, musicId, musicType, clearType, score, exscore, time] = match;
1479
+ const parsedMusicId = parseInt(musicId);
1480
+ tempScores.push({
1481
+ musicId: parsedMusicId,
1482
+ musicType: parseInt(musicType),
1483
+ clearType: parseInt(clearType),
1484
+ score: parseInt(score),
1485
+ exscore: parseInt(exscore),
1486
+ time
1487
+ });
1488
+ musicIds.push(parsedMusicId);
1489
+ }
1490
+ }
1491
+ }
1492
+ if (tempScores.length === 0) {
1493
+ return [];
1494
+ }
1495
+ const musicService = MusicService.getInstance(config);
1496
+ const musicDataList = await musicService.getMusic(ctx, musicIds);
1497
+ const musicDataMap = new Map(musicDataList.map((music) => [music.id, music]));
1498
+ tempScores.forEach((tempScore) => {
1499
+ const scoreNum = tempScore.score;
1500
+ const musicId = tempScore.musicId;
1501
+ const musicTypeNum = tempScore.musicType;
1502
+ const music = musicDataMap.get(musicId);
1503
+ const diffStr = this.getDifficultyString(musicTypeNum);
1504
+ let difficultyData = null;
1505
+ if (music && music.difficulty && music.difficulty.length > 0) {
1506
+ difficultyData = music.difficulty.find(
1507
+ (diff) => diff.difstr.toLowerCase() === diffStr
1508
+ ) || null;
1509
+ }
1510
+ const infVer = music ? music.inf_ver : 2;
1511
+ const scoreObj = {
1512
+ music: {
1513
+ music_id: musicId,
1514
+ music_diff: difficultyData ? difficultyData.difnum : 0,
1515
+ music_diff_name: getDiffName(diffStr, infVer),
1516
+ music_diff_full_name: getDiffFullName(diffStr, infVer),
1517
+ score: scoreNum,
1518
+ exscore: tempScore.exscore,
1519
+ clear_type: getSDVXClearType(tempScore.clearType),
1520
+ score_grade: getSDVXGrade(scoreNum),
1521
+ btn_rate: "0",
1522
+ // Not provided in API response
1523
+ long_rate: "0",
1524
+ // Not provided in API response
1525
+ vol_rate: "0"
1526
+ // Not provided in API response
1527
+ },
1528
+ extra: {
1529
+ play_count: 0,
1530
+ update_at: tempScore.time,
1531
+ volforce: 0
1532
+ },
1533
+ music_data: music || null,
1534
+ difficulty_data: difficultyData || null
1535
+ };
1536
+ scoreObj.extra.volforce = calculateVolforce(scoreObj);
1537
+ scores.push(scoreObj);
1538
+ });
1539
+ return scores;
1540
+ } catch (error) {
1541
+ throw error;
1542
+ }
1543
+ }
1544
+ };
1545
+
1546
+ // src/servers/Mao/index.ts
1547
+ var Mao = class {
1548
+ static {
1549
+ __name(this, "Mao");
1550
+ }
1551
+ name = "mao";
1552
+ supportedGames = ["sdvx"];
1553
+ gameServices = {};
1554
+ logger = new import_koishi6.Logger("Noah-Mao");
1555
+ constructor() {
1556
+ this.gameServices["sdvx"] = SDVXService2.getInstance(this.logger);
1557
+ }
1558
+ };
1559
+
1560
+ // src/servers/ServerFactory.ts
1561
+ var ServerFactory = class {
1562
+ static {
1563
+ __name(this, "ServerFactory");
1564
+ }
1565
+ serverInstances = /* @__PURE__ */ new Map();
1566
+ getServer(serverType) {
1567
+ if (!this.serverInstances.has(serverType)) {
1568
+ let serverInstance;
1569
+ switch (serverType) {
1570
+ case "asphyxia":
1571
+ serverInstance = new Asphyxia();
1572
+ break;
1573
+ case "mao":
1574
+ serverInstance = new Mao();
1575
+ break;
1576
+ default:
1577
+ throw new Error(`Unsupported server type: ${serverType}`);
1578
+ }
1579
+ this.serverInstances.set(serverType, serverInstance);
1580
+ }
1581
+ return this.serverInstances.get(serverType);
1582
+ }
1583
+ };
1584
+
1585
+ // src/servers/index.ts
1586
+ var name5 = "Noah-Server";
1587
+ var ServerManager = class _ServerManager {
1588
+ static {
1589
+ __name(this, "ServerManager");
1590
+ }
1591
+ static instance;
1592
+ factory;
1593
+ constructor() {
1594
+ this.factory = new ServerFactory();
1595
+ }
1596
+ static getInstance() {
1597
+ if (!_ServerManager.instance) {
1598
+ _ServerManager.instance = new _ServerManager();
1599
+ }
1600
+ return _ServerManager.instance;
1601
+ }
1602
+ getServer(serverType) {
1603
+ return this.factory.getServer(serverType);
1604
+ }
1605
+ getGameService(serverType, gameType) {
1606
+ const server2 = this.factory.getServer(serverType);
1607
+ return server2.gameServices[gameType];
1608
+ }
1609
+ };
1610
+ function apply5(ctx, config) {
1611
+ }
1612
+ __name(apply5, "apply");
1613
+
1614
+ // src/games/sdvx/services/score-service.ts
1615
+ var ScoreService = class _ScoreService {
1616
+ static {
1617
+ __name(this, "ScoreService");
1618
+ }
1619
+ static instance;
1620
+ constructor() {
1621
+ }
1622
+ static getInstance() {
1623
+ if (!_ScoreService.instance) {
1624
+ _ScoreService.instance = new _ScoreService();
1625
+ }
1626
+ return _ScoreService.instance;
1627
+ }
1628
+ /**
1629
+ * Sort scores by volforce in descending order
1630
+ * @param scores Array of SDVX scores to sort
1631
+ * @returns New array of scores sorted by volforce (highest to lowest)
1632
+ */
1633
+ sortByVolforce(scores) {
1634
+ return [...scores].sort((a, b) => b.extra.volforce - a.extra.volforce);
1635
+ }
1636
+ /**
1637
+ * Get the top 50 scores by volforce, including ties
1638
+ * @param scores Array of SDVX scores to process
1639
+ * @returns Array of scores with highest volforce values (may exceed 50 if there are ties)
1640
+ */
1641
+ getBest50(scores) {
1642
+ const sortedScores = this.sortByVolforce(scores);
1643
+ if (sortedScores.length <= 50) {
1644
+ return sortedScores;
1645
+ }
1646
+ const fiftiethVF = sortedScores[49].extra.volforce;
1647
+ return sortedScores.filter((score) => score.extra.volforce >= fiftiethVF);
1648
+ }
1649
+ };
1650
+
1651
+ // src/drawer/index.ts
1652
+ var drawer_exports = {};
1653
+ __export(drawer_exports, {
1654
+ DrawerManager: () => DrawerManager,
1655
+ apply: () => apply6,
1656
+ name: () => name6
1657
+ });
1658
+ var import_koishi7 = require("koishi");
1659
+
1660
+ // src/drawer/BaseDrawer.ts
1661
+ var import_sharp = __toESM(require("sharp"));
1662
+ var BaseDrawer = class {
1663
+ constructor(ctx) {
1664
+ this.ctx = ctx;
1665
+ }
1666
+ static {
1667
+ __name(this, "BaseDrawer");
1668
+ }
1669
+ /**
1670
+ * Compress an image buffer
1671
+ * @param canvas The canvas object to compress
1672
+ * @param compression Compression options
1673
+ * @returns Promise resolving to a compressed buffer
1674
+ */
1675
+ async compressImage(canvas, compression) {
1676
+ if (!compression) {
1677
+ compression = { lossless: false };
1678
+ }
1679
+ try {
1680
+ if (compression.lossless) {
1681
+ const pngBuffer = await canvas.toBuffer("png");
1682
+ return (0, import_sharp.default)(pngBuffer).png({
1683
+ compressionLevel: 9,
1684
+ // Maximum compression
1685
+ adaptiveFiltering: true,
1686
+ progressive: true
1687
+ }).toBuffer();
1688
+ } else {
1689
+ const quality = compression?.quality || 0.9;
1690
+ return canvas.toBuffer("jpeg", { quality });
1691
+ }
1692
+ } catch (error) {
1693
+ this.ctx.logger("BaseDrawer").error(`Error compressing image: ${error.message}`);
1694
+ return canvas.toBuffer("png");
1695
+ }
1696
+ }
1697
+ };
1698
+
1699
+ // src/drawer/Core/index.ts
1700
+ var CoreDrawer = class extends BaseDrawer {
1701
+ constructor(ctx) {
1702
+ super(ctx);
1703
+ this.ctx = ctx;
1704
+ }
1705
+ static {
1706
+ __name(this, "CoreDrawer");
1707
+ }
1708
+ /**
1709
+ * Generate a generic image for core functionality
1710
+ * @param options Configuration options for image generation
1711
+ * @param compression Compression options
1712
+ * @returns Promise resolving to a Buffer containing the image data
1713
+ */
1714
+ async generateImage(options, compression) {
1715
+ const { Canvas, loadImage } = this.ctx.skia;
1716
+ const width = options?.width || 800;
1717
+ const height = options?.height || 600;
1718
+ const text = options?.text || "Noah Core";
1719
+ const backgroundColor = options?.backgroundColor || "#2b2b2b";
1720
+ const textColor = options?.textColor || "#ffffff";
1721
+ const canvas = new Canvas(width, height);
1722
+ const ctx = canvas.getContext("2d");
1723
+ ctx.fillStyle = backgroundColor;
1724
+ ctx.fillRect(0, 0, width, height);
1725
+ ctx.fillStyle = textColor;
1726
+ ctx.font = "bold 48px Arial";
1727
+ ctx.textAlign = "center";
1728
+ ctx.textBaseline = "middle";
1729
+ ctx.fillText(text, width / 2, height / 2);
1730
+ return this.compressImage(canvas, compression);
1731
+ }
1732
+ };
1733
+
1734
+ // src/drawer/SDVX/index.ts
1735
+ var import_path = require("path");
1736
+ var fs = __toESM(require("fs"));
1737
+ var SDVXDrawer = class extends BaseDrawer {
1738
+ constructor(ctx) {
1739
+ super(ctx);
1740
+ this.ctx = ctx;
1741
+ }
1742
+ static {
1743
+ __name(this, "SDVXDrawer");
1744
+ }
1745
+ /**
1746
+ * Generate a Volforce image for SDVX scores
1747
+ * @param options Configuration options for image generation
1748
+ * @param compression Compression options
1749
+ * @returns Promise resolving to a Buffer containing the image data
1750
+ */
1751
+ async generateVFImage(options, compression) {
1752
+ const { Canvas, loadImage, FontLibrary } = this.ctx.skia;
1753
+ if (!compression) {
1754
+ compression = { lossless: true };
1755
+ }
1756
+ try {
1757
+ const notoSansPath = (0, import_path.resolve)(__dirname, "../../assets/fonts/NotoSans-VariableFont_wdth,wght.ttf");
1758
+ const slantPath = (0, import_path.resolve)(__dirname, "../../assets/fonts/Slant.ttf");
1759
+ const fredokaPath = (0, import_path.resolve)(__dirname, "../../assets/fonts/FredokaOne.ttf");
1760
+ const notoSansJPPath = (0, import_path.resolve)(__dirname, "../../assets/fonts/NotoSansJP-VariableFont_wght.ttf");
1761
+ if (fs.existsSync(notoSansPath)) {
1762
+ FontLibrary.use("Noto Sans", notoSansPath);
1763
+ this.ctx.logger("SDVX-Drawer").debug("Loaded Noto Sans font successfully");
1764
+ } else {
1765
+ this.ctx.logger("SDVX-Drawer").warn(`Noto Sans font file not found at: ${notoSansPath}`);
1766
+ }
1767
+ if (fs.existsSync(slantPath)) {
1768
+ FontLibrary.use("Slant", slantPath);
1769
+ this.ctx.logger("SDVX-Drawer").debug("Loaded Slant font successfully");
1770
+ } else {
1771
+ this.ctx.logger("SDVX-Drawer").warn(`Slant font file not found at: ${slantPath}`);
1772
+ }
1773
+ if (fs.existsSync(fredokaPath)) {
1774
+ FontLibrary.use("Fredoka One", fredokaPath);
1775
+ this.ctx.logger("SDVX-Drawer").debug("Loaded Fredoka One font successfully");
1776
+ } else {
1777
+ this.ctx.logger("SDVX-Drawer").warn(`Fredoka One font file not found at: ${fredokaPath}`);
1778
+ }
1779
+ if (fs.existsSync(notoSansJPPath)) {
1780
+ FontLibrary.use("Noto Sans JP", notoSansJPPath);
1781
+ this.ctx.logger("SDVX-Drawer").debug("Loaded Noto Sans JP font successfully");
1782
+ } else {
1783
+ this.ctx.logger("SDVX-Drawer").warn(`Noto Sans JP font file not found at: ${notoSansJPPath}`);
1784
+ }
1785
+ } catch (error) {
1786
+ this.ctx.logger("SDVX-Drawer").warn(`Error loading fonts: ${error.message}`);
1787
+ }
1788
+ const bgImage = await loadImage((0, import_path.resolve)(__dirname, "../../assets/sdvx/vf/main_bg.png"));
1789
+ const canvas = new Canvas(bgImage.width, bgImage.height);
1790
+ const ctx = canvas.getContext("2d");
1791
+ ctx.imageSmoothingEnabled = true;
1792
+ ctx.imageSmoothingQuality = "high";
1793
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1794
+ ctx.drawImage(bgImage, 0, 0);
1795
+ const averageVF = this.calculateAverageVF(options.scores);
1796
+ ctx.fillStyle = "#FFFFFF";
1797
+ ctx.textAlign = "left";
1798
+ ctx.textBaseline = "top";
1799
+ ctx.font = '300 128px "Noto Sans"';
1800
+ ctx.fillText(options.playerName, 1451, 187);
1801
+ ctx.fillText(averageVF.toFixed(3), 3849, 187);
1802
+ const cardBgImage = await loadImage((0, import_path.resolve)(__dirname, "../../assets/sdvx/vf/card_bg.png"));
1803
+ const sortedScores = [...options.scores].sort((a, b) => b.extra.volforce - a.extra.volforce).slice(0, 50);
1804
+ await this.drawScoreCards(ctx, sortedScores, cardBgImage, loadImage, options.config);
1805
+ return this.compressImage(canvas, compression);
1806
+ }
1807
+ /**
1808
+ * Draw score cards on the canvas
1809
+ */
1810
+ async drawScoreCards(ctx, scores, cardBgImage, loadImage, config) {
1811
+ if (scores.length === 0)
1812
+ return;
1813
+ const startX = 144;
1814
+ const startY = 692;
1815
+ const cardsPerRow = 5;
1816
+ const cardHorizontalSpacing = 48;
1817
+ const cardVerticalSpacing = 100;
1818
+ const cardWidth = cardBgImage.width;
1819
+ const cardHeight = cardBgImage.height;
1820
+ const effectiveCardWidth = cardWidth + cardHorizontalSpacing;
1821
+ const gradeImages = {};
1822
+ const vfBadges = {};
1823
+ const circleImages = {};
1824
+ const jacketImages = {};
1825
+ try {
1826
+ const grades = ["D", "C", "B", "A", "A+", "AA", "AA+", "AAA", "AAA+", "S"];
1827
+ for (const grade of grades) {
1828
+ try {
1829
+ const gradePath = (0, import_path.resolve)(__dirname, `../../assets/sdvx/vf/grade/Type=${grade}.png`);
1830
+ gradeImages[grade] = await loadImage(gradePath);
1831
+ } catch (error) {
1832
+ this.ctx.logger("SDVX-Drawer").warn(`Failed to load grade image for ${grade}: ${error.message}`);
1833
+ }
1834
+ }
1835
+ for (let i = 1; i <= 10; i++) {
1836
+ try {
1837
+ const circlePath = (0, import_path.resolve)(__dirname, `../../assets/sdvx/vf/circle/Class=${i}.png`);
1838
+ circleImages[i] = await loadImage(circlePath);
1839
+ } catch (error) {
1840
+ this.ctx.logger("SDVX-Drawer").warn(`Failed to load circle image for class ${i}: ${error.message}`);
1841
+ }
1842
+ }
1843
+ for (let i = 1; i <= 10; i++) {
1844
+ try {
1845
+ const badgePath = (0, import_path.resolve)(__dirname, `../../assets/sdvx/vf/badge/Class=${i}.png`);
1846
+ vfBadges[i] = await loadImage(badgePath);
1847
+ } catch (error) {
1848
+ this.ctx.logger("SDVX-Drawer").warn(`Failed to load badge image for class ${i}: ${error.message}`);
1849
+ }
1850
+ }
1851
+ for (const score of scores) {
1852
+ try {
1853
+ if (score.difficulty_data?.cover_url) {
1854
+ const coverUrl = `${config.sdvx_data_url}${score.difficulty_data.cover_url}`.replace(".webp", ".png");
1855
+ if (!jacketImages[coverUrl]) {
1856
+ jacketImages[coverUrl] = await loadImage(coverUrl);
1857
+ }
1858
+ }
1859
+ } catch (error) {
1860
+ this.ctx.logger("SDVX-Drawer").warn(`Failed to load jacket image: ${error.message}`);
1861
+ }
1862
+ }
1863
+ } catch (error) {
1864
+ this.ctx.logger("SDVX-Drawer").warn(`Failed to load images: ${error.message}`);
1865
+ }
1866
+ for (let i = 0; i < scores.length; i++) {
1867
+ const score = scores[i];
1868
+ const row = Math.floor(i / cardsPerRow);
1869
+ const col = i % cardsPerRow;
1870
+ const x = startX + col * effectiveCardWidth;
1871
+ const y = startY + row * (cardHeight + cardVerticalSpacing);
1872
+ const vf2 = score.extra.volforce;
1873
+ let vfClass = 1;
1874
+ if (vf2 >= 20)
1875
+ vfClass = 10;
1876
+ else if (vf2 >= 19)
1877
+ vfClass = 9;
1878
+ else if (vf2 >= 18)
1879
+ vfClass = 8;
1880
+ else if (vf2 >= 17)
1881
+ vfClass = 7;
1882
+ else if (vf2 >= 16)
1883
+ vfClass = 6;
1884
+ else if (vf2 >= 15)
1885
+ vfClass = 5;
1886
+ else if (vf2 >= 14)
1887
+ vfClass = 4;
1888
+ else if (vf2 >= 12)
1889
+ vfClass = 3;
1890
+ else if (vf2 >= 10)
1891
+ vfClass = 2;
1892
+ else
1893
+ vfClass = 1;
1894
+ if (circleImages[vfClass]) {
1895
+ ctx.drawImage(circleImages[vfClass], x + 628, y - 42);
1896
+ }
1897
+ ctx.drawImage(cardBgImage, x, y);
1898
+ try {
1899
+ const diffName = score.music.music_diff_name || this.getDifficultyAbbreviation(score.music.music_diff);
1900
+ const diffLevel = score.music.music_diff.toString();
1901
+ const diffColors = {
1902
+ "NOV": "#914FC3",
1903
+ // Novice - Purple
1904
+ "ADV": "#BAB616",
1905
+ // Advanced - Yellow
1906
+ "EXH": "#BC3535",
1907
+ // Exhaust - Red
1908
+ "INF": "#CE3378",
1909
+ // Infinite - Pink
1910
+ "MXM": "#7C7C7C",
1911
+ // Maximum - Gray
1912
+ "GRV": "#BE5906",
1913
+ // Gravity - Orange
1914
+ "HVN": "#04A2D1",
1915
+ // Heaven - Light Blue
1916
+ "VVD": "#CF55B0",
1917
+ // Vivid - Magenta
1918
+ "XCD": "#4265B1"
1919
+ // Exceed - Blue
1920
+ };
1921
+ const bgColor = diffColors[diffName] || "#7C7C7C";
1922
+ ctx.font = '24px "Fredoka One"';
1923
+ const diffNameWidth = ctx.measureText(diffName).width;
1924
+ const diffLevelWidth = ctx.measureText(diffLevel).width;
1925
+ const paddingHorizontal = 16;
1926
+ const paddingVertical = 4;
1927
+ const gap = 8;
1928
+ const totalWidth = diffNameWidth + diffLevelWidth + gap + paddingHorizontal * 2;
1929
+ const height = 24 * 1.21 + paddingVertical * 2;
1930
+ ctx.fillStyle = bgColor;
1931
+ this.roundRect(ctx, x + 410 - totalWidth / 2, y + 158 - height / 2, totalWidth, height, 100);
1932
+ ctx.fill();
1933
+ ctx.fillStyle = "#FFFFFF";
1934
+ ctx.textAlign = "center";
1935
+ ctx.textBaseline = "middle";
1936
+ ctx.fillText(diffName, x + 410 - gap / 2 - diffLevelWidth / 2, y + 158);
1937
+ ctx.fillText(diffLevel, x + 410 + gap / 2 + diffNameWidth / 2, y + 158);
1938
+ const clearType = score.music.clear_type || "PLAYED";
1939
+ const clearColors = {
1940
+ "PUC": "#F2C027",
1941
+ // Perfect Ultimate Chain - Gold
1942
+ "S-PUC": "#F2E127",
1943
+ // S-Perfect Ultimate Chain - Light Gold/Yellow
1944
+ "UC": "#C539AB",
1945
+ // Ultimate Chain - Magenta
1946
+ "HC": "#C5393B",
1947
+ // Hard Clear - Red
1948
+ "NC": "#39C539",
1949
+ // Normal Clear - Green
1950
+ "PLAYED": "#AF970A",
1951
+ // Played - Dark Yellow
1952
+ "NO PLAY": "#8F8E8B"
1953
+ // No Play - Gray
1954
+ };
1955
+ const clearDisplayText = {
1956
+ "PUC": "PUC",
1957
+ "S-PUC": "S-PUC",
1958
+ "UC": "UC",
1959
+ "HC": "COMP",
1960
+ "NC": "COMP",
1961
+ "PLAYED": "PLAYED",
1962
+ "NO PLAY": "NO PLAY"
1963
+ };
1964
+ const clearDisplayName = clearDisplayText[clearType] || clearType;
1965
+ const clearBgColor = clearColors[clearType] || "#8F8E8B";
1966
+ ctx.font = '24px "Fredoka One"';
1967
+ const clearTextWidth = ctx.measureText(clearDisplayName).width;
1968
+ const clearTotalWidth = clearTextWidth + paddingHorizontal * 2;
1969
+ const clearX = x + 410 + totalWidth / 2 + 22;
1970
+ ctx.fillStyle = clearBgColor;
1971
+ this.roundRect(ctx, clearX, y + 158 - height / 2, clearTotalWidth, height, 100);
1972
+ ctx.fill();
1973
+ ctx.fillStyle = "#FFFFFF";
1974
+ ctx.textAlign = "center";
1975
+ ctx.textBaseline = "middle";
1976
+ ctx.fillText(clearDisplayName, clearX + clearTotalWidth / 2, y + 158);
1977
+ ctx.textAlign = "left";
1978
+ ctx.textBaseline = "alphabetic";
1979
+ const scoreText = score.music.score.toString();
1980
+ const firstThreeDigits = scoreText.slice(0, -4);
1981
+ const lastDigit = scoreText.slice(-4);
1982
+ const scoreGradient = ctx.createLinearGradient(x + 525, y + 215, x + 600, y + 380);
1983
+ scoreGradient.addColorStop(0, "#878787");
1984
+ scoreGradient.addColorStop(0.4, "#878787");
1985
+ scoreGradient.addColorStop(0.41, "#5C5C5C");
1986
+ scoreGradient.addColorStop(1, "#5C5C5C");
1987
+ ctx.fillStyle = scoreGradient;
1988
+ ctx.font = '64px "Fredoka One"';
1989
+ const firstThreeWidth = ctx.measureText(firstThreeDigits).width;
1990
+ ctx.fillText(firstThreeDigits, x + 525, y + 278);
1991
+ ctx.font = '36px "Fredoka One"';
1992
+ ctx.fillText(lastDigit, x + 525 + firstThreeWidth, y + 278);
1993
+ ctx.textAlign = "left";
1994
+ ctx.textBaseline = "top";
1995
+ ctx.font = '64px "Fredoka One"';
1996
+ const vfText = vf2.toFixed(2);
1997
+ const vfColorMap = {
1998
+ 1: { from: "#CC7631", to: "#854F24" },
1999
+ // Class 1
2000
+ 2: { from: "#5087ED", to: "#1354CD" },
2001
+ // Class 2
2002
+ 3: { from: "#F2971A", to: "#CA7B0E" },
2003
+ // Class 3
2004
+ 4: { from: "#1BC8CE", to: "#14A0A5" },
2005
+ // Class 4
2006
+ 5: { from: "#D6373D", to: "#981E22" },
2007
+ // Class 5
2008
+ 6: { from: "#E586A1", to: "#D94A72" },
2009
+ // Class 6
2010
+ 7: { from: "#ABBAC8", to: "#738DA5" },
2011
+ // Class 7
2012
+ 8: { from: "#EEBB40", to: "#D9A015" },
2013
+ // Class 8
2014
+ 9: { from: "#D56763", to: "#BC3531" },
2015
+ // Class 9
2016
+ 10: { from: "#9531D9", to: "#63278B" }
2017
+ // Class 10
2018
+ };
2019
+ const vfColors = vfColorMap[vfClass] || vfColorMap[1];
2020
+ const vfGradient = ctx.createLinearGradient(x + 565, y + 296, x + 675, y + 450);
2021
+ vfGradient.addColorStop(0, vfColors.from);
2022
+ vfGradient.addColorStop(0.4, vfColors.from);
2023
+ vfGradient.addColorStop(0.41, vfColors.to);
2024
+ vfGradient.addColorStop(1, vfColors.to);
2025
+ ctx.fillStyle = vfGradient;
2026
+ ctx.fillText(vfText, x + 525, y + 323);
2027
+ ctx.fillStyle = "#FFFFFF";
2028
+ ctx.font = '32px "Fredoka One"';
2029
+ ctx.fillStyle = "#FFFFFF";
2030
+ const indexText = `#${String(i + 1).padStart(2, "0")}`;
2031
+ ctx.fillText(indexText, x + 744, y + 349);
2032
+ ctx.textAlign = "left";
2033
+ ctx.font = '400 24px "Noto Sans JP"';
2034
+ ctx.fillStyle = "#FFFFFF";
2035
+ const musicId = score.music.music_id;
2036
+ let mainTitle = `Music ID ${musicId}`;
2037
+ let subTitle = ``;
2038
+ if (score.music_data) {
2039
+ if (score.music_data.main_title_name) {
2040
+ mainTitle = score.music_data.main_title_name;
2041
+ } else if (score.music_data.title_name) {
2042
+ mainTitle = score.music_data.title_name;
2043
+ }
2044
+ if (score.music_data.sub_title_name) {
2045
+ subTitle = score.music_data.sub_title_name;
2046
+ }
2047
+ }
2048
+ const wrappedText = this.wrapText(ctx, mainTitle, 413);
2049
+ wrappedText.forEach((line, index) => {
2050
+ ctx.fillText(line, x + 388, y + 90 + index * 30);
2051
+ });
2052
+ ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
2053
+ const wrappedSubTitle = this.wrapText(ctx, subTitle, 413);
2054
+ const startY2 = y + 388 + wrappedText.length * 30;
2055
+ wrappedSubTitle.forEach((line, index) => {
2056
+ ctx.fillText(line, x + 388, startY2 + index * 28);
2057
+ });
2058
+ ctx.fillStyle = "#FFFFFF";
2059
+ const grade = score.music.score_grade;
2060
+ if (gradeImages[grade]) {
2061
+ ctx.drawImage(gradeImages[grade], x + 352, y + 230);
2062
+ }
2063
+ if (vfBadges[vfClass]) {
2064
+ ctx.drawImage(vfBadges[vfClass], x + 396, y + 340);
2065
+ }
2066
+ if (score.difficulty_data?.cover_url) {
2067
+ const coverUrl = `${config.sdvx_data_url}${score.difficulty_data.cover_url}`.replace(".webp", ".png");
2068
+ const jacketImage = jacketImages[coverUrl];
2069
+ if (jacketImage) {
2070
+ ctx.drawImage(jacketImage, x + 24, y + 89, 300, 300);
2071
+ }
2072
+ }
2073
+ } catch (error) {
2074
+ this.ctx.logger("SDVX-Drawer").warn(`Error drawing card ${i}: ${error.message}`);
2075
+ }
2076
+ }
2077
+ }
2078
+ /**
2079
+ * Get abbreviation for a difficulty level
2080
+ */
2081
+ getDifficultyAbbreviation(diff) {
2082
+ const diffMap = {
2083
+ 0: "NOV",
2084
+ // Novice
2085
+ 1: "ADV",
2086
+ // Advanced
2087
+ 2: "EXH",
2088
+ // Exhaust
2089
+ 3: "INF",
2090
+ // Infinite
2091
+ 4: "MXM"
2092
+ // Maximum
2093
+ };
2094
+ return diffMap[diff] || "UNK";
2095
+ }
2096
+ /**
2097
+ * Calculate the average volforce from best 50 scores
2098
+ */
2099
+ calculateAverageVF(scores) {
2100
+ if (scores.length === 0)
2101
+ return 0;
2102
+ const sortedScores = [...scores].sort(
2103
+ (a, b) => b.extra.volforce - a.extra.volforce
2104
+ ).slice(0, 50);
2105
+ const total = sortedScores.reduce((sum, score) => sum + score.extra.volforce, 0);
2106
+ return total / sortedScores.length;
2107
+ }
2108
+ wrapText(ctx, text, maxWidth) {
2109
+ const words = text.split(" ");
2110
+ const lines = [];
2111
+ let currentLine = "";
2112
+ for (const word of words) {
2113
+ const testLine = currentLine + word + " ";
2114
+ const testWidth = ctx.measureText(testLine).width;
2115
+ if (testWidth > maxWidth) {
2116
+ lines.push(currentLine.trim());
2117
+ currentLine = word + " ";
2118
+ } else {
2119
+ currentLine = testLine;
2120
+ }
2121
+ }
2122
+ if (currentLine.trim()) {
2123
+ lines.push(currentLine.trim());
2124
+ }
2125
+ return lines;
2126
+ }
2127
+ roundRect(ctx, x, y, width, height, radius) {
2128
+ if (width < 2 * radius)
2129
+ radius = width / 2;
2130
+ if (height < 2 * radius)
2131
+ radius = height / 2;
2132
+ ctx.beginPath();
2133
+ ctx.moveTo(x + radius, y);
2134
+ ctx.arcTo(x + width, y, x + width, y + height, radius);
2135
+ ctx.arcTo(x + width, y + height, x, y + height, radius);
2136
+ ctx.arcTo(x, y + height, x, y, radius);
2137
+ ctx.arcTo(x, y, x + width, y, radius);
2138
+ ctx.closePath();
2139
+ }
2140
+ };
2141
+
2142
+ // src/drawer/IIDX/index.ts
2143
+ var IIDXDrawer = class extends BaseDrawer {
2144
+ constructor(ctx) {
2145
+ super(ctx);
2146
+ this.ctx = ctx;
2147
+ }
2148
+ static {
2149
+ __name(this, "IIDXDrawer");
2150
+ }
2151
+ /**
2152
+ * Generate an image for IIDX data
2153
+ * @param options Configuration options for image generation
2154
+ * @param compression Compression options
2155
+ * @returns Promise resolving to a Buffer containing the image data
2156
+ */
2157
+ async generateImage(options, compression) {
2158
+ const { Canvas, loadImage } = this.ctx.skia;
2159
+ const width = options?.width || 1e3;
2160
+ const height = options?.height || 600;
2161
+ const playerName = options?.playerName || "IIDX Player";
2162
+ const backgroundColor = options?.backgroundColor || "#000000";
2163
+ const textColor = options?.textColor || "#FFFFFF";
2164
+ const dj_level = options?.dj_level || "UNKNOWN";
2165
+ const sp_dan = options?.sp_dan || "UNKNOWN";
2166
+ const dp_dan = options?.dp_dan || "UNKNOWN";
2167
+ const canvas = new Canvas(width, height);
2168
+ const ctx = canvas.getContext("2d");
2169
+ ctx.fillStyle = backgroundColor;
2170
+ ctx.fillRect(0, 0, width, height);
2171
+ ctx.fillStyle = "#FFCC00";
2172
+ ctx.fillRect(0, 0, width, 100);
2173
+ ctx.fillStyle = "#000000";
2174
+ ctx.font = "bold 32px Arial";
2175
+ ctx.textAlign = "center";
2176
+ ctx.fillText(`BEATMANIA IIDX`, width / 2, 60);
2177
+ ctx.fillStyle = textColor;
2178
+ ctx.font = "bold 24px Arial";
2179
+ ctx.textAlign = "center";
2180
+ ctx.fillText(`${playerName}`, width / 2, 150);
2181
+ ctx.font = "20px Arial";
2182
+ ctx.fillText(`DJ LEVEL: ${dj_level}`, width / 2, 200);
2183
+ this.drawRankingInfo(ctx, width / 2, 250, sp_dan, dp_dan, textColor);
2184
+ return this.compressImage(canvas, compression);
2185
+ }
2186
+ /**
2187
+ * Draw ranking information
2188
+ */
2189
+ drawRankingInfo(ctx, centerX, y, sp_dan, dp_dan, textColor) {
2190
+ ctx.fillStyle = textColor;
2191
+ ctx.font = "18px Arial";
2192
+ ctx.textAlign = "right";
2193
+ ctx.fillText("SP DAN:", centerX - 10, y);
2194
+ ctx.textAlign = "left";
2195
+ ctx.fillText(sp_dan, centerX + 10, y);
2196
+ ctx.textAlign = "right";
2197
+ ctx.fillText("DP DAN:", centerX - 10, y + 40);
2198
+ ctx.textAlign = "left";
2199
+ ctx.fillText(dp_dan, centerX + 10, y + 40);
2200
+ }
2201
+ };
2202
+
2203
+ // src/drawer/DrawerFactory.ts
2204
+ var DrawerFactory = class {
2205
+ constructor(ctx) {
2206
+ this.ctx = ctx;
2207
+ }
2208
+ static {
2209
+ __name(this, "DrawerFactory");
2210
+ }
2211
+ drawerInstances = /* @__PURE__ */ new Map();
2212
+ getDrawer(drawerType) {
2213
+ if (!this.drawerInstances.has(drawerType)) {
2214
+ let drawerInstance;
2215
+ switch (drawerType) {
2216
+ case "sdvx":
2217
+ drawerInstance = new SDVXDrawer(this.ctx);
2218
+ break;
2219
+ case "iidx":
2220
+ drawerInstance = new IIDXDrawer(this.ctx);
2221
+ break;
2222
+ case "core":
2223
+ drawerInstance = new CoreDrawer(this.ctx);
2224
+ break;
2225
+ default:
2226
+ throw new Error(`Unsupported drawer type: ${drawerType}`);
2227
+ }
2228
+ this.drawerInstances.set(drawerType, drawerInstance);
2229
+ }
2230
+ return this.drawerInstances.get(drawerType);
2231
+ }
2232
+ };
2233
+
2234
+ // src/drawer/index.ts
2235
+ var name6 = "Noah-Drawer";
2236
+ var DrawerManager = class _DrawerManager {
2237
+ static {
2238
+ __name(this, "DrawerManager");
2239
+ }
2240
+ static instance;
2241
+ factory;
2242
+ logger;
2243
+ constructor(ctx) {
2244
+ this.factory = new DrawerFactory(ctx);
2245
+ this.logger = new import_koishi7.Logger("Noah-Drawer");
2246
+ }
2247
+ static getInstance(ctx) {
2248
+ if (!_DrawerManager.instance) {
2249
+ _DrawerManager.instance = new _DrawerManager(ctx);
2250
+ }
2251
+ return _DrawerManager.instance;
2252
+ }
2253
+ getDrawer(gameType) {
2254
+ this.logger.info(`Getting drawer for ${gameType}`);
2255
+ return this.factory.getDrawer(gameType);
2256
+ }
2257
+ };
2258
+ function apply6(ctx, config) {
2259
+ ctx.logger("Noah-Drawer").info("Initializing drawer manager");
2260
+ const drawerManager = DrawerManager.getInstance(ctx);
2261
+ }
2262
+ __name(apply6, "apply");
2263
+
2264
+ // src/games/sdvx/commands/vf.ts
2265
+ function vf(ctx, config) {
2266
+ ctx.command("vf [cardCode]").userFields(["defaultCardId", "defaultServerId", "id"]).channelFields(["defaultServerId", "id"]).option("lossless", "-l Use lossless compression for webp").action(async ({ session, options }, cardCode) => {
2267
+ const model = config.default_model;
2268
+ const cardService = new CardService(ctx);
2269
+ const serverService = new ServerService(ctx);
2270
+ const userCards = await cardService.getCardsByUid(session.user.id);
2271
+ if (userCards.length === 0)
2272
+ return session.text(".card-not-found");
2273
+ const userRes = await serverService.getServersByUid(session.user.id);
2274
+ const channelRes = await serverService.getServersByCid(session.channel.id);
2275
+ const serverRes = channelRes.concat(userRes);
2276
+ if (serverRes.length === 0)
2277
+ return session.text(".server-not-found");
2278
+ if (!cardCode) {
2279
+ const cardRes = await cardService.getCardById(session.user.defaultCardId);
2280
+ cardCode = cardRes.code;
2281
+ }
2282
+ const card2 = await cardService.getCardByCode(cardCode);
2283
+ let server2;
2284
+ if (card2.defaultServerId != 0)
2285
+ server2 = await serverService.getServerById(card2.defaultServerId);
2286
+ else if (session.channel.defaultServerId != 0)
2287
+ server2 = await serverService.getServerById(session.channel.defaultServerId);
2288
+ else if (session.user.defaultServerId != 0)
2289
+ server2 = await serverService.getServerById(session.user.defaultServerId);
2290
+ const serverManager = ServerManager.getInstance();
2291
+ const sdvxService = serverManager.getGameService(server2.type, "sdvx");
2292
+ const scoreService = ScoreService.getInstance();
2293
+ try {
2294
+ const scoreList = scoreService.getBest50(await sdvxService.getAllScore(ctx, server2.baseUrl, card2.code, config));
2295
+ if (scoreList.length === 0) {
2296
+ return session.text(".no-scores-found");
2297
+ }
2298
+ session.send(session.text(".drawing"));
2299
+ const drawerManager = DrawerManager.getInstance(ctx);
2300
+ const sdvxDrawer = drawerManager.getDrawer("sdvx");
2301
+ const imageBuffer = await sdvxDrawer.generateVFImage({
2302
+ scores: scoreList,
2303
+ playerName: card2.name || "Unknown Player",
2304
+ config
2305
+ }, {
2306
+ lossless: options.lossless || false
2307
+ });
2308
+ if (options.lossless) {
2309
+ return import_koishi8.h.file(imageBuffer, "image/png");
2310
+ }
2311
+ return import_koishi8.h.image(imageBuffer, "image/jpg");
2312
+ } catch (error) {
2313
+ ctx.logger("SDVX-VF").warn(error);
2314
+ return session.text(".error", { message: error.message });
2315
+ }
2316
+ });
2317
+ }
2318
+ __name(vf, "vf");
2319
+
2320
+ // src/games/sdvx/command.ts
2321
+ var name7 = "command";
2322
+ function apply7(ctx, config) {
2323
+ vf(ctx, config);
2324
+ }
2325
+ __name(apply7, "apply");
2326
+
2327
+ // src/games/sdvx/event.ts
2328
+ var event_exports = {};
2329
+ __export(event_exports, {
2330
+ apply: () => apply8,
2331
+ name: () => name8
2332
+ });
2333
+ var name8 = "event";
2334
+ function apply8(ctx) {
2335
+ }
2336
+ __name(apply8, "apply");
2337
+
2338
+ // src/games/sdvx/index.ts
2339
+ var name9 = "Noah-SDVX";
2340
+ var inject2 = ["database"];
2341
+ var logger2 = new import_koishi9.Logger("Noah-SDVX");
2342
+ async function apply9(ctx, config) {
2343
+ [["en-US", en_US_default2], ["zh-CN", zh_CN_default2]].forEach(([lang, file]) => ctx.i18n.define(lang, file));
2344
+ ctx.plugin(database_exports2, config.sdvx);
2345
+ ctx.plugin(command_exports2, config.sdvx);
2346
+ ctx.plugin(event_exports, config.sdvx);
2347
+ const serverManager = ServerManager.getInstance();
2348
+ const sdvxService = serverManager.getGameService("mao", "sdvx");
2349
+ const musicService = MusicService.getInstance(config.sdvx);
2350
+ const music = await musicService.getMusic(ctx, [1438, 1259]);
2351
+ }
2352
+ __name(apply9, "apply");
2353
+
2354
+ // src/config.ts
2355
+ var import_koishi11 = require("koishi");
2356
+
2357
+ // src/games/sdvx/config.ts
2358
+ var import_koishi10 = require("koishi");
2359
+ var sdvxConfig = import_koishi10.Schema.object({
2360
+ default_model: import_koishi10.Schema.string().default("2024110700"),
2361
+ sdvx_data_url: import_koishi10.Schema.string().required()
2362
+ }).i18n({
2363
+ "en-US": en_US_default2._config,
2364
+ "zh-CN": zh_CN_default2._config
2365
+ });
2366
+
2367
+ // src/config.ts
2368
+ var Config = import_koishi11.Schema.object({
2369
+ core: coreConfig,
2370
+ sdvx: sdvxConfig
2371
+ });
2372
+
2373
+ // src/index.ts
2374
+ var name10 = "noah";
2375
+ var inject3 = ["database", "skia"];
2376
+ function apply10(ctx, config) {
2377
+ ctx.plugin(core_exports, config);
2378
+ ctx.plugin(sdvx_exports, config);
2379
+ ctx.plugin(servers_exports, config);
2380
+ ctx.plugin(drawer_exports, config);
2381
+ }
2382
+ __name(apply10, "apply");
2383
+ // Annotate the CommonJS export names for ESM import in node:
2384
+ 0 && (module.exports = {
2385
+ Config,
2386
+ apply,
2387
+ inject,
2388
+ name
2389
+ });