fixed shit

This commit is contained in:
Quinten0508 2024-04-22 17:27:39 +02:00
parent 93f5b14420
commit 47a81dca29
7 changed files with 42 additions and 1078 deletions

10
.gitignore vendored
View File

@ -2,7 +2,7 @@
Thumbs.db Thumbs.db
desktop.ini desktop.ini
# OS X # MacOS
.DS_Store .DS_Store
.Spotlight-V100 .Spotlight-V100
.Trashes .Trashes
@ -16,5 +16,9 @@ desktop.ini
N_m3u8DL-RE* N_m3u8DL-RE*
mp4decrypt* mp4decrypt*
env/ env/
avondshow.txt urls.txt
urls/ urls/
cdm/
# misc
test.py

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,8 @@
# Pre-requisites: # Pre-requisites:
# * N_m3u8DL-RE and mp4decrypt in current directory # * N_m3u8DL-RE and mp4decrypt in current directory
# * ffmpeg in PATH # * ffmpeg in PATH
# * pip install -r requirements.txt
# PIP Requirements:
# * protobuf
# * bs4
# * xmltodict
# * browser_cookie3
# * requests
# * pycryptodomex
import argparse import argparse
import requests import requests
@ -16,14 +10,17 @@ import subprocess
import os import os
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import json import json
import platform # check for windows OS import platform # check for windows OS
import shutil # check for ffmpeg in PATH, part of python std import shutil # check for ffmpeg in PATH
import browser_cookie3 # cookies for premium accs import browser_cookie3 # cookies for premium accs
from fake_useragent import UserAgent # sets useragent
import concurrent.futures # concurrent downloads when using a -file
import time # just for lols
from cdm.wks import WvDecrypt, device_android_generic, PsshExtractor, KeyExtractor from cdm.wks import WvDecrypt, device_android_generic, PsshExtractor, KeyExtractor
import concurrent.futures
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.3', 'User-Agent': UserAgent(platforms='pc', min_version=120.0).random,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5', 'Accept-Language': 'en-US,en;q=0.5',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
@ -71,13 +68,15 @@ def find_cookies():
if not userinput or userinput.lower() != 'y': if not userinput or userinput.lower() != 'y':
return return
cookies = browser_cookie3.load(domain_name='npo.nl') # browser_cookie3.load() should use ALL browsers' cookies. If this doesn't work, replace browser_cookie3.load with browser_cookie3.<browser>.
# See notes at the end of this script for possible options. Example: browser_cookie3.chrome or browser_cookie3.librewolf.
cookies = browser_cookie3.librewolf(domain_name='npo.nl')
return cookies return cookies
def find_targetId(url): def find_targetId(url):
# Get full HTML and extract productId and episode number # Get full HTML and extract productId and episode number
# "future proof" # "future proof" :)
response_targetId = requests.get(url) response_targetId = requests.get(url)
content = response_targetId.content content = response_targetId.content
@ -97,7 +96,6 @@ def find_targetId(url):
script_content = script_tag.contents[0] script_content = script_tag.contents[0]
else: else:
print("Script tag not found.") print("Script tag not found.")
print("Hint: Use the -token <token> argument to supply your own.")
def search(data, target_slug): def search(data, target_slug):
if isinstance(data, list): if isinstance(data, list):
@ -123,10 +121,20 @@ def find_targetId(url):
def find_CSRF(targetId, plus_cookie): def find_CSRF(targetId, plus_cookie):
response_CSRF = requests.get('https://npo.nl/start/api/auth/session', headers=headers, cookies=plus_cookie) response_CSRF = requests.get('https://npo.nl/start/api/auth/session', headers=headers, cookies=plus_cookie)
response_cookies = response_CSRF.cookies.get_dict() response_cookies = response_CSRF.cookies.get_dict()
csrf = response_cookies["__Host-next-auth.csrf-token"]
if plus_cookie:
csrf_label = "__Secure-next-auth.session-token"
else:
csrf_label = "__Host-next-auth.csrf-token"
# LOGGED IN: __Secure-next-auth.session-token
# LOGGED OUT: __Host-next-auth.csrf-token
# sigh.
csrf = response_cookies[csrf_label]
csrf_cookies = { csrf_cookies = {
'__Host-next-auth.csrf-token': csrf, csrf_label: csrf,
'__Secure-next-auth.callback-url': 'https%3A%2F%2Fnpo.nl', '__Secure-next-auth.callback-url': 'https%3A%2F%2Fnpo.nl',
} }
@ -137,11 +145,13 @@ def find_CSRF(targetId, plus_cookie):
'productId': targetId, 'productId': targetId,
} }
response_token = requests.post('https://npo.nl/start/api/domain/player-token', cookies=plus_cookie, headers=headers, json=json_productId) response_token = requests.get('https://npo.nl/start/api/domain/player-token', cookies=plus_cookie, headers=headers, params=json_productId)
token = response_token.json()["token"] token = response_token.json()["token"]
return token return token
def find_MPD(token, url, plus_cookie): def find_MPD(token, url, plus_cookie):
headers['Authorization'] = token headers['Authorization'] = token
@ -209,13 +219,14 @@ def check_prereq():
def create_filename(url, programKey): def create_filename(url, programKey):
# season title
# 1 2 3 4 5 6 7 8 (optional) # 1 2 3 4 5 6 7 8 (optional)
# create filename based on input URL: https://npo.nl/start/serie /wie-is-de-mol /seizoen-24 /wie-is-de-mol_56 /afspelen # create filename based on input URL: https://npo.nl/start/serie /wie-is-de-mol /seizoen-24 /wie-is-de-mol_56 /afspelen
# https://npo.nl/start/serie /de-avondshow-met-arjen-lubach /seizoen-8_1 /de-avondshow-met-arjen-lubach_93 /afspelen # https://npo.nl/start/serie /de-avondshow-met-arjen-lubach /seizoen-8_1 /de-avondshow-met-arjen-lubach_93 /afspelen
# https://npo.nl/start/serie /taarten-van-abel /seizoen-17 /joto /afspelen
url_split = url.split("/") url_split = url.split("/")
title = url_split[7].split("_")[0] title = url_split[7].split("_")[0]
season = url_split[6].split("_")[0] season = url_split[6].split("_")[0]
# filename_enc = title + "_" + season + "_ep-" + programKey + "_encrypted"
filename_enc = title + "_" + season + "_ep-" + programKey + "_encrypted" filename_enc = title + "_" + season + "_ep-" + programKey + "_encrypted"
filename = filename_enc.replace("_encrypted", "") filename = filename_enc.replace("_encrypted", "")
return filename_enc, filename return filename_enc, filename
@ -285,6 +296,7 @@ def execute(url, plus_cookie, process_no):
plus_cookie = find_cookies() plus_cookie = find_cookies()
start_time = time.time()
max_workers = min(os.cpu_count(), len(urls)) max_workers = min(os.cpu_count(), len(urls))
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
@ -296,6 +308,8 @@ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
completed_videos += 1 completed_videos += 1
print(f"{completed_videos}/{len(urls)} video{'s'[:len(urls) != 1]} completed") print(f"{completed_videos}/{len(urls)} video{'s'[:len(urls) != 1]} completed")
execution_time = time.time() - start_time
print("Execution time in seconds: " + str(execution_time))
######### #########
# NOTES # # NOTES #

135
npo.py
View File

@ -1,135 +0,0 @@
import argparse
import requests
from bs4 import BeautifulSoup
import json
from cdm.wks import WvDecrypt, device_android_generic, PsshExtractor, KeyExtractor
# Parse URL input
parser = argparse.ArgumentParser(description='PYWKS-NPO')
parser.add_argument('-url', dest='url', required=True, help='NPO Video URL')
args = parser.parse_args()
# Get HTML and extract productId
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-site',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
}
response_targetId = requests.get(args.url, headers=headers)
content = response_targetId.content
try:
url_split = args.url.split("/")
target_slug = url_split[7]
except:
print("URL invalid.")
print("URL format: https://npo.nl/start/serie/wie-is-de-mol/seizoen-24/wie-is-de-mol_56/afspelen")
print(f"Your URL: {args.url}")
exit()
soup = BeautifulSoup(content, 'html.parser')
script_tag = soup.find('script', {'id': '__NEXT_DATA__'})
if script_tag:
script_content = script_tag.contents[0]
else:
print("Script tag not found.")
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
data_dict = json.loads(script_content)
target_product_id = search(data_dict, target_slug)
# Get CSRF token
response_CSRF = requests.get('https://npo.nl/start/api/auth/session', headers=headers)
response_cookies = response_CSRF.cookies.get_dict()
csrf = response_cookies["__Host-next-auth.csrf-token"]
# Get player token
cookies = {
'__Host-next-auth.csrf-token': csrf,
'__Secure-next-auth.callback-url': 'https%3A%2F%2Fnpo.nl',
}
json_productId = {
'productId': target_product_id,
}
response_token = requests.post('https://npo.nl/start/api/domain/player-token', cookies=cookies, headers=headers, json=json_productId)
token = response_token.json()["token"]
# Get MPD URL
headers['authorization'] = token
json_auth = {
'profileName': 'dash',
'drmType': 'widevine',
'referrerUrl': args.url,
}
response = requests.post('https://prod.npoplayer.nl/stream-link', headers=headers, json=json_auth)
response_data = response.json()
stream_data = response_data.get('stream', {})
if stream_data.get('streamURL'):
print('MPD URL:', stream_data.get('streamURL'))
else:
print("NO MPD URL - BAD TOKEN")
exit()
# Get PSSH
mpd_url = stream_data.get('streamURL')
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()
print("PSSH:", pssh_value)
headers_license = {
'x-custom-data': stream_data.get('drmToken'),
'origin': 'https://start-player.npo.nl',
'referer': 'https://start-player.npo.nl/',
}
# Get Key
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()
data = raw_challenge
for key in keys:
if isinstance(key, list):
if key:
for key_str in key:
print(f"KEY: {key_str}")

View File

@ -1,78 +0,0 @@
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}")

View File

@ -3,4 +3,5 @@ bs4
xmltodict xmltodict
browser_cookie3 browser_cookie3
requests requests
pycryptodomex pycryptodomex
fake-useragent