cs2tracker 2.1.3__py3-none-any.whl → 2.1.5__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.3'
21
- __version_tuple__ = version_tuple = (2, 1, 3)
20
+ __version__ = version = '2.1.5'
21
+ __version_tuple__ = version_tuple = (2, 1, 5)
cs2tracker/application.py CHANGED
@@ -1,15 +1,37 @@
1
- import os
2
- import subprocess
3
1
  import tkinter as tk
2
+ from subprocess import Popen
3
+ from threading import Thread
4
4
  from typing import cast
5
5
 
6
6
  import matplotlib.pyplot as plt
7
7
  from matplotlib.axes import Axes
8
8
  from matplotlib.dates import DateFormatter
9
9
 
10
- from cs2tracker.constants import CONFIG_FILE, OUTPUT_FILE, TEXT_EDITOR
10
+ from cs2tracker.constants import (
11
+ CONFIG_FILE,
12
+ OS,
13
+ OUTPUT_FILE,
14
+ PYTHON_EXECUTABLE,
15
+ RUNNING_IN_EXE,
16
+ TEXT_EDITOR,
17
+ OSType,
18
+ )
11
19
  from cs2tracker.scraper import Scraper
12
20
 
21
+ WINDOW_TITLE = "CS2Tracker"
22
+ WINDOW_SIZE = "450x380"
23
+ BACKGROUND_COLOR = "#1e1e1e"
24
+ BUTTON_COLOR = "#3c3f41"
25
+ BUTTON_HOVER_COLOR = "#505354"
26
+ BUTTON_ACTIVE_COLOR = "#5c5f61"
27
+ FONT_STYLE = "Segoe UI"
28
+ FONT_COLOR = "white"
29
+
30
+ SCRAPER_WINDOW_TITLE = "CS2Tracker"
31
+ SCRAPER_WINDOW_HEIGHT = 40
32
+ SCRAPER_WINDOW_WIDTH = 80
33
+ SCRAPER_WINDOW_BACKGROUND_COLOR = "Black"
34
+
13
35
 
14
36
  class Application:
15
37
  def __init__(self):
@@ -22,88 +44,156 @@ class Application:
22
44
  application_window = self._configure_window()
23
45
  application_window.mainloop()
24
46
 
47
+ def _add_button(self, frame, text, command):
48
+ """Create and style a button for the main application window."""
49
+ button_style = {
50
+ "font": (FONT_STYLE, 12),
51
+ "fg": FONT_COLOR,
52
+ "bg": BUTTON_COLOR,
53
+ "activebackground": BUTTON_ACTIVE_COLOR,
54
+ }
55
+ button = tk.Button(frame, text=text, command=command, **button_style)
56
+ button.pack(pady=5, fill="x")
57
+ button.bind("<Enter>", lambda _: button.config(bg=BUTTON_HOVER_COLOR))
58
+ button.bind("<Leave>", lambda _: button.config(bg=BUTTON_COLOR))
59
+ return button
60
+
25
61
  def _configure_window(self):
26
- """Configure the main application window layout with buttons for the various
27
- actions.
62
+ """Configure the main application window UI and add buttons for the main
63
+ functionalities.
28
64
  """
29
65
  window = tk.Tk()
30
- window.title("CS2Tracker")
31
- window.geometry("450x450")
32
-
33
- label = tk.Label(window, text="Welcome to CS2Tracker!")
34
- run_button = tk.Button(window, text="Run!", command=self._scrape_prices)
35
- edit_button = tk.Button(window, text="Edit Config", command=self._edit_config)
36
- plot_button = tk.Button(window, text="Show History (Chart)", command=self._draw_plot)
37
- plot_file_button = tk.Button(
38
- window, text="Show History (File)", command=self._edit_log_file
66
+ window.title(WINDOW_TITLE)
67
+ window.geometry(WINDOW_SIZE)
68
+ window.configure(bg=BACKGROUND_COLOR)
69
+
70
+ frame = tk.Frame(window, bg=BACKGROUND_COLOR, padx=30, pady=30)
71
+ frame.pack(expand=True, fill="both")
72
+
73
+ label = tk.Label(
74
+ frame,
75
+ text=f"Welcome to {WINDOW_TITLE}!",
76
+ font=(FONT_STYLE, 16, "bold"),
77
+ fg=FONT_COLOR,
78
+ bg=BACKGROUND_COLOR,
39
79
  )
80
+ label.pack(pady=(0, 30))
81
+
82
+ self._add_button(frame, "Run!", self.scrape_prices)
83
+ self._add_button(frame, "Edit Config", self._edit_config)
84
+ self._add_button(frame, "Show History (Chart)", self._draw_plot)
85
+ self._add_button(frame, "Show History (File)", self._edit_log_file)
86
+
40
87
  background_checkbox_value = tk.BooleanVar(value=self.scraper.identify_background_task())
41
88
  background_checkbox = tk.Checkbutton(
42
- window,
89
+ frame,
43
90
  text="Daily Background Calculation",
44
- command=lambda: self._toggle_background_task(background_checkbox_value.get()),
45
91
  variable=background_checkbox_value,
92
+ command=lambda: self._toggle_background_task(background_checkbox_value.get()),
93
+ bg=BACKGROUND_COLOR,
94
+ fg=FONT_COLOR,
95
+ selectcolor=BUTTON_COLOR,
96
+ activebackground=BACKGROUND_COLOR,
97
+ font=(FONT_STYLE, 10),
46
98
  )
47
-
48
- label.grid(row=0, column=0, pady=50, sticky="NSEW")
49
- run_button.grid(row=1, column=0, pady=10, sticky="NSEW")
50
- edit_button.grid(row=2, column=0, pady=10, sticky="NSEW")
51
- plot_button.grid(row=3, 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")
54
-
55
- window.grid_columnconfigure(0, weight=1)
56
- window.grid_rowconfigure(1, weight=1)
57
- window.grid_rowconfigure(2, weight=1)
58
- window.grid_rowconfigure(3, weight=1)
59
- window.grid_rowconfigure(4, weight=1)
60
- window.grid_rowconfigure(5, weight=1)
61
-
62
- label.grid_configure(sticky="NSEW")
63
- run_button.grid_configure(sticky="NSEW")
64
- edit_button.grid_configure(sticky="NSEW")
65
- plot_button.grid_configure(sticky="NSEW")
66
- plot_file_button.grid_configure(sticky="NSEW")
67
- background_checkbox.grid_configure(sticky="NSEW")
99
+ background_checkbox.pack(pady=20)
68
100
 
69
101
  return window
70
102
 
71
- def _scrape_prices(self):
103
+ def _construct_scraper_command(self):
104
+ """Construct the command to run the scraper in a new window."""
105
+ if OS == OSType.WINDOWS:
106
+ set_utf8_encoding = "[Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8;"
107
+ get_size = "$size = $Host.UI.RawUI.WindowSize;"
108
+ set_size = "$Host.UI.RawUI.WindowSize = $size;"
109
+ set_window_title = f"$Host.UI.RawUI.WindowTitle = '{SCRAPER_WINDOW_TITLE}';"
110
+ set_window_width = f"$size.Width = {SCRAPER_WINDOW_WIDTH};"
111
+ set_window_height = f"$size.Height = {SCRAPER_WINDOW_HEIGHT};"
112
+ set_background_color = (
113
+ f"$Host.UI.RawUI.BackgroundColor = '{SCRAPER_WINDOW_BACKGROUND_COLOR}';"
114
+ )
115
+ clear = "Clear-Host;"
116
+
117
+ if RUNNING_IN_EXE:
118
+ # The python executable is set as the executable itself in PyInstaller
119
+ scraper_cmd = f"{PYTHON_EXECUTABLE} --only-scrape | Out-Host -Paging"
120
+ else:
121
+ scraper_cmd = f"{PYTHON_EXECUTABLE} -m cs2tracker --only-scrape"
122
+
123
+ cmd = (
124
+ 'start powershell -NoExit -Command "& {'
125
+ + set_utf8_encoding
126
+ + set_window_title
127
+ + get_size
128
+ + set_window_width
129
+ + set_window_height
130
+ + set_size
131
+ + set_background_color
132
+ + clear
133
+ + scraper_cmd
134
+ + '}"'
135
+ )
136
+ return cmd
137
+ else:
138
+ # TODO: Implement for Linux
139
+ return ""
140
+
141
+ def scrape_prices(self):
72
142
  """Scrape prices from the configured sources, print the total, and save the
73
143
  results to a file.
74
144
  """
75
- self.scraper.scrape_prices()
145
+ if OS == OSType.WINDOWS:
146
+ scraper_cmd = self._construct_scraper_command()
147
+ Popen(scraper_cmd, shell=True)
148
+ else:
149
+ # TODO: implement external window for Linux
150
+ self.scraper.scrape_prices()
76
151
 
77
152
  def _edit_config(self):
78
153
  """Edit the configuration file using the specified text editor."""
79
- subprocess.call([TEXT_EDITOR, CONFIG_FILE])
80
- self.scraper.parse_config()
154
+ _popen_and_call(
155
+ popen_args={"args": [TEXT_EDITOR, CONFIG_FILE], "shell": True},
156
+ callback=self.scraper.parse_config,
157
+ )
81
158
 
82
159
  def _draw_plot(self):
83
160
  """Draw a plot of the scraped prices over time."""
84
161
  dates, dollars, euros = self.scraper.read_price_log()
85
162
 
86
- fig, ax_raw = plt.subplots()
87
- ax = cast(Axes, ax_raw)
163
+ fig, ax_raw = plt.subplots(figsize=(10, 8), num="CS2Tracker Price History")
164
+ fig.suptitle("CS2Tracker Price History", fontsize=16)
165
+ fig.autofmt_xdate()
88
166
 
167
+ ax = cast(Axes, ax_raw)
89
168
  ax.plot(dates, dollars, label="Dollars")
90
169
  ax.plot(dates, euros, label="Euros")
91
- ax.set_xlabel("Date")
92
- ax.set_ylabel("Price")
93
170
  ax.legend()
94
-
95
171
  date_formatter = DateFormatter("%d-%m-%Y")
96
172
  ax.xaxis.set_major_formatter(date_formatter)
97
- fig.autofmt_xdate()
98
173
 
99
174
  plt.show()
100
175
 
101
176
  def _edit_log_file(self):
102
177
  """Opens the file containing past price calculations."""
103
- if not os.path.isfile(OUTPUT_FILE):
104
- open(OUTPUT_FILE, "w", encoding="utf-8").close()
105
- subprocess.call([TEXT_EDITOR, OUTPUT_FILE])
178
+ Popen([TEXT_EDITOR, OUTPUT_FILE], shell=True)
106
179
 
107
180
  def _toggle_background_task(self, enabled: bool):
108
181
  """Toggle whether a daily price calculation should run in the background."""
109
182
  self.scraper.toggle_background_task(enabled)
183
+
184
+
185
+ def _popen_and_call(popen_args, callback):
186
+ """
187
+ Runs the given args in a subprocess.Popen, and then calls the function callback when
188
+ the subprocess completes.
189
+
190
+ Source: https://stackoverflow.com/questions/2581817/python-subprocess-callback-when-cmd-exits
191
+ """
192
+
193
+ def process_and_callback(popen_args, callback):
194
+ process = Popen(**popen_args)
195
+ process.wait()
196
+ callback()
197
+
198
+ thread = Thread(target=process_and_callback, args=(popen_args, callback), daemon=True)
199
+ thread.start()
cs2tracker/constants.py CHANGED
@@ -1,8 +1,25 @@
1
+ import enum
1
2
  import os
2
3
  import sys
4
+ from datetime import datetime
5
+ from shutil import copy
3
6
 
4
- TEXT_EDITOR = "notepad" if sys.platform.startswith("win") else "nano"
7
+ try:
8
+ from cs2tracker._version import version # pylint: disable=E0611
9
+ except ImportError:
10
+ version = "0.0.0"
11
+
12
+
13
+ class OSType(enum.Enum):
14
+ WINDOWS = "Windows"
15
+ LINUX = "Linux"
16
+
17
+
18
+ OS = OSType.WINDOWS if sys.platform.startswith("win") else OSType.LINUX
19
+ TEXT_EDITOR = "notepad" if OS == OSType.WINDOWS else "nano"
5
20
  PYTHON_EXECUTABLE = sys.executable
21
+
22
+
6
23
  MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
7
24
  PROJECT_DIR = os.path.dirname(MODULE_DIR)
8
25
  OUTPUT_FILE = os.path.join(MODULE_DIR, "data", "output.csv")
@@ -10,6 +27,44 @@ CONFIG_FILE = os.path.join(MODULE_DIR, "data", "config.ini")
10
27
  BATCH_FILE = os.path.join(MODULE_DIR, "data", "cs2tracker_scraper.bat")
11
28
 
12
29
 
30
+ RUNNING_IN_EXE = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
31
+
32
+ if RUNNING_IN_EXE:
33
+ MEIPASS_DIR = sys._MEIPASS # type: ignore pylint: disable=protected-access
34
+ MODULE_DIR = MEIPASS_DIR
35
+ PROJECT_DIR = MEIPASS_DIR
36
+ CONFIG_FILE_SOURCE = os.path.join(MODULE_DIR, "data", "config.ini")
37
+ OUTPUT_FILE_SOURCE = os.path.join(MODULE_DIR, "data", "output.csv")
38
+
39
+ APP_DATA_DIR = os.path.join(os.path.expanduser("~"), "AppData", "Local")
40
+ DATA_DIR = os.path.join(APP_DATA_DIR, "cs2tracker", "data")
41
+ os.makedirs(DATA_DIR, exist_ok=True)
42
+
43
+ CONFIG_FILE = os.path.join(DATA_DIR, "config.ini")
44
+ OUTPUT_FILE = os.path.join(DATA_DIR, "output.csv")
45
+ BATCH_FILE = os.path.join(DATA_DIR, "cs2tracker_scraper.bat")
46
+ if not os.path.exists(CONFIG_FILE):
47
+ copy(CONFIG_FILE_SOURCE, CONFIG_FILE)
48
+ if not os.path.exists(OUTPUT_FILE):
49
+ copy(OUTPUT_FILE_SOURCE, OUTPUT_FILE)
50
+
51
+
52
+ BANNER = """
53
+ __ _____ _____ ______ ____ ____ __ __ _ ___ ____
54
+ / ] / ___/| T| T| \\ / T / ]| l/ ] / _]| \\
55
+ / / ( \\_ l__/ || || D )Y o | / / | ' / / [_ | D )
56
+ / / \\__ T| __jl_j l_j| / | | / / | \\ Y _]| /
57
+ / \\_ / \\ || / | | | | \\ | _ |/ \\_ | Y| [_ | \\
58
+ \\ | \\ || | | | | . Y| | |\\ || . || T| . Y
59
+ \\____j \\___jl_____j l__j l__j\\_jl__j__j \\____jl__j\\_jl_____jl__j\\_j
60
+
61
+
62
+ """
63
+ AUTHOR_STRING = (
64
+ f"Version: v{version} - {datetime.today().strftime('%Y/%m/%d')} - Jannik Novak @ashiven\n"
65
+ )
66
+
67
+
13
68
  RMR_CAPSULES = {
14
69
  "page": "https://steamcommunity.com/market/search?q=2020+rmr",
15
70
  "items": [
@@ -172,13 +227,13 @@ AUSTIN_CAPSULES = {
172
227
 
173
228
  CAPSULE_INFO = {
174
229
  "2020 RMR Sticker Capsule": RMR_CAPSULES,
175
- "Stockholm Sticker Capsule": STOCKHOLM_CAPSULES,
176
- "Antwerp Sticker Capsule": ANTWERP_CAPSULES,
177
- "Rio Sticker Capsule": RIO_CAPSULES,
178
- "Paris Sticker Capsule": PARIS_CAPSULES,
179
- "Copenhagen Sticker Capsule": COPENHAGEN_CAPSULES,
180
- "Shanghai Sticker Capsule": SHANGHAI_CAPSULES,
181
- "Austin Sticker Capsule": AUSTIN_CAPSULES,
230
+ "Stockholm 2021 Sticker Capsule": STOCKHOLM_CAPSULES,
231
+ "Antwerp 2022 Sticker Capsule": ANTWERP_CAPSULES,
232
+ "Rio 2022 Sticker Capsule": RIO_CAPSULES,
233
+ "Paris 2023 Sticker Capsule": PARIS_CAPSULES,
234
+ "Copenhagen 2024 Sticker Capsule": COPENHAGEN_CAPSULES,
235
+ "Shanghai 2024 Sticker Capsule": SHANGHAI_CAPSULES,
236
+ "Austin 2025 Sticker Capsule": AUSTIN_CAPSULES,
182
237
  }
183
238
 
184
239
 
@@ -7,14 +7,14 @@ RMR_Challengers = 0
7
7
  RMR_Legends = 0
8
8
  RMR_Contenders = 0
9
9
 
10
- [Stockholm Sticker Capsule]
10
+ [Stockholm 2021 Sticker Capsule]
11
11
  Stockholm_Challengers = 0
12
12
  Stockholm_Legends = 0
13
13
  Stockholm_Contenders = 0
14
14
  Stockholm_Champions_Autographs = 0
15
15
  Stockholm_Finalists_Autographs = 0
16
16
 
17
- [Antwerp Sticker Capsule]
17
+ [Antwerp 2022 Sticker Capsule]
18
18
  Antwerp_Challengers = 0
19
19
  Antwerp_Legends = 0
20
20
  Antwerp_Contenders = 0
@@ -23,7 +23,7 @@ Antwerp_Contenders_Autographs = 0
23
23
  Antwerp_Challengers_Autographs = 0
24
24
  Antwerp_Legends_Autographs = 0
25
25
 
26
- [Rio Sticker Capsule]
26
+ [Rio 2022 Sticker Capsule]
27
27
  Rio_Challengers = 0
28
28
  Rio_Legends = 0
29
29
  Rio_Contenders = 0
@@ -32,7 +32,7 @@ Rio_Contenders_Autographs = 0
32
32
  Rio_Challengers_Autographs = 0
33
33
  Rio_Legends_Autographs = 0
34
34
 
35
- [Paris Sticker Capsule]
35
+ [Paris 2023 Sticker Capsule]
36
36
  Paris_Challengers = 0
37
37
  Paris_Legends = 0
38
38
  Paris_Contenders = 0
@@ -41,7 +41,7 @@ Paris_Contenders_Autographs = 0
41
41
  Paris_Challengers_Autographs = 0
42
42
  Paris_Legends_Autographs = 0
43
43
 
44
- [Copenhagen Sticker Capsule]
44
+ [Copenhagen 2024 Sticker Capsule]
45
45
  Copenhagen_Challengers = 0
46
46
  Copenhagen_Legends = 0
47
47
  Copenhagen_Contenders = 0
@@ -50,7 +50,7 @@ Copenhagen_Contenders_Autographs = 0
50
50
  Copenhagen_Challengers_Autographs = 0
51
51
  Copenhagen_Legends_Autographs = 0
52
52
 
53
- [Shanghai Sticker Capsule]
53
+ [Shanghai 2024 Sticker Capsule]
54
54
  Shanghai_Challengers = 0
55
55
  Shanghai_Legends = 0
56
56
  Shanghai_Contenders = 0
@@ -59,7 +59,7 @@ Shanghai_Contenders_Autographs = 0
59
59
  Shanghai_Challengers_Autographs = 0
60
60
  Shanghai_Legends_Autographs = 0
61
61
 
62
- [Austin Sticker Capsule]
62
+ [Austin 2025 Sticker Capsule]
63
63
  Austin_Challengers = 0
64
64
  Austin_Legends = 0
65
65
  Austin_Contenders = 0
File without changes
cs2tracker/main.py CHANGED
@@ -1,10 +1,11 @@
1
- from datetime import datetime
1
+ import sys
2
2
 
3
3
  import urllib3
4
- from rich.console import Console
5
4
 
6
- from cs2tracker._version import version # pylint: disable=E0611
7
5
  from cs2tracker.application import Application
6
+ from cs2tracker.constants import AUTHOR_STRING, BANNER
7
+ from cs2tracker.padded_console import PaddedConsole
8
+ from cs2tracker.scraper import Scraper
8
9
 
9
10
 
10
11
  def main():
@@ -18,25 +19,15 @@ def main():
18
19
  # Disable warnings for proxy requests
19
20
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20
21
 
21
- console = Console()
22
- console.print(
23
- "[bold yellow]"
24
- + """
25
- __ _____ _____ ______ ____ ____ __ __ _ ___ ____
26
- / ] / ___/| T| T| \\ / T / ]| l/ ] / _]| \\
27
- / / ( \\_ l__/ || || D )Y o | / / | ' / / [_ | D )
28
- / / \\__ T| __jl_j l_j| / | | / / | \\ Y _]| /
29
- / \\_ / \\ || / | | | | \\ | _ |/ \\_ | Y| [_ | \\
30
- \\ | \\ || | | | | . Y| | |\\ || . || T| . Y
31
- \\____j \\___jl_____j l__j l__j\\_jl__j__j \\____jl__j\\_jl_____jl__j\\_j
32
-
33
-
34
- """
35
- + f"Version: v{version} - {datetime.today().strftime('%Y/%m/%d')} - Jannik Novak @ashiven\n"
36
- )
37
-
38
- application = Application()
39
- application.run()
22
+ if "--only-scrape" in sys.argv:
23
+ scraper = Scraper()
24
+ scraper.console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
25
+ scraper.scrape_prices()
26
+ else:
27
+ console = PaddedConsole()
28
+ console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
29
+ application = Application()
30
+ application.run()
40
31
 
41
32
 
42
33
  if __name__ == "__main__":
@@ -0,0 +1,22 @@
1
+ from rich.console import Console
2
+ from rich.padding import Padding
3
+
4
+ PADDING_BOTTOM = 0
5
+ PADDING_TOP = 0
6
+ PADDING_LEFT = 4
7
+ PADDING_RIGHT = 0
8
+
9
+
10
+ class PaddedConsole:
11
+ def __init__(self, padding=(PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT)):
12
+ """Initialize a PaddedConsole with specified padding."""
13
+ self.console = Console()
14
+ self.padding = padding
15
+
16
+ def print(self, text):
17
+ """Print text with padding to the console."""
18
+ self.console.print(Padding(text, self.padding))
19
+
20
+ def __getattr__(self, attr):
21
+ """Ensure console methods can be called directly on PaddedConsole."""
22
+ return getattr(self.console, attr)
cs2tracker/scraper.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import csv
2
2
  import os
3
- import sys
4
3
  import time
5
4
  from configparser import ConfigParser
6
5
  from datetime import datetime
@@ -11,31 +10,44 @@ from bs4.element import Tag
11
10
  from currency_converter import CurrencyConverter
12
11
  from requests import RequestException, Session
13
12
  from requests.adapters import HTTPAdapter, Retry
14
- from rich.console import Console
15
13
  from tenacity import RetryError, retry, stop_after_attempt
16
14
 
17
15
  from cs2tracker.constants import (
16
+ AUTHOR_STRING,
17
+ BANNER,
18
18
  BATCH_FILE,
19
19
  CAPSULE_INFO,
20
20
  CASE_HREFS,
21
21
  CASE_PAGES,
22
22
  CONFIG_FILE,
23
+ OS,
23
24
  OUTPUT_FILE,
24
25
  PROJECT_DIR,
25
26
  PYTHON_EXECUTABLE,
27
+ RUNNING_IN_EXE,
28
+ OSType,
26
29
  )
30
+ from cs2tracker.padded_console import PaddedConsole
27
31
 
28
32
  MAX_LINE_LEN = 72
29
33
  SEPARATOR = "-"
30
34
  PRICE_INFO = "Owned: {} Steam market price: ${} Total: ${}\n"
31
- BACKGROUND_TASK_NAME = "CS2Tracker Daily Calculation"
32
- BACKGROUND_TASK_TIME = "12:00"
35
+
36
+ HTTP_PROXY_URL = "http://{}:@smartproxy.crawlbase.com:8012"
37
+ HTTPS_PROXY_URL = "http://{}:@smartproxy.crawlbase.com:8012"
38
+
39
+ WIN_BACKGROUND_TASK_NAME = "CS2Tracker Daily Calculation"
40
+ WIN_BACKGROUND_TASK_SCHEDULE = "DAILY"
41
+ WIN_BACKGROUND_TASK_TIME = "12:00"
42
+ WIN_BACKGROUND_TASK_CMD = (
43
+ f"powershell -WindowStyle Hidden -Command \"Start-Process '{BATCH_FILE}' -WindowStyle Hidden\""
44
+ )
33
45
 
34
46
 
35
47
  class Scraper:
36
48
  def __init__(self):
37
49
  """Initialize the Scraper class."""
38
- self.console = Console()
50
+ self.console = PaddedConsole()
39
51
  self.parse_config()
40
52
  self._start_session()
41
53
 
@@ -111,21 +123,33 @@ class Scraper:
111
123
  This will append a new entry to the output file if no entry has been made for
112
124
  today.
113
125
  """
114
- if not os.path.isfile(OUTPUT_FILE):
115
- open(OUTPUT_FILE, "w", encoding="utf-8").close()
116
-
117
126
  with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
118
127
  price_logs_reader = csv.reader(price_logs)
119
- last_log_date = ""
120
- for row in price_logs_reader:
121
- last_log_date, _ = row
128
+ rows = list(price_logs_reader)
129
+ last_log_date, _, _ = rows[-1] if rows else ("", "", "")
122
130
 
123
131
  today = datetime.now().strftime("%Y-%m-%d")
124
132
  if last_log_date != today:
133
+ # Append first price calculation of the day
125
134
  with open(OUTPUT_FILE, "a", newline="", encoding="utf-8") as price_logs:
126
135
  price_logs_writer = csv.writer(price_logs)
127
- price_logs_writer.writerow([today, f"{self.usd_total:.2f}$"])
128
- price_logs_writer.writerow([today, f"{self.eur_total:.2f}€"])
136
+ price_logs_writer.writerow(
137
+ [today, f"{self.usd_total:.2f}$", f"{self.eur_total:.2f}€"]
138
+ )
139
+ else:
140
+ # Replace the last calculation of today with the most recent one of today
141
+ with open(OUTPUT_FILE, "r+", newline="", encoding="utf-8") as price_logs:
142
+ price_logs_reader = csv.reader(price_logs)
143
+ rows = list(price_logs_reader)
144
+ rows_without_today = rows[:-1]
145
+ price_logs.seek(0)
146
+ price_logs.truncate()
147
+
148
+ price_logs_writer = csv.writer(price_logs)
149
+ price_logs_writer.writerows(rows_without_today)
150
+ price_logs_writer.writerow(
151
+ [today, f"{self.usd_total:.2f}$", f"{self.eur_total:.2f}€"]
152
+ )
129
153
 
130
154
  def read_price_log(self):
131
155
  """
@@ -134,23 +158,18 @@ class Scraper:
134
158
 
135
159
  :return: A tuple containing three lists: dates, dollar prices, and euro prices.
136
160
  """
137
- if not os.path.isfile(OUTPUT_FILE):
138
- open(OUTPUT_FILE, "w", encoding="utf-8").close()
139
-
140
161
  dates, dollars, euros = [], [], []
141
162
  with open(OUTPUT_FILE, "r", encoding="utf-8") as price_logs:
142
163
  price_logs_reader = csv.reader(price_logs)
143
164
  for row in price_logs_reader:
144
- date, price_with_currency = row
165
+ date, price_usd, price_eur = row
145
166
  date = datetime.strptime(date, "%Y-%m-%d")
146
- price = float(price_with_currency.rstrip("$€"))
147
- if price_with_currency.endswith("€"):
148
- euros.append(price)
149
- else:
150
- dollars.append(price)
151
- # Only append every second date since the dates are the same for euros and dollars
152
- # and we want the length of dates to match the lengths of dollars and euros
153
- dates.append(date)
167
+ price_usd = float(price_usd.rstrip("$"))
168
+ price_eur = float(price_eur.rstrip("€"))
169
+
170
+ dates.append(date)
171
+ dollars.append(price_usd)
172
+ euros.append(price_eur)
154
173
 
155
174
  return dates, dollars, euros
156
175
 
@@ -167,12 +186,13 @@ class Scraper:
167
186
  """
168
187
  use_proxy = self.config.getboolean("Settings", "Use_Proxy", fallback=False)
169
188
  api_key = self.config.get("Settings", "API_Key", fallback=None)
189
+ api_key = None if api_key in ("None", "") else api_key
170
190
  if use_proxy and api_key:
171
191
  page = self.session.get(
172
192
  url=url,
173
193
  proxies={
174
- "http": f"http://{api_key}:@smartproxy.crawlbase.com:8012",
175
- "https": f"http://{api_key}:@smartproxy.crawlbase.com:8012",
194
+ "http": HTTP_PROXY_URL.format(api_key),
195
+ "https": HTTPS_PROXY_URL.format(api_key),
176
196
  },
177
197
  verify=False,
178
198
  )
@@ -310,8 +330,8 @@ class Scraper:
310
330
 
311
331
  :return: True if a background task is found, False otherwise.
312
332
  """
313
- if sys.platform.startswith("win"):
314
- cmd = ["schtasks", "/query", "/tn", BACKGROUND_TASK_NAME]
333
+ if OS == OSType.WINDOWS:
334
+ cmd = ["schtasks", "/query", "/tn", WIN_BACKGROUND_TASK_NAME]
315
335
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
316
336
  found = return_code == 0
317
337
  return found
@@ -328,8 +348,13 @@ class Scraper:
328
348
  """
329
349
  if enabled:
330
350
  with open(BATCH_FILE, "w", encoding="utf-8") as batch_file:
331
- batch_file.write(f"cd {PROJECT_DIR}\n")
332
- batch_file.write(f"{PYTHON_EXECUTABLE} -m cs2tracker.scraper\n")
351
+ if RUNNING_IN_EXE:
352
+ # The python executable is set to the executable itself
353
+ # for executables created with PyInstaller
354
+ batch_file.write(f"{PYTHON_EXECUTABLE} --only-scrape\n")
355
+ else:
356
+ batch_file.write(f"cd {PROJECT_DIR}\n")
357
+ batch_file.write(f"{PYTHON_EXECUTABLE} -m cs2tracker --only-scrape\n")
333
358
  else:
334
359
  if os.path.exists(BATCH_FILE):
335
360
  os.remove(BATCH_FILE)
@@ -347,13 +372,13 @@ class Scraper:
347
372
  "schtasks",
348
373
  "/create",
349
374
  "/tn",
350
- BACKGROUND_TASK_NAME,
375
+ WIN_BACKGROUND_TASK_NAME,
351
376
  "/tr",
352
- BATCH_FILE,
377
+ WIN_BACKGROUND_TASK_CMD,
353
378
  "/sc",
354
- "DAILY",
379
+ WIN_BACKGROUND_TASK_SCHEDULE,
355
380
  "/st",
356
- BACKGROUND_TASK_TIME,
381
+ WIN_BACKGROUND_TASK_TIME,
357
382
  ]
358
383
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
359
384
  if return_code == 0:
@@ -361,7 +386,7 @@ class Scraper:
361
386
  else:
362
387
  self.console.print("[bold red][!] Failed to enable background task.")
363
388
  else:
364
- cmd = ["schtasks", "/delete", "/tn", BACKGROUND_TASK_NAME, "/f"]
389
+ cmd = ["schtasks", "/delete", "/tn", WIN_BACKGROUND_TASK_NAME, "/f"]
365
390
  return_code = call(cmd, stdout=DEVNULL, stderr=DEVNULL)
366
391
  if return_code == 0:
367
392
  self.console.print("[bold green][-] Background task disabled.")
@@ -375,7 +400,7 @@ class Scraper:
375
400
  :param enabled: If True, the task will be created; if False, the task will be
376
401
  deleted.
377
402
  """
378
- if sys.platform.startswith("win"):
403
+ if OS == OSType.WINDOWS:
379
404
  self._toggle_background_task_windows(enabled)
380
405
  else:
381
406
  # TODO: implement toggle for cron jobs
@@ -383,7 +408,6 @@ class Scraper:
383
408
 
384
409
 
385
410
  if __name__ == "__main__":
386
- # If this file is run as a script, create a Scraper instance and run the
387
- # scrape_prices method.
388
411
  scraper = Scraper()
412
+ scraper.console.print(f"[bold yellow]{BANNER}\n{AUTHOR_STRING}\n")
389
413
  scraper.scrape_prices()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cs2tracker
3
- Version: 2.1.3
3
+ Version: 2.1.5
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
@@ -40,11 +40,17 @@ Dynamic: license-file
40
40
 
41
41
  ### Prerequisites
42
42
 
43
- - Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/).
44
- - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
43
+ - Download and install the latest versions of [Python](https://www.python.org/downloads/) and [Pip](https://pypi.org/project/pip/). (Required for Linux)
44
+ - Register for the [Crawlbase Smart Proxy API](https://crawlbase.com/) and retrieve your API key. (Optional)
45
45
 
46
46
  ### Setup
47
47
 
48
+ #### Windows Executable (No color support)
49
+
50
+ - Simply download the [latest executable](https://github.com/ashiven/cs2tracker/releases) and run it.
51
+
52
+ #### Install via Pip
53
+
48
54
  1. Install the program via pip:
49
55
 
50
56
  ```bash
@@ -58,11 +64,11 @@ Dynamic: license-file
58
64
 
59
65
  ### Options
60
66
 
61
- - `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
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 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`.
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.
67
+ - `Run!` to gather the current market prices of your items and calculate the total amount in USD and EUR.
68
+ - `Edit Config` to change the specific numbers of each item you own and then save the config file.
69
+ - `Show History` to see a price chart consisting of past calculations. A new data point is generated once a day upon running the program.
70
+ - `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`.
71
+ - 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 in the config file. This will route every request through a different proxy server.
66
72
 
67
73
  ---
68
74
 
@@ -0,0 +1,16 @@
1
+ cs2tracker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ cs2tracker/__main__.py,sha256=Ub--oSMv48YzfWF1CZqYlkn1-HvZ7Bhxoc7urn1oY6o,249
3
+ cs2tracker/_version.py,sha256=ezuEPMmZBPsFTNec3uKYV6IfWrjNpfiIok1e_GQvZGQ,511
4
+ cs2tracker/application.py,sha256=pFmPRmm9OdYRLLl6qnmkHyV7oLI-Le4YSO26VX7eP4U,7180
5
+ cs2tracker/constants.py,sha256=er-YFaEe8WachO_q-RU6vik5SUcFEtacHxGYhD15Stk,17104
6
+ cs2tracker/main.py,sha256=kiTADEXXnX1y3kh0zII91mjadztMB145RBteXM2WG3I,915
7
+ cs2tracker/padded_console.py,sha256=lPEa34p-8LTmTbpf-2S5uYPaA2UmsIOPq2_UoVhMRgU,674
8
+ cs2tracker/scraper.py,sha256=hrL01_HWHpZZgkevj5JCmET0o2T1wSbmsxapuK11gv4,16202
9
+ cs2tracker/data/config.ini,sha256=q-T7_W3krPVJJoMcRJGGczNRvCZw-wJfNobpsdUEDNs,2672
10
+ cs2tracker/data/output.csv,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ cs2tracker-2.1.5.dist-info/licenses/LICENSE.md,sha256=G5wqQ_8KGA808kVuF-Fpu_Yhteg8K_5ux9n2v8eQK7s,1069
12
+ cs2tracker-2.1.5.dist-info/METADATA,sha256=zFEptkI_0AXDALL3uGqz0pTsRGn5OQMmnn-qAL6azfs,3126
13
+ cs2tracker-2.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ cs2tracker-2.1.5.dist-info/entry_points.txt,sha256=K8IwDIkg8QztSB9g9c89B9jR_2pG4QyJGrNs4z5RcZw,63
15
+ cs2tracker-2.1.5.dist-info/top_level.txt,sha256=2HB4xDDOxaU5BDc_yvdi9UlYLgL768n8aR-hRhFM6VQ,11
16
+ cs2tracker-2.1.5.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=BiGPztNyl3M8e5PrbQdJPlg8lFWgerKsXwbz544Fc2s,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=PjcGga-_odlcFBrh5poNYcgAwRQferXU7HLpf67W8L8,15229
8
- cs2tracker/data/config.ini,sha256=LbdwEMFlgD8ubuEvGhYZdPtYsWhlho3nX3SLWi4T7Kg,2637
9
- cs2tracker-2.1.3.dist-info/licenses/LICENSE.md,sha256=G5wqQ_8KGA808kVuF-Fpu_Yhteg8K_5ux9n2v8eQK7s,1069
10
- cs2tracker-2.1.3.dist-info/METADATA,sha256=jW4vLzcuQamN64985L4bFfAXNcidLOSNY84ncgoBNs8,2954
11
- cs2tracker-2.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- cs2tracker-2.1.3.dist-info/entry_points.txt,sha256=K8IwDIkg8QztSB9g9c89B9jR_2pG4QyJGrNs4z5RcZw,63
13
- cs2tracker-2.1.3.dist-info/top_level.txt,sha256=2HB4xDDOxaU5BDc_yvdi9UlYLgL768n8aR-hRhFM6VQ,11
14
- cs2tracker-2.1.3.dist-info/RECORD,,