Merge branch 'TeamNewPipe:dev' into fix-broken-yt-liked-comments
This commit is contained in:
commit
8c96545e57
|
@ -211,12 +211,37 @@ public class PeertubeStreamExtractor extends StreamExtractor {
|
|||
public List<VideoStream> getVideoStreams() throws ExtractionException {
|
||||
assertPageFetched();
|
||||
final List<VideoStream> videoStreams = new ArrayList<>();
|
||||
// mp4
|
||||
try {
|
||||
final JsonArray streams = json.getArray("files");
|
||||
videoStreams.addAll(getVideoStreamsFromArray(json.getArray("files")));
|
||||
} catch (Exception ignored) { }
|
||||
// HLS
|
||||
try {
|
||||
final JsonArray streamingPlaylists = json.getArray("streamingPlaylists");
|
||||
for (final Object p : streamingPlaylists) {
|
||||
if (!(p instanceof JsonObject)) continue;
|
||||
final JsonObject playlist = (JsonObject) p;
|
||||
videoStreams.addAll(getVideoStreamsFromArray(playlist.getArray("files")));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get video streams", e);
|
||||
}
|
||||
|
||||
return videoStreams;
|
||||
}
|
||||
|
||||
private List<VideoStream> getVideoStreamsFromArray(final JsonArray streams) throws ParsingException {
|
||||
try {
|
||||
final List<VideoStream> videoStreams = new ArrayList<>();
|
||||
for (final Object s : streams) {
|
||||
if (!(s instanceof JsonObject)) continue;
|
||||
final JsonObject stream = (JsonObject) s;
|
||||
final String url = JsonUtils.getString(stream, "fileUrl");
|
||||
final String url;
|
||||
if (stream.has("fileDownloadUrl")) {
|
||||
url = JsonUtils.getString(stream, "fileDownloadUrl");
|
||||
} else {
|
||||
url = JsonUtils.getString(stream, "fileUrl");
|
||||
}
|
||||
final String torrentUrl = JsonUtils.getString(stream, "torrentUrl");
|
||||
final String resolution = JsonUtils.getString(stream, "resolution.label");
|
||||
final String extension = url.substring(url.lastIndexOf(".") + 1);
|
||||
|
@ -226,13 +251,12 @@ public class PeertubeStreamExtractor extends StreamExtractor {
|
|||
videoStreams.add(videoStream);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get video streams", e);
|
||||
}
|
||||
|
||||
return videoStreams;
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get video streams from array");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VideoStream> getVideoOnlyStreams() {
|
||||
|
|
|
@ -16,7 +16,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudChannelInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
|
@ -41,8 +40,10 @@ import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
|||
import static org.schabi.newpipe.extractor.utils.Utils.*;
|
||||
|
||||
public class SoundcloudParsingHelper {
|
||||
private static final String HARDCODED_CLIENT_ID = "NcIaRZItQCNQp3Vq9Plvzf7tvjmVJnF6"; // Updated on 26/04/21
|
||||
private static final String HARDCODED_CLIENT_ID =
|
||||
"TT9Uj7PkasKPYxBlhLNxg2nFm9cLcKmv"; // Updated on 15/05/21
|
||||
private static String clientId;
|
||||
public static final String SOUNDCLOUD_API_V2_URL = "https://api-v2.soundcloud.com/";
|
||||
|
||||
private SoundcloudParsingHelper() {
|
||||
}
|
||||
|
@ -50,7 +51,7 @@ public class SoundcloudParsingHelper {
|
|||
public static synchronized String clientId() throws ExtractionException, IOException {
|
||||
if (!isNullOrEmpty(clientId)) return clientId;
|
||||
|
||||
Downloader dl = NewPipe.getDownloader();
|
||||
final Downloader dl = NewPipe.getDownloader();
|
||||
clientId = HARDCODED_CLIENT_ID;
|
||||
if (checkIfHardcodedClientIdIsValid()) {
|
||||
return clientId;
|
||||
|
@ -62,20 +63,23 @@ public class SoundcloudParsingHelper {
|
|||
final String responseBody = download.responseBody();
|
||||
final String clientIdPattern = ",client_id:\"(.*?)\"";
|
||||
|
||||
Document doc = Jsoup.parse(responseBody);
|
||||
final Elements possibleScripts = doc.select("script[src*=\"sndcdn.com/assets/\"][src$=\".js\"]");
|
||||
final Document doc = Jsoup.parse(responseBody);
|
||||
final Elements possibleScripts = doc.select(
|
||||
"script[src*=\"sndcdn.com/assets/\"][src$=\".js\"]");
|
||||
// The one containing the client id will likely be the last one
|
||||
Collections.reverse(possibleScripts);
|
||||
|
||||
final HashMap<String, List<String>> headers = new HashMap<>();
|
||||
headers.put("Range", singletonList("bytes=0-50000"));
|
||||
|
||||
for (Element element : possibleScripts) {
|
||||
for (final Element element : possibleScripts) {
|
||||
final String srcUrl = element.attr("src");
|
||||
if (!isNullOrEmpty(srcUrl)) {
|
||||
try {
|
||||
return clientId = Parser.matchGroup1(clientIdPattern, dl.get(srcUrl, headers).responseBody());
|
||||
} catch (RegexException ignored) {
|
||||
clientId = Parser.matchGroup1(clientIdPattern, dl.get(srcUrl, headers)
|
||||
.responseBody());
|
||||
return clientId;
|
||||
} catch (final RegexException ignored) {
|
||||
// Ignore it and proceed to try searching other script
|
||||
}
|
||||
}
|
||||
|
@ -85,77 +89,83 @@ public class SoundcloudParsingHelper {
|
|||
throw new ExtractionException("Couldn't extract client id");
|
||||
}
|
||||
|
||||
static boolean checkIfHardcodedClientIdIsValid() {
|
||||
try {
|
||||
SoundcloudStreamExtractor e = (SoundcloudStreamExtractor) SoundCloud
|
||||
.getStreamExtractor("https://soundcloud.com/liluzivert/do-what-i-want-produced-by-maaly-raw-don-cannon");
|
||||
e.fetchPage();
|
||||
return !e.getAudioStreams().isEmpty();
|
||||
} catch (Exception ignored) {
|
||||
// No need to throw an exception here. If something went wrong, the client_id is wrong
|
||||
return false;
|
||||
}
|
||||
static boolean checkIfHardcodedClientIdIsValid() throws IOException, ReCaptchaException {
|
||||
final int responseCode = NewPipe.getDownloader().get(SOUNDCLOUD_API_V2_URL + "?client_id="
|
||||
+ HARDCODED_CLIENT_ID).responseCode();
|
||||
// If the response code is 404, it means that the client_id is valid; otherwise,
|
||||
// it should be not valid
|
||||
return responseCode == 404;
|
||||
}
|
||||
|
||||
public static OffsetDateTime parseDateFrom(String textualUploadDate) throws ParsingException {
|
||||
public static OffsetDateTime parseDateFrom(final String textualUploadDate)
|
||||
throws ParsingException {
|
||||
try {
|
||||
return OffsetDateTime.parse(textualUploadDate);
|
||||
} catch (DateTimeParseException e1) {
|
||||
} catch (final DateTimeParseException e1) {
|
||||
try {
|
||||
return OffsetDateTime.parse(textualUploadDate, DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss +0000"));
|
||||
} catch (DateTimeParseException e2) {
|
||||
throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\"" + ", " + e1.getMessage(), e2);
|
||||
return OffsetDateTime.parse(textualUploadDate, DateTimeFormatter
|
||||
.ofPattern("yyyy/MM/dd HH:mm:ss +0000"));
|
||||
} catch (final DateTimeParseException e2) {
|
||||
throw new ParsingException("Could not parse date: \"" + textualUploadDate + "\""
|
||||
+ ", " + e1.getMessage(), e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the endpoint "/resolve" of the api.<p>
|
||||
* Call the endpoint "/resolve" of the API.<p>
|
||||
* <p>
|
||||
* See https://developers.soundcloud.com/docs/api/reference#resolve
|
||||
*/
|
||||
public static JsonObject resolveFor(Downloader downloader, String url) throws IOException, ExtractionException {
|
||||
String apiUrl = "https://api-v2.soundcloud.com/resolve"
|
||||
public static JsonObject resolveFor(@Nonnull final Downloader downloader, final String url)
|
||||
throws IOException, ExtractionException {
|
||||
final String apiUrl = SOUNDCLOUD_API_V2_URL + "resolve"
|
||||
+ "?url=" + URLEncoder.encode(url, UTF_8)
|
||||
+ "&client_id=" + clientId();
|
||||
|
||||
try {
|
||||
final String response = downloader.get(apiUrl, SoundCloud.getLocalization()).responseBody();
|
||||
final String response = downloader.get(apiUrl, SoundCloud.getLocalization())
|
||||
.responseBody();
|
||||
return JsonParser.object().from(response);
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the embed player with the apiUrl and return the canonical url (like the permalink_url from the json api).
|
||||
* Fetch the embed player with the apiUrl and return the canonical url (like the permalink_url
|
||||
* from the json API).
|
||||
*
|
||||
* @return the url resolved
|
||||
*/
|
||||
public static String resolveUrlWithEmbedPlayer(String apiUrl) throws IOException, ReCaptchaException {
|
||||
public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOException,
|
||||
ReCaptchaException {
|
||||
|
||||
String response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url="
|
||||
final String response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url="
|
||||
+ URLEncoder.encode(apiUrl, UTF_8), SoundCloud.getLocalization()).responseBody();
|
||||
|
||||
return Jsoup.parse(response).select("link[rel=\"canonical\"]").first().attr("abs:href");
|
||||
return Jsoup.parse(response).select("link[rel=\"canonical\"]").first()
|
||||
.attr("abs:href");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the widget API with the url and return the id (like the id from the json api).
|
||||
* Fetch the widget API with the url and return the id (like the id from the json API).
|
||||
*
|
||||
* @return the resolved id
|
||||
*/
|
||||
public static String resolveIdWithWidgetApi(String urlString) throws IOException, ReCaptchaException, ParsingException {
|
||||
public static String resolveIdWithWidgetApi(String urlString) throws IOException,
|
||||
ParsingException {
|
||||
// Remove the tailing slash from URLs due to issues with the SoundCloud API
|
||||
if (urlString.charAt(urlString.length() - 1) == '/') urlString = urlString.substring(0, urlString.length() - 1);
|
||||
// Make URL lower case and remove www. if it exists.
|
||||
if (urlString.charAt(urlString.length() - 1) == '/') urlString = urlString.substring(0,
|
||||
urlString.length() - 1);
|
||||
// Make URL lower case and remove m. and www. if it exists.
|
||||
// Without doing this, the widget API does not recognize the URL.
|
||||
urlString = Utils.removeWWWFromUrl(urlString.toLowerCase());
|
||||
urlString = Utils.removeMAndWWWFromUrl(urlString.toLowerCase());
|
||||
|
||||
final URL url;
|
||||
try {
|
||||
url = Utils.stringToURL(urlString);
|
||||
} catch (MalformedURLException e) {
|
||||
} catch (final MalformedURLException e) {
|
||||
throw new IllegalArgumentException("The given URL is not valid");
|
||||
}
|
||||
|
||||
|
@ -167,22 +177,27 @@ public class SoundcloudParsingHelper {
|
|||
SoundCloud.getLocalization()).responseBody();
|
||||
final JsonObject o = JsonParser.object().from(response);
|
||||
return String.valueOf(JsonUtils.getValue(o, "id"));
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse JSON response", e);
|
||||
} catch (ExtractionException e) {
|
||||
throw new ParsingException("Could not resolve id with embedded player. ClientId not extracted", e);
|
||||
} catch (final ExtractionException e) {
|
||||
throw new ParsingException(
|
||||
"Could not resolve id with embedded player. ClientId not extracted", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the users from the given api and commit each of them to the collector.
|
||||
* Fetch the users from the given API and commit each of them to the collector.
|
||||
* <p>
|
||||
* This differ from {@link #getUsersFromApi(ChannelInfoItemsCollector, String)} in the sense that they will always
|
||||
* get MIN_ITEMS or more.
|
||||
* This differ from {@link #getUsersFromApi(ChannelInfoItemsCollector, String)} in the sense
|
||||
* that they will always get MIN_ITEMS or more.
|
||||
*
|
||||
* @param minItems the method will return only when it have extracted that many items (equal or more)
|
||||
* @param minItems the method will return only when it have extracted that many items
|
||||
* (equal or more)
|
||||
*/
|
||||
public static String getUsersFromApiMinItems(int minItems, ChannelInfoItemsCollector collector, String apiUrl) throws IOException, ReCaptchaException, ParsingException {
|
||||
public static String getUsersFromApiMinItems(final int minItems,
|
||||
final ChannelInfoItemsCollector collector,
|
||||
final String apiUrl) throws IOException,
|
||||
ReCaptchaException, ParsingException {
|
||||
String nextPageUrl = SoundcloudParsingHelper.getUsersFromApi(collector, apiUrl);
|
||||
|
||||
while (!nextPageUrl.isEmpty() && collector.getItems().size() < minItems) {
|
||||
|
@ -193,23 +208,27 @@ public class SoundcloudParsingHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetch the user items from the given api and commit each of them to the collector.
|
||||
* Fetch the user items from the given API and commit each of them to the collector.
|
||||
*
|
||||
* @return the next streams url, empty if don't have
|
||||
*/
|
||||
public static String getUsersFromApi(ChannelInfoItemsCollector collector, String apiUrl) throws IOException, ReCaptchaException, ParsingException {
|
||||
String response = NewPipe.getDownloader().get(apiUrl, SoundCloud.getLocalization()).responseBody();
|
||||
JsonObject responseObject;
|
||||
public static String getUsersFromApi(final ChannelInfoItemsCollector collector,
|
||||
final String apiUrl) throws IOException,
|
||||
ReCaptchaException, ParsingException {
|
||||
final String response = NewPipe.getDownloader().get(apiUrl, SoundCloud.getLocalization())
|
||||
.responseBody();
|
||||
final JsonObject responseObject;
|
||||
|
||||
try {
|
||||
responseObject = JsonParser.object().from(response);
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
|
||||
JsonArray responseCollection = responseObject.getArray("collection");
|
||||
for (Object o : responseCollection) {
|
||||
final JsonArray responseCollection = responseObject.getArray("collection");
|
||||
for (final Object o : responseCollection) {
|
||||
if (o instanceof JsonObject) {
|
||||
JsonObject object = (JsonObject) o;
|
||||
final JsonObject object = (JsonObject) o;
|
||||
collector.commit(new SoundcloudChannelInfoItemExtractor(object));
|
||||
}
|
||||
}
|
||||
|
@ -217,8 +236,9 @@ public class SoundcloudParsingHelper {
|
|||
String nextPageUrl;
|
||||
try {
|
||||
nextPageUrl = responseObject.getString("next_href");
|
||||
if (!nextPageUrl.contains("client_id=")) nextPageUrl += "&client_id=" + SoundcloudParsingHelper.clientId();
|
||||
} catch (Exception ignored) {
|
||||
if (!nextPageUrl.contains("client_id=")) nextPageUrl += "&client_id="
|
||||
+ SoundcloudParsingHelper.clientId();
|
||||
} catch (final Exception ignored) {
|
||||
nextPageUrl = "";
|
||||
}
|
||||
|
||||
|
@ -226,14 +246,18 @@ public class SoundcloudParsingHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetch the streams from the given api and commit each of them to the collector.
|
||||
* Fetch the streams from the given API and commit each of them to the collector.
|
||||
* <p>
|
||||
* This differ from {@link #getStreamsFromApi(StreamInfoItemsCollector, String)} in the sense that they will always
|
||||
* get MIN_ITEMS or more items.
|
||||
* This differ from {@link #getStreamsFromApi(StreamInfoItemsCollector, String)} in the sense
|
||||
* that they will always get MIN_ITEMS or more items.
|
||||
*
|
||||
* @param minItems the method will return only when it have extracted that many items (equal or more)
|
||||
* @param minItems the method will return only when it have extracted that many items
|
||||
* (equal or more)
|
||||
*/
|
||||
public static String getStreamsFromApiMinItems(int minItems, StreamInfoItemsCollector collector, String apiUrl) throws IOException, ReCaptchaException, ParsingException {
|
||||
public static String getStreamsFromApiMinItems(final int minItems,
|
||||
final StreamInfoItemsCollector collector,
|
||||
final String apiUrl) throws IOException,
|
||||
ReCaptchaException, ParsingException {
|
||||
String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApi(collector, apiUrl);
|
||||
|
||||
while (!nextPageUrl.isEmpty() && collector.getItems().size() < minItems) {
|
||||
|
@ -244,59 +268,68 @@ public class SoundcloudParsingHelper {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetch the streams from the given api and commit each of them to the collector.
|
||||
* Fetch the streams from the given API and commit each of them to the collector.
|
||||
*
|
||||
* @return the next streams url, empty if don't have
|
||||
*/
|
||||
public static String getStreamsFromApi(StreamInfoItemsCollector collector, String apiUrl, boolean charts) throws IOException, ReCaptchaException, ParsingException {
|
||||
final Response response = NewPipe.getDownloader().get(apiUrl, SoundCloud.getLocalization());
|
||||
public static String getStreamsFromApi(final StreamInfoItemsCollector collector,
|
||||
final String apiUrl,
|
||||
final boolean charts) throws IOException,
|
||||
ReCaptchaException, ParsingException {
|
||||
final Response response = NewPipe.getDownloader().get(apiUrl, SoundCloud
|
||||
.getLocalization());
|
||||
if (response.responseCode() >= 400) {
|
||||
throw new IOException("Could not get streams from API, HTTP " + response.responseCode());
|
||||
throw new IOException("Could not get streams from API, HTTP " + response
|
||||
.responseCode());
|
||||
}
|
||||
|
||||
JsonObject responseObject;
|
||||
final JsonObject responseObject;
|
||||
try {
|
||||
responseObject = JsonParser.object().from(response.responseBody());
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
|
||||
JsonArray responseCollection = responseObject.getArray("collection");
|
||||
for (Object o : responseCollection) {
|
||||
final JsonArray responseCollection = responseObject.getArray("collection");
|
||||
for (final Object o : responseCollection) {
|
||||
if (o instanceof JsonObject) {
|
||||
JsonObject object = (JsonObject) o;
|
||||
collector.commit(new SoundcloudStreamInfoItemExtractor(charts ? object.getObject("track") : object));
|
||||
final JsonObject object = (JsonObject) o;
|
||||
collector.commit(new SoundcloudStreamInfoItemExtractor(charts
|
||||
? object.getObject("track") : object));
|
||||
}
|
||||
}
|
||||
|
||||
String nextPageUrl;
|
||||
try {
|
||||
nextPageUrl = responseObject.getString("next_href");
|
||||
if (!nextPageUrl.contains("client_id=")) nextPageUrl += "&client_id=" + SoundcloudParsingHelper.clientId();
|
||||
} catch (Exception ignored) {
|
||||
if (!nextPageUrl.contains("client_id=")) nextPageUrl += "&client_id="
|
||||
+ SoundcloudParsingHelper.clientId();
|
||||
} catch (final Exception ignored) {
|
||||
nextPageUrl = "";
|
||||
}
|
||||
|
||||
return nextPageUrl;
|
||||
}
|
||||
|
||||
public static String getStreamsFromApi(StreamInfoItemsCollector collector, String apiUrl) throws ReCaptchaException, ParsingException, IOException {
|
||||
public static String getStreamsFromApi(final StreamInfoItemsCollector collector,
|
||||
final String apiUrl) throws ReCaptchaException,
|
||||
ParsingException, IOException {
|
||||
return getStreamsFromApi(collector, apiUrl, false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static String getUploaderUrl(JsonObject object) {
|
||||
String url = object.getObject("user").getString("permalink_url", EMPTY_STRING);
|
||||
public static String getUploaderUrl(final JsonObject object) {
|
||||
final String url = object.getObject("user").getString("permalink_url", EMPTY_STRING);
|
||||
return replaceHttpWithHttps(url);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static String getAvatarUrl(JsonObject object) {
|
||||
String url = object.getObject("user").getString("avatar_url", EMPTY_STRING);
|
||||
public static String getAvatarUrl(final JsonObject object) {
|
||||
final String url = object.getObject("user").getString("avatar_url", EMPTY_STRING);
|
||||
return replaceHttpWithHttps(url);
|
||||
}
|
||||
|
||||
public static String getUploaderName(JsonObject object) {
|
||||
public static String getUploaderName(final JsonObject object) {
|
||||
return object.getObject("user").getString("username", EMPTY_STRING);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import org.schabi.newpipe.extractor.StreamingService;
|
|||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||
import org.schabi.newpipe.extractor.linkhandler.*;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
|
@ -23,7 +22,7 @@ import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCap
|
|||
|
||||
public class SoundcloudService extends StreamingService {
|
||||
|
||||
public SoundcloudService(int id) {
|
||||
public SoundcloudService(final int id) {
|
||||
super(id, "SoundCloud", asList(AUDIO, COMMENTS));
|
||||
}
|
||||
|
||||
|
@ -54,29 +53,29 @@ public class SoundcloudService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public List<ContentCountry> getSupportedCountries() {
|
||||
//Country selector here https://soundcloud.com/charts/top?genre=all-music
|
||||
// Country selector here: https://soundcloud.com/charts/top?genre=all-music
|
||||
return ContentCountry.listFrom(
|
||||
"AU", "CA", "DE", "FR", "GB", "IE", "NL", "NZ", "US"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamExtractor getStreamExtractor(LinkHandler LinkHandler) {
|
||||
return new SoundcloudStreamExtractor(this, LinkHandler);
|
||||
public StreamExtractor getStreamExtractor(final LinkHandler linkHandler) {
|
||||
return new SoundcloudStreamExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelExtractor getChannelExtractor(ListLinkHandler linkHandler) {
|
||||
public ChannelExtractor getChannelExtractor(final ListLinkHandler linkHandler) {
|
||||
return new SoundcloudChannelExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaylistExtractor getPlaylistExtractor(ListLinkHandler linkHandler) {
|
||||
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
|
||||
return new SoundcloudPlaylistExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchExtractor getSearchExtractor(SearchQueryHandler queryHandler) {
|
||||
public SearchExtractor getSearchExtractor(final SearchQueryHandler queryHandler) {
|
||||
return new SoundcloudSearchExtractor(this, queryHandler);
|
||||
}
|
||||
|
||||
|
@ -87,18 +86,11 @@ public class SoundcloudService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public KioskList getKioskList() throws ExtractionException {
|
||||
KioskList.KioskExtractorFactory chartsFactory = new KioskList.KioskExtractorFactory() {
|
||||
@Override
|
||||
public KioskExtractor createNewKiosk(StreamingService streamingService,
|
||||
String url,
|
||||
String id)
|
||||
throws ExtractionException {
|
||||
return new SoundcloudChartsExtractor(SoundcloudService.this,
|
||||
final KioskList.KioskExtractorFactory chartsFactory = (streamingService, url, id) ->
|
||||
new SoundcloudChartsExtractor(SoundcloudService.this,
|
||||
new SoundcloudChartsLinkHandlerFactory().fromUrl(url), id);
|
||||
}
|
||||
};
|
||||
|
||||
KioskList list = new KioskList(this);
|
||||
final KioskList list = new KioskList(this);
|
||||
|
||||
// add kiosks here e.g.:
|
||||
final SoundcloudChartsLinkHandlerFactory h = new SoundcloudChartsLinkHandlerFactory();
|
||||
|
@ -106,7 +98,7 @@ public class SoundcloudService extends StreamingService {
|
|||
list.addKioskEntry(chartsFactory, h, "Top 50");
|
||||
list.addKioskEntry(chartsFactory, h, "New & hot");
|
||||
list.setDefaultKiosk("New & hot");
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new ExtractionException(e);
|
||||
}
|
||||
|
||||
|
@ -124,9 +116,8 @@ public class SoundcloudService extends StreamingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CommentsExtractor getCommentsExtractor(ListLinkHandler linkHandler)
|
||||
public CommentsExtractor getCommentsExtractor(final ListLinkHandler linkHandler)
|
||||
throws ExtractionException {
|
||||
return new SoundcloudCommentsExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
|||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
|
@ -24,22 +25,25 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|||
public class SoundcloudChannelExtractor extends ChannelExtractor {
|
||||
private String userId;
|
||||
private JsonObject user;
|
||||
private static final String USERS_ENDPOINT = SOUNDCLOUD_API_V2_URL + "users/";
|
||||
|
||||
public SoundcloudChannelExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||
public SoundcloudChannelExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws IOException, ExtractionException {
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws IOException,
|
||||
ExtractionException {
|
||||
|
||||
userId = getLinkHandler().getId();
|
||||
final String apiUrl = "https://api-v2.soundcloud.com/users/" + userId +
|
||||
"?client_id=" + SoundcloudParsingHelper.clientId();
|
||||
final String apiUrl = USERS_ENDPOINT + userId + "?client_id="
|
||||
+ SoundcloudParsingHelper.clientId();
|
||||
|
||||
final String response = downloader.get(apiUrl, getExtractorLocalization()).responseBody();
|
||||
try {
|
||||
user = JsonParser.object().from(response);
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +67,8 @@ public class SoundcloudChannelExtractor extends ChannelExtractor {
|
|||
|
||||
@Override
|
||||
public String getBannerUrl() {
|
||||
return user.getObject("visuals").getArray("visuals").getObject(0).getString("visual_url");
|
||||
return user.getObject("visuals").getArray("visuals").getObject(0)
|
||||
.getString("visual_url");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,29 +110,31 @@ public class SoundcloudChannelExtractor extends ChannelExtractor {
|
|||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ExtractionException {
|
||||
try {
|
||||
final StreamInfoItemsCollector streamInfoItemsCollector = new StreamInfoItemsCollector(getServiceId());
|
||||
final StreamInfoItemsCollector streamInfoItemsCollector =
|
||||
new StreamInfoItemsCollector(getServiceId());
|
||||
|
||||
final String apiUrl = "https://api-v2.soundcloud.com/users/" + getId() + "/tracks"
|
||||
+ "?client_id=" + SoundcloudParsingHelper.clientId()
|
||||
+ "&limit=20"
|
||||
+ "&linked_partitioning=1";
|
||||
final String apiUrl = USERS_ENDPOINT + getId() + "/tracks" + "?client_id="
|
||||
+ SoundcloudParsingHelper.clientId() + "&limit=20" + "&linked_partitioning=1";
|
||||
|
||||
final String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, streamInfoItemsCollector, apiUrl);
|
||||
final String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15,
|
||||
streamInfoItemsCollector, apiUrl);
|
||||
|
||||
return new InfoItemsPage<>(streamInfoItemsCollector, new Page(nextPageUrl));
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new ExtractionException("Could not get next page", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException, ExtractionException {
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException,
|
||||
ExtractionException {
|
||||
if (page == null || isNullOrEmpty(page.getUrl())) {
|
||||
throw new IllegalArgumentException("Page doesn't contain an URL");
|
||||
}
|
||||
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
final String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector, page.getUrl());
|
||||
final String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector,
|
||||
page.getUrl());
|
||||
|
||||
return new InfoItemsPage<>(collector, new Page(nextPageUrl));
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
|||
public class SoundcloudChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||
private final JsonObject itemObject;
|
||||
|
||||
public SoundcloudChannelInfoItemExtractor(JsonObject itemObject) {
|
||||
public SoundcloudChannelInfoItemExtractor(final JsonObject itemObject) {
|
||||
this.itemObject = itemObject;
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,8 @@ public class SoundcloudChannelInfoItemExtractor implements ChannelInfoItemExtrac
|
|||
@Override
|
||||
public String getThumbnailUrl() {
|
||||
String avatarUrl = itemObject.getString("avatar_url", EMPTY_STRING);
|
||||
String avatarUrlBetterResolution = avatarUrl.replace("large.jpg", "crop.jpg");
|
||||
return avatarUrlBetterResolution;
|
||||
// An avatar URL with a better resolution
|
||||
return avatarUrl.replace("large.jpg", "crop.jpg");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,17 +15,18 @@ import javax.annotation.Nonnull;
|
|||
import java.io.IOException;
|
||||
|
||||
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public class SoundcloudChartsExtractor extends KioskExtractor<StreamInfoItem> {
|
||||
public SoundcloudChartsExtractor(StreamingService service,
|
||||
ListLinkHandler linkHandler,
|
||||
String kioskId) {
|
||||
public SoundcloudChartsExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler,
|
||||
final String kioskId) {
|
||||
super(service, linkHandler, kioskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull Downloader downloader) {
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) {
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -35,13 +36,15 @@ public class SoundcloudChartsExtractor extends KioskExtractor<StreamInfoItem> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException, ExtractionException {
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException,
|
||||
ExtractionException {
|
||||
if (page == null || isNullOrEmpty(page.getUrl())) {
|
||||
throw new IllegalArgumentException("Page doesn't contain an URL");
|
||||
}
|
||||
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
final String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApi(collector, page.getUrl(), true);
|
||||
final String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApi(collector,
|
||||
page.getUrl(), true);
|
||||
|
||||
return new InfoItemsPage<>(collector, new Page(nextPageUrl));
|
||||
}
|
||||
|
@ -51,9 +54,8 @@ public class SoundcloudChartsExtractor extends KioskExtractor<StreamInfoItem> {
|
|||
public InfoItemsPage<StreamInfoItem> getInitialPage() throws IOException, ExtractionException {
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
|
||||
String apiUrl = "https://api-v2.soundcloud.com/charts" +
|
||||
"?genre=soundcloud:genres:all-music" +
|
||||
"&client_id=" + SoundcloudParsingHelper.clientId();
|
||||
String apiUrl = SOUNDCLOUD_API_V2_URL + "charts" + "?genre=soundcloud:genres:all-music"
|
||||
+ "&client_id=" + SoundcloudParsingHelper.clientId();
|
||||
|
||||
if (getId().equals("Top 50")) {
|
||||
apiUrl += "&kind=top";
|
||||
|
@ -64,15 +66,18 @@ public class SoundcloudChartsExtractor extends KioskExtractor<StreamInfoItem> {
|
|||
final ContentCountry contentCountry = SoundCloud.getContentCountry();
|
||||
String apiUrlWithRegion = null;
|
||||
if (getService().getSupportedCountries().contains(contentCountry)) {
|
||||
apiUrlWithRegion = apiUrl + "®ion=soundcloud:regions:" + contentCountry.getCountryCode();
|
||||
apiUrlWithRegion = apiUrl + "®ion=soundcloud:regions:"
|
||||
+ contentCountry.getCountryCode();
|
||||
}
|
||||
|
||||
String nextPageUrl;
|
||||
try {
|
||||
nextPageUrl = SoundcloudParsingHelper.getStreamsFromApi(collector, apiUrlWithRegion == null ? apiUrl : apiUrlWithRegion, true);
|
||||
} catch (IOException e) {
|
||||
// Request to other region may be geo-restricted. See https://github.com/TeamNewPipe/NewPipeExtractor/issues/537
|
||||
// we retry without the specified region.
|
||||
nextPageUrl = SoundcloudParsingHelper.getStreamsFromApi(collector,
|
||||
apiUrlWithRegion == null ? apiUrl : apiUrlWithRegion, true);
|
||||
} catch (final IOException e) {
|
||||
// Request to other region may be geo-restricted.
|
||||
// See https://github.com/TeamNewPipe/NewPipeExtractor/issues/537.
|
||||
// We retry without the specified region.
|
||||
nextPageUrl = SoundcloudParsingHelper.getStreamsFromApi(collector, apiUrl, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,24 +24,27 @@ import javax.annotation.Nonnull;
|
|||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public class SoundcloudCommentsExtractor extends CommentsExtractor {
|
||||
public SoundcloudCommentsExtractor(final StreamingService service, final ListLinkHandler uiHandler) {
|
||||
public SoundcloudCommentsExtractor(final StreamingService service,
|
||||
final ListLinkHandler uiHandler) {
|
||||
super(service, uiHandler);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<CommentsInfoItem> getInitialPage() throws ExtractionException, IOException {
|
||||
public InfoItemsPage<CommentsInfoItem> getInitialPage() throws ExtractionException,
|
||||
IOException {
|
||||
final Downloader downloader = NewPipe.getDownloader();
|
||||
final Response response = downloader.get(getUrl());
|
||||
|
||||
final JsonObject json;
|
||||
try {
|
||||
json = JsonParser.object().from(response.responseBody());
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json", e);
|
||||
}
|
||||
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(getServiceId());
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(
|
||||
getServiceId());
|
||||
|
||||
collectStreamsFrom(collector, json.getArray("collection"));
|
||||
|
||||
|
@ -49,7 +52,8 @@ public class SoundcloudCommentsExtractor extends CommentsExtractor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<CommentsInfoItem> getPage(final Page page) throws ExtractionException, IOException {
|
||||
public InfoItemsPage<CommentsInfoItem> getPage(final Page page) throws ExtractionException,
|
||||
IOException {
|
||||
if (page == null || isNullOrEmpty(page.getUrl())) {
|
||||
throw new IllegalArgumentException("Page doesn't contain an URL");
|
||||
}
|
||||
|
@ -60,11 +64,12 @@ public class SoundcloudCommentsExtractor extends CommentsExtractor {
|
|||
final JsonObject json;
|
||||
try {
|
||||
json = JsonParser.object().from(response.responseBody());
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json", e);
|
||||
}
|
||||
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(getServiceId());
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(
|
||||
getServiceId());
|
||||
|
||||
collectStreamsFrom(collector, json.getArray("collection"));
|
||||
|
||||
|
@ -74,9 +79,10 @@ public class SoundcloudCommentsExtractor extends CommentsExtractor {
|
|||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) { }
|
||||
|
||||
private void collectStreamsFrom(final CommentsInfoItemsCollector collector, final JsonArray entries) throws ParsingException {
|
||||
private void collectStreamsFrom(final CommentsInfoItemsCollector collector,
|
||||
final JsonArray entries) throws ParsingException {
|
||||
final String url = getUrl();
|
||||
for (Object comment : entries) {
|
||||
for (final Object comment : entries) {
|
||||
collector.commit(new SoundcloudCommentsInfoItemExtractor((JsonObject) comment, url));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ import javax.annotation.Nullable;
|
|||
import java.util.Objects;
|
||||
|
||||
public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||
private JsonObject json;
|
||||
private String url;
|
||||
private final JsonObject json;
|
||||
private final String url;
|
||||
|
||||
public SoundcloudCommentsInfoItemExtractor(JsonObject json, String url) {
|
||||
public SoundcloudCommentsInfoItemExtractor(final JsonObject json, final String url) {
|
||||
this.json = json;
|
||||
this.url = url;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.List;
|
|||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
||||
|
@ -33,22 +34,23 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||
private String playlistId;
|
||||
private JsonObject playlist;
|
||||
|
||||
public SoundcloudPlaylistExtractor(StreamingService service, ListLinkHandler linkHandler) {
|
||||
public SoundcloudPlaylistExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws IOException,
|
||||
ExtractionException {
|
||||
|
||||
playlistId = getLinkHandler().getId();
|
||||
String apiUrl = "https://api-v2.soundcloud.com/playlists/" + playlistId +
|
||||
"?client_id=" + SoundcloudParsingHelper.clientId() +
|
||||
"&representation=compact";
|
||||
final String apiUrl = SOUNDCLOUD_API_V2_URL + "playlists/" + playlistId + "?client_id="
|
||||
+ SoundcloudParsingHelper.clientId() + "&representation=compact";
|
||||
|
||||
String response = downloader.get(apiUrl, getExtractorLocalization()).responseBody();
|
||||
final String response = downloader.get(apiUrl, getExtractorLocalization()).responseBody();
|
||||
try {
|
||||
playlist = JsonParser.object().from(response);
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
}
|
||||
|
@ -76,11 +78,11 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||
try {
|
||||
final InfoItemsPage<StreamInfoItem> infoItems = getInitialPage();
|
||||
|
||||
for (StreamInfoItem item : infoItems.getItems()) {
|
||||
for (final StreamInfoItem item : infoItems.getItems()) {
|
||||
artworkUrl = item.getThumbnailUrl();
|
||||
if (!isNullOrEmpty(artworkUrl)) break;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
|
||||
if (artworkUrl == null) {
|
||||
|
@ -139,18 +141,22 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() {
|
||||
final StreamInfoItemsCollector streamInfoItemsCollector = new StreamInfoItemsCollector(getServiceId());
|
||||
final StreamInfoItemsCollector streamInfoItemsCollector =
|
||||
new StreamInfoItemsCollector(getServiceId());
|
||||
final List<String> ids = new ArrayList<>();
|
||||
|
||||
final JsonArray tracks = playlist.getArray("tracks");
|
||||
for (Object o : tracks) {
|
||||
for (final Object o : tracks) {
|
||||
if (o instanceof JsonObject) {
|
||||
final JsonObject track = (JsonObject) o;
|
||||
if (track.has("title")) { // i.e. if full info is available
|
||||
streamInfoItemsCollector.commit(new SoundcloudStreamInfoItemExtractor(track));
|
||||
} else {
|
||||
// %09d would be enough, but a 0 before the number does not create problems, so let's be sure
|
||||
// %09d would be enough, but a 0 before the number does not create problems, so
|
||||
// let's be sure
|
||||
ids.add(String.format("%010d", track.getInt("id")));
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +166,8 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException, ExtractionException {
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException,
|
||||
ExtractionException {
|
||||
if (page == null || isNullOrEmpty(page.getIds())) {
|
||||
throw new IllegalArgumentException("Page doesn't contain IDs");
|
||||
}
|
||||
|
@ -176,21 +183,21 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
|
|||
nextIds = page.getIds().subList(STREAMS_PER_REQUESTED_PAGE, page.getIds().size());
|
||||
}
|
||||
|
||||
final String currentPageUrl = "https://api-v2.soundcloud.com/tracks?client_id="
|
||||
+ SoundcloudParsingHelper.clientId()
|
||||
+ "&ids=" + Utils.join(",", currentIds);
|
||||
final String currentPageUrl = SOUNDCLOUD_API_V2_URL + "tracks?client_id="
|
||||
+ SoundcloudParsingHelper.clientId() + "&ids=" + Utils.join(",", currentIds);
|
||||
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
final String response = NewPipe.getDownloader().get(currentPageUrl, getExtractorLocalization()).responseBody();
|
||||
final String response = NewPipe.getDownloader().get(currentPageUrl,
|
||||
getExtractorLocalization()).responseBody();
|
||||
|
||||
try {
|
||||
final JsonArray tracks = JsonParser.array().from(response);
|
||||
for (Object track : tracks) {
|
||||
for (final Object track : tracks) {
|
||||
if (track instanceof JsonObject) {
|
||||
collector.commit(new SoundcloudStreamInfoItemExtractor((JsonObject) track));
|
||||
}
|
||||
}
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtr
|
|||
|
||||
private final JsonObject itemObject;
|
||||
|
||||
public SoundcloudPlaylistInfoItemExtractor(JsonObject itemObject) {
|
||||
public SoundcloudPlaylistInfoItemExtractor(final JsonObject itemObject) {
|
||||
this.itemObject = itemObject;
|
||||
}
|
||||
|
||||
|
@ -34,22 +34,22 @@ public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtr
|
|||
if (itemObject.isString(ARTWORK_URL_KEY)) {
|
||||
final String artworkUrl = itemObject.getString(ARTWORK_URL_KEY, EMPTY_STRING);
|
||||
if (!artworkUrl.isEmpty()) {
|
||||
String artworkUrlBetterResolution = artworkUrl.replace("large.jpg", "crop.jpg");
|
||||
return artworkUrlBetterResolution;
|
||||
// An artwork URL with a better resolution
|
||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Look for artwork url inside the track list
|
||||
for (Object track : itemObject.getArray("tracks")) {
|
||||
for (final Object track : itemObject.getArray("tracks")) {
|
||||
final JsonObject trackObject = (JsonObject) track;
|
||||
|
||||
// First look for track artwork url
|
||||
if (trackObject.isString(ARTWORK_URL_KEY)) {
|
||||
String artworkUrl = trackObject.getString(ARTWORK_URL_KEY, EMPTY_STRING);
|
||||
if (!artworkUrl.isEmpty()) {
|
||||
String artworkUrlBetterResolution = artworkUrl.replace("large.jpg", "crop.jpg");
|
||||
return artworkUrlBetterResolution;
|
||||
// An artwork URL with a better resolution
|
||||
return artworkUrl.replace("large.jpg", "crop.jpg");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,14 +58,14 @@ public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtr
|
|||
final String creatorAvatar = creator.getString(AVATAR_URL_KEY, EMPTY_STRING);
|
||||
if (!creatorAvatar.isEmpty()) return creatorAvatar;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} catch (final Exception ignored) {
|
||||
// Try other method
|
||||
}
|
||||
|
||||
try {
|
||||
// Last resort, use user avatar url. If still not found, then throw exception.
|
||||
return itemObject.getObject(USER_KEY).getString(AVATAR_URL_KEY, EMPTY_STRING);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException("Failed to extract playlist thumbnail url", e);
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ public class SoundcloudPlaylistInfoItemExtractor implements PlaylistInfoItemExtr
|
|||
public String getUploaderName() throws ParsingException {
|
||||
try {
|
||||
return itemObject.getObject(USER_KEY).getString("username");
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException("Failed to extract playlist uploader", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
|||
public class SoundcloudSearchExtractor extends SearchExtractor {
|
||||
private JsonArray searchCollection;
|
||||
|
||||
public SoundcloudSearchExtractor(StreamingService service, SearchQueryHandler linkHandler) {
|
||||
public SoundcloudSearchExtractor(final StreamingService service,
|
||||
final SearchQueryHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
|
@ -52,34 +53,39 @@ public class SoundcloudSearchExtractor extends SearchExtractor {
|
|||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<InfoItem> getInitialPage() throws IOException, ExtractionException {
|
||||
return new InfoItemsPage<>(collectItems(searchCollection), getNextPageFromCurrentUrl(getUrl()));
|
||||
return new InfoItemsPage<>(collectItems(searchCollection), getNextPageFromCurrentUrl(
|
||||
getUrl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<InfoItem> getPage(final Page page) throws IOException, ExtractionException {
|
||||
public InfoItemsPage<InfoItem> getPage(final Page page) throws IOException,
|
||||
ExtractionException {
|
||||
if (page == null || isNullOrEmpty(page.getUrl())) {
|
||||
throw new IllegalArgumentException("Page doesn't contain an URL");
|
||||
}
|
||||
|
||||
final Downloader dl = getDownloader();
|
||||
try {
|
||||
final String response = dl.get(page.getUrl(), getExtractorLocalization()).responseBody();
|
||||
final String response = dl.get(page.getUrl(), getExtractorLocalization())
|
||||
.responseBody();
|
||||
searchCollection = JsonParser.object().from(response).getArray("collection");
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collectItems(searchCollection), getNextPageFromCurrentUrl(page.getUrl()));
|
||||
return new InfoItemsPage<>(collectItems(searchCollection), getNextPageFromCurrentUrl(page
|
||||
.getUrl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws IOException,
|
||||
ExtractionException {
|
||||
final Downloader dl = getDownloader();
|
||||
final String url = getUrl();
|
||||
try {
|
||||
final String response = dl.get(url, getExtractorLocalization()).responseBody();
|
||||
searchCollection = JsonParser.object().from(response).getArray("collection");
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
|
||||
|
@ -88,14 +94,14 @@ public class SoundcloudSearchExtractor extends SearchExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
private InfoItemsCollector<InfoItem, InfoItemExtractor> collectItems(JsonArray searchCollection) {
|
||||
private InfoItemsCollector<InfoItem, InfoItemExtractor> collectItems(
|
||||
final JsonArray searchCollection) {
|
||||
final InfoItemsSearchCollector collector = new InfoItemsSearchCollector(getServiceId());
|
||||
|
||||
for (Object result : searchCollection) {
|
||||
for (final Object result : searchCollection) {
|
||||
if (!(result instanceof JsonObject)) continue;
|
||||
//noinspection ConstantConditions
|
||||
JsonObject searchResult = (JsonObject) result;
|
||||
String kind = searchResult.getString("kind", EMPTY_STRING);
|
||||
final JsonObject searchResult = (JsonObject) result;
|
||||
final String kind = searchResult.getString("kind", EMPTY_STRING);
|
||||
switch (kind) {
|
||||
case "user":
|
||||
collector.commit(new SoundcloudChannelInfoItemExtractor(searchResult));
|
||||
|
@ -112,15 +118,12 @@ public class SoundcloudSearchExtractor extends SearchExtractor {
|
|||
return collector;
|
||||
}
|
||||
|
||||
private Page getNextPageFromCurrentUrl(String currentUrl)
|
||||
private Page getNextPageFromCurrentUrl(final String currentUrl)
|
||||
throws MalformedURLException, UnsupportedEncodingException {
|
||||
final int pageOffset = Integer.parseInt(
|
||||
Parser.compatParseMap(
|
||||
new URL(currentUrl)
|
||||
.getQuery())
|
||||
.get("offset"));
|
||||
Parser.compatParseMap(new URL(currentUrl).getQuery()).get("offset"));
|
||||
|
||||
return new Page(currentUrl.replace("&offset=" + pageOffset,
|
||||
"&offset=" + (pageOffset + ITEMS_PER_PAGE)));
|
||||
return new Page(currentUrl.replace("&offset=" + pageOffset, "&offset="
|
||||
+ (pageOffset + ITEMS_PER_PAGE)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,18 +30,21 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.*;
|
||||
|
||||
public class SoundcloudStreamExtractor extends StreamExtractor {
|
||||
private JsonObject track;
|
||||
private boolean isAvailable = true;
|
||||
|
||||
public SoundcloudStreamExtractor(StreamingService service, LinkHandler linkHandler) {
|
||||
public SoundcloudStreamExtractor(final StreamingService service,
|
||||
final LinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws IOException,
|
||||
ExtractionException {
|
||||
track = SoundcloudParsingHelper.resolveFor(downloader, getUrl());
|
||||
|
||||
final String policy = track.getString("policy", EMPTY_STRING);
|
||||
|
@ -50,9 +53,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
if (policy.equals("SNIP")) {
|
||||
throw new SoundCloudGoPlusContentException();
|
||||
}
|
||||
if (policy.equals("BLOCK")) {
|
||||
throw new GeographicRestrictionException("This track is not available in user's country");
|
||||
}
|
||||
if (policy.equals("BLOCK")) throw new GeographicRestrictionException(
|
||||
"This track is not available in user's country");
|
||||
throw new ContentNotAvailableException("Content not available: policy " + policy);
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +82,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
@Nonnull
|
||||
@Override
|
||||
public DateWrapper getUploadDate() throws ParsingException {
|
||||
return new DateWrapper(SoundcloudParsingHelper.parseDateFrom(track.getString("created_at")));
|
||||
return new DateWrapper(SoundcloudParsingHelper.parseDateFrom(track.getString(
|
||||
"created_at")));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -220,9 +223,12 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
}
|
||||
|
||||
@Nonnull
|
||||
private static String getTranscodingUrl(final String endpointUrl, final String protocol) throws IOException, ExtractionException {
|
||||
private static String getTranscodingUrl(final String endpointUrl,
|
||||
final String protocol)
|
||||
throws IOException, ExtractionException {
|
||||
final Downloader downloader = NewPipe.getDownloader();
|
||||
final String apiStreamUrl = endpointUrl + "?client_id=" + SoundcloudParsingHelper.clientId();
|
||||
final String apiStreamUrl = endpointUrl + "?client_id="
|
||||
+ SoundcloudParsingHelper.clientId();
|
||||
final String response = downloader.get(apiStreamUrl).responseBody();
|
||||
final JsonObject urlObject;
|
||||
try {
|
||||
|
@ -255,7 +261,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
}
|
||||
final String mediaUrl;
|
||||
final String preset = transcodingJsonObject.getString("preset");
|
||||
final String protocol = transcodingJsonObject.getObject("format").getString("protocol");
|
||||
final String protocol = transcodingJsonObject.getObject("format")
|
||||
.getString("protocol");
|
||||
MediaFormat mediaFormat = null;
|
||||
int bitrate = 0;
|
||||
if (preset.contains("mp3")) {
|
||||
|
@ -285,7 +292,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
/** Parses a SoundCloud HLS manifest to get a single URL of HLS streams.
|
||||
/**
|
||||
* Parses a SoundCloud HLS manifest to get a single URL of HLS streams.
|
||||
* <p>
|
||||
* This method downloads the provided manifest URL, find all web occurrences in the manifest,
|
||||
* get the last segment URL, changes its segment range to {@code 0/track-length} and return
|
||||
|
@ -293,7 +301,8 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
* @param hlsManifestUrl the URL of the manifest to be parsed
|
||||
* @return a single URL that contains a range equal to the length of the track
|
||||
*/
|
||||
private static String getSingleUrlFromHlsManifest(final String hlsManifestUrl) throws ParsingException {
|
||||
private static String getSingleUrlFromHlsManifest(final String hlsManifestUrl)
|
||||
throws ParsingException {
|
||||
final Downloader dl = NewPipe.getDownloader();
|
||||
final String hlsManifestResponse;
|
||||
|
||||
|
@ -306,11 +315,11 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
final String[] lines = hlsManifestResponse.split("\\r?\\n");
|
||||
for (int l = lines.length - 1; l >= 0; l--) {
|
||||
final String line = lines[l];
|
||||
// get the last URL from manifest, because it contains the range of the stream
|
||||
// Get the last URL from manifest, because it contains the range of the stream
|
||||
if (line.trim().length() != 0 && !line.startsWith("#") && line.startsWith("https")) {
|
||||
final String[] hlsLastRangeUrlArray = line.split("/");
|
||||
return HTTPS + hlsLastRangeUrlArray[2] + "/media/0/" + hlsLastRangeUrlArray[5] + "/"
|
||||
+ hlsLastRangeUrlArray[6];
|
||||
return HTTPS + hlsLastRangeUrlArray[2] + "/media/0/" + hlsLastRangeUrlArray[5]
|
||||
+ "/" + hlsLastRangeUrlArray[6];
|
||||
}
|
||||
}
|
||||
throw new ParsingException("Could not get any URL from HLS manifest");
|
||||
|
@ -356,7 +365,7 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
public StreamInfoItemsCollector getRelatedItems() throws IOException, ExtractionException {
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
|
||||
final String apiUrl = "https://api-v2.soundcloud.com/tracks/" + urlEncode(getId())
|
||||
final String apiUrl = SOUNDCLOUD_API_V2_URL + "tracks/" + urlEncode(getId())
|
||||
+ "/related?client_id=" + urlEncode(SoundcloudParsingHelper.clientId());
|
||||
|
||||
SoundcloudParsingHelper.getStreamsFromApi(collector, apiUrl);
|
||||
|
@ -399,15 +408,15 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
|
|||
@Nonnull
|
||||
@Override
|
||||
public List<String> getTags() {
|
||||
// tags are separated by spaces, but they can be multiple words escaped by quotes "
|
||||
final String[] tag_list = track.getString("tag_list").split(" ");
|
||||
// Tags are separated by spaces, but they can be multiple words escaped by quotes "
|
||||
final String[] tagList = track.getString("tag_list").split(" ");
|
||||
final List<String> tags = new ArrayList<>();
|
||||
String escapedTag = "";
|
||||
boolean isEscaped = false;
|
||||
for (int i = 0; i < tag_list.length; i++) {
|
||||
String tag = tag_list[i];
|
||||
for (int i = 0; i < tagList.length; i++) {
|
||||
String tag = tagList[i];
|
||||
if (tag.startsWith("\"")) {
|
||||
escapedTag += tag_list[i].replace("\"", "");
|
||||
escapedTag += tagList[i].replace("\"", "");
|
||||
isEscaped = true;
|
||||
} else if (isEscaped) {
|
||||
if (tag.endsWith("\"")) {
|
||||
|
|
|
@ -14,7 +14,7 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
|
|||
|
||||
protected final JsonObject itemObject;
|
||||
|
||||
public SoundcloudStreamInfoItemExtractor(JsonObject itemObject) {
|
||||
public SoundcloudStreamInfoItemExtractor(final JsonObject itemObject) {
|
||||
this.itemObject = itemObject;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,16 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||
|
||||
/**
|
||||
* Extract the "followings" from a user in SoundCloud.
|
||||
*/
|
||||
public class SoundcloudSubscriptionExtractor extends SubscriptionExtractor {
|
||||
|
||||
public SoundcloudSubscriptionExtractor(SoundcloudService service) {
|
||||
public SoundcloudSubscriptionExtractor(final SoundcloudService service) {
|
||||
super(service, Collections.singletonList(ContentSource.CHANNEL_URL));
|
||||
}
|
||||
|
||||
|
@ -28,20 +32,21 @@ public class SoundcloudSubscriptionExtractor extends SubscriptionExtractor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<SubscriptionItem> fromChannelUrl(String channelUrl) throws IOException, ExtractionException {
|
||||
if (channelUrl == null) throw new InvalidSourceException("channel url is null");
|
||||
public List<SubscriptionItem> fromChannelUrl(final String channelUrl) throws IOException,
|
||||
ExtractionException {
|
||||
if (channelUrl == null) throw new InvalidSourceException("Channel url is null");
|
||||
|
||||
String id;
|
||||
final String id;
|
||||
try {
|
||||
id = service.getChannelLHFactory().fromUrl(getUrlFrom(channelUrl)).getId();
|
||||
} catch (ExtractionException e) {
|
||||
} catch (final ExtractionException e) {
|
||||
throw new InvalidSourceException(e);
|
||||
}
|
||||
|
||||
String apiUrl = "https://api-v2.soundcloud.com/users/" + id + "/followings"
|
||||
+ "?client_id=" + SoundcloudParsingHelper.clientId()
|
||||
+ "&limit=200";
|
||||
ChannelInfoItemsCollector collector = new ChannelInfoItemsCollector(service.getServiceId());
|
||||
final String apiUrl = SOUNDCLOUD_API_V2_URL + "users/" + id + "/followings" + "?client_id="
|
||||
+ SoundcloudParsingHelper.clientId() + "&limit=200";
|
||||
final ChannelInfoItemsCollector collector = new ChannelInfoItemsCollector(service
|
||||
.getServiceId());
|
||||
// ± 2000 is the limit of followings on SoundCloud, so this minimum should be enough
|
||||
SoundcloudParsingHelper.getUsersFromApiMinItems(2500, collector, apiUrl);
|
||||
|
||||
|
@ -49,13 +54,13 @@ public class SoundcloudSubscriptionExtractor extends SubscriptionExtractor {
|
|||
}
|
||||
|
||||
private String getUrlFrom(String channelUrl) {
|
||||
channelUrl = channelUrl.replace("http://", "https://").trim();
|
||||
channelUrl = replaceHttpWithHttps(channelUrl);
|
||||
|
||||
if (!channelUrl.startsWith("https://")) {
|
||||
if (!channelUrl.startsWith(HTTPS)) {
|
||||
if (!channelUrl.contains("soundcloud.com/")) {
|
||||
channelUrl = "https://soundcloud.com/" + channelUrl;
|
||||
} else {
|
||||
channelUrl = "https://" + channelUrl;
|
||||
channelUrl = HTTPS + channelUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,9 +71,9 @@ public class SoundcloudSubscriptionExtractor extends SubscriptionExtractor {
|
|||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private List<SubscriptionItem> toSubscriptionItems(List<ChannelInfoItem> items) {
|
||||
List<SubscriptionItem> result = new ArrayList<>(items.size());
|
||||
for (ChannelInfoItem item : items) {
|
||||
private List<SubscriptionItem> toSubscriptionItems(final List<ChannelInfoItem> items) {
|
||||
final List<SubscriptionItem> result = new ArrayList<>(items.size());
|
||||
for (final ChannelInfoItem item : items) {
|
||||
result.add(new SubscriptionItem(item.getServiceId(), item.getUrl(), item.getName()));
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -17,34 +17,34 @@ import java.net.URLEncoder;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.UTF_8;
|
||||
|
||||
public class SoundcloudSuggestionExtractor extends SuggestionExtractor {
|
||||
|
||||
public SoundcloudSuggestionExtractor(StreamingService service) {
|
||||
public SoundcloudSuggestionExtractor(final StreamingService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggestionList(String query) throws IOException, ExtractionException {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
public List<String> suggestionList(final String query) throws IOException,
|
||||
ExtractionException {
|
||||
final List<String> suggestions = new ArrayList<>();
|
||||
final Downloader dl = NewPipe.getDownloader();
|
||||
final String url = SOUNDCLOUD_API_V2_URL + "search/queries" + "?q="
|
||||
+ URLEncoder.encode(query, UTF_8) + "&client_id="
|
||||
+ SoundcloudParsingHelper.clientId() + "&limit=10";
|
||||
final String response = dl.get(url, getExtractorLocalization()).responseBody();
|
||||
|
||||
Downloader dl = NewPipe.getDownloader();
|
||||
|
||||
String url = "https://api-v2.soundcloud.com/search/queries"
|
||||
+ "?q=" + URLEncoder.encode(query, UTF_8)
|
||||
+ "&client_id=" + SoundcloudParsingHelper.clientId()
|
||||
+ "&limit=10";
|
||||
|
||||
String response = dl.get(url, getExtractorLocalization()).responseBody();
|
||||
try {
|
||||
JsonArray collection = JsonParser.object().from(response).getArray("collection");
|
||||
for (Object suggestion : collection) {
|
||||
if (suggestion instanceof JsonObject) suggestions.add(((JsonObject) suggestion).getString("query"));
|
||||
final JsonArray collection = JsonParser.object().from(response).getArray("collection");
|
||||
for (final Object suggestion : collection) {
|
||||
if (suggestion instanceof JsonObject) suggestions.add(((JsonObject) suggestion)
|
||||
.getString("query"));
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
} catch (JsonParserException e) {
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse json response", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
|||
import java.util.List;
|
||||
|
||||
public class SoundcloudChannelLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
private static final SoundcloudChannelLinkHandlerFactory instance = new SoundcloudChannelLinkHandlerFactory();
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/[0-9a-z_-]+" +
|
||||
"(/((tracks|albums|sets|reposts|followers|following)/?)?)?([#?].*)?$";
|
||||
private static final SoundcloudChannelLinkHandlerFactory instance =
|
||||
new SoundcloudChannelLinkHandlerFactory();
|
||||
private static final String URL_PATTERN ="^https?://(www\\.|m\\.)?soundcloud.com/[0-9a-z_-]+"
|
||||
+ "(/((tracks|albums|sets|reposts|followers|following)/?)?)?([#?].*)?$";
|
||||
|
||||
public static SoundcloudChannelLinkHandlerFactory getInstance() {
|
||||
return instance;
|
||||
|
@ -19,21 +20,24 @@ public class SoundcloudChannelLinkHandlerFactory extends ListLinkHandlerFactory
|
|||
|
||||
|
||||
@Override
|
||||
public String getId(String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException {
|
||||
Utils.checkUrl(URL_PATTERN, url);
|
||||
|
||||
try {
|
||||
return SoundcloudParsingHelper.resolveIdWithWidgetApi(url);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(String id, List<String> contentFilter, String sortFilter) throws ParsingException {
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
try {
|
||||
return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer("https://api.soundcloud.com/users/" + id);
|
||||
} catch (Exception e) {
|
||||
return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer(
|
||||
"https://api.soundcloud.com/users/" + id);
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,13 @@ import org.schabi.newpipe.extractor.utils.Parser;
|
|||
import java.util.List;
|
||||
|
||||
public class SoundcloudChartsLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
private static final String TOP_URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/charts(/top)?/?([#?].*)?$";
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/charts(/top|/new)?/?([#?].*)?$";
|
||||
|
||||
private static final String TOP_URL_PATTERN =
|
||||
"^https?://(www\\.|m\\.)?soundcloud.com/charts(/top)?/?([#?].*)?$";
|
||||
private static final String URL_PATTERN =
|
||||
"^https?://(www\\.|m\\.)?soundcloud.com/charts(/top|/new)?/?([#?].*)?$";
|
||||
|
||||
@Override
|
||||
public String getId(String url) {
|
||||
public String getId(final String url) {
|
||||
if (Parser.isMatch(TOP_URL_PATTERN, url.toLowerCase())) {
|
||||
return "Top 50";
|
||||
} else {
|
||||
|
@ -20,7 +21,9 @@ public class SoundcloudChartsLinkHandlerFactory extends ListLinkHandlerFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(String id, List<String> contentFilter, String sortFilter) {
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) {
|
||||
if (id.equals("Top 50")) {
|
||||
return "https://soundcloud.com/charts/top";
|
||||
} else {
|
||||
|
|
|
@ -11,36 +11,40 @@ import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsing
|
|||
|
||||
public class SoundcloudCommentsLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
|
||||
private static final SoundcloudCommentsLinkHandlerFactory instance = new SoundcloudCommentsLinkHandlerFactory();
|
||||
private static final SoundcloudCommentsLinkHandlerFactory instance =
|
||||
new SoundcloudCommentsLinkHandlerFactory();
|
||||
|
||||
public static SoundcloudCommentsLinkHandlerFactory getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(String id, List<String> contentFilter, String sortFilter) throws ParsingException {
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
try {
|
||||
return "https://api-v2.soundcloud.com/tracks/" + id + "/comments" + "?client_id=" + clientId() +
|
||||
"&threaded=0" + "&filter_replies=1"; // anything but 1 = sort by new
|
||||
return "https://api-v2.soundcloud.com/tracks/" + id + "/comments" + "?client_id="
|
||||
+ clientId() + "&threaded=0" + "&filter_replies=1";
|
||||
// Anything but 1 = sort by new
|
||||
// + "&limit=NUMBER_OF_ITEMS_PER_REQUEST". We let the API control (default = 10)
|
||||
// + "&offset=OFFSET". We let the API control (default = 0, then we use nextPageUrl)
|
||||
} catch (ExtractionException | IOException e) {
|
||||
} catch (final ExtractionException | IOException e) {
|
||||
throw new ParsingException("Could not get comments");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(String url) throws ParsingException {
|
||||
// delagation to avoid duplicate code, as we need the same id
|
||||
public String getId(final String url) throws ParsingException {
|
||||
// Delegation to avoid duplicate code, as we need the same id
|
||||
return SoundcloudStreamLinkHandlerFactory.getInstance().getId(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onAcceptUrl(String url) {
|
||||
public boolean onAcceptUrl(final String url) {
|
||||
try {
|
||||
getId(url);
|
||||
return true;
|
||||
} catch (ParsingException e) {
|
||||
} catch (final ParsingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,30 +9,36 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
|||
import java.util.List;
|
||||
|
||||
public class SoundcloudPlaylistLinkHandlerFactory extends ListLinkHandlerFactory {
|
||||
private static final SoundcloudPlaylistLinkHandlerFactory instance = new SoundcloudPlaylistLinkHandlerFactory();
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/[0-9a-z_-]+" +
|
||||
"/sets/[0-9a-z_-]+/?([#?].*)?$";
|
||||
private static final SoundcloudPlaylistLinkHandlerFactory instance =
|
||||
new SoundcloudPlaylistLinkHandlerFactory();
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/[0-9a-z_-]+"
|
||||
+ "/sets/[0-9a-z_-]+/?([#?].*)?$";
|
||||
|
||||
public static SoundcloudPlaylistLinkHandlerFactory getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException {
|
||||
Utils.checkUrl(URL_PATTERN, url);
|
||||
|
||||
try {
|
||||
return SoundcloudParsingHelper.resolveIdWithWidgetApi(url);
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get id of url: " + url + " " + e.getMessage(), e);
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException("Could not get id of url: " + url + " " + e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(String id, List<String> contentFilter, String sortFilter) throws ParsingException {
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter)
|
||||
throws ParsingException {
|
||||
try {
|
||||
return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer("https://api.soundcloud.com/playlists/" + id);
|
||||
} catch (Exception e) {
|
||||
return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer(
|
||||
"https://api.soundcloud.com/playlists/" + id);
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper.SOUNDCLOUD_API_V2_URL;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.UTF_8;
|
||||
|
||||
public class SoundcloudSearchQueryHandlerFactory extends SearchQueryHandlerFactory {
|
||||
|
@ -23,11 +24,14 @@ public class SoundcloudSearchQueryHandlerFactory extends SearchQueryHandlerFacto
|
|||
public static final int ITEMS_PER_PAGE = 10;
|
||||
|
||||
@Override
|
||||
public String getUrl(String id, List<String> contentFilter, String sortFilter) throws ParsingException {
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter)
|
||||
throws ParsingException {
|
||||
try {
|
||||
String url = "https://api-v2.soundcloud.com/search";
|
||||
String url = SOUNDCLOUD_API_V2_URL + "search";
|
||||
|
||||
if (contentFilter.size() > 0) {
|
||||
if (!contentFilter.isEmpty()) {
|
||||
switch (contentFilter.get(0)) {
|
||||
case TRACKS:
|
||||
url += "/tracks";
|
||||
|
@ -44,16 +48,15 @@ public class SoundcloudSearchQueryHandlerFactory extends SearchQueryHandlerFacto
|
|||
}
|
||||
}
|
||||
|
||||
return url + "?q=" + URLEncoder.encode(id, UTF_8)
|
||||
+ "&client_id=" + SoundcloudParsingHelper.clientId()
|
||||
+ "&limit=" + ITEMS_PER_PAGE
|
||||
return url + "?q=" + URLEncoder.encode(id, UTF_8) + "&client_id="
|
||||
+ SoundcloudParsingHelper.clientId() + "&limit=" + ITEMS_PER_PAGE
|
||||
+ "&offset=0";
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
throw new ParsingException("Could not encode query", e);
|
||||
} catch (ReCaptchaException e) {
|
||||
} catch (final ReCaptchaException e) {
|
||||
throw new ParsingException("ReCaptcha required", e);
|
||||
} catch (IOException | ExtractionException e) {
|
||||
} catch (final IOException | ExtractionException e) {
|
||||
throw new ParsingException("Could not get client id", e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,10 @@ import org.schabi.newpipe.extractor.utils.Parser;
|
|||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
public class SoundcloudStreamLinkHandlerFactory extends LinkHandlerFactory {
|
||||
private static final SoundcloudStreamLinkHandlerFactory instance = new SoundcloudStreamLinkHandlerFactory();
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/[0-9a-z_-]+" +
|
||||
"/(?!(tracks|albums|sets|reposts|followers|following)/?$)[0-9a-z_-]+/?([#?].*)?$";
|
||||
private static final SoundcloudStreamLinkHandlerFactory instance =
|
||||
new SoundcloudStreamLinkHandlerFactory();
|
||||
private static final String URL_PATTERN = "^https?://(www\\.|m\\.)?soundcloud.com/[0-9a-z_-]+"
|
||||
+ "/(?!(tracks|albums|sets|reposts|followers|following)/?$)[0-9a-z_-]+/?([#?].*)?$";
|
||||
|
||||
private SoundcloudStreamLinkHandlerFactory() {
|
||||
}
|
||||
|
@ -19,21 +20,22 @@ public class SoundcloudStreamLinkHandlerFactory extends LinkHandlerFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(String id) throws ParsingException {
|
||||
public String getUrl(final String id) throws ParsingException {
|
||||
try {
|
||||
return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer("https://api.soundcloud.com/tracks/" + id);
|
||||
} catch (Exception e) {
|
||||
return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer(
|
||||
"https://api.soundcloud.com/tracks/" + id);
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId(String url) throws ParsingException {
|
||||
public String getId(final String url) throws ParsingException {
|
||||
Utils.checkUrl(URL_PATTERN, url);
|
||||
|
||||
try {
|
||||
return SoundcloudParsingHelper.resolveIdWithWidgetApi(url);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new ParsingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -978,7 +978,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
private static String getVideoInfoUrl(final String id, final String sts) {
|
||||
// TODO: Try parsing embedded_player_response first
|
||||
return "https://www.youtube.com/get_video_info?" + "video_id=" + id +
|
||||
"&eurl=https://youtube.googleapis.com/v/" + id +
|
||||
"&html5=1&eurl=https://youtube.googleapis.com/v/" + id +
|
||||
"&sts=" + sts + "&ps=default&gl=US&hl=en";
|
||||
}
|
||||
|
||||
|
|
|
@ -15,10 +15,11 @@ public class Utils {
|
|||
public static final String HTTPS = "https://";
|
||||
public static final String UTF_8 = "UTF-8";
|
||||
public static final String EMPTY_STRING = "";
|
||||
private static final Pattern M_PATTERN = Pattern.compile("(https?)?:\\/\\/m\\.");
|
||||
private static final Pattern WWW_PATTERN = Pattern.compile("(https?)?:\\/\\/www\\.");
|
||||
|
||||
private Utils() {
|
||||
//no instance
|
||||
// no instance
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,7 +31,7 @@ public class Utils {
|
|||
* @param toRemove string to remove non-digit chars
|
||||
* @return a string that contains only digits
|
||||
*/
|
||||
public static String removeNonDigitCharacters(String toRemove) {
|
||||
public static String removeNonDigitCharacters(final String toRemove) {
|
||||
return toRemove.replaceAll("\\D+", "");
|
||||
}
|
||||
|
||||
|
@ -48,7 +49,8 @@ public class Utils {
|
|||
* @throws NumberFormatException
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public static long mixedNumberWordToLong(String numberWord) throws NumberFormatException, ParsingException {
|
||||
public static long mixedNumberWordToLong(final String numberWord) throws NumberFormatException,
|
||||
ParsingException {
|
||||
String multiplier = "";
|
||||
try {
|
||||
multiplier = Parser.matchGroup("[\\d]+([\\.,][\\d]+)?([KMBkmb])+", numberWord, 2);
|
||||
|
@ -74,7 +76,7 @@ public class Utils {
|
|||
* @param pattern the pattern that will be used to check the url
|
||||
* @param url the url to be tested
|
||||
*/
|
||||
public static void checkUrl(String pattern, String url) throws ParsingException {
|
||||
public static void checkUrl(final String pattern, final String url) throws ParsingException {
|
||||
if (isNullOrEmpty(url)) {
|
||||
throw new IllegalArgumentException("Url can't be null or empty");
|
||||
}
|
||||
|
@ -101,14 +103,14 @@ public class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* get the value of a URL-query by name.
|
||||
* if a url-query is give multiple times, only the value of the first query is returned
|
||||
* Get the value of a URL-query by name.
|
||||
* If a url-query is give multiple times, only the value of the first query is returned
|
||||
*
|
||||
* @param url the url to be used
|
||||
* @param parameterName the pattern that will be used to check the url
|
||||
* @return a string that contains the value of the query parameter or null if nothing was found
|
||||
*/
|
||||
public static String getQueryValue(URL url, String parameterName) {
|
||||
public static String getQueryValue(final URL url, final String parameterName) {
|
||||
String urlQuery = url.getQuery();
|
||||
|
||||
if (urlQuery != null) {
|
||||
|
@ -118,8 +120,9 @@ public class Utils {
|
|||
String query;
|
||||
try {
|
||||
query = URLDecoder.decode(params[0], UTF_8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
System.err.println("Cannot decode string with UTF-8. using the string without decoding");
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
System.err.println(
|
||||
"Cannot decode string with UTF-8. using the string without decoding");
|
||||
e.printStackTrace();
|
||||
query = params[0];
|
||||
}
|
||||
|
@ -127,8 +130,9 @@ public class Utils {
|
|||
if (query.equals(parameterName)) {
|
||||
try {
|
||||
return URLDecoder.decode(params[1], UTF_8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
System.err.println("Cannot decode string with UTF-8. using the string without decoding");
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
System.err.println(
|
||||
"Cannot decode string with UTF-8. using the string without decoding");
|
||||
e.printStackTrace();
|
||||
return params[1];
|
||||
}
|
||||
|
@ -146,7 +150,7 @@ public class Utils {
|
|||
* @param url the string to be converted to a URL-Object
|
||||
* @return a URL-Object containing the url
|
||||
*/
|
||||
public static URL stringToURL(String url) throws MalformedURLException {
|
||||
public static URL stringToURL(final String url) throws MalformedURLException {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (MalformedURLException e) {
|
||||
|
@ -159,7 +163,7 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isHTTP(URL url) {
|
||||
public static boolean isHTTP(final URL url) {
|
||||
// make sure its http or https
|
||||
String protocol = url.getProtocol();
|
||||
if (!protocol.equals("http") && !protocol.equals("https")) {
|
||||
|
@ -172,7 +176,10 @@ public class Utils {
|
|||
return setsNoPort || usesDefaultPort;
|
||||
}
|
||||
|
||||
public static String removeWWWFromUrl(String url) {
|
||||
public static String removeMAndWWWFromUrl(final String url) {
|
||||
if (M_PATTERN.matcher(url).find()) {
|
||||
return url.replace("m.", "");
|
||||
}
|
||||
if (WWW_PATTERN.matcher(url).find()) {
|
||||
return url.replace("www.", "");
|
||||
}
|
||||
|
@ -216,7 +223,8 @@ public class Utils {
|
|||
try {
|
||||
final URL decoded = Utils.stringToURL(url);
|
||||
if (decoded.getHost().contains("google") && decoded.getPath().equals("/url")) {
|
||||
return URLDecoder.decode(Parser.matchGroup1("&url=([^&]+)(?:&|$)", url), UTF_8);
|
||||
return URLDecoder.decode(Parser.matchGroup1("&url=([^&]+)(?:&|$)", url),
|
||||
UTF_8);
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
|
@ -258,7 +266,8 @@ public class Utils {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static String join(final CharSequence delimiter, final Iterable<? extends CharSequence> elements) {
|
||||
public static String join(final CharSequence delimiter,
|
||||
final Iterable<? extends CharSequence> elements) {
|
||||
final StringBuilder stringBuilder = new StringBuilder();
|
||||
final Iterator<? extends CharSequence> iterator = elements.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
|
@ -283,7 +292,8 @@ public class Utils {
|
|||
/**
|
||||
* Concatenate all non-null, non-empty and strings which are not equal to <code>"null"</code>.
|
||||
*/
|
||||
public static String nonEmptyAndNullJoin(final CharSequence delimiter, final String[] elements) {
|
||||
public static String nonEmptyAndNullJoin(final CharSequence delimiter,
|
||||
final String[] elements) {
|
||||
final List<String> list = new java.util.ArrayList<>(Arrays.asList(elements));
|
||||
list.removeIf(s -> isNullOrEmpty(s) || s.equals("null"));
|
||||
return join(delimiter, list);
|
||||
|
|
|
@ -22,10 +22,13 @@ import javax.annotation.Nullable;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.schabi.newpipe.extractor.ServiceList.PeerTube;
|
||||
|
||||
public class PeertubeStreamExtractorTest {
|
||||
public abstract class PeertubeStreamExtractorTest extends DefaultStreamExtractorTest {
|
||||
private static final String BASE_URL = "/videos/watch/";
|
||||
|
||||
public static class WhatIsPeertube extends DefaultStreamExtractorTest {
|
||||
@Override public boolean expectedHasAudioStreams() { return false; }
|
||||
@Override public boolean expectedHasFrames() { return false; }
|
||||
|
||||
public static class WhatIsPeertube extends PeertubeStreamExtractorTest {
|
||||
private static final String ID = "9c9de5e8-0a1e-484a-b099-e80766180a6d";
|
||||
private static final String INSTANCE = "https://framatube.org";
|
||||
private static final int TIMESTAMP_MINUTE = 1;
|
||||
|
@ -85,18 +88,60 @@ public class PeertubeStreamExtractorTest {
|
|||
@Nullable @Override public String expectedTextualUploadDate() { return "2018-10-01T10:52:46.396Z"; }
|
||||
@Override public long expectedLikeCountAtLeast() { return 120; }
|
||||
@Override public long expectedDislikeCountAtLeast() { return 0; }
|
||||
@Override public boolean expectedHasAudioStreams() { return false; }
|
||||
@Override public boolean expectedHasFrames() { return false; }
|
||||
@Override public String expectedHost() { return "framatube.org"; }
|
||||
@Override public String expectedCategory() { return "Science & Technology"; }
|
||||
@Override public String expectedLicence() { return "Attribution - Share Alike"; }
|
||||
@Override public Locale expectedLanguageInfo() { return Locale.forLanguageTag("en"); }
|
||||
@Override public List<String> expectedTags() { return Arrays.asList("framasoft", "peertube"); }
|
||||
@Override public int expectedStreamSegmentsCount() { return 0; }
|
||||
}
|
||||
|
||||
public static class HlsOnlyStreams extends PeertubeStreamExtractorTest {
|
||||
private static final String ID = "41342cb4-6fa8-402d-a116-1f63a7f438a3";
|
||||
private static final String INSTANCE = "https://tilvids.com";
|
||||
|
||||
private static final String URL = INSTANCE + BASE_URL + ID;
|
||||
private static StreamExtractor extractor;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws Exception {
|
||||
NewPipe.init(DownloaderTestImpl.getInstance());
|
||||
// setting instance might break test when running in parallel (!)
|
||||
PeerTube.setInstance(new PeertubeInstance(INSTANCE, "TILvids"));
|
||||
extractor = PeerTube.getStreamExtractor(URL);
|
||||
extractor.fetchPage();
|
||||
}
|
||||
|
||||
@Override public StreamExtractor extractor() { return extractor; }
|
||||
@Override public StreamingService expectedService() { return PeerTube; }
|
||||
@Override public String expectedName() { return "A Goodbye to Flash Games"; }
|
||||
@Override public String expectedId() { return ID; }
|
||||
@Override public String expectedUrlContains() { return INSTANCE + BASE_URL + ID; }
|
||||
@Override public String expectedOriginalUrlContains() { return URL; }
|
||||
|
||||
@Override public StreamType expectedStreamType() { return StreamType.VIDEO_STREAM; }
|
||||
@Override public String expectedUploaderName() { return "Marinauts"; }
|
||||
@Override public String expectedUploaderUrl() { return "https://tilvids.com/accounts/marinauts@tilvids.com"; }
|
||||
@Override public String expectedSubChannelName() { return "Main marinauts channel"; }
|
||||
@Override public String expectedSubChannelUrl() { return "https://tilvids.com/video-channels/marinauts_channel"; }
|
||||
@Override public List<String> expectedDescriptionContains() { // CRLF line ending
|
||||
return Arrays.asList("Goodbye", "Flash Games", "Anthony takes a minute", "Songs used:");
|
||||
}
|
||||
@Override public long expectedLength() { return 362; }
|
||||
@Override public long expectedViewCountAtLeast() { return 20; }
|
||||
@Nullable @Override public String expectedUploadDate() { return "2021-04-08 20:15:32.434"; }
|
||||
@Nullable @Override public String expectedTextualUploadDate() { return "2021-04-08T20:15:32.434Z"; }
|
||||
@Override public long expectedLikeCountAtLeast() { return 6; }
|
||||
@Override public long expectedDislikeCountAtLeast() { return 0; }
|
||||
@Override public boolean expectedHasSubtitles() { return false; }
|
||||
@Override public String expectedHost() { return "tilvids.com"; }
|
||||
@Override public String expectedCategory() { return "Entertainment"; }
|
||||
@Override public String expectedLicence() { return "Unknown"; }
|
||||
@Override public Locale expectedLanguageInfo() { return null; }
|
||||
@Override public List<String> expectedTags() { return Arrays.asList("Marinauts", "adobe flash", "adobe flash player", "flash games", "the marinauts"); }
|
||||
}
|
||||
|
||||
@Ignore("Test broken, SSL problem")
|
||||
public static class AgeRestricted extends DefaultStreamExtractorTest {
|
||||
public static class AgeRestricted extends PeertubeStreamExtractorTest {
|
||||
private static final String ID = "dbd8e5e1-c527-49b6-b70c-89101dbb9c08";
|
||||
private static final String INSTANCE = "https://nocensoring.net";
|
||||
private static final String URL = INSTANCE + "/videos/embed/" + ID;
|
||||
|
@ -134,9 +179,6 @@ public class PeertubeStreamExtractorTest {
|
|||
@Override public long expectedLikeCountAtLeast() { return 1; }
|
||||
@Override public long expectedDislikeCountAtLeast() { return 0; }
|
||||
@Override public int expectedAgeLimit() { return 18; }
|
||||
@Override public boolean expectedHasAudioStreams() { return false; }
|
||||
@Override public boolean expectedHasSubtitles() { return false; }
|
||||
@Override public boolean expectedHasFrames() { return false; }
|
||||
@Override public String expectedHost() { return "nocensoring.net"; }
|
||||
@Override public String expectedCategory() { return "Art"; }
|
||||
@Override public String expectedLicence() { return "Attribution"; }
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -38,7 +38,7 @@
|
|||
"application/json; charset\u003dutf-8"
|
||||
],
|
||||
"date": [
|
||||
"Sat, 10 Apr 2021 09:30:27 GMT"
|
||||
"Sat, 22 May 2021 18:30:38 GMT"
|
||||
],
|
||||
"expires": [
|
||||
"Mon, 01 Jan 1990 00:00:00 GMT"
|
||||
|
@ -46,6 +46,9 @@
|
|||
"p3p": [
|
||||
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
|
||||
],
|
||||
"permissions-policy": [
|
||||
"ch-ua-full-version\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*, ch-ua-arch\u003d*, ch-ua-model\u003d*"
|
||||
],
|
||||
"pragma": [
|
||||
"no-cache"
|
||||
],
|
||||
|
@ -53,8 +56,8 @@
|
|||
"ESF"
|
||||
],
|
||||
"set-cookie": [
|
||||
"YSC\u003dace_-8ga0IY; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
|
||||
"CONSENT\u003dPENDING+058; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
|
||||
"YSC\u003dbWHlSChZfJE; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
|
||||
"CONSENT\u003dPENDING+805; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
|
||||
],
|
||||
"strict-transport-security": [
|
||||
"max-age\u003d31536000"
|
||||
|
@ -72,7 +75,7 @@
|
|||
"0"
|
||||
]
|
||||
},
|
||||
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r7---sn-uxax4vopj5qx-q0nl.googlevideo.com\\/generate_204\",\"https:\\/\\/r7---sn-uxax4vopj5qx-q0nl.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\":\"23968386,23974595,23993357,24005969,24022876,24011119,24005646,24013830,23858057,23890959,9453586,24021968,23918597,23934970,24022616,23744176,23986016,24002010,23995928,23744530,24007246,24006570,23976696,23938215,23983296,24001373,23804281,23946420,23986713,24006795,9407156,24016478,23882502,23966208,24022308,23970529,1714252,24020359,23884386,23984130,24023962,23857949,24008565,23891346,23969934,24014268,9453587,24012117,23990877,23940237,24022633,23944779,23987676,24022914,24024647,23891344\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0x87c76fb9f63c64f8\"}]},{\"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\":\"CAAQu2kiEwii9aXyrvPvAhXKNOAKHYIDAFE\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgstRUlYMFpIOXhXOCiz4MWDBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbGczR24zVzJmNVltdFlzZXZsVm05TndaRVMzUXxBQ3Jtc0trWjRSYmwwNnZ0QV9VTmNvcHNENnhJekxOYlQ3R05hRGdnZTVzYVRoM1VmakNEczlJd3lKaW96VTdQbGRZQXdaaUFWVllxWW9Eb3lYdkdkOWtsVFZwRU5mRDBYMTlSb211aFV5QktPUEhLSjNBbFhiYw\\u003d\\u003d\",\"url\": \"/watch?v\\u003dINVALID_ID_\",\"endpoint\": {\"clickTrackingParams\":\"IhMI-6Kl8q7z7wIVhKh7Ch1S9gZuMghleHRlcm5hbA\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-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\":\"24037794,24040786,24044575,23884386,23944779,23983296,23882685,24014268,24027649,23995927,23735347,24043867,24032296,24011649,23975059,23891344,23968099,23970529,23966208,24005207,24042868,24005646,24019954,23986021,23940237,23891346,24037806,24003105,23996830,1714251,24005802,23804281,24030040,23946420,23744176,24035514,23890959,23918597,24007246,23974595,24045350,23934970,24001373,24036086,23968386,23997375,24042653,24027133,23857949,24004644,24035275,24024949,24036899,24042802\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0xd8a06d19cc578a00\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210324\"},{\"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\":\"CAAQu2kiEwi_h9z79d3wAhXw1xEIHUqjCfI\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgswTjM5MHFXN0hlayjOm6WFBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbmQyWVlOb0RMYzZ2YXVzdmhjWDZpRjdLQUJMQXxBQ3Jtc0trS2FPZGRQUzJZNFhoTU5EcnZjQjZFV0dMYnE0ZkNxYUNkeVlLd3N4MU9fRGpLYjRzdkliTVE4N3prNVByTVl2Mi04aFpZcHdvckhGSDI2U1Ewa24tbWFHNjNMa1pBb2lET0JSQW0xTUY4WklUOEtTcw\\u003d\\u003d\",\"url\": \"/watch?v\\u003dINVALID_ID_\",\"endpoint\": {\"clickTrackingParams\":\"IhMIobfb-_Xd8AIVzWjgCh0aDgYWMghleHRlcm5hbA\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"
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@
|
|||
"application/json; charset\u003dutf-8"
|
||||
],
|
||||
"date": [
|
||||
"Sat, 10 Apr 2021 09:30:28 GMT"
|
||||
"Sat, 22 May 2021 18:30:40 GMT"
|
||||
],
|
||||
"expires": [
|
||||
"Mon, 01 Jan 1990 00:00:00 GMT"
|
||||
|
@ -46,6 +46,9 @@
|
|||
"p3p": [
|
||||
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
|
||||
],
|
||||
"permissions-policy": [
|
||||
"ch-ua-full-version\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*, ch-ua-arch\u003d*, ch-ua-model\u003d*"
|
||||
],
|
||||
"pragma": [
|
||||
"no-cache"
|
||||
],
|
||||
|
@ -53,8 +56,8 @@
|
|||
"ESF"
|
||||
],
|
||||
"set-cookie": [
|
||||
"YSC\u003dRjtYgUall5M; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
|
||||
"CONSENT\u003dPENDING+320; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
|
||||
"YSC\u003dnvQcdNQproI; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
|
||||
"CONSENT\u003dPENDING+932; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
|
||||
],
|
||||
"strict-transport-security": [
|
||||
"max-age\u003d31536000"
|
||||
|
@ -72,7 +75,7 @@
|
|||
"0"
|
||||
]
|
||||
},
|
||||
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r8---sn-uxax4vopj5qx-q0nl.googlevideo.com\\/generate_204\",\"https:\\/\\/r8---sn-uxax4vopj5qx-q0nl.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\":\"23966208,24019382,23891346,23970529,24005646,23969934,24007151,23934970,1714256,24011119,23987364,24007246,24015458,23845000,23990877,24021967,24016478,23804281,23884386,23946420,24001373,23987676,24006795,23891344,23968386,24014268,24022617,24022308,23857949,24023962,24012117,24013778,23974595,23890959,24010465,23744176,24022875,23735347,23976696,23987907,23918597,24008425,24024426,23882503,24022633,23983296,23944779,24022914,23986030,24023202\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0x6feaa0c336e1250e\"}]},{\"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\":\"CAEQ8FsiEwj-043zrvPvAhXEMuAKHRaSDWQ\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\":\"CAEQ8FsiEwj-043zrvPvAhXEMuAKHRaSDWQ\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003d8VajtrESJzA\",\"webPageType\":\"WEB_PAGE_TYPE_UNKNOWN\",\"rootVe\":83769}},\"urlEndpoint\":{\"url\":\"/watch?v\u003d8VajtrESJzA\"}}}},\"trackingParams\":\"CAEQ8FsiEwj-043zrvPvAhXEMuAKHRaSDWQ\u003d\"}},\"thumbnail\":{\"thumbnails\":[{\"url\":\"//s.ytimg.com/yts/img/meh7-vflGevej7.png\",\"width\":140,\"height\":100}]},\"icon\":{\"iconType\":\"ERROR_OUTLINE\"}}},\"contextParams\":\"Q0FBU0FnZ0E\u003d\"},\"trackingParams\":\"CAAQu2kiEwj-043zrvPvAhXEMuAKHRaSDWQ\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgtqMTVZNXNhMEFqQSi04MWDBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbjBlVWk1T3BHRTc2T2NpbXptQzltMElISVN6QXxBQ3Jtc0ttSjB3ZXp4bGZkLVdXSUVOOE00cDZWWFh1UmowNmdEU0FNVDNMcUQweWpnckRmRWE0V0RmVTBHMFNmcXJHN2V0bzJTdlM4WmFpV202Tm9sdzdHQ29oNERta3RjZ2lQR3JzWTBIclRPMnc2bGI3Y3haTQ\\u003d\\u003d\",\"url\": \"/watch?v\\u003d8VajtrESJzA\",\"endpoint\": {\"clickTrackingParams\":\"IhMIjvuM867z7wIVi4h7Ch0LRgXcMghleHRlcm5hbA\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",
|
||||
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r3---sn-4g5ednsy.googlevideo.com\\/generate_204\",\"https:\\/\\/r3---sn-4g5ednsy.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\":\"24007246,23991737,23997375,23877022,23804281,23946420,23744176,23930656,23890959,23974595,24030040,23986029,23934970,23884386,23885487,23968100,23857949,23882502,24043868,24590263,23968386,24043529,24037791,23918597,24001373,24042802,24027649,24004644,23975058,24044575,24027133,24040786,24014268,24037794,1714258,24035275,23944779,23983296,23996830,23966208,24042868,23858057,24005646,23891346,24011649,24037806,23970529,24017220,24005802,24046871,23995927,23891344,24019954,24590455\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0x45da485bb856afc1\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210324\"},{\"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\":\"CAEQ8FsiEwiJyvP89d3wAhUMlHsKHTNnBlE\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\":\"CAEQ8FsiEwiJyvP89d3wAhUMlHsKHTNnBlE\u003d\",\"commandMetadata\":{\"webCommandMetadata\":{\"url\":\"/watch?v\u003d8VajtrESJzA\",\"webPageType\":\"WEB_PAGE_TYPE_UNKNOWN\",\"rootVe\":83769}},\"urlEndpoint\":{\"url\":\"/watch?v\u003d8VajtrESJzA\"}}}},\"trackingParams\":\"CAEQ8FsiEwiJyvP89d3wAhUMlHsKHTNnBlE\u003d\"}},\"thumbnail\":{\"thumbnails\":[{\"url\":\"//s.ytimg.com/yts/img/meh7-vflGevej7.png\",\"width\":140,\"height\":100}]},\"icon\":{\"iconType\":\"ERROR_OUTLINE\"}}},\"contextParams\":\"Q0FBU0FnZ0E\u003d\"},\"trackingParams\":\"CAAQu2kiEwiJyvP89d3wAhUMlHsKHTNnBlE\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"Cgs2OHBNWkFad3BwMCjQm6WFBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqa0dPdkVzNExyYjdlZS1yQ0NEX2Jvd3ViRk5DZ3xBQ3Jtc0tsVVFfS2hrc0NhemlFNlhyU1pMN3Q5U3NTeElpcmVOODllTVMyWi1nYUplU0V0Tm9rbDlicGFTdTVMekpBU3VkUE1idHp6dU1vY01zN1AtQVNqXzB6aDNOc20tc0pJbWJSVS1sWGxPeW41dkZWejNDMA\\u003d\\u003d\",\"url\": \"/watch?v\\u003d8VajtrESJzA\",\"endpoint\": {\"clickTrackingParams\":\"IhMI8_jy_PXd8AIVl4zeCh37AAMZMghleHRlcm5hbA\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"
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -38,7 +38,7 @@
|
|||
"application/json; charset\u003dutf-8"
|
||||
],
|
||||
"date": [
|
||||
"Sat, 10 Apr 2021 09:30:29 GMT"
|
||||
"Sat, 22 May 2021 18:30:40 GMT"
|
||||
],
|
||||
"expires": [
|
||||
"Mon, 01 Jan 1990 00:00:00 GMT"
|
||||
|
@ -46,6 +46,9 @@
|
|||
"p3p": [
|
||||
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
|
||||
],
|
||||
"permissions-policy": [
|
||||
"ch-ua-full-version\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*, ch-ua-arch\u003d*, ch-ua-model\u003d*"
|
||||
],
|
||||
"pragma": [
|
||||
"no-cache"
|
||||
],
|
||||
|
@ -53,8 +56,8 @@
|
|||
"ESF"
|
||||
],
|
||||
"set-cookie": [
|
||||
"YSC\u003dXRfZ94HTQG8; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
|
||||
"CONSENT\u003dPENDING+158; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com"
|
||||
"YSC\u003dlWKLRR9UNg0; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
|
||||
"CONSENT\u003dPENDING+495; expires\u003dFri, 01-Jan-2038 00:00:00 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
|
||||
],
|
||||
"strict-transport-security": [
|
||||
"max-age\u003d31536000"
|
||||
|
@ -72,7 +75,7 @@
|
|||
"0"
|
||||
]
|
||||
},
|
||||
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r3---sn-uxax4vopj5qx-q0nl.googlevideo.com\\/generate_204\",\"https:\\/\\/r3---sn-uxax4vopj5qx-q0nl.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\":\"24014421,23967549,23882503,24022914,23934970,23744176,24023274,24022814,23890959,23976696,23944779,23983296,23918597,24024352,1714258,24005646,23968386,23857948,24012117,24001373,23940238,24006795,24022875,24010254,23815352,24014268,24023962,24014700,24007246,23974595,24022308,24022617,24002010,23987676,23804281,23946420,24590381,24016478,24000197,23969934,24021967,23996297,23891346,24012812,23966208,23970529,23884386,23986026,24024809,23891344,23984130,24011119,23748146\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0xaa4e26070023b25a\"}]},{\"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\":\"CAAQu2kiEwi88qPzrvPvAhVIQ-AKHbLLBQY\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgtETFJQZnBNLTl5ayi14MWDBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbFh6b3V3TTB0aWFBQlZIcHB0ckhXTTBiSGdCQXxBQ3Jtc0tsQWNkUTAxWVZiUWc1QUR1cWlPd0NWR2pSVHk1X1BfR0lRSFU3eE1nLXFFamljbGdDRmIxOUV4UmpDTFFNQ1FpNnFQbnNJMlYzQmE4YmJjbWlOdkxKSm4zWTVxbUNlOTFrWmFJdGNiakZhdTNfUGdQSQ\\u003d\\u003d\",\"url\": \"/watch?v\\u003ddon-t-exist\",\"endpoint\": {\"clickTrackingParams\":\"IhMImoOj867z7wIVjtsRCB1FiACzMghleHRlcm5hbA\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",
|
||||
"responseBody": "[\r\n{\"page\": \"watch\",\"rootVe\": \"3832\"},\r\n{\"page\": \"watch\",\"preconnect\": [\"https:\\/\\/r3---sn-4g5e6nlk.googlevideo.com\\/generate_204\",\"https:\\/\\/r3---sn-4g5e6nlk.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\":\"24036085,23891344,23804281,23997375,23946420,24001373,24039105,24004644,24042868,23891346,23966208,24030040,9479112,23970529,23974595,24043867,23968099,24037806,23884386,23940238,23882502,24027649,23934970,24037794,23857950,24007246,24022729,23918597,23986023,24035164,24044568,24040786,1714242,24005802,24046663,23944779,24042802,23983296,9406121,24045792,24014268,24005646,24027133,24042860,23968386,23939457,24035275,23996830,24043529,23890959,23984883,24010465,24019954,23744176,24045810,24026152,24036004,24044576,23995927,23990877,23868992\"}]},{\"service\":\"CSI\",\"params\":[{\"key\":\"c\",\"value\":\"WEB\"},{\"key\":\"cver\",\"value\":\"2.20200214.04.00\"},{\"key\":\"yt_li\",\"value\":\"0\"},{\"key\":\"GetPlayer_rid\",\"value\":\"0x899d9973680ec201\"}]},{\"service\":\"GUIDED_HELP\",\"params\":[{\"key\":\"logged_in\",\"value\":\"0\"}]},{\"service\":\"ECATCHER\",\"params\":[{\"key\":\"client.version\",\"value\":\"2.20210324\"},{\"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\":\"CAAQu2kiEwj_zob99d3wAhXBrnsKHS0SBPI\u003d\"}},\r\n{\"page\": \"watch\",\"response\": {\"responseContext\":{\"webResponseContextExtensionData\":{\"ytConfigData\":{\"visitorData\":\"CgtuN1hhZ1RER2c2WSjQm6WFBg%3D%3D\",\"rootVisualElementType\":3832}}}},\"xsrf_token\": \"QUFFLUhqbHE4OV9DcUszaUJyQU5QNnNOSWowczFaMVJad3xBQ3Jtc0ttb1NRdjVEQXdGQ1BhaE5CLUxpNEZMZkRuWnBrU2tHWk92elZ3VVFsTlM4YWYweEFRSDB5SVllSVBfTVZrRzQwOFlDclF1b1hXOVA2TF9UV3A2eDVLRTBtY0VyRktQM0hCdldqT1hvOTJJcnNJZkFIZw\\u003d\\u003d\",\"url\": \"/watch?v\\u003ddon-t-exist\",\"endpoint\": {\"clickTrackingParams\":\"IhMIvfaF_fXd8AIVbcURCB0PPgimMghleHRlcm5hbA\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"
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue