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:
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/
logs/
Temp/
binaries/
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -17,7 +17,7 @@ from Crypto.Random import get_random_bytes
import jsonpickle
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
import requests
from langcodes import Language
@@ -563,13 +563,15 @@ class Netflix(Service):
'esn': esn,
'type': self.cdm.device_type
}
if self.esn.data["esn"] != esn:
self.esn.set(self.config["esn_map"][self.cdm.system_id], 1 * 60 * 60)
cached_esn = self.esn.data.get("esn") if isinstance(self.esn.data, dict) else self.esn.data
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:
ESN_GEN = "".join(random.choice("0123456789ABCDEF") for _ in range(30))
generated_esn = f"NFCDIE-03-{ESN_GEN}"
# 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
esn_value = {
'esn': generated_esn,
@@ -579,7 +581,8 @@ class Netflix(Service):
self.log.info(f"Generated new ESN with 1-hour expiration")
else:
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):
@@ -1216,7 +1219,7 @@ class Netflix(Service):
def get_drm_system(self) -> Literal["widevine", "playready"]:
# This is widevine?
if isinstance(self.cdm, Widevine):
if isinstance(self.cdm, WidevineCDM):
return "widevine"
elif isinstance(self.cdm, PlayReady):
return "playready"

View File

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

View File

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