Skip to content

Commit

Permalink
Merge pull request #2 from nekooooooooo/gui-customtkinter
Browse files Browse the repository at this point in the history
  • Loading branch information
nekooooooooo committed Apr 4, 2023
2 parents e13ce88 + 018ee71 commit bacdf07
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 68 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ tempCodeRunnerFile.python
testcode.py
.vscode
*.json
__pycache__
__pycache__
old.py
utils/testing.py
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
customtkinter==5.1.2
Pillow==9.5.0
tqdm==4.64.1
74 changes: 12 additions & 62 deletions steam_wishlist.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,17 @@
import time
import os
from utils.item_filters import filter_games
from utils.combinations import random_combination, print_combination
from utils.input import get_input
from utils.wishlist_data import get_wishlist_from_steam, get_wishlist_from_file
from utils.constants import CURRENCY
from utils.ui import WishlistGeneratorUI

def main():

print("Please choose an option:")
print("[1] Use File")
print("[2] Use Steam ID/Steam URL")
while True:
option = input("Enter your choice (1 or 2): ")
if option == "1":
# Load wishlist from file
data = get_wishlist_from_file("wishlist.json")
break
elif option == "2":
# Load wishlist data from steam
while True:
input_id = input("Enter steam id: ")
data = get_wishlist_from_steam(input_id)
if data['data']:
break

print(f"Sorry, the specified ID could not be found: {input_id}")
break

print("Invalid option. Please enter 1 or 2.")

# print(f"Currency: {CURRENCY}")

# Get user inputs for budget, minimum spend, max game price, and number of combinations
budget = get_input("Enter your budget: ", float, min_=1)
min_spend = get_input("Enter your minimum spend: ",float, min_=1, max_=budget)
max_game_price = get_input("Enter max game price: ", int, min_=1, max_=budget)
num_combinations = get_input("Enter the number of combinations to generate (up to 5): ", int, min_=1, max_=100)
discount_only = input("Only discounted games? (Y/N): ").lower() == "y"
# exclusions = ['app/397540', 'app/349040']
exclusions = []

# Filter games from wishlist data based on budget and criteria for games
games = filter_games(data, budget, max_game_price, exclusions, discount_only)

while True:
# Clear console and generate random game combinations based on user inputs
os.system('cls')
print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n")
for i in range(num_combinations):
combo, total_price = random_combination(games, budget, min_spend)
if num_combinations != 1:
print(f"Combination {i + 1}")
print_combination(combo, total_price)

# Prompt user to generate more combinations or exit the program
print(f"\nPress any key to generate {num_combinations} {'combinations' if num_combinations != 1 else 'combination'} again, or")
user_input = input("Enter the number of combinations to generate (up to 5), or 'e' to exit: ")
if user_input.lower() == "e":
break
elif user_input.isnumeric() and 1 <= int(user_input) <= 5:
num_combinations = int(user_input)

app = WishlistGeneratorUI()
# set the window size
width = app._current_width
height = app._current_height
x = (app.winfo_screenwidth() // 2) - (width // 2) # calculate the x coordinate for the window
y = (app.winfo_screenheight() // 2) - (height // 2) # calculate the y coordinate for the window

# set the window position
app.geometry(f"{width}x{height}+{x}+{y}")
app.mainloop()


if __name__ == "__main__":
# start_time = time.time()
Expand Down
4 changes: 2 additions & 2 deletions utils/item_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def is_game(item):
def is_excluded(item, exclusions):
return item['gameid'][1] in exclusions

def filter_games(data, budget, max_game_price, exclusions, discount_only=False):
def filter_games(data, budget, max_game_price, exclusions, discount_only=False, game_only=False):
games = [{
'title': item['title'],
'price': get_price(item),
Expand All @@ -28,6 +28,6 @@ def filter_games(data, budget, max_game_price, exclusions, discount_only=False):
within_budget(item, budget) and
under_max_price(item, max_game_price) and
(not discount_only or has_discount(item)) and
is_game(item) and
(not game_only or is_game(item)) and
not is_excluded(item, exclusions)]
return games
228 changes: 228 additions & 0 deletions utils/ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import customtkinter as ctk
from tkinter import ttk
from customtkinter import filedialog
from tkinter import messagebox
from utils.item_filters import filter_games
from utils.combinations import random_combination
from utils.wishlist_data import get_wishlist_from_steam, get_wishlist_from_file

ctk.set_appearance_mode("system") # Modes: "System" (standard), "Dark", "Light"
ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue"

MIN_WIDTH = 750
MIN_HEIGHT = 600

class MethodTab(ctk.CTkTabview):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)

self.add("SteamID")
self.add("File")
self.tab("File").grid_columnconfigure((0, 2), weight=0)
self.tab("File").grid_columnconfigure(1, weight=1)
self.tab("SteamID").grid_columnconfigure((0, 2), weight=0)
self.tab("SteamID").grid_columnconfigure(1, weight=1)

self.filepath_label = ctk.CTkLabel(self.tab("File"), text="File Path:")
self.filepath_label.grid(row=0, column=0, padx=10, pady=10, sticky="ew")

self.filepath_entry = ctk.CTkEntry(self.tab("File"))
self.filepath_entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")

self.file_button = ctk.CTkButton(self.tab("File"), text="Select File", command=self.select_file)
self.file_button.grid(row=0, column=2, padx=10, pady=10, sticky="nsew")

self.steamid_label = ctk.CTkLabel(self.tab("SteamID"), text="SteamID:")
self.steamid_label.grid(row=1, column=0, padx=10, pady=10, sticky="ew")

self.steamid_entry = ctk.CTkEntry(self.tab("SteamID"), placeholder_text="SteamID32, URL, or Custom URL")
self.steamid_entry.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")


def select_file(self):
# Open a file dialog to select a file
filepath = filedialog.askopenfilename()

# Update the file path entry widget with the selected file path
self.filepath_entry.delete(0, ctk.END)
self.filepath_entry.insert(0, filepath)

class InputsFrame(ctk.CTkFrame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)

self.grid_columnconfigure((0,1,2), weight=1)
vcmd = (self.register(self.callback))

# add widgets onto the frame...
self.budget_label = ctk.CTkLabel(self, text="Budget")
self.budget_label.grid(row=0, column=0, padx=10, pady=0, sticky="ew")
self.budget_entry = ctk.CTkEntry(self, validate='all', validatecommand=(vcmd, '%P'))
self.budget_entry.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nsew")

self.minimum_label = ctk.CTkLabel(self, text="Minimum Spend")
self.minimum_label.grid(row=0, column=1, padx=10, pady=0, sticky="ew")
self.minimum_entry = ctk.CTkEntry(self, validate='all', validatecommand=(vcmd, '%P'))
self.minimum_entry.grid(row=1, column=1, padx=10, pady=(0, 10), sticky="nsew")

self.max_price_entry = ctk.CTkLabel(self, text="Maximum Game Price")
self.max_price_entry.grid(row=0, column=2, padx=10, pady=0, sticky="ew")
self.max_price_entry = ctk.CTkEntry(self, validate='all', validatecommand=(vcmd, '%P'))
self.max_price_entry.grid(row=1, column=2, padx=10, pady=(0, 10), sticky="nsew")

self.game_only_switch = ctk.CTkSwitch(self, text="Game only?")
self.game_only_switch.grid(row=2, column=0, padx=10, pady=(10, 0))

self.discount_only_switch = ctk.CTkSwitch(self, text="Discounted only?")
self.discount_only_switch.grid(row=2, column=1, padx=10, pady=(10, 0))

self.exclusions_entry = ctk.CTkLabel(self, text="Exclusions", anchor="w")
self.exclusions_entry.grid(row=3, column=0, padx=10, pady=0, sticky="ew")
self.exclusions_entry = ctk.CTkEntry(self, placeholder_text="Use game app id, separate with comma")
self.exclusions_entry.grid(row=4, column=0, columnspan=3, padx=10, pady=(0, 10), sticky="nsew")


def callback(self, P):
if str.isdigit(P) or P == "":
return True
else:
return False

class OutputsFrame(ctk.CTkScrollableFrame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)

self.style = ttk.Style()
self.style.theme_use("default")
self.style.configure("wishlist.Treeview",
highlightthickness=0,
bd=0,
rowheight=35,
fieldbackground="#212121",
background="#212121",
foreground="white"
)
self.style.layout("wishlist.Treeview", [('wishlist.Treeview.treearea', {'sticky': 'nswe'})])
self.style.map('wishlist.Treeview', background=[('selected', '#2463AA')])

self.columns = ['title', 'discount', 'price']
self.output_tree = ttk.Treeview(self, columns=self.columns, show='tree', style="wishlist.Treeview")
self.output_tree.tag_configure('odd', background='#212121')
self.output_tree.tag_configure('even', background='#181818')

# Define the columns
self.output_tree.heading('title', text='Title')
self.output_tree.heading('discount', text='Discount', anchor="e")
self.output_tree.heading('price', text='Price', anchor="e")

self.output_tree.column("#0", width=0, stretch="no")
self.output_tree.column("title", width=200)
self.output_tree.column("discount", width=25, anchor="e")
self.output_tree.column("price", width=30, anchor="e")


class WishlistGeneratorUI(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Steam Wishlist Generator")
self.geometry(f"{MIN_WIDTH}x{MIN_HEIGHT}")
self.minsize(MIN_WIDTH, MIN_HEIGHT)
# configure grid layout (4x4)
self.grid_columnconfigure((0, 2), weight=0)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure((0, 1, 2), weight=0)
self.grid_rowconfigure(3, weight=1)

self.temp = ""
self.data = []

self.method_tab = MethodTab(self, width=250, height=0)
self.method_tab.grid(row=0, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew")

self.input_frame = InputsFrame(self)
self.input_frame.grid(row=1, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew")

self.get_button = ctk.CTkButton(self, text="Get", command=self.get_button_callback)
self.get_button.grid(row=2, column=0, columnspan=5, padx=10, pady=10, sticky="nsew")

self.output_frame = OutputsFrame(self, border_width=0)
self.output_frame.grid(row=3, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew")

self.total_label = ctk.CTkLabel(self, text="Total: ", anchor="e")
self.total_label.grid(row=4, column=4, padx=10, pady=0, sticky="ew")

# self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle)
# self.theme_button.grid(row=6, column=0, padx=10, pady=10)

def get_button_callback(self):

if self.method_tab.get() == "File":
entry = self.method_tab.filepath_entry
else:
entry = self.method_tab.steamid_entry

if not self.data or self.temp != entry.get():
if self.method_tab.get() == "File":
self.data = get_wishlist_from_file(entry.get())
else:
self.data = get_wishlist_from_steam(entry.get())
self.temp = entry.get()
if not self.data['data']:
self.data = []
messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {entry.get()}")

budget = self.input_frame.budget_entry.get()
min_spend = self.input_frame.minimum_entry.get()
max_game_price = self.input_frame.max_price_entry.get()
game_only = bool(self.input_frame.game_only_switch.get())
discount_only = bool(self.input_frame.discount_only_switch.get())
exclusions = self.input_frame.exclusions_entry.get()
format_exclusions = self.format_app_ids(exclusions) if exclusions else []

if not budget:
return messagebox.showerror("Input Error", "Budget can't be empty!")

if not min_spend:
return messagebox.showerror("Input Error", "Minimum Spend can't be empty!")

if not max_game_price:
return messagebox.showerror("Input Error", "Max Price can't be empty!")

budget, min_spend, max_game_price = map(int, [budget, min_spend, max_game_price])

if min_spend > budget or max_game_price > budget:
return messagebox.showerror("Input Error", "Minimum Spend or Max Price can't be more than budget!")

games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only, game_only)
combo, total_price = random_combination(games, budget, min_spend)

tree = self.output_frame.output_tree

tree.delete(*tree.get_children())

for i, item in enumerate(combo):
tag = "even" if (i + 1) % 2 == 0 else "odd"
values = (
item['title'],
f"{item['discount']}%",
item['price']
)
tree.insert('', 'end', values=values, tags=(tag,))

total_height = len(tree.get_children())
tree.configure(height=total_height)

tree.pack(fill='both', expand=True)
self.total_label.configure(text=f"Total: {total_price:.2f}")

# print(f"{title:<67} {f'-{discount}%':<9} {CURRENCY}{price:>10,.2f}")
# print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n")
# combo, total_price = random_combination(games, budget, min_spend)
# print_combination(combo, total_price)

def format_app_ids(self, exclusions):
return ['app/' + x.strip() for x in exclusions.split(',')]

def theme_toggle(self):
appearance_mode = "light" if ctk.get_appearance_mode() == "Dark" else "dark"
ctk.set_appearance_mode(appearance_mode)
7 changes: 4 additions & 3 deletions utils/wishlist_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ def get_wishlist_from_steam(input_id):
if len(data) == 0 or data.get('success') == 2:
break

for _, games in tqdm(data.items(), desc=f"Extracting wishlist data page {page_num + 1}"):
for key, games in tqdm(data.items(), desc=f"Extracting wishlist data page {page_num + 1}"):
if games['subs']:
# extract relevant data from the game data
app_id = f"app/{games['subs'][0]['id']}"
app_id = f"app/{key}"
title = games['name'].replace("'", "\'")
game_type = games['type']
price = games['subs'][0]['price']
Expand All @@ -50,7 +50,8 @@ def get_wishlist_from_steam(input_id):
"title": title,
"type": game_type,
"price": price,
"discount": discount
"discount": discount,
"capsule": games['capsule']
}

# add it to the result list
Expand Down

0 comments on commit bacdf07

Please sign in to comment.