mirror of
https://github.com/Ap0dexMe0/NF-MSL.git
synced 2026-10-27 17:22:17 +01:00
refactored
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from __future__ import annotations
|
||||
@@ -0,0 +1,386 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import quote
|
||||
from pywidevine import Cdm as WidevineCdm, Device as WidevineDevice
|
||||
from modules.msl.android import MSL_ANDROID
|
||||
from modules.helpers import (
|
||||
ensure_output_dir, restore_auth_cookies, get_nfvdid, get_flow_session_cookies,
|
||||
save_session_cookies,
|
||||
generate_netflix_uuid, generate_request_id, generate_esn_random_suffix,
|
||||
decrypt_msl_header, extract_clcs_session_id, extract_rendition_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(wvd_path: Path,
|
||||
new_msl: bool = False, no_verify: bool = False,
|
||||
proxy: Optional[str] = None):
|
||||
log = setup_logger('ANDROID MSL')
|
||||
OUTPUT_DIR = ensure_output_dir("android")
|
||||
|
||||
NETFLIX_HOME_URL = "https://www.netflix.com/"
|
||||
NETFLIX_CANONICAL_URL = "https://netflix.com/"
|
||||
LOGIN_URL = "https://www.netflix.com/login"
|
||||
APPBOOT_URL = "https://android15.appboot.netflix.com/appboot/NFANDROID1-PRV-P-"
|
||||
MSL_HANDSHAKE_ENDPOINT = "https://android.prod.ftl.netflix.com/nq/androidui/pbo_license/~1.0.0/router"
|
||||
VERIFY_LOGIN_URL = "https://android.prod.ftl.netflix.com/nq/androidui/samurai/v1/config"
|
||||
|
||||
USER_AGENT = "com.netflix.mediaclient/63988 (Linux; U; Android 15; en_US; SM-F711N; Build/AP3A.240905.015.A2; Cronet/143.0.7445.0)"
|
||||
CLIENT_VERSION = "18.26.0"
|
||||
APP_VERSION = "9.60.0"
|
||||
HAWKINS_VERSION = "5.15.0"
|
||||
UI_FLAVOR = "android"
|
||||
OS_VERSION = "35"
|
||||
FORM_FACTOR = "phone"
|
||||
FEATURE_CAPABILITIES = "supportsStudioBranding"
|
||||
LOCALE = "en-US"
|
||||
DEVICE_MODEL = "SM-F711N"
|
||||
|
||||
VERIFY_TLS = not no_verify
|
||||
RESTORE_AUTH_COOKIES = False
|
||||
|
||||
if not wvd_path.exists():
|
||||
raise FileNotFoundError(f"Missing WVD file: {wvd_path}")
|
||||
widevine_device = WidevineDevice.load(wvd_path)
|
||||
cdm = WidevineCdm.from_device(widevine_device)
|
||||
_sid = widevine_device.system_id
|
||||
MSL_CACHE_PATH = OUTPUT_DIR / f"msl_keys_cache_android_{_sid}.json"
|
||||
AUTH_COOKIES_PATH = OUTPUT_DIR / f"netflix_auth_cookies_{_sid}.json"
|
||||
USERIDTOKEN_PATH = OUTPUT_DIR / f"netflix_auth_useridtoken_{_sid}.json"
|
||||
TOKENS_OUTPUT_PATH = OUTPUT_DIR / f"netflix_auth_tokens_{_sid}.json"
|
||||
|
||||
ESN = f"NFANDROID1-PRV-P-SAMSUSM-F711N-{_sid}-{generate_esn_random_suffix(64)}"
|
||||
log.info("ESN: %s", ESN)
|
||||
|
||||
REQUEST_CLIENT_CONTEXT_UNKNOWN = '{"appView":"unknown","appState":"foreground"}'
|
||||
APPBOOT_REQUEST_CLIENT_CONTEXT = '{"appView":"unknown","appState":"foreground"}'
|
||||
|
||||
session = setup_session(verify_tls=VERIFY_TLS, proxy=proxy)
|
||||
_proxy = {"http": proxy, "https": proxy} if proxy else None
|
||||
|
||||
if RESTORE_AUTH_COOKIES:
|
||||
restore_auth_cookies(session, AUTH_COOKIES_PATH, log)
|
||||
|
||||
log.info("Initializing session")
|
||||
response = session.get(NETFLIX_CANONICAL_URL, timeout=30, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
response = session.get(NETFLIX_HOME_URL, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
log.info("Requesting initial nfvdid cookie")
|
||||
appboot_headers = {
|
||||
"Host": "android15.appboot.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"X-Netflix.Request.Client.Context": APPBOOT_REQUEST_CLIENT_CONTEXT,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
}
|
||||
|
||||
response = session.post(
|
||||
APPBOOT_URL,
|
||||
params={"keyVersion": "1"},
|
||||
headers=appboot_headers,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
nfvdid = get_nfvdid(session, response)
|
||||
|
||||
log.info("Initial nfvdid cookie obtained")
|
||||
|
||||
log.info("Starting MSL Widevine exchange")
|
||||
|
||||
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": APP_VERSION,
|
||||
"x-netflix.clienttype": "samurai",
|
||||
"x-netflix.request.client.context": REQUEST_CLIENT_CONTEXT_UNKNOWN,
|
||||
"x-netflix.esnprefix": "NFANDROID1-PRV-P-",
|
||||
"x-netflix.request.uuid": (
|
||||
generate_netflix_uuid()
|
||||
),
|
||||
"x-netflix.androidapi": "35",
|
||||
"x-netflix.deviceformfactor": "PHONE",
|
||||
"x-netflix.devicememorylevel": "HIGH",
|
||||
"x-netflix.request.attempt": "1",
|
||||
"x-netflix.request.id": generate_request_id(),
|
||||
"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,
|
||||
},
|
||||
)
|
||||
|
||||
handshake_cookies = {
|
||||
"nfvdid": nfvdid,
|
||||
}
|
||||
|
||||
msl_keys = MSL_ANDROID.handshake(
|
||||
msl_keys_path=str(MSL_CACHE_PATH),
|
||||
session=session,
|
||||
sender=ESN,
|
||||
cdm=cdm,
|
||||
cdm_device=str(wvd_path),
|
||||
new_msl=False,
|
||||
cookies=handshake_cookies,
|
||||
drm="widevine",
|
||||
endpoint=MSL_HANDSHAKE_ENDPOINT,
|
||||
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,
|
||||
)
|
||||
|
||||
nfvdid, flow_session_id = get_flow_session_cookies(session)
|
||||
|
||||
log.info("MSL Widevine exchange completed")
|
||||
|
||||
log.info("Loading login page")
|
||||
response = session.get(LOGIN_URL, timeout=30)
|
||||
response.raise_for_status()
|
||||
login_html = response.text
|
||||
|
||||
cookie_dict = session.cookies.get_dict()
|
||||
flow_session_id = cookie_dict.get("flwssn", flow_session_id)
|
||||
if not flow_session_id:
|
||||
raise RuntimeError("The flwssn flow session cookie is missing")
|
||||
|
||||
log.info("Submitting VerifyLoginMslRequest")
|
||||
|
||||
confirm_login_query = {
|
||||
"api": "33",
|
||||
"appType": "samurai",
|
||||
"appVer": "62902",
|
||||
"appVersion": "9.18.0",
|
||||
"chipset": "sm8150",
|
||||
"chipsetHardware": "qcom",
|
||||
"clientAppState": "FOREGROUND",
|
||||
"clientAppVersionState": "NORMAL",
|
||||
"countryIsoCode": "US",
|
||||
"ctgr": "phone",
|
||||
"dbg": "false",
|
||||
"deviceLocale": "en-US",
|
||||
"devmod": f"samsung_{DEVICE_MODEL}",
|
||||
"ffbc": "phone",
|
||||
"flwssn": flow_session_id,
|
||||
"installType": "regular",
|
||||
"isAutomation": "false",
|
||||
"isConsumptionOnly": "true",
|
||||
"isNetflixPreloaded": "false",
|
||||
"isPlayBillingEnabled": "true",
|
||||
"isStubInSystemPartition": "false",
|
||||
"lackLocale": "false",
|
||||
"landingOrigin": "https://www.netflix.com",
|
||||
"mId": "SAMSUSM-F711N",
|
||||
"memLevel": "HIGH",
|
||||
"method": "get",
|
||||
"mnf": "samsung",
|
||||
"model": DEVICE_MODEL,
|
||||
"netflixClientPlatform": "androidNative",
|
||||
"netflixId": cookie_dict.get("NetflixId", ""),
|
||||
"networkType": "wifi",
|
||||
"osBoard": "kona",
|
||||
"osDevice": "bloom",
|
||||
"osDisplay": "RP1A.200720.012",
|
||||
"password": PASSWORD,
|
||||
"path": '["signInVerify"]',
|
||||
"pathFormat": "hierarchical",
|
||||
"platform": "android",
|
||||
"preloadSignupRoValue": "",
|
||||
"progressive": "false",
|
||||
"qlty": "hd",
|
||||
"recaptchaResponseTime": 445,
|
||||
"recaptchaResponseToken": "",
|
||||
"responseFormat": "json",
|
||||
"roBspVer": "RP1A.200720.012",
|
||||
"secureNetflixId": cookie_dict.get("SecureNetflixId", ""),
|
||||
"sid": "7176",
|
||||
"store": "google",
|
||||
"userLoginId": EMAIL,
|
||||
}
|
||||
|
||||
confirm_login_headers = {
|
||||
"X-Netflix.Request.NqTracking": "VerifyLoginMslRequest",
|
||||
"X-Netflix.Client.Request.Name": "VerifyLoginMslRequest",
|
||||
"X-Netflix.Request.Client.Context": '{"appState":"foreground"}',
|
||||
"X-Netflix-Esn": ESN,
|
||||
"X-Netflix.EsnPrefix": "NFANDROID1-PRV-P-",
|
||||
"X-Netflix.msl-header-friendly-client": "true",
|
||||
"content-encoding": "msl_v1",
|
||||
}
|
||||
|
||||
_THROTTLE_RETRIES = 3
|
||||
_THROTTLE_WAIT = 60
|
||||
_clcs_attempted = False
|
||||
|
||||
for _attempt in range(1, _THROTTLE_RETRIES + 2): # +1 slot for the CLCS retry
|
||||
try:
|
||||
confirm_login_header, confirm_login_payload_chunks = msl_client.send_message(endpoint=VERIFY_LOGIN_URL,
|
||||
params=confirm_login_query,
|
||||
application_data={},
|
||||
headers=confirm_login_headers)
|
||||
except Exception:
|
||||
log.error("VerifyLoginMslRequest failed")
|
||||
log.debug("Request URL: %s", VERIFY_LOGIN_URL)
|
||||
log.debug("Request params: %s", json.dumps(confirm_login_query, indent=2))
|
||||
log.debug("Request headers: %s", json.dumps(confirm_login_headers, indent=2))
|
||||
log.debug("Session cookies: %s", json.dumps(session.cookies.get_dict(), indent=2))
|
||||
log.exception("Exception occurred")
|
||||
sys.exit(1)
|
||||
|
||||
_error_code = None
|
||||
if (
|
||||
isinstance(confirm_login_payload_chunks, dict)
|
||||
and "errorCode" in confirm_login_payload_chunks.get("jsonGraph", {}).get("signInVerify", {}).get("value", {}).get("fields", {})
|
||||
):
|
||||
_error_code = (
|
||||
confirm_login_payload_chunks.get("jsonGraph", {})
|
||||
.get("signInVerify", {})
|
||||
.get("value", {})
|
||||
.get("fields", {})
|
||||
.get("errorCode", {})
|
||||
.get("value")
|
||||
)
|
||||
|
||||
if _error_code == "throttling_failure" and _attempt < _THROTTLE_RETRIES:
|
||||
log.warning("Throttled by Netflix (attempt %d/%d), retrying in %ds...", _attempt, _THROTTLE_RETRIES, _THROTTLE_WAIT)
|
||||
time.sleep(_THROTTLE_WAIT)
|
||||
continue
|
||||
|
||||
elif _error_code == "incorrect_password" and not _clcs_attempted:
|
||||
# Samurai rejected credentials without auth cookies — fall back to CLCS
|
||||
# web login to obtain NetflixId/SecureNetflixId, then retry once.
|
||||
log.warning("incorrect_password — falling back to CLCS web login")
|
||||
clcs_session_id = extract_clcs_session_id(login_html)
|
||||
rendition_id = extract_rendition_id(login_html)
|
||||
if not clcs_session_id or not rendition_id:
|
||||
log.error("Cannot extract CLCS session IDs from login page for fallback")
|
||||
sys.exit(1)
|
||||
clcs_resp = session.post(
|
||||
"https://web.prod.cloud.netflix.com/graphql",
|
||||
json={
|
||||
"operationName": "CLCSScreenUpdate",
|
||||
"variables": {
|
||||
"format": "HTML",
|
||||
"imageFormat": "PNG",
|
||||
"locale": "en-US",
|
||||
"serverState": json.dumps({
|
||||
"realm": "growth",
|
||||
"name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": clcs_session_id,
|
||||
"sessionContext": {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
"login.navigationSettings": {"hideOtpToggle": True},
|
||||
},
|
||||
}, separators=(",", ":")),
|
||||
"serverScreenUpdate": json.dumps({
|
||||
"realm": "custom",
|
||||
"name": "growthLoginByPassword",
|
||||
"metadata": {"recaptchaSiteKey": "6Lf8hrcUAAAAAIpQAFW2VFjtiYnThOjZOA5xvLyR"},
|
||||
"loggingAction": "Submitted",
|
||||
"loggingCommand": "SubmitCommand",
|
||||
"referrerRenditionId": rendition_id,
|
||||
}, separators=(",", ":")),
|
||||
"inputFields": [
|
||||
{"name": "password", "value": {"stringValue": PASSWORD}},
|
||||
{"name": "userLoginId", "value": {"stringValue": EMAIL}},
|
||||
{"name": "countryCode", "value": {"stringValue": "1"}},
|
||||
{"name": "countryIsoCode", "value": {"stringValue": "US"}},
|
||||
{"name": "recaptchaResponseTime", "value": {"intValue": 445}},
|
||||
{"name": "recaptchaResponseToken", "value": {"stringValue": ""}},
|
||||
],
|
||||
},
|
||||
"extensions": {"persistedQuery": {"id": "1c276cdf-caef-49cf-b38e-384972c2b47e", "version": 102}},
|
||||
},
|
||||
headers={
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": LOGIN_URL,
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
if "errors" in clcs_resp.json():
|
||||
log.error("CLCS fallback login failed: %s", clcs_resp.json().get("errors"))
|
||||
sys.exit(1)
|
||||
session.get("https://www.netflix.com/browse", timeout=30)
|
||||
_fresh = session.cookies.get_dict()
|
||||
confirm_login_query["netflixId"] = _fresh.get("NetflixId", "")
|
||||
confirm_login_query["secureNetflixId"] = _fresh.get("SecureNetflixId", "")
|
||||
confirm_login_query["flwssn"] = _fresh.get("flwssn", flow_session_id)
|
||||
_clcs_attempted = True
|
||||
log.info("CLCS fallback complete, retrying VerifyLoginMslRequest")
|
||||
continue
|
||||
|
||||
elif _error_code:
|
||||
log.error("Login errorCode: %s", _error_code)
|
||||
sys.exit(1)
|
||||
break
|
||||
|
||||
if "headerdata" not in confirm_login_header:
|
||||
log.critical("Missing 'headerdata' in MSL response")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
header_data = decrypt_msl_header(confirm_login_header["headerdata"], msl_client.keys.encryption, msl_client.keys.sign)
|
||||
except Exception:
|
||||
log.exception("Failed to decrypt MSL header")
|
||||
sys.exit(1)
|
||||
|
||||
tokens = header_data.get("useridtoken")
|
||||
if not tokens:
|
||||
log.error("Authentication failed: invalid ESN, email, or password")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
TOKENS_OUTPUT_PATH.write_text(json.dumps(header_data, indent=4), encoding="utf-8")
|
||||
USERIDTOKEN_PATH.write_text(json.dumps(tokens, indent=2), encoding="utf-8")
|
||||
log.info("User ID token data saved to: %s", TOKENS_OUTPUT_PATH)
|
||||
log.info("User ID token saved to: %s", USERIDTOKEN_PATH)
|
||||
except Exception:
|
||||
log.exception("Failed to save token files")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
auth_cookies = save_session_cookies(session, AUTH_COOKIES_PATH, log)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
result = {
|
||||
"useridtoken": tokens,
|
||||
"auth_cookies": auth_cookies,
|
||||
"header_data": header_data,
|
||||
}
|
||||
|
||||
log.info("VerifyLoginMslRequest succeeded")
|
||||
# print(json.dumps(result, indent=2))
|
||||
@@ -0,0 +1,201 @@
|
||||
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'(?<!clcs)renditionId[\\"\'": ]+([0-9a-f\-]{36})',
|
||||
]
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, login_html)
|
||||
if match:
|
||||
if not clcs_session_id:
|
||||
clcs_session_id = match.group(1)
|
||||
elif not rendition_id:
|
||||
rendition_id = match.group(1)
|
||||
|
||||
if not clcs_session_id or not rendition_id:
|
||||
logger.error("Could not extract CLCS session IDs from login page")
|
||||
sys.exit(1)
|
||||
|
||||
logger.info("Submitting credentials via CLCSScreenUpdate (web flow)")
|
||||
|
||||
full_variables = {
|
||||
"format": "HTML", "imageFormat": "PNG", "locale": "en-US",
|
||||
"serverState": json.dumps({
|
||||
"realm": "growth", "name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": clcs_session_id,
|
||||
"sessionContext": {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
"login.navigationSettings": {"hideOtpToggle": True},
|
||||
},
|
||||
}, separators=(",", ":")),
|
||||
"serverScreenUpdate": json.dumps({
|
||||
"realm": "custom", "name": "growthLoginByPassword",
|
||||
"metadata": {"recaptchaSiteKey": "6Lf8hrcUAAAAAIpQAFW2VFjtiYnThOjZOA5xvLyR"},
|
||||
"loggingAction": "Submitted", "loggingCommand": "SubmitCommand",
|
||||
"referrerRenditionId": rendition_id,
|
||||
}, separators=(",", ":")),
|
||||
"inputFields": [
|
||||
{"name": "password", "value": {"stringValue": PASSWORD}},
|
||||
{"name": "userLoginId", "value": {"stringValue": EMAIL}},
|
||||
{"name": "countryCode", "value": {"stringValue": "1"}},
|
||||
{"name": "countryIsoCode", "value": {"stringValue": "US"}},
|
||||
{"name": "recaptchaResponseTime", "value": {"intValue": 445}},
|
||||
{"name": "recaptchaResponseToken", "value": {"stringValue": ""}},
|
||||
],
|
||||
}
|
||||
|
||||
response = session.post(
|
||||
"https://web.prod.cloud.netflix.com/graphql",
|
||||
json={
|
||||
"operationName": "CLCSScreenUpdate",
|
||||
"variables": full_variables,
|
||||
"extensions": {"persistedQuery": {"id": "1c276cdf-caef-49cf-b38e-384972c2b47e", "version": 102}},
|
||||
},
|
||||
headers={
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": "https://www.netflix.com/login",
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
login_resp_json = response.json()
|
||||
if "errors" in login_resp_json:
|
||||
logger.error("CLCSScreenUpdate failed: %s", login_resp_json["errors"])
|
||||
sys.exit(1)
|
||||
|
||||
# Check login result
|
||||
data = login_resp_json.get("data", {})
|
||||
result_block = data.get("result", {}) if isinstance(data, dict) else {}
|
||||
status = result_block.get("status") if isinstance(result_block, dict) else None
|
||||
|
||||
# Finalise the session
|
||||
session.get("https://www.netflix.com/browse", timeout=30)
|
||||
|
||||
auth_cookies = {cookie.name: cookie.value for cookie in session.cookies}
|
||||
auth_cookies_path.write_text(json.dumps(auth_cookies, indent=2), encoding="utf-8")
|
||||
logger.info("Authentication cookies saved")
|
||||
|
||||
has_netflix_id = "NetflixId" in auth_cookies
|
||||
if status == "SUCCESS" or has_netflix_id:
|
||||
logger.info("LOGIN SUCCESSFUL")
|
||||
tokens_output_path.write_text(json.dumps(login_resp_json, indent=4), encoding="utf-8")
|
||||
result = {"status": "SUCCESS", "auth_cookies": auth_cookies}
|
||||
# print(json.dumps(result, indent=2))
|
||||
else:
|
||||
logger.error("LOGIN FAILED — status=%s cookies=%s", status, list(auth_cookies.keys()))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,336 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
from typing import Any, Dict, Optional
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
from pywidevine import Cdm as WidevineCdm, Device as WidevineDevice
|
||||
from modules.msl.ios import MSL_IOS
|
||||
from modules.helpers import (
|
||||
ensure_output_dir,
|
||||
get_nfvdid, get_flow_session_cookies,
|
||||
save_session_cookies,
|
||||
generate_request_id, generate_esn_random_suffix,
|
||||
decrypt_msl_header,
|
||||
extract_clcs_session_id, extract_rendition_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_ios(wvd_path: Path,
|
||||
new_msl: bool = False, no_verify: bool = False,
|
||||
proxy: Optional[str] = None):
|
||||
log = setup_logger('IOS MSL')
|
||||
|
||||
OUTPUT_DIR = ensure_output_dir("ios")
|
||||
|
||||
NETFLIX_HOME_URL = "https://www.netflix.com/"
|
||||
NETFLIX_CANONICAL_URL = "https://netflix.com/"
|
||||
GRAPHQL_URL = "https://ios.prod.cloud.netflix.com/graphql"
|
||||
LOGIN_URL = "https://www.netflix.com/login"
|
||||
BROWSE_URL = "https://www.netflix.com/browse"
|
||||
APPBOOT_URL = "https://ios18.appboot.netflix.com/appboot/NFANDROID1-PRV-P-"
|
||||
MSL_HANDSHAKE_ENDPOINT = "https://ios.prod.ftl.netflix.com/nq/iosplatform/pbo_license/~1.0.0/router"
|
||||
|
||||
USER_AGENT = "Netflix/5850 CFNetwork/3826.600.41 Darwin/24.6.0"
|
||||
CLIENT_VERSION = "18.26.0"
|
||||
APP_VERSION = "18.26.0"
|
||||
HAWKINS_VERSION = "5.16.0"
|
||||
UI_FLAVOR = "argo"
|
||||
OS_VERSION = "18.6.2"
|
||||
FORM_FACTOR = "phone"
|
||||
FEATURE_CAPABILITIES = "supportsStudioBranding"
|
||||
LOCALE = "en-US"
|
||||
DEVICE_MODEL = "iPhone15,3"
|
||||
|
||||
if not wvd_path.exists():
|
||||
raise FileNotFoundError(f"Missing WVD file: {wvd_path}")
|
||||
device = WidevineDevice.load(wvd_path)
|
||||
cdm = WidevineCdm.from_device(device)
|
||||
_sid = device.system_id
|
||||
MSL_CACHE_PATH = OUTPUT_DIR / f"msl_keys_cache_ios_{_sid}.json"
|
||||
AUTH_COOKIES_PATH = OUTPUT_DIR / f"netflix_auth_cookies_{_sid}.json"
|
||||
|
||||
ESN = f"NFANDROID1-PRV-P-IPHONE15=3-{_sid}-{generate_esn_random_suffix(64)}"
|
||||
log.info("ESN: %s", ESN)
|
||||
|
||||
REQUEST_CLIENT_CONTEXT_LANDING = '{"appView":"nmLanding","appState":"foreground"}'
|
||||
REQUEST_CLIENT_CONTEXT_IDENTIFIER = '{"appView":"login","appState":"foreground"}'
|
||||
REQUEST_CLIENT_CONTEXT_PASSWORD = '{"appView":"passwordLogin","appState":"foreground"}'
|
||||
REQUEST_CLIENT_CONTEXT_PROFILES = '{"appView":"profilesGate","appState":"foreground"}'
|
||||
|
||||
APPBOOT_CLIENT_CONTEXT = '{"appState":"foreground","reason":"user-action"}'
|
||||
APPBOOT_REQUEST_CLIENT_CONTEXT = '{"appView":"unknown","appState":"foreground"}'
|
||||
|
||||
RECAPTCHA_SITE_KEY = "6Lf8hrcUAAAAAIpQAFW2VFjtiYnThOjZOA5xvLyR"
|
||||
|
||||
QUERY_IDS = {
|
||||
"MembershipStatus": {"id": "3f50f3b3-fff8-48c0-bbd3-5fa2cb04b3c1", "version": 102},
|
||||
"CLCSScreenUpdate": {"id": "1c276cdf-caef-49cf-b38e-384972c2b47e", "version": 102},
|
||||
"CLCSSendFeedback": {"id": "079b2271-196b-4edd-b65c-e9439b22e305", "version": 102},
|
||||
"CLCSInterstitialProfileGate": {"id": "b6e10c7d-0e6f-4921-83b5-177995a80d97", "version": 102},
|
||||
}
|
||||
|
||||
recaptcha_token = ""
|
||||
|
||||
verify_tls = not no_verify
|
||||
restore_auth_cookies = False
|
||||
|
||||
session = setup_session(verify_tls=verify_tls, proxy=proxy)
|
||||
_proxy = {"http": proxy, "https": proxy} if proxy else None
|
||||
|
||||
if restore_auth_cookies:
|
||||
restore_auth_cookies(session, AUTH_COOKIES_PATH, log)
|
||||
|
||||
log.info("Initializing session")
|
||||
response = session.get(NETFLIX_CANONICAL_URL, timeout=30, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
response = session.get(NETFLIX_HOME_URL, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
log.info("Requesting initial nfvdid cookie")
|
||||
appboot_request_id = generate_request_id()
|
||||
|
||||
appboot_headers = {
|
||||
"Host": "ios18.appboot.netflix.com",
|
||||
"X-Netflix.Client.appVersion": APP_VERSION,
|
||||
"Accept": "*/*",
|
||||
"X-Netflix.Request.Id": appboot_request_id,
|
||||
"X-Netflix.APIAction": "appboot",
|
||||
"X-Netflix.Client.Context": APPBOOT_CLIENT_CONTEXT,
|
||||
"X-Netflix.Client.Request.Name": "appboot",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"X-Netflix.Request.Attempt": "1",
|
||||
"X-Netflix.Request.Client.Context": APPBOOT_REQUEST_CLIENT_CONTEXT,
|
||||
"User-Agent": USER_AGENT,
|
||||
}
|
||||
|
||||
response = session.post(
|
||||
APPBOOT_URL,
|
||||
params={"keyVersion": "1"},
|
||||
headers=appboot_headers,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
nfvdid = get_nfvdid(session, response)
|
||||
|
||||
log.info("Initial nfvdid cookie obtained")
|
||||
log.info("Starting MSL Widevine exchange")
|
||||
|
||||
msl_headers = MSL_IOS.build_request_headers(
|
||||
request_name="mintCookies",
|
||||
user_agent=USER_AGENT,
|
||||
referer=None,
|
||||
esn=ESN,
|
||||
expiry_timeout=12750,
|
||||
host="ios.prod.ftl.netflix.com",
|
||||
language="en-US,en",
|
||||
device_model=quote(DEVICE_MODEL, safe=""),
|
||||
extra_headers={
|
||||
"Accept-Encoding": "deflate,gzip",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Content-Encoding": "msl_v1",
|
||||
"X-Gibbon-Cache-Control": "no-cache",
|
||||
"X-AllowCompression": "true",
|
||||
"X-Client-Request-Id": str(random.randint(10**17, 10**18 - 1)),
|
||||
"X-DeviceModel": quote(DEVICE_MODEL, safe=""),
|
||||
"x-netflix.esn": ESN,
|
||||
"X-Netflix.Client.Request.Name": "mintCookies",
|
||||
"X-Netflix.Request.Client.Context": '{"appView":"login","appState":"foreground"}',
|
||||
},
|
||||
)
|
||||
|
||||
handshake_cookies = {
|
||||
"nfvdid": nfvdid,
|
||||
}
|
||||
|
||||
msl_keys = MSL_IOS.handshake(
|
||||
msl_keys_path=str(MSL_CACHE_PATH),
|
||||
session=session,
|
||||
sender=ESN,
|
||||
cdm=cdm,
|
||||
cdm_device=str(wvd_path),
|
||||
new_msl=False,
|
||||
cookies=handshake_cookies,
|
||||
drm="widevine",
|
||||
endpoint=MSL_HANDSHAKE_ENDPOINT,
|
||||
headers=msl_headers,
|
||||
)
|
||||
|
||||
msl_client = MSL_IOS(
|
||||
session=session,
|
||||
keys=msl_keys,
|
||||
message_id=random.randint(0, pow(2, 52)),
|
||||
sender=ESN,
|
||||
drm="widevine",
|
||||
proxy=_proxy,
|
||||
)
|
||||
|
||||
nfvdid, flow_session_id = get_flow_session_cookies(session)
|
||||
|
||||
log.info("MSL Widevine exchange completed")
|
||||
|
||||
log.info("Submitting membership request")
|
||||
operation_name = "MembershipStatus"
|
||||
variables = {}
|
||||
|
||||
headers = {
|
||||
"Host": "ios.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"X-Netflix.Request.Client.Context": REQUEST_CLIENT_CONTEXT_LANDING,
|
||||
"Content-Encoding": "msl_v1",
|
||||
"x-netflix.context.feature-capabilities": FEATURE_CAPABILITIES,
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"X-Netflix.request.expiry.timeout": "15000",
|
||||
"X-Netflix.Request.Id": generate_request_id(),
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.form-factor": FORM_FACTOR,
|
||||
"X-Netflix.Request.Attempt": "1",
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "application/json",
|
||||
"x-netflix.context.locales": LOCALE,
|
||||
"x-netflix.context.os-version": OS_VERSION,
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
}
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": variables,
|
||||
"extensions": {"persistedQuery": QUERY_IDS[operation_name]},
|
||||
}
|
||||
membership_header, membership_status_response = msl_client.send_message(
|
||||
endpoint=GRAPHQL_URL,
|
||||
params={},
|
||||
application_data=body,
|
||||
headers=headers,
|
||||
)
|
||||
if isinstance(membership_status_response, dict) and "errors" in membership_status_response:
|
||||
raise RuntimeError(json.dumps(membership_status_response["errors"], indent=2))
|
||||
|
||||
log.info("Loading login page")
|
||||
response = session.get(LOGIN_URL, timeout=30)
|
||||
response.raise_for_status()
|
||||
login_html = response.text
|
||||
|
||||
clcs_session_id = extract_clcs_session_id(login_html)
|
||||
rendition_id = extract_rendition_id(login_html)
|
||||
|
||||
log.info("Submitting password screen update")
|
||||
|
||||
session_context: Dict[str, Any] = {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
}
|
||||
session_context.update({
|
||||
"login.navigationSettings": {"hideOtpToggle": True},
|
||||
})
|
||||
|
||||
full_server_state = {
|
||||
"realm": "growth",
|
||||
"name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": clcs_session_id,
|
||||
"sessionContext": session_context,
|
||||
}
|
||||
|
||||
full_screen_update = {
|
||||
"realm": "custom",
|
||||
"name": "growthLoginByPassword",
|
||||
"metadata": {"recaptchaSiteKey": RECAPTCHA_SITE_KEY},
|
||||
"loggingAction": "Submitted",
|
||||
"loggingCommand": "SubmitCommand",
|
||||
"referrerRenditionId": rendition_id,
|
||||
}
|
||||
|
||||
full_variables = {
|
||||
"format": "HTML",
|
||||
"imageFormat": "PNG",
|
||||
"locale": "en-US",
|
||||
"serverState": json.dumps(full_server_state, separators=(",", ":")),
|
||||
"serverScreenUpdate": json.dumps(full_screen_update, separators=(",", ":")),
|
||||
"inputFields": [
|
||||
{"name": "password", "value": {"stringValue": PASSWORD}},
|
||||
{"name": "userLoginId", "value": {"stringValue": EMAIL}},
|
||||
{"name": "countryCode", "value": {"stringValue": "1"}},
|
||||
{"name": "countryIsoCode", "value": {"stringValue": "US"}},
|
||||
{"name": "recaptchaResponseTime", "value": {"intValue": 445}},
|
||||
{"name": "recaptchaResponseToken", "value": {"stringValue": recaptcha_token}},
|
||||
],
|
||||
}
|
||||
try:
|
||||
operation_name = "CLCSScreenUpdate"
|
||||
headers = {
|
||||
"Host": "ios.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"X-Netflix.Request.Client.Context": REQUEST_CLIENT_CONTEXT_PASSWORD,
|
||||
"Content-Encoding": "msl_v1",
|
||||
"x-netflix.context.feature-capabilities": FEATURE_CAPABILITIES,
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"X-Netflix.request.expiry.timeout": "15000",
|
||||
"X-Netflix.Request.Id": generate_request_id(),
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.form-factor": FORM_FACTOR,
|
||||
"X-Netflix.Request.Attempt": "1",
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept": "*/*",
|
||||
"Content-Type": "application/json",
|
||||
"x-netflix.context.locales": LOCALE,
|
||||
"x-netflix.context.os-version": OS_VERSION,
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
}
|
||||
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": full_variables,
|
||||
"extensions": {
|
||||
"persistedQuery": QUERY_IDS[operation_name]
|
||||
},
|
||||
}
|
||||
|
||||
login_header, login_response = msl_client.send_message(endpoint=GRAPHQL_URL,
|
||||
params={},
|
||||
application_data=body,
|
||||
headers=headers)
|
||||
|
||||
data = login_response.get("data", {}) if isinstance(login_response, dict) else {}
|
||||
result = data.get("result", {}) if isinstance(data, dict) else {}
|
||||
|
||||
status = result.get("status")
|
||||
|
||||
encrypted_header_b64 = login_header.get("headerdata")
|
||||
header_data = {}
|
||||
|
||||
if encrypted_header_b64:
|
||||
header_data = decrypt_msl_header(encrypted_header_b64, msl_client.keys.encryption, msl_client.keys.sign)
|
||||
|
||||
except Exception:
|
||||
log.exception("Failed to process the login response")
|
||||
sys.exit(1)
|
||||
|
||||
if status == "SUCCESS":
|
||||
log.info("LOGIN SUCCESSFUL")
|
||||
|
||||
try:
|
||||
save_session_cookies(session, AUTH_COOKIES_PATH, log)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
log.error("LOGIN FAILED")
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from modules.msl.mgk import MSL_MGK, UserAuthentication
|
||||
from modules.helpers import (
|
||||
ensure_output_dir,
|
||||
save_session_cookies,
|
||||
)
|
||||
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_mgk(kpekph_path: Optional[str], esnid: str,
|
||||
new_msl: bool = False, no_verify: bool = False,
|
||||
proxy: Optional[str] = None):
|
||||
log = setup_logger('MGK MSL')
|
||||
|
||||
OUTPUT_DIR = ensure_output_dir("mgk")
|
||||
MSL_CACHE_PATH = OUTPUT_DIR / "msl_keys_cache_mgk.json"
|
||||
AUTH_COOKIES_PATH = OUTPUT_DIR / "netflix_auth_cookies_mgk.json"
|
||||
|
||||
DEVICE_TYPE = "NFANDROID2-PRV-NVIDIASHIELDANDROIDTV2019"
|
||||
|
||||
# Resolve ESNID: auto-detect file path vs raw string
|
||||
esnid_path = Path(esnid)
|
||||
if esnid_path.is_file():
|
||||
ESN = MSL_MGK.load_esnid_file(esnid_path)
|
||||
log.info("Loaded ESNID from file: %s", esnid_path)
|
||||
else:
|
||||
ESN = esnid
|
||||
log.info("Using ESNID as raw string")
|
||||
|
||||
# Resolve KpeKph: auto-detect file path vs raw string
|
||||
kpekph_file_path = None
|
||||
kpekph_raw = None
|
||||
if kpekph_path:
|
||||
kpekph_as_path = Path(kpekph_path)
|
||||
if kpekph_as_path.is_file():
|
||||
kpekph_file_path = str(kpekph_as_path)
|
||||
log.info("Loading KpeKph from file: %s", kpekph_as_path)
|
||||
else:
|
||||
kpekph_raw = kpekph_path
|
||||
log.info("Using KpeKph as raw string")
|
||||
|
||||
session = setup_session(verify_tls=not no_verify, proxy=proxy)
|
||||
_proxy = {"http": proxy, "https": proxy} if proxy else None
|
||||
|
||||
log.info("Starting MSL MGK handshake with ESN: %s", ESN)
|
||||
|
||||
handshake_headers = MSL_MGK.build_request_headers(
|
||||
request_name="mintCookies",
|
||||
esn=ESN,
|
||||
expiry_timeout=12750,
|
||||
)
|
||||
|
||||
msl_client = MSL_MGK.handshake(
|
||||
session=session,
|
||||
sender=ESN,
|
||||
kpekph_path=kpekph_file_path,
|
||||
kpekph_raw=kpekph_raw,
|
||||
msl_keys_path=str(MSL_CACHE_PATH),
|
||||
cookies=None,
|
||||
headers=handshake_headers,
|
||||
proxy=_proxy,
|
||||
new_msl=new_msl,
|
||||
)
|
||||
|
||||
log.info("MGK handshake completed successfully")
|
||||
user_auth = UserAuthentication.EmailPassword(EMAIL, PASSWORD).__dict__
|
||||
|
||||
manifest_endpoint, manifest_params = MSL_MGK.manifest_request_defaults()
|
||||
manifest_headers = MSL_MGK.build_request_headers(
|
||||
request_name="licensedManifest",
|
||||
esn=ESN,
|
||||
expiry_timeout=12750,
|
||||
)
|
||||
|
||||
log.info("Sending authenticated MSL request with EMAIL_PASSWORD user auth")
|
||||
try:
|
||||
header, payload = msl_client.send_message(
|
||||
endpoint=manifest_endpoint,
|
||||
params=manifest_params,
|
||||
application_data={},
|
||||
userauthdata=user_auth,
|
||||
headers=manifest_headers,
|
||||
)
|
||||
except Exception:
|
||||
log.exception("MGK authenticated request failed")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
auth_cookies = save_session_cookies(session, AUTH_COOKIES_PATH, log)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
result = {
|
||||
"auth_cookies": auth_cookies,
|
||||
"header": header,
|
||||
"payload": payload,
|
||||
}
|
||||
|
||||
log.info("MGK login succeeded")
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,475 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import quote
|
||||
from modules.msl.web import MSL_WEB
|
||||
from modules.helpers import (
|
||||
ensure_output_dir,
|
||||
generate_request_id, generate_hex_id,
|
||||
extract_clcs_session_id, extract_rendition_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_web(new_msl: bool = False, no_verify: bool = False,
|
||||
recaptcha_token: str = '', proxy: Optional[str] = None):
|
||||
log = setup_logger('BROWSER MSL')
|
||||
|
||||
OUTPUT_DIR = ensure_output_dir("browser")
|
||||
MSL_CACHE_PATH = OUTPUT_DIR / "msl_keys_cache_web.json"
|
||||
PRELOGIN_COOKIES_PATH = OUTPUT_DIR / "netflix_prelogin_cookies.json"
|
||||
AUTH_COOKIES_PATH = OUTPUT_DIR / "netflix_auth_cookies.json"
|
||||
|
||||
NETFLIX_HOME_URL = "https://www.netflix.com/"
|
||||
NETFLIX_CANONICAL_URL = "https://netflix.com/"
|
||||
GRAPHQL_URL = "https://web.prod.cloud.netflix.com/graphql"
|
||||
LOGIN_URL = "https://www.netflix.com/login"
|
||||
BROWSE_URL = "https://www.netflix.com/browse"
|
||||
MSL_ALE_ENDPOINT = "https://www.netflix.com/nq/msl_v1/nrdjs/pbo_tokens/%5E1.0.0/router"
|
||||
|
||||
USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/146.0.0.0 Safari/537.36"
|
||||
)
|
||||
CLIENT_VERSION = "6.135.459.031"
|
||||
APP_VERSION = "ve300d66c"
|
||||
HAWKINS_VERSION = "5.16.0"
|
||||
UI_FLAVOR = "akira"
|
||||
MSL_ESN = f"NFCDCH-02-{generate_hex_id(32, uppercase=True)}"
|
||||
REQUEST_CLIENT_CONTEXT = '{"appstate":"foreground"}'
|
||||
RECAPTCHA_SITE_KEY = "6Lf8hrcUAAAAAIpQAFW2VFjtiYnThOjZOA5xvLyR"
|
||||
|
||||
QUERY_IDS = {
|
||||
"MembershipStatus": {"id": "3f50f3b3-fff8-48c0-bbd3-5fa2cb04b3c1", "version": 102},
|
||||
"CLCSScreenUpdate": {"id": "1c276cdf-caef-49cf-b38e-384972c2b47e", "version": 102},
|
||||
"CLCSSendFeedback": {"id": "079b2271-196b-4edd-b65c-e9439b22e305", "version": 102},
|
||||
"CLCSInterstitialProfileGate": {"id": "b6e10c7d-0e6f-4921-83b5-177995a80d97", "version": 102},
|
||||
}
|
||||
|
||||
recaptcha_token = ""
|
||||
|
||||
verify_tls = not no_verify
|
||||
restore_prelogin_cookies = True
|
||||
restore_auth_cookies = False
|
||||
|
||||
session = setup_session(verify_tls=verify_tls, proxy=proxy)
|
||||
_proxy = {"http": proxy, "https": proxy} if proxy else None
|
||||
|
||||
if restore_auth_cookies:
|
||||
MSL_WEB.load_cookiejar(session, AUTH_COOKIES_PATH)
|
||||
elif restore_prelogin_cookies:
|
||||
MSL_WEB.load_cookiejar(session, PRELOGIN_COOKIES_PATH)
|
||||
|
||||
log.info("Bootstrapping anonymous browser session")
|
||||
response = session.get(NETFLIX_CANONICAL_URL, timeout=30, allow_redirects=True)
|
||||
response.raise_for_status()
|
||||
|
||||
home_response = session.get(NETFLIX_HOME_URL, timeout=30)
|
||||
home_response.raise_for_status()
|
||||
|
||||
log.info("Sending MembershipStatus probe")
|
||||
operation_name = "MembershipStatus"
|
||||
variables = {}
|
||||
referer = NETFLIX_HOME_URL
|
||||
originating_url = NETFLIX_HOME_URL
|
||||
|
||||
headers = {
|
||||
"Host": "web.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"x-netflix.request.id": generate_request_id(),
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"x-netflix.request.originating.url": originating_url,
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.locales": "en-us",
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
"x-netflix.request.toplevel.uuid": str(uuid.uuid4()),
|
||||
"x-netflix.request.attempt": "1",
|
||||
"x-netflix.request.client.context": REQUEST_CLIENT_CONTEXT,
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": referer,
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"x-netflix.request.client.version": CLIENT_VERSION,
|
||||
"x-netflix.request.client.id": "ui/akiraWeb",
|
||||
}
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": variables,
|
||||
"extensions": {"persistedQuery": QUERY_IDS[operation_name]},
|
||||
}
|
||||
response = session.post(GRAPHQL_URL, json=body, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
membership_status_response = response.json()
|
||||
if "errors" in membership_status_response:
|
||||
raise RuntimeError(json.dumps(membership_status_response["errors"], indent=2))
|
||||
|
||||
log.info("Fetching login page and extracting screen context")
|
||||
response = session.get(LOGIN_URL, timeout=30)
|
||||
response.raise_for_status()
|
||||
login_html = response.text
|
||||
|
||||
clcs_session_id = extract_clcs_session_id(login_html)
|
||||
rendition_id = extract_rendition_id(login_html)
|
||||
|
||||
screen_name = "IDENTIFICATION"
|
||||
screen_name = "PASSWORD_LOGIN"
|
||||
|
||||
log.info("Submitting password step directly to PASSWORD_LOGIN")
|
||||
|
||||
from typing import Any, Dict
|
||||
session_context: Dict[str, Any] = {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
}
|
||||
session_context.update({
|
||||
"login.navigationSettings": {"hideOtpToggle": True},
|
||||
})
|
||||
full_server_state = {
|
||||
"realm": "growth",
|
||||
"name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": clcs_session_id,
|
||||
"sessionContext": session_context,
|
||||
}
|
||||
|
||||
full_screen_update = {
|
||||
"realm": "custom",
|
||||
"name": "growthLoginByPassword",
|
||||
"metadata": {"recaptchaSiteKey": RECAPTCHA_SITE_KEY},
|
||||
"loggingAction": "Submitted",
|
||||
"loggingCommand": "SubmitCommand",
|
||||
"referrerRenditionId": rendition_id,
|
||||
}
|
||||
|
||||
full_variables = {
|
||||
"format": "HTML",
|
||||
"imageFormat": "PNG",
|
||||
"locale": "en-US",
|
||||
"serverState": json.dumps(full_server_state, separators=(",", ":")),
|
||||
"serverScreenUpdate": json.dumps(full_screen_update, separators=(",", ":")),
|
||||
"inputFields": [
|
||||
{"name": "password", "value": {"stringValue": PASSWORD}},
|
||||
{"name": "userLoginId", "value": {"stringValue": EMAIL}},
|
||||
{"name": "countryCode", "value": {"stringValue": "1"}},
|
||||
{"name": "countryIsoCode", "value": {"stringValue": "US"}},
|
||||
{"name": "recaptchaResponseTime", "value": {"intValue": 445}},
|
||||
{"name": "recaptchaResponseToken", "value": {"stringValue": recaptcha_token}},
|
||||
],
|
||||
}
|
||||
|
||||
try:
|
||||
operation_name = "CLCSScreenUpdate"
|
||||
referer = NETFLIX_HOME_URL
|
||||
originating_url = f"{LOGIN_URL}?serverState={quote(json.dumps(full_server_state, separators=(',', ':')))}"
|
||||
|
||||
headers = {
|
||||
"Host": "web.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"x-netflix.request.id": generate_request_id(),
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"x-netflix.request.originating.url": originating_url,
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.locales": "en-us",
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
"x-netflix.request.toplevel.uuid": str(uuid.uuid4()),
|
||||
"x-netflix.request.attempt": "1",
|
||||
"x-netflix.request.client.context": REQUEST_CLIENT_CONTEXT,
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": referer,
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"x-netflix.request.client.version": CLIENT_VERSION,
|
||||
"x-netflix.request.client.id": "ui/akiraWeb",
|
||||
}
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": full_variables,
|
||||
"extensions": {"persistedQuery": QUERY_IDS[operation_name]},
|
||||
}
|
||||
response = session.post(GRAPHQL_URL, json=body, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
login_response = response.json()
|
||||
if "errors" in login_response:
|
||||
raise RuntimeError(json.dumps(login_response["errors"], indent=2))
|
||||
|
||||
except Exception as exc:
|
||||
log.warning("Full PASSWORD_LOGIN submit failed, retrying with minimal payload: %s", exc)
|
||||
|
||||
session_context = {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
}
|
||||
minimal_server_state = {
|
||||
"realm": "growth",
|
||||
"name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": clcs_session_id,
|
||||
"sessionContext": session_context,
|
||||
}
|
||||
|
||||
minimal_screen_update = {
|
||||
"realm": "custom",
|
||||
"name": "growthLoginByPassword",
|
||||
}
|
||||
|
||||
minimal_variables = {
|
||||
"format": "HTML",
|
||||
"imageFormat": "PNG",
|
||||
"locale": "en-US",
|
||||
"serverState": json.dumps(minimal_server_state, separators=(",", ":")),
|
||||
"serverScreenUpdate": json.dumps(minimal_screen_update, separators=(",", ":")),
|
||||
"inputFields": [
|
||||
{"name": "userLoginId", "value": {"stringValue": EMAIL}},
|
||||
{"name": "password", "value": {"stringValue": PASSWORD}},
|
||||
],
|
||||
}
|
||||
|
||||
operation_name = "CLCSScreenUpdate"
|
||||
referer = NETFLIX_HOME_URL
|
||||
originating_url = f"{LOGIN_URL}?serverState={quote(json.dumps(minimal_server_state, separators=(',', ':')))}"
|
||||
|
||||
headers = {
|
||||
"Host": "web.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"x-netflix.request.id": generate_request_id(),
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"x-netflix.request.originating.url": originating_url,
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.locales": "en-us",
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
"x-netflix.request.toplevel.uuid": str(uuid.uuid4()),
|
||||
"x-netflix.request.attempt": "1",
|
||||
"x-netflix.request.client.context": REQUEST_CLIENT_CONTEXT,
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": referer,
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"x-netflix.request.client.version": CLIENT_VERSION,
|
||||
"x-netflix.request.client.id": "ui/akiraWeb",
|
||||
}
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": minimal_variables,
|
||||
"extensions": {"persistedQuery": QUERY_IDS[operation_name]},
|
||||
}
|
||||
response = session.post(GRAPHQL_URL, json=body, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
login_response = response.json()
|
||||
if "errors" in login_response:
|
||||
raise RuntimeError(json.dumps(login_response["errors"], indent=2))
|
||||
|
||||
response_text = json.dumps(login_response, ensure_ascii=False)
|
||||
screen_names = re.findall(r'"name":"([A-Z_]+)"', response_text)
|
||||
rendition_ids = re.findall(r'"renditionId":"([0-9a-f\-]{36})"', response_text)
|
||||
clcs_match = re.search(r'"clcsSessionId":"([0-9a-f\-]{36})"', response_text)
|
||||
|
||||
next_clcs_session_id = clcs_match.group(1) if clcs_match else clcs_session_id
|
||||
next_screen_name = screen_names[-1] if screen_names else "PASSWORD_LOGIN"
|
||||
next_rendition_id = rendition_ids[-1] if rendition_ids else rendition_id
|
||||
|
||||
feedback_payload = None
|
||||
effect = login_response.get("data", {}).get("result", {}).get("effect", {})
|
||||
nodes = effect.get("nodes", []) if isinstance(effect, dict) else []
|
||||
for node in nodes:
|
||||
if node.get("__typename") == "CLCSSendFeedback" and node.get("serverFeedback"):
|
||||
feedback_payload = json.loads(node["serverFeedback"])
|
||||
break
|
||||
|
||||
if feedback_payload:
|
||||
log.info("Sending CLCSSendFeedback after successful login")
|
||||
|
||||
session_context = {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
}
|
||||
session_context.update({
|
||||
"login.navigationSettings": {"hideOtpToggle": True},
|
||||
})
|
||||
feedback_server_state = {
|
||||
"realm": "growth",
|
||||
"name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": next_clcs_session_id,
|
||||
"sessionContext": session_context,
|
||||
}
|
||||
|
||||
operation_name = "CLCSSendFeedback"
|
||||
feedback_variables = {
|
||||
"inputFields": [],
|
||||
"serverFeedback": json.dumps(feedback_payload, separators=(",", ":")),
|
||||
"serverState": json.dumps(feedback_server_state, separators=(",", ":")),
|
||||
}
|
||||
referer = NETFLIX_HOME_URL
|
||||
originating_url = f"{LOGIN_URL}?serverState={quote(json.dumps(feedback_server_state, separators=(',', ':')))}"
|
||||
|
||||
headers = {
|
||||
"Host": "web.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"x-netflix.request.id": generate_request_id(),
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"x-netflix.request.originating.url": originating_url,
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.locales": "en-us",
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
"x-netflix.request.toplevel.uuid": str(uuid.uuid4()),
|
||||
"x-netflix.request.attempt": "1",
|
||||
"x-netflix.request.client.context": REQUEST_CLIENT_CONTEXT,
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": referer,
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"x-netflix.request.client.version": CLIENT_VERSION,
|
||||
"x-netflix.request.client.id": "ui/akiraWeb",
|
||||
}
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": feedback_variables,
|
||||
"extensions": {"persistedQuery": QUERY_IDS[operation_name]},
|
||||
}
|
||||
response = session.post(GRAPHQL_URL, json=body, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
feedback_response = response.json()
|
||||
if "errors" in feedback_response:
|
||||
raise RuntimeError(json.dumps(feedback_response["errors"], indent=2))
|
||||
else:
|
||||
log.info("No post-login feedback payload was found")
|
||||
|
||||
log.info("Opening /browse to finalize the authenticated web session")
|
||||
session_context = {
|
||||
"session-breadcrumbs": {"funnel_name": "loginWeb"},
|
||||
}
|
||||
session_context.update({
|
||||
"login.navigationSettings": {"hideOtpToggle": True},
|
||||
})
|
||||
browse_server_state = {
|
||||
"realm": "growth",
|
||||
"name": "PASSWORD_LOGIN",
|
||||
"clcsSessionId": next_clcs_session_id,
|
||||
"sessionContext": session_context,
|
||||
}
|
||||
browse_originating_url = f"{LOGIN_URL}?serverState={quote(json.dumps(browse_server_state, separators=(',', ':')))}"
|
||||
|
||||
response = session.get(
|
||||
BROWSE_URL,
|
||||
headers={
|
||||
"Referer": browse_originating_url,
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
log.info("Probing the post-login profile gate")
|
||||
profile_gate_response = None
|
||||
try:
|
||||
operation_name = "CLCSInterstitialProfileGate"
|
||||
variables = {"format": "HTML", "resolutionMode": "WEB_1X"}
|
||||
referer = NETFLIX_HOME_URL
|
||||
originating_url = BROWSE_URL
|
||||
|
||||
headers = {
|
||||
"Host": "web.prod.cloud.netflix.com",
|
||||
"Connection": "keep-alive",
|
||||
"x-netflix.request.id": generate_request_id(),
|
||||
"x-netflix.context.operation-name": operation_name,
|
||||
"x-netflix.request.originating.url": originating_url,
|
||||
"x-netflix.context.app-version": APP_VERSION,
|
||||
"x-netflix.context.hawkins-version": HAWKINS_VERSION,
|
||||
"x-netflix.context.locales": "en-us",
|
||||
"x-netflix.context.ui-flavor": UI_FLAVOR,
|
||||
"x-netflix.request.toplevel.uuid": str(uuid.uuid4()),
|
||||
"x-netflix.request.attempt": "1",
|
||||
"x-netflix.request.client.context": REQUEST_CLIENT_CONTEXT,
|
||||
"x-netflix.request.clcs.bucket": "high",
|
||||
"User-Agent": USER_AGENT,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Origin": "https://www.netflix.com",
|
||||
"Referer": referer,
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"x-netflix.request.client.version": CLIENT_VERSION,
|
||||
"x-netflix.request.client.id": "ui/akiraWeb",
|
||||
}
|
||||
body = {
|
||||
"operationName": operation_name,
|
||||
"variables": variables,
|
||||
"extensions": {"persistedQuery": QUERY_IDS[operation_name]},
|
||||
}
|
||||
response = session.post(GRAPHQL_URL, json=body, headers=headers, timeout=30)
|
||||
response.raise_for_status()
|
||||
profile_gate_response = response.json()
|
||||
if "errors" in profile_gate_response:
|
||||
raise RuntimeError(json.dumps(profile_gate_response["errors"], indent=2))
|
||||
except Exception as exc:
|
||||
log.warning("Profile gate probe failed: %s", exc)
|
||||
|
||||
log.info("Attempting post-login ALE provision")
|
||||
ale_response = None
|
||||
try:
|
||||
final_cookies = MSL_WEB.cookiejar_to_ordered_dict(session.cookies)
|
||||
|
||||
req_id = generate_request_id()
|
||||
endpoint = (
|
||||
f"{MSL_ALE_ENDPOINT}?reqAttempt=1&reqName=aleProvision&reqId={req_id}"
|
||||
f"&clienttype={UI_FLAVOR}&uiversion={APP_VERSION}&browsername=chrome"
|
||||
f"&browserversion=146.0.0.0&osname=windows&osversion=10.0"
|
||||
)
|
||||
|
||||
headers = MSL_WEB.build_request_headers(
|
||||
request_name="aleProvision",
|
||||
user_agent=USER_AGENT,
|
||||
referer=BROWSE_URL,
|
||||
esn=MSL_ESN,
|
||||
extra_headers={
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
)
|
||||
|
||||
ale_response = MSL_WEB.handshake(
|
||||
msl_keys_path=MSL_CACHE_PATH,
|
||||
session=session,
|
||||
sender=MSL_ESN,
|
||||
new_msl=False,
|
||||
cookies=final_cookies,
|
||||
endpoint=endpoint,
|
||||
headers=headers,
|
||||
)
|
||||
except Exception as exc:
|
||||
log.error("ALE provision failed: %s", exc)
|
||||
|
||||
auth_cookies = MSL_WEB.cookiejar_to_ordered_dict(session.cookies)
|
||||
AUTH_COOKIES_PATH.write_text(json.dumps(auth_cookies, indent=2), encoding="utf-8")
|
||||
|
||||
result = {
|
||||
"auth_cookies": auth_cookies,
|
||||
}
|
||||
|
||||
#print(json.dumps(result, indent=2))
|
||||
Reference in New Issue
Block a user