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;
}
}