|  | @@ -1959,7 +1959,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |                              invidio\.us
 | 
	
		
			
				|  |  |                          )/
 | 
	
		
			
				|  |  |                          (?:
 | 
	
		
			
				|  |  | -                            (?:channel|c|user|feed)/|
 | 
	
		
			
				|  |  | +                            (?:channel|c|user|feed|hashtag)/|
 | 
	
		
			
				|  |  |                              (?:playlist|watch)\?.*?\blist=|
 | 
	
		
			
				|  |  |                              (?!(?:watch|embed|v|e)\b)
 | 
	
		
			
				|  |  |                          )
 | 
	
	
		
			
				|  | @@ -2245,6 +2245,13 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |      }, {
 | 
	
		
			
				|  |  |          'url': 'https://www.youtube.com/TheYoungTurks/live',
 | 
	
		
			
				|  |  |          'only_matching': True,
 | 
	
		
			
				|  |  | +    }, {
 | 
	
		
			
				|  |  | +        'url': 'https://www.youtube.com/hashtag/cctv9',
 | 
	
		
			
				|  |  | +        'info_dict': {
 | 
	
		
			
				|  |  | +            'id': 'cctv9',
 | 
	
		
			
				|  |  | +            'title': '#cctv9',
 | 
	
		
			
				|  |  | +        },
 | 
	
		
			
				|  |  | +        'playlist_mincount': 350,
 | 
	
		
			
				|  |  |      }]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @classmethod
 | 
	
	
		
			
				|  | @@ -2392,6 +2399,14 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |              for entry in self._post_thread_entries(renderer):
 | 
	
		
			
				|  |  |                  yield entry
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    def _rich_grid_entries(self, contents):
 | 
	
		
			
				|  |  | +        for content in contents:
 | 
	
		
			
				|  |  | +            video_renderer = try_get(content, lambda x: x['richItemRenderer']['content']['videoRenderer'], dict)
 | 
	
		
			
				|  |  | +            if video_renderer:
 | 
	
		
			
				|  |  | +                entry = self._video_entry(video_renderer)
 | 
	
		
			
				|  |  | +                if entry:
 | 
	
		
			
				|  |  | +                    yield entry
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      @staticmethod
 | 
	
		
			
				|  |  |      def _build_continuation_query(continuation, ctp=None):
 | 
	
		
			
				|  |  |          query = {
 | 
	
	
		
			
				|  | @@ -2442,55 +2457,60 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |          if not tab_content:
 | 
	
		
			
				|  |  |              return
 | 
	
		
			
				|  |  |          slr_renderer = try_get(tab_content, lambda x: x['sectionListRenderer'], dict)
 | 
	
		
			
				|  |  | -        if not slr_renderer:
 | 
	
		
			
				|  |  | -            return
 | 
	
		
			
				|  |  | -        is_channels_tab = tab.get('title') == 'Channels'
 | 
	
		
			
				|  |  | -        continuation = None
 | 
	
		
			
				|  |  | -        slr_contents = try_get(slr_renderer, lambda x: x['contents'], list) or []
 | 
	
		
			
				|  |  | -        for slr_content in slr_contents:
 | 
	
		
			
				|  |  | -            if not isinstance(slr_content, dict):
 | 
	
		
			
				|  |  | -                continue
 | 
	
		
			
				|  |  | -            is_renderer = try_get(slr_content, lambda x: x['itemSectionRenderer'], dict)
 | 
	
		
			
				|  |  | -            if not is_renderer:
 | 
	
		
			
				|  |  | -                continue
 | 
	
		
			
				|  |  | -            isr_contents = try_get(is_renderer, lambda x: x['contents'], list) or []
 | 
	
		
			
				|  |  | -            for isr_content in isr_contents:
 | 
	
		
			
				|  |  | -                if not isinstance(isr_content, dict):
 | 
	
		
			
				|  |  | -                    continue
 | 
	
		
			
				|  |  | -                renderer = isr_content.get('playlistVideoListRenderer')
 | 
	
		
			
				|  |  | -                if renderer:
 | 
	
		
			
				|  |  | -                    for entry in self._playlist_entries(renderer):
 | 
	
		
			
				|  |  | -                        yield entry
 | 
	
		
			
				|  |  | -                    continuation = self._extract_continuation(renderer)
 | 
	
		
			
				|  |  | -                    continue
 | 
	
		
			
				|  |  | -                renderer = isr_content.get('gridRenderer')
 | 
	
		
			
				|  |  | -                if renderer:
 | 
	
		
			
				|  |  | -                    for entry in self._grid_entries(renderer):
 | 
	
		
			
				|  |  | -                        yield entry
 | 
	
		
			
				|  |  | -                    continuation = self._extract_continuation(renderer)
 | 
	
		
			
				|  |  | -                    continue
 | 
	
		
			
				|  |  | -                renderer = isr_content.get('shelfRenderer')
 | 
	
		
			
				|  |  | -                if renderer:
 | 
	
		
			
				|  |  | -                    for entry in self._shelf_entries(renderer, not is_channels_tab):
 | 
	
		
			
				|  |  | -                        yield entry
 | 
	
		
			
				|  |  | +        if slr_renderer:
 | 
	
		
			
				|  |  | +            is_channels_tab = tab.get('title') == 'Channels'
 | 
	
		
			
				|  |  | +            continuation = None
 | 
	
		
			
				|  |  | +            slr_contents = try_get(slr_renderer, lambda x: x['contents'], list) or []
 | 
	
		
			
				|  |  | +            for slr_content in slr_contents:
 | 
	
		
			
				|  |  | +                if not isinstance(slr_content, dict):
 | 
	
		
			
				|  |  |                      continue
 | 
	
		
			
				|  |  | -                renderer = isr_content.get('backstagePostThreadRenderer')
 | 
	
		
			
				|  |  | -                if renderer:
 | 
	
		
			
				|  |  | -                    for entry in self._post_thread_entries(renderer):
 | 
	
		
			
				|  |  | -                        yield entry
 | 
	
		
			
				|  |  | -                    continuation = self._extract_continuation(renderer)
 | 
	
		
			
				|  |  | +                is_renderer = try_get(slr_content, lambda x: x['itemSectionRenderer'], dict)
 | 
	
		
			
				|  |  | +                if not is_renderer:
 | 
	
		
			
				|  |  |                      continue
 | 
	
		
			
				|  |  | -                renderer = isr_content.get('videoRenderer')
 | 
	
		
			
				|  |  | -                if renderer:
 | 
	
		
			
				|  |  | -                    entry = self._video_entry(renderer)
 | 
	
		
			
				|  |  | -                    if entry:
 | 
	
		
			
				|  |  | -                        yield entry
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +                isr_contents = try_get(is_renderer, lambda x: x['contents'], list) or []
 | 
	
		
			
				|  |  | +                for isr_content in isr_contents:
 | 
	
		
			
				|  |  | +                    if not isinstance(isr_content, dict):
 | 
	
		
			
				|  |  | +                        continue
 | 
	
		
			
				|  |  | +                    renderer = isr_content.get('playlistVideoListRenderer')
 | 
	
		
			
				|  |  | +                    if renderer:
 | 
	
		
			
				|  |  | +                        for entry in self._playlist_entries(renderer):
 | 
	
		
			
				|  |  | +                            yield entry
 | 
	
		
			
				|  |  | +                        continuation = self._extract_continuation(renderer)
 | 
	
		
			
				|  |  | +                        continue
 | 
	
		
			
				|  |  | +                    renderer = isr_content.get('gridRenderer')
 | 
	
		
			
				|  |  | +                    if renderer:
 | 
	
		
			
				|  |  | +                        for entry in self._grid_entries(renderer):
 | 
	
		
			
				|  |  | +                            yield entry
 | 
	
		
			
				|  |  | +                        continuation = self._extract_continuation(renderer)
 | 
	
		
			
				|  |  | +                        continue
 | 
	
		
			
				|  |  | +                    renderer = isr_content.get('shelfRenderer')
 | 
	
		
			
				|  |  | +                    if renderer:
 | 
	
		
			
				|  |  | +                        for entry in self._shelf_entries(renderer, not is_channels_tab):
 | 
	
		
			
				|  |  | +                            yield entry
 | 
	
		
			
				|  |  | +                        continue
 | 
	
		
			
				|  |  | +                    renderer = isr_content.get('backstagePostThreadRenderer')
 | 
	
		
			
				|  |  | +                    if renderer:
 | 
	
		
			
				|  |  | +                        for entry in self._post_thread_entries(renderer):
 | 
	
		
			
				|  |  | +                            yield entry
 | 
	
		
			
				|  |  | +                        continuation = self._extract_continuation(renderer)
 | 
	
		
			
				|  |  | +                        continue
 | 
	
		
			
				|  |  | +                    renderer = isr_content.get('videoRenderer')
 | 
	
		
			
				|  |  | +                    if renderer:
 | 
	
		
			
				|  |  | +                        entry = self._video_entry(renderer)
 | 
	
		
			
				|  |  | +                        if entry:
 | 
	
		
			
				|  |  | +                            yield entry
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if not continuation:
 | 
	
		
			
				|  |  | +                    continuation = self._extract_continuation(is_renderer)
 | 
	
		
			
				|  |  |              if not continuation:
 | 
	
		
			
				|  |  | -                continuation = self._extract_continuation(is_renderer)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if not continuation:
 | 
	
		
			
				|  |  | -            continuation = self._extract_continuation(slr_renderer)
 | 
	
		
			
				|  |  | +                continuation = self._extract_continuation(slr_renderer)
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            rich_grid_renderer = tab_content.get('richGridRenderer')
 | 
	
		
			
				|  |  | +            if not rich_grid_renderer:
 | 
	
		
			
				|  |  | +                return
 | 
	
		
			
				|  |  | +            for entry in self._rich_grid_entries(rich_grid_renderer.get('contents') or []):
 | 
	
		
			
				|  |  | +                yield entry
 | 
	
		
			
				|  |  | +            continuation = self._extract_continuation(rich_grid_renderer)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          headers = {
 | 
	
		
			
				|  |  |              'x-youtube-client-name': '1',
 | 
	
	
		
			
				|  | @@ -2586,6 +2606,12 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |                          yield entry
 | 
	
		
			
				|  |  |                      continuation = self._extract_continuation(continuation_renderer)
 | 
	
		
			
				|  |  |                      continue
 | 
	
		
			
				|  |  | +                renderer = continuation_item.get('richItemRenderer')
 | 
	
		
			
				|  |  | +                if renderer:
 | 
	
		
			
				|  |  | +                    for entry in self._rich_grid_entries(continuation_items):
 | 
	
		
			
				|  |  | +                        yield entry
 | 
	
		
			
				|  |  | +                    continuation = self._extract_continuation({'contents': continuation_items})
 | 
	
		
			
				|  |  | +                    continue
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              break
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2642,7 +2668,8 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |          selected_tab = self._extract_selected_tab(tabs)
 | 
	
		
			
				|  |  |          renderer = try_get(
 | 
	
		
			
				|  |  |              data, lambda x: x['metadata']['channelMetadataRenderer'], dict)
 | 
	
		
			
				|  |  | -        playlist_id = title = description = None
 | 
	
		
			
				|  |  | +        playlist_id = item_id
 | 
	
		
			
				|  |  | +        title = description = None
 | 
	
		
			
				|  |  |          if renderer:
 | 
	
		
			
				|  |  |              channel_title = renderer.get('title') or item_id
 | 
	
		
			
				|  |  |              tab_title = selected_tab.get('title')
 | 
	
	
		
			
				|  | @@ -2651,12 +2678,16 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
 | 
	
		
			
				|  |  |                  title += ' - %s' % tab_title
 | 
	
		
			
				|  |  |              description = renderer.get('description')
 | 
	
		
			
				|  |  |              playlist_id = renderer.get('externalId')
 | 
	
		
			
				|  |  | -        renderer = try_get(
 | 
	
		
			
				|  |  | -            data, lambda x: x['metadata']['playlistMetadataRenderer'], dict)
 | 
	
		
			
				|  |  | -        if renderer:
 | 
	
		
			
				|  |  | -            title = renderer.get('title')
 | 
	
		
			
				|  |  | -            description = None
 | 
	
		
			
				|  |  | -            playlist_id = item_id
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            renderer = try_get(
 | 
	
		
			
				|  |  | +                data, lambda x: x['metadata']['playlistMetadataRenderer'], dict)
 | 
	
		
			
				|  |  | +            if renderer:
 | 
	
		
			
				|  |  | +                title = renderer.get('title')
 | 
	
		
			
				|  |  | +            else:
 | 
	
		
			
				|  |  | +                renderer = try_get(
 | 
	
		
			
				|  |  | +                    data, lambda x: x['header']['hashtagHeaderRenderer'], dict)
 | 
	
		
			
				|  |  | +                if renderer:
 | 
	
		
			
				|  |  | +                    title = try_get(renderer, lambda x: x['hashtag']['simpleText'])
 | 
	
		
			
				|  |  |          playlist = self.playlist_result(
 | 
	
		
			
				|  |  |              self._entries(selected_tab, identity_token),
 | 
	
		
			
				|  |  |              playlist_id=playlist_id, playlist_title=title,
 |