Skip to content

Commit

Permalink
fix(YouTube - Spoof video streams): Add more client parameters used b…
Browse files Browse the repository at this point in the history
…y YouTube clients
  • Loading branch information
LisoUseInAIKyrios committed Jan 16, 2025
1 parent d47beb7 commit 2ee47ae
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@

import androidx.annotation.Nullable;

import java.util.Locale;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;

public enum ClientType {
// https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_NO_AUTH(
28,
"ANDROID_VR",
"com.google.android.apps.youtube.vr.oculus",
"Oculus",
"Quest 3",
"Qualcomm;SXR2230P",
null,
"Android",
"12",
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
"32", // Android 12.1
"SQ3A.220605.009.A1",
"1.56.21",
false,
"Android VR No auth"
Expand All @@ -26,58 +32,70 @@ public enum ClientType {
ANDROID_UNPLUGGED(
29,
"ANDROID_UNPLUGGED",
"com.google.android.apps.youtube.unplugged",
"Google",
"Google TV Streamer",
"Mediatek;MT8696",
"244336107",
"Android",
"14",
"com.google.android.apps.youtube.unplugged/8.49.0 (Linux; U; Android 14; GB) gzip",
"34",
"UTT3.240625.001.K5",
"8.49.0",
true,
"Android TV"
),
// Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
// Google Pixel 9 Pro Fold
// https://dumps.tadiphone.dev/dumps/google/barbet
ANDROID_CREATOR(
14,
"ANDROID_CREATOR",
Build.MANUFACTURER,
Build.MODEL,
"com.google.android.apps.youtube.creator",
"Google",
"Pixel 9 Pro Fold",
"Google;Tensor G4",
"244738035",
"Android",
"11",
"com.google.android.apps.youtube.creator/24.45.100 (Linux; U; Android 11) gzip",
"30",
"24.45.100",
"15",
"35",
"AP3A.241005.015.A2",
"23.47.101",
true,
"Android Creator"
),
ANDROID_VR(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.packageName,
ANDROID_VR_NO_AUTH.deviceMake,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.chipset,
ANDROID_VR_NO_AUTH.gmscoreVersionCode,
ANDROID_VR_NO_AUTH.osName,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.userAgent,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.buildId,
ANDROID_VR_NO_AUTH.clientVersion,
true,
"Android VR"
),
IOS_UNPLUGGED(
33,
"IOS_UNPLUGGED",
"com.google.ios.youtubeunplugged",
"Apple",
forceAVC()
? "iPhone12,5" // 11 Pro Max (last device with iOS 13)
: "iPhone16,2", // 15 Pro Max
null,
null,
"iOS",
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
forceAVC()
? "13.7.17H35" // Last release of iOS 13.
: "18.2.22C152",
forceAVC()
? "com.google.ios.youtubeunplugged/6.45 (iPhone12,5; U; CPU iOS 13_7 like Mac OS X)"
: "com.google.ios.youtubeunplugged/8.49 (iPhone16,2; U; CPU iOS 18_2_22 like Mac OS X)",
null,
null,
// Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/152043/
Expand All @@ -104,6 +122,11 @@ private static boolean forceAVC() {

public final String clientName;

/**
* App package name.
*/
public final String packageName;

/**
* Device model, equivalent to {@link Build#MANUFACTURER} (System property: ro.product.vendor.manufacturer)
*/
Expand All @@ -114,6 +137,20 @@ private static boolean forceAVC() {
*/
public final String deviceModel;

/**
* Android chipset.
* Field is null if not applicable.
*/
@Nullable
public final String chipset;

/**
* GmsCore versionCode.
* Field is null if not applicable.
*/
@Nullable
public final String gmscoreVersionCode; // JSON field does not use 'gmsCore' casing.

/**
* Device OS name.
*/
Expand All @@ -136,6 +173,13 @@ private static boolean forceAVC() {
@Nullable
public final String androidSdkVersion;

/**
* Android build id, equivalent to {@link Build#ID}.
* Field is null if not applicable.
*/
@Nullable
public final String buildId;

/**
* App version.
*/
Expand All @@ -151,25 +195,47 @@ private static boolean forceAVC() {
*/
public final String friendlyName;

@SuppressWarnings("ConstantLocale")
ClientType(int id,
String clientName,
String packageName,
String deviceMake,
String deviceModel,
@Nullable String chipset,
@Nullable String gmscoreVersionCode,
String osName,
String osVersion,
String userAgent,
@Nullable String androidSdkVersion,
@Nullable String buildId,
String clientVersion,
boolean canLogin,
String friendlyName) {
this.id = id;
this.clientName = clientName;
this.packageName = packageName;
this.deviceMake = deviceMake;
this.deviceModel = deviceModel;
this.chipset = chipset;
this.gmscoreVersionCode = gmscoreVersionCode;
this.osName = osName;
this.osVersion = osVersion;
this.userAgent = userAgent;
this.androidSdkVersion = androidSdkVersion;
this.buildId = buildId;
Locale defaultLocale = Locale.getDefault();
if (androidSdkVersion != null) {
// https://whatmyuseragent.com/apps/youtube
this.userAgent = packageName + "/" + clientVersion + "(Linux; U; Android " + osVersion + "; "
+ defaultLocale + "; " + deviceModel + " " + "Build/" + buildId + ") gzip";
} else {
// https://github.com/mitmproxy/mitmproxy/issues/4836
// Convert version from '18.2.22C152' into '18_2_22'
String userAgentOsVersion = osVersion
.replaceAll("(\\d+\\.\\d+\\.\\d+).*", "$1")
.replace(".", "_");
this.userAgent = packageName + "/" + clientVersion + "(" + deviceModel + "; U; CPU " +
userAgentOsVersion + " like Mac OS X; " + defaultLocale + ")";
}
Logger.printDebug(() -> "user agent: " + this.userAgent);
this.clientVersion = clientVersion;
this.canLogin = canLogin;
this.friendlyName = friendlyName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Date;
import java.util.TimeZone;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;

final class PlayerRoutes {
Expand All @@ -31,7 +34,7 @@ final class PlayerRoutes {
private PlayerRoutes() {
}

static String createInnertubeBody(ClientType clientType) {
static String createInnertubeBody(ClientType clientType, String videoId) {
JSONObject innerTubeBody = new JSONObject();

try {
Expand All @@ -47,22 +50,32 @@ static String createInnertubeBody(ClientType clientType) {
: AppLanguage.DEFAULT;

JSONObject client = new JSONObject();
client.put("hl", language.getLanguage());
client.put("clientName", clientType.clientName);
client.put("clientVersion", clientType.clientVersion);
client.put("deviceMake", clientType.deviceMake);
client.put("deviceModel", clientType.deviceModel);
client.put("clientName", clientType.clientName);
client.put("clientVersion", clientType.clientVersion);
client.put("osName", clientType.osName);
client.put("osVersion", clientType.osVersion);
if (clientType.androidSdkVersion != null) {
client.put("androidSdkVersion", clientType.androidSdkVersion);
if (clientType.gmscoreVersionCode != null) {
client.put("gmscoreVersionCode", clientType.gmscoreVersionCode);
}
if (clientType.chipset != null) {
client.put("chipset", clientType.chipset);
}
}
client.put("hl", language.getLanguage());
client.put("gl", Utils.getContext().getResources().getConfiguration().locale.getCountry());
TimeZone zone = TimeZone.getDefault();
client.put("timeZone", zone.getID());
client.put("utcOffsetMinutes", zone.getOffset(new Date().getTime()) / 60000);
context.put("client", client);

innerTubeBody.put("context", context);
innerTubeBody.put("contentCheckOk", true);
innerTubeBody.put("racyCheckOk", true);
innerTubeBody.put("videoId", "%s");
innerTubeBody.put("videoId", videoId);
} catch (JSONException e) {
Logger.printException(() -> "Failed to create innerTubeBody", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ private static void handleConnectionError(String toastMessage, @Nullable Excepti
}

@Nullable
private static HttpURLConnection send(ClientType clientType, String videoId,
private static HttpURLConnection send(ClientType clientType,
String videoId,
Map<String, String> playerHeaders,
boolean showErrorToasts) {
Objects.requireNonNull(clientType);
Expand Down Expand Up @@ -150,7 +151,7 @@ private static HttpURLConnection send(ClientType clientType, String videoId,
}
}

String innerTubeBody = String.format(PlayerRoutes.createInnertubeBody(clientType), videoId);
String innerTubeBody = PlayerRoutes.createInnertubeBody(clientType, videoId);
byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(requestBody.length);
connection.getOutputStream().write(requestBody);
Expand Down

0 comments on commit 2ee47ae

Please sign in to comment.