tf2-sku-to-name 1.0.6__py3-none-any.whl → 2.0.0__py3-none-any.whl

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.
sku/schema.py ADDED
@@ -0,0 +1,646 @@
1
+ import json
2
+ import time
3
+ import requests
4
+ from typing import Dict, Optional
5
+ from pathlib import Path
6
+
7
+
8
+ class Schema:
9
+ PAINT_MAPPINGS = {
10
+ '(paint: color no. 216-190-216)': 3100495,
11
+ '(paint: a color similar to slate)': 3100495,
12
+ '(paint: indubitably green)': 7511618,
13
+ '(paint: color no. 729e42)': 7511618,
14
+ '(paint: zepheniah\'s greed)': 4345659,
15
+ '(paint: color no. 424f3b)': 4345659,
16
+ '(paint: noble hatter\'s violet)': 5322826,
17
+ '(paint: color no. 51384a)': 5322826,
18
+ '(paint: color no. 483838)': 5322826,
19
+ '(paint: a deep commitment to purple)': 8208497,
20
+ '(paint: color no. 7d4071)': 8208497,
21
+ '(paint: mann co. orange)': 12377523,
22
+ '(paint: color no. cf7336)': 12377523,
23
+ '(paint: muskelmannbraun)': 10843461,
24
+ '(paint: color no. a57545)': 10843461,
25
+ '(paint: peculiarly drab tincture)': 12955537,
26
+ '(paint: color no. c5af91)': 12955537,
27
+ '(paint: radigan conagher brown)': 6901050,
28
+ '(paint: color no. 694d3a)': 6901050,
29
+ '(paint: ye olde rustic colour)': 8154199,
30
+ '(paint: color no. 7c6c57)': 8154199,
31
+ '(paint: australium gold)': 15185211,
32
+ '(paint: color no. e7b53b)': 15185211,
33
+ '(paint: aged moustache grey)': 8289918,
34
+ '(paint: color no. 7e7e7e)': 8289918,
35
+ '(paint: an extraordinary abundance of tinge)': 15132390,
36
+ '(paint: color no. e6e6e6)': 15132390,
37
+ '(paint: a distinctive lack of hue)': 1315860,
38
+ '(paint: color no. 141414)': 1315860,
39
+ '(paint: pink as hell)': 16738740,
40
+ '(paint: color no. ff69b4)': 16738740,
41
+ '(paint: the bitter taste of defeat and lime)': 3329330,
42
+ '(paint: color no. 32cd32)': 3329330,
43
+ '(paint: the color of a gentlemann\'s business pants)': 15787660,
44
+ '(paint: color no. f0e68c)': 15787660,
45
+ '(paint: dark salmon injustice)': 15308410,
46
+ '(paint: color no. e9967a)': 15308410,
47
+ '(paint: a mann\'s mint)': 12377306,
48
+ '(paint: color no. bcddb3)': 12377306,
49
+ '(paint: after eight)': 2960676,
50
+ '(paint: color no. 2d2d24)': 2960676,
51
+ '(paint: legacy paint)': 5801378,
52
+ '(paint: waterlogged lab coat)': 11049612,
53
+ '(paint: color no. a89a8c)': 11049612,
54
+ '(paint: balaclavas are forever)': 3874595,
55
+ '(paint: color no. 3b1f23)': 3874595,
56
+ '(paint: an air of debonair)': 6637376,
57
+ '(paint: color no. 654740)': 6637376,
58
+ '(paint: the value of teamwork)': 8400928,
59
+ '(paint: color no. 803020)': 8400928,
60
+ '(paint: cream spirit)': 12807213,
61
+ '(paint: color no. c36c2d)': 12807213,
62
+ '(paint: operator\'s overalls)': 4732984,
63
+ '(paint: color no. 483838)': 4732984,
64
+ '(paint: drably olive)': 8421376,
65
+ '(paint: color no. 808000)': 8421376,
66
+ }
67
+
68
+ MUNITION_CRATES = {
69
+ 82: 5734, 83: 5735, 84: 5742, 85: 5752,
70
+ 90: 5781, 91: 5802, 92: 5803, 103: 5859
71
+ }
72
+
73
+ PISTOL_SKINS = {
74
+ 0: 15013, 18: 15018, 35: 15035, 41: 15041, 46: 15046,
75
+ 56: 15056, 61: 15061, 63: 15060, 69: 15100, 70: 15101,
76
+ 74: 15102, 78: 15126, 81: 15148
77
+ }
78
+
79
+ ROCKET_LAUNCHER_SKINS = {
80
+ 1: 15014, 6: 15006, 28: 15028, 43: 15043, 52: 15052,
81
+ 57: 15057, 60: 15081, 69: 15104, 70: 15105, 76: 15129,
82
+ 79: 15130, 80: 15150
83
+ }
84
+
85
+ MEDIGUN_SKINS = {
86
+ 2: 15010, 5: 15008, 25: 15025, 39: 15039, 50: 15050,
87
+ 65: 15078, 72: 15097, 76: 15120, 78: 15121, 79: 15122,
88
+ 81: 15145, 83: 15146
89
+ }
90
+
91
+ REVOLVER_SKINS = {
92
+ 3: 15011, 27: 15027, 42: 15042, 51: 15051, 63: 15064,
93
+ 64: 15062, 65: 15063, 72: 15103, 76: 15127, 77: 15128,
94
+ 81: 15149
95
+ }
96
+
97
+ STICKYBOMB_SKINS = {
98
+ 4: 15012, 8: 15009, 24: 15024, 38: 15038, 45: 15045,
99
+ 48: 15048, 60: 15082, 62: 15083, 63: 15084, 68: 15113,
100
+ 76: 15137, 78: 15138, 81: 15155
101
+ }
102
+
103
+ SNIPER_RIFLE_SKINS = {
104
+ 7: 15007, 14: 15000, 19: 15019, 23: 15023, 33: 15033,
105
+ 59: 15059, 62: 15070, 64: 15071, 65: 15072, 76: 15135,
106
+ 66: 15111, 67: 15112, 78: 15136, 82: 15154
107
+ }
108
+
109
+ FLAME_THROWER_SKINS = {
110
+ 9: 15005, 17: 15017, 30: 15030, 34: 15034, 49: 15049,
111
+ 54: 15054, 60: 15066, 61: 15068, 62: 15067, 66: 15089,
112
+ 67: 15090, 76: 15115, 80: 15141
113
+ }
114
+
115
+ MINIGUN_SKINS = {
116
+ 10: 15004, 20: 15020, 26: 15026, 31: 15031, 40: 15040,
117
+ 55: 15055, 61: 15088, 62: 15087, 63: 15086, 70: 15098,
118
+ 73: 15099, 76: 15123, 77: 15125, 78: 15124, 84: 15147
119
+ }
120
+
121
+ SCATTERGUN_SKINS = {
122
+ 11: 15002, 15: 15015, 21: 15021, 29: 15029, 36: 15036,
123
+ 53: 15053, 61: 15069, 63: 15065, 69: 15106, 72: 15107,
124
+ 74: 15108, 76: 15131, 83: 15157, 85: 15151
125
+ }
126
+
127
+ SHOTGUN_SKINS = {
128
+ 12: 15003, 16: 15016, 44: 15044, 47: 15047, 60: 15085,
129
+ 72: 15109, 76: 15132, 78: 15133, 86: 15152
130
+ }
131
+
132
+ SMG_SKINS = {
133
+ 13: 15001, 22: 15022, 32: 15032, 37: 15037, 58: 15058,
134
+ 65: 15076, 69: 15110, 79: 15134, 81: 15153
135
+ }
136
+
137
+ WRENCH_SKINS = {
138
+ 60: 15074, 61: 15073, 64: 15075, 75: 15114,
139
+ 77: 15140, 78: 15139, 82: 15156
140
+ }
141
+
142
+ GRENADE_LAUNCHER_SKINS = {
143
+ 60: 15077, 63: 15079, 67: 15091, 68: 15092,
144
+ 76: 15116, 77: 15117, 80: 15142, 84: 15158
145
+ }
146
+
147
+ KNIFE_SKINS = {
148
+ 64: 15080, 69: 15094, 70: 15095, 71: 15096,
149
+ 77: 15119, 78: 15118, 81: 15143, 82: 15144
150
+ }
151
+
152
+ EXCLUSIVE_GENUINE = {
153
+ 810: 831, 811: 832, 812: 833, 813: 834, 814: 835,
154
+ 815: 836, 816: 837, 817: 838, 30720: 30740,
155
+ 30721: 30741, 30724: 30739
156
+ }
157
+
158
+ EXCLUSIVE_GENUINE_REVERSED = {
159
+ 831: 810, 832: 811, 833: 812, 834: 813, 835: 814,
160
+ 836: 815, 837: 816, 838: 817, 30740: 30720,
161
+ 30741: 30721, 30739: 30724
162
+ }
163
+
164
+ RETIRED_KEYS = {
165
+ '5049': {'defindex': 5049, 'name': 'Festive Winter Crate Key'},
166
+ '5067': {'defindex': 5067, 'name': 'Refreshing Summer Cooler Key'},
167
+ '5072': {'defindex': 5072, 'name': 'Naughty Winter Crate Key'},
168
+ '5073': {'defindex': 5073, 'name': 'Nice Winter Crate Key'},
169
+ '5079': {'defindex': 5079, 'name': 'Scorched Key'},
170
+ '5081': {'defindex': 5081, 'name': 'Fall Key'},
171
+ '5628': {'defindex': 5628, 'name': 'Eerie Key'},
172
+ '5631': {'defindex': 5631, 'name': 'Naughty Winter Crate Key 2012'},
173
+ '5632': {'defindex': 5632, 'name': 'Nice Winter Crate Key 2012'},
174
+ '5713': {'defindex': 5713, 'name': 'Spooky Key'},
175
+ '5716': {'defindex': 5716, 'name': 'Naughty Winter Crate Key 2013'},
176
+ '5717': {'defindex': 5717, 'name': 'Nice Winter Crate Key 2013'},
177
+ '5762': {'defindex': 5762, 'name': 'Limited Late Summer Crate Key'},
178
+ '5791': {'defindex': 5791, 'name': 'Naughty Winter Crate Key 2014'},
179
+ '5792': {'defindex': 5792, 'name': 'Nice Winter Crate Key 2014'}
180
+ }
181
+
182
+ CACHE_DIR = Path.home() / '.tf2_sku_cache'
183
+ SCHEMA_CACHE_FILE = CACHE_DIR / 'schema.json'
184
+ CACHE_DURATION = 24 * 60 * 60 * 30 # 30 days
185
+
186
+ def __init__(self, data: Optional[Dict] = None, api_key: Optional[str] = None, use_autobot: bool = True):
187
+ self.api_key = api_key
188
+ self.use_autobot = use_autobot
189
+
190
+ if data:
191
+ self.version = data.get('version')
192
+ self.raw = data.get('raw')
193
+ self.time = data.get('time', int(time.time() * 1000))
194
+ else:
195
+ # Load from cache or fetch new schema
196
+ if not self._load_from_cache():
197
+ self._fetch_schema()
198
+
199
+ self._set_properties_data()
200
+
201
+ def _set_properties_data(self):
202
+ self.crate_series_list = self._get_crate_series_list()
203
+ self.qualities = self._get_qualities()
204
+ self.effects = self._get_particle_effects()
205
+ self.paintkits = self._get_paint_kits()
206
+ self.paints = self._get_paints()
207
+
208
+ def _load_from_cache(self) -> bool:
209
+ if not self.SCHEMA_CACHE_FILE.exists():
210
+ return False
211
+
212
+ try:
213
+ with open(self.SCHEMA_CACHE_FILE, 'r') as f:
214
+ data = json.load(f)
215
+
216
+ if time.time() * 1000 - data.get('time', 0) > self.CACHE_DURATION * 1000:
217
+ return False
218
+
219
+ self.version = data.get('version')
220
+ self.raw = data.get('raw')
221
+ self.time = data.get('time')
222
+ return True
223
+ except Exception:
224
+ return False
225
+
226
+ def _save_to_cache(self):
227
+ try:
228
+ self.CACHE_DIR.mkdir(parents=True, exist_ok=True)
229
+ with open(self.SCHEMA_CACHE_FILE, 'w') as f:
230
+ json.dump({
231
+ 'version': self.version,
232
+ 'raw': self.raw,
233
+ 'time': self.time
234
+ }, f)
235
+ except Exception:
236
+ pass # Ignore cache write errors
237
+
238
+ def _fetch_schema(self):
239
+ if self.use_autobot:
240
+ self._fetch_from_autobot()
241
+ else:
242
+ if not self.api_key:
243
+ raise ValueError("API key required for Steam API schema fetch")
244
+ self._fetch_from_steam()
245
+
246
+ def _fetch_from_autobot(self):
247
+ try:
248
+ response = requests.get('https://schema.autobot.tf/schema')
249
+ response.raise_for_status()
250
+ data = response.json()
251
+
252
+ self.version = data.get('version')
253
+ self.raw = data.get('raw')
254
+ self.time = data.get('time', int(time.time() * 1000))
255
+
256
+ self._save_to_cache()
257
+ except Exception as e:
258
+ raise ValueError(f"Failed to fetch schema from autobot.tf: {e}")
259
+
260
+ def _fetch_from_steam(self):
261
+ raise NotImplementedError("Steam API schema fetching not yet implemented")
262
+
263
+ def get_item_by_defindex(self, defindex: int) -> Optional[Dict]:
264
+ items = self.raw['schema']['items']
265
+
266
+ start, end = 0, len(items) - 1
267
+ while start <= end:
268
+ mid = (start + end) // 2
269
+ if items[mid]['defindex'] < defindex:
270
+ start = mid + 1
271
+ elif items[mid]['defindex'] > defindex:
272
+ end = mid - 1
273
+ else:
274
+ return items[mid]
275
+
276
+ for item in items:
277
+ if item['defindex'] == defindex:
278
+ return item
279
+
280
+ return None
281
+
282
+ def get_item_by_item_name(self, name: str) -> Optional[Dict]:
283
+ name_lower = name.lower()
284
+
285
+ for item in self.raw['schema']['items']:
286
+ if name_lower == item['item_name'].lower():
287
+ if item['item_name'] == 'Name Tag' and item['defindex'] == 2093:
288
+ continue
289
+
290
+ if item.get('item_quality', 0) == 0:
291
+ continue
292
+
293
+ return item
294
+
295
+ return None
296
+
297
+ def get_item_by_item_name_with_the(self, name: str) -> Optional[Dict]:
298
+ name_lower = name.lower()
299
+
300
+ if 'the ' in name_lower:
301
+ name_lower = name_lower.replace('the ', '').strip()
302
+
303
+ for item in self.raw['schema']['items']:
304
+ item_name = item['item_name'].lower()
305
+
306
+ if 'the ' in item_name:
307
+ item_name = item_name.replace('the ', '').strip()
308
+
309
+ if name_lower == item_name:
310
+ if item['item_name'] == 'Name Tag' and item['defindex'] == 2093:
311
+ continue
312
+
313
+ if item.get('item_quality', 0) == 0:
314
+ continue
315
+
316
+ return item
317
+
318
+ return None
319
+
320
+ def get_quality_by_id(self, quality_id: int) -> Optional[str]:
321
+ qualities = self.raw['schema']['qualities']
322
+ quality_names = self.raw['schema']['qualityNames']
323
+
324
+ for name, qid in qualities.items():
325
+ if qid == quality_id:
326
+ return quality_names.get(name)
327
+
328
+ return None
329
+
330
+ def get_quality_id_by_name(self, name: str) -> Optional[int]:
331
+ qualities = self.raw['schema']['qualities']
332
+ quality_names = self.raw['schema']['qualityNames']
333
+
334
+ name_lower = name.lower()
335
+ for key, quality_name in quality_names.items():
336
+ if quality_name.lower() == name_lower:
337
+ return qualities.get(key)
338
+
339
+ return None
340
+
341
+ def get_effect_by_id(self, effect_id: int) -> Optional[str]:
342
+ particles = self.raw['schema']['attribute_controlled_attached_particles']
343
+
344
+ start, end = 0, len(particles) - 1
345
+ while start <= end:
346
+ mid = (start + end) // 2
347
+ if particles[mid]['id'] < effect_id:
348
+ start = mid + 1
349
+ elif particles[mid]['id'] > effect_id:
350
+ end = mid - 1
351
+ else:
352
+ return particles[mid]['name']
353
+
354
+ for particle in particles:
355
+ if particle['id'] == effect_id:
356
+ return particle['name']
357
+
358
+ return None
359
+
360
+ def get_effect_id_by_name(self, name: str) -> Optional[int]:
361
+ particles = self.raw['schema']['attribute_controlled_attached_particles']
362
+ name_lower = name.lower()
363
+
364
+ for particle in particles:
365
+ if particle['name'].lower() == name_lower:
366
+ return particle['id']
367
+
368
+ return None
369
+
370
+ def get_skin_by_id(self, skin_id: int) -> Optional[str]:
371
+ paintkits = self.raw['schema'].get('paintkits', {})
372
+ return paintkits.get(str(skin_id))
373
+
374
+ def get_skin_id_by_name(self, name: str) -> Optional[int]:
375
+ paintkits = self.raw['schema'].get('paintkits', {})
376
+ name_lower = name.lower()
377
+
378
+ for sid, skin_name in paintkits.items():
379
+ if skin_name.lower() == name_lower:
380
+ return int(sid)
381
+
382
+ return None
383
+
384
+ def get_paint_name_by_decimal(self, decimal: int) -> Optional[str]:
385
+ if decimal == 5801378:
386
+ return 'Legacy Paint'
387
+
388
+ paint_cans = [item for item in self.raw['schema']['items']
389
+ if 'Paint Can' in item.get('name', '') and item.get('name') != 'Paint Can']
390
+
391
+ for paint in paint_cans:
392
+ if 'attributes' not in paint:
393
+ continue
394
+
395
+ for attr in paint['attributes']:
396
+ if attr.get('value') == decimal:
397
+ return paint['item_name']
398
+
399
+ return None
400
+
401
+ def get_paint_decimal_by_name(self, name: str) -> Optional[int]:
402
+ if name == 'Legacy Paint':
403
+ return 5801378
404
+
405
+ paint_cans = [item for item in self.raw['schema']['items']
406
+ if 'Paint Can' in item.get('name', '') and item.get('name') != 'Paint Can']
407
+
408
+ name_lower = name.lower()
409
+ for paint in paint_cans:
410
+ if paint['item_name'].lower() == name_lower:
411
+ if 'attributes' in paint and paint['attributes']:
412
+ return paint['attributes'][0]['value']
413
+
414
+ return None
415
+
416
+ def _get_crate_series_list(self) -> Dict[int, int]:
417
+ crate_series = {}
418
+
419
+ for item in self.raw['schema']['items']:
420
+ if 'attributes' in item:
421
+ for attr in item['attributes']:
422
+ if attr.get('name') == 'set supply crate series':
423
+ crate_series[item['defindex']] = attr['value']
424
+ break
425
+
426
+ if 'items_game' in self.raw:
427
+ items = self.raw['items_game'].get('items', {})
428
+ for defindex, item_data in items.items():
429
+ if 'static_attrs' in item_data:
430
+ series_attr = item_data['static_attrs'].get('set supply crate series')
431
+ if series_attr:
432
+ value = series_attr.get('value', series_attr) if isinstance(series_attr, dict) else series_attr
433
+ crate_series[int(defindex)] = int(value)
434
+
435
+ return crate_series
436
+
437
+ def _get_qualities(self) -> Dict[str, int]:
438
+ qualities_raw = self.raw['schema']['qualities']
439
+ quality_names = self.raw['schema']['qualityNames']
440
+
441
+ return {quality_names[key]: value for key, value in qualities_raw.items() if key in quality_names}
442
+
443
+ def _get_particle_effects(self) -> Dict[str, int]:
444
+ effects = {}
445
+ previous = ''
446
+
447
+ for particle in self.raw['schema']['attribute_controlled_attached_particles']:
448
+ name = particle['name']
449
+ if name and name != previous:
450
+ effects[name] = particle['id']
451
+
452
+ if name == 'Eerie Orbiting Fire':
453
+ effects.pop('Orbiting Fire', None)
454
+ effects['Orbiting Fire'] = 33
455
+ elif name == 'Nether Trail':
456
+ effects.pop('Ether Trail', None)
457
+ effects['Ether Trail'] = 103
458
+ elif name == 'Refragmenting Reality':
459
+ effects.pop('Fragmenting Reality', None)
460
+ effects['Fragmenting Reality'] = 141
461
+
462
+ previous = name
463
+
464
+ effects.pop('', None)
465
+ return effects
466
+
467
+ def _get_paint_kits(self) -> Dict[str, int]:
468
+ paintkits = self.raw['schema'].get('paintkits', {})
469
+ return {name: int(kit_id) for kit_id, name in paintkits.items()}
470
+
471
+ def _get_paints(self) -> Dict[str, int]:
472
+ paints = {}
473
+
474
+ paint_cans = [item for item in self.raw['schema']['items']
475
+ if 'Paint Can' in item.get('name', '') and item.get('name') != 'Paint Can']
476
+
477
+ for paint in paint_cans:
478
+ if 'attributes' in paint and paint['attributes']:
479
+ paints[paint['item_name']] = paint['attributes'][0]['value']
480
+
481
+ paints['Legacy Paint'] = 5801378
482
+ return paints
483
+
484
+ def check_existence(self, item: Dict) -> bool:
485
+ schema_item = self.get_item_by_defindex(item['defindex'])
486
+ if not schema_item:
487
+ return False
488
+
489
+ if schema_item.get('item_quality') in [0, 3, 5, 11]:
490
+ if item.get('quality') != schema_item['item_quality']:
491
+ return False
492
+
493
+ if ((item.get('quality') != 1 and item['defindex'] in self.EXCLUSIVE_GENUINE_REVERSED) or
494
+ (item.get('quality') == 1 and item['defindex'] in self.EXCLUSIVE_GENUINE)):
495
+ return False
496
+
497
+ if str(item['defindex']) in self.RETIRED_KEYS:
498
+ if item['defindex'] in [5713, 5716, 5717, 5762]:
499
+ if item.get('craftable', True):
500
+ return False
501
+ elif item['defindex'] not in [5791, 5792]:
502
+ if not item.get('craftable', True):
503
+ return False
504
+
505
+ if schema_item.get('item_class') == 'supply_crate' and not item.get('crateseries'):
506
+ if item['defindex'] not in [5739, 5760, 5737, 5738]:
507
+ return False
508
+
509
+ if (item.get('quality', 6) != 6 or item.get('killstreak', 0) != 0 or
510
+ item.get('australium', False) or item.get('effect') or
511
+ item.get('festive', False) or item.get('paintkit') or
512
+ item.get('wear') or item.get('quality2') or
513
+ item.get('craftnumber') or item.get('target') or
514
+ item.get('output') or item.get('outputQuality') or
515
+ item.get('paint')):
516
+ return False
517
+
518
+ if item.get('crateseries'):
519
+ valid_series = [
520
+ 1, 3, 7, 12, 13, 18, 19, 23, 26, 31, 34, 39, 43, 47, 54, 57, 75,
521
+ 2, 4, 8, 11, 14, 17, 20, 24, 27, 32, 37, 42, 44, 49, 56, 71, 76,
522
+ 5, 9, 10, 15, 16, 21, 25, 28, 29, 33, 38, 41, 45, 55, 59, 77,
523
+ 30, 40, 50, 82, 83, 84, 85, 90, 91, 92, 103
524
+ ]
525
+
526
+ if item['crateseries'] not in valid_series:
527
+ if item['crateseries'] not in self.crate_series_list.values():
528
+ return False
529
+
530
+ if item['crateseries'] != self.crate_series_list.get(item['defindex']):
531
+ return False
532
+ else:
533
+ series = item['crateseries']
534
+ defindex = item['defindex']
535
+
536
+ valid = False
537
+ if series in [1, 3, 7, 12, 13, 18, 19, 23, 26, 31, 34, 39, 43, 47, 54, 57, 75]:
538
+ valid = defindex == 5022
539
+ elif series in [2, 4, 8, 11, 14, 17, 20, 24, 27, 32, 37, 42, 44, 49, 56, 71, 76]:
540
+ valid = defindex == 5041
541
+ elif series in [5, 9, 10, 15, 16, 21, 25, 28, 29, 33, 38, 41, 45, 55, 59, 77]:
542
+ valid = defindex == 5045
543
+ elif series in [30, 40, 50]:
544
+ valid = defindex == 5068
545
+ elif series in self.MUNITION_CRATES:
546
+ valid = defindex == self.MUNITION_CRATES[series]
547
+
548
+ if not valid:
549
+ return False
550
+
551
+ return True
552
+
553
+ def get_name(self, item: Dict, proper: bool = True, use_pipe_for_skin: bool = False,
554
+ scm_format: bool = False) -> Optional[str]:
555
+ schema_item = self.get_item_by_defindex(item['defindex'])
556
+ if not schema_item:
557
+ return None
558
+
559
+ name = ''
560
+
561
+ if not scm_format and not item.get('tradable', True):
562
+ name = 'Non-Tradable '
563
+
564
+ if not scm_format and not item.get('craftable', True):
565
+ name += 'Non-Craftable '
566
+
567
+ if item.get('quality2'):
568
+ quality2_name = self.get_quality_by_id(item['quality2'])
569
+ elevated_suffix = '(e)' if not scm_format and (item.get('wear') or item.get('paintkit')) else ''
570
+ name += f"{quality2_name}{elevated_suffix} "
571
+
572
+ should_add_quality = (
573
+ (item.get('quality') == 6 and item.get('quality2')) or
574
+ (item.get('quality') not in [6, 15, 5]) or
575
+ (item.get('quality') == 5 and not item.get('effect')) or
576
+ (item.get('quality') == 5 and scm_format) or
577
+ schema_item.get('item_quality') == 5
578
+ )
579
+
580
+ if should_add_quality:
581
+ name += f"{self.get_quality_by_id(item.get('quality', 6))} "
582
+
583
+ if not scm_format and item.get('effect'):
584
+ name += f"{self.get_effect_by_id(item['effect'])} "
585
+
586
+ if item.get('festive'):
587
+ name += 'Festivized '
588
+
589
+ if item.get('killstreak', 0) > 0:
590
+ ks_names = ['Killstreak', 'Specialized Killstreak', 'Professional Killstreak']
591
+ name += f"{ks_names[item['killstreak'] - 1]} "
592
+
593
+ if item.get('target'):
594
+ target_item = self.get_item_by_defindex(item['target'])
595
+ if target_item:
596
+ name += f"{target_item['item_name']} "
597
+
598
+ if item.get('outputQuality') and item['outputQuality'] != 6:
599
+ name = f"{self.get_quality_by_id(item['outputQuality'])} {name}"
600
+
601
+ if item.get('output'):
602
+ output_item = self.get_item_by_defindex(item['output'])
603
+ if output_item:
604
+ name += f"{output_item['item_name']} "
605
+
606
+ if item.get('australium'):
607
+ name += 'Australium '
608
+
609
+ if item.get('paintkit'):
610
+ skin_name = self.get_skin_by_id(item['paintkit'])
611
+ if skin_name:
612
+ separator = ' | ' if use_pipe_for_skin else ' '
613
+ name += f"{skin_name}{separator}"
614
+
615
+ if proper and not name and schema_item.get('proper_name'):
616
+ name = 'The '
617
+
618
+ if str(item['defindex']) in self.RETIRED_KEYS:
619
+ name += self.RETIRED_KEYS[str(item['defindex'])]['name']
620
+ else:
621
+ name += schema_item['item_name']
622
+
623
+ if item.get('wear'):
624
+ wear_names = ['Factory New', 'Minimal Wear', 'Field-Tested', 'Well-Worn', 'Battle Scarred']
625
+ name += f" ({wear_names[item['wear'] - 1]})"
626
+
627
+ if item.get('crateseries'):
628
+ has_attr = (schema_item.get('attributes') and
629
+ schema_item['attributes'][0].get('class') == 'supply_crate_series')
630
+ if scm_format:
631
+ if has_attr:
632
+ name += f" Series %23{item['crateseries']}"
633
+ else:
634
+ name += f" #{item['crateseries']}"
635
+ elif item.get('craftnumber'):
636
+ name += f" #{item['craftnumber']}"
637
+
638
+ if not scm_format and item.get('paint'):
639
+ paint_name = self.get_paint_name_by_decimal(item['paint'])
640
+ if paint_name:
641
+ name += f" (Paint: {paint_name})"
642
+
643
+ if scm_format and item.get('wear') and item.get('effect') and item.get('quality') == 15:
644
+ name = f"Unusual {name}"
645
+
646
+ return name