Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YouTube] Potokens support implementation #1247

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,9 @@

<module name="SuppressWarningsHolder" />
</module>

<!-- https://github.com/checkstyle/checkstyle/issues/11581 -->
<module name="SuppressionSingleFilter">
<property name="message" value="Unknown tag '(implNote|implSpec|apiNote)'\."/>
</module>
</module>
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";
AudricV marked this conversation as resolved.
Show resolved Hide resolved
// 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";
AudricV marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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
@@ -0,0 +1,113 @@
package org.schabi.newpipe.extractor.services.youtube;

import javax.annotation.Nullable;

/**
* An interface to provide poTokens to YouTube player requests.
*
*
* <p>
* On some major clients, YouTube requires that the integrity of the device passes some checks to
* allow playback.
* </p>
*
* <p>
* These checks involve running codes to verify the integrity and using their result to generate a
* poToken (which likely stands for proof of origin token), using a visitor data ID for logged-out
* users.
* </p>
*
* <p>
* These tokens may have a role in triggering the sign in requirement.
* </p>
*
* <p>
* <b>Implementations of this interface are expected to be thread-safe, as they may be accessed by
* multiple threads.</b>
* </p>
*/
public interface PoTokenProvider {

/**
* Get a {@link PoTokenResult} specific to the desktop website, a.k.a. the WEB InnerTube client.
*
* <p>
* To be generated and valid, poTokens from this client must be generated using Google's
* BotGuard machine, which requires a JavaScript engine with a good DOM implementation. They
* must be added to adaptive/DASH streaming URLs with the {@code pot} parameter.
* </p>
*
* <p>
* Note that YouTube desktop website generates two poTokens:
* - one for the player requests poTokens, using the videoId as the minter value;
* - one for the streaming URLs, using a visitor data for logged-out users.
* </p>
*
* @return a {@link PoTokenResult} specific to the WEB InnerTube client
*/
@Nullable
PoTokenResult getWebClientPoToken(String videoId);

/**
* Get a {@link PoTokenResult} specific to the web embeds, a.k.a. the WEB_EMBEDDED_PLAYER
* InnerTube client.
*
* <p>
* To be generated and valid, poTokens from this client must be generated using Google's
* BotGuard machine, which requires a JavaScript engine with a good DOM implementation. They
* should be added to adaptive/DASH streaming URLs with the {@code pot} parameter and do not
* seem to be mandatory for now.
* </p>
*
* <p>
* As of writing, like the YouTube desktop website previously did, it generates only one
* poToken, sent in player requests and streaming URLs, using a visitor data for logged-out
* users.
* </p>
*
* @return a {@link PoTokenResult} specific to the WEB_EMBEDDED_PLAYER InnerTube client
*/
@Nullable
PoTokenResult getWebEmbedClientPoToken(String videoId);

/**
* Get a {@link PoTokenResult} specific to the Android app, a.k.a. the ANDROID InnerTube client.
*
* <p>
* Implementation details are not known, the app uses DroidGuard, a native virtual machine
* ran by Google Play Services for which its code is updated pretty frequently.
* </p>
*
* <p>
* As of writing, DroidGuard seem to check for the Android app signature and package ID, as
* unrooted YouTube patched with reVanced doesn't work without spoofing another InnerTube
* client while the rooted version works without any client spoofing.
* </p>
*
* <p>
* There should be only poToken needed, for the player requests.
* </p>
*
* @return a {@link PoTokenResult} specific to the ANDROID InnerTube client
*/
@Nullable
PoTokenResult getAndroidClientPoToken(String videoId);

/**
* Get a {@link PoTokenResult} specific to the Android app, a.k.a. the ANDROID InnerTube client.
AudricV marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>
* Implementation details are not really known, the app seem to use something called
* iosGuard which should be something similar to Android's DroidGuard. It may rely on Apple's
* attestation APIs.
* </p>
*
* <p>
* There should be only poToken needed, for the player requests.
* </p>
*
* @return a {@link PoTokenResult} specific to the IOS InnerTube client
*/
@Nullable
PoTokenResult getIosClientPoToken(String videoId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.schabi.newpipe.extractor.services.youtube;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;

public final class PoTokenResult {

/**
* The visitor data associated with a {@code poToken}.
*/
@Nonnull
public final String visitorData;

/**
* The {@code poToken} of a player request, a Protobuf object encoded as a base 64 string.
*/
@Nonnull
public final String playerRequestPoToken;

/**
* The {@code poToken} to be appended to streaming URLs, a Protobuf object encoded as a base
* 64 string.
*
* <p>
* It may be required on some clients such as HTML5 ones and may also differ from the player
* request {@code poToken}.
* </p>
*/
@Nullable
public final String streamingDataPoToken;

public PoTokenResult(@Nonnull final String visitorData,
@Nonnull final String playerRequestPoToken,
@Nullable final String streamingDataPoToken) {
this.visitorData = Objects.requireNonNull(visitorData);
this.playerRequestPoToken = Objects.requireNonNull(playerRequestPoToken);
this.streamingDataPoToken = streamingDataPoToken;
}
}
Loading
Loading