koishi-plugin-rocom 1.0.18 → 1.0.19
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/announcement-service.d.ts +21 -0
- package/lib/announcement-service.js +63 -0
- package/lib/async-control.d.ts +3 -0
- package/lib/async-control.js +48 -0
- package/lib/client.d.ts +48 -7
- package/lib/client.js +244 -125
- package/lib/commands/egg.js +66 -4
- package/lib/commands/merchant.js +144 -146
- package/lib/commands/query.js +148 -117
- package/lib/commands/ranking.d.ts +2 -0
- package/lib/commands/ranking.js +31 -0
- package/lib/commands/share-code.d.ts +2 -0
- package/lib/commands/share-code.js +66 -0
- package/lib/commands/tools.js +47 -59
- package/lib/egg-service.d.ts +82 -8
- package/lib/egg-service.js +235 -19
- package/lib/img//345/274/202/350/211/262.png +0 -0
- package/lib/img//347/202/253/345/275/251.png +0 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +16 -3
- package/lib/merchant-clock.d.ts +22 -0
- package/lib/merchant-clock.js +95 -0
- package/lib/merchant-service.d.ts +24 -0
- package/lib/merchant-service.js +148 -0
- package/lib/permissions.d.ts +16 -0
- package/lib/permissions.js +66 -0
- package/lib/pet-data-service.js +7 -9
- package/lib/pet-size.d.ts +13 -0
- package/lib/pet-size.js +54 -0
- package/lib/player-service.d.ts +37 -0
- package/lib/player-service.js +201 -0
- package/lib/ranking-service.d.ts +46 -0
- package/lib/ranking-service.js +137 -0
- package/lib/render-templates/announcement/detail.html +8 -0
- package/lib/render-templates/announcement/list.html +5 -0
- package/lib/render-templates/announcement/style.css +1 -0
- package/lib/render-templates/pet-ranking/index.html +99 -0
- package/lib/render-templates/pet-ranking/style.css +338 -0
- package/lib/render-templates/player-search/index.html +7 -0
- package/lib/render-templates/searcheggs/index.html +1 -1
- package/lib/render-templates/searcheggs/size.html +70 -68
- package/lib/render-templates/share-code-team/index.html +105 -0
- package/lib/render-templates/share-code-team/style.css +345 -0
- package/lib/render.d.ts +15 -2
- package/lib/render.js +120 -37
- package/lib/send-image.d.ts +1 -1
- package/lib/send-image.js +12 -1
- package/lib/share-code-service.d.ts +32 -0
- package/lib/share-code-service.js +184 -0
- package/lib/subscription-runner.d.ts +6 -0
- package/lib/subscription-runner.js +21 -0
- package/lib/subscription-send.d.ts +4 -2
- package/lib/subscription-send.js +45 -20
- package/lib/types.d.ts +5 -0
- package/lib/user.d.ts +5 -0
- package/package.json +4 -1
- package/readme.md +145 -124
package/lib/egg-service.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.formatEggGroups = formatEggGroups;
|
|
|
9
9
|
const koishi_1 = require("koishi");
|
|
10
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const pet_size_1 = require("./pet-size");
|
|
12
13
|
const logger = new koishi_1.Logger('rocom-egg');
|
|
13
14
|
exports.EGG_GROUP_META = {
|
|
14
15
|
1: { label: '未发现', desc: '不能和任何精灵生蛋' },
|
|
@@ -70,6 +71,8 @@ function fmtRange(lo, hi, u) {
|
|
|
70
71
|
return `${lo ?? hi}${u}`;
|
|
71
72
|
}
|
|
72
73
|
function num(value) {
|
|
74
|
+
if (value === null || value === undefined || value === '')
|
|
75
|
+
return null;
|
|
73
76
|
const parsed = Number(value);
|
|
74
77
|
return Number.isFinite(parsed) ? parsed : null;
|
|
75
78
|
}
|
|
@@ -275,33 +278,60 @@ class EggService {
|
|
|
275
278
|
weight_min: weightMin,
|
|
276
279
|
weight_max: weightMax,
|
|
277
280
|
weight_label: fmtRange(weightMin, weightMax, 'kg'),
|
|
281
|
+
...(0, pet_size_1.sizeVariantPayload)(queryWeight, pet?.breeding?.weight_low == null ? null : pet.breeding.weight_low / 1000, pet?.breeding?.weight_high == null ? null : pet.breeding.weight_high / 1000),
|
|
278
282
|
probability,
|
|
279
283
|
match_count: matchCount,
|
|
280
284
|
match_info_label: this.formatMatchSummary(probability, matchCount),
|
|
281
285
|
};
|
|
282
286
|
}
|
|
283
|
-
formatSizeApiCard(item) {
|
|
284
|
-
//
|
|
287
|
+
formatSizeApiCard(item, queryWeight) {
|
|
288
|
+
// 兼容 /wiki/pet-size/query 的 items[].pet/egg_size/match 结构与 /egg/search 的
|
|
289
|
+
// height_range_m/cm、weight_range_kg/g、r_value/range_area 平铺结构。
|
|
285
290
|
const petInfo = item?.pet && typeof item.pet === 'object' ? item.pet : {};
|
|
286
291
|
const eggSize = item?.egg_size && typeof item.egg_size === 'object' ? item.egg_size : {};
|
|
287
292
|
const heightRange = eggSize?.height && typeof eggSize.height === 'object' ? eggSize.height : {};
|
|
288
293
|
const weightRange = eggSize?.weight && typeof eggSize.weight === 'object' ? eggSize.weight : {};
|
|
289
294
|
const match = item?.match && typeof item.match === 'object' ? item.match : {};
|
|
290
|
-
const petId = petInfo?.pet_id || item?.petId || item?.pet_id || '-';
|
|
291
|
-
const
|
|
295
|
+
const petId = petInfo?.pet_id || petInfo?.id || item?.petId || item?.pet_id || item?.id || '-';
|
|
296
|
+
const petForm = String(petInfo?.form || item?.form || '').trim();
|
|
297
|
+
let petName = petInfo?.name || (typeof item?.pet === 'string' ? item.pet : '') || item?.name || item?.pet_name || '未知精灵';
|
|
298
|
+
if (petForm && !String(petName).includes(petForm))
|
|
299
|
+
petName = `${petName}(${petForm})`;
|
|
292
300
|
const typeNames = Array.isArray(petInfo?.type_names) ? petInfo.type_names.filter(Boolean) : [];
|
|
293
301
|
const eggGroupNames = Array.isArray(petInfo?.egg_group_names) ? petInfo.egg_group_names.filter(Boolean) : [];
|
|
302
|
+
const [heightRangeMMin, heightRangeMMax] = this.rangePair(item?.height_range_m);
|
|
303
|
+
const [heightRangeCmMin, heightRangeCmMax] = this.rangePair(item?.height_range_cm);
|
|
304
|
+
const [weightRangeKgMin, weightRangeKgMax] = this.rangePair(item?.weight_range_kg);
|
|
305
|
+
const [weightRangeGMin, weightRangeGMax] = this.rangePair(item?.weight_range_g);
|
|
294
306
|
const probability = num(item?.probability) ?? num(match?.percent);
|
|
295
307
|
const matchCount = num(item?.matchCount);
|
|
296
|
-
const heightMin = num(heightRange?.min_m ??
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
308
|
+
const heightMin = num(heightRange?.min_m) ?? heightRangeMMin
|
|
309
|
+
?? (heightRangeCmMin == null ? null : heightRangeCmMin / 100)
|
|
310
|
+
?? num(item?.diameterMin);
|
|
311
|
+
const heightMax = num(heightRange?.max_m) ?? heightRangeMMax
|
|
312
|
+
?? (heightRangeCmMax == null ? null : heightRangeCmMax / 100)
|
|
313
|
+
?? num(item?.diameterMax);
|
|
314
|
+
const weightMin = num(weightRange?.min_kg) ?? weightRangeKgMin
|
|
315
|
+
?? (weightRangeGMin == null ? null : weightRangeGMin / 1000)
|
|
316
|
+
?? num(item?.weightMin);
|
|
317
|
+
const weightMax = num(weightRange?.max_kg) ?? weightRangeKgMax
|
|
318
|
+
?? (weightRangeGMax == null ? null : weightRangeGMax / 1000)
|
|
319
|
+
?? num(item?.weightMax);
|
|
320
|
+
const rValue = num(item?.r_value);
|
|
321
|
+
const rangeArea = num(item?.range_area);
|
|
322
|
+
const sizeVariant = (0, pet_size_1.sizeVariantPayload)(queryWeight, weightMin, weightMax, fmtRange(weightMin, weightMax, 'kg'));
|
|
323
|
+
const fallbackMatchLabel = [
|
|
324
|
+
rValue != null ? `R值 ${formatNumber(rValue, 3)}` : '',
|
|
325
|
+
rangeArea != null ? `范围面积 ${formatNumber(rangeArea, 0)}` : '',
|
|
326
|
+
].filter(Boolean).join(' / ');
|
|
327
|
+
const matchInfoLabel = String(match?.match_percent_text || '')
|
|
328
|
+
|| fallbackMatchLabel
|
|
329
|
+
|| this.formatMatchSummary(probability, matchCount);
|
|
300
330
|
return {
|
|
301
331
|
id: petId,
|
|
302
332
|
name: petName,
|
|
303
|
-
icon: petInfo?.icon || item?.petIcon || petIconUrl(petId),
|
|
304
|
-
image: petInfo?.small_icon || item?.petImage || petImageUrl(petId),
|
|
333
|
+
icon: petInfo?.icon || item?.pet_icon_url || item?.petIcon || petIconUrl(petId),
|
|
334
|
+
image: petInfo?.small_icon || item?.pet_img_url || item?.petImage || petImageUrl(petId),
|
|
305
335
|
type_label: typeNames.length ? typeNames.join(' / ') : '后端未提供',
|
|
306
336
|
egg_group_ids: [],
|
|
307
337
|
egg_groups_label: eggGroupNames.length ? eggGroupNames.join(' / ') : '后端未提供',
|
|
@@ -311,11 +341,18 @@ class EggService {
|
|
|
311
341
|
weight_min: weightMin,
|
|
312
342
|
weight_max: weightMax,
|
|
313
343
|
weight_label: fmtRange(weightMin, weightMax, 'kg'),
|
|
344
|
+
query_weight: queryWeight,
|
|
345
|
+
...sizeVariant,
|
|
314
346
|
probability,
|
|
315
347
|
match_count: matchCount,
|
|
316
|
-
match_info_label:
|
|
348
|
+
match_info_label: matchInfoLabel,
|
|
317
349
|
};
|
|
318
350
|
}
|
|
351
|
+
rangePair(value) {
|
|
352
|
+
if (Array.isArray(value) && value.length >= 2)
|
|
353
|
+
return [num(value[0]), num(value[1])];
|
|
354
|
+
return [null, null];
|
|
355
|
+
}
|
|
319
356
|
// 新版尺寸反查按 match.layer 划分完美/范围匹配。
|
|
320
357
|
splitNewSizeApiItems(items) {
|
|
321
358
|
const perfect = [];
|
|
@@ -473,7 +510,7 @@ class EggService {
|
|
|
473
510
|
lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${results.perfect.length} 只):`);
|
|
474
511
|
results.perfect.slice(0, 10).forEach((p, i) => {
|
|
475
512
|
const br = p.breeding || {};
|
|
476
|
-
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), 'm')} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg')} · ${formatEggGroups(this.getEggGroups(p))}`);
|
|
513
|
+
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), 'm')} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg')} · ${formatEggGroups(this.getEggGroups(p))}${this.formatPetCard(p, height, weight).size_variant_label ? ' · 【' + this.formatPetCard(p, height, weight).size_variant_label + '】' : ''}`);
|
|
477
514
|
});
|
|
478
515
|
}
|
|
479
516
|
if (results.range.length) {
|
|
@@ -482,7 +519,7 @@ class EggService {
|
|
|
482
519
|
lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${results.range.length} 只,容差±15%):`);
|
|
483
520
|
results.range.slice(0, 10).forEach((p, i) => {
|
|
484
521
|
const br = p.breeding || {};
|
|
485
|
-
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), 'm')} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg')} · ${formatEggGroups(this.getEggGroups(p))}`);
|
|
522
|
+
lines.push(` ${i + 1}. ${petName(p)} (#${p.id}) — ${fmtRange(ht(br.height_low), ht(br.height_high), 'm')} / ${fmtRange(wt(br.weight_low), wt(br.weight_high), 'kg')} · ${formatEggGroups(this.getEggGroups(p))}${this.formatPetCard(p, height, weight).size_variant_label ? ' · 【' + this.formatPetCard(p, height, weight).size_variant_label + '】' : ''}`);
|
|
486
523
|
});
|
|
487
524
|
}
|
|
488
525
|
return lines.join('\n');
|
|
@@ -501,8 +538,8 @@ class EggService {
|
|
|
501
538
|
if (exact.length) {
|
|
502
539
|
lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${exact.length} 只):`);
|
|
503
540
|
exact.slice(0, 10).forEach((item, i) => {
|
|
504
|
-
const card = this.formatSizeApiCard(item);
|
|
505
|
-
lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}`);
|
|
541
|
+
const card = this.formatSizeApiCard(item, weight);
|
|
542
|
+
lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}${card.size_variant_label ? ` · 【${card.size_variant_label}】` : ''}`);
|
|
506
543
|
});
|
|
507
544
|
}
|
|
508
545
|
if (candidates.length) {
|
|
@@ -510,8 +547,8 @@ class EggService {
|
|
|
510
547
|
lines.push('');
|
|
511
548
|
lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${candidates.length} 只):`);
|
|
512
549
|
candidates.slice(0, 10).forEach((item, i) => {
|
|
513
|
-
const card = this.formatSizeApiCard(item);
|
|
514
|
-
lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}`);
|
|
550
|
+
const card = this.formatSizeApiCard(item, weight);
|
|
551
|
+
lines.push(` ${i + 1}. ${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label}${card.size_variant_label ? ` · 【${card.size_variant_label}】` : ''}`);
|
|
515
552
|
});
|
|
516
553
|
}
|
|
517
554
|
return lines.join('\n');
|
|
@@ -534,7 +571,7 @@ class EggService {
|
|
|
534
571
|
`🥚 ${data?.pet_name || '未知精灵'} (#${data?.pet_id || '-'})`,
|
|
535
572
|
`属性:${data?.type_label || '未知'}`,
|
|
536
573
|
`蛋组:${data?.egg_groups_label || '暂无蛋组数据'}`,
|
|
537
|
-
|
|
574
|
+
`${data?.compatible_total_known === false ? '当前预览精灵数' : '可配种精灵数'}:${data?.total_compatible || 0}`,
|
|
538
575
|
];
|
|
539
576
|
if (data?.is_undiscovered)
|
|
540
577
|
lines.push('⚠️ 该精灵属于「未发现」蛋组,无法配种。');
|
|
@@ -792,6 +829,185 @@ class EggService {
|
|
|
792
829
|
copyright: 'Koishi & WeGame 洛克王国插件',
|
|
793
830
|
};
|
|
794
831
|
}
|
|
832
|
+
// ─── 后端 Egg API 转换(上游 v3.8.0)───
|
|
833
|
+
buildCandidatesFromEggApi(keyword, candidates) {
|
|
834
|
+
return {
|
|
835
|
+
keyword,
|
|
836
|
+
count: candidates.length,
|
|
837
|
+
candidates: candidates.map(item => this.formatEggApiCard(item)),
|
|
838
|
+
commandHint: '请使用更精确的名称重新查询',
|
|
839
|
+
copyright: 'Koishi & WeGame 洛克王国插件',
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
buildSearchDataFromEggApi(pet, compatibleByGroup = {}) {
|
|
843
|
+
const groups = this.eggApiGroups(pet);
|
|
844
|
+
const groupIds = [];
|
|
845
|
+
const groupLabels = {};
|
|
846
|
+
for (const group of groups) {
|
|
847
|
+
const id = this.eggGroupId(group);
|
|
848
|
+
if (id === null || groupIds.includes(id))
|
|
849
|
+
continue;
|
|
850
|
+
groupIds.push(id);
|
|
851
|
+
groupLabels[id] = this.eggGroupLabel(group);
|
|
852
|
+
}
|
|
853
|
+
const seen = new Set();
|
|
854
|
+
let totalCompatible = 0;
|
|
855
|
+
const sections = groups.map((group) => {
|
|
856
|
+
const gid = this.eggGroupId(group);
|
|
857
|
+
const label = this.eggGroupLabel(group);
|
|
858
|
+
if (gid === null)
|
|
859
|
+
return null;
|
|
860
|
+
if (this.isUndiscoveredEggGroup(gid, label)) {
|
|
861
|
+
return {
|
|
862
|
+
id: gid,
|
|
863
|
+
label: label || '未发现',
|
|
864
|
+
desc: '不能和任何精灵生蛋,多用于传说中的精灵',
|
|
865
|
+
count: 0,
|
|
866
|
+
members: [],
|
|
867
|
+
has_more: false,
|
|
868
|
+
total: 0,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
const groupResult = compatibleByGroup[String(gid)] ?? compatibleByGroup[gid] ?? {};
|
|
872
|
+
const rawMembers = Array.isArray(groupResult)
|
|
873
|
+
? groupResult
|
|
874
|
+
: (Array.isArray(groupResult?.items) ? groupResult.items : []);
|
|
875
|
+
const members = [];
|
|
876
|
+
for (const item of rawMembers) {
|
|
877
|
+
const itemId = item?.id ?? item?.pet_id;
|
|
878
|
+
if (String(itemId ?? '') === String(pet?.id ?? pet?.pet_id ?? ''))
|
|
879
|
+
continue;
|
|
880
|
+
const key = String(itemId ?? item?.name ?? '');
|
|
881
|
+
if (key && !seen.has(key)) {
|
|
882
|
+
seen.add(key);
|
|
883
|
+
totalCompatible += 1;
|
|
884
|
+
}
|
|
885
|
+
members.push(this.formatEggApiMember(item));
|
|
886
|
+
}
|
|
887
|
+
const total = num(groupResult?.total) ?? members.length;
|
|
888
|
+
return {
|
|
889
|
+
id: gid,
|
|
890
|
+
label,
|
|
891
|
+
desc: this.eggGroupDesc(group),
|
|
892
|
+
count: total,
|
|
893
|
+
members: members.slice(0, 30),
|
|
894
|
+
has_more: total > 30 || members.length > 30,
|
|
895
|
+
total,
|
|
896
|
+
};
|
|
897
|
+
}).filter(Boolean);
|
|
898
|
+
const [heightMin, heightMax] = this.eggHeightRangeM(pet);
|
|
899
|
+
const [weightMin, weightMax] = this.eggWeightRangeKg(pet);
|
|
900
|
+
const petId = pet?.id ?? pet?.pet_id ?? '-';
|
|
901
|
+
return {
|
|
902
|
+
pet_name: this.eggPetDisplayName(pet),
|
|
903
|
+
pet_id: petId,
|
|
904
|
+
pet_icon: pet?.pet_icon_url || pet?.icon_url || petIconUrl(petId),
|
|
905
|
+
pet_image: pet?.pet_img_url || pet?.image_url || petImageUrl(petId),
|
|
906
|
+
type_label: this.eggApiTypeLabel(pet),
|
|
907
|
+
egg_groups_label: groupIds.map(id => groupLabels[id] || String(id)).join(' / ') || '暂无蛋组数据',
|
|
908
|
+
egg_groups: groupIds,
|
|
909
|
+
egg_group_labels: groupLabels,
|
|
910
|
+
male_rate: null,
|
|
911
|
+
female_rate: null,
|
|
912
|
+
hatch_label: '后端未提供',
|
|
913
|
+
weight_label: fmtRange(weightMin, weightMax, 'kg'),
|
|
914
|
+
height_label: fmtRange(heightMin, heightMax, 'm'),
|
|
915
|
+
total_compatible: num(compatibleByGroup.__all__?.total) ?? totalCompatible,
|
|
916
|
+
compatible_total_known: num(compatibleByGroup.__all__?.total) !== null,
|
|
917
|
+
is_undiscovered: groups.some(group => this.isUndiscoveredEggGroup(this.eggGroupId(group), this.eggGroupLabel(group))),
|
|
918
|
+
egg_group_sections: sections,
|
|
919
|
+
total_stats: '后端未提供',
|
|
920
|
+
egg_details: { has_data: false },
|
|
921
|
+
commandHint: '数据来自后端查蛋;接口不可用时自动回退 Wiki / 本地查蛋' + (num(compatibleByGroup.__all__?.total) === null ? '。总数不可用,当前数量仅为已加载预览' : ''),
|
|
922
|
+
copyright: 'Koishi & WeGame 洛克王国插件',
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
formatEggApiCard(item) {
|
|
926
|
+
const petId = item?.id ?? item?.pet_id ?? '-';
|
|
927
|
+
const [heightMin, heightMax] = this.eggHeightRangeM(item);
|
|
928
|
+
const [weightMin, weightMax] = this.eggWeightRangeKg(item);
|
|
929
|
+
return {
|
|
930
|
+
id: petId,
|
|
931
|
+
name: this.eggPetDisplayName(item),
|
|
932
|
+
icon: item?.pet_icon_url || item?.icon_url || petIconUrl(petId),
|
|
933
|
+
image: item?.pet_img_url || item?.image_url || petImageUrl(petId),
|
|
934
|
+
type_label: this.eggApiTypeLabel(item),
|
|
935
|
+
egg_group_ids: [],
|
|
936
|
+
egg_groups_label: this.eggGroupsLabel(item),
|
|
937
|
+
height_min: heightMin,
|
|
938
|
+
height_max: heightMax,
|
|
939
|
+
height_label: fmtRange(heightMin, heightMax, 'm'),
|
|
940
|
+
weight_min: weightMin,
|
|
941
|
+
weight_max: weightMax,
|
|
942
|
+
weight_label: fmtRange(weightMin, weightMax, 'kg'),
|
|
943
|
+
probability: null,
|
|
944
|
+
match_count: null,
|
|
945
|
+
match_info_label: '后端命中',
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
formatEggApiMember(item) {
|
|
949
|
+
return {
|
|
950
|
+
name: this.eggPetDisplayName(item),
|
|
951
|
+
id: item?.id ?? item?.pet_id ?? '-',
|
|
952
|
+
type_label: this.eggApiTypeLabel(item),
|
|
953
|
+
egg_groups_label: this.eggGroupsLabel(item),
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
eggApiGroups(item) {
|
|
957
|
+
const groups = item?.egg_groups ?? item?.egg_group ?? [];
|
|
958
|
+
const list = Array.isArray(groups) ? groups : [groups];
|
|
959
|
+
return list
|
|
960
|
+
.filter(Boolean)
|
|
961
|
+
.map(group => (group && typeof group === 'object' ? group : { name: String(group), official_name: String(group) }));
|
|
962
|
+
}
|
|
963
|
+
eggGroupId(group) {
|
|
964
|
+
return num(group?.group_id ?? group?.id);
|
|
965
|
+
}
|
|
966
|
+
eggGroupLabel(group) {
|
|
967
|
+
return String(group?.official_name || group?.name || group?.display_name || '').trim();
|
|
968
|
+
}
|
|
969
|
+
eggGroupDesc(group) {
|
|
970
|
+
const label = this.eggGroupLabel(group);
|
|
971
|
+
const display = String(group?.display_name || '').trim();
|
|
972
|
+
return display && display !== label ? display : '';
|
|
973
|
+
}
|
|
974
|
+
isUndiscoveredEggGroup(id, label) {
|
|
975
|
+
const text = String(label || '');
|
|
976
|
+
return String(id) === '1' || text.includes('未发现') || text.includes('无法孵蛋');
|
|
977
|
+
}
|
|
978
|
+
eggPetDisplayName(item) {
|
|
979
|
+
const name = String(item?.name || item?.pet_name || '未知精灵').trim() || '未知精灵';
|
|
980
|
+
const form = String(item?.form || '').trim();
|
|
981
|
+
return form && !name.includes(form) ? `${name}(${form})` : name;
|
|
982
|
+
}
|
|
983
|
+
eggApiTypeLabel(item) {
|
|
984
|
+
const types = item?.unit_type;
|
|
985
|
+
if (Array.isArray(types) && types.length)
|
|
986
|
+
return types.filter(Boolean).join(' / ') || '未知';
|
|
987
|
+
return String(item?.type || item?.attribute_name || '未知');
|
|
988
|
+
}
|
|
989
|
+
eggGroupsLabel(item) {
|
|
990
|
+
const labels = this.eggApiGroups(item).map(group => this.eggGroupLabel(group)).filter(Boolean);
|
|
991
|
+
return labels.length ? labels.join(' / ') : '暂无蛋组数据';
|
|
992
|
+
}
|
|
993
|
+
eggHeightRangeM(item) {
|
|
994
|
+
const direct = this.rangePair(item?.height_range_m);
|
|
995
|
+
if (direct[0] != null || direct[1] != null)
|
|
996
|
+
return direct;
|
|
997
|
+
const cm = this.rangePair(item?.height_range_cm);
|
|
998
|
+
if (cm[0] != null || cm[1] != null)
|
|
999
|
+
return [cm[0] == null ? null : cm[0] / 100, cm[1] == null ? null : cm[1] / 100];
|
|
1000
|
+
return [null, null];
|
|
1001
|
+
}
|
|
1002
|
+
eggWeightRangeKg(item) {
|
|
1003
|
+
const direct = this.rangePair(item?.weight_range_kg);
|
|
1004
|
+
if (direct[0] != null || direct[1] != null)
|
|
1005
|
+
return direct;
|
|
1006
|
+
const g = this.rangePair(item?.weight_range_g);
|
|
1007
|
+
if (g[0] != null || g[1] != null)
|
|
1008
|
+
return [g[0] == null ? null : g[0] / 1000, g[1] == null ? null : g[1] / 1000];
|
|
1009
|
+
return [null, null];
|
|
1010
|
+
}
|
|
795
1011
|
buildSizeSearchData(height, weight, results, heightDisplay) {
|
|
796
1012
|
const conditions = [];
|
|
797
1013
|
if (height != null)
|
|
@@ -816,7 +1032,7 @@ class EggService {
|
|
|
816
1032
|
if (weight != null)
|
|
817
1033
|
conditions.push(`体重 ${weight} kg`);
|
|
818
1034
|
const groups = this.sizeApiResultGroups(results);
|
|
819
|
-
const [perfect, ranged] = this.mergeCardsByName(groups.exact.map((item) => this.formatSizeApiCard(item)), groups.candidates.map((item) => this.formatSizeApiCard(item)));
|
|
1035
|
+
const [perfect, ranged] = this.mergeCardsByName(groups.exact.map((item) => this.formatSizeApiCard(item, weight)), groups.candidates.map((item) => this.formatSizeApiCard(item, weight)));
|
|
820
1036
|
const poolName = results?.query?.pool && typeof results.query.pool === 'object'
|
|
821
1037
|
? results.query.pool.name
|
|
822
1038
|
: '';
|
|
Binary file
|
|
Binary file
|
package/lib/index.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface Config {
|
|
|
17
17
|
merchantCheckMode: 'interval' | 'times';
|
|
18
18
|
merchantCheckInterval: number;
|
|
19
19
|
merchantCheckTimes: string[];
|
|
20
|
+
merchantTimezone: string;
|
|
21
|
+
subscriptionGroupAdminEnabled: boolean;
|
|
22
|
+
subscriptionBotAdminEnabled: boolean;
|
|
23
|
+
subscriptionBotAdminAuthority: number;
|
|
20
24
|
homeSubscriptionEnabled: boolean;
|
|
21
25
|
homeSubscriptionIntervalMinutes: number;
|
|
22
26
|
announcementSubscriptionEnabled: boolean;
|
|
@@ -25,6 +29,7 @@ export interface Config {
|
|
|
25
29
|
homeQueryPollIntervalMs: number;
|
|
26
30
|
homeQueryTimeoutMs: number;
|
|
27
31
|
lowBandwidthMode: boolean;
|
|
32
|
+
renderTimeout: number;
|
|
28
33
|
atlasZipUrls: string[];
|
|
29
34
|
atlasGitUrl: string;
|
|
30
35
|
imageCompressionEnabled: boolean;
|
package/lib/index.js
CHANGED
|
@@ -22,6 +22,8 @@ const wiki_1 = require("./commands/wiki");
|
|
|
22
22
|
const egg_1 = require("./commands/egg");
|
|
23
23
|
const tools_1 = require("./commands/tools");
|
|
24
24
|
const admin_1 = require("./commands/admin");
|
|
25
|
+
const ranking_1 = require("./commands/ranking");
|
|
26
|
+
const share_code_1 = require("./commands/share-code");
|
|
25
27
|
const send_image_1 = require("./send-image");
|
|
26
28
|
exports.name = 'rocom';
|
|
27
29
|
exports.inject = { required: ['puppeteer', 'database'] };
|
|
@@ -51,10 +53,10 @@ const MENU_GROUPS = [
|
|
|
51
53
|
{ cmd: '洛克.阵容', desc: '查看阵容推荐' },
|
|
52
54
|
{ cmd: '查看阵容', desc: '查看阵容详情' },
|
|
53
55
|
{ cmd: '洛克.交换大厅', desc: '查看交换大厅' },
|
|
54
|
-
{ cmd: '洛克.玩家', desc: '查询 ingame
|
|
56
|
+
{ cmd: '洛克.玩家', desc: '查询 ingame 玩家资料(可选 UID)' },
|
|
55
57
|
{ cmd: '洛克.家园', desc: '查询家园菜园' },
|
|
56
58
|
{ cmd: '洛克.家园详情', desc: '家园精灵完整数据' },
|
|
57
|
-
{ cmd: '洛克.商店', desc: '查询 ingame
|
|
59
|
+
{ cmd: '洛克.商店', desc: '查询 ingame 商店(默认 3009)' },
|
|
58
60
|
{ cmd: '洛克.日历', desc: '查看活动日历' },
|
|
59
61
|
{ cmd: '洛克.公告', desc: '查看公告列表' },
|
|
60
62
|
{ cmd: '洛克.好友关系', desc: '查询好友关系' },
|
|
@@ -84,6 +86,10 @@ const MENU_GROUPS = [
|
|
|
84
86
|
{ cmd: '图鉴下载', desc: '下载图鉴缓存(管理员)' },
|
|
85
87
|
{ cmd: '洛克.查蛋', desc: '精灵查蛋 / 尺寸反查' },
|
|
86
88
|
{ cmd: '洛克.配种', desc: '配种查询' },
|
|
89
|
+
{ cmd: '异色排行榜', desc: '异色精灵收集排行 [UID] [数量]' },
|
|
90
|
+
{ cmd: '炫彩排行榜', desc: '炫彩精灵收集排行 [UID] [数量]' },
|
|
91
|
+
{ cmd: '阵容码 解析', desc: '解析阵容分享码或链接' },
|
|
92
|
+
{ cmd: '阵容码 查询', desc: '查询阵容分享码历史' },
|
|
87
93
|
],
|
|
88
94
|
},
|
|
89
95
|
];
|
|
@@ -126,7 +132,11 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
126
132
|
merchantCheckMode: koishi_1.Schema.union(['interval', 'times']).default('interval').description('商人检查模式:interval 定期轮询 / times 指定时间点'),
|
|
127
133
|
merchantCheckInterval: koishi_1.Schema.number().default(300000).description('商人检查间隔,单位毫秒(仅 interval 模式生效)'),
|
|
128
134
|
merchantCheckTimes: koishi_1.Schema.array(String).default([]).description('商人定时检查时间点(HH:MM 格式,如 08:00,仅 times 模式生效)'),
|
|
135
|
+
merchantTimezone: koishi_1.Schema.string().default('Asia/Shanghai').description('远行商人时区(IANA 名称,如 Asia/Shanghai;默认 UTC+8)'),
|
|
129
136
|
merchantPrivateSubscriptionEnabled: koishi_1.Schema.boolean().default(true).description('允许个人私聊订阅远行商人推送'),
|
|
137
|
+
subscriptionGroupAdminEnabled: koishi_1.Schema.boolean().default(true).description('允许群主/管理员管理当前群订阅(对应上游 merchant_group_admin_enabled)'),
|
|
138
|
+
subscriptionBotAdminEnabled: koishi_1.Schema.boolean().default(true).description('允许白名单/高权限 Bot 管理员管理订阅(对应上游 merchant_bot_admin_enabled)'),
|
|
139
|
+
subscriptionBotAdminAuthority: koishi_1.Schema.number().default(4).description('Bot 管理员 authority 阈值;达到该值即视为 Bot 管理员'),
|
|
130
140
|
homeSubscriptionEnabled: koishi_1.Schema.boolean().default(true).description('启用家园菜园和灵感订阅推送'),
|
|
131
141
|
homeSubscriptionIntervalMinutes: koishi_1.Schema.number().default(5).description('家园订阅检查间隔,单位分钟'),
|
|
132
142
|
announcementSubscriptionEnabled: koishi_1.Schema.boolean().default(true).description('启用洛克公告订阅推送'),
|
|
@@ -137,6 +147,7 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
137
147
|
homeQueryPollIntervalMs: koishi_1.Schema.number().default(3000).description('家园查询进入排队后的轮询间隔,单位毫秒'),
|
|
138
148
|
homeQueryTimeoutMs: koishi_1.Schema.number().default(180000).description('家园查询排队等候的总超时,单位毫秒,超时后提示稍后重试'),
|
|
139
149
|
lowBandwidthMode: koishi_1.Schema.boolean().default(false).description('低带宽模式:家园详情不再加载技能图标,降低长图生成压力'),
|
|
150
|
+
renderTimeout: koishi_1.Schema.number().default(30000).description('单次渲染总超时,单位毫秒(导航、资源等待与截图共享截止时间)'),
|
|
140
151
|
}).description('家园查询排队设置'),
|
|
141
152
|
koishi_1.Schema.object({
|
|
142
153
|
atlasZipUrls: koishi_1.Schema.array(String).default([
|
|
@@ -155,7 +166,7 @@ function apply(ctx, config) {
|
|
|
155
166
|
const homeSubMgr = new user_1.HomeSubscriptionManager(dataDir);
|
|
156
167
|
const announcementSubMgr = new user_1.AnnouncementSubscriptionManager(dataDir);
|
|
157
168
|
const resPath = node_path_1.default.resolve(__dirname, '..');
|
|
158
|
-
const renderer = new render_1.Renderer(resPath);
|
|
169
|
+
const renderer = new render_1.Renderer(resPath, { timeoutMs: config.renderTimeout || 30000 });
|
|
159
170
|
const renderTemplateRoot = node_fs_1.default.existsSync(node_path_1.default.join(resPath, 'lib', 'render-templates'))
|
|
160
171
|
? node_path_1.default.join(resPath, 'lib', 'render-templates')
|
|
161
172
|
: node_path_1.default.join(resPath, 'src', 'render-templates');
|
|
@@ -186,4 +197,6 @@ function apply(ctx, config) {
|
|
|
186
197
|
(0, egg_1.register)(deps);
|
|
187
198
|
(0, tools_1.register)(deps);
|
|
188
199
|
(0, admin_1.register)(deps);
|
|
200
|
+
(0, ranking_1.register)(deps);
|
|
201
|
+
(0, share_code_1.register)(deps);
|
|
189
202
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ClockParts {
|
|
2
|
+
date: string;
|
|
3
|
+
hour: number;
|
|
4
|
+
minute: number;
|
|
5
|
+
second: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class MerchantClock {
|
|
8
|
+
private formatter;
|
|
9
|
+
private fixedOffset;
|
|
10
|
+
readonly zone: string;
|
|
11
|
+
constructor(requested?: string);
|
|
12
|
+
parts(nowMs?: number): ClockParts;
|
|
13
|
+
date(nowMs?: number): string;
|
|
14
|
+
/** Boundaries in absolute time, including 23/25-hour DST days. */
|
|
15
|
+
dayBounds(nowMs?: number): {
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
};
|
|
19
|
+
minuteOfDay(nowMs?: number): number;
|
|
20
|
+
timeText(nowMs?: number): string;
|
|
21
|
+
nextTimeFor(times: string[], nowMs?: number): number | null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MerchantClock = void 0;
|
|
4
|
+
function pad(value, width = 2) {
|
|
5
|
+
return String(value).padStart(width, '0');
|
|
6
|
+
}
|
|
7
|
+
// 统一时钟:默认上海时区失败时固定 +08:00,避免 UTC 服务器上商品日期/轮次错位。
|
|
8
|
+
class MerchantClock {
|
|
9
|
+
formatter;
|
|
10
|
+
fixedOffset = false;
|
|
11
|
+
zone;
|
|
12
|
+
constructor(requested = 'Asia/Shanghai') {
|
|
13
|
+
const wanted = requested || 'Asia/Shanghai';
|
|
14
|
+
let zone = wanted;
|
|
15
|
+
try {
|
|
16
|
+
new Intl.DateTimeFormat('en-CA', { timeZone: zone }).format(0);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
if (wanted === 'Asia/Shanghai') {
|
|
20
|
+
zone = 'UTC';
|
|
21
|
+
this.fixedOffset = true;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
zone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
this.zone = this.fixedOffset ? 'UTC+08:00' : zone;
|
|
28
|
+
this.formatter = new Intl.DateTimeFormat('en-CA', {
|
|
29
|
+
timeZone: zone,
|
|
30
|
+
hourCycle: 'h23',
|
|
31
|
+
year: 'numeric',
|
|
32
|
+
month: '2-digit',
|
|
33
|
+
day: '2-digit',
|
|
34
|
+
hour: '2-digit',
|
|
35
|
+
minute: '2-digit',
|
|
36
|
+
second: '2-digit',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
parts(nowMs = Date.now()) {
|
|
40
|
+
const timestamp = nowMs + (this.fixedOffset ? 8 * 3600_000 : 0);
|
|
41
|
+
const values = {};
|
|
42
|
+
for (const part of this.formatter.formatToParts(timestamp)) {
|
|
43
|
+
if (part.type !== 'literal')
|
|
44
|
+
values[part.type] = part.value;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
date: `${values.year}-${values.month}-${values.day}`,
|
|
48
|
+
hour: Number(values.hour),
|
|
49
|
+
minute: Number(values.minute),
|
|
50
|
+
second: Number(values.second),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
date(nowMs = Date.now()) {
|
|
54
|
+
return this.parts(nowMs).date;
|
|
55
|
+
}
|
|
56
|
+
/** Boundaries in absolute time, including 23/25-hour DST days. */
|
|
57
|
+
dayBounds(nowMs = Date.now()) {
|
|
58
|
+
const day = this.date(nowMs);
|
|
59
|
+
const edge = (before) => {
|
|
60
|
+
let low = before ? nowMs - 48 * 3600_000 : nowMs;
|
|
61
|
+
let high = before ? nowMs : nowMs + 48 * 3600_000;
|
|
62
|
+
while (high - low > 1) {
|
|
63
|
+
const mid = Math.floor((low + high) / 2);
|
|
64
|
+
if (before ? this.date(mid) < day : this.date(mid) === day)
|
|
65
|
+
low = mid;
|
|
66
|
+
else
|
|
67
|
+
high = mid;
|
|
68
|
+
}
|
|
69
|
+
return high;
|
|
70
|
+
};
|
|
71
|
+
return { start: edge(true), end: edge(false) };
|
|
72
|
+
}
|
|
73
|
+
minuteOfDay(nowMs = Date.now()) {
|
|
74
|
+
const p = this.parts(nowMs);
|
|
75
|
+
return p.hour * 60 + p.minute;
|
|
76
|
+
}
|
|
77
|
+
timeText(nowMs = Date.now()) {
|
|
78
|
+
const p = this.parts(nowMs);
|
|
79
|
+
return `${pad(p.hour)}:${pad(p.minute)}`;
|
|
80
|
+
}
|
|
81
|
+
// 下一个匹配给定 HH:MM 墙钟列表的时刻(毫秒),扫描精度 1 分钟。
|
|
82
|
+
nextTimeFor(times, nowMs = Date.now()) {
|
|
83
|
+
const targets = new Set(times.map(item => String(item || '').trim()).filter(Boolean));
|
|
84
|
+
if (!targets.size)
|
|
85
|
+
return null;
|
|
86
|
+
let cursor = Math.floor(nowMs / 60000) * 60000 + 60000;
|
|
87
|
+
for (let i = 0; i < 60 * 24 * 8; i++) {
|
|
88
|
+
if (targets.has(this.timeText(cursor)))
|
|
89
|
+
return cursor;
|
|
90
|
+
cursor += 60000;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.MerchantClock = MerchantClock;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface MerchantItem {
|
|
2
|
+
id: string;
|
|
3
|
+
parentId?: string;
|
|
4
|
+
name: string;
|
|
5
|
+
icon: string;
|
|
6
|
+
price: number | null;
|
|
7
|
+
limit: number | null;
|
|
8
|
+
startsAt: number | null;
|
|
9
|
+
endsAt: number | null;
|
|
10
|
+
active: boolean;
|
|
11
|
+
category: string;
|
|
12
|
+
source: 'live' | 'legacy';
|
|
13
|
+
}
|
|
14
|
+
export interface NormalizedMerchant {
|
|
15
|
+
source: 'live' | 'legacy';
|
|
16
|
+
items: MerchantItem[];
|
|
17
|
+
}
|
|
18
|
+
export declare const numberOrNull: (v: unknown) => number | null;
|
|
19
|
+
export declare function livePrice(value: any): number | null;
|
|
20
|
+
export declare function merchantTimestampMs(value: any): number | null;
|
|
21
|
+
export declare function normalizeLiveMerchant(payload: any, nowMs?: number): MerchantItem[];
|
|
22
|
+
export declare function normalizeLegacyMerchant(payload: any, nowMs?: number): MerchantItem[];
|
|
23
|
+
export declare function isLiveMerchantPayload(payload: any): boolean;
|
|
24
|
+
export declare function normalizeMerchant(payload: any, nowMs?: number): NormalizedMerchant;
|