Skip to content

Commit 40059ed

Browse files
committed
fix: update iOS client, add visitor data to YouTube requests
1 parent 8e92227 commit 40059ed

2 files changed

Lines changed: 101 additions & 12 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.net.URLEncoder;
6+
import java.nio.charset.StandardCharsets;
7+
import java.util.Base64;
8+
9+
public class ProtoBuilder {
10+
ByteArrayOutputStream byteBuffer;
11+
12+
public ProtoBuilder() {
13+
this.byteBuffer = new ByteArrayOutputStream();
14+
}
15+
16+
public byte[] toBytes() {
17+
return byteBuffer.toByteArray();
18+
}
19+
20+
public String toUrlencodedBase64() {
21+
final String b64 = Base64.getUrlEncoder().encodeToString(toBytes());
22+
return URLEncoder.encode(b64, StandardCharsets.UTF_8);
23+
}
24+
25+
private void writeVarint(long val) {
26+
try {
27+
if (val == 0) {
28+
byteBuffer.write(new byte[]{(byte) 0});
29+
} else {
30+
while (val != 0) {
31+
byte b = (byte) (val & 0x7f);
32+
val >>= 7;
33+
34+
if (val != 0) {
35+
b |= (byte) 0x80;
36+
}
37+
byteBuffer.write(new byte[]{b});
38+
}
39+
}
40+
} catch (IOException e) {
41+
throw new RuntimeException(e);
42+
}
43+
}
44+
45+
private void field(final int field, final byte wire) {
46+
final long fbits = ((long) field) << 3;
47+
final long wbits = ((long) wire) & 0x07;
48+
final long val = fbits | wbits;
49+
writeVarint(val);
50+
}
51+
52+
public void varint(final int field, final long val) {
53+
field(field, (byte) 0);
54+
writeVarint(val);
55+
}
56+
57+
public void string(final int field, final String string) {
58+
final byte[] strBts = string.getBytes(StandardCharsets.UTF_8);
59+
bytes(field, strBts);
60+
}
61+
62+
public void bytes(final int field, final byte[] bytes) {
63+
field(field, (byte) 2);
64+
writeVarint(bytes.length);
65+
try {
66+
byteBuffer.write(bytes);
67+
} catch (IOException e) {
68+
throw new RuntimeException(e);
69+
}
70+
}
71+
}

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

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private YoutubeParsingHelper() {
174174
* Store page of the YouTube app</a>, in the {@code What’s New} section.
175175
* </p>
176176
*/
177-
private static final String IOS_YOUTUBE_CLIENT_VERSION = "19.28.1";
177+
private static final String IOS_YOUTUBE_CLIENT_VERSION = "19.45.4";
178178

179179
/**
180180
* The hardcoded client version used for InnerTube requests with the TV HTML5 embed client.
@@ -235,15 +235,15 @@ private YoutubeParsingHelper() {
235235
*
236236
* @see #IOS_USER_AGENT_VERSION
237237
*/
238-
private static final String IOS_OS_VERSION = "17.5.1.21F90";
238+
private static final String IOS_OS_VERSION = "18.1.0.22B83";
239239

240240
/**
241241
* Spoofing an iPhone 15 running iOS 17.5.1 with the hardcoded version of the iOS app. To be
242242
* used in the user agent for requests.
243243
*
244244
* @see #IOS_OS_VERSION
245245
*/
246-
private static final String IOS_USER_AGENT_VERSION = "17_5_1";
246+
private static final String IOS_USER_AGENT_VERSION = "18_1_0";
247247

248248
private static Random numberGenerator = new Random();
249249

@@ -303,6 +303,23 @@ public static boolean isY2ubeURL(@Nonnull final URL url) {
303303
return url.getHost().equalsIgnoreCase("y2u.be");
304304
}
305305

306+
public static String randomVisitorData(final ContentCountry country) {
307+
final ProtoBuilder pbE2 = new ProtoBuilder();
308+
pbE2.string(2, "");
309+
pbE2.varint(4, numberGenerator.nextInt(1, 256));
310+
311+
final ProtoBuilder pbE = new ProtoBuilder();
312+
pbE.string(1, country.getCountryCode());
313+
pbE.bytes(2, pbE2.toBytes());
314+
315+
final ProtoBuilder pb = new ProtoBuilder();
316+
pb.string(1, RandomStringFromAlphabetGenerator.generate(
317+
CONTENT_PLAYBACK_NONCE_ALPHABET, 11, numberGenerator));
318+
pb.varint(5, System.currentTimeMillis() / 1000 - numberGenerator.nextInt(600000));
319+
pb.bytes(6, pbE.toBytes());
320+
return pb.toUrlencodedBase64();
321+
}
322+
306323
/**
307324
* Parses the duration string of the video expecting ":" or "." as separators
308325
*
@@ -1164,10 +1181,14 @@ public static JsonBuilder<JsonObject> prepareDesktopJsonBuilder(
11641181
public static JsonBuilder<JsonObject> prepareDesktopJsonBuilder(
11651182
@Nonnull final Localization localization,
11661183
@Nonnull final ContentCountry contentCountry,
1167-
@Nullable final String visitorData)
1184+
@Nullable String visitorData)
11681185
throws IOException, ExtractionException {
1186+
if (visitorData == null) {
1187+
visitorData = randomVisitorData(contentCountry);
1188+
}
1189+
11691190
// @formatter:off
1170-
final JsonBuilder<JsonObject> builder = JsonObject.builder()
1191+
return JsonObject.builder()
11711192
.object("context")
11721193
.object("client")
11731194
.value("hl", localization.getLocalizationCode())
@@ -1176,13 +1197,9 @@ public static JsonBuilder<JsonObject> prepareDesktopJsonBuilder(
11761197
.value("clientVersion", getClientVersion())
11771198
.value("originalUrl", "https://www.youtube.com")
11781199
.value("platform", "DESKTOP")
1179-
.value("utcOffsetMinutes", 0);
1180-
1181-
if (visitorData != null) {
1182-
builder.value("visitorData", visitorData);
1183-
}
1184-
1185-
return builder.end()
1200+
.value("utcOffsetMinutes", 0)
1201+
.value("visitorData", visitorData)
1202+
.end()
11861203
.object("request")
11871204
.array("internalExperimentFlags")
11881205
.end()
@@ -1256,6 +1273,7 @@ public static JsonBuilder<JsonObject> prepareIosMobileJsonBuilder(
12561273
.value("platform", "MOBILE")
12571274
.value("osName", "iOS")
12581275
.value("osVersion", IOS_OS_VERSION)
1276+
.value("visitorData", randomVisitorData(contentCountry))
12591277
.value("hl", localization.getLocalizationCode())
12601278
.value("gl", contentCountry.getCountryCode())
12611279
.value("utcOffsetMinutes", 0)

0 commit comments

Comments
 (0)