Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/crunchyroll.py (Python)

Code before:
        },
        'params': {'skip_download': 'm3u8'},
        'expected_warnings': ['Unable to download XML']
    }]

    def _real_extract(self, url):
        lang, internal_id, display_id = self._match_valid_url(url).group('lang', 'internal_id', 'id')
        webpage = self._download_webpage(url, display_id)
        initial_state = self._parse_json(
            self._search_regex(r'__INITIAL_STATE__\s*=\s*({.+?})\s*;', webpage, 'initial state'),
            display_id)
        episode_data = traverse_obj(initial_state, ('content', 'byId', internal_id))
        if not self._get_cookies(url).get('etp_rt'):
            video_id = episode_data.get('external_id').split('.')[1]
            series_id = traverse_obj(episode_data, ('episode_metadata', 'series_slug_title'))
            return self.url_result(f'https://www.crunchyroll.com/{lang}{series_id}/{display_id}-{video_id}',
                                   CrunchyrollIE.ie_key(), video_id)

        app_config = self._parse_json(
            self._search_regex(r'__APP_CONFIG__\s*=\s*({.+?})\s*;', webpage, 'app config'),
            display_id)
        client_id = traverse_obj(app_config, ('cxApiParams', 'accountAuthClientId'))
        api_domain = traverse_obj(app_config, ('cxApiParams', 'apiDomain'))
        basic_token = str(base64.b64encode(('%s:' % client_id).encode('ascii')), 'ascii')
        auth_response = self._download_json(
            api_domain + '/auth/v1/token', display_id,
            note='Authenticating with cookie',
            headers={
                'Authorization': 'Basic ' + basic_token
            }, data='grant_type=etp_rt_cookie'.encode('ascii'))
        policy_response = self._download_json(
            api_domain + '/index/v2', display_id,
            note='Retrieving signed policy',
            headers={
                'Authorization': auth_response.get('token_type') + ' ' + auth_response.get('access_token')
            })
        bucket = traverse_obj(policy_response, ('cms', 'bucket'))
        params = {
            'Policy': traverse_obj(policy_response, ('cms', 'policy')),
            'Signature': traverse_obj(policy_response, ('cms', 'signature')),
            'Key-Pair-Id': traverse_obj(policy_response, ('cms', 'key_pair_id')),
            'locale': traverse_obj(initial_state, ('localization', 'locale'))
        }
        episode_response = self._download_json(
            api_domain + '/cms/v2' + bucket + '/episodes/' + internal_id, display_id,
            note='Retrieving episode metadata',
            query=params)
        stream_response = self._download_json(
            episode_response.get('playback'), display_id,
            note='Retrieving stream info')


Review comment (security):
```suggestion
            f'{api_domain}/auth/v1/token', display_id,
```
etc

Code after:
            'upload_date': '20160402',
        },
        'params': {'skip_download': 'm3u8'},
        'expected_warnings': ['Unable to download XML']
    }]

    def _real_extract(self, url):
        lang, internal_id, display_id = self._match_valid_url(url).group('lang', 'internal_id', 'id')
        webpage = self._download_webpage(url, display_id)
        initial_state = self._parse_json(
            self._search_regex(r'__INITIAL_STATE__\s*=\s*({.+?})\s*;', webpage, 'initial state'),
            display_id)
        episode_data = initial_state['content']['byId'][internal_id]
        if not self._get_cookies(url).get('etp_rt'):
            video_id = episode_data['external_id'].split('.')[1]
            series_id = episode_data['episode_metadata']['series_slug_title']
            return self.url_result(f'https://www.crunchyroll.com/{lang}{series_id}/{display_id}-{video_id}',
                                   CrunchyrollIE.ie_key(), video_id)

        app_config = self._parse_json(
            self._search_regex(r'__APP_CONFIG__\s*=\s*({.+?})\s*;', webpage, 'app config'),
            display_id)
        client_id = app_config['cxApiParams']['accountAuthClientId']
        api_domain = app_config['cxApiParams']['apiDomain']
        basic_token = str(base64.b64encode(('%s:' % client_id).encode('ascii')), 'ascii')
        auth_response = self._download_json(
            f'{api_domain}/auth/v1/token', display_id,
            note='Authenticating with cookie',
            headers={
                'Authorization': 'Basic ' + basic_token
            }, data='grant_type=etp_rt_cookie'.encode('ascii'))
        policy_response = self._download_json(
            f'{api_domain}/index/v2', display_id,
            note='Retrieving signed policy',
            headers={
                'Authorization': auth_response['token_type'] + ' ' + auth_response['access_token']
            })
        bucket = policy_response['cms']['bucket']
        params = {
            'Policy': policy_response['cms']['policy'],
            'Signature': policy_response['cms']['signature'],
            'Key-Pair-Id': policy_response['cms']['key_pair_id']
        }
        locale = traverse_obj(initial_state, ('localization', 'locale'))
        if locale:
            params['locale'] = locale
        episode_response = self._download_json(
            f'{api_domain}/cms/v2{bucket}/episodes/{internal_id}', display_id,
            note='Retrieving episode metadata',
            query=params)
        if episode_response.get('is_premium_only') and not episode_response.get('playback'):