koishi-plugin-warframe 1.16.1 → 1.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1542,15 +1542,6 @@ function levenshtein(a, b) {
1542
1542
  return dp[a.length][b.length];
1543
1543
  }
1544
1544
  __name(levenshtein, "levenshtein");
1545
- function tokenSimilarity(a, b) {
1546
- const tokenize = /* @__PURE__ */ __name((s) => s.replace(/[^\w\u4E00-\u9FA5]+/g, " ").split(/\s+/).filter(Boolean), "tokenize");
1547
- const A = new Set(tokenize(a));
1548
- const B = new Set(tokenize(b));
1549
- const intersection = [...A].filter((x) => B.has(x)).length;
1550
- const union = (/* @__PURE__ */ new Set([...A, ...B])).size;
1551
- return intersection / union;
1552
- }
1553
- __name(tokenSimilarity, "tokenSimilarity");
1554
1545
 
1555
1546
  // src/warframe/utils/time.ts
1556
1547
  function toTimeStamp(timeStr) {
@@ -1619,7 +1610,7 @@ async function getHotRivenWeapons2() {
1619
1610
  __name(getHotRivenWeapons2, "getHotRivenWeapons");
1620
1611
 
1621
1612
  // src/warframe/services/wf-service/wf-service.alert.ts
1622
- var import_warframe_public_export_plus5 = require("warframe-public-export-plus");
1613
+ var import_warframe_public_export_plus6 = require("warframe-public-export-plus");
1623
1614
 
1624
1615
  // src/warframe/assets/index.ts
1625
1616
  var {
@@ -2098,6 +2089,9 @@ async function globalWorldStateFactory(json) {
2098
2089
  __name(globalWorldStateFactory, "globalWorldStateFactory");
2099
2090
  var globalWorldState = createAsyncCache(globalWorldStateFactory, -1);
2100
2091
 
2092
+ // src/warframe/infrastructure/wf/alert-adapter.ts
2093
+ var import_warframe_public_export_plus5 = require("warframe-public-export-plus");
2094
+
2101
2095
  // src/warframe/infrastructure/wf/bounty-adapter.ts
2102
2096
  var import_warframe_public_export_plus4 = require("warframe-public-export-plus");
2103
2097
  var bountyLocationTag = {
@@ -2506,6 +2500,56 @@ function adaptOracleBountyBoard(location, cycle) {
2506
2500
  }
2507
2501
  __name(adaptOracleBountyBoard, "adaptOracleBountyBoard");
2508
2502
 
2503
+ // src/warframe/infrastructure/wf/alert-adapter.ts
2504
+ var alertTranslations = createAsyncCache(async () => {
2505
+ const { default: worldstateData } = await import("warframe-worldstate-data");
2506
+ const { languageString } = await import("warframe-worldstate-data/utilities");
2507
+ const entries = Object.entries({ ...dictZhExtra, ...import_warframe_public_export_plus5.dict_zh });
2508
+ const byKey = new Map(entries.map(([key, value]) => [key.toLowerCase(), value]));
2509
+ const byEnglish = /* @__PURE__ */ new Map();
2510
+ for (const [key, english] of Object.entries({ ...dictEnExtra, ...import_warframe_public_export_plus5.dict_en })) {
2511
+ const chinese = byKey.get(key.toLowerCase());
2512
+ if (chinese) {
2513
+ byEnglish.set(english.toLowerCase(), chinese);
2514
+ }
2515
+ }
2516
+ for (const [key, entry] of Object.entries(worldstateData.languages)) {
2517
+ const chinese = byKey.get(key.toLowerCase()) ?? worldstateData.zh?.languages[key]?.value;
2518
+ if (chinese && chinese !== entry.value) {
2519
+ byEnglish.set(entry.value.toLowerCase(), chinese);
2520
+ }
2521
+ }
2522
+ const aliases2 = /* @__PURE__ */ new Map();
2523
+ for (const [key, chinese] of entries) {
2524
+ if (!/\/Alerts\/|\/[^/]*Alert(?:Title|Desc)$/i.test(key)) {
2525
+ continue;
2526
+ }
2527
+ const english = languageString(key, "en").toLowerCase();
2528
+ aliases2.set(english, aliases2.has(english) && aliases2.get(english) !== chinese ? void 0 : chinese);
2529
+ }
2530
+ for (const [english, chinese] of aliases2) {
2531
+ if (chinese && !byEnglish.has(english)) {
2532
+ byEnglish.set(english, chinese);
2533
+ }
2534
+ }
2535
+ return { byKey, byEnglish };
2536
+ }, -1);
2537
+ async function translateAlertText(source) {
2538
+ const { byKey, byEnglish } = await alertTranslations.get();
2539
+ const translated = byKey.get(source.toLowerCase()) ?? byEnglish.get(source.toLowerCase());
2540
+ if (translated) {
2541
+ return translated;
2542
+ }
2543
+ if (source.startsWith("/Lotus/")) {
2544
+ const itemName = resolveExportItemNameZh(source);
2545
+ if (itemName !== source.split("/").pop() && !itemName.startsWith("/Lotus/")) {
2546
+ return itemName;
2547
+ }
2548
+ }
2549
+ return source;
2550
+ }
2551
+ __name(translateAlertText, "translateAlertText");
2552
+
2509
2553
  // src/warframe/services/wf-service/wf-service.alert.ts
2510
2554
  var ALERT_TITLE_KEY = "/Lotus/Language/Menu/AlertPopup_Alert";
2511
2555
  var ALERT_NIGHTMARE_KEY = "/Lotus/Language/Menu/NightmareModeName";
@@ -2517,13 +2561,14 @@ async function adaptAlerts(alerts = [], now = Date.now()) {
2517
2561
  continue;
2518
2562
  }
2519
2563
  const mission = raw.mission ?? {};
2520
- const typeKey = mission.typeKey ?? "";
2564
+ const typeKey = mission.typeKey || mission.type || "";
2521
2565
  const receivedType = await getMissionTypeKey(typeKey);
2522
- const nodeKey = mission.nodeKey ?? "";
2566
+ const missionType = import_warframe_public_export_plus6.ExportMissionTypes[typeKey] ?? import_warframe_public_export_plus6.ExportMissionTypes[receivedType];
2567
+ const nodeKey = mission.nodeKey || mission.node || "";
2523
2568
  const solNodeKey = await getSolNodeKey(nodeKey);
2524
- const region = solNodeKey ? import_warframe_public_export_plus5.ExportRegions[solNodeKey] : void 0;
2525
- const short = region ? regionToShort(region, import_warframe_public_export_plus5.dict_zh) : {
2526
- name: nodeKey,
2569
+ const region = import_warframe_public_export_plus6.ExportRegions[nodeKey] ?? import_warframe_public_export_plus6.ExportRegions[solNodeKey];
2570
+ const short = region ? regionToShort(region, import_warframe_public_export_plus6.dict_zh) : {
2571
+ name: await translateAlertText(nodeKey),
2527
2572
  system: "",
2528
2573
  type: "",
2529
2574
  faction: "",
@@ -2532,19 +2577,26 @@ async function adaptAlerts(alerts = [], now = Date.now()) {
2532
2577
  };
2533
2578
  const reward = mission.reward;
2534
2579
  const rewards = [];
2580
+ const mirroredItems = (reward?.countedItems ?? []).filter((item) => item.uniqueName && item.count === 1).map((item) => [item.uniqueName, item.key, item.type]);
2535
2581
  for (const item of reward?.items ?? []) {
2582
+ const mirrored = mirroredItems.findIndex((names) => names.includes(item));
2583
+ if (mirrored >= 0) {
2584
+ mirroredItems.splice(mirrored, 1);
2585
+ continue;
2586
+ }
2536
2587
  rewards.push({
2537
- name: item.startsWith("/Lotus") ? resolveExportItemNameZh(item) : item,
2588
+ name: await translateAlertText(item),
2538
2589
  count: 1
2539
2590
  });
2540
2591
  }
2541
2592
  for (const item of reward?.countedItems ?? []) {
2542
- const source = item.key ?? item.type ?? "";
2593
+ const source = item.uniqueName || item.key || item.type || "";
2543
2594
  if (!source) {
2544
2595
  continue;
2545
2596
  }
2597
+ const name2 = await translateAlertText(source);
2546
2598
  rewards.push({
2547
- name: source.startsWith("/Lotus") ? resolveExportItemNameZh(source) : source,
2599
+ name: name2 === source ? await translateAlertText(item.key || item.type || source) : name2,
2548
2600
  count: item.count ?? 1
2549
2601
  });
2550
2602
  }
@@ -2554,9 +2606,10 @@ async function adaptAlerts(alerts = [], now = Date.now()) {
2554
2606
  count: 1
2555
2607
  });
2556
2608
  }
2557
- const nightmare = mission.nightmare ? import_warframe_public_export_plus5.dict_zh[ALERT_NIGHTMARE_KEY] ?? dictZhExtra[ALERT_NIGHTMARE_KEY] : void 0;
2609
+ const nightmare = mission.nightmare ? import_warframe_public_export_plus6.dict_zh[ALERT_NIGHTMARE_KEY] ?? dictZhExtra[ALERT_NIGHTMARE_KEY] : void 0;
2558
2610
  adapted.push({
2559
- type: import_warframe_public_export_plus5.dict_zh[import_warframe_public_export_plus5.ExportMissionTypes[receivedType]?.name ?? ""] ?? typeKey,
2611
+ description: await translateAlertText(raw.description || mission.description || ""),
2612
+ type: import_warframe_public_export_plus6.dict_zh[missionType?.name ?? ""] ?? await translateAlertText(typeKey),
2560
2613
  node: {
2561
2614
  ...short,
2562
2615
  minLevel: mission.minEnemyLevel ?? 0,
@@ -2569,7 +2622,7 @@ async function adaptAlerts(alerts = [], now = Date.now()) {
2569
2622
  });
2570
2623
  }
2571
2624
  return {
2572
- title: import_warframe_public_export_plus5.dict_zh[ALERT_TITLE_KEY] ?? dictZhExtra[ALERT_TITLE_KEY] ?? ALERT_TITLE_KEY,
2625
+ title: import_warframe_public_export_plus6.dict_zh[ALERT_TITLE_KEY] ?? dictZhExtra[ALERT_TITLE_KEY] ?? ALERT_TITLE_KEY,
2573
2626
  alerts: adapted
2574
2627
  };
2575
2628
  }
@@ -2592,7 +2645,7 @@ async function getAlerts() {
2592
2645
  __name(getAlerts, "getAlerts");
2593
2646
 
2594
2647
  // src/warframe/services/wf-service/wf-service.arbitration.ts
2595
- var import_warframe_public_export_plus6 = require("warframe-public-export-plus");
2648
+ var import_warframe_public_export_plus7 = require("warframe-public-export-plus");
2596
2649
 
2597
2650
  // src/warframe/data/wf/arbitrationSchedule.ts
2598
2651
  var arbitrationSchedule = arbitrationScheduleText.split("\n").map((line) => line.split(",")).filter((arr) => arr.length === 2).map((arr) => {
@@ -2620,7 +2673,7 @@ function getArbitrations(day = 3) {
2620
2673
  return {
2621
2674
  ok: true,
2622
2675
  data: weekArbys.filter((a) => arbyRewards[a.node]).map((a) => {
2623
- const obj = regionToShort(import_warframe_public_export_plus6.ExportRegions[a.node], import_warframe_public_export_plus6.dict_zh);
2676
+ const obj = regionToShort(import_warframe_public_export_plus7.ExportRegions[a.node], import_warframe_public_export_plus7.dict_zh);
2624
2677
  return {
2625
2678
  ...obj,
2626
2679
  time: new Date(a.time * 1e3).toLocaleString("zh-cn", {
@@ -2685,7 +2738,7 @@ async function getBounty(location) {
2685
2738
  __name(getBounty, "getBounty");
2686
2739
 
2687
2740
  // src/warframe/infrastructure/wf/calendar-adapter.ts
2688
- var import_warframe_public_export_plus7 = require("warframe-public-export-plus");
2741
+ var import_warframe_public_export_plus8 = require("warframe-public-export-plus");
2689
2742
  var CALENDAR_SEASON_HEADER_KEY = "/Lotus/Language/1999/CalendarSeasonHeader";
2690
2743
  var CALENDAR_HEADER_KEY = "/Lotus/Language/1999/CalendarHeader";
2691
2744
  var SEASON_KEYS = {
@@ -2722,7 +2775,7 @@ function calendarTimestampMs(value) {
2722
2775
  }
2723
2776
  __name(calendarTimestampMs, "calendarTimestampMs");
2724
2777
  function translate(key, fallback = key) {
2725
- return stripLotusMarkup(import_warframe_public_export_plus7.dict_zh[key] ?? dictZhExtra[key] ?? fallback);
2778
+ return stripLotusMarkup(import_warframe_public_export_plus8.dict_zh[key] ?? dictZhExtra[key] ?? fallback);
2726
2779
  }
2727
2780
  __name(translate, "translate");
2728
2781
  function seasonLabel(season) {
@@ -2756,7 +2809,7 @@ function stripLotusMarkup(text) {
2756
2809
  }
2757
2810
  __name(stripLotusMarkup, "stripLotusMarkup");
2758
2811
  function adaptChallenge(path) {
2759
- const exported = import_warframe_public_export_plus7.ExportChallenges[path];
2812
+ const exported = import_warframe_public_export_plus8.ExportChallenges[path];
2760
2813
  if (!exported?.name) {
2761
2814
  return { name: pathLeaf(path) };
2762
2815
  }
@@ -2800,7 +2853,7 @@ function plotCharacterName(id) {
2800
2853
  }
2801
2854
  for (const keyId of keyIds) {
2802
2855
  const key = `/Lotus/Language/1999/Messenger${keyId}Name`;
2803
- const text = import_warframe_public_export_plus7.dict_zh[key] ?? dictZhExtra[key];
2856
+ const text = import_warframe_public_export_plus8.dict_zh[key] ?? dictZhExtra[key];
2804
2857
  if (text) {
2805
2858
  return stripLotusMarkup(text);
2806
2859
  }
@@ -2890,14 +2943,14 @@ async function getCalendar() {
2890
2943
  __name(getCalendar, "getCalendar");
2891
2944
 
2892
2945
  // src/warframe/services/wf-service/wf-service.circuit.ts
2893
- var import_warframe_public_export_plus8 = require("warframe-public-export-plus");
2946
+ var import_warframe_public_export_plus9 = require("warframe-public-export-plus");
2894
2947
  function getCircuitWeek() {
2895
2948
  const EPOCH = 1734307200 * 1e3;
2896
2949
  const week = Math.trunc((Date.now() - EPOCH) / 6048e5);
2897
2950
  const index1 = (week + 1) % incarnons.length;
2898
2951
  const index2 = (week + 8) % warframes.length;
2899
- const incarnons2 = incarnons.map((v) => v.map((i) => import_warframe_public_export_plus8.dict_zh[i]));
2900
- const warframes2 = warframes.map((v) => v.map((i) => import_warframe_public_export_plus8.dict_zh[i]));
2952
+ const incarnons2 = incarnons.map((v) => v.map((i) => import_warframe_public_export_plus9.dict_zh[i]));
2953
+ const warframes2 = warframes.map((v) => v.map((i) => import_warframe_public_export_plus9.dict_zh[i]));
2901
2954
  return {
2902
2955
  currentIncarnons: index1,
2903
2956
  currentWarframes: index2,
@@ -2940,10 +2993,10 @@ ${zariman}`;
2940
2993
  __name(getEnvironment, "getEnvironment");
2941
2994
 
2942
2995
  // src/warframe/services/wf-service/wf-service.event.ts
2943
- var import_warframe_public_export_plus9 = require("warframe-public-export-plus");
2996
+ var import_warframe_public_export_plus10 = require("warframe-public-export-plus");
2944
2997
  var EVENT_TITLE_KEY = "/Lotus/Language/Menu/WorldStatePanel_Event";
2945
2998
  function translate2(key, fallback = key) {
2946
- return stripLotusMarkup(import_warframe_public_export_plus9.dict_zh[key] ?? dictZhExtra[key] ?? fallback);
2999
+ return stripLotusMarkup(import_warframe_public_export_plus10.dict_zh[key] ?? dictZhExtra[key] ?? fallback);
2947
3000
  }
2948
3001
  __name(translate2, "translate");
2949
3002
  function mongoDateMs2(value) {
@@ -3023,7 +3076,7 @@ function adaptEvents(rawEvents = [], now = Date.now()) {
3023
3076
  const maximumScore = raw.Goal ?? 0;
3024
3077
  const currentScore = raw.Count ?? 0;
3025
3078
  const progress = maximumScore > 0 ? Math.min(1, Math.max(0, currentScore / maximumScore)) : typeof raw.HealthPct === "number" ? Math.min(1, Math.max(0, raw.HealthPct)) : void 0;
3026
- const region = raw.Node && import_warframe_public_export_plus9.ExportRegions[raw.Node] ? regionToShort(import_warframe_public_export_plus9.ExportRegions[raw.Node], import_warframe_public_export_plus9.dict_zh) : void 0;
3079
+ const region = raw.Node && import_warframe_public_export_plus10.ExportRegions[raw.Node] ? regionToShort(import_warframe_public_export_plus10.ExportRegions[raw.Node], import_warframe_public_export_plus10.dict_zh) : void 0;
3027
3080
  adapted.push({
3028
3081
  id: raw._id?.$oid ?? raw._id?.$id ?? raw.Tag ?? name2,
3029
3082
  name: name2,
@@ -3091,7 +3144,7 @@ async function getRailjackFissures() {
3091
3144
  __name(getRailjackFissures, "getRailjackFissures");
3092
3145
 
3093
3146
  // src/warframe/services/wf-service/wf-service.invasion.ts
3094
- var import_warframe_public_export_plus10 = require("warframe-public-export-plus");
3147
+ var import_warframe_public_export_plus11 = require("warframe-public-export-plus");
3095
3148
  var INVASION_TITLE_KEY = "/Lotus/Language/Menu/WorldStatePanel_Invasions";
3096
3149
  var INFESTED_INVASION_BOSS_TAG = "/Lotus/Language/Menu/InfestedInvasionBoss";
3097
3150
  function invasionProgress(count, goal, vsInfested) {
@@ -3121,9 +3174,9 @@ function invasionSide(faction, reward) {
3121
3174
  })),
3122
3175
  ...reward.credits && reward.credits > 0 ? [{ name: resolveExportItemNameZh(`/${reward.credits}Credits`), count: 1 }] : []
3123
3176
  ];
3124
- const nameKey = faction ? import_warframe_public_export_plus10.ExportFactions[faction]?.name ?? faction : void 0;
3177
+ const nameKey = faction ? import_warframe_public_export_plus11.ExportFactions[faction]?.name ?? faction : void 0;
3125
3178
  return {
3126
- faction: nameKey ? import_warframe_public_export_plus10.dict_zh[nameKey] ?? dictZhExtra[nameKey] ?? "" : "",
3179
+ faction: nameKey ? import_warframe_public_export_plus11.dict_zh[nameKey] ?? dictZhExtra[nameKey] ?? "" : "",
3127
3180
  tone: invasionTone(faction),
3128
3181
  rewards
3129
3182
  };
@@ -3144,9 +3197,9 @@ async function adaptInvasions(rawInvasions) {
3144
3197
  raw.DefenderMissionInfo?.faction
3145
3198
  ].some((faction) => /infest/i.test(faction ?? ""));
3146
3199
  const nodeId = raw.Node;
3147
- const solNodeKey = import_warframe_public_export_plus10.ExportRegions[nodeId] ? nodeId : await getSolNodeKey(nodeId);
3148
- const region = solNodeKey ? import_warframe_public_export_plus10.ExportRegions[solNodeKey] : void 0;
3149
- const short = region ? regionToShort(region, import_warframe_public_export_plus10.dict_zh) : void 0;
3200
+ const solNodeKey = import_warframe_public_export_plus11.ExportRegions[nodeId] ? nodeId : await getSolNodeKey(nodeId);
3201
+ const region = solNodeKey ? import_warframe_public_export_plus11.ExportRegions[solNodeKey] : void 0;
3202
+ const short = region ? regionToShort(region, import_warframe_public_export_plus11.dict_zh) : void 0;
3150
3203
  const locTag = raw.LocTag ?? "";
3151
3204
  drafts.push({
3152
3205
  id: raw._id?.$oid ?? raw._id?.$id ?? raw.Node,
@@ -3154,7 +3207,7 @@ async function adaptInvasions(rawInvasions) {
3154
3207
  node: short?.name ?? "",
3155
3208
  systemIndex: region?.systemIndex ?? Number.MAX_SAFE_INTEGER,
3156
3209
  locTag,
3157
- campaign: locTag ? import_warframe_public_export_plus10.dict_zh[locTag] ?? dictZhExtra[locTag] ?? await translateLanguageString(locTag) : "",
3210
+ campaign: locTag ? import_warframe_public_export_plus11.dict_zh[locTag] ?? dictZhExtra[locTag] ?? await translateLanguageString(locTag) : "",
3158
3211
  count,
3159
3212
  goal,
3160
3213
  attacker: invasionSide(raw.DefenderMissionInfo?.faction ?? raw.Faction, raw.AttackerReward),
@@ -3169,7 +3222,7 @@ async function adaptInvasions(rawInvasions) {
3169
3222
  groups.set(key, [...groups.get(key) ?? [], draft]);
3170
3223
  }
3171
3224
  return {
3172
- title: import_warframe_public_export_plus10.dict_zh[INVASION_TITLE_KEY] ?? dictZhExtra[INVASION_TITLE_KEY] ?? INVASION_TITLE_KEY,
3225
+ title: import_warframe_public_export_plus11.dict_zh[INVASION_TITLE_KEY] ?? dictZhExtra[INVASION_TITLE_KEY] ?? INVASION_TITLE_KEY,
3173
3226
  planets: [...groups.values()].sort((a, b) => a[0].systemIndex - b[0].systemIndex).map((list) => {
3174
3227
  const boss = list.filter((draft) => draft.locTag === INFESTED_INVASION_BOSS_TAG);
3175
3228
  const ordered = [...boss, ...list.filter((draft) => draft.locTag !== INFESTED_INVASION_BOSS_TAG)];
@@ -3224,7 +3277,7 @@ async function getInvasions() {
3224
3277
  __name(getInvasions, "getInvasions");
3225
3278
 
3226
3279
  // src/warframe/infrastructure/wf/nightwave-adapter.ts
3227
- var import_warframe_public_export_plus11 = require("warframe-public-export-plus");
3280
+ var import_warframe_public_export_plus12 = require("warframe-public-export-plus");
3228
3281
  var RADIO_LEGION_TITLE_KEY = "/Lotus/Language/Syndicates/RadioLegionTitle";
3229
3282
  function timestampMs(value) {
3230
3283
  const raw = value?.$date?.$numberLong;
@@ -3240,11 +3293,11 @@ function remainingLabel(expiry, now) {
3240
3293
  }
3241
3294
  __name(remainingLabel, "remainingLabel");
3242
3295
  function translate3(key) {
3243
- return import_warframe_public_export_plus11.dict_zh[key] ?? dictZhExtra[key] ?? key;
3296
+ return import_warframe_public_export_plus12.dict_zh[key] ?? dictZhExtra[key] ?? key;
3244
3297
  }
3245
3298
  __name(translate3, "translate");
3246
3299
  function seasonTitle(affiliationTag) {
3247
- const syndicate = import_warframe_public_export_plus11.ExportSyndicates[affiliationTag];
3300
+ const syndicate = import_warframe_public_export_plus12.ExportSyndicates[affiliationTag];
3248
3301
  if (syndicate?.name) {
3249
3302
  return translate3(syndicate.name);
3250
3303
  }
@@ -3279,7 +3332,7 @@ function adaptChallenge2(raw, now) {
3279
3332
  }
3280
3333
  const kind = classifyChallenge(path, raw);
3281
3334
  const remaining = remainingLabel(expiry, now);
3282
- const exported = import_warframe_public_export_plus11.ExportNightwave.challenges[path];
3335
+ const exported = import_warframe_public_export_plus12.ExportNightwave.challenges[path];
3283
3336
  if (!exported) {
3284
3337
  return {
3285
3338
  name: challengeFallbackName(path),
@@ -3337,12 +3390,12 @@ async function getNightwave() {
3337
3390
  __name(getNightwave, "getNightwave");
3338
3391
 
3339
3392
  // src/warframe/data/wf/relics.ts
3340
- var import_warframe_public_export_plus12 = require("warframe-public-export-plus");
3393
+ var import_warframe_public_export_plus13 = require("warframe-public-export-plus");
3341
3394
  var relics = (() => {
3342
3395
  const result = {};
3343
- for (const key in import_warframe_public_export_plus12.ExportRelics) {
3344
- const exportRelic = import_warframe_public_export_plus12.ExportRelics[key];
3345
- const exportRewards = import_warframe_public_export_plus12.ExportRewards[exportRelic.rewardManifest];
3396
+ for (const key in import_warframe_public_export_plus13.ExportRelics) {
3397
+ const exportRelic = import_warframe_public_export_plus13.ExportRelics[key];
3398
+ const exportRewards = import_warframe_public_export_plus13.ExportRewards[exportRelic.rewardManifest];
3346
3399
  const era = `/Lotus/Language/Relics/Era_${exportRelic.era.toUpperCase()}`;
3347
3400
  const relicKey = normalizeName(exportRelic.era + exportRelic.category);
3348
3401
  const rewards = (exportRewards[0] ?? []).map((r) => {
@@ -3410,7 +3463,7 @@ async function getRelic(input) {
3410
3463
  __name(getRelic, "getRelic");
3411
3464
 
3412
3465
  // src/warframe/infrastructure/wf/riven-export-adapter.ts
3413
- var import_warframe_public_export_plus13 = require("warframe-public-export-plus");
3466
+ var import_warframe_public_export_plus14 = require("warframe-public-export-plus");
3414
3467
  var rivenDefinitionTypes = {
3415
3468
  LotusRifleRandomModRare: "Rifle",
3416
3469
  LotusShotgunRandomModRare: "Shotgun",
@@ -3504,7 +3557,7 @@ function adaptRivenDefinitions(definitions) {
3504
3557
  const englishName = tag === "WeaponMeleeDamageMod" ? "Damage" : tag === "WeaponFireRateMod" ? "Fire Rate / Attack Speed" : templateName;
3505
3558
  const name2 = splicedNames[tag] ?? traditionalNames[tag] ?? englishName;
3506
3559
  const unit = tag.includes("FactionDamage") ? "multiply" : raw.locTag.includes("%") ? "percent" : /\|s\b/.test(raw.locTag) ? "seconds" : "";
3507
- const metadata = import_warframe_public_export_plus13.ExportUpgrades[definition.uniqueName]?.upgradeEntries?.find((x) => x.tag === tag);
3560
+ const metadata = import_warframe_public_export_plus14.ExportUpgrades[definition.uniqueName]?.upgradeEntries?.find((x) => x.tag === tag);
3508
3561
  const baseValue = raw.value * 90 * (unit === "percent" ? 100 : 1) * (raw.reverseValueSymbol ? -1 : 1);
3509
3562
  result[type].push({
3510
3563
  gameRef: tag,
@@ -3537,9 +3590,9 @@ var rivenAttrValueDict = Object.fromEntries(
3537
3590
  );
3538
3591
 
3539
3592
  // src/warframe/data/wf/rivenDisposition.ts
3540
- var import_warframe_public_export_plus14 = require("warframe-public-export-plus");
3593
+ var import_warframe_public_export_plus15 = require("warframe-public-export-plus");
3541
3594
  var weaponTypes = {};
3542
- for (const [path, definition] of Object.entries(import_warframe_public_export_plus14.ExportUpgrades)) {
3595
+ for (const [path, definition] of Object.entries(import_warframe_public_export_plus15.ExportUpgrades)) {
3543
3596
  const type = rivenDefinitionTypes[path.split("/").pop()];
3544
3597
  if (!type)
3545
3598
  continue;
@@ -3577,7 +3630,7 @@ __name(buildWeaponRivenDispositionDict, "buildWeaponRivenDispositionDict");
3577
3630
  for (const weapon of rivenPublicExport.weapons) {
3578
3631
  if (weaponTypes[weapon.uniqueName])
3579
3632
  continue;
3580
- const metadata = import_warframe_public_export_plus14.ExportWeapons[weapon.uniqueName];
3633
+ const metadata = import_warframe_public_export_plus15.ExportWeapons[weapon.uniqueName];
3581
3634
  const category = weapon.productCategory;
3582
3635
  const type = category === "Melee" ? "Melee" : category === "SpaceGuns" ? "Archgun" : category === "Pistols" ? "Pistol" : category === "LongGuns" && metadata?.holsterCategory ? metadata.holsterCategory === "SHOTGUN" ? "Shotgun" : "Rifle" : void 0;
3583
3636
  if (type)
@@ -3744,8 +3797,8 @@ async function getTextFromTencentOCR(image, secret) {
3744
3797
  const OcrClient = tencentcloud.ocr.v20181119.Client;
3745
3798
  const clientConfig = {
3746
3799
  credential: {
3747
- secretId: secret.id,
3748
- secretKey: secret.key
3800
+ secretId: secret.id.trim(),
3801
+ secretKey: secret.key.trim()
3749
3802
  },
3750
3803
  region: "",
3751
3804
  profile: {
@@ -3758,10 +3811,17 @@ async function getTextFromTencentOCR(image, secret) {
3758
3811
  const { TextDetections } = await client.GeneralAccurateOCR({
3759
3812
  ImageBase64: image
3760
3813
  });
3761
- const result = TextDetections?.map((t2) => t2.DetectedText).filter(
3762
- Boolean
3763
- );
3764
- return result;
3814
+ return (TextDetections ?? []).flatMap((t2) => {
3815
+ const text = t2.DetectedText;
3816
+ if (!text?.trim())
3817
+ return [];
3818
+ const box = t2.ItemPolygon;
3819
+ return [{
3820
+ text,
3821
+ confidence: t2.Confidence,
3822
+ box: box ? { x: box.X, y: box.Y, width: box.Width, height: box.Height } : void 0
3823
+ }];
3824
+ });
3765
3825
  }
3766
3826
  __name(getTextFromTencentOCR, "getTextFromTencentOCR");
3767
3827
  async function extractTextFromImage(image, secret) {
@@ -3778,6 +3838,172 @@ async function extractTextFromImage(image, secret) {
3778
3838
  }
3779
3839
  __name(extractTextFromImage, "extractTextFromImage");
3780
3840
 
3841
+ // src/warframe/services/riven/ocr.ts
3842
+ function cleanText(text) {
3843
+ return fullWidthToHalfWidth(text).trim().replace(/−/g, "-").replace(/×/g, "x");
3844
+ }
3845
+ __name(cleanText, "cleanText");
3846
+ function validBox(box) {
3847
+ return !!box && Object.values(box).every(Number.isFinite) && box.width > 0 && box.height > 0;
3848
+ }
3849
+ __name(validBox, "validBox");
3850
+ function restoreRows(input) {
3851
+ const blocks = input.map((b) => typeof b === "string" ? { text: b } : b).map((b) => ({ ...b, text: cleanText(b.text) })).filter((b) => b.text);
3852
+ const positioned = blocks.filter((b) => validBox(b.box)).length;
3853
+ if (positioned > 0 && positioned !== blocks.length)
3854
+ return void 0;
3855
+ if (!positioned)
3856
+ return blocks.map((b) => ({ text: b.text, fragmented: false }));
3857
+ const rows = [];
3858
+ for (const block of [...blocks].sort((a, b) => a.box.y + a.box.height / 2 - b.box.y - b.box.height / 2)) {
3859
+ const box = block.box;
3860
+ const row = rows.find((r) => {
3861
+ const anchor = r[0].box;
3862
+ return Math.abs(box.y + box.height / 2 - anchor.y - anchor.height / 2) <= Math.min(box.height, anchor.height) * 0.6 || /^\d+%$/.test(block.text) && box.height < anchor.height * 0.65 && box.y >= anchor.y && box.y + box.height <= anchor.y + anchor.height;
3863
+ });
3864
+ if (row)
3865
+ row.push(block);
3866
+ else
3867
+ rows.push([block]);
3868
+ }
3869
+ const result = [];
3870
+ for (const row of rows) {
3871
+ const parts = row.sort((a, b) => a.box.x - b.box.x);
3872
+ const first = parts[0];
3873
+ const last = parts.at(-1);
3874
+ const fragmented = parts.length > 1 && /^[+\-x]?\d+(?:\.\d+)?%?$/i.test(first.text) && /\p{L}/u.test(last.text);
3875
+ const kept = parts.filter((b, i) => !(fragmented && i > 0 && i < parts.length - 1 && /^\d+%$/.test(b.text) && b.box.height < Math.min(first.box.height, last.box.height) * 0.65));
3876
+ if (fragmented && kept.slice(1).some((b) => /^[+\-x]?\d/i.test(b.text)))
3877
+ return void 0;
3878
+ for (let i = 1; i < kept.length; i++) {
3879
+ const left = kept[i - 1].box;
3880
+ const right = kept[i].box;
3881
+ if (!isMetadata(first.text) && right.x - left.x - left.width > Math.max(left.height, right.height) * 4)
3882
+ return void 0;
3883
+ }
3884
+ result.push({ text: kept.map((b) => b.text).join(" "), fragmented });
3885
+ }
3886
+ return result;
3887
+ }
3888
+ __name(restoreRows, "restoreRows");
3889
+ var weaponNames = [...new Map(Object.values(weaponRivenDispositionDict).flatMap(
3890
+ (weapon) => [weapon.name.zh, weapon.name.en].map((display) => {
3891
+ const name2 = display.replace(/\s*\((?:Primary|Secondary)\)|([主副]武器)/g, "");
3892
+ return [normalizeName(name2), { name: name2, type: weapon.calc.riventype }];
3893
+ })
3894
+ )).entries()].sort((a, b) => b[0].length - a[0].length);
3895
+ var rivenNameParts = [...new Set(rivenPublicExport.upgrades.flatMap((d) => d.upgradeEntries.flatMap((e) => [e.prefixTag, e.suffixTag]).filter((s) => !!s && /^[a-z]+$/.test(s))))];
3896
+ var rivenNamePattern = new RegExp(`^(?:${rivenNameParts.join("|")}){1,3}$`, "i");
3897
+ function isRivenName(text) {
3898
+ return rivenNamePattern.test(normalizeName(text));
3899
+ }
3900
+ __name(isRivenName, "isRivenName");
3901
+ function findWeapon(text) {
3902
+ const normalized = normalizeName(text);
3903
+ for (const [key, weapon] of weaponNames) {
3904
+ if (normalized === key)
3905
+ return weapon;
3906
+ const remainder = normalized.slice(key.length);
3907
+ if (normalized.startsWith(key) && isRivenName(remainder) && !weaponNames.some(([name2]) => name2 === remainder))
3908
+ return weapon;
3909
+ }
3910
+ }
3911
+ __name(findWeapon, "findWeapon");
3912
+ function normalizeStat(text) {
3913
+ return normalizeName(text).replace(/的/g, "").replace(/暴击率/g, "暴击几率").replace(/弹药最大值/g, "弹药上限").replace(/触发时间/g, "触发持续时间").replace(/触发伤害/g, "异常状态伤害").replace(/orokin/g, "奥罗金").replace(/techrot/g, "科腐者").replace(/scaldra/g, "炽蛇军");
3914
+ }
3915
+ __name(normalizeStat, "normalizeStat");
3916
+ var elemental = /Weapon(?:Electricity|Fire|Freeze|Toxin|Impact|ArmorPiercing|Slash|Blast|Corrosive|Gas|Magnetic|Radiation|Viral)DamageMod/;
3917
+ function aliases(stat) {
3918
+ const names = stat.aliases.flatMap((name2) => name2.split("/"));
3919
+ if (elemental.test(stat.gameRef))
3920
+ names.push(`${stat.name}伤害`);
3921
+ if (stat.gameRef === "WeaponDamageAmountMod" || stat.gameRef === "WeaponMeleeDamageMod")
3922
+ names.push("伤害");
3923
+ return [...new Set(names.map(normalizeStat))];
3924
+ }
3925
+ __name(aliases, "aliases");
3926
+ function matchStat(label, stats, fragmented) {
3927
+ const input = normalizeStat(label);
3928
+ const candidates = stats.map((stat) => ({ stat, names: aliases(stat) }));
3929
+ const exact = candidates.filter((c) => c.names.includes(input));
3930
+ if (exact.length)
3931
+ return exact.length === 1 ? exact[0].stat : void 0;
3932
+ const icon = candidates.filter((c) => elemental.test(c.stat.gameRef) && /^[入人]/.test(input) && c.names.includes(input.slice(1)));
3933
+ if (icon.length === 1)
3934
+ return icon[0].stat;
3935
+ if (fragmented && /^[\u4E00-\u9FFF]{3,}$/.test(input)) {
3936
+ const suffix = candidates.filter((c) => c.names.some((name2) => name2.endsWith(input) && name2.length - input.length <= 3));
3937
+ if (suffix.length)
3938
+ return suffix.length === 1 ? suffix[0].stat : void 0;
3939
+ }
3940
+ const ranked = candidates.map((c) => ({ stat: c.stat, score: Math.max(...c.names.map((name2) => normalSimilarity(name2, input))) })).sort((a, b) => b.score - a.score);
3941
+ if (candidates.some((c) => c.names.some((name2) => name2.length >= 3 && input !== name2 && (input.endsWith(name2) || name2.endsWith(input)))))
3942
+ return void 0;
3943
+ return ranked[0]?.score >= 0.8 && ranked[0].score - (ranked[1]?.score ?? 0) >= 0.15 ? ranked[0].stat : void 0;
3944
+ }
3945
+ __name(matchStat, "matchStat");
3946
+ function isRank(text) {
3947
+ return /^(?:段位|等级|循环)\s*\d|^\d+\s*(?:段位|等级|次|循环)/.test(text);
3948
+ }
3949
+ __name(isRank, "isRank");
3950
+ function isMetadata(text) {
3951
+ return isRank(text) || /^\d[\d\s~\-]*$/.test(text);
3952
+ }
3953
+ __name(isMetadata, "isMetadata");
3954
+ function parseRivenOcr(input) {
3955
+ const rows = restoreRows(input);
3956
+ if (!rows?.length)
3957
+ return void 0;
3958
+ const titles = rows.map((r, index) => ({ weapon: findWeapon(r.text), index })).filter((t2) => t2.weapon);
3959
+ if (titles.length !== 1)
3960
+ return void 0;
3961
+ const title = titles[0];
3962
+ if (rows.slice(0, title.index).some((r) => /^6?[+\-x]\d|^\d+(?:\.\d+)?%/i.test(r.text)))
3963
+ return void 0;
3964
+ const weapon = title.weapon;
3965
+ const stats = rivenStatsByType[weapon.type];
3966
+ if (!stats)
3967
+ return void 0;
3968
+ const attributes = [];
3969
+ let footer = false;
3970
+ for (let i = title.index + 1; i < rows.length; i++) {
3971
+ const row = rows[i];
3972
+ let text = row.text.replace(/\s/g, "");
3973
+ if (isRank(text)) {
3974
+ footer = true;
3975
+ continue;
3976
+ }
3977
+ if (footer && /^\d+$/.test(text))
3978
+ continue;
3979
+ if (!attributes.length && isRivenName(text))
3980
+ continue;
3981
+ text = text.replace(/^[6🔒]([+\-])/u, "$1");
3982
+ if (text.includes("(") && !text.includes(")") && rows[i + 1] && !/^[+\-x\d]/i.test(rows[i + 1].text)) {
3983
+ text += rows[++i].text.replace(/\s/g, "");
3984
+ }
3985
+ text = text.replace(/\((?:弓类武器效果加倍|重击时x2|x2forBows|x2forHeavyAttacks)\)/gi, "");
3986
+ const match = text.match(/^([+\-x]?)(\d+(?:\.\d+)?)(%|s)?([\p{L}/]+)$/iu);
3987
+ if (!match)
3988
+ return void 0;
3989
+ const [, symbol, number, unit, label] = match;
3990
+ if (!symbol && input.some((b) => typeof b !== "string"))
3991
+ return void 0;
3992
+ const attr = matchStat(label, stats, row.fragmented);
3993
+ if (!attr || attributes.some((a) => a.attr.gameRef === attr.gameRef))
3994
+ return void 0;
3995
+ const prefix = symbol.toLowerCase();
3996
+ if (prefix === "x" !== (attr.unit === "multiply") || unit === "%" && attr.unit !== "percent" || unit === "s" && attr.unit !== "seconds")
3997
+ return void 0;
3998
+ const value = Number(number) * (prefix === "-" ? -1 : 1);
3999
+ if (!Number.isFinite(value) || value === 0)
4000
+ return void 0;
4001
+ attributes.push({ attr, value, prefix });
4002
+ }
4003
+ return attributes.length >= 2 && attributes.length <= 4 ? { name: weapon.name, attributes } : void 0;
4004
+ }
4005
+ __name(parseRivenOcr, "parseRivenOcr");
4006
+
3781
4007
  // src/warframe/services/wf-service/wf-service.riven.ts
3782
4008
  async function getAnalyzedRiven(secret, url, variant) {
3783
4009
  const img = await fetchAsyncImage(url);
@@ -3912,138 +4138,7 @@ function getWeaponRivenDisposition(name2) {
3912
4138
  }
3913
4139
  __name(getWeaponRivenDisposition, "getWeaponRivenDisposition");
3914
4140
  async function parseOCRResult(ocrResult) {
3915
- const globalRivenCalculationAttributeList = rivenAttributes;
3916
- const list = ocrResult;
3917
- if (!list.length) {
3918
- return;
3919
- }
3920
- function normalizeStatName(text) {
3921
- return normalizeName(text).replace(/的/g, "").replace(/暴击率/g, "暴击几率").replace(/弹药最大值/g, "弹药上限").replace(/^伤害$/, "基础伤害").replace(/触发伤害/g, "异常状态伤害").replace(/orokin/g, "奥罗金").replace(/techrot/g, "科腐者").replace(/scaldra/g, "炽蛇军");
3922
- }
3923
- __name(normalizeStatName, "normalizeStatName");
3924
- function similarity(standard, input) {
3925
- standard = normalizeStatName(standard);
3926
- input = normalizeStatName(input);
3927
- if (!standard || !input)
3928
- return 0;
3929
- if (standard === input)
3930
- return 2;
3931
- if (input.startsWith(standard))
3932
- return 1 + standard.length / input.length * 0.5;
3933
- if (standard.split("/").includes(input))
3934
- return 1.8;
3935
- if (standard.split("/").some((x) => input.startsWith(x)))
3936
- return 1.2;
3937
- return Math.max(tokenSimilarity(standard, input), normalSimilarity(standard, input));
3938
- }
3939
- __name(similarity, "similarity");
3940
- const texts = list;
3941
- const attributes = [];
3942
- const statLines = [];
3943
- for (const t2 of texts) {
3944
- if (!t2 || !/^[x×+−-]|^\d/i.test(t2)) {
3945
- continue;
3946
- }
3947
- if (/^(?:\d+\s*(?:段位|等级|次|循环)|(?:段位|等级|循环)\s*\d+)$/.test(t2.trim()))
3948
- continue;
3949
- const prefix = /^[x×+−-]/i.test(t2) ? t2[0] : "";
3950
- const attrNamePart = removeSpace(t2).replace(/^[xX×+−-]?\d+(?:\.\d+)?[%s]?/, "");
3951
- if (!attrNamePart || !/[A-Z\u4E00-\u9FA5]/i.test(attrNamePart))
3952
- continue;
3953
- if (!prefix && /^[a-z]{1,2}$/i.test(attrNamePart))
3954
- continue;
3955
- const matches = globalRivenCalculationAttributeList.map((attr2) => ({
3956
- attr: attr2,
3957
- score: Math.max(...attr2.aliases.map((name2) => similarity(name2, attrNamePart)))
3958
- })).sort((a, b) => b.score - a.score);
3959
- const best = matches[0];
3960
- if (!best || best.score < 0.8)
3961
- return void 0;
3962
- const attr = best.attr;
3963
- statLines.push(t2);
3964
- const value = (/* @__PURE__ */ __name((function extractStatValue(text) {
3965
- const t3 = text.replace(/\s+/g, "").replace(/×/g, "x").replace(/−/g, "-");
3966
- const multMatch = t3.match(/^x(\d+(?:\.\d+)?)/i);
3967
- if (multMatch) {
3968
- return {
3969
- value: Number.parseFloat(multMatch[1]),
3970
- type: "multiply"
3971
- };
3972
- }
3973
- const percentMatch = t3.match(/^([+-]?\d+(?:\.\d+)?)%/);
3974
- if (percentMatch) {
3975
- return {
3976
- value: Number.parseFloat(percentMatch[1]),
3977
- type: "percent"
3978
- };
3979
- }
3980
- const numMatch = t3.match(/^([+-]?\d+(?:\.\d+)?)/);
3981
- if (numMatch) {
3982
- return {
3983
- value: Number.parseFloat(numMatch[1]),
3984
- type: "number"
3985
- };
3986
- }
3987
- return void 0;
3988
- }), "extractStatValue"))(t2);
3989
- if (!value) {
3990
- return void 0;
3991
- }
3992
- attributes.push({ attr, value: value.value, prefix });
3993
- }
3994
- const weaponName = (/* @__PURE__ */ __name((function extractWeaponName(ocrData) {
3995
- const rejectPatterns = [
3996
- /%/,
3997
- /x\d/i,
3998
- /\d/,
3999
- // numbers, %, multipliers
4000
- /伤害/,
4001
- /暴击/,
4002
- /射速/,
4003
- /攻击/,
4004
- /后坐力/,
4005
- /段位/,
4006
- /加倍/,
4007
- /效/,
4008
- /武器/,
4009
- /果/,
4010
- /\)/,
4011
- /\(/
4012
- // junk OCR fragments
4013
- ];
4014
- const exact = ocrData.find(
4015
- (text) => getWeaponRivenDisposition(text.trim()) || getWeaponRivenDisposition(`${text.trim()} (Primary)`) || getWeaponRivenDisposition(`${text.trim()} (Secondary)`) || getWeaponRivenDisposition(`${text.trim()}(主武器)`) || getWeaponRivenDisposition(`${text.trim()}(副武器)`)
4016
- );
4017
- if (exact)
4018
- return exact.trim();
4019
- const candidates = ocrData.filter((str) => {
4020
- const s = str.trim();
4021
- if (/^\d+$/.test(s))
4022
- return false;
4023
- if (/[+%]/.test(s))
4024
- return false;
4025
- if (!/[A-Z\u4E00-\u9FA5]/i.test(s))
4026
- return false;
4027
- if (rejectPatterns.some((p) => p.test(s)))
4028
- return false;
4029
- return true;
4030
- });
4031
- const merged = candidates.join("");
4032
- function removeRivenSuffix(name2) {
4033
- const s = name2.replace(/\s+/g, "");
4034
- const rivenPattern = /(?:[A-Z]+-[A-Z]+|[A-Z]{2,})$/i;
4035
- return s.replace(rivenPattern, "");
4036
- }
4037
- __name(removeRivenSuffix, "removeRivenSuffix");
4038
- return merged ? removeRivenSuffix(merged) : null;
4039
- }), "extractWeaponName"))(texts.filter((t2) => !statLines.includes(t2)));
4040
- if (!weaponName || !attributes.length) {
4041
- return void 0;
4042
- }
4043
- return {
4044
- name: weaponName,
4045
- attributes
4046
- };
4141
+ return parseRivenOcr(ocrResult);
4047
4142
  }
4048
4143
  __name(parseOCRResult, "parseOCRResult");
4049
4144
  function analyzeRivenStat(parseResult, variant) {
@@ -4065,6 +4160,7 @@ function analyzeRivenStat(parseResult, variant) {
4065
4160
  return failure("riven.statTypeError");
4066
4161
  }
4067
4162
  const entries = [];
4163
+ const notes = [];
4068
4164
  for (const entry of parseResult.attributes) {
4069
4165
  const stat = stats.find((s) => s.gameRef === entry.attr.gameRef) ?? stats.find((s) => s.key === normalizeName(entry.attr.i18n?.en?.name ?? ""));
4070
4166
  if (!stat || !Number.isFinite(entry.value) || !stat.baseValue)
@@ -4073,8 +4169,11 @@ function analyzeRivenStat(parseResult, variant) {
4073
4169
  return failure("riven.unverifiedStat", false, { stat: stat.name });
4074
4170
  const value = stat.unit === "multiply" ? entry.value - 1 : entry.value;
4075
4171
  const positive = value / stat.baseValue > 0;
4076
- if (value === 0 || (positive ? !stat.canBeBuff : !stat.canBeCurse))
4172
+ const observedShotgunZoom = weaponRiven.calc.riventype === "Shotgun" && stat.gameRef === "WeaponZoomFovMod" && !positive;
4173
+ if (value === 0 || (positive ? !stat.canBeBuff : !stat.canBeCurse && !observedShotgunZoom))
4077
4174
  return failure("riven.statTypeError");
4175
+ if (observedShotgunZoom)
4176
+ notes.push("霰弹枪负变焦按现有卡片兼容解析,范围使用导出基础值作参考;不代表当前可洗出该词条。");
4078
4177
  if (entries.some((e) => e.stat.gameRef === stat.gameRef))
4079
4178
  return failure("riven.statTypeError");
4080
4179
  entries.push({ stat, value, displayedValue: entry.value, positive });
@@ -4097,21 +4196,23 @@ function analyzeRivenStat(parseResult, variant) {
4097
4196
  max: Math.max(standard * 0.9, standard * 1.1) + offset
4098
4197
  };
4099
4198
  }, "analyze");
4199
+ if (entries.some((e) => e.stat.spliced))
4200
+ notes.push("融合词条为参考范围;实际融合数值的继承方式尚未验证。");
4100
4201
  return { ok: true, data: {
4101
4202
  name: weaponRiven.name.zh,
4102
4203
  disposition,
4103
4204
  buffs: buffs.map(analyze),
4104
4205
  curses: curses.map(analyze),
4105
- notes: entries.some((e) => e.stat.spliced) ? ["融合词条为参考范围;实际融合数值的继承方式尚未验证。"] : void 0
4206
+ notes: notes.length ? notes : void 0
4106
4207
  } };
4107
4208
  }
4108
4209
  __name(analyzeRivenStat, "analyzeRivenStat");
4109
4210
 
4110
4211
  // src/warframe/services/wf-service/wf-service.sortie.ts
4111
- var import_warframe_public_export_plus16 = require("warframe-public-export-plus");
4212
+ var import_warframe_public_export_plus17 = require("warframe-public-export-plus");
4112
4213
 
4113
4214
  // src/warframe/data/wf/sortie.ts
4114
- var import_warframe_public_export_plus15 = require("warframe-public-export-plus");
4215
+ var import_warframe_public_export_plus16 = require("warframe-public-export-plus");
4115
4216
  var MODE_NAME_KEY = "/Lotus/Language/Menu/SortieMissionName";
4116
4217
  var SORTIE_ENEMY_LEVELS = [
4117
4218
  { minLevel: 50, maxLevel: 60 },
@@ -4119,7 +4220,7 @@ var SORTIE_ENEMY_LEVELS = [
4119
4220
  { minLevel: 80, maxLevel: 100 }
4120
4221
  ];
4121
4222
  function getSortieModeName() {
4122
- return import_warframe_public_export_plus15.dict_zh[MODE_NAME_KEY] ?? dictZhExtra[MODE_NAME_KEY] ?? MODE_NAME_KEY;
4223
+ return import_warframe_public_export_plus16.dict_zh[MODE_NAME_KEY] ?? dictZhExtra[MODE_NAME_KEY] ?? MODE_NAME_KEY;
4123
4224
  }
4124
4225
  __name(getSortieModeName, "getSortieModeName");
4125
4226
  function getSortieEnemyLevels(index) {
@@ -4143,13 +4244,13 @@ async function adaptSortie(source, dates) {
4143
4244
  (source.Variants ?? []).map(async (variant, index) => {
4144
4245
  const missionType = variant.missionType ?? "";
4145
4246
  const receivedType = await getMissionTypeKey(missionType) || missionType;
4146
- const type = import_warframe_public_export_plus16.dict_zh[import_warframe_public_export_plus16.ExportMissionTypes[receivedType]?.name ?? ""] ?? missionType;
4247
+ const type = import_warframe_public_export_plus17.dict_zh[import_warframe_public_export_plus17.ExportMissionTypes[receivedType]?.name ?? ""] ?? missionType;
4147
4248
  const nodeId = variant.node ?? "";
4148
4249
  const solNodeKey = await getSolNodeKey(nodeId) || nodeId;
4149
- const region = solNodeKey ? import_warframe_public_export_plus16.ExportRegions[solNodeKey] : void 0;
4250
+ const region = solNodeKey ? import_warframe_public_export_plus17.ExportRegions[solNodeKey] : void 0;
4150
4251
  const levels = getSortieEnemyLevels(index);
4151
4252
  const node = region ? {
4152
- ...regionToShort(region, import_warframe_public_export_plus16.dict_zh),
4253
+ ...regionToShort(region, import_warframe_public_export_plus17.dict_zh),
4153
4254
  minLevel: levels.minLevel,
4154
4255
  maxLevel: levels.maxLevel
4155
4256
  } : {
@@ -4210,7 +4311,7 @@ async function getSortie() {
4210
4311
  __name(getSortie, "getSortie");
4211
4312
 
4212
4313
  // src/warframe/services/wf-service/wf-service.steel-essence.ts
4213
- var import_warframe_public_export_plus17 = require("warframe-public-export-plus");
4314
+ var import_warframe_public_export_plus18 = require("warframe-public-export-plus");
4214
4315
  var STEEL_ESSENCE_TESHIN_KEY = "/Lotus/Language/Bosses/Teshin";
4215
4316
  var STEEL_ESSENCE_KEY = "/Lotus/Language/Resources/SteelEssence";
4216
4317
  var STEEL_ESSENCE_EPOCH_MS = Date.parse("2020-11-16T00:00:00.000Z");
@@ -4257,8 +4358,8 @@ async function adaptSteelEssence(raw = {}, now = Date.now()) {
4257
4358
  };
4258
4359
  const upcoming = rotationZh.length > 1 ? rotationZh.map((_, offset) => rotationZh[(index + offset + 1) % rotationZh.length]).filter((item) => item !== void 0).slice(0, rotationZh.length - 1) : [];
4259
4360
  const expiry = raw.expiry?.getTime() ?? steelEssenceWeekExpiryUtc(now);
4260
- const teshin = import_warframe_public_export_plus17.dict_zh[STEEL_ESSENCE_TESHIN_KEY] ?? dictZhExtra[STEEL_ESSENCE_TESHIN_KEY] ?? STEEL_ESSENCE_TESHIN_KEY;
4261
- const essence = import_warframe_public_export_plus17.dict_zh[STEEL_ESSENCE_KEY] ?? dictZhExtra[STEEL_ESSENCE_KEY] ?? STEEL_ESSENCE_KEY;
4361
+ const teshin = import_warframe_public_export_plus18.dict_zh[STEEL_ESSENCE_TESHIN_KEY] ?? dictZhExtra[STEEL_ESSENCE_TESHIN_KEY] ?? STEEL_ESSENCE_TESHIN_KEY;
4362
+ const essence = import_warframe_public_export_plus18.dict_zh[STEEL_ESSENCE_KEY] ?? dictZhExtra[STEEL_ESSENCE_KEY] ?? STEEL_ESSENCE_KEY;
4262
4363
  return {
4263
4364
  title: `${teshin} · ${essence}商店`,
4264
4365
  costLabel: essence,
@@ -4311,10 +4412,10 @@ async function getVoidTrader() {
4311
4412
  __name(getVoidTrader, "getVoidTrader");
4312
4413
 
4313
4414
  // src/warframe/services/wf-service/wf-service.weekly.ts
4314
- var import_warframe_public_export_plus19 = require("warframe-public-export-plus");
4415
+ var import_warframe_public_export_plus20 = require("warframe-public-export-plus");
4315
4416
 
4316
4417
  // src/warframe/data/wf/archonHunt.ts
4317
- var import_warframe_public_export_plus18 = require("warframe-public-export-plus");
4418
+ var import_warframe_public_export_plus19 = require("warframe-public-export-plus");
4318
4419
  var MODE_NAME_KEY2 = "/Lotus/Language/WorldStateWindow/LiteSortieMissionName";
4319
4420
  var HUNT_ENEMY_LEVELS = [
4320
4421
  { minLevel: 130, maxLevel: 135 },
@@ -4322,7 +4423,7 @@ var HUNT_ENEMY_LEVELS = [
4322
4423
  { minLevel: 145, maxLevel: 150 }
4323
4424
  ];
4324
4425
  function getArchonHuntModeName() {
4325
- return import_warframe_public_export_plus18.dict_zh[MODE_NAME_KEY2] ?? dictZhExtra[MODE_NAME_KEY2] ?? MODE_NAME_KEY2;
4426
+ return import_warframe_public_export_plus19.dict_zh[MODE_NAME_KEY2] ?? dictZhExtra[MODE_NAME_KEY2] ?? MODE_NAME_KEY2;
4326
4427
  }
4327
4428
  __name(getArchonHuntModeName, "getArchonHuntModeName");
4328
4429
  function getArchonHuntEnemyLevels(index) {
@@ -4332,15 +4433,15 @@ __name(getArchonHuntEnemyLevels, "getArchonHuntEnemyLevels");
4332
4433
 
4333
4434
  // src/warframe/services/wf-service/wf-service.weekly.ts
4334
4435
  async function adaptArchonHunt(source) {
4335
- const name2 = import_warframe_public_export_plus19.dict_zh[`/Lotus/Language/Narmer/${removeSpace(source.boss)}`] ?? source.boss;
4436
+ const name2 = import_warframe_public_export_plus20.dict_zh[`/Lotus/Language/Narmer/${removeSpace(source.boss)}`] ?? source.boss;
4336
4437
  const missions = await Promise.all(
4337
4438
  (source.missions ?? []).map(async (mission, index) => {
4338
4439
  const receivedType = await getMissionTypeKey(mission.type);
4339
- const type = import_warframe_public_export_plus19.dict_zh[import_warframe_public_export_plus19.ExportMissionTypes[receivedType]?.name ?? ""] ?? mission.type;
4440
+ const type = import_warframe_public_export_plus20.dict_zh[import_warframe_public_export_plus20.ExportMissionTypes[receivedType]?.name ?? ""] ?? mission.type;
4340
4441
  const solNodeKey = await getSolNodeKey(mission.nodeKey);
4341
- const region = solNodeKey ? import_warframe_public_export_plus19.ExportRegions[solNodeKey] : void 0;
4442
+ const region = solNodeKey ? import_warframe_public_export_plus20.ExportRegions[solNodeKey] : void 0;
4342
4443
  const levels = getArchonHuntEnemyLevels(index);
4343
- const node = region ? regionToShort(region, import_warframe_public_export_plus19.dict_zh) : {
4444
+ const node = region ? regionToShort(region, import_warframe_public_export_plus20.dict_zh) : {
4344
4445
  name: mission.node ?? mission.nodeKey,
4345
4446
  system: "",
4346
4447
  type: "",
@@ -4393,7 +4494,7 @@ async function getWeekly() {
4393
4494
  const deepArchimMissions = await Promise.all(
4394
4495
  deepArchim.missions.map(async (m) => {
4395
4496
  const receivedType = await getMissionTypeKey(m.missionType);
4396
- const type = import_warframe_public_export_plus19.dict_zh[import_warframe_public_export_plus19.ExportMissionTypes[receivedType]?.name ?? ""] ?? m.missionType;
4497
+ const type = import_warframe_public_export_plus20.dict_zh[import_warframe_public_export_plus20.ExportMissionTypes[receivedType]?.name ?? ""] ?? m.missionType;
4397
4498
  const deviation = stringToDebuff(
4398
4499
  m.deviation.key,
4399
4500
  m.deviation.name,
@@ -4421,7 +4522,7 @@ async function getWeekly() {
4421
4522
  const temporalArchimMissions = await Promise.all(
4422
4523
  temporalArchim.missions.map(async (m) => {
4423
4524
  const receivedType = await getMissionTypeKey(m.missionType);
4424
- const type = import_warframe_public_export_plus19.dict_zh[import_warframe_public_export_plus19.ExportMissionTypes[receivedType]?.name ?? ""] ?? receivedType;
4525
+ const type = import_warframe_public_export_plus20.dict_zh[import_warframe_public_export_plus20.ExportMissionTypes[receivedType]?.name ?? ""] ?? receivedType;
4425
4526
  const deviation = stringToDebuff(
4426
4527
  m.deviation.key,
4427
4528
  m.deviation.name,
@@ -4457,7 +4558,7 @@ async function getWeekly() {
4457
4558
  __name(getWeekly, "getWeekly");
4458
4559
 
4459
4560
  // src/warframe/services/wfm-service/index.ts
4460
- var import_warframe_public_export_plus20 = require("warframe-public-export-plus");
4561
+ var import_warframe_public_export_plus21 = require("warframe-public-export-plus");
4461
4562
 
4462
4563
  // src/warframe/data/wfm/globalDucatnator.ts
4463
4564
  async function globalDucatnatorIDDictFactory(data) {
@@ -4664,10 +4765,10 @@ var wfmItemMatcher = (() => {
4664
4765
  const headSuffixLookup = [neuropticsSuffix, cerebrumSuffix];
4665
4766
  const warframeAliasDict = ((aliasObject) => {
4666
4767
  const transformedObject = {};
4667
- for (const [key, aliases] of Object.entries(aliasObject)) {
4768
+ for (const [key, aliases2] of Object.entries(aliasObject)) {
4668
4769
  const normalizedKey = normalizeName(key);
4669
4770
  transformedObject[normalizedKey] = normalizedKey;
4670
- for (const alias of aliases) {
4771
+ for (const alias of aliases2) {
4671
4772
  if (typeof alias !== "string" || alias.length === 0) {
4672
4773
  continue;
4673
4774
  }
@@ -5310,7 +5411,7 @@ async function getRivenOrders(input) {
5310
5411
  }
5311
5412
  __name(getRivenOrders, "getRivenOrders");
5312
5413
  async function applyRelicData(relic) {
5313
- const tier = import_warframe_public_export_plus20.dict_zh[relic.tierKey] ?? relic.tier;
5414
+ const tier = import_warframe_public_export_plus21.dict_zh[relic.tierKey] ?? relic.tier;
5314
5415
  const { globalItemGameRefDict } = await globalItemData.get();
5315
5416
  const wfmDict = await globalDucatnatorIDDict.get();
5316
5417
  const loadedItems = relic.items.map((element) => {
@@ -5399,7 +5500,7 @@ var primedModHistory = createAsyncCache(async () => {
5399
5500
  }, 432e5);
5400
5501
 
5401
5502
  // src/warframe/services/world-state-refresh.ts
5402
- var import_warframe_public_export_plus21 = require("warframe-public-export-plus");
5503
+ var import_warframe_public_export_plus22 = require("warframe-public-export-plus");
5403
5504
  var ALLOWED_FISSURE_MISSION_TYPES = /* @__PURE__ */ new Set(["MT_EXTERMINATION", "MT_DEFENSE", "MT_ARTIFACT"]);
5404
5505
  function time(value) {
5405
5506
  return value?.getTime() ?? 0;
@@ -5418,7 +5519,7 @@ function fissureId(value) {
5418
5519
  __name(fissureId, "fissureId");
5419
5520
  async function fissureMissionType(fissure) {
5420
5521
  const nodeKey = await getSolNodeKey(fissure.nodeKey);
5421
- return import_warframe_public_export_plus21.ExportRegions[nodeKey]?.missionType;
5522
+ return import_warframe_public_export_plus22.ExportRegions[nodeKey]?.missionType;
5422
5523
  }
5423
5524
  __name(fissureMissionType, "fissureMissionType");
5424
5525
  function dailyDealId(value) {
@@ -5446,21 +5547,6 @@ function traderId(value) {
5446
5547
  ].join(":");
5447
5548
  }
5448
5549
  __name(traderId, "traderId");
5449
- function alertReward(alert) {
5450
- const reward = alert.mission?.reward;
5451
- if (!reward) {
5452
- return "";
5453
- }
5454
- const values = [
5455
- ...reward.items,
5456
- ...reward.countedItems.map((item) => `${item.key} x${item.count}`)
5457
- ];
5458
- if (reward.credits) {
5459
- values.push(`${reward.credits} 现金`);
5460
- }
5461
- return values.join("、");
5462
- }
5463
- __name(alertReward, "alertReward");
5464
5550
  async function diffWorldStates(previous, current) {
5465
5551
  const notifications = [];
5466
5552
  const previousFissures = new Set(previous.raw.fissures.map(fissureId));
@@ -5518,13 +5604,17 @@ async function diffWorldStates(previous, current) {
5518
5604
  for (const alert of current.raw.alerts) {
5519
5605
  const id = alertId(alert);
5520
5606
  if (!previousAlerts.has(id)) {
5607
+ const { alerts: [mapped] } = await adaptAlerts([alert], currentTimestamp);
5608
+ if (!mapped) {
5609
+ continue;
5610
+ }
5521
5611
  notifications.push({
5522
5612
  type: "alert",
5523
5613
  id,
5524
- description: alert.description,
5525
- node: alert.mission?.node ?? "",
5526
- missionType: alert.mission?.type ?? "",
5527
- reward: alertReward(alert),
5614
+ description: mapped.description ?? "",
5615
+ node: mapped.node.system ? `${mapped.node.name} (${mapped.node.system})` : mapped.node.name,
5616
+ missionType: mapped.type,
5617
+ reward: mapped.rewards.map((item) => item.count > 1 ? `${item.name} x${item.count}` : item.name).join("、"),
5528
5618
  expiry: time(alert.expiry)
5529
5619
  });
5530
5620
  }
@@ -7443,6 +7533,7 @@ function AlertComponent(board) {
7443
7533
  const timeLeft = entry.expiry - Date.now();
7444
7534
  const timeColor = timeLeft > 36e5 ? "var(--wf-success)" : timeLeft > 6e5 ? "var(--wf-info)" : "var(--wf-danger)";
7445
7535
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: missionCardStyle, children: [
7536
+ entry.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: "font-size:14px;font-weight:600;margin-bottom:4px;color:var(--wf-text-primary);", children: entry.description }) : null,
7446
7537
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: "font-size:13px;font-weight:600;margin-bottom:4px;color:var(--wf-text-body);", children: [
7447
7538
  entry.type,
7448
7539
  entry.nightmare ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: "margin-left:8px;font-size:12px;color:var(--wf-accent);", children: entry.nightmare }) : null