@wfcd/relics 2.0.30 → 2.0.31

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.
@@ -19,79 +19,123 @@ var __copyProps = (to, from, except, desc) => {
19
19
  }
20
20
  return to;
21
21
  };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
23
23
  value: mod,
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
+ let node_fetch = require("node-fetch");
28
+ node_fetch = __toESM(node_fetch);
27
29
  let node_fs_promises = require("node:fs/promises");
28
30
  node_fs_promises = __toESM(node_fs_promises);
29
31
  let node_path = require("node:path");
30
32
  node_path = __toESM(node_path);
31
- let node_fetch = require("node-fetch");
32
- node_fetch = __toESM(node_fetch);
33
33
  let node_console = require("node:console");
34
34
  node_console = __toESM(node_console);
35
35
  //#region src/Config.ts
36
36
  const Config = {
37
- warframeRelicDropUrl: "https://drops.warframestat.us/data/relics.json",
38
- warframeMarketItemUrl: "https://api.warframe.market/v2/items",
37
+ warframeItemByNameUrl: "https://api.warframestat.us/items/",
38
+ warframeItemsSparseUrl: "https://api.warframestat.us/items/?only=name,uniqueName",
39
39
  warframeItemsUrl: "https://api.warframestat.us/items/search/Relics?by=category",
40
+ warframeMarketItemUrl: "https://api.warframe.market/v2/items",
41
+ warframePatchlogsUrl: "https://raw.githubusercontent.com/WFCD/warframe-patchlogs/master/data/patchlogs.json",
40
42
  warframeRelicDropInfoUrl: "https://drops.warframestat.us/data/info.json",
41
- warframePatchlogsUrl: "https://raw.githubusercontent.com/WFCD/warframe-patchlogs/master/data/patchlogs.json"
43
+ warframeRelicDropUrl: "https://drops.warframestat.us/data/relics.json"
42
44
  };
43
45
  //#endregion
46
+ //#region src/itemUniqueName.ts
47
+ const QTY_PREFIX = /^\d+X\s+/i;
48
+ const BLUEPRINT_SUFFIX = / Blueprint$/i;
49
+ /**
50
+ * Index sparse warframestat items by lowercase name, preferring higher-ranked uniqueNames.
51
+ * @param {Array<WFCDSparseItem>} items Sparse item list from warframestat
52
+ * @returns {Map<string, string>} Lowercase name → uniqueName
53
+ */
54
+ function buildItemUniqueNameIndex(items) {
55
+ const index = /* @__PURE__ */ new Map();
56
+ items.forEach((item) => {
57
+ if (!(item === null || item === void 0 ? void 0 : item.name) || !(item === null || item === void 0 ? void 0 : item.uniqueName)) return;
58
+ const key = item.name.toLowerCase();
59
+ const existing = index.get(key);
60
+ if (!existing || uniqueNameRank(item.uniqueName) > uniqueNameRank(existing)) index.set(key, item.uniqueName);
61
+ });
62
+ return index;
63
+ }
64
+ /**
65
+ * Strip drop quantity prefixes (e.g. "1200X Kuva" → "Kuva").
66
+ * @param {string} itemName Raw drop item name
67
+ * @returns {string} Name without quantity prefix
68
+ */
69
+ function normalizeRewardName(itemName) {
70
+ return itemName.replace(QTY_PREFIX, "").trim();
71
+ }
72
+ /**
73
+ * Resolve reward uniqueName from WFM gameRef or sparse name index.
74
+ * @param {string} itemName Raw drop item name
75
+ * @param {string|undefined} gameRef WFM gameRef when matched
76
+ * @param {Map<string, string>} itemUniqueNames Sparse (+ Blueprint) index
77
+ * @returns {string} Lotus uniqueName or empty string
78
+ */
79
+ function resolveRewardUniqueName(itemName, gameRef, itemUniqueNames) {
80
+ if (gameRef) return gameRef;
81
+ return itemUniqueNames.get(normalizeRewardName(itemName).toLowerCase()) ?? "";
82
+ }
83
+ /**
84
+ * Prefer in-world item/recipe paths over StoreItems, enemies, and other duplicates.
85
+ * @param {string} uniqueName Lotus path
86
+ * @returns {number} Higher is preferred
87
+ */
88
+ function uniqueNameRank(uniqueName) {
89
+ if (uniqueName.includes("/Enemies/")) return 0;
90
+ if (uniqueName.startsWith("/Lotus/Types/Items/") || uniqueName.startsWith("/Lotus/Types/Recipes/")) return 3;
91
+ if (uniqueName.startsWith("/Lotus/Types/")) return 2;
92
+ if (uniqueName.startsWith("/Lotus/StoreItems/")) return 1;
93
+ return 0;
94
+ }
95
+ //#endregion
44
96
  //#region src/Logger.ts
45
97
  const fromString = (logLevelIsh) => {
46
98
  switch (logLevelIsh === null || logLevelIsh === void 0 ? void 0 : logLevelIsh.toLowerCase()) {
99
+ case "bad":
100
+ case "error": return 0;
101
+ case "debug": return 2;
47
102
  case "fatal": return -1;
48
- case "error":
49
- case "bad": return 0;
50
103
  case "info":
51
104
  case "log": return 1;
52
- case "debug": return 2;
53
105
  default: return -1;
54
106
  }
55
107
  };
56
108
  var Logger = class {
57
- logLevel = fromString(process.env.LOG_LEVEL || "fatal");
58
- log(message) {
59
- if (this.logLevel >= 1) node_console.log(message);
109
+ logLevel = fromString(process.env.LOG_LEVEL ?? "fatal");
110
+ debug(message) {
111
+ if (this.logLevel === 2) node_console.debug(message);
60
112
  }
61
113
  error(message) {
62
114
  if (this.logLevel >= 0) node_console.error(message);
63
115
  }
64
- debug(message) {
65
- if (this.logLevel === 2) node_console.debug(message);
66
- }
67
116
  fatal(message) {
68
117
  if (this.logLevel >= -1) {
69
118
  node_console.error(`FATAL: ${message}`);
70
119
  throw new Error(message);
71
120
  }
72
121
  }
122
+ log(message) {
123
+ if (this.logLevel >= 1) node_console.log(message);
124
+ }
73
125
  };
74
126
  var Logger_default = new Logger();
75
127
  //#endregion
76
128
  //#region src/Generator.ts
77
129
  var Generator = class {
130
+ /** Lowercase item name → uniqueName from sparse warframestat index (+ Blueprint follow-ups) */
131
+ itemUniqueNames;
132
+ relics;
78
133
  relicsRaw;
79
134
  wfcdItems;
80
135
  wfmItems;
81
- relics;
82
136
  constructor() {
83
137
  this.relics = [];
84
- }
85
- /**
86
- * Main function to fetch and generate the relic data
87
- * @returns {Promise<Array<TitaniaRelic>>} The Relics data array
88
- */
89
- async generate() {
90
- Logger_default.log("Starting Generation");
91
- await this.fetchRawData();
92
- this.filterWFCDRelics();
93
- this.generateTitaniaRelics();
94
- return this.relics;
138
+ this.itemUniqueNames = /* @__PURE__ */ new Map();
95
139
  }
96
140
  /**
97
141
  * Fetches all required data from WFCD and WFM.
@@ -116,6 +160,25 @@ var Generator = class {
116
160
  return;
117
161
  }
118
162
  this.wfcdItems = await wfcdItemRequest.json();
163
+ const sparseRequest = await (0, node_fetch.default)(Config.warframeItemsSparseUrl);
164
+ if (!sparseRequest.ok) {
165
+ Logger_default.error("Failed to fetch sparse items from WFCD!");
166
+ return;
167
+ }
168
+ const sparseItems = await sparseRequest.json();
169
+ this.indexSparseItems(sparseItems);
170
+ }
171
+ /**
172
+ * Main function to fetch and generate the relic data
173
+ * @returns {Promise<Array<TitaniaRelic>>} The Relics data array
174
+ */
175
+ async generate() {
176
+ Logger_default.log("Starting Generation");
177
+ await this.fetchRawData();
178
+ this.filterWFCDRelics();
179
+ await this.resolveSpecialRewardUniqueNames();
180
+ this.generateTitaniaRelics();
181
+ return this.relics;
119
182
  }
120
183
  /**
121
184
  * Generates the relic data
@@ -152,17 +215,46 @@ var Generator = class {
152
215
  }
153
216
  }
154
217
  /**
218
+ * Fetch a single warframestat item by name with components for Blueprint resolution.
219
+ * @param {string} name Item display name
220
+ * @returns {Promise<WFCDSparseItem|undefined>} Item or undefined on failure
221
+ */
222
+ async fetchItemByName(name) {
223
+ const url = `${Config.warframeItemByNameUrl}${encodeURIComponent(name)}?only=name,uniqueName,components`;
224
+ const response = await (0, node_fetch.default)(url);
225
+ if (!response.ok) {
226
+ Logger_default.debug(`Failed to fetch item by name: ${name} (${response.status})`);
227
+ return;
228
+ }
229
+ const data = await response.json();
230
+ if ("error" in data && data.error) {
231
+ Logger_default.debug(`No item result for: ${name}`);
232
+ return;
233
+ }
234
+ return data;
235
+ }
236
+ /**
237
+ * Filters WFCD's relic data to only include Intact variants, since we just need the base.
238
+ */
239
+ filterWFCDRelics() {
240
+ var _this$relicsRaw;
241
+ var _this$relicsRaw2;
242
+ var _this$relicsRaw3;
243
+ const before = ((_this$relicsRaw = this.relicsRaw) === null || _this$relicsRaw === void 0 ? void 0 : _this$relicsRaw.length) ?? 0;
244
+ this.relicsRaw = (_this$relicsRaw2 = this.relicsRaw) === null || _this$relicsRaw2 === void 0 ? void 0 : _this$relicsRaw2.filter((x) => x.state === "Intact");
245
+ Logger_default.log(`Filtered relics to intact variants. Before: ${before} After: ${((_this$relicsRaw3 = this.relicsRaw) === null || _this$relicsRaw3 === void 0 ? void 0 : _this$relicsRaw3.length) ?? 0}`);
246
+ }
247
+ /**
155
248
  * Generates a single relic from all available data
156
249
  * @param {WFCDRelic} rawRelic relic to pull Titania data from
157
250
  * @returns {TitaniaRelicReward}
158
251
  */
159
252
  generateTitaniaRelic(rawRelic) {
160
- var _this$wfcdItems2;
253
+ var _this$wfcdItems;
161
254
  var _this$wfmItems2;
162
255
  const name = `${rawRelic.tier} ${rawRelic.relicName}`;
163
256
  const rewards = rawRelic.rewards.map((rawReward) => {
164
257
  var _this$wfmItems;
165
- var _this$wfcdItems;
166
258
  const { chance } = rawReward;
167
259
  const { rarity } = rawReward;
168
260
  const wfmInfo = (_this$wfmItems = this.wfmItems) === null || _this$wfmItems === void 0 ? void 0 : _this$wfmItems.data.find((x) => {
@@ -177,7 +269,7 @@ var Generator = class {
177
269
  if (!(wfmInfo || isSpecial)) Logger_default.debug(`Failed to find wfm item for ${rawReward.itemName}`);
178
270
  const item = {
179
271
  name: rawReward.itemName,
180
- uniqueName: ((_this$wfcdItems = this.wfcdItems) === null || _this$wfcdItems === void 0 || (_this$wfcdItems = _this$wfcdItems.find((x) => x.name.toLowerCase() === `${name.trim()} Intact`.toLowerCase())) === null || _this$wfcdItems === void 0 ? void 0 : _this$wfcdItems.uniqueName) || "",
272
+ uniqueName: resolveRewardUniqueName(rawReward.itemName, wfmInfo === null || wfmInfo === void 0 ? void 0 : wfmInfo.gameRef, this.itemUniqueNames),
181
273
  warframeMarket: void 0
182
274
  };
183
275
  if (wfmInfo) item.warframeMarket = {
@@ -185,19 +277,19 @@ var Generator = class {
185
277
  urlName: wfmInfo.slug
186
278
  };
187
279
  return {
188
- rarity,
189
280
  chance,
190
- item
281
+ item,
282
+ rarity
191
283
  };
192
284
  });
193
285
  let drops = [];
194
- const wfcdItem = (_this$wfcdItems2 = this.wfcdItems) === null || _this$wfcdItems2 === void 0 ? void 0 : _this$wfcdItems2.find((x) => x.name.toLowerCase() === `${name.trim()} Intact`.toLowerCase());
286
+ const wfcdItem = (_this$wfcdItems = this.wfcdItems) === null || _this$wfcdItems === void 0 ? void 0 : _this$wfcdItems.find((x) => x.name.toLowerCase() === `${name.trim()} Intact`.toLowerCase());
195
287
  if (!wfcdItem) Logger_default.error(`Failed to get WFCD item for relic: ${name}`);
196
- if (wfcdItem && wfcdItem.drops) drops = wfcdItem.drops.map((rawDrop) => {
288
+ if (wfcdItem === null || wfcdItem === void 0 ? void 0 : wfcdItem.drops) drops = wfcdItem.drops.map((rawDrop) => {
197
289
  return {
198
- rarity: rawDrop.rarity,
199
290
  chance: rawDrop.chance,
200
- location: rawDrop.location
291
+ location: rawDrop.location,
292
+ rarity: rawDrop.rarity
201
293
  };
202
294
  });
203
295
  const wfm = (_this$wfmItems2 = this.wfmItems) === null || _this$wfmItems2 === void 0 ? void 0 : _this$wfmItems2.data.find((x) => {
@@ -205,35 +297,71 @@ var Generator = class {
205
297
  });
206
298
  if (!wfm) Logger_default.error(`Failed to get relic item from wfm: ${name}`);
207
299
  return {
300
+ locations: drops,
208
301
  name,
209
302
  rewards,
210
- locations: drops,
211
- uniqueName: (wfcdItem === null || wfcdItem === void 0 ? void 0 : wfcdItem.uniqueName) || "",
303
+ uniqueName: (wfcdItem === null || wfcdItem === void 0 ? void 0 : wfcdItem.uniqueName) ?? "",
212
304
  vaultInfo: { vaulted: drops.length === 0 },
213
- ...(wfm === null || wfm === void 0 ? void 0 : wfm.id) && (wfm === null || wfm === void 0 ? void 0 : wfm.slug) && { warframeMarket: {
214
- id: wfm === null || wfm === void 0 ? void 0 : wfm.id,
215
- urlName: wfm === null || wfm === void 0 ? void 0 : wfm.slug
305
+ ...(wfm === null || wfm === void 0 ? void 0 : wfm.id) && wfm.slug && { warframeMarket: {
306
+ id: wfm.id,
307
+ urlName: wfm.slug
216
308
  } }
217
309
  };
218
310
  }
219
311
  /**
220
- * Filters WFCD's relic data to only include Intact variants, since we just need the base.
312
+ * Index sparse warframestat items by lowercase name, preferring /Lotus/Types/ paths.
313
+ * @param {Array<WFCDSparseItem>} items Sparse item list from warframestat
221
314
  */
222
- filterWFCDRelics() {
223
- var _this$relicsRaw;
224
- var _this$relicsRaw2;
225
- var _this$relicsRaw3;
226
- const before = ((_this$relicsRaw = this.relicsRaw) === null || _this$relicsRaw === void 0 ? void 0 : _this$relicsRaw.length) || 0;
227
- this.relicsRaw = (_this$relicsRaw2 = this.relicsRaw) === null || _this$relicsRaw2 === void 0 ? void 0 : _this$relicsRaw2.filter((x) => x.state === "Intact");
228
- Logger_default.log(`Filtered relics to intact variants. Before: ${before} After: ${((_this$relicsRaw3 = this.relicsRaw) === null || _this$relicsRaw3 === void 0 ? void 0 : _this$relicsRaw3.length) || 0}`);
315
+ indexSparseItems(items) {
316
+ this.itemUniqueNames = buildItemUniqueNameIndex(items);
317
+ Logger_default.debug(`Indexed ${this.itemUniqueNames.size} sparse item uniqueNames`);
318
+ }
319
+ /**
320
+ * For reward names missing WFM, resolve uniqueNames from sparse index.
321
+ * Blueprint-suffixed names need a parent item fetch for the Blueprint component.
322
+ */
323
+ async resolveSpecialRewardUniqueNames() {
324
+ if (!this.relicsRaw || !this.wfmItems) return;
325
+ const specialNames = /* @__PURE__ */ new Set();
326
+ this.relicsRaw.forEach((relic) => {
327
+ relic.rewards.forEach((reward) => {
328
+ var _this$wfmItems3;
329
+ if (!((_this$wfmItems3 = this.wfmItems) === null || _this$wfmItems3 === void 0 ? void 0 : _this$wfmItems3.data.some((x) => {
330
+ return x.i18n.en.name.toLowerCase() === reward.itemName.toLowerCase();
331
+ }))) specialNames.add(normalizeRewardName(reward.itemName));
332
+ });
333
+ });
334
+ const blueprintParents = /* @__PURE__ */ new Map();
335
+ Array.from(specialNames).forEach((name) => {
336
+ const key = name.toLowerCase();
337
+ if (this.itemUniqueNames.has(key)) return;
338
+ if (!BLUEPRINT_SUFFIX.test(name)) {
339
+ Logger_default.debug(`No uniqueName for special reward: ${name}`);
340
+ return;
341
+ }
342
+ const parent = name.replace(BLUEPRINT_SUFFIX, "").trim();
343
+ if (parent) blueprintParents.set(name, parent);
344
+ });
345
+ const parentCache = /* @__PURE__ */ new Map();
346
+ for (const [blueprintName, parent] of blueprintParents.entries()) {
347
+ var _parentItem$component;
348
+ let parentItem = parentCache.get(parent);
349
+ if (!parentCache.has(parent)) {
350
+ parentItem = await this.fetchItemByName(parent);
351
+ parentCache.set(parent, parentItem);
352
+ }
353
+ const blueprint = parentItem === null || parentItem === void 0 || (_parentItem$component = parentItem.components) === null || _parentItem$component === void 0 ? void 0 : _parentItem$component.find((c) => c.name.toLowerCase() === "blueprint");
354
+ if (blueprint === null || blueprint === void 0 ? void 0 : blueprint.uniqueName) this.itemUniqueNames.set(blueprintName.toLowerCase(), blueprint.uniqueName);
355
+ else Logger_default.debug(`Failed to resolve Blueprint uniqueName for: ${blueprintName}`);
356
+ }
229
357
  }
230
358
  };
231
359
  //#endregion
232
360
  //#region src/VersionManager.ts
233
361
  var VersionManager = class {
362
+ hashPath;
234
363
  versionPath;
235
364
  versionRawPath;
236
- hashPath;
237
365
  /**
238
366
  * Creates a new VersionManager instance.
239
367
  * @param {string} dataDir Folder to write version information to.
@@ -255,7 +383,7 @@ var VersionManager = class {
255
383
  try {
256
384
  await node_fs_promises.default.access(this.hashPath);
257
385
  return JSON.parse(await node_fs_promises.default.readFile(this.hashPath, "utf-8")).hash !== info.hash;
258
- } catch (ex) {
386
+ } catch {
259
387
  return true;
260
388
  }
261
389
  }
@@ -278,12 +406,12 @@ var VersionManager = class {
278
406
  }
279
407
  const hashInfo = await hashReq.json();
280
408
  const versionInfo = {
281
- version,
282
- title: patchlogs[0].name
409
+ title: patchlogs[0].name,
410
+ version
283
411
  };
284
412
  const hashFile = {
285
- hash: hashInfo.hash,
286
413
  deUpdated: hashInfo.modified,
414
+ hash: hashInfo.hash,
287
415
  timestamp
288
416
  };
289
417
  await node_fs_promises.default.writeFile(this.hashPath, JSON.stringify(hashFile, void 0, 2), "utf-8");
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_VersionManager = require("./VersionManager-ZP_iui8x.cjs");
2
+ const require_VersionManager = require("./VersionManager-CDrWT0KK.cjs");
3
3
  exports.Generator = require_VersionManager.Generator;
4
4
  exports.VersionManager = require_VersionManager.VersionManager;