From fd82ec585b79142681b06cb93754dc979aa1bbb8 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Tue, 28 Jan 2025 20:16:13 +0100 Subject: [PATCH] [YouTube] Move to their own file and update clients' constants Also update client version and device info and add TVHTML5 client and WEB_EMBEDDED_PLAYER constants, these will be used in the future. TVHTML5_SIMPLY_EMBED_CLIENT_VERSION has been kept in its place as the corresponding client does not work anonymously anymore, so it will be removed soon. --- .../services/youtube/ClientsConstants.java | 116 ++++++++++++++++ .../youtube/YoutubeParsingHelper.java | 130 ++++-------------- 2 files changed, 144 insertions(+), 102 deletions(-) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ClientsConstants.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ClientsConstants.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ClientsConstants.java new file mode 100644 index 000000000..22b002e8f --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/ClientsConstants.java @@ -0,0 +1,116 @@ +package org.schabi.newpipe.extractor.services.youtube; + +final class ClientsConstants { + private ClientsConstants() { + } + + // Common client fields + + static final String DESKTOP_CLIENT_PLATFORM = "DESKTOP"; + static final String MOBILE_CLIENT_PLATFORM = "MOBILE"; + static final String WATCH_CLIENT_SCREEN = "WATCH"; + static final String EMBED_CLIENT_SCREEN = "EMBED"; + + // WEB (YouTube desktop) client fields + + static final String WEB_CLIENT_ID = "1"; + static final String WEB_CLIENT_NAME = "WEB"; + /** + * The client version for InnerTube requests with the {@code WEB} client, used as the last + * fallback if the extraction of the real one failed. + */ + static final String WEB_HARDCODED_CLIENT_VERSION = "2.20250122.04.00"; + + // WEB_REMIX (YouTube Music) client fields + + static final String WEB_REMIX_CLIENT_ID = "67"; + static final String WEB_REMIX_CLIENT_NAME = "WEB_REMIX"; + static final String WEB_REMIX_HARDCODED_CLIENT_VERSION = "1.20250122.01.00"; + + // TVHTML5 (YouTube on TVs and consoles using HTML5) client fields + static final String TVHTML5_CLIENT_ID = "7"; + static final String TVHTML5_CLIENT_NAME = "TVHTML5"; + static final String TVHTML5_CLIENT_VERSION = "7.20250122.15.00"; + static final String TVHTML5_CLIENT_PLATFORM = "GAME_CONSOLE"; + static final String TVHTML5_DEVICE_MAKE = "Sony"; + static final String TVHTML5_DEVICE_MODEL_AND_OS_NAME = "PlayStation 4"; + // CHECKSTYLE:OFF + static final String TVHTML5_USER_AGENT = + "Mozilla/5.0 (PlayStation; PlayStation 4/12.00) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15"; + // CHECKSTYLE:ON + + // WEB_EMBEDDED_PLAYER (YouTube embeds) + + static final String WEB_EMBEDDED_CLIENT_ID = "56"; + static final String WEB_EMBEDDED_CLIENT_NAME = "WEB_EMBEDDED_PLAYER"; + static final String WEB_EMBEDDED_CLIENT_VERSION = "1.20250121.00.00"; + + // IOS (iOS YouTube app) client fields + + static final String IOS_CLIENT_NAME = "IOS"; + + /** + * The hardcoded client version of the iOS app used for InnerTube requests with this client. + * + *

+ * It can be extracted by getting the latest release version of the app on + * the App + * Store page of the YouTube app, in the {@code What’s New} section. + *

+ */ + static final String IOS_CLIENT_VERSION = "20.03.02"; + + /** + * The device machine id for the iPhone 15 Pro Max, used to get 60fps with the {@code iOS} + * client. + * + *

+ * See this GitHub Gist for more + * information. + *

+ */ + static final String IOS_DEVICE_MODEL = "iPhone16,2"; + + /** + * The iOS version to be used in JSON POST requests, the one of an iPhone 15 Pro Max running + * iOS 18.2.1 with the hardcoded version of the iOS app (for the {@code "osVersion"} field). + * + *

+ * The value of this field seems to use the following structure: + * "iOS major version.minor version.patch version.build version", where + * "patch version" is equal to 0 if it isn't set + * The build version corresponding to the iOS version used can be found on + * + * https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max + *

+ * + * @see #IOS_USER_AGENT_VERSION + */ + static final String IOS_OS_VERSION = "18.2.1.22C161"; + + /** + * The iOS version to be used in the HTTP user agent for requests. + * + *

+ * This should be the same of as {@link #IOS_OS_VERSION}. + *

+ * + * @see #IOS_OS_VERSION + */ + static final String IOS_USER_AGENT_VERSION = "18_2_1"; + + // ANDROID (Android YouTube app) client fields + + static final String ANDROID_CLIENT_NAME = "ANDROID"; + + /** + * The hardcoded client version of the Android app used for InnerTube requests with this + * client. + * + *

+ * It can be extracted by getting the latest release version of the app in an APK repository + * such as APKMirror. + *

+ */ + static final String ANDROID_CLIENT_VERSION = "19.28.35"; +} 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 54b4b90fb..d3c88deab 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 @@ -21,6 +21,17 @@ package org.schabi.newpipe.extractor.services.youtube; import static org.schabi.newpipe.extractor.NewPipe.getDownloader; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.ANDROID_CLIENT_VERSION; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.DESKTOP_CLIENT_PLATFORM; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_CLIENT_VERSION; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_DEVICE_MODEL; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_USER_AGENT_VERSION; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_ID; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_NAME; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_HARDCODED_CLIENT_VERSION; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_REMIX_CLIENT_ID; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_REMIX_CLIENT_NAME; +import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_REMIX_HARDCODED_CLIENT_VERSION; import static org.schabi.newpipe.extractor.utils.Utils.HTTP; import static org.schabi.newpipe.extractor.utils.Utils.HTTPS; import static org.schabi.newpipe.extractor.utils.Utils.getStringResultFromRegexArray; @@ -144,55 +155,11 @@ public final class YoutubeParsingHelper { */ public static final String RACY_CHECK_OK = "racyCheckOk"; - /** - * The hardcoded client ID used for InnerTube requests with the {@code WEB} client. - */ - private static final String WEB_CLIENT_ID = "1"; - - /** - * The client version for InnerTube requests with the {@code WEB} client, used as the last - * fallback if the extraction of the real one failed. - */ - private static final String HARDCODED_CLIENT_VERSION = "2.20240718.01.00"; - - /** - * The hardcoded client version of the Android app used for InnerTube requests with this - * client. - * - *

- * It can be extracted by getting the latest release version of the app in an APK repository - * such as APKMirror. - *

- */ - private static final String ANDROID_YOUTUBE_CLIENT_VERSION = "19.28.35"; - - /** - * The hardcoded client version of the iOS app used for InnerTube requests with this client. - * - *

- * It can be extracted by getting the latest release version of the app on - * the App - * Store page of the YouTube app, in the {@code What’s New} section. - *

- */ - private static final String IOS_YOUTUBE_CLIENT_VERSION = "20.03.02"; - /** * The hardcoded client version used for InnerTube requests with the TV HTML5 embed client. */ private static final String TVHTML5_SIMPLY_EMBED_CLIENT_VERSION = "2.0"; - /** - * The hardcoded client ID used for InnerTube requests with the YouTube Music desktop client. - */ - private static final String YOUTUBE_MUSIC_CLIENT_ID = "67"; - - /** - * The hardcoded client version used for InnerTube requests with the YouTube Music desktop - * client. - */ - private static final String HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION = "1.20240715.01.00"; - private static String clientVersion; private static String youtubeMusicClientVersion; @@ -212,41 +179,6 @@ public final class YoutubeParsingHelper { private static final String CONTENT_PLAYBACK_NONCE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - /** - * The device machine id for the iPhone 15 Pro Max, - * used to get 60fps with the {@code iOS} client. - * - *

- * See this GitHub Gist for more - * information. - *

- */ - private static final String IOS_DEVICE_MODEL = "iPhone16,2"; - - /** - * Spoofing an iPhone 15 Pro Max running iOS 18.2.1 with the hardcoded version of the iOS app. - * To be used for the {@code "osVersion"} field in JSON POST requests. - *

- * The value of this field seems to use the following structure: - * "iOS major version.minor version.patch version.build version", where - * "patch version" is equal to 0 if it isn't set - * The build version corresponding to the iOS version used can be found on - * - * https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max - *

- * - * @see #IOS_USER_AGENT_VERSION - */ - private static final String IOS_OS_VERSION = "18.2.1.22C161"; - - /** - * Spoofing an iPhone 15 Pro Max running iOS 18.2.1 with the hardcoded version of the iOS app. - * To be used in the user agent for requests. - * - * @see #IOS_OS_VERSION - */ - private static final String IOS_USER_AGENT_VERSION = "18_2_1"; - private static Random numberGenerator = new Random(); private static final String FEED_BASE_CHANNEL_ID = @@ -561,9 +493,9 @@ public final class YoutubeParsingHelper { .object("client") .value("hl", "en-GB") .value("gl", "GB") - .value("clientName", "WEB") - .value("clientVersion", HARDCODED_CLIENT_VERSION) - .value("platform", "DESKTOP") + .value("clientName", WEB_CLIENT_NAME) + .value("clientVersion", WEB_HARDCODED_CLIENT_VERSION) + .value("platform", DESKTOP_CLIENT_PLATFORM) .value("utcOffsetMinutes", 0) .end() .object("request") @@ -581,7 +513,7 @@ public final class YoutubeParsingHelper { .end().done().getBytes(StandardCharsets.UTF_8); // @formatter:on - final var headers = getClientHeaders(WEB_CLIENT_ID, HARDCODED_CLIENT_VERSION); + final var headers = getClientHeaders(WEB_CLIENT_ID, WEB_HARDCODED_CLIENT_VERSION); // This endpoint is fetched by the YouTube website to get the items of its main menu and is // pretty lightweight (around 30kB) @@ -705,7 +637,7 @@ public final class YoutubeParsingHelper { // Fallback to the hardcoded one if it is valid if (isHardcodedClientVersionValid()) { - clientVersion = HARDCODED_CLIENT_VERSION; + clientVersion = WEB_HARDCODED_CLIENT_VERSION; return clientVersion; } @@ -752,11 +684,11 @@ public final class YoutubeParsingHelper { .object() .object("context") .object("client") - .value("clientName", "WEB_REMIX") - .value("clientVersion", HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION) + .value("clientName", WEB_REMIX_CLIENT_NAME) + .value("clientVersion", WEB_REMIX_HARDCODED_CLIENT_VERSION) .value("hl", "en-GB") .value("gl", "GB") - .value("platform", "DESKTOP") + .value("platform", DESKTOP_CLIENT_PLATFORM) .value("utcOffsetMinutes", 0) .end() .object("request") @@ -775,8 +707,7 @@ public final class YoutubeParsingHelper { // @formatter:on final var headers = new HashMap<>(getOriginReferrerHeaders(YOUTUBE_MUSIC_URL)); - headers.putAll(getClientHeaders(YOUTUBE_MUSIC_CLIENT_ID, - HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION)); + headers.putAll(getClientHeaders(WEB_REMIX_CLIENT_ID, WEB_HARDCODED_CLIENT_VERSION)); final Response response = getDownloader().postWithContentTypeJson(url, headers, json); // Ensure to have a valid response @@ -789,7 +720,7 @@ public final class YoutubeParsingHelper { return youtubeMusicClientVersion; } if (isHardcodedYoutubeMusicClientVersionValid()) { - youtubeMusicClientVersion = HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION; + youtubeMusicClientVersion = WEB_REMIX_HARDCODED_CLIENT_VERSION; return youtubeMusicClientVersion; } @@ -1196,10 +1127,10 @@ public final class YoutubeParsingHelper { .object("client") .value("hl", localization.getLocalizationCode()) .value("gl", contentCountry.getCountryCode()) - .value("clientName", "WEB") + .value("clientName", WEB_CLIENT_NAME) .value("clientVersion", getClientVersion()) .value("originalUrl", "https://www.youtube.com") - .value("platform", "DESKTOP") + .value("platform", DESKTOP_CLIENT_PLATFORM) .value("utcOffsetMinutes", 0) .value("visitorData", vData) .end() @@ -1391,9 +1322,8 @@ public final class YoutubeParsingHelper { */ @Nonnull public static String getAndroidUserAgent(@Nullable final Localization localization) { - // Spoofing an Android 14 device with the hardcoded version of the Android app - return "com.google.android.youtube/" + ANDROID_YOUTUBE_CLIENT_VERSION - + " (Linux; U; Android 14; " + return "com.google.android.youtube/" + ANDROID_CLIENT_VERSION + + " (Linux; U; Android 15; " + (localization != null ? localization : Localization.DEFAULT).getCountryCode() + ") gzip"; } @@ -1413,11 +1343,8 @@ public final class YoutubeParsingHelper { */ @Nonnull public static String getIosUserAgent(@Nullable final Localization localization) { - // Spoofing an iPhone 15 Pro Max running iOS 18.1.0 - // with the hardcoded version of the iOS app - return "com.google.ios.youtube/" + IOS_YOUTUBE_CLIENT_VERSION - + "(" + IOS_DEVICE_MODEL + "; U; CPU iOS " - + IOS_USER_AGENT_VERSION + " like Mac OS X; " + return "com.google.ios.youtube/" + IOS_CLIENT_VERSION + "(" + IOS_DEVICE_MODEL + + "; U; CPU iOS " + IOS_USER_AGENT_VERSION + " like Mac OS X; " + (localization != null ? localization : Localization.DEFAULT).getCountryCode() + ")"; } @@ -1428,8 +1355,7 @@ public final class YoutubeParsingHelper { @Nonnull public static Map> getYoutubeMusicHeaders() { final var headers = new HashMap<>(getOriginReferrerHeaders(YOUTUBE_MUSIC_URL)); - headers.putAll(getClientHeaders(YOUTUBE_MUSIC_CLIENT_ID, - youtubeMusicClientVersion)); + headers.putAll(getClientHeaders(WEB_REMIX_CLIENT_ID, youtubeMusicClientVersion)); return headers; }