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

Code before:
            'timestamp': 1640661495,
            'is_live': True,
        },
        'skip': 'Livestream has ended',
    }, {
        'url': 'https://play.afreecatv.com/pyh3646/237852185',
        'only_matching': True,
    }, {
        'url': 'https://play.afreecatv.com/pyh3646',
        'only_matching': True,
    }]

    _LIVE_API_URL = 'https://live.afreecatv.com/afreeca/player_live_api.php'

    def _real_extract(self, url):
        broadcaster_id, broadcast_no = self._match_valid_url(url).group('id', 'bno')
        password = self.get_param('videopassword')
        info = self._download_json(self._LIVE_API_URL, broadcaster_id, data=urlencode_postdata({'bid': broadcaster_id}))
        channel_info = info.get('CHANNEL')
        broadcaster_id = channel_info.get('BJID') or broadcaster_id
        broadcast_no = channel_info.get('BNO') or broadcast_no
        password_protected = channel_info.get('BPWD')
        if not broadcast_no:
            raise UserNotLive(video_id=broadcaster_id)

        if password_protected == 'Y' and password is None:
            raise ExtractorError(
                'This livestream is protected by a password, use the --video-password option',
                expected=True)

        params = {'bno': broadcast_no, 'stream_type': 'common', 'type': 'aid', 'quality': 'master', 'pwd': password}

        aid_response = self._download_json(self._LIVE_API_URL, broadcast_no, data=urlencode_postdata(params),
                                           note='Downloading access token for stream',
                                           errnote='Unable to download access token for stream')
        aid = traverse_obj(aid_response, ('CHANNEL', 'AID'))
        if not aid:
            raise ExtractorError(f'Unable to extract AID from {aid_response}', expected=False)

        stream_base_url = channel_info.get('RMD') or 'https://livestream-manager.afreecatv.com'
        stream_info = self._download_json(f'{stream_base_url}/broad_stream_assign.html', broadcast_no, query={
            # works: gs_cdn_pc_app, gs_cdn_mobile_web, gs_cdn_pc_web
            'return_type': 'gs_cdn_pc_app',
            'broad_key': f'{broadcast_no}-common-master-hls',
        }, note='Downloading metadata for stream', errnote='Unable to download metadata for stream')

        station_info = self._download_json('https://st.afreecatv.com/api/get_station_status.php', broadcast_no,
                                           query={'szBjId': broadcaster_id}, note='Downloading channel metadata',
                                           errnote='Unable to download channel metadata')

        formats = self._extract_m3u8_formats(update_url_query(stream_info['view_url'], {'aid': aid}),

Review comment (suggestion):
as suggested above
```suggestion
        password = self.get_param('videopassword')
        if channel_info.get('BPWD') == 'Y' and password is None:
```

Code after:
            'ext': 'mp4',
            'title': '【 우루과이 오늘은 무슨일이? 】',
            'uploader': '박진우[JINU]',
            'uploader_id': 'pyh3646',
            'timestamp': 1640661495,
            'is_live': True,
        },
        'skip': 'Livestream has ended',
    }, {
        'url': 'https://play.afreecatv.com/pyh3646/237852185',
        'only_matching': True,
    }, {
        'url': 'https://play.afreecatv.com/pyh3646',
        'only_matching': True,
    }]

    _LIVE_API_URL = 'https://live.afreecatv.com/afreeca/player_live_api.php'

    def _real_extract(self, url):
        broadcaster_id, broadcast_no = self._match_valid_url(url).group('id', 'bno')
        channel_info = traverse_obj(self._download_json(
            self._LIVE_API_URL, broadcaster_id, data=urlencode_postdata({'bid': broadcaster_id})),
            ('CHANNEL', {dict})) or {}

        broadcaster_id = channel_info.get('BJID') or broadcaster_id
        broadcast_no = channel_info.get('BNO') or broadcast_no
        if not broadcast_no:
            raise UserNotLive(video_id=broadcaster_id)

        password = self.get_param('videopassword')
        if channel_info.get('BPWD') == 'Y' and password is None:
            raise ExtractorError(
                'This livestream is protected by a password, use the --video-password option',
                expected=True)

        aid = self._download_json(
            self._LIVE_API_URL, broadcast_no, 'Downloading access token for stream',
            'Unable to download access token for stream', data=urlencode_postdata(filter_dict({
                'bno': broadcast_no,
                'stream_type': 'common',
                'type': 'aid',
                'quality': 'master',
                'pwd': password,
            })))['CHANNEL']['AID']

        stream_base_url = channel_info.get('RMD') or 'https://livestream-manager.afreecatv.com'
        stream_info = self._download_json(f'{stream_base_url}/broad_stream_assign.html', broadcast_no, query={
            # works: gs_cdn_pc_app, gs_cdn_mobile_web, gs_cdn_pc_web
            'return_type': 'gs_cdn_pc_app',
            'broad_key': f'{broadcast_no}-common-master-hls',
        }, note='Downloading metadata for stream', errnote='Unable to download metadata for stream')