koishi-plugin-rocom 1.0.17 → 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.
Files changed (57) hide show
  1. package/lib/announcement-service.d.ts +21 -0
  2. package/lib/announcement-service.js +63 -0
  3. package/lib/async-control.d.ts +3 -0
  4. package/lib/async-control.js +48 -0
  5. package/lib/client.d.ts +49 -9
  6. package/lib/client.js +250 -141
  7. package/lib/commands/egg.js +101 -9
  8. package/lib/commands/merchant.js +144 -146
  9. package/lib/commands/query.js +148 -117
  10. package/lib/commands/ranking.d.ts +2 -0
  11. package/lib/commands/ranking.js +31 -0
  12. package/lib/commands/share-code.d.ts +2 -0
  13. package/lib/commands/share-code.js +66 -0
  14. package/lib/commands/tools.js +47 -59
  15. package/lib/egg-service.d.ts +125 -20
  16. package/lib/egg-service.js +364 -96
  17. package/lib/img//345/274/202/350/211/262.png +0 -0
  18. package/lib/img//347/202/253/345/275/251.png +0 -0
  19. package/lib/index.d.ts +5 -0
  20. package/lib/index.js +193 -8740
  21. package/lib/merchant-clock.d.ts +22 -0
  22. package/lib/merchant-clock.js +95 -0
  23. package/lib/merchant-service.d.ts +24 -0
  24. package/lib/merchant-service.js +148 -0
  25. package/lib/permissions.d.ts +16 -0
  26. package/lib/permissions.js +66 -0
  27. package/lib/pet-data-service.js +7 -9
  28. package/lib/pet-size.d.ts +13 -0
  29. package/lib/pet-size.js +54 -0
  30. package/lib/player-service.d.ts +37 -0
  31. package/lib/player-service.js +201 -0
  32. package/lib/ranking-service.d.ts +46 -0
  33. package/lib/ranking-service.js +137 -0
  34. package/lib/render-templates/announcement/detail.html +8 -0
  35. package/lib/render-templates/announcement/list.html +5 -0
  36. package/lib/render-templates/announcement/style.css +1 -0
  37. package/lib/render-templates/pet-ranking/index.html +99 -0
  38. package/lib/render-templates/pet-ranking/style.css +338 -0
  39. package/lib/render-templates/player-search/index.html +7 -0
  40. package/lib/render-templates/searcheggs/index.html +1 -1
  41. package/lib/render-templates/searcheggs/size.html +70 -68
  42. package/lib/render-templates/share-code-team/index.html +105 -0
  43. package/lib/render-templates/share-code-team/style.css +345 -0
  44. package/lib/render.d.ts +15 -2
  45. package/lib/render.js +120 -37
  46. package/lib/send-image.d.ts +1 -1
  47. package/lib/send-image.js +12 -1
  48. package/lib/share-code-service.d.ts +32 -0
  49. package/lib/share-code-service.js +184 -0
  50. package/lib/subscription-runner.d.ts +6 -0
  51. package/lib/subscription-runner.js +21 -0
  52. package/lib/subscription-send.d.ts +4 -2
  53. package/lib/subscription-send.js +45 -20
  54. package/lib/types.d.ts +5 -0
  55. package/lib/user.d.ts +5 -0
  56. package/package.json +4 -1
  57. package/readme.md +145 -124
@@ -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
- // 新版 /wiki/pet-size/query 返回 items[].pet / egg_size / match 结构;旧接口是平铺字段。
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 petName = petInfo?.name || (typeof item?.pet === 'string' ? item.pet : '') || item?.name || '未知精灵';
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 ?? item?.diameterMin);
297
- const heightMax = num(heightRange?.max_m ?? item?.diameterMax);
298
- const weightMin = num(weightRange?.min_kg ?? item?.weightMin);
299
- const weightMax = num(weightRange?.max_kg ?? item?.weightMax);
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: String(match?.match_percent_text || '') || this.formatMatchSummary(probability, matchCount),
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 = [];
@@ -341,42 +378,6 @@ class EggService {
341
378
  candidates: Array.isArray(results?.candidates) ? results.candidates : [],
342
379
  };
343
380
  }
344
- formatEggSearchCard(item) {
345
- const eggGroups = Array.isArray(item?.egg_groups)
346
- ? item.egg_groups
347
- .map((group) => group?.official_name || group?.display_name || `蛋组${group?.group_id}`)
348
- .filter(Boolean)
349
- : [];
350
- const heightRange = Array.isArray(item?.height_range_m) ? item.height_range_m : [];
351
- const weightRange = Array.isArray(item?.weight_range_kg) ? item.weight_range_kg : [];
352
- const typeLabel = Array.isArray(item?.unit_type) && item.unit_type.length
353
- ? item.unit_type.join(' / ')
354
- : '未知';
355
- return {
356
- id: item?.id || '-',
357
- name: item?.name || '未知精灵',
358
- icon: item?.pet_icon_url || petIconUrl(item?.id),
359
- image: item?.pet_img_url || petImageUrl(item?.id),
360
- type_label: typeLabel,
361
- egg_group_ids: Array.isArray(item?.egg_groups)
362
- ? item.egg_groups.map((group) => Number(group?.group_id)).filter((value) => Number.isFinite(value))
363
- : [],
364
- egg_groups_label: eggGroups.length ? eggGroups.join(' / ') : '暂无蛋组数据',
365
- height_min: num(heightRange[0]),
366
- height_max: num(heightRange[1]),
367
- height_label: fmtRange(num(heightRange[0]), num(heightRange[1]), 'm'),
368
- weight_min: num(weightRange[0]),
369
- weight_max: num(weightRange[1]),
370
- weight_label: fmtRange(num(weightRange[0]), num(weightRange[1]), 'kg'),
371
- probability: null,
372
- match_count: null,
373
- match_info_label: '',
374
- };
375
- }
376
- formatEggSearchTextLine(item) {
377
- const card = this.formatEggSearchCard(item);
378
- return `${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label} · ${card.egg_groups_label}`;
379
- }
380
381
  mergeCardsByName(perfect, ranged) {
381
382
  const perfectMap = new Map();
382
383
  const rangedMap = new Map();
@@ -509,7 +510,7 @@ class EggService {
509
510
  lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${results.perfect.length} 只):`);
510
511
  results.perfect.slice(0, 10).forEach((p, i) => {
511
512
  const br = p.breeding || {};
512
- 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 + '】' : ''}`);
513
514
  });
514
515
  }
515
516
  if (results.range.length) {
@@ -518,7 +519,7 @@ class EggService {
518
519
  lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${results.range.length} 只,容差±15%):`);
519
520
  results.range.slice(0, 10).forEach((p, i) => {
520
521
  const br = p.breeding || {};
521
- 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 + '】' : ''}`);
522
523
  });
523
524
  }
524
525
  return lines.join('\n');
@@ -537,8 +538,8 @@ class EggService {
537
538
  if (exact.length) {
538
539
  lines.push(`✅ 完美匹配 ${condStr} 的精灵(共 ${exact.length} 只):`);
539
540
  exact.slice(0, 10).forEach((item, i) => {
540
- const card = this.formatSizeApiCard(item);
541
- 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}】` : ''}`);
542
543
  });
543
544
  }
544
545
  if (candidates.length) {
@@ -546,32 +547,12 @@ class EggService {
546
547
  lines.push('');
547
548
  lines.push(`🔍 范围匹配 ${condStr} 的精灵(共 ${candidates.length} 只):`);
548
549
  candidates.slice(0, 10).forEach((item, i) => {
549
- const card = this.formatSizeApiCard(item);
550
- 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}】` : ''}`);
551
552
  });
552
553
  }
553
554
  return lines.join('\n');
554
555
  }
555
- buildEggSearchText(heightMeters, weight, results, heightDisplay) {
556
- const cond = [];
557
- if (heightMeters != null)
558
- cond.push(`身高=${heightDisplay || `${formatNumber(heightMeters)}m`}`);
559
- if (weight != null)
560
- cond.push(`体重=${weight}kg`);
561
- const condStr = cond.join(' + ') || '当前条件';
562
- const items = Array.isArray(results?.items) ? results.items : [];
563
- if (!items.length)
564
- return `❌ 未找到符合 ${condStr} 的精灵。`;
565
- const total = results?.total ?? items.length;
566
- const lines = [`✅ 符合 ${condStr} 的精灵(共 ${total} 只):`];
567
- items.slice(0, 10).forEach((item, index) => {
568
- lines.push(` ${index + 1}. ${this.formatEggSearchTextLine(item)}`);
569
- });
570
- if (results?.has_more) {
571
- lines.push(` ... 还有更多结果,可尝试更精确的尺寸或等待后续分页支持。`);
572
- }
573
- return lines.join('\n');
574
- }
575
556
  buildSearchText(pet) {
576
557
  const egs = this.getEggGroups(pet);
577
558
  const compat = this.getCompatiblePets(pet);
@@ -585,6 +566,17 @@ class EggService {
585
566
  lines.push('⚠️ 该精灵属于「未发现」蛋组,无法配种。');
586
567
  return lines.join('\n');
587
568
  }
569
+ buildSearchTextFromWiki(data) {
570
+ const lines = [
571
+ `🥚 ${data?.pet_name || '未知精灵'} (#${data?.pet_id || '-'})`,
572
+ `属性:${data?.type_label || '未知'}`,
573
+ `蛋组:${data?.egg_groups_label || '暂无蛋组数据'}`,
574
+ `${data?.compatible_total_known === false ? '当前预览精灵数' : '可配种精灵数'}:${data?.total_compatible || 0}`,
575
+ ];
576
+ if (data?.is_undiscovered)
577
+ lines.push('⚠️ 该精灵属于「未发现」蛋组,无法配种。');
578
+ return lines.join('\n');
579
+ }
588
580
  buildCandidatesText(keyword, candidates) {
589
581
  const lines = [`🔍 「${keyword}」匹配到 ${candidates.length} 只精灵,请精确输入:`];
590
582
  candidates.slice(0, 10).forEach((p, i) => {
@@ -683,6 +675,120 @@ class EggService {
683
675
  copyright: 'Koishi & WeGame 洛克王国插件',
684
676
  };
685
677
  }
678
+ buildSearchDataFromWiki(pet, compatibleByGroup = {}) {
679
+ const eggGroups = this.wikiEggGroups(pet);
680
+ const eggGroupIds = eggGroups.map(group => group.id);
681
+ const eggGroupLabels = Object.fromEntries(eggGroups.map(group => [group.id, group.name]));
682
+ const seen = new Set();
683
+ let totalCompatible = 0;
684
+ const sections = eggGroups.map(group => {
685
+ const isUndiscovered = group.id === 1 || group.name.includes('未发现');
686
+ if (isUndiscovered) {
687
+ return {
688
+ id: 1,
689
+ label: '未发现',
690
+ desc: '不能和任何精灵生蛋,多用于传说中的精灵',
691
+ count: 0,
692
+ members: [],
693
+ has_more: false,
694
+ total: 0,
695
+ };
696
+ }
697
+ const rawMembers = compatibleByGroup[String(group.id)] || [];
698
+ const members = rawMembers
699
+ .filter(item => String(item?.pet_id ?? item?.id ?? '') !== String(pet?.pet_id ?? pet?.id ?? ''))
700
+ .map(item => {
701
+ const key = String(item?.pet_id ?? item?.id ?? item?.name ?? '');
702
+ if (key && !seen.has(key)) {
703
+ seen.add(key);
704
+ totalCompatible += 1;
705
+ }
706
+ return this.formatWikiMember(item);
707
+ });
708
+ return {
709
+ id: group.id,
710
+ label: group.name,
711
+ desc: '',
712
+ count: members.length,
713
+ members: members.slice(0, 30),
714
+ has_more: members.length > 30,
715
+ total: members.length,
716
+ };
717
+ });
718
+ const petId = pet?.pet_id ?? pet?.id ?? '-';
719
+ const height = pet?.body_size?.height || {};
720
+ const weight = pet?.body_size?.weight || {};
721
+ const attributes = pet?.attributes || {};
722
+ const maleRate = num(pet?.gender_ratio?.male_percent);
723
+ const femaleRate = num(pet?.gender_ratio?.female_percent);
724
+ const totalStats = num(attributes?.sum) ?? ['hp', 'physical_attack', 'magic_attack', 'physical_defense', 'magic_defense', 'speed']
725
+ .reduce((sum, key) => sum + (num(attributes?.[key]) || 0), 0);
726
+ return {
727
+ pet_name: pet?.name || '未知精灵',
728
+ pet_id: petId,
729
+ pet_icon: pet?.icon || pet?.small_icon || petIconUrl(petId),
730
+ pet_image: pet?.small_icon || pet?.icon || petImageUrl(petId),
731
+ type_label: this.wikiTypeLabel(pet),
732
+ egg_groups_label: eggGroups.map(group => group.name).join(' / ') || '暂无蛋组数据',
733
+ egg_groups: eggGroupIds,
734
+ egg_group_labels: eggGroupLabels,
735
+ male_rate: maleRate,
736
+ female_rate: femaleRate,
737
+ hatch_label: '暂无数据',
738
+ weight_label: fmtRange(num(weight?.min_kg), num(weight?.max_kg), 'kg'),
739
+ height_label: fmtRange(num(height?.min_m), num(height?.max_m), 'm'),
740
+ total_compatible: totalCompatible,
741
+ is_undiscovered: eggGroups.some(group => group.id === 1 || group.name.includes('未发现')),
742
+ egg_group_sections: sections,
743
+ total_stats: totalStats,
744
+ egg_details: { has_data: false },
745
+ commandHint: '数据来自新版 Wiki;接口不可用时自动回退本地查蛋',
746
+ copyright: 'Koishi & WeGame 洛克王国插件',
747
+ };
748
+ }
749
+ wikiEggGroups(pet) {
750
+ const groups = Array.isArray(pet?.egg_groups) ? pet.egg_groups : [];
751
+ const normalized = groups
752
+ .map((group) => {
753
+ if (group && typeof group === 'object') {
754
+ const name = String(group?.name || group?.label || '').trim();
755
+ const id = group?.id ?? group?.group_id ?? (name.includes('未发现') ? 1 : name);
756
+ return name ? { id, name } : null;
757
+ }
758
+ const name = String(group || '').trim();
759
+ return name ? { id: name.includes('未发现') ? 1 : name, name } : null;
760
+ })
761
+ .filter(Boolean);
762
+ if (normalized.length)
763
+ return normalized;
764
+ const ids = Array.isArray(pet?.egg_group_ids) ? pet.egg_group_ids : [];
765
+ const names = Array.isArray(pet?.egg_group_names) ? pet.egg_group_names : [];
766
+ return names.map((rawName, index) => {
767
+ const name = String(rawName || '').trim();
768
+ return {
769
+ id: ids[index] ?? (name.includes('未发现') ? 1 : name),
770
+ name,
771
+ };
772
+ }).filter(group => group.name);
773
+ }
774
+ wikiTypeLabel(pet) {
775
+ const typeNames = Array.isArray(pet?.type_names) ? pet.type_names.filter(Boolean) : [];
776
+ if (typeNames.length)
777
+ return typeNames.join(' / ');
778
+ const types = Array.isArray(pet?.types)
779
+ ? pet.types.map((type) => type?.name || type).filter(Boolean)
780
+ : [];
781
+ return types.length ? types.join(' / ') : '未知';
782
+ }
783
+ formatWikiMember(item) {
784
+ const groups = this.wikiEggGroups(item);
785
+ return {
786
+ name: item?.name || '未知精灵',
787
+ id: item?.pet_id ?? item?.id ?? '-',
788
+ type_label: this.wikiTypeLabel(item),
789
+ egg_groups_label: groups.map(group => group.name).join(' / ') || '暂无蛋组数据',
790
+ };
791
+ }
686
792
  buildPairData(a, b) {
687
793
  const ev = this.evaluatePair(a, b);
688
794
  const makePetCard = (p) => ({
@@ -723,6 +829,185 @@ class EggService {
723
829
  copyright: 'Koishi & WeGame 洛克王国插件',
724
830
  };
725
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
+ }
726
1011
  buildSizeSearchData(height, weight, results, heightDisplay) {
727
1012
  const conditions = [];
728
1013
  if (height != null)
@@ -747,8 +1032,11 @@ class EggService {
747
1032
  if (weight != null)
748
1033
  conditions.push(`体重 ${weight} kg`);
749
1034
  const groups = this.sizeApiResultGroups(results);
750
- const [perfect, ranged] = this.mergeCardsByName(groups.exact.map((item) => this.formatSizeApiCard(item)), groups.candidates.map((item) => this.formatSizeApiCard(item)));
751
- const searchMode = results?.searchMode || '';
1035
+ const [perfect, ranged] = this.mergeCardsByName(groups.exact.map((item) => this.formatSizeApiCard(item, weight)), groups.candidates.map((item) => this.formatSizeApiCard(item, weight)));
1036
+ const poolName = results?.query?.pool && typeof results.query.pool === 'object'
1037
+ ? results.query.pool.name
1038
+ : '';
1039
+ const searchMode = results?.searchMode || poolName || '';
752
1040
  const queryLabel = `${conditions.join(' / ') || '尺寸反查'}${searchMode ? ` · 模式 ${searchMode}` : ''}`;
753
1041
  return {
754
1042
  query_label: queryLabel,
@@ -760,28 +1048,8 @@ class EggService {
760
1048
  copyright: 'Koishi & WeGame 洛克王国插件',
761
1049
  };
762
1050
  }
763
- buildEggSearchData(heightMeters, weight, results, heightDisplay) {
764
- const conditions = [];
765
- if (heightMeters != null)
766
- conditions.push(`身高 ${heightDisplay || `${formatNumber(heightMeters)} m`}`);
767
- if (weight != null)
768
- conditions.push(`体重 ${weight} kg`);
769
- const cards = (Array.isArray(results?.items) ? results.items : []).map((item) => this.formatEggSearchCard(item));
770
- const pageNo = num(results?.page_no);
771
- const totalPages = num(results?.total_pages);
772
- const pageLabel = pageNo && totalPages ? ` · 第 ${pageNo}/${totalPages} 页` : '';
773
- return {
774
- query_label: `${conditions.join(' / ') || '孵蛋反查'}${pageLabel}`,
775
- perfect_matches: cards,
776
- range_matches: [],
777
- total_count: results?.total ?? cards.length,
778
- has_results: cards.length > 0,
779
- commandHint: '洛克查蛋 <精灵名> | 洛克查蛋 0.18m 1.5kg | 洛克配种 <父体> <母体>',
780
- copyright: 'Koishi & WeGame 洛克王国插件',
781
- };
782
- }
783
1051
  buildEggDetails(breeding) {
784
- if (!breeding)
1052
+ if (!breeding || !Object.keys(breeding).length)
785
1053
  return { has_data: false };
786
1054
  const baseProb = breeding.egg_base_glass_prob_array;
787
1055
  const addProb = breeding.egg_add_glass_prob_array;
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;