From c152e511a011acd1ccb87caca3eecd90d7302137 Mon Sep 17 00:00:00 2001 From: jimbohimself Date: Sat, 15 Feb 2025 00:26:30 -0800 Subject: [PATCH] __main__ --- __main__.py | 455 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 456 insertions(+) create mode 100644 __main__.py create mode 100644 requirements.txt diff --git a/__main__.py b/__main__.py new file mode 100644 index 0000000..b295943 --- /dev/null +++ b/__main__.py @@ -0,0 +1,455 @@ +import tkinter as tk +from tkinter import ttk +from tkinter import messagebox, font +from ttkthemes import ThemedTk +from tkinter.scrolledtext import ScrolledText +import json +import webbrowser +from subprocess import Popen, PIPE +from pathlib import Path +import threading +from sys import platform +import requests +from hashlib import sha256 +from os import listdir +from os.path import isfile, join, exists +import shutil + +logarr = [] +with open('lang.json') as f: lang=json.load(f) + +class menus: + def clientmenu(file, mainframe): + for child in mainframe.winfo_children(): child.destroy() + checkclient = str(helpers.check4client(helpers.getclientpath(file))) + clientdetect = ttk.Label(mainframe, text=f"Was Bootstrapper found: {checkclient}", anchor=tk.W) + clientdetect.grid(row=0, sticky=tk.W) + #not threading this prob stalls the gui, but it's not like the server, where I actually need it to run in the background; in otherwords, I do not care :) + dlldownload = ttk.Button(mainframe, text="Download Latest Bootstrapper", command=lambda: helpers.downloadlatestdll(helpers.getclientpath(file))) + dlldownload.grid(row=1, sticky=tk.W) + + #menu for pluto scripts; running them is not currently implemented + def plutomenu(file, mainframe): + for child in mainframe.winfo_children(): child.destroy() + openwfpath = helpers.getclientpath(file) + "/OpenWF/scripts/" + def openfolder(folder): + Popen(['xdg-open', folder]) + + onlyfiles = [f for f in listdir(openwfpath) if isfile(join(openwfpath, f))] + i = 0 + for file in onlyfiles: + filelabel = ttk.Label(mainframe, text=file, anchor=tk.W) + filelabel.grid(row = i, column = 0, sticky = tk.W, pady = 2) + plutorun = ttk.Button(mainframe, text="Running not implemented yet", command=lambda: runpluto(file)) + plutorun.grid(row = i, column = 1, sticky = tk.W, pady = 2, padx=10) + i += 1 + + # Input would be a json file + def settingsmenu(file, mainframe, vcmd): + + for child in mainframe.winfo_children(): child.destroy() + if Path(file).is_file(): + with open(file) as f: + settings = json.load(f) + i = 0 + frameunder = ttk.Frame(mainframe) + tempsettings = {} + print(settings) + for key in settings.keys(): + if key in lang.keys(): + item = ttk.Label(frameunder, text=(lang[key] + " : ")) + else: + item = ttk.Label(frameunder, text=(key + " : ")) + item.grid(row = i, column = 0, sticky = tk.W, pady = 2) + print (type(settings[key])) + if type(settings[key]) is str: + value = ttk.Entry(frameunder, width=50) + value.insert(0,settings[key]) + + elif type(settings[key]) not in [str, bool, int, dict]: + value = ttk.Label(frameunder, text="N/A") + elif type(settings[key]) is bool: + boeol = tk.StringVar() + value = ttk.OptionMenu(frameunder, boeol, settings[key], True, False) + + elif type(settings[key]) is int: + value = ttk.Entry(frameunder, width=50, validate='key', validatecommand=(vcmd, '%P')) + value.insert(0,settings[key]) + #stupid ass nested dicts eat my whole balls + elif type(settings[key]) is dict: + value = {} + for k in settings[key].keys(): + value[k] ={} + if type(settings[key][k]) is str: + value[k]["value"] = ttk.Entry(frameunder, width=50) + value[k]["value"].insert(0,settings[key][k]) + elif type(settings[key][k]) not in [str, bool, int]: + value[k]["value"] = ttk.Label(frameunder, text="N/A") + elif type(settings[key][k]) is bool: + boeol = tk.BooleanVar() + value[k]["value"] = ttk.OptionMenu(frameunder, boeol, settings[key][k], True, False) + boeol.set(settings[key][k]) + value[k]["valueop"] = boeol + elif type(settings[key][k]) is int: + value[k]["value"] = ttk.Entry(frameunder, width=50, validate='key', validatecommand=(vcmd, '%P')) + value[k]["value"].insert(0,settings[key][k]) + value[k]["type"] = type(settings[key][k]) + + + if type(value) is not dict: + value.grid(row = i, column = 1, sticky = tk.W, pady = 2) + i += 1 + else: + i+=1 + sp1=ttk.Separator(mainframe,orient='horizontal') + sp1.grid(row=i,column=1,sticky='ew') + i+=1 + for k in value.keys(): + print(k) + dictlabel = ttk.Label(frameunder, text=(k + " : ")) + dictlabel.grid(row = i, column = 0, sticky = tk.E, pady = 2) + value[k]["value"].grid(row = i, column = 1, sticky = tk.W, pady = 2) + i+=1 + ttk.Separator(mainframe, orient=tk.HORIZONTAL).grid(row=i) + i+=1 + if type(settings[key]) is bool: + tempsettings[key] = { + "value" : boeol, + "type" : type(settings[key]) + } + else: + tempsettings[key] = { + "value" : value, + "type" : type(settings[key]) + } + + save = ttk.Button(frameunder, command=lambda: helpers.savefile(file, tempsettings), text="SAVE") + save.grid(row=i, column=2, sticky=tk.E, pady=2) + frameunder.grid() + + + def servermenu(mainframe, configfile): + for child in mainframe.winfo_children(): child.destroy() #oneline to destroy everything in the main frame + side1 = ttk.Frame(mainframe) + side2 = ttk.Frame(mainframe) + # Side one of the 'server' menu + logarea = ScrolledText(side2, + wrap = tk.WORD, + state="disabled") + + startb = ttk.Button(side1, text="Start Server", command=lambda: [helpers.bgthread(helpers.serverbackground, [helpers.getsnpath(configfile), logarea])]) + startb.grid(row = 0, column = 0, sticky = tk.W, pady = 2) + # Side two of the 'server' menu + + logarea.grid(row = 0, column = 0, sticky = tk.E, pady = 2) + + side1.grid(row = 0, column = 0, sticky = tk.N, pady = 2) + side2.grid(row = 0, column = 1, sticky = tk.N, pady = 2) + +class helpers: + def downloadlatestdll(filepath): + x = requests.get('https://openwf.io/supplementals/client%20drop-in/meta') + meta = json.loads(x.text) + url = f"https://openwf.io/supplementals/client%20drop-in/{meta['version']}/dwmapi.dll" + + content = requests.get(url, stream=True).content + hashofdll = sha256() + hashofdll.update(content) + + if hashofdll.hexdigest() == meta['sha256']: + with open(filepath + "dwmapi.dll", "wb") as out_file: + out_file.write(content) + messagebox.showinfo(title="Bootstrapper Downloaded", message="Bootstrapper Downloaded!") + else: + messagebox.showerror(title="Unknown Error.", message="SpaceninGUI could not verify the hash of the Bootstrapper. Sorry :(") + + def check4client(clientpath): + onlyfiles = [f for f in listdir(clientpath) if isfile(join(clientpath, f))] + if "dwmapi.dll" in onlyfiles: + return True + else: + return False + + # Not implemented yet lol + def runpluto(name): + pass + + def is_number(data): + """Validate the contents of an entry widget as a int.""" + if data == '': + return True + try: + rv = int(data) + except ValueError: + return False + return True + + def savefile(file, save): + if Path(file).is_file: + with open(file) as f: + data = json.load(f) + + for key in save.keys(): + if save[key]["type"] is bool: + if save[key]['value'].get() == '1': + data[key] = True + else: + data[key] = False + print(save[key]['value'].get()) + elif save[key]["type"] is str: + data[key] = str(save[key]["value"].get()) + elif save[key]["type"] is int: + data[key] = int(save[key]["value"].get()) + elif save[key]["type"] is dict: + for k in save[key]['value'].keys(): + if save[key]['value'][k]["type"] is bool: + data[key][k] = bool(save[key]['value'][k]["valueop"].get()) + elif save[key]['value'][k]["type"] is str: + data[key][k] = str(save[key]['value'][k]["value"].get()) + elif save[key]['value'][k]["type"] is int: + data[key][k] = int(save[key]['value'][k]["value"].get()) + + with open(file, "w") as f: + json.dump(data, f, indent=2) + + def getclientpath(file): + if Path(file).is_file(): + with open(file) as f: + d = json.load(f) + return d["wfpath"] + else: + return False + + # Grabs the serverpath, returns false if non-existant + def getsnpath(file): + if Path(file).is_file(): + with open(file) as f: + d = json.load(f) + return d["spaceninjapath"] + else: + return False + + def getlogmax(): + file = 'sngconfig.json' + if Path(file).is_file(): + with open(file) as f: + d = json.load(f) + return int(d["maxlogsize"]) + else: + return False + + def bgthread(func, args): + th = threading.Thread(target=func, args=args) + th.daemon = True + th.start() + + def serverbackground(cd, logarea): # To my knowledge, running the server in the main thread would stall the gui, fuck that lol + logmax = helpers.getlogmax() + + def mongo(): + sysctl = Popen(['systemctl', 'start', 'mongodb']) + sysctl.wait() + + + def execute(cd): + popen = Popen(['npm', 'run', 'dev'], stdin=PIPE, stdout=PIPE, universal_newlines=True, cwd=cd) + for stdout_line in iter(popen.stdout.readline, ""): + yield stdout_line + popen.stdout.close() + return_code = popen.wait() + if return_code: + raise subprocess.CalledProcessError(return_code) + + if platform == "linux": + print("Running on linux requires mongodb to be started!") + mongo() + + for path in execute(cd): + if len(logarr) < logmax: + logarr.append(path) + else: + logarr.pop(0) + logarr.append(path) + if logarea: + logarea.configure(state="normal") + logarea.delete(1.0, "end") + for element in logarr: + logarea.insert(tk.INSERT, element) + logarea.configure(state="disabled") + + def check4sn(configfile): + file = helpers.getsnpath(configfile) + "package.json" + if Path(file).is_file(): + with open(file) as f: + d = json.load(f) + if (d["name"] == "wf-emulator"): + return True + else: + return False + + def on_closing(root): + if messagebox.askokcancel("Quit", "Do you want to quit?"): + root.destroy() + +class main: + def __init__(self, configfile): + + root = ThemedTk(theme='black', themebg=True) + root.title('SpaceNinGui Version 1.0') + root.minsize(400, 300) + serverlogs = tk.StringVar() + wfemucheck = str(helpers.check4sn(configfile)) + menubar = tk.Menu(root) + vcmd = root.register(helpers.is_number) + filemenu = tk.Menu(menubar, tearoff=0) + filemenu.add_command(label="Options", command=lambda: menus.settingsmenu(file = configfile, mainframe=mainframe, vcmd=vcmd)) + filemenu.add_separator() + filemenu.add_command(label="Exit", command=lambda: helpers.on_closing(root)) + menubar.add_cascade(label="SpaceNinGUI", menu=filemenu) + menubar.add_separator() + + menubar.add_command(label="\u22EE", activebackground=menubar.cget("background")) + + menubar.add_command(label="Server", command=lambda: menus.servermenu(mainframe, configfile)) + menubar.add_command(label="Settings", command=lambda: menus.settingsmenu(file=(helpers.getsnpath(configfile) + "config.json"), mainframe=mainframe, vcmd=vcmd)) + mainframe = ttk.Frame(root) + menubar.add_command(label="\u22EE", activebackground=menubar.cget("background")) + menubar.add_command(label="Client", command=lambda: menus.clientmenu(file="sngconfig.json", mainframe=mainframe)) + menubar.add_command(label="Settings", command=lambda: menus.settingsmenu(file=(helpers.getclientpath(configfile) + "/OpenWF/client_config.json"), mainframe=mainframe, vcmd=vcmd)) + menubar.add_command(label="Scripts", command=lambda: menus.plutomenu(file="sngconfig.json", mainframe=mainframe)) + + + + + checklabel = ttk.Label(mainframe, text=f"Was Spaceninja Found: {wfemucheck}", anchor=tk.W, font=("Arial", 16, "bold")) + checklabel.grid(row = 0, column = 0, sticky = tk.W, pady = 2) + mainframe.grid(row = 0, column = 0, pady = 2) + root.protocol("WM_DELETE_WINDOW", lambda: helpers.on_closing(root)) + root.config(menu=menubar) + root.mainloop() + +class install(): + + + def __init__(self): + self.step = 1 + + self.config = {} + self.elements = {} + + root = ThemedTk(theme='black', themebg=True) + self.root = root + self.installstep=tk.StringVar() + self.installprog=tk.IntVar() + self.installprog.set(0) + self.installstep.set("Init") + #font options lmao + standard = ('Noto Sans', 10) + standardbold = ('Noto Sans', 10, "bold") + self.standard = standard + self.standardbold = standardbold + + root.title('SpaceNinGui Installer') + root.minsize(400, 300) + leftframe = ttk.Frame(root, borderwidth=10) + rightframe = ttk.Frame(root, borderwidth=1) + steps=["1: Initial Message", "2: Install Spaceninja Server", "3: Install Warframe Bootstrapper", "4: Final Steps"] + ttk.Label(leftframe, text="Steps:").grid(padx=20) + self.steplabel=[] + for step in steps: + label=ttk.Label(leftframe, text=step, anchor=tk.W) + self.steplabel.append(label) + for label in self.steplabel: + label.grid(padx=20, pady=5, sticky=tk.W) + + + self.steplabel[0].config(font=standardbold) + rightinnerframe = ttk.Frame(rightframe) + ttk.Label(rightinnerframe, text="Welcome to SpaceNinGui! \nFirst, We have to get some inital settings setup. \nWe will start with the server configuration.").grid() + leftframe.grid(row=0, column=0, sticky=tk.W) + + rightinnerframe.grid() + rightframe.grid(row=0, column=1, sticky=tk.E) + nextbutton = ttk.Button(rightframe, text="Next", command=lambda: self.next(rightinnerframe)) + nextbutton.grid(row=1, sticky=tk.SE) + root.protocol("WM_DELETE_WINDOW", lambda: helpers.on_closing(root)) + root.mainloop() + + def configsave(self): + with open('sngconfig.json', 'w') as f: + tot = {} + tot["spaceninjapath"] = self.elements['serverpath'].get() + tot["wfpath"] = self.elements['clientpath'].get() + tot['maxlogsize']=10 + json.dump(tot, f, indent=2) + + + + def install(self, clientpath, serverpath): + #first install server + self.installstep.set("Downloading Server Through Git") + sysctl = Popen(['git', 'clone', 'https://openwf.io/SpaceNinjaServer.git', serverpath]) + self.installprog.set(1) + sysctl.wait() + self.installstep.set("Installing Server Requirements (via npm)") + self.installprog.set(25) + sysctl = Popen(["npm", '--prefix', f'{serverpath}', 'install']) + sysctl.wait() + self.installstep.set("Copying Config File") + shutil.copy(serverpath + "/config.json.example", serverpath + "/config.json") + self.installstep.set("Starting Bootstrapper Download") + self.installprog.set(50) + print("Server install worked. ") + helpers.downloadlatestdll(clientpath) + self.installprog.set(99) + self.installstep.set("Install Complete!\nPress next then restart to get started!") + + + def next(self, inner): + if self.step == 1: + self.step+=1 + for child in inner.winfo_children(): child.destroy() + self.elements["serverpath"] = tk.StringVar() + path = ttk.Entry(inner, width=50, textvariable=self.elements["serverpath"]) + flavortext = ttk.Label(inner, text="Please input a path that you would like to install the Server in!") + flavortext.grid(padx=10) + path.grid(padx=10) + self.steplabel[0].config(font=self.standard) + self.steplabel[1].config(font=self.standardbold) + elif self.step == 2: + self.step+=1 + self.elements["clientpath"] = tk.StringVar() + for child in inner.winfo_children(): child.destroy() + path = ttk.Entry(inner, width=50, textvariable=self.elements["clientpath"]) + flavortext = ttk.Label(inner, text="Please input the path that has Warframe installed\nI highly suggest making a copy to prevent potental bans.", anchor=tk.CENTER) + flavortext.grid(padx=10, pady=5) + path.grid(padx=10) + self.elements["wfpath"] = path + self.steplabel[1].config(font=self.standard) + self.steplabel[2].config(font=self.standardbold) + elif self.step == 3: + self.step+= 1 + print("final step!!!") + for child in inner.winfo_children(): child.destroy() + flavortext = ttk.Label(inner, text="Thank you! The OpenWF setup should be downloading in the background!", anchor=tk.CENTER) + flavortext.grid(padx=10, pady=5) + statustext = ttk.Label(inner, textvariable=self.installstep, anchor=tk.CENTER) + statustext.grid(padx=10, pady=5) + statusbar = ttk.Progressbar(inner, variable=self.installprog) + statusbar.grid() + helpers.bgthread(self.install, [self.elements["clientpath"].get(), self.elements["serverpath"].get()]) + elif self.step == 4: + if self.installstep.get() == "Install Complete!\nPress next then restart to get started!": + self.configsave() + self.root.destroy() + + + + + + +if __name__ == "__main__": + if isfile('sngconfig.json'): + main('sngconfig.json') + else: + install() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +