koishi-plugin-rocom 1.0.13 → 1.0.18

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 (37) hide show
  1. package/lib/atlas-service.d.ts +33 -0
  2. package/lib/atlas-service.js +439 -0
  3. package/lib/client.d.ts +20 -2
  4. package/lib/client.js +99 -9
  5. package/lib/commands/account.js +1 -0
  6. package/lib/commands/egg.js +41 -11
  7. package/lib/commands/merchant.js +11 -2
  8. package/lib/commands/query.js +141 -9
  9. package/lib/commands/tools.js +104 -0
  10. package/lib/commands/wiki.js +167 -6
  11. package/lib/egg-service.d.ts +45 -12
  12. package/lib/egg-service.js +177 -90
  13. package/lib/index.d.ts +5 -0
  14. package/lib/index.js +180 -5775
  15. package/lib/pet-data-service.d.ts +62 -0
  16. package/lib/pet-data-service.js +468 -0
  17. package/lib/render-templates/pet-data/index.html +172 -0
  18. package/lib/render-templates/pet-data/style.css +553 -0
  19. package/lib/render-templates/wiki/detail/index.html +84 -0
  20. package/lib/render-templates/wiki/list/index.html +84 -0
  21. package/lib/render-templates/wiki/menu/index.html +65 -0
  22. package/lib/render-templates/wiki/pet/index.html +158 -0
  23. package/lib/render-templates/wiki/pet/style.css +393 -0
  24. package/lib/render-templates/wiki/shared/style.css +431 -0
  25. package/lib/render-templates/wiki/skill/index.html +79 -0
  26. package/lib/render-templates/wiki/skill/style.css +233 -0
  27. package/lib/render-templates/wiki/suggestions/index.html +84 -0
  28. package/lib/render.js +37 -8
  29. package/lib/types.d.ts +9 -1
  30. package/lib/user.d.ts +23 -1
  31. package/lib/user.js +32 -1
  32. package/lib/wiki-catalog.d.ts +19 -0
  33. package/lib/wiki-catalog.js +124 -0
  34. package/lib/wiki-service.d.ts +220 -0
  35. package/lib/wiki-service.js +1463 -0
  36. package/package.json +1 -1
  37. package/readme.md +4 -5
@@ -27,8 +27,8 @@ export declare class EggService {
27
27
  private rangeMatchScore;
28
28
  private formatPetCard;
29
29
  private formatSizeApiCard;
30
- private formatEggSearchCard;
31
- private formatEggSearchTextLine;
30
+ private splitNewSizeApiItems;
31
+ private sizeApiResultGroups;
32
32
  private mergeCardsByName;
33
33
  private mergeSizeCard;
34
34
  private minValue;
@@ -49,8 +49,8 @@ export declare class EggService {
49
49
  range: any[];
50
50
  }, heightDisplay?: string): string;
51
51
  buildSizeSearchTextFromApi(height?: number, weight?: number, results?: any, heightDisplay?: string): string;
52
- buildEggSearchText(heightMeters?: number, weight?: number, results?: any, heightDisplay?: string): string;
53
52
  buildSearchText(pet: any): string;
53
+ buildSearchTextFromWiki(data: any): string;
54
54
  buildCandidatesText(keyword: string, candidates: any[]): string;
55
55
  buildWantPetText(pet: any): string;
56
56
  buildPairText(a: any, b: any): string;
@@ -115,6 +115,48 @@ export declare class EggService {
115
115
  commandHint: string;
116
116
  copyright: string;
117
117
  };
118
+ buildSearchDataFromWiki(pet: any, compatibleByGroup?: Record<string, any[]>): {
119
+ pet_name: any;
120
+ pet_id: any;
121
+ pet_icon: any;
122
+ pet_image: any;
123
+ type_label: string;
124
+ egg_groups_label: string;
125
+ egg_groups: (string | number)[];
126
+ egg_group_labels: {
127
+ [k: string]: string;
128
+ };
129
+ male_rate: number;
130
+ female_rate: number;
131
+ hatch_label: string;
132
+ weight_label: string;
133
+ height_label: string;
134
+ total_compatible: number;
135
+ is_undiscovered: boolean;
136
+ egg_group_sections: {
137
+ id: string | number;
138
+ label: string;
139
+ desc: string;
140
+ count: number;
141
+ members: {
142
+ name: any;
143
+ id: any;
144
+ type_label: string;
145
+ egg_groups_label: string;
146
+ }[];
147
+ has_more: boolean;
148
+ total: number;
149
+ }[];
150
+ total_stats: number;
151
+ egg_details: {
152
+ has_data: boolean;
153
+ };
154
+ commandHint: string;
155
+ copyright: string;
156
+ };
157
+ private wikiEggGroups;
158
+ private wikiTypeLabel;
159
+ private formatWikiMember;
118
160
  buildPairData(a: any, b: any): {
119
161
  commandHint: string;
120
162
  copyright: string;
@@ -228,14 +270,5 @@ export declare class EggService {
228
270
  commandHint: string;
229
271
  copyright: string;
230
272
  };
231
- buildEggSearchData(heightMeters?: number, weight?: number, results?: any, heightDisplay?: string): {
232
- query_label: string;
233
- perfect_matches: any;
234
- range_matches: any[];
235
- total_count: any;
236
- has_results: boolean;
237
- commandHint: string;
238
- copyright: string;
239
- };
240
273
  private buildEggDetails;
241
274
  }
@@ -281,20 +281,30 @@ class EggService {
281
281
  };
282
282
  }
283
283
  formatSizeApiCard(item) {
284
- const probability = num(item?.probability);
284
+ // 新版 /wiki/pet-size/query 返回 items[].pet / egg_size / match 结构;旧接口是平铺字段。
285
+ const petInfo = item?.pet && typeof item.pet === 'object' ? item.pet : {};
286
+ const eggSize = item?.egg_size && typeof item.egg_size === 'object' ? item.egg_size : {};
287
+ const heightRange = eggSize?.height && typeof eggSize.height === 'object' ? eggSize.height : {};
288
+ const weightRange = eggSize?.weight && typeof eggSize.weight === 'object' ? eggSize.weight : {};
289
+ 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 || '未知精灵';
292
+ const typeNames = Array.isArray(petInfo?.type_names) ? petInfo.type_names.filter(Boolean) : [];
293
+ const eggGroupNames = Array.isArray(petInfo?.egg_group_names) ? petInfo.egg_group_names.filter(Boolean) : [];
294
+ const probability = num(item?.probability) ?? num(match?.percent);
285
295
  const matchCount = num(item?.matchCount);
286
- const heightMin = num(item?.diameterMin);
287
- const heightMax = num(item?.diameterMax);
288
- const weightMin = num(item?.weightMin);
289
- const weightMax = num(item?.weightMax);
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);
290
300
  return {
291
- id: item?.petId || '-',
292
- name: item?.pet || '未知精灵',
293
- icon: item?.petIcon || petIconUrl(item?.petId),
294
- image: item?.petImage || petImageUrl(item?.petId),
295
- type_label: '后端未提供',
301
+ id: petId,
302
+ name: petName,
303
+ icon: petInfo?.icon || item?.petIcon || petIconUrl(petId),
304
+ image: petInfo?.small_icon || item?.petImage || petImageUrl(petId),
305
+ type_label: typeNames.length ? typeNames.join(' / ') : '后端未提供',
296
306
  egg_group_ids: [],
297
- egg_groups_label: '后端未提供',
307
+ egg_groups_label: eggGroupNames.length ? eggGroupNames.join(' / ') : '后端未提供',
298
308
  height_min: heightMin,
299
309
  height_max: heightMax,
300
310
  height_label: fmtRange(heightMin, heightMax, 'm'),
@@ -303,45 +313,34 @@ class EggService {
303
313
  weight_label: fmtRange(weightMin, weightMax, 'kg'),
304
314
  probability,
305
315
  match_count: matchCount,
306
- match_info_label: this.formatMatchSummary(probability, matchCount),
316
+ match_info_label: String(match?.match_percent_text || '') || this.formatMatchSummary(probability, matchCount),
307
317
  };
308
318
  }
309
- formatEggSearchCard(item) {
310
- const eggGroups = Array.isArray(item?.egg_groups)
311
- ? item.egg_groups
312
- .map((group) => group?.official_name || group?.display_name || `蛋组${group?.group_id}`)
313
- .filter(Boolean)
314
- : [];
315
- const heightRange = Array.isArray(item?.height_range_m) ? item.height_range_m : [];
316
- const weightRange = Array.isArray(item?.weight_range_kg) ? item.weight_range_kg : [];
317
- const typeLabel = Array.isArray(item?.unit_type) && item.unit_type.length
318
- ? item.unit_type.join(' / ')
319
- : '未知';
319
+ // 新版尺寸反查按 match.layer 划分完美/范围匹配。
320
+ splitNewSizeApiItems(items) {
321
+ const perfect = [];
322
+ const ranged = [];
323
+ for (const item of items) {
324
+ const match = item?.match && typeof item.match === 'object' ? item.match : {};
325
+ const layer = String(match?.layer || '').toLowerCase();
326
+ const displayOnly = Boolean(match?.display_only);
327
+ if (['strict', 'exact'].includes(layer) && !displayOnly)
328
+ perfect.push(item);
329
+ else
330
+ ranged.push(item);
331
+ }
332
+ return [perfect, ranged];
333
+ }
334
+ sizeApiResultGroups(results) {
335
+ if (Array.isArray(results?.items)) {
336
+ const [perfect, ranged] = this.splitNewSizeApiItems(results.items);
337
+ return { exact: perfect, candidates: ranged };
338
+ }
320
339
  return {
321
- id: item?.id || '-',
322
- name: item?.name || '未知精灵',
323
- icon: item?.pet_icon_url || petIconUrl(item?.id),
324
- image: item?.pet_img_url || petImageUrl(item?.id),
325
- type_label: typeLabel,
326
- egg_group_ids: Array.isArray(item?.egg_groups)
327
- ? item.egg_groups.map((group) => Number(group?.group_id)).filter((value) => Number.isFinite(value))
328
- : [],
329
- egg_groups_label: eggGroups.length ? eggGroups.join(' / ') : '暂无蛋组数据',
330
- height_min: num(heightRange[0]),
331
- height_max: num(heightRange[1]),
332
- height_label: fmtRange(num(heightRange[0]), num(heightRange[1]), 'm'),
333
- weight_min: num(weightRange[0]),
334
- weight_max: num(weightRange[1]),
335
- weight_label: fmtRange(num(weightRange[0]), num(weightRange[1]), 'kg'),
336
- probability: null,
337
- match_count: null,
338
- match_info_label: '',
340
+ exact: Array.isArray(results?.exactResults) ? results.exactResults : [],
341
+ candidates: Array.isArray(results?.candidates) ? results.candidates : [],
339
342
  };
340
343
  }
341
- formatEggSearchTextLine(item) {
342
- const card = this.formatEggSearchCard(item);
343
- return `${card.name} (#${card.id}) — ${card.height_label} / ${card.weight_label} · ${card.egg_groups_label}`;
344
- }
345
344
  mergeCardsByName(perfect, ranged) {
346
345
  const perfectMap = new Map();
347
346
  const rangedMap = new Map();
@@ -495,8 +494,7 @@ class EggService {
495
494
  if (weight != null)
496
495
  cond.push(`体重=${weight}kg`);
497
496
  const condStr = cond.join(' + ') || '当前条件';
498
- const exact = results?.exactResults || [];
499
- const candidates = results?.candidates || [];
497
+ const { exact, candidates } = this.sizeApiResultGroups(results);
500
498
  if (!exact.length && !candidates.length)
501
499
  return `❌ 未找到符合 ${condStr} 的精灵。`;
502
500
  const lines = [];
@@ -518,26 +516,6 @@ class EggService {
518
516
  }
519
517
  return lines.join('\n');
520
518
  }
521
- buildEggSearchText(heightMeters, weight, results, heightDisplay) {
522
- const cond = [];
523
- if (heightMeters != null)
524
- cond.push(`身高=${heightDisplay || `${formatNumber(heightMeters)}m`}`);
525
- if (weight != null)
526
- cond.push(`体重=${weight}kg`);
527
- const condStr = cond.join(' + ') || '当前条件';
528
- const items = Array.isArray(results?.items) ? results.items : [];
529
- if (!items.length)
530
- return `❌ 未找到符合 ${condStr} 的精灵。`;
531
- const total = results?.total ?? items.length;
532
- const lines = [`✅ 符合 ${condStr} 的精灵(共 ${total} 只):`];
533
- items.slice(0, 10).forEach((item, index) => {
534
- lines.push(` ${index + 1}. ${this.formatEggSearchTextLine(item)}`);
535
- });
536
- if (results?.has_more) {
537
- lines.push(` ... 还有更多结果,可尝试更精确的尺寸或等待后续分页支持。`);
538
- }
539
- return lines.join('\n');
540
- }
541
519
  buildSearchText(pet) {
542
520
  const egs = this.getEggGroups(pet);
543
521
  const compat = this.getCompatiblePets(pet);
@@ -551,6 +529,17 @@ class EggService {
551
529
  lines.push('⚠️ 该精灵属于「未发现」蛋组,无法配种。');
552
530
  return lines.join('\n');
553
531
  }
532
+ buildSearchTextFromWiki(data) {
533
+ const lines = [
534
+ `🥚 ${data?.pet_name || '未知精灵'} (#${data?.pet_id || '-'})`,
535
+ `属性:${data?.type_label || '未知'}`,
536
+ `蛋组:${data?.egg_groups_label || '暂无蛋组数据'}`,
537
+ `可配种精灵数:${data?.total_compatible || 0}`,
538
+ ];
539
+ if (data?.is_undiscovered)
540
+ lines.push('⚠️ 该精灵属于「未发现」蛋组,无法配种。');
541
+ return lines.join('\n');
542
+ }
554
543
  buildCandidatesText(keyword, candidates) {
555
544
  const lines = [`🔍 「${keyword}」匹配到 ${candidates.length} 只精灵,请精确输入:`];
556
545
  candidates.slice(0, 10).forEach((p, i) => {
@@ -649,6 +638,120 @@ class EggService {
649
638
  copyright: 'Koishi & WeGame 洛克王国插件',
650
639
  };
651
640
  }
641
+ buildSearchDataFromWiki(pet, compatibleByGroup = {}) {
642
+ const eggGroups = this.wikiEggGroups(pet);
643
+ const eggGroupIds = eggGroups.map(group => group.id);
644
+ const eggGroupLabels = Object.fromEntries(eggGroups.map(group => [group.id, group.name]));
645
+ const seen = new Set();
646
+ let totalCompatible = 0;
647
+ const sections = eggGroups.map(group => {
648
+ const isUndiscovered = group.id === 1 || group.name.includes('未发现');
649
+ if (isUndiscovered) {
650
+ return {
651
+ id: 1,
652
+ label: '未发现',
653
+ desc: '不能和任何精灵生蛋,多用于传说中的精灵',
654
+ count: 0,
655
+ members: [],
656
+ has_more: false,
657
+ total: 0,
658
+ };
659
+ }
660
+ const rawMembers = compatibleByGroup[String(group.id)] || [];
661
+ const members = rawMembers
662
+ .filter(item => String(item?.pet_id ?? item?.id ?? '') !== String(pet?.pet_id ?? pet?.id ?? ''))
663
+ .map(item => {
664
+ const key = String(item?.pet_id ?? item?.id ?? item?.name ?? '');
665
+ if (key && !seen.has(key)) {
666
+ seen.add(key);
667
+ totalCompatible += 1;
668
+ }
669
+ return this.formatWikiMember(item);
670
+ });
671
+ return {
672
+ id: group.id,
673
+ label: group.name,
674
+ desc: '',
675
+ count: members.length,
676
+ members: members.slice(0, 30),
677
+ has_more: members.length > 30,
678
+ total: members.length,
679
+ };
680
+ });
681
+ const petId = pet?.pet_id ?? pet?.id ?? '-';
682
+ const height = pet?.body_size?.height || {};
683
+ const weight = pet?.body_size?.weight || {};
684
+ const attributes = pet?.attributes || {};
685
+ const maleRate = num(pet?.gender_ratio?.male_percent);
686
+ const femaleRate = num(pet?.gender_ratio?.female_percent);
687
+ const totalStats = num(attributes?.sum) ?? ['hp', 'physical_attack', 'magic_attack', 'physical_defense', 'magic_defense', 'speed']
688
+ .reduce((sum, key) => sum + (num(attributes?.[key]) || 0), 0);
689
+ return {
690
+ pet_name: pet?.name || '未知精灵',
691
+ pet_id: petId,
692
+ pet_icon: pet?.icon || pet?.small_icon || petIconUrl(petId),
693
+ pet_image: pet?.small_icon || pet?.icon || petImageUrl(petId),
694
+ type_label: this.wikiTypeLabel(pet),
695
+ egg_groups_label: eggGroups.map(group => group.name).join(' / ') || '暂无蛋组数据',
696
+ egg_groups: eggGroupIds,
697
+ egg_group_labels: eggGroupLabels,
698
+ male_rate: maleRate,
699
+ female_rate: femaleRate,
700
+ hatch_label: '暂无数据',
701
+ weight_label: fmtRange(num(weight?.min_kg), num(weight?.max_kg), 'kg'),
702
+ height_label: fmtRange(num(height?.min_m), num(height?.max_m), 'm'),
703
+ total_compatible: totalCompatible,
704
+ is_undiscovered: eggGroups.some(group => group.id === 1 || group.name.includes('未发现')),
705
+ egg_group_sections: sections,
706
+ total_stats: totalStats,
707
+ egg_details: { has_data: false },
708
+ commandHint: '数据来自新版 Wiki;接口不可用时自动回退本地查蛋',
709
+ copyright: 'Koishi & WeGame 洛克王国插件',
710
+ };
711
+ }
712
+ wikiEggGroups(pet) {
713
+ const groups = Array.isArray(pet?.egg_groups) ? pet.egg_groups : [];
714
+ const normalized = groups
715
+ .map((group) => {
716
+ if (group && typeof group === 'object') {
717
+ const name = String(group?.name || group?.label || '').trim();
718
+ const id = group?.id ?? group?.group_id ?? (name.includes('未发现') ? 1 : name);
719
+ return name ? { id, name } : null;
720
+ }
721
+ const name = String(group || '').trim();
722
+ return name ? { id: name.includes('未发现') ? 1 : name, name } : null;
723
+ })
724
+ .filter(Boolean);
725
+ if (normalized.length)
726
+ return normalized;
727
+ const ids = Array.isArray(pet?.egg_group_ids) ? pet.egg_group_ids : [];
728
+ const names = Array.isArray(pet?.egg_group_names) ? pet.egg_group_names : [];
729
+ return names.map((rawName, index) => {
730
+ const name = String(rawName || '').trim();
731
+ return {
732
+ id: ids[index] ?? (name.includes('未发现') ? 1 : name),
733
+ name,
734
+ };
735
+ }).filter(group => group.name);
736
+ }
737
+ wikiTypeLabel(pet) {
738
+ const typeNames = Array.isArray(pet?.type_names) ? pet.type_names.filter(Boolean) : [];
739
+ if (typeNames.length)
740
+ return typeNames.join(' / ');
741
+ const types = Array.isArray(pet?.types)
742
+ ? pet.types.map((type) => type?.name || type).filter(Boolean)
743
+ : [];
744
+ return types.length ? types.join(' / ') : '未知';
745
+ }
746
+ formatWikiMember(item) {
747
+ const groups = this.wikiEggGroups(item);
748
+ return {
749
+ name: item?.name || '未知精灵',
750
+ id: item?.pet_id ?? item?.id ?? '-',
751
+ type_label: this.wikiTypeLabel(item),
752
+ egg_groups_label: groups.map(group => group.name).join(' / ') || '暂无蛋组数据',
753
+ };
754
+ }
652
755
  buildPairData(a, b) {
653
756
  const ev = this.evaluatePair(a, b);
654
757
  const makePetCard = (p) => ({
@@ -712,8 +815,12 @@ class EggService {
712
815
  conditions.push(`身高 ${heightDisplay || fmtRange(ht(height), ht(height), 'm')}`);
713
816
  if (weight != null)
714
817
  conditions.push(`体重 ${weight} kg`);
715
- const [perfect, ranged] = this.mergeCardsByName((results?.exactResults || []).map((item) => this.formatSizeApiCard(item)), (results?.candidates || []).map((item) => this.formatSizeApiCard(item)));
716
- const searchMode = results?.searchMode || '';
818
+ 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)));
820
+ const poolName = results?.query?.pool && typeof results.query.pool === 'object'
821
+ ? results.query.pool.name
822
+ : '';
823
+ const searchMode = results?.searchMode || poolName || '';
717
824
  const queryLabel = `${conditions.join(' / ') || '尺寸反查'}${searchMode ? ` · 模式 ${searchMode}` : ''}`;
718
825
  return {
719
826
  query_label: queryLabel,
@@ -725,28 +832,8 @@ class EggService {
725
832
  copyright: 'Koishi & WeGame 洛克王国插件',
726
833
  };
727
834
  }
728
- buildEggSearchData(heightMeters, weight, results, heightDisplay) {
729
- const conditions = [];
730
- if (heightMeters != null)
731
- conditions.push(`身高 ${heightDisplay || `${formatNumber(heightMeters)} m`}`);
732
- if (weight != null)
733
- conditions.push(`体重 ${weight} kg`);
734
- const cards = (Array.isArray(results?.items) ? results.items : []).map((item) => this.formatEggSearchCard(item));
735
- const pageNo = num(results?.page_no);
736
- const totalPages = num(results?.total_pages);
737
- const pageLabel = pageNo && totalPages ? ` · 第 ${pageNo}/${totalPages} 页` : '';
738
- return {
739
- query_label: `${conditions.join(' / ') || '孵蛋反查'}${pageLabel}`,
740
- perfect_matches: cards,
741
- range_matches: [],
742
- total_count: results?.total ?? cards.length,
743
- has_results: cards.length > 0,
744
- commandHint: '洛克查蛋 <精灵名> | 洛克查蛋 0.18m 1.5kg | 洛克配种 <父体> <母体>',
745
- copyright: 'Koishi & WeGame 洛克王国插件',
746
- };
747
- }
748
835
  buildEggDetails(breeding) {
749
- if (!breeding)
836
+ if (!breeding || !Object.keys(breeding).length)
750
837
  return { has_data: false };
751
838
  const baseProb = breeding.egg_base_glass_prob_array;
752
839
  const addProb = breeding.egg_add_glass_prob_array;
package/lib/index.d.ts CHANGED
@@ -19,9 +19,14 @@ export interface Config {
19
19
  merchantCheckTimes: string[];
20
20
  homeSubscriptionEnabled: boolean;
21
21
  homeSubscriptionIntervalMinutes: number;
22
+ announcementSubscriptionEnabled: boolean;
23
+ announcementPollIntervalMinutes: number;
22
24
  homeQueryWaitMs: number;
23
25
  homeQueryPollIntervalMs: number;
24
26
  homeQueryTimeoutMs: number;
27
+ lowBandwidthMode: boolean;
28
+ atlasZipUrls: string[];
29
+ atlasGitUrl: string;
25
30
  imageCompressionEnabled: boolean;
26
31
  imageCompressionMinBytes: number;
27
32
  imageCompressionLevel: number;