@juice789/tf2items 2.0.0 → 2.0.2

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 CHANGED
@@ -1,3 +1,450 @@
1
+ # @juice789/tf2items
2
+
3
+ Utility functions for working with Team Fortress 2 items: building and parsing **SKUs**, converting Steam economy items / backpack.tf listings into SKUs, generating marketplace URLs, and building/reading the bundled item **schema** (`schema.json`) — a pre-built snapshot derived from the game's own item schema.
4
+
1
5
  sku playground: https://juice789.github.io/tf2items/
2
6
 
3
- todo readme
7
+ ## Install
8
+
9
+ ```
10
+ npm install @juice789/tf2items
11
+ ```
12
+
13
+ ## Node vs Browser
14
+
15
+ This package has two entry points:
16
+
17
+ - **Node** (`main` / default export) — `app.js`. Includes everything: SKU helpers, the schema, link builders, econ-item/listing converters, **and** the schema-building functions (`saveSchema`, `getInstance`, `fromEconItem`, etc.) which probably won't run in your browser.
18
+ - **Browser** (`browser` export condition) — `browser.js`. A safe subset for bundlers/browsers: only the SKU helpers, schema lookups, and link builders. Nothing that touches the network, the Steam API, or Node built-ins.
19
+
20
+ Bundlers that respect the `"browser"` field/condition (webpack, Vite, etc.) will pick `browser.js` automatically. If you `import` directly in Node, you get `app.js`.
21
+
22
+ ```js
23
+ // Node
24
+ import { skuFromItem, saveSchema, getInstance } from '@juice789/tf2items'
25
+
26
+ // Browser bundle
27
+ import { skuFromItem, marketHashNameFromSku } from '@juice789/tf2items'
28
+ ```
29
+
30
+ Everything documented below is available from both entry points **except** `saveSchema`, `getInstance`, `fromEconItem`/`fromEconItemOptions`, `fromListingV1`, `fromListingV2`, and a handful of related internal schema-fetching helpers — those are Node-only and are called out as such.
31
+
32
+ ---
33
+
34
+ ## Table of contents
35
+
36
+ 1. [SKUs](#skus)
37
+ 2. [Converting Steam EconItems into SKUs](#converting-steam-econitems-into-skus)
38
+ 3. [Converting backpack.tf listings into SKUs](#converting-backpacktf-listings-into-skus)
39
+ 4. [Link builders (`skuLinks.js`)](#link-builders-skulinksjs)
40
+ 5. [Steam Community items (trading cards, backgrounds, emotes, etc...)](#steam-community-items-trading-cards-backgrounds-emotes-etc)
41
+ 6. [The schema](#the-schema)
42
+ 7. [Building the schema yourself (`saveSchema`)](#building-the-schema-yourself-saveschema)
43
+ 8. [Reference data exports](#reference-data-exports)
44
+
45
+ ---
46
+
47
+ ## SKUs
48
+
49
+ A **SKU** is this package's compact string encoding of a TF2 item, e.g. `"5021;6"` (a Mann Co. Supply Crate Key) or `"205;11;kt-3;festive"` (a Strange Festivized Professional Killstreak Rocket Launcher). It's built from `;`-separated parts:
50
+
51
+ | Part | Meaning | Example |
52
+ |---|---|---|
53
+ | `<defindex>` | item definition index (always first) | `5021` |
54
+ | `<quality>` | quality id, see [`qualityNames`](#reference-data-exports) | `6` |
55
+ | `uncraftable` | item is non-craftable | `uncraftable` |
56
+ | `strange` | "Strange" / elevated quality | `strange` |
57
+ | `u-<id>` | unusual particle effect id | `u-702` |
58
+ | `kt-<1, 2 or 3>` | killstreak tier (Killstreak / Specialized / Professional) | `kt-3` |
59
+ | `festive` | festivized | `festive` |
60
+ | `td-<defindex>` | **target** defindex — the item a Strangifier/Kit/Killstreak Kit/Unusualifier applies to | `td-588` |
61
+ | `od-<defindex>` | **output** defindex — what a Fabricator/Chemistry Set produces | `od-6523` |
62
+ | `oq-<id>` | output quality — quality of the produced `od` item | `oq-6` |
63
+ | `pk-<id>` | paintkit/texture id (War Paints, Decorated weapons) | `pk-213` |
64
+ | `w-<1-5>` | wear tier (Factory New .. Battle Scarred) | `w-1` |
65
+ | `australium` | is Australium | `australium` |
66
+ | `c-<n>` | crate/case series number | `c-31` |
67
+ | `no-<n>` | "Number Of" — the low craft number (1-100) on items that track it, shown as `#13` in the name | `no-13` |
68
+ | `pc-<defindex>` | paint can defindex applied to the item — **omitted by default** when constructing the sku from an `EconItem`, see [below](#fromeconitemoptionsoptions---fromeconitem) | `pc-5052` |
69
+ | `hs-<defindex[_defindex...]>` | Halloween spell defindex(es), `_`-joined if more than one — **omitted by default** when constructing the sku from an `EconItem`, see [below](#fromeconitemoptionsoptions---fromeconitem) | `hs-8921_8922` |
70
+ | `rch-<defindex>` | which hat a "Random Craft Hat" (`defindex 0000`) actually resolved to — **omitted by default** when constructing the sku from an `EconItem`, see [Random Craft Hats](#random-craft-hats-rch-) below | `rch-30425` |
71
+
72
+ Parts are omitted when not applicable, and always appear in the order above.
73
+
74
+ ### `skuFromItem(item) -> string`
75
+
76
+ Builds a SKU from a plain item object. Only `defindex` and `quality` are required; everything else is optional.
77
+
78
+ ```js
79
+ skuFromItem({ defindex: '5021', quality: '6' })
80
+ // '5021;6'
81
+
82
+ skuFromItem({ defindex: '378', quality: '5', effect: '13' })
83
+ // '378;5;u-13'
84
+
85
+ skuFromItem({ defindex: '211', quality: '11', killstreakTier: '3', australium: '1', festivized: '1' })
86
+ // '211;11;kt-3;festive;australium'
87
+
88
+ skuFromItem({ defindex: '20002', quality: '6', killstreakTier: '2', target: '40', output: '6523', oq: '6' })
89
+ // '20002;6;kt-2;td-40;od-6523;oq-6'
90
+ ```
91
+
92
+ Booleans accept `true`/`'1'` interchangeably for the flag-style fields (`uncraftable`, `elevated`, `festivized`, `australium`).
93
+
94
+ ### `itemFromSku(sku) -> item`
95
+
96
+ The inverse of `skuFromItem` — parses a SKU string back into an item object (plus the original `sku` string on the result). Unset parts are simply absent from the object.
97
+
98
+ ```js
99
+ itemFromSku('5021;6')
100
+ // { defindex: '5021', quality: '6', sku: '5021;6' }
101
+
102
+ itemFromSku('312;3;kt-2')
103
+ // { defindex: '312', quality: '3', killstreakTier: '2', sku: '312;3;kt-2' }
104
+ ```
105
+
106
+ ### `getName(item, bpTexture?, qualityBpStyle?, useProperName?, showUncraft?) -> string`
107
+
108
+ Builds a plain, human-readable item name from an item object (as returned by `itemFromSku`). Normally you'll use `itemNameFromSku` instead. The optional flags default to off and, when turned on, nudge the output towards backpack.tf's naming conventions instead:
109
+
110
+ | Param | Default | Effect |
111
+ |---|---|---|
112
+ | `bpTexture` | `false` | Insert a `\|` right after the texture/paintkit name instead of folding it straight into the rest of the name, e.g. `"Sand Cannon \| Rocket Launcher"` instead of `"Sand Cannon Rocket Launcher"`. |
113
+ | `qualityBpStyle` | `false` | When the item has a particle effect, suppress the quality name (e.g. don't print `"Unusual"`) — backpack.tf shows quality separately rather than baking it into the name. |
114
+ | `useProperName` | `false` | Prefix plain Unique, non-Strange/Festivized/Killstreak items whose schema entry has `propername: '1'` with `"The"` (e.g. `"The Wrangler"`) — off by default, backpack.tf-style naming includes it. |
115
+ | `showUncraft` | `true` | Whether to include `"Non-Craftable"` in the name for uncraftable items. |
116
+
117
+ ### `itemNameFromSku(sku, ...sameParamsAsGetName) -> string`
118
+
119
+ Parses a SKU with `itemFromSku` and feeds the result straight into `getName` — same `bpTexture`/`qualityBpStyle`/`useProperName`/`showUncraft` params, same plain-readable-name defaults.
120
+
121
+ ```js
122
+ itemNameFromSku('5021;6')
123
+ // 'Mann Co. Supply Crate Key'
124
+
125
+ itemNameFromSku('211;11;kt-3;festive;australium')
126
+ // 'Strange Festivized Professional Killstreak Australium Medi Gun'
127
+
128
+ itemNameFromSku('312;3;kt-2')
129
+ // 'Vintage Specialized Killstreak Brass Beast'
130
+
131
+ itemNameFromSku('1151;5;strange;u-703;kt-3;festive;pk-282;w-2')
132
+ // 'Strange Unusual Cool Festivized Professional Killstreak Glacial Glazed Iron Bomber (Minimal Wear)'
133
+ ```
134
+
135
+ backpack.tf uses a different quality/name/price-index convention than the Steam Market. The following convert a SKU into that convention.
136
+
137
+ ### `toBpQuality(sku) -> string`
138
+
139
+ backpack.tf-style quality string. Painted/Decorated/unusual-effect weapons get folded into `'Unusual'`, `'Strange Unusual'`, `'Decorated Weapon'`, etc. instead of the raw quality name.
140
+
141
+ ```js
142
+ toBpQuality('200;11') // 'Strange'
143
+ toBpQuality('15008;15;pk-5;w-1') // 'Decorated Weapon'
144
+ toBpQuality('518;5;strange;u-8') // 'Strange Unusual'
145
+ ```
146
+
147
+ ### `toBpName(sku) -> string`
148
+
149
+ Item name formatted the way backpack.tf displays it.
150
+
151
+ ```js
152
+ toBpName('15008;15;pk-5;w-1') // 'Masked Mender | Medi Gun (Factory New)'
153
+ toBpName('5022;6;c-31') // 'Mann Co. Supply Crate'
154
+ toBpName('211;11;kt-3;festive;australium') // 'Festivized Professional Killstreak Australium Medi Gun'
155
+ ```
156
+
157
+ ### `toBpPriceIndex(sku) -> string`
158
+
159
+ The backpack.tf "priceindex" suffix — used to disambiguate particle effects, kits, fabricators, and series numbers within the same quality+name bucket.
160
+
161
+ ```js
162
+ toBpPriceIndex('31000;5;u-120') // '120'
163
+ toBpPriceIndex('6523;6;uncraftable;kt-2;td-656') // '2-656'
164
+ toBpPriceIndex('20003;6;kt-3;td-36;od-6526;oq-6') // '6526-6-36'
165
+ ```
166
+
167
+ ### `toBpSku(sku) -> string`
168
+
169
+ Re-derives a "canonical" sku-like name string the way backpack.tf would represent it (drops `craft`, applies bp texture/quality-style rules).
170
+
171
+ ### `toBpId(sku) -> string`
172
+
173
+ md5 hash of `toBpSku(sku)`; the id backpack.tf assigns to a listing for this sku.
174
+
175
+ ### `listingV1FromSku(sku) -> object` / `listingV2ResolvableFromSku(sku) -> object`
176
+
177
+ Build the `{ quality, craftable, item_name/item, priceindex, ... }` shape backpack.tf's listing v1/v2 APIs expect when you want to *create* a listing for a given sku.
178
+
179
+ ---
180
+
181
+ ## Converting Steam EconItems into SKUs
182
+
183
+ These take real EconItem payloads from Steam and turn them into a SKU. All **Node only**.
184
+
185
+ ### `fromEconItem(econItem) -> { sku, id, old_id, contextid, old_contextid, appid }`
186
+
187
+ `econItem` is an [`EconItem`](https://github.com/DoctorMcKay/node-steam-tradeoffer-manager/wiki/EconItem) as produced by [`node-steam-tradeoffer-manager`](https://github.com/DoctorMcKay/node-steam-tradeoffer-manager). `fromEconItem` mainly supports **Team Fortress 2** (`appid 440`) or **Steam Community** (`appid 753`) items — trading cards, backgrounds, emotes, etc. It converts the econ item into a sku plus its asset identity. Dispatches internally based on `econItem.appid`:
188
+
189
+ - `440` → full TF2 item parsing (reads tags/descriptions/market_hash_name **and `app_data`** to recover quality, killstreak tier, paint, spells, etc., then builds the sku via `skuFromItem`).
190
+ - `753` → trading cards / community items, sku built via `skuFromItem753` (see [below](#steam-community-items-trading-cards-backgrounds-emotes-etc)).
191
+ - anything else → `{ sku: 'other;<appid>;<market_hash_name>', id, old_id, appid }`.
192
+
193
+ ```js
194
+ fromEconItem(econItem)
195
+ // { sku: '971;11;kt-2', id: '9252885411', old_id: null, contextid: null, old_contextid: null, appid: 440 }
196
+ ```
197
+
198
+ `id`/`contextid` and `old_id`/`old_contextid` are built from the `assetid`/`contextid`/`new_assetid`/`new_contextid`/`rollback_new_assetid`/`rollback_new_contextid` fields Steam attaches to items returned by [`TradeOfferManager#getExchangeDetails`](https://github.com/DoctorMcKay/node-steam-tradeoffer-manager/wiki/TradeOffer#getexchangedetailsgetdetailsiffailed-callback): `assetid`/`contextid` are an item's identity *before* a trade, and `new_assetid`/`new_contextid` (or, if the trade got reversed, `rollback_new_assetid`/`rollback_new_contextid`) are its identity *after*. Since an item's asset id changes once it changes hands, `fromEconItem` collapses these into:
199
+
200
+ - **`id`/`contextid`** — the item's *current* identity: `new_assetid`/`new_contextid` if the trade completed, else `rollback_new_assetid`/`rollback_new_contextid` if it was rolled back, else the original `assetid`/`contextid` if neither applies (the item never moved).
201
+ - **`old_id`/`old_contextid`** — the item's identity *before* the trade, but only populated when the id actually changed (i.e. `new_assetid` or `rollback_new_assetid` was present); `null` otherwise.
202
+
203
+ So for an item straight out of an inventory (no trade involved) `id`/`contextid` are just its current asset/context id and `old_id`/`old_contextid` stay `null` — which is what the example above shows.
204
+
205
+ #### `app_data` — important for TF2 items
206
+
207
+ For `appid 440` items, `fromEconItem` needs `econItem.app_data` (which carries `def_index` and `quality`) to resolve the item reliably. Whether your `EconItem` already has it depends on where it came from:
208
+
209
+ - **Trade offers** — `TradeOfferManager`'s `'newOffer'`/`'receivedOfferChanged'` events give you `ETradeOffer` objects whose `itemsToGive`/`itemsToReceive` (`EconItem[]`) should **already include `app_data`**. You can pass these straight to `fromEconItem`.
210
+ - **Inventories** — [`TradeOfferManager#getUserInventoryContents`](https://github.com/DoctorMcKay/node-steam-tradeoffer-manager/wiki/TradeOfferManager#getuserinventorycontentssteamid-appid-contextid-tradableonly-callback) may **not** include `app_data`. Run the inventory through `fetchAppDataInventory` first to fill it in before calling `fromEconItem`.
211
+
212
+ ### `fetchAppDataInventory(inventory, delayMs?, cache?)`
213
+
214
+ A function (**Node only**) that takes an inventory array (e.g. from `getUserInventoryContents`) and merges in the `app_data`/`tags`/`descriptions` each item is missing, by batching calls to the Steam `GetAssetClassInfo` API (125 `classid`/`instanceid` pairs per request, `delayMs` — default `1000` — between batches to avoid rate limits). It's accessed through `getInstance`, the same helper `saveSchema` uses (see [below](#getinstanceoptions) for the full rundown of its `options`) — here it's enough to know it takes a `steamApiKey` and gives back a `Promise`-returning function.
215
+
216
+ ```js
217
+ import { getInstance } from '@juice789/tf2items'
218
+
219
+ const { fetchAppDataInventory } = getInstance({ steamApiKey: 'YOUR_STEAM_API_KEY' })
220
+
221
+ const inventory = await new Promise((resolve, reject) =>
222
+ manager.getUserInventoryContents(steamID, 440, 2, true, (err, inv) => err ? reject(err) : resolve(inv))
223
+ )
224
+
225
+ const withAppData = await fetchAppDataInventory(inventory)
226
+ const skus = withAppData.map(item => fromEconItem(item).sku)
227
+ ```
228
+
229
+ `cache` (default `[]`) lets you pass in previously-fetched asset-class objects to skip re-fetching classes you've already resolved — useful since the same `classid`/`instanceid` pair is reused across many items/inventories. Each entry just needs the `classid`/`instanceid` it was fetched for (pass `'0'` for `instanceid` if the item doesn't have one), plus whatever `app_data` (and `tags`/`descriptions`) came back for it:
230
+
231
+ ```js
232
+ const cache = [
233
+ { classid: '79979043', instanceid: '11040671', app_data: { def_index: '6025', quality: '6' } },
234
+ { classid: '5564', instanceid: '11040547', app_data: { def_index: '5001', quality: '6' } },
235
+ { classid: '1336083996', instanceid: '74041787', app_data: { def_index: '891', quality: '11' } }
236
+ ]
237
+
238
+ const withAppData = await fetchAppDataInventory(inventory, 1000, cache)
239
+ ```
240
+
241
+ ### `fromEconItemOptions(options) -> fromEconItem`
242
+
243
+ Same as `fromEconItem`, but lets you tweak appid-440 parsing:
244
+
245
+ ```js
246
+ const convert = fromEconItemOptions({
247
+ omitProps: ['paintColor', 'halloweenSpell', 'rch'], // skip these situational lookups, default shown
248
+ uncraftRemapDefindex: ['5021'] // defindexes to always treat as craftable (keys can't be uncraftable)
249
+ })
250
+ convert(econItem)
251
+ ```
252
+
253
+ `omitProps` defaults to skipping these three props since each only matters in specific situations:
254
+
255
+ - **`paintColor`** — which paint can was applied. Only relevant if you actually care about the item's color.
256
+ - **`halloweenSpell`** — which Halloween spell(s) are on the item. Spells don't affect the item's value, so most pricing/trading use cases can skip this.
257
+ - **`rch`** — whether the item is a Random Craft Hat. See [Random Craft Hats](#random-craft-hats-rch-) below for why this one needs special attention.
258
+
259
+ ### Random Craft Hats (`rch-`)
260
+
261
+ Crafting three weapons together (the basic "craft a hat" recipe) gives you one random cosmetic out of a pool of 599 possible unique, craftable hats. Because the result is one of many interchangeable-ish outcomes, this package can represent such an item generically under defindex `0000` instead of its real defindex:
262
+
263
+ - `0000;6;rch-30425` — a Random Craft Hat known to have resolved to defindex `30425` (Tipped Lid), kept grouped with the RCH pool via the `rch-` part.
264
+ - `30425;6` — the very same Tipped Lid, but addressed as an ordinary, distinct item — not flagged as part of the RCH pool at all.
265
+
266
+ `itemFromSku`/`itemNameFromSku` resolve the *real* defindex for you whenever `rch-` is present:
267
+
268
+ ```js
269
+ itemFromSku('0000;6;rch-30425').defindex // '30425'
270
+ itemNameFromSku('0000;6;rch-30425') // 'Tipped Lid'
271
+ ```
272
+
273
+ A bare `0000;6` (no `rch-`) never comes out of this package's own functions — `defindex` only ever becomes `'0000'` as a side effect of the `rch` flag, which always adds the `rch-` suffix too. But `itemFromSku`/`itemNameFromSku` still handle it gracefully if you encounter that form elsewhere (e.g. another tool using bare `0000` as a generic "some Random Craft Hat" placeholder):
274
+
275
+ ```js
276
+ itemNameFromSku('0000;6') // 'Random Craft Hat'
277
+ ```
278
+
279
+ **This is where it gets surprising:** whether you end up with the `0000;rch-` form or the plain-defindex form when converting a real econ item depends entirely on whether the `rch` prop was computed for it —
280
+
281
+ - `fromEconItem(econItem)` (no options) uses `defaultOptions440`, which **omits `rch` by default**. A Random Craft Hat coming out of `fromEconItem` gets a sku built from its **real** defindex (e.g. `'30425;6'`), *not* `'0000;6;rch-30425'`.
282
+ - To opt into the grouped `0000;6;rch-<defindex>` form, call `fromEconItemOptions({ omitProps: [] })` (or any `omitProps` array that excludes `'rch'`) instead of plain `fromEconItem`.
283
+
284
+ ```js
285
+ fromEconItem(econItem).sku // '30425;6' (default — rch omitted)
286
+ fromEconItemOptions({ omitProps: [] })(econItem).sku // '0000;6;rch-30425'
287
+ ```
288
+
289
+ One more wrinkle: `skuFromItem` only collapses to the `0000;rch-` form when the item *isn't also* carrying a `craft` number or a `halloweenSpell` — when one of those is present, the specific hat is considered significant enough that the sku keeps the real defindex and drops `rch-` entirely, even if you did request the `rch` prop.
290
+
291
+ ---
292
+
293
+ ## Converting backpack.tf listings into SKUs
294
+
295
+ These take real listing payloads from backpack.tf and turn them into a SKU. All **Node only**.
296
+
297
+ ### `fromListingV1(listing) -> sku` / `fromListingV2(listing) -> sku`
298
+
299
+ Convert a backpack.tf listing object (v1 or v2 API shape) directly into a sku string. Use whichever matches the API version you're consuming.
300
+
301
+ ```js
302
+ fromListingV1(listing) // '5021;6'
303
+ fromListingV2(listing) // '5021;6'
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Link builders (`skuLinks.js`)
309
+
310
+ Given a sku, build the matching market-hash-name or a direct link to common TF2 trading sites.
311
+
312
+ ### `marketHashNameFromSku(sku) -> string`
313
+
314
+ Steam Community Market display name for the item.
315
+
316
+ ```js
317
+ marketHashNameFromSku('5021;6') // 'Mann Co. Supply Crate Key'
318
+ marketHashNameFromSku('6527;6;uncraftable;kt-1;td-998') // 'Killstreak Vaccinator Kit'
319
+ marketHashNameFromSku('17412;15;pk-412;w-3') // 'Secretly Serviced War Paint (Field-Tested)'
320
+ ```
321
+
322
+ ### `scmUrl(sku) -> string`
323
+
324
+ Full Steam Community Market listing URL for the item.
325
+
326
+ ### `bpUrl(sku) -> string`
327
+
328
+ backpack.tf stats page URL for the item.
329
+
330
+ ### `manncoUrl(sku) -> string`
331
+
332
+ mannco.store item page URL.
333
+
334
+ ### `marketplaceUrl(sku) -> string`
335
+
336
+ marketplace.tf item page URL (uses marketplace.tf's own sku dialect, distinct from this package's).
337
+
338
+ ---
339
+
340
+ ## Steam Community items (trading cards, backgrounds, emotes, etc...)
341
+
342
+ A separate, simpler sku format for Steam Community items under `appid 753` (trading cards, backgrounds, emotes), since they don't have a TF2-style defindex.
343
+
344
+ ```js
345
+ skuFromItem753({ market_hash_name: 'SPY (Trading Card)', game: '440', type: '2', border: '0' })
346
+ // '753;2-0;440;SPY%20(Trading%20Card)'
347
+
348
+ itemFromSku753('753;2-0;440;SPY%20(Trading%20Card)')
349
+ // { type: '2', border: '0', game: '440', market_hash_name: 'SPY (Trading Card)', sku: '753;2-0;440;SPY%20(Trading%20Card)' }
350
+ ```
351
+
352
+ `type` is the card border/item type digit; `border` (only present for `type === '2'`, foil cards) further distinguishes normal vs. foil borders.
353
+
354
+ ---
355
+
356
+ ## The schema
357
+
358
+ A large, pre-built snapshot of the TF2 item schema, shipped with the package and used internally by the SKU/name functions. Its pieces are available as named exports:
359
+
360
+ ```js
361
+ import { safeItems, items, particleEffects, textures, collections } from '@juice789/tf2items'
362
+ ```
363
+
364
+ ```js
365
+ particleEffects // { "<id>": "<name>", ... } unusual effect names, e.g. "31": "Nuts n' Bolts"
366
+ textures // { "<id>": "<name>", ... } war paint / decorated weapon skin names
367
+ collections // { "<defindex>": { rarity, collection }, ... }
368
+ items // { "<defindex>": { ...item }, ... } see below
369
+ ```
370
+
371
+ For effects with a team-colored variant (e.g. "Defragmenting Reality"), only the first id is kept in `particleEffects` — the duplicate id for the other team's variant is dropped.
372
+
373
+ `safeItems` (used internally throughout the package) wraps `items` in a `Proxy` that returns `{ defindex, item_name: 'Undefined item' }` for any defindex not present in the schema, instead of throwing. Prefer it over indexing `items` directly when looking items up dynamically.
374
+
375
+ ### `items` object shape
376
+
377
+ Every entry in `items` always has:
378
+
379
+ ```js
380
+ {
381
+ name: '...', // internal Valve item name
382
+ defindex: '...',
383
+ item_class: '...', // e.g. 'tf_weapon_sniperrifle', 'tool', 'craft_item'
384
+ item_name: '...', // display name, e.g. 'Sniper Rifle'
385
+ item_quality: 6 // numeric quality id, see qualityNames
386
+ }
387
+ ```
388
+
389
+ Plus, only when applicable:
390
+
391
+ | Prop | Present on | Meaning |
392
+ |---|---|---|
393
+ | `propername` | items whose display name takes "The" (e.g. Strange Unique Wrangler → "The Wrangler") | `'1'` |
394
+ | `item_slot` | weapons/wearables | `'primary'`, `'melee'`, `'misc'`, ... |
395
+ | `used_by_classes` | class-restricted items | array of class names, or `['all']` / `['multi']` when applicable to many/all classes |
396
+ | `untradable` | items that can't be traded | `'1'` |
397
+ | `festivized` | items that have a festivized variant | `'1'` |
398
+ | `type2` | keys / paint cans | `'key'` or `'paint'` |
399
+ | `texture` | War Paints / Decorated weapon skins | paintkit id, matches `textures` keys |
400
+ | `target` | Strangifiers, Kits, Killstreak Kits, Unusualifiers | array of one or more target defindexes |
401
+ | `series` | crates/cases | array of series numbers |
402
+ | `seriesHidden` | crates whose series number isn't shown in the name | `true` |
403
+ | `rarity`, `collection` | items belonging to a collection (skins, cosmetics) | e.g. `rarity: 'rare'`, `collection: 'Concealed Killer Collection'` |
404
+ | `od`, `td`, `oq` | Fabricators / Chemistry Sets | `od` = possible output defindexes, `td` = possible target defindexes, `oq` = output quality |
405
+ | `image_url` | most items | Steam CDN image filename |
406
+
407
+ Some of these extra props correspond directly to the SKU parts of the same name (`texture` → `pk-`, `target` → `td-`, `series` → `c-`, etc.)
408
+
409
+ ---
410
+
411
+ ## Building the schema yourself (`saveSchema`)
412
+
413
+ `schema.json` is checked into the repo, but it goes stale as Valve updates the game. If you want to refresh it, use `saveSchema` — **Node only**.
414
+
415
+ `saveSchema` (and a few other internals like `fetchAppDataInventory`, see [above](#app_data--important-for-tf2-items)) are exposed through `getInstance` as plain `Promise`-returning functions — just `await` them like any other async function.
416
+
417
+ ### `getInstance(options)`
418
+
419
+ ```js
420
+ import { getInstance } from '@juice789/tf2items'
421
+
422
+ const { saveSchema } = getInstance({
423
+ steamApiKey: 'YOUR_STEAM_API_KEY'
424
+ })
425
+
426
+ const schema = await saveSchema()
427
+ // schema === { particleEffects, textures, collections, items } (same shape as schema.json)
428
+ ```
429
+
430
+ `options.steamApiKey` is required for anything that calls the Steam Web API (schema items, schema overview, items_game URL, asset class info).
431
+
432
+ `saveSchema` fetches the Steam `GetSchemaItems` API, the `items_game.txt` (prefab-expanded weapon/item definitions), particle effects, paintkit textures, and `tf_english.txt` (localization), then merges everything into the `items` shape described above.
433
+
434
+ ---
435
+
436
+ ## Reference data exports
437
+
438
+ A set of lookup tables (sourced from `schemaHelper.json`) are exported directly, useful for building UIs or validating input:
439
+
440
+ `qualityNames`, `qualityIds`, `rarities`, `killstreakTiers`, `wears`, `classes`, `itemSlots`, `itemClasses`, `paintHex`, `paintDefindex`, `spellDefindex`, `cosmeticCollections`, `weaponCollections`, `warPaintCollections`, `fabricatorDefindex`, `strangifierTargets`, `australiumDefindex`, `crateSeries`, `paintableDefindex`, `chemsetDefindex`, `serieslessDefindex`, `impossibleEffects`
441
+
442
+ For example:
443
+
444
+ ```js
445
+ import { qualityNames, wears, killstreakTiers } from '@juice789/tf2items'
446
+
447
+ qualityNames['11'] // 'Strange'
448
+ wears['1'] // 'Factory New'
449
+ killstreakTiers['3'] // 'Professional Killstreak'
450
+ ```
package/fromEconItem.js CHANGED
@@ -102,7 +102,7 @@ const killstreakTier = item => {
102
102
 
103
103
  function effect(item) {
104
104
  if (item?.app_data?.quality !== '5') return null
105
- const desc = (item.descriptions ?? []).find(d => d.color === 'ffd700')
105
+ const desc = (item.descriptions || []).find(d => d.color === 'ffd700')
106
106
  if (!desc) return null
107
107
  return particleEffectsInv[desc.value.replace('★ Unusual Effect: ', '')] ?? '-1'
108
108
  }
@@ -114,12 +114,12 @@ const elevated = item =>
114
114
  .includes('Strange')
115
115
 
116
116
  const uncraftable = item =>
117
- Boolean((item.descriptions ?? []).find(d => d.value === '( Not Usable in Crafting )'))
117
+ Boolean((item.descriptions || []).find(d => d.value === '( Not Usable in Crafting )'))
118
118
 
119
119
  const paintOptions = Object.keys(paintDefindex).map(name => `Paint Color: ${name}`)
120
120
 
121
121
  const paintColor = item => {
122
- const desc = (item.descriptions ?? []).find(d => paintOptions.includes(d.value))
122
+ const desc = (item.descriptions || []).find(d => paintOptions.includes(d.value))
123
123
  if (!desc) return desc
124
124
  return paintDefindex[desc.value.split('Paint Color: ')[1]]
125
125
  }
@@ -127,7 +127,7 @@ const paintColor = item => {
127
127
  const spellOptions = Object.keys(spellDefindex).map(name => `Halloween: ${name} (spell only active during event)`)
128
128
 
129
129
  const halloweenSpell = item => {
130
- const spells = (item.descriptions ?? [])
130
+ const spells = (item.descriptions || [])
131
131
  .filter(d => spellOptions.includes(d.value))
132
132
  .map(d => spellDefindex[d.value.split('Halloween: ')[1].replace(' (spell only active during event)', '')])
133
133
  return spells.length === 0 ? null : spells.join('_')
@@ -1,4 +1,3 @@
1
1
  {
2
- "steamApiKey": "",
3
- "saga": false
2
+ "steamApiKey": ""
4
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juice789/tf2items",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "tf2 item schema thingys",
5
5
  "main": "app.js",
6
6
  "type": "module",
package/schemaHelper.json CHANGED
@@ -270,7 +270,9 @@
270
270
  "Summer 2024 Cosmetics Collection",
271
271
  "Terrifying Trove Collection",
272
272
  "Winter 2024 Cosmetics Collection",
273
- "Summer 2025 Cosmetics Collection"
273
+ "Summer 2025 Cosmetics Collection",
274
+ "Spectral Stash Collection",
275
+ "Winter 2025 Cosmetics Collection"
274
276
  ],
275
277
  "weaponCollections": [
276
278
  "Concealed Killer Collection",