Skip to content

Commit 3691fc2

Browse files
AudricVStypox
authored andcommitted
[YouTube] Add an interface and a class to fetch and provide poTokens
1 parent fd82ec5 commit 3691fc2

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
import javax.annotation.Nullable;
4+
5+
/**
6+
* Interface to provide {@code poToken}s to YouTube player requests.
7+
*
8+
* <p>
9+
* On some major clients, YouTube requires that the integrity of the device passes some checks to
10+
* allow playback.
11+
* </p>
12+
*
13+
* <p>
14+
* These checks involve running codes to verify the integrity and using their result to generate
15+
* one or multiple {@code poToken}(s) (which stands for proof of origin token(s)).
16+
* </p>
17+
*
18+
* <p>
19+
* These tokens may have a role in triggering the sign in requirement.
20+
* </p>
21+
*
22+
* <p>
23+
* If an implementation does not want to return a {@code poToken} for a specific client, it <b>must
24+
* return {@code null}</b>.
25+
* </p>
26+
*
27+
* <p>
28+
* <b>Implementations of this interface are expected to be thread-safe, as they may be accessed by
29+
* multiple threads.</b>
30+
* </p>
31+
*/
32+
public interface PoTokenProvider {
33+
34+
/**
35+
* Get a {@link PoTokenResult} specific to the desktop website, a.k.a. the WEB InnerTube client.
36+
*
37+
* <p>
38+
* To be generated and valid, {@code poToken}s from this client must be generated using Google's
39+
* BotGuard machine, which requires a JavaScript engine with a good DOM implementation. They
40+
* must be added to adaptive/DASH streaming URLs with the {@code pot} parameter.
41+
* </p>
42+
*
43+
* <p>
44+
* Note that YouTube desktop website generates two {@code poToken}s:
45+
* - one for the player requests {@code poToken}s, using the videoId as the minter value;
46+
* - one for the streaming URLs, using a visitor data for logged-out users as the minter value.
47+
* </p>
48+
*
49+
* @return a {@link PoTokenResult} specific to the WEB InnerTube client
50+
*/
51+
@Nullable
52+
PoTokenResult getWebClientPoToken(String videoId);
53+
54+
/**
55+
* Get a {@link PoTokenResult} specific to the web embeds, a.k.a. the WEB_EMBEDDED_PLAYER
56+
* InnerTube client.
57+
*
58+
* <p>
59+
* To be generated and valid, {@code poToken}s from this client must be generated using Google's
60+
* BotGuard machine, which requires a JavaScript engine with a good DOM implementation. They
61+
* should be added to adaptive/DASH streaming URLs with the {@code pot} parameter.
62+
* </p>
63+
*
64+
* <p>
65+
* As of writing, like the YouTube desktop website previously did, it generates only one
66+
* {@code poToken}, sent in player requests and streaming URLs, using a visitor data for
67+
* logged-out users. {@code poToken}s do not seem to be mandatory for now on this client.
68+
* </p>
69+
*
70+
* @return a {@link PoTokenResult} specific to the WEB_EMBEDDED_PLAYER InnerTube client
71+
*/
72+
@Nullable
73+
PoTokenResult getWebEmbedClientPoToken(String videoId);
74+
75+
/**
76+
* Get a {@link PoTokenResult} specific to the Android app, a.k.a. the ANDROID InnerTube client.
77+
*
78+
* <p>
79+
* Implementation details are not known, the app uses DroidGuard, a downloaded native virtual
80+
* machine ran by Google Play Services for which its code is updated pretty frequently.
81+
* </p>
82+
*
83+
* <p>
84+
* As of writing, DroidGuard seem to check for the Android app signature and package ID, as
85+
* non-rooted YouTube patched with reVanced doesn't work without spoofing another InnerTube
86+
* client while the rooted version works without any client spoofing.
87+
* </p>
88+
*
89+
* <p>
90+
* There should be only one {@code poToken} needed for the player requests, it shouldn't be
91+
* required for regular adaptive URLs (i.e. not server adaptive bitrate (SABR) URLs). HLS
92+
* formats returned (only for premieres and running and post-live livestreams) in the client's
93+
* HLS manifest URL should work without {@code poToken}s.
94+
* </p>
95+
*
96+
* @return a {@link PoTokenResult} specific to the ANDROID InnerTube client
97+
*/
98+
@Nullable
99+
PoTokenResult getAndroidClientPoToken(String videoId);
100+
101+
/**
102+
* Get a {@link PoTokenResult} specific to the iOS app, a.k.a. the IOS InnerTube client.
103+
*
104+
* <p>
105+
* Implementation details are not known, the app seem to use something called iosGuard which
106+
* should be similar to Android's DroidGuard. It may rely on Apple's attestation APIs.
107+
* </p>
108+
*
109+
* <p>
110+
* As of writing, there should be only one {@code poToken} needed for the player requests, it
111+
* shouldn't be required for regular adaptive URLs (i.e. not server adaptive bitrate (SABR)
112+
* URLs). HLS formats returned in the client's HLS manifest URL should also work without a
113+
* {@code poToken}.
114+
* </p>
115+
*
116+
* @return a {@link PoTokenResult} specific to the IOS InnerTube client
117+
*/
118+
@Nullable
119+
PoTokenResult getIosClientPoToken(String videoId);
120+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
import javax.annotation.Nonnull;
4+
import javax.annotation.Nullable;
5+
import java.util.Objects;
6+
7+
/**
8+
* The result of a supported/successful {@code poToken} extraction request by a
9+
* {@link PoTokenProvider}.
10+
*/
11+
public final class PoTokenResult {
12+
13+
/**
14+
* The visitor data associated with a {@code poToken}.
15+
*/
16+
@Nonnull
17+
public final String visitorData;
18+
19+
/**
20+
* The {@code poToken} of a player request, a Protobuf object encoded as a base 64 string.
21+
*/
22+
@Nonnull
23+
public final String playerRequestPoToken;
24+
25+
/**
26+
* The {@code poToken} to be appended to streaming URLs, a Protobuf object encoded as a base
27+
* 64 string.
28+
*
29+
* <p>
30+
* It may be required on some clients such as HTML5 ones and may also differ from the player
31+
* request {@code poToken}.
32+
* </p>
33+
*/
34+
@Nullable
35+
public final String streamingDataPoToken;
36+
37+
/**
38+
* Construct a {@link PoTokenResult} instance.
39+
*
40+
* @param visitorData see {@link #visitorData}
41+
* @param playerRequestPoToken see {@link #playerRequestPoToken}
42+
* @param streamingDataPoToken see {@link #streamingDataPoToken}
43+
* @throws NullPointerException if a non-null parameter is null
44+
*/
45+
public PoTokenResult(@Nonnull final String visitorData,
46+
@Nonnull final String playerRequestPoToken,
47+
@Nullable final String streamingDataPoToken) {
48+
this.visitorData = Objects.requireNonNull(visitorData);
49+
this.playerRequestPoToken = Objects.requireNonNull(playerRequestPoToken);
50+
this.streamingDataPoToken = streamingDataPoToken;
51+
}
52+
}

0 commit comments

Comments
 (0)