@nekosuneprojects/warframe-api 1.0.0 → 1.0.1
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/LICENSE +21 -21
- package/README.md +298 -298
- package/data/characters/amesha.json +226 -226
- package/data/characters/ash.json +1354 -1354
- package/data/characters/ashprime.json +1994 -1994
- package/data/characters/atlas.json +916 -916
- package/data/characters/atlasprime.json +1632 -1632
- package/data/characters/banshee.json +1036 -1036
- package/data/characters/bansheeprime.json +1750 -1750
- package/data/characters/baruuk.json +555 -555
- package/data/characters/baruukprime.json +1298 -1298
- package/data/characters/bonewidow.json +354 -354
- package/data/characters/caliban.json +434 -434
- package/data/characters/calibanprime.json +683 -683
- package/data/characters/chroma.json +1344 -1344
- package/data/characters/chromaprime.json +1977 -1977
- package/data/characters/citrine.json +220 -220
- package/data/characters/citrineprime.json +62 -62
- package/data/characters/cyte09.json +218 -218
- package/data/characters/dagath.json +194 -194
- package/data/characters/dante.json +223 -223
- package/data/characters/elytron.json +300 -300
- package/data/characters/ember.json +1209 -1209
- package/data/characters/emberprime.json +1678 -1678
- package/data/characters/equinox.json +958 -958
- package/data/characters/equinoxprime.json +1644 -1644
- package/data/characters/excalibur.json +1603 -1603
- package/data/characters/excaliburprime.json +1524 -1524
- package/data/characters/excaliburumbra.json +397 -397
- package/data/characters/follie.json +246 -246
- package/data/characters/frost.json +1330 -1330
- package/data/characters/frostprime.json +1686 -1686
- package/data/characters/gara.json +925 -925
- package/data/characters/garaprime.json +1494 -1494
- package/data/characters/garuda.json +851 -851
- package/data/characters/garudaprime.json +1449 -1449
- package/data/characters/gauss.json +506 -506
- package/data/characters/gaussprime.json +1077 -1077
- package/data/characters/grendel.json +463 -463
- package/data/characters/grendelprime.json +772 -772
- package/data/characters/gyre.json +381 -381
- package/data/characters/gyreprime.json +403 -403
- package/data/characters/harrow.json +916 -916
- package/data/characters/harrowprime.json +1700 -1700
- package/data/characters/helminth.json +220 -220
- package/data/characters/hildryn.json +627 -627
- package/data/characters/hildrynprime.json +996 -996
- package/data/characters/hydroid.json +1028 -1028
- package/data/characters/hydroidprime.json +1621 -1621
- package/data/characters/inaros.json +976 -976
- package/data/characters/inarosprime.json +1721 -1721
- package/data/characters/itzal.json +465 -465
- package/data/characters/ivara.json +1349 -1349
- package/data/characters/ivaraprime.json +1921 -1921
- package/data/characters/jade.json +240 -240
- package/data/characters/khora.json +804 -804
- package/data/characters/khoraprime.json +1403 -1403
- package/data/characters/koumei.json +221 -221
- package/data/characters/kullervo.json +191 -191
- package/data/characters/lavos.json +511 -511
- package/data/characters/lavosprime.json +711 -711
- package/data/characters/limbo.json +1363 -1363
- package/data/characters/limboprime.json +2084 -2084
- package/data/characters/loki.json +1209 -1209
- package/data/characters/lokiprime.json +1457 -1457
- package/data/characters/mag.json +2089 -2089
- package/data/characters/magprime.json +2532 -2532
- package/data/characters/mesa.json +1195 -1195
- package/data/characters/mesaprime.json +2059 -2059
- package/data/characters/mirage.json +1561 -1561
- package/data/characters/mirageprime.json +2257 -2257
- package/data/characters/narin.json +43 -43
- package/data/characters/nekros.json +1235 -1235
- package/data/characters/nekrosprime.json +1882 -1882
- package/data/characters/nezha.json +868 -868
- package/data/characters/nezhaprime.json +1695 -1695
- package/data/characters/nidus.json +1027 -1027
- package/data/characters/nidusprime.json +1706 -1706
- package/data/characters/nokko.json +308 -308
- package/data/characters/nova.json +1226 -1226
- package/data/characters/novaprime.json +1667 -1667
- package/data/characters/nyx.json +1610 -1610
- package/data/characters/nyxprime.json +1967 -1967
- package/data/characters/oberon.json +1249 -1249
- package/data/characters/oberonprime.json +1872 -1872
- package/data/characters/octavia.json +1049 -1049
- package/data/characters/octaviaprime.json +1735 -1735
- package/data/characters/odonata.json +336 -336
- package/data/characters/odonataprime.json +787 -787
- package/data/characters/oraxia.json +246 -246
- package/data/characters/orionsirius.json +134 -134
- package/data/characters/protea.json +521 -521
- package/data/characters/proteaprime.json +992 -992
- package/data/characters/qorvex.json +214 -214
- package/data/characters/revenant.json +931 -931
- package/data/characters/revenantprime.json +1494 -1494
- package/data/characters/rhino.json +1344 -1344
- package/data/characters/rhinoprime.json +1675 -1675
- package/data/characters/saryn.json +1210 -1210
- package/data/characters/sarynprime.json +2042 -2042
- package/data/characters/sevagoth.json +478 -478
- package/data/characters/sevagothprime.json +910 -910
- package/data/characters/siriusorion.json +247 -247
- package/data/characters/styanax.json +320 -320
- package/data/characters/styanaxprime.json +321 -321
- package/data/characters/temple.json +271 -271
- package/data/characters/titania.json +1227 -1227
- package/data/characters/titaniaprime.json +2000 -2000
- package/data/characters/trinity.json +1097 -1097
- package/data/characters/trinityprime.json +1849 -1849
- package/data/characters/uriel.json +215 -215
- package/data/characters/valkyr.json +1165 -1165
- package/data/characters/valkyrprime.json +1969 -1969
- package/data/characters/vauban.json +1067 -1067
- package/data/characters/vaubanprime.json +1614 -1614
- package/data/characters/voidrig.json +355 -355
- package/data/characters/volt.json +1324 -1324
- package/data/characters/voltprime.json +1843 -1843
- package/data/characters/voruna.json +293 -293
- package/data/characters/vorunaprime.json +431 -431
- package/data/characters/wisp.json +824 -824
- package/data/characters/wispprime.json +908 -908
- package/data/characters/wukong.json +927 -927
- package/data/characters/wukongprime.json +1754 -1754
- package/data/characters/xaku.json +620 -620
- package/data/characters/xakuprime.json +879 -879
- package/data/characters/yareli.json +409 -409
- package/data/characters/yareliprime.json +683 -683
- package/data/characters/zephyr.json +1149 -1149
- package/data/characters/zephyrprime.json +1864 -1864
- package/data/codes/warframe.json +20824 -20824
- package/data/meta.json +14 -14
- package/index.js +11 -11
- package/package.json +50 -50
- package/src/catalog.js +105 -105
- package/src/client-error.js +9 -9
- package/src/data-source.js +7 -7
- package/src/data-store.js +138 -138
- package/src/provider.js +22 -22
- package/src/tracker.js +115 -115
- package/src/warframe-client.js +57 -57
- package/src/warframe-codes.js +82 -82
- package/src/wfcd.js +93 -93
package/data/meta.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Warframe",
|
|
3
|
-
"title": "Cooperative science-fiction action game by Digital Extremes.",
|
|
4
|
-
"engine": "Evolution",
|
|
5
|
-
"Platform": "Windows, PlayStation, Xbox, Nintendo Switch, iOS, Android",
|
|
6
|
-
"Release date": "March 25, 2013",
|
|
7
|
-
"Genre": "Action RPG, Third-person shooter, Online co-op",
|
|
8
|
-
"developers": "Digital Extremes",
|
|
9
|
-
"publisher": "Digital Extremes",
|
|
10
|
-
"aliases": "WF, Warframe",
|
|
11
|
-
"image": "https://www.warframe.com/favicon.ico",
|
|
12
|
-
"description": "Online action game built around collectible Warframes, weapons, companions and the Origin System.",
|
|
13
|
-
"url": "https://www.warframe.com/"
|
|
14
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "Warframe",
|
|
3
|
+
"title": "Cooperative science-fiction action game by Digital Extremes.",
|
|
4
|
+
"engine": "Evolution",
|
|
5
|
+
"Platform": "Windows, PlayStation, Xbox, Nintendo Switch, iOS, Android",
|
|
6
|
+
"Release date": "March 25, 2013",
|
|
7
|
+
"Genre": "Action RPG, Third-person shooter, Online co-op",
|
|
8
|
+
"developers": "Digital Extremes",
|
|
9
|
+
"publisher": "Digital Extremes",
|
|
10
|
+
"aliases": "WF, Warframe",
|
|
11
|
+
"image": "https://www.warframe.com/favicon.ico",
|
|
12
|
+
"description": "Online action game built around collectible Warframes, weapons, companions and the Origin System.",
|
|
13
|
+
"url": "https://www.warframe.com/"
|
|
14
|
+
}
|
package/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const {createTracker,TrackerError}=require('./src/tracker');
|
|
3
|
-
const provider=require('./src/provider');
|
|
4
|
-
|
|
5
|
-
module.exports=createTracker;
|
|
6
|
-
module.exports.Warframe=createTracker;
|
|
7
|
-
module.exports.createTracker=createTracker;
|
|
8
|
-
module.exports.TrackerError=TrackerError;
|
|
9
|
-
module.exports.platforms=provider.platforms;
|
|
10
|
-
module.exports.worldstateFields=provider.worldstateFields;
|
|
11
|
-
module.exports.staticKeys=provider.staticKeys;
|
|
1
|
+
'use strict';
|
|
2
|
+
const {createTracker,TrackerError}=require('./src/tracker');
|
|
3
|
+
const provider=require('./src/provider');
|
|
4
|
+
|
|
5
|
+
module.exports=createTracker;
|
|
6
|
+
module.exports.Warframe=createTracker;
|
|
7
|
+
module.exports.createTracker=createTracker;
|
|
8
|
+
module.exports.TrackerError=TrackerError;
|
|
9
|
+
module.exports.platforms=provider.platforms;
|
|
10
|
+
module.exports.worldstateFields=provider.worldstateFields;
|
|
11
|
+
module.exports.staticKeys=provider.staticKeys;
|
package/package.json
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nekosuneprojects/warframe-api",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "WarframeStat.us live API, offline Warframe characters, and redeem-code/glyph JSON browsing with independent GitHub synchronization.",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"type": "commonjs",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/NekoSuneProjectsNPM/game-tracker.git"
|
|
10
|
-
},
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/NekoSuneProjectsNPM/game-tracker/issues"
|
|
13
|
-
},
|
|
14
|
-
"funding": "https://github.com/NekoSuneProjectsNPM/game-tracker?sponsor",
|
|
15
|
-
"keywords": [
|
|
16
|
-
"warframe",
|
|
17
|
-
"warframe-api",
|
|
18
|
-
"warframestat",
|
|
19
|
-
"wfcd",
|
|
20
|
-
"worldstate",
|
|
21
|
-
"characters",
|
|
22
|
-
"json",
|
|
23
|
-
"offline",
|
|
24
|
-
"game-tracker",
|
|
25
|
-
"redeem-codes",
|
|
26
|
-
"glyphs",
|
|
27
|
-
"creator-glyphs"
|
|
28
|
-
],
|
|
29
|
-
"author": "NekoSuneVR",
|
|
30
|
-
"license": "MIT",
|
|
31
|
-
"engines": {
|
|
32
|
-
"node": ">=18"
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"index.js",
|
|
36
|
-
"src/",
|
|
37
|
-
"data/",
|
|
38
|
-
"README.md",
|
|
39
|
-
"LICENSE"
|
|
40
|
-
],
|
|
41
|
-
"scripts": {
|
|
42
|
-
"test": "node --test",
|
|
43
|
-
"check": "node scripts/validate-package.js",
|
|
44
|
-
"prepack": "npm run check && npm test"
|
|
45
|
-
},
|
|
46
|
-
"publishConfig": {
|
|
47
|
-
"access": "public",
|
|
48
|
-
"registry": "https://registry.npmjs.org/"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nekosuneprojects/warframe-api",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "WarframeStat.us live API, offline Warframe characters, and redeem-code/glyph JSON browsing with independent GitHub synchronization.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/NekoSuneProjectsNPM/game-tracker.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/NekoSuneProjectsNPM/game-tracker/issues"
|
|
13
|
+
},
|
|
14
|
+
"funding": "https://github.com/NekoSuneProjectsNPM/game-tracker?sponsor",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"warframe",
|
|
17
|
+
"warframe-api",
|
|
18
|
+
"warframestat",
|
|
19
|
+
"wfcd",
|
|
20
|
+
"worldstate",
|
|
21
|
+
"characters",
|
|
22
|
+
"json",
|
|
23
|
+
"offline",
|
|
24
|
+
"game-tracker",
|
|
25
|
+
"redeem-codes",
|
|
26
|
+
"glyphs",
|
|
27
|
+
"creator-glyphs"
|
|
28
|
+
],
|
|
29
|
+
"author": "NekoSuneVR",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"index.js",
|
|
36
|
+
"src/",
|
|
37
|
+
"data/",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "node --test",
|
|
43
|
+
"check": "node scripts/validate-package.js",
|
|
44
|
+
"prepack": "npm run check && npm test"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"registry": "https://registry.npmjs.org/"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/catalog.js
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Common reusable JSON discovery across any game or category.
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
|
|
6
|
-
const normalise = value => String(value ?? '').trim().normalize('NFKC').toLowerCase().replace(/[\s_-]+/g, ' ');
|
|
7
|
-
const slug = value => normalise(value).replace(/[^a-z0-9 ]/g, '').replace(/ +/g, '');
|
|
8
|
-
|
|
9
|
-
function descriptor(file, record) {
|
|
10
|
-
const type = file.split('/')[0] || 'data';
|
|
11
|
-
const id = path.posix.basename(file, '.json');
|
|
12
|
-
const name = typeof record?.name === 'string' ? record.name :
|
|
13
|
-
typeof record?.title === 'string' ? record.title : id.replace(/[-_]/g, ' ');
|
|
14
|
-
return { id: String(record?.id || id), name, type, path: file };
|
|
15
|
-
}
|
|
16
|
-
function valuesFrom(record, limit = 10000) {
|
|
17
|
-
const values = [];
|
|
18
|
-
const stack = [record];
|
|
19
|
-
while (stack.length && values.length < limit) {
|
|
20
|
-
const value = stack.pop();
|
|
21
|
-
if (typeof value === 'string' || typeof value === 'number') values.push(String(value));
|
|
22
|
-
else if (Array.isArray(value)) stack.push(...value.slice(0,1000));
|
|
23
|
-
else if (value && typeof value === 'object') stack.push(...Object.values(value).slice(0,1000));
|
|
24
|
-
}
|
|
25
|
-
return values;
|
|
26
|
-
}
|
|
27
|
-
function createCatalog(data) {
|
|
28
|
-
if (!data || typeof data.list !== 'function' || typeof data.get !== 'function')
|
|
29
|
-
throw new TypeError('A JSON data store is required');
|
|
30
|
-
|
|
31
|
-
async function all(category) {
|
|
32
|
-
const files = await data.list();
|
|
33
|
-
const prefix = category ? category + '/' : '';
|
|
34
|
-
const matches = [];
|
|
35
|
-
for (const file of files) {
|
|
36
|
-
if (!file.endsWith('.json') || (category && !file.startsWith(prefix))) continue;
|
|
37
|
-
try {
|
|
38
|
-
const record = await data.get(file);
|
|
39
|
-
matches.push({ ...descriptor(file, record), record });
|
|
40
|
-
} catch (error) {
|
|
41
|
-
if (error.code === 'ENOENT') continue;
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return matches;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function search(query, { category, limit = 50 } = {}) {
|
|
49
|
-
if (typeof query !== 'string' || !query.trim()) throw new TypeError('Search query is required');
|
|
50
|
-
if (category && !/^[a-z0-9_-]+$/.test(category)) throw new TypeError('Invalid category');
|
|
51
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new RangeError('limit must be between 1 and 500');
|
|
52
|
-
const needle = normalise(query);
|
|
53
|
-
const results = [];
|
|
54
|
-
for (const item of await all(category)) {
|
|
55
|
-
const shortFields = [item.id, item.name, item.path, item.record?.title, ...(Array.isArray(item.record?.aliases) ? item.record.aliases : [])];
|
|
56
|
-
const direct = shortFields.some(v => v != null && normalise(v).includes(needle));
|
|
57
|
-
const content = !direct && valuesFrom(item.record).some(v => normalise(v).includes(needle));
|
|
58
|
-
if (!direct && !content) continue;
|
|
59
|
-
const score = normalise(item.name) === needle || normalise(item.id) === needle || slug(item.name) === slug(query) ? 0 :
|
|
60
|
-
direct ? 1 : 2;
|
|
61
|
-
results.push({ id:item.id, name:item.name, type:item.type, path:item.path, score });
|
|
62
|
-
}
|
|
63
|
-
results.sort((a,b) => a.score-b.score || a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
64
|
-
const found = results.slice(0,limit).map(({score,...result})=>result);
|
|
65
|
-
return {
|
|
66
|
-
query,
|
|
67
|
-
total: results.length,
|
|
68
|
-
results: found,
|
|
69
|
-
message: results.length ? `Found ${results.length} result(s) for "${query}".` : `We don't have anything related to "${query}".`
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function get(category, idOrName) {
|
|
74
|
-
if (typeof category !== 'string' || !/^[a-z0-9_-]+$/.test(category))
|
|
75
|
-
throw new TypeError('Invalid category');
|
|
76
|
-
if (typeof idOrName !== 'string' || !idOrName.trim()) throw new TypeError('Record name or ID is required');
|
|
77
|
-
const needle = normalise(idOrName);
|
|
78
|
-
const matches = (await all(category)).filter(item =>
|
|
79
|
-
normalise(item.id) === needle || normalise(item.name) === needle ||
|
|
80
|
-
slug(item.id) === slug(idOrName) || slug(item.name) === slug(idOrName) ||
|
|
81
|
-
normalise(item.path) === needle);
|
|
82
|
-
if (!matches.length) {
|
|
83
|
-
const error = new Error(`We don't have anything related to "${idOrName}" in ${category}.`);
|
|
84
|
-
error.code = 'NOT_FOUND';
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
if (matches.length > 1) {
|
|
88
|
-
const error = new Error(`Multiple ${category} records match "${idOrName}". Select an exact result path.`);
|
|
89
|
-
error.code = 'AMBIGUOUS';
|
|
90
|
-
error.results = matches.map(({record,...info})=>info);
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
return matches[0].record;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return Object.freeze({
|
|
97
|
-
search,
|
|
98
|
-
get,
|
|
99
|
-
character: id => get('characters', id),
|
|
100
|
-
item: id => get('items', id),
|
|
101
|
-
code: id => get('codes', id),
|
|
102
|
-
map: id => get('maps', id)
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
module.exports = { createCatalog };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Common reusable JSON discovery across any game or category.
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const normalise = value => String(value ?? '').trim().normalize('NFKC').toLowerCase().replace(/[\s_-]+/g, ' ');
|
|
7
|
+
const slug = value => normalise(value).replace(/[^a-z0-9 ]/g, '').replace(/ +/g, '');
|
|
8
|
+
|
|
9
|
+
function descriptor(file, record) {
|
|
10
|
+
const type = file.split('/')[0] || 'data';
|
|
11
|
+
const id = path.posix.basename(file, '.json');
|
|
12
|
+
const name = typeof record?.name === 'string' ? record.name :
|
|
13
|
+
typeof record?.title === 'string' ? record.title : id.replace(/[-_]/g, ' ');
|
|
14
|
+
return { id: String(record?.id || id), name, type, path: file };
|
|
15
|
+
}
|
|
16
|
+
function valuesFrom(record, limit = 10000) {
|
|
17
|
+
const values = [];
|
|
18
|
+
const stack = [record];
|
|
19
|
+
while (stack.length && values.length < limit) {
|
|
20
|
+
const value = stack.pop();
|
|
21
|
+
if (typeof value === 'string' || typeof value === 'number') values.push(String(value));
|
|
22
|
+
else if (Array.isArray(value)) stack.push(...value.slice(0,1000));
|
|
23
|
+
else if (value && typeof value === 'object') stack.push(...Object.values(value).slice(0,1000));
|
|
24
|
+
}
|
|
25
|
+
return values;
|
|
26
|
+
}
|
|
27
|
+
function createCatalog(data) {
|
|
28
|
+
if (!data || typeof data.list !== 'function' || typeof data.get !== 'function')
|
|
29
|
+
throw new TypeError('A JSON data store is required');
|
|
30
|
+
|
|
31
|
+
async function all(category) {
|
|
32
|
+
const files = await data.list();
|
|
33
|
+
const prefix = category ? category + '/' : '';
|
|
34
|
+
const matches = [];
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
if (!file.endsWith('.json') || (category && !file.startsWith(prefix))) continue;
|
|
37
|
+
try {
|
|
38
|
+
const record = await data.get(file);
|
|
39
|
+
matches.push({ ...descriptor(file, record), record });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT') continue;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return matches;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function search(query, { category, limit = 50 } = {}) {
|
|
49
|
+
if (typeof query !== 'string' || !query.trim()) throw new TypeError('Search query is required');
|
|
50
|
+
if (category && !/^[a-z0-9_-]+$/.test(category)) throw new TypeError('Invalid category');
|
|
51
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new RangeError('limit must be between 1 and 500');
|
|
52
|
+
const needle = normalise(query);
|
|
53
|
+
const results = [];
|
|
54
|
+
for (const item of await all(category)) {
|
|
55
|
+
const shortFields = [item.id, item.name, item.path, item.record?.title, ...(Array.isArray(item.record?.aliases) ? item.record.aliases : [])];
|
|
56
|
+
const direct = shortFields.some(v => v != null && normalise(v).includes(needle));
|
|
57
|
+
const content = !direct && valuesFrom(item.record).some(v => normalise(v).includes(needle));
|
|
58
|
+
if (!direct && !content) continue;
|
|
59
|
+
const score = normalise(item.name) === needle || normalise(item.id) === needle || slug(item.name) === slug(query) ? 0 :
|
|
60
|
+
direct ? 1 : 2;
|
|
61
|
+
results.push({ id:item.id, name:item.name, type:item.type, path:item.path, score });
|
|
62
|
+
}
|
|
63
|
+
results.sort((a,b) => a.score-b.score || a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
64
|
+
const found = results.slice(0,limit).map(({score,...result})=>result);
|
|
65
|
+
return {
|
|
66
|
+
query,
|
|
67
|
+
total: results.length,
|
|
68
|
+
results: found,
|
|
69
|
+
message: results.length ? `Found ${results.length} result(s) for "${query}".` : `We don't have anything related to "${query}".`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function get(category, idOrName) {
|
|
74
|
+
if (typeof category !== 'string' || !/^[a-z0-9_-]+$/.test(category))
|
|
75
|
+
throw new TypeError('Invalid category');
|
|
76
|
+
if (typeof idOrName !== 'string' || !idOrName.trim()) throw new TypeError('Record name or ID is required');
|
|
77
|
+
const needle = normalise(idOrName);
|
|
78
|
+
const matches = (await all(category)).filter(item =>
|
|
79
|
+
normalise(item.id) === needle || normalise(item.name) === needle ||
|
|
80
|
+
slug(item.id) === slug(idOrName) || slug(item.name) === slug(idOrName) ||
|
|
81
|
+
normalise(item.path) === needle);
|
|
82
|
+
if (!matches.length) {
|
|
83
|
+
const error = new Error(`We don't have anything related to "${idOrName}" in ${category}.`);
|
|
84
|
+
error.code = 'NOT_FOUND';
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
if (matches.length > 1) {
|
|
88
|
+
const error = new Error(`Multiple ${category} records match "${idOrName}". Select an exact result path.`);
|
|
89
|
+
error.code = 'AMBIGUOUS';
|
|
90
|
+
error.results = matches.map(({record,...info})=>info);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return matches[0].record;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
search,
|
|
98
|
+
get,
|
|
99
|
+
character: id => get('characters', id),
|
|
100
|
+
item: id => get('items', id),
|
|
101
|
+
code: id => get('codes', id),
|
|
102
|
+
map: id => get('maps', id)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
module.exports = { createCatalog };
|
package/src/client-error.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
class TrackerError extends Error {
|
|
3
|
-
constructor(message,{status,cause}={}) {
|
|
4
|
-
super(message,{cause});
|
|
5
|
-
this.name='TrackerError';
|
|
6
|
-
if(status!==undefined)this.status=status;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
module.exports={TrackerError};
|
|
1
|
+
'use strict';
|
|
2
|
+
class TrackerError extends Error {
|
|
3
|
+
constructor(message,{status,cause}={}) {
|
|
4
|
+
super(message,{cause});
|
|
5
|
+
this.name='TrackerError';
|
|
6
|
+
if(status!==undefined)this.status=status;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
module.exports={TrackerError};
|
package/src/data-source.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const path=require('node:path');
|
|
3
|
-
module.exports=Object.freeze({
|
|
4
|
-
gameId:'warframe',
|
|
5
|
-
bundledDir:path.join(__dirname,'..','data'),
|
|
6
|
-
github:{owner:'NekoSuneProjectsNPM',repo:'game-tracker',branch:'JsonData',directory:'games/warframe'}
|
|
7
|
-
});
|
|
1
|
+
'use strict';
|
|
2
|
+
const path=require('node:path');
|
|
3
|
+
module.exports=Object.freeze({
|
|
4
|
+
gameId:'warframe',
|
|
5
|
+
bundledDir:path.join(__dirname,'..','data'),
|
|
6
|
+
github:{owner:'NekoSuneProjectsNPM',repo:'game-tracker',branch:'JsonData',directory:'games/warframe'}
|
|
7
|
+
});
|