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()