fix(YouTube - Hook download actions, Overlay buttons): Queue manager fails to identify brand account

This commit is contained in:
inotia00 2025-03-30 18:22:29 +09:00
parent edccd61e6b
commit f19dd54026
8 changed files with 165 additions and 81 deletions

View File

@ -279,6 +279,7 @@ object InnerTubeRequestBody {
route: CompiledRoute,
clientType: YouTubeAppClient.ClientType,
requestHeader: Map<String, String>? = null,
dataSyncId: String? = null,
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
) = getInnerTubeResponseConnectionFromRoute(
@ -288,6 +289,7 @@ object InnerTubeRequestBody {
clientVersion = clientType.clientVersion,
supportsCookies = clientType.supportsCookies,
requestHeader = requestHeader,
dataSyncId = dataSyncId,
connectTimeout = connectTimeout,
readTimeout = readTimeout,
)
@ -297,6 +299,7 @@ object InnerTubeRequestBody {
route: CompiledRoute,
clientType: YouTubeWebClient.ClientType,
requestHeader: Map<String, String>? = null,
dataSyncId: String? = null,
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
) = getInnerTubeResponseConnectionFromRoute(
@ -305,6 +308,7 @@ object InnerTubeRequestBody {
clientId = clientType.id.toString(),
clientVersion = clientType.clientVersion,
requestHeader = requestHeader,
dataSyncId = dataSyncId,
connectTimeout = connectTimeout,
readTimeout = readTimeout,
)
@ -317,6 +321,7 @@ object InnerTubeRequestBody {
clientVersion: String,
supportsCookies: Boolean = true,
requestHeader: Map<String, String>? = null,
dataSyncId: String? = null,
connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS,
): HttpURLConnection {
@ -348,6 +353,11 @@ object InnerTubeRequestBody {
}
}
// Used to identify brand accounts
if (dataSyncId != null && dataSyncId.isNotEmpty()) {
connection.setRequestProperty("X-Goog-PageId", dataSyncId)
}
return connection
}

View File

@ -1,5 +1,7 @@
package app.revanced.extension.youtube.patches.utils;
import static app.revanced.extension.shared.utils.StringRef.str;
import android.content.Context;
import android.view.KeyEvent;
import android.widget.LinearLayout;
@ -47,43 +49,52 @@ public class PlaylistPatch extends VideoUtils {
private static Context mContext;
private static volatile String authorization = "";
private static volatile boolean isIncognito = false;
public static volatile String dataSyncId = "";
public static volatile boolean isIncognito = false;
private static volatile Map<String, String> requestHeader;
private static volatile String playlistId = "";
private static volatile String videoId = "";
private static final String checkFailedAuth =
ResourceUtils.getString("revanced_queue_manager_check_failed_auth");
private static final String checkFailedPlaylistId =
ResourceUtils.getString("revanced_queue_manager_check_failed_playlist_id");
private static final String checkFailedQueue =
ResourceUtils.getString("revanced_queue_manager_check_failed_queue");
private static final String checkFailedVideoId =
ResourceUtils.getString("revanced_queue_manager_check_failed_video_id");
private static final String checkFailedGeneric =
ResourceUtils.getString("revanced_queue_manager_check_failed_generic");
private static String checkFailedAuth;
private static String checkFailedPlaylistId;
private static String checkFailedQueue;
private static String checkFailedVideoId;
private static String checkFailedGeneric;
private static final String fetchFailedAdd =
ResourceUtils.getString("revanced_queue_manager_fetch_failed_add");
private static final String fetchFailedCreate =
ResourceUtils.getString("revanced_queue_manager_fetch_failed_create");
private static final String fetchFailedDelete =
ResourceUtils.getString("revanced_queue_manager_fetch_failed_delete");
private static final String fetchFailedRemove =
ResourceUtils.getString("revanced_queue_manager_fetch_failed_remove");
private static final String fetchFailedSave =
ResourceUtils.getString("revanced_queue_manager_fetch_failed_save");
private static String fetchFailedAdd;
private static String fetchFailedCreate;
private static String fetchFailedDelete;
private static String fetchFailedRemove;
private static String fetchFailedSave;
private static final String fetchSucceededAdd =
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_add");
private static final String fetchSucceededCreate =
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_create");
private static final String fetchSucceededDelete =
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_delete");
private static final String fetchSucceededRemove =
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_remove");
private static final String fetchSucceededSave =
ResourceUtils.getString("revanced_queue_manager_fetch_succeeded_save");
private static String fetchSucceededAdd;
private static String fetchSucceededCreate;
private static String fetchSucceededDelete;
private static String fetchSucceededRemove;
private static String fetchSucceededSave;
static {
Context mContext = Utils.getContext();
if (mContext != null && mContext.getResources() != null) {
checkFailedAuth = str("revanced_queue_manager_check_failed_auth");
checkFailedPlaylistId = str("revanced_queue_manager_check_failed_playlist_id");
checkFailedQueue = str("revanced_queue_manager_check_failed_queue");
checkFailedVideoId = str("revanced_queue_manager_check_failed_video_id");
checkFailedGeneric = str("revanced_queue_manager_check_failed_generic");
fetchFailedAdd = str("revanced_queue_manager_fetch_failed_add");
fetchFailedCreate = str("revanced_queue_manager_fetch_failed_create");
fetchFailedDelete = str("revanced_queue_manager_fetch_failed_delete");
fetchFailedRemove = str("revanced_queue_manager_fetch_failed_remove");
fetchFailedSave = str("revanced_queue_manager_fetch_failed_save");
fetchSucceededAdd = str("revanced_queue_manager_fetch_succeeded_add");
fetchSucceededCreate = str("revanced_queue_manager_fetch_succeeded_create");
fetchSucceededDelete = str("revanced_queue_manager_fetch_succeeded_delete");
fetchSucceededRemove = str("revanced_queue_manager_fetch_succeeded_remove");
fetchSucceededSave = str("revanced_queue_manager_fetch_succeeded_save");
}
}
@GuardedBy("itself")
private static final BidiMap<String, String> lastVideoIds = new DualHashBidiMap<>();
@ -118,15 +129,6 @@ public class PlaylistPatch extends VideoUtils {
}
}
/**
* Injection point.
*/
public static void setIncognitoStatus(boolean incognito) {
if (QUEUE_MANAGER) {
isIncognito = incognito;
}
}
/**
* Injection point.
*/
@ -171,7 +173,7 @@ public class PlaylistPatch extends VideoUtils {
* Invoked by extension.
*/
public static void prepareDialogBuilder(@NonNull String currentVideoId) {
if (authorization.isEmpty() || isIncognito) {
if (authorization.isEmpty() || (dataSyncId.isEmpty() && isIncognito)) {
handleCheckError(checkFailedAuth);
return;
}
@ -217,7 +219,7 @@ public class PlaylistPatch extends VideoUtils {
String currentVideoId = videoId;
synchronized (lastVideoIds) {
if (currentPlaylistId.isEmpty()) { // Queue is empty, create new playlist.
CreatePlaylistRequest.fetchRequestIfNeeded(currentVideoId, requestHeader);
CreatePlaylistRequest.fetchRequestIfNeeded(currentVideoId, requestHeader, dataSyncId);
runOnMainThreadDelayed(() -> {
CreatePlaylistRequest request = CreatePlaylistRequest.getRequestForVideoId(currentVideoId);
if (request != null) {
@ -241,7 +243,7 @@ public class PlaylistPatch extends VideoUtils {
}, 1000);
} else { // Queue is not empty, add or remove video.
String setVideoId = lastVideoIds.get(currentVideoId);
EditPlaylistRequest.fetchRequestIfNeeded(currentVideoId, currentPlaylistId, setVideoId, requestHeader);
EditPlaylistRequest.fetchRequestIfNeeded(currentVideoId, currentPlaylistId, setVideoId, requestHeader, dataSyncId);
runOnMainThreadDelayed(() -> {
EditPlaylistRequest request = EditPlaylistRequest.getRequestForVideoId(currentVideoId);
@ -286,7 +288,7 @@ public class PlaylistPatch extends VideoUtils {
return;
}
try {
GetPlaylistsRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader);
GetPlaylistsRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader, dataSyncId);
runOnMainThreadDelayed(() -> {
GetPlaylistsRequest request = GetPlaylistsRequest.getRequestForPlaylistId(currentPlaylistId);
if (request != null) {
@ -328,7 +330,7 @@ public class PlaylistPatch extends VideoUtils {
handleCheckError(checkFailedPlaylistId);
return;
}
SavePlaylistRequest.fetchRequestIfNeeded(playlistId, libraryId, requestHeader);
SavePlaylistRequest.fetchRequestIfNeeded(playlistId, libraryId, requestHeader, dataSyncId);
runOnMainThreadDelayed(() -> {
SavePlaylistRequest request = SavePlaylistRequest.getRequestForLibraryId(libraryId);
@ -354,7 +356,7 @@ public class PlaylistPatch extends VideoUtils {
return;
}
try {
DeletePlaylistRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader);
DeletePlaylistRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader, dataSyncId);
runOnMainThreadDelayed(() -> {
DeletePlaylistRequest request = DeletePlaylistRequest.getRequestForPlaylistId(currentPlaylistId);
if (request != null) {

View File

@ -24,9 +24,14 @@ import java.util.concurrent.TimeoutException
class CreatePlaylistRequest private constructor(
private val videoId: String,
private val requestHeader: Map<String, String>,
private val dataSyncId: String,
) {
private val future: Future<Pair<String, String>> = Utils.submitOnBackgroundThread {
fetch(videoId, requestHeader)
fetch(
videoId,
requestHeader,
dataSyncId,
)
}
val playlistId: Pair<String, String>?
@ -75,11 +80,19 @@ class CreatePlaylistRequest private constructor(
}
@JvmStatic
fun fetchRequestIfNeeded(videoId: String, requestHeader: Map<String, String>) {
fun fetchRequestIfNeeded(
videoId: String,
requestHeader: Map<String, String>,
dataSyncId: String,
) {
Objects.requireNonNull(videoId)
synchronized(cache) {
if (!cache.containsKey(videoId)) {
cache[videoId] = CreatePlaylistRequest(videoId, requestHeader)
cache[videoId] = CreatePlaylistRequest(
videoId,
requestHeader,
dataSyncId,
)
}
}
}
@ -97,7 +110,8 @@ class CreatePlaylistRequest private constructor(
private fun sendCreatePlaylistRequest(
videoId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): JSONObject? {
Objects.requireNonNull(videoId)
@ -112,6 +126,7 @@ class CreatePlaylistRequest private constructor(
CREATE_PLAYLIST,
clientType,
requestHeader,
dataSyncId,
)
val requestBody = createPlaylistRequestBody(videoId = videoId)
@ -143,7 +158,8 @@ class CreatePlaylistRequest private constructor(
private fun sendSetVideoIdRequest(
videoId: String,
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): JSONObject? {
Objects.requireNonNull(playlistId)
@ -157,7 +173,8 @@ class CreatePlaylistRequest private constructor(
val connection = getInnerTubeResponseConnectionFromRoute(
GET_SET_VIDEO_ID,
clientType,
requestHeader
requestHeader,
dataSyncId,
)
val requestBody = createApplicationRequestBody(
@ -232,13 +249,23 @@ class CreatePlaylistRequest private constructor(
private fun fetch(
videoId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): Pair<String, String>? {
val createPlaylistJson = sendCreatePlaylistRequest(videoId, requestHeader)
val createPlaylistJson = sendCreatePlaylistRequest(
videoId,
requestHeader,
dataSyncId
)
if (createPlaylistJson != null) {
val playlistId = parseCreatePlaylistResponse(createPlaylistJson)
if (playlistId != null) {
val setVideoIdJson = sendSetVideoIdRequest(videoId, playlistId, requestHeader)
val setVideoIdJson = sendSetVideoIdRequest(
videoId,
playlistId,
requestHeader,
dataSyncId
)
if (setVideoIdJson != null) {
val setVideoId = parseSetVideoIdResponse(setVideoIdJson)
if (setVideoId != null) {

View File

@ -22,11 +22,13 @@ import java.util.concurrent.TimeoutException
class DeletePlaylistRequest private constructor(
private val playlistId: String,
private val requestHeader: Map<String, String>,
private val dataSyncId: String,
) {
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
fetch(
playlistId,
requestHeader,
dataSyncId,
)
}
@ -78,14 +80,16 @@ class DeletePlaylistRequest private constructor(
@JvmStatic
fun fetchRequestIfNeeded(
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
) {
Objects.requireNonNull(playlistId)
synchronized(cache) {
if (!cache.containsKey(playlistId)) {
cache[playlistId] = DeletePlaylistRequest(
playlistId,
requestHeader
requestHeader,
dataSyncId,
)
}
}
@ -104,7 +108,8 @@ class DeletePlaylistRequest private constructor(
private fun sendRequest(
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): JSONObject? {
Objects.requireNonNull(playlistId)
@ -118,7 +123,8 @@ class DeletePlaylistRequest private constructor(
val connection = getInnerTubeResponseConnectionFromRoute(
DELETE_PLAYLIST,
clientType,
requestHeader
requestHeader,
dataSyncId
)
val requestBody = deletePlaylistRequestBody(playlistId)
@ -163,9 +169,14 @@ class DeletePlaylistRequest private constructor(
private fun fetch(
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): Boolean? {
val json = sendRequest(playlistId, requestHeader)
val json = sendRequest(
playlistId,
requestHeader,
dataSyncId,
)
if (json != null) {
return parseResponse(json)
}

View File

@ -25,6 +25,7 @@ class EditPlaylistRequest private constructor(
private val playlistId: String,
private val setVideoId: String?,
private val requestHeader: Map<String, String>,
private val dataSyncId: String,
) {
private val future: Future<String> = Utils.submitOnBackgroundThread {
fetch(
@ -32,6 +33,7 @@ class EditPlaylistRequest private constructor(
playlistId,
setVideoId,
requestHeader,
dataSyncId,
)
}
@ -92,7 +94,8 @@ class EditPlaylistRequest private constructor(
videoId: String,
playlistId: String,
setVideoId: String?,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
) {
Objects.requireNonNull(videoId)
synchronized(cache) {
@ -101,7 +104,8 @@ class EditPlaylistRequest private constructor(
videoId,
playlistId,
setVideoId,
requestHeader
requestHeader,
dataSyncId,
)
}
}
@ -122,7 +126,8 @@ class EditPlaylistRequest private constructor(
videoId: String,
playlistId: String,
setVideoId: String?,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): JSONObject? {
Objects.requireNonNull(videoId)
@ -136,7 +141,8 @@ class EditPlaylistRequest private constructor(
val connection = getInnerTubeResponseConnectionFromRoute(
EDIT_PLAYLIST,
clientType,
requestHeader
requestHeader,
dataSyncId
)
val requestBody = editPlaylistRequestBody(
@ -199,9 +205,16 @@ class EditPlaylistRequest private constructor(
videoId: String,
playlistId: String,
setVideoId: String?,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): String? {
val json = sendRequest(videoId, playlistId, setVideoId, requestHeader)
val json = sendRequest(
videoId,
playlistId,
setVideoId,
requestHeader,
dataSyncId,
)
if (json != null) {
return parseResponse(json, StringUtils.isNotEmpty(setVideoId))
}

View File

@ -22,11 +22,13 @@ import java.util.concurrent.TimeoutException
class GetPlaylistsRequest private constructor(
private val playlistId: String,
private val requestHeader: Map<String, String>,
private val dataSyncId: String,
) {
private val future: Future<Array<Pair<String, String>>> = Utils.submitOnBackgroundThread {
fetch(
playlistId,
requestHeader,
dataSyncId,
)
}
@ -78,14 +80,16 @@ class GetPlaylistsRequest private constructor(
@JvmStatic
fun fetchRequestIfNeeded(
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
) {
Objects.requireNonNull(playlistId)
synchronized(cache) {
if (!cache.containsKey(playlistId)) {
cache[playlistId] = GetPlaylistsRequest(
playlistId,
requestHeader
requestHeader,
dataSyncId,
)
}
}
@ -104,7 +108,8 @@ class GetPlaylistsRequest private constructor(
private fun sendRequest(
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): JSONObject? {
Objects.requireNonNull(playlistId)
@ -118,7 +123,8 @@ class GetPlaylistsRequest private constructor(
val connection = getInnerTubeResponseConnectionFromRoute(
GET_PLAYLISTS,
clientType,
requestHeader
requestHeader,
dataSyncId
)
val requestBody = getPlaylistsRequestBody(playlistId)
@ -202,9 +208,10 @@ class GetPlaylistsRequest private constructor(
private fun fetch(
playlistId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): Array<Pair<String, String>>? {
val json = sendRequest(playlistId, requestHeader)
val json = sendRequest(playlistId, requestHeader, dataSyncId)
if (json != null) {
return parseResponse(json)
}

View File

@ -23,12 +23,14 @@ class SavePlaylistRequest private constructor(
private val playlistId: String,
private val libraryId: String,
private val requestHeader: Map<String, String>,
private val dataSyncId: String,
) {
private val future: Future<Boolean> = Utils.submitOnBackgroundThread {
fetch(
playlistId,
libraryId,
requestHeader,
dataSyncId,
)
}
@ -81,14 +83,16 @@ class SavePlaylistRequest private constructor(
fun fetchRequestIfNeeded(
playlistId: String,
libraryId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
) {
Objects.requireNonNull(playlistId)
synchronized(cache) {
cache[libraryId] = SavePlaylistRequest(
playlistId,
libraryId,
requestHeader
requestHeader,
dataSyncId,
)
}
}
@ -107,7 +111,8 @@ class SavePlaylistRequest private constructor(
private fun sendRequest(
playlistId: String,
libraryId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): JSONObject? {
Objects.requireNonNull(playlistId)
Objects.requireNonNull(libraryId)
@ -122,7 +127,8 @@ class SavePlaylistRequest private constructor(
val connection = getInnerTubeResponseConnectionFromRoute(
EDIT_PLAYLIST,
clientType,
requestHeader
requestHeader,
dataSyncId
)
val requestBody = savePlaylistRequestBody(libraryId, playlistId)
@ -168,9 +174,15 @@ class SavePlaylistRequest private constructor(
private fun fetch(
playlistId: String,
libraryId: String,
requestHeader: Map<String, String>
requestHeader: Map<String, String>,
dataSyncId: String,
): Boolean? {
val json = sendRequest(playlistId, libraryId, requestHeader)
val json = sendRequest(
playlistId,
libraryId,
requestHeader,
dataSyncId,
)
if (json != null) {
return parseResponse(json)
}

View File

@ -34,8 +34,10 @@ val playlistPatch = bytecodePatch(
execute {
// In Incognito mode, sending a request always seems to fail.
accountIdentityFingerprint.methodOrThrow().addInstructions(
1,
"invoke-static/range {p4 .. p4}, $EXTENSION_CLASS_DESCRIPTOR->setIncognitoStatus(Z)V"
1, """
sput-object p3, $EXTENSION_CLASS_DESCRIPTOR->dataSyncId:Ljava/lang/String;
sput-boolean p4, $EXTENSION_CLASS_DESCRIPTOR->isIncognito:Z
"""
)
// Get the header to use the auth token.