cs2-inventory-resolver 0.4.19 → 0.4.21
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 +145 -91
- package/data/inventory.json +1 -1
- package/dist/resolver.d.ts +11 -10
- package/dist/resolver.js +40 -20
- package/dist/resolver.test.js +76 -2
- package/dist/types.d.ts +7 -3
- package/package.json +39 -39
package/dist/resolver.d.ts
CHANGED
|
@@ -6,16 +6,17 @@ import type { GcItemInput, ResolvedItemData } from './types.js';
|
|
|
6
6
|
* 1. Skins (by `def_index` + `paint_index`)
|
|
7
7
|
* 2. Music kits (by attribute 166)
|
|
8
8
|
* 3. Highlights / souvenir charms (by attribute 314)
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
13
|
-
* 8.
|
|
14
|
-
* 9.
|
|
15
|
-
* 10.
|
|
16
|
-
* 11.
|
|
17
|
-
* 12.
|
|
18
|
-
* 13.
|
|
9
|
+
* 4. Sticker slabs (by attribute 321)
|
|
10
|
+
* 5. Keychains (by attribute 299)
|
|
11
|
+
* 6. Graffiti (by `stickers[0].sticker_id` + attribute 233)
|
|
12
|
+
* 7. Keys (by `def_index`)
|
|
13
|
+
* 8. Crates / cases (by `def_index`)
|
|
14
|
+
* 9. Collectibles (by `def_index`)
|
|
15
|
+
* 10. Agents (by `def_index`)
|
|
16
|
+
* 11. Tools (by `def_index`)
|
|
17
|
+
* 12. Sticker slabs (by `stickers[0].sticker_id`, only for `def_index` 1355)
|
|
18
|
+
* 13. Patches (by `stickers[0].sticker_id`)
|
|
19
|
+
* 14. Stickers (by `stickers[0].sticker_id`)
|
|
19
20
|
*
|
|
20
21
|
* @param gcItem - The raw GC item to resolve.
|
|
21
22
|
* @returns The resolved item data, or `null` if the item could not be identified.
|
package/dist/resolver.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getData } from './data-loader.js';
|
|
2
2
|
/** The `trade_protected_escrow_date` attribute def_index. */
|
|
3
3
|
const TRADE_PROTECTED_ESCROW_DATE = 312;
|
|
4
|
+
/** The item def_index shared by every sticker slab. */
|
|
5
|
+
const STICKER_SLAB_DEF_INDEX = 1355;
|
|
4
6
|
/**
|
|
5
7
|
* Derive the trade state of an item instance from attribute 312
|
|
6
8
|
* (`trade_protected_escrow_date`).
|
|
@@ -92,16 +94,17 @@ function makeResult(entry, gcItem, entity) {
|
|
|
92
94
|
* 1. Skins (by `def_index` + `paint_index`)
|
|
93
95
|
* 2. Music kits (by attribute 166)
|
|
94
96
|
* 3. Highlights / souvenir charms (by attribute 314)
|
|
95
|
-
* 4.
|
|
96
|
-
* 5.
|
|
97
|
-
* 6.
|
|
98
|
-
* 7.
|
|
99
|
-
* 8.
|
|
100
|
-
* 9.
|
|
101
|
-
* 10.
|
|
102
|
-
* 11.
|
|
103
|
-
* 12.
|
|
104
|
-
* 13.
|
|
97
|
+
* 4. Sticker slabs (by attribute 321)
|
|
98
|
+
* 5. Keychains (by attribute 299)
|
|
99
|
+
* 6. Graffiti (by `stickers[0].sticker_id` + attribute 233)
|
|
100
|
+
* 7. Keys (by `def_index`)
|
|
101
|
+
* 8. Crates / cases (by `def_index`)
|
|
102
|
+
* 9. Collectibles (by `def_index`)
|
|
103
|
+
* 10. Agents (by `def_index`)
|
|
104
|
+
* 11. Tools (by `def_index`)
|
|
105
|
+
* 12. Sticker slabs (by `stickers[0].sticker_id`, only for `def_index` 1355)
|
|
106
|
+
* 13. Patches (by `stickers[0].sticker_id`)
|
|
107
|
+
* 14. Stickers (by `stickers[0].sticker_id`)
|
|
105
108
|
*
|
|
106
109
|
* @param gcItem - The raw GC item to resolve.
|
|
107
110
|
* @returns The resolved item data, or `null` if the item could not be identified.
|
|
@@ -119,6 +122,7 @@ export function resolveItem(gcItem) {
|
|
|
119
122
|
const graffitiTint = getAttributeUint32(gcItem, 233);
|
|
120
123
|
const highlightIndex = getAttributeUint32(gcItem, 314);
|
|
121
124
|
const keychainIndex = getAttributeUint32(gcItem, 299);
|
|
125
|
+
const stickerSlabIndex = getAttributeUint32(gcItem, 321);
|
|
122
126
|
// 1. Skins: def_index + paint_index
|
|
123
127
|
if (gcItem.paint_index && gcItem.paint_index > 0) {
|
|
124
128
|
const weapon = data.skins[defIdx];
|
|
@@ -140,13 +144,23 @@ export function resolveItem(gcItem) {
|
|
|
140
144
|
if (highlight)
|
|
141
145
|
return makeResult(highlight, gcItem, 'highlight');
|
|
142
146
|
}
|
|
143
|
-
// 4.
|
|
147
|
+
// 4. Sticker slabs: sticker_slab_index (attribute 321)
|
|
148
|
+
//
|
|
149
|
+
// Checked before keychains: slabs also carry attribute 299 (a constant slab
|
|
150
|
+
// marker, not a keychain id), so leaving keychains first would let a slab
|
|
151
|
+
// resolve as the wrong charm.
|
|
152
|
+
if (stickerSlabIndex && stickerSlabIndex > 0) {
|
|
153
|
+
const slab = data.sticker_slabs[String(stickerSlabIndex)];
|
|
154
|
+
if (slab)
|
|
155
|
+
return makeResult(slab, gcItem, 'sticker_slab');
|
|
156
|
+
}
|
|
157
|
+
// 5. Keychains (charms): keychain_index (attribute 299)
|
|
144
158
|
if (keychainIndex && keychainIndex > 0) {
|
|
145
159
|
const keychain = data.keychains[String(keychainIndex)];
|
|
146
160
|
if (keychain)
|
|
147
161
|
return makeResult(keychain, gcItem, 'keychain');
|
|
148
162
|
}
|
|
149
|
-
//
|
|
163
|
+
// 6. Graffiti: stickers[0].sticker_id + graffiti_tint (attribute 233)
|
|
150
164
|
if (graffitiTint !== undefined && gcItem.stickers?.length) {
|
|
151
165
|
const stickerId = gcItem.stickers[0].sticker_id;
|
|
152
166
|
if (stickerId) {
|
|
@@ -159,38 +173,44 @@ export function resolveItem(gcItem) {
|
|
|
159
173
|
return makeResult(mono, gcItem, 'graffiti');
|
|
160
174
|
}
|
|
161
175
|
}
|
|
162
|
-
//
|
|
176
|
+
// 7. Keys
|
|
163
177
|
const key = data.keys[defIdx];
|
|
164
178
|
if (key)
|
|
165
179
|
return makeResult(key, gcItem, 'key');
|
|
166
|
-
//
|
|
180
|
+
// 8. Crates / cases
|
|
167
181
|
const crate = data.crates[defIdx];
|
|
168
182
|
if (crate)
|
|
169
183
|
return makeResult(crate, gcItem, 'crate');
|
|
170
|
-
//
|
|
184
|
+
// 9. Collectibles (coins, pins, etc.)
|
|
171
185
|
const collectible = data.collectibles[defIdx];
|
|
172
186
|
if (collectible)
|
|
173
187
|
return makeResult(collectible, gcItem, 'collectible');
|
|
174
|
-
//
|
|
188
|
+
// 10. Agents (characters)
|
|
175
189
|
const agent = data.agents[defIdx];
|
|
176
190
|
if (agent)
|
|
177
191
|
return makeResult(agent, gcItem, 'agent');
|
|
178
|
-
//
|
|
192
|
+
// 11. Tools (Name Tag, Storage Unit, etc.)
|
|
179
193
|
const tool = data.tools[defIdx];
|
|
180
194
|
if (tool)
|
|
181
195
|
return makeResult(tool, gcItem, 'tool');
|
|
182
|
-
// 11. Patches: use stickers[0].sticker_id (checked before stickers)
|
|
183
196
|
if (gcItem.stickers?.length) {
|
|
184
197
|
const stickerId = gcItem.stickers[0].sticker_id;
|
|
185
198
|
if (stickerId) {
|
|
199
|
+
// 12. Sticker slabs: slabs share their id with the applied sticker of the
|
|
200
|
+
// same design, so only def_index 1355 may resolve to a slab.
|
|
201
|
+
if (gcItem.def_index === STICKER_SLAB_DEF_INDEX) {
|
|
202
|
+
const slab = data.sticker_slabs[String(stickerId)];
|
|
203
|
+
if (slab)
|
|
204
|
+
return makeResult(slab, gcItem, 'sticker_slab');
|
|
205
|
+
}
|
|
206
|
+
// 13. Patches (checked before stickers)
|
|
186
207
|
const patch = data.patches[String(stickerId)];
|
|
187
208
|
if (patch)
|
|
188
209
|
return makeResult(patch, gcItem, 'patch');
|
|
189
|
-
//
|
|
210
|
+
// 14. Stickers
|
|
190
211
|
const sticker = data.stickers[String(stickerId)];
|
|
191
212
|
if (sticker)
|
|
192
213
|
return makeResult(sticker, gcItem, 'sticker');
|
|
193
|
-
// TODO: add sticker_slabs resolution (separate entity from stickers)
|
|
194
214
|
}
|
|
195
215
|
}
|
|
196
216
|
return null;
|
package/dist/resolver.test.js
CHANGED
|
@@ -230,14 +230,39 @@ describe('resolveItem', () => {
|
|
|
230
230
|
expect(result.name).toBe(data.stickers[String(stickerId)].name);
|
|
231
231
|
expect(typeof result.marketable).toBe('boolean');
|
|
232
232
|
});
|
|
233
|
-
// 14.
|
|
233
|
+
// 14. Sticker slab
|
|
234
|
+
it('resolves a sticker slab by attribute 321', () => {
|
|
235
|
+
const slabId = firstKey(data.sticker_slabs);
|
|
236
|
+
const result = resolveItem({ def_index: 1355, attribute: [makeAttr(321, slabId)] });
|
|
237
|
+
expect(result).not.toBeNull();
|
|
238
|
+
expect(result.entity).toBe('sticker_slab');
|
|
239
|
+
expect(result.name).toBe(data.sticker_slabs[String(slabId)].name);
|
|
240
|
+
expect(typeof result.marketable).toBe('boolean');
|
|
241
|
+
});
|
|
242
|
+
// 14b. Sticker slab — real GC item (Sticker Slab | rain | Rio 2022)
|
|
243
|
+
it('resolves a real sticker slab GC item', () => {
|
|
244
|
+
const result = resolveItem({
|
|
245
|
+
def_index: 1355,
|
|
246
|
+
quality: 8,
|
|
247
|
+
attribute: [
|
|
248
|
+
makeAttr(299, 37),
|
|
249
|
+
makeAttr(321, 6110),
|
|
250
|
+
makeAttr(75, 1786777200),
|
|
251
|
+
],
|
|
252
|
+
});
|
|
253
|
+
expect(result).not.toBeNull();
|
|
254
|
+
expect(result.entity).toBe('sticker_slab');
|
|
255
|
+
expect(result.name).toBe('Sticker Slab | rain | Rio 2022');
|
|
256
|
+
expect(result.image).toBe(data.sticker_slabs['6110'].image);
|
|
257
|
+
});
|
|
258
|
+
// 15. Non-skin name is unchanged
|
|
234
259
|
it('non-skin entities return name unchanged', () => {
|
|
235
260
|
const crateId = firstKey(data.crates);
|
|
236
261
|
const result = resolveItem({ def_index: crateId });
|
|
237
262
|
expect(result).not.toBeNull();
|
|
238
263
|
expect(result.name).toBe(data.crates[String(crateId)].name);
|
|
239
264
|
});
|
|
240
|
-
//
|
|
265
|
+
// 16. Unknown item → null
|
|
241
266
|
it('returns null for an unknown def_index', () => {
|
|
242
267
|
expect(resolveItem({ def_index: 999999 })).toBeNull();
|
|
243
268
|
});
|
|
@@ -261,6 +286,21 @@ describe('resolveItem — priority', () => {
|
|
|
261
286
|
const result = resolveItem({ def_index: keyId });
|
|
262
287
|
expect(result.entity).toBe('key');
|
|
263
288
|
});
|
|
289
|
+
it('sticker_id 6110 is a slab at def_index 1355 and a sticker at 1209', () => {
|
|
290
|
+
const slab = resolveItem({ def_index: 1355, stickers: [{ sticker_id: 6110 }] });
|
|
291
|
+
expect(slab.entity).toBe('sticker_slab');
|
|
292
|
+
expect(slab.name).toBe('Sticker Slab | rain | Rio 2022');
|
|
293
|
+
const sticker = resolveItem({ def_index: 1209, stickers: [{ sticker_id: 6110 }] });
|
|
294
|
+
expect(sticker.entity).toBe('sticker');
|
|
295
|
+
expect(sticker.name).toBe('Sticker | rain | Rio 2022');
|
|
296
|
+
});
|
|
297
|
+
it('sticker slab attribute 321 takes priority over keychain attribute 299', () => {
|
|
298
|
+
const result = resolveItem({
|
|
299
|
+
def_index: 1355,
|
|
300
|
+
attribute: [makeAttr(299, firstKey(data.keychains)), makeAttr(321, 6110)],
|
|
301
|
+
});
|
|
302
|
+
expect(result.entity).toBe('sticker_slab');
|
|
303
|
+
});
|
|
264
304
|
it('patch takes priority over sticker when sticker_id matches both', () => {
|
|
265
305
|
const patchKeys = Object.keys(data.patches);
|
|
266
306
|
const overlapping = patchKeys.find(k => data.stickers[k]);
|
|
@@ -280,6 +320,40 @@ describe('resolveItem — edge cases', () => {
|
|
|
280
320
|
expect(result).not.toBeNull();
|
|
281
321
|
expect(result.entity).toBe('crate');
|
|
282
322
|
});
|
|
323
|
+
it('unknown sticker slab id falls through to null', () => {
|
|
324
|
+
const result = resolveItem({ def_index: 1355, attribute: [makeAttr(321, 999999)] });
|
|
325
|
+
expect(result).toBeNull();
|
|
326
|
+
});
|
|
327
|
+
it('sticker slab id of 0 is treated as absent', () => {
|
|
328
|
+
const result = resolveItem({ def_index: 1355, attribute: [makeAttr(321, 0)] });
|
|
329
|
+
expect(result).toBeNull();
|
|
330
|
+
});
|
|
331
|
+
it('def_index 1355 with an unknown sticker_id resolves to null', () => {
|
|
332
|
+
const result = resolveItem({ def_index: 1355, stickers: [{ sticker_id: 999999 }] });
|
|
333
|
+
expect(result).toBeNull();
|
|
334
|
+
});
|
|
335
|
+
it('sticker_id of 0 is treated as absent', () => {
|
|
336
|
+
const result = resolveItem({ def_index: 1355, stickers: [{ sticker_id: 0 }] });
|
|
337
|
+
expect(result).toBeNull();
|
|
338
|
+
});
|
|
339
|
+
it('resolves trade status for a sticker slab', () => {
|
|
340
|
+
const result = resolveItem({
|
|
341
|
+
def_index: 1355,
|
|
342
|
+
attribute: [makeAttr(321, 6110), makeAttr(312, 0)],
|
|
343
|
+
});
|
|
344
|
+
expect(result.entity).toBe('sticker_slab');
|
|
345
|
+
expect(result.status).toBe('market_listed');
|
|
346
|
+
});
|
|
347
|
+
it('StatTrak takes priority over Souvenir when both attributes are present', () => {
|
|
348
|
+
const result = resolveItem({
|
|
349
|
+
def_index: 7,
|
|
350
|
+
paint_index: 282,
|
|
351
|
+
paint_wear: 0.3,
|
|
352
|
+
attribute: [makeAttr(80, 1), makeAttr(140, 1)],
|
|
353
|
+
});
|
|
354
|
+
expect(result.name).toMatch(/^StatTrak™ /);
|
|
355
|
+
expect(result.name).not.toContain('Souvenir');
|
|
356
|
+
});
|
|
283
357
|
it('empty stickers array does not crash', () => {
|
|
284
358
|
const crateId = firstKey(data.crates);
|
|
285
359
|
const result = resolveItem({ def_index: crateId, stickers: [] });
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Possible CS2 item entity types returned by {@link resolveItem}. */
|
|
2
|
-
export type ItemEntity = 'skin' | 'music_kit' | 'highlight' | 'keychain' | 'graffiti' | 'crate' | 'key' | 'collectible' | 'agent' | 'tool' | 'patch' | 'sticker';
|
|
2
|
+
export type ItemEntity = 'skin' | 'music_kit' | 'highlight' | 'keychain' | 'graffiti' | 'crate' | 'key' | 'collectible' | 'agent' | 'tool' | 'patch' | 'sticker' | 'sticker_slab';
|
|
3
3
|
/**
|
|
4
4
|
* Current trade state of a specific item instance, derived from attribute 312
|
|
5
5
|
* (`trade_protected_escrow_date`).
|
|
@@ -48,7 +48,10 @@ export interface GcItemInput {
|
|
|
48
48
|
def_index: number;
|
|
49
49
|
/** The paint/skin index. Non-zero for weapon skins. */
|
|
50
50
|
paint_index?: number;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* The item quality. Only `3` (★ knife/glove) affects resolution; StatTrak™ and
|
|
53
|
+
* Souvenir are detected from attributes 80 and 140, not from this field.
|
|
54
|
+
*/
|
|
52
55
|
quality?: number;
|
|
53
56
|
/** The paint wear float (0.0–1.0). Present only for skins. */
|
|
54
57
|
paint_wear?: number;
|
|
@@ -60,7 +63,8 @@ export interface GcItemInput {
|
|
|
60
63
|
* Raw attribute array from the GC message.
|
|
61
64
|
*
|
|
62
65
|
* Used to extract `music_index` (def_index 166), `graffiti_tint` (def_index 233),
|
|
63
|
-
* `keychain_index` (def_index 299), `highlight_index` (def_index 314),
|
|
66
|
+
* `keychain_index` (def_index 299), `highlight_index` (def_index 314),
|
|
67
|
+
* `sticker_slab_index` (def_index 321), and the
|
|
64
68
|
* `trade_protected_escrow_date` (def_index 312) via {@link getAttributeUint32}.
|
|
65
69
|
*/
|
|
66
70
|
attribute?: {
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cs2-inventory-resolver",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Resolve CS2 Game Coordinator protobuf items into human-readable names, images, and categories",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"cs2",
|
|
7
|
-
"csgo",
|
|
8
|
-
"counter-strike",
|
|
9
|
-
"steam",
|
|
10
|
-
"inventory",
|
|
11
|
-
"skins",
|
|
12
|
-
"game-coordinator",
|
|
13
|
-
"protobuf",
|
|
14
|
-
"resolver",
|
|
15
|
-
"valve"
|
|
16
|
-
],
|
|
17
|
-
"type": "module",
|
|
18
|
-
"main": "./dist/index.js",
|
|
19
|
-
"types": "./dist/index.d.ts",
|
|
20
|
-
"exports": {
|
|
21
|
-
".": {
|
|
22
|
-
"types": "./dist/index.d.ts",
|
|
23
|
-
"default": "./dist/index.js"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"dist/",
|
|
28
|
-
"data/"
|
|
29
|
-
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "tsc",
|
|
32
|
-
"test": "vitest run"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@types/node": "^22.0.0",
|
|
36
|
-
"typescript": "^5.9.3",
|
|
37
|
-
"vitest": "^3.0.0"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cs2-inventory-resolver",
|
|
3
|
+
"version": "0.4.21",
|
|
4
|
+
"description": "Resolve CS2 Game Coordinator protobuf items into human-readable names, images, and categories",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cs2",
|
|
7
|
+
"csgo",
|
|
8
|
+
"counter-strike",
|
|
9
|
+
"steam",
|
|
10
|
+
"inventory",
|
|
11
|
+
"skins",
|
|
12
|
+
"game-coordinator",
|
|
13
|
+
"protobuf",
|
|
14
|
+
"resolver",
|
|
15
|
+
"valve"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/",
|
|
28
|
+
"data/"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"test": "vitest run"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^3.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|