diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YouTubeChannelHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YouTubeChannelHelper.java
new file mode 100644
index 000000000..2e43cbb13
--- /dev/null
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YouTubeChannelHelper.java
@@ -0,0 +1,181 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import com.grack.nanojson.JsonObject;
+import com.grack.nanojson.JsonWriter;
+import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.localization.ContentCountry;
+import org.schabi.newpipe.extractor.localization.Localization;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+
+/**
+ * Shared functions for extracting YouTube channel pages and tabs.
+ */
+public final class YouTubeChannelHelper {
+ private YouTubeChannelHelper() {
+ }
+
+ /**
+ * Take a YouTube channel ID or URL path, resolve it if necessary and return a channel ID.
+ * @param idOrPath YouTube channel ID or URL path
+ * @return YouTube Channel ID
+ */
+ public static String resolveChannelId(final String idOrPath)
+ throws ExtractionException, IOException {
+ final String[] channelId = idOrPath.split("/");
+
+ if (channelId[0].startsWith("UC")) {
+ return channelId[0];
+ }
+
+ // If the url is an URL which is not a /channel URL, we need to use the
+ // navigation/resolve_url endpoint of the InnerTube API to get the channel id. Otherwise,
+ // we couldn't get information about the channel associated with this URL, if there is one.
+ if (!channelId[0].equals("channel")) {
+ final byte[] body = JsonWriter.string(YoutubeParsingHelper.prepareDesktopJsonBuilder(
+ Localization.DEFAULT, ContentCountry.DEFAULT)
+ .value("url", "https://www.youtube.com/" + idOrPath)
+ .done())
+ .getBytes(StandardCharsets.UTF_8);
+
+ final JsonObject jsonResponse = YoutubeParsingHelper.getJsonPostResponse(
+ "navigation/resolve_url", body, Localization.DEFAULT);
+
+ checkIfChannelResponseIsValid(jsonResponse);
+
+ final JsonObject endpoint = jsonResponse.getObject("endpoint");
+
+ final String webPageType = endpoint.getObject("commandMetadata")
+ .getObject("webCommandMetadata")
+ .getString("webPageType", "");
+
+ final JsonObject browseEndpoint = endpoint.getObject("browseEndpoint");
+ final String browseId = browseEndpoint.getString("browseId", "");
+
+ if (webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_BROWSE")
+ || webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_CHANNEL")
+ && !browseId.isEmpty()) {
+ if (!browseId.startsWith("UC")) {
+ throw new ExtractionException("Redirected id is not pointing to a channel");
+ }
+
+ return browseId;
+ }
+ }
+ return channelId[1];
+ }
+
+ /**
+ * Response data object for
+ * {@link #getChannelResponse(String, String, Localization, ContentCountry)}
+ */
+ public static final class ChannelResponseData {
+ public final JsonObject responseJson;
+ public final String channelId;
+
+ private ChannelResponseData(final JsonObject responseJson, final String channelId) {
+ this.responseJson = responseJson;
+ this.channelId = channelId;
+ }
+ }
+
+ /**
+ * Fetch YouTube channel data.
+ *
Parameter list:
+ *
+ * - Videos: {@code EgZ2aWRlb3PyBgQKAjoA}
+ * - Shorts: {@code EgZzaG9ydHPyBgUKA5oBAA%3D%3D}
+ * - Livestreams: {@code EgdzdHJlYW1z8gYECgJ6AA%3D%3D}
+ * - Playlists: {@code EglwbGF5bGlzdHMgAQ%3D%3D}
+ * - Info: {@code EgVhYm91dPIGBAoCEgA%3D}
+ *
+ *
+ * @param channelId YouTube channel ID
+ * @param params Parameters to specify the YouTube channel tab
+ * @param loc YouTube localization
+ * @param country YouTube content country
+ * @return Channel response data
+ */
+ public static ChannelResponseData getChannelResponse(final String channelId,
+ final String params,
+ final Localization loc,
+ final ContentCountry country)
+ throws ExtractionException, IOException {
+ String id = channelId;
+ JsonObject ajaxJson = null;
+
+ int level = 0;
+ while (level < 3) {
+ final byte[] body = JsonWriter.string(YoutubeParsingHelper.prepareDesktopJsonBuilder(
+ loc, country)
+ .value("browseId", id)
+ .value("params", params) // Equal to videos
+ .done())
+ .getBytes(StandardCharsets.UTF_8);
+
+ final JsonObject jsonResponse = YoutubeParsingHelper.getJsonPostResponse(
+ "browse", body, loc);
+
+ checkIfChannelResponseIsValid(jsonResponse);
+
+ final JsonObject endpoint = jsonResponse.getArray("onResponseReceivedActions")
+ .getObject(0)
+ .getObject("navigateAction")
+ .getObject("endpoint");
+
+ final String webPageType = endpoint.getObject("commandMetadata")
+ .getObject("webCommandMetadata")
+ .getString("webPageType", "");
+
+ final String browseId = endpoint.getObject("browseEndpoint").getString("browseId",
+ "");
+
+ if (webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_BROWSE")
+ || webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_CHANNEL")
+ && !browseId.isEmpty()) {
+ if (!browseId.startsWith("UC")) {
+ throw new ExtractionException("Redirected id is not pointing to a channel");
+ }
+
+ id = browseId;
+ level++;
+ } else {
+ ajaxJson = jsonResponse;
+ break;
+ }
+ }
+
+ if (ajaxJson == null) {
+ throw new ExtractionException("Got no channel response");
+ }
+
+ YoutubeParsingHelper.defaultAlertsCheck(ajaxJson);
+
+ return new ChannelResponseData(ajaxJson, id);
+ }
+
+ /**
+ * Assert that a channel JSON response does not contain a 404 error.
+ * @param jsonResponse channel JSON response
+ * @throws ContentNotAvailableException if the channel was not found
+ */
+ private static void checkIfChannelResponseIsValid(@Nonnull final JsonObject jsonResponse)
+ throws ContentNotAvailableException {
+ if (!isNullOrEmpty(jsonResponse.getObject("error"))) {
+ final JsonObject errorJsonObject = jsonResponse.getObject("error");
+ final int errorCode = errorJsonObject.getInt("code");
+ if (errorCode == 404) {
+ throw new ContentNotAvailableException("This channel doesn't exist.");
+ } else {
+ throw new ContentNotAvailableException("Got error:\""
+ + errorJsonObject.getString("status") + "\": "
+ + errorJsonObject.getString("message"));
+ }
+ }
+ }
+}
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 1b8f8ade4..2b00515fa 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
@@ -1742,164 +1742,6 @@ public final class YoutubeParsingHelper {
return false;
}
- /**
- * Take a YouTube channel ID or URL path, resolve it if necessary and return a channel ID.
- * @param idOrPath YouTube channel ID or URL path
- * @return YouTube Channel ID
- */
- public static String resolveChannelId(final String idOrPath)
- throws ExtractionException, IOException {
- final String[] channelId = idOrPath.split("/");
-
- if (channelId[0].startsWith("UC")) {
- return channelId[0];
- }
-
- // If the url is an URL which is not a /channel URL, we need to use the
- // navigation/resolve_url endpoint of the InnerTube API to get the channel id. Otherwise,
- // we couldn't get information about the channel associated with this URL, if there is one.
- if (!channelId[0].equals("channel")) {
- final byte[] body = JsonWriter.string(prepareDesktopJsonBuilder(
- Localization.DEFAULT, ContentCountry.DEFAULT)
- .value("url", "https://www.youtube.com/" + idOrPath)
- .done())
- .getBytes(StandardCharsets.UTF_8);
-
- final JsonObject jsonResponse = getJsonPostResponse("navigation/resolve_url",
- body, Localization.DEFAULT);
-
- checkIfChannelResponseIsValid(jsonResponse);
-
- final JsonObject endpoint = jsonResponse.getObject("endpoint");
-
- final String webPageType = endpoint.getObject("commandMetadata")
- .getObject("webCommandMetadata")
- .getString("webPageType", "");
-
- final JsonObject browseEndpoint = endpoint.getObject("browseEndpoint");
- final String browseId = browseEndpoint.getString("browseId", "");
-
- if (webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_BROWSE")
- || webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_CHANNEL")
- && !browseId.isEmpty()) {
- if (!browseId.startsWith("UC")) {
- throw new ExtractionException("Redirected id is not pointing to a channel");
- }
-
- return browseId;
- }
- }
- return channelId[1];
- }
-
- /**
- * Response data object for
- * {@link #getChannelResponse(String, String, Localization, ContentCountry)}
- */
- public static final class ChannelResponseData {
- public final JsonObject responseJson;
- public final String channelId;
-
- private ChannelResponseData(final JsonObject responseJson, final String channelId) {
- this.responseJson = responseJson;
- this.channelId = channelId;
- }
- }
-
- /**
- * Fetch YouTube channel data.
- * Parameter list:
- *
- * - Videos: {@code EgZ2aWRlb3PyBgQKAjoA}
- * - Shorts: {@code EgZzaG9ydHPyBgUKA5oBAA%3D%3D}
- * - Livestreams: {@code EgdzdHJlYW1z8gYECgJ6AA%3D%3D}
- * - Playlists: {@code EglwbGF5bGlzdHMgAQ%3D%3D}
- * - Info: {@code EgVhYm91dPIGBAoCEgA%3D}
- *
- *
- * @param channelId YouTube channel ID
- * @param params Parameters to specify the YouTube channel tab
- * @param loc YouTube localization
- * @param country YouTube content country
- * @return Channel response data
- */
- public static ChannelResponseData getChannelResponse(final String channelId,
- final String params,
- final Localization loc,
- final ContentCountry country)
- throws ExtractionException, IOException {
- String id = channelId;
- JsonObject ajaxJson = null;
-
- int level = 0;
- while (level < 3) {
- final byte[] body = JsonWriter.string(prepareDesktopJsonBuilder(
- loc, country)
- .value("browseId", id)
- .value("params", params) // Equal to videos
- .done())
- .getBytes(StandardCharsets.UTF_8);
-
- final JsonObject jsonResponse = getJsonPostResponse("browse", body, loc);
-
- checkIfChannelResponseIsValid(jsonResponse);
-
- final JsonObject endpoint = jsonResponse.getArray("onResponseReceivedActions")
- .getObject(0)
- .getObject("navigateAction")
- .getObject("endpoint");
-
- final String webPageType = endpoint.getObject("commandMetadata")
- .getObject("webCommandMetadata")
- .getString("webPageType", "");
-
- final String browseId = endpoint.getObject("browseEndpoint").getString("browseId",
- "");
-
- if (webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_BROWSE")
- || webPageType.equalsIgnoreCase("WEB_PAGE_TYPE_CHANNEL")
- && !browseId.isEmpty()) {
- if (!browseId.startsWith("UC")) {
- throw new ExtractionException("Redirected id is not pointing to a channel");
- }
-
- id = browseId;
- level++;
- } else {
- ajaxJson = jsonResponse;
- break;
- }
- }
-
- if (ajaxJson == null) {
- throw new ExtractionException("Got no channel response");
- }
-
- defaultAlertsCheck(ajaxJson);
-
- return new ChannelResponseData(ajaxJson, id);
- }
-
- /**
- * Assert that a channel JSON response does not contain a 404 error.
- * @param jsonResponse channel JSON response
- * @throws ContentNotAvailableException if the channel was not found
- */
- private static void checkIfChannelResponseIsValid(@Nonnull final JsonObject jsonResponse)
- throws ContentNotAvailableException {
- if (!isNullOrEmpty(jsonResponse.getObject("error"))) {
- final JsonObject errorJsonObject = jsonResponse.getObject("error");
- final int errorCode = errorJsonObject.getInt("code");
- if (errorCode == 404) {
- throw new ContentNotAvailableException("This channel doesn't exist.");
- } else {
- throw new ContentNotAvailableException("Got error:\""
- + errorJsonObject.getString("status") + "\": "
- + errorJsonObject.getString("message"));
- }
- }
- }
-
/**
* Generate a content playback nonce (also called {@code cpn}), sent by YouTube clients in
* playback requests (and also for some clients, in the player request body).
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelExtractor.java
index 5df0de62d..ae1794005 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelExtractor.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelExtractor.java
@@ -1,15 +1,15 @@
package org.schabi.newpipe.extractor.services.youtube.extractors;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.ChannelResponseData;
+import static org.schabi.newpipe.extractor.services.youtube.YouTubeChannelHelper.ChannelResponseData;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.YOUTUBEI_V1_URL;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getChannelResponse;
+import static org.schabi.newpipe.extractor.services.youtube.YouTubeChannelHelper.getChannelResponse;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getKey;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.resolveChannelId;
+import static org.schabi.newpipe.extractor.services.youtube.YouTubeChannelHelper.resolveChannelId;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import com.grack.nanojson.JsonArray;
diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelTabExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelTabExtractor.java
index 204fe9ddb..87d53f693 100644
--- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelTabExtractor.java
+++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelTabExtractor.java
@@ -24,14 +24,14 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.ChannelResponseData;
+import static org.schabi.newpipe.extractor.services.youtube.YouTubeChannelHelper.ChannelResponseData;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.YOUTUBEI_V1_URL;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getChannelResponse;
+import static org.schabi.newpipe.extractor.services.youtube.YouTubeChannelHelper.getChannelResponse;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getKey;
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
-import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.resolveChannelId;
+import static org.schabi.newpipe.extractor.services.youtube.YouTubeChannelHelper.resolveChannelId;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
public class YoutubeChannelTabExtractor extends ChannelTabExtractor {