cs2tracker 2.1.15__tar.gz → 2.1.17__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.15 → cs2tracker-2.1.17}/.gitignore +1 -0
  2. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/PKG-INFO +35 -15
  3. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/README.md +34 -14
  4. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/_version.py +2 -2
  5. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/app/app.py +48 -15
  6. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/app/editor_frame.py +40 -16
  7. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/constants.py +104 -27
  8. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/scraper/background_task.py +79 -5
  9. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/scraper/parser.py +7 -1
  10. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker.egg-info/PKG-INFO +35 -15
  11. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/.flake8 +0 -0
  12. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/.isort.cfg +0 -0
  13. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/.pre-commit-config.yaml +0 -0
  14. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/.pylintrc +0 -0
  15. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/LICENSE +0 -0
  16. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/MANIFEST.in +0 -0
  17. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/assets/demo.gif +0 -0
  18. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/assets/icon.png +0 -0
  19. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/__init__.py +0 -0
  20. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/__main__.py +0 -0
  21. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/app/__init__.py +0 -0
  22. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/app/history_frame.py +0 -0
  23. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/app/scraper_frame.py +0 -0
  24. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/config.py +0 -0
  25. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/data/config.ini +0 -0
  26. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/data/convert_inventory.js +0 -0
  27. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/data/get_inventory.js +0 -0
  28. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/data/output.csv +0 -0
  29. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/logs.py +0 -0
  30. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/main.py +0 -0
  31. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/scraper/__init__.py +0 -0
  32. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/scraper/discord_notifier.py +0 -0
  33. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/scraper/scraper.py +0 -0
  34. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/util/__init__.py +0 -0
  35. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/util/currency_conversion.py +0 -0
  36. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/util/padded_console.py +0 -0
  37. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker/util/tkinter.py +0 -0
  38. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker.egg-info/SOURCES.txt +0 -0
  39. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker.egg-info/dependency_links.txt +0 -0
  40. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker.egg-info/entry_points.txt +0 -0
  41. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker.egg-info/requires.txt +0 -0
  42. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/cs2tracker.egg-info/top_level.txt +0 -0
  43. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/pyproject.toml +0 -0
  44. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/requirements.txt +0 -0
  45. {cs2tracker-2.1.15 → cs2tracker-2.1.17}/setup.cfg +0 -0
@@ -4,6 +4,7 @@
4
4
  __pycache__
5
5
  venv
6
6
  venv-test
7
+ venv-wsl
7
8
 
8
9
  # Python build output
9
10
  build
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.15
3
+ Version: 2.1.17
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
@@ -49,18 +49,19 @@ Dynamic: license-file
49
49
 
50
50
  - [Features](#features)
51
51
  - [Getting Started](#getting-started)
52
- - [Prerequisites](#prerequisites)
53
52
  - [Installation](#installation)
53
+ - [Additional Setup](#additional-setup)
54
54
  - [Usage](#usage)
55
55
  - [Configuration](#configuration)
56
56
  - [Advanced Features](#advanced-features)
57
+ - [FAQ](#faq)
57
58
  - [Contributing](#contributing)
58
59
  - [License](#license)
59
60
 
60
61
  ## Features
61
62
 
62
63
  - ⚡ Rapidly import your Storage Units
63
- - 🔍 Track prices on Steam, Buff163, Youpin898
64
+ - 🔍 Track prices on Steam, Buff163, CSFloat
64
65
  - 📈 View investment price history
65
66
  - 🧾 Export/Import history data
66
67
  - 📤 Discord notifications on updates
@@ -69,19 +70,16 @@ Dynamic: license-file
69
70
 
70
71
  ## Getting Started
71
72
 
72
- ### Prerequisites
73
-
74
- - Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required on Linux)
75
- - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
76
- - Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
77
-
78
73
  ### Installation
79
74
 
80
- #### Option 1: Windows Executable
75
+ #### Method 1: Executable
76
+
77
+ Simply download the program and run it:
81
78
 
82
- - Simply [download the latest executable](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip) and run it.
79
+ - [Windows](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip)
80
+ - [Linux](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-linux.zip)
83
81
 
84
- #### Option 2: Install via Pip
82
+ #### Method 2: Install via Pip
85
83
 
86
84
  1. Install the program:
87
85
 
@@ -95,9 +93,14 @@ Dynamic: license-file
95
93
  cs2tracker
96
94
  ```
97
95
 
96
+ ### Additional Setup
97
+
98
+ - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
99
+ - Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
100
+
98
101
  ## Usage
99
102
 
100
- - Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and EUR.
103
+ - Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and your selected currency.
101
104
  - The generated Excel sheet can be saved by right-clicking and then selecting **Save Sheet**.
102
105
  - Use **Edit Config** to specify the numbers of items owned in the configuration.
103
106
  - Click **Show History** to see a price chart consisting of past calculations.
@@ -121,14 +124,31 @@ This will open the config editor where you can change any setting by double clic
121
124
  - Enable **Proxy Requests** to prevent your requests from being rate limited by the steamcommunity server.
122
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`.
123
126
 
127
+ ## FAQ
128
+
129
+ **Q: Is it safe to login with my Steam account?**
130
+
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
+
133
+
134
+ **Q: Do I have to login with my Steam account?**
135
+
136
+ **A:** No, you can also manually specify the number of items you own in the config editor.
137
+
138
+
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.
142
+
124
143
  ## Contributing
125
144
 
126
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.
127
146
 
128
147
  1. Fork the repository
129
- 2. Create a new branch
148
+ 2. Create a new branch: `git checkout -b feature-name`.
130
149
  3. Make your changes
131
- 4. Submit a PR
150
+ 4. Push your branch: `git push origin feature-name`.
151
+ 5. Submit a PR
132
152
 
133
153
  ## License
134
154
 
@@ -21,18 +21,19 @@
21
21
 
22
22
  - [Features](#features)
23
23
  - [Getting Started](#getting-started)
24
- - [Prerequisites](#prerequisites)
25
24
  - [Installation](#installation)
25
+ - [Additional Setup](#additional-setup)
26
26
  - [Usage](#usage)
27
27
  - [Configuration](#configuration)
28
28
  - [Advanced Features](#advanced-features)
29
+ - [FAQ](#faq)
29
30
  - [Contributing](#contributing)
30
31
  - [License](#license)
31
32
 
32
33
  ## Features
33
34
 
34
35
  - ⚡ Rapidly import your Storage Units
35
- - 🔍 Track prices on Steam, Buff163, Youpin898
36
+ - 🔍 Track prices on Steam, Buff163, CSFloat
36
37
  - 📈 View investment price history
37
38
  - 🧾 Export/Import history data
38
39
  - 📤 Discord notifications on updates
@@ -41,19 +42,16 @@
41
42
 
42
43
  ## Getting Started
43
44
 
44
- ### Prerequisites
45
-
46
- - Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required on Linux)
47
- - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
48
- - Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
49
-
50
45
  ### Installation
51
46
 
52
- #### Option 1: Windows Executable
47
+ #### Method 1: Executable
48
+
49
+ Simply download the program and run it:
53
50
 
54
- - Simply [download the latest executable](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip) and run it.
51
+ - [Windows](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip)
52
+ - [Linux](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-linux.zip)
55
53
 
56
- #### Option 2: Install via Pip
54
+ #### Method 2: Install via Pip
57
55
 
58
56
  1. Install the program:
59
57
 
@@ -67,9 +65,14 @@
67
65
  cs2tracker
68
66
  ```
69
67
 
68
+ ### Additional Setup
69
+
70
+ - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
71
+ - Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
72
+
70
73
  ## Usage
71
74
 
72
- - Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and EUR.
75
+ - Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and your selected currency.
73
76
  - The generated Excel sheet can be saved by right-clicking and then selecting **Save Sheet**.
74
77
  - Use **Edit Config** to specify the numbers of items owned in the configuration.
75
78
  - Click **Show History** to see a price chart consisting of past calculations.
@@ -93,14 +96,31 @@ This will open the config editor where you can change any setting by double clic
93
96
  - Enable **Proxy Requests** to prevent your requests from being rate limited by the steamcommunity server.
94
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`.
95
98
 
99
+ ## FAQ
100
+
101
+ **Q: Is it safe to login with my Steam account?**
102
+
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
+
105
+
106
+ **Q: Do I have to login with my Steam account?**
107
+
108
+ **A:** No, you can also manually specify the number of items you own in the config editor.
109
+
110
+
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.
114
+
96
115
  ## Contributing
97
116
 
98
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.
99
118
 
100
119
  1. Fork the repository
101
- 2. Create a new branch
120
+ 2. Create a new branch: `git checkout -b feature-name`.
102
121
  3. Make your changes
103
- 4. Submit a PR
122
+ 4. Push your branch: `git push origin feature-name`.
123
+ 5. Submit a PR
104
124
 
105
125
  ## License
106
126
 
@@ -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.15'
21
- __version_tuple__ = version_tuple = (2, 1, 15)
20
+ __version__ = version = '2.1.17'
21
+ __version_tuple__ = version_tuple = (2, 1, 17)
@@ -15,6 +15,7 @@ from cs2tracker.logs import PriceLogs
15
15
  from cs2tracker.scraper.background_task import BackgroundTask
16
16
  from cs2tracker.scraper.scraper import Scraper
17
17
  from cs2tracker.util.currency_conversion import CURRENCY_SYMBOLS
18
+ from cs2tracker.util.padded_console import get_console
18
19
  from cs2tracker.util.tkinter import centered, fix_sv_ttk, size_info
19
20
 
20
21
  APPLICATION_NAME = "CS2Tracker"
@@ -31,6 +32,7 @@ PRICE_HISTORY_TITLE = "Price History"
31
32
  PRICE_HISTORY_SIZE = "900x700"
32
33
 
33
34
  config = get_config()
35
+ console = get_console()
34
36
 
35
37
 
36
38
  class Application:
@@ -48,6 +50,7 @@ class Application:
48
50
 
49
51
  fix_sv_ttk(ttk.Style())
50
52
 
53
+ window.focus_force()
51
54
  window.mainloop()
52
55
 
53
56
  def _configure_window(self):
@@ -76,6 +79,10 @@ class MainFrame(ttk.Frame):
76
79
  super().__init__(parent, padding=15)
77
80
  self.parent = parent
78
81
  self.scraper = scraper
82
+
83
+ self.scraper_window = None
84
+ self.config_editor_window = None
85
+ self.price_history_window = None
79
86
  self._add_widgets()
80
87
 
81
88
  def _add_widgets(self):
@@ -181,13 +188,21 @@ class MainFrame(ttk.Frame):
181
188
  """Scrape prices from the configured sources, print the total, and save the
182
189
  results to a file.
183
190
  """
184
- scraper_window = tk.Toplevel(self.parent)
185
- scraper_window.geometry(centered(scraper_window, SCRAPER_WINDOW_SIZE))
186
- scraper_window.minsize(*size_info(SCRAPER_WINDOW_SIZE))
187
- scraper_window.title(SCRAPER_WINDOW_TITLE)
191
+ if self.scraper_window is None or not self.scraper_window.winfo_exists():
192
+ self._open_scraper_window()
193
+ else:
194
+ self.scraper_window.lift()
195
+ self.scraper_window.focus_set()
196
+
197
+ def _open_scraper_window(self):
198
+ """Open a new window with the scraper GUI."""
199
+ self.scraper_window = tk.Toplevel(self.parent)
200
+ self.scraper_window.geometry(centered(self.scraper_window, SCRAPER_WINDOW_SIZE))
201
+ self.scraper_window.minsize(*size_info(SCRAPER_WINDOW_SIZE))
202
+ self.scraper_window.title(SCRAPER_WINDOW_TITLE)
188
203
 
189
204
  run_frame = ScraperFrame(
190
- scraper_window,
205
+ self.scraper_window,
191
206
  self.scraper,
192
207
  sheet_size=SCRAPER_WINDOW_SIZE,
193
208
  dark_theme=self.dark_theme_checkbox_value.get(),
@@ -196,26 +211,42 @@ class MainFrame(ttk.Frame):
196
211
  run_frame.start()
197
212
 
198
213
  def _edit_config(self):
214
+ """Open a new window with a config editor GUI or lift the existing one."""
215
+ if self.config_editor_window is None or not self.config_editor_window.winfo_exists():
216
+ self._open_config_editor()
217
+ else:
218
+ self.config_editor_window.lift()
219
+ self.config_editor_window.focus_set()
220
+
221
+ def _open_config_editor(self):
199
222
  """Open a new window with a config editor GUI."""
200
- config_editor_window = tk.Toplevel(self.parent)
201
- config_editor_window.geometry(centered(config_editor_window, CONFIG_EDITOR_SIZE))
202
- config_editor_window.minsize(*size_info(CONFIG_EDITOR_SIZE))
203
- config_editor_window.title(CONFIG_EDITOR_TITLE)
223
+ self.config_editor_window = tk.Toplevel(self.parent)
224
+ self.config_editor_window.geometry(centered(self.config_editor_window, CONFIG_EDITOR_SIZE))
225
+ self.config_editor_window.minsize(*size_info(CONFIG_EDITOR_SIZE))
226
+ self.config_editor_window.title(CONFIG_EDITOR_TITLE)
204
227
 
205
- editor_frame = ConfigEditorFrame(config_editor_window)
228
+ editor_frame = ConfigEditorFrame(self.config_editor_window)
206
229
  editor_frame.pack(expand=True, fill="both")
207
230
 
208
231
  def _show_history(self):
209
232
  """Show a chart consisting of past calculations."""
233
+ if self.price_history_window is None or not self.price_history_window.winfo_exists():
234
+ self._open_history_window()
235
+ else:
236
+ self.price_history_window.lift()
237
+ self.price_history_window.focus_set()
238
+
239
+ def _open_history_window(self):
240
+ """Open a new window with a price history GUI."""
210
241
  if PriceLogs.empty():
211
242
  return
212
243
 
213
- price_history_window = tk.Toplevel(self.parent)
214
- price_history_window.geometry(centered(price_history_window, PRICE_HISTORY_SIZE))
215
- price_history_window.minsize(*size_info(PRICE_HISTORY_SIZE))
216
- price_history_window.title(PRICE_HISTORY_TITLE)
244
+ self.price_history_window = tk.Toplevel(self.parent)
245
+ self.price_history_window.geometry(centered(self.price_history_window, PRICE_HISTORY_SIZE))
246
+ self.price_history_window.minsize(*size_info(PRICE_HISTORY_SIZE))
247
+ self.price_history_window.title(PRICE_HISTORY_TITLE)
217
248
 
218
- history_frame = PriceHistoryFrame(price_history_window)
249
+ history_frame = PriceHistoryFrame(self.price_history_window)
219
250
  history_frame.pack(expand=True, fill="both")
220
251
 
221
252
  def _export_log_file(self):
@@ -239,8 +270,10 @@ class MainFrame(ttk.Frame):
239
270
  filetypes=[("CSV files", "*.csv")],
240
271
  )
241
272
  if not PriceLogs.validate_file(import_path):
273
+ console.error("Invalid log file format.")
242
274
  return
243
275
  copy(import_path, OUTPUT_FILE)
276
+ console.info("Log file imported successfully.")
244
277
 
245
278
  def _toggle_background_task(self, enabled: bool):
246
279
  """Toggle whether a daily price calculation should run in the background."""
@@ -253,6 +253,8 @@ class ConfigEditorButtonFrame(ttk.Frame):
253
253
  self.editor_frame = editor_frame
254
254
  self.custom_item_dialog = None
255
255
 
256
+ self.custom_item_window = None
257
+ self.steam_inventory_window = None
256
258
  self._add_widgets()
257
259
 
258
260
  def _add_widgets(self):
@@ -285,37 +287,59 @@ class ConfigEditorButtonFrame(ttk.Frame):
285
287
  self.editor_frame.tree.focus_set()
286
288
 
287
289
  def _add_custom_item(self):
290
+ """Open a window to add a new custom item or lift the existing one if it is
291
+ already open.
292
+ """
293
+ if self.custom_item_window is None or not self.custom_item_window.winfo_exists():
294
+ self._open_custom_item_window()
295
+ else:
296
+ self.custom_item_window.lift()
297
+ self.custom_item_window.focus_set()
298
+
299
+ def _open_custom_item_window(self):
288
300
  """Open a window to add a new custom item."""
289
- custom_item_window = tk.Toplevel(self.editor_frame)
290
- custom_item_window.title(ADD_CUSTOM_ITEM_TITLE)
291
- custom_item_window.geometry(centered(custom_item_window, ADD_CUSTOM_ITEM_SIZE))
292
- custom_item_window.minsize(*size_info(ADD_CUSTOM_ITEM_SIZE))
293
- custom_item_window.focus_set()
301
+ self.custom_item_window = tk.Toplevel(self.editor_frame)
302
+ self.custom_item_window.title(ADD_CUSTOM_ITEM_TITLE)
303
+ self.custom_item_window.geometry(centered(self.custom_item_window, ADD_CUSTOM_ITEM_SIZE))
304
+ self.custom_item_window.minsize(*size_info(ADD_CUSTOM_ITEM_SIZE))
305
+ self.custom_item_window.focus_set()
294
306
 
295
307
  def on_close():
296
- custom_item_window.destroy()
308
+ self.custom_item_window.destroy() # type: ignore
297
309
  self.editor_frame.tree.focus_set()
298
310
 
299
- custom_item_window.protocol("WM_DELETE_WINDOW", on_close)
311
+ self.custom_item_window.protocol("WM_DELETE_WINDOW", on_close)
300
312
 
301
- custom_item_frame = CustomItemFrame(custom_item_window, self.editor_frame)
313
+ custom_item_frame = CustomItemFrame(self.custom_item_window, self.editor_frame)
302
314
  custom_item_frame.pack(expand=True, fill="both", padx=15, pady=15)
303
315
 
304
316
  def _import_steam_inventory(self):
317
+ """Open a window to import the user's Steam inventory or lift the existing one
318
+ if it is already open.
319
+ """
320
+ if self.steam_inventory_window is None or not self.steam_inventory_window.winfo_exists():
321
+ self._open_steam_inventory_window()
322
+ else:
323
+ self.steam_inventory_window.lift()
324
+ self.steam_inventory_window.focus_set()
325
+
326
+ def _open_steam_inventory_window(self):
305
327
  """Open a window to import the user's Steam inventory."""
306
- steam_inventory_window = tk.Toplevel(self.editor_frame)
307
- steam_inventory_window.title(IMPORT_INVENTORY_TITLE)
308
- steam_inventory_window.geometry(centered(steam_inventory_window, IMPORT_INVENTORY_SIZE))
309
- steam_inventory_window.minsize(*size_info(IMPORT_INVENTORY_SIZE))
310
- steam_inventory_window.focus_set()
328
+ self.steam_inventory_window = tk.Toplevel(self.editor_frame)
329
+ self.steam_inventory_window.title(IMPORT_INVENTORY_TITLE)
330
+ self.steam_inventory_window.geometry(
331
+ centered(self.steam_inventory_window, IMPORT_INVENTORY_SIZE)
332
+ )
333
+ self.steam_inventory_window.minsize(*size_info(IMPORT_INVENTORY_SIZE))
334
+ self.steam_inventory_window.focus_set()
311
335
 
312
336
  def on_close():
313
- steam_inventory_window.destroy()
337
+ self.steam_inventory_window.destroy() # type: ignore
314
338
  self.editor_frame.tree.focus_set()
315
339
 
316
- steam_inventory_window.protocol("WM_DELETE_WINDOW", on_close)
340
+ self.steam_inventory_window.protocol("WM_DELETE_WINDOW", on_close)
317
341
 
318
- steam_inventory_frame = InventoryImportFrame(steam_inventory_window, self.editor_frame)
342
+ steam_inventory_frame = InventoryImportFrame(self.steam_inventory_window, self.editor_frame)
319
343
  steam_inventory_frame.pack(expand=True, fill="both", padx=15, pady=15)
320
344
 
321
345
 
@@ -1,12 +1,18 @@
1
+ import ctypes
1
2
  import enum
2
3
  import os
3
4
  import sys
5
+ import tkinter as tk
4
6
  from datetime import datetime
5
- from shutil import copy
7
+ from shutil import copy, copytree
6
8
  from subprocess import DEVNULL
9
+ from tkinter import ttk
7
10
 
11
+ import sv_ttk
8
12
  from nodejs import npm
9
13
 
14
+ from cs2tracker.util.tkinter import centered
15
+
10
16
  try:
11
17
  from cs2tracker._version import version # pylint: disable=E0611
12
18
 
@@ -18,46 +24,116 @@ except ImportError:
18
24
  class OSType(enum.Enum):
19
25
  WINDOWS = "Windows"
20
26
  LINUX = "Linux"
27
+ MACOS = "MacOS"
21
28
 
22
29
 
23
- OS = OSType.WINDOWS if sys.platform.startswith("win") else OSType.LINUX
24
- PYTHON_EXECUTABLE = sys.executable
30
+ if sys.platform.startswith("win"):
31
+ OS = OSType.WINDOWS
32
+ elif sys.platform.startswith("linux"):
33
+ OS = OSType.LINUX
34
+ elif sys.platform.startswith("darwin"):
35
+ OS = OSType.MACOS
36
+ else:
37
+ raise NotImplementedError(f"Unsupported OS: {sys.platform}")
25
38
 
26
39
 
40
+ PYTHON_EXECUTABLE = sys.executable
27
41
  RUNNING_IN_EXE = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
28
42
 
29
43
  if RUNNING_IN_EXE:
30
44
  MEIPASS_DIR = sys._MEIPASS # type: ignore pylint: disable=protected-access
31
45
  MODULE_DIR = MEIPASS_DIR
32
46
  PROJECT_DIR = MEIPASS_DIR
33
- APP_DATA_DIR = os.path.join(os.path.expanduser("~"), "AppData", "Local")
34
- DATA_DIR = os.path.join(APP_DATA_DIR, "cs2tracker", "data")
35
- os.makedirs(DATA_DIR, exist_ok=True)
47
+
48
+ if OS == OSType.WINDOWS:
49
+ APP_DATA_DIR = os.path.join(os.path.expanduser("~"), "AppData", "Local")
50
+ elif OS == OSType.LINUX:
51
+ APP_DATA_DIR = os.environ.get(
52
+ "XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share")
53
+ )
54
+ else:
55
+ raise NotImplementedError(f"Unsupported OS: {OS}")
36
56
 
37
57
  CONFIG_FILE_SOURCE = os.path.join(MODULE_DIR, "data", "config.ini")
38
58
  OUTPUT_FILE_SOURCE = os.path.join(MODULE_DIR, "data", "output.csv")
39
- IVENTORY_CONVERT_SCRIPT_SOURCE = os.path.join(MODULE_DIR, "data", "convert_inventory.js")
59
+ INVENTORY_CONVERT_SCRIPT_SOURCE = os.path.join(MODULE_DIR, "data", "convert_inventory.js")
40
60
  INVENTORY_IMPORT_SCRIPT_SOURCE = os.path.join(MODULE_DIR, "data", "get_inventory.js")
61
+ NODE_MODULES_SOURCE = os.path.join(MODULE_DIR, "data", "node_modules")
41
62
 
63
+ DATA_DIR = os.path.join(APP_DATA_DIR, "cs2tracker", "data")
42
64
  CONFIG_FILE = os.path.join(DATA_DIR, "config.ini")
43
65
  CONFIG_FILE_BACKUP = os.path.join(DATA_DIR, "config.ini.bak")
44
66
  OUTPUT_FILE = os.path.join(DATA_DIR, "output.csv")
45
- IVENTORY_CONVERT_SCRIPT = os.path.join(DATA_DIR, "convert_inventory.js")
67
+ INVENTORY_CONVERT_SCRIPT = os.path.join(DATA_DIR, "convert_inventory.js")
46
68
  INVENTORY_IMPORT_SCRIPT = os.path.join(DATA_DIR, "get_inventory.js")
69
+ NODE_MODULES = os.path.join(DATA_DIR, "node_modules")
70
+
71
+ ICON_FILE = os.path.join(PROJECT_DIR, "assets", "icon.png")
72
+ BATCH_FILE = os.path.join(DATA_DIR, "cs2tracker_scraper.bat")
73
+ INVENTORY_IMPORT_FILE = os.path.join(DATA_DIR, "inventory.json")
74
+ INVENTORY_IMPORT_SCRIPT_DEPENDENCIES = [
75
+ "steam-user",
76
+ "globaloffensive",
77
+ "@node-steam/vdf",
78
+ "axios",
79
+ ]
80
+
81
+ def show_temp_popup():
82
+ """Show a temporary popup window while copying initial files."""
83
+ popup = tk.Tk()
84
+ popup.title("Please wait")
85
+ popup.geometry(centered(popup, "300x80"))
86
+ popup.resizable(False, False)
87
+ if OS == OSType.WINDOWS:
88
+ app_id = "cs2tracker.unique.id"
89
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
90
+ icon = tk.PhotoImage(file=ICON_FILE)
91
+ popup.wm_iconphoto(True, icon)
92
+
93
+ label = ttk.Label(popup, text="Setting up the application. Please wait...")
94
+ label.pack(pady=20)
95
+
96
+ sv_ttk.use_dark_theme()
97
+
98
+ popup.update()
99
+ return popup
100
+
101
+ def copy_initial_files_with_popup():
102
+ """Copy initial files to the user data directory with a temporary popup."""
103
+ popup = show_temp_popup()
104
+
105
+ try:
106
+ if not os.path.exists(DATA_DIR):
107
+ os.makedirs(DATA_DIR)
108
+ if not os.path.exists(OUTPUT_FILE):
109
+ copy(OUTPUT_FILE_SOURCE, OUTPUT_FILE)
110
+ if not os.path.exists(CONFIG_FILE):
111
+ copy(CONFIG_FILE_SOURCE, CONFIG_FILE)
112
+ if not os.path.exists(INVENTORY_CONVERT_SCRIPT):
113
+ copy(INVENTORY_CONVERT_SCRIPT_SOURCE, INVENTORY_CONVERT_SCRIPT)
114
+ if not os.path.exists(INVENTORY_IMPORT_SCRIPT):
115
+ copy(INVENTORY_IMPORT_SCRIPT_SOURCE, INVENTORY_IMPORT_SCRIPT)
116
+ if not os.path.exists(NODE_MODULES):
117
+ copytree(NODE_MODULES_SOURCE, NODE_MODULES)
118
+ finally:
119
+ popup.destroy()
120
+
121
+ # pylint: disable=too-many-boolean-expressions
122
+ if (
123
+ not os.path.exists(DATA_DIR)
124
+ or not os.path.exists(OUTPUT_FILE)
125
+ or not os.path.exists(CONFIG_FILE)
126
+ or not os.path.exists(INVENTORY_CONVERT_SCRIPT)
127
+ or not os.path.exists(INVENTORY_IMPORT_SCRIPT)
128
+ or not os.path.exists(NODE_MODULES)
129
+ ):
130
+ copy_initial_files_with_popup()
47
131
 
48
132
  # Always copy the source config into the user data directory as a backup
49
133
  # and overwrite the existing backup if it exists
50
134
  # (This is to ensure that no outdated config backup remains in the user data directory)
51
135
  copy(CONFIG_FILE_SOURCE, CONFIG_FILE_BACKUP)
52
136
 
53
- if not os.path.exists(OUTPUT_FILE):
54
- copy(OUTPUT_FILE_SOURCE, OUTPUT_FILE)
55
- if not os.path.exists(CONFIG_FILE):
56
- copy(CONFIG_FILE_SOURCE, CONFIG_FILE)
57
- if not os.path.exists(IVENTORY_CONVERT_SCRIPT):
58
- copy(IVENTORY_CONVERT_SCRIPT_SOURCE, IVENTORY_CONVERT_SCRIPT)
59
- if not os.path.exists(INVENTORY_IMPORT_SCRIPT):
60
- copy(INVENTORY_IMPORT_SCRIPT_SOURCE, INVENTORY_IMPORT_SCRIPT)
61
137
 
62
138
  else:
63
139
  MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -69,24 +145,25 @@ else:
69
145
  OUTPUT_FILE = os.path.join(DATA_DIR, "output.csv")
70
146
  INVENTORY_CONVERT_SCRIPT = os.path.join(DATA_DIR, "convert_inventory.js")
71
147
  INVENTORY_IMPORT_SCRIPT = os.path.join(DATA_DIR, "get_inventory.js")
148
+ NODE_MODULES = os.path.join(DATA_DIR, "node_modules")
149
+
150
+ ICON_FILE = os.path.join(PROJECT_DIR, "assets", "icon.png")
151
+ BATCH_FILE = os.path.join(DATA_DIR, "cs2tracker_scraper.bat")
152
+ INVENTORY_IMPORT_FILE = os.path.join(DATA_DIR, "inventory.json")
153
+ INVENTORY_IMPORT_SCRIPT_DEPENDENCIES = [
154
+ "steam-user",
155
+ "globaloffensive",
156
+ "@node-steam/vdf",
157
+ "axios",
158
+ ]
72
159
 
73
160
  if not os.path.exists(CONFIG_FILE_BACKUP):
74
161
  copy(CONFIG_FILE, CONFIG_FILE_BACKUP)
75
162
 
76
163
 
77
- ICON_FILE = os.path.join(PROJECT_DIR, "assets", "icon.png")
78
- BATCH_FILE = os.path.join(DATA_DIR, "cs2tracker_scraper.bat")
79
- INVENTORY_IMPORT_FILE = os.path.join(DATA_DIR, "inventory.json")
80
- INVENTORY_IMPORT_SCRIPT_DEPENDENCIES = [
81
- "steam-user",
82
- "globaloffensive",
83
- "@node-steam/vdf",
84
- "axios",
85
- ]
86
-
87
164
  # Ensures that the necessary node modules are installed if a user wants
88
165
  # to import their steam inventory via the cs2tracker/data/get_inventory.js Node.js script.
89
- if not os.path.exists(os.path.join(DATA_DIR, "node_modules")):
166
+ if not os.path.exists(NODE_MODULES):
90
167
  npm.Popen(
91
168
  ["install", "-g", "--prefix", DATA_DIR] + INVENTORY_IMPORT_SCRIPT_DEPENDENCIES,
92
169
  stdout=DEVNULL,
@@ -1,5 +1,5 @@
1
1
  import os
2
- from subprocess import DEVNULL, call
2
+ from subprocess import DEVNULL, STDOUT, CalledProcessError, call, check_output, run
3
3
 
4
4
  from cs2tracker.constants import (
5
5
  BATCH_FILE,
@@ -18,6 +18,18 @@ WIN_BACKGROUND_TASK_CMD = (
18
18
  f"powershell -WindowStyle Hidden -Command \"Start-Process '{BATCH_FILE}' -WindowStyle Hidden\""
19
19
  )
20
20
 
21
+ LINUX_BACKGROUND_TASK_SCHEDULE = "0 12 * * *"
22
+ LINUX_BACKGROUND_TASK_CMD = (
23
+ f"bash -c 'cd {PROJECT_DIR} && {PYTHON_EXECUTABLE} -m cs2tracker --only-scrape'"
24
+ )
25
+ LINUX_BACKGROUND_TASK_CMD_EXE = f"bash -c 'cd {PROJECT_DIR} && {PYTHON_EXECUTABLE} --only-scrape'"
26
+
27
+ if RUNNING_IN_EXE:
28
+ LINUX_CRON_JOB = f"{LINUX_BACKGROUND_TASK_SCHEDULE} {LINUX_BACKGROUND_TASK_CMD_EXE}"
29
+ else:
30
+ LINUX_CRON_JOB = f"{LINUX_BACKGROUND_TASK_SCHEDULE} {LINUX_BACKGROUND_TASK_CMD}"
31
+
32
+
21
33
  console = get_console()
22
34
 
23
35
 
@@ -34,8 +46,17 @@ class BackgroundTask:
34
46
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
35
47
  found = return_code == 0
36
48
  return found
49
+ elif OS == OSType.LINUX:
50
+ try:
51
+ existing_jobs = (
52
+ check_output(["crontab", "-l"], stderr=STDOUT).decode("utf-8").strip()
53
+ )
54
+ except CalledProcessError:
55
+ existing_jobs = ""
56
+
57
+ found = LINUX_CRON_JOB in existing_jobs.splitlines()
58
+ return found
37
59
  else:
38
- # TODO: implement finder for cron jobs
39
60
  return False
40
61
 
41
62
  @classmethod
@@ -83,17 +104,69 @@ class BackgroundTask:
83
104
  ]
84
105
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
85
106
  if return_code == 0:
86
- console.print("[bold green][+] Background task enabled.")
107
+ console.info("Background task enabled.")
87
108
  else:
88
109
  console.error("Failed to enable background task.")
89
110
  else:
90
111
  cmd = ["schtasks", "/delete", "/tn", WIN_BACKGROUND_TASK_NAME, "/f"]
91
112
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
92
113
  if return_code == 0:
93
- console.print("[bold green][-] Background task disabled.")
114
+ console.info("Background task disabled.")
94
115
  else:
95
116
  console.error("Failed to disable background task.")
96
117
 
118
+ @classmethod
119
+ def _toggle_linux(cls, enabled: bool):
120
+ """
121
+ Create or delete a daily background task that runs the scraper on Linux.
122
+
123
+ :param enabled: If True, the task will be created; if False, the task will be
124
+ deleted.
125
+ """
126
+ try:
127
+ existing_jobs = check_output(["crontab", "-l"], stderr=STDOUT).decode("utf-8").strip()
128
+ except CalledProcessError:
129
+ existing_jobs = ""
130
+
131
+ cron_lines = existing_jobs.splitlines()
132
+
133
+ if enabled and LINUX_CRON_JOB not in cron_lines:
134
+ updated_jobs = (
135
+ existing_jobs + "\n" + LINUX_CRON_JOB + "\n"
136
+ if existing_jobs
137
+ else LINUX_CRON_JOB + "\n"
138
+ )
139
+ try:
140
+ run(
141
+ ["crontab", "-"],
142
+ input=updated_jobs.encode("utf-8"),
143
+ stdout=DEVNULL,
144
+ stderr=DEVNULL,
145
+ check=True,
146
+ )
147
+ console.info("Background task enabled.")
148
+ except CalledProcessError:
149
+ console.error("Failed to enable background task.")
150
+
151
+ elif not enabled and LINUX_CRON_JOB in cron_lines:
152
+ updated_jobs = "\n".join(
153
+ line for line in cron_lines if line.strip() != LINUX_CRON_JOB
154
+ ).strip()
155
+ try:
156
+ if updated_jobs:
157
+ run(
158
+ ["crontab", "-"],
159
+ input=(updated_jobs + "\n").encode("utf-8"),
160
+ stdout=DEVNULL,
161
+ stderr=DEVNULL,
162
+ check=True,
163
+ )
164
+ else:
165
+ run(["crontab", "-r"], stdout=DEVNULL, stderr=DEVNULL, check=True)
166
+ console.info("Background task disabled.")
167
+ except CalledProcessError:
168
+ console.error("Failed to disable background task.")
169
+
97
170
  @classmethod
98
171
  def toggle(cls, enabled: bool):
99
172
  """
@@ -104,6 +177,7 @@ class BackgroundTask:
104
177
  """
105
178
  if OS == OSType.WINDOWS:
106
179
  cls._toggle_windows(enabled)
180
+ elif OS == OSType.LINUX:
181
+ cls._toggle_linux(enabled)
107
182
  else:
108
- # TODO: implement toggle for cron jobs
109
183
  pass
@@ -127,7 +127,7 @@ class CSGOTraderParser(BaseParser):
127
127
  CSGOTRADER_PRICE_LIST = "https://prices.csgotrader.app/latest/{}.json"
128
128
  PRICE_INFO = "Owned: {:<10} {:<10}: ${:<10} Total: ${:<10}"
129
129
  NEEDS_TIMEOUT = False
130
- SOURCES = [PriceSource.STEAM, PriceSource.BUFF163, PriceSource.YOUPIN898]
130
+ SOURCES = [PriceSource.STEAM, PriceSource.BUFF163, PriceSource.CSFLOAT]
131
131
 
132
132
  @classmethod
133
133
  def get_item_page_url(cls, item_href, source=PriceSource.STEAM):
@@ -176,6 +176,12 @@ class CSGOTraderParser(BaseParser):
176
176
  raise ValueError(
177
177
  f"CSGOTrader: Could not find recent youpin898 price: {url_decoded_name}"
178
178
  )
179
+ elif source == PriceSource.CSFLOAT:
180
+ price = price_info.get("price")
181
+ if not price:
182
+ raise ValueError(
183
+ f"CSGOTrader: Could not find recent csfloat price: {url_decoded_name}"
184
+ )
179
185
  else:
180
186
  price = price_info.get("starting_at")
181
187
  if not price:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.15
3
+ Version: 2.1.17
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
@@ -49,18 +49,19 @@ Dynamic: license-file
49
49
 
50
50
  - [Features](#features)
51
51
  - [Getting Started](#getting-started)
52
- - [Prerequisites](#prerequisites)
53
52
  - [Installation](#installation)
53
+ - [Additional Setup](#additional-setup)
54
54
  - [Usage](#usage)
55
55
  - [Configuration](#configuration)
56
56
  - [Advanced Features](#advanced-features)
57
+ - [FAQ](#faq)
57
58
  - [Contributing](#contributing)
58
59
  - [License](#license)
59
60
 
60
61
  ## Features
61
62
 
62
63
  - ⚡ Rapidly import your Storage Units
63
- - 🔍 Track prices on Steam, Buff163, Youpin898
64
+ - 🔍 Track prices on Steam, Buff163, CSFloat
64
65
  - 📈 View investment price history
65
66
  - 🧾 Export/Import history data
66
67
  - 📤 Discord notifications on updates
@@ -69,19 +70,16 @@ Dynamic: license-file
69
70
 
70
71
  ## Getting Started
71
72
 
72
- ### Prerequisites
73
-
74
- - Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required on Linux)
75
- - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
76
- - Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
77
-
78
73
  ### Installation
79
74
 
80
- #### Option 1: Windows Executable
75
+ #### Method 1: Executable
76
+
77
+ Simply download the program and run it:
81
78
 
82
- - Simply [download the latest executable](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip) and run it.
79
+ - [Windows](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-windows.zip)
80
+ - [Linux](https://github.com/ashiven/cs2tracker/releases/latest/download/cs2tracker-linux.zip)
83
81
 
84
- #### Option 2: Install via Pip
82
+ #### Method 2: Install via Pip
85
83
 
86
84
  1. Install the program:
87
85
 
@@ -95,9 +93,14 @@ Dynamic: license-file
95
93
  cs2tracker
96
94
  ```
97
95
 
96
+ ### Additional Setup
97
+
98
+ - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
99
+ - Create a [Discord Webhook](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to be notified about recent price updates. (Optional)
100
+
98
101
  ## Usage
99
102
 
100
- - Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and EUR.
103
+ - Click **Run!** to gather the current market prices of your items and calculate the total amount in USD and your selected currency.
101
104
  - The generated Excel sheet can be saved by right-clicking and then selecting **Save Sheet**.
102
105
  - Use **Edit Config** to specify the numbers of items owned in the configuration.
103
106
  - Click **Show History** to see a price chart consisting of past calculations.
@@ -121,14 +124,31 @@ This will open the config editor where you can change any setting by double clic
121
124
  - Enable **Proxy Requests** to prevent your requests from being rate limited by the steamcommunity server.
122
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`.
123
126
 
127
+ ## FAQ
128
+
129
+ **Q: Is it safe to login with my Steam account?**
130
+
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
+
133
+
134
+ **Q: Do I have to login with my Steam account?**
135
+
136
+ **A:** No, you can also manually specify the number of items you own in the config editor.
137
+
138
+
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.
142
+
124
143
  ## Contributing
125
144
 
126
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.
127
146
 
128
147
  1. Fork the repository
129
- 2. Create a new branch
148
+ 2. Create a new branch: `git checkout -b feature-name`.
130
149
  3. Make your changes
131
- 4. Submit a PR
150
+ 4. Push your branch: `git push origin feature-name`.
151
+ 5. Submit a PR
132
152
 
133
153
  ## License
134
154
 
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