@tf2-automatic/tf2-format 8.0.0
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/README.md +19 -0
- package/package.json +11 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +7 -0
- package/src/index.js.map +1 -0
- package/src/lib/parsing/econ/parser.d.ts +43 -0
- package/src/lib/parsing/econ/parser.js +747 -0
- package/src/lib/parsing/econ/parser.js.map +1 -0
- package/src/lib/parsing/econ/types.d.ts +107 -0
- package/src/lib/parsing/econ/types.js +3 -0
- package/src/lib/parsing/econ/types.js.map +1 -0
- package/src/lib/parsing/index.d.ts +3 -0
- package/src/lib/parsing/index.js +7 -0
- package/src/lib/parsing/index.js.map +1 -0
- package/src/lib/parsing/parser.d.ts +24 -0
- package/src/lib/parsing/parser.js +16 -0
- package/src/lib/parsing/parser.js.map +1 -0
- package/src/lib/parsing/tf2/constants.d.ts +90 -0
- package/src/lib/parsing/tf2/constants.js +90 -0
- package/src/lib/parsing/tf2/constants.js.map +1 -0
- package/src/lib/parsing/tf2/parser.d.ts +12 -0
- package/src/lib/parsing/tf2/parser.js +400 -0
- package/src/lib/parsing/tf2/parser.js.map +1 -0
- package/src/lib/parsing/tf2/tf2.proto +8 -0
- package/src/lib/parsing/tf2/types.d.ts +77 -0
- package/src/lib/parsing/tf2/types.js +3 -0
- package/src/lib/parsing/tf2/types.js.map +1 -0
- package/src/lib/sku/index.d.ts +1 -0
- package/src/lib/sku/index.js +5 -0
- package/src/lib/sku/index.js.map +1 -0
- package/src/lib/sku/sku.d.ts +5 -0
- package/src/lib/sku/sku.js +187 -0
- package/src/lib/sku/sku.js.map +1 -0
- package/src/lib/types.d.ts +313 -0
- package/src/lib/types.js +3 -0
- package/src/lib/types.js.map +1 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EconParser = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const parser_1 = require("../parser");
|
|
6
|
+
const TAGS_OF_INTEREST = new Set([
|
|
7
|
+
'Quality',
|
|
8
|
+
'Exterior',
|
|
9
|
+
'Rarity',
|
|
10
|
+
'Type',
|
|
11
|
+
]);
|
|
12
|
+
const NON_CRAFTABLE_DESCRIPTIONS = {
|
|
13
|
+
'( Not Usable in Crafting )': true,
|
|
14
|
+
'( Not Tradable, Marketable, or Usable in Crafting )': true,
|
|
15
|
+
'( Not Tradable, Marketable, Usable in Crafting, or Gift Wrappable )': true,
|
|
16
|
+
};
|
|
17
|
+
const WEAR = {
|
|
18
|
+
'Factory New': 1,
|
|
19
|
+
'Minimal Wear': 2,
|
|
20
|
+
'Field-Tested': 3,
|
|
21
|
+
'Well-Worn': 4,
|
|
22
|
+
'Battle Scarred': 5,
|
|
23
|
+
};
|
|
24
|
+
const KILLSTREAK_FABRICATORS = {
|
|
25
|
+
1: 6527,
|
|
26
|
+
2: 6523,
|
|
27
|
+
3: 6526,
|
|
28
|
+
};
|
|
29
|
+
const KILLSTREAK_TIERS = {
|
|
30
|
+
Killstreak: 1,
|
|
31
|
+
'Specialized Killstreak': 2,
|
|
32
|
+
'Professional Killstreak': 3,
|
|
33
|
+
};
|
|
34
|
+
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
|
|
35
|
+
var IdentifiableDescription;
|
|
36
|
+
(function (IdentifiableDescription) {
|
|
37
|
+
IdentifiableDescription[IdentifiableDescription["Skip"] = 0] = "Skip";
|
|
38
|
+
IdentifiableDescription[IdentifiableDescription["All"] = 1] = "All";
|
|
39
|
+
// Paints, spells and parts have the same precedence because it has been shown
|
|
40
|
+
// that they may appear in any order.
|
|
41
|
+
IdentifiableDescription[IdentifiableDescription["Paint"] = 1] = "Paint";
|
|
42
|
+
IdentifiableDescription[IdentifiableDescription["Spells"] = 1] = "Spells";
|
|
43
|
+
IdentifiableDescription[IdentifiableDescription["Parts"] = 1] = "Parts";
|
|
44
|
+
IdentifiableDescription[IdentifiableDescription["Effect"] = 2] = "Effect";
|
|
45
|
+
IdentifiableDescription[IdentifiableDescription["Festivized"] = 3] = "Festivized";
|
|
46
|
+
IdentifiableDescription[IdentifiableDescription["Killstreaker"] = 4] = "Killstreaker";
|
|
47
|
+
IdentifiableDescription[IdentifiableDescription["Sheen"] = 5] = "Sheen";
|
|
48
|
+
IdentifiableDescription[IdentifiableDescription["Killstreak"] = 6] = "Killstreak";
|
|
49
|
+
IdentifiableDescription[IdentifiableDescription["Texture"] = 7] = "Texture";
|
|
50
|
+
IdentifiableDescription[IdentifiableDescription["Input"] = 8] = "Input";
|
|
51
|
+
IdentifiableDescription[IdentifiableDescription["Output"] = 9] = "Output";
|
|
52
|
+
IdentifiableDescription[IdentifiableDescription["Target"] = 10] = "Target";
|
|
53
|
+
IdentifiableDescription[IdentifiableDescription["Uses"] = 11] = "Uses";
|
|
54
|
+
IdentifiableDescription[IdentifiableDescription["Craftable"] = 12] = "Craftable";
|
|
55
|
+
})(IdentifiableDescription || (IdentifiableDescription = {}));
|
|
56
|
+
/* eslint-enable @typescript-eslint/no-duplicate-enum-values */
|
|
57
|
+
/**
|
|
58
|
+
* This is a mapping from the item type to the description that we want to
|
|
59
|
+
* start the description parsing from
|
|
60
|
+
*/
|
|
61
|
+
var TypeToIdentifiableDescription;
|
|
62
|
+
(function (TypeToIdentifiableDescription) {
|
|
63
|
+
// Cosmetics can have many different descriptions so we will check for all
|
|
64
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Cosmetic"] = 1] = "Cosmetic";
|
|
65
|
+
// Taunts may have effects, so we will check for that
|
|
66
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Taunt 1"] = 2] = "Taunt 1";
|
|
67
|
+
// Nothing interesting in these items
|
|
68
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Party Favor"] = 0] = "Party Favor";
|
|
69
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Action"] = 0] = "Action";
|
|
70
|
+
// Most craft items do not have descriptions but we will check for craftable
|
|
71
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Craft Item"] = 12] = "Craft Item";
|
|
72
|
+
// Check for uses on limited use items
|
|
73
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Festivizer"] = 11] = "Festivizer";
|
|
74
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Tool"] = 11] = "Tool";
|
|
75
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Gift"] = 11] = "Gift";
|
|
76
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Strange Part"] = 11] = "Strange Part";
|
|
77
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Usable Item"] = 11] = "Usable Item";
|
|
78
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Crate"] = 11] = "Crate";
|
|
79
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Unusualifier"] = 9] = "Unusualifier";
|
|
80
|
+
// Start checking for the target
|
|
81
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Strangifier"] = 10] = "Strangifier";
|
|
82
|
+
// War paints may have effects
|
|
83
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["War Paint"] = 2] = "War Paint";
|
|
84
|
+
// Start checking for inputs
|
|
85
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Recipe"] = 8] = "Recipe";
|
|
86
|
+
// We know what we should look for in Killstreak Kits
|
|
87
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Professional Killstreak Kit"] = 4] = "Professional Killstreak Kit";
|
|
88
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Specialized Killstreak Kit"] = 5] = "Specialized Killstreak Kit";
|
|
89
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Killstreak Kit"] = 6] = "Killstreak Kit";
|
|
90
|
+
// Start by checking for parts on any weapons
|
|
91
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Primary weapon"] = 1] = "Primary weapon";
|
|
92
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Secondary weapon"] = 1] = "Secondary weapon";
|
|
93
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Melee weapon"] = 1] = "Melee weapon";
|
|
94
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Building"] = 1] = "Building";
|
|
95
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Primary PDA"] = 1] = "Primary PDA";
|
|
96
|
+
TypeToIdentifiableDescription[TypeToIdentifiableDescription["Secondary PDA"] = 1] = "Secondary PDA";
|
|
97
|
+
})(TypeToIdentifiableDescription || (TypeToIdentifiableDescription = {}));
|
|
98
|
+
const WEAPON_TYPES = new Set([
|
|
99
|
+
'Primary weapon',
|
|
100
|
+
'Secondary weapon',
|
|
101
|
+
'Melee weapon',
|
|
102
|
+
'Building',
|
|
103
|
+
'Primary PDA',
|
|
104
|
+
'Secondary PDA',
|
|
105
|
+
]);
|
|
106
|
+
class EconParser extends parser_1.Parser {
|
|
107
|
+
extract(item) {
|
|
108
|
+
var _a, _b, _c;
|
|
109
|
+
const tags = EconParser.getTagAttributes(item);
|
|
110
|
+
let start;
|
|
111
|
+
if (tags.Type !== undefined) {
|
|
112
|
+
start = TypeToIdentifiableDescription[tags.Type];
|
|
113
|
+
}
|
|
114
|
+
else if (item.type !== '') {
|
|
115
|
+
// If we are missing the type from the tags we can in some cases get it
|
|
116
|
+
// from the type attribute of the item.
|
|
117
|
+
const match = item.type.match(/Level\s\d+\s(.+)/);
|
|
118
|
+
if (match !== null) {
|
|
119
|
+
start = TypeToIdentifiableDescription[match[1]];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const descriptions = EconParser.getDescriptionAttributes(item, tags, start);
|
|
123
|
+
const raw = {
|
|
124
|
+
type: (_a = tags.Type) !== null && _a !== void 0 ? _a : null,
|
|
125
|
+
assetid: item.assetid,
|
|
126
|
+
defindex: EconParser.getDefindex(item),
|
|
127
|
+
quality: (_b = tags.Quality) !== null && _b !== void 0 ? _b : null,
|
|
128
|
+
elevated: false,
|
|
129
|
+
craftable: descriptions.craftable,
|
|
130
|
+
tradable: item.tradable,
|
|
131
|
+
australium: EconParser.isAustralium(item, tags),
|
|
132
|
+
festivized: descriptions.festivized,
|
|
133
|
+
effect: descriptions.effect,
|
|
134
|
+
wear: (_c = tags.Exterior) !== null && _c !== void 0 ? _c : null,
|
|
135
|
+
paint: descriptions.paint,
|
|
136
|
+
killstreak: EconParser.getKillstreak(descriptions),
|
|
137
|
+
sheen: descriptions.sheen,
|
|
138
|
+
killstreaker: descriptions.killstreaker,
|
|
139
|
+
spells: descriptions.spells,
|
|
140
|
+
parts: descriptions.parts,
|
|
141
|
+
paintkit: descriptions.paintkit,
|
|
142
|
+
uses: descriptions.uses,
|
|
143
|
+
inputs: descriptions.inputs,
|
|
144
|
+
output: descriptions.output,
|
|
145
|
+
outputQuality: null,
|
|
146
|
+
target: descriptions.target,
|
|
147
|
+
crateSeries: EconParser.getCrateSeries(tags, item),
|
|
148
|
+
};
|
|
149
|
+
// Clean up the painkit name and overwrite the quality in certain cases
|
|
150
|
+
if (raw.paintkit !== null) {
|
|
151
|
+
if (tags.Rarity !== undefined && tags.Exterior !== undefined) {
|
|
152
|
+
// The length is the length of the description minus the length of the
|
|
153
|
+
// grade and wear
|
|
154
|
+
const length = item.descriptions[0].value.length -
|
|
155
|
+
(tags.Exterior.length + 3 + tags.Rarity.length + 7);
|
|
156
|
+
// The new paintkit is the old paintkit without the name of the item
|
|
157
|
+
raw.paintkit = raw.paintkit.slice(0, raw.paintkit.length - length - 1);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Some items with a paintkit does not have a rarity, so we remove
|
|
161
|
+
// the " War Paint" from the paintkit name manually
|
|
162
|
+
const index = raw.paintkit.indexOf(' War Paint');
|
|
163
|
+
if (index !== -1) {
|
|
164
|
+
raw.paintkit = raw.paintkit.slice(0, index);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const isWarPaint = tags.Type === 'War Paint';
|
|
168
|
+
// Set the quality of the item based on certain conditions to better match
|
|
169
|
+
// how the TF2 GC stores the quality of items with a paint kit.
|
|
170
|
+
if (raw.effect !== null && !isWarPaint) {
|
|
171
|
+
// The quality of unusual war paints is still "Unusual", hence the check
|
|
172
|
+
// for the type.
|
|
173
|
+
raw.quality = 'Decorated Weapon';
|
|
174
|
+
}
|
|
175
|
+
else if (isWarPaint && descriptions.statclock) {
|
|
176
|
+
// If the item is a war paint and it has a statclock attatched then we
|
|
177
|
+
// know that the quality is strange.
|
|
178
|
+
raw.quality = 'Strange';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Handle elevated qualities
|
|
182
|
+
if (raw.quality !== 'Strange') {
|
|
183
|
+
const lastChar = item.type.charCodeAt(item.type.length - 1);
|
|
184
|
+
// Check if last char is a number
|
|
185
|
+
if (lastChar >= 48 && lastChar <= 57) {
|
|
186
|
+
let dashIndex = -1;
|
|
187
|
+
let colonIndex = -1;
|
|
188
|
+
// We can skip 2 chars, this is because we know that there is atleast a
|
|
189
|
+
// space before the number
|
|
190
|
+
for (let i = item.type.length - 3; i >= 0; i--) {
|
|
191
|
+
if (item.type.charAt(i) === '-') {
|
|
192
|
+
dashIndex = i;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
if (item.type.charAt(i) === ':') {
|
|
196
|
+
colonIndex = i;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (colonIndex > dashIndex) {
|
|
200
|
+
raw.elevated = true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (raw.output !== null) {
|
|
205
|
+
// This is a little dumb but it is for consistency with the TF2 API
|
|
206
|
+
raw.outputQuality = 'Unique';
|
|
207
|
+
if (raw.killstreak !== 0) {
|
|
208
|
+
// Handle Killstreak Kit Fabricators
|
|
209
|
+
raw.target = raw.output.slice(raw.output.indexOf('Killstreak ') + 11, raw.output.lastIndexOf(' Kit'));
|
|
210
|
+
raw.output = 'Kit';
|
|
211
|
+
}
|
|
212
|
+
else if (raw.output.endsWith('Strangifier')) {
|
|
213
|
+
// Handle Strangifier Chemistry Sets
|
|
214
|
+
raw.target = raw.output.slice(0, raw.output.length - 12);
|
|
215
|
+
raw.output = 'Strangifier';
|
|
216
|
+
}
|
|
217
|
+
else if (raw.output.startsWith("Collector's ")) {
|
|
218
|
+
// Handle Collector's Chemistry Sets
|
|
219
|
+
raw.output = raw.output.slice(12);
|
|
220
|
+
raw.outputQuality = "Collector's";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return raw;
|
|
224
|
+
}
|
|
225
|
+
parse(raw) {
|
|
226
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
227
|
+
if (raw.defindex === null) {
|
|
228
|
+
throw new Error('Defindex is null');
|
|
229
|
+
}
|
|
230
|
+
// TODO: Can probably optimize the fetching of values
|
|
231
|
+
// Maybe start fetching and then await the promises at the end?
|
|
232
|
+
// Get quality from some cache
|
|
233
|
+
let quality = raw.quality !== null ? this.schema.getQualityByName(raw.quality) : null;
|
|
234
|
+
if (quality === undefined) {
|
|
235
|
+
// Quality is not in cache, fetch it from the schema
|
|
236
|
+
quality = yield this.schema.fetchQualityByName(raw.quality);
|
|
237
|
+
}
|
|
238
|
+
else if (quality instanceof Error) {
|
|
239
|
+
throw quality;
|
|
240
|
+
}
|
|
241
|
+
if (!quality) {
|
|
242
|
+
throw new Error('Quality is undefined');
|
|
243
|
+
}
|
|
244
|
+
let effect = raw.effect !== null ? this.schema.getEffectByName(raw.effect) : null;
|
|
245
|
+
if (effect === undefined) {
|
|
246
|
+
effect = yield this.schema.fetchEffectByName(raw.effect);
|
|
247
|
+
}
|
|
248
|
+
else if (effect instanceof Error) {
|
|
249
|
+
throw effect;
|
|
250
|
+
}
|
|
251
|
+
let paintkit = raw.paintkit !== null ? this.schema.getTextureByName(raw.paintkit) : null;
|
|
252
|
+
if (paintkit === undefined) {
|
|
253
|
+
paintkit = yield this.schema.fetchTextureByName(raw.paintkit);
|
|
254
|
+
}
|
|
255
|
+
else if (paintkit instanceof Error) {
|
|
256
|
+
throw paintkit;
|
|
257
|
+
}
|
|
258
|
+
let wear = null;
|
|
259
|
+
if (raw.wear !== null) {
|
|
260
|
+
wear = WEAR[raw.wear];
|
|
261
|
+
}
|
|
262
|
+
let target = raw.target !== null ? this.schema.getDefindexByName(raw.target) : null;
|
|
263
|
+
if (target === undefined) {
|
|
264
|
+
target = yield this.schema.fetchDefindexByName(raw.target);
|
|
265
|
+
}
|
|
266
|
+
let outputQuality = raw.outputQuality !== null
|
|
267
|
+
? this.schema.getQualityByName(raw.outputQuality)
|
|
268
|
+
: null;
|
|
269
|
+
if (outputQuality === undefined) {
|
|
270
|
+
outputQuality = yield this.schema.fetchQualityByName(raw.outputQuality);
|
|
271
|
+
}
|
|
272
|
+
else if (outputQuality instanceof Error) {
|
|
273
|
+
throw outputQuality;
|
|
274
|
+
}
|
|
275
|
+
let output = null;
|
|
276
|
+
if (raw.output !== null) {
|
|
277
|
+
if (raw.output === 'Kit') {
|
|
278
|
+
output = KILLSTREAK_FABRICATORS[raw.killstreak];
|
|
279
|
+
}
|
|
280
|
+
else if (raw.output === 'Strangifier') {
|
|
281
|
+
output = 6522;
|
|
282
|
+
}
|
|
283
|
+
else if (raw.defindex === 20006) {
|
|
284
|
+
output = yield this.schema.fetchDefindexByName(raw.output);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const intermediate = this.schema.getDefindexByName(raw.output);
|
|
288
|
+
if (intermediate === undefined) {
|
|
289
|
+
output = yield this.schema.fetchDefindexByName(raw.output);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
output = intermediate;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const parts = [];
|
|
297
|
+
for (const scoreType of raw.parts) {
|
|
298
|
+
const part = yield this.getPartByScoreType(scoreType, raw.type);
|
|
299
|
+
if (part !== null) {
|
|
300
|
+
parts.push(part);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const spells = [];
|
|
304
|
+
for (const spell of raw.spells) {
|
|
305
|
+
const cached = this.schema.getSpellByName(spell);
|
|
306
|
+
if (cached instanceof Error) {
|
|
307
|
+
throw cached;
|
|
308
|
+
}
|
|
309
|
+
else if (cached !== undefined) {
|
|
310
|
+
spells.push(cached);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const fetched = yield this.schema.fetchSpellByName(spell);
|
|
314
|
+
if (fetched) {
|
|
315
|
+
spells.push(fetched);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
let paint = raw.paint !== null ? this.schema.getDefindexByName(raw.paint) : null;
|
|
319
|
+
if (paint === undefined) {
|
|
320
|
+
paint = yield this.schema.fetchDefindexByName(raw.paint);
|
|
321
|
+
}
|
|
322
|
+
let inputs = null;
|
|
323
|
+
if (raw.inputs !== null) {
|
|
324
|
+
inputs = [];
|
|
325
|
+
for (const rawInput of raw.inputs) {
|
|
326
|
+
let name = rawInput.name;
|
|
327
|
+
let rawQuality = 'Unique';
|
|
328
|
+
// Handle strange items for strangifier chemistry sets
|
|
329
|
+
if (name.startsWith('Strange ') && raw.output === 'Strangifier') {
|
|
330
|
+
name = name.slice(8);
|
|
331
|
+
rawQuality = 'Strange';
|
|
332
|
+
}
|
|
333
|
+
// Get the quality
|
|
334
|
+
let quality = this.schema.getQualityByName(rawQuality);
|
|
335
|
+
if (quality === undefined) {
|
|
336
|
+
quality = yield this.schema.fetchQualityByName(rawQuality);
|
|
337
|
+
}
|
|
338
|
+
else if (quality instanceof Error) {
|
|
339
|
+
throw quality;
|
|
340
|
+
}
|
|
341
|
+
const input = {
|
|
342
|
+
quality,
|
|
343
|
+
amount: rawInput.amount,
|
|
344
|
+
};
|
|
345
|
+
// Handle killstreak items for killstreak kit fabricators
|
|
346
|
+
if (name.startsWith('Unique') && name.endsWith(' Killstreak Item')) {
|
|
347
|
+
const killstreak = name.slice(7, name.length - 5);
|
|
348
|
+
name = null;
|
|
349
|
+
input.killstreak = KILLSTREAK_TIERS[killstreak];
|
|
350
|
+
}
|
|
351
|
+
if (name !== null) {
|
|
352
|
+
// Get the defindex of the input item
|
|
353
|
+
let defindex = this.getInputByName(name);
|
|
354
|
+
if (defindex === undefined) {
|
|
355
|
+
defindex = yield this.fetchInputByName(name);
|
|
356
|
+
}
|
|
357
|
+
if (!defindex) {
|
|
358
|
+
throw new Error(`Could not find the defindex of the input item "${name}"`);
|
|
359
|
+
}
|
|
360
|
+
input.defindex = defindex;
|
|
361
|
+
}
|
|
362
|
+
inputs.push(input);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const parsed = {
|
|
366
|
+
assetid: raw.assetid,
|
|
367
|
+
defindex: raw.defindex,
|
|
368
|
+
quality,
|
|
369
|
+
craftable: raw.craftable,
|
|
370
|
+
tradable: raw.tradable,
|
|
371
|
+
australium: raw.australium,
|
|
372
|
+
festivized: raw.festivized,
|
|
373
|
+
effect,
|
|
374
|
+
wear,
|
|
375
|
+
paintkit,
|
|
376
|
+
killstreak: raw.killstreak,
|
|
377
|
+
target,
|
|
378
|
+
output,
|
|
379
|
+
outputQuality,
|
|
380
|
+
elevated: raw.elevated,
|
|
381
|
+
crateSeries: raw.crateSeries,
|
|
382
|
+
paint,
|
|
383
|
+
parts,
|
|
384
|
+
spells,
|
|
385
|
+
sheen: raw.sheen,
|
|
386
|
+
killstreaker: raw.killstreaker,
|
|
387
|
+
inputs,
|
|
388
|
+
};
|
|
389
|
+
return parsed;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
getPartByScoreType(scoreType, itemType) {
|
|
393
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
394
|
+
let part = this.schema.getStrangePartByScoreType(scoreType);
|
|
395
|
+
if (part === undefined) {
|
|
396
|
+
part = yield this.schema.fetchStrangePartByScoreType(scoreType);
|
|
397
|
+
}
|
|
398
|
+
if (!part) {
|
|
399
|
+
return part;
|
|
400
|
+
}
|
|
401
|
+
let item = this.schema.getItemByDefindex(part);
|
|
402
|
+
if (item === undefined) {
|
|
403
|
+
item = yield this.schema.fetchItemByDefindex(part);
|
|
404
|
+
}
|
|
405
|
+
else if (item instanceof Error) {
|
|
406
|
+
throw item;
|
|
407
|
+
}
|
|
408
|
+
if (!item) {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
if (item.name.startsWith('Strange Cosmetic Part: ') &&
|
|
412
|
+
itemType !== 'Cosmetic') {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return part;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
getInputByName(name) {
|
|
419
|
+
if (name.startsWith('The ')) {
|
|
420
|
+
const input = this.schema.getDefindexByName(name.slice(4));
|
|
421
|
+
if (input) {
|
|
422
|
+
return input;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return this.schema.getDefindexByName(name);
|
|
426
|
+
}
|
|
427
|
+
fetchInputByName(name) {
|
|
428
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
429
|
+
if (name.startsWith('The ')) {
|
|
430
|
+
const input = yield this.schema.fetchDefindexByName(name.slice(4));
|
|
431
|
+
if (input) {
|
|
432
|
+
return input;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return this.schema.fetchDefindexByName(name);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
static getDefindex(item) {
|
|
439
|
+
for (const action of item.actions) {
|
|
440
|
+
if (action.name !== 'Item Wiki Page...') {
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const defindex = new URL(action.link).searchParams.get('id');
|
|
444
|
+
if (defindex === null) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
return parseInt(defindex, 10);
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
static getKillstreak(descriptions) {
|
|
452
|
+
if (descriptions.killstreaker !== null) {
|
|
453
|
+
return 3;
|
|
454
|
+
}
|
|
455
|
+
else if (descriptions.sheen !== null) {
|
|
456
|
+
return 2;
|
|
457
|
+
}
|
|
458
|
+
else if (descriptions.killstreak) {
|
|
459
|
+
return 1;
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
return 0;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
static getCrateSeries(tags, item) {
|
|
466
|
+
if (tags.Type !== 'Crate') {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
// We can get the crate series from the description of the item, but that
|
|
470
|
+
// would add more description checks.
|
|
471
|
+
const lastChar = item.market_hash_name.charCodeAt(item.market_hash_name.length - 1);
|
|
472
|
+
if (!(lastChar >= 48 && lastChar <= 57)) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
// We know there is atleast one number in the name
|
|
476
|
+
let series = lastChar - 48;
|
|
477
|
+
const length = item.market_hash_name.length;
|
|
478
|
+
// We start from the end of the string and go backwards towards a number sign
|
|
479
|
+
for (let i = length - 2; i >= length - 4; i--) {
|
|
480
|
+
// Get the char code of the current character
|
|
481
|
+
const charCode = item.market_hash_name.charCodeAt(i);
|
|
482
|
+
if (charCode >= 48 && charCode <= 57) {
|
|
483
|
+
// The character is a number
|
|
484
|
+
series = series + (charCode - 48) * Math.pow(10, length - i - 1);
|
|
485
|
+
}
|
|
486
|
+
else if (charCode === 35) {
|
|
487
|
+
// The character is a number sign
|
|
488
|
+
return series;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
// The character is not a number or a number sign
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
static getTagAttributes(item) {
|
|
498
|
+
const tags = {};
|
|
499
|
+
let tagCount = 0;
|
|
500
|
+
for (const tag of item.tags) {
|
|
501
|
+
if (TAGS_OF_INTEREST.has(tag.category)) {
|
|
502
|
+
tagCount++;
|
|
503
|
+
tags[tag.category] = tag.name;
|
|
504
|
+
if (tagCount === TAGS_OF_INTEREST.size) {
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return tags;
|
|
510
|
+
}
|
|
511
|
+
static isAustralium(item, tags) {
|
|
512
|
+
var _a;
|
|
513
|
+
// It might be possible for australiums to be qualities other than Strange.
|
|
514
|
+
// But I think all australium items are a weapon of some sort.
|
|
515
|
+
if (!WEAPON_TYPES.has((_a = tags.Type) !== null && _a !== void 0 ? _a : '')) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
// All australium items have Australium in their name. Except for "Saxxy",
|
|
519
|
+
// but that item is always australium.
|
|
520
|
+
return item.market_hash_name.includes('Australium ');
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* This function tries to extract as much useful information as possible from
|
|
524
|
+
* the descriptions of an item.
|
|
525
|
+
* @param item The item to extract the information from
|
|
526
|
+
* @param tags The tags of the item
|
|
527
|
+
* @param start The description to start from
|
|
528
|
+
* @returns An object with the extracted information
|
|
529
|
+
*/
|
|
530
|
+
static getDescriptionAttributes(item, tags, start = IdentifiableDescription.Paint) {
|
|
531
|
+
const attributes = {
|
|
532
|
+
craftable: true,
|
|
533
|
+
effect: null,
|
|
534
|
+
killstreak: false,
|
|
535
|
+
sheen: null,
|
|
536
|
+
killstreaker: null,
|
|
537
|
+
spells: [],
|
|
538
|
+
parts: [],
|
|
539
|
+
paint: null,
|
|
540
|
+
festivized: false,
|
|
541
|
+
paintkit: null,
|
|
542
|
+
uses: null,
|
|
543
|
+
inputs: null,
|
|
544
|
+
output: null,
|
|
545
|
+
target: null,
|
|
546
|
+
grade: null,
|
|
547
|
+
statclock: false,
|
|
548
|
+
};
|
|
549
|
+
if (start === IdentifiableDescription.Skip) {
|
|
550
|
+
return attributes;
|
|
551
|
+
}
|
|
552
|
+
let next = start;
|
|
553
|
+
const descriptions = item.descriptions;
|
|
554
|
+
// Add empty description to make sure we do not get out of bounds errors
|
|
555
|
+
descriptions.push({ value: '' });
|
|
556
|
+
let i = 0;
|
|
557
|
+
// I don't like that this is outside the switch statement but it is the most
|
|
558
|
+
// efficient way to do it.
|
|
559
|
+
if (tags.Rarity !== undefined) {
|
|
560
|
+
if (tags.Exterior !== undefined &&
|
|
561
|
+
descriptions[i].value.startsWith(tags.Rarity + ' Grade ') &&
|
|
562
|
+
descriptions[i].value.endsWith(' (' + tags.Exterior + ')')) {
|
|
563
|
+
attributes.grade = descriptions[i].value;
|
|
564
|
+
i++;
|
|
565
|
+
}
|
|
566
|
+
if (descriptions[i].value === 'Strange Stat Clock Attached') {
|
|
567
|
+
attributes.statclock = true;
|
|
568
|
+
i++;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
loop: while (i < descriptions.length - 1) {
|
|
572
|
+
// Skip descriptions that are too short/long to contain any useful information
|
|
573
|
+
// TODO: Fix "magic numbers"
|
|
574
|
+
if (descriptions[i].value.length < 10) {
|
|
575
|
+
i++;
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
/* eslint-disable no-fallthrough */
|
|
579
|
+
switch (next) {
|
|
580
|
+
case IdentifiableDescription.All:
|
|
581
|
+
case IdentifiableDescription.Paint:
|
|
582
|
+
if (descriptions[i].value.startsWith('Paint Color: ')) {
|
|
583
|
+
attributes.paint = descriptions[i].value.slice(13);
|
|
584
|
+
next = IdentifiableDescription.Spells;
|
|
585
|
+
i++;
|
|
586
|
+
}
|
|
587
|
+
case IdentifiableDescription.Spells:
|
|
588
|
+
if (descriptions[i].value.startsWith('Halloween: ')) {
|
|
589
|
+
attributes.spells.push(descriptions[i].value.slice(11, descriptions[i].value.lastIndexOf(' (spell only active during event)')));
|
|
590
|
+
// This is redundant
|
|
591
|
+
next = IdentifiableDescription.Spells;
|
|
592
|
+
i++;
|
|
593
|
+
// We break because that might be the best to do if there are more spells
|
|
594
|
+
continue loop;
|
|
595
|
+
}
|
|
596
|
+
case IdentifiableDescription.Parts:
|
|
597
|
+
if (descriptions[i].value.startsWith('(') &&
|
|
598
|
+
// Make sure it does not match Craftable/Tradable/Marketable descriptions
|
|
599
|
+
descriptions[i].value.charAt(1) !== ' ') {
|
|
600
|
+
attributes.parts.push(descriptions[i].value.slice(1, descriptions[i].value.lastIndexOf(': ')));
|
|
601
|
+
next = IdentifiableDescription.Parts;
|
|
602
|
+
i++;
|
|
603
|
+
// Break again so we can check for more parts
|
|
604
|
+
continue loop;
|
|
605
|
+
}
|
|
606
|
+
else if (descriptions[i].value.startsWith(' ')) {
|
|
607
|
+
attributes.parts.push(descriptions[i].value.slice(5, descriptions[i].value.lastIndexOf(': ')));
|
|
608
|
+
next = IdentifiableDescription.Parts;
|
|
609
|
+
i++;
|
|
610
|
+
// Break again so we can check for more parts
|
|
611
|
+
continue loop;
|
|
612
|
+
}
|
|
613
|
+
case IdentifiableDescription.Effect:
|
|
614
|
+
if (descriptions[i].value.startsWith('\u2605 Unusual Effect: ')) {
|
|
615
|
+
attributes.effect = descriptions[i].value.slice(18);
|
|
616
|
+
next = IdentifiableDescription.Festivized;
|
|
617
|
+
i++;
|
|
618
|
+
}
|
|
619
|
+
case IdentifiableDescription.Festivized:
|
|
620
|
+
if (descriptions[i].value === 'Festivized') {
|
|
621
|
+
attributes.festivized = true;
|
|
622
|
+
next = IdentifiableDescription.Killstreaker;
|
|
623
|
+
i++;
|
|
624
|
+
}
|
|
625
|
+
case IdentifiableDescription.Killstreaker:
|
|
626
|
+
if (descriptions[i].value.startsWith('Killstreaker: ')) {
|
|
627
|
+
attributes.killstreaker = descriptions[i].value.slice(14);
|
|
628
|
+
next = IdentifiableDescription.Sheen;
|
|
629
|
+
i++;
|
|
630
|
+
}
|
|
631
|
+
case IdentifiableDescription.Sheen:
|
|
632
|
+
if (descriptions[i].value.startsWith('Sheen: ')) {
|
|
633
|
+
attributes.sheen = descriptions[i].value.slice(7);
|
|
634
|
+
next = IdentifiableDescription.Killstreak;
|
|
635
|
+
i++;
|
|
636
|
+
}
|
|
637
|
+
case IdentifiableDescription.Killstreak:
|
|
638
|
+
if (descriptions[i].value === 'Killstreaks Active') {
|
|
639
|
+
attributes.killstreak = true;
|
|
640
|
+
next = IdentifiableDescription.Texture;
|
|
641
|
+
i++;
|
|
642
|
+
}
|
|
643
|
+
case IdentifiableDescription.Texture: {
|
|
644
|
+
let extract = false;
|
|
645
|
+
if (descriptions[i].value.endsWith('Collection')) {
|
|
646
|
+
if (tags.Exterior !== undefined) {
|
|
647
|
+
next = IdentifiableDescription.Texture;
|
|
648
|
+
extract = true;
|
|
649
|
+
}
|
|
650
|
+
i++;
|
|
651
|
+
// Might be a little "dangerous" to use these while loops
|
|
652
|
+
while (descriptions[i].value.charAt(0) === ' ') {
|
|
653
|
+
i++;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
let paintkit = null;
|
|
657
|
+
let found = false;
|
|
658
|
+
if (descriptions[i].value.startsWith('\u2714 ')) {
|
|
659
|
+
paintkit = descriptions[i].value.slice(2);
|
|
660
|
+
found = true;
|
|
661
|
+
}
|
|
662
|
+
else if (descriptions[i].value.startsWith('\u2605 ')) {
|
|
663
|
+
found = true;
|
|
664
|
+
paintkit = descriptions[i].value.slice(2);
|
|
665
|
+
}
|
|
666
|
+
if (found) {
|
|
667
|
+
i++;
|
|
668
|
+
// Skip all the other stuff if any
|
|
669
|
+
while (descriptions[i].value.charAt(0) === ' ') {
|
|
670
|
+
i++;
|
|
671
|
+
}
|
|
672
|
+
if (extract) {
|
|
673
|
+
attributes.paintkit = paintkit;
|
|
674
|
+
next = IdentifiableDescription.Uses;
|
|
675
|
+
}
|
|
676
|
+
// We continue the loop so that we skip to the next checks
|
|
677
|
+
continue loop;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
case IdentifiableDescription.Input:
|
|
681
|
+
if (descriptions[i].value ===
|
|
682
|
+
'The following are the inputs that must be fulfilled.') {
|
|
683
|
+
i++;
|
|
684
|
+
const inputs = [];
|
|
685
|
+
attributes.inputs = inputs;
|
|
686
|
+
while (descriptions[i].value !== ' ') {
|
|
687
|
+
const split = descriptions[i].value.lastIndexOf(' x ');
|
|
688
|
+
const name = descriptions[i].value.slice(0, split);
|
|
689
|
+
const amount = parseInt(descriptions[i].value.slice(split + 3), 10);
|
|
690
|
+
inputs.push({ name, amount });
|
|
691
|
+
i++;
|
|
692
|
+
}
|
|
693
|
+
next = IdentifiableDescription.Output;
|
|
694
|
+
}
|
|
695
|
+
case IdentifiableDescription.Output:
|
|
696
|
+
if (descriptions[i].value ===
|
|
697
|
+
'You will receive all of the following outputs once all of the inputs are fulfilled.') {
|
|
698
|
+
i++;
|
|
699
|
+
attributes.output = descriptions[i].value;
|
|
700
|
+
i++;
|
|
701
|
+
if (descriptions[i].value !== ' ') {
|
|
702
|
+
let start = 1;
|
|
703
|
+
if (descriptions[i].value.startsWith('(Killstreaker: ')) {
|
|
704
|
+
start = descriptions[i].value.indexOf(', ');
|
|
705
|
+
attributes.killstreaker = descriptions[i].value.slice(15, start);
|
|
706
|
+
start += 2;
|
|
707
|
+
}
|
|
708
|
+
attributes.sheen = descriptions[i].value.slice(start + 7, descriptions[i].value.length - 1);
|
|
709
|
+
i++;
|
|
710
|
+
}
|
|
711
|
+
next = IdentifiableDescription.Uses;
|
|
712
|
+
}
|
|
713
|
+
case IdentifiableDescription.Target:
|
|
714
|
+
if (descriptions[i].value.startsWith('This ')) {
|
|
715
|
+
// Can probably speed this up by just slicing strings instead of
|
|
716
|
+
// using regex
|
|
717
|
+
const match = descriptions[i].value.match(/^This .*? can be applied to a (.*?)(?=\.)/);
|
|
718
|
+
if (match !== null) {
|
|
719
|
+
attributes.target = match[1];
|
|
720
|
+
next = IdentifiableDescription.Uses;
|
|
721
|
+
i++;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
case IdentifiableDescription.Uses:
|
|
725
|
+
if (descriptions[i].value.startsWith('This is a limited use item. Uses: ')) {
|
|
726
|
+
attributes.uses = parseInt(descriptions[i].value.slice(34), 10);
|
|
727
|
+
next = IdentifiableDescription.Craftable;
|
|
728
|
+
i++;
|
|
729
|
+
}
|
|
730
|
+
case IdentifiableDescription.Craftable:
|
|
731
|
+
if (NON_CRAFTABLE_DESCRIPTIONS[descriptions[i].value]) {
|
|
732
|
+
attributes.craftable = false;
|
|
733
|
+
// We found everything we could possibly look for.
|
|
734
|
+
break loop;
|
|
735
|
+
}
|
|
736
|
+
default:
|
|
737
|
+
i++;
|
|
738
|
+
continue loop;
|
|
739
|
+
}
|
|
740
|
+
/* eslint-enable no-fallthrough */
|
|
741
|
+
}
|
|
742
|
+
return attributes;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
exports.EconParser = EconParser;
|
|
746
|
+
tslib_1.__exportStar(require("./types"), exports);
|
|
747
|
+
//# sourceMappingURL=parser.js.map
|