cs2tracker 2.1.13__py3-none-any.whl → 2.1.14__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 +2 -2
- cs2tracker/app/application.py +72 -76
- cs2tracker/app/editor_frame.py +208 -127
- cs2tracker/app/scraper_frame.py +27 -10
- cs2tracker/constants.py +58 -321
- cs2tracker/data/config.ini +153 -153
- cs2tracker/data/get_inventory.js +64 -21
- cs2tracker/main.py +1 -16
- cs2tracker/scraper/parsers.py +192 -0
- cs2tracker/scraper/scraper.py +102 -212
- cs2tracker/util/padded_console.py +19 -0
- cs2tracker/util/validated_config.py +72 -18
- {cs2tracker-2.1.13.dist-info → cs2tracker-2.1.14.dist-info}/METADATA +3 -2
- cs2tracker-2.1.14.dist-info/RECORD +28 -0
- cs2tracker-2.1.13.dist-info/RECORD +0 -27
- {cs2tracker-2.1.13.dist-info → cs2tracker-2.1.14.dist-info}/WHEEL +0 -0
- {cs2tracker-2.1.13.dist-info → cs2tracker-2.1.14.dist-info}/entry_points.txt +0 -0
- {cs2tracker-2.1.13.dist-info → cs2tracker-2.1.14.dist-info}/licenses/LICENSE +0 -0
- {cs2tracker-2.1.13.dist-info → cs2tracker-2.1.14.dist-info}/top_level.txt +0 -0
cs2tracker/app/editor_frame.py
CHANGED
|
@@ -4,7 +4,6 @@ from shutil import copy
|
|
|
4
4
|
from subprocess import PIPE, STDOUT
|
|
5
5
|
from threading import Thread
|
|
6
6
|
from tkinter import messagebox, ttk
|
|
7
|
-
from urllib.parse import unquote
|
|
8
7
|
|
|
9
8
|
from nodejs import node
|
|
10
9
|
from ttk_text import ThemedText
|
|
@@ -12,6 +11,7 @@ from ttk_text import ThemedText
|
|
|
12
11
|
from cs2tracker.constants import (
|
|
13
12
|
CONFIG_FILE,
|
|
14
13
|
CONFIG_FILE_BACKUP,
|
|
14
|
+
DATA_DIR,
|
|
15
15
|
INVENTORY_IMPORT_FILE,
|
|
16
16
|
INVENTORY_IMPORT_SCRIPT,
|
|
17
17
|
)
|
|
@@ -21,7 +21,7 @@ ADD_CUSTOM_ITEM_TITLE = "Add Custom Item"
|
|
|
21
21
|
ADD_CUSTOM_ITEM_SIZE = "500x220"
|
|
22
22
|
|
|
23
23
|
IMPORT_INVENTORY_TITLE = "Import Steam Inventory"
|
|
24
|
-
IMPORT_INVENTORY_SIZE = "
|
|
24
|
+
IMPORT_INVENTORY_SIZE = "600x550"
|
|
25
25
|
|
|
26
26
|
IMPORT_INVENTORY_PROCESS_TITLE = "Importing Steam Inventory..."
|
|
27
27
|
IMPORT_INVENTORY_PROCESS_SIZE = "700x500"
|
|
@@ -37,8 +37,11 @@ class ConfigEditorFrame(ttk.Frame):
|
|
|
37
37
|
super().__init__(parent, padding=15)
|
|
38
38
|
|
|
39
39
|
self.parent = parent
|
|
40
|
+
self.edit_entry = None
|
|
40
41
|
self._add_widgets()
|
|
41
42
|
|
|
43
|
+
self.tree.focus_set()
|
|
44
|
+
|
|
42
45
|
def _add_widgets(self):
|
|
43
46
|
"""Configure the main editor frame which displays the configuration options in a
|
|
44
47
|
structured way.
|
|
@@ -46,59 +49,109 @@ class ConfigEditorFrame(ttk.Frame):
|
|
|
46
49
|
self._configure_treeview()
|
|
47
50
|
self.tree.pack(expand=True, fill="both")
|
|
48
51
|
|
|
49
|
-
button_frame = ConfigEditorButtonFrame(self
|
|
52
|
+
button_frame = ConfigEditorButtonFrame(self)
|
|
50
53
|
button_frame.pack(side="bottom", padx=10, pady=(0, 10))
|
|
51
54
|
|
|
52
|
-
def
|
|
55
|
+
def save_config(self):
|
|
56
|
+
"""Save the current configuration from the treeview to the config file."""
|
|
57
|
+
config.delete_display_sections()
|
|
58
|
+
for section in self.tree.get_children():
|
|
59
|
+
config.add_section(section)
|
|
60
|
+
for item in self.tree.get_children(section):
|
|
61
|
+
item_name = self.tree.item(item, "text")
|
|
62
|
+
if section not in ("App Settings", "User Settings"):
|
|
63
|
+
config_option = config.name_to_option(item_name, href=True)
|
|
64
|
+
else:
|
|
65
|
+
config_option = config.name_to_option(item_name)
|
|
66
|
+
value = self.tree.item(item, "values")[0]
|
|
67
|
+
config.set(section, config_option, value)
|
|
68
|
+
|
|
69
|
+
config.write_to_file()
|
|
70
|
+
if not config.valid:
|
|
71
|
+
config.load_from_file()
|
|
72
|
+
self.reload_config_into_tree()
|
|
73
|
+
messagebox.showerror(
|
|
74
|
+
"Config Error", f"The configuration is invalid. ({config.last_error})", parent=self
|
|
75
|
+
)
|
|
76
|
+
self.parent.focus_set()
|
|
77
|
+
self.tree.focus_set()
|
|
78
|
+
|
|
79
|
+
def _save_edit(self, event, row, column):
|
|
80
|
+
"""Save the edited value in the treeview and destroy the entry widget."""
|
|
81
|
+
self.tree.set(row, column=column, value=event.widget.get())
|
|
82
|
+
self.save_config()
|
|
83
|
+
event.widget.destroy()
|
|
84
|
+
|
|
85
|
+
def _set_cell_value(self, event, row=None, column=None):
|
|
53
86
|
"""
|
|
54
|
-
Set the value of a cell in the treeview to be editable when double-
|
|
87
|
+
Set the value of a cell in the treeview to be editable when double-clicked.
|
|
55
88
|
|
|
56
89
|
Source: https://stackoverflow.com/questions/75787251/create-an-editable-tkinter-treeview-with-keyword-connection
|
|
57
90
|
"""
|
|
91
|
+
try:
|
|
92
|
+
if not row or not column:
|
|
93
|
+
row = self.tree.identify_row(event.y)
|
|
94
|
+
column = self.tree.identify_column(event.x)
|
|
58
95
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
96
|
+
item_text = self.tree.item(row, "text")
|
|
97
|
+
if column == "#0" or any(item_text == section for section in config.sections()):
|
|
98
|
+
return
|
|
99
|
+
item_value = self.tree.item(row, "values")[0]
|
|
62
100
|
|
|
63
|
-
try:
|
|
64
|
-
row = self.tree.identify_row(event.y)
|
|
65
|
-
column = self.tree.identify_column(event.x)
|
|
66
|
-
item_text = self.tree.set(row, column)
|
|
67
|
-
if item_text.strip() == "":
|
|
68
|
-
left_item_text = self.tree.item(row, "text")
|
|
69
|
-
# Don't allow editing of section headers
|
|
70
|
-
if any(left_item_text == section for section in config.sections()):
|
|
71
|
-
return
|
|
72
101
|
x, y, w, h = self.tree.bbox(row, column)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
self.edit_entry = ttk.Entry(self, justify="center", font=("Helvetica", 11))
|
|
103
|
+
self.edit_entry.place(x=x, y=y, width=w, height=h + 3) # type: ignore
|
|
104
|
+
self.edit_entry.insert("end", item_value)
|
|
105
|
+
self.edit_entry.bind("<Return>", lambda e: self._save_edit(e, row, column))
|
|
106
|
+
self.edit_entry.focus_set()
|
|
107
|
+
self.edit_entry.grab_set()
|
|
79
108
|
except Exception:
|
|
80
|
-
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
def _set_selection_value(self, _):
|
|
112
|
+
"""Set the value of the currently selected cell in the treeview to be
|
|
113
|
+
editable.
|
|
114
|
+
"""
|
|
115
|
+
selected = self.tree.selection()
|
|
116
|
+
if selected:
|
|
117
|
+
row = selected[0]
|
|
118
|
+
column = "#1"
|
|
119
|
+
self._set_cell_value(None, row=row, column=column)
|
|
81
120
|
|
|
82
|
-
def
|
|
121
|
+
def _delete_selection_value(self, _):
|
|
122
|
+
"""
|
|
123
|
+
Delete the value of the currently selected cell in the treeview.
|
|
124
|
+
|
|
125
|
+
This only works for custom items, as other sections are not editable.
|
|
126
|
+
"""
|
|
127
|
+
selected = self.tree.selection()
|
|
128
|
+
if selected:
|
|
129
|
+
row = selected[0]
|
|
130
|
+
section_name = self.tree.parent(row)
|
|
131
|
+
if section_name == "Custom Items":
|
|
132
|
+
self.tree.delete(row)
|
|
133
|
+
self.save_config()
|
|
134
|
+
self.tree.focus("Custom Items")
|
|
135
|
+
self.tree.selection_set("Custom Items")
|
|
136
|
+
|
|
137
|
+
def _destroy_entry(self, _):
|
|
83
138
|
"""Destroy any entry widgets in the treeview on an event, such as a mouse wheel
|
|
84
139
|
movement.
|
|
85
140
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _destroy_entry(self, event):
|
|
91
|
-
"""Destroy the entry widget on an even targeting it."""
|
|
92
|
-
if isinstance(event.widget, ttk.Entry):
|
|
93
|
-
event.widget.destroy()
|
|
141
|
+
if self.edit_entry:
|
|
142
|
+
self.edit_entry.destroy()
|
|
143
|
+
self.edit_entry = None
|
|
144
|
+
self.tree.focus_set()
|
|
94
145
|
|
|
95
146
|
def _make_tree_editable(self):
|
|
96
147
|
"""Add a binding to the treeview that allows double-clicking on a cell to edit
|
|
97
148
|
its value.
|
|
98
149
|
"""
|
|
99
150
|
self.tree.bind("<Double-1>", self._set_cell_value)
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
151
|
+
self.tree.bind("<Return>", self._set_selection_value)
|
|
152
|
+
self.tree.bind("<BackSpace>", self._delete_selection_value)
|
|
153
|
+
self.parent.bind("<MouseWheel>", self._destroy_entry)
|
|
154
|
+
self.parent.bind("<Escape>", self._destroy_entry)
|
|
102
155
|
|
|
103
156
|
def _load_config_into_tree(self):
|
|
104
157
|
"""Load the configuration options into the treeview for display and editing."""
|
|
@@ -107,13 +160,16 @@ class ConfigEditorFrame(ttk.Frame):
|
|
|
107
160
|
continue
|
|
108
161
|
section_level = self.tree.insert("", "end", iid=section, text=section)
|
|
109
162
|
for config_option, value in config.items(section):
|
|
110
|
-
if section
|
|
111
|
-
|
|
112
|
-
self.tree.insert(section_level, "end", text=
|
|
163
|
+
if section not in ("User Settings", "App Settings"):
|
|
164
|
+
option_name = config.option_to_name(config_option, href=True)
|
|
165
|
+
self.tree.insert(section_level, "end", text=option_name, values=[value])
|
|
113
166
|
else:
|
|
114
|
-
option_name =
|
|
167
|
+
option_name = config.option_to_name(config_option)
|
|
115
168
|
self.tree.insert(section_level, "end", text=option_name, values=[value])
|
|
116
169
|
|
|
170
|
+
self.tree.focus("User Settings")
|
|
171
|
+
self.tree.selection_set("User Settings")
|
|
172
|
+
|
|
117
173
|
def reload_config_into_tree(self):
|
|
118
174
|
"""Reload the configuration options into the treeview for display and
|
|
119
175
|
editing.
|
|
@@ -146,14 +202,13 @@ class ConfigEditorFrame(ttk.Frame):
|
|
|
146
202
|
|
|
147
203
|
|
|
148
204
|
class ConfigEditorButtonFrame(ttk.Frame):
|
|
149
|
-
def __init__(self,
|
|
205
|
+
def __init__(self, editor_frame):
|
|
150
206
|
"""Initialize the button frame that contains buttons for saving the updated
|
|
151
207
|
configuration and adding custom items.
|
|
152
208
|
"""
|
|
153
|
-
super().__init__(
|
|
209
|
+
super().__init__(editor_frame, padding=10)
|
|
154
210
|
|
|
155
|
-
self.
|
|
156
|
-
self.tree = tree
|
|
211
|
+
self.editor_frame = editor_frame
|
|
157
212
|
self.custom_item_dialog = None
|
|
158
213
|
|
|
159
214
|
self._add_widgets()
|
|
@@ -162,9 +217,6 @@ class ConfigEditorButtonFrame(ttk.Frame):
|
|
|
162
217
|
"""Add buttons to the button frame for saving the configuration and adding
|
|
163
218
|
custom items.
|
|
164
219
|
"""
|
|
165
|
-
save_button = ttk.Button(self, text="Save", command=self._save_config)
|
|
166
|
-
save_button.pack(side="left", expand=True, padx=5)
|
|
167
|
-
|
|
168
220
|
reset_button = ttk.Button(self, text="Reset", command=self._reset_config)
|
|
169
221
|
reset_button.pack(side="left", expand=True, padx=5)
|
|
170
222
|
|
|
@@ -176,67 +228,47 @@ class ConfigEditorButtonFrame(ttk.Frame):
|
|
|
176
228
|
)
|
|
177
229
|
import_inventory_button.pack(side="left", expand=True, padx=5)
|
|
178
230
|
|
|
179
|
-
def _save_config(self):
|
|
180
|
-
"""Save the current configuration from the treeview to the config file."""
|
|
181
|
-
for child in self.tree.get_children():
|
|
182
|
-
for item in self.tree.get_children(child):
|
|
183
|
-
title_option = self.tree.item(item, "text")
|
|
184
|
-
config_option = title_option.lower().replace(" ", "_")
|
|
185
|
-
value = self.tree.item(item, "values")[0]
|
|
186
|
-
section = self.tree.parent(item)
|
|
187
|
-
section_name = self.tree.item(section, "text")
|
|
188
|
-
if section_name == "Custom Items":
|
|
189
|
-
# custom items are already saved upon creation (Saving them again would result in duplicates)
|
|
190
|
-
continue
|
|
191
|
-
config.set(section_name, config_option, value)
|
|
192
|
-
|
|
193
|
-
config.write_to_file()
|
|
194
|
-
if config.valid:
|
|
195
|
-
messagebox.showinfo("Config Saved", "The configuration has been saved successfully.")
|
|
196
|
-
else:
|
|
197
|
-
config.load()
|
|
198
|
-
self.parent.reload_config_into_tree()
|
|
199
|
-
messagebox.showerror(
|
|
200
|
-
"Config Error",
|
|
201
|
-
f"The configuration is invalid. ({config.last_error})",
|
|
202
|
-
)
|
|
203
|
-
|
|
204
231
|
def _reset_config(self):
|
|
205
232
|
"""Reset the configuration file to its default state."""
|
|
206
233
|
confirm = messagebox.askokcancel(
|
|
207
|
-
"Reset Config", "Are you sure you want to reset the configuration?"
|
|
234
|
+
"Reset Config", "Are you sure you want to reset the configuration?", parent=self
|
|
208
235
|
)
|
|
209
236
|
if confirm:
|
|
210
237
|
copy(CONFIG_FILE_BACKUP, CONFIG_FILE)
|
|
211
|
-
config.
|
|
212
|
-
self.
|
|
238
|
+
config.load_from_file()
|
|
239
|
+
self.editor_frame.reload_config_into_tree()
|
|
240
|
+
self.editor_frame.focus_set()
|
|
241
|
+
self.editor_frame.tree.focus_set()
|
|
213
242
|
|
|
214
243
|
def _add_custom_item(self):
|
|
215
244
|
"""Open a window to add a new custom item."""
|
|
216
|
-
custom_item_window = tk.Toplevel(self.
|
|
245
|
+
custom_item_window = tk.Toplevel(self.editor_frame)
|
|
217
246
|
custom_item_window.title(ADD_CUSTOM_ITEM_TITLE)
|
|
218
247
|
custom_item_window.geometry(ADD_CUSTOM_ITEM_SIZE)
|
|
248
|
+
custom_item_window.focus_set()
|
|
219
249
|
|
|
220
|
-
custom_item_frame = CustomItemFrame(custom_item_window, self.
|
|
250
|
+
custom_item_frame = CustomItemFrame(custom_item_window, self.editor_frame)
|
|
221
251
|
custom_item_frame.pack(expand=True, fill="both", padx=15, pady=15)
|
|
252
|
+
self.editor_frame.tree.focus_set()
|
|
222
253
|
|
|
223
254
|
def _import_steam_inventory(self):
|
|
224
255
|
"""Open a window to import the user's Steam inventory."""
|
|
225
|
-
steam_inventory_window = tk.Toplevel(self.
|
|
256
|
+
steam_inventory_window = tk.Toplevel(self.editor_frame)
|
|
226
257
|
steam_inventory_window.title(IMPORT_INVENTORY_TITLE)
|
|
227
258
|
steam_inventory_window.geometry(IMPORT_INVENTORY_SIZE)
|
|
259
|
+
steam_inventory_window.focus_set()
|
|
228
260
|
|
|
229
|
-
steam_inventory_frame = InventoryImportFrame(steam_inventory_window, self)
|
|
261
|
+
steam_inventory_frame = InventoryImportFrame(steam_inventory_window, self.editor_frame)
|
|
230
262
|
steam_inventory_frame.pack(expand=True, fill="both", padx=15, pady=15)
|
|
263
|
+
self.editor_frame.tree.focus_set()
|
|
231
264
|
|
|
232
265
|
|
|
233
266
|
class CustomItemFrame(ttk.Frame):
|
|
234
|
-
def __init__(self, parent,
|
|
267
|
+
def __init__(self, parent, editor_frame):
|
|
235
268
|
"""Initialize the custom item frame that allows users to add custom items."""
|
|
236
269
|
super().__init__(parent, style="Card.TFrame", padding=15)
|
|
237
270
|
self.parent = parent
|
|
238
|
-
self.
|
|
239
|
-
self.tree = tree
|
|
271
|
+
self.editor_frame = editor_frame
|
|
240
272
|
self._add_widgets()
|
|
241
273
|
|
|
242
274
|
def _add_widgets(self):
|
|
@@ -255,68 +287,108 @@ class CustomItemFrame(ttk.Frame):
|
|
|
255
287
|
command=lambda: self._add_custom_item(item_url_entry.get(), item_owned_entry.get()),
|
|
256
288
|
)
|
|
257
289
|
add_button.pack(pady=10)
|
|
290
|
+
self.parent.bind("<Return>", lambda _: add_button.invoke())
|
|
258
291
|
|
|
259
|
-
def _add_custom_item(self,
|
|
292
|
+
def _add_custom_item(self, item_href, item_owned):
|
|
260
293
|
"""Add a custom item to the configuration."""
|
|
261
|
-
if not
|
|
262
|
-
messagebox.showerror("Input Error", "All fields must be filled out.")
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if int(item_owned) < 0:
|
|
266
|
-
raise ValueError("Owned count must be a non-negative integer.")
|
|
267
|
-
except ValueError as error:
|
|
268
|
-
messagebox.showerror("Input Error", f"Invalid owned count: {error}")
|
|
294
|
+
if not item_href or not item_owned:
|
|
295
|
+
messagebox.showerror("Input Error", "All fields must be filled out.", parent=self)
|
|
296
|
+
self.editor_frame.focus_set()
|
|
297
|
+
self.parent.focus_set()
|
|
269
298
|
return
|
|
270
299
|
|
|
271
|
-
config.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
self.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
300
|
+
item_name = config.option_to_name(item_href, href=True)
|
|
301
|
+
|
|
302
|
+
# Make sure not to reinsert custom items that have already been added
|
|
303
|
+
for option in self.editor_frame.tree.get_children("Custom Items"):
|
|
304
|
+
option_name = self.editor_frame.tree.item(option, "text")
|
|
305
|
+
if option_name == item_name:
|
|
306
|
+
self.editor_frame.tree.set(option, column="#1", value=item_owned)
|
|
307
|
+
self.editor_frame.focus_set()
|
|
308
|
+
self.editor_frame.save_config()
|
|
309
|
+
self.parent.destroy()
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
self.editor_frame.tree.insert(
|
|
313
|
+
"Custom Items",
|
|
314
|
+
"end",
|
|
315
|
+
text=item_name,
|
|
316
|
+
values=[item_owned],
|
|
317
|
+
)
|
|
318
|
+
self.editor_frame.focus_set()
|
|
319
|
+
self.editor_frame.save_config()
|
|
320
|
+
self.parent.destroy()
|
|
283
321
|
|
|
284
322
|
|
|
285
323
|
class InventoryImportFrame(ttk.Frame):
|
|
286
324
|
# pylint: disable=too-many-instance-attributes
|
|
287
|
-
def __init__(self, parent,
|
|
325
|
+
def __init__(self, parent, editor_frame):
|
|
288
326
|
"""Initialize the inventory import frame that allows users to import their Steam
|
|
289
327
|
inventory.
|
|
290
328
|
"""
|
|
291
329
|
super().__init__(parent, style="Card.TFrame", padding=10)
|
|
292
330
|
self.parent = parent
|
|
293
|
-
self.
|
|
331
|
+
self.editor_frame = editor_frame
|
|
294
332
|
self._add_widgets()
|
|
295
333
|
|
|
296
334
|
def _add_widgets(self):
|
|
297
335
|
"""Add widgets to the inventory import frame."""
|
|
298
336
|
self._configure_checkboxes()
|
|
299
|
-
self.
|
|
300
|
-
self.
|
|
301
|
-
|
|
302
|
-
self.
|
|
337
|
+
self.storage_units_checkbox.pack(anchor="w", padx=20, pady=(15, 5))
|
|
338
|
+
self.regular_inventory_checkbox.pack(anchor="w", padx=20, pady=5)
|
|
339
|
+
|
|
340
|
+
self.import_cases_checkbox.pack(anchor="w", padx=20, pady=5)
|
|
341
|
+
self.import_sticker_capsules_checkbox.pack(anchor="w", padx=20, pady=5)
|
|
342
|
+
self.import_stickers_checkbox.pack(anchor="w", padx=20, pady=5)
|
|
343
|
+
self.import_others_checkbox.pack(anchor="w", padx=20, pady=5)
|
|
303
344
|
|
|
304
345
|
self._configure_entries()
|
|
305
|
-
self.user_name_label.pack(pady=10)
|
|
346
|
+
self.user_name_label.pack(pady=(20, 10))
|
|
306
347
|
self.user_name_entry.pack(fill="x", padx=50)
|
|
307
348
|
self.password_label.pack(pady=10)
|
|
308
349
|
self.password_entry.pack(fill="x", padx=50)
|
|
309
350
|
self.two_factor_label.pack(pady=10)
|
|
310
351
|
self.two_factor_entry.pack(fill="x", padx=50)
|
|
311
352
|
|
|
312
|
-
self.import_button = ttk.Button(
|
|
353
|
+
self.import_button = ttk.Button(
|
|
354
|
+
self, text="Import", command=self._import_inventory, state="disabled"
|
|
355
|
+
)
|
|
313
356
|
self.import_button.pack(pady=10)
|
|
357
|
+
self.parent.bind("<Return>", lambda _: self.import_button.invoke())
|
|
358
|
+
|
|
359
|
+
def form_complete(_):
|
|
360
|
+
if (
|
|
361
|
+
len(self.user_name_entry.get().strip()) > 0
|
|
362
|
+
and len(self.password_entry.get().strip()) > 0
|
|
363
|
+
and len(self.two_factor_entry.get().strip()) > 0
|
|
364
|
+
):
|
|
365
|
+
self.import_button.configure(state="normal")
|
|
366
|
+
else:
|
|
367
|
+
self.import_button.configure(state="disabled")
|
|
368
|
+
|
|
369
|
+
self.parent.bind("<KeyRelease>", form_complete)
|
|
314
370
|
|
|
315
371
|
def _configure_checkboxes(self):
|
|
316
372
|
# pylint: disable=attribute-defined-outside-init
|
|
317
373
|
"""Configure the checkboxes for selecting what to import from the Steam
|
|
318
374
|
inventory.
|
|
319
375
|
"""
|
|
376
|
+
self.regular_inventory_value = tk.BooleanVar(value=False)
|
|
377
|
+
self.regular_inventory_checkbox = ttk.Checkbutton(
|
|
378
|
+
self,
|
|
379
|
+
text="Regular Inventory",
|
|
380
|
+
variable=self.regular_inventory_value,
|
|
381
|
+
style="Switch.TCheckbutton",
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
self.storage_units_value = tk.BooleanVar(value=True)
|
|
385
|
+
self.storage_units_checkbox = ttk.Checkbutton(
|
|
386
|
+
self,
|
|
387
|
+
text="Storage Units",
|
|
388
|
+
variable=self.storage_units_value,
|
|
389
|
+
style="Switch.TCheckbutton",
|
|
390
|
+
)
|
|
391
|
+
|
|
320
392
|
self.import_cases_value = tk.BooleanVar(value=True)
|
|
321
393
|
self.import_cases_checkbox = ttk.Checkbutton(
|
|
322
394
|
self, text="Import Cases", variable=self.import_cases_value, style="Switch.TCheckbutton"
|
|
@@ -330,7 +402,7 @@ class InventoryImportFrame(ttk.Frame):
|
|
|
330
402
|
style="Switch.TCheckbutton",
|
|
331
403
|
)
|
|
332
404
|
|
|
333
|
-
self.import_stickers_value = tk.BooleanVar(value=
|
|
405
|
+
self.import_stickers_value = tk.BooleanVar(value=False)
|
|
334
406
|
self.import_stickers_checkbox = ttk.Checkbutton(
|
|
335
407
|
self,
|
|
336
408
|
text="Import Stickers",
|
|
@@ -338,7 +410,7 @@ class InventoryImportFrame(ttk.Frame):
|
|
|
338
410
|
style="Switch.TCheckbutton",
|
|
339
411
|
)
|
|
340
412
|
|
|
341
|
-
self.import_others_value = tk.BooleanVar(value=
|
|
413
|
+
self.import_others_value = tk.BooleanVar(value=False)
|
|
342
414
|
self.import_others_checkbox = ttk.Checkbutton(
|
|
343
415
|
self,
|
|
344
416
|
text="Import Other Items",
|
|
@@ -352,13 +424,13 @@ class InventoryImportFrame(ttk.Frame):
|
|
|
352
424
|
code.
|
|
353
425
|
"""
|
|
354
426
|
self.user_name_label = ttk.Label(self, text="Steam Username:")
|
|
355
|
-
self.user_name_entry = ttk.Entry(self)
|
|
427
|
+
self.user_name_entry = ttk.Entry(self, justify="center", font=("Helvetica", 11))
|
|
356
428
|
|
|
357
429
|
self.password_label = ttk.Label(self, text="Steam Password:")
|
|
358
|
-
self.password_entry = ttk.Entry(self, show="*")
|
|
430
|
+
self.password_entry = ttk.Entry(self, show="*", justify="center", font=("Helvetica", 11))
|
|
359
431
|
|
|
360
|
-
self.two_factor_label = ttk.Label(self, text="Steam Guard Code
|
|
361
|
-
self.two_factor_entry = ttk.Entry(self)
|
|
432
|
+
self.two_factor_label = ttk.Label(self, text="Steam Guard Code:")
|
|
433
|
+
self.two_factor_entry = ttk.Entry(self, justify="center", font=("Helvetica", 11))
|
|
362
434
|
|
|
363
435
|
def _import_inventory(self):
|
|
364
436
|
"""
|
|
@@ -367,6 +439,9 @@ class InventoryImportFrame(ttk.Frame):
|
|
|
367
439
|
This will also install the necessary npm packages if they are not already
|
|
368
440
|
installed.
|
|
369
441
|
"""
|
|
442
|
+
regular_inventory = self.regular_inventory_value.get()
|
|
443
|
+
storage_units = self.storage_units_value.get()
|
|
444
|
+
|
|
370
445
|
import_cases = self.import_cases_value.get()
|
|
371
446
|
import_sticker_capsules = self.import_sticker_capsules_value.get()
|
|
372
447
|
import_stickers = self.import_stickers_value.get()
|
|
@@ -380,6 +455,8 @@ class InventoryImportFrame(ttk.Frame):
|
|
|
380
455
|
[
|
|
381
456
|
INVENTORY_IMPORT_SCRIPT,
|
|
382
457
|
INVENTORY_IMPORT_FILE,
|
|
458
|
+
str(regular_inventory),
|
|
459
|
+
str(storage_units),
|
|
383
460
|
str(import_cases),
|
|
384
461
|
str(import_sticker_capsules),
|
|
385
462
|
str(import_stickers),
|
|
@@ -393,23 +470,25 @@ class InventoryImportFrame(ttk.Frame):
|
|
|
393
470
|
self.parent.destroy()
|
|
394
471
|
|
|
395
472
|
def _display_node_subprocess(self, node_cmd):
|
|
396
|
-
text_window = tk.Toplevel(self.
|
|
473
|
+
text_window = tk.Toplevel(self.editor_frame)
|
|
397
474
|
text_window.title(IMPORT_INVENTORY_PROCESS_TITLE)
|
|
398
475
|
text_window.geometry(IMPORT_INVENTORY_PROCESS_SIZE)
|
|
476
|
+
text_window.focus_set()
|
|
399
477
|
|
|
400
|
-
process_frame = InventoryImportProcessFrame(text_window)
|
|
478
|
+
process_frame = InventoryImportProcessFrame(text_window, self.editor_frame)
|
|
401
479
|
process_frame.pack(expand=True, fill="both", padx=15, pady=15)
|
|
402
|
-
process_frame.start(node_cmd)
|
|
403
480
|
process_frame.console.focus_set()
|
|
481
|
+
process_frame.start(node_cmd)
|
|
404
482
|
|
|
405
483
|
|
|
406
484
|
class InventoryImportProcessFrame(ttk.Frame):
|
|
407
485
|
# pylint: disable=attribute-defined-outside-init
|
|
408
486
|
# Source: https://stackoverflow.com/questions/27327886/issues-intercepting-subprocess-output-in-real-time
|
|
409
|
-
def __init__(self, parent):
|
|
487
|
+
def __init__(self, parent, editor_frame):
|
|
410
488
|
"""Initialize the frame that displays the output of the subprocess."""
|
|
411
489
|
super().__init__(parent)
|
|
412
490
|
self.parent = parent
|
|
491
|
+
self.editor_frame = editor_frame
|
|
413
492
|
self._add_widgets()
|
|
414
493
|
|
|
415
494
|
def _add_widgets(self):
|
|
@@ -438,14 +517,14 @@ class InventoryImportProcessFrame(ttk.Frame):
|
|
|
438
517
|
stdin=PIPE,
|
|
439
518
|
stderr=STDOUT,
|
|
440
519
|
text=True,
|
|
441
|
-
bufsize=1,
|
|
442
520
|
encoding="utf-8",
|
|
521
|
+
shell=True,
|
|
522
|
+
cwd=DATA_DIR,
|
|
443
523
|
)
|
|
444
524
|
self.queue = Queue()
|
|
445
525
|
self.thread = Thread(target=self._read_lines, args=(self.process, self.queue), daemon=True)
|
|
446
526
|
self.thread.start()
|
|
447
|
-
|
|
448
|
-
self.after(100, self._update_lines)
|
|
527
|
+
self._update_lines()
|
|
449
528
|
|
|
450
529
|
def _update_lines(self):
|
|
451
530
|
"""Update the text widget with lines from the subprocess output."""
|
|
@@ -459,7 +538,7 @@ class InventoryImportProcessFrame(ttk.Frame):
|
|
|
459
538
|
pass
|
|
460
539
|
|
|
461
540
|
if self.process.poll() is None or not self.queue.empty():
|
|
462
|
-
self.after(
|
|
541
|
+
self.after(50, self._update_lines)
|
|
463
542
|
else:
|
|
464
543
|
self._cleanup()
|
|
465
544
|
|
|
@@ -467,8 +546,10 @@ class InventoryImportProcessFrame(ttk.Frame):
|
|
|
467
546
|
"""Clean up the process and thread after completion and trigger a config update
|
|
468
547
|
from the newly written inventory file.
|
|
469
548
|
"""
|
|
470
|
-
config.read_from_inventory_file()
|
|
471
|
-
self.parent.master.master.reload_config_into_tree()
|
|
472
|
-
|
|
473
549
|
self.process.wait()
|
|
474
550
|
self.thread.join()
|
|
551
|
+
|
|
552
|
+
config.read_from_inventory_file()
|
|
553
|
+
self.editor_frame.reload_config_into_tree()
|
|
554
|
+
self.editor_frame.tree.focus_set()
|
|
555
|
+
self.parent.destroy()
|
cs2tracker/app/scraper_frame.py
CHANGED
|
@@ -4,6 +4,8 @@ from tkinter.filedialog import asksaveasfilename
|
|
|
4
4
|
|
|
5
5
|
from tksheet import Sheet
|
|
6
6
|
|
|
7
|
+
from cs2tracker.scraper.scraper import ParsingError
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class ScraperFrame(ttk.Frame):
|
|
9
11
|
def __init__(self, parent, scraper, sheet_size, dark_theme):
|
|
@@ -43,18 +45,36 @@ class ScraperFrame(ttk.Frame):
|
|
|
43
45
|
height=self.sheet_height,
|
|
44
46
|
width=self.sheet_width,
|
|
45
47
|
auto_resize_columns=150,
|
|
48
|
+
default_column_width=150,
|
|
46
49
|
sticky="nsew",
|
|
47
50
|
)
|
|
48
51
|
self.sheet.enable_bindings()
|
|
52
|
+
|
|
53
|
+
source_titles = []
|
|
54
|
+
for price_source in self.scraper.parser.SOURCES:
|
|
55
|
+
source_titles += [
|
|
56
|
+
f"{price_source.value.title()} (USD)",
|
|
57
|
+
f"{price_source.value.title()} Owned (USD)",
|
|
58
|
+
]
|
|
49
59
|
self.sheet.insert_row(
|
|
50
|
-
[
|
|
60
|
+
[
|
|
61
|
+
"Item Name",
|
|
62
|
+
"Item Owned",
|
|
63
|
+
]
|
|
64
|
+
+ source_titles
|
|
51
65
|
)
|
|
52
|
-
self.sheet.column_width(0, 220)
|
|
53
|
-
self.sheet.column_width(1, 20)
|
|
54
66
|
self.sheet.align_rows([0], "c")
|
|
55
|
-
self.sheet.align_columns([1, 2, 3], "c")
|
|
56
|
-
self.sheet.popup_menu_add_command("Save Sheet", self._save_sheet)
|
|
57
67
|
|
|
68
|
+
price_columns = list(range(2 * len(self.scraper.parser.SOURCES)))
|
|
69
|
+
price_columns = [1] + [column_index + 2 for column_index in price_columns]
|
|
70
|
+
self.sheet.align_columns(price_columns, "c")
|
|
71
|
+
self.sheet.column_width(0, 220)
|
|
72
|
+
|
|
73
|
+
required_window_width = 220 + 150 * len(price_columns)
|
|
74
|
+
if int(self.sheet_width) < required_window_width:
|
|
75
|
+
self.parent.geometry(f"{required_window_width}x{self.sheet_height}")
|
|
76
|
+
|
|
77
|
+
self.sheet.popup_menu_add_command("Save Sheet", self._save_sheet)
|
|
58
78
|
self.parent.bind("<Configure>", self._readjust_sheet_size_with_window_size)
|
|
59
79
|
|
|
60
80
|
def _save_sheet(self):
|
|
@@ -84,10 +104,7 @@ class ScraperFrame(ttk.Frame):
|
|
|
84
104
|
|
|
85
105
|
self.scraper.scrape_prices(update_sheet_callback)
|
|
86
106
|
|
|
87
|
-
row_heights = self.sheet.get_row_heights()
|
|
88
|
-
last_row_index = len(row_heights) - 1
|
|
89
|
-
self.sheet.align_rows(last_row_index, "c")
|
|
90
|
-
|
|
91
107
|
if self.scraper.error_stack:
|
|
92
108
|
last_error = self.scraper.error_stack[-1]
|
|
93
|
-
|
|
109
|
+
if not isinstance(last_error, ParsingError):
|
|
110
|
+
messagebox.showerror("An Error Occurred", f"{last_error.message}", parent=self)
|