From 2e3da445e60b0b1712eba644f81e580a83dbd498 Mon Sep 17 00:00:00 2001 From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> Date: Sat, 26 Mar 2022 20:02:35 +0100 Subject: [PATCH] [YouTube] Add documentation about parameters added and clients versions and key Also move the iPhone device machine id to a constant, explain how it is used and move the licence in the header of the file, and fix missing imports in YoutubeStreamExtractor (due to a rebase issue). --- .../youtube/YoutubeParsingHelper.java | 70 +++++++++++++++++-- .../extractors/YoutubeStreamExtractor.java | 3 + 2 files changed, 69 insertions(+), 4 deletions(-) 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 86e4b98e..ac00f4d4 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 @@ -80,6 +80,9 @@ public final class YoutubeParsingHelper { private YoutubeParsingHelper() { } + /** + * The base URL of requests of the {@code WEB} client to the InnerTube internal API + */ public static final String YOUTUBEI_V1_URL = "https://www.youtube.com/youtubei/v1/"; /** @@ -91,14 +94,60 @@ public final class YoutubeParsingHelper { *

**/ public static final String DISABLE_PRETTY_PRINT_PARAMETER = "&prettyPrint=false"; + + /** + * A parameter sent by official clients named {@code contentPlaybackNonce}. + * + *

+ * It is sent by official clients on videoplayback requests, and by all clients (except the + * {@code WEB} one to the player requests. + *

+ * + *

+ * It is composed of 16 characters which are generated from + * {@link #CONTENT_PLAYBACK_NONCE_ALPHABET this alphabet}, with the use of strong random + * values. + *

+ * + * @see #generateContentPlaybackNonce() + */ public static final String CPN = "cpn"; public static final String VIDEO_ID = "videoId"; + /** + * The client version for InnerTube requests with the {@code WEB} client, used as the last + * fallback if the extraction of the real one failed. + * + * You can get it directly either into YouTube pages or the service worker JavaScript file + * ({@code https://www.youtube.com/sw.js}) (also applies for YouTube Music). + */ private static final String HARDCODED_CLIENT_VERSION = "2.20220315.01.00"; private static final String HARDCODED_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"; + /** + * The InnerTube API key used by the {@code ANDROID} client. Found with the help of + * reverse-engineering app network requests. + */ private static final String ANDROID_YOUTUBE_KEY = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w"; + + /** + * The InnerTube API key used by the {@code iOS} client. Found with the help of + * reverse-engineering app network requests. + */ private static final String IOS_YOUTUBE_KEY = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc"; + + /** + * 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. + *

+ * + * @implNote This version is also used for the {@code iOS} client, as getting the app version + * without an iPhone device is not so easily. + */ private static final String MOBILE_YOUTUBE_CLIENT_VERSION = "17.10.35"; private static String clientVersion; @@ -128,6 +177,16 @@ public final class YoutubeParsingHelper { private static final String CONTENT_PLAYBACK_NONCE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + /** + * The device machine id for the iPhone 13, used to get 60fps with the {@code iOS} client. + * + *

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

+ */ + private static final String IOS_DEVICE_MODEL = "iPhone14,5"; + private static Random numberGenerator = new SecureRandom(); /** @@ -1071,7 +1130,7 @@ public final class YoutubeParsingHelper { .value("clientName", "IOS") .value("clientVersion", MOBILE_YOUTUBE_CLIENT_VERSION) // Device model is required to get 60fps streams - .value("deviceModel", "iPhone14,5") + .value("deviceModel", IOS_DEVICE_MODEL) .value("platform", "MOBILE") .value("hl", localization.getLocalizationCode()) .value("gl", contentCountry.getCountryCode()) @@ -1169,7 +1228,7 @@ public final class YoutubeParsingHelper { .value("clientVersion", MOBILE_YOUTUBE_CLIENT_VERSION) .value("clientScreen", "EMBED") // Device model is required to get 60fps streams - .value("deviceModel", "iPhone14,5") + .value("deviceModel", IOS_DEVICE_MODEL) .value("platform", "MOBILE") .value("hl", localization.getLocalizationCode()) .value("gl", contentCountry.getCountryCode()) @@ -1208,6 +1267,8 @@ public final class YoutubeParsingHelper { : prepareDesktopJsonBuilder(localization, contentCountry)) .object("playbackContext") .object("contentPlaybackContext") + // Some parameters which are sent by the official WEB client (probably some + // of them are not useful) .value("currentUrl", "/watch?v=" + videoId) .value("vis", 0) .value("splay", false) @@ -1260,9 +1321,10 @@ public final class YoutubeParsingHelper { */ @Nonnull public static String getIosUserAgent(@Nullable final Localization localization) { - // Spoofing an iPhone 13 running iOS 15.4 with the hardcoded mobile client version + // Spoofing an iPhone running iOS 15.4 with the hardcoded mobile client version return "com.google.ios.youtube/" + MOBILE_YOUTUBE_CLIENT_VERSION - + "(iPhone14,5; U; CPU iOS 15_4 like Mac OS X; " + + "(" + IOS_DEVICE_MODEL + + "; U; CPU iOS 15_4 like Mac OS X; " + (localization != null ? localization.getCountryCode() : Localization.DEFAULT.getCountryCode()) + ")"; diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index 151677d8..1beb62f9 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -7,12 +7,15 @@ import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.generateContentPlaybackNonce; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.generateTParameter; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonAndroidPostResponse; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonIosPostResponse; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getJsonPostResponse; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareAndroidMobileEmbedVideoJsonBuilder; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareAndroidMobileJsonBuilder; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopEmbedVideoJsonBuilder; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareIosMobileEmbedVideoJsonBuilder; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareIosMobileJsonBuilder; import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING; import static org.schabi.newpipe.extractor.utils.Utils.UTF_8; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;