diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
index e21b17f3..dcde0aff 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
@@ -16,6 +16,7 @@ import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
+import org.schabi.newpipe.extractor.utils.Utils;
import javax.annotation.Nullable;
import java.util.Collections;
@@ -277,18 +278,19 @@ public abstract class StreamingService {
* Figures out where the link is pointing to (a channel, a video, a playlist, etc.)
* @param url the url on which it should be decided of which link type it is
* @return the link type of url
- * @throws ParsingException
*/
- public final LinkType getLinkTypeByUrl(String url) throws ParsingException {
- LinkHandlerFactory sH = getStreamLHFactory();
- LinkHandlerFactory cH = getChannelLHFactory();
- LinkHandlerFactory pH = getPlaylistLHFactory();
+ public final LinkType getLinkTypeByUrl(final String url) throws ParsingException {
+ final String polishedUrl = Utils.followGoogleRedirectIfNeeded(url);
- if (sH != null && sH.acceptUrl(url)) {
+ final LinkHandlerFactory sH = getStreamLHFactory();
+ final LinkHandlerFactory cH = getChannelLHFactory();
+ final LinkHandlerFactory pH = getPlaylistLHFactory();
+
+ if (sH != null && sH.acceptUrl(polishedUrl)) {
return LinkType.STREAM;
- } else if (cH != null && cH.acceptUrl(url)) {
+ } else if (cH != null && cH.acceptUrl(polishedUrl)) {
return LinkType.CHANNEL;
- } else if (pH != null && pH.acceptUrl(url)) {
+ } else if (pH != null && pH.acceptUrl(polishedUrl)) {
return LinkType.PLAYLIST;
} else {
return LinkType.NONE;
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/LinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/LinkHandlerFactory.java
index 6bba7b4e..ca428b70 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/LinkHandlerFactory.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/LinkHandlerFactory.java
@@ -42,12 +42,29 @@ public abstract class LinkHandlerFactory {
// Logic
///////////////////////////////////
- public LinkHandler fromUrl(String url) throws ParsingException {
- if (url == null) throw new IllegalArgumentException("url can not be null");
- final String baseUrl = Utils.getBaseUrl(url);
- return fromUrl(url, baseUrl);
+ /**
+ * Builds a {@link LinkHandler} from a url.
+ * Be sure to call {@link Utils#followGoogleRedirectIfNeeded(String)} on the url if overriding
+ * this function.
+ * @param url the url to extract path and id from
+ * @return a {@link LinkHandler} complete with information
+ */
+ public LinkHandler fromUrl(final String url) throws ParsingException {
+ final String polishedUrl = Utils.followGoogleRedirectIfNeeded(url);
+ final String baseUrl = Utils.getBaseUrl(polishedUrl);
+ return fromUrl(polishedUrl, baseUrl);
}
+ /**
+ * Builds a {@link LinkHandler} from a url and a base url. The url is expected to be already
+ * polished from google search redirects (otherwise how could {@code baseUrl} have been
+ * extracted?).
+ * So do not call {@link Utils#followGoogleRedirectIfNeeded(String)} on the url if overriding
+ * this function, since that should be done in {@link #fromUrl(String)}.
+ * @param url the url without google search redirects to extract id from
+ * @param baseUrl the base url
+ * @return a {@link LinkHandler} complete with information
+ */
public LinkHandler fromUrl(String url, String baseUrl) throws ParsingException {
if (url == null) throw new IllegalArgumentException("url can not be null");
if (!acceptUrl(url)) {
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java
index 9ea478b0..4980c319 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/linkhandler/ListLinkHandlerFactory.java
@@ -31,9 +31,10 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
///////////////////////////////////
@Override
- public ListLinkHandler fromUrl(String url) throws ParsingException {
- String baseUrl = Utils.getBaseUrl(url);
- return fromUrl(url, baseUrl);
+ public ListLinkHandler fromUrl(final String url) throws ParsingException {
+ final String polishedUrl = Utils.followGoogleRedirectIfNeeded(url);
+ final String baseUrl = Utils.getBaseUrl(polishedUrl);
+ return fromUrl(polishedUrl, baseUrl);
}
@Override
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java
index ac7db985..a8ca2a03 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java
@@ -55,12 +55,6 @@ public class YoutubeParsingHelper {
private YoutubeParsingHelper() {
}
- /**
- * The official youtube app supports intents in this format, where after the ':' is the videoId.
- * Accordingly there are other apps sharing streams in this format.
- */
- public final static String BASE_YOUTUBE_INTENT_URL = "vnd.youtube";
-
private static final String HARDCODED_CLIENT_VERSION = "2.20200214.04.00";
private static String clientVersion;
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeCommentsLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeCommentsLinkHandlerFactory.java
index 15bc31b6..421fc13f 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeCommentsLinkHandlerFactory.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeCommentsLinkHandlerFactory.java
@@ -1,10 +1,7 @@
package org.schabi.newpipe.extractor.services.youtube.linkHandler;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.BASE_YOUTUBE_INTENT_URL;
-
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
import java.util.List;
@@ -17,15 +14,6 @@ public class YoutubeCommentsLinkHandlerFactory extends ListLinkHandlerFactory {
return instance;
}
- @Override
- public ListLinkHandler fromUrl(String url) throws ParsingException {
- if (url.startsWith(BASE_YOUTUBE_INTENT_URL)){
- return super.fromUrl(url, BASE_YOUTUBE_INTENT_URL);
- } else {
- return super.fromUrl(url);
- }
- }
-
@Override
public String getUrl(String id) {
return "https://m.youtube.com/watch?v=" + id;
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeStreamLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeStreamLinkHandlerFactory.java
index bcc1fac5..efc06da2 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeStreamLinkHandlerFactory.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeStreamLinkHandlerFactory.java
@@ -2,7 +2,6 @@ package org.schabi.newpipe.extractor.services.youtube.linkHandler;
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.utils.Utils;
@@ -15,8 +14,6 @@ import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.BASE_YOUTUBE_INTENT_URL;
-
/*
* Created by Christian Schabesberger on 02.02.16.
*
@@ -67,15 +64,6 @@ public class YoutubeStreamLinkHandlerFactory extends LinkHandlerFactory {
}
}
- @Override
- public LinkHandler fromUrl(String url) throws ParsingException {
- if (url.startsWith(BASE_YOUTUBE_INTENT_URL)) {
- return super.fromUrl(url, BASE_YOUTUBE_INTENT_URL);
- } else {
- return super.fromUrl(url);
- }
- }
-
@Override
public String getUrl(String id) {
return "https://www.youtube.com/watch?v=" + id;
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
index 288e401c..95920270 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
@@ -181,14 +181,39 @@ public class Utils {
return s;
}
- public static String getBaseUrl(String url) throws ParsingException {
- URL uri;
+ public static String getBaseUrl(final String url) throws ParsingException {
try {
- uri = stringToURL(url);
- } catch (MalformedURLException e) {
+ final URL uri = stringToURL(url);
+ return uri.getProtocol() + "://" + uri.getAuthority();
+ } catch (final MalformedURLException e) {
+ final String message = e.getMessage();
+ if (message.startsWith("unknown protocol: ")) {
+ // return just the protocol (e.g. vnd.youtube)
+ return message.substring("unknown protocol: ".length());
+ }
+
throw new ParsingException("Malformed url: " + url, e);
}
- return uri.getProtocol() + "://" + uri.getAuthority();
+ }
+
+ /**
+ * If the provided url is a Google search redirect, then the actual url is extracted from the
+ * {@code url=} query value and returned, otherwise the original url is returned.
+ * @param url the url which can possibly be a Google search redirect
+ * @return an url with no Google search redirects
+ */
+ public static String followGoogleRedirectIfNeeded(final String url) {
+ // if the url is a redirect from a Google search, extract the actual url
+ 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");
+ }
+ } catch (final Exception ignored) {
+ }
+
+ // url is not a google search redirect
+ return url;
}
public static boolean isNullOrEmpty(final String str) {
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java
index bdad6cb6..5dbc4317 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java
@@ -6,6 +6,7 @@ import java.util.HashSet;
import static org.junit.Assert.*;
import static org.schabi.newpipe.extractor.NewPipe.getServiceByUrl;
+import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
public class NewPipeTest {
@@ -39,8 +40,10 @@ public class NewPipeTest {
assertEquals(getServiceByUrl("https://www.youtube.com/watch?v=_r6CgaFNAGg"), YouTube);
assertEquals(getServiceByUrl("https://www.youtube.com/channel/UCi2bIyFtz-JdI-ou8kaqsqg"), YouTube);
assertEquals(getServiceByUrl("https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH"), YouTube);
+ assertEquals(getServiceByUrl("https://www.google.it/url?sa=t&rct=j&q=&esrc=s&cd=&cad=rja&uact=8&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DHu80uDzh8RY&source=video"), YouTube);
- assertNotEquals(getServiceByUrl("https://soundcloud.com/pegboardnerds"), YouTube);
+ assertEquals(getServiceByUrl("https://soundcloud.com/pegboardnerds"), SoundCloud);
+ assertEquals(getServiceByUrl("https://www.google.com/url?sa=t&url=https%3A%2F%2Fsoundcloud.com%2Fciaoproduction&rct=j&q=&esrc=s&source=web&cd="), SoundCloud);
}
@Test
diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java
index 5b0dfdb3..e4a65505 100644
--- a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java
+++ b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java
@@ -21,4 +21,28 @@ public class UtilsTest {
public void testJoin() {
assertEquals("some,random,stuff", Utils.join(",", Arrays.asList("some", "random", "stuff")));
}
+
+ @Test
+ public void testGetBaseUrl() throws ParsingException {
+ assertEquals("https://www.youtube.com", Utils.getBaseUrl("https://www.youtube.com/watch?v=Hu80uDzh8RY"));
+ assertEquals("vnd.youtube", Utils.getBaseUrl("vnd.youtube://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertEquals("vnd.youtube", Utils.getBaseUrl("vnd.youtube:jZViOEv90dI"));
+ assertEquals("vnd.youtube", Utils.getBaseUrl("vnd.youtube://n8X9_MgEdCg"));
+ assertEquals("https://music.youtube.com", Utils.getBaseUrl("https://music.youtube.com/watch?v=O0EDx9WAelc"));
+ }
+
+ @Test
+ public void testFollowGoogleRedirect() {
+ assertEquals("https://www.youtube.com/watch?v=Hu80uDzh8RY",
+ Utils.followGoogleRedirectIfNeeded("https://www.google.it/url?sa=t&rct=j&q=&esrc=s&cd=&cad=rja&uact=8&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DHu80uDzh8RY&source=video"));
+ assertEquals("https://www.youtube.com/watch?v=0b6cFWG45kA",
+ Utils.followGoogleRedirectIfNeeded("https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=video&cd=&cad=rja&uact=8&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D0b6cFWG45kA"));
+ assertEquals("https://soundcloud.com/ciaoproduction",
+ Utils.followGoogleRedirectIfNeeded("https://www.google.com/url?sa=t&url=https%3A%2F%2Fsoundcloud.com%2Fciaoproduction&rct=j&q=&esrc=s&source=web&cd="));
+
+ assertEquals("https://www.youtube.com/watch?v=Hu80uDzh8RY¶m=xyz",
+ Utils.followGoogleRedirectIfNeeded("https://www.youtube.com/watch?v=Hu80uDzh8RY¶m=xyz"));
+ assertEquals("https://www.youtube.com/watch?v=Hu80uDzh8RY&url=hello",
+ Utils.followGoogleRedirectIfNeeded("https://www.youtube.com/watch?v=Hu80uDzh8RY&url=hello"));
+ }
}