from __future__ import annotations import json import random import re import sys from pathlib import Path from typing import Optional from urllib.parse import quote from modules.msl.android import MSL_ANDROID from modules.helpers import ( ensure_output_dir, get_nfvdid, generate_hex_id, ) from modules.config import setup_config from modules.logging import setup_logger from modules.session import setup_session config = setup_config() EMAIL = config["NETFLIX"]["EMAIL"] PASSWORD = config["NETFLIX"]["PASSWORD"] def run_android_rsa(new_msl: bool = False, no_verify: bool = False, proxy: Optional[str] = None): logger = setup_logger('ANDROID MSL RSA') output_dir = ensure_output_dir("android") msl_cache_path = output_dir / "msl_keys_cache_android_rsa.json" auth_cookies_path = output_dir / "netflix_auth_cookies_rsa.json" useridtoken_path = output_dir / "netflix_auth_useridtoken_rsa.json" tokens_output_path = output_dir / "netflix_auth_tokens_rsa.json" # NFCDCH-02-* ESN is accepted by the Android FTL endpoint without a WVD esn = f"NFCDCH-02-{''.join(random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') for _ in range(32))}" user_agent = f"com.netflix.mediaclient/63988 (Linux; U; Android 15; en_US; SM-F711N; Build/AP3A.240905.015.A2; Cronet/143.0.7445.0)" device_model = "SM-F711N" session = setup_session(verify_tls=not no_verify, proxy=proxy) _proxy = {"http": proxy, "https": proxy} if proxy else None response = session.post( "https://android15.appboot.netflix.com/appboot/NFANDROID1-PRV-P-", params={"keyVersion": "1"}, headers={ "Host": "android15.appboot.netflix.com", "X-Netflix.Request.Client.Context": '{"appView":"unknown","appState":"foreground"}', "Content-Type": "application/x-www-form-urlencoded", "User-Agent": user_agent, "Accept-Encoding": "gzip, deflate, br", }, timeout=30, ) nfvdid = get_nfvdid(session, response) logger.info("Initial nfvdid cookie obtained") msl_headers = MSL_ANDROID.build_request_headers( request_name="getProxyEsn", user_agent=user_agent, referer=None, esn=esn, expiry_timeout=12750, host="android15.prod.cloud.netflix.com", language="en-US,en", device_model=quote(device_model, safe=""), extra_headers={ "Accept-Encoding": "gzip, deflate, br", "Content-Encoding": "msl_v1", "x-netflix.zuul.brotli.allowed": "true", "x-netflix.appver": "9.60.0", "x-netflix.clienttype": "samurai", "x-netflix.request.client.context": '{"appView":"unknown","appState":"foreground"}', "x-netflix.esnprefix": "NFANDROID1-PRV-P-", "x-netflix.request.uuid": f"{generate_hex_id(8)}-{generate_hex_id(4)}-{generate_hex_id(4)}-{generate_hex_id(4)}-{generate_hex_id(12)}", "x-netflix.androidapi": "35", "x-netflix.deviceformfactor": "PHONE", "x-netflix.devicememorylevel": "HIGH", "x-netflix.request.attempt": "1", "x-netflix.request.id": generate_hex_id(32), "Content-Type": "application/json", "x-netflix.client.request.name": "getProxyEsn", "x-netflix.request.routing": '{"path":"\\/nq\\/android\\/playback\\/~1.0.0\\/router"}', "user-agent": user_agent, }, ) logger.info("Performing RSA/ASYMMETRIC_WRAPPED MSL handshake (no WVD needed)") msl_keys = MSL_ANDROID.rsa_handshake( msl_keys_path=str(msl_cache_path), session=session, sender=esn, new_msl=new_msl, cookies={"nfvdid": nfvdid}, endpoint="https://android.prod.ftl.netflix.com/nq/androidui/pbo_license/~1.0.0/router", headers=msl_headers, ) msl_client = MSL_ANDROID( session=session, keys=msl_keys, message_id=random.randint(0, 2**52), sender=esn, drm="widevine", proxy=_proxy, ) logger.info("MSL RSA key exchange completed") # The NFCDCH-02-* ESN triggers the web CLCS auth flow (not samurai useridtoken). # After the MSL handshake the HTTP session carries Netflix cookies, so we use # the same CLCSScreenUpdate GraphQL path that run_web() uses. logger.info("Fetching login page and extracting CLCS session context") login_response = session.get("https://www.netflix.com/login", timeout=30) login_html = login_response.text clcs_session_id = None rendition_id = None patterns = [ r'clcsSessionId[\\"\'": ]+([0-9a-f\-]{36})', r'(?