diff --git a/yt_dlp/extractor/xhamster.py b/yt_dlp/extractor/xhamster.py index 3cbe27f61..f7267ee52 100644 --- a/yt_dlp/extractor/xhamster.py +++ b/yt_dlp/extractor/xhamster.py @@ -423,7 +423,7 @@ def _real_extract(self, url): class XHamsterUserIE(InfoExtractor): - _VALID_URL = rf'https?://(?:[^/?#]+\.)?{XHamsterIE._DOMAINS}/(?:(?Pusers)|creators)/(?P[^/?#&]+)' + _VALID_URL = rf'https?://(?:[^/?#]+\.)?{XHamsterIE._DOMAINS}/(?Pusers|creators|channels)/(?P[^/?#&]+)(?P/favorites/videos)?' _TESTS = [{ # Paginated user profile 'url': 'https://xhamster.com/users/netvideogirls/videos', @@ -437,13 +437,25 @@ class XHamsterUserIE(InfoExtractor): 'info_dict': { 'id': 'firatkaan', }, - 'playlist_mincount': 1, + 'playlist_mincount': 0, }, { 'url': 'https://xhamster.com/creators/squirt-orgasm-69', 'info_dict': { 'id': 'squirt-orgasm-69', }, 'playlist_mincount': 150, + }, { + 'url': 'https://xhamster.com/channels/patreon/', + 'info_dict': { + 'id': 'patreon', + }, + 'playlist_mincount': 500, + }, { + 'url': 'https://xhamster.com/users/cubafidel/favorites/videos', + 'info_dict': { + 'id': 'cubafidel', + }, + 'playlist_mincount': 220, }, { 'url': 'https://xhday.com/users/mobhunter', 'only_matching': True, @@ -452,9 +464,15 @@ class XHamsterUserIE(InfoExtractor): 'only_matching': True, }] - def _entries(self, user_id, is_user): - prefix, suffix = ('users', 'videos') if is_user else ('creators', 'exclusive') - next_page_url = f'https://xhamster.com/{prefix}/{user_id}/{suffix}/1' + def _entries(self, user_id, collection_type): + collection_prefixes = { + 'users': ('users', 'videos/'), + 'creators': ('creators', 'exclusive/'), + 'channels': ('channels', ''), + 'favorites': ('users', 'favorites/videos/'), + } + prefix, suffix = collection_prefixes[collection_type] + next_page_url = f'https://xhamster.com/{prefix}/{user_id}/{suffix}1' for pagenum in itertools.count(1): page = self._download_webpage( next_page_url, user_id, f'Downloading page {pagenum}') @@ -468,7 +486,7 @@ def _entries(self, user_id, is_user): video_id = XHamsterIE._match_id(video_url) yield self.url_result( video_url, ie=XHamsterIE.ie_key(), video_id=video_id) - mobj = re.search(r']+data-page=["\']next[^>]+>', page) + mobj = re.search(r']+(?:data-page=["\']next|rel=["\']next)[^>]+>', page) if not mobj: break next_page = extract_attributes(mobj.group(0)) @@ -477,5 +495,6 @@ def _entries(self, user_id, is_user): break def _real_extract(self, url): - user, user_id = self._match_valid_url(url).group('user', 'id') - return self.playlist_result(self._entries(user_id, bool(user)), user_id) + collection_type, user_id, favorites = self._match_valid_url(url).group('collection', 'id', 'favorites') + collection_type = 'favorites' if bool(favorites) else collection_type + return self.playlist_result(self._entries(user_id, collection_type), user_id)