78 lines
3.6 KiB
Python
78 lines
3.6 KiB
Python
import argparse
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
import json
|
|
import logging
|
|
import coloredlogs
|
|
from cdm.wks import WvDecrypt, device_android_generic, PsshExtractor, KeyExtractor
|
|
|
|
# def search(data, target_slug):
|
|
# if isinstance(data, list):
|
|
# for item in data:
|
|
# result = search(item, target_slug)
|
|
# if result:
|
|
# return result
|
|
# elif isinstance(data, dict):
|
|
# for key, value in data.items():
|
|
# if key == "slug" and value == target_slug:
|
|
# return data.get("productId")
|
|
# else:
|
|
# result = search(value, target_slug)
|
|
# if result:
|
|
# return result
|
|
# return None
|
|
|
|
parser = argparse.ArgumentParser(description='PYWKS-NPO')
|
|
|
|
parser.add_argument('-url', dest='url', required=True, help='NPO Video URL')
|
|
parser.add_argument("-logger", default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], help="Logger level")
|
|
args = parser.parse_args()
|
|
LOG_FORMAT = "{asctime} [{levelname[0]}] {name} : {message}"
|
|
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
LOG_STYLE = "{"
|
|
coloredlogs.install(level=args.logger, fmt=LOG_FORMAT, datefmt=LOG_DATE_FORMAT, style=LOG_STYLE)
|
|
logger = logging.getLogger("NPO")
|
|
|
|
response_target_id = requests.get(args.url)
|
|
content = response_target_id.content
|
|
try:
|
|
target_slug = args.url.split("/")[7]
|
|
except IndexError:
|
|
logger.error("Invalid URL format. Example: https://npo.nl/start/serie/wie-is-de-mol/seizoen-24/wie-is-de-mol_56/afspelen")
|
|
exit()
|
|
soup = BeautifulSoup(content, 'html.parser')
|
|
script_tag = soup.find('script', {'id': '__NEXT_DATA__'})
|
|
script_content = script_tag.contents[0] if script_tag else logger.error("Script tag not found.")
|
|
data_dict = json.loads(script_content)
|
|
target_product_id = search(data_dict, target_slug)
|
|
if not target_product_id:
|
|
logger.error("Failed to retrieve target product ID.")
|
|
exit()
|
|
response_csrf = requests.get('https://npo.nl/start/api/auth/session')
|
|
cookies = {'__Host-next-auth.csrf-token': response_csrf.cookies.get_dict()["__Host-next-auth.csrf-token"],'__Secure-next-auth.callback-url': 'https://npo.nl'}
|
|
json_product_id = {'productId': target_product_id}
|
|
response_token = requests.post('https://npo.nl/start/api/domain/player-token', cookies=cookies, json=json_product_id)
|
|
headers = {'authorization': response_token.json()["token"]}
|
|
json_auth = {'profileName': 'dash', 'drmType': 'widevine', 'referrerUrl': args.url}
|
|
response = requests.post('https://prod.npoplayer.nl/stream-link', headers=headers, json=json_auth)
|
|
stream_data = response.json().get('stream', {})
|
|
if not stream_data.get('streamURL'):
|
|
logger.error("Failed to retrieve MPD URL. Invalid or expired authentication token.")
|
|
exit()
|
|
mpd_url = stream_data.get('streamURL')
|
|
logger.info(f"MPD URL: {mpd_url}")
|
|
license_url = "https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication"
|
|
response = requests.get(mpd_url, headers=headers)
|
|
pssh_extractor = PsshExtractor(response.text)
|
|
pssh_value = pssh_extractor.extract_pssh()
|
|
logger.info(f"PSSH: {pssh_value}")
|
|
headers_license = {'x-custom-data': stream_data.get('drmToken'),'origin': 'https://start-player.npo.nl','referer': 'https://start-player.npo.nl/'}
|
|
cert_b64 = None
|
|
key_extractor = KeyExtractor(pssh_value, cert_b64, license_url, headers_license)
|
|
keys = key_extractor.get_keys()
|
|
wvdecrypt = WvDecrypt(init_data_b64=pssh_value, cert_data_b64=cert_b64, device=device_android_generic)
|
|
raw_challenge = wvdecrypt.get_challenge()
|
|
for key in keys:
|
|
if isinstance(key, list) and key:
|
|
for key_str in key:
|
|
logger.info(f"\u251C KEY: {key_str}") |