Merge pull request #509 from TiA4f8R/soundcloud-improvements

Add new exceptions to be able to display different error messages in apps
This commit is contained in:
Tobi 2021-03-07 13:51:15 +01:00 committed by GitHub
commit 7e6f464407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1239 additions and 131 deletions

View file

@ -0,0 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class AgeRestrictedContentException extends ContentNotAvailableException {
public AgeRestrictedContentException(final String message) {
super(message);
}
public AgeRestrictedContentException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -1,11 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class ContentNotAvailableException extends ParsingException {
public ContentNotAvailableException(String message) {
public ContentNotAvailableException(final String message) {
super(message);
}
public ContentNotAvailableException(String message, Throwable cause) {
public ContentNotAvailableException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -1,11 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class ContentNotSupportedException extends ParsingException {
public ContentNotSupportedException(String message) {
public ContentNotSupportedException(final String message) {
super(message);
}
public ContentNotSupportedException(String message, Throwable cause) {
public ContentNotSupportedException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -21,15 +21,15 @@ package org.schabi.newpipe.extractor.exceptions;
*/
public class ExtractionException extends Exception {
public ExtractionException(String message) {
public ExtractionException(final String message) {
super(message);
}
public ExtractionException(Throwable cause) {
public ExtractionException(final Throwable cause) {
super(cause);
}
public ExtractionException(String message, Throwable cause) {
public ExtractionException(final String message, final Throwable cause) {
super(message, cause);
}
}
}

View file

@ -21,11 +21,11 @@ package org.schabi.newpipe.extractor.exceptions;
*/
public class FoundAdException extends ParsingException {
public FoundAdException(String message) {
public FoundAdException(final String message) {
super(message);
}
public FoundAdException(String message, Throwable cause) {
public FoundAdException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class GeographicRestrictionException extends ContentNotAvailableException {
public GeographicRestrictionException(final String message) {
super(message);
}
public GeographicRestrictionException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class PaidContentException extends ContentNotAvailableException {
public PaidContentException(final String message) {
super(message);
}
public PaidContentException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -22,11 +22,11 @@ package org.schabi.newpipe.extractor.exceptions;
public class ParsingException extends ExtractionException {
public ParsingException(String message) {
public ParsingException(final String message) {
super(message);
}
public ParsingException(String message, Throwable cause) {
public ParsingException(final String message, final Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class PrivateContentException extends ContentNotAvailableException {
public PrivateContentException(final String message) {
super(message);
}
public PrivateContentException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -23,7 +23,7 @@ package org.schabi.newpipe.extractor.exceptions;
public class ReCaptchaException extends ExtractionException {
private String url;
public ReCaptchaException(String message, String url) {
public ReCaptchaException(final String message, final String url) {
super(message);
this.url = url;
}

View file

@ -0,0 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class SoundCloudGoPlusContentException extends ContentNotAvailableException {
public SoundCloudGoPlusContentException() {
super("This track is a SoundCloud Go+ track");
}
public SoundCloudGoPlusContentException(final Throwable cause) {
super("This track is a SoundCloud Go+ track", cause);
}
}

View file

@ -0,0 +1,11 @@
package org.schabi.newpipe.extractor.exceptions;
public class YoutubeMusicPremiumContentException extends ContentNotAvailableException {
public YoutubeMusicPremiumContentException() {
super("This video is a YouTube Music Premium video");
}
public YoutubeMusicPremiumContentException(final Throwable cause) {
super("This video is a YouTube Music Premium video", cause);
}
}

View file

@ -12,7 +12,9 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.extractor.localization.DateWrapper;
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
@ -43,6 +45,12 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
String policy = track.getString("policy", EMPTY_STRING);
if (!policy.equals("ALLOW") && !policy.equals("MONETIZE")) {
if (policy.equals("SNIP")) {
throw new SoundCloudGoPlusContentException();
}
if (policy.equals("BLOCK")) {
throw new GeographicRestrictionException("This track is not available in user's country");
}
throw new ContentNotAvailableException("Content not available: policy " + policy);
}
}

View file

@ -16,10 +16,15 @@ import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.extractor.localization.DateWrapper;
import org.schabi.newpipe.extractor.localization.Localization;
@ -71,7 +76,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
//////////////////////////////////////////////////////////////////////////*/
public static class DeobfuscateException extends ParsingException {
DeobfuscateException(String message, Throwable cause) {
DeobfuscateException(final String message, final Throwable cause) {
super(message, cause);
}
}
@ -94,7 +99,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
@Nullable
private List<SubtitlesStream> subtitles = null;
public YoutubeStreamExtractor(StreamingService service, LinkHandler linkHandler) {
public YoutubeStreamExtractor(final StreamingService service, final LinkHandler linkHandler) {
super(service, linkHandler);
}
@ -110,7 +115,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
title = getTextFromObject(getVideoPrimaryInfoRenderer().getObject("title"));
} catch (ParsingException ignored) {
} catch (final ParsingException ignored) {
// age-restricted videos cause a ParsingException here
}
@ -153,14 +158,14 @@ public class YoutubeStreamExtractor extends StreamExtractor {
TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(Localization.fromLocalizationCode("en"));
OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime();
return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime);
} catch (Exception ignored) {
} catch (final Exception ignored) {
}
try { // Premiered Feb 21, 2020
final LocalDate localDate = LocalDate.parse(time,
DateTimeFormatter.ofPattern("MMM dd, yyyy", Locale.ENGLISH));
return DateTimeFormatter.ISO_LOCAL_DATE.format(localDate);
} catch (Exception ignored) {
} catch (final Exception ignored) {
}
}
@ -169,7 +174,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
LocalDate localDate = LocalDate.parse(getTextFromObject(getVideoPrimaryInfoRenderer().getObject("dateText")),
DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.ENGLISH));
return DateTimeFormatter.ISO_LOCAL_DATE.format(localDate);
} catch (Exception ignored) {
} catch (final Exception ignored) {
}
throw new ParsingException("Could not get upload date");
@ -196,7 +201,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
String url = thumbnails.getObject(thumbnails.size() - 1).getString("url");
return fixThumbnailUrl(url);
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get thumbnail url");
}
@ -210,7 +215,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
String description = getTextFromObject(getVideoSecondaryInfoRenderer().getObject("description"), true);
if (description != null && !description.isEmpty()) return new Description(description, Description.HTML);
} catch (ParsingException ignored) {
} catch (final ParsingException ignored) {
// age-restricted videos cause a ParsingException here
}
@ -260,7 +265,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.getObject("videoDetails")
.getString("lengthSeconds");
return Long.parseLong(duration);
} catch (Exception e) {
} catch (final Exception e) {
try {
String durationMs = playerResponse
.getObject("streamingData")
@ -268,7 +273,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.getObject(0)
.getString("approxDurationMs");
return Math.round(Long.parseLong(durationMs) / 1000f);
} catch (Exception ignored) {
} catch (final Exception ignored) {
throw new ParsingException("Could not get duration", e);
}
}
@ -300,7 +305,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
views = getTextFromObject(getVideoPrimaryInfoRenderer().getObject("viewCount")
.getObject("videoViewCountRenderer").getObject("viewCount"));
} catch (ParsingException ignored) {
} catch (final ParsingException ignored) {
// age-restricted videos cause a ParsingException here
}
@ -323,17 +328,17 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
likesString = getVideoPrimaryInfoRenderer().getObject("sentimentBar")
.getObject("sentimentBarRenderer").getString("tooltip").split("/")[0];
} catch (NullPointerException e) {
//if this kicks in our button has no content and therefore ratings must be disabled
} catch (final NullPointerException e) {
// if this kicks in our button has no content and therefore ratings must be disabled
if (playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
throw new ParsingException("Ratings are enabled even though the like button is missing", e);
}
return -1;
}
return Integer.parseInt(Utils.removeNonDigitCharacters(likesString));
} catch (NumberFormatException nfe) {
} catch (final NumberFormatException nfe) {
throw new ParsingException("Could not parse \"" + likesString + "\" as an Integer", nfe);
} catch (Exception e) {
} catch (final Exception e) {
if (getAgeLimit() == NO_AGE_LIMIT) {
throw new ParsingException("Could not get like count", e);
}
@ -349,17 +354,17 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
dislikesString = getVideoPrimaryInfoRenderer().getObject("sentimentBar")
.getObject("sentimentBarRenderer").getString("tooltip").split("/")[1];
} catch (NullPointerException e) {
//if this kicks in our button has no content and therefore ratings must be disabled
} catch (final NullPointerException e) {
// if this kicks in our button has no content and therefore ratings must be disabled
if (playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
throw new ParsingException("Ratings are enabled even though the dislike button is missing", e);
}
return -1;
}
return Integer.parseInt(Utils.removeNonDigitCharacters(dislikesString));
} catch (NumberFormatException nfe) {
} catch (final NumberFormatException nfe) {
throw new ParsingException("Could not parse \"" + dislikesString + "\" as an Integer", nfe);
} catch (Exception e) {
} catch (final Exception e) {
if (getAgeLimit() == NO_AGE_LIMIT) {
throw new ParsingException("Could not get dislike count", e);
}
@ -373,16 +378,16 @@ public class YoutubeStreamExtractor extends StreamExtractor {
assertPageFetched();
try {
String uploaderUrl = getUrlFromNavigationEndpoint(getVideoSecondaryInfoRenderer()
final String uploaderUrl = getUrlFromNavigationEndpoint(getVideoSecondaryInfoRenderer()
.getObject("owner").getObject("videoOwnerRenderer").getObject("navigationEndpoint"));
if (!isNullOrEmpty(uploaderUrl)) {
return uploaderUrl;
}
} catch (ParsingException ignored) {
} catch (final ParsingException ignored) {
// age-restricted videos cause a ParsingException here
}
String uploaderId = playerResponse.getObject("videoDetails").getString("channelId");
final String uploaderId = playerResponse.getObject("videoDetails").getString("channelId");
if (!isNullOrEmpty(uploaderId)) {
return YoutubeChannelLinkHandlerFactory.getInstance().getUrl("channel/" + uploaderId);
}
@ -400,7 +405,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
uploaderName = getTextFromObject(getVideoSecondaryInfoRenderer().getObject("owner")
.getObject("videoOwnerRenderer").getObject("title"));
} catch (ParsingException ignored) {
} catch (final ParsingException ignored) {
}
if (isNullOrEmpty(uploaderName)) {
@ -430,7 +435,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
url = getVideoSecondaryInfoRenderer().getObject("owner").getObject("videoOwnerRenderer")
.getObject("thumbnail").getArray("thumbnails").getObject(0).getString("url");
} catch (ParsingException ignored) {
} catch (final ParsingException ignored) {
// age-restricted videos cause a ParsingException here
}
@ -478,14 +483,14 @@ public class YoutubeStreamExtractor extends StreamExtractor {
if (!dashManifestUrl.contains("/signature/")) {
String obfuscatedSig = Parser.matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifestUrl);
String deobfuscatedSig;
final String deobfuscatedSig;
deobfuscatedSig = deobfuscateSignature(obfuscatedSig);
dashManifestUrl = dashManifestUrl.replace("/s/" + obfuscatedSig, "/signature/" + deobfuscatedSig);
}
return dashManifestUrl;
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get dash manifest url", e);
}
}
@ -497,7 +502,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
return playerResponse.getObject("streamingData").getString("hlsManifestUrl");
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get hls manifest url", e);
}
}
@ -505,17 +510,17 @@ public class YoutubeStreamExtractor extends StreamExtractor {
@Override
public List<AudioStream> getAudioStreams() throws ExtractionException {
assertPageFetched();
List<AudioStream> audioStreams = new ArrayList<>();
try {
for (Map.Entry<String, ItagItem> entry : getItags(ADAPTIVE_FORMATS, ItagItem.ItagType.AUDIO).entrySet()) {
ItagItem itag = entry.getValue();
final List<AudioStream> audioStreams = new ArrayList<>();
AudioStream audioStream = new AudioStream(entry.getKey(), itag);
try {
for (final Map.Entry<String, ItagItem> entry : getItags(ADAPTIVE_FORMATS, ItagItem.ItagType.AUDIO).entrySet()) {
final ItagItem itag = entry.getValue();
final AudioStream audioStream = new AudioStream(entry.getKey(), itag);
if (!Stream.containSimilarStream(audioStream, audioStreams)) {
audioStreams.add(audioStream);
}
}
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get audio streams", e);
}
@ -525,17 +530,17 @@ public class YoutubeStreamExtractor extends StreamExtractor {
@Override
public List<VideoStream> getVideoStreams() throws ExtractionException {
assertPageFetched();
List<VideoStream> videoStreams = new ArrayList<>();
try {
for (Map.Entry<String, ItagItem> entry : getItags(FORMATS, ItagItem.ItagType.VIDEO).entrySet()) {
ItagItem itag = entry.getValue();
final List<VideoStream> videoStreams = new ArrayList<>();
VideoStream videoStream = new VideoStream(entry.getKey(), false, itag);
try {
for (final Map.Entry<String, ItagItem> entry : getItags(FORMATS, ItagItem.ItagType.VIDEO).entrySet()) {
final ItagItem itag = entry.getValue();
final VideoStream videoStream = new VideoStream(entry.getKey(), false, itag);
if (!Stream.containSimilarStream(videoStream, videoStreams)) {
videoStreams.add(videoStream);
}
}
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get video streams", e);
}
@ -545,17 +550,17 @@ public class YoutubeStreamExtractor extends StreamExtractor {
@Override
public List<VideoStream> getVideoOnlyStreams() throws ExtractionException {
assertPageFetched();
List<VideoStream> videoOnlyStreams = new ArrayList<>();
final List<VideoStream> videoOnlyStreams = new ArrayList<>();
try {
for (Map.Entry<String, ItagItem> entry : getItags(ADAPTIVE_FORMATS, ItagItem.ItagType.VIDEO_ONLY).entrySet()) {
ItagItem itag = entry.getValue();
for (final Map.Entry<String, ItagItem> entry : getItags(ADAPTIVE_FORMATS, ItagItem.ItagType.VIDEO_ONLY).entrySet()) {
final ItagItem itag = entry.getValue();
VideoStream videoStream = new VideoStream(entry.getKey(), true, itag);
final VideoStream videoStream = new VideoStream(entry.getKey(), true, itag);
if (!Stream.containSimilarStream(videoStream, videoOnlyStreams)) {
videoOnlyStreams.add(videoStream);
}
}
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get video only streams", e);
}
@ -572,7 +577,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
@Nonnull
public List<SubtitlesStream> getSubtitles(final MediaFormat format) throws ParsingException {
assertPageFetched();
// If the video is age restricted getPlayerConfig will fail
// if the video is age restricted getPlayerConfig will fail
if (getAgeLimit() != NO_AGE_LIMIT) {
return Collections.emptyList();
}
@ -630,7 +635,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.getArray("contents").getObject(0).getObject("compactVideoRenderer");
return new YoutubeStreamInfoItemExtractor(videoInfo, getTimeAgoParser());
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get next video", e);
}
}
@ -663,7 +668,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
}
return collector;
} catch (Exception e) {
} catch (final Exception e) {
throw new ParsingException("Could not get related videos", e);
}
}
@ -677,7 +682,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
return getTextFromObject(initialAjaxJson.getObject(2).getObject("playerResponse")
.getObject("playabilityStatus").getObject("errorScreen")
.getObject("playerErrorMessageRenderer").getObject("reason"));
} catch (ParsingException | NullPointerException e) {
} catch (final ParsingException | NullPointerException e) {
return null; // no error message
}
}
@ -713,16 +718,74 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
playerResponse = initialAjaxJson.getObject(2).getObject("playerResponse", null);
// Save the playerResponse from the youtube.com website,
// because there can be restrictions on the embedded player.
// E.g. if a video is age-restricted, the embedded player's playabilityStatus says,
// that the video cannot be played outside of YouTube,
// but does not show the original message.
JsonObject youtubePlayerResponse = playerResponse;
if (playerResponse == null || !playerResponse.has("streamingData")) {
// try to get player response by fetching video info page
fetchVideoInfoPage();
}
final JsonObject playabilityStatus = playerResponse.getObject("playabilityStatus");
final String status = playabilityStatus.getString("status");
// If status exist, and is not "OK", throw a ContentNotAvailableException with the reason.
if (status != null && !status.toLowerCase().equals("ok")) {
if (playerResponse == null && youtubePlayerResponse == null) {
throw new ExtractionException("Could not get playerResponse");
} else if (youtubePlayerResponse == null) {
youtubePlayerResponse = playerResponse;
}
JsonObject playabilityStatus = (playerResponse == null ? youtubePlayerResponse : playerResponse)
.getObject("playabilityStatus");
String status = playabilityStatus.getString("status");
// If status exist, and is not "OK", throw the specific exception based on error message
// or a ContentNotAvailableException with the reason text if it's an unknown reason.
if (status != null && !status.equalsIgnoreCase("ok")) {
playabilityStatus = youtubePlayerResponse.getObject("playabilityStatus");
status = playabilityStatus.getString("status");
final String reason = playabilityStatus.getString("reason");
if (status.equalsIgnoreCase("login_required")) {
if (reason == null) {
final String message = playabilityStatus.getArray("messages").getString(0);
if (message != null && message.equals("This is a private video. Please sign in to verify that you may see it.")) {
throw new PrivateContentException("This video is private.");
}
} else if (reason.equals("Sign in to confirm your age")) {
// No streams can be fetched, therefore thrown an AgeRestrictedContentException explicitly.
throw new AgeRestrictedContentException("This age-restricted video cannot be watched.");
}
}
if (status.equalsIgnoreCase("unplayable")) {
if (reason != null) {
if (reason.equals("This video is only available to Music Premium members")) {
throw new YoutubeMusicPremiumContentException();
}
if (reason.equals("This video requires payment to watch.")) {
throw new PaidContentException("This video is a paid video");
}
if (reason.equals("Join this channel to get access to members-only content like this video, and other exclusive perks.") ||
reason.equals("Join this channel to get access to members-only content like this video and other exclusive perks.")) {
throw new PaidContentException("This video is only available for members of the channel of this video");
}
if (reason.equals("Video unavailable")) {
final String detailedErrorMessage = playabilityStatus.getObject("errorScreen")
.getObject("playerErrorMessageRenderer")
.getObject("subreason")
.getArray("runs")
.getObject(0)
.getString("text");
if (detailedErrorMessage != null) {
if (detailedErrorMessage.equals("The uploader has not made this video available in your country.")) {
throw new GeographicRestrictionException("This video is not available in user's country.");
}
}
}
}
}
throw new ContentNotAvailableException("Got error: \"" + reason + "\"");
}
}
@ -736,7 +799,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try {
playerResponse = JsonParser.object().from(videoInfoPage.get("player_response"));
} catch (JsonParserException e) {
} catch (final JsonParserException e) {
throw new ParsingException(
"Could not parse YouTube player response from video info page", e);
}
@ -753,12 +816,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
final String assetsPattern = "\"assets\":.+?\"js\":\\s*(\"[^\"]+\")";
playerJsUrl = Parser.matchGroup1(assetsPattern, embedPageContent)
.replace("\\", "").replace("\"", "");
} catch (Parser.RegexException ex) {
} catch (final Parser.RegexException ex) {
// playerJsUrl is still available in the file, just somewhere else TODO
// it is ok not to find it, see how that's handled in getDeobfuscationCode()
final Document doc = Jsoup.parse(embedPageContent);
final Elements elems = doc.select("script").attr("name", "player_ias/base");
for (Element elem : elems) {
for (final Element elem : elems) {
if (elem.attr("src").contains("base.js")) {
playerJsUrl = elem.attr("src");
break;
@ -768,19 +831,18 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// Get embed sts
return Parser.matchGroup1("\"sts\"\\s*:\\s*(\\d+)", embedPageContent);
} catch (Exception i) {
} catch (final Exception i) {
// if it fails we simply reply with no sts as then it does not seem to be necessary
return "";
}
}
private String getDeobfuscationFuncName(final String playerCode) throws DeobfuscateException {
Parser.RegexException exception = null;
for (final String regex : REGEXES) {
try {
return Parser.matchGroup1(regex, playerCode);
} catch (Parser.RegexException re) {
} catch (final Parser.RegexException re) {
if (exception == null) {
exception = re;
}
@ -812,9 +874,9 @@ public class YoutubeStreamExtractor extends StreamExtractor {
"function " + DEOBFUSCATION_FUNC_NAME + "(a){return " + deobfuscationFunctionName + "(a);}";
return helperObject + deobfuscateFunction + callerFunction;
} catch (IOException ioe) {
} catch (final IOException ioe) {
throw new DeobfuscateException("Could not load deobfuscate function", ioe);
} catch (Exception e) {
} catch (final Exception e) {
throw new DeobfuscateException("Could not parse deobfuscate function ", e);
}
}
@ -855,7 +917,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
context.evaluateString(scope, deobfuscationCode, "deobfuscationCode", 1, null);
final Function deobfuscateFunc = (Function) scope.get(DEOBFUSCATION_FUNC_NAME, scope);
result = deobfuscateFunc.call(context, scope, scope, new Object[]{obfuscatedSig});
} catch (Exception e) {
} catch (final Exception e) {
throw new DeobfuscateException("Could not get deobfuscate signature", e);
} finally {
Context.exit();
@ -874,7 +936,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.getObject("results").getObject("results").getArray("contents");
JsonObject videoPrimaryInfoRenderer = null;
for (Object content : contents) {
for (final Object content : contents) {
if (((JsonObject) content).has("videoPrimaryInfoRenderer")) {
videoPrimaryInfoRenderer = ((JsonObject) content).getObject("videoPrimaryInfoRenderer");
break;
@ -896,7 +958,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
.getObject("results").getObject("results").getArray("contents");
JsonObject videoSecondaryInfoRenderer = null;
for (Object content : contents) {
for (final Object content : contents) {
if (((JsonObject) content).has("videoSecondaryInfoRenderer")) {
videoSecondaryInfoRenderer = ((JsonObject) content).getObject("videoSecondaryInfoRenderer");
break;
@ -975,7 +1037,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
urlAndItags.put(streamUrl, itagItem);
}
} catch (UnsupportedEncodingException ignored) {
} catch (final UnsupportedEncodingException ignored) {
}
}
}
@ -1032,7 +1094,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
result.trimToSize();
return result;
} catch (Exception e) {
} catch (final Exception e) {
throw new ExtractionException(e);
}
}

View file

@ -48,6 +48,7 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
public boolean expectedUploaderVerified() { return false; }
public String expectedSubChannelName() { return ""; } // default: there is no subchannel
public String expectedSubChannelUrl() { return ""; } // default: there is no subchannel
public boolean expectedDescriptionIsEmpty() { return false; } // default: description is not empty
public abstract List<String> expectedDescriptionContains(); // e.g. for full links
public abstract long expectedLength();
public long expectedTimestamp() { return 0; } // default: there is no timestamp
@ -146,7 +147,12 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
public void testDescription() throws Exception {
final Description description = extractor().getDescription();
assertNotNull(description);
assertFalse("description is empty", description.getContent().isEmpty());
if (expectedDescriptionIsEmpty()) {
assertTrue("description is not empty", description.getContent().isEmpty());
} else {
assertFalse("description is empty", description.getContent().isEmpty());
}
for (final String s : expectedDescriptionContains()) {
assertThat(description.getContent(), containsString(s));
@ -225,7 +231,7 @@ public abstract class DefaultStreamExtractorTest extends DefaultExtractorTest<St
defaultTestListOfItems(extractor().getService(), relatedStreams.getItems(),
relatedStreams.getErrors());
} else {
assertNull(relatedStreams);
assertTrue(relatedStreams == null || relatedStreams.getItems().isEmpty());
}
}

View file

@ -1,14 +1,20 @@
package org.schabi.newpipe.extractor.services.soundcloud;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.schabi.newpipe.downloader.DownloaderTestImpl;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamType;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
@ -16,10 +22,93 @@ import javax.annotation.Nullable;
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
public class SoundcloudStreamExtractorTest {
private static final String SOUNDCLOUD = "https://soundcloud.com/";
@Ignore("Ignore until #526 is merged. Throwing the ContentNotSupportedException is wrong and going to be fixed by that PR.")
public static class SoundcloudGeoRestrictedTrack extends DefaultStreamExtractorTest {
private static final String ID = "one-touch";
private static final String UPLOADER = SOUNDCLOUD + "jessglynne";
private static final int TIMESTAMP = 0;
private static final String URL = UPLOADER + "/" + ID + "#t=" + TIMESTAMP;
private static StreamExtractor extractor;
@Test(expected = GeographicRestrictionException.class)
public void geoRestrictedContent() throws Exception {
NewPipe.init(DownloaderTestImpl.getInstance());
extractor = SoundCloud.getStreamExtractor(URL);
extractor.fetchPage();
}
@Override public StreamExtractor extractor() { return extractor; }
@Override public StreamingService expectedService() { return SoundCloud; }
@Override public String expectedName() { return "Jess Glynne & Jax Jones - One Touch"; }
@Override public String expectedId() { return "621612588"; }
@Override public String expectedUrlContains() { return UPLOADER + "/" + ID; }
@Override public String expectedOriginalUrlContains() { return URL; }
@Override public StreamType expectedStreamType() { return StreamType.AUDIO_STREAM; }
@Override public String expectedUploaderName() { return "Jess Glynne"; }
@Override public String expectedUploaderUrl() { return UPLOADER; }
@Override public boolean expectedUploaderVerified() { return true; }
@Override public boolean expectedDescriptionIsEmpty() { return true; }
@Override public List<String> expectedDescriptionContains() { return Collections.emptyList(); }
@Override public long expectedLength() { return 197; }
@Override public long expectedTimestamp() { return TIMESTAMP; }
@Override public long expectedViewCountAtLeast() { return 43000; }
@Nullable @Override public String expectedUploadDate() { return "2019-05-16 16:28:45.000"; }
@Nullable @Override public String expectedTextualUploadDate() { return "2019-05-16 16:28:45"; }
@Override public long expectedLikeCountAtLeast() { return -1; }
@Override public long expectedDislikeCountAtLeast() { return -1; }
@Override public boolean expectedHasVideoStreams() { return false; }
@Override public boolean expectedHasSubtitles() { return false; }
@Override public boolean expectedHasFrames() { return false; }
@Override public int expectedStreamSegmentsCount() { return 0; }
@Override public boolean expectedHasRelatedStreams() { return false; }
}
public static class SoundcloudGoPlusTrack extends DefaultStreamExtractorTest {
private static final String ID = "places";
private static final String UPLOADER = SOUNDCLOUD + "martinsolveig";
private static final int TIMESTAMP = 0;
private static final String URL = UPLOADER + "/" + ID + "#t=" + TIMESTAMP;
private static StreamExtractor extractor;
@Test(expected = SoundCloudGoPlusContentException.class)
public void goPlusContent() throws Exception {
NewPipe.init(DownloaderTestImpl.getInstance());
extractor = SoundCloud.getStreamExtractor(URL);
extractor.fetchPage();
}
@Override public StreamExtractor extractor() { return extractor; }
@Override public StreamingService expectedService() { return SoundCloud; }
@Override public String expectedName() { return "Places (feat. Ina Wroldsen)"; }
@Override public String expectedId() { return "292479564"; }
@Override public String expectedUrlContains() { return UPLOADER + "/" + ID; }
@Override public String expectedOriginalUrlContains() { return URL; }
@Override public StreamType expectedStreamType() { return StreamType.AUDIO_STREAM; }
@Override public String expectedUploaderName() { return "martinsolveig"; }
@Override public String expectedUploaderUrl() { return UPLOADER; }
@Override public boolean expectedUploaderVerified() { return true; }
@Override public boolean expectedDescriptionIsEmpty() { return true; }
@Override public List<String> expectedDescriptionContains() { return Collections.emptyList(); }
@Override public long expectedLength() { return 30; }
@Override public long expectedTimestamp() { return TIMESTAMP; }
@Override public long expectedViewCountAtLeast() { return 386000; }
@Nullable @Override public String expectedUploadDate() { return "2016-11-11 01:16:37.000"; }
@Nullable @Override public String expectedTextualUploadDate() { return "2016-11-11 01:16:37"; }
@Override public long expectedLikeCountAtLeast() { return -1; }
@Override public long expectedDislikeCountAtLeast() { return -1; }
@Override public boolean expectedHasVideoStreams() { return false; }
@Override public boolean expectedHasSubtitles() { return false; }
@Override public boolean expectedHasFrames() { return false; }
@Override public int expectedStreamSegmentsCount() { return 0; }
}
public static class CreativeCommonsPlaysWellWithOthers extends DefaultStreamExtractorTest {
private static final String ID = "plays-well-with-others-ep-2-what-do-an-army-of-ants-and-an-online-encyclopedia-have-in-common";
private static final String UPLOADER = "https://soundcloud.com/wearecc";
private static final String UPLOADER = SOUNDCLOUD + "wearecc";
private static final int TIMESTAMP = 69;
private static final String URL = UPLOADER + "/" + ID + "#t=" + TIMESTAMP;
private static StreamExtractor extractor;

View file

@ -8,7 +8,11 @@ import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.services.DefaultStreamExtractorTest;
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.stream.Description;
@ -60,6 +64,13 @@ public class YoutubeStreamExtractorDefaultTest {
NewPipe.init(new DownloaderFactory().getDownloader(RESOURCE_PATH + "notAvailable"));
}
@Test(expected = GeographicRestrictionException.class)
public void geoRestrictedContent() throws Exception {
final StreamExtractor extractor =
YouTube.getStreamExtractor(BASE_URL + "_PL2HJKxnOM");
extractor.fetchPage();
}
@Test(expected = ContentNotAvailableException.class)
public void nonExistentFetch() throws Exception {
final StreamExtractor extractor =
@ -73,6 +84,27 @@ public class YoutubeStreamExtractorDefaultTest {
YouTube.getStreamExtractor(BASE_URL + "INVALID_ID_INVALID_ID");
extractor.fetchPage();
}
@Test(expected = PaidContentException.class)
public void paidContent() throws Exception {
final StreamExtractor extractor =
YouTube.getStreamExtractor(BASE_URL + "ayI2iBwGdxw");
extractor.fetchPage();
}
@Test(expected = PrivateContentException.class)
public void privateContent() throws Exception {
final StreamExtractor extractor =
YouTube.getStreamExtractor(BASE_URL + "8VajtrESJzA");
extractor.fetchPage();
}
@Test(expected = YoutubeMusicPremiumContentException.class)
public void youtubeMusicPremiumContent() throws Exception {
final StreamExtractor extractor =
YouTube.getStreamExtractor(BASE_URL + "sMJ8bRN2dak");
extractor.fetchPage();
}
}
public static class DescriptionTestPewdiepie extends DefaultStreamExtractorTest {

View file

@ -20,7 +20,7 @@
},
"response": {
"responseCode": 200,
"responseMessage": "",
"responseMessage": "OK",
"responseHeaders": {
"alt-svc": [
"h3-29\u003d\":443\"; ma\u003d2592000,h3-T051\u003d\":443\"; ma\u003d2592000,h3-Q050\u003d\":443\"; ma\u003d2592000,h3-Q046\u003d\":443\"; ma\u003d2592000,h3-Q043\u003d\":443\"; ma\u003d2592000,quic\u003d\":443\"; ma\u003d2592000; v\u003d\"46,43\""
@ -35,7 +35,7 @@
"application/json; charset\u003dutf-8"
],
"date": [
"Sat, 13 Feb 2021 19:13:27 GMT"
"Sun, 21 Feb 2021 16:32:21 GMT"
],
"expires": [
"Mon, 01 Jan 1990 00:00:00 GMT"
@ -50,14 +50,17 @@
"ESF"
],
"set-cookie": [
"GPS\u003d1; Domain\u003d.youtube.com; Expires\u003dSat, 13-Feb-2021 19:43:27 GMT; Path\u003d/; Secure; HttpOnly",
"YSC\u003djEoKPLL534o; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003d3tVKAqhB50g; Domain\u003d.youtube.com; Expires\u003dThu, 12-Aug-2021 19:13:27 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+694; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
"GPS\u003d1; Domain\u003d.youtube.com; Expires\u003dSun, 21-Feb-2021 17:02:21 GMT; Path\u003d/; Secure; HttpOnly",
"YSC\u003dmqhSM1eHxP0; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003dtBlN1T2LiYc; Domain\u003d.youtube.com; Expires\u003dFri, 20-Aug-2021 16:32:21 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+871; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
],
"strict-transport-security": [
"max-age\u003d31536000"
],
"transfer-encoding": [
"chunked"
],
"x-content-type-options": [
"nosniff"
],
@ -71,7 +74,7 @@
"0"
]
},
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r5---sn-4g5ednly.googlevideo.com\\/generate_204\",\"https:\\/\\/r5---sn-4g5ednly.googlevideo.com\\/generate_204?conn2\"]},\r\n{\"page\": \"watch\",\"playerResponse\": {\"responseContext\":{\"serviceTrackingParams\":[{\"service\":\"GFEEDBACK\",\"params\":[{\"key\":\"is_viewed_live\",\"value\":\"False\"},{\"key\":\"logged_in\",\"value\":\"0\"},{\"key\":\"e\",\"value\":\"23744176,23987676,23994373,23912909,23890959,23970529,23839597,23934970,23891344,23974595,1714255,23804281,23946420,23918597,23971936,23858057,23891346,23884386,23969934,23996751,23882502,23968386,23986026,23976578,23996375,24000882,23944779\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0xedf6baa3a1796f75\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210114\"},{\"key\":\"client.name\",\"value\":\"WEB\"}]}],\"mainAppWebResponseContext\":{\"loggedOut\":true},\"webResponseContextExtensionData\":{\"hasDecorated\":true}},\"playabilityStatus\":{\"status\":\"ERROR\",\"reason\":\"Video unavailable\",\"errorScreen\":{\"playerErrorMessageRenderer\":{\"reason\":{\"simpleText\":\"Video unavailable\"},\"thumbnail\":{\"thumbnails\":[{\"url\":\"//s.ytimg.com/yts/img/meh7-vflGevej7.png\",\"width\":140,\"height\":100}]},\"icon\":{\"iconType\":\"ERROR_OUTLINE\"}}},\"contextParams\":\"Q0FBU0FnZ0E\u003d\"},\"trackingParams\":\"CAAQu2kiEwjP37bhyOfuAhUWHeAKHRPGDjM\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgszdFZLQXFoQjUwZyjXyaCBBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbW5vX0NhQzZZc0JmRkdQRHJLWElKSkZUVDc3UXxBQ3Jtc0tuTnNpZm5haW1ySHkzZjM1THpxYk1OMUpMVWtuWC1Pd0FrbnE5Tm0yTldoYkgzdjdxUXBTVzhNaVRuZ2FFc2d4QngzWFBral8xM3d1UGlmdkxnSXp0ekhMWUJCamtkeEV4eUFlR3lZUWl1b1o5RkpObw\\u003d\\u003d\",\"url\": \"/watch?v\\u003dINVALID_ID_\",\"endpoint\": {\"clickTrackingParams\":\"IhMIn9m14cjn7gIV0ox8Ch2Udg5_MghleHRlcm5hbA\u003d\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003dINVALID_ID_\",\"webPageType\":\"WEB_PAGE_TYPE_WATCH\",\"rootVe\":3832}},\"watchEndpoint\":{\"videoId\":\"INVALID_ID_\"}}},\r\n{\"page\": \"watch\",\"timing\": {\"info\": {\"st\": 0.0 }}}]\r\n",
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r5---sn-25glen7r.googlevideo.com\\/generate_204\",\"https:\\/\\/r5---sn-25glen7r.googlevideo.com\\/generate_204?conn2\"]},\r\n{\"page\": \"watch\",\"playerResponse\": {\"responseContext\":{\"serviceTrackingParams\":[{\"service\":\"GFEEDBACK\",\"params\":[{\"key\":\"is_viewed_live\",\"value\":\"False\"},{\"key\":\"logged_in\",\"value\":\"0\"},{\"key\":\"e\",\"value\":\"1714259,23981915,23974595,23890959,23744176,23987676,23986020,23974883,23999517,23944779,23992808,23839597,24000882,23996624,23891344,23977071,23994373,23966208,23970529,24006263,23891346,23997047,23741522,23857950,23882502,23991264,23996751,23969934,23884386,9466593,23976578,23848212,23934970,23946420,23973444,23804281,23971936,23877025,23980552,23998149,23994474,23968386,23918597,23999478\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0xbe1d80f13a2a71eb\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210114\"},{\"key\":\"client.name\",\"value\":\"WEB\"}]}],\"mainAppWebResponseContext\":{\"loggedOut\":true},\"webResponseContextExtensionData\":{\"hasDecorated\":true}},\"playabilityStatus\":{\"status\":\"ERROR\",\"reason\":\"Video unavailable\",\"errorScreen\":{\"playerErrorMessageRenderer\":{\"reason\":{\"simpleText\":\"Video unavailable\"},\"thumbnail\":{\"thumbnails\":[{\"url\":\"//s.ytimg.com/yts/img/meh7-vflGevej7.png\",\"width\":140,\"height\":100}]},\"icon\":{\"iconType\":\"ERROR_OUTLINE\"}}},\"contextParams\":\"Q0FBU0FnZ0E\u003d\"},\"trackingParams\":\"CAAQu2kiEwiM2ZfWs_vuAhVMBQYAHSlPDN4\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"Cgt0QmxOMVQyTGlZYyiVlsqBBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbFdNMzIzMmFLWGdvTjI3M255R3lOMm1lb2h4d3xBQ3Jtc0ttODlkSUMtS3ZlR1RnRVFTLWVvY3hpVjZ5Z091N1R4aXRxanJkaEQzb1p3SlFUVVNLSi1lZTBzR1VhUkRnTG5WQVk0UkNmTFBqeGFnTG5UN3k5dDA2Zk9kbkgyaXdPbzgtZjVtX190ZE5YdzlWR1lCQQ\\u003d\\u003d\",\"url\": \"/watch?v\\u003dINVALID_ID_\",\"endpoint\": {\"clickTrackingParams\":\"IhMIhvSW1rP77gIVrhvxBR0B-w8mMghleHRlcm5hbA\u003d\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003dINVALID_ID_\",\"webPageType\":\"WEB_PAGE_TYPE_WATCH\",\"rootVe\":3832}},\"watchEndpoint\":{\"videoId\":\"INVALID_ID_\"}}},\r\n{\"page\": \"watch\",\"timing\": {\"info\": {\"st\": 0.0 }}}]\r\n",
"latestUrl": "https://www.youtube.com/watch?v\u003dINVALID_ID_\u0026pbj\u003d1"
}
}

View file

@ -0,0 +1,80 @@
{
"request": {
"httpMethod": "GET",
"url": "https://www.youtube.com/watch?v\u003d8VajtrESJzA\u0026pbj\u003d1",
"headers": {
"Accept-Language": [
"en-GB, en;q\u003d0.9"
],
"X-YouTube-Client-Name": [
"1"
],
"X-YouTube-Client-Version": [
"2.20200214.04.00"
]
},
"localization": {
"languageCode": "en",
"countryCode": "GB"
}
},
"response": {
"responseCode": 200,
"responseMessage": "OK",
"responseHeaders": {
"alt-svc": [
"h3-29\u003d\":443\"; ma\u003d2592000,h3-T051\u003d\":443\"; ma\u003d2592000,h3-Q050\u003d\":443\"; ma\u003d2592000,h3-Q046\u003d\":443\"; ma\u003d2592000,h3-Q043\u003d\":443\"; ma\u003d2592000,quic\u003d\":443\"; ma\u003d2592000; v\u003d\"46,43\""
],
"cache-control": [
"no-cache, no-store, max-age\u003d0, must-revalidate"
],
"content-disposition": [
"attachment"
],
"content-type": [
"application/json; charset\u003dutf-8"
],
"date": [
"Sun, 21 Feb 2021 16:32:24 GMT"
],
"expires": [
"Mon, 01 Jan 1990 00:00:00 GMT"
],
"p3p": [
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
],
"pragma": [
"no-cache"
],
"server": [
"ESF"
],
"set-cookie": [
"GPS\u003d1; Domain\u003d.youtube.com; Expires\u003dSun, 21-Feb-2021 17:02:24 GMT; Path\u003d/; Secure; HttpOnly",
"YSC\u003d19P0gGWXP2o; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003da-2fj78bANo; Domain\u003d.youtube.com; Expires\u003dFri, 20-Aug-2021 16:32:24 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+569; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
],
"strict-transport-security": [
"max-age\u003d31536000"
],
"transfer-encoding": [
"chunked"
],
"x-content-type-options": [
"nosniff"
],
"x-frame-options": [
"SAMEORIGIN"
],
"x-spf-response-type": [
"multipart"
],
"x-xss-protection": [
"0"
]
},
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r3---sn-25ge7nsl.googlevideo.com\\/generate_204\",\"https:\\/\\/r3---sn-25ge7nsl.googlevideo.com\\/generate_204?conn2\"]},\r\n{\"page\": \"watch\",\"playerResponse\": {\"responseContext\":{\"serviceTrackingParams\":[{\"service\":\"GFEEDBACK\",\"params\":[{\"key\":\"is_viewed_live\",\"value\":\"False\"},{\"key\":\"logged_in\",\"value\":\"0\"},{\"key\":\"e\",\"value\":\"24002342,23918597,23986023,23983514,23999478,23744176,23880830,9466593,23934970,23931562,23978419,23891347,23940248,23996625,23974595,23890959,23995022,23991736,23966208,23992809,23970529,23946420,23891344,23804281,23882502,23996751,23884386,23971936,24000882,23944779,23885487,23974884,23994984,23994373,23976578,23998150,23994475,23969934,23880836,23839597,23857949,23968386,23983297,23993730,23987676,1714259\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0xbf3eb825a2665b63\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210114\"},{\"key\":\"client.name\",\"value\":\"WEB\"}]}],\"mainAppWebResponseContext\":{\"loggedOut\":true},\"webResponseContextExtensionData\":{\"hasDecorated\":true}},\"playabilityStatus\":{\"status\":\"LOGIN_REQUIRED\",\"messages\":[\"This is a private video. Please sign in to verify that you may see it.\"],\"errorScreen\":{\"playerErrorMessageRenderer\":{\"subreason\":{\"simpleText\":\"Sign in if you\u0027ve been granted access to this video\"},\"reason\":{\"simpleText\":\"Private video\"},\"proceedButton\":{\"buttonRenderer\":{\"style\":\"STYLE_OVERLAY\",\"size\":\"SIZE_DEFAULT\",\"isDisabled\":false,\"text\":{\"simpleText\":\"Sign in\"},\"navigationEndpoint\":{\"clickTrackingParams\":\"CAEQ8FsiEwjlysbXs_vuAhUVGPEFHZMpD64\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"https://accounts.google.com/ServiceLogin?service\u003dyoutube\\u0026uilel\u003d3\\u0026passive\u003dtrue\\u0026continue\u003dhttps%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26app%3Ddesktop%26hl%3Den-GB%26next%3D%252Fwatch%253Fv%253D8VajtrESJzA\\u0026hl\u003den-GB\",\"webPageType\":\"WEB_PAGE_TYPE_UNKNOWN\",\"rootVe\":83769}},\"signInEndpoint\":{\"nextEndpoint\":{\"clickTrackingParams\":\"CAEQ8FsiEwjlysbXs_vuAhUVGPEFHZMpD64\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003d8VajtrESJzA\",\"webPageType\":\"WEB_PAGE_TYPE_UNKNOWN\",\"rootVe\":83769}},\"urlEndpoint\":{\"url\":\"/watch?v\u003d8VajtrESJzA\"}}}},\"trackingParams\":\"CAEQ8FsiEwjlysbXs_vuAhUVGPEFHZMpD64\u003d\"}},\"thumbnail\":{\"thumbnails\":[{\"url\":\"//s.ytimg.com/yts/img/meh7-vflGevej7.png\",\"width\":140,\"height\":100}]},\"icon\":{\"iconType\":\"ERROR_OUTLINE\"}}},\"contextParams\":\"Q0FBU0FnZ0E\u003d\"},\"trackingParams\":\"CAAQu2kiEwjlysbXs_vuAhUVGPEFHZMpD64\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgthLTJmajc4YkFObyiYlsqBBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbXBCdnJkelFzdnNHaEpnUEZObGNvbk5JSTNJd3xBQ3Jtc0tuSDlkZHJYcExYTkt6TVplTDU1cUQwUUgzUThPOVhPYXVPM2N1bDJmYV9weVNpdDdNVUd4cnNCX1VTU0NXcFRQTGdCWk1aNW5FM25nWVJORGs3S0hUSVdDV3NFcHRpNF91THJpN1N2QWp6VkNqSTdLOA\\u003d\\u003d\",\"url\": \"/watch?v\\u003d8VajtrESJzA\",\"endpoint\": {\"clickTrackingParams\":\"IhMI_6bF17P77gIV1h_xBR134Ae2MghleHRlcm5hbA\u003d\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003d8VajtrESJzA\",\"webPageType\":\"WEB_PAGE_TYPE_WATCH\",\"rootVe\":3832}},\"watchEndpoint\":{\"videoId\":\"8VajtrESJzA\"}}},\r\n{\"page\": \"watch\",\"timing\": {\"info\": {\"st\": 0.0 }}}]\r\n",
"latestUrl": "https://www.youtube.com/watch?v\u003d8VajtrESJzA\u0026pbj\u003d1"
}
}

View file

@ -0,0 +1,80 @@
{
"request": {
"httpMethod": "GET",
"url": "https://www.youtube.com/watch?v\u003ddon-t-exist\u0026pbj\u003d1",
"headers": {
"Accept-Language": [
"en-GB, en;q\u003d0.9"
],
"X-YouTube-Client-Name": [
"1"
],
"X-YouTube-Client-Version": [
"2.20200214.04.00"
]
},
"localization": {
"languageCode": "en",
"countryCode": "GB"
}
},
"response": {
"responseCode": 200,
"responseMessage": "OK",
"responseHeaders": {
"alt-svc": [
"h3-29\u003d\":443\"; ma\u003d2592000,h3-T051\u003d\":443\"; ma\u003d2592000,h3-Q050\u003d\":443\"; ma\u003d2592000,h3-Q046\u003d\":443\"; ma\u003d2592000,h3-Q043\u003d\":443\"; ma\u003d2592000,quic\u003d\":443\"; ma\u003d2592000; v\u003d\"46,43\""
],
"cache-control": [
"no-cache, no-store, max-age\u003d0, must-revalidate"
],
"content-disposition": [
"attachment"
],
"content-type": [
"application/json; charset\u003dutf-8"
],
"date": [
"Sun, 21 Feb 2021 16:32:25 GMT"
],
"expires": [
"Mon, 01 Jan 1990 00:00:00 GMT"
],
"p3p": [
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
],
"pragma": [
"no-cache"
],
"server": [
"ESF"
],
"set-cookie": [
"GPS\u003d1; Domain\u003d.youtube.com; Expires\u003dSun, 21-Feb-2021 17:02:25 GMT; Path\u003d/; Secure; HttpOnly",
"YSC\u003dZYtZ6I2oRtc; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"VISITOR_INFO1_LIVE\u003dlGs7Hq6Bd-s; Domain\u003d.youtube.com; Expires\u003dFri, 20-Aug-2021 16:32:25 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
"CONSENT\u003dPENDING+377; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
],
"strict-transport-security": [
"max-age\u003d31536000"
],
"transfer-encoding": [
"chunked"
],
"x-content-type-options": [
"nosniff"
],
"x-frame-options": [
"SAMEORIGIN"
],
"x-spf-response-type": [
"multipart"
],
"x-xss-protection": [
"0"
]
},
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r3---sn-25ge7nzs.googlevideo.com\\/generate_204\",\"https:\\/\\/r3---sn-25ge7nzs.googlevideo.com\\/generate_204?conn2\"]},\r\n{\"page\": \"watch\",\"playerResponse\": {\"responseContext\":{\"serviceTrackingParams\":[{\"service\":\"GFEEDBACK\",\"params\":[{\"key\":\"is_viewed_live\",\"value\":\"False\"},{\"key\":\"logged_in\",\"value\":\"0\"},{\"key\":\"e\",\"value\":\"23987364,23891344,23999475,23987908,23884386,23966208,23970529,23983515,23944779,23891347,23890959,23987676,23974595,23994373,9407157,23744176,24002218,23839597,23999565,24000882,23998150,23992808,1714242,23994368,23735348,23918597,23984880,23968386,23986016,23996751,24306355,23934970,23821390,23804281,23882502,23857950,23969934,23946420,23988063,23994474,23971936,23976578,23991736,23996625\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0xa53847d070bf2c39\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210114\"},{\"key\":\"client.name\",\"value\":\"WEB\"}]}],\"mainAppWebResponseContext\":{\"loggedOut\":true},\"webResponseContextExtensionData\":{\"hasDecorated\":true}},\"playabilityStatus\":{\"status\":\"ERROR\",\"reason\":\"Video unavailable\",\"errorScreen\":{\"playerErrorMessageRenderer\":{\"reason\":{\"simpleText\":\"Video unavailable\"},\"thumbnail\":{\"thumbnails\":[{\"url\":\"//s.ytimg.com/yts/img/meh7-vflGevej7.png\",\"width\":140,\"height\":100}]},\"icon\":{\"iconType\":\"ERROR_OUTLINE\"}}},\"contextParams\":\"Q0FBU0FnZ0E\u003d\"},\"trackingParams\":\"CAAQu2kiEwjdxOPXs_vuAhVbIEwKHbXzD7Y\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgtsR3M3SHE2QmQtcyiZlsqBBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbTNNcmg1NEltaHZEODVYZmZqNmI4RGpheDVxUXxBQ3Jtc0tuMzBYdHEzM096ZGJ1ZXUtSFpRRWNlTk0xakhJM2xIZHJqSkFNUkpHd2tnRk1WSXVDNFFpeXRiWWJ1NHk3T2xvalFBZzBTQjZETWkxZFotYUpyZkhnNjUtcjIxbVo0aFVxdC1XZmhYblpHRnJKS3VqSQ\\u003d\\u003d\",\"url\": \"/watch?v\\u003ddon-t-exist\",\"endpoint\": {\"clickTrackingParams\":\"IhMIvebi17P77gIVXzTxBR2cKA8lMghleHRlcm5hbA\u003d\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003ddon-t-exist\",\"webPageType\":\"WEB_PAGE_TYPE_WATCH\",\"rootVe\":3832}},\"watchEndpoint\":{\"videoId\":\"don-t-exist\"}}},\r\n{\"page\": \"watch\",\"timing\": {\"info\": {\"st\": 0.0 }}}]\r\n",
"latestUrl": "https://www.youtube.com/watch?v\u003ddon-t-exist\u0026pbj\u003d1"
}
}