Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
BuildTools committed Oct 24, 2024
0 parents commit 69fc848
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/__pycache__
1 change: 1 addition & 0 deletions SpotMod-dat/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"mods": []}
31 changes: 31 additions & 0 deletions SpotMod-dat/loader.js
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();
}
1 change: 1 addition & 0 deletions data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"path": ""}
133 changes: 133 additions & 0 deletions inject.py
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()
120 changes: 120 additions & 0 deletions main.py
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()
3 changes: 3 additions & 0 deletions requirements.txt
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

0 comments on commit 69fc848

Please sign in to comment.