From 3691fc22c62667d007c0eb48ea06f44aa7754b42 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Tue, 28 Jan 2025 20:45:54 +0100 Subject: [PATCH] [YouTube] Add an interface and a class to fetch and provide poTokens --- .../services/youtube/PoTokenProvider.java | 120 ++++++++++++++++++ .../services/youtube/PoTokenResult.java | 52 ++++++++ 2 files changed, 172 insertions(+) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenProvider.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenResult.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenProvider.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenProvider.java new file mode 100644 index 000000000..a11a25bae --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenProvider.java @@ -0,0 +1,120 @@ +package org.schabi.newpipe.extractor.services.youtube; + +import javax.annotation.Nullable; + +/** + * Interface to provide {@code poToken}s to YouTube player requests. + * + *

+ * On some major clients, YouTube requires that the integrity of the device passes some checks to + * allow playback. + *

+ * + *

+ * These checks involve running codes to verify the integrity and using their result to generate + * one or multiple {@code poToken}(s) (which stands for proof of origin token(s)). + *

+ * + *

+ * These tokens may have a role in triggering the sign in requirement. + *

+ * + *

+ * If an implementation does not want to return a {@code poToken} for a specific client, it must + * return {@code null}. + *

+ * + *

+ * Implementations of this interface are expected to be thread-safe, as they may be accessed by + * multiple threads. + *

+ */ +public interface PoTokenProvider { + + /** + * Get a {@link PoTokenResult} specific to the desktop website, a.k.a. the WEB InnerTube client. + * + *

+ * To be generated and valid, {@code poToken}s 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. + *

+ * + *

+ * Note that YouTube desktop website generates two {@code poToken}s: + * - one for the player requests {@code poToken}s, using the videoId as the minter value; + * - one for the streaming URLs, using a visitor data for logged-out users as the minter value. + *

+ * + * @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. + * + *

+ * To be generated and valid, {@code poToken}s 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. + *

+ * + *

+ * As of writing, like the YouTube desktop website previously did, it generates only one + * {@code poToken}, sent in player requests and streaming URLs, using a visitor data for + * logged-out users. {@code poToken}s do not seem to be mandatory for now on this client. + *

+ * + * @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. + * + *

+ * Implementation details are not known, the app uses DroidGuard, a downloaded native virtual + * machine ran by Google Play Services for which its code is updated pretty frequently. + *

+ * + *

+ * As of writing, DroidGuard seem to check for the Android app signature and package ID, as + * non-rooted YouTube patched with reVanced doesn't work without spoofing another InnerTube + * client while the rooted version works without any client spoofing. + *

+ * + *

+ * There should be only one {@code poToken} needed for the player requests, it shouldn't be + * required for regular adaptive URLs (i.e. not server adaptive bitrate (SABR) URLs). HLS + * formats returned (only for premieres and running and post-live livestreams) in the client's + * HLS manifest URL should work without {@code poToken}s. + *

+ * + * @return a {@link PoTokenResult} specific to the ANDROID InnerTube client + */ + @Nullable + PoTokenResult getAndroidClientPoToken(String videoId); + + /** + * Get a {@link PoTokenResult} specific to the iOS app, a.k.a. the IOS InnerTube client. + * + *

+ * Implementation details are not known, the app seem to use something called iosGuard which + * should be similar to Android's DroidGuard. It may rely on Apple's attestation APIs. + *

+ * + *

+ * As of writing, there should be only one {@code poToken} needed for the player requests, it + * shouldn't be required for regular adaptive URLs (i.e. not server adaptive bitrate (SABR) + * URLs). HLS formats returned in the client's HLS manifest URL should also work without a + * {@code poToken}. + *

+ * + * @return a {@link PoTokenResult} specific to the IOS InnerTube client + */ + @Nullable + PoTokenResult getIosClientPoToken(String videoId); +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenResult.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenResult.java new file mode 100644 index 000000000..4ccbaf1ba --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/PoTokenResult.java @@ -0,0 +1,52 @@ +package org.schabi.newpipe.extractor.services.youtube; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; + +/** + * The result of a supported/successful {@code poToken} extraction request by a + * {@link PoTokenProvider}. + */ +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. + * + *

+ * It may be required on some clients such as HTML5 ones and may also differ from the player + * request {@code poToken}. + *

+ */ + @Nullable + public final String streamingDataPoToken; + + /** + * Construct a {@link PoTokenResult} instance. + * + * @param visitorData see {@link #visitorData} + * @param playerRequestPoToken see {@link #playerRequestPoToken} + * @param streamingDataPoToken see {@link #streamingDataPoToken} + * @throws NullPointerException if a non-null parameter is null + */ + 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; + } +}