#!/usr/bin/env python3 # Thanks to https://github.com/pogmommy for making the original macOS installer # Thanks to https://github.com/xenoncolt for making the original Windows installer # Their contributions made this universal script a lot easier to produce. import json import os import subprocess import platform from time import sleep import sys # Covering all the possible arch, aarch variations def is_arm64(): return True if 'aarch64' in platform.machine().lower() or 'armv8' in platform.machine().lower() else False def is_arm32(): return True if 'aarch' in platform.machine().lower() or 'arm' in platform.machine().lower() else False path = "" if platform.system() != "Windows": if os.environ.get("XDG_CONFIG_HOME"): path = os.environ["XDG_CONFIG_HOME"].removesuffix("/") + "/jellyfin-rpc/main.json" else: path = os.environ["HOME"].removesuffix("/") + "/.config/jellyfin-rpc/main.json" subprocess.run(["mkdir", "-p", path.removesuffix("main.json")]) else: path = os.environ["APPDATA"].removesuffix("\\") + "\\jellyfin-rpc\\main.json" subprocess.run(["powershell", "-Command", f'mkdir "{path.removesuffix("main.json")}"'], stdout=subprocess.DEVNULL) print(""" Welcome to the Jellyfin-RPC installer [https://github.com/Radiicall/jellyfin-rpc#Setup] """) current = "" if os.path.isfile(path): print(f"Found existing config: {path}") for arg in sys.argv: if arg == "--use-existing-config": print("Using existing config") current = "y" break if current != "y": while True: current = input("Use existing config? (y/N): ").lower() if current == "n" or current == "y" or current == "": break print("Invalid input, please type y or n") if current == "n" or current == "": print("----------Jellyfin----------") url = input("URL (include http/https): ") api_key = input(f"API key [Create one here: {url}/web/index.html#!/apikeys.html]: ") print("Enter a single username or enter multiple usernames in a comma separated list.") username = input("username[s]: ").split(",") if url.startswith("https://"): while True: val = input("Are you using a self signed certificate? (y/N): ").lower() if val == "n" or val == "": self_signed_cert = False break elif val == "y": self_signed_cert = True break else: print("Invalid input, please type y or n") continue else: self_signed_cert = None print("If you dont want anything else you can just press enter through all of these") while True: val = input("Do you want to customize music display? (y/N): ").lower() if val == "n" or val == "": music = None break elif val != "y": print("Invalid input, please type y or n") continue print("Enter what you would like to be shown in a comma seperated list") print("Remember that it will show in the order you type it in") print("Valid options are year, album and/or genres") display = input("[Default: genres]: ").split(",") print("Choose the separator between the artist name and the info") separator = input("[Default: -]: ") if display == "": display = None if separator == "": separator = None music = { "display": display, "separator": separator } break while True: val = input("Do you want to customize movie display? (y/N): ").lower() if val == "n" or val == "": movies = None break elif val != "y": print("Invalid input, please type y or n") continue print("Enter what you would like to be shown in a comma seperated list") print("Remember that it will show in the order you type it in") print("Valid options are year, critic-score, community-score and/or genres") display = input("[Default: genres]: ").split(",") print("Choose the separator between the artist name and the info") separator = input("[Default: -]: ") if display == "": display = None if separator == "": separator = None movies = { "display": display, "separator": separator } break while True: val = input("Do you want to blacklist media types or libraries? (y/N): ").lower() if val == "n" or val == "": blacklist = None break elif val != "y": print("Invalid input, please type y or n") continue print("You will first type in what media types to blacklist, this should be a comma separated list WITHOUT SPACES") print("then after that you can choose what libraries to blacklist, this should ALSO be a comma separated list,") print("there should be no spaces before or after the commas but there can be spaces in the names of libraries") sleep(2) print("Media types 1/2") media_types = input("Valid types are music, movie, episode and/or livetv [Default: ]: ").split(",") print("Libraries 2/2") libraries = input("Enter libraries to blacklist [Default: ]: ").split(",") blacklist = { "media_types": media_types, "libraries": libraries } break show_simple = input("Do you want to show episode names in RPC? (Y/n): ").lower() if show_simple == "y": show_simple = False elif show_simple == "n": show_simple = True else: show_simple = False append_prefix = input("Do you want to add a leading 0 to season and episode numbers? (y/N): ").lower() if append_prefix == "y": append_prefix = True elif append_prefix == "n": append_prefix = False else: append_prefix = False add_divider = input("Do you want to add a divider between numbers, ex. S01 - E01? (y/N): ").lower() if add_divider == "y": add_divider = True elif add_divider == "n": add_divider = False else: add_divider = False jellyfin = { "url": url, "api_key": api_key, "username": username, "music": music, "movies": movies, "blacklist": blacklist, "self_signed_cert": self_signed_cert, "show_simple": show_simple, "append_prefix": append_prefix, "add_divider": add_divider } print("----------Discord----------") appid = input("Enter your discord application ID [Default: 1053747938519679018]: ") if appid == "": appid = None show_paused = input("Do you want to show paused videos? (Y/n): ").lower() if show_paused == "y": show_paused = True elif show_paused == "n": show_paused = False else: show_paused = True print("----------Buttons----------") while True: val = input("Do you want custom buttons? (y/N): ").lower() if val == "n" or val == "": buttons = None break elif val != "y": print("Invalid input, please type y or n") continue buttons = [] print("If you want one button to continue being dynamic then you have to specifically enter dynamic into both name and url fields") print("If you dont want any buttons to appear then you can leave everything blank here and it wont show anything.") print("Button 1/2") name = input("Choose what the button will show [Default: dynamic]: ") url = input("Choose where the button will direct to [Default: dynamic]: ") if name != "" and url != "": buttons.append({ "name": name, "url": url }) print("Button 2/2") name = input("Choose what the button will show [Default: dynamic]: ") url = input("Choose where the button will direct to [Default: dynamic]: ") if name != "" and url != "": buttons.append({ "name": name, "url": url }) break print("----------Images----------") while True: val = input("Do you want images? (y/N): ").lower() if val == "n" or val == "": images = None imgur = None break elif val != "y": print("Invalid input, please type y or n") continue val2 = input("Do you want imgur images? (y/N): ").lower() client_id = "" imgur_images = False if val2 == "y": imgur_images = True client_id = input("Enter your imgur client id: ") elif val2 != "n" and val2 != "": print("Invalid input, please type y or n") continue imgur = { "client_id": client_id } images = { "enable_images": True, "imgur_images": imgur_images, } break discord = { "application_id": appid, "buttons": buttons, "show_paused": show_paused } config = { "jellyfin": jellyfin, "discord" : discord, "imgur": imgur, "images": images } print(f"\nPlacing config in '{path}'") file = open(path, "w") file.write(json.dumps(config, indent=2)) file.close() for arg in sys.argv: if arg == "--no-install": print("Skipping installation") exit(0) while True: val = input("Do you want to download Jellyfin-RPC? (Y/n): ").lower() if val == "y" or val == "": break elif val != "n": print("Invalid input, please type y or n") continue print("Exiting...") exit(0) print("\nDownloading Jellyfin-RPC") if platform.system() == "Windows": path = path.removesuffix("main.json") subprocess.run(["curl", "-o", path + "jellyfin-rpc.exe", "-L", "https://github.com/Radiicall/jellyfin-rpc/releases/latest/download/jellyfin-rpc.exe"]) while True: val = input("Do you want to autostart Jellyfin-RPC at login? (y/N): ").lower() if val == "n" or val == "": break if val != "y": print("Invalid input, please type y or n") continue if os.path.isfile(path + "winsw.exe"): print("The script will prompt for administrator to remove the already installed service") sleep(1) subprocess.run([path + "winsw.exe", "uninstall"]) subprocess.run(["curl", "-o", path + "winsw.exe", "-L", "https://github.com/winsw/winsw/releases/latest/download/WinSW-x64.exe"]) content = f""" jellyfin-rpc Jellyfin-RPC This service is running Jellyfin-RPC for rich presence support {path}jellyfin-rpc.exe -c {path}main.json -i {path}urls.json """ file = open(path + "winsw.xml", "w") file.write(content) file.close() print("The program will now ask you for administrator rights twice, this is so the service can be installed!") print("waiting 5 seconds") sleep(5) subprocess.run([path + "winsw.exe", "install"]) subprocess.run([path + "winsw.exe", "start"]) print("Autostart has been set up, jellyfin-rpc should now launch at login\nas long as there are no issues with the configuration") break elif platform.system() == "Darwin": file = f"https://github.com/Radiicall/jellyfin-rpc/releases/latest/download/jellyfin-rpc-{platform.machine()}-darwin" subprocess.run(["curl", "-o", "/usr/local/bin/jellyfin-rpc", "-L", file]) subprocess.run(["chmod", "+x", "/usr/local/bin/jellyfin-rpc"]) while True: val = input("Do you want to autostart Jellyfin-RPC at login? (y/N): ").lower() if val == "n" or val == "": break if val != "y": print("Invalid input, please type y or n") continue if subprocess.run(["pgrep", "-xq", "--", "'jellyfin-rpc'"]).returncode == 0: subprocess.run(["killall", "jellyfin-rpc"]) if "Jellyfin-RPC" in subprocess.Popen("launchctl list", shell=True, stdout=subprocess.PIPE).stdout.read().decode(): subprocess.run(["launchctl", "remove", "Jellyfin-RPC"]) content = """ Label Jellyfin-RPC Program /usr/local/bin/jellyfin-rpc RunAtLoad StandardErrorPath /tmp/jellyfinrpc.local.stderr.txt StandardOutPath /tmp/jellyfinrpc.local.stdout.txt """ path = os.environ["HOME"] + "/Library/LaunchAgents/jellyfinrpc.local.plist" file = open(path, "w") file.write(content) file.close() subprocess.run(["chmod", "644", path]) subprocess.run(["launchctl", "load", path]) print("Jellyfin RPC is now set up to start at login.") print("If needed, you can run Jellyfin RPC at any time by running 'jellyfin-rpc' in a terminal.") break else: if is_arm64(): linux_binary = "jellyfin-rpc-arm64-linux" elif is_arm32(): linux_binary = "jellyfin-rpc-arm32-linux" else : linux_binary = "jellyfin-rpc-x86_64-linux" subprocess.run(["mkdir", "-p", os.environ["HOME"].removesuffix("/") + "/.local/bin"]) subprocess.run(["curl", "-o", os.environ["HOME"].removesuffix("/") + "/.local/bin/jellyfin-rpc", "-L", f"https://github.com/Radiicall/jellyfin-rpc/releases/latest/download/{linux_binary}"]) subprocess.run(["chmod", "+x", os.environ["HOME"].removesuffix("/") + "/.local/bin/jellyfin-rpc"]) if os.environ.get("XDG_CONFIG_HOME"): path = os.environ["XDG_CONFIG_HOME"].removesuffix("/") + "/systemd/user/jellyfin-rpc.service" else: path = os.environ["HOME"].removesuffix("/") + "/.config/systemd/user/jellyfin-rpc.service" while True: val = input("Do you want to autostart Jellyfin-RPC at login using Systemd? (y/N): ").lower() if val == "n" or val == "": break if val != "y": print("Invalid input, please type y or n") continue print(f"\nSetting up service file in {path}") subprocess.run(["mkdir", "-p", path.removesuffix("jellyfin-rpc.service")]) content = f"""[Unit] Description=Jellyfin-RPC Service Documentation=https://github.com/Radiicall/jellyfin-rpc After=network.target [Service] Type=simple ExecStart={os.environ["HOME"].removesuffix("/") + "/.local/bin/jellyfin-rpc"} Restart=on-failure [Install] WantedBy=default.target""" file = open(path, "w") file.write(content) file.close() subprocess.run(["systemctl", "--user", "daemon-reload"]) subprocess.run(["systemctl", "--user", "enable", "--now", "jellyfin-rpc.service"]) print("Jellyfin-RPC is now set up to start at login.") break print("Installation complete!")