22
33import com .grack .nanojson .JsonArray ;
44import com .grack .nanojson .JsonObject ;
5- import com .grack .nanojson .JsonParser ;
6- import com .grack .nanojson .JsonParserException ;
75
8- import org .jsoup .nodes .Document ;
96import org .schabi .newpipe .extractor .StreamingService ;
107import org .schabi .newpipe .extractor .channel .ChannelExtractor ;
118import org .schabi .newpipe .extractor .downloader .Downloader ;
12- import org .schabi .newpipe .extractor .downloader .Response ;
139import org .schabi .newpipe .extractor .exceptions .ExtractionException ;
1410import org .schabi .newpipe .extractor .exceptions .ParsingException ;
1511import org .schabi .newpipe .extractor .linkhandler .ListLinkHandler ;
1612import org .schabi .newpipe .extractor .localization .TimeAgoParser ;
13+ import org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeChannelLinkHandlerFactory ;
1714import org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeParsingHelper ;
1815import org .schabi .newpipe .extractor .stream .StreamInfoItem ;
1916import org .schabi .newpipe .extractor .stream .StreamInfoItemsCollector ;
2017import org .schabi .newpipe .extractor .utils .Utils ;
2118
2219import java .io .IOException ;
23- import java .util .Collections ;
24- import java .util .HashMap ;
25- import java .util .List ;
26- import java .util .Map ;
2720
2821import javax .annotation .Nonnull ;
2922
30- import static org .schabi .newpipe .extractor .utils .Utils .HTTP ;
31- import static org .schabi .newpipe .extractor .utils .Utils .HTTPS ;
23+ import static org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeParsingHelper .fixThumbnailUrl ;
24+ import static org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeParsingHelper .getJsonResponse ;
25+ import static org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeParsingHelper .getTextFromObject ;
3226
3327/*
3428 * Created by Christian Schabesberger on 25.07.16.
5246
5347@ SuppressWarnings ("WeakerAccess" )
5448public class YoutubeChannelExtractor extends ChannelExtractor {
55- /*package-private*/ static final String CHANNEL_URL_BASE = "https://www.youtube.com/channel/" ;
56- private static final String CHANNEL_URL_PARAMETERS = "/videos?view=0&flow=list&sort=dd&live_view=10000" ;
57-
58- private Document doc ;
5949 private JsonObject initialData ;
50+ private JsonObject videoTab ;
6051
6152 public YoutubeChannelExtractor (StreamingService service , ListLinkHandler linkHandler ) {
6253 super (service , linkHandler );
6354 }
6455
6556 @ Override
6657 public void onFetchPage (@ Nonnull Downloader downloader ) throws IOException , ExtractionException {
67- String channelUrl = super .getUrl () + CHANNEL_URL_PARAMETERS ;
68- final Response response = downloader .get (channelUrl , getExtractorLocalization ());
69- doc = YoutubeParsingHelper .parseAndCheckPage (channelUrl , response );
70- initialData = YoutubeParsingHelper .getInitialData (response .responseBody ());
58+ final String url = super .getUrl () + "/videos?pbj=1&view=0&flow=grid" ;
59+
60+ final JsonArray ajaxJson = getJsonResponse (url , getExtractorLocalization ());
61+
62+ initialData = ajaxJson .getObject (1 ).getObject ("response" );
7163 }
7264
7365
7466 @ Override
7567 public String getNextPageUrl () throws ExtractionException {
76- return getNextPageUrlFrom (getVideoTab ().getObject ("content" ).getObject ("sectionListRenderer" ).getArray ("continuations" ));
68+ if (getVideoTab () == null ) return "" ;
69+ return getNextPageUrlFrom (getVideoTab ().getObject ("content" ).getObject ("sectionListRenderer" )
70+ .getArray ("contents" ).getObject (0 ).getObject ("itemSectionRenderer" )
71+ .getArray ("contents" ).getObject (0 ).getObject ("gridRenderer" ).getArray ("continuations" ));
7772 }
7873
7974 @ Nonnull
8075 @ Override
8176 public String getUrl () throws ParsingException {
8277 try {
83- return CHANNEL_URL_BASE + getId ();
78+ return YoutubeChannelLinkHandlerFactory . getInstance (). getUrl ( "channel/" + getId () );
8479 } catch (ParsingException e ) {
8580 return super .getUrl ();
8681 }
@@ -109,8 +104,10 @@ public String getName() throws ParsingException {
109104 @ Override
110105 public String getAvatarUrl () throws ParsingException {
111106 try {
112- return initialData .getObject ("header" ).getObject ("c4TabbedHeaderRenderer" ).getObject ("avatar" )
107+ String url = initialData .getObject ("header" ).getObject ("c4TabbedHeaderRenderer" ).getObject ("avatar" )
113108 .getArray ("thumbnails" ).getObject (0 ).getString ("url" );
109+
110+ return fixThumbnailUrl (url );
114111 } catch (Exception e ) {
115112 throw new ParsingException ("Could not get avatar" , e );
116113 }
@@ -127,17 +124,8 @@ public String getBannerUrl() throws ParsingException {
127124 if (url == null || url .contains ("s.ytimg.com" ) || url .contains ("default_banner" )) {
128125 return null ;
129126 }
130- // the first characters of the banner URLs are different for each channel and some are not even valid URLs
131- if (url .startsWith ("//" )) {
132- url = url .substring (2 );
133- }
134- if (url .startsWith (HTTP )) {
135- url = Utils .replaceHttpWithHttps (url );
136- } else if (!url .startsWith (HTTPS )) {
137- url = HTTPS + url ;
138- }
139127
140- return url ;
128+ return fixThumbnailUrl ( url ) ;
141129 } catch (Exception e ) {
142130 throw new ParsingException ("Could not get banner" , e );
143131 }
@@ -157,13 +145,17 @@ public long getSubscriberCount() throws ParsingException {
157145 final JsonObject subscriberInfo = initialData .getObject ("header" ).getObject ("c4TabbedHeaderRenderer" ).getObject ("subscriberCountText" );
158146 if (subscriberInfo != null ) {
159147 try {
160- return Utils .mixedNumberWordToLong (subscriberInfo . getArray ( "runs" ). getObject ( 0 ). getString ( "text" ));
148+ return Utils .mixedNumberWordToLong (getTextFromObject ( subscriberInfo ));
161149 } catch (NumberFormatException e ) {
162150 throw new ParsingException ("Could not get subscriber count" , e );
163151 }
164152 } else {
165- // If the element is null, the channel have the subscriber count disabled
166- return -1 ;
153+ // If there's no subscribe button, the channel has the subscriber count disabled
154+ if (initialData .getObject ("header" ).getObject ("c4TabbedHeaderRenderer" ).getObject ("subscribeButton" ) == null ) {
155+ return -1 ;
156+ } else {
157+ return 0 ;
158+ }
167159 }
168160 }
169161
@@ -181,8 +173,12 @@ public String getDescription() throws ParsingException {
181173 public InfoItemsPage <StreamInfoItem > getInitialPage () throws ExtractionException {
182174 StreamInfoItemsCollector collector = new StreamInfoItemsCollector (getServiceId ());
183175
184- JsonArray videos = getVideoTab ().getObject ("content" ).getObject ("sectionListRenderer" ).getArray ("contents" );
185- collectStreamsFrom (collector , videos );
176+ if (getVideoTab () != null ) {
177+ JsonArray videos = getVideoTab ().getObject ("content" ).getObject ("sectionListRenderer" ).getArray ("contents" )
178+ .getObject (0 ).getObject ("itemSectionRenderer" ).getArray ("contents" ).getObject (0 )
179+ .getObject ("gridRenderer" ).getArray ("items" );
180+ collectStreamsFrom (collector , videos );
181+ }
186182
187183 return new InfoItemsPage <>(collector , getNextPageUrl ());
188184 }
@@ -198,46 +194,19 @@ public InfoItemsPage<StreamInfoItem> getPage(String pageUrl) throws IOException,
198194 fetchPage ();
199195
200196 StreamInfoItemsCollector collector = new StreamInfoItemsCollector (getServiceId ());
201- JsonArray ajaxJson ;
202-
203- Map <String , List <String >> headers = new HashMap <>();
204- headers .put ("X-YouTube-Client-Name" , Collections .singletonList ("1" ));
205- try {
206- // Use the hardcoded client version first to get JSON with a structure we know
207- headers .put ("X-YouTube-Client-Version" ,
208- Collections .singletonList (YoutubeParsingHelper .HARDCODED_CLIENT_VERSION ));
209- final String response = getDownloader ().get (pageUrl , headers , getExtractorLocalization ()).responseBody ();
210- if (response .length () < 50 ) { // ensure to have a valid response
211- throw new ParsingException ("Could not parse json data for next streams" );
212- }
213- ajaxJson = JsonParser .array ().from (response );
214- } catch (Exception e ) {
215- try {
216- headers .put ("X-YouTube-Client-Version" ,
217- Collections .singletonList (YoutubeParsingHelper .getClientVersion (initialData , doc .toString ())));
218- final String response = getDownloader ().get (pageUrl , headers , getExtractorLocalization ()).responseBody ();
219- if (response .length () < 50 ) { // ensure to have a valid response
220- throw new ParsingException ("Could not parse json data for next streams" );
221- }
222- ajaxJson = JsonParser .array ().from (response );
223- } catch (JsonParserException ignored ) {
224- throw new ParsingException ("Could not parse json data for next streams" , e );
225- }
226- }
197+ final JsonArray ajaxJson = getJsonResponse (pageUrl , getExtractorLocalization ());
227198
228199 JsonObject sectionListContinuation = ajaxJson .getObject (1 ).getObject ("response" )
229- .getObject ("continuationContents" ).getObject ("sectionListContinuation " );
200+ .getObject ("continuationContents" ).getObject ("gridContinuation " );
230201
231- collectStreamsFrom (collector , sectionListContinuation .getArray ("contents " ));
202+ collectStreamsFrom (collector , sectionListContinuation .getArray ("items " ));
232203
233204 return new InfoItemsPage <>(collector , getNextPageUrlFrom (sectionListContinuation .getArray ("continuations" )));
234205 }
235206
236207
237208 private String getNextPageUrlFrom (JsonArray continuations ) {
238- if (continuations == null ) {
239- return "" ;
240- }
209+ if (continuations == null ) return "" ;
241210
242211 JsonObject nextContinuationData = continuations .getObject (0 ).getObject ("nextContinuationData" );
243212 String continuation = nextContinuationData .getString ("continuation" );
@@ -254,10 +223,9 @@ private void collectStreamsFrom(StreamInfoItemsCollector collector, JsonArray vi
254223 final TimeAgoParser timeAgoParser = getTimeAgoParser ();
255224
256225 for (Object video : videos ) {
257- JsonObject videoInfo = ((JsonObject ) video ).getObject ("itemSectionRenderer" )
258- .getArray ("contents" ).getObject (0 );
259- if (videoInfo .getObject ("videoRenderer" ) != null ) {
260- collector .commit (new YoutubeStreamInfoItemExtractor (videoInfo .getObject ("videoRenderer" ), timeAgoParser ) {
226+ if (((JsonObject ) video ).getObject ("gridVideoRenderer" ) != null ) {
227+ collector .commit (new YoutubeStreamInfoItemExtractor (
228+ ((JsonObject ) video ).getObject ("gridVideoRenderer" ), timeAgoParser ) {
261229 @ Override
262230 public String getUploaderName () {
263231 return uploaderName ;
@@ -273,6 +241,8 @@ public String getUploaderUrl() {
273241 }
274242
275243 private JsonObject getVideoTab () throws ParsingException {
244+ if (this .videoTab != null ) return this .videoTab ;
245+
276246 JsonArray tabs = initialData .getObject ("contents" ).getObject ("twoColumnBrowseResultsRenderer" )
277247 .getArray ("tabs" );
278248 JsonObject videoTab = null ;
@@ -290,6 +260,15 @@ private JsonObject getVideoTab() throws ParsingException {
290260 throw new ParsingException ("Could not find Videos tab" );
291261 }
292262
263+ try {
264+ if (getTextFromObject (videoTab .getObject ("content" ).getObject ("sectionListRenderer" )
265+ .getArray ("contents" ).getObject (0 ).getObject ("itemSectionRenderer" )
266+ .getArray ("contents" ).getObject (0 ).getObject ("messageRenderer" )
267+ .getObject ("text" )).equals ("This channel has no videos." ))
268+ return null ;
269+ } catch (Exception ignored ) {}
270+
271+ this .videoTab = videoTab ;
293272 return videoTab ;
294273 }
295274}
0 commit comments