22
33import static org .schabi .newpipe .extractor .services .youtube .YoutubeParsingHelper .DISABLE_PRETTY_PRINT_PARAMETER ;
44import static org .schabi .newpipe .extractor .services .youtube .YoutubeParsingHelper .YOUTUBEI_V1_URL ;
5- import static org .schabi .newpipe .extractor .services .youtube .YoutubeParsingHelper .fixThumbnailUrl ;
65import static org .schabi .newpipe .extractor .services .youtube .YoutubeParsingHelper .getJsonPostResponse ;
76import static org .schabi .newpipe .extractor .services .youtube .YoutubeParsingHelper .getKey ;
87import static org .schabi .newpipe .extractor .services .youtube .YoutubeParsingHelper .getTextFromObject ;
3433import java .util .ArrayList ;
3534import java .util .List ;
3635import java .util .Objects ;
36+ import java .util .Optional ;
3737
3838import javax .annotation .Nonnull ;
3939import javax .annotation .Nullable ;
6060
6161public class YoutubeChannelExtractor extends ChannelExtractor {
6262 private JsonObject initialData ;
63+ private Optional <JsonObject > channelHeader ;
64+ private boolean isCarouselHeader = false ;
6365 private JsonObject videoTab ;
6466
6567 /**
@@ -189,6 +191,30 @@ private void checkIfChannelResponseIsValid(@Nonnull final JsonObject jsonRespons
189191 }
190192 }
191193
194+ @ Nonnull
195+ private Optional <JsonObject > getChannelHeader () {
196+ if (channelHeader == null ) {
197+ final JsonObject h = initialData .getObject ("header" );
198+
199+ if (h .has ("c4TabbedHeaderRenderer" )) {
200+ channelHeader = Optional .of (h .getObject ("c4TabbedHeaderRenderer" ));
201+ } else if (h .has ("carouselHeaderRenderer" )) {
202+ isCarouselHeader = true ;
203+ channelHeader = h .getObject ("carouselHeaderRenderer" )
204+ .getArray ("contents" )
205+ .stream ()
206+ .filter (JsonObject .class ::isInstance )
207+ .map (JsonObject .class ::cast )
208+ .filter (itm -> itm .has ("topicChannelDetailsRenderer" ))
209+ .findFirst ()
210+ .map (itm -> itm .getObject ("topicChannelDetailsRenderer" ));
211+ } else {
212+ channelHeader = Optional .empty ();
213+ }
214+ }
215+ return channelHeader ;
216+ }
217+
192218 @ Nonnull
193219 @ Override
194220 public String getUrl () throws ParsingException {
@@ -202,58 +228,61 @@ public String getUrl() throws ParsingException {
202228 @ Nonnull
203229 @ Override
204230 public String getId () throws ParsingException {
205- final String channelId = initialData .getObject ("header" )
206- .getObject ("c4TabbedHeaderRenderer" )
207- .getString ("channelId" , "" );
208-
209- if (!channelId .isEmpty ()) {
210- return channelId ;
211- } else if (!isNullOrEmpty (redirectedChannelId )) {
212- return redirectedChannelId ;
213- } else {
214- throw new ParsingException ("Could not get channel id" );
215- }
231+ return getChannelHeader ()
232+ .flatMap (header -> Optional .ofNullable (header .getString ("channelId" )).or (
233+ () -> Optional .ofNullable (header .getObject ("navigationEndpoint" )
234+ .getObject ("browseEndpoint" )
235+ .getString ("browseId" ))
236+ ))
237+ .or (() -> Optional .ofNullable (redirectedChannelId ))
238+ .orElseThrow (() -> new ParsingException ("Could not get channel id" ));
216239 }
217240
218241 @ Nonnull
219242 @ Override
220243 public String getName () throws ParsingException {
221- try {
222- return initialData .getObject ("header" ).getObject ("c4TabbedHeaderRenderer" )
223- .getString ("title" );
224- } catch (final Exception e ) {
225- throw new ParsingException ("Could not get channel name" , e );
244+ final String mdName = initialData .getObject ("metadata" )
245+ .getObject ("channelMetadataRenderer" )
246+ .getString ("title" );
247+ if (!isNullOrEmpty (mdName )) {
248+ return mdName ;
249+ }
250+
251+ final Optional <JsonObject > header = getChannelHeader ();
252+ if (header .isPresent ()) {
253+ final Object title = header .get ().get ("title" );
254+ if (title instanceof String ) {
255+ return (String ) title ;
256+ } else if (title instanceof JsonObject ) {
257+ final String headerName = getTextFromObject ((JsonObject ) title );
258+ if (!isNullOrEmpty (headerName )) {
259+ return headerName ;
260+ }
261+ }
226262 }
263+
264+ throw new ParsingException ("Could not get channel name" );
227265 }
228266
229267 @ Override
230268 public String getAvatarUrl () throws ParsingException {
231- try {
232- final String url = initialData .getObject ("header" )
233- .getObject ("c4TabbedHeaderRenderer" ).getObject ("avatar" ).getArray ("thumbnails" )
234- .getObject (0 ).getString ("url" );
235-
236- return fixThumbnailUrl (url );
237- } catch (final Exception e ) {
238- throw new ParsingException ("Could not get avatar" , e );
239- }
269+ return getChannelHeader ().flatMap (header -> Optional .ofNullable (
270+ header .getObject ("avatar" ).getArray ("thumbnails" )
271+ .getObject (0 ).getString ("url" )
272+ ))
273+ .map (YoutubeParsingHelper ::fixThumbnailUrl )
274+ .orElseThrow (() -> new ParsingException ("Could not get avatar" ));
240275 }
241276
242277 @ Override
243278 public String getBannerUrl () throws ParsingException {
244- try {
245- final String url = initialData .getObject ("header" )
246- .getObject ("c4TabbedHeaderRenderer" ).getObject ("banner" ).getArray ("thumbnails" )
247- .getObject (0 ).getString ("url" );
248-
249- if (url == null || url .contains ("s.ytimg.com" ) || url .contains ("default_banner" )) {
250- return null ;
251- }
252-
253- return fixThumbnailUrl (url );
254- } catch (final Exception e ) {
255- throw new ParsingException ("Could not get banner" , e );
256- }
279+ return getChannelHeader ().flatMap (header -> Optional .ofNullable (
280+ header .getObject ("banner" ).getArray ("thumbnails" )
281+ .getObject (0 ).getString ("url" )
282+ ))
283+ .filter (url -> !url .contains ("s.ytimg.com" ) && !url .contains ("default_banner" ))
284+ .map (YoutubeParsingHelper ::fixThumbnailUrl )
285+ .orElseThrow (() -> new ParsingException ("Could not get banner" ));
257286 }
258287
259288 @ Override
@@ -267,17 +296,25 @@ public String getFeedUrl() throws ParsingException {
267296
268297 @ Override
269298 public long getSubscriberCount () throws ParsingException {
270- final JsonObject c4TabbedHeaderRenderer = initialData .getObject ("header" )
271- .getObject ("c4TabbedHeaderRenderer" );
272- if (!c4TabbedHeaderRenderer .has ("subscriberCountText" )) {
273- return UNKNOWN_SUBSCRIBER_COUNT ;
274- }
275- try {
276- return Utils .mixedNumberWordToLong (getTextFromObject (c4TabbedHeaderRenderer
277- .getObject ("subscriberCountText" )));
278- } catch (final NumberFormatException e ) {
279- throw new ParsingException ("Could not get subscriber count" , e );
299+ final Optional <JsonObject > header = getChannelHeader ();
300+ if (header .isPresent ()) {
301+ JsonObject textObject = null ;
302+
303+ if (header .get ().has ("subscriberCountText" )) {
304+ textObject = header .get ().getObject ("subscriberCountText" );
305+ } else if (header .get ().has ("subtitle" )) {
306+ textObject = header .get ().getObject ("subtitle" );
307+ }
308+
309+ if (textObject != null ) {
310+ try {
311+ return Utils .mixedNumberWordToLong (getTextFromObject (textObject ));
312+ } catch (final NumberFormatException e ) {
313+ throw new ParsingException ("Could not get subscriber count" , e );
314+ }
315+ }
280316 }
317+ return UNKNOWN_SUBSCRIBER_COUNT ;
281318 }
282319
283320 @ Override
@@ -307,11 +344,17 @@ public String getParentChannelAvatarUrl() {
307344
308345 @ Override
309346 public boolean isVerified () throws ParsingException {
310- final JsonArray badges = initialData .getObject ("header" )
311- .getObject ("c4TabbedHeaderRenderer" )
312- .getArray ("badges" );
347+ // The CarouselHeaderRenderer does not contain any verification badges.
348+ // Since it is only shown on YT-internal channels or on channels of large organizations
349+ // broadcasting live events, we can assume the channel to be verified.
350+ if (isCarouselHeader ) {
351+ return true ;
352+ }
313353
314- return YoutubeParsingHelper .isVerified (badges );
354+ return getChannelHeader ()
355+ .map (header -> header .getArray ("badges" ))
356+ .map (YoutubeParsingHelper ::isVerified )
357+ .orElse (false );
315358 }
316359
317360 @ Nonnull
0 commit comments