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

Code before:
            return
        return cls._build_notification_continuation_query(ctoken)

    @staticmethod
    def _build_notification_continuation_query(ctoken):
        query = {
            'ctoken': ctoken
        }
        return query

    def _notification_menu_entries(self, headers, max_requests):
        continuation_list = [None]
        request_count = 0
        entries = []
        while request_count < max_requests:
            query = {}
            if continuation_list[0]:
                query = self._extract_next_notification_continuation_data(continuation_list[0])
            request_count += 1
            response = self._extract_response(
                note=f'Downloading API JSON (page {request_count})',
                ep='notification/get_notification_menu', headers=headers,
                check_get_keys=['responseContext', 'actions', 'trackingParams'],
                item_id='notification_menu', query=query)
            for notification in self._extract_notification_menu(response, continuation_list) or []:
                entry = self._extract_notification(notification)
                if entry:
                    entries.append(entry)
            if not continuation_list[0]:
                break
        return entries

    def get_notifications(self, max_requests=1):
        return self._notification_menu_entries(self.generate_api_headers(), max_requests)

    def _real_extract(self, url):
        max_pages = int(self._match_valid_url(url).group('max_pages') or 1)
        self.write_debug(f'max_pages: {max_pages}')
        return self.playlist_result(
            entries=self.get_notifications(max_requests=max_pages), playlist_title='Notifications', ie=YoutubeTabIE.ie_key())


class YoutubeSearchIE(YoutubeTabBaseInfoExtractor, SearchInfoExtractor):
    IE_DESC = 'YouTube search'
    IE_NAME = 'youtube:search'
    _SEARCH_KEY = 'ytsearch'
    _SEARCH_PARAMS = 'EgIQAQ%3D%3D'  # Videos only
    _TESTS = [{
        'url': 'ytsearch5:youtube-dl test video',
        'playlist_count': 5,
        'info_dict': {

Review comment (refactor):
```suggestion
            for entry in map(self._extract_notification, self._extract_notification_menu(response, continuation_list)):
```
or even better, in `_extract_notification_menu`, directly `yield self._extract_notification(renderer)`

Code after:
            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):
    IE_DESC = 'YouTube search'