mirror of
https://github.com/Ap0dexMe0/NF-MSL.git
synced 2026-09-16 06:02:14 +02:00
158 lines
5.7 KiB
Python
158 lines
5.7 KiB
Python
from __future__ import annotations
|
|
import argparse, glob as _glob, os, re, sys, time
|
|
from pathlib import Path
|
|
from typing import List
|
|
from modules.config import setup_config
|
|
from modules.logging import setup_logger
|
|
from modules.platforms.android_rsa import run_android_rsa
|
|
from modules.platforms.android import run_android
|
|
from modules.platforms.ios import run_ios
|
|
from modules.platforms.tv import run_tv
|
|
from modules.platforms.tv_otp import run_tv_otp
|
|
from modules.platforms.web import run_web
|
|
from modules.platforms.mgk import run_mgk
|
|
|
|
log = setup_logger("MSL HANDSHAKE")
|
|
|
|
|
|
class _ColoredHelpFormatter(argparse.HelpFormatter):
|
|
"""HelpFormatter with ANSI colors applied after layout so column widths are unaffected."""
|
|
|
|
_RST = "\033[0m"
|
|
_BOLD = "\033[1m"
|
|
_CYAN = "\033[36m"
|
|
_GREEN = "\033[32m"
|
|
_YELLOW = "\033[33m"
|
|
|
|
@staticmethod
|
|
def _tty() -> bool:
|
|
return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
|
|
|
def _c(self, codes: str, text: str) -> str:
|
|
return f"{codes}{text}{self._RST}" if self._tty() else text
|
|
|
|
def _format_usage(self, usage, actions, groups, prefix):
|
|
if prefix is None:
|
|
prefix = self._c(f"{self._YELLOW}{self._BOLD}", "usage") + ": "
|
|
return super()._format_usage(usage, actions, groups, prefix)
|
|
|
|
def start_section(self, heading: str | None) -> None:
|
|
if heading:
|
|
heading = self._c(f"{self._CYAN}{self._BOLD}", heading)
|
|
super().start_section(heading)
|
|
|
|
def _format_action(self, action: argparse.Action) -> str:
|
|
text = super()._format_action(action)
|
|
if not self._tty():
|
|
return text
|
|
# Color flag/option text (e.g. "-h, --help" or "--platform {…}")
|
|
# Applied after layout so len() calculations are already done.
|
|
return re.sub(
|
|
r"^(\s+)(-\S.*?)(\s{2,}|$)",
|
|
lambda m: m.group(1) + self._c(self._GREEN, m.group(2)) + m.group(3),
|
|
text,
|
|
flags=re.MULTILINE,
|
|
)
|
|
|
|
|
|
class _ColoredArgumentParser(argparse.ArgumentParser):
|
|
"""ArgumentParser with colored --help and colored error messages."""
|
|
|
|
def error(self, message: str) -> None:
|
|
log.error(message)
|
|
self.print_usage(sys.stderr)
|
|
sys.exit(2)
|
|
|
|
|
|
_WVD_LOOP_DELAY = 10 # seconds between WVD iterations to avoid throttling
|
|
|
|
|
|
def _run_wvd_loop(run_fn, wvd_paths: List[Path], **kwargs) -> None:
|
|
passed: List[str] = []
|
|
failed: List[str] = []
|
|
for wvd_path in wvd_paths:
|
|
if passed or failed:
|
|
log.info("Waiting %ds before next WVD to avoid throttling...", _WVD_LOOP_DELAY)
|
|
time.sleep(_WVD_LOOP_DELAY)
|
|
log.info("--- [%d/%d] WVD: %s ---", len(passed) + len(failed) + 1, len(wvd_paths), wvd_path.name)
|
|
try:
|
|
run_fn(wvd_path=wvd_path, **kwargs)
|
|
passed.append(wvd_path.name)
|
|
except SystemExit:
|
|
failed.append(wvd_path.name)
|
|
log.warning("WVD failed, continuing to next...")
|
|
except Exception as exc:
|
|
failed.append(wvd_path.name)
|
|
log.warning("WVD raised %s: %s — continuing to next...", type(exc).__name__, exc)
|
|
|
|
log.info("=== Results: %d/%d passed ===", len(passed), len(wvd_paths))
|
|
for name in passed:
|
|
log.info(" PASS: %s", name)
|
|
for name in failed:
|
|
log.warning(" FAIL: %s", name)
|
|
|
|
if not passed:
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
WVD_PLATFORMS = {"android", "ios", "tv", "tv_otp"}
|
|
|
|
parser = _ColoredArgumentParser(
|
|
description="Netflix MSL multi-platform login",
|
|
formatter_class=_ColoredHelpFormatter,
|
|
)
|
|
parser.add_argument("--platform", required=True, choices=["android", "android_rsa", "ios", "tv", "tv_otp", "web", "mgk"], help="Target platform")
|
|
parser.add_argument("--wvd", type=str, help="Path or glob pattern to .wvd file(s) (e.g. devices/*.wvd)")
|
|
parser.add_argument("--kpekph", type=str, default=None, help="KpeKph value (Kpe:Kph) or file path (mgk platform); auto-discovered if omitted")
|
|
parser.add_argument("--esnid", type=str, help="ESN identity string or file path (mgk platform)")
|
|
parser.add_argument("--new-msl", action="store_true", help="Force new MSL key exchange")
|
|
parser.add_argument("--no-verify", action="store_true", help="Skip TLS verification")
|
|
parser.add_argument("--proxy", type=str, default=None, help="Proxy URL (e.g. http://ip:port or http://user:pass@ip:port)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
wvd_paths: List[Path] = []
|
|
if args.platform in WVD_PLATFORMS:
|
|
if not args.wvd:
|
|
parser.error(f"--wvd is required for {args.platform} platform")
|
|
wvd_paths = sorted(
|
|
Path(p) for p in _glob.glob(args.wvd) if p.endswith(".wvd")
|
|
)
|
|
if not wvd_paths:
|
|
parser.error(f"No .wvd files found matching: {args.wvd}")
|
|
log.info("Found %d .wvd file(s) to test", len(wvd_paths))
|
|
|
|
kwargs = {"new_msl": args.new_msl, "no_verify": args.no_verify, "proxy": args.proxy}
|
|
|
|
if args.platform == "android_rsa":
|
|
run_android_rsa(**kwargs)
|
|
|
|
elif args.platform == "android":
|
|
_run_wvd_loop(run_android, wvd_paths, **kwargs)
|
|
|
|
elif args.platform == "ios":
|
|
_run_wvd_loop(run_ios, wvd_paths, **kwargs)
|
|
|
|
elif args.platform == "tv":
|
|
_run_wvd_loop(run_tv, wvd_paths, **kwargs)
|
|
|
|
elif args.platform == "tv_otp":
|
|
_run_wvd_loop(run_tv_otp, wvd_paths, **kwargs)
|
|
|
|
elif args.platform == "web":
|
|
run_web(**kwargs)
|
|
|
|
elif args.platform == "mgk":
|
|
if not args.esnid:
|
|
parser.error("--esnid is required for mgk platform")
|
|
run_mgk(kpekph_path=args.kpekph, esnid=args.esnid, **kwargs)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if os.name == "nt":
|
|
os.system('cls')
|
|
else:
|
|
os.system('clear')
|
|
main()
|