MovixOpenSource/API/proxiesembed/drmproxy/services/app_nzrplus_com.py
Movix 1401dc20f3 tird release
mon compte github a été détecté comme du spam je crois
2026-04-28 17:55:00 +02:00

581 lines
22 KiB
Python

import builtins
import json
import os
import re
import threading
from os.path import join
import m3u8
import requests
from utils.constants.macros import ERR_MSG, USER_ERROR, CACHE_DIR, APP_ERROR
from utils.main_service import main_service
from utils.structs import BaseElement, BaseService, CustomException
from utils.tools.args import check_range
from utils.tools.cdm import get_pssh_from_default_kid
from utils.tools.common import clean_url, get_valid_filename, dict_to_file, file_to_dict
class app_nzrplus_com(BaseService):
DEMO_URLS = [
"https://app.nzrplus.com/video/642249",
"https://app.nzrplus.com/video/526149?playlistId=25137",
"https://app.nzrplus.com/playlist/25137",
"https://app.nzrplus.com/playlist/17076",
"https://app.nzrplus.com/section/BlackFerns",
"https://app.nzrplus.com/section/AllBlacks",
]
BUCKETS_URL = 'https://dce-frontoffice.imggaming.com/api/v4/content/{section_id}'
BUCKET_VODS_URL = 'https://dce-frontoffice.imggaming.com/api/v4/content/{section_id}/bucket/{bucket_id}'
PLAYLIST_URL = 'https://dce-frontoffice.imggaming.com/api/v4/playlist/{playlist_id}'
REALM_URL = 'https://dce-frontoffice.imggaming.com/api/v1/init/'
LOGIN_URL = 'https://dce-frontoffice.imggaming.com/api/v2/login'
REFRESH_URL = 'https://dce-frontoffice.imggaming.com/api/v2/token/refresh'
VOD_URL = 'https://dce-frontoffice.imggaming.com/api/v4/vod/{vod_id}'
BASE_URL = "https://app.nzrplus.com"
VIDEO_URL = BASE_URL + "/video/{vod_id}"
EMAIL = "YOUR_EMAIL"
PASSWORD = "YOUR_PASSWORD"
SITE_DICT = None
CACHE_FILE = None
LOCK = None
DRM_INFO = "eyJzeXN0ZW0iOiJjb20ud2lkZXZpbmUuYWxwaGEifQ=="
PAGE_SIZE = 25
BUCKET_SIZE = 10
@staticmethod
def test_service():
main_service.run_service(app_nzrplus_com)
@staticmethod
def get_additional_params(additional):
add_params = BaseService.get_additional_params(additional)
if additional.get("USE_BASE_URL", False) is True:
add_params += [("BASE_URL", lambda s: s.format(value=additional["MANIFEST_URL"]))]
return add_params
@staticmethod
def credentials_needed():
return {
"EMAIL": app_nzrplus_com.EMAIL,
"PASSWORD": app_nzrplus_com.PASSWORD
}
@staticmethod
def set_site_dict():
try:
file_dict = file_to_dict(app_nzrplus_com.CACHE_FILE)
assert len(file_dict.keys()) == 4
app_nzrplus_com.SITE_DICT = file_dict
return True
except:
pass
class_name = app_nzrplus_com.__name__.replace("_", ".")
credentials = builtins.CONFIG["SERVICE_CREDENTIALS"][class_name]
try:
assert type(credentials["EMAIL"]) is str
assert type(credentials["PASSWORD"]) is str
except:
return None
response = requests.get(app_nzrplus_com.BASE_URL).content.decode()
app_js = re.findall(r'src="([^"]*app\.js)"', response)[0]
if app_js[0] != "/":
app_js = "/" + app_js
app_js = app_nzrplus_com.BASE_URL + app_js
response = requests.get(app_js).content.decode()
realm_api_key = re.findall(r'API_KEY:"([^"]+)"', response)[0]
response = requests.get(
app_nzrplus_com.REALM_URL,
headers={
'x-api-key': realm_api_key,
'Origin': app_nzrplus_com.BASE_URL
}
)
response = response.content.decode()
response = json.loads(response)
realm_id = response["settings"]["realm"]
response = requests.post(
app_nzrplus_com.LOGIN_URL,
headers={
'x-api-key': realm_api_key, 'Realm': realm_id,
'Authorization': f'Bearer {response["authentication"]["authorisationToken"]}'
},
json={
'id': credentials["EMAIL"],
'secret': credentials["PASSWORD"]
}
)
response = response.content.decode()
response = json.loads(response)
message = str(response.get("messages", [])).lower()
if response.get("status", "") in [404] and response.get("code", "").lower() in ["not_found"]:
if "failedauthentication" in message:
return None
site_dict = {
"REALM_ID": realm_id,
"REALM_API_KEY": realm_api_key,
"AUTH_TOKEN": response["authorisationToken"],
"REFRESH_TOKEN": response["refreshToken"]
}
dict_to_file(app_nzrplus_com.CACHE_FILE, site_dict)
app_nzrplus_com.SITE_DICT = site_dict
return True
@staticmethod
def initialize_service():
if app_nzrplus_com.CACHE_FILE is None:
app_nzrplus_com.CACHE_FILE = join(CACHE_DIR, f'{app_nzrplus_com.__name__}.json')
if builtins.CONFIG.get("FRESH", False) is True:
dict_to_file(app_nzrplus_com.CACHE_FILE, {})
if app_nzrplus_com.SITE_DICT is None:
flag = app_nzrplus_com.set_site_dict()
if flag is None:
return flag
if app_nzrplus_com.LOCK is None:
app_nzrplus_com.LOCK = threading.Lock()
return app_nzrplus_com
@staticmethod
def refresh_account_tokens(source_url):
old_auth_token = app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]
with app_nzrplus_com.LOCK:
if old_auth_token == app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]:
response = requests.post(
app_nzrplus_com.REFRESH_URL,
headers={
'realm': app_nzrplus_com.SITE_DICT["REALM_ID"],
'x-api-key': app_nzrplus_com.SITE_DICT["REALM_API_KEY"],
'Authorization': f'Bearer {app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]}'
},
json={'refreshToken': app_nzrplus_com.SITE_DICT["REFRESH_TOKEN"]}
)
response = response.content.decode()
response = json.loads(response)
if response.get("authorisationToken", None) in ["", None]:
raise CustomException(ERR_MSG.format(
type=USER_ERROR,
url=source_url,
reason="Can't access the video content because the cached information expired",
solution=f'Delete the {app_nzrplus_com.CACHE_FILE} cache manually or add the parameter --fresh to your command'
))
app_nzrplus_com.SITE_DICT["AUTH_TOKEN"] = response["authorisationToken"]
dict_to_file(app_nzrplus_com.CACHE_FILE, app_nzrplus_com.SITE_DICT)
@staticmethod
def get_keys(challenge, additional):
licence = requests.post(
additional["license_url"],
headers={
'Authorization': f'Bearer {additional["drm_token"]}',
'X-DRM-INFO': app_nzrplus_com.DRM_INFO
},
data=challenge
)
licence.raise_for_status()
return licence.content
@staticmethod
def generate_master_file(output_path, manifest, manifest_type, manifest_content):
if not os.path.exists(output_path):
os.makedirs(output_path)
if manifest_type not in ["hls"]:
output_path = join(output_path, "master.mpd")
with open(output_path, "w") as f:
f.write(manifest_content)
return output_path
base_url = clean_url(manifest).split("/")
del base_url[-1]
base_url = "/".join(base_url)
manifest_content = m3u8.loads(manifest_content)
for segment in manifest_content.playlists + manifest_content.media + manifest_content.segments:
if not segment.uri.startswith("http"):
if segment.uri.startswith("/"):
segment.uri = ".." + segment.uri
dots = segment.uri.count("../")
temp_base_url = base_url.split("/")
temp_base_url = temp_base_url[0:len(temp_base_url) - dots]
temp_base_url = "/".join(temp_base_url)
segment.uri = segment.uri.replace("../", "")
if not segment.uri.startswith("/"):
segment.uri = "/" + segment.uri
segment.uri = temp_base_url + segment.uri
output_path = join(output_path, "master.m3u8")
with open(output_path, "w") as f:
f.write(manifest_content.dumps())
return output_path
@staticmethod
def get_video_data(source_element):
vod_id = re.search(r"/video/([^/?]*)", source_element.url).group(1)
response = {}
for i in range(0, 2):
response = requests.get(
app_nzrplus_com.VOD_URL.format(vod_id=vod_id),
params={'includePlaybackDetails': 'URL'},
headers={
'realm': app_nzrplus_com.SITE_DICT["REALM_ID"],
'x-api-key': app_nzrplus_com.SITE_DICT["REALM_API_KEY"],
'Authorization': f'Bearer {app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]}'
}
)
response = response.content.decode()
response = json.loads(response)
message = str(response.get("messages", [])).lower()
if 400 <= response.get("status", 200) < 500 and " not found" in message:
raise CustomException(ERR_MSG.format(
type=f'{USER_ERROR}',
url=source_element.url,
reason="The content isn't available",
solution="Do not attempt to download it"
))
if response.get("playerUrlCallback", None) in ["", None]:
if i == 1:
break
app_nzrplus_com.refresh_account_tokens(source_element.url)
continue
break
if source_element.collection is None:
source_element.collection = join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
app_nzrplus_com.__name__
)
if source_element.element is None:
title = response.get("title", f'Video_{vod_id}')
info = response.get("episodeInformation", {})
for f in ["seasonNumber", "episodeNumber"]:
try:
assert info[f] not in ["", None]
title += " " + f[0].upper() + str(info[f])
except:
pass
source_element.element = get_valid_filename(title)
response = requests.get(response["playerUrlCallback"])
response = response.content.decode()
response = json.loads(response)
manifests = []
for f in ["hls", "dash"]:
try:
m = response[f]
assert len(m) > 0
for mt in m:
manifests.append((mt, f))
except:
pass
non_drm_manifests = []
drm_manifests = []
for m, t in manifests:
m_drm = m.get("drm", None)
if m_drm is None or type(m_drm) is not dict:
non_drm_manifests.append((m, t))
continue
if m_drm.get("jwtToken", None) is None:
non_drm_manifests.append((m, t))
continue
systems = m_drm.get("keySystems", [])
if systems is None or type(systems) is not list:
continue
systems = [s.lower() for s in systems]
if "widevine" in systems:
drm_manifests.append((m, t))
additional = {}
pssh_value = None
is_drm = False
if len(non_drm_manifests) > 0:
manifest, manifest_type = non_drm_manifests[0]
else:
if len(drm_manifests) == 0:
raise CustomException(ERR_MSG.format(
type=USER_ERROR,
url=source_element.url,
reason=f"DRM not supported. Widevine wasn't found",
solution="Do not attempt to download it"
))
manifest, manifest_type = drm_manifests[0]
additional["license_url"] = manifest["drm"]["url"]
additional["drm_token"] = manifest["drm"]["jwtToken"]
is_drm = True
manifest = manifest["url"]
manifest_content = requests.get(manifest).content.decode()
if is_drm:
try:
pssh_value = get_pssh_from_default_kid(manifest_content)
except:
pass
if pssh_value is None:
raise CustomException(ERR_MSG.format(
type=APP_ERROR,
url=source_element.url,
reason=f"Manifest format not supported: {manifest}",
solution=f"Extend the {app_nzrplus_com.__name__} service"
))
additional["USE_BASE_URL"] = manifest_type in ["dash"]
additional["MANIFEST_URL"] = clean_url(manifest)
manifest = app_nzrplus_com.generate_master_file(
join(source_element.collection, source_element.element),
manifest, manifest_type, manifest_content
)
return manifest, pssh_value, additional
@staticmethod
def get_playlist(collection_url):
playlist_id = re.search(r"/playlist/([^/?]*)", collection_url).group(1)
response = {}
last_seen = None
collection_title = None
collection = []
vod_index = 0
while True:
playlist_params = {'rpp': app_nzrplus_com.PAGE_SIZE}
if last_seen is not None:
playlist_params["lastSeen"] = last_seen
for i in range(0, 2):
response = requests.get(
app_nzrplus_com.PLAYLIST_URL.format(playlist_id=playlist_id),
params=playlist_params,
headers={
'realm': app_nzrplus_com.SITE_DICT["REALM_ID"],
'x-api-key': app_nzrplus_com.SITE_DICT["REALM_API_KEY"],
'Authorization': f'Bearer {app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]}'
}
)
response = response.content.decode()
response = json.loads(response)
if response.get("code", None) in ["UNAUTHORIZED"]:
if i == 1:
break
app_nzrplus_com.refresh_account_tokens(collection_url)
continue
break
vods = response.get("vods", [])
if len(vods) == 0:
break
if collection_title is None:
collection_title = response.get("title", f'Playlist_{playlist_id}')
collection_title = join(
join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
app_nzrplus_com.__name__
),
get_valid_filename(collection_title)
)
for vod in vods:
vod_index += 1
check = check_range(False, None, vod_index)
if check is True:
continue
elif check is False:
return collection
if vod.get("type", "").lower() not in ["vod"]:
continue
vod_url = app_nzrplus_com.VIDEO_URL.format(vod_id=vod["id"])
vod_title = vod.get("title", vod["id"])
vod_title = f'Vod_{vod_index}_{vod_title}'
collection.append(BaseElement(
url=vod_url,
collection=collection_title,
element=get_valid_filename(vod_title)
))
paging = response.get("paging", {})
if paging in ["", None]:
break
if paging.get("moreDataAvailable", False) in [None, False]:
break
last_seen = paging.get("lastSeen", None)
if last_seen is None:
break
return collection
@staticmethod
def get_section(collection_url):
section_id = collection_url.split("/")[-1]
buckets_response = {}
buckets_last_seen = None
collection_title = join(
join(
str(builtins.CONFIG["DOWNLOAD_COMMANDS"]["OUTPUT_MEDIA_PATH"]),
app_nzrplus_com.__name__
),
get_valid_filename(f"Section {section_id}")
)
collection = []
bucket_index = 0
while True:
buckets_params = {
'bpp': app_nzrplus_com.BUCKET_SIZE,
'rpp': app_nzrplus_com.PAGE_SIZE,
'displaySectionLinkBuckets': 'SHOW',
'displayEpgBuckets': 'HIDE',
'displayEmptyBucketShortcuts': 'SHOW',
'displayContentAvailableOnSignIn': 'SHOW',
'displayGeoblocked': 'SHOW',
'bspp': app_nzrplus_com.BUCKET_SIZE
}
if buckets_last_seen is not None:
buckets_params["lastSeen"] = buckets_last_seen
for i in range(0, 2):
buckets_response = requests.get(
app_nzrplus_com.BUCKETS_URL.format(section_id=section_id),
params=buckets_params,
headers={
'realm': app_nzrplus_com.SITE_DICT["REALM_ID"],
'x-api-key': app_nzrplus_com.SITE_DICT["REALM_API_KEY"],
'Authorization': f'Bearer {app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]}'
}
)
buckets_response = buckets_response.content.decode()
buckets_response = json.loads(buckets_response)
if buckets_response.get("code", None) in ["UNAUTHORIZED"]:
if i == 1:
break
app_nzrplus_com.refresh_account_tokens(collection_url)
continue
break
buckets = buckets_response.get("buckets", [])
if len(buckets) == 0:
break
for bucket in buckets:
bucket_index += 1
check = check_range(True, bucket_index, None)
if check is True:
continue
elif check is False:
return collection
bucket_type = bucket.get("type", "").lower()
if "vod" not in bucket_type and "video" not in bucket_type:
continue
bucket_title = bucket.get("name", bucket.get("rowTypeData", {}).get("title", ""))
bucket_title = get_valid_filename(f'Rail {bucket_index} {bucket_title}')
bucket_id = bucket["exid"]
vods_response = {}
vods_last_seen = None
vod_index = 0
while True:
vods_params = {
'rpp': app_nzrplus_com.PAGE_SIZE,
'displayContentAvailableOnSignIn': 'SHOW',
'displayGeoblocked': 'SHOW'
}
if vods_last_seen is not None:
vods_params["lastSeen"] = vods_last_seen
for i in range(0, 2):
vods_response = requests.get(
app_nzrplus_com.BUCKET_VODS_URL.format(section_id=section_id, bucket_id=bucket_id),
params=vods_params,
headers={
'realm': app_nzrplus_com.SITE_DICT["REALM_ID"],
'x-api-key': app_nzrplus_com.SITE_DICT["REALM_API_KEY"],
'Authorization': f'Bearer {app_nzrplus_com.SITE_DICT["AUTH_TOKEN"]}'
}
)
vods_response = vods_response.content.decode()
vods_response = json.loads(vods_response)
if vods_response.get("code", None) in ["UNAUTHORIZED"]:
if i == 1:
break
app_nzrplus_com.refresh_account_tokens(collection_url)
continue
break
vods = vods_response.get("contentList", [])
if len(vods) == 0:
break
for vod in vods:
vod_index += 1
check = check_range(False, bucket_index, vod_index)
if check is True:
continue
elif check is False:
return collection
if vod.get("type", "").lower() not in ["vod"]:
continue
vod_url = app_nzrplus_com.VIDEO_URL.format(vod_id=vod["id"])
vod_title = vod.get("title", vod["id"])
vod_title = f'Vod_{vod_index}_{vod_title}'
collection.append(BaseElement(
url=vod_url,
collection=join(collection_title, bucket_title),
element=get_valid_filename(vod_title)
))
vods_paging = vods_response.get("paging", {})
if vods_paging in ["", None]:
break
if vods_paging.get("moreDataAvailable", False) in [None, False]:
break
vods_last_seen = vods_paging.get("lastSeen", None)
if vods_last_seen is None:
break
buckets_paging = buckets_response.get("paging", {})
if buckets_paging in ["", None]:
break
if buckets_paging.get("moreDataAvailable", False) in [None, False]:
break
buckets_last_seen = buckets_paging.get("lastSeen", None)
if buckets_last_seen is None:
break
return collection
@staticmethod
def get_collection_elements(collection_url):
collection_url = clean_url(collection_url)
if "/video/" in collection_url:
return [BaseElement(url=collection_url)]
if "/playlist/" in collection_url:
return app_nzrplus_com.get_playlist(collection_url)
if "/section/" in collection_url:
return app_nzrplus_com.get_section(collection_url)
return None