@wfcd/relics 2.0.30 → 2.0.32

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