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

Code before:
            'uploader': ('upper', 'name', {str_or_none}),
            'uploader_id': ('upper', 'mid', {str_or_none}),
            'timestamp': ('ctime', {int_or_none}),
            'modified_timestamp': ('mtime', {int_or_none}),
            'thumbnail': ('cover', {url_or_none}),
            'view_count': ('cnt_info', 'play', {int_or_none}),
            'like_count': ('cnt_info', 'thumb_up', {int_or_none}),
        })))


class BilibiliWatchlaterIE(BilibiliSpaceListBaseIE):
    _VALID_URL = r'https?://www\.bilibili\.com/watchlater/?(?:[?#]|$)'
    _TESTS = [{
        'url': 'https://www.bilibili.com/watchlater/#/list',
        'info_dict': {'id': 'watchlater'},
        'playlist_mincount': 0,
        'skip': 'login required',
    }]

    def _real_extract(self, url):
        list_id = getattr(self._get_cookies(url).get('DedeUserID'), 'value', 'watchlater')
        watchlater_info = self._download_json(
            'https://api.bilibili.com/x/v2/history/toview/web?jsonp=jsonp', list_id)
        if watchlater_info['code'] == -101:
            self.raise_login_required(msg='You need to login to access your watchlater list')
        entries = self._get_entries(watchlater_info, ('data', 'list', ..., 'bvid', {str}))
        return self.playlist_result(entries, id=list_id, title='稍后再看')


class BilibiliPlaylistIE(BilibiliSpaceListBaseIE):
    _VALID_URL = r'https?://www.bilibili\.com/(?:medialist/play|list)/(?P<id>\w+)'
    _TESTS = [{
        'url': 'https://www.bilibili.com/list/1958703906?sid=547718',
        'info_dict': {
            'id': '5_547718',
            'title': '直播回放',
            'uploader': '靡烟miya',
            'uploader_id': '1958703906',
            'timestamp': 1637985853,
            'upload_date': '20211127',
        },
        'playlist_mincount': 513,
    }, {
        'url': 'https://www.bilibili.com/medialist/play/1958703906?business=space_series&business_id=547718&desc=1',
        'info_dict': {
            'id': '5_547718',
        },
        'playlist_mincount': 513,
        'skip': 'redirect url',
    }, {
        'url': 'https://www.bilibili.com/list/ml1103407912',

Review comment (suggestion):
To go along with the suggestion for `BilibiliSpaceListBaseIE._get_entries()`:
```suggestion
        entries = self._get_entries(watchlater_info, ('data', 'list'))
```

Code after:
        metadata, paged_list = self._extract_playlist(fetch_page, get_metadata, get_entries)
        return self.playlist_result(paged_list, playlist_id, **metadata)


class BilibiliFavoritesListIE(BilibiliSpaceListBaseIE):
    _VALID_URL = r'https?://(?:space\.bilibili\.com/\d+/favlist/?\?fid=|(?:www\.)?bilibili\.com/medialist/detail/ml)(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://space.bilibili.com/84912/favlist?fid=1103407912&ftype=create',
        'info_dict': {
            'id': '1103407912',
            'title': '【V2】（旧）',
            'description': '',
            'uploader': '晓月春日',
            'uploader_id': '84912',
            'timestamp': 1604905176,
            'upload_date': '20201109',
            'modified_timestamp': int,
            'modified_date': str,
            'thumbnail': r"re:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg",
            'view_count': int,
            'like_count': int,
        },
        'playlist_mincount': 22,
    }, {
        'url': 'https://www.bilibili.com/medialist/detail/ml1103407912',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        fid = self._match_id(url)

        list_info = self._download_json(
            f'https://api.bilibili.com/x/v3/fav/resource/list?media_id={fid}&pn=1&ps=20',
            fid, note='Downloading favlist metadata')
        if list_info['code'] == -403:
            self.raise_login_required(msg='This is a private favorites list. You need to log in as its owner')

        entries = self._get_entries(self._download_json(
            f'https://api.bilibili.com/x/v3/fav/resource/ids?media_id={fid}',
            fid, note='Download favlist entries'), 'data')

        return self.playlist_result(entries, fid, **traverse_obj(list_info, ('data', 'info', {
            'title': ('title', {str}),
            'description': ('intro', {str}),
            'uploader': ('upper', 'name', {str}),
            'uploader_id': ('upper', 'mid', {str_or_none}),
            'timestamp': ('ctime', {int_or_none}),
            'modified_timestamp': ('mtime', {int_or_none}),
            'thumbnail': ('cover', {url_or_none}),
            'view_count': ('cnt_info', 'play', {int_or_none}),
            'like_count': ('cnt_info', 'thumb_up', {int_or_none}),