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

Code before:
    IE_NAME = 'mave:channel'
    _VALID_URL = r'https?://(?P<id>[\w-]+)\.mave\.digital/?$'
    _TESTS = [{
        'url': 'https://budem.mave.digital/',
        'info_dict': {
            'id': 'budem',
            'title': 'Все там будем',
            'description': 'md5:f04ae12a42be0f1d765c5e326b41987a',
        },
        'playlist_mincount': 15,
    }, {
        'url': 'https://ochenlichnoe.mave.digital/',
        'info_dict': {
            'id': 'ochenlichnoe',
            'title': 'Очень личное',
            'description': 'md5:ee36a6a52546b91b487fe08c552fdbb2',
        },
        'playlist_mincount': 20,
    }]

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

        webpage = self._download_webpage(url, channel_id)

        nuxt_data = self._search_nuxt_json(webpage, channel_id)

        channel_title = traverse_obj(nuxt_data, (
            'pinia', 'common', 'podcast', 'podcast', 'title'))
        channel_description = traverse_obj(nuxt_data, (
            'pinia', 'common', 'podcast', 'podcast', 'description'))

        episodes_data = traverse_obj(nuxt_data, ('pinia', 'common', 'episodes'))

        entries = []
        for episode in episodes_data[::-1]:
            episode_id = traverse_obj(episode, 'id')
            episode_code = 'ep-' + str(traverse_obj(episode, 'code'))
            if episode_code:
                entries.append(self.url_result(
                    f'https://{channel_id}.mave.digital/{episode_code}',
                    ie=MaveIE.ie_key(),
                    video_id=episode_id,
                ))

        return self.playlist_result(
            entries, channel_id, channel_title, channel_description)


Review comment (refactor):
This limits us to the first 50 entries. Example with more episodes: https://geekcity.mave.digital/
Instead I would use the API and properly paginate https://api.mave.digital/v1/website/geekcity/episodes?view=all&page=1&sort=newest&format=all

Arguably even the single page extractor should have used the api. Either way, a base class can be used to share the metadata extraction. It seems like the playlist api returns the full data, so we can probably return actual video objects instead of url_results, saving one request per episode.

Code after:
            'series': 'Все там будем',
            'series_id': 'fe9347bf-c009-4ebd-87e8-b06f2f324746',
            'season': 'Season 2',
            'season_number': 2,
            'episode': 'Episode 5',
            'episode_number': 5,
            'timestamp': 1735538400,
            'upload_date': '20241230',
        },
    }]

    def _real_extract(self, url):
        channel_id, episode_code = self._match_valid_url(url).group(
            'channel_id', 'episode_code')
        display_id = f'{channel_id}-{episode_code}'

        channel_meta = self._load_channel_meta(channel_id, display_id)
        episode_meta = self._load_episode_meta(channel_id, episode_code, display_id)

        return self._create_entry(channel_id, channel_meta, episode_meta)


class MaveChannelIE(MaveBaseIE):
    IE_NAME = 'mave:channel'
    _VALID_URL = r'https?://(?P<id>[\w-]+)\.mave\.digital/?(?:$|[?#])'
    _TESTS = [{
        'url': 'https://budem.mave.digital/',
        'info_dict': {
            'id': 'budem',
            'title': 'Все там будем',
            'description': 'md5:f04ae12a42be0f1d765c5e326b41987a',
        },
        'playlist_mincount': 15,
    }, {
        'url': 'https://ochenlichnoe.mave.digital/',
        'info_dict': {
            'id': 'ochenlichnoe',
            'title': 'Очень личное',
            'description': 'md5:ee36a6a52546b91b487fe08c552fdbb2',
        },
        'playlist_mincount': 20,
    }, {
        'url': 'https://geekcity.mave.digital/',
        'info_dict': {
            'id': 'geekcity',
            'title': 'Мужчины в трико',
            'description': 'md5:4164d425d60a0d97abdce9d1f6f8e049',
        },
        'playlist_mincount': 80,
    }]
    _PAGE_SIZE = 50