From bd1db99934af6ef9eda6b4c069a6a6544e4d5597 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:46:35 +0900 Subject: [PATCH] fix(YouTube - Spoof client): Partial fix for watch history issue of brand accounts on iOS clients --- .../patches/misc/SpoofClientPatch.java | 100 ++++++++++++++++++ .../patches/misc/WatchHistoryPatch.java | 26 ++--- 2 files changed, 110 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java index 461e31c9e7..3a3ba25733 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java @@ -10,6 +10,8 @@ import org.apache.commons.lang3.StringUtils; +import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -252,6 +254,104 @@ public static boolean forceCreatePlaybackSpeedMenuReversed(boolean original) { return original; } + private static final Uri VIDEO_STATS_PLAYBACK_URI = Uri.parse("https://www.youtube.com/api/stats/playback?ns=yt&ver=2&final=1"); + + private static final String PARAM_DOC_ID = "docid"; + private static final String PARAM_LEN = "len"; + private static final String PARAM_CPN = "cpn"; + + private static final String PARAM_EVENT_ID = "ei"; + private static final String PARAM_VM = "vm"; + private static final String PARAM_OF = "of"; + + private static String mDocId; + private static String mLen; + private static String mCpn; + private static String mEventId; + private static String mVisitorMonitoringData; + private static String mOfParam; + + /** + * Injection point. + */ + public static void setCpn(String cpn) { + if (SPOOF_CLIENT_ENABLED && !Objects.equals(mCpn, cpn)) { + mCpn = cpn; + } + } + + /** + * Injection point. + * + * Parse parameters from the Tracking URL. + * See yuliskov/MediaServiceCore. + */ + public static void setTrackingUriParameter(Uri trackingUri) { + try { + if (SPOOF_CLIENT_ENABLED) { + String path = trackingUri.getPath(); + + if (path == null || (!path.contains("playback") && !path.contains("watchtime"))) { + return; + } + + mDocId = getQueryParameter(trackingUri, PARAM_DOC_ID); + mLen = getQueryParameter(trackingUri, PARAM_LEN); + mEventId = getQueryParameter(trackingUri, PARAM_EVENT_ID); + mVisitorMonitoringData = getQueryParameter(trackingUri, PARAM_VM); + mOfParam = getQueryParameter(trackingUri, PARAM_OF); + + Logger.printDebug(() -> "docId: " + mDocId + ", len: " + mLen + ", eventId: " + mEventId + ", visitorMonitoringData: " + mVisitorMonitoringData + ", of: " + mOfParam); + } + } catch (Exception ex) { + Logger.printException(() -> "setTrackingUriParameter failure", ex); + } + } + + /** + * Injection point. + * This only works on YouTube 18.38.45 or earlier. + * + * Build a Tracking URL. + * This does not include the last watched time. + * See yuliskov/MediaServiceCore. + */ + public static Uri overrideTrackingUrl(Uri trackingUrl) { + try { + if (SPOOF_CLIENT_ENABLED && + getSpoofClientType() == ClientType.IOS && + trackingUrl.toString().contains("youtube.com/csi") && + !StringUtils.isAnyEmpty(mDocId, mLen, mCpn, mEventId, mVisitorMonitoringData, mOfParam) + ) { + final Uri videoStatsPlaybackUri = VIDEO_STATS_PLAYBACK_URI + .buildUpon() + .appendQueryParameter(PARAM_DOC_ID, mDocId) + .appendQueryParameter(PARAM_LEN, mLen) + .appendQueryParameter(PARAM_CPN, mCpn) + .appendQueryParameter(PARAM_EVENT_ID, mEventId) + .appendQueryParameter(PARAM_VM, mVisitorMonitoringData) + .appendQueryParameter(PARAM_OF, mOfParam) + .build(); + + Logger.printDebug(() -> "Replaced: '" + trackingUrl + "' with: '" + videoStatsPlaybackUri + "'"); + return videoStatsPlaybackUri; + } + } catch (Exception ex) { + Logger.printException(() -> "overrideTrackingUrl failure", ex); + } + + return trackingUrl; + } + + private static String getQueryParameter(Uri uri, String key) { + List queryParams = uri.getQueryParameters(key); + if (queryParams == null || queryParams.isEmpty()) { + return ""; + } else { + return queryParams.get(0); + } + } + /** * Injection point. */ diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/WatchHistoryPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/WatchHistoryPatch.java index 84b5824aa7..327ab04fff 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/WatchHistoryPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/WatchHistoryPatch.java @@ -1,5 +1,7 @@ package app.revanced.integrations.youtube.patches.misc; +import android.net.Uri; + import app.revanced.integrations.shared.utils.Logger; import app.revanced.integrations.youtube.settings.Settings; @@ -12,32 +14,24 @@ public enum WatchHistoryType { BLOCK } - private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0"; - private static final String YOUTUBE_DOMAIN = "www.youtube.com"; - private static final String YOUTUBE_TRACKING_DOMAIN = "s.youtube.com"; + private static final Uri UNREACHABLE_HOST_URI = Uri.parse("https://127.0.0.0"); + private static final String WWW_TRACKING_URL_AUTHORITY = "www.youtube.com"; - public static String replaceTrackingUrl(String originalUrl) { + public static Uri replaceTrackingUrl(Uri trackingUrl) { final WatchHistoryType watchHistoryType = Settings.WATCH_HISTORY_TYPE.get(); if (watchHistoryType != WatchHistoryType.ORIGINAL) { try { - if (originalUrl.contains(YOUTUBE_TRACKING_DOMAIN)) { - if (watchHistoryType == WatchHistoryType.REPLACE) { - final String replacement = originalUrl.replaceAll(YOUTUBE_TRACKING_DOMAIN, YOUTUBE_DOMAIN); - if (!replacement.equals(originalUrl)) { - Logger.printDebug(() -> "Replaced: '" + originalUrl + "'\nwith: '" + replacement + "'"); - } - return replacement; - } else if (watchHistoryType == WatchHistoryType.BLOCK) { - Logger.printDebug(() -> "Blocking: " + originalUrl + " by returning: " + UNREACHABLE_HOST_URI_STRING); - return UNREACHABLE_HOST_URI_STRING; - } + if (watchHistoryType == WatchHistoryType.REPLACE) { + return trackingUrl.buildUpon().authority(WWW_TRACKING_URL_AUTHORITY).build(); + } else if (watchHistoryType == WatchHistoryType.BLOCK) { + return UNREACHABLE_HOST_URI; } } catch (Exception ex) { Logger.printException(() -> "replaceTrackingUrl failure", ex); } } - return originalUrl; + return trackingUrl; } }