cs2tracker 2.1.8__tar.gz → 2.1.9__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 (29) hide show
  1. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/.gitignore +1 -0
  2. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/PKG-INFO +1 -1
  3. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/_version.py +2 -2
  4. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/application.py +19 -2
  5. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/constants.py +32 -54
  6. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/scraper.py +146 -76
  7. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker.egg-info/PKG-INFO +1 -1
  8. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/.flake8 +0 -0
  9. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/.isort.cfg +0 -0
  10. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/.pre-commit-config.yaml +0 -0
  11. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/.pylintrc +0 -0
  12. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/LICENSE.md +0 -0
  13. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/MANIFEST.in +0 -0
  14. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/README.md +0 -0
  15. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/assets/icon.png +0 -0
  16. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/__init__.py +0 -0
  17. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/__main__.py +0 -0
  18. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/data/config.ini +0 -0
  19. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/data/output.csv +0 -0
  20. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/main.py +0 -0
  21. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker/padded_console.py +0 -0
  22. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker.egg-info/SOURCES.txt +0 -0
  23. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker.egg-info/dependency_links.txt +0 -0
  24. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker.egg-info/entry_points.txt +0 -0
  25. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker.egg-info/requires.txt +0 -0
  26. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/cs2tracker.egg-info/top_level.txt +0 -0
  27. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/pyproject.toml +0 -0
  28. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/requirements.txt +0 -0
  29. {cs2tracker-2.1.8 → cs2tracker-2.1.9}/setup.cfg +0 -0
@@ -8,3 +8,4 @@ venv-test
8
8
  cs2tracker.egg-info
9
9
  _version.py
10
10
  cs2tracker_scraper.bat
11
+ config.ini.bak
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.8
3
+ Version: 2.1.9
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
@@ -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.8'
21
- __version_tuple__ = version_tuple = (2, 1, 8)
20
+ __version__ = version = '2.1.9'
21
+ __version_tuple__ = version_tuple = (2, 1, 9)
@@ -1,7 +1,9 @@
1
1
  import ctypes
2
2
  import tkinter as tk
3
+ from shutil import copy
3
4
  from subprocess import Popen
4
5
  from threading import Thread
6
+ from tkinter import messagebox
5
7
  from typing import cast
6
8
 
7
9
  import matplotlib.pyplot as plt
@@ -10,9 +12,11 @@ from matplotlib.dates import DateFormatter
10
12
 
11
13
  from cs2tracker.constants import (
12
14
  CONFIG_FILE,
15
+ CONFIG_FILE_BACKUP,
13
16
  ICON_FILE,
14
17
  OS,
15
18
  OUTPUT_FILE,
19
+ POWERSHELL_COLORIZE_OUTPUT,
16
20
  PYTHON_EXECUTABLE,
17
21
  RUNNING_IN_EXE,
18
22
  TEXT_EDITOR,
@@ -22,7 +26,7 @@ from cs2tracker.scraper import Scraper
22
26
 
23
27
  APPLICATION_NAME = "CS2Tracker"
24
28
 
25
- WINDOW_SIZE = "500x450"
29
+ WINDOW_SIZE = "550x500"
26
30
  BACKGROUND_COLOR = "#1e1e1e"
27
31
  BUTTON_COLOR = "#3c3f41"
28
32
  BUTTON_HOVER_COLOR = "#505354"
@@ -104,6 +108,7 @@ class Application:
104
108
 
105
109
  self._add_button(frame, "Run!", self.scrape_prices)
106
110
  self._add_button(frame, "Edit Config", self._edit_config)
111
+ self._add_button(frame, "Reset Config", self._confirm_reset_config)
107
112
  self._add_button(frame, "Show History (Chart)", self._draw_plot)
108
113
  self._add_button(frame, "Show History (File)", self._edit_log_file)
109
114
 
@@ -158,7 +163,7 @@ class Application:
158
163
 
159
164
  if RUNNING_IN_EXE:
160
165
  # The python executable is set as the executable itself in PyInstaller
161
- scraper_cmd = f"{PYTHON_EXECUTABLE} --only-scrape | Out-Host -Paging"
166
+ scraper_cmd = f"{PYTHON_EXECUTABLE} --only-scrape | {POWERSHELL_COLORIZE_OUTPUT}"
162
167
  else:
163
168
  scraper_cmd = f"{PYTHON_EXECUTABLE} -m cs2tracker --only-scrape"
164
169
 
@@ -202,6 +207,18 @@ class Application:
202
207
  callback=self.scraper.parse_config,
203
208
  )
204
209
 
210
+ def _confirm_reset_config(self):
211
+ confirm = messagebox.askokcancel(
212
+ "Reset Config", "Are you sure you want to reset the config file?"
213
+ )
214
+ if confirm:
215
+ self._reset_config()
216
+
217
+ def _reset_config(self):
218
+ """Reset the configuration file to its default state."""
219
+ copy(CONFIG_FILE_BACKUP, CONFIG_FILE)
220
+ self.scraper.parse_config()
221
+
205
222
  def _draw_plot(self):
206
223
  """Draw a plot of the scraped prices over time."""
207
224
  dates, dollars, euros = self.scraper.read_price_log()
@@ -22,14 +22,6 @@ TEXT_EDITOR = "notepad" if OS == OSType.WINDOWS else "nano"
22
22
  PYTHON_EXECUTABLE = sys.executable
23
23
 
24
24
 
25
- MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
26
- PROJECT_DIR = os.path.dirname(MODULE_DIR)
27
- ICON_FILE = os.path.join(PROJECT_DIR, "assets", "icon.png")
28
- OUTPUT_FILE = os.path.join(MODULE_DIR, "data", "output.csv")
29
- CONFIG_FILE = os.path.join(MODULE_DIR, "data", "config.ini")
30
- BATCH_FILE = os.path.join(MODULE_DIR, "data", "cs2tracker_scraper.bat")
31
-
32
-
33
25
  RUNNING_IN_EXE = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
34
26
 
35
27
  if RUNNING_IN_EXE:
@@ -45,12 +37,31 @@ if RUNNING_IN_EXE:
45
37
  os.makedirs(DATA_DIR, exist_ok=True)
46
38
 
47
39
  CONFIG_FILE = os.path.join(DATA_DIR, "config.ini")
40
+ CONFIG_FILE_BACKUP = os.path.join(DATA_DIR, "config.ini.bak")
48
41
  OUTPUT_FILE = os.path.join(DATA_DIR, "output.csv")
49
42
  BATCH_FILE = os.path.join(DATA_DIR, "cs2tracker_scraper.bat")
50
- if not os.path.exists(CONFIG_FILE):
51
- copy(CONFIG_FILE_SOURCE, CONFIG_FILE)
43
+
44
+ # Always copy the source config into the user data directory as a backup
45
+ # and overwrite the existing backup if it exists
46
+ # (This is to ensure that no outdated config backup remains in the user data directory)
47
+ copy(CONFIG_FILE_SOURCE, CONFIG_FILE_BACKUP)
48
+
52
49
  if not os.path.exists(OUTPUT_FILE):
53
50
  copy(OUTPUT_FILE_SOURCE, OUTPUT_FILE)
51
+ if not os.path.exists(CONFIG_FILE):
52
+ copy(CONFIG_FILE_SOURCE, CONFIG_FILE)
53
+
54
+ else:
55
+ MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
56
+ PROJECT_DIR = os.path.dirname(MODULE_DIR)
57
+ ICON_FILE = os.path.join(PROJECT_DIR, "assets", "icon.png")
58
+ CONFIG_FILE = os.path.join(MODULE_DIR, "data", "config.ini")
59
+ CONFIG_FILE_BACKUP = os.path.join(MODULE_DIR, "data", "config.ini.bak")
60
+ OUTPUT_FILE = os.path.join(MODULE_DIR, "data", "output.csv")
61
+ BATCH_FILE = os.path.join(MODULE_DIR, "data", "cs2tracker_scraper.bat")
62
+
63
+ if not os.path.exists(CONFIG_FILE_BACKUP):
64
+ copy(CONFIG_FILE, CONFIG_FILE_BACKUP)
54
65
 
55
66
 
56
67
  BANNER = """
@@ -68,50 +79,17 @@ AUTHOR_STRING = (
68
79
  )
69
80
 
70
81
 
71
- CASE_PAGES = [
72
- "https://steamcommunity.com/market/search?q=revolution+case",
73
- "https://steamcommunity.com/market/search?q=recoil+case",
74
- "https://steamcommunity.com/market/search?q=dreams+and+nightmares+case",
75
- "https://steamcommunity.com/market/search?q=operation+riptide+case",
76
- "https://steamcommunity.com/market/search?q=snakebite+case",
77
- "https://steamcommunity.com/market/search?q=broken+fang+case",
78
- "https://steamcommunity.com/market/search?q=fracture+case",
79
- "https://steamcommunity.com/market/search?q=chroma+case",
80
- "https://steamcommunity.com/market/search?q=chroma+case",
81
- "https://steamcommunity.com/market/search?q=chroma+case",
82
- "https://steamcommunity.com/market/search?q=clutch+case",
83
- "https://steamcommunity.com/market/search?q=csgo+weapon+case",
84
- "https://steamcommunity.com/market/search?q=csgo+weapon+case",
85
- "https://steamcommunity.com/market/search?q=csgo+weapon+case",
86
- "https://steamcommunity.com/market/search?q=cs20+case",
87
- "https://steamcommunity.com/market/search?q=danger+zone+case",
88
- "https://steamcommunity.com/market/search?q=esports+case",
89
- "https://steamcommunity.com/market/search?q=esports+case",
90
- "https://steamcommunity.com/market/search?q=esports+case",
91
- "https://steamcommunity.com/market/search?q=falchion+case",
92
- "https://steamcommunity.com/market/search?q=gamma+case",
93
- "https://steamcommunity.com/market/search?q=gamma+case",
94
- "https://steamcommunity.com/market/search?q=glove+case",
95
- "https://steamcommunity.com/market/search?q=horizon+case",
96
- "https://steamcommunity.com/market/search?q=huntsman+weapon+case",
97
- "https://steamcommunity.com/market/search?q=operation+bravo+case",
98
- "https://steamcommunity.com/market/search?q=operation+breakout+case",
99
- "https://steamcommunity.com/market/search?q=operation+hydra+case",
100
- "https://steamcommunity.com/market/search?q=operation+phoenix+case",
101
- "https://steamcommunity.com/market/search?q=operation+vanguard+case",
102
- "https://steamcommunity.com/market/search?q=operation+wildfire+case",
103
- "https://steamcommunity.com/market/search?q=prisma+case",
104
- "https://steamcommunity.com/market/search?q=prisma+case",
105
- "https://steamcommunity.com/market/search?q=revolver+case",
106
- "https://steamcommunity.com/market/search?q=shadow+case",
107
- "https://steamcommunity.com/market/search?q=shattered+web+case",
108
- "https://steamcommunity.com/market/search?q=spectrum+case",
109
- "https://steamcommunity.com/market/search?q=spectrum+case",
110
- "https://steamcommunity.com/market/search?q=winter+offensive+case",
111
- "https://steamcommunity.com/market/search?q=kilowatt+case",
112
- "https://steamcommunity.com/market/search?q=gallery+case",
113
- "https://steamcommunity.com/market/search?q=fever+case",
114
- ]
82
+ POWERSHELL_COLORIZE_OUTPUT = (
83
+ "%{ "
84
+ "if($_ -match 'Version|\\\\|_') { Write-Host $_ -ForegroundColor yellow } "
85
+ "elseif($_ -match 'USD|EUR|^[-|\\s]+$') { Write-Host $_ -ForegroundColor green } "
86
+ "elseif($_ -match 'Case|Capsule|-[A-Za-z0-9]') { Write-Host $_ -ForegroundColor magenta } "
87
+ "elseif($_ -match '\\[!\\]') { Write-Host $_ -ForegroundColor red } "
88
+ "elseif($_ -match 'Legends|Challengers|Contenders|Champions|Finalists') { Write-Host $_ -ForegroundColor blue } "
89
+ "else { Write-Host $_ } "
90
+ "}"
91
+ )
92
+
115
93
 
116
94
  CASE_HREFS = [
117
95
  "https://steamcommunity.com/market/listings/730/Revolution%20Case",
@@ -4,7 +4,6 @@ import time
4
4
  from configparser import ConfigParser
5
5
  from datetime import datetime
6
6
  from subprocess import DEVNULL, call
7
- from urllib.parse import unquote
8
7
 
9
8
  from bs4 import BeautifulSoup
10
9
  from bs4.element import Tag
@@ -19,7 +18,6 @@ from cs2tracker.constants import (
19
18
  BATCH_FILE,
20
19
  CAPSULE_INFO,
21
20
  CASE_HREFS,
22
- CASE_PAGES,
23
21
  CONFIG_FILE,
24
22
  OS,
25
23
  OUTPUT_FILE,
@@ -59,10 +57,75 @@ class Scraper:
59
57
  self.usd_total = 0
60
58
  self.eur_total = 0
61
59
 
60
+ def _validate_config_sections(self):
61
+ """Validate that the configuration file has all required sections."""
62
+ if not self.config.has_section("User Settings"):
63
+ raise ValueError("Missing 'User Settings' section in the configuration file.")
64
+ if not self.config.has_section("App Settings"):
65
+ raise ValueError("Missing 'App Settings' section in the configuration file.")
66
+ if not self.config.has_section("Custom Items"):
67
+ raise ValueError("Missing 'Custom Items' section in the configuration file.")
68
+ if not self.config.has_section("Cases"):
69
+ raise ValueError("Missing 'Cases' section in the configuration file.")
70
+ for capsule_section in CAPSULE_INFO:
71
+ if not self.config.has_section(capsule_section):
72
+ raise ValueError(f"Missing '{capsule_section}' section in the configuration file.")
73
+
74
+ def _validate_config_values(self):
75
+ """Validate that the configuration file has valid values for all sections."""
76
+ try:
77
+ for custom_item_name, custom_item_owned in self.config.items("Custom Items"):
78
+ if " " not in custom_item_owned:
79
+ raise ValueError(
80
+ f"Invalid custom item format (<item_name> = <owned_count> <item_url>): {custom_item_name} = {custom_item_owned}"
81
+ )
82
+ owned, _ = custom_item_owned.split(" ", 1)
83
+ if int(owned) < 0:
84
+ raise ValueError(
85
+ f"Invalid value in 'Custom Items' section: {custom_item_name} = {custom_item_owned}"
86
+ )
87
+ for case_name, case_owned in self.config.items("Cases"):
88
+ if int(case_owned) < 0:
89
+ raise ValueError(
90
+ f"Invalid value in 'Cases' section: {case_name} = {case_owned}"
91
+ )
92
+ for capsule_section in CAPSULE_INFO:
93
+ for capsule_name, capsule_owned in self.config.items(capsule_section):
94
+ if int(capsule_owned) < 0:
95
+ raise ValueError(
96
+ f"Invalid value in '{capsule_section}' section: {capsule_name} = {capsule_owned}"
97
+ )
98
+ except ValueError as error:
99
+ if "Invalid " in str(error):
100
+ raise
101
+ raise ValueError("Invalid value type. All values must be integers.") from error
102
+
103
+ def _validate_config(self):
104
+ """
105
+ Validate the configuration file to ensure all required sections exist with the
106
+ right values.
107
+
108
+ :raises ValueError: If any required section is missing or if any value is
109
+ invalid.
110
+ """
111
+ self._validate_config_sections()
112
+ self._validate_config_values()
113
+
62
114
  def parse_config(self):
63
- """Parse the configuration file to read settings and user-owned items."""
115
+ """
116
+ Parse the configuration file to read settings and user-owned items.
117
+
118
+ Sets self.valid_config to True if the configuration is valid, and False if it is
119
+ not.
120
+ """
64
121
  self.config = ConfigParser(interpolation=None)
65
122
  self.config.read(CONFIG_FILE)
123
+ try:
124
+ self._validate_config()
125
+ self.valid_config = True
126
+ except ValueError as error:
127
+ self.console.print(f"[bold red][!] Configuration error: {error}")
128
+ self.valid_config = False
66
129
 
67
130
  def _start_session(self):
68
131
  """Start a requests session with custom headers and retry logic."""
@@ -80,29 +143,15 @@ class Scraper:
80
143
  """Scrape prices for capsules and cases, calculate totals in USD and EUR, and
81
144
  print/save the results.
82
145
  """
83
- capsule_usd_total = 0
84
- try:
85
- capsule_usd_total = self.scrape_capsule_section_prices()
86
- except (RequestException, AttributeError, RetryError, ValueError):
146
+ if not self.valid_config:
87
147
  self.console.print(
88
- "[bold red][!] Failed to scrape capsule prices. (Consider using proxies to prevent rate limiting)\n"
148
+ "[bold red][!] Invalid configuration. Please fix the config file before running."
89
149
  )
150
+ return
90
151
 
91
- case_usd_total = 0
92
- try:
93
- case_usd_total = self._scrape_case_prices()
94
- except (RequestException, AttributeError, RetryError, ValueError):
95
- self.console.print(
96
- "[bold red][!] Failed to scrape case prices. (Consider using proxies to prevent rate limiting)\n"
97
- )
98
-
99
- custom_item_usd_total = 0
100
- try:
101
- custom_item_usd_total = self._scrape_custom_item_prices()
102
- except (RequestException, AttributeError, RetryError, ValueError):
103
- self.console.print(
104
- "[bold red][!] Failed to scrape custom item prices. (Consider using proxies to prevent rate limiting)\n"
105
- )
152
+ capsule_usd_total = self._scrape_capsule_section_prices()
153
+ case_usd_total = self._scrape_case_prices()
154
+ custom_item_usd_total = self._scrape_custom_item_prices()
106
155
 
107
156
  self.usd_total += capsule_usd_total
108
157
  self.usd_total += case_usd_total
@@ -283,6 +332,7 @@ class Scraper:
283
332
  use_proxy = self.config.getboolean("App Settings", "use_proxy", fallback=False)
284
333
  api_key = self.config.get("User Settings", "api_key", fallback=None)
285
334
  api_key = None if api_key in ("None", "") else api_key
335
+
286
336
  if use_proxy and api_key:
287
337
  page = self.session.get(
288
338
  url=url,
@@ -296,8 +346,9 @@ class Scraper:
296
346
  page = self.session.get(url)
297
347
 
298
348
  if not page.ok or not page.content:
299
- status = page.status_code
300
- self.console.print(f"[bold red][!] Failed to load page ({status}). Retrying...\n")
349
+ self.console.print(
350
+ f"[bold red][!] Failed to load page ({page.status_code}). Retrying...\n"
351
+ )
301
352
  raise RequestException(f"Failed to load page: {url}")
302
353
 
303
354
  return page
@@ -343,23 +394,32 @@ class Scraper:
343
394
  self.console.print(f"[bold magenta]{capsule_title}\n")
344
395
 
345
396
  capsule_usd_total = 0
346
- capsule_page = self._get_page(capsule_info["page"])
347
- for capsule_name, capsule_href in zip(capsule_info["names"], capsule_info["items"]):
348
- config_capsule_name = capsule_name.replace(" ", "_")
349
- owned = self.config.getint(capsule_section, config_capsule_name, fallback=0)
350
- if owned == 0:
351
- continue
352
-
353
- price_usd = self._parse_item_price(capsule_page, capsule_href)
354
- price_usd_owned = round(float(owned * price_usd), 2)
355
-
356
- self.console.print(f"[bold deep_sky_blue4]{capsule_name}")
357
- self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
358
- capsule_usd_total += price_usd_owned
397
+ try:
398
+ capsule_page = self._get_page(capsule_info["page"])
399
+ for capsule_name, capsule_href in zip(capsule_info["names"], capsule_info["items"]):
400
+ config_capsule_name = capsule_name.replace(" ", "_")
401
+ owned = self.config.getint(capsule_section, config_capsule_name, fallback=0)
402
+ if owned == 0:
403
+ continue
404
+
405
+ price_usd = self._parse_item_price(capsule_page, capsule_href)
406
+ price_usd_owned = round(float(owned * price_usd), 2)
407
+
408
+ self.console.print(f"[bold deep_sky_blue4]{capsule_name}")
409
+ self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
410
+ capsule_usd_total += price_usd_owned
411
+ except (RetryError, ValueError):
412
+ self.console.print(
413
+ "[bold red][!] Failed to scrape capsule prices. (Consider using proxies to prevent rate limiting)\n"
414
+ )
415
+ except Exception as error:
416
+ self.console.print(
417
+ f"[bold red][!] An unexpected error occurred while scraping capsule prices: {error}\n"
418
+ )
359
419
 
360
420
  return capsule_usd_total
361
421
 
362
- def scrape_capsule_section_prices(self):
422
+ def _scrape_capsule_section_prices(self):
363
423
  """Scrape prices for all capsule sections defined in the configuration."""
364
424
  capsule_usd_total = 0
365
425
  for capsule_section, capsule_info in CAPSULE_INFO.items():
@@ -369,6 +429,19 @@ class Scraper:
369
429
 
370
430
  return capsule_usd_total
371
431
 
432
+ def _market_page_from_href(self, item_href):
433
+ """
434
+ Convert an href of a Steam Community Market item to a market page URL.
435
+
436
+ :param item_href: The href of the item listing, typically ending with the item's
437
+ name.
438
+ :return: A URL string for the Steam Community Market page of the item.
439
+ """
440
+ url_encoded_name = item_href.split("/")[-1]
441
+ page_url = f"https://steamcommunity.com/market/search?q={url_encoded_name}"
442
+
443
+ return page_url
444
+
372
445
  def _scrape_case_prices(self):
373
446
  """
374
447
  Scrape prices for all cases defined in the configuration.
@@ -385,34 +458,28 @@ class Scraper:
385
458
  case_title = case_name.center(MAX_LINE_LEN, SEPARATOR)
386
459
  self.console.print(f"[bold magenta]{case_title}\n")
387
460
 
388
- case_page = self._get_page(CASE_PAGES[case_index])
389
- price_usd = self._parse_item_price(case_page, CASE_HREFS[case_index])
390
- price_usd_owned = round(float(int(owned) * price_usd), 2)
461
+ try:
462
+ case_page_url = self._market_page_from_href(CASE_HREFS[case_index])
463
+ case_page = self._get_page(case_page_url)
464
+ price_usd = self._parse_item_price(case_page, CASE_HREFS[case_index])
465
+ price_usd_owned = round(float(int(owned) * price_usd), 2)
391
466
 
392
- self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
393
- case_usd_total += price_usd_owned
467
+ self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
468
+ case_usd_total += price_usd_owned
394
469
 
395
- if not self.config.getboolean("App Settings", "use_proxy", fallback=False):
396
- time.sleep(1)
470
+ if not self.config.getboolean("App Settings", "use_proxy", fallback=False):
471
+ time.sleep(1)
472
+ except (RetryError, ValueError):
473
+ self.console.print(
474
+ "[bold red][!] Failed to scrape case prices. (Consider using proxies to prevent rate limiting)\n"
475
+ )
476
+ except Exception as error:
477
+ self.console.print(
478
+ f"[bold red][!] An unexpected error occurred while scraping case prices: {error}\n"
479
+ )
397
480
 
398
481
  return case_usd_total
399
482
 
400
- def _market_page_from_href(self, item_href):
401
- """
402
- Convert an href of a Steam Community Market item to a market page URL. This is
403
- done by decoding the URL-encoded item name and formatting it into a search URL.
404
-
405
- :param item_href: The href of the item listing, typically ending with the item's
406
- name.
407
- :return: A URL string for the Steam Community Market page of the item.
408
- """
409
- url_encoded_name = item_href.split("/")[-1]
410
- decoded_name = unquote(url_encoded_name)
411
- decoded_name_query = decoded_name.lower().replace(" ", "+")
412
- page_url = f"https://steamcommunity.com/market/search?q={decoded_name_query}"
413
-
414
- return page_url
415
-
416
483
  def _scrape_custom_item_prices(self):
417
484
  """
418
485
  Scrape prices for custom items defined in the configuration.
@@ -422,12 +489,6 @@ class Scraper:
422
489
  """
423
490
  custom_item_usd_total = 0
424
491
  for config_custom_item_name, owned_and_href in self.config.items("Custom Items"):
425
- if " " not in owned_and_href:
426
- self.console.print(
427
- "[bold red][!] Invalid custom item format (<item_name> = <owned_count> <item_url>)\n"
428
- )
429
- continue
430
-
431
492
  owned, custom_item_href = owned_and_href.split(" ", 1)
432
493
  if int(owned) == 0:
433
494
  continue
@@ -436,16 +497,25 @@ class Scraper:
436
497
  custom_item_title = custom_item_name.center(MAX_LINE_LEN, SEPARATOR)
437
498
  self.console.print(f"[bold magenta]{custom_item_title}\n")
438
499
 
439
- custom_item_page_url = self._market_page_from_href(custom_item_href)
440
- custom_item_page = self._get_page(custom_item_page_url)
441
- price_usd = self._parse_item_price(custom_item_page, custom_item_href)
442
- price_usd_owned = round(float(int(owned) * price_usd), 2)
500
+ try:
501
+ custom_item_page_url = self._market_page_from_href(custom_item_href)
502
+ custom_item_page = self._get_page(custom_item_page_url)
503
+ price_usd = self._parse_item_price(custom_item_page, custom_item_href)
504
+ price_usd_owned = round(float(int(owned) * price_usd), 2)
443
505
 
444
- self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
445
- custom_item_usd_total += price_usd_owned
506
+ self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
507
+ custom_item_usd_total += price_usd_owned
446
508
 
447
- if not self.config.getboolean("App Settings", "use_proxy", fallback=False):
448
- time.sleep(1)
509
+ if not self.config.getboolean("App Settings", "use_proxy", fallback=False):
510
+ time.sleep(1)
511
+ except (RetryError, ValueError):
512
+ self.console.print(
513
+ "[bold red][!] Failed to scrape custom item prices. (Consider using proxies to prevent rate limiting)\n"
514
+ )
515
+ except Exception as error:
516
+ self.console.print(
517
+ f"[bold red][!] An unexpected error occurred while scraping custom item prices: {error}\n"
518
+ )
449
519
 
450
520
  return custom_item_usd_total
451
521
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.8
3
+ Version: 2.1.9
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
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
File without changes