# seedbox.py import argparse import re import urllib.error import urllib.parse import urllib.request from pathlib import Path from trackers import trackers parser = argparse.ArgumentParser( prog="Unit3D seedbox manager", description="Automagically add or remove seedbox entries on any Unit3D", usage=f"{Path(__file__).name} tracker username [ip] [options] ", epilog="For bugs/errors, please DM me on FnP or make an issue on GitHub", ) parser.add_argument("tracker", type=str, help="Tracker") parser.add_argument("username", type=str, help="Username") parser.add_argument("ip", nargs="?", type=str, help="IP of the seedbox") parser.add_argument( "--print", default=False, action=argparse.BooleanOptionalAction, help="Prints list of seedboxes", ) parser.add_argument( "--seedbox-name", type=str, default="PikminBox", help="Name of the seedbox (default: %(default)s)", ) parser.add_argument( "--delete", default=False, action=argparse.BooleanOptionalAction, help="Deletes all old seedbox entries", ) parser.add_argument( "--cookie-file", type=Path, default=Path(__file__).parent.joinpath("unit3d.cookies"), help="Cookies to be used", ) parsed_args = parser.parse_args() COOKIE_HEADER = "" def generateHeaders(host: str) -> dict: headers = { "Host": host, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Origin": f"https://{host}", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", } if COOKIE_HEADER: headers["Cookie"] = COOKIE_HEADER return headers def parseTracker(tracker: str) -> dict: for t in trackers: if tracker.lower() in [x.lower() for x in t.values()]: return t print(f'No tracker in database matched "{tracker}"') exit(1) def parseCookieFile(file: Path, domain: str) -> None: global COOKIE_HEADER with open(file, "r") as f: for line in f: if not re.match(r"^\#", line) and not re.match(r"^\s*$", line): lineFields = line.strip().split("\t") if domain in lineFields[0]: COOKIE_HEADER = f"{lineFields[5]}={lineFields[6]}" return print(f"No remember_web_* cookie was found in {file}") exit(1) def getSeedboxes(domain: str) -> tuple[list[dict], str]: global COOKIE_HEADER try: req = urllib.request.Request( f"https://{domain}/users/{parsed_args.username}/seedboxes", headers=generateHeaders(domain), ) with urllib.request.urlopen(req) as resp: html = resp.read().decode("utf-8") for header, value in resp.getheaders(): if header.lower() == "set-cookie": cookie_val = value.split(";")[0] if cookie_val not in COOKIE_HEADER: COOKIE_HEADER += f"; {cookie_val}" token_match = re.search( r'\s*(.*?)\s*\s*\s*(.*?)\s*.*?action="[^"]+/seedboxes/(\d+)".*?name="_token"\s+value="([^"]+)"', re.DOTALL | re.IGNORECASE, ) seedboxes = [ { "name": m.group(1).strip(), "ip": m.group(2).strip(), "id": m.group(3), "token": m.group(4), } for m in pattern.finditer(html) ] return seedboxes, add_token except Exception as e: print(f"Error while getting seedboxes: {e}", flush=True) exit(1) def addSeedbox(domain: str, token: str) -> bool: try: data = urllib.parse.urlencode( { "_token": token, "name": parsed_args.seedbox_name, "ip": parsed_args.ip, } ).encode("utf-8") req = urllib.request.Request( f"https://{domain}/users/{parsed_args.username}/seedboxes", data=data, headers=generateHeaders(domain), ) with urllib.request.urlopen(req) as resp: html = resp.read().decode("utf-8") error_match = re.search( r']*id="ERROR_COPY"[^>]*>(.*?)', html, re.DOTALL | re.IGNORECASE, ) if error_match: clean_error = re.sub(r"<[^>]+>", "", error_match.group(1)) error_msg = " ".join( [x.strip() for x in clean_error.split("\n") if x.strip()] ) raise Exception(error_msg) return True except Exception as e: print("Failed to add seedbox", flush=True) print(f"Error: {e}", flush=True) return False def main(): tracker = parseTracker(parsed_args.tracker) domain = tracker["domain"] parseCookieFile(parsed_args.cookie_file, domain) seedboxes, add_token = getSeedboxes(domain) if parsed_args.print: print(f"Tracker: {tracker['name']}") print(f"\n| {'Name':<25}{'IP':<20}{'ID':<5}") print("| " + "-" * 53) for sbox in seedboxes: print(f"| {sbox['name']:<25}{sbox['ip']:<20}{sbox['id']:<5}") print() if parsed_args.delete: for sbox in seedboxes: print(f'Deleting Seedbox: "{sbox["name"]}" ({sbox["ip"]})', flush=True) data = urllib.parse.urlencode( {"_token": sbox["token"], "_method": "DELETE"} ).encode("utf-8") req = urllib.request.Request( f"https://{domain}/users/{parsed_args.username}/seedboxes/{sbox['id']}", data=data, headers=generateHeaders(domain), ) try: urllib.request.urlopen(req) except urllib.error.HTTPError as e: if e.code != 302: print(f"Failed to delete {sbox['name']}: HTTP {e.code}", flush=True) if parsed_args.ip: if addSeedbox(domain, add_token): print( f"Successfully added Seedbox \"{parsed_args.seedbox_name}\" ({parsed_args.ip}) to {tracker['name']}", flush=True, ) if __name__ == "__main__": main()