diff --git a/main.py b/main.py index 9974274..d3bd008 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,17 @@ import json +import os +import sys +import shutil +import requests +import subprocess import webview import threading -from join_room import join_room +import tkinter as tk +from tkinter import messagebox + +from join_room import join_room, showError +from updater import compare_versions + def server(): import server @@ -14,10 +24,57 @@ def server(): config = json.load(f) +def check_for_updates(): + try: + current_version = config['client-version'] + + url = "https://api.github.com/repos/Bishoko/JigglyConnect/releases?per_page=1" + response = requests.get(url) + releases = response.json() + try: + latest_release = next((release for release in releases if not release["draft"]), None) + except Exception: + latest_release = None + latest_version = "0.1" + if latest_release is not None and "tag_name" in latest_release: + latest_version = latest_release["tag_name"].replace('v', '') + + + if compare_versions(latest_version, current_version) > 0: + print(f"New version available: {latest_version}") + + fenetre = tk.Tk() + fenetre.withdraw() + answer = messagebox.askyesno("JigglyConnect Update", "An update is available, do you want to install it?") + + if answer: + if os.path.exists("update"): + shutil.rmtree("update", ignore_errors=True) + os.makedirs("update") + shutil.copyfile("JC-updater.exe", "update/JC-updater.exe") + shutil.copyfile("python3.dll", "update/python3.dll") + shutil.copyfile("python311.dll", "update/python311.dll") + shutil.copytree("lib", "update/lib") + subprocess.Popen(["update/JC-updater.exe"]) + + sys.exit() + else: + print('Removing update directory') + if os.path.exists("update"): + shutil.rmtree("update", ignore_errors=True) + + except Exception as e: + showError(f"Auto Update error: {e}\n\n Please update manually on https://github.com/Bishoko/JigglyConnect/releases") + os.startfile("https://github.com/Bishoko/JigglyConnect/releases/latest") + sys.exit() + + def read_cookies(window): cookies = window.get_cookies() if __name__ == '__main__': + check_for_updates() + window = webview.create_window( 'JigglyConnect', f'http://{config["server"]}/', diff --git a/setup/win/build.bat b/setup/win/build.bat index cb06a35..5fcca2a 100644 --- a/setup/win/build.bat +++ b/setup/win/build.bat @@ -22,7 +22,8 @@ rem Navigate to the build directory cd build cd exe.win-amd64-3.11 -rem Sign the executable +rem Sign all the executables "C:\Program Files (x86)\Windows Kits\10\bin\x64\signtool" sign /a /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 JigglyConnect.exe +"C:\Program Files (x86)\Windows Kits\10\bin\x64\signtool" sign /a /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 JC-updater.exe echo Build done! diff --git a/setup/win/requirements.txt b/setup/win/requirements.txt index 9c19e51..848ad95 100644 --- a/setup/win/requirements.txt +++ b/setup/win/requirements.txt @@ -1,4 +1,6 @@ cx_Freeze +requests +psutil pywin32 pywebview pyautogui diff --git a/setup/win/setup.py b/setup/win/setup.py index 287b86d..c5d2104 100644 --- a/setup/win/setup.py +++ b/setup/win/setup.py @@ -25,7 +25,8 @@ # base = 'console' if sys.platform=='win32' else None executables = [ - Executable("main.py", base=base, target_name="JigglyConnect", icon="icon.ico") + Executable("main.py", base=base, target_name="JigglyConnect", icon="icon.ico"), + Executable("updater.py", base=base, target_name="JC-updater") ] with open("config.json", encoding="utf-8") as f: @@ -50,6 +51,7 @@ "requirements.txt", "build.bat", "JigglyConnect.exe", + "JC-updater.exe", "icon.ico", "DOC.txt", "TODO.txt", @@ -73,6 +75,7 @@ target_dir = os.path.join(build_dir, first_folder) shutil.copy2("join_room.py", os.path.join(target_dir, "lib")) + shutil.copy2("updater.py", os.path.join(target_dir, "lib")) for item in os.listdir(script_dir): item_path = os.path.join(script_dir, item) diff --git a/updater.py b/updater.py new file mode 100644 index 0000000..e02b49e --- /dev/null +++ b/updater.py @@ -0,0 +1,181 @@ +import ctypes +import sys +import os +import shutil +import json +import requests +import psutil +import win32com +import subprocess +import ctypes +import zipfile +import tkinter as tk +from tkinter import messagebox + + +def error(message): + print(message) + ctypes.windll.user32.MessageBoxW(None, message, "WebDeck Updater Error", 0) + + +def move_folder_content(source, destination): + if not os.path.exists(destination): + os.makedirs(destination) + + for element in os.listdir(source): + source_path = os.path.join(source, element) + destination_path = os.path.join(destination, element) + + if os.path.isfile(source_path): + shutil.copy2(source_path, destination_path) + + elif os.path.isdir(source_path): + move_folder_content(source_path, destination_path) + + +def close_process(process_name): + try: + for proc in psutil.process_iter(["pid", "name"]): + if process_name.lower() in proc.info["name"].lower(): + try: + pid = proc.info["pid"] + os.kill(pid, psutil.signal.SIGTERM) + except ( + psutil.NoSuchProcess, + psutil.AccessDenied, + psutil.ZombieProcess, + ): + pass + + except: + try: + wmi = win32com.client.GetObject("winmgmts:") + processes = wmi.InstancesOf("Win32_Process") + for process in processes: + if process.Properties_('Name').Value.replace('.exe','').lower().strip() in ["jigglyconnect"]: + print(f"Stopping process: {process.Properties_('Name').Value}") + result = process.Terminate() + if result == 0: + print("Process terminated successfully.") + else: + print("Failed to terminate process.") + except: + try: + subprocess.Popen(f"taskkill /f /IM {process_name}", shell=True) + except: + pass + + +def compare_versions(version1, version2): + v1_components = list(map(int, version1.split("."))) + v2_components = list(map(int, version2.split("."))) + + for v1, v2 in zip(v1_components, v2_components): + if v1 > v2: + return 1 + elif v1 < v2: + return -1 + + if len(v1_components) > len(v2_components): + return 1 + elif len(v1_components) < len(v2_components): + return -1 + + return 0 + + +# TESTING +# def compare_versions(version1, version2): +# return 1 + + +def check_updates(current_version): + url = "https://api.github.com/repos/Bishoko/JigglyConnect/releases?per_page=1" + response = requests.get(url) + releases = response.json() + try: + latest_release = next( + (release for release in releases if not release["draft"]), None + ) + except Exception: + latest_release = None + latest_version = "0.1" + if latest_release is not None and "tag_name" in latest_release: + latest_version = latest_release["tag_name"].replace("v", "") + + if compare_versions(latest_version, current_version) > 0: + print(f"New version available: {latest_version}") + + close_process("JigglyConnect.exe") + for file_url in latest_release["assets"]: + if ( + file_url["browser_download_url"].endswith("portable.zip") + and file_url["state"] == "uploaded" + ): + download_and_extract(file_url["browser_download_url"]) + break + + # Remove the JigglyConnect directory + print("Removing JigglyConnect directory") + update_dir_path = os.path.join(jc_dir, "JigglyConnect") + shutil.rmtree(update_dir_path, ignore_errors=True) + + # Delete the JC-update.zip file + zip_file_path = os.path.join(jc_dir, "JC-update.zip") + os.remove(zip_file_path) + print("JC-update.zip deleted") + + # Launch JigglyConnect.exe from the jc_dir (root) directory + print("Restarting JigglyConnect.exe") + exe_path = os.path.join(jc_dir, "JigglyConnect.exe") + os.system(exe_path) + + +def download_and_extract(download_url): + response = requests.get(download_url, stream=True) + if response.status_code != 200: + error("Failed to download update ZIP file.") + else: + with open("JC-update.zip", "wb") as file: + for chunk in response.iter_content(chunk_size=8192): + file.write(chunk) + + with zipfile.ZipFile("JC-update.zip", "r") as zip_ref: + zip_ref.extractall("JC-update") + + source = os.path.join(jc_dir, "JigglyConnect") + destination = jc_dir + + move_folder_content(source, destination) + + +# TESTING +# def download_and_extract(download_url): +# shutil.copyfile("E:/Users/81len/Downloads/JC-fake-update.zip", "JC-update.zip") +# with zipfile.ZipFile('JC-update.zip', 'r') as zip_ref: +# zip_ref.extractall(jc_dir) +# +# source = os.path.join(jc_dir, "JigglyConnect") +# destination = jc_dir +# +# move_folder_content(source, destination) + + +if __name__ == "__main__": + print("Starting updater...") + + current_dir = f"{os.path.abspath(os.path.dirname(__file__))}/update" + jc_dir = os.path.abspath(os.path.join(current_dir, os.pardir)) + + if not current_dir.endswith("update"): + sys.exit() + version_path = os.path.join(jc_dir, "config.json") + + if not ctypes.windll.shell32.IsUserAnAdmin(): + ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1) + sys.exit() + + with open(version_path, encoding="utf-8") as f: + current_version = json.load(f)["client-version"] + + check_updates(current_version)