215 lines
6.5 KiB
Python
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()
|