Skip to content

Commit e65333c

Browse files
mauriciocolliTobiGr
authored andcommitted
[YouTube] Detect deleted/nonexistent/invalid channels and playlists
- Added tests for these cases.
1 parent 98e3594 commit e65333c

5 files changed

Lines changed: 85 additions & 1 deletion

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ public void onFetchPage(@Nonnull Downloader downloader) throws IOException, Extr
5858
final String url = super.getUrl() + "/videos?pbj=1&view=0&flow=grid";
5959

6060
final JsonArray ajaxJson = getJsonResponse(url, getExtractorLocalization());
61-
6261
initialData = ajaxJson.getObject(1).getObject("response");
62+
YoutubeParsingHelper.defaultAlertsCheck(initialData);
6363
}
6464

6565

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubePlaylistExtractor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
1111
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
1212
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
13+
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeParsingHelper;
1314
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
1415
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
1516
import org.schabi.newpipe.extractor.utils.Utils;
@@ -39,6 +40,8 @@ public void onFetchPage(@Nonnull Downloader downloader) throws IOException, Extr
3940
final JsonArray ajaxJson = getJsonResponse(url, getExtractorLocalization());
4041

4142
initialData = ajaxJson.getObject(1).getObject("response");
43+
YoutubeParsingHelper.defaultAlertsCheck(initialData);
44+
4245
playlistInfo = getPlaylistInfo();
4346
}
4447

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeParsingHelper.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,10 +362,45 @@ public static JsonArray getJsonResponse(String url, Localization localization) t
362362
throw new ParsingException("JSON response is too short");
363363
}
364364

365+
// Check if the request was redirected to the error page.
366+
final URL latestUrl = new URL(response.latestUrl());
367+
if (latestUrl.getHost().equalsIgnoreCase("www.youtube.com")) {
368+
final String path = latestUrl.getPath();
369+
if (path.equalsIgnoreCase("/oops") || path.equalsIgnoreCase("/error")) {
370+
throw new ContentNotAvailableException("Content unavailable");
371+
}
372+
}
373+
374+
final String responseContentType = response.getHeader("Content-Type");
375+
if (responseContentType != null && responseContentType.toLowerCase().contains("text/html")) {
376+
throw new ParsingException("Got HTML document, expected JSON response" +
377+
" (latest url was: \"" + response.latestUrl() + "\")");
378+
}
379+
365380
try {
366381
return JsonParser.array().from(responseBody);
367382
} catch (JsonParserException e) {
368383
throw new ParsingException("Could not parse JSON", e);
369384
}
370385
}
386+
387+
/**
388+
* Shared alert detection function, multiple endpoints return the error similarly structured.
389+
* <p>
390+
* Will check if the object has an alert of the type "ERROR".
391+
*
392+
* @param initialData the object which will be checked if an alert is present
393+
* @throws ContentNotAvailableException if an alert is detected
394+
*/
395+
public static void defaultAlertsCheck(JsonObject initialData) throws ContentNotAvailableException {
396+
final JsonArray alerts = initialData.getArray("alerts");
397+
if (alerts != null && !alerts.isEmpty()) {
398+
final JsonObject alertRenderer = alerts.getObject(0).getObject("alertRenderer");
399+
final String alertText = alertRenderer.getObject("text").getString("simpleText");
400+
final String alertType = alertRenderer.getString("type");
401+
if (alertType.equalsIgnoreCase("ERROR")) {
402+
throw new ContentNotAvailableException("Got error: \"" + alertText + "\"");
403+
}
404+
}
405+
}
371406
}

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.schabi.newpipe.extractor.NewPipe;
77
import org.schabi.newpipe.extractor.ServiceList;
88
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
9+
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
910
import org.schabi.newpipe.extractor.exceptions.ParsingException;
1011
import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest;
1112
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeChannelExtractor;
@@ -19,6 +20,28 @@
1920
* Test for {@link ChannelExtractor}
2021
*/
2122
public class YoutubeChannelExtractorTest {
23+
24+
public static class NotAvailable {
25+
@BeforeClass
26+
public static void setUp() {
27+
NewPipe.init(DownloaderTestImpl.getInstance());
28+
}
29+
30+
@Test(expected = ContentNotAvailableException.class)
31+
public void deletedFetch() throws Exception {
32+
final ChannelExtractor extractor =
33+
YouTube.getChannelExtractor("https://www.youtube.com/channel/UCAUc4iz6edWerIjlnL8OSSw");
34+
extractor.fetchPage();
35+
}
36+
37+
@Test(expected = ContentNotAvailableException.class)
38+
public void nonExistentFetch() throws Exception {
39+
final ChannelExtractor extractor =
40+
YouTube.getChannelExtractor("https://www.youtube.com/channel/DOESNT-EXIST");
41+
extractor.fetchPage();
42+
}
43+
}
44+
2245
public static class Gronkh implements BaseChannelExtractorTest {
2346
private static YoutubeChannelExtractor extractor;
2447

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractorTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.schabi.newpipe.extractor.ListExtractor;
88
import org.schabi.newpipe.extractor.NewPipe;
99
import org.schabi.newpipe.extractor.ServiceList;
10+
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
1011
import org.schabi.newpipe.extractor.exceptions.ParsingException;
1112
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
1213
import org.schabi.newpipe.extractor.services.BasePlaylistExtractorTest;
@@ -23,6 +24,28 @@
2324
* Test for {@link YoutubePlaylistExtractor}
2425
*/
2526
public class YoutubePlaylistExtractorTest {
27+
28+
public static class NotAvailable {
29+
@BeforeClass
30+
public static void setUp() {
31+
NewPipe.init(DownloaderTestImpl.getInstance());
32+
}
33+
34+
@Test(expected = ContentNotAvailableException.class)
35+
public void nonExistentFetch() throws Exception {
36+
final PlaylistExtractor extractor =
37+
YouTube.getPlaylistExtractor("https://www.youtube.com/playlist?list=PL11111111111111111111111111111111");
38+
extractor.fetchPage();
39+
}
40+
41+
@Test(expected = ContentNotAvailableException.class)
42+
public void invalidId() throws Exception {
43+
final PlaylistExtractor extractor =
44+
YouTube.getPlaylistExtractor("https://www.youtube.com/playlist?list=INVALID_ID");
45+
extractor.fetchPage();
46+
}
47+
}
48+
2649
public static class TimelessPopHits implements BasePlaylistExtractorTest {
2750
private static YoutubePlaylistExtractor extractor;
2851

0 commit comments

Comments
 (0)