diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index b604754274..cc25fc9b03 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -1792,7 +1792,10 @@ from .safari import ( from .saitosan import SaitosanIE from .samplefocus import SampleFocusIE from .sapo import SapoIE -from .sauceplus import SaucePlusIE +from .sauceplus import ( + SaucePlusChannelIE, + SaucePlusIE, +) from .sbs import SBSIE from .sbscokr import ( SBSCoKrAllvodProgramIE, diff --git a/yt_dlp/extractor/floatplane.py b/yt_dlp/extractor/floatplane.py index 31723c232f..3c24c6aa2a 100644 --- a/yt_dlp/extractor/floatplane.py +++ b/yt_dlp/extractor/floatplane.py @@ -318,9 +318,48 @@ class FloatplaneIE(FloatplaneBaseIE): self.raise_login_required() -class FloatplaneChannelIE(InfoExtractor): +class FloatplaneChannelBaseIE(InfoExtractor): + """Subclasses must set _RESULT_IE, _BASE_URL and _PAGE_SIZE""" + + def _fetch_page(self, display_id, creator_id, channel_id, page): + query = { + 'id': creator_id, + 'limit': self._PAGE_SIZE, + 'fetchAfter': page * self._PAGE_SIZE, + } + if channel_id: + query['channel'] = channel_id + page_data = self._download_json( + f'{self._BASE_URL}/api/v3/content/creator', display_id, + query=query, note=f'Downloading page {page + 1}') + for post in page_data or []: + yield self.url_result( + f'{self._BASE_URL}/post/{post["id"]}', + self._RESULT_IE, id=post['id'], title=post.get('title'), + release_timestamp=parse_iso8601(post.get('releaseDate'))) + + def _real_extract(self, url): + creator, channel = self._match_valid_url(url).group('id', 'channel') + display_id = join_nonempty(creator, channel, delim='/') + + creator_data = self._download_json( + f'{self._BASE_URL}/api/v3/creator/named', + display_id, query={'creatorURL[0]': creator})[0] + + channel_data = traverse_obj( + creator_data, ('channels', lambda _, v: v['urlname'] == channel), get_all=False) or {} + + return self.playlist_result(OnDemandPagedList(functools.partial( + self._fetch_page, display_id, creator_data['id'], channel_data.get('id')), self._PAGE_SIZE), + display_id, title=channel_data.get('title') or creator_data.get('title'), + description=channel_data.get('about') or creator_data.get('about')) + + +class FloatplaneChannelIE(FloatplaneChannelBaseIE): _VALID_URL = r'https?://(?:(?:www|beta)\.)?floatplane\.com/channel/(?P[\w-]+)/home(?:/(?P[\w-]+))?' + _BASE_URL = 'https://www.floatplane.com' _PAGE_SIZE = 20 + _RESULT_IE = FloatplaneIE _TESTS = [{ 'url': 'https://www.floatplane.com/channel/linustechtips/home/ltxexpo', 'info_dict': { @@ -346,36 +385,3 @@ class FloatplaneChannelIE(InfoExtractor): }, 'playlist_mincount': 200, }] - - def _fetch_page(self, display_id, creator_id, channel_id, page): - query = { - 'id': creator_id, - 'limit': self._PAGE_SIZE, - 'fetchAfter': page * self._PAGE_SIZE, - } - if channel_id: - query['channel'] = channel_id - page_data = self._download_json( - 'https://www.floatplane.com/api/v3/content/creator', display_id, - query=query, note=f'Downloading page {page + 1}') - for post in page_data or []: - yield self.url_result( - f'https://www.floatplane.com/post/{post["id"]}', - FloatplaneIE, id=post['id'], title=post.get('title'), - release_timestamp=parse_iso8601(post.get('releaseDate'))) - - def _real_extract(self, url): - creator, channel = self._match_valid_url(url).group('id', 'channel') - display_id = join_nonempty(creator, channel, delim='/') - - creator_data = self._download_json( - 'https://www.floatplane.com/api/v3/creator/named', - display_id, query={'creatorURL[0]': creator})[0] - - channel_data = traverse_obj( - creator_data, ('channels', lambda _, v: v['urlname'] == channel), get_all=False) or {} - - return self.playlist_result(OnDemandPagedList(functools.partial( - self._fetch_page, display_id, creator_data['id'], channel_data.get('id')), self._PAGE_SIZE), - display_id, title=channel_data.get('title') or creator_data.get('title'), - description=channel_data.get('about') or creator_data.get('about')) diff --git a/yt_dlp/extractor/sauceplus.py b/yt_dlp/extractor/sauceplus.py index 75d7022d3c..e0041a7355 100644 --- a/yt_dlp/extractor/sauceplus.py +++ b/yt_dlp/extractor/sauceplus.py @@ -1,4 +1,4 @@ -from .floatplane import FloatplaneBaseIE +from .floatplane import FloatplaneBaseIE, FloatplaneChannelBaseIE class SaucePlusIE(FloatplaneBaseIE): @@ -39,3 +39,19 @@ class SaucePlusIE(FloatplaneBaseIE): def _real_initialize(self): if not self._get_cookies(self._BASE_URL).get('__Host-sp-sess'): self.raise_login_required() + + +class SaucePlusChannelIE(FloatplaneChannelBaseIE): + _VALID_URL = r'https?://(?:(?:www|beta)\.)?sauceplus\.com/channel/(?P[\w-]+)/home(?:/(?P[\w-]+))?' + _BASE_URL = 'https://www.sauceplus.com' + _RESULT_IE = SaucePlusIE + _PAGE_SIZE = 20 + _TESTS = [{ + 'url': 'https://www.sauceplus.com/channel/williamosman/home', + 'info_dict': { + 'id': 'williamosman', + 'title': 'William Osman', + 'description': 'md5:a67bc961d23c293b2c5308d84f34f26c', + }, + 'playlist_mincount': 158, + }]