cs2tracker 2.1.17__tar.gz → 2.1.18__tar.gz

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.

Potentially problematic release.


This version of cs2tracker might be problematic. Click here for more details.

Files changed (45) hide show
  1. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/.gitignore +3 -1
  2. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/PKG-INFO +11 -10
  3. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/README.md +10 -9
  4. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/_version.py +2 -2
  5. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/app/editor_frame.py +59 -29
  6. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/config.py +61 -28
  7. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/constants.py +1 -0
  8. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/data/config.ini +14 -0
  9. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/data/convert_inventory.js +50 -19
  10. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/data/get_inventory.js +53 -24
  11. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker.egg-info/PKG-INFO +11 -10
  12. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/.flake8 +0 -0
  13. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/.isort.cfg +0 -0
  14. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/.pre-commit-config.yaml +0 -0
  15. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/.pylintrc +0 -0
  16. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/LICENSE +0 -0
  17. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/MANIFEST.in +0 -0
  18. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/assets/demo.gif +0 -0
  19. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/assets/icon.png +0 -0
  20. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/__init__.py +0 -0
  21. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/__main__.py +0 -0
  22. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/app/__init__.py +0 -0
  23. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/app/app.py +0 -0
  24. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/app/history_frame.py +0 -0
  25. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/app/scraper_frame.py +0 -0
  26. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/data/output.csv +0 -0
  27. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/logs.py +0 -0
  28. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/main.py +0 -0
  29. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/scraper/__init__.py +0 -0
  30. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/scraper/background_task.py +0 -0
  31. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/scraper/discord_notifier.py +0 -0
  32. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/scraper/parser.py +0 -0
  33. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/scraper/scraper.py +0 -0
  34. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/util/__init__.py +0 -0
  35. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/util/currency_conversion.py +0 -0
  36. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/util/padded_console.py +0 -0
  37. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker/util/tkinter.py +0 -0
  38. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker.egg-info/SOURCES.txt +0 -0
  39. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker.egg-info/dependency_links.txt +0 -0
  40. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker.egg-info/entry_points.txt +0 -0
  41. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker.egg-info/requires.txt +0 -0
  42. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/cs2tracker.egg-info/top_level.txt +0 -0
  43. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/pyproject.toml +0 -0
  44. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/requirements.txt +0 -0
  45. {cs2tracker-2.1.17 → cs2tracker-2.1.18}/setup.cfg +0 -0
@@ -14,8 +14,10 @@ cs2tracker.egg-info
14
14
  # Auto generated version file on build
15
15
  _version.py
16
16
 
17
- # PyInstaller output
17
+ # PyInstaller files
18
18
  output
19
+ cs2tracker.spec
20
+ build.ps1
19
21
 
20
22
  # Files generated by the program
21
23
  cs2tracker_scraper.bat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.17
3
+ Version: 2.1.18
4
4
  Summary: Tracking the steam market prices of CS2 items
5
5
  Home-page: https://github.com/ashiven/cs2tracker
6
6
  Author: Jannik Novak
@@ -120,29 +120,30 @@ This will open the config editor where you can change any setting by double clic
120
120
 
121
121
  - Enable **Daily Background Calculations** to automatically run a daily calculation of your investment in the background.
122
122
  - Use **Receive Discord Notifications** to receive a notification on your Discord server whenever the program has finished calculating your investment.
123
- - You need to set up a [webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in your Discord server and enter the webhook url into the `discord_webhook_url` field in the config `User Settings`.
123
+ - You need to set up a [webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in your Discord server and enter the webhook URL into the `discord_webhook_url` field in the config `User Settings`.
124
124
  - Enable **Proxy Requests** to prevent your requests from being rate limited by the steamcommunity server.
125
125
  - You need to register for a free API key on [Crawlbase](crawlbase.com) and enter it into the `proxy_api_key` field in the config `User Settings`.
126
126
 
127
127
  ## FAQ
128
128
 
129
- **Q: Is it safe to login with my Steam account?**
129
+ - **Q: Is it safe to login with my Steam account?**
130
+ - **A:** Yes, the program uses the [SteamUser](https://github.com/DoctorMcKay/node-steam-user?tab=readme-ov-file#methods-) and [Globaloffensive](https://github.com/DoctorMcKay/node-globaloffensive) libraries to sign in and import your Storage Units (the same method is used by [casemove](https://github.com/nombersDev/casemove)) and all of the login-related code is transparently available in [this file](cs2tracker/data/get_inventory.js).
130
131
 
131
- **A:** Yes, the program uses the [SteamUser](https://github.com/DoctorMcKay/node-steam-user?tab=readme-ov-file#methods-) and [Globaloffensive](https://github.com/DoctorMcKay/node-globaloffensive) libraries to sign in and import your Storage Units (the same method is used by [casemove](https://github.com/nombersDev/casemove)) and all of the login-related code is transparently available in [this file](cs2tracker/data/get_inventory.js).
132
132
 
133
+ - **Q: Do I have to login with my Steam account?**
134
+ - **A:** No, you can also manually specify the number of items you own in the config editor.
133
135
 
134
- **Q: Do I have to login with my Steam account?**
135
136
 
136
- **A:** No, you can also manually specify the number of items you own in the config editor.
137
+ - **Q: Can I get VAC-banned for using this program?**
138
+ - **A:** No, this program does not interact with the game in any way and only reads your Storage Units.
137
139
 
138
140
 
139
- **Q: Can I get VAC-banned for using this program?**
140
-
141
- **A:** No, this program does not interact with the game in any way and only reads your Storage Units.
141
+ - **Q: Why does Windows Defender flag this program as potentially harmful?**
142
+ - **A:** This is because the program is not signed with a [Code Signing Certificate](https://www.globalsign.com/en/code-signing-certificate/what-is-code-signing-certificate), which Windows uses to verify the identity of publishers. These certificates are very expensive and not something I am willing to invest in for a free and open source project like this.
142
143
 
143
144
  ## Contributing
144
145
 
145
- Please feel free to submit a pull request or open an issue. See [issues](https://github.com/ashiven/cs2tracker/issues) and [pull requests](https://github.com/ashiven/cs2tracker/pulls) for current work.
146
+ Please feel free to submit a [pull request](https://github.com/ashiven/cs2tracker/pulls) or open an [issue](https://github.com/ashiven/cs2tracker/issues).
146
147
 
147
148
  1. Fork the repository
148
149
  2. Create a new branch: `git checkout -b feature-name`.
@@ -92,29 +92,30 @@ This will open the config editor where you can change any setting by double clic
92
92
 
93
93
  - Enable **Daily Background Calculations** to automatically run a daily calculation of your investment in the background.
94
94
  - Use **Receive Discord Notifications** to receive a notification on your Discord server whenever the program has finished calculating your investment.
95
- - You need to set up a [webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in your Discord server and enter the webhook url into the `discord_webhook_url` field in the config `User Settings`.
95
+ - You need to set up a [webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in your Discord server and enter the webhook URL into the `discord_webhook_url` field in the config `User Settings`.
96
96
  - Enable **Proxy Requests** to prevent your requests from being rate limited by the steamcommunity server.
97
97
  - You need to register for a free API key on [Crawlbase](crawlbase.com) and enter it into the `proxy_api_key` field in the config `User Settings`.
98
98
 
99
99
  ## FAQ
100
100
 
101
- **Q: Is it safe to login with my Steam account?**
101
+ - **Q: Is it safe to login with my Steam account?**
102
+ - **A:** Yes, the program uses the [SteamUser](https://github.com/DoctorMcKay/node-steam-user?tab=readme-ov-file#methods-) and [Globaloffensive](https://github.com/DoctorMcKay/node-globaloffensive) libraries to sign in and import your Storage Units (the same method is used by [casemove](https://github.com/nombersDev/casemove)) and all of the login-related code is transparently available in [this file](cs2tracker/data/get_inventory.js).
102
103
 
103
- **A:** Yes, the program uses the [SteamUser](https://github.com/DoctorMcKay/node-steam-user?tab=readme-ov-file#methods-) and [Globaloffensive](https://github.com/DoctorMcKay/node-globaloffensive) libraries to sign in and import your Storage Units (the same method is used by [casemove](https://github.com/nombersDev/casemove)) and all of the login-related code is transparently available in [this file](cs2tracker/data/get_inventory.js).
104
104
 
105
+ - **Q: Do I have to login with my Steam account?**
106
+ - **A:** No, you can also manually specify the number of items you own in the config editor.
105
107
 
106
- **Q: Do I have to login with my Steam account?**
107
108
 
108
- **A:** No, you can also manually specify the number of items you own in the config editor.
109
+ - **Q: Can I get VAC-banned for using this program?**
110
+ - **A:** No, this program does not interact with the game in any way and only reads your Storage Units.
109
111
 
110
112
 
111
- **Q: Can I get VAC-banned for using this program?**
112
-
113
- **A:** No, this program does not interact with the game in any way and only reads your Storage Units.
113
+ - **Q: Why does Windows Defender flag this program as potentially harmful?**
114
+ - **A:** This is because the program is not signed with a [Code Signing Certificate](https://www.globalsign.com/en/code-signing-certificate/what-is-code-signing-certificate), which Windows uses to verify the identity of publishers. These certificates are very expensive and not something I am willing to invest in for a free and open source project like this.
114
115
 
115
116
  ## Contributing
116
117
 
117
- Please feel free to submit a pull request or open an issue. See [issues](https://github.com/ashiven/cs2tracker/issues) and [pull requests](https://github.com/ashiven/cs2tracker/pulls) for current work.
118
+ Please feel free to submit a [pull request](https://github.com/ashiven/cs2tracker/pulls) or open an [issue](https://github.com/ashiven/cs2tracker/issues).
118
119
 
119
120
  1. Fork the repository
120
121
  2. Create a new branch: `git checkout -b feature-name`.
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.1.17'
21
- __version_tuple__ = version_tuple = (2, 1, 17)
20
+ __version__ = version = '2.1.18'
21
+ __version_tuple__ = version_tuple = (2, 1, 18)
@@ -8,7 +8,7 @@ from tkinter import messagebox, ttk
8
8
  from nodejs import node
9
9
  from ttk_text import ThemedText
10
10
 
11
- from cs2tracker.config import get_config
11
+ from cs2tracker.config import CUSTOM_SECTIONS, get_config
12
12
  from cs2tracker.constants import (
13
13
  CONFIG_FILE,
14
14
  CONFIG_FILE_BACKUP,
@@ -131,7 +131,7 @@ class ConfigEditorFrame(ttk.Frame):
131
131
  if selected:
132
132
  item = selected[0]
133
133
  section_name = self.tree.parent(item)
134
- if section_name in ("Stickers", "Skins"):
134
+ if section_name in CUSTOM_SECTIONS:
135
135
  next_option = self.tree.next(item)
136
136
  self.tree.delete(item)
137
137
  self.save_config()
@@ -169,31 +169,19 @@ class ConfigEditorFrame(ttk.Frame):
169
169
  continue
170
170
 
171
171
  section_level = self.tree.insert("", "end", iid=section, text=section)
172
-
173
- # Items in the Stickers, Cases, and Skins sections should be displayed alphabetically sorted
174
- section_items = config.items(section)
175
- if section in ("Stickers", "Cases", "Skins"):
176
- section_items = sorted(section_items)
177
-
178
- for config_option, value in section_items:
172
+ sorted_section_items = sorted(config.items(section))
173
+ for config_option, value in sorted_section_items:
179
174
  if section not in ("User Settings", "App Settings"):
180
175
  option_name = config.option_to_name(config_option, href=True)
181
- self.tree.insert(
182
- section_level,
183
- "end",
184
- iid=f"{section}-{option_name}",
185
- text=option_name,
186
- values=[value],
187
- )
188
176
  else:
189
177
  option_name = config.option_to_name(config_option)
190
- self.tree.insert(
191
- section_level,
192
- "end",
193
- iid=f"{section}-{option_name}",
194
- text=option_name,
195
- values=[value],
196
- )
178
+ self.tree.insert(
179
+ section_level,
180
+ "end",
181
+ iid=f"{section}-{option_name}",
182
+ text=option_name,
183
+ values=[value],
184
+ )
197
185
 
198
186
  self.tree.focus("User Settings")
199
187
  self.tree.selection_set("User Settings")
@@ -267,6 +255,25 @@ class ConfigEditorButtonFrame(ttk.Frame):
267
255
  custom_item_button = ttk.Button(self, text="Add Item", command=self._add_custom_item)
268
256
  custom_item_button.pack(side="left", expand=True, padx=5)
269
257
 
258
+ def open_custom_enter(_):
259
+ selected = self.editor_frame.tree.selection()
260
+ if selected and not self.editor_frame.tree.parent(selected[0]):
261
+ selected_section = self.editor_frame.tree.item(selected[0], "text")
262
+ if selected_section in CUSTOM_SECTIONS and not self.editor_frame.tree.get_children(
263
+ selected_section
264
+ ):
265
+ custom_item_button.invoke()
266
+
267
+ def open_custom_click(event):
268
+ selected_section = self.editor_frame.tree.identify_row(event.y)
269
+ if selected_section in CUSTOM_SECTIONS and not self.editor_frame.tree.get_children(
270
+ selected_section
271
+ ):
272
+ custom_item_button.invoke()
273
+
274
+ self.editor_frame.tree.bind("<Return>", open_custom_enter, add="+")
275
+ self.editor_frame.tree.bind("<Double-1>", open_custom_click, add="+")
276
+
270
277
  import_inventory_button = ttk.Button(
271
278
  self, text="Import Steam Inventory", command=self._import_steam_inventory
272
279
  )
@@ -397,6 +404,30 @@ class CustomItemFrame(ttk.Frame):
397
404
  break
398
405
  return insert_index
399
406
 
407
+ def _identify_custom_section(self, item_name):
408
+ # pylint: disable=too-many-return-statements
409
+ """Given an item name, identify the custom section it belongs to."""
410
+ if "Patch Pack" in item_name or "Patch Collection" in item_name:
411
+ return "Patch Packs"
412
+ elif "Patch |" in item_name:
413
+ return "Patches"
414
+ elif "Sticker |" in item_name:
415
+ return "Stickers"
416
+ elif "Charm |" in item_name:
417
+ return "Charms"
418
+ elif "Souvenir" in item_name and "|" not in item_name:
419
+ return "Souvenirs"
420
+ elif "★ " in item_name:
421
+ return "Special Items"
422
+ elif " | " in item_name and "(" in item_name and ")" in item_name:
423
+ return "Skins"
424
+ elif "Music Kit |" in item_name:
425
+ return "Others"
426
+ elif " | " in item_name:
427
+ return "Agents"
428
+ else:
429
+ return "Others"
430
+
400
431
  def _add_custom_item(self, item_href, item_owned):
401
432
  """Add a custom item to the configuration."""
402
433
  if not item_href or not item_owned:
@@ -404,7 +435,7 @@ class CustomItemFrame(ttk.Frame):
404
435
  "Input Error", "All fields must be filled out.", parent=self.window
405
436
  )
406
437
  return
407
- if config.option_exists(item_href, exclude_sections=("Stickers", "Skins")):
438
+ if config.option_exists(item_href, exclude_sections=CUSTOM_SECTIONS):
408
439
  messagebox.showerror(
409
440
  "Item Exists", "This item already exists in another section.", parent=self.window
410
441
  )
@@ -416,12 +447,11 @@ class CustomItemFrame(ttk.Frame):
416
447
  messagebox.showerror("Invalid URL", str(error), parent=self.window)
417
448
  return
418
449
 
419
- if self._update_existing("Stickers", item_name, item_owned):
420
- return
421
- if self._update_existing("Skins", item_name, item_owned):
422
- return
450
+ for section in CUSTOM_SECTIONS:
451
+ if self._update_existing(section, item_name, item_owned):
452
+ return
423
453
 
424
- section = "Stickers" if item_name.startswith("Sticker") else "Skins"
454
+ section = self._identify_custom_section(item_name)
425
455
  insert_index = self._get_insert_index(item_name, section)
426
456
  self.editor_frame.tree.insert(
427
457
  section,
@@ -3,12 +3,53 @@ import re
3
3
  from configparser import ConfigParser, ParsingError
4
4
  from urllib.parse import quote, unquote
5
5
 
6
- from cs2tracker.constants import CAPSULE_PAGES, CONFIG_FILE, INVENTORY_IMPORT_FILE
6
+ from cs2tracker.constants import (
7
+ CONFIG_FILE,
8
+ INVENTORY_IMPORT_FILE,
9
+ )
7
10
  from cs2tracker.util.padded_console import get_console
8
11
 
9
12
  STEAM_MARKET_LISTING_BASEURL_CS2 = "https://steamcommunity.com/market/listings/730/"
10
13
  STEAM_MARKET_LISTING_REGEX = r"^https://steamcommunity.com/market/listings/\d+/.+$"
11
14
 
15
+ CUSTOM_SECTIONS = [
16
+ "Skins",
17
+ "Special Items",
18
+ "Agents",
19
+ "Charms",
20
+ "Patches",
21
+ "Patch Packs",
22
+ "Stickers",
23
+ "Souvenirs",
24
+ "Others",
25
+ ]
26
+
27
+ PREEXISTING_SECTIONS = [
28
+ "Cases",
29
+ "Katowice 2014 Sticker Capsule",
30
+ "Cologne 2014 Sticker Capsule",
31
+ "DreamHack 2014 Sticker Capsule",
32
+ "Katowice 2015 Sticker Capsule",
33
+ "Cologne 2015 Sticker Capsule",
34
+ "Cluj-Napoca 2015 Sticker Capsule",
35
+ "Columbus 2016 Sticker Capsule",
36
+ "Cologne 2016 Sticker Capsule",
37
+ "Atlanta 2017 Sticker Capsule",
38
+ "Krakow 2017 Sticker Capsule",
39
+ "Boston 2018 Sticker Capsule",
40
+ "London 2018 Sticker Capsule",
41
+ "Katowice 2019 Sticker Capsule",
42
+ "Berlin 2019 Sticker Capsule",
43
+ "2020 RMR Sticker Capsule",
44
+ "Stockholm 2021 Sticker Capsule",
45
+ "Antwerp 2022 Sticker Capsule",
46
+ "Rio 2022 Sticker Capsule",
47
+ "Paris 2023 Sticker Capsule",
48
+ "Copenhagen 2024 Sticker Capsule",
49
+ "Shanghai 2024 Sticker Capsule",
50
+ "Austin 2025 Sticker Capsule",
51
+ ]
52
+
12
53
  console = get_console()
13
54
 
14
55
 
@@ -46,19 +87,12 @@ class ValidatedConfig(ConfigParser):
46
87
 
47
88
  def _validate_config_sections(self):
48
89
  """Validate that the configuration file has all required sections."""
49
- if not self.has_section("User Settings"):
50
- raise ValueError("Missing 'User Settings' section in the configuration file.")
51
- if not self.has_section("App Settings"):
52
- raise ValueError("Missing 'App Settings' section in the configuration file.")
53
- if not self.has_section("Stickers"):
54
- raise ValueError("Missing 'Stickers' section in the configuration file.")
55
- if not self.has_section("Cases"):
56
- raise ValueError("Missing 'Cases' section in the configuration file.")
57
- if not self.has_section("Skins"):
58
- raise ValueError("Missing 'Skins' section in the configuration file.")
59
- for capsule_section in CAPSULE_PAGES:
60
- if not self.has_section(capsule_section):
61
- raise ValueError(f"Missing '{capsule_section}' section in the configuration file.")
90
+ for section in CUSTOM_SECTIONS:
91
+ if not self.has_section(section):
92
+ raise ValueError(f"Missing '{section}' section in the configuration file.")
93
+ for section in PREEXISTING_SECTIONS:
94
+ if not self.has_section(section):
95
+ raise ValueError(f"Missing '{section}' section in the configuration file.")
62
96
 
63
97
  def _validate_config_values(self):
64
98
  # pylint: disable=too-many-branches
@@ -136,23 +170,22 @@ class ValidatedConfig(ConfigParser):
136
170
  try:
137
171
  with open(INVENTORY_IMPORT_FILE, "r", encoding="utf-8") as inventory_file:
138
172
  inventory_data = json.load(inventory_file)
139
- sorted_inventory_data = dict(sorted(inventory_data.items()))
140
173
 
141
174
  added_to_config = set()
142
- for item_name, item_owned in sorted_inventory_data.items():
143
- option = self.name_to_option(item_name, href=True)
144
- for section in self.sections():
145
- if option in self.options(section):
146
- self.set(section, option, str(item_owned))
147
- added_to_config.add(item_name)
148
-
149
- for item_name, item_owned in sorted_inventory_data.items():
150
- if item_name not in added_to_config:
175
+ for _, item_infos in inventory_data.items():
176
+ for item_name, item_owned in item_infos.items():
151
177
  option = self.name_to_option(item_name, href=True)
152
- if item_name.startswith("Sticker"):
153
- self.set("Stickers", option, str(item_owned))
154
- else:
155
- self.set("Skins", option, str(item_owned))
178
+ for section in self.sections():
179
+ if option in self.options(section):
180
+ self.set(section, option, str(item_owned))
181
+ added_to_config.add(item_name)
182
+
183
+ for section, item_infos in inventory_data.items():
184
+ sorted_item_infos = dict(sorted(item_infos.items()))
185
+ for item_name, item_owned in sorted_item_infos.items():
186
+ if item_name not in added_to_config:
187
+ option = self.name_to_option(item_name, href=True)
188
+ self.set(section, option, str(item_owned))
156
189
 
157
190
  self.write_to_file()
158
191
  except (FileNotFoundError, json.JSONDecodeError) as error:
@@ -187,6 +187,7 @@ AUTHOR_STRING = (
187
187
  f"Version: {VERSION} - {datetime.today().strftime('%Y/%m/%d')} - Jannik Novak @ashiven\n"
188
188
  )
189
189
 
190
+
190
191
  CAPSULE_PAGES = {
191
192
  "Katowice 2014 Sticker Capsule": "https://steamcommunity.com/market/search?q=katowice+2014+legends+challengers",
192
193
  "Cologne 2014 Sticker Capsule": "https://steamcommunity.com/market/search?q=cologne+2014+legends+challengers",
@@ -9,8 +9,22 @@ discord_webhook_url ~
9
9
 
10
10
  [Skins]
11
11
 
12
+ [Special Items]
13
+
14
+ [Agents]
15
+
16
+ [Charms]
17
+
18
+ [Patches]
19
+
20
+ [Patch Packs]
21
+
12
22
  [Stickers]
13
23
 
24
+ [Souvenirs]
25
+
26
+ [Others]
27
+
14
28
  [Cases]
15
29
  https://steamcommunity.com/market/listings/730/CS%3AGO%20Weapon%20Case ~ 0
16
30
  https://steamcommunity.com/market/listings/730/CS%3AGO%20Weapon%20Case%202 ~ 0
@@ -11,6 +11,9 @@ const itemsGameLink =
11
11
  const translationsCacheFile = path.join(__dirname, "translations.json");
12
12
  const itemsCacheFile = path.join(__dirname, "items.json");
13
13
 
14
+ const translationRegex = /"(.+?)"\s+"(.+?)"/;
15
+ const souvenirRegex = /^csgo_crate_[a-z0-9]+_promo.*/;
16
+
14
17
  class ItemNameConverter {
15
18
  constructor() {
16
19
  this.translations = {};
@@ -38,7 +41,7 @@ class ItemNameConverter {
38
41
  const res = await axios.get(translationsLink);
39
42
  const lines = res.data.split(/\n/);
40
43
  for (const line of lines) {
41
- const match = line.match(/"(.+?)"\s+"(.+?)"/);
44
+ const match = line.match(translationRegex);
42
45
  if (match) {
43
46
  this.translations[match[1].toLowerCase()] = match[2];
44
47
  }
@@ -174,7 +177,7 @@ class ItemNameConverter {
174
177
 
175
178
  getItemType(item) {
176
179
  const def = this.items[item.def_index];
177
- if (def === undefined) return "unknown";
180
+ if (def === undefined) return "Unknown";
178
181
 
179
182
  if (def.item_name !== undefined) {
180
183
  let translatedName =
@@ -183,17 +186,48 @@ class ItemNameConverter {
183
186
  translatedName.startsWith("csgo_crate_sticker_pack") ||
184
187
  translatedName.startsWith("csgo_crate_signature_pack")
185
188
  ) {
186
- return "sticker capsule";
187
- } else if (translatedName.startsWith("csgo_crate_community")) {
188
- return "case";
189
+ return "Sticker Capsules";
190
+ } else if (translatedName.startsWith("csgo_crate_patch_pack")) {
191
+ return "Patch Packs";
192
+ } else if (translatedName.match(souvenirRegex)) {
193
+ return "Souvenirs";
194
+ } else if (
195
+ translatedName.startsWith("csgo_crate_community") ||
196
+ translatedName.startsWith("csgo_crate_gamma") ||
197
+ translatedName.startsWith("csgo_crate_valve") ||
198
+ translatedName.startsWith("csgo_crate_esports") ||
199
+ translatedName.startsWith("csgo_crate_operation")
200
+ ) {
201
+ return "Cases";
189
202
  } else if (translatedName.startsWith("csgo_tool_spray")) {
190
- return "graffiti kit";
203
+ return "Graffitis";
191
204
  } else if (translatedName.startsWith("csgo_tool_sticker")) {
192
- return "sticker";
205
+ return "Stickers";
206
+ } else if (translatedName.startsWith("csgo_tool_patch")) {
207
+ return "Patches";
208
+ } else if (translatedName.startsWith("csgo_tool_keychain")) {
209
+ return "Charms";
210
+ } else if (translatedName.startsWith("csgo_customplayer")) {
211
+ return "Agents";
193
212
  }
194
213
  }
195
214
 
196
- return "other";
215
+ if (item.quality === 3) {
216
+ return "Special Items";
217
+ }
218
+
219
+ if (def.prefab !== undefined) {
220
+ let prefab = this.prefabs[def.prefab];
221
+ if (
222
+ prefab !== undefined &&
223
+ prefab.image_inventory !== undefined &&
224
+ prefab.image_inventory.startsWith("econ/weapons/base_weapons")
225
+ ) {
226
+ return "Skins";
227
+ }
228
+ }
229
+
230
+ return "Others";
197
231
  }
198
232
 
199
233
  getItemTradable(item) {
@@ -204,11 +238,13 @@ class ItemNameConverter {
204
238
  let translatedName =
205
239
  def.item_name.replace("#", "").toLowerCase() || def.item_name;
206
240
  if (
207
- translatedName.startsWith("csgo_collectible") ||
241
+ (translatedName.startsWith("csgo_collectible") &&
242
+ !translatedName.startsWith("csgo_collectible_pin")) ||
208
243
  translatedName.startsWith("csgo_tournamentpass") ||
209
244
  translatedName.startsWith("csgo_tournamentjournal") ||
210
245
  translatedName.startsWith("csgo_ticket") ||
211
- translatedName.startsWith("csgo_tool_casket_tag")
246
+ translatedName.startsWith("csgo_tool_casket_tag") ||
247
+ translatedName.startsWith("sfui_wpnhud_c4")
212
248
  ) {
213
249
  return false;
214
250
  }
@@ -221,14 +257,6 @@ class ItemNameConverter {
221
257
  return false;
222
258
  }
223
259
 
224
- // Base weapons with stickers/name tags
225
- if (
226
- def.image_inventory !== undefined &&
227
- def.image_inventory.startsWith("econ/weapons/base_weapons")
228
- ) {
229
- return false;
230
- }
231
-
232
260
  // Base weapons with stickers/name tags
233
261
  if (
234
262
  item.paint_index === undefined &&
@@ -239,7 +267,10 @@ class ItemNameConverter {
239
267
  if (
240
268
  prefab !== undefined &&
241
269
  prefab.image_inventory !== undefined &&
242
- prefab.image_inventory.startsWith("econ/weapons/base_weapons")
270
+ prefab.image_inventory.startsWith("econ/weapons/base_weapons") &&
271
+ !prefab.image_inventory.startsWith(
272
+ "econ/weapons/base_weapons/weapon_knife",
273
+ )
243
274
  ) {
244
275
  return false;
245
276
  }
@@ -91,16 +91,12 @@ console.error = (...args) => {
91
91
 
92
92
  if (importInventory) {
93
93
  const inventoryItemCounts = await processInventory();
94
- for (const [itemName, count] of Object.entries(inventoryItemCounts)) {
95
- finalItemCounts[itemName] = (finalItemCounts[itemName] || 0) + count;
96
- }
94
+ mergeItemCounts(finalItemCounts, inventoryItemCounts);
97
95
  }
98
96
 
99
97
  if (importStorageUnits) {
100
98
  const storageUnitItemCounts = await processStorageUnits();
101
- for (const [itemName, count] of Object.entries(storageUnitItemCounts)) {
102
- finalItemCounts[itemName] = (finalItemCounts[itemName] || 0) + count;
103
- }
99
+ mergeItemCounts(finalItemCounts, storageUnitItemCounts);
104
100
  }
105
101
 
106
102
  paddedLog("Saving config...");
@@ -127,7 +123,7 @@ console.error = (...args) => {
127
123
  const convertedItems =
128
124
  nameConverter.convertInventory(prefilteredInventory);
129
125
  const filteredItems = filterItems(convertedItems);
130
- const itemCounts = countItems(filteredItems);
126
+ const itemCounts = groupAndCountItems(filteredItems);
131
127
  paddedLog(`${filteredItems.length} items found in inventory`);
132
128
  console.log(itemCounts);
133
129
  return itemCounts;
@@ -145,10 +141,8 @@ console.error = (...args) => {
145
141
  const items = await getCasketContentsAsync(cs2, unitId);
146
142
  const convertedItems = nameConverter.convertInventory(items);
147
143
  const filteredItems = filterItems(convertedItems);
148
- const itemCounts = countItems(filteredItems);
149
- for (const [itemName, count] of Object.entries(itemCounts)) {
150
- finalItemCounts[itemName] = (finalItemCounts[itemName] || 0) + count;
151
- }
144
+ const itemCounts = groupAndCountItems(filteredItems);
145
+ mergeItemCounts(finalItemCounts, itemCounts);
152
146
  paddedLog(
153
147
  `${filteredItems.length} items found in storage unit: ${unitIndex + 1}/${storageUnitIds.length}`,
154
148
  );
@@ -181,16 +175,27 @@ console.error = (...args) => {
181
175
  }
182
176
 
183
177
  function filterItems(items) {
178
+ const otherItemTypes = [
179
+ "Skins",
180
+ "Special Items",
181
+ "Agents",
182
+ "Charms",
183
+ "Patches",
184
+ "Patch Packs",
185
+ "Souvenirs",
186
+ "Others",
187
+ ];
184
188
  let filteredItems = [];
189
+
185
190
  items.forEach((item) => {
186
191
  if (!item.item_tradable) {
187
192
  return;
188
193
  }
189
194
  if (
190
- (item.item_type === "case" && importCases) ||
191
- (item.item_type === "sticker capsule" && importStickerCapsules) ||
192
- (item.item_type === "sticker" && importStickers) ||
193
- (item.item_type === "other" && importOthers)
195
+ (item.item_type === "Cases" && importCases) ||
196
+ (item.item_type === "Sticker Capsules" && importStickerCapsules) ||
197
+ (item.item_type === "Stickers" && importStickers) ||
198
+ (otherItemTypes.includes(item.item_type) && importOthers)
194
199
  ) {
195
200
  filteredItems.push(item);
196
201
  }
@@ -198,15 +203,39 @@ console.error = (...args) => {
198
203
  return filteredItems;
199
204
  }
200
205
 
201
- function countItems(items) {
202
- let itemCounts = {};
203
- items.forEach((item) => {
204
- if (itemCounts[item.item_name]) {
205
- itemCounts[item.item_name]++;
206
- } else {
207
- itemCounts[item.item_name] = 1;
206
+ function groupAndCountItems(items) {
207
+ let groupedItems = items.reduce((acc, item) => {
208
+ const { item_name, item_type } = item;
209
+
210
+ if (!acc[item_type]) {
211
+ acc[item_type] = {};
208
212
  }
209
- });
210
- return itemCounts;
213
+
214
+ if (!acc[item_type][item_name]) {
215
+ acc[item_type][item_name] = 0;
216
+ }
217
+
218
+ acc[item_type][item_name]++;
219
+ return acc;
220
+ }, {});
221
+
222
+ return groupedItems;
223
+ }
224
+
225
+ function mergeItemCounts(finalItemCounts, currentItemCounts) {
226
+ for (const item_type in currentItemCounts) {
227
+ if (!finalItemCounts[item_type]) {
228
+ finalItemCounts[item_type] = {};
229
+ }
230
+
231
+ for (const item_name in currentItemCounts[item_type]) {
232
+ if (!finalItemCounts[item_type][item_name]) {
233
+ finalItemCounts[item_type][item_name] = 0;
234
+ }
235
+
236
+ finalItemCounts[item_type][item_name] +=
237
+ currentItemCounts[item_type][item_name];
238
+ }
239
+ }
211
240
  }
212
241
  })();
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.17
3
+ Version: 2.1.18
4
4
  Summary: Tracking the steam market prices of CS2 items
5
5
  Home-page: https://github.com/ashiven/cs2tracker
6
6
  Author: Jannik Novak
@@ -120,29 +120,30 @@ This will open the config editor where you can change any setting by double clic
120
120
 
121
121
  - Enable **Daily Background Calculations** to automatically run a daily calculation of your investment in the background.
122
122
  - Use **Receive Discord Notifications** to receive a notification on your Discord server whenever the program has finished calculating your investment.
123
- - You need to set up a [webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in your Discord server and enter the webhook url into the `discord_webhook_url` field in the config `User Settings`.
123
+ - You need to set up a [webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) in your Discord server and enter the webhook URL into the `discord_webhook_url` field in the config `User Settings`.
124
124
  - Enable **Proxy Requests** to prevent your requests from being rate limited by the steamcommunity server.
125
125
  - You need to register for a free API key on [Crawlbase](crawlbase.com) and enter it into the `proxy_api_key` field in the config `User Settings`.
126
126
 
127
127
  ## FAQ
128
128
 
129
- **Q: Is it safe to login with my Steam account?**
129
+ - **Q: Is it safe to login with my Steam account?**
130
+ - **A:** Yes, the program uses the [SteamUser](https://github.com/DoctorMcKay/node-steam-user?tab=readme-ov-file#methods-) and [Globaloffensive](https://github.com/DoctorMcKay/node-globaloffensive) libraries to sign in and import your Storage Units (the same method is used by [casemove](https://github.com/nombersDev/casemove)) and all of the login-related code is transparently available in [this file](cs2tracker/data/get_inventory.js).
130
131
 
131
- **A:** Yes, the program uses the [SteamUser](https://github.com/DoctorMcKay/node-steam-user?tab=readme-ov-file#methods-) and [Globaloffensive](https://github.com/DoctorMcKay/node-globaloffensive) libraries to sign in and import your Storage Units (the same method is used by [casemove](https://github.com/nombersDev/casemove)) and all of the login-related code is transparently available in [this file](cs2tracker/data/get_inventory.js).
132
132
 
133
+ - **Q: Do I have to login with my Steam account?**
134
+ - **A:** No, you can also manually specify the number of items you own in the config editor.
133
135
 
134
- **Q: Do I have to login with my Steam account?**
135
136
 
136
- **A:** No, you can also manually specify the number of items you own in the config editor.
137
+ - **Q: Can I get VAC-banned for using this program?**
138
+ - **A:** No, this program does not interact with the game in any way and only reads your Storage Units.
137
139
 
138
140
 
139
- **Q: Can I get VAC-banned for using this program?**
140
-
141
- **A:** No, this program does not interact with the game in any way and only reads your Storage Units.
141
+ - **Q: Why does Windows Defender flag this program as potentially harmful?**
142
+ - **A:** This is because the program is not signed with a [Code Signing Certificate](https://www.globalsign.com/en/code-signing-certificate/what-is-code-signing-certificate), which Windows uses to verify the identity of publishers. These certificates are very expensive and not something I am willing to invest in for a free and open source project like this.
142
143
 
143
144
  ## Contributing
144
145
 
145
- Please feel free to submit a pull request or open an issue. See [issues](https://github.com/ashiven/cs2tracker/issues) and [pull requests](https://github.com/ashiven/cs2tracker/pulls) for current work.
146
+ Please feel free to submit a [pull request](https://github.com/ashiven/cs2tracker/pulls) or open an [issue](https://github.com/ashiven/cs2tracker/issues).
146
147
 
147
148
  1. Fork the repository
148
149
  2. Create a new branch: `git checkout -b feature-name`.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes