cs2tracker 2.1.13__py3-none-any.whl → 2.1.15__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.13'
21
- __version_tuple__ = version_tuple = (2, 1, 13)
20
+ __version__ = version = '2.1.15'
21
+ __version_tuple__ = version_tuple = (2, 1, 15)
@@ -1,3 +0,0 @@
1
- from cs2tracker.app.application import ( # noqa: F401 # pylint:disable=unused-import
2
- Application,
3
- )
cs2tracker/app/app.py ADDED
@@ -0,0 +1,283 @@
1
+ import ctypes
2
+ import tkinter as tk
3
+ from shutil import copy
4
+ from tkinter import messagebox, ttk
5
+ from tkinter.filedialog import askopenfilename, asksaveasfile
6
+
7
+ import sv_ttk
8
+
9
+ from cs2tracker.app.editor_frame import ConfigEditorFrame
10
+ from cs2tracker.app.history_frame import PriceHistoryFrame
11
+ from cs2tracker.app.scraper_frame import ScraperFrame
12
+ from cs2tracker.config import get_config
13
+ from cs2tracker.constants import ICON_FILE, OS, OUTPUT_FILE, OSType
14
+ from cs2tracker.logs import PriceLogs
15
+ from cs2tracker.scraper.background_task import BackgroundTask
16
+ from cs2tracker.scraper.scraper import Scraper
17
+ from cs2tracker.util.currency_conversion import CURRENCY_SYMBOLS
18
+ from cs2tracker.util.tkinter import centered, fix_sv_ttk, size_info
19
+
20
+ APPLICATION_NAME = "CS2Tracker"
21
+ WINDOW_SIZE = "630x335"
22
+ DARK_THEME = True
23
+
24
+ SCRAPER_WINDOW_TITLE = "Price Overview"
25
+ SCRAPER_WINDOW_SIZE = "900x750"
26
+
27
+ CONFIG_EDITOR_TITLE = "Config Editor"
28
+ CONFIG_EDITOR_SIZE = "850x750"
29
+
30
+ PRICE_HISTORY_TITLE = "Price History"
31
+ PRICE_HISTORY_SIZE = "900x700"
32
+
33
+ config = get_config()
34
+
35
+
36
+ class Application:
37
+ def __init__(self):
38
+ self.scraper = Scraper()
39
+
40
+ def run(self):
41
+ """Run the main application window."""
42
+ window = self._configure_window()
43
+
44
+ if DARK_THEME:
45
+ sv_ttk.use_dark_theme()
46
+ else:
47
+ sv_ttk.use_light_theme()
48
+
49
+ fix_sv_ttk(ttk.Style())
50
+
51
+ window.mainloop()
52
+
53
+ def _configure_window(self):
54
+ """Configure the main application window."""
55
+ window = tk.Tk()
56
+ window.title(APPLICATION_NAME)
57
+ window.geometry(centered(window, WINDOW_SIZE))
58
+ window.minsize(*size_info(WINDOW_SIZE))
59
+
60
+ if OS == OSType.WINDOWS:
61
+ app_id = "cs2tracker.unique.id"
62
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(app_id)
63
+
64
+ icon = tk.PhotoImage(file=ICON_FILE)
65
+ window.wm_iconphoto(True, icon)
66
+
67
+ main_frame = MainFrame(window, self.scraper)
68
+ main_frame.pack(expand=True, fill="both")
69
+
70
+ return window
71
+
72
+
73
+ class MainFrame(ttk.Frame):
74
+ # pylint: disable=too-many-instance-attributes,attribute-defined-outside-init
75
+ def __init__(self, parent, scraper):
76
+ super().__init__(parent, padding=15)
77
+ self.parent = parent
78
+ self.scraper = scraper
79
+ self._add_widgets()
80
+
81
+ def _add_widgets(self):
82
+ """Add widgets to the main frame."""
83
+ self.columnconfigure(0, weight=1)
84
+ self.columnconfigure(1, weight=1)
85
+ self.rowconfigure(0, weight=1)
86
+
87
+ self._configure_button_frame()
88
+ self.button_frame.grid(row=0, column=0, padx=10, pady=(0, 20), sticky="nsew")
89
+ self._configure_settings_frame()
90
+ self.settings_frame.grid(row=0, column=1, padx=10, pady=(0, 20), sticky="nsew")
91
+
92
+ def _add_button(self, text, command, row):
93
+ """Create and style a button for the button frame."""
94
+ grid_pos = {"row": row, "column": 0, "sticky": "ew", "padx": 10, "pady": 10}
95
+ button = ttk.Button(self.button_frame, text=text, command=command)
96
+ button.grid(**grid_pos)
97
+
98
+ def _configure_button_frame(self):
99
+ """Configure the button frame of the application main frame."""
100
+ self.button_frame = ttk.Frame(self, style="Card.TFrame", padding=15)
101
+ self.button_frame.columnconfigure(0, weight=1)
102
+
103
+ self._add_button("Run!", self.scrape_prices, 0)
104
+ self._add_button("Edit Config", self._edit_config, 1)
105
+ self._add_button("Show History", self._show_history, 2)
106
+ self._add_button("Export History", self._export_log_file, 3)
107
+ self._add_button("Import History", self._import_log_file, 4)
108
+
109
+ def _add_checkbox(
110
+ self, text, variable, command, row
111
+ ): # pylint: disable=too-many-arguments,too-many-positional-arguments,attribute-defined-outside-init
112
+ """Create and style a checkbox for the checkbox frame."""
113
+ grid_pos = {"row": row, "column": 0, "sticky": "w", "padx": (10, 0), "pady": 5}
114
+ checkbox = ttk.Checkbutton(
115
+ self.settings_frame,
116
+ text=text,
117
+ variable=variable,
118
+ command=command,
119
+ style="Switch.TCheckbutton",
120
+ )
121
+ checkbox.grid(**grid_pos)
122
+
123
+ def _configure_settings_frame(self):
124
+ """Configure the settings frame for background tasks and other settings."""
125
+ self.settings_frame = ttk.LabelFrame(self, text="Settings", padding=15)
126
+ self.settings_frame.columnconfigure(0, weight=1)
127
+
128
+ self.background_checkbox_value = tk.BooleanVar(value=BackgroundTask.identify())
129
+ self._add_checkbox(
130
+ "Background Task",
131
+ self.background_checkbox_value,
132
+ lambda: self._toggle_background_task(self.background_checkbox_value.get()),
133
+ 0,
134
+ )
135
+
136
+ self.discord_webhook_checkbox_value = tk.BooleanVar(value=config.discord_notifications)
137
+ self._add_checkbox(
138
+ "Discord Notifications",
139
+ self.discord_webhook_checkbox_value,
140
+ lambda: self.discord_webhook_checkbox_value.set(
141
+ self._toggle_discord_webhook(self.discord_webhook_checkbox_value.get())
142
+ ),
143
+ 1,
144
+ )
145
+
146
+ self.use_proxy_checkbox_value = tk.BooleanVar(value=config.use_proxy)
147
+ self._add_checkbox(
148
+ "Proxy Requests",
149
+ self.use_proxy_checkbox_value,
150
+ lambda: self.use_proxy_checkbox_value.set(
151
+ self._toggle_use_proxy(self.use_proxy_checkbox_value.get())
152
+ ),
153
+ 2,
154
+ )
155
+
156
+ self.dark_theme_checkbox_value = tk.BooleanVar(value=DARK_THEME)
157
+ self._add_checkbox("Dark Theme", self.dark_theme_checkbox_value, self._toggle_theme, 3)
158
+
159
+ self.currency_selection_label = ttk.Label(self.settings_frame, text="Currency:")
160
+ self.currency_selection_label.grid(row=4, column=0, sticky="w", padx=(20, 0), pady=5)
161
+ self.currency_selection = ttk.Combobox(
162
+ self.settings_frame,
163
+ state="readonly",
164
+ values=list(CURRENCY_SYMBOLS),
165
+ postcommand=self.parent.focus_set,
166
+ )
167
+ self.currency_selection.set(config.conversion_currency)
168
+ self.currency_selection.grid(row=5, column=0, sticky="w", padx=(20, 0), pady=5)
169
+
170
+ def on_currency_change(_):
171
+ config.set_app_option("conversion_currency", self.currency_selection.get())
172
+ self.currency_selection.selection_clear()
173
+ self.parent.focus_set()
174
+
175
+ self.currency_selection.bind(
176
+ "<<ComboboxSelected>>",
177
+ on_currency_change,
178
+ )
179
+
180
+ def scrape_prices(self):
181
+ """Scrape prices from the configured sources, print the total, and save the
182
+ results to a file.
183
+ """
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)
188
+
189
+ run_frame = ScraperFrame(
190
+ scraper_window,
191
+ self.scraper,
192
+ sheet_size=SCRAPER_WINDOW_SIZE,
193
+ dark_theme=self.dark_theme_checkbox_value.get(),
194
+ )
195
+ run_frame.pack(expand=True, fill="both")
196
+ run_frame.start()
197
+
198
+ def _edit_config(self):
199
+ """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)
204
+
205
+ editor_frame = ConfigEditorFrame(config_editor_window)
206
+ editor_frame.pack(expand=True, fill="both")
207
+
208
+ def _show_history(self):
209
+ """Show a chart consisting of past calculations."""
210
+ if PriceLogs.empty():
211
+ return
212
+
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)
217
+
218
+ history_frame = PriceHistoryFrame(price_history_window)
219
+ history_frame.pack(expand=True, fill="both")
220
+
221
+ def _export_log_file(self):
222
+ """Lets the user export the log file to a different location."""
223
+ if PriceLogs.empty():
224
+ return
225
+
226
+ export_path = asksaveasfile(
227
+ title="Export Log File",
228
+ defaultextension=".csv",
229
+ filetypes=[("CSV File", "*.csv")],
230
+ )
231
+ if export_path:
232
+ copy(OUTPUT_FILE, export_path.name)
233
+
234
+ def _import_log_file(self):
235
+ """Lets the user import a log file from a different location."""
236
+ import_path = askopenfilename(
237
+ title="Import Log File",
238
+ defaultextension=".csv",
239
+ filetypes=[("CSV files", "*.csv")],
240
+ )
241
+ if not PriceLogs.validate_file(import_path):
242
+ return
243
+ copy(import_path, OUTPUT_FILE)
244
+
245
+ def _toggle_background_task(self, enabled: bool):
246
+ """Toggle whether a daily price calculation should run in the background."""
247
+ BackgroundTask.toggle(enabled)
248
+
249
+ def _toggle_use_proxy(self, enabled: bool):
250
+ """Toggle whether the scraper should use proxy servers for requests."""
251
+ proxy_api_key = config.proxy_api_key
252
+ if not proxy_api_key and enabled:
253
+ messagebox.showerror(
254
+ "Config Error",
255
+ "You need to enter a valid crawlbase API key into the configuration to use this feature.",
256
+ parent=self.parent,
257
+ )
258
+ return False
259
+
260
+ config.toggle_app_option("use_proxy", enabled)
261
+ return True
262
+
263
+ def _toggle_discord_webhook(self, enabled: bool):
264
+ """Toggle whether the scraper should send notifications to a Discord webhook."""
265
+ discord_webhook_url = config.discord_webhook_url
266
+ if not discord_webhook_url and enabled:
267
+ messagebox.showerror(
268
+ "Config Error",
269
+ "You need to enter a valid Discord webhook URL into the configuration to use this feature.",
270
+ parent=self.parent,
271
+ )
272
+ return False
273
+
274
+ config.toggle_app_option("discord_webhook", enabled)
275
+ return True
276
+
277
+ def _toggle_theme(self):
278
+ """Toggle the theme of the application."""
279
+ if self.dark_theme_checkbox_value.get():
280
+ sv_ttk.use_dark_theme()
281
+ else:
282
+ sv_ttk.use_light_theme()
283
+ fix_sv_ttk(ttk.Style())