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

Code before:
        'skip': 'livestream',
    }, {
        'url': 'https://live2.nicovideo.jp/watch/lv339533123',
        'only_matching': True,
    }, {
        'url': 'https://sp.live.nicovideo.jp/watch/lv339533123',
        'only_matching': True,
    }, {
        'url': 'https://sp.live2.nicovideo.jp/watch/lv339533123',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id, expected_status=404)
        if err_msg := traverse_obj(webpage, (
            {find_element(cls='message')}, {clean_html},
        )):
            raise ExtractorError(err_msg, expected=True)

        embedded_data = traverse_obj(webpage, (
            {find_element(tag='script', id='embedded-data', html=True)},
            {extract_attributes}, 'data-props', {json.loads},
        ))
        frontend_id = traverse_obj(embedded_data, (
            'site', 'frontendId', {str_or_none}), default='9')

        if not (ws_url := traverse_obj(embedded_data, (
            'site', 'relive', 'webSocketUrl', {url_or_none},
        ))):
            raise ExtractorError('Unable to fetch WebSocket URL', expected=True)

        ws_url = update_url_query(ws_url, {'frontend_id': frontend_id})
        ws = self._request_webpage(
            ws_url, video_id, 'Connecting to WebSocket server',
            headers={'Origin': 'https://live.nicovideo.jp'})

        self.write_debug('Sending HLS server request')
        ws.send(json.dumps({
            'data': {
                'reconnect': False,
                'room': {
                    'commentable': True,
                    'protocol': 'webSocket',
                },
                'stream': {
                    'accessRightMethod': 'single_cookie',
                    'chasePlay': False,
                    'latency': 'high',
                    'protocol': 'hls',
                    'quality': 'abr',

Review comment (refactor):
```suggestion
        embedded_data = traverse_obj(webpage, (
            {find_element(tag='script', id='embedded-data', html=True)},
            {extract_attributes}, 'data-props', {json.loads}))
        frontend_id = traverse_obj(embedded_data, ('site', 'frontendId', {str_or_none}), default='9')
```

Code after:


class NiconicoLiveIE(NiconicoBaseIE):
    IE_NAME = 'niconico:live'
    IE_DESC = 'ニコニコ生放送'
    _VALID_URL = r'https?://(?:sp\.)?live2?\.nicovideo\.jp/(?:watch|gate)/(?P<id>lv\d+)'
    _TESTS = [{
        'note': 'this test case includes invisible characters for title, pasting them as-is',
        'url': 'https://live.nicovideo.jp/watch/lv339533123',
        'info_dict': {
            'id': 'lv339533123',
            'title': '激辛ペヤング食べます\u202a( ;ᯅ; )\u202c（歌枠オーディション参加中）',
            'view_count': 1526,
            'comment_count': 1772,
            'description': '初めましてもかって言います❕\nのんびり自由に適当に暮らしてます',
            'uploader': 'もか',
            'channel': 'ゲストさんのコミュニティ',
            'channel_id': 'co5776900',
            'channel_url': 'https://com.nicovideo.jp/community/co5776900',
            'timestamp': 1670677328,
            'is_live': True,
        },
        'skip': 'livestream',
    }, {
        'url': 'https://live2.nicovideo.jp/watch/lv339533123',
        'only_matching': True,
    }, {
        'url': 'https://sp.live.nicovideo.jp/watch/lv339533123',
        'only_matching': True,
    }, {
        'url': 'https://sp.live2.nicovideo.jp/watch/lv339533123',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id, expected_status=404)
        if err_msg := traverse_obj(webpage, ({find_element(cls='message')}, {clean_html})):
            raise ExtractorError(err_msg, expected=True)

        embedded_data = traverse_obj(webpage, (
            {find_element(tag='script', id='embedded-data', html=True)},
            {extract_attributes}, 'data-props', {json.loads}))
        frontend_id = traverse_obj(embedded_data, ('site', 'frontendId', {str_or_none}), default='9')

        ws_url = traverse_obj(embedded_data, (
            'site', 'relive', 'webSocketUrl', {url_or_none}, {require('websocket URL')}))
        ws_url = update_url_query(ws_url, {'frontend_id': frontend_id})
        ws = self._request_webpage(
            ws_url, video_id, 'Connecting to WebSocket server',
            headers={'Origin': 'https://live.nicovideo.jp'})