koishi-plugin-rocom 1.0.18 → 1.0.24
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/atlas-service.js +1 -1
- package/lib/client.d.ts +58 -7
- package/lib/client.js +282 -127
- package/lib/commands/egg.js +66 -4
- package/lib/commands/merchant.d.ts +105 -0
- package/lib/commands/merchant.js +600 -182
- package/lib/commands/query.d.ts +18 -0
- package/lib/commands/query.js +585 -145
- 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 +111 -0
- package/lib/commands/tools.js +48 -60
- package/lib/commands/wiki.js +1 -1
- package/lib/egg-service.d.ts +82 -8
- package/lib/egg-service.js +235 -19
- package/lib/element-colors.d.ts +3 -0
- package/lib/element-colors.js +34 -0
- package/lib/icon-metrics.d.ts +32 -0
- package/lib/icon-metrics.js +255 -0
- 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 +10 -0
- package/lib/index.js +11857 -175
- package/lib/merchant-catalog.d.ts +47 -0
- package/lib/merchant-catalog.js +193 -0
- package/lib/merchant-clock.d.ts +22 -0
- package/lib/merchant-clock.js +95 -0
- package/lib/merchant-price-log.d.ts +63 -0
- package/lib/merchant-price-log.js +187 -0
- package/lib/merchant-service.d.ts +38 -0
- package/lib/merchant-service.js +216 -0
- package/lib/permissions.d.ts +16 -0
- package/lib/permissions.js +66 -0
- package/lib/pet-data-service.d.ts +13 -0
- package/lib/pet-data-service.js +117 -10
- 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/home/style.css +15 -14
- package/lib/render-templates/home-pets/index.html +80 -0
- package/lib/render-templates/home-pets/style.css +338 -0
- package/lib/render-templates/pet-detail-classic/index.html +151 -0
- package/lib/render-templates/pet-detail-classic/style.css +503 -0
- package/lib/render-templates/pet-detail-classic/texture2D/a_title.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/bg.jpg +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/footer.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/icon.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/img_head.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/jinhua_bg.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/level_icon.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/line.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/mask_bar.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/pet_bg.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/pet_rocom.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/pet_rocom_bg.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/poro_bar.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/right_jinhua.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/skill_bg.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/skill_mask.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/star.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/star_1.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/star_8.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/star_9.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/table.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/tags.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/title.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/top_bg.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/xuancai_overlay.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/yise_overlay.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D/ys_bar.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/10.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/11.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/12.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/13.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/14.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/15.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/16.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/17.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/18.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/19.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/2.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/20.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/23.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/3.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/4.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/5.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/6.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/8.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/9.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/205/211.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/206/260.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/234/260.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/271/273.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/271/275.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/201/266.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/227/240.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/231/256/351/200/232.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/234/272/346/242/260.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/255/246.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/257/222.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/260/264.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//347/201/253.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//347/224/265.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//347/277/274.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//350/215/211.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//350/220/214.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//350/231/253.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//351/276/231.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/1.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/10.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/11.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/12.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/13.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/14.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/15.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/16.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/17.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/18.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/19.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/2.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/21.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/23.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/24.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/3.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/4.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/5.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/6.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/7.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/8.png +0 -0
- package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/9.png +0 -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-classic/assets/eit-logo.png +0 -0
- package/lib/render-templates/share-code-classic/assets/img_xingxingdi.png +0 -0
- package/lib/render-templates/share-code-classic/assets/roco_icon.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/255/227/344/275/223/Rocofontblack-Regular .ttf +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/205/211.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/206/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/234/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/271/273.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/271/275.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/201/266.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/227/240.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/231/256/351/200/232.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/234/272/346/242/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/255/246.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/257/222.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/260/264.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//347/201/253.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//347/224/265.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//347/277/274.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//350/215/211.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//350/220/214.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//350/231/253.png +0 -0
- package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//351/276/231.png +0 -0
- package/lib/render-templates/share-code-classic/assets//347/201/253/346/274/206/345/215/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//347/253/213/347/273/230/350/203/214/346/231/257/346/241/206.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/205/211.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/206/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/234/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/245/207/345/274/202.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/271/273.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/271/275.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/201/266.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/231/256/351/200/232.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/234/272/346/242/260.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/255/246.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/257/222.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/260/264.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/261/241/346/237/223.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//347/201/253.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//347/224/265.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//347/277/274.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//350/215/211.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//350/220/214.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//350/231/253.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//351/246/226/351/242/206.png +0 -0
- package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//351/276/231.png +0 -0
- package/lib/render-templates/share-code-classic/index.html +119 -0
- package/lib/render-templates/share-code-classic/style.css +646 -0
- 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-templates/yuanxing-shangren/merchant.html +479 -461
- package/lib/render-templates/yuanxing-shangren/today.html +407 -461
- package/lib/render.d.ts +18 -2
- package/lib/render.js +138 -39
- package/lib/send-image.d.ts +1 -1
- package/lib/send-image.js +22 -1
- package/lib/share-code-service.d.ts +68 -0
- package/lib/share-code-service.js +257 -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/ttf/rocom_origin.ttf +0 -0
- package/lib/types.d.ts +14 -1
- package/lib/user.d.ts +11 -0
- package/lib/wiki-catalog.js +1 -1
- package/lib/wiki-service.js +1 -1
- package/package.json +5 -1
- package/readme.md +125 -124
- package/lib/ttf/rocom_skill_origin.ttf +0 -0
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 转换───
|
|
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
|
: '';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ELEMENT_COLORS = void 0;
|
|
4
|
+
exports.elementColor = elementColor;
|
|
5
|
+
exports.bloodlineElement = bloodlineElement;
|
|
6
|
+
// 元素配色表,供信笺风阵容分享与家园详情 classic 模板共用。
|
|
7
|
+
exports.ELEMENT_COLORS = {
|
|
8
|
+
草: '#4ebc73',
|
|
9
|
+
火: '#db5525',
|
|
10
|
+
水: '#3f89b4',
|
|
11
|
+
光: '#6aa9fe',
|
|
12
|
+
地: '#9a7e3f',
|
|
13
|
+
冰: '#63aeda',
|
|
14
|
+
龙: '#ed4962',
|
|
15
|
+
电: '#e7c506',
|
|
16
|
+
毒: '#ba62e0',
|
|
17
|
+
虫: '#96ca0f',
|
|
18
|
+
武: '#ff9636',
|
|
19
|
+
翼: '#3ec7ca',
|
|
20
|
+
萌: '#fc74a7',
|
|
21
|
+
幽: '#9446ec',
|
|
22
|
+
恶: '#cf467a',
|
|
23
|
+
机械: '#40cba9',
|
|
24
|
+
幻: '#9fa7f8',
|
|
25
|
+
普通: '#babbc6',
|
|
26
|
+
无: '#babbc6',
|
|
27
|
+
};
|
|
28
|
+
function elementColor(name) {
|
|
29
|
+
return exports.ELEMENT_COLORS[String(name || '').trim()] || '#a687d5';
|
|
30
|
+
}
|
|
31
|
+
// 「烈焰血脉」→「火」这类血脉名到元素名的归一。
|
|
32
|
+
function bloodlineElement(name) {
|
|
33
|
+
return String(name || '').replace(/系血脉$/, '').replace(/血脉$/, '').trim() || '普通';
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ImageContentBox {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ImageMetrics {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
content: ImageContentBox;
|
|
11
|
+
}
|
|
12
|
+
export interface IconFit {
|
|
13
|
+
scale: number;
|
|
14
|
+
offsetX: number;
|
|
15
|
+
offsetY: number;
|
|
16
|
+
}
|
|
17
|
+
/** 读一张图的尺寸与内容框;无法识别时返回 null。 */
|
|
18
|
+
export declare function readImageMetrics(buffer: Buffer): ImageMetrics | null;
|
|
19
|
+
export interface IconFitBox {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
targetArea: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 把「等面积归一」算成 CSS transform 参数。
|
|
26
|
+
* `.goods-img` 本身 object-fit: contain 按整张画布贴边居中,这里再叠一层
|
|
27
|
+
* 以框中心为原点的 translate(...) scale(...):
|
|
28
|
+
* - scale 把不透明内容缩放到 targetArea 对应面积,上限是不溢出图标框;
|
|
29
|
+
* - translate 写在 scale 前,矩阵乘法上位移不会被 scale 再放大,
|
|
30
|
+
* 用于把不在画布中心的内容挪回框中心。
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildIconFit(metrics: ImageMetrics | null | undefined, options: IconFitBox): IconFit | null;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// 图片尺寸与「不透明内容框」测量,用于商品图标等面积归一。
|
|
3
|
+
// 商品图标画布大小、四周留白、内容长宽比都不一致,object-fit: contain 按整张画布贴边,
|
|
4
|
+
// 会导致同一张卡片里圆球占满、细长商品偏小。这里量出不透明内容包围盒供缩放换算。
|
|
5
|
+
// PNG 只精确支持 8bit 非隔行且带独立 alpha 的变体;WebP 只读画布尺寸;其余返回 null 按原样渲染。
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.readImageMetrics = readImageMetrics;
|
|
11
|
+
exports.buildIconFit = buildIconFit;
|
|
12
|
+
const node_zlib_1 = __importDefault(require("node:zlib"));
|
|
13
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
14
|
+
/** PNG 颜色类型 → 每像素通道数 */
|
|
15
|
+
const PNG_CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
|
|
16
|
+
/** 带独立 alpha 通道的颜色类型 → alpha 在像素里的下标 */
|
|
17
|
+
const PNG_ALPHA_INDEX = { 4: 1, 6: 3 };
|
|
18
|
+
function paeth(a, b, c) {
|
|
19
|
+
const p = a + b - c;
|
|
20
|
+
const pa = Math.abs(p - a);
|
|
21
|
+
const pb = Math.abs(p - b);
|
|
22
|
+
const pc = Math.abs(p - c);
|
|
23
|
+
if (pa <= pb && pa <= pc)
|
|
24
|
+
return a;
|
|
25
|
+
if (pb <= pc)
|
|
26
|
+
return b;
|
|
27
|
+
return c;
|
|
28
|
+
}
|
|
29
|
+
/** 按 PNG 的 5 种行过滤器还原一行原始像素 */
|
|
30
|
+
function unfilterScanline(src, dest, prev, filter, bpp) {
|
|
31
|
+
const length = src.length;
|
|
32
|
+
if (filter === 0) {
|
|
33
|
+
src.copy(dest, 0, 0, length);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (filter === 1) {
|
|
37
|
+
for (let i = 0; i < length; i += 1) {
|
|
38
|
+
dest[i] = (src[i] + (i >= bpp ? dest[i - bpp] : 0)) & 0xff;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (filter === 2) {
|
|
43
|
+
for (let i = 0; i < length; i += 1) {
|
|
44
|
+
dest[i] = (src[i] + prev[i]) & 0xff;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (filter === 3) {
|
|
49
|
+
for (let i = 0; i < length; i += 1) {
|
|
50
|
+
const left = i >= bpp ? dest[i - bpp] : 0;
|
|
51
|
+
dest[i] = (src[i] + ((left + prev[i]) >> 1)) & 0xff;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (filter === 4) {
|
|
56
|
+
for (let i = 0; i < length; i += 1) {
|
|
57
|
+
const left = i >= bpp ? dest[i - bpp] : 0;
|
|
58
|
+
const up = prev[i];
|
|
59
|
+
const upLeft = i >= bpp ? prev[i - bpp] : 0;
|
|
60
|
+
dest[i] = (src[i] + paeth(left, up, upLeft)) & 0xff;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
function readPngHeader(buffer) {
|
|
67
|
+
if (buffer.length < 33)
|
|
68
|
+
return null;
|
|
69
|
+
if (buffer.toString('ascii', 12, 16) !== 'IHDR')
|
|
70
|
+
return null;
|
|
71
|
+
const width = buffer.readUInt32BE(16);
|
|
72
|
+
const height = buffer.readUInt32BE(20);
|
|
73
|
+
if (width <= 0 || height <= 0)
|
|
74
|
+
return null;
|
|
75
|
+
return {
|
|
76
|
+
width,
|
|
77
|
+
height,
|
|
78
|
+
bitDepth: buffer[24],
|
|
79
|
+
colorType: buffer[25],
|
|
80
|
+
interlace: buffer[28],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** 量 PNG 的不透明内容框;只处理 8bit 非隔行带独立 alpha 的图,其余返回 null(整张都算内容)。 */
|
|
84
|
+
function readPngContentBox(buffer, header) {
|
|
85
|
+
const alphaIndex = PNG_ALPHA_INDEX[header.colorType];
|
|
86
|
+
if (alphaIndex === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
if (header.bitDepth !== 8 || header.interlace !== 0)
|
|
89
|
+
return null;
|
|
90
|
+
const channels = PNG_CHANNELS[header.colorType];
|
|
91
|
+
const stride = header.width * channels;
|
|
92
|
+
if (stride <= 0)
|
|
93
|
+
return null;
|
|
94
|
+
const idatParts = [];
|
|
95
|
+
let offset = 8;
|
|
96
|
+
while (offset + 8 <= buffer.length) {
|
|
97
|
+
const length = buffer.readUInt32BE(offset);
|
|
98
|
+
const type = buffer.toString('ascii', offset + 4, offset + 8);
|
|
99
|
+
const dataStart = offset + 8;
|
|
100
|
+
if (dataStart + length > buffer.length)
|
|
101
|
+
break;
|
|
102
|
+
if (type === 'IDAT')
|
|
103
|
+
idatParts.push(buffer.subarray(dataStart, dataStart + length));
|
|
104
|
+
if (type === 'IEND')
|
|
105
|
+
break;
|
|
106
|
+
offset = dataStart + length + 4;
|
|
107
|
+
}
|
|
108
|
+
if (idatParts.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
let raw;
|
|
111
|
+
try {
|
|
112
|
+
raw = node_zlib_1.default.inflateSync(Buffer.concat(idatParts));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
let current = Buffer.alloc(stride);
|
|
118
|
+
let previous = Buffer.alloc(stride);
|
|
119
|
+
let cursor = 0;
|
|
120
|
+
let minX = header.width;
|
|
121
|
+
let minY = header.height;
|
|
122
|
+
let maxX = -1;
|
|
123
|
+
let maxY = -1;
|
|
124
|
+
for (let y = 0; y < header.height; y += 1) {
|
|
125
|
+
if (cursor + 1 + stride > raw.length)
|
|
126
|
+
return null;
|
|
127
|
+
const filter = raw[cursor];
|
|
128
|
+
cursor += 1;
|
|
129
|
+
const line = raw.subarray(cursor, cursor + stride);
|
|
130
|
+
cursor += stride;
|
|
131
|
+
if (!unfilterScanline(line, current, previous, filter, channels))
|
|
132
|
+
return null;
|
|
133
|
+
for (let x = 0; x < header.width; x += 1) {
|
|
134
|
+
if (current[(x * channels) + alphaIndex] === 0)
|
|
135
|
+
continue;
|
|
136
|
+
if (x < minX)
|
|
137
|
+
minX = x;
|
|
138
|
+
if (x > maxX)
|
|
139
|
+
maxX = x;
|
|
140
|
+
if (y < minY)
|
|
141
|
+
minY = y;
|
|
142
|
+
if (y > maxY)
|
|
143
|
+
maxY = y;
|
|
144
|
+
}
|
|
145
|
+
const swap = previous;
|
|
146
|
+
previous = current;
|
|
147
|
+
current = swap;
|
|
148
|
+
}
|
|
149
|
+
if (maxX < 0)
|
|
150
|
+
return null;
|
|
151
|
+
return {
|
|
152
|
+
x: minX,
|
|
153
|
+
y: minY,
|
|
154
|
+
width: (maxX - minX) + 1,
|
|
155
|
+
height: (maxY - minY) + 1,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** 读 WebP 画布尺寸(VP8X / VP8L / VP8) */
|
|
159
|
+
function readWebpSize(buffer) {
|
|
160
|
+
if (buffer.length < 30)
|
|
161
|
+
return null;
|
|
162
|
+
if (buffer.toString('ascii', 0, 4) !== 'RIFF')
|
|
163
|
+
return null;
|
|
164
|
+
if (buffer.toString('ascii', 8, 12) !== 'WEBP')
|
|
165
|
+
return null;
|
|
166
|
+
const chunk = buffer.toString('ascii', 12, 16);
|
|
167
|
+
if (chunk === 'VP8X') {
|
|
168
|
+
const width = 1 + (buffer[24] | (buffer[25] << 8) | (buffer[26] << 16));
|
|
169
|
+
const height = 1 + (buffer[27] | (buffer[28] << 8) | (buffer[29] << 16));
|
|
170
|
+
return { width, height };
|
|
171
|
+
}
|
|
172
|
+
if (chunk === 'VP8L') {
|
|
173
|
+
const bits = buffer.readUInt32LE(21);
|
|
174
|
+
return {
|
|
175
|
+
width: (bits & 0x3fff) + 1,
|
|
176
|
+
height: ((bits >> 14) & 0x3fff) + 1,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (chunk === 'VP8 ') {
|
|
180
|
+
return {
|
|
181
|
+
width: buffer.readUInt16LE(26) & 0x3fff,
|
|
182
|
+
height: buffer.readUInt16LE(28) & 0x3fff,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/** 读一张图的尺寸与内容框;无法识别时返回 null。 */
|
|
188
|
+
function readImageMetrics(buffer) {
|
|
189
|
+
if (!Buffer.isBuffer(buffer) || buffer.length < 16)
|
|
190
|
+
return null;
|
|
191
|
+
if (buffer.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
192
|
+
const header = readPngHeader(buffer);
|
|
193
|
+
if (!header)
|
|
194
|
+
return null;
|
|
195
|
+
return {
|
|
196
|
+
width: header.width,
|
|
197
|
+
height: header.height,
|
|
198
|
+
content: readPngContentBox(buffer, header) || {
|
|
199
|
+
x: 0,
|
|
200
|
+
y: 0,
|
|
201
|
+
width: header.width,
|
|
202
|
+
height: header.height,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const webp = readWebpSize(buffer);
|
|
207
|
+
if (webp) {
|
|
208
|
+
return {
|
|
209
|
+
width: webp.width,
|
|
210
|
+
height: webp.height,
|
|
211
|
+
content: { x: 0, y: 0, width: webp.width, height: webp.height },
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
function round4(value) {
|
|
217
|
+
// +0 归一掉 -0,避免模板里出现 "scale(-0)" 之类的输出
|
|
218
|
+
return Math.round(value * 10000) / 10000 + 0;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 把「等面积归一」算成 CSS transform 参数。
|
|
222
|
+
* `.goods-img` 本身 object-fit: contain 按整张画布贴边居中,这里再叠一层
|
|
223
|
+
* 以框中心为原点的 translate(...) scale(...):
|
|
224
|
+
* - scale 把不透明内容缩放到 targetArea 对应面积,上限是不溢出图标框;
|
|
225
|
+
* - translate 写在 scale 前,矩阵乘法上位移不会被 scale 再放大,
|
|
226
|
+
* 用于把不在画布中心的内容挪回框中心。
|
|
227
|
+
*/
|
|
228
|
+
function buildIconFit(metrics, options) {
|
|
229
|
+
const boxWidth = Number(options.width) || 0;
|
|
230
|
+
const boxHeight = Number(options.height) || 0;
|
|
231
|
+
const targetArea = Number(options.targetArea) || 0;
|
|
232
|
+
if (boxWidth <= 0 || boxHeight <= 0 || targetArea <= 0)
|
|
233
|
+
return null;
|
|
234
|
+
const width = Number(metrics?.width) || 0;
|
|
235
|
+
const height = Number(metrics?.height) || 0;
|
|
236
|
+
const content = metrics?.content;
|
|
237
|
+
const contentWidth = Number(content?.width) || 0;
|
|
238
|
+
const contentHeight = Number(content?.height) || 0;
|
|
239
|
+
if (width <= 0 || height <= 0 || contentWidth <= 0 || contentHeight <= 0)
|
|
240
|
+
return null;
|
|
241
|
+
const containScale = Math.min(boxWidth / width, boxHeight / height);
|
|
242
|
+
if (!Number.isFinite(containScale) || containScale <= 0)
|
|
243
|
+
return null;
|
|
244
|
+
const contentScale = Math.min(Math.sqrt(targetArea / (contentWidth * contentHeight)), boxWidth / contentWidth, boxHeight / contentHeight);
|
|
245
|
+
if (!Number.isFinite(contentScale) || contentScale <= 0)
|
|
246
|
+
return null;
|
|
247
|
+
const contentCenterX = (Number(content?.x) || 0) + (contentWidth / 2);
|
|
248
|
+
const contentCenterY = (Number(content?.y) || 0) + (contentHeight / 2);
|
|
249
|
+
const scale = contentScale / containScale;
|
|
250
|
+
return {
|
|
251
|
+
scale: round4(scale),
|
|
252
|
+
offsetX: round4(-contentScale * (contentCenterX - (width / 2))),
|
|
253
|
+
offsetY: round4(-contentScale * (contentCenterY - (height / 2))),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
Binary file
|