-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
BuildTools
committed
Oct 24, 2024
0 parents
commit 69fc848
Showing
7 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"mods": []} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
fetch('SpotMod-dat/data.json') | ||
.then(response => response.json()) | ||
.then(data => { | ||
data.mods.forEach(mod => { | ||
if (mod.enabled) { | ||
const script = document.createElement('script'); | ||
script.src = `SpotMod-dat/mods/${mod.id}`; | ||
script.onerror = function() { | ||
toast(`Error loading mod: ${mod.id}`, true); | ||
}; | ||
document.body.appendChild(script); | ||
} | ||
}); | ||
toast("SpotMod loaded!") | ||
}) | ||
.catch(error => toast(`Error loading mod list: ${error}`, true)) | ||
|
||
function toast(text, error = false){ | ||
Toastify({ | ||
text: text, | ||
duration: 3000, | ||
close: true, | ||
gravity: "bottom", | ||
position: "right", | ||
stopOnFocus: true, | ||
style: { | ||
background: error ? "red" : "green", | ||
fontFamily: "sans-serif" | ||
} | ||
}).showToast(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"path": ""} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import zipfile as zf | ||
import os, shutil, json, keyboard | ||
from bs4 import BeautifulSoup | ||
from time import sleep | ||
|
||
def patch_spotify(spotify_path, delete_data = False): | ||
if delete_data: | ||
delete_local_files() | ||
|
||
extract_xpui(spotify_path) | ||
|
||
replace_spotmod_dat() | ||
open(f"{spotify_path}/SpotMod.txt", "w").write("This file tells SpotMod that you have patched Spotify.") | ||
|
||
print("Adding JavaScript libraries...") | ||
soup = BeautifulSoup(open("xpui-spa/index.html"), features="lxml") | ||
soup.head.append(soup.new_tag("script", src="https://cdn.jsdelivr.net/npm/toastify-js")) | ||
soup.head.append(soup.new_tag("link", rel="stylesheet", href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css")) | ||
|
||
print("Linking loader.js to HTML...") | ||
soup.body.append(soup.new_tag("script", id="spotmod-patch", src=f"SpotMod-dat/loader.js")) | ||
html = soup.prettify("utf-8") | ||
with open("xpui-spa/index.html", "wb") as index: | ||
index.write(html) | ||
|
||
compile_xpui(spotify_path) | ||
|
||
clean_up() | ||
|
||
print("Patch applied!\nPress enter to continue...") | ||
keyboard.wait("Enter") | ||
|
||
def unpatch_spotify(spotify_path): | ||
extract_xpui(spotify_path) | ||
|
||
print("Undoing modifications...") | ||
|
||
shutil.rmtree("xpui-spa/SpotMod-dat") | ||
os.remove(f"{spotify_path}/SpotMod.txt") | ||
|
||
soup = BeautifulSoup(open("xpui-spa/index.html"), features="lxml") | ||
soup.find("script", {"src": "SpotMod-dat/loader.js"}).decompose() | ||
soup.find("script", {"src": "https://cdn.jsdelivr.net/npm/toastify-js"}).decompose() | ||
soup.find("link", {"href": "https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css"}).decompose() | ||
open("xpui-spa/index.html", "wb").write(soup.prettify("utf-8")) | ||
|
||
compile_xpui(spotify_path) | ||
delete_local_files() | ||
clean_up() | ||
|
||
print("SpotMod has been uninstalled.\nPress enter to quit...") | ||
keyboard.wait("Enter") | ||
quit() | ||
|
||
def delete_local_files(): | ||
print("Deleting local files...") | ||
shutil.rmtree("SpotMod-dat/mods") | ||
os.mkdir("SpotMod-dat/mods") | ||
open(f"SpotMod-dat/data.json", "w").write(json.dumps({"mods": []})) | ||
|
||
def extract_xpui(spotify_path): | ||
print("Extracting xpui.spa...") | ||
with zf.ZipFile(f"{spotify_path}/apps/xpui.spa", "r") as spa_file: | ||
spa_file.extractall("xpui-spa") | ||
spa_file.close() | ||
|
||
def compile_xpui(spotify_path): | ||
print("Compiling new xpui.spa...") | ||
with zf.ZipFile("xpui.spa", 'w') as spa_file: | ||
for root, _, files in os.walk("xpui-spa"): | ||
for file in files: | ||
file_path = os.path.join(root, file) | ||
arcname = os.path.relpath(file_path, "xpui-spa") | ||
spa_file.write(file_path, arcname) | ||
spa_file.close() | ||
print("Replacing old xpui.spa...") | ||
os.remove(f"{spotify_path}/apps/xpui.spa") | ||
shutil.copyfile("xpui.spa", f"{spotify_path}/apps/xpui.spa") | ||
|
||
def replace_spotmod_dat(): | ||
print("Adding/Replacing SpotMod files and folders...") | ||
if os.path.exists("xpui-spa/SpotMod-dat"): | ||
shutil.rmtree("xpui-spa/SpotMod-dat") | ||
shutil.copytree("SpotMod-dat", "xpui-spa/SpotMod-dat") | ||
|
||
def clean_up(): | ||
print("Cleaning up...") | ||
shutil.rmtree("xpui-spa") | ||
os.remove("xpui.spa") | ||
|
||
def add_mod(mod_path, spotify_path): | ||
mod_id = os.path.basename(mod_path) | ||
if os.path.exists(f"SpotMod-dat/mods/{mod_id}"): | ||
print("Duplicate mod detected!\nProcess aborted.") | ||
else: | ||
print("Copying mod file...") | ||
shutil.copyfile(mod_path, f"SpotMod-dat/mods/{mod_id}") | ||
print("Editing data.json...") | ||
data = json.load(open("SpotMod-dat/data.json")) | ||
data["mods"].append({"id": mod_id, "enabled": True}) | ||
open("SpotMod-dat/data.json", "w").write(json.dumps(data)) | ||
extract_xpui(spotify_path) | ||
replace_spotmod_dat() | ||
compile_xpui(spotify_path) | ||
clean_up() | ||
print("Mod added!") | ||
print("Press enter to continue...") | ||
keyboard.wait("Enter") | ||
|
||
def remove_mod(mod_id, mod_ids, spotify_path): | ||
print("Editing data.json...") | ||
data = json.load(open("SpotMod-dat/data.json")) | ||
data["mods"].pop(mod_ids.index(mod_id)) | ||
open("SpotMod-dat/data.json", "w").write(json.dumps(data)) | ||
|
||
print("Deleting mod file...") | ||
os.remove(f"SpotMod-dat/mods/{mod_id}") | ||
|
||
extract_xpui(spotify_path) | ||
replace_spotmod_dat() | ||
compile_xpui(spotify_path) | ||
clean_up() | ||
|
||
def enable_mod(mod_id, mod_ids, spotify_path, enable = True): | ||
print("Editing data.json...") | ||
data = json.load(open("SpotMod-dat/data.json")) | ||
mod_data = data["mods"][mod_ids.index(mod_id)] | ||
mod_data["enabled"] = enable | ||
open("SpotMod-dat/data.json", "w").write(json.dumps(data)) | ||
extract_xpui(spotify_path) | ||
replace_spotmod_dat() | ||
compile_xpui(spotify_path) | ||
clean_up() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
from urllib.request import urlretrieve | ||
from colorama import Fore, Style, Back | ||
from tkinter import filedialog | ||
from functools import partial | ||
import os, keyboard, inject, json, time | ||
|
||
version = 0.1 | ||
|
||
spotify_path = os.getenv("APPDATA") + "\Spotify" | ||
|
||
def clear(): | ||
os.system("cls") | ||
print(f"{Back.GREEN}{Fore.BLACK} SpotMod Patcher v{str(version)} {Style.RESET_ALL}{Fore.GREEN}\n") | ||
|
||
def main(): | ||
global spotify_path | ||
clear() | ||
data = json.load(open("data.json")) | ||
if not data["path"] == "": spotify_path = data["path"] | ||
if not os.path.exists(f"{spotify_path}/Spotify.exe"): | ||
not_installed() | ||
if not os.path.exists(f"{spotify_path}/SpotMod.txt"): | ||
not_detected() | ||
main_menu() | ||
|
||
def main_menu(): | ||
while True: | ||
clear() | ||
option_list(["Add mod", "Manage mods", "Uninstall SpotMod", "Quit"], [add_mod, manage_mods, uninstall, quit]) | ||
|
||
def add_mod(): | ||
clear() | ||
print("Please select your mod file...") | ||
mod_file = filedialog.askopenfilename(filetypes=[("JavaScript Files", "*.js")]) | ||
if mod_file: | ||
clear() | ||
inject.add_mod(mod_file, spotify_path) | ||
|
||
def manage_mods(): | ||
while True: | ||
clear() | ||
mod_ids = [] | ||
data = json.load(open("SpotMod-dat/data.json", "r")) | ||
for mod in data["mods"]: | ||
mod_ids.append(mod["id"]) | ||
mod_ids.append("Cancel") | ||
mod_id = option_list(mod_ids, None, "Select a mod:") | ||
if mod_id == "Cancel": return | ||
|
||
clear() | ||
mod_enabled = data["mods"][mod_ids.index(mod_id)]["enabled"] | ||
mod_option = option_list(["Disable mod" if mod_enabled else "Enable mod", "Remove mod", "Cancel"]) | ||
|
||
clear() | ||
match mod_option: | ||
case "Disable mod": | ||
inject.enable_mod(mod_id, mod_ids, spotify_path, False) | ||
case "Enable mod": | ||
inject.enable_mod(mod_id, mod_ids, spotify_path, True) | ||
case "Remove mod": | ||
inject.remove_mod(mod_id, mod_ids, spotify_path) | ||
case _: | ||
pass | ||
|
||
def not_detected(): | ||
if len(os.listdir("SpotMod-dat/mods")) == 0: | ||
print("SpotMod is not detected on this system.\n") | ||
option_list(["Patch Spotify", "Quit"], [patch, quit]) | ||
else: | ||
print("SpotMod is not detected on this system, but you have saved mods.\n") | ||
patch_and_del = partial(patch, True) | ||
option_list(["Patch Spotify and add saved mods", "Patch Spotify and delete saved mods", "Quit"], [patch, patch_and_del, quit]) | ||
|
||
def not_installed(): | ||
global spotify_path | ||
print(f"Spotify is not detected on this system at [{spotify_path}].\nMake sure you downloaded it from Spotify.com and not the Microsoft Store.\n") | ||
option_list(["Spotify is installed somewhere else", "Quit"], [None, quit]) | ||
data = json.load(open("data.json")) | ||
clear() | ||
print("Please select your Spotify install folder...") | ||
data["path"] = filedialog.askdirectory(initialdir=os.getenv("APPDATA")) | ||
json.dump(data, open("data.json", "w")) | ||
main() | ||
|
||
def patch(delete_data = False): | ||
clear() | ||
inject.patch_spotify(spotify_path, delete_data) | ||
main() | ||
|
||
def uninstall(): | ||
clear() | ||
option_list(["Yes. Remove all my mods.", "No! Take me back!"], [None, main_menu], "Are you sure you wish to un-patch Spotify and remove all mods?") | ||
clear() | ||
inject.unpatch_spotify(spotify_path) | ||
|
||
def option_list(itemlist, calllist = None, prompt_text = "Please choose an option:"): | ||
print(prompt_text) | ||
for item in itemlist: | ||
print(f"{itemlist.index(item) + 1}) {item}") | ||
while True: | ||
wait_for_no_keys() | ||
key = keyboard.read_event().name | ||
keyboard.send("Backspace") | ||
keyboard.send("Backspace") # idk, it only works when you do it twice | ||
if key.isdigit(): | ||
if int(key) <= len(itemlist) and not int(key) == 0: | ||
if calllist is not None: | ||
if calllist[int(key) - 1] is not None: calllist[int(key) - 1]() | ||
break | ||
else: | ||
return itemlist[int(key) - 1] | ||
|
||
def wait_for_no_keys(): | ||
while True: | ||
if not any(keyboard.is_pressed(scan_code) for scan_code in range(1, 256)): # There has to be a better way to do this... | ||
break | ||
time.sleep(0.1) | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
beautifulsoup4==4.11.2 | ||
colorama==0.4.4 | ||
keyboard==0.13.5 |