koishi-plugin-blhx-wiki-assistant 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,2490 @@
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 name2 in all)
10
+ __defProp(target, name2, { get: all[name2], 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
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ var src_exports = {};
27
+ __export(src_exports, {
28
+ Config: () => Config,
29
+ apply: () => apply,
30
+ inject: () => inject,
31
+ name: () => name,
32
+ usage: () => usage
33
+ });
34
+ module.exports = __toCommonJS(src_exports);
35
+ var import_koishi = require("koishi");
36
+ var import_https = __toESM(require("https"));
37
+ var import_cheerio = require("cheerio");
38
+ var import_iconv_lite = __toESM(require("iconv-lite"));
39
+ var inject = {
40
+ required: ["puppeteer"],
41
+ optional: ["markdownToImage"]
42
+ };
43
+ var name = "azur-lane-assistant";
44
+ var usage = `1. 启动 \`puppeteer\` 服务。
45
+ 2. 设置指令别名。
46
+
47
+ - 长图偶而出现 Bug,重试可解。
48
+
49
+ - 956758505`;
50
+ var Config = import_koishi.Schema.intersect([
51
+ import_koishi.Schema.object({
52
+ defaultShipGirlsListBatchCount: import_koishi.Schema.number().min(1).max(10).default(5).description(`发送舰娘列表的默认批次数,最大值为 \`10\`。`),
53
+ defaultEquipmentsListBatchCount: import_koishi.Schema.number().min(1).max(10).default(5).description(`发送装备列表的默认批次数,最大值为 \`10\`。`),
54
+ defaultRanksListBatchCount: import_koishi.Schema.number().min(1).max(5).default(1).description(`发送井号碧蓝榜列表的默认批次数,最大值为 \`5\`。`),
55
+ defaultStagesListBatchCount: import_koishi.Schema.number().min(1).max(10).default(5).description(`发送关卡列表的默认批次数,最大值为 \`10\`。`)
56
+ }).description("发送列表默认批次数"),
57
+ import_koishi.Schema.object({
58
+ imageType: import_koishi.Schema.union(["png", "jpeg", "webp"]).default("png").description(`发送的图片类型。`)
59
+ }).description("图片发送设置")
60
+ ]);
61
+
62
+ // 添加通用的下载函数
63
+ async function downloadFile(url) {
64
+ return new Promise((resolve, reject) => {
65
+ import_https.default.get(url, (response) => {
66
+ if (response.statusCode !== 200) {
67
+ reject(new Error(`HTTP ${response.statusCode}: ${url}`));
68
+ return;
69
+ }
70
+
71
+ const chunks = [];
72
+ response.on('data', (chunk) => chunks.push(chunk));
73
+ response.on('end', () => {
74
+ const buffer = Buffer.concat(chunks);
75
+ resolve(buffer);
76
+ });
77
+ }).on('error', (error) => {
78
+ reject(error);
79
+ });
80
+ });
81
+ }
82
+
83
+ // 添加判断文件类型的函数
84
+ function getMimeTypeFromUrl(url) {
85
+ const lowerUrl = url.toLowerCase();
86
+ if (lowerUrl.endsWith('.jpg') || lowerUrl.endsWith('.jpeg')) {
87
+ return 'image/jpeg';
88
+ } else if (lowerUrl.endsWith('.png')) {
89
+ return 'image/png';
90
+ } else if (lowerUrl.endsWith('.gif')) {
91
+ return 'image/gif';
92
+ } else if (lowerUrl.endsWith('.webp')) {
93
+ return 'image/webp';
94
+ } else if (lowerUrl.endsWith('.mp3')) {
95
+ return 'audio/mpeg';
96
+ } else if (lowerUrl.endsWith('.wav')) {
97
+ return 'audio/wav';
98
+ } else if (lowerUrl.endsWith('.ogg')) {
99
+ return 'audio/ogg';
100
+ }
101
+ return null;
102
+ }
103
+
104
+ function apply(ctx, config) {
105
+ const logger = ctx.logger("azurLaneAssistant");
106
+ const {
107
+ defaultShipGirlsListBatchCount,
108
+ defaultEquipmentsListBatchCount,
109
+ defaultRanksListBatchCount,
110
+ defaultStagesListBatchCount,
111
+ imageType
112
+ } = config;
113
+ let shipGirls = [];
114
+ let equipments = [];
115
+ let ranks = [];
116
+ let stages = [];
117
+ const manlineStages = [
118
+ {
119
+ "stageNumber": "1-1",
120
+ "stageName": "近海演习"
121
+ },
122
+ {
123
+ "stageNumber": "1-1Hard",
124
+ "stageName": "近海演习(困难)"
125
+ },
126
+ {
127
+ "stageNumber": "1-2",
128
+ "stageName": "虎!虎!虎!"
129
+ },
130
+ {
131
+ "stageNumber": "1-2Hard",
132
+ "stageName": "虎!虎!虎!(困难)"
133
+ },
134
+ {
135
+ "stageNumber": "1-3",
136
+ "stageName": "燃烧的军港"
137
+ },
138
+ {
139
+ "stageNumber": "1-3Hard",
140
+ "stageName": "燃烧的军港(困难)"
141
+ },
142
+ {
143
+ "stageNumber": "1-4",
144
+ "stageName": "来自东方的舰队"
145
+ },
146
+ {
147
+ "stageNumber": "1-4Hard",
148
+ "stageName": "来自东方的舰队(困难)"
149
+ },
150
+ {
151
+ "stageNumber": "2-1",
152
+ "stageName": "支援图拉岛"
153
+ },
154
+ {
155
+ "stageNumber": "2-1Hard",
156
+ "stageName": "支援图拉岛(困难)"
157
+ },
158
+ {
159
+ "stageNumber": "2-2",
160
+ "stageName": "乌云蔽日"
161
+ },
162
+ {
163
+ "stageNumber": "2-2Hard",
164
+ "stageName": "乌云蔽日(困难)"
165
+ },
166
+ {
167
+ "stageNumber": "2-3",
168
+ "stageName": "珊瑚海的首秀"
169
+ },
170
+ {
171
+ "stageNumber": "2-3Hard",
172
+ "stageName": "珊瑚海的首秀(困难)"
173
+ },
174
+ {
175
+ "stageNumber": "2-4",
176
+ "stageName": "救援约克城"
177
+ },
178
+ {
179
+ "stageNumber": "2-4Hard",
180
+ "stageName": "救援约克城(困难)"
181
+ },
182
+ {
183
+ "stageNumber": "3-1",
184
+ "stageName": "决战中途岛!"
185
+ },
186
+ {
187
+ "stageNumber": "3-1Hard",
188
+ "stageName": "决战中途岛!(困难)"
189
+ },
190
+ {
191
+ "stageNumber": "3-2",
192
+ "stageName": "命运的五分钟"
193
+ },
194
+ {
195
+ "stageNumber": "3-2Hard",
196
+ "stageName": "命运的五分钟(困难)"
197
+ },
198
+ {
199
+ "stageNumber": "3-3",
200
+ "stageName": "背水一战"
201
+ },
202
+ {
203
+ "stageNumber": "3-3Hard",
204
+ "stageName": "背水一战(困难)"
205
+ },
206
+ {
207
+ "stageNumber": "3-4",
208
+ "stageName": "最后的反击"
209
+ },
210
+ {
211
+ "stageNumber": "3-4Hard",
212
+ "stageName": "最后的反击(困难)"
213
+ },
214
+ {
215
+ "stageNumber": "4-1",
216
+ "stageName": "午夜惊魂"
217
+ },
218
+ {
219
+ "stageNumber": "4-1Hard",
220
+ "stageName": "午夜惊魂(困难)"
221
+ },
222
+ {
223
+ "stageNumber": "4-2",
224
+ "stageName": "血色黎明"
225
+ },
226
+ {
227
+ "stageNumber": "4-2Hard",
228
+ "stageName": "血色黎明(困难)"
229
+ },
230
+ {
231
+ "stageNumber": "4-3",
232
+ "stageName": "东所罗门遭遇战"
233
+ },
234
+ {
235
+ "stageNumber": "4-3Hard",
236
+ "stageName": "东所罗门遭遇战(困难)"
237
+ },
238
+ {
239
+ "stageNumber": "4-4",
240
+ "stageName": "复仇之战"
241
+ },
242
+ {
243
+ "stageNumber": "4-4Hard",
244
+ "stageName": "复仇之战(困难)"
245
+ },
246
+ {
247
+ "stageNumber": "5-1",
248
+ "stageName": "物资拦截战"
249
+ },
250
+ {
251
+ "stageNumber": "5-1Hard",
252
+ "stageName": "物资拦截战(困难)"
253
+ },
254
+ {
255
+ "stageNumber": "5-2",
256
+ "stageName": "圣克鲁斯的天空"
257
+ },
258
+ {
259
+ "stageNumber": "5-2Hard",
260
+ "stageName": "圣克鲁斯的天空(困难)"
261
+ },
262
+ {
263
+ "stageNumber": "5-3",
264
+ "stageName": "大黄蜂的陨落"
265
+ },
266
+ {
267
+ "stageNumber": "5-3Hard",
268
+ "stageName": "大黄蜂的陨落(困难)"
269
+ },
270
+ {
271
+ "stageNumber": "5-4",
272
+ "stageName": "撤离战区"
273
+ },
274
+ {
275
+ "stageNumber": "5-4Hard",
276
+ "stageName": "撤离战区(困难)"
277
+ },
278
+ {
279
+ "stageNumber": "6-1",
280
+ "stageName": "夜战精英"
281
+ },
282
+ {
283
+ "stageNumber": "6-1Hard",
284
+ "stageName": "夜战精英(困难)"
285
+ },
286
+ {
287
+ "stageNumber": "6-2",
288
+ "stageName": "反攻"
289
+ },
290
+ {
291
+ "stageNumber": "6-2Hard",
292
+ "stageName": "反攻(困难)"
293
+ },
294
+ {
295
+ "stageNumber": "6-3",
296
+ "stageName": "巨炮最后的对决"
297
+ },
298
+ {
299
+ "stageNumber": "6-3Hard",
300
+ "stageName": "巨炮最后的对决(困难)"
301
+ },
302
+ {
303
+ "stageNumber": "6-4",
304
+ "stageName": "所罗门的噩梦"
305
+ },
306
+ {
307
+ "stageNumber": "6-4Hard",
308
+ "stageName": "所罗门的噩梦(困难)"
309
+ },
310
+ {
311
+ "stageNumber": "7-1",
312
+ "stageName": "增援拦截"
313
+ },
314
+ {
315
+ "stageNumber": "7-1Hard",
316
+ "stageName": "增援拦截(困难)"
317
+ },
318
+ {
319
+ "stageNumber": "7-2",
320
+ "stageName": "短兵相接"
321
+ },
322
+ {
323
+ "stageNumber": "7-2Hard",
324
+ "stageName": "短兵相接(困难)"
325
+ },
326
+ {
327
+ "stageNumber": "7-3",
328
+ "stageName": "措手不及"
329
+ },
330
+ {
331
+ "stageNumber": "7-3Hard",
332
+ "stageName": "措手不及(困难)"
333
+ },
334
+ {
335
+ "stageNumber": "7-4",
336
+ "stageName": "预料外的混乱"
337
+ },
338
+ {
339
+ "stageNumber": "7-4Hard",
340
+ "stageName": "预料外的混乱(困难)"
341
+ },
342
+ {
343
+ "stageNumber": "8-1",
344
+ "stageName": "寒风"
345
+ },
346
+ {
347
+ "stageNumber": "8-1Hard",
348
+ "stageName": "寒风(困难)"
349
+ },
350
+ {
351
+ "stageNumber": "8-2",
352
+ "stageName": "北极圈的拂晓"
353
+ },
354
+ {
355
+ "stageNumber": "8-2Hard",
356
+ "stageName": "北极圈的拂晓(困难)"
357
+ },
358
+ {
359
+ "stageNumber": "8-3",
360
+ "stageName": "冰海怒涛"
361
+ },
362
+ {
363
+ "stageNumber": "8-3Hard",
364
+ "stageName": "冰海怒涛(困难)"
365
+ },
366
+ {
367
+ "stageNumber": "8-4",
368
+ "stageName": "被遗忘的战场"
369
+ },
370
+ {
371
+ "stageNumber": "8-4Hard",
372
+ "stageName": "被遗忘的战场(困难)"
373
+ },
374
+ {
375
+ "stageNumber": "9-1",
376
+ "stageName": "不祥之夜"
377
+ },
378
+ {
379
+ "stageNumber": "9-1Hard",
380
+ "stageName": "不祥之夜(困难)"
381
+ },
382
+ {
383
+ "stageNumber": "9-2",
384
+ "stageName": "拦截作战"
385
+ },
386
+ {
387
+ "stageNumber": "9-2Hard",
388
+ "stageName": "拦截作战(困难)"
389
+ },
390
+ {
391
+ "stageNumber": "9-3",
392
+ "stageName": "黑夜中的光芒"
393
+ },
394
+ {
395
+ "stageNumber": "9-3Hard",
396
+ "stageName": "黑夜中的光芒(困难)"
397
+ },
398
+ {
399
+ "stageNumber": "9-4",
400
+ "stageName": "海伦娜"
401
+ },
402
+ {
403
+ "stageNumber": "9-4Hard",
404
+ "stageName": "海伦娜(困难)"
405
+ },
406
+ {
407
+ "stageNumber": "10-1",
408
+ "stageName": "再次出击,再次!"
409
+ },
410
+ {
411
+ "stageNumber": "10-1Hard",
412
+ "stageName": "再次出击,再次!(困难)"
413
+ },
414
+ {
415
+ "stageNumber": "10-2",
416
+ "stageName": "先发制人"
417
+ },
418
+ {
419
+ "stageNumber": "10-2Hard",
420
+ "stageName": "先发制人(困难)"
421
+ },
422
+ {
423
+ "stageNumber": "10-3",
424
+ "stageName": "乘胜追击"
425
+ },
426
+ {
427
+ "stageNumber": "10-3Hard",
428
+ "stageName": "乘胜追击(困难)"
429
+ },
430
+ {
431
+ "stageNumber": "10-4",
432
+ "stageName": "回马枪"
433
+ },
434
+ {
435
+ "stageNumber": "10-4Hard",
436
+ "stageName": "回马枪(困难)"
437
+ },
438
+ {
439
+ "stageNumber": "11-1",
440
+ "stageName": "拂晓登陆!"
441
+ },
442
+ {
443
+ "stageNumber": "11-1Hard",
444
+ "stageName": "拂晓登陆!(困难)"
445
+ },
446
+ {
447
+ "stageNumber": "11-2",
448
+ "stageName": "暴风雨之夜"
449
+ },
450
+ {
451
+ "stageNumber": "11-2Hard",
452
+ "stageName": "暴风雨之夜(困难)"
453
+ },
454
+ {
455
+ "stageNumber": "11-3",
456
+ "stageName": "所罗门四骑士"
457
+ },
458
+ {
459
+ "stageNumber": "11-3Hard",
460
+ "stageName": "所罗门四骑士(困难)"
461
+ },
462
+ {
463
+ "stageNumber": "11-4",
464
+ "stageName": "撕裂黑夜!"
465
+ },
466
+ {
467
+ "stageNumber": "11-4Hard",
468
+ "stageName": "撕裂黑夜!(困难)"
469
+ },
470
+ {
471
+ "stageNumber": "12-1",
472
+ "stageName": "先声夺人"
473
+ },
474
+ {
475
+ "stageNumber": "12-1Hard",
476
+ "stageName": "先声夺人(困难)"
477
+ },
478
+ {
479
+ "stageNumber": "12-2",
480
+ "stageName": "鲁莽的后果"
481
+ },
482
+ {
483
+ "stageNumber": "12-2Hard",
484
+ "stageName": "鲁莽的后果(困难)"
485
+ },
486
+ {
487
+ "stageNumber": "12-3",
488
+ "stageName": "空中对决"
489
+ },
490
+ {
491
+ "stageNumber": "12-3Hard",
492
+ "stageName": "空中对决(困难)"
493
+ },
494
+ {
495
+ "stageNumber": "12-4",
496
+ "stageName": "TF58,翱翔于天际"
497
+ },
498
+ {
499
+ "stageNumber": "12-4Hard",
500
+ "stageName": "TF58,翱翔于天际(困难)"
501
+ },
502
+ {
503
+ "stageNumber": "13-1",
504
+ "stageName": "激战的长空"
505
+ },
506
+ {
507
+ "stageNumber": "13-1Hard",
508
+ "stageName": "激战的长空(困难)"
509
+ },
510
+ {
511
+ "stageNumber": "13-2",
512
+ "stageName": "羽栖之鹤"
513
+ },
514
+ {
515
+ "stageNumber": "13-2Hard",
516
+ "stageName": "羽栖之鹤(困难)"
517
+ },
518
+ {
519
+ "stageNumber": "13-3",
520
+ "stageName": "奋起之鹤"
521
+ },
522
+ {
523
+ "stageNumber": "13-3Hard",
524
+ "stageName": "奋起之鹤(困难)"
525
+ },
526
+ {
527
+ "stageNumber": "13-4",
528
+ "stageName": "起舞之凤"
529
+ },
530
+ {
531
+ "stageNumber": "13-4Hard",
532
+ "stageName": "起舞之凤(困难)"
533
+ },
534
+ {
535
+ "stageNumber": "14-1",
536
+ "stageName": "夜间遭遇"
537
+ },
538
+ {
539
+ "stageNumber": "14-2",
540
+ "stageName": "T字对决"
541
+ },
542
+ {
543
+ "stageNumber": "14-3",
544
+ "stageName": "缠斗"
545
+ },
546
+ {
547
+ "stageNumber": "14-4",
548
+ "stageName": "晨曦下的追击"
549
+ },
550
+ {
551
+ "stageNumber": "14-1Hard",
552
+ "stageName": "夜间遭遇"
553
+ },
554
+ {
555
+ "stageNumber": "14-2Hard",
556
+ "stageName": "T字对决"
557
+ },
558
+ {
559
+ "stageNumber": "14-3Hard",
560
+ "stageName": "缠斗"
561
+ },
562
+ {
563
+ "stageNumber": "14-4Hard",
564
+ "stageName": "晨曦下的追击"
565
+ },
566
+ {
567
+ "stageNumber": "15-1",
568
+ "stageName": "破晓突袭"
569
+ },
570
+ {
571
+ "stageNumber": "15-2",
572
+ "stageName": "胜券在握"
573
+ },
574
+ {
575
+ "stageNumber": "15-3",
576
+ "stageName": "紧急求援"
577
+ },
578
+ {
579
+ "stageNumber": "15-4",
580
+ "stageName": "笼中之鹤"
581
+ },
582
+ {
583
+ "stageNumber": "16-1",
584
+ "stageName": "水下的监视者"
585
+ },
586
+ {
587
+ "stageNumber": "16-2",
588
+ "stageName": "破阵"
589
+ },
590
+ {
591
+ "stageNumber": "16-3",
592
+ "stageName": "自空中而来"
593
+ },
594
+ {
595
+ "stageNumber": "16-4",
596
+ "stageName": "海战的尾声"
597
+ }
598
+
599
+ ];
600
+
601
+ function fuzzySearch(query, items, searchKeys = ['name', 'title']) {
602
+ if (!query) return [];
603
+
604
+ const normalizedQuery = query.toLowerCase().trim();
605
+ const results = [];
606
+
607
+ for (const item of items) {
608
+ let score = 0;
609
+ let matched = false;
610
+
611
+ for (const key of searchKeys) {
612
+ if (item[key]) {
613
+ const text = item[key].toLowerCase();
614
+
615
+ if (text === normalizedQuery) {
616
+ score = 100;
617
+ matched = true;
618
+ break;
619
+ }
620
+
621
+ if (text.includes(normalizedQuery)) {
622
+ const position = text.indexOf(normalizedQuery);
623
+ score = Math.max(score, 80 - position);
624
+ matched = true;
625
+ }
626
+
627
+ const cleanText = text.replace(/[^\w\u4e00-\u9fa5]/g, '');
628
+ const cleanQuery = normalizedQuery.replace(/[^\w\u4e00-\u9fa5]/g, '');
629
+
630
+ if (cleanText.includes(cleanQuery)) {
631
+ const position = cleanText.indexOf(cleanQuery);
632
+ score = Math.max(score, 60 - position);
633
+ matched = true;
634
+ }
635
+
636
+ if (/[\u4e00-\u9fa5]/.test(normalizedQuery)) {
637
+ const firstChars = text.replace(/[^\u4e00-\u9fa5]/g, '')
638
+ .split('')
639
+ .map(char => char)
640
+ .join('');
641
+
642
+ if (firstChars.includes(normalizedQuery)) {
643
+ score = Math.max(score, 40);
644
+ matched = true;
645
+ }
646
+ }
647
+ }
648
+ }
649
+
650
+ if (matched) {
651
+ results.push({ item, score });
652
+ }
653
+ }
654
+
655
+ return results.sort((a, b) => b.score - a.score).map(r => r.item);
656
+ }
657
+
658
+ function findBestShipGirlMatch(query, items) {
659
+ const results = fuzzySearch(query, items, ['name', 'title', 'tags']);
660
+
661
+ if (results.length > 0) {
662
+ const exactNameMatch = items.find(item =>
663
+ item.name && item.name.toLowerCase() === query.toLowerCase()
664
+ );
665
+ if (exactNameMatch) return exactNameMatch;
666
+
667
+ const exactTitleMatch = items.find(item =>
668
+ item.title && item.title.toLowerCase() === query.toLowerCase()
669
+ );
670
+ if (exactTitleMatch) return exactTitleMatch;
671
+
672
+ return results[0];
673
+ }
674
+
675
+ return null;
676
+ }
677
+
678
+ function findBestEquipmentMatch(query, items) {
679
+ const results = fuzzySearch(query, items, ['name', 'title', 'tags']);
680
+
681
+ if (results.length > 0) {
682
+ const exactNameMatch = items.find(item =>
683
+ item.name && item.name.toLowerCase() === query.toLowerCase()
684
+ );
685
+ if (exactNameMatch) return exactNameMatch;
686
+
687
+ const exactTitleMatch = items.find(item =>
688
+ item.title && item.title.toLowerCase() === query.toLowerCase()
689
+ );
690
+ if (exactTitleMatch) return exactTitleMatch;
691
+
692
+ return results[0];
693
+ }
694
+
695
+ return null;
696
+ }
697
+
698
+ ctx.command("azurLaneAssistant", "查看碧蓝航线小助手帮助").action(async ({ session }) => {
699
+ await session.execute(`azurLaneAssistant -h`);
700
+ });
701
+ ctx.command("azurLaneAssistant.舰娘", "查看舰娘指令帮助").action(async ({ session }) => {
702
+ await session.execute(`azurLaneAssistant.舰娘 -h`);
703
+ });
704
+ ctx.command("azurLaneAssistant.舰娘.列表 [batchCount:number]", "查看舰娘列表").option("initialize", "-i 初始化舰娘列表").action(async ({ session, options }, batchCount = defaultShipGirlsListBatchCount) => {
705
+ if (isNaN(batchCount) || batchCount <= 0) {
706
+ return "批次数必须是一个大于 0 的数字!";
707
+ }
708
+ if (batchCount > 10)
709
+ return `批次数超出范围,最大值为 10。`;
710
+ const url = "https://wiki.biligame.com/blhx/%E8%88%B0%E8%88%B9%E5%9B%BE%E9%89%B4";
711
+ import_https.default.get(url, (response) => {
712
+ let chunks = [];
713
+ response.on("data", (chunk) => {
714
+ chunks.push(chunk);
715
+ });
716
+ response.on("end", async () => {
717
+ const buffer = Buffer.concat(chunks);
718
+ const data = import_iconv_lite.default.decode(buffer, "utf-8");
719
+ const $ = (0, import_cheerio.load)(data);
720
+ const cardSelectTr = $("#CardSelectTr");
721
+ const newShipGirls = [];
722
+ cardSelectTr.children().each((index, element) => {
723
+ const name2 = $(element).find(".jntj-4 a").text();
724
+ const tagsArray = [];
725
+ for (let i = 1; i <= 4; i++) {
726
+ const param = $(element).attr(`data-param${i}`);
727
+ if (param) {
728
+ const cleanedParam = param.split(",").map((tag) => tag.trim()).filter((tag) => tag !== "");
729
+ tagsArray.push(...cleanedParam);
730
+ }
731
+ }
732
+ const tags = tagsArray.join(" ");
733
+ const pics = $(element).find(".jntj-2 img").map((i, img) => $(img).prop("outerHTML")).get();
734
+ const obj = {
735
+ name: name2,
736
+ tags,
737
+ pics,
738
+ title: $(element).find(".jntj-4 a").attr("title")
739
+ };
740
+ newShipGirls.push(obj);
741
+ });
742
+ if (newShipGirls !== shipGirls)
743
+ shipGirls = newShipGirls;
744
+ const shipGirlsLength = shipGirls.length;
745
+ const batchSize = Math.floor(shipGirlsLength / batchCount);
746
+ let serialNumber = 1;
747
+ let tableRows = [];
748
+ const tableStyle = `
749
+ <style>
750
+ body {
751
+ margin: 0;
752
+ zoom: 200%;
753
+ }
754
+
755
+ .list {
756
+ display: flex;
757
+ flex-direction: column;
758
+ width: max-content;
759
+ overflow: scroll;
760
+ }
761
+
762
+ table {
763
+ border-collapse: collapse;
764
+ border-spacing: 0;
765
+ width: 100%;
766
+ height: min-content;
767
+ border: 1px solid #ddd;
768
+ }
769
+
770
+ th,
771
+ td {
772
+ text-align: left;
773
+ padding-top: 3px;
774
+ padding-bottom: 3px;
775
+ padding-right: 5px;
776
+ display: flex;
777
+ flex-direction: column;
778
+ width: max-content;
779
+ }
780
+
781
+ tr:nth-child(even) {
782
+ background-color: #f5f5f5;
783
+ }
784
+
785
+ .line1 {
786
+ display: flex;
787
+ flex-direction: row;
788
+ align-items: center;
789
+ width: max-content;
790
+ position: relative;
791
+ }
792
+
793
+ .avatar {
794
+ position: relative;
795
+ }
796
+
797
+ .avatar img:first-child {
798
+ position: absolute;
799
+ top: 0;
800
+ left: 0;
801
+ }
802
+
803
+ .avatar img:last-child {
804
+ width: 60px;
805
+ height: 60px;
806
+ }
807
+
808
+ .name {
809
+ color: #444444;
810
+ margin-left: 5px;
811
+ }
812
+
813
+ .tags {
814
+ color: #6c757d;
815
+ font-size: xx-small;
816
+ margin-left: 0;
817
+ }
818
+ </style>
819
+ `;
820
+ const generateTable = (rows) => {
821
+ return `
822
+ <html lang="zh">
823
+ <head>
824
+ ${tableStyle}
825
+ <title>舰娘列表</title></head>
826
+ <body>
827
+ <div class="list">
828
+ <table>
829
+ ${rows.join("\n")}
830
+ </table>
831
+ </div>
832
+ </body>
833
+ </html>
834
+ `;
835
+ };
836
+ const browser = ctx.puppeteer.browser;
837
+ const context = await browser.createBrowserContext();
838
+ const page = await context.newPage();
839
+ await page.setViewport({ width: 100, height: 100 });
840
+ for (let i = 1; i <= shipGirlsLength; i++) {
841
+ const shipGirl = shipGirls[i - 1];
842
+ const row = `
843
+ <tr>
844
+ <td>
845
+ <div class="line1">
846
+ <div class="avatar">
847
+ ${shipGirl.pics[1]}
848
+ ${shipGirl.pics[0]}
849
+ </div>
850
+ <div class="name">${serialNumber++}. ${shipGirl.name}</div>
851
+ </div>
852
+ <div class="line2">
853
+ <div class="tags">${shipGirl.tags}</div>
854
+ </div>
855
+ </td>
856
+ </tr>
857
+ `;
858
+ tableRows.push(row);
859
+ if ((i % batchSize === 0 || i === shipGirlsLength) && !options.initialize) {
860
+ const html = generateTable(tableRows);
861
+ await page.setContent(html, { waitUntil: "load" });
862
+ const imgBuffer = await page.screenshot({ fullPage: true, type: imageType });
863
+ await session.send(import_koishi.h.image(imgBuffer, `image/${imageType}`));
864
+ tableRows = [];
865
+ }
866
+ }
867
+ if (options.initialize)
868
+ logger.success(`舰娘列表初始化成功!`);
869
+ await page.close();
870
+ await context.close();
871
+ });
872
+ }).on("error", (error) => {
873
+ logger.error("请求出错:", error.message);
874
+ });
875
+ }).execute({ options: { initialize: true } });
876
+
877
+ ctx.command("azurLaneAssistant.舰娘.查询 [indexOrName:text]", "查询舰娘信息").action(async ({ session }, indexOrName) => {
878
+ if (!indexOrName) {
879
+ await session.send(`请输入待查询的【舰娘名】或【序号】或【取消】:`);
880
+ const userInput = await session.prompt();
881
+ if (!userInput)
882
+ return `输入超时。`;
883
+ if (userInput === "取消")
884
+ return `本次查询已取消。`;
885
+ indexOrName = userInput;
886
+ }
887
+
888
+ let selectedShipGirl;
889
+
890
+ if (!isNaN(Number(indexOrName))) {
891
+ const index = parseInt(indexOrName);
892
+ if (index > 0 && index <= shipGirls.length) {
893
+ selectedShipGirl = shipGirls[index - 1];
894
+ } else {
895
+ return `序号 ${index} 超出范围(1~${shipGirls.length})。`;
896
+ }
897
+ } else {
898
+ selectedShipGirl = findBestShipGirlMatch(indexOrName, shipGirls);
899
+
900
+ if (!selectedShipGirl) {
901
+ const suggestions = fuzzySearch(indexOrName, shipGirls, ['name', 'title'])
902
+ .slice(0, 5)
903
+ .map(girl => girl.name);
904
+
905
+ if (suggestions.length > 0) {
906
+ return `未找到舰娘:"${indexOrName}"。\n您是否在找:${suggestions.join('、')}?`;
907
+ }
908
+ return `未找到舰娘:"${indexOrName}"。`;
909
+ }
910
+
911
+ if (selectedShipGirl.name.toLowerCase() !== indexOrName.toLowerCase() &&
912
+ selectedShipGirl.title.toLowerCase() !== indexOrName.toLowerCase()) {
913
+ await session.send(`猜你在找:${selectedShipGirl.name}`);
914
+ }
915
+ }
916
+
917
+ const url = `https://wiki.biligame.com/blhx/${selectedShipGirl.title}`;
918
+ const page = await ctx.puppeteer.page();
919
+ await page.setViewport({ width: 0, height: 20000, deviceScaleFactor: 1 });
920
+ await page.goto(url, { waitUntil: "load" });
921
+ await page.waitForSelector(".mw-parser-output");
922
+
923
+ await page.evaluate(() => {
924
+ const modifyCollapsedElements = () => {
925
+ const collapsedElements = document.querySelectorAll(".panel-collapse.collapse:not(.in)");
926
+ collapsedElements.forEach((element2) => {
927
+ element2.classList.add("in");
928
+ element2.setAttribute("aria-expanded", "true");
929
+ });
930
+ };
931
+
932
+ const removeElements = () => {
933
+ const elementsToDelete = document.querySelectorAll(
934
+ ".wiki-nav.hidden-xs.wiki-nav-celling, " +
935
+ ".bread.mwiki_hide, .bread, " +
936
+ ".qchar-container, " +
937
+ "div.sm-bar, " +
938
+ "span.badge.pull-right, " +
939
+ "div.mw-references-wrap, " +
940
+ "div.panel.panel-shiptable, " +
941
+ ".alert.alert-danger, " +
942
+ ".wiki-nav.hidden-xs, " +
943
+ "ul.nav.nav-tabs, " +
944
+ ".nav-tabs, " +
945
+ ".tab-content > .tab-pane:not(:first-child)"
946
+ );
947
+ elementsToDelete.forEach((element2) => element2.remove());
948
+ };
949
+ const shipRelatedH2 = document.querySelector('h2 span#舰船相关')?.closest('h2');
950
+ if (shipRelatedH2) {
951
+ let currentElement = shipRelatedH2;
952
+ while (currentElement) {
953
+ currentElement.style.display = 'none';
954
+ currentElement = currentElement.nextElementSibling;
955
+ }
956
+ }
957
+
958
+ const removeCanvasElements = () => {
959
+ const canvasElements = document.querySelectorAll('canvas[data-type="canvas"]');
960
+ canvasElements.forEach((el) => el.remove());
961
+ };
962
+ const modifyTableWidth = () => {
963
+ const tableElements = document.querySelectorAll("th:has(table.wikitable.sv-breakthrough)");
964
+ tableElements.forEach((el) => el.style.width = "100%");
965
+ };
966
+
967
+ const elements = document.querySelectorAll('div[style*="max-height"]');
968
+ elements.forEach((element2) => {
969
+ element2.removeAttribute("style");
970
+ });
971
+
972
+ modifyCollapsedElements();
973
+ removeElements();
974
+ removeCanvasElements();
975
+ modifyTableWidth();
976
+
977
+ const otherShipSection = document.querySelector('a[href="#其它舰船"]');
978
+ if (otherShipSection)
979
+ otherShipSection.parentNode.remove();
980
+ const otherShipHeader = document.querySelector("h2 span.mw-headline#其它舰船");
981
+ if (otherShipHeader)
982
+ otherShipHeader.parentNode.remove();
983
+
984
+ const removeHeimuClass = () => {
985
+ const heimuElements = document.querySelectorAll("span.heimu");
986
+ heimuElements.forEach((el) => el.removeAttribute("class"));
987
+ };
988
+ removeHeimuClass();
989
+ });
990
+
991
+ const element = await page.$(".mw-parser-output");
992
+ const imageBuffer = await element.screenshot({ type: imageType });
993
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
994
+ await page.close();
995
+ });
996
+
997
+ ctx.command("azurLaneAssistant.舰娘.立绘 [indexOrName:text]", "查询舰娘立绘").action(async ({ session }, indexOrName) => {
998
+ if (!indexOrName) {
999
+ await session.send(`请输入待查询立绘的【舰娘名】或【序号】或【取消】:`);
1000
+ const userInput = await session.prompt();
1001
+ if (!userInput)
1002
+ return `输入超时。`;
1003
+ if (userInput === "取消")
1004
+ return `本次查询已取消。`;
1005
+ indexOrName = userInput;
1006
+ }
1007
+
1008
+ let selectedShipGirl;
1009
+ if (!isNaN(Number(indexOrName))) {
1010
+ const index = parseInt(indexOrName);
1011
+ if (index > 0 && index <= shipGirls.length) {
1012
+ selectedShipGirl = shipGirls[index - 1];
1013
+ } else {
1014
+ return `序号 ${index} 超出范围(1~${shipGirls.length})。`;
1015
+ }
1016
+ } else {
1017
+ selectedShipGirl = findBestShipGirlMatch(indexOrName, shipGirls);
1018
+
1019
+ if (!selectedShipGirl) {
1020
+ const suggestions = fuzzySearch(indexOrName, shipGirls, ['name', 'title'])
1021
+ .slice(0, 5)
1022
+ .map(girl => girl.name);
1023
+
1024
+ if (suggestions.length > 0) {
1025
+ return `未找到立绘:"${indexOrName}"。\n您是否在找:${suggestions.join('、')}?`;
1026
+ }
1027
+ return `未找到立绘:"${indexOrName}"。`;
1028
+ }
1029
+
1030
+ if (selectedShipGirl.name.toLowerCase() !== indexOrName.toLowerCase() &&
1031
+ selectedShipGirl.title.toLowerCase() !== indexOrName.toLowerCase()) {
1032
+ await session.send(`猜你在找:${selectedShipGirl.name}`);
1033
+ }
1034
+ }
1035
+
1036
+ const url = `https://wiki.biligame.com/blhx/${selectedShipGirl.title}`;
1037
+ import_https.default.get(url, (res) => {
1038
+ let chunks = [];
1039
+ res.on("data", (chunk) => {
1040
+ chunks.push(chunk);
1041
+ });
1042
+ res.on("end", async () => {
1043
+ const buffer = Buffer.concat(chunks);
1044
+ const data = import_iconv_lite.default.decode(buffer, "utf-8");
1045
+ const $ = (0, import_cheerio.load)(data);
1046
+ const illustrations = [];
1047
+ $(".tab_con img").each((index, element) => {
1048
+ const name2 = $(element).attr("alt");
1049
+ const nameNoFileExtension = removeFileExtension(name2);
1050
+ let src = $(element).attr("src");
1051
+
1052
+ if (src && !src.startsWith('http')) {
1053
+ src = `https:${src}`;
1054
+ }
1055
+
1056
+ illustrations.push({
1057
+ index: index + 1,
1058
+ name: nameNoFileExtension,
1059
+ src
1060
+ });
1061
+ });
1062
+
1063
+ if (illustrations.length === 0) {
1064
+ await session.send(`未找到 ${selectedShipGirl.name} 的立绘图片。`);
1065
+ return;
1066
+ }
1067
+
1068
+ const separator = "─".repeat(30);
1069
+ let listText = `【${selectedShipGirl.name}】的立绘列表\n`;
1070
+ listText += `${separator}\n`;
1071
+
1072
+ illustrations.forEach(illustration => {
1073
+ listText += `${illustration.index}.\t${illustration.name}\n`;
1074
+ });
1075
+
1076
+ listText += `${separator}\n`;
1077
+ listText += `发送对应序号获取立绘,0全部发送`;
1078
+
1079
+ await session.send(listText);
1080
+
1081
+ const userInput = await session.prompt(60000);
1082
+ if (!userInput) {
1083
+ return `输入超时,立绘查询结束。`;
1084
+ }
1085
+
1086
+ const input = userInput.trim();
1087
+ if (input === "0") {
1088
+ for (let i = 0; i < illustrations.length; i++) {
1089
+ const illustration = illustrations[i];
1090
+ try {
1091
+ // 下载图片后发送缓冲区
1092
+ const imageBuffer = await downloadFile(illustration.src);
1093
+ const mimeType = getMimeTypeFromUrl(illustration.src) || 'image/jpeg';
1094
+ await session.send(import_koishi.h.image(imageBuffer, mimeType));
1095
+
1096
+ if (i < illustrations.length - 1) {
1097
+ await new Promise(resolve => setTimeout(resolve, 300));
1098
+ }
1099
+ } catch (error) {
1100
+ logger.error(`发送立绘失败:`, error.message);
1101
+ // 如果下载失败,尝试直接发送URL作为备选
1102
+ try {
1103
+ await session.send(import_koishi.h.image(illustration.src));
1104
+ } catch (fallbackError) {
1105
+ logger.error(`备选方案也失败:`, fallbackError.message);
1106
+ }
1107
+ }
1108
+ }
1109
+ } else {
1110
+ const selections = input.split(/\s+/).filter(s => s.trim() !== '');
1111
+ for (const selection of selections) {
1112
+ if (!isNaN(Number(selection))) {
1113
+ const index = parseInt(selection);
1114
+ if (index > 0 && index <= illustrations.length) {
1115
+ const illustration = illustrations[index - 1];
1116
+ try {
1117
+ // 下载图片后发送缓冲区
1118
+ const imageBuffer = await downloadFile(illustration.src);
1119
+ const mimeType = getMimeTypeFromUrl(illustration.src) || 'image/jpeg';
1120
+ await session.send(import_koishi.h.image(imageBuffer, mimeType));
1121
+
1122
+ if (selections.length > 1) {
1123
+ await new Promise(resolve => setTimeout(resolve, 300));
1124
+ }
1125
+ } catch (error) {
1126
+ logger.error(`发送立绘失败:`, error.message);
1127
+ // 如果下载失败,尝试直接发送URL作为备选
1128
+ try {
1129
+ await session.send(import_koishi.h.image(illustration.src));
1130
+ } catch (fallbackError) {
1131
+ logger.error(`备选方案也失败:`, fallbackError.message);
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+ });
1139
+ }).on("error", (err) => {
1140
+ logger.error("请求失败:", err.message);
1141
+ });
1142
+ });
1143
+
1144
+ ctx.command("azurLaneAssistant.舰娘.语音 [indexOrName:text]", "查询舰娘语音").action(async ({ session }, indexOrName) => {
1145
+ if (!indexOrName) {
1146
+ await session.send(`请输入待查询立绘的【舰娘名】或【序号】或【取消】:`);
1147
+ const userInput = await session.prompt();
1148
+ if (!userInput)
1149
+ return `输入超时。`;
1150
+ if (userInput === "取消")
1151
+ return `本次查询已取消。`;
1152
+ indexOrName = userInput;
1153
+ }
1154
+ let selectedShipGirl;
1155
+ if (!isNaN(Number(indexOrName))) {
1156
+ const index = parseInt(indexOrName);
1157
+ if (index > 0 && index <= shipGirls.length) {
1158
+ selectedShipGirl = shipGirls[index - 1];
1159
+ } else {
1160
+ return `序号 ${index} 超出范围(1~${shipGirls.length})。`;
1161
+ }
1162
+ } else {
1163
+ selectedShipGirl = findBestShipGirlMatch(indexOrName, shipGirls);
1164
+
1165
+ if (!selectedShipGirl) {
1166
+ const suggestions = fuzzySearch(indexOrName, shipGirls, ['name', 'title'])
1167
+ .slice(0, 5)
1168
+ .map(girl => girl.name);
1169
+
1170
+ if (suggestions.length > 0) {
1171
+ return `未找到语音:"${indexOrName}"。\n您是否在找:${suggestions.join('、')}?`;
1172
+ }
1173
+ return `未找到语音:"${indexOrName}"。`;
1174
+ }
1175
+
1176
+ if (selectedShipGirl.name.toLowerCase() !== indexOrName.toLowerCase() &&
1177
+ selectedShipGirl.title.toLowerCase() !== indexOrName.toLowerCase()) {
1178
+ await session.send(`猜你在找:${selectedShipGirl.name}`);
1179
+ }
1180
+ }
1181
+ const url = `https://wiki.biligame.com/blhx/${selectedShipGirl.title}`;
1182
+ import_https.default.get(url, (res) => {
1183
+ let chunks = [];
1184
+ res.on("data", (chunk) => {
1185
+ chunks.push(chunk);
1186
+ });
1187
+ res.on("end", async () => {
1188
+ const buffer = Buffer.concat(chunks);
1189
+ const data = import_iconv_lite.default.decode(buffer, "utf-8");
1190
+ const $ = (0, import_cheerio.load)(data);
1191
+ const shipWords = [];
1192
+ $("div.sm-bar").each((index, element) => {
1193
+ const shipWordLine = $(element).prev(".ship_word_line").text().trim();
1194
+ const audioSrc = $(element).find(".sm-audio-src a").attr("href") || "";
1195
+ const dataKey = $(element).closest("tr").find("th").text().trim();
1196
+ const tableName = $(element).closest(".table-ShipWordsTable").attr("data-title") || "舰船台词";
1197
+ const shipWord = {
1198
+ tableName,
1199
+ dataKey,
1200
+ shipWordLine,
1201
+ audioSrc
1202
+ };
1203
+ shipWords.push(shipWord);
1204
+ });
1205
+ const batchCount = 1;
1206
+ const shipWordsLength = shipWords.length;
1207
+ const batchSize = Math.floor(shipWordsLength / batchCount);
1208
+ let serialNumber = 1;
1209
+ let tableRows = [];
1210
+ const tableStyle = `
1211
+ <style>
1212
+ body {
1213
+ margin: 0;
1214
+ zoom: 200%;
1215
+ }
1216
+ .list {
1217
+ display: flex;
1218
+ flex-direction: column;
1219
+ width: max-content;
1220
+ overflow: scroll;
1221
+ }
1222
+ table {
1223
+ border-collapse: collapse;
1224
+ border-spacing: 0;
1225
+ width: 100%;
1226
+ height: min-content;
1227
+ border: 1px solid #ddd;
1228
+ }
1229
+ th,
1230
+ td {
1231
+ text-align: left;
1232
+ padding-top: 3px;
1233
+ padding-bottom: 3px;
1234
+ padding-right: 5px;
1235
+ display: flex;
1236
+ flex-direction: column;
1237
+ width: max-content;
1238
+ }
1239
+ tr:nth-child(even) {
1240
+ background-color: #f5f5f5;
1241
+ }
1242
+ .line1 {
1243
+ display: flex;
1244
+ flex-direction: row;
1245
+ align-items: center;
1246
+ width: max-content;
1247
+ }
1248
+ .line2 {
1249
+ display: flex;
1250
+ flex-direction: row;
1251
+ width: max-content;
1252
+ }
1253
+ .id {
1254
+ color: #444444;
1255
+ font-size: x-small;
1256
+ background-color: #f8f8f8;
1257
+ padding: 1px 3px;
1258
+ border-radius: 5px;
1259
+ border-color: #dee2e6;
1260
+ border-style: solid;
1261
+ border-width: 1px;
1262
+ margin-left: 5px;
1263
+ }
1264
+ .command {
1265
+ color: #444444;
1266
+ margin-left: 5px;
1267
+ }
1268
+ .title {
1269
+ color: #6c757d;
1270
+ font-size: xx-small;
1271
+ margin-left: 10px;
1272
+ }
1273
+ </style>
1274
+ `;
1275
+ const generateTable = (rows) => {
1276
+ return `
1277
+ <html lang="zh">
1278
+ <head>
1279
+ ${tableStyle}
1280
+ <title>舰娘台词</title>
1281
+ </head>
1282
+ <body>
1283
+ <div class="list">
1284
+ <table>
1285
+ ${rows.join("\n")}
1286
+ </table>
1287
+ </div>
1288
+ </body>
1289
+ </html>
1290
+ `;
1291
+ };
1292
+ const browser = ctx.puppeteer.browser;
1293
+ const context = await browser.createBrowserContext();
1294
+ const page = await context.newPage();
1295
+ await page.setViewport({ width: 100, height: 100 });
1296
+ for (let i = 1; i <= shipWordsLength; i++) {
1297
+ const shipWord = shipWords[i - 1];
1298
+ const row = `
1299
+ <tr>
1300
+ <td>
1301
+ <div class="line1">
1302
+ <div class="id">${selectedShipGirl.name}</div>
1303
+ <div class="command">${serialNumber++}. ${shipWord.tableName}-${shipWord.dataKey}</div>
1304
+ </div>
1305
+ <div class="line2">
1306
+ <div class="title">${shipWord.shipWordLine}</div>
1307
+ </div>
1308
+ </td>
1309
+ </tr>
1310
+ `;
1311
+ tableRows.push(row);
1312
+ if (i % batchSize === 0 || i === shipWordsLength) {
1313
+ const html = generateTable(tableRows);
1314
+ await page.setContent(html, { waitUntil: "load" });
1315
+ const imgBuffer = await page.screenshot({ fullPage: true, type: imageType });
1316
+ await session.send(import_koishi.h.image(imgBuffer, `image/${imageType}`));
1317
+ tableRows = [];
1318
+ }
1319
+ }
1320
+ await page.close();
1321
+ await context.close();
1322
+ await session.send(`请输入待提取的【语音名】或【序号】:
1323
+ 支持输入多个(用空格隔开)
1324
+ 例如:1 2`);
1325
+ const userInput = await session.prompt();
1326
+ if (!userInput)
1327
+ return `输入超时。`;
1328
+ const stringArray = userInput.split(" ");
1329
+ for (const element of stringArray) {
1330
+ let selectedShipWord;
1331
+ if (!isNaN(Number(element))) {
1332
+ const index = parseInt(element);
1333
+ if (index > 0 && index <= shipWords.length) {
1334
+ selectedShipWord = shipWords[index - 1];
1335
+ } else {
1336
+ await session.send(`序号 ${index} 超出范围(1~${shipWords.length})。`);
1337
+ continue;
1338
+ }
1339
+ } else {
1340
+ selectedShipWord = shipWords.find((shipWord) => `${shipWord.tableName}-${shipWord.dataKey}` === element);
1341
+ if (!selectedShipWord) {
1342
+ await session.send(`未找到语音:${element}。`);
1343
+ continue;
1344
+ }
1345
+ }
1346
+ try {
1347
+ // 下载音频后发送缓冲区
1348
+ const audioBuffer = await downloadFile(selectedShipWord.audioSrc);
1349
+ const mimeType = getMimeTypeFromUrl(selectedShipWord.audioSrc) || 'audio/mpeg';
1350
+ await session.send(import_koishi.h.audio(audioBuffer, mimeType));
1351
+ } catch (error) {
1352
+ logger.error(`发送语音失败:`, error.message);
1353
+ // 如果下载失败,尝试直接发送URL作为备选
1354
+ try {
1355
+ await session.send(import_koishi.h.audio(selectedShipWord.audioSrc));
1356
+ } catch (fallbackError) {
1357
+ logger.error(`备选方案也失败:`, fallbackError.message);
1358
+ }
1359
+ }
1360
+ }
1361
+ });
1362
+ }).on("error", (err) => {
1363
+ logger.error("请求失败:", err.message);
1364
+ });
1365
+ });
1366
+ ctx.command("azurLaneAssistant.装备", "查看装备指令帮助").action(async ({ session }) => {
1367
+ await session.execute(`azurLaneAssistant.装备 -h`);
1368
+ });
1369
+ ctx.command("azurLaneAssistant.装备.列表 [batchCount:number]", "查看装备列表").option("initialize", "-i 初始化装备列表").action(async ({ session, options }, batchCount = defaultEquipmentsListBatchCount) => {
1370
+ if (isNaN(batchCount) || batchCount <= 0) {
1371
+ return "批次数必须是一个大于 0 的数字";
1372
+ }
1373
+ if (batchCount > 10)
1374
+ return `批次数超出范围,最大值为 10。`;
1375
+ const url = "https://wiki.biligame.com/blhx/%E8%A3%85%E5%A4%87%E5%9B%BE%E9%89%B4";
1376
+ import_https.default.get(url, (response) => {
1377
+ let chunks = [];
1378
+ response.on("data", (chunk) => {
1379
+ chunks.push(chunk);
1380
+ });
1381
+ response.on("end", async () => {
1382
+ const buffer = Buffer.concat(chunks);
1383
+ const data = import_iconv_lite.default.decode(buffer, "utf-8");
1384
+ const $ = (0, import_cheerio.load)(data);
1385
+ const cardSelectTr = $("#CardSelectTr");
1386
+ const newEquipments = [];
1387
+ cardSelectTr.children().each((index, element) => {
1388
+ const tagsArray = [];
1389
+ for (let i = 1; i <= 4; i++) {
1390
+ const param = $(element).attr(`data-param${i}`);
1391
+ if (param) {
1392
+ const paramTags = param.split(",").filter((tag) => tag.trim() !== "");
1393
+ tagsArray.push(...paramTags);
1394
+ }
1395
+ }
1396
+ const tags = tagsArray.join(" ");
1397
+ const title = $(element).find("a").attr("title") || "";
1398
+ const img = $(element).find("img").prop("outerHTML") || "";
1399
+ const name2 = $(element).find("a").text() || "";
1400
+ const equipment = {
1401
+ tags,
1402
+ title,
1403
+ img,
1404
+ name: name2
1405
+ };
1406
+ newEquipments.push(equipment);
1407
+ });
1408
+ if (newEquipments !== equipments)
1409
+ equipments = newEquipments;
1410
+ const equipmentsLength = equipments.length;
1411
+ const batchSize = Math.floor(equipmentsLength / batchCount);
1412
+ let serialNumber = 1;
1413
+ let tableRows = [];
1414
+ const tableStyle = `
1415
+ <style>
1416
+ body {
1417
+ margin: 0;
1418
+ zoom: 200%;
1419
+ }
1420
+
1421
+ .list {
1422
+ display: flex;
1423
+ flex-direction: column;
1424
+ width: max-content;
1425
+ overflow: scroll;
1426
+ }
1427
+
1428
+ table {
1429
+ border-collapse: collapse;
1430
+ border-spacing: 0;
1431
+ width: 100%;
1432
+ height: min-content;
1433
+ border: 1px solid #ddd;
1434
+ }
1435
+
1436
+ th,
1437
+ td {
1438
+ text-align: left;
1439
+ padding-top: 3px;
1440
+ padding-bottom: 3px;
1441
+ padding-right: 5px;
1442
+ display: flex;
1443
+ flex-direction: column;
1444
+ width: max-content;
1445
+ }
1446
+
1447
+ tr:nth-child(even) {
1448
+ background-color: #f5f5f5;
1449
+ }
1450
+
1451
+ .line1 {
1452
+ display: flex;
1453
+ flex-direction: row;
1454
+ align-items: center;
1455
+ width: max-content;
1456
+ }
1457
+
1458
+ .name {
1459
+ color: #444444;
1460
+ margin-left: 5px;
1461
+ }
1462
+
1463
+ .tags {
1464
+ color: #6c757d;
1465
+ font-size: xx-small;
1466
+ margin-left: 0;
1467
+ }
1468
+ </style>
1469
+ `;
1470
+ const generateTable = (rows) => {
1471
+ return `
1472
+ <html lang="zh">
1473
+ <head>
1474
+ ${tableStyle}
1475
+ <title>舰娘列表</title></head>
1476
+ <body>
1477
+ <div class="list">
1478
+ <table>
1479
+ ${rows.join("\n")}
1480
+ </table>
1481
+ </div>
1482
+ </body>
1483
+ </html>
1484
+ `;
1485
+ };
1486
+ const browser = ctx.puppeteer.browser;
1487
+ const context = await browser.createBrowserContext();
1488
+ const page = await context.newPage();
1489
+ await page.setViewport({ width: 100, height: 100 });
1490
+ for (let i = 1; i <= equipmentsLength; i++) {
1491
+ const equipment = equipments[i - 1];
1492
+ const row = `
1493
+ <tr>
1494
+ <td>
1495
+ <div class="line1">
1496
+ <div class="avatar">
1497
+ ${equipment.img}
1498
+ </div>
1499
+ <div class="name">${serialNumber++}. ${equipment.name}</div>
1500
+ </div>
1501
+ <div class="line2">
1502
+ <div class="tags">${equipment.tags}</div>
1503
+ </div>
1504
+ </td>
1505
+ </tr>
1506
+ `;
1507
+ tableRows.push(row);
1508
+ if ((i % batchSize === 0 || i === equipmentsLength) && !options.initialize) {
1509
+ const html = generateTable(tableRows);
1510
+ await page.setContent(html, { waitUntil: "load" });
1511
+ const imgBuffer = await page.screenshot({ fullPage: true, type: imageType });
1512
+ await session.send(import_koishi.h.image(imgBuffer, `image/${imageType}`));
1513
+ tableRows = [];
1514
+ }
1515
+ }
1516
+ if (options.initialize)
1517
+ logger.success(`装备列表初始化成功!`);
1518
+ await page.close();
1519
+ await context.close();
1520
+ });
1521
+ }).on("error", (error) => {
1522
+ logger.error("请求出错:", error.message);
1523
+ });
1524
+ }).execute({ options: { initialize: true } });
1525
+
1526
+ ctx.command("azurLaneAssistant.装备.查询 [indexOrName:text]", "查询装备信息").action(async ({ session }, indexOrName) => {
1527
+ if (!indexOrName) {
1528
+ await session.send(`请输入待查询的【装备名】或【序号】或【取消】:`);
1529
+ const userInput = await session.prompt();
1530
+ if (!userInput)
1531
+ return `输入超时。`;
1532
+ if (userInput === "取消")
1533
+ return `本次查询已取消。`;
1534
+ indexOrName = userInput;
1535
+ }
1536
+
1537
+ let selecteEquipment;
1538
+
1539
+ if (!isNaN(Number(indexOrName))) {
1540
+ const index = parseInt(indexOrName);
1541
+ if (index > 0 && index <= equipments.length) {
1542
+ selecteEquipment = equipments[index - 1];
1543
+ } else {
1544
+ return `序号 ${index} 超出范围(1~${equipments.length})。`;
1545
+ }
1546
+ } else {
1547
+ selecteEquipment = findBestEquipmentMatch(indexOrName, equipments);
1548
+
1549
+ if (!selecteEquipment) {
1550
+ const suggestions = fuzzySearch(indexOrName, equipments, ['name', 'title'])
1551
+ .slice(0, 5)
1552
+ .map(eq => eq.name);
1553
+
1554
+ if (suggestions.length > 0) {
1555
+ return `未找到装备:"${indexOrName}"。\n您是否在找:${suggestions.join('、')}?`;
1556
+ }
1557
+ return `未找到装备:"${indexOrName}"。`;
1558
+ }
1559
+
1560
+ if (selecteEquipment.name.toLowerCase() !== indexOrName.toLowerCase() &&
1561
+ selecteEquipment.title.toLowerCase() !== indexOrName.toLowerCase()) {
1562
+ await session.send(`猜你在找:${selecteEquipment.name}`);
1563
+ }
1564
+ }
1565
+
1566
+ const url = `https://wiki.biligame.com/blhx/${selecteEquipment.title.replace(/ /g, "_")}`;
1567
+ const page = await ctx.puppeteer.page();
1568
+ await page.setViewport({ width: 0, height: 20000, deviceScaleFactor: 1 });
1569
+ await page.goto(url, { waitUntil: "load" });
1570
+ await page.waitForSelector(".mw-parser-output");
1571
+
1572
+ await page.evaluate(() => {
1573
+ const elementsToDelete = [
1574
+ 'div.sm-bar',
1575
+ '.bread.mwiki_hide, .bread',
1576
+ '.qchar-container',
1577
+ 'div.mw-references-wrap',
1578
+ '.alert.alert-danger',
1579
+ '.wiki-nav.hidden-xs',
1580
+ 'ul.nav.nav-tabs',
1581
+ '.nav-tabs',
1582
+ 'a[href="#装备导航"]',
1583
+ 'h2 span.mw-headline#装备导航',
1584
+ '.alert.alert-info[role="alert"]'
1585
+ ];
1586
+
1587
+ const shipRelatedH2 = document.querySelector('h2 span#装备考究')?.closest('h2');
1588
+ if (shipRelatedH2) {
1589
+ let currentElement = shipRelatedH2;
1590
+ while (currentElement) {
1591
+ currentElement.style.display = 'none';
1592
+ currentElement = currentElement.nextElementSibling;
1593
+ }
1594
+ }
1595
+
1596
+ elementsToDelete.forEach(selector => {
1597
+ document.querySelectorAll(selector).forEach(el => el.remove());
1598
+ });
1599
+
1600
+ document.querySelectorAll('li[role="presentation"]').forEach(el => {
1601
+ el.classList.add("active");
1602
+ });
1603
+
1604
+ document.querySelectorAll("div.tab-pane").forEach(el => {
1605
+ el.classList.add("active");
1606
+ });
1607
+
1608
+ document.querySelectorAll('div[style*="max-height"]').forEach(el => {
1609
+ el.removeAttribute("style");
1610
+ });
1611
+
1612
+ document.querySelectorAll('[style*="max-width"]').forEach(el => {
1613
+ const style = el.getAttribute("style");
1614
+ if (style) {
1615
+ const newStyle = style.replace(/max-width\s*:\s*\d+px\s*;?/gi, "");
1616
+ el.setAttribute("style", newStyle);
1617
+ }
1618
+ });
1619
+
1620
+ document.querySelectorAll("span.heimu").forEach(el => {
1621
+ el.removeAttribute("class");
1622
+ });
1623
+
1624
+ document.querySelectorAll('a[href^="/blhx"]').forEach(link => {
1625
+ const href = link.getAttribute("href");
1626
+ if (href) {
1627
+ link.setAttribute("href", `https://wiki.biligame.com${href}`);
1628
+ }
1629
+ });
1630
+
1631
+ const shiptables = document.querySelectorAll("div.panel.panel-shiptable");
1632
+ if (shiptables.length > 0) {
1633
+ shiptables[shiptables.length - 1].remove();
1634
+ }
1635
+
1636
+ const colMd4Elements = document.querySelectorAll(".col-md-4");
1637
+ colMd4Elements.forEach(el => {
1638
+ if (el.textContent && el.textContent.includes("延伸阅读")) {
1639
+ el.remove();
1640
+ }
1641
+ });
1642
+
1643
+ const links = document.querySelectorAll('a');
1644
+ links.forEach(link => {
1645
+ if (link.textContent && link.textContent.includes('文件:')) {
1646
+ link.remove();
1647
+ }
1648
+ });
1649
+ });
1650
+
1651
+ const element = await page.$(".mw-parser-output");
1652
+ const imageBuffer = await element.screenshot({ type: imageType });
1653
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
1654
+ await page.close();
1655
+ });
1656
+ ctx.command("azurLaneAssistant.井号碧蓝榜", "查看井号碧蓝榜指令帮助").action(async ({ session }) => {
1657
+ await session.execute(`azurLaneAssistant.井号碧蓝榜 -h`);
1658
+ });
1659
+ ctx.command("azurLaneAssistant.井号碧蓝榜.列表 [batchCount:number]", "查看井号碧蓝榜列表").option("initialize", "-i 初始化井号碧蓝榜列表").action(async ({ session, options }, batchCount = defaultRanksListBatchCount) => {
1660
+ if (isNaN(batchCount) || batchCount <= 0) {
1661
+ return "批次数必须是一个大于 0 的数字!";
1662
+ }
1663
+ if (batchCount > 5)
1664
+ return `批次数超出范围,最大值为 5。`;
1665
+ const url = "https://wiki.biligame.com/blhx/%E4%BA%95%E5%8F%B7%E7%A2%A7%E8%93%9D%E6%A6%9C%E5%90%88%E9%9B%86";
1666
+ import_https.default.get(url, (response) => {
1667
+ let chunks = [];
1668
+ response.on("data", (chunk) => {
1669
+ chunks.push(chunk);
1670
+ });
1671
+ response.on("end", async () => {
1672
+ const buffer = Buffer.concat(chunks);
1673
+ const data = import_iconv_lite.default.decode(buffer, "utf-8");
1674
+ const $ = (0, import_cheerio.load)(data);
1675
+ const newRanks = [];
1676
+ $("div.mw-parser-output img").each((index, element) => {
1677
+ const alt = $(element).attr("alt") || "";
1678
+ const src = $(element).attr("src") || "";
1679
+ const altWithoutExtension = alt.split(".").slice(0, -1).join(".");
1680
+ newRanks.push({ altWithoutExtension, src });
1681
+ });
1682
+ if (newRanks !== ranks)
1683
+ ranks = newRanks;
1684
+ const ranksLength = ranks.length;
1685
+ const batchSize = Math.floor(ranksLength / batchCount);
1686
+ let serialNumber = 1;
1687
+ let tableRows = [];
1688
+ const tableStyle = `
1689
+ <style>
1690
+ body {
1691
+ margin: 0;
1692
+ zoom: 200%;
1693
+ }
1694
+ .list {
1695
+ display: flex;
1696
+ flex-direction: column;
1697
+ width: max-content;
1698
+ overflow: scroll;
1699
+ }
1700
+ table {
1701
+ border-collapse: collapse;
1702
+ border-spacing: 0;
1703
+ width: 100%;
1704
+ height: min-content;
1705
+ border: 1px solid #ddd;
1706
+ }
1707
+ th,
1708
+ td {
1709
+ text-align: left;
1710
+ padding-top: 3px;
1711
+ padding-bottom: 3px;
1712
+ padding-right: 5px;
1713
+ display: flex;
1714
+ flex-direction: column;
1715
+ width: max-content;
1716
+ }
1717
+ tr:nth-child(even) {
1718
+ background-color: #f5f5f5;
1719
+ }
1720
+ .line1 {
1721
+ display: flex;
1722
+ flex-direction: row;
1723
+ align-items: center;
1724
+ width: max-content;
1725
+ }
1726
+ .line2 {
1727
+ display: flex;
1728
+ flex-direction: row;
1729
+ width: max-content;
1730
+ }
1731
+ .id {
1732
+ color: #444444;
1733
+ font-size: x-small;
1734
+ background-color: #f8f8f8;
1735
+ padding: 1px 3px;
1736
+ border-radius: 5px;
1737
+ border-color: #dee2e6;
1738
+ border-style: solid;
1739
+ border-width: 1px;
1740
+ margin-left: 5px;
1741
+ }
1742
+ .command {
1743
+ color: #444444;
1744
+ margin-left: 5px;
1745
+ }
1746
+ .title {
1747
+ color: #6c757d;
1748
+ font-size: xx-small;
1749
+ margin-left: 10px;
1750
+ }
1751
+ </style>
1752
+ `;
1753
+ const generateTable = (rows) => {
1754
+ return `
1755
+ <html lang="zh">
1756
+ <head>
1757
+ ${tableStyle}
1758
+ <title>井号碧蓝榜列表</title>
1759
+ </head>
1760
+ <body>
1761
+ <div class="list">
1762
+ <table>
1763
+ ${rows.join("\n")}
1764
+ </table>
1765
+ </div>
1766
+ </body>
1767
+ </html>
1768
+ `;
1769
+ };
1770
+ const browser = ctx.puppeteer.browser;
1771
+ const context = await browser.createBrowserContext();
1772
+ const page = await context.newPage();
1773
+ await page.setViewport({ width: 100, height: 100 });
1774
+ for (let i = 1; i <= ranksLength; i++) {
1775
+ const rank = ranks[i - 1];
1776
+ const row = `
1777
+ <tr>
1778
+ <td>
1779
+ <div class="line1">
1780
+ <div class="id">By 井号5467</div>
1781
+ <div class="command">${serialNumber++}. ${rank.altWithoutExtension}</div>
1782
+ </div>
1783
+ <div class="line2">
1784
+ <div class="title"></div>
1785
+ </div>
1786
+ </td>
1787
+ </tr>
1788
+ `;
1789
+ tableRows.push(row);
1790
+ if ((i % batchSize === 0 || i === ranksLength) && !options.initialize) {
1791
+ const html = generateTable(tableRows);
1792
+ await page.setContent(html, { waitUntil: "load" });
1793
+ const imgBuffer = await page.screenshot({ fullPage: true, type: imageType });
1794
+ await session.send(import_koishi.h.image(imgBuffer, `image/${imageType}`));
1795
+ tableRows = [];
1796
+ }
1797
+ }
1798
+ if (options.initialize)
1799
+ logger.success(`井号碧蓝榜列表初始化成功!`);
1800
+ await page.close();
1801
+ await context.close();
1802
+ });
1803
+ }).on("error", (error) => {
1804
+ logger.error("请求出错:", error.message);
1805
+ });
1806
+ }).execute({ options: { initialize: true } });
1807
+ ctx.command("azurLaneAssistant.井号碧蓝榜.查询 [indexOrName:text]", "查询井号碧蓝榜").action(async ({ session }, indexOrName) => {
1808
+ if (!indexOrName) {
1809
+ await session.send(`请输入待查询的【榜单名】或【序号】或【取消】:`);
1810
+ const userInput = await session.prompt();
1811
+ if (!userInput)
1812
+ return `输入超时。`;
1813
+ if (userInput === "取消")
1814
+ return `本次查询已取消。`;
1815
+ indexOrName = userInput;
1816
+ }
1817
+ let selecteRank;
1818
+ if (!isNaN(Number(indexOrName))) {
1819
+ const index = parseInt(indexOrName);
1820
+ if (index > 0 && index <= ranks.length) {
1821
+ selecteRank = ranks[index - 1];
1822
+ } else {
1823
+ return `序号 ${index} 超出范围(1~${ranks.length})。`;
1824
+ }
1825
+ } else {
1826
+ selecteRank = ranks.find((rank) => rank.altWithoutExtension === indexOrName);
1827
+ if (!selecteRank) {
1828
+ return `未找到榜单:${indexOrName}。`;
1829
+ }
1830
+ }
1831
+
1832
+ try {
1833
+ // 下载图片后发送缓冲区
1834
+ const imageBuffer = await downloadFile(selecteRank.src);
1835
+ const mimeType = getMimeTypeFromUrl(selecteRank.src) || 'image/jpeg';
1836
+ await session.send(import_koishi.h.image(imageBuffer, mimeType));
1837
+ } catch (error) {
1838
+ logger.error(`发送榜单图片失败:`, error.message);
1839
+ // 如果下载失败,尝试直接发送URL作为备选
1840
+ try {
1841
+ await session.send(import_koishi.h.image(selecteRank.src));
1842
+ } catch (fallbackError) {
1843
+ logger.error(`备选方案也失败:`, fallbackError.message);
1844
+ }
1845
+ }
1846
+ });
1847
+ ctx.command("azurLaneAssistant.关卡", "查看关卡指令帮助").action(async ({ session }) => {
1848
+ await session.execute(`azurLaneAssistant.关卡 -h`);
1849
+ });
1850
+ ctx.command("azurLaneAssistant.关卡.总览.列表 [batchCount:number]", "查看关卡总览列表").option("initialize", "-i 初始化关卡列表").action(async ({ session, options }, batchCount = defaultStagesListBatchCount) => {
1851
+ if (isNaN(batchCount) || batchCount <= 0) {
1852
+ return "批次数必须是一个大于 0 的数字!";
1853
+ }
1854
+ if (batchCount > 10)
1855
+ return `批次数超出范围,最大值为 10。`;
1856
+ const url = "https://wiki.biligame.com/blhx/%E7%AB%A0%E8%8A%82%E5%85%B3%E5%8D%A1";
1857
+ import_https.default.get(url, (response) => {
1858
+ let chunks = [];
1859
+ response.on("data", (chunk) => {
1860
+ chunks.push(chunk);
1861
+ });
1862
+ response.on("end", async () => {
1863
+ const buffer = Buffer.concat(chunks);
1864
+ const data = import_iconv_lite.default.decode(buffer, "utf-8");
1865
+ const $ = (0, import_cheerio.load)(data);
1866
+ let newStages = [];
1867
+ $('a[href^="/blhx"]').each((index, element) => {
1868
+ const href = $(element).attr("href");
1869
+ if (href) {
1870
+ $(element).attr("href", `https://wiki.biligame.com${href}`);
1871
+ }
1872
+ });
1873
+ $("div.mw-parser-output table").each((index, table) => {
1874
+ const title = $(table).find("a[title]").attr("title");
1875
+ const name2 = title.replace(/#.*/, "");
1876
+ const href = $(table).find("a[title]").attr("href");
1877
+ const img = $(table).find("img").prop("outerHTML");
1878
+ const alt = $(table).find("img").attr("alt") || "";
1879
+ const altName = alt.substring(0, alt.lastIndexOf(".")) || alt;
1880
+ if (title && href && img) {
1881
+ newStages.push({ title, name: name2, href, img, altName });
1882
+ }
1883
+ });
1884
+ if (newStages !== stages)
1885
+ stages = newStages;
1886
+ const stagesLength = stages.length;
1887
+ const batchSize = Math.floor(stagesLength / batchCount);
1888
+ let serialNumber = 1;
1889
+ let tableRows = [];
1890
+ const tableStyle = `
1891
+ <style>
1892
+ body {
1893
+ margin: 0;
1894
+ zoom: 200%;
1895
+ }
1896
+
1897
+ .list {
1898
+ display: flex;
1899
+ flex-direction: column;
1900
+ width: max-content;
1901
+ overflow: scroll;
1902
+ }
1903
+
1904
+ table {
1905
+ border-collapse: collapse;
1906
+ border-spacing: 0;
1907
+ width: 100%;
1908
+ height: min-content;
1909
+ border: 1px solid #ddd;
1910
+ }
1911
+
1912
+ th,
1913
+ td {
1914
+ text-align: left;
1915
+ padding-top: 3px;
1916
+ padding-bottom: 3px;
1917
+ padding-right: 5px;
1918
+ display: flex;
1919
+ flex-direction: column;
1920
+ width: max-content;
1921
+ }
1922
+
1923
+ tr:nth-child(even) {
1924
+ background-color: #f5f5f5;
1925
+ }
1926
+
1927
+ .line1 {
1928
+ display: flex;
1929
+ flex-direction: row;
1930
+ align-items: center;
1931
+ width: max-content;
1932
+ }
1933
+
1934
+ .name {
1935
+ color: #444444;
1936
+ margin-left: 5px;
1937
+ }
1938
+
1939
+ .tags {
1940
+ color: #6c757d;
1941
+ font-size: xx-small;
1942
+ margin-left: 0;
1943
+ }
1944
+ </style>
1945
+ `;
1946
+ const generateTable = (rows) => {
1947
+ return `
1948
+ <html lang="zh">
1949
+ <head>
1950
+ ${tableStyle}
1951
+ <title>关卡列表</title></head>
1952
+ <body>
1953
+ <div class="list">
1954
+ <table>
1955
+ ${rows.join("\n")}
1956
+ </table>
1957
+ </div>
1958
+ </body>
1959
+ </html>
1960
+ `;
1961
+ };
1962
+ const browser = ctx.puppeteer.browser;
1963
+ const context = await browser.createBrowserContext();
1964
+ const page = await context.newPage();
1965
+ await page.setViewport({ width: 100, height: 100 });
1966
+ for (let i = 1; i <= stagesLength; i++) {
1967
+ const stage = stages[i - 1];
1968
+ const row = `
1969
+ <tr>
1970
+ <td>
1971
+ <div class="line1">
1972
+ <div class="avatar">
1973
+ ${stage.img}
1974
+ </div>
1975
+ <div class="name">${serialNumber++}. ${stage.altName}</div>
1976
+ </div>
1977
+ <div class="line2">
1978
+ <div class="tags">${stage.name}</div>
1979
+ </div>
1980
+ </td>
1981
+ </tr>
1982
+ `;
1983
+ tableRows.push(row);
1984
+ if ((i % batchSize === 0 || i === stagesLength) && !options.initialize) {
1985
+ const html = generateTable(tableRows);
1986
+ await page.setContent(html, { waitUntil: "load" });
1987
+ const imgBuffer = await page.screenshot({ fullPage: true, type: imageType });
1988
+ await session.send(import_koishi.h.image(imgBuffer, `image/${imageType}`));
1989
+ tableRows = [];
1990
+ }
1991
+ }
1992
+ if (options.initialize)
1993
+ logger.success(`关卡总览列表初始化成功!`);
1994
+ await page.close();
1995
+ await context.close();
1996
+ });
1997
+ }).on("error", (error) => {
1998
+ logger.error("请求出错:", error.message);
1999
+ });
2000
+ }).execute({ options: { initialize: true } });
2001
+ ctx.command("azurLaneAssistant.关卡.总览.查询 [indexOrName:text]", "查询关卡总览信息").action(async ({ session }, indexOrName) => {
2002
+ if (!indexOrName) {
2003
+ await session.send(`请输入待查询的【关卡名】或【序号】或【取消】:`);
2004
+ const userInput = await session.prompt();
2005
+ if (!userInput)
2006
+ return `输入超时。`;
2007
+ if (userInput === "取消")
2008
+ return `本次查询已取消。`;
2009
+ indexOrName = userInput;
2010
+ }
2011
+ let selectedStage;
2012
+ if (!isNaN(Number(indexOrName))) {
2013
+ const index = parseInt(indexOrName);
2014
+ if (index > 0 && index <= stages.length) {
2015
+ selectedStage = stages[index - 1];
2016
+ } else {
2017
+ return `序号 ${index} 超出范围(1~${stages.length})。`;
2018
+ }
2019
+ } else {
2020
+ selectedStage = stages.find((stage) => stage.altName === indexOrName);
2021
+ if (!selectedStage)
2022
+ selectedStage = stages.find((stage) => stage.title === indexOrName);
2023
+ if (!selectedStage)
2024
+ selectedStage = stages.find((stage) => stage.name === indexOrName);
2025
+ if (!selectedStage) {
2026
+ return `未找到关卡:${indexOrName}。`;
2027
+ }
2028
+ }
2029
+ const url = `https://wiki.biligame.com/blhx/${selectedStage.title}`;
2030
+ const page = await ctx.puppeteer.page();
2031
+ await page.setViewport({ width: 0, height: 2500, deviceScaleFactor: 1 });
2032
+ await page.goto(url, { waitUntil: "networkidle0" });
2033
+ await page.waitForSelector(".mw-parser-output");
2034
+ await page.evaluate(() => {
2035
+ const lis = document.querySelectorAll(".tabbernav > li");
2036
+ lis.forEach((li) => {
2037
+ if (!li.classList.contains("tabberactive")) {
2038
+ li.classList.add("tabberactive");
2039
+ }
2040
+ });
2041
+ const tabberTabs = document.querySelectorAll(".tabbertab");
2042
+ tabberTabs.forEach((tabberTab) => {
2043
+ const style = tabberTab.getAttribute("style");
2044
+ if (style && style.includes("display: none;")) {
2045
+ tabberTab.setAttribute("style", style.replace("display: none;", ""));
2046
+ }
2047
+ });
2048
+ const elements = document.querySelectorAll('div[style*="max-height"]');
2049
+ elements.forEach((element2) => {
2050
+ element2.removeAttribute("style");
2051
+ });
2052
+ const removeElements = () => {
2053
+ const elementsToDelete = document.querySelectorAll('.wiki-nav.hidden-xs.wiki-nav-celling, .bread.mwiki_hide, .bread, .qchar-container, div.sm-bar, span.badge.pull-right, div.mw-references-wrap, div.panel.panel-shiptable, .alert.alert-danger, .wiki-nav.hidden-xs, .alert.alert-info[role="alert"], dl dd, div.dbxx, ul>li>dl>dt, table[style="float:left;margin:5px;text-align:center;"]');
2054
+ elementsToDelete.forEach((element2) => element2.remove());
2055
+ };
2056
+ removeElements();
2057
+ const modifyCollapsedElements = () => {
2058
+ const collapsedElements = document.querySelectorAll(".panel-collapse.collapse:not(.in)");
2059
+ collapsedElements.forEach((element2) => {
2060
+ element2.classList.add("in");
2061
+ element2.setAttribute("aria-expanded", "true");
2062
+ });
2063
+ };
2064
+ modifyCollapsedElements();
2065
+ const modifyTabElements = () => {
2066
+ const elements2 = document.querySelectorAll('div[role="tabpanel"].tab-pane, li[role="presentation"]');
2067
+ elements2.forEach((element2) => {
2068
+ element2.classList.add("active");
2069
+ });
2070
+ };
2071
+ modifyTabElements();
2072
+ const otherShipSection = document.querySelector('a[href="#章节关卡"]');
2073
+ if (otherShipSection)
2074
+ otherShipSection.parentNode.remove();
2075
+ const headersToRemove = [
2076
+ "h2 span.mw-headline#章节关卡",
2077
+ "h3 span.mw-headline#常规关卡",
2078
+ "h3 span.mw-headline#大型活动关卡E\\.X\\.",
2079
+ "h3 span.mw-headline#小型活动关卡S\\.P\\.",
2080
+ "h3 span.mw-headline#特殊活动关卡T",
2081
+ "h4 span.mw-headline#常规关卡列表",
2082
+ "h4 span.mw-headline#困难难度常规关卡"
2083
+ ];
2084
+ headersToRemove.forEach((selector) => {
2085
+ const header = document.querySelector(selector);
2086
+ if (header)
2087
+ header.parentNode.remove();
2088
+ });
2089
+ const removeHeimuClass = () => {
2090
+ const heimuElements = document.querySelectorAll("span.heimu");
2091
+ heimuElements.forEach((el) => el.removeAttribute("class"));
2092
+ };
2093
+ removeHeimuClass();
2094
+ });
2095
+ const element = await page.$(".mw-parser-output");
2096
+ const imageBuffer = await element.screenshot({ type: imageType });
2097
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
2098
+ await page.close();
2099
+ });
2100
+ ctx.command("azurLaneAssistant.关卡.主线.列表 [batchCount:number]", "查看主线关卡列表").option("initialize", "-i 初始化关卡列表").action(async ({ session, options }, batchCount = defaultStagesListBatchCount) => {
2101
+ if (isNaN(batchCount) || batchCount <= 0) {
2102
+ return "批次数必须是一个大于 0 的数字!";
2103
+ }
2104
+ if (batchCount > 10)
2105
+ return `批次数超出范围,最大值为 10。`;
2106
+ const manlineStagesLength = manlineStages.length;
2107
+ const batchSize = Math.floor(manlineStagesLength / batchCount);
2108
+ let serialNumber = 1;
2109
+ let tableRows = [];
2110
+ const tableStyle = `
2111
+ <style>
2112
+ body {
2113
+ margin: 0;
2114
+ zoom: 200%;
2115
+ }
2116
+
2117
+ .list {
2118
+ display: flex;
2119
+ flex-direction: column;
2120
+ width: max-content;
2121
+ overflow: scroll;
2122
+ }
2123
+
2124
+ table {
2125
+ border-collapse: collapse;
2126
+ border-spacing: 0;
2127
+ width: 100%;
2128
+ height: min-content;
2129
+ border: 1px solid #ddd;
2130
+ }
2131
+
2132
+ th,
2133
+ td {
2134
+ text-align: left;
2135
+ padding-top: 3px;
2136
+ padding-bottom: 3px;
2137
+ padding-right: 5px;
2138
+ display: flex;
2139
+ flex-direction: column;
2140
+ width: max-content;
2141
+ }
2142
+
2143
+ tr:nth-child(even) {
2144
+ background-color: #f5f5f5;
2145
+ }
2146
+
2147
+ .line1 {
2148
+ display: flex;
2149
+ flex-direction: row;
2150
+ align-items: center;
2151
+ width: max-content;
2152
+ }
2153
+
2154
+ .id {
2155
+ color: #444444;
2156
+ font-size: x-small;
2157
+ background-color: #f8f8f8;
2158
+ padding: 1px 3px;
2159
+ border-radius: 5px;
2160
+ border-color: #dee2e6;
2161
+ border-style: solid;
2162
+ border-width: 1px;
2163
+ margin-left: 5px;
2164
+ }
2165
+
2166
+ .name {
2167
+ color: #444444;
2168
+ margin-left: 5px;
2169
+ }
2170
+
2171
+ .tags {
2172
+ color: #6c757d;
2173
+ font-size: xx-small;
2174
+ margin-left: 0;
2175
+ }
2176
+ </style>
2177
+ `;
2178
+ const generateTable = (rows) => {
2179
+ return `
2180
+ <html lang="zh">
2181
+ <head>
2182
+ ${tableStyle}
2183
+ <title>关卡列表</title></head>
2184
+ <body>
2185
+ <div class="list">
2186
+ <table>
2187
+ ${rows.join("\n")}
2188
+ </table>
2189
+ </div>
2190
+ </body>
2191
+ </html>
2192
+ `;
2193
+ };
2194
+ const browser = ctx.puppeteer.browser;
2195
+ const context = await browser.createBrowserContext();
2196
+ const page = await context.newPage();
2197
+ await page.setViewport({ width: 100, height: 100 });
2198
+ for (let i = 1; i <= manlineStagesLength; i++) {
2199
+ const manlineStage = manlineStages[i - 1];
2200
+ const row = `
2201
+ <tr>
2202
+ <td>
2203
+ <div class="line1">
2204
+ <div class="id">${manlineStage.stageNumber}</div>
2205
+ <div class="name">${serialNumber++}. ${manlineStage.stageName}</div>
2206
+ </div>
2207
+ <div class="line2">
2208
+ <div class="tags"></div>
2209
+ </div>
2210
+ </td>
2211
+ </tr>
2212
+ `;
2213
+ tableRows.push(row);
2214
+ if ((i % batchSize === 0 || i === manlineStagesLength) && !options.initialize) {
2215
+ const html = generateTable(tableRows);
2216
+ await page.setContent(html, { waitUntil: "load" });
2217
+ const imgBuffer = await page.screenshot({ fullPage: true, type: imageType });
2218
+ await session.send(import_koishi.h.image(imgBuffer, `image/${imageType}`));
2219
+ tableRows = [];
2220
+ }
2221
+ }
2222
+ if (options.initialize)
2223
+ logger.success(`主线关卡列表初始化成功!`);
2224
+ await page.close();
2225
+ await context.close();
2226
+ }).execute({ options: { initialize: true } });
2227
+ ctx.command("azurLaneAssistant.关卡.主线.查询 [indexOrName:text]", "查询主线关卡信息").action(async ({ session }, indexOrName) => {
2228
+ if (!indexOrName) {
2229
+ await session.send(`请输入待查询的【关卡名】
2230
+ 或【关卡号(例如1-1、1-1Hard)】
2231
+ 或【取消】:`);
2232
+ const userInput = await session.prompt();
2233
+ if (!userInput)
2234
+ return `输入超时。`;
2235
+ if (userInput === "取消")
2236
+ return `本次查询已取消。`;
2237
+ indexOrName = userInput;
2238
+ }
2239
+
2240
+ let selectedMainlineStage;
2241
+ if (!isNaN(Number(indexOrName))) {
2242
+ const index = parseInt(indexOrName);
2243
+ if (index > 0 && index <= manlineStages.length) {
2244
+ selectedMainlineStage = manlineStages[index - 1];
2245
+ } else {
2246
+ return `序号 ${index} 超出范围(1~${manlineStages.length})。`;
2247
+ }
2248
+ } else {
2249
+ selectedMainlineStage = manlineStages.find((mainlineStage) => mainlineStage.stageNumber === indexOrName);
2250
+ if (!selectedMainlineStage)
2251
+ selectedMainlineStage = manlineStages.find((mainlineStage) => mainlineStage.stageName === indexOrName);
2252
+ if (!selectedMainlineStage) {
2253
+ return `未找到关卡:${indexOrName}。`;
2254
+ }
2255
+ }
2256
+
2257
+ const url = `https://wiki.biligame.com/blhx/${selectedMainlineStage.stageNumber}`;
2258
+ const page = await ctx.puppeteer.page();
2259
+ await page.setViewport({ width: 0, height: 2500, deviceScaleFactor: 1 });
2260
+ await page.goto(url, { waitUntil: "load" });
2261
+ await page.waitForSelector(".mw-parser-output");
2262
+
2263
+ await page.evaluate(() => {
2264
+ // 移除不需要的元素
2265
+ const elementsToDelete = [
2266
+ 'div.sm-bar',
2267
+ 'span.badge.pull-right',
2268
+ 'div.mw-references-wrap',
2269
+ 'div.bread.mwiki_hide, div.bread',
2270
+ '.wiki-nav.hidden-xs.wiki-nav-celling',
2271
+ '.qchar-container',
2272
+ '.alert.alert-danger',
2273
+ '.wiki-nav.hidden-xs',
2274
+ 'div.panel.panel-shiptable:last-of-type'
2275
+ ];
2276
+
2277
+ elementsToDelete.forEach(selector => {
2278
+ document.querySelectorAll(selector).forEach(el => el.remove());
2279
+ });
2280
+
2281
+ // 展开所有折叠的面板
2282
+ const collapsedElements = document.querySelectorAll('.panel-collapse.collapse:not(.in)');
2283
+ collapsedElements.forEach(el => {
2284
+ el.classList.add('in');
2285
+ el.setAttribute('aria-expanded', 'true');
2286
+ });
2287
+
2288
+ // 确保标签页是激活状态
2289
+ document.querySelectorAll('li[role="presentation"]').forEach(el => {
2290
+ el.classList.add('active');
2291
+ });
2292
+
2293
+ document.querySelectorAll('div.tab-pane').forEach(el => {
2294
+ el.classList.add('active');
2295
+ });
2296
+
2297
+ // 移除内联样式
2298
+ document.querySelectorAll('div[style*="max-height"]').forEach(el => {
2299
+ el.removeAttribute('style');
2300
+ });
2301
+
2302
+ // 移除黑幕类
2303
+ document.querySelectorAll('span.heimu').forEach(el => {
2304
+ el.removeAttribute('class');
2305
+ });
2306
+
2307
+ // 修正链接
2308
+ document.querySelectorAll('a[href^="/blhx"]').forEach(link => {
2309
+ const href = link.getAttribute('href');
2310
+ if (href) {
2311
+ link.setAttribute('href', `https://wiki.biligame.com${href}`);
2312
+ }
2313
+ });
2314
+ });
2315
+
2316
+ const element = await page.$(".mw-parser-output");
2317
+ const imageBuffer = await element.screenshot({ type: imageType });
2318
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
2319
+ await page.close();
2320
+ });ctx.command("azurLaneAssistant.关卡.主线.查询 [indexOrName:text]", "查询主线关卡信息").action(async ({ session }, indexOrName) => {
2321
+ if (!indexOrName) {
2322
+ await session.send(`请输入待查询的【关卡名】
2323
+ 或【关卡号(例如1-1、1-1Hard)】
2324
+ 或【取消】:`);
2325
+ const userInput = await session.prompt();
2326
+ if (!userInput)
2327
+ return `输入超时。`;
2328
+ if (userInput === "取消")
2329
+ return `本次查询已取消。`;
2330
+ indexOrName = userInput;
2331
+ }
2332
+
2333
+ let selectedMainlineStage;
2334
+ if (!isNaN(Number(indexOrName))) {
2335
+ const index = parseInt(indexOrName);
2336
+ if (index > 0 && index <= manlineStages.length) {
2337
+ selectedMainlineStage = manlineStages[index - 1];
2338
+ } else {
2339
+ return `序号 ${index} 超出范围(1~${manlineStages.length})。`;
2340
+ }
2341
+ } else {
2342
+ selectedMainlineStage = manlineStages.find((mainlineStage) => mainlineStage.stageNumber === indexOrName);
2343
+ if (!selectedMainlineStage)
2344
+ selectedMainlineStage = manlineStages.find((mainlineStage) => mainlineStage.stageName === indexOrName);
2345
+ if (!selectedMainlineStage) {
2346
+ return `未找到关卡:${indexOrName}。`;
2347
+ }
2348
+ }
2349
+
2350
+ const url = `https://wiki.biligame.com/blhx/${selectedMainlineStage.stageNumber}`;
2351
+ const page = await ctx.puppeteer.page();
2352
+ await page.setViewport({ width: 0, height: 2500, deviceScaleFactor: 1 });
2353
+ await page.goto(url, { waitUntil: "load" });
2354
+ await page.waitForSelector(".mw-parser-output");
2355
+
2356
+ await page.evaluate(() => {
2357
+ // 移除不需要的元素
2358
+ const elementsToDelete = [
2359
+ 'div.sm-bar',
2360
+ 'span.badge.pull-right',
2361
+ 'div.mw-references-wrap',
2362
+ 'div.bread.mwiki_hide, div.bread',
2363
+ '.wiki-nav.hidden-xs.wiki-nav-celling',
2364
+ '.qchar-container',
2365
+ '.alert.alert-danger',
2366
+ '.wiki-nav.hidden-xs',
2367
+ 'div.panel.panel-shiptable:last-of-type'
2368
+ ];
2369
+
2370
+ elementsToDelete.forEach(selector => {
2371
+ document.querySelectorAll(selector).forEach(el => el.remove());
2372
+ });
2373
+
2374
+ // 展开所有折叠的面板
2375
+ const collapsedElements = document.querySelectorAll('.panel-collapse.collapse:not(.in)');
2376
+ collapsedElements.forEach(el => {
2377
+ el.classList.add('in');
2378
+ el.setAttribute('aria-expanded', 'true');
2379
+ });
2380
+
2381
+ // 确保标签页是激活状态
2382
+ document.querySelectorAll('li[role="presentation"]').forEach(el => {
2383
+ el.classList.add('active');
2384
+ });
2385
+
2386
+ document.querySelectorAll('div.tab-pane').forEach(el => {
2387
+ el.classList.add('active');
2388
+ });
2389
+
2390
+ // 移除内联样式
2391
+ document.querySelectorAll('div[style*="max-height"]').forEach(el => {
2392
+ el.removeAttribute('style');
2393
+ });
2394
+
2395
+ // 移除黑幕类
2396
+ document.querySelectorAll('span.heimu').forEach(el => {
2397
+ el.removeAttribute('class');
2398
+ });
2399
+
2400
+ // 修正链接
2401
+ document.querySelectorAll('a[href^="/blhx"]').forEach(link => {
2402
+ const href = link.getAttribute('href');
2403
+ if (href) {
2404
+ link.setAttribute('href', `https://wiki.biligame.com${href}`);
2405
+ }
2406
+ });
2407
+ });
2408
+
2409
+ const element = await page.$(".mw-parser-output");
2410
+ const imageBuffer = await element.screenshot({ type: imageType });
2411
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
2412
+ await page.close();
2413
+ });
2414
+ ctx.command("azurLaneAssistant.攻略", "查看攻略指令帮助").action(async ({ session }) => {
2415
+ await session.execute(`azurLaneAssistant.攻略 -h`);
2416
+ });
2417
+ ctx.command("azurLaneAssistant.攻略.月度BOSS解析", "查看月度BOSS解析攻略").action(async ({ session }) => {
2418
+ const url = `https://wiki.biligame.com/blhx/%E5%A4%A7%E5%9E%8B%E4%BD%9C%E6%88%98%E6%9C%88%E5%BA%A6BOSS%E8%A7%A3%E6%9E%90`;
2419
+ const page = await ctx.puppeteer.page();
2420
+ await page.setViewport({ width: 3000, height: 17000, deviceScaleFactor: 1 });
2421
+ await page.goto(url, { waitUntil: "load" });
2422
+ await page.waitForSelector(".mw-parser-output");
2423
+ await page.evaluate(() => {
2424
+ const modifyCollapsedElements = () => {
2425
+ const collapsedElements = document.querySelectorAll(".panel-collapse.collapse:not(.in)");
2426
+ collapsedElements.forEach((element2) => {
2427
+ element2.classList.add("in");
2428
+ element2.setAttribute("aria-expanded", "true");
2429
+ });
2430
+ };
2431
+ modifyCollapsedElements();
2432
+ const removeElements = () => {
2433
+ const elementsToDelete = document.querySelectorAll(".wiki-nav.hidden-xs.wiki-nav-celling, .bread.mwiki_hide, .bread, .qchar-container, div.sm-bar, span.badge.pull-right, div.mw-references-wrap, div.panel.panel-shiptable, .alert.alert-danger, .wiki-nav.hidden-xs");
2434
+ elementsToDelete.forEach((element2) => element2.remove());
2435
+ };
2436
+ removeElements();
2437
+ });
2438
+ const element = await page.$(".mw-parser-output");
2439
+ const imageBuffer = await element.screenshot({ type: imageType });
2440
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
2441
+ await page.close();
2442
+ });
2443
+ ctx.command("azurLaneAssistant.攻略.余烬BOSS攻略要点", "查看余烬BOSS攻略要点").action(async ({ session }) => {
2444
+ const url = `https://wiki.biligame.com/blhx/METAboss%E6%94%BB%E7%95%A5%E8%A6%81%E7%82%B9`;
2445
+ const page = await ctx.puppeteer.page();
2446
+ await page.setViewport({ width: 3000, height: 17000, deviceScaleFactor: 1 });
2447
+ await page.goto(url, { waitUntil: "load" });
2448
+ await page.waitForSelector(".mw-parser-output");
2449
+ await page.evaluate(() => {
2450
+ const collapsedElements = document.querySelectorAll(".panel-collapse.collapse:not(.in)");
2451
+ collapsedElements.forEach((element2) => {
2452
+ element2.classList.add("in");
2453
+ element2.setAttribute("aria-expanded", "true");
2454
+ });
2455
+ const modifyTabElements = () => {
2456
+ const elements = document.querySelectorAll('div[role="tabpanel"].tab-pane, li[role="presentation"]');
2457
+ elements.forEach((element2) => {
2458
+ element2.classList.add("active");
2459
+ });
2460
+ };
2461
+ modifyTabElements();
2462
+ const removeElements = () => {
2463
+ const elementsToDelete = document.querySelectorAll(".wiki-nav.hidden-xs.wiki-nav-celling, .bread.mwiki_hide, .bread, .qchar-container, div.sm-bar, span.badge.pull-right, div.mw-references-wrap, div.panel.panel-shiptable, .alert.alert-danger, .wiki-nav.hidden-xs");
2464
+ elementsToDelete.forEach((element2) => element2.remove());
2465
+ };
2466
+ removeElements();
2467
+ });
2468
+ const element = await page.$(".mw-parser-output");
2469
+ const imageBuffer = await element.screenshot({ type: imageType });
2470
+ await session.send(import_koishi.h.image(imageBuffer, `image/${imageType}`));
2471
+ await page.close();
2472
+ });
2473
+ }
2474
+ __name(apply, "apply");
2475
+ function removeFileExtension(filename) {
2476
+ const lastDotIndex = filename.lastIndexOf(".");
2477
+ if (lastDotIndex === -1) {
2478
+ return filename;
2479
+ } else {
2480
+ return filename.substring(0, lastDotIndex);
2481
+ }
2482
+ }
2483
+ __name(removeFileExtension, "removeFileExtension");
2484
+ 0 && (module.exports = {
2485
+ Config,
2486
+ apply,
2487
+ inject,
2488
+ name,
2489
+ usage
2490
+ });