From ef57bda36605eb84b965efe520409df170b7c1b7 Mon Sep 17 00:00:00 2001 From: Arthur Joppart Date: Sun, 28 Jul 2024 19:07:39 +0200 Subject: [PATCH] feat: [dl-downer] support new GoPlay site (no DRM content for now) --- dl-downer/src/downloaders/GOPLAY.py | 151 ++++++++++++++++++---------- 1 file changed, 96 insertions(+), 55 deletions(-) diff --git a/dl-downer/src/downloaders/GOPLAY.py b/dl-downer/src/downloaders/GOPLAY.py index b314d76..2ff7631 100644 --- a/dl-downer/src/downloaders/GOPLAY.py +++ b/dl-downer/src/downloaders/GOPLAY.py @@ -1,3 +1,4 @@ +import base64 import json import re import os @@ -41,30 +42,33 @@ def extract_goplay_bearer_token() -> str: playwright = None try: + assert os.getenv('AUTH_GOPLAY_EMAIL'), 'AUTH_GOPLAY_EMAIL not set' + assert os.getenv('AUTH_GOPLAY_PASSWORD'), 'AUTH_GOPLAY_PASSWORD not set' playwright, browser, page = create_playwright_page(DLRequestPlatform.GOPLAY) page.goto("https://www.goplay.be/profiel", wait_until='networkidle') handle_goplay_consent_popup(page) try: - page.wait_for_selector('span.profile-page__header__email', timeout=2000) + # Find the email on the page to check if we're already logged in + assert os.getenv('AUTH_GOPLAY_EMAIL') in page.content(), 'Not logged in' logger.debug('Already logged in') page.context.storage_state(path=state_file) except: logger.debug('Logging in ...') - openLogin = page.wait_for_selector('div.not-logged-in button') + # This used to be a popup, but now it's a link + openLogin = page.wait_for_selector('a[href="/login"]') openLogin.click() - emailInput = page.wait_for_selector('input#email') - assert os.getenv('AUTH_GOPLAY_EMAIL'), 'AUTH_GOPLAY_EMAIL not set' + emailInput = page.wait_for_selector('input#login-form-email') emailInput.type(os.getenv('AUTH_GOPLAY_EMAIL')) - passwordInput = page.wait_for_selector('input#password') - assert os.getenv('AUTH_GOPLAY_PASSWORD'), 'AUTH_GOPLAY_PASSWORD not set' + passwordInput = page.wait_for_selector('input#login-form-password') passwordInput.type(os.getenv('AUTH_GOPLAY_PASSWORD')) - submitButton = page.wait_for_selector('form button[type="submit"]') + submitButton = page.wait_for_selector('form:has(input#login-form-email) button') submitButton.click() - # find h2 element with test 'Mijn lijst' - page.wait_for_selector('span.profile-page__header__email') + page.wait_for_selector('aside a[href="/"] svg') + page.goto("https://www.goplay.be/profiel", wait_until='networkidle') + assert os.getenv('AUTH_GOPLAY_EMAIL') in page.content(), 'Login failed' logger.debug('Logged in successfully') page.context.storage_state(path=state_file) @@ -92,28 +96,23 @@ def GOPLAY_DL(dl_request: DLRequest): # Parse video uuid from the page page_resp = requests.get(dl_request.video_page_or_manifest_url) page_content = page_resp.text - obj_string = re.search(r'
(?:(?!).)*playerContainer(?:(?!).)*', page_content) + if scr_tag is None: + logger.error('No video data found') + return + full_obj_string = re.search(r'push\((.*)\)', scr_tag.group(0)) + obj_string_match = re.search(r'playerContainer.*?children.*?(\{.*?\})\]\}\]', full_obj_string.group(1)) + obj_string = obj_string_match.group(1).replace('\\"', '"') obj = json.loads(obj_string) - video_object = None - if len(obj['data']['playlists']) == 0: - # This is a movie - video_object = obj['data']['movie'] - else: - # This is a series - for playlist in obj['data']['playlists']: - for episode in playlist['episodes']: - if episode['pageInfo']['url'] == dl_request.video_page_or_manifest_url.split('#')[0]: - video_object = episode - break - - video_uuid = video_object['videoUuid'] - type_form = video_object['type'] - type_form = re.sub('_', '-', type_form) - is_drm = video_object['isDrm'] + video_object = obj['video'] + # logger.debug(f'Video object: {video_object}') + video_uuid = video_object['uuid'] + type_form = video_object['videoType'] + is_drm = video_object['flags']['isDrm'] if dl_request.output_filename is None: - title = video_object['pageInfo']['title'] + title = video_object['title'] title = parse_filename(title) else: title = dl_request.output_filename @@ -123,40 +122,82 @@ def GOPLAY_DL(dl_request: DLRequest): logger.debug(f'Is DRM: {is_drm}') logger.debug(f'Title: {title}') - # get video data - video_data_url = f'https://api.goplay.be/web/v1/videos/{type_form}/{video_uuid}' - logger.debug(f'Video data URL: {video_data_url}') - bearer_token = extract_goplay_bearer_token() - video_data_resp = requests.get( - video_data_url, - headers={ 'authorization': f'Bearer {bearer_token}' }, - ) - if video_data_resp.status_code != 200: - logger.debug(video_data_resp.text) - video_data = video_data_resp.json() - content_source_id = video_data['ssai']['contentSourceID'] - logger.debug(f'Content source ID: {content_source_id}') - video_id = video_data['ssai']['videoID'] - logger.debug(f'Video ID: {video_id}') - - # get video streams - streams_resp = requests.post(f'https://dai.google.com/ondemand/dash/content/{content_source_id}/vid/{video_id}/streams') - streams = streams_resp.json() - stream_manifest = streams['stream_manifest'] + stream_collection = video_object['streamCollection'] + stream_collection_streams = stream_collection['streams'] + stream_drm_protected = stream_collection['drmProtected'] + # Find dash stream + stream_manifest = '' + for stream in stream_collection_streams: + if stream['protocol'] == 'dash': + stream_manifest = stream['url'] + break + if stream_manifest == '': + logger.error('No dash stream found') + return logger.debug(f'Stream manifest: {stream_manifest}') - + logger.debug(f'DRM protected: {stream_drm_protected}') + keys = {} - if is_drm: - drm_xml = video_data['drmXml'] - logger.debug(f'DRM XML: {drm_xml}') + if is_drm or stream_drm_protected: + raise NotImplementedError('DRM protected content not supported') manifest_response = requests.get(stream_manifest) - pssh = re.findall(r']*>(.{,120})', manifest_response.text)[0] + pssh = re.findall(r']*>(.{,120})', manifest_response.text)[0] logger.debug(f'PSSH: {pssh}') cdm = Local_CDM() challenge = cdm.generate_challenge(pssh) - headers = { 'Customdata': drm_xml } - lic_res = requests.post('https://wv-keyos.licensekeyserver.com/', data=challenge, headers=headers) + drm_key = stream_collection['drmKey'] + custom_data_xml = f''' + + + + + 1 + HDCP_NONE + CGMS_NONE + + + HDCP_NONE + + + {drm_key} + + 962c4b25-4c65-4b21-a6a3-fe2c7590053c + + + c7e7a2b0-87ad-4659-b59b-e5edf68bd5ea + + + + 2000 + + + + + 500 + 250 + 150 + + + C3FD11C6-F8B7-4D20-B008-1DB17D61F2DA + + + + + {drm_key} + + local + 2024-07-28 17:01:37.817 + 2024-07-28 17:11:37.817 + 4dcdb908372d410cad01c0fc4120911e + 3c202c0ea50420870eb63e2412fa69f9 + + kDDHB7xDVEbJMb6DucvdA0PxoeUslNn4AkqMq+kKMQQ/zATmjAYmE+iV8pcLanwPIp9kjKt6/RFcZuoYov8F2gWKfvaeSrxvBIMbjsmXpvoio61Kvl9GmnFkjh8KZxFDj23XxZ+dpCmoveUFSJvbq8VAsRqkCQGtpOJMzPdkdifE/TxmyIJXI0f6x5jPThpu/muqpaYLRCzpscbURx5vbU5kOSYDFcgEdL3jgoP4aOlM7p4mwo7FLyifNtqTNCP1+eKwIZf1ZkfhK7JHPFxOo7a//CaCaG52ER6amo2KloR+Vh1xWY4TPAiAj7VdxAac4Sp+AZpak79hdrpwZEDyig== + + ''' + base64_custom_data = base64.b64encode(custom_data_xml.encode()).decode() + headers = { 'Customdata': base64_custom_data } + lic_res = requests.post('https://widevine.keyos.com/api/v4/getLicense', data=challenge, headers=headers) lic_res.raise_for_status() keys = cdm.decrypt_response(lic_res.content) logger.debug(f'Keys: {keys}')