spaceningui/__main__.py

455 lines
20 KiB
Python
Raw Normal View History

2025-02-15 00:26:30 -08:00
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()