from os import getenv, path from tkinter import N, NSEW, S, W, E, END, IntVar, StringVar, filedialog, messagebox, ttk from ttkthemes import ThemedTk from classes.custom.themed_frame import ThemedFrame from modules.theme_titlebar import theme_title_bar from modules.utils import configGet, configSet, get_string_mode, use_dark_mode from modules.logger import logger import requests import sv_ttk class FrameSettings(ThemedFrame): def __init__(self, master: ThemedTk, **kwargs) -> None: super().__init__(master, **kwargs) master.title("Configuration") self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=3) master.columnconfigure(1, weight=1) # Address self.address_label = ttk.Label(self, text="Address:") self.address_label.grid(column=0, row=0, sticky=W, padx=9, pady=9) self.address_entry = ttk.Entry(self) self.address_entry.grid(column=1, row=0, sticky=N+S+E+W, padx=9, pady=9) if configGet("address") is not None: self.address_entry.insert(0, configGet("address")) # ======= # API Key self.apikey_label = ttk.Label(self, text="API key:") self.apikey_label.grid(column=0, row=1, sticky=W, padx=9, pady=9) self.apikey_entry = ttk.Entry(self) self.apikey_entry.grid(column=1, row=1, sticky=N+S+E+W, padx=9, pady=9) if configGet("apikey") is not None: self.apikey_entry.insert(0, configGet("apikey")) # ======= # Self-signed if configGet("allow_self_signed") is not None: self.self_signed_check_bool = IntVar(value=int(configGet("allow_self_signed"))) else: self.self_signed_check_bool = IntVar() self.self_signed_check = ttk.Checkbutton(self, text="Allow self-signed certificates", variable=self.self_signed_check_bool) self.self_signed_check.grid(column=1, row=2, sticky=W, padx=9, pady=9) # =========== # Saves location self.saves_location_label = ttk.Label(self, text="Saves location:") self.saves_location_label.grid(column=0, row=3, sticky=W, padx=9, pady=9) self.saves_frame = ThemedFrame(self) self.saves_frame.grid(column=1, row=3, sticky=N+S+E+W, padx=9, pady=9) self.saves_frame.grid_columnconfigure(0, weight=1) self.saves_frame.grid_columnconfigure(1, weight=3) self.saves_location_entry = ttk.Entry(self.saves_frame, width=36) self.saves_location_entry.grid(column=0, row=0, sticky=NSEW) if configGet("saves_location") is not None: self.saves_location_entry.insert(0, configGet("saves_location")) self.saves_location_button = ttk.Button(self.saves_frame, text="Browse", width=6, command=lambda:self.select_location(self.saves_location_entry)) self.saves_location_button.grid(column=1, row=0, sticky=E) # ============== # Saves location self.saves_location_label = ttk.Label(self, text="Color theme:") self.saves_location_label.grid(column=0, row=5, sticky=W, padx=9, pady=9) if configGet("dark_mode_auto") is True: self.default_theme = "Auto " else: self.default_theme = "Dark " if configGet("dark_mode") is True else "Light " self.chosen_theme = StringVar() self.themes = ("Auto ", "Light ", "Dark ") self.saves_location_button = ttk.OptionMenu(self, self.chosen_theme, self.default_theme, *self.themes, direction="below", command=self.change_theme) self.saves_location_button.grid(column=1, row=5, sticky=W, padx=9, pady=9) # ============== self.buttons_frame = ThemedFrame(self) self.buttons_frame.grid(column=0, columnspan=2, sticky=NSEW, padx=9, pady=9) self.buttons_frame.grid_columnconfigure(0, weight=1) self.validate_button = ttk.Button(self.buttons_frame, text="Validate", width=11, command=self.validate_configuration) self.validate_button.grid(column=0, row=0, sticky=E, padx=9) self.save_button = ttk.Button(self.buttons_frame, text="Save", style="Accent.TButton", width=11, state="disabled", command=self.save_configuration) self.save_button.grid(column=1, row=0, sticky=E) def change_theme(self, *args): if self.chosen_theme.get().strip().lower() == "auto": self.chosen_theme_real = "dark" if use_dark_mode(no_config=True) is True else "light" else: self.chosen_theme_real = self.chosen_theme.get().strip().lower() theme_title_bar(self.master, self.chosen_theme_real) def select_location(self, entry: ttk.Entry): if path.exists(path.join(str(getenv("APPDATA")), "Stardew Valley")): self.path_start = path.join(str(getenv("APPDATA")), "Stardew Valley") elif path.exists(path.join(path.expanduser("~"), ".config", "Stardew Valley")): self.path_start = path.join(path.expanduser("~"), ".config", "Stardew Valley") else: self.path_start = None self.path_dir = filedialog.askdirectory(initialdir=self.path_start, title="Select Stardew Valley Saves folder") if self.path_dir != "": entry.delete(0, END) entry.insert(0, self.path_dir) def save_configuration(self): if len(self.address_entry.get()) > 0: if self.address_entry.get().endswith("/"): self.address_text = self.address_entry.get()[:-1] else: self.address_text = self.address_entry.get() else: self.address_text = None configSet(["address"], self.address_text) configSet(["apikey"], self.apikey_entry.get()) configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) configSet(["saves_location"], self.saves_location_entry.get()) if self.chosen_theme.get().strip().lower() == "auto": configSet(["dark_mode_auto"], True) else: configSet(["dark_mode_auto"], False) if self.chosen_theme.get().strip().lower() == "dark": configSet(["dark_mode"], True) else: configSet(["dark_mode"], False) # messagebox.showinfo(title="Configuration saved", message="Your client's configuration has been saved") self.save_button.state(["disabled"]) self.master.item_saves.state(["!disabled"]) self.master.item_devices.state(["!disabled"]) def validate_configuration(self): if len(self.address_entry.get()) > 0: if self.address_entry.get().endswith("/"): self.address_text = self.address_entry.get() self.address_entry.delete(0, END) self.address_entry.insert(0, self.address_text[:-1]) try: requests.get(self.address_entry.get()+"/check", verify=not bool(self.self_signed_check_bool.get())) except (requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema, requests.exceptions.MissingSchema) as exp: logger.error(f"Could not validate '{self.address_entry.get()}' due to {exp}") messagebox.showerror(title="Invalid address", message="Address entered does not seem to be correct one. Please provide API address starting with http:// or https:// \n\nFor example:\n- https://your-api.com:8043\n- https://your-api.com") return except (requests.exceptions.SSLError): logger.error(f"SSL certificate of '{self.address_entry.get()}' does not seem to be valid or is self-signed") messagebox.showerror(title="Invalid SSL", message="SSL certificate seems to be invalid or self-signed and thus won't be trusted. You can overwrite this by checking 'Allow self-signed certificates'.") return except Exception as exp: logger.error(f"Could not reach '{self.address_entry.get()}' due to {exp}") messagebox.showerror(title="Connection error", message="Address entered does not seem to be reachable. Please make sure you've entered the correct API address.") return response_apikey = requests.get(self.address_entry.get()+"/apikey", headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) if response_apikey.status_code != 200: logger.error(f"API key seems to be invalid. API returned {response_apikey.status_code} as a status code") messagebox.showerror(title="Invalid apikey", message="API key provided does not seem to be valid. Please check for any mistakes and if none found - take a look at the docs to learn how to generate one.") return if not path.exists(self.saves_location_entry.get()): logger.error(f"Path {self.saves_location_entry.get()} does not seem to exist") messagebox.showerror(title="Location error", message="Saves folder seems to be invalid. Please provide a valid directory path where Stardew Valley's save files (and folders) are stored.") return configSet(["address"], self.address_entry.get()) configSet(["apikey"], self.apikey_entry.get()) configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) configSet(["saves_location"], self.saves_location_entry.get()) self.save_button.state(["!disabled"]) messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!")