Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
import org.schabi.newpipe.extractor.utils.ExtractorLogger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand All @@ -15,6 +16,8 @@
import java.util.Objects;

public abstract class Extractor {
private final String TAG = getClass().getSimpleName() + "@" + hashCode();

/**
* {@link StreamingService} currently related to this extractor.<br>
* Useful for getting other things from a service (like the url handlers for
Expand Down Expand Up @@ -54,7 +57,9 @@ public LinkHandler getLinkHandler() {
* @throws ExtractionException if the pages content is not understood
*/
public void fetchPage() throws IOException, ExtractionException {
ExtractorLogger.d(TAG, "base fetchPage called");
if (pageFetched) {
ExtractorLogger.d(TAG, "Page already fetched");
return;
}
onFetchPage(downloader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.extractor.utils.ExtractorLogger;

import java.io.Serializable;
import java.util.ArrayList;
Expand All @@ -10,6 +11,7 @@

public abstract class Info implements Serializable {

private static final String TAG = "Info";
private final int serviceId;
/**
* Id of this Info object <br>
Expand Down Expand Up @@ -52,6 +54,7 @@ public Info(final int serviceId,
this.url = url;
this.originalUrl = originalUrl;
this.name = name;
ExtractorLogger.d(TAG, "Base Created " + this);
}

public Info(final int serviceId, final LinkHandler linkHandler, final String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.utils.ExtractorLogger;

import java.util.List;

Expand All @@ -34,6 +35,7 @@
* Provides access to streaming services supported by NewPipe.
*/
public final class NewPipe {
private static final String TAG = NewPipe.class.getSimpleName();
private static Downloader downloader;
private static Localization preferredLocalization;
private static ContentCountry preferredContentCountry;
Expand All @@ -42,15 +44,19 @@ private NewPipe() {
}

public static void init(final Downloader d) {
ExtractorLogger.d(TAG, "Default init called");
init(d, Localization.DEFAULT);
}

public static void init(final Downloader d, final Localization l) {
ExtractorLogger.d(TAG, "Default init called with localization");
init(d, l, l.getCountryCode().isEmpty()
? ContentCountry.DEFAULT : new ContentCountry(l.getCountryCode()));
}

public static void init(final Downloader d, final Localization l, final ContentCountry c) {
ExtractorLogger.d(TAG, "Initializing with downloader: "
+ d.getClass().getSimpleName() + ", " + l + ", " + c);
downloader = d;
preferredLocalization = l;
preferredContentCountry = c;
Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file used anywhere? Also, the reason why services are not implemented as an enum is also because it should be possible to dynamically add service implementations (think e.g. of plugins). Obviously this has never happened yet, but iirc that's why the API is like that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in the NewPipe PR for logging the names of services.

It's possible to have enums and still allow for dynamic implementations. Since plugins are a long way away, I think it makes a lot more sense to have enums for each service to improve readability, traceability, logging and type safety.

Howbeit, I'm not going to change the entire API to use enums because that's long: I just want the nice id -> name static method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you use this utility method instead? https://github.com/TeamNewPipe/NewPipe/blob/2a9c6f05387866bef2c42ab67226c14936917d27/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java#L138

But yeah I agree this is not the best. We should not have the concept of service ID at all except for saving stuff to database, so every StreamInfo should not have any getServiceId() method but rather getService() directly. So we don't have to keep switching back and forth between int and Service

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.schabi.newpipe.extractor;

import java.util.Objects;

public enum StreamingServiceId {
NO_SERVICE_ID,
YOUTUBE,
SOUNDCLOUD,
MEDIACCC,
PEERTUBE,
BANDCAMP;


private static final StreamingServiceId[] VALUES = values();

public static String nameFromId(final int serviceId) {
try {
return VALUES[Objects.checkIndex(serviceId + 1, VALUES.length)].name();
} catch (final IndexOutOfBoundsException e) {
throw new IllegalArgumentException("Invalid serviceId: " + serviceId, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import java.util.List;
import java.util.Map;

import org.schabi.newpipe.extractor.exceptions.HttpResponseException;
import org.schabi.newpipe.extractor.utils.HttpUtils;

/**
* A Data class used to hold the results from requests made by the Downloader implementation.
*/
Expand Down Expand Up @@ -80,4 +83,21 @@ public String getHeader(final String name) {

return null;
}
// CHECKSTYLE:OFF
/**
* Helper function simply to make it easier to validate response code inline
* before getting the code/body/latestUrl/etc.
* Validates the response codes for the given {@link Response}, and throws a {@link HttpResponseException} if the code is invalid
* @see HttpUtils#validateResponseCode(Response, int...)
* @param validResponseCodes Expected valid response codes
* @return {@link this} response
* @throws HttpResponseException Thrown when the response code is not in {@code validResponseCodes},
* or when {@code validResponseCodes} is empty and the code is a 4xx or 5xx error.
*/
// CHECKSTYLE:ON
public Response validateResponseCode(final int... validResponseCodes)
throws HttpResponseException {
HttpUtils.validateResponseCode(this, validResponseCodes);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.schabi.newpipe.extractor.exceptions;

import java.io.IOException;
import org.schabi.newpipe.extractor.downloader.Response;

public class HttpResponseException extends IOException {
public HttpResponseException(final Response response) {
this("Error in HTTP Response for " + response.latestUrl() + "\n\t"
+ response.responseCode() + " - " + response.responseMessage());
}

public HttpResponseException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
import static org.schabi.newpipe.extractor.utils.HttpUtils.validateResponseCode;

import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
import org.schabi.newpipe.extractor.Image;
Expand All @@ -28,6 +28,7 @@
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudLikesInfoItemExtractor;
import org.schabi.newpipe.extractor.services.soundcloud.extractors.SoundcloudStreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
import org.schabi.newpipe.extractor.utils.ExtractorLogger;
import org.schabi.newpipe.extractor.utils.ImageSuffix;
import org.schabi.newpipe.extractor.utils.JsonUtils;
import org.schabi.newpipe.extractor.utils.Parser;
Expand Down Expand Up @@ -86,6 +87,7 @@ public final class SoundcloudParsingHelper {
private static final List<ImageSuffix> VISUALS_IMAGE_SUFFIXES =
List.of(new ImageSuffix("t1240x260", 1240, 260, MEDIUM),
new ImageSuffix("t2480x520", 2480, 520, MEDIUM));
public static final String TAG = SoundcloudParsingHelper.class.getSimpleName();

private static String clientId;
public static final String SOUNDCLOUD_API_V2_URL = "https://api-v2.soundcloud.com/";
Expand All @@ -99,13 +101,14 @@ private SoundcloudParsingHelper() {

public static synchronized String clientId() throws ExtractionException, IOException {
if (!isNullOrEmpty(clientId)) {
ExtractorLogger.d(TAG, "Returning clientId=" + clientId);
return clientId;
}

final Downloader dl = NewPipe.getDownloader();

final Response download = dl.get("https://soundcloud.com");
final String responseBody = download.responseBody();
final Response downloadResponse = dl.get("https://soundcloud.com").validateResponseCode();
final String responseBody = downloadResponse.responseBody();
final String clientIdPattern = ",client_id:\"(.*?)\"";

final Document doc = Jsoup.parse(responseBody);
Expand All @@ -116,12 +119,15 @@ public static synchronized String clientId() throws ExtractionException, IOExcep

final var headers = Map.of("Range", List.of("bytes=0-50000"));

for (final Element element : possibleScripts) {
for (final var element : possibleScripts) {
final String srcUrl = element.attr("src");
if (!isNullOrEmpty(srcUrl)) {
try {
ExtractorLogger.d(TAG, "Searching for clientId in " + srcUrl);
clientId = Parser.matchGroup1(clientIdPattern, dl.get(srcUrl, headers)
.validateResponseCode()
.responseBody());
ExtractorLogger.d(TAG, "Found clientId=" + clientId);
return clientId;
} catch (final RegexException ignored) {
// Ignore it and proceed to try searching other script
Expand All @@ -148,13 +154,16 @@ public static OffsetDateTime parseDateFrom(final String textualUploadDate)
}
}

// CHECKSTYLE:OFF
/**
* Call the endpoint "/resolve" of the API.<p>
* Call the endpoint "/resolve" of the API.
* <p>
* See https://developers.soundcloud.com/docs/api/reference#resolve
* See https://web.archive.org/web/20170804051146/https://developers.soundcloud.com/docs/api/reference#resolve
*/
// CHECKSTYLE:ON
public static JsonObject resolveFor(@Nonnull final Downloader downloader, final String url)
throws IOException, ExtractionException {
ExtractorLogger.d(TAG, "resolveFor(" + url + ")");
final String apiUrl = SOUNDCLOUD_API_V2_URL + "resolve"
+ "?url=" + Utils.encodeUrlUtf8(url)
+ "&client_id=" + clientId();
Expand All @@ -177,10 +186,11 @@ public static JsonObject resolveFor(@Nonnull final Downloader downloader, final
public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOException,
ReCaptchaException {

final String response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url="
+ Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization()).responseBody();

return Jsoup.parse(response).select("link[rel=\"canonical\"]").first()
final var response = NewPipe.getDownloader().get("https://w.soundcloud.com/player/?url="
+ Utils.encodeUrlUtf8(apiUrl), SoundCloud.getLocalization());
validateResponseCode(response);
final var responseBody = response.responseBody();
return Jsoup.parse(responseBody).select("link[rel=\"canonical\"]").first()
.attr("abs:href");
}

Expand All @@ -189,6 +199,7 @@ public static String resolveUrlWithEmbedPlayer(final String apiUrl) throws IOExc
*
* @return the resolved id
*/
// TODO: what makes this method different from the others? Don' they all return the same?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can get an answer by git blaming?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found answer to this question as per #1322 (comment)

public static String resolveIdWithWidgetApi(final String urlString) throws IOException,
ParsingException {
String fixedUrl = urlString;
Expand Down Expand Up @@ -224,9 +235,12 @@ public static String resolveIdWithWidgetApi(final String urlString) throws IOExc
final String widgetUrl = "https://api-widget.soundcloud.com/resolve?url="
+ Utils.encodeUrlUtf8(url.toString())
+ "&format=json&client_id=" + SoundcloudParsingHelper.clientId();
final String response = NewPipe.getDownloader().get(widgetUrl,
SoundCloud.getLocalization()).responseBody();
final JsonObject o = JsonParser.object().from(response);

final var response = NewPipe.getDownloader().get(widgetUrl,
SoundCloud.getLocalization());

final var responseBody = response.validateResponseCode().responseBody();
final JsonObject o = JsonParser.object().from(responseBody);
return String.valueOf(JsonUtils.getValue(o, "id"));
} catch (final JsonParserException e) {
throw new ParsingException("Could not parse JSON response", e);
Expand Down
Loading
Loading