fix(netflix): harden ESN cache checks and Widevine type test

Handle Netflix ESN cache values more defensively to avoid key/type errors and
stale reuse by validating cache shape, cache expiry, and device type before
reusing values. Also log the final ESN safely when cache data is not a dict.

Alias `pywidevine.Cdm` to `WidevineCDM` and use it in DRM system detection so
Widevine instances are identified correctly.

Also include related config updates: add ESN map entry for system ID `12063`,
ignore `binaries/`, and refresh local runtime defaults in `unshackle.yaml`.fix(netflix): harden ESN cache checks and Widevine type test

Handle Netflix ESN cache values more defensively to avoid key/type errors and
stale reuse by validating cache shape, cache expiry, and device type before
reusing values. Also log the final ESN safely when cache data is not a dict.

Alias `pywidevine.Cdm` to `WidevineCDM` and use it in DRM system detection so
Widevine instances are identified correctly.

Also include related config updates: add ESN map entry for system ID `12063`,
ignore `binaries/`, and refresh local runtime defaults in `unshackle.yaml`.
This commit is contained in:
kenzuya
2026-03-02 17:29:32 +07:00
parent fb14f412d4
commit 3e45f3efe7
4 changed files with 16 additions and 9 deletions

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@ unshackle/PRDs/
temp/ temp/
logs/ logs/
Temp/ Temp/
binaries/
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@@ -17,7 +17,7 @@ from Crypto.Random import get_random_bytes
import jsonpickle import jsonpickle
from pymp4.parser import Box from pymp4.parser import Box
from pywidevine import PSSH, Cdm, DeviceTypes from pywidevine import PSSH, Cdm as WidevineCDM, DeviceTypes
from pyplayready import PSSH as PlayReadyPSSH from pyplayready import PSSH as PlayReadyPSSH
import requests import requests
from langcodes import Language from langcodes import Language
@@ -563,13 +563,15 @@ class Netflix(Service):
'esn': esn, 'esn': esn,
'type': self.cdm.device_type 'type': self.cdm.device_type
} }
if self.esn.data["esn"] != esn: cached_esn = self.esn.data.get("esn") if isinstance(self.esn.data, dict) else self.esn.data
self.esn.set(self.config["esn_map"][self.cdm.system_id], 1 * 60 * 60) cached_type = self.esn.data.get("type") if isinstance(self.esn.data, dict) else None
if cached_esn != esn or cached_type != DeviceTypes.ANDROID or (hasattr(self.esn, "expired") and self.esn.expired):
self.esn.set(esn_value, expiration=1 * 60 * 60)
else: else:
ESN_GEN = "".join(random.choice("0123456789ABCDEF") for _ in range(30)) ESN_GEN = "".join(random.choice("0123456789ABCDEF") for _ in range(30))
generated_esn = f"NFCDIE-03-{ESN_GEN}" generated_esn = f"NFCDIE-03-{ESN_GEN}"
# Check if ESN is expired or doesn't exist # Check if ESN is expired or doesn't exist
if self.esn.data is None or self.esn.data == {} or (hasattr(self.esn, 'expired') and self.esn.expired) or (self.esn.data["type"] != DeviceTypes.CHROME): if not isinstance(self.esn.data, dict) or self.esn.data == {} or (hasattr(self.esn, 'expired') and self.esn.expired) or (self.esn.data.get("type") != DeviceTypes.CHROME):
# Set new ESN with 6-hour expiration # Set new ESN with 6-hour expiration
esn_value = { esn_value = {
'esn': generated_esn, 'esn': generated_esn,
@@ -579,7 +581,8 @@ class Netflix(Service):
self.log.info(f"Generated new ESN with 1-hour expiration") self.log.info(f"Generated new ESN with 1-hour expiration")
else: else:
self.log.info(f"Using cached ESN.") self.log.info(f"Using cached ESN.")
self.log.info(f"ESN: {self.esn.data["esn"]}") final_esn = self.esn.data.get("esn") if isinstance(self.esn.data, dict) else self.esn.data
self.log.info(f"ESN: {final_esn}")
def get_metadata(self, title_id: str): def get_metadata(self, title_id: str):
@@ -1216,7 +1219,7 @@ class Netflix(Service):
def get_drm_system(self) -> Literal["widevine", "playready"]: def get_drm_system(self) -> Literal["widevine", "playready"]:
# This is widevine? # This is widevine?
if isinstance(self.cdm, Widevine): if isinstance(self.cdm, WidevineCDM):
return "widevine" return "widevine"
elif isinstance(self.cdm, PlayReady): elif isinstance(self.cdm, PlayReady):
return "playready" return "playready"

View File

@@ -16,6 +16,7 @@ esn_map:
8159: "NFANDROID1-PRV-P-GOOGLEPIXEL" 8159: "NFANDROID1-PRV-P-GOOGLEPIXEL"
8131: "HISETVK84500000000000000000000000007401422" 8131: "HISETVK84500000000000000000000000007401422"
22590: "NFANDROID1-PXA-P-L3-XIAOMM2102J20SG-22590-0202084EBTP55D0HO2TOCSM3VR9MOSTTJT2L97EKVN9E8PFA1QQ439QC70QTTTV82LC7KUSD3O0HUB0HKH51DH0N7A7GFJKSJ5S6FFE0" 22590: "NFANDROID1-PXA-P-L3-XIAOMM2102J20SG-22590-0202084EBTP55D0HO2TOCSM3VR9MOSTTJT2L97EKVN9E8PFA1QQ439QC70QTTTV82LC7KUSD3O0HUB0HKH51DH0N7A7GFJKSJ5S6FFE0"
12063: "NFANDROID1-PRV-P-SHENZHENKTC-49B1U-12063-2PAENERYJWY35H7F24163TMUCBBA4VRHQ2XZX4OBU4MUTKYFW50BMFBVGTUMN6IM0"
endpoints: endpoints:
website: "https://www.netflix.com/nq/website/memberapi/{build_id}/pathEvaluator" website: "https://www.netflix.com/nq/website/memberapi/{build_id}/pathEvaluator"
manifest: "https://www.netflix.com/msl/playapi/cadmium/licensedmanifest/1" manifest: "https://www.netflix.com/msl/playapi/cadmium/licensedmanifest/1"

View File

@@ -57,18 +57,17 @@ credentials:
default: ["user@email.com", ":PasswordWith:Colons"] default: ["user@email.com", ":PasswordWith:Colons"]
Netflix: Netflix:
default: ["sako.sako1109@gmail.com", "sako1109"] default: ["ariel-prinsess828@ezweb.ne.jp", "AiNe892186"]
# default: ["pbgarena0838@gmail.com", "Andhika1978"] # default: ["pbgarena0838@gmail.com", "Andhika1978"]
# Override default directories used across unshackle # Override default directories used across unshackle
directories: directories:
cache: Cache cache: Cache
# cookies: Cookies # cookies: Cookies
dcsl: DCSL # Device Certificate Status List dcsl: DCSL # Device Certificate Status List
downloads: Downloads downloads: /home/kenzuya/Mounts/ketuakenzuya/Downloads
logs: Logs logs: Logs
temp: Temp temp: Temp
# wvds: WVDs # wvds: WVDs
prds: PRDs
# Additional directories that can be configured: # Additional directories that can be configured:
# commands: Commands # commands: Commands
# services: # services:
@@ -252,6 +251,9 @@ services:
# External proxy provider services # External proxy provider services
proxy_providers: proxy_providers:
basic:
SG:
- "http://127.0.0.1:6004"
surfsharkvpn: surfsharkvpn:
username: SkyCBP7kH8KqxDwy5Qw36mQn # Service credentials from https://my.surfshark.com/vpn/manual-setup/main/openvpn username: SkyCBP7kH8KqxDwy5Qw36mQn # Service credentials from https://my.surfshark.com/vpn/manual-setup/main/openvpn
password: pcmewxKTNPvLENdbKJGh8Cgt # Service credentials (not your login password) password: pcmewxKTNPvLENdbKJGh8Cgt # Service credentials (not your login password)