Skip to content

Commit fd82ec5

Browse files
AudricVStypox
authored andcommitted
[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.
1 parent 186e32c commit fd82ec5

2 files changed

Lines changed: 144 additions & 102 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
final class ClientsConstants {
4+
private ClientsConstants() {
5+
}
6+
7+
// Common client fields
8+
9+
static final String DESKTOP_CLIENT_PLATFORM = "DESKTOP";
10+
static final String MOBILE_CLIENT_PLATFORM = "MOBILE";
11+
static final String WATCH_CLIENT_SCREEN = "WATCH";
12+
static final String EMBED_CLIENT_SCREEN = "EMBED";
13+
14+
// WEB (YouTube desktop) client fields
15+
16+
static final String WEB_CLIENT_ID = "1";
17+
static final String WEB_CLIENT_NAME = "WEB";
18+
/**
19+
* The client version for InnerTube requests with the {@code WEB} client, used as the last
20+
* fallback if the extraction of the real one failed.
21+
*/
22+
static final String WEB_HARDCODED_CLIENT_VERSION = "2.20250122.04.00";
23+
24+
// WEB_REMIX (YouTube Music) client fields
25+
26+
static final String WEB_REMIX_CLIENT_ID = "67";
27+
static final String WEB_REMIX_CLIENT_NAME = "WEB_REMIX";
28+
static final String WEB_REMIX_HARDCODED_CLIENT_VERSION = "1.20250122.01.00";
29+
30+
// TVHTML5 (YouTube on TVs and consoles using HTML5) client fields
31+
static final String TVHTML5_CLIENT_ID = "7";
32+
static final String TVHTML5_CLIENT_NAME = "TVHTML5";
33+
static final String TVHTML5_CLIENT_VERSION = "7.20250122.15.00";
34+
static final String TVHTML5_CLIENT_PLATFORM = "GAME_CONSOLE";
35+
static final String TVHTML5_DEVICE_MAKE = "Sony";
36+
static final String TVHTML5_DEVICE_MODEL_AND_OS_NAME = "PlayStation 4";
37+
// CHECKSTYLE:OFF
38+
static final String TVHTML5_USER_AGENT =
39+
"Mozilla/5.0 (PlayStation; PlayStation 4/12.00) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15";
40+
// CHECKSTYLE:ON
41+
42+
// WEB_EMBEDDED_PLAYER (YouTube embeds)
43+
44+
static final String WEB_EMBEDDED_CLIENT_ID = "56";
45+
static final String WEB_EMBEDDED_CLIENT_NAME = "WEB_EMBEDDED_PLAYER";
46+
static final String WEB_EMBEDDED_CLIENT_VERSION = "1.20250121.00.00";
47+
48+
// IOS (iOS YouTube app) client fields
49+
50+
static final String IOS_CLIENT_NAME = "IOS";
51+
52+
/**
53+
* The hardcoded client version of the iOS app used for InnerTube requests with this client.
54+
*
55+
* <p>
56+
* It can be extracted by getting the latest release version of the app on
57+
* <a href="https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664/">the App
58+
* Store page of the YouTube app</a>, in the {@code What’s New} section.
59+
* </p>
60+
*/
61+
static final String IOS_CLIENT_VERSION = "20.03.02";
62+
63+
/**
64+
* The device machine id for the iPhone 15 Pro Max, used to get 60fps with the {@code iOS}
65+
* client.
66+
*
67+
* <p>
68+
* See <a href="https://gist.github.com/adamawolf/3048717">this GitHub Gist</a> for more
69+
* information.
70+
* </p>
71+
*/
72+
static final String IOS_DEVICE_MODEL = "iPhone16,2";
73+
74+
/**
75+
* The iOS version to be used in JSON POST requests, the one of an iPhone 15 Pro Max running
76+
* iOS 18.2.1 with the hardcoded version of the iOS app (for the {@code "osVersion"} field).
77+
*
78+
* <p>
79+
* The value of this field seems to use the following structure:
80+
* "iOS major version.minor version.patch version.build version", where
81+
* "patch version" is equal to 0 if it isn't set
82+
* The build version corresponding to the iOS version used can be found on
83+
* <a href="https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max">
84+
* https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max</a>
85+
* </p>
86+
*
87+
* @see #IOS_USER_AGENT_VERSION
88+
*/
89+
static final String IOS_OS_VERSION = "18.2.1.22C161";
90+
91+
/**
92+
* The iOS version to be used in the HTTP user agent for requests.
93+
*
94+
* <p>
95+
* This should be the same of as {@link #IOS_OS_VERSION}.
96+
* </p>
97+
*
98+
* @see #IOS_OS_VERSION
99+
*/
100+
static final String IOS_USER_AGENT_VERSION = "18_2_1";
101+
102+
// ANDROID (Android YouTube app) client fields
103+
104+
static final String ANDROID_CLIENT_NAME = "ANDROID";
105+
106+
/**
107+
* The hardcoded client version of the Android app used for InnerTube requests with this
108+
* client.
109+
*
110+
* <p>
111+
* It can be extracted by getting the latest release version of the app in an APK repository
112+
* such as <a href="https://www.apkmirror.com/apk/google-inc/youtube/">APKMirror</a>.
113+
* </p>
114+
*/
115+
static final String ANDROID_CLIENT_VERSION = "19.28.35";
116+
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java

Lines changed: 28 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@
2121
package org.schabi.newpipe.extractor.services.youtube;
2222

2323
import static org.schabi.newpipe.extractor.NewPipe.getDownloader;
24+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.ANDROID_CLIENT_VERSION;
25+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.DESKTOP_CLIENT_PLATFORM;
26+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_CLIENT_VERSION;
27+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_DEVICE_MODEL;
28+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.IOS_USER_AGENT_VERSION;
29+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_ID;
30+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_CLIENT_NAME;
31+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_HARDCODED_CLIENT_VERSION;
32+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_REMIX_CLIENT_ID;
33+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_REMIX_CLIENT_NAME;
34+
import static org.schabi.newpipe.extractor.services.youtube.ClientsConstants.WEB_REMIX_HARDCODED_CLIENT_VERSION;
2435
import static org.schabi.newpipe.extractor.utils.Utils.HTTP;
2536
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
2637
import static org.schabi.newpipe.extractor.utils.Utils.getStringResultFromRegexArray;
@@ -144,55 +155,11 @@ private YoutubeParsingHelper() {
144155
*/
145156
public static final String RACY_CHECK_OK = "racyCheckOk";
146157

147-
/**
148-
* The hardcoded client ID used for InnerTube requests with the {@code WEB} client.
149-
*/
150-
private static final String WEB_CLIENT_ID = "1";
151-
152-
/**
153-
* The client version for InnerTube requests with the {@code WEB} client, used as the last
154-
* fallback if the extraction of the real one failed.
155-
*/
156-
private static final String HARDCODED_CLIENT_VERSION = "2.20240718.01.00";
157-
158-
/**
159-
* The hardcoded client version of the Android app used for InnerTube requests with this
160-
* client.
161-
*
162-
* <p>
163-
* It can be extracted by getting the latest release version of the app in an APK repository
164-
* such as <a href="https://www.apkmirror.com/apk/google-inc/youtube/">APKMirror</a>.
165-
* </p>
166-
*/
167-
private static final String ANDROID_YOUTUBE_CLIENT_VERSION = "19.28.35";
168-
169-
/**
170-
* The hardcoded client version of the iOS app used for InnerTube requests with this client.
171-
*
172-
* <p>
173-
* It can be extracted by getting the latest release version of the app on
174-
* <a href="https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664/">the App
175-
* Store page of the YouTube app</a>, in the {@code What’s New} section.
176-
* </p>
177-
*/
178-
private static final String IOS_YOUTUBE_CLIENT_VERSION = "20.03.02";
179-
180158
/**
181159
* The hardcoded client version used for InnerTube requests with the TV HTML5 embed client.
182160
*/
183161
private static final String TVHTML5_SIMPLY_EMBED_CLIENT_VERSION = "2.0";
184162

185-
/**
186-
* The hardcoded client ID used for InnerTube requests with the YouTube Music desktop client.
187-
*/
188-
private static final String YOUTUBE_MUSIC_CLIENT_ID = "67";
189-
190-
/**
191-
* The hardcoded client version used for InnerTube requests with the YouTube Music desktop
192-
* client.
193-
*/
194-
private static final String HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION = "1.20240715.01.00";
195-
196163
private static String clientVersion;
197164

198165
private static String youtubeMusicClientVersion;
@@ -212,41 +179,6 @@ private YoutubeParsingHelper() {
212179
private static final String CONTENT_PLAYBACK_NONCE_ALPHABET =
213180
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
214181

215-
/**
216-
* The device machine id for the iPhone 15 Pro Max,
217-
* used to get 60fps with the {@code iOS} client.
218-
*
219-
* <p>
220-
* See <a href="https://gist.github.com/adamawolf/3048717">this GitHub Gist</a> for more
221-
* information.
222-
* </p>
223-
*/
224-
private static final String IOS_DEVICE_MODEL = "iPhone16,2";
225-
226-
/**
227-
* Spoofing an iPhone 15 Pro Max running iOS 18.2.1 with the hardcoded version of the iOS app.
228-
* To be used for the {@code "osVersion"} field in JSON POST requests.
229-
* <p>
230-
* The value of this field seems to use the following structure:
231-
* "iOS major version.minor version.patch version.build version", where
232-
* "patch version" is equal to 0 if it isn't set
233-
* The build version corresponding to the iOS version used can be found on
234-
* <a href="https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max">
235-
* https://theapplewiki.com/wiki/Firmware/iPhone/18.x#iPhone_15_Pro_Max</a>
236-
* </p>
237-
*
238-
* @see #IOS_USER_AGENT_VERSION
239-
*/
240-
private static final String IOS_OS_VERSION = "18.2.1.22C161";
241-
242-
/**
243-
* Spoofing an iPhone 15 Pro Max running iOS 18.2.1 with the hardcoded version of the iOS app.
244-
* To be used in the user agent for requests.
245-
*
246-
* @see #IOS_OS_VERSION
247-
*/
248-
private static final String IOS_USER_AGENT_VERSION = "18_2_1";
249-
250182
private static Random numberGenerator = new Random();
251183

252184
private static final String FEED_BASE_CHANNEL_ID =
@@ -561,9 +493,9 @@ public static boolean isHardcodedClientVersionValid()
561493
.object("client")
562494
.value("hl", "en-GB")
563495
.value("gl", "GB")
564-
.value("clientName", "WEB")
565-
.value("clientVersion", HARDCODED_CLIENT_VERSION)
566-
.value("platform", "DESKTOP")
496+
.value("clientName", WEB_CLIENT_NAME)
497+
.value("clientVersion", WEB_HARDCODED_CLIENT_VERSION)
498+
.value("platform", DESKTOP_CLIENT_PLATFORM)
567499
.value("utcOffsetMinutes", 0)
568500
.end()
569501
.object("request")
@@ -581,7 +513,7 @@ public static boolean isHardcodedClientVersionValid()
581513
.end().done().getBytes(StandardCharsets.UTF_8);
582514
// @formatter:on
583515

584-
final var headers = getClientHeaders(WEB_CLIENT_ID, HARDCODED_CLIENT_VERSION);
516+
final var headers = getClientHeaders(WEB_CLIENT_ID, WEB_HARDCODED_CLIENT_VERSION);
585517

586518
// This endpoint is fetched by the YouTube website to get the items of its main menu and is
587519
// pretty lightweight (around 30kB)
@@ -705,7 +637,7 @@ public static String getClientVersion() throws IOException, ExtractionException
705637

706638
// Fallback to the hardcoded one if it is valid
707639
if (isHardcodedClientVersionValid()) {
708-
clientVersion = HARDCODED_CLIENT_VERSION;
640+
clientVersion = WEB_HARDCODED_CLIENT_VERSION;
709641
return clientVersion;
710642
}
711643

@@ -752,11 +684,11 @@ public static boolean isHardcodedYoutubeMusicClientVersionValid() throws IOExcep
752684
.object()
753685
.object("context")
754686
.object("client")
755-
.value("clientName", "WEB_REMIX")
756-
.value("clientVersion", HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION)
687+
.value("clientName", WEB_REMIX_CLIENT_NAME)
688+
.value("clientVersion", WEB_REMIX_HARDCODED_CLIENT_VERSION)
757689
.value("hl", "en-GB")
758690
.value("gl", "GB")
759-
.value("platform", "DESKTOP")
691+
.value("platform", DESKTOP_CLIENT_PLATFORM)
760692
.value("utcOffsetMinutes", 0)
761693
.end()
762694
.object("request")
@@ -775,8 +707,7 @@ public static boolean isHardcodedYoutubeMusicClientVersionValid() throws IOExcep
775707
// @formatter:on
776708

777709
final var headers = new HashMap<>(getOriginReferrerHeaders(YOUTUBE_MUSIC_URL));
778-
headers.putAll(getClientHeaders(YOUTUBE_MUSIC_CLIENT_ID,
779-
HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION));
710+
headers.putAll(getClientHeaders(WEB_REMIX_CLIENT_ID, WEB_HARDCODED_CLIENT_VERSION));
780711

781712
final Response response = getDownloader().postWithContentTypeJson(url, headers, json);
782713
// Ensure to have a valid response
@@ -789,7 +720,7 @@ public static String getYoutubeMusicClientVersion()
789720
return youtubeMusicClientVersion;
790721
}
791722
if (isHardcodedYoutubeMusicClientVersionValid()) {
792-
youtubeMusicClientVersion = HARDCODED_YOUTUBE_MUSIC_CLIENT_VERSION;
723+
youtubeMusicClientVersion = WEB_REMIX_HARDCODED_CLIENT_VERSION;
793724
return youtubeMusicClientVersion;
794725
}
795726

@@ -1196,10 +1127,10 @@ public static JsonBuilder<JsonObject> prepareDesktopJsonBuilder(
11961127
.object("client")
11971128
.value("hl", localization.getLocalizationCode())
11981129
.value("gl", contentCountry.getCountryCode())
1199-
.value("clientName", "WEB")
1130+
.value("clientName", WEB_CLIENT_NAME)
12001131
.value("clientVersion", getClientVersion())
12011132
.value("originalUrl", "https://www.youtube.com")
1202-
.value("platform", "DESKTOP")
1133+
.value("platform", DESKTOP_CLIENT_PLATFORM)
12031134
.value("utcOffsetMinutes", 0)
12041135
.value("visitorData", vData)
12051136
.end()
@@ -1391,9 +1322,8 @@ public static byte[] createTvHtml5EmbedPlayerBody(
13911322
*/
13921323
@Nonnull
13931324
public static String getAndroidUserAgent(@Nullable final Localization localization) {
1394-
// Spoofing an Android 14 device with the hardcoded version of the Android app
1395-
return "com.google.android.youtube/" + ANDROID_YOUTUBE_CLIENT_VERSION
1396-
+ " (Linux; U; Android 14; "
1325+
return "com.google.android.youtube/" + ANDROID_CLIENT_VERSION
1326+
+ " (Linux; U; Android 15; "
13971327
+ (localization != null ? localization : Localization.DEFAULT).getCountryCode()
13981328
+ ") gzip";
13991329
}
@@ -1413,11 +1343,8 @@ public static String getAndroidUserAgent(@Nullable final Localization localizati
14131343
*/
14141344
@Nonnull
14151345
public static String getIosUserAgent(@Nullable final Localization localization) {
1416-
// Spoofing an iPhone 15 Pro Max running iOS 18.1.0
1417-
// with the hardcoded version of the iOS app
1418-
return "com.google.ios.youtube/" + IOS_YOUTUBE_CLIENT_VERSION
1419-
+ "(" + IOS_DEVICE_MODEL + "; U; CPU iOS "
1420-
+ IOS_USER_AGENT_VERSION + " like Mac OS X; "
1346+
return "com.google.ios.youtube/" + IOS_CLIENT_VERSION + "(" + IOS_DEVICE_MODEL
1347+
+ "; U; CPU iOS " + IOS_USER_AGENT_VERSION + " like Mac OS X; "
14211348
+ (localization != null ? localization : Localization.DEFAULT).getCountryCode()
14221349
+ ")";
14231350
}
@@ -1428,8 +1355,7 @@ public static String getIosUserAgent(@Nullable final Localization localization)
14281355
@Nonnull
14291356
public static Map<String, List<String>> getYoutubeMusicHeaders() {
14301357
final var headers = new HashMap<>(getOriginReferrerHeaders(YOUTUBE_MUSIC_URL));
1431-
headers.putAll(getClientHeaders(YOUTUBE_MUSIC_CLIENT_ID,
1432-
youtubeMusicClientVersion));
1358+
headers.putAll(getClientHeaders(WEB_REMIX_CLIENT_ID, youtubeMusicClientVersion));
14331359
return headers;
14341360
}
14351361

0 commit comments

Comments
 (0)