Files
2026-03-11 18:41:49 +01:00

215 lines
6.5 KiB
Python

# 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'<meta\s+name="csrf-token"\s+content="([^"]+)"', html, re.IGNORECASE
)
if not token_match:
raise Exception("Could not find CSRF token on the page.")
add_token = token_match.group(1)
pattern = re.compile(
r'<td>\s*(.*?)\s*</td>\s*<td>\s*(.*?)\s*</td>.*?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'<div[^>]*id="ERROR_COPY"[^>]*>(.*?)</div>',
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()