Skip to content

Commit

Permalink
Add ClientsConstants for client versions and names
Browse files Browse the repository at this point in the history
  • Loading branch information
Stypox committed Jan 26, 2025
1 parent 96c2884 commit 7c30969
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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 web on TV and consoles) 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 = "CONSOLE";
// CHECKSTYLE:OFF
static final String TVHTML5_USER_AGENT =
"Mozilla/5.0 (PlayStation; PlayStation 4/8.50) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 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.
*
* <p>
* It can be extracted by getting the latest release version of the app on
* <a href="https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664/">the App
* Store page of the YouTube app</a>, in the {@code What’s New} section.
* </p>
*/
static final String IOS_CLIENT_VERSION = "19.28.1";

/**
* The device machine id for the iPhone 15 Pro Max, used to get 60fps with the {@code iOS}
* client.
*
* <p>
* See <a href="https://gist.github.com/adamawolf/3048717">this GitHub Gist</a> for more
* information.
* </p>
*/
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).
*
* <p>
* 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
* <a href="https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max">
* https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max</a>
* </p>
*
* @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.
*
* <p>
* This should be the same of as {@link #IOS_OS_VERSION}.
* </p>
*
* @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.
*
* <p>
* It can be extracted by getting the latest release version of the app in an APK repository
* such as <a href="https://www.apkmirror.com/apk/google-inc/youtube/">APKMirror</a>.
* </p>
*/
static final String ANDROID_CLIENT_VERSION = "19.28.35";
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
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_OS_VERSION;
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_USER_AGENT_VERSION;
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.TVHTML5_CLIENT_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;
Expand Down Expand Up @@ -144,55 +157,6 @@ private 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.
*
* <p>
* It can be extracted by getting the latest release version of the app in an APK repository
* such as <a href="https://www.apkmirror.com/apk/google-inc/youtube/">APKMirror</a>.
* </p>
*/
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.
*
* <p>
* It can be extracted by getting the latest release version of the app on
* <a href="https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664/">the App
* Store page of the YouTube app</a>, in the {@code What’s New} section.
* </p>
*/
private static final String IOS_YOUTUBE_CLIENT_VERSION = "19.45.4";

/**
* 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;
Expand All @@ -212,41 +176,6 @@ private 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.
*
* <p>
* See <a href="https://gist.github.com/adamawolf/3048717">this GitHub Gist</a> for more
* information.
* </p>
*/
private static final String IOS_DEVICE_MODEL = "iPhone16,2";

/**
* Spoofing an iPhone 15 Pro Max running iOS 18.1.0 with the hardcoded version of the iOS app.
* To be used for the {@code "osVersion"} field in JSON POST requests.
* <p>
* 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
* <a href="https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max">
* https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max</a>
* </p>
*
* @see #IOS_USER_AGENT_VERSION
*/
private static final String IOS_OS_VERSION = "18.1.0.22B83";

/**
* Spoofing an iPhone 15 Pro Max running iOS 18.1.0 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_1_0";

private static Random numberGenerator = new Random();

private static final String FEED_BASE_CHANNEL_ID =
Expand Down Expand Up @@ -561,9 +490,9 @@ public static boolean isHardcodedClientVersionValid()
.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")
Expand All @@ -581,7 +510,7 @@ public static boolean isHardcodedClientVersionValid()
.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)
Expand Down Expand Up @@ -705,7 +634,7 @@ public static String getClientVersion() throws IOException, ExtractionException

// Fallback to the hardcoded one if it is valid
if (isHardcodedClientVersionValid()) {
clientVersion = HARDCODED_CLIENT_VERSION;
clientVersion = WEB_HARDCODED_CLIENT_VERSION;
return clientVersion;
}

Expand Down Expand Up @@ -752,11 +681,11 @@ public static boolean isHardcodedYoutubeMusicClientVersionValid() throws IOExcep
.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")
Expand All @@ -775,8 +704,7 @@ public static boolean isHardcodedYoutubeMusicClientVersionValid() throws IOExcep
// @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
Expand All @@ -789,7 +717,7 @@ public static String getYoutubeMusicClientVersion()
return youtubeMusicClientVersion;
}
if (isHardcodedYoutubeMusicClientVersionValid()) {
youtubeMusicClientVersion = HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION;
youtubeMusicClientVersion = WEB_REMIX_HARDCODED_CLIENT_VERSION;
return youtubeMusicClientVersion;
}

Expand Down Expand Up @@ -1196,10 +1124,10 @@ public static JsonBuilder<JsonObject> prepareDesktopJsonBuilder(
.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()
Expand Down Expand Up @@ -1227,7 +1155,7 @@ public static JsonBuilder<JsonObject> prepareAndroidMobileJsonBuilder(
.object("context")
.object("client")
.value("clientName", "ANDROID")
.value("clientVersion", ANDROID_YOUTUBE_CLIENT_VERSION)
.value("clientVersion", ANDROID_CLIENT_VERSION)
.value("platform", "MOBILE")
.value("osName", "Android")
.value("osVersion", "14")
Expand Down Expand Up @@ -1276,7 +1204,7 @@ public static JsonBuilder<JsonObject> prepareIosMobileJsonBuilder(
.object("context")
.object("client")
.value("clientName", "IOS")
.value("clientVersion", IOS_YOUTUBE_CLIENT_VERSION)
.value("clientVersion", IOS_CLIENT_VERSION)
.value("deviceMake", "Apple")
// Device model is required to get 60fps streams
.value("deviceModel", IOS_DEVICE_MODEL)
Expand Down Expand Up @@ -1312,7 +1240,7 @@ public static JsonBuilder<JsonObject> prepareTvHtml5EmbedJsonBuilder(
.object("context")
.object("client")
.value("clientName", "TVHTML5_SIMPLY_EMBEDDED_PLAYER")
.value("clientVersion", TVHTML5_SIMPLY_EMBED_CLIENT_VERSION)
.value("clientVersion", TVHTML5_CLIENT_VERSION)
.value("clientScreen", "EMBED")
.value("platform", "TV")
.value("hl", localization.getLocalizationCode())
Expand Down Expand Up @@ -1378,9 +1306,8 @@ public static byte[] createTvHtml5EmbedPlayerBody(
*/
@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";
}
Expand All @@ -1400,11 +1327,8 @@ public static String getAndroidUserAgent(@Nullable final Localization localizati
*/
@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()
+ ")";
}
Expand All @@ -1415,8 +1339,7 @@ public static String getIosUserAgent(@Nullable final Localization localization)
@Nonnull
public static Map<String, List<String>> 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;
}

Expand Down

0 comments on commit 7c30969

Please sign in to comment.