mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-31 06:35:12 +00:00 
			
		
		
		
	[youtube] Add :ytnotifications extractor (#3347)
				
					
				
			Authored by: krichbanana
This commit is contained in:
		| @@ -79,7 +79,7 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t | |||||||
| * **Merged with animelover1984/youtube-dl**: You get most of the features and improvements from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl) including `--write-comments`, `BiliBiliSearch`, `BilibiliChannel`, Embedding thumbnail in mp4/ogg/opus, playlist infojson etc. Note that the NicoNico livestreams are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details. | * **Merged with animelover1984/youtube-dl**: You get most of the features and improvements from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl) including `--write-comments`, `BiliBiliSearch`, `BilibiliChannel`, Embedding thumbnail in mp4/ogg/opus, playlist infojson etc. Note that the NicoNico livestreams are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details. | ||||||
| 
 | 
 | ||||||
| * **Youtube improvements**: | * **Youtube improvements**: | ||||||
|     * All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) and private playlists supports downloading multiple pages of content |     * All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`, `:ytnotif`) and private playlists supports downloading multiple pages of content | ||||||
|     * Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works |     * Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works | ||||||
|     * Mixes supports downloading multiple pages of content |     * Mixes supports downloading multiple pages of content | ||||||
|     * Some (but not all) age-gated content can be downloaded without cookies |     * Some (but not all) age-gated content can be downloaded without cookies | ||||||
|   | |||||||
| @@ -2100,6 +2100,7 @@ from .youtube import ( | |||||||
|     YoutubeIE, |     YoutubeIE, | ||||||
|     YoutubeClipIE, |     YoutubeClipIE, | ||||||
|     YoutubeFavouritesIE, |     YoutubeFavouritesIE, | ||||||
|  |     YoutubeNotificationsIE, | ||||||
|     YoutubeHistoryIE, |     YoutubeHistoryIE, | ||||||
|     YoutubeTabIE, |     YoutubeTabIE, | ||||||
|     YoutubeLivestreamEmbedIE, |     YoutubeLivestreamEmbedIE, | ||||||
|   | |||||||
| @@ -5526,6 +5526,95 @@ class YoutubeFavouritesIE(YoutubeBaseInfoExtractor): | |||||||
|             ie=YoutubeTabIE.ie_key()) |             ie=YoutubeTabIE.ie_key()) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class YoutubeNotificationsIE(YoutubeTabBaseInfoExtractor): | ||||||
|  |     IE_NAME = 'youtube:notif' | ||||||
|  |     IE_DESC = 'YouTube notifications; ":ytnotif" keyword (requires cookies)' | ||||||
|  |     _VALID_URL = r':ytnotif(?:ication)?s?' | ||||||
|  |     _LOGIN_REQUIRED = True | ||||||
|  |     _TESTS = [{ | ||||||
|  |         'url': ':ytnotif', | ||||||
|  |         'only_matching': True, | ||||||
|  |     }, { | ||||||
|  |         'url': ':ytnotifications', | ||||||
|  |         'only_matching': True, | ||||||
|  |     }] | ||||||
|  | 
 | ||||||
|  |     def _extract_notification_menu(self, response, continuation_list): | ||||||
|  |         notification_list = traverse_obj( | ||||||
|  |             response, | ||||||
|  |             ('actions', 0, 'openPopupAction', 'popup', 'multiPageMenuRenderer', 'sections', 0, 'multiPageMenuNotificationSectionRenderer', 'items'), | ||||||
|  |             ('actions', 0, 'appendContinuationItemsAction', 'continuationItems'), | ||||||
|  |             expected_type=list) or [] | ||||||
|  |         continuation_list[0] = None | ||||||
|  |         for item in notification_list: | ||||||
|  |             entry = self._extract_notification_renderer(item.get('notificationRenderer')) | ||||||
|  |             if entry: | ||||||
|  |                 yield entry | ||||||
|  |             continuation = item.get('continuationItemRenderer') | ||||||
|  |             if continuation: | ||||||
|  |                 continuation_list[0] = continuation | ||||||
|  | 
 | ||||||
|  |     def _extract_notification_renderer(self, notification): | ||||||
|  |         video_id = traverse_obj( | ||||||
|  |             notification, ('navigationEndpoint', 'watchEndpoint', 'videoId'), expected_type=str) | ||||||
|  |         url = f'https://www.youtube.com/watch?v={video_id}' | ||||||
|  |         channel_id = None | ||||||
|  |         if not video_id: | ||||||
|  |             browse_ep = traverse_obj( | ||||||
|  |                 notification, ('navigationEndpoint', 'browseEndpoint'), expected_type=dict) | ||||||
|  |             channel_id = traverse_obj(browse_ep, 'browseId', expected_type=str) | ||||||
|  |             post_id = self._search_regex( | ||||||
|  |                 r'/post/(.+)', traverse_obj(browse_ep, 'canonicalBaseUrl', expected_type=str), | ||||||
|  |                 'post id', default=None) | ||||||
|  |             if not channel_id or not post_id: | ||||||
|  |                 return | ||||||
|  |             # The direct /post url redirects to this in the browser | ||||||
|  |             url = f'https://www.youtube.com/channel/{channel_id}/community?lb={post_id}' | ||||||
|  | 
 | ||||||
|  |         channel = traverse_obj( | ||||||
|  |             notification, ('contextualMenu', 'menuRenderer', 'items', 1, 'menuServiceItemRenderer', 'text', 'runs', 1, 'text'), | ||||||
|  |             expected_type=str) | ||||||
|  |         title = self._search_regex( | ||||||
|  |             rf'{re.escape(channel)} [^:]+: (.+)', self._get_text(notification, 'shortMessage'), | ||||||
|  |             'video title', default=None) | ||||||
|  |         if title: | ||||||
|  |             title = title.replace('\xad', '')  # remove soft hyphens | ||||||
|  |         upload_date = (strftime_or_none(self._extract_time_text(notification, 'sentTimeText')[0], '%Y%m%d') | ||||||
|  |                        if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE.ie_key()) | ||||||
|  |                        else None) | ||||||
|  |         return { | ||||||
|  |             '_type': 'url', | ||||||
|  |             'url': url, | ||||||
|  |             'ie_key': (YoutubeIE if video_id else YoutubeTabIE).ie_key(), | ||||||
|  |             'video_id': video_id, | ||||||
|  |             'title': title, | ||||||
|  |             'channel_id': channel_id, | ||||||
|  |             'channel': channel, | ||||||
|  |             'thumbnails': self._extract_thumbnails(notification, 'videoThumbnail'), | ||||||
|  |             'upload_date': upload_date, | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     def _notification_menu_entries(self, ytcfg): | ||||||
|  |         continuation_list = [None] | ||||||
|  |         response = None | ||||||
|  |         for page in itertools.count(1): | ||||||
|  |             ctoken = traverse_obj( | ||||||
|  |                 continuation_list, (0, 'continuationEndpoint', 'getNotificationMenuEndpoint', 'ctoken'), expected_type=str) | ||||||
|  |             response = self._extract_response( | ||||||
|  |                 item_id=f'page {page}', query={'ctoken': ctoken} if ctoken else {}, ytcfg=ytcfg, | ||||||
|  |                 ep='notification/get_notification_menu', check_get_keys='actions', | ||||||
|  |                 headers=self.generate_api_headers(ytcfg=ytcfg, visitor_data=self._extract_visitor_data(response))) | ||||||
|  |             yield from self._extract_notification_menu(response, continuation_list) | ||||||
|  |             if not continuation_list[0]: | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |     def _real_extract(self, url): | ||||||
|  |         display_id = 'notifications' | ||||||
|  |         ytcfg = self._download_ytcfg('web', display_id) if not self.skip_webpage else {} | ||||||
|  |         self._report_playlist_authcheck(ytcfg) | ||||||
|  |         return self.playlist_result(self._notification_menu_entries(ytcfg), display_id, display_id) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class YoutubeSearchIE(YoutubeTabBaseInfoExtractor, SearchInfoExtractor): | class YoutubeSearchIE(YoutubeTabBaseInfoExtractor, SearchInfoExtractor): | ||||||
|     IE_DESC = 'YouTube search' |     IE_DESC = 'YouTube search' | ||||||
|     IE_NAME = 'youtube:search' |     IE_NAME = 'youtube:search' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 krichbanana
					krichbanana