cs2tracker 2.1.2__py3-none-any.whl → 2.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

cs2tracker/_version.py CHANGED
@@ -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.2'
21
- __version_tuple__ = version_tuple = (2, 1, 2)
20
+ __version__ = version = '2.1.4'
21
+ __version_tuple__ = version_tuple = (2, 1, 4)
cs2tracker/application.py CHANGED
@@ -28,31 +28,43 @@ class Application:
28
28
  """
29
29
  window = tk.Tk()
30
30
  window.title("CS2Tracker")
31
- window.geometry("400x400")
31
+ window.geometry("450x450")
32
32
 
33
33
  label = tk.Label(window, text="Welcome to CS2Tracker!")
34
- label.grid(column=0, row=0, pady=50, sticky="NSEW")
35
-
36
34
  run_button = tk.Button(window, text="Run!", command=self._scrape_prices)
37
35
  edit_button = tk.Button(window, text="Edit Config", command=self._edit_config)
38
36
  plot_button = tk.Button(window, text="Show History (Chart)", command=self._draw_plot)
39
- plotfile_button = tk.Button(window, text="Show History (File)", command=self._edit_log_file)
40
-
37
+ plot_file_button = tk.Button(
38
+ window, text="Show History (File)", command=self._edit_log_file
39
+ )
40
+ background_checkbox_value = tk.BooleanVar(value=self.scraper.identify_background_task())
41
+ background_checkbox = tk.Checkbutton(
42
+ window,
43
+ text="Daily Background Calculation",
44
+ command=lambda: self._toggle_background_task(background_checkbox_value.get()),
45
+ variable=background_checkbox_value,
46
+ )
47
+
48
+ label.grid(row=0, column=0, pady=50, sticky="NSEW")
41
49
  run_button.grid(row=1, column=0, pady=10, sticky="NSEW")
42
50
  edit_button.grid(row=2, column=0, pady=10, sticky="NSEW")
43
51
  plot_button.grid(row=3, column=0, pady=10, sticky="NSEW")
44
- plotfile_button.grid(row=4, column=0, pady=10, sticky="NSEW")
52
+ plot_file_button.grid(row=4, column=0, pady=10, sticky="NSEW")
53
+ background_checkbox.grid(row=5, column=0, pady=10, sticky="NS")
45
54
 
46
55
  window.grid_columnconfigure(0, weight=1)
47
56
  window.grid_rowconfigure(1, weight=1)
48
57
  window.grid_rowconfigure(2, weight=1)
49
58
  window.grid_rowconfigure(3, weight=1)
50
59
  window.grid_rowconfigure(4, weight=1)
60
+ window.grid_rowconfigure(5, weight=1)
61
+
51
62
  label.grid_configure(sticky="NSEW")
52
63
  run_button.grid_configure(sticky="NSEW")
53
64
  edit_button.grid_configure(sticky="NSEW")
54
65
  plot_button.grid_configure(sticky="NSEW")
55
- plotfile_button.grid_configure(sticky="NSEW")
66
+ plot_file_button.grid_configure(sticky="NSEW")
67
+ background_checkbox.grid_configure(sticky="NSEW")
56
68
 
57
69
  return window
58
70
 
@@ -80,8 +92,8 @@ class Application:
80
92
  ax.set_ylabel("Price")
81
93
  ax.legend()
82
94
 
83
- date_form = DateFormatter("%d-%m-%Y")
84
- ax.xaxis.set_major_formatter(date_form)
95
+ date_formatter = DateFormatter("%d-%m-%Y")
96
+ ax.xaxis.set_major_formatter(date_formatter)
85
97
  fig.autofmt_xdate()
86
98
 
87
99
  plt.show()
@@ -91,3 +103,7 @@ class Application:
91
103
  if not os.path.isfile(OUTPUT_FILE):
92
104
  open(OUTPUT_FILE, "w", encoding="utf-8").close()
93
105
  subprocess.call([TEXT_EDITOR, OUTPUT_FILE])
106
+
107
+ def _toggle_background_task(self, enabled: bool):
108
+ """Toggle whether a daily price calculation should run in the background."""
109
+ self.scraper.toggle_background_task(enabled)
cs2tracker/constants.py CHANGED
@@ -1,10 +1,13 @@
1
1
  import os
2
2
  import sys
3
3
 
4
- TEXT_EDITOR = "notepad" if sys.platform == "win32" else "nano"
5
- BASE_DIR = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
6
- OUTPUT_FILE = f"{BASE_DIR}/data/output.csv"
7
- CONFIG_FILE = f"{BASE_DIR}/data/config.ini"
4
+ TEXT_EDITOR = "notepad" if sys.platform.startswith("win") else "nano"
5
+ PYTHON_EXECUTABLE = sys.executable
6
+ MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
7
+ PROJECT_DIR = os.path.dirname(MODULE_DIR)
8
+ OUTPUT_FILE = os.path.join(MODULE_DIR, "data", "output.csv")
9
+ CONFIG_FILE = os.path.join(MODULE_DIR, "data", "config.ini")
10
+ BATCH_FILE = os.path.join(MODULE_DIR, "data", "cs2tracker_scraper.bat")
8
11
 
9
12
 
10
13
  RMR_CAPSULES = {
@@ -145,7 +148,6 @@ SHANGHAI_CAPSULES = {
145
148
  ],
146
149
  }
147
150
 
148
-
149
151
  AUSTIN_CAPSULES = {
150
152
  "page": "https://steamcommunity.com/market/search?q=austin+capsule",
151
153
  "items": [
@@ -1,3 +1,7 @@
1
+ [Settings]
2
+ Use_Proxy = False
3
+ API_Key = None
4
+
1
5
  [2020 RMR Sticker Capsule]
2
6
  RMR_Challengers = 0
3
7
  RMR_Legends = 0
@@ -107,7 +111,3 @@ Winter_Offensive_Case = 0
107
111
  Kilowatt_Case = 0
108
112
  Gallery_Case = 0
109
113
  Fever_Case = 0
110
-
111
- [Settings]
112
- Use_Proxy = False
113
- API_Key = None
cs2tracker/main.py CHANGED
@@ -15,7 +15,7 @@ def main():
15
15
  application.
16
16
  """
17
17
 
18
- ## disable warnings for proxy requests
18
+ # Disable warnings for proxy requests
19
19
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20
20
 
21
21
  console = Console()
cs2tracker/scraper.py CHANGED
@@ -1,8 +1,10 @@
1
1
  import csv
2
2
  import os
3
+ import sys
3
4
  import time
4
5
  from configparser import ConfigParser
5
6
  from datetime import datetime
7
+ from subprocess import DEVNULL, call
6
8
 
7
9
  from bs4 import BeautifulSoup
8
10
  from bs4.element import Tag
@@ -13,17 +15,27 @@ from rich.console import Console
13
15
  from tenacity import RetryError, retry, stop_after_attempt
14
16
 
15
17
  from cs2tracker.constants import (
18
+ BATCH_FILE,
16
19
  CAPSULE_INFO,
17
20
  CASE_HREFS,
18
21
  CASE_PAGES,
19
22
  CONFIG_FILE,
20
23
  OUTPUT_FILE,
24
+ PROJECT_DIR,
25
+ PYTHON_EXECUTABLE,
21
26
  )
22
27
 
23
28
  MAX_LINE_LEN = 72
24
29
  SEPARATOR = "-"
25
30
  PRICE_INFO = "Owned: {} Steam market price: ${} Total: ${}\n"
26
31
 
32
+ WIN_BACKGROUND_TASK_NAME = "CS2Tracker Daily Calculation"
33
+ WIN_BACKGROUND_TASK_SCHEDULE = "DAILY"
34
+ WIN_BACKGROUND_TASK_TIME = "12:00"
35
+ WIN_BACKGROUND_TASK_CMD = (
36
+ f"powershell -WindowStyle Hidden -Command \"Start-Process '{BATCH_FILE}' -WindowStyle Hidden\""
37
+ )
38
+
27
39
 
28
40
  class Scraper:
29
41
  def __init__(self):
@@ -61,7 +73,7 @@ class Scraper:
61
73
  capsule_usd_total = self.scrape_capsule_section_prices()
62
74
  except (RequestException, AttributeError, RetryError, ValueError):
63
75
  self.console.print(
64
- "[bold red]Failed to scrape capsule prices. (Consider using proxies to prevent rate limiting)\n"
76
+ "[bold red][!] Failed to scrape capsule prices. (Consider using proxies to prevent rate limiting)\n"
65
77
  )
66
78
 
67
79
  case_usd_total = 0
@@ -69,7 +81,7 @@ class Scraper:
69
81
  case_usd_total = self._scrape_case_prices()
70
82
  except (RequestException, AttributeError, RetryError, ValueError):
71
83
  self.console.print(
72
- "[bold red]Failed to scrape case prices. (Consider using proxies to prevent rate limiting)\n"
84
+ "[bold red][!] Failed to scrape case prices. (Consider using proxies to prevent rate limiting)\n"
73
85
  )
74
86
 
75
87
  self.usd_total += capsule_usd_total
@@ -79,7 +91,7 @@ class Scraper:
79
91
  self._print_total()
80
92
  self._save_price_log()
81
93
 
82
- # reset totals for next run
94
+ # Reset totals for next run
83
95
  self.usd_total, self.eur_total = 0, 0
84
96
 
85
97
  def _print_total(self):
@@ -109,13 +121,9 @@ class Scraper:
109
121
 
110
122
  with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
111
123
  price_logs_reader = csv.reader(price_logs)
112
- last_row = None
124
+ last_log_date = ""
113
125
  for row in price_logs_reader:
114
- last_row = row
115
- if last_row:
116
- last_log_date = last_row[0][:10]
117
- else:
118
- last_log_date = ""
126
+ last_log_date, _ = row
119
127
 
120
128
  today = datetime.now().strftime("%Y-%m-%d")
121
129
  if last_log_date != today:
@@ -135,7 +143,7 @@ class Scraper:
135
143
  open(OUTPUT_FILE, "w", encoding="utf-8").close()
136
144
 
137
145
  dates, dollars, euros = [], [], []
138
- with open(OUTPUT_FILE, "r", newline="", encoding="utf-8") as price_logs:
146
+ with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
139
147
  price_logs_reader = csv.reader(price_logs)
140
148
  for row in price_logs_reader:
141
149
  date, price_with_currency = row
@@ -217,18 +225,15 @@ class Scraper:
217
225
  console.
218
226
 
219
227
  :param capsule_section: The section name in the config for the capsule.
220
- :param capsule_info: A dictionary containing information about the capsule
221
- section,
228
+ :param capsule_info: A dictionary containing information about the capsule page,
229
+ hrefs, and names.
222
230
  """
223
231
  capsule_title = capsule_section.center(MAX_LINE_LEN, SEPARATOR)
224
232
  self.console.print(f"[bold magenta]{capsule_title}")
225
233
 
226
- capsule_price_total = 0
227
- capsule_page = capsule_info["page"]
228
- capsule_names = capsule_info["names"]
229
- capsule_hrefs = capsule_info["items"]
230
- capsule_page = self._get_page(capsule_page)
231
- for capsule_name, capsule_href in zip(capsule_names, capsule_hrefs):
234
+ capsule_usd_total = 0
235
+ capsule_page = self._get_page(capsule_info["page"])
236
+ for capsule_name, capsule_href in zip(capsule_info["names"], capsule_info["items"]):
232
237
  config_capsule_name = capsule_name.replace(" ", "_")
233
238
  owned = self.config.getint(capsule_section, config_capsule_name, fallback=0)
234
239
  if owned == 0:
@@ -239,15 +244,15 @@ class Scraper:
239
244
 
240
245
  self.console.print(f"[bold deep_sky_blue4]{capsule_name}")
241
246
  self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
242
- capsule_price_total += price_usd_owned
247
+ capsule_usd_total += price_usd_owned
243
248
 
244
- return capsule_price_total
249
+ return capsule_usd_total
245
250
 
246
251
  def scrape_capsule_section_prices(self):
247
252
  """Scrape prices for all capsule sections defined in the configuration."""
248
253
  capsule_usd_total = 0
249
254
  for capsule_section, capsule_info in CAPSULE_INFO.items():
250
- # only scrape capsule sections where the user owns at least one item
255
+ # Only scrape capsule sections where the user owns at least one item
251
256
  if any(int(owned) > 0 for _, owned in self.config.items(capsule_section)):
252
257
  capsule_usd_total += self._scrape_capsule_prices(capsule_section, capsule_info)
253
258
 
@@ -283,7 +288,7 @@ class Scraper:
283
288
  For each case, it prints the case name, owned count, price per item, and total
284
289
  price for owned items.
285
290
  """
286
- case_price_total = 0
291
+ case_usd_total = 0
287
292
  for case_index, (config_case_name, owned) in enumerate(self.config.items("Cases")):
288
293
  if int(owned) == 0:
289
294
  continue
@@ -297,9 +302,93 @@ class Scraper:
297
302
  price_usd_owned = round(float(int(owned) * price_usd), 2)
298
303
 
299
304
  self.console.print(PRICE_INFO.format(owned, price_usd, price_usd_owned))
300
- case_price_total += price_usd_owned
305
+ case_usd_total += price_usd_owned
301
306
 
302
307
  if not self.config.getboolean("Settings", "Use_Proxy", fallback=False):
303
308
  time.sleep(1)
304
309
 
305
- return case_price_total
310
+ return case_usd_total
311
+
312
+ def identify_background_task(self):
313
+ """
314
+ Search the OS for a daily background task that runs the scraper.
315
+
316
+ :return: True if a background task is found, False otherwise.
317
+ """
318
+ if sys.platform.startswith("win"):
319
+ cmd = ["schtasks", "/query", "/tn", WIN_BACKGROUND_TASK_NAME]
320
+ return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
321
+ found = return_code == 0
322
+ return found
323
+ else:
324
+ # TODO: implement finder for cron jobs
325
+ return False
326
+
327
+ def _toggle_task_batch_file(self, enabled: bool):
328
+ """
329
+ Create or delete a batch file that runs the scraper.
330
+
331
+ :param enabled: If True, the batch file will be created; if False, the batch
332
+ file will be deleted.
333
+ """
334
+ if enabled:
335
+ with open(BATCH_FILE, "w", encoding="utf-8") as batch_file:
336
+ batch_file.write(f"cd {PROJECT_DIR}\n")
337
+ batch_file.write(f"{PYTHON_EXECUTABLE} -m cs2tracker.scraper\n")
338
+ else:
339
+ if os.path.exists(BATCH_FILE):
340
+ os.remove(BATCH_FILE)
341
+
342
+ def _toggle_background_task_windows(self, enabled: bool):
343
+ """
344
+ Create or delete a daily background task that runs the scraper on Windows.
345
+
346
+ :param enabled: If True, the task will be created; if False, the task will be
347
+ deleted.
348
+ """
349
+ self._toggle_task_batch_file(enabled)
350
+ if enabled:
351
+ cmd = [
352
+ "schtasks",
353
+ "/create",
354
+ "/tn",
355
+ WIN_BACKGROUND_TASK_NAME,
356
+ "/tr",
357
+ WIN_BACKGROUND_TASK_CMD,
358
+ "/sc",
359
+ WIN_BACKGROUND_TASK_SCHEDULE,
360
+ "/st",
361
+ WIN_BACKGROUND_TASK_TIME,
362
+ ]
363
+ return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
364
+ if return_code == 0:
365
+ self.console.print("[bold green][+] Background task enabled.")
366
+ else:
367
+ self.console.print("[bold red][!] Failed to enable background task.")
368
+ else:
369
+ cmd = ["schtasks", "/delete", "/tn", WIN_BACKGROUND_TASK_NAME, "/f"]
370
+ return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
371
+ if return_code == 0:
372
+ self.console.print("[bold green][-] Background task disabled.")
373
+ else:
374
+ self.console.print("[bold red][!] Failed to disable background task.")
375
+
376
+ def toggle_background_task(self, enabled: bool):
377
+ """
378
+ Create or delete a daily background task that runs the scraper.
379
+
380
+ :param enabled: If True, the task will be created; if False, the task will be
381
+ deleted.
382
+ """
383
+ if sys.platform.startswith("win"):
384
+ self._toggle_background_task_windows(enabled)
385
+ else:
386
+ # TODO: implement toggle for cron jobs
387
+ pass
388
+
389
+
390
+ if __name__ == "__main__":
391
+ # If this file is run as a script, create a Scraper instance and run the
392
+ # scrape_prices method.
393
+ scraper = Scraper()
394
+ scraper.scrape_prices()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.2
3
+ Version: 2.1.4
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
@@ -60,7 +60,8 @@ Dynamic: license-file
60
60
 
61
61
  - `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
62
62
  - `Edit Config` to change the specific numbers of each item you own and then save the config file.
63
- - `Show History` to see a price chart consisting of past calculations. A new data point for this chart is generated once a day upon running the program.
63
+ - `Show History` to see a price chart consisting of past calculations. A new data point is generated once a day upon running the program.
64
+ - `Daily Background Calculation` to automatically run a daily calculation of your investment in the background and save the results such that they can later be viewed via `Show History`.
64
65
  - If you want to prevent your requests from being rate limited by the steamcommunity server, register for an API key on [Crawlbase](crawlbase.com) and enter it into the `API_Key` field at the end of the config file. This will route every request through a different proxy server.
65
66
 
66
67
  ---
@@ -0,0 +1,14 @@
1
+ cs2tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ cs2tracker/__main__.py,sha256=Ub--oSMv48YzfWF1CZqYlkn1-HvZ7Bhxoc7urn1oY6o,249
3
+ cs2tracker/_version.py,sha256=cuCBChX_WW6rxX7DfFGzi-EJBf76bHcqGsjRlHgeMvg,511
4
+ cs2tracker/application.py,sha256=CYuvEV2PlAKQ0MD_9vLViTonzspQFWJBGN6rzAOpAQY,4212
5
+ cs2tracker/constants.py,sha256=VG4QBedqHT9kjehWU-Uop7MNlD1VuERgL_VA4oN3obc,15127
6
+ cs2tracker/main.py,sha256=YmWXza0lmDDvLbOWzkJ6n0B1c-tq7Ub-IpwXSeIoKuQ,1309
7
+ cs2tracker/scraper.py,sha256=XoRtTPwN2Lxzqqghvcp3TOe9VdRge7Y_JTGOOJJyd3g,15457
8
+ cs2tracker/data/config.ini,sha256=LbdwEMFlgD8ubuEvGhYZdPtYsWhlho3nX3SLWi4T7Kg,2637
9
+ cs2tracker-2.1.4.dist-info/licenses/LICENSE.md,sha256=G5wqQ_8KGA808kVuF-Fpu_Yhteg8K_5ux9n2v8eQK7s,1069
10
+ cs2tracker-2.1.4.dist-info/METADATA,sha256=CfTvVmloYwmxtvnu0UUeOY6CzO7W3gsBNjGdMFxkBbk,2954
11
+ cs2tracker-2.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ cs2tracker-2.1.4.dist-info/entry_points.txt,sha256=K8IwDIkg8QztSB9g9c89B9jR_2pG4QyJGrNs4z5RcZw,63
13
+ cs2tracker-2.1.4.dist-info/top_level.txt,sha256=2HB4xDDOxaU5BDc_yvdi9UlYLgL768n8aR-hRhFM6VQ,11
14
+ cs2tracker-2.1.4.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- cs2tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- cs2tracker/__main__.py,sha256=Ub--oSMv48YzfWF1CZqYlkn1-HvZ7Bhxoc7urn1oY6o,249
3
- cs2tracker/_version.py,sha256=R_mUEqlkeWeuDiw8DRbHmYUvRxDtmQQJqhTpV8pds-Y,511
4
- cs2tracker/application.py,sha256=8nPUPcPOnatg98_XfSpWjD06-V0jDoy7CT_DNSGuszk,3431
5
- cs2tracker/constants.py,sha256=8jqaMM9eKEmqNTGWQ_h9EiSqYTIEPOAhe5l1zg2ycko,14953
6
- cs2tracker/main.py,sha256=T4X-oLYfltnu0ALBTIJagZTLFmWMhxZrfD1pfFLAUpg,1310
7
- cs2tracker/scraper.py,sha256=XiRjYSLkBd_DFjJ-1951D_3iIaL5jDMMRWCqgWGBMJ0,12201
8
- cs2tracker/data/config.ini,sha256=zieID1dhlEAquPCXbpmvsnvxJAnrDqHSNrS6AIj1F-Q,2637
9
- cs2tracker-2.1.2.dist-info/licenses/LICENSE.md,sha256=G5wqQ_8KGA808kVuF-Fpu_Yhteg8K_5ux9n2v8eQK7s,1069
10
- cs2tracker-2.1.2.dist-info/METADATA,sha256=bS8LgXrOUv0_t84kiUfU6Vybyoq3U8Q4tIqeeKJMMrs,2781
11
- cs2tracker-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- cs2tracker-2.1.2.dist-info/entry_points.txt,sha256=K8IwDIkg8QztSB9g9c89B9jR_2pG4QyJGrNs4z5RcZw,63
13
- cs2tracker-2.1.2.dist-info/top_level.txt,sha256=2HB4xDDOxaU5BDc_yvdi9UlYLgL768n8aR-hRhFM6VQ,11
14
- cs2tracker-2.1.2.dist-info/RECORD,,