-
Notifications
You must be signed in to change notification settings - Fork 54
/
index.mjs
181 lines (161 loc) · 5.31 KB
/
index.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/* eslint-disable no-restricted-syntax, import/no-dynamic-require */
/**
* Configuration options for warframe-items
* @typedef {Object} Options
* @property {string[]} category List of allowed categories to be pulled in.
* Default ['All'].
* Allows any of:
* - All
* - Arcanes
* - Archwing
* - Arch-Gun
* - Arch-Melee
* - Corpus
* - Enemy
* - Fish
* - Gear
* - Glyphs
* - Melee
* - Misc
* - Mods
* - Pets
* - Primary
* - Quests
* - Relics
* - Resources
* - Secondary
* - Sentinels
* - Skins
* - Warframes
* @property {boolean} ignoreEnemies If true, don't load any enemy categories
* @property {boolean|Array<string>} i18n Whether or not to include i18n, or a list of allowed locales
* @property {boolean} i18nOnObject Whether or not to include i18n on the object itself and not on the "array"
*/
import { resolve, dirname } from 'node:path';
import { readFileSync, readdirSync, accessSync, constants } from 'node:fs';
import { fileURLToPath } from 'url';
const directory = dirname(fileURLToPath(import.meta.url));
const canAccess = (path) => {
try {
accessSync(path, constants.R_OK);
return true;
} catch (e) {
return false;
}
};
const cache = {};
const require = (filePath) => {
if (cache[filePath]) return cache[filePath];
const resolved = resolve(directory, filePath);
if (canAccess(resolved)) {
const parsed = JSON.parse(readFileSync(resolved, 'utf-8'));
cache[filePath] = parsed;
return parsed;
}
return [];
};
const versions = require('./data/cache/.export.json');
let i18n = {};
try {
i18n = require('./data/json/i18n.json');
} catch (ignored) {
// can only happen in really weird stuff, and we're already defaulting, so it's ok
}
const ignored = ['All', 'i18n'];
const defaultCategories = readdirSync(resolve(directory, './data/json/'))
.filter((f) => f.includes('.json'))
.map((f) => f.replace('.json', ''))
.filter((f) => !ignored.includes(f));
const defaultOptions = { category: defaultCategories, i18n: false, i18nOnObject: false };
export default class Items extends Array {
constructor(options, ...existingItems) {
super(...existingItems);
// Merge provided options with defaults
this.options = {
...defaultOptions,
...options,
};
const containedAll = this.options.category.includes('All');
if (containedAll) {
this.options.category = Array.from(
new Set(this.options.category.filter((c) => c !== 'All').concat(defaultCategories))
);
}
this.i18n = {};
// Add items from options to array. Type equals the file name.
for (const category of this.options.category) {
// Ignores the enemy category.
if (this.options.ignoreEnemies && category === 'Enemy') continue;
const items = require(`./data/json/${category}.json`);
for (const item of items) {
if (this.options.i18n) {
// only insert i18n for the objects we're inserting so we don't bloat memory
if (Array.isArray(this.options.i18n)) {
const raw = i18n[item.uniqueName];
// only process if passed language is a supported i18n value
if (raw) {
Object.keys(raw).forEach((locale) => {
if (!this.options.i18n.includes(locale)) {
delete raw[locale];
}
});
}
this.i18n[item.uniqueName] = raw;
} else {
this.i18n[item.uniqueName] = i18n[item.uniqueName];
}
}
if (this.options.i18n && this.options.i18nOnObject) {
item.i18n = this.i18n[item.uniqueName];
// keep data just on the object so no bloat in extra this.i18n
delete this.i18n[item.uniqueName];
}
this.push(item);
}
}
if (!this.options.i18n || (this.options.i18n && this.options.i18nOnObject)) {
this.i18n = undefined;
}
// Output won't be sorted if separate categories are chosen
this.sort((a, b) => {
const res = a.name.localeCompare(b.name);
if (res === 0) {
return a.uniqueName.localeCompare(b.uniqueName);
}
return res;
});
this.versions = versions;
}
/**
* @Override Array.prototype.filter
*
* This roughly implements Mozilla's builtin for `Array.prototype.filter`[1].
* V8 passes the prototype of the original Array into `ArraySpeciesCreate`[2][3],
* which is the Array that gets returned from `filter()`. However, they don't
* pass the arguments passed to the constructor of the original Array (this Class),
* which means that it will always return a new Array with ALL items, even when
* different categories are specified.[4]
*
* [1] https://hg.mozilla.org/mozilla-central/file/tip/js/src/builtin/Array.js#l320
* [2] https://github.com/v8/v8/blob/master/src/builtins/array-filter.tq#L193
* [3] https://www.ecma-international.org/ecma-262/7.0/#sec-arrayspeciescreate
* [4] https://runkit.com/kaptard/5c9daf33090ab900120465fe
*/
filter(fn) {
const A = [];
for (const el of this) {
if (fn(el)) A.push(el);
}
return A;
}
/**
* @Override Array.prototype.filter
*
* See filter override
*/
map(fn) {
const a = [];
for (const el of this) a.push(fn(el));
return a;
}
}