Initial commit

This commit is contained in:
Profitroll
2023-01-22 00:22:08 +01:00
commit 46d02b4de5
13 changed files with 892 additions and 0 deletions

166
classes/app.py Normal file
View File

@@ -0,0 +1,166 @@
import requests
import sv_ttk
from os import path
from tkinter import ttk, messagebox, N, S, W, E, NW
from ttkthemes import ThemedTk
from modules.dark_titlebar import dark_title_bar
from modules.utils import configGet, use_dark_mode
from classes.window_config import WindowConfig
from classes.frames import FrameDevices, FrameSaves, FrameSettings
class App(ThemedTk):
def __init__(self):
super().__init__()
self.window_width = 650
self.window_height = 400
self.screen_width = self.winfo_screenwidth()
self.screen_height = self.winfo_screenheight()
self.center_x = int(self.screen_width/2 - self.window_width / 2)
self.center_y = int(self.screen_height/2 - self.window_height / 2)
self.title("Stardew Sync")
self.geometry(f'{self.window_width}x{self.window_height}+{self.center_x}+{self.center_y}')
self.iconbitmap(path.join("assets", "favicon.ico"))
self.focus_set()
sv_ttk.init_theme(self)
if use_dark_mode():
self.configure(background="#1c1c1c")
dark_title_bar(self, mode="dark")
sv_ttk.set_theme("dark")
else:
sv_ttk.set_theme("light")
self.draw_main()
def try_connecting(self):
try:
requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")})
# messagebox.showinfo(title="Connection succeeded", message="Server is reachable, apikey is valid, so your client is now ready to be used!")
self.destroy_everything()
except:
messagebox.showerror(title="Configuration error", message="Your client configuration is incorrect. Set it up and try again.")
self.destroy_everything()
def verify_connecting(self):
try:
if requests.get(configGet("address")+"/check").status_code == 200:
return
else:
raise requests.exceptions.HTTPError()
except:
self.verify_answer = messagebox.askyesno(title="Connection failed", message="You might have no connection to the server or your api key could expire. Reconfigure the client?\n\nYes to reconfigure and No to retry connecting")
if self.verify_answer is True:
window = WindowConfig(self)
window.grab_set()
if hasattr(self, "setup_button"):
self.setup_button.destroy()
if hasattr(self, "setup_label"):
self.setup_label.destroy()
ttk.Label(self, text="Client configured, try to connect").pack(expand=True)
ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True)
return False
else:
self.destroy_everything()
return True
def verify_authorization(self):
try:
if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}).status_code == 200:
return
else:
self.verify_answer = messagebox.askyesno(title="Authorization failed", message="Your apikey could have expired or is invalid. Reconfigure the client?\n\nYes to reconfigure and No to retry connecting")
if self.verify_answer is True:
window = WindowConfig(self)
window.grab_set()
if hasattr(self, "setup_button"):
self.setup_button.destroy()
if hasattr(self, "setup_label"):
self.setup_label.destroy()
ttk.Label(self, text="Client configured, try to connect").pack(expand=True)
ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True)
return False
else:
self.destroy_everything()
return True
except:
self.verify_answer = messagebox.askyesno(title="Connection failed", message="You might have no connection to the server or your api key could expire. Reconfigure the client?\n\nYes to reconfigure and No to retry connecting")
if self.verify_answer is True:
window = WindowConfig(self)
window.grab_set()
ttk.Label(self, text="Client configured, try to connect").pack(expand=True)
ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True)
else:
self.destroy_everything()
def frame_saves(self):
self.frame_saves_object = FrameSaves(self)
self.frame_saves_object.grid(column=1, row=0, sticky=N+S+W+E)
return
def frame_devices(self):
self.frame_devices_object = FrameDevices(self)
self.frame_devices_object.grid(column=1, row=0, sticky=N+S+W+E)
return
def frame_settings(self):
self.frame_settings_object = FrameSettings(self)
self.frame_settings_object.grid(column=1, row=0, sticky=N+S+W+E)
return
def draw_main(self):
if configGet("address") is None or configGet("apikey") is None:
self.setup_label = ttk.Label(self, text="Your client seems to be unconfigured")
self.setup_label.pack(expand=True)
self.setup_button = ttk.Button(self, text="Set up", style="Accent.TButton", width=10, command=self.open_window_config)
self.setup_button.pack(expand=True)
if self.verify_connecting() is False:
return
if self.verify_authorization() is False:
return
self.frame_sidebar = ttk.Frame(self)
self.frame_sidebar.grid(column=0, row=0, sticky=NW)
self.item_saves = ttk.Button(self.frame_sidebar, text="Saves", width=10, command=self.frame_saves)
self.item_saves.grid(column=0, row=0, sticky=W, padx=9, pady=9)
self.item_devices = ttk.Button(self.frame_sidebar, text="Devices", width=10, command=self.frame_devices)
self.item_devices.grid(column=0, row=1, sticky=W, padx=9, pady=3)
self.item_settings = ttk.Button(self.frame_sidebar, text="Settings", width=10, command=self.frame_settings)
self.item_settings.grid(column=0, row=2, sticky=W+S, padx=9, pady=9)
self.item_test = ttk.Button(self.frame_sidebar, text="Test", width=10, command=lambda:sv_ttk.set_theme("light"))
self.item_test.grid(column=0, row=3, sticky=W+S, padx=9, pady=9)
self.frame_saves()
def destroy_everything(self):
for widget in self.winfo_children():
widget.destroy()
self.draw_main()
def open_window_config(self):
window = WindowConfig(self)
window.grab_set()
self.setup_button.destroy()
self.setup_label.destroy()
ttk.Label(self, text="Client configured, try to connect").pack(expand=True)
ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True)

56
classes/frames.py Normal file
View File

@@ -0,0 +1,56 @@
from tkinter import ttk, NS, EW
from ttkthemes import ThemedTk
class FrameSaves(ttk.Frame):
def __init__(self, master: ThemedTk, **kwargs) -> None:
super().__init__(master, **kwargs)
master.title("Saves - Stardew Sync")
self["borderwidth"] = 2
self["relief"] = "solid"
for btn in range(0, 10):
ttk.Button(self, text="SAVES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW)
self.scrollbar = ttk.Scrollbar(self, orient="vertical")
self.scrollbar.grid(column=1, row=0, sticky=NS)
class FrameDevices(ttk.Frame):
def __init__(self, master: ThemedTk, **kwargs) -> None:
super().__init__(master, **kwargs)
master.title("Devices - Stardew Sync")
self["borderwidth"] = 2
self["relief"] = "solid"
for btn in range(0, 10):
ttk.Button(self, text="DEVICES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW)
self.scrollbar = ttk.Scrollbar(self, orient="vertical")
self.scrollbar.grid(column=1, row=0, sticky=NS)
class FrameSettings(ttk.Frame):
def __init__(self, master: ThemedTk, **kwargs) -> None:
super().__init__(master, **kwargs)
master.title("Settings - Stardew Sync")
self["borderwidth"] = 2
self["relief"] = "solid"
for btn in range(0, 10):
ttk.Button(self, text="SETTINGS "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW)
self.scrollbar = ttk.Scrollbar(self, orient="vertical")
self.scrollbar.grid(column=1, row=0, sticky=NS)

110
classes/window_config.py Normal file
View File

@@ -0,0 +1,110 @@
import time
import requests
from os import path
from tkinter import ttk, messagebox, IntVar, Toplevel, N, S, W, E, END
from modules.dark_titlebar import dark_title_bar
from modules.utils import configGet, configSet, use_dark_mode
from modules.logger import logger
class WindowConfig(Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.window_width = 430
self.window_height = 247
self.screen_width = self.winfo_screenwidth()
self.screen_height = self.winfo_screenheight()
self.center_x = int(self.screen_width/2 - self.window_width / 2)
self.center_y = int(self.screen_height/2 - self.window_height / 2)
self.title("Configuration")
self.geometry(f'{self.window_width}x{self.window_height}+{self.center_x}+{self.center_y}')
self.resizable(False, False)
self.iconbitmap(path.join("assets", "favicon.ico"))
self.focus_set()
if use_dark_mode() is True:
dark_title_bar(self, "dark")
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=3)
# 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_location_button = ttk.Button(self, text="Browse", width=6)
self.saves_location_button.grid(column=1, row=3, sticky=W, padx=9, pady=9)
# ==============
self.login_button = ttk.Button(self, text="Log in", style="Accent.TButton", width=10, command=self.validate_configuration)
self.login_button.grid(column=1, row=4, sticky=E, padx=9, pady=9)
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
configSet(["address"], self.address_entry.get())
configSet(["apikey"], self.apikey_entry.get())
configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get()))
messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!")
self.destroy()