Skip to content
This repository was archived by the owner on Dec 11, 2024. It is now read-only.
Draft
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
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ on:
jobs:
release:
name: Release
permissions:
contents: write
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down Expand Up @@ -49,5 +53,5 @@ jobs:

- name: Release
env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm exec semantic-release
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# [0.159.0-dev.1](https://github.com/anddea/revanced-integrations/compare/v0.158.0...v0.159.0-dev.1) (2024-11-10)


### Bug Fixes

* **YouTube - Hide feed components:** Rollback `CarouselShelfFilter` ([15dff30](https://github.com/anddea/revanced-integrations/commit/15dff30e761ab606bd2a77c82950e2f400b734a4))
* **YouTube - Toolbar components:** Premium header not applied when `Hide YouTube Doodles` is turned on ([e8e9923](https://github.com/anddea/revanced-integrations/commit/e8e9923458e67f9fa67e8ae099677ed292ece5ec))


### Features

* **YouTube - Spoof app version:** Remove obsolete `19.13.37` spoof target ([78ffd65](https://github.com/anddea/revanced-integrations/commit/78ffd6571e682fd05f7c81fb83725a3e942f910a))
* **YouTube - Spoof streaming data:** Add `iOS Compatibility mode` setting ([07b3d8a](https://github.com/anddea/revanced-integrations/commit/07b3d8ac3c0f3b774e4ad2126c6d820faf40939c))
* **YouTube - Spoof streaming data:** Change default client to iOS ([79d34e4](https://github.com/anddea/revanced-integrations/commit/79d34e44023817d087e79a32d63a3a104d455a13))
* **YouTube - Spoof streaming data:** Update the hardcoded iOS client version ([74d26e5](https://github.com/anddea/revanced-integrations/commit/74d26e5923648c93eaa8031d4fc64b491b9af1c0))

# [0.158.0](https://github.com/anddea/revanced-integrations/compare/v0.157.0...v0.158.0) (2024-11-07)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import app.revanced.integrations.shared.patches.components.Filter;
import app.revanced.integrations.shared.patches.components.StringFilterGroup;
import app.revanced.integrations.shared.utils.Logger;
import app.revanced.integrations.shared.utils.StringTrieSearch;
import app.revanced.integrations.youtube.settings.Settings;
import app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
import app.revanced.integrations.youtube.shared.RootView;
Expand All @@ -33,16 +34,25 @@ public final class CarouselShelfFilter extends Filter {
BROWSE_ID_NOTIFICATION_INBOX
);

private final StringTrieSearch exceptions = new StringTrieSearch();
public final StringFilterGroup horizontalShelf;

public CarouselShelfFilter() {
addPathCallbacks(
new StringFilterGroup(
Settings.HIDE_CAROUSEL_SHELF,
"horizontal_video_shelf.eml",
"horizontal_shelf.eml",
"horizontal_shelf_inline.eml",
"horizontal_tile_shelf.eml"
)
exceptions.addPattern("library_recent_shelf.eml");

final StringFilterGroup carouselShelf = new StringFilterGroup(
Settings.HIDE_CAROUSEL_SHELF,
"horizontal_shelf_inline.eml",
"horizontal_tile_shelf.eml",
"horizontal_video_shelf.eml"
);

horizontalShelf = new StringFilterGroup(
Settings.HIDE_CAROUSEL_SHELF,
"horizontal_shelf.eml"
);

addPathCallbacks(carouselShelf, horizontalShelf);
}

private static boolean hideShelves(boolean playerActive, boolean searchBarActive, NavigationButton selectedNavButton, String browseId) {
Expand All @@ -64,12 +74,15 @@ private static boolean hideShelves(boolean playerActive, boolean searchBarActive
@Override
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (exceptions.matches(path)) {
return false;
}
final boolean playerActive = RootView.isPlayerActive();
final boolean searchBarActive = RootView.isSearchBarActive();
final NavigationButton navigationButton = NavigationButton.getSelectedNavigationButton();
final String navigation = navigationButton == null ? "null" : navigationButton.name();
final String browseId = RootView.getBrowseId();
final boolean hideShelves = hideShelves(playerActive, searchBarActive, navigationButton, browseId);
final boolean hideShelves = matchedGroup != horizontalShelf || hideShelves(playerActive, searchBarActive, navigationButton, browseId);
if (contentIndex != 0) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,10 @@ public static void setDrawerNavigationHeader(View lithoView) {
return;
if (!(viewGroup.getChildAt(0) instanceof ImageView imageView))
return;
final Activity mAcrivity = Utils.getActivity();
if (mAcrivity == null)
final Activity mActivity = Utils.getActivity();
if (mActivity == null)
return;
imageView.setImageDrawable(getHeaderDrawable(mAcrivity, headerAttributeId));
imageView.setImageDrawable(getHeaderDrawable(mActivity, headerAttributeId));
});
}

Expand Down Expand Up @@ -481,10 +481,18 @@ public static void hideVoiceSearchButton(View view, int visibility) {
}
}

/**
* In ReVanced, image files are replaced to change the header,
* Whereas in RVX, the header is changed programmatically.
* There is an issue where the header is not changed in RVX when YouTube Doodles are hidden.
* As a workaround, manually set the header when YouTube Doodles are hidden.
*/
public static void hideYouTubeDoodles(ImageView imageView, Drawable drawable) {
if (!Settings.HIDE_YOUTUBE_DOODLES.get()) {
imageView.setImageDrawable(drawable);
final Activity mActivity = Utils.getActivity();
if (Settings.HIDE_YOUTUBE_DOODLES.get() && mActivity != null) {
drawable = getHeaderDrawable(mActivity, getHeaderAttributeId());
}
imageView.setImageDrawable(drawable);
}

private static final int settingsDrawableId =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class AppClient {
* Store page of the YouTube app</a>, in the {@code What’s New} section.
* </p>
*/
private static final String CLIENT_VERSION_IOS = "19.16.3";
private static final String CLIENT_VERSION_IOS = "19.30.2";
private static final String DEVICE_MAKE_IOS = "Apple";
/**
* The device machine id for the iPhone XS Max (iPhone11,4), used to get 60fps.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public final class PlayerRoutes {
"?fields=contents.singleColumnWatchNextResults.playlist.playlist"
).compile();

static final Route.CompiledRoute GET_LIVE_STREAM_RENDERER = new Route(
Route.Method.POST,
"player" +
"?fields=playabilityStatus.status," +
"videoDetails.isLiveContent"
).compile();

/**
* TCP connection and HTTP read timeout
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private static JSONObject send(ClientType clientType, String videoId) {

final long startTime = System.currentTimeMillis();
String clientTypeName = clientType.name();
Logger.printDebug(() -> "Fetching playlist request for: " + videoId + " using client: " + clientType.name());
Logger.printDebug(() -> "Fetching playlist request for: " + videoId + " using client: " + clientTypeName);

try {
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_PLAYLIST_PAGE, clientType);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package app.revanced.integrations.youtube.patches.misc.requests;

import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.GET_LIVE_STREAM_RENDERER;
import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.GET_STREAMING_DATA;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
Expand All @@ -23,16 +27,19 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import app.revanced.integrations.shared.requests.Requester;
import app.revanced.integrations.shared.utils.Logger;
import app.revanced.integrations.shared.utils.Utils;
import app.revanced.integrations.youtube.patches.misc.client.AppClient.ClientType;
import app.revanced.integrations.youtube.settings.Settings;

public class StreamingDataRequest {
private static final boolean SPOOF_STREAMING_DATA_IOS_COMPATIBILITY = Settings.SPOOF_STREAMING_DATA_IOS_COMPATIBILITY.get();

private static final ClientType[] allClientTypes = {
ClientType.IOS,
ClientType.ANDROID_VR,
ClientType.ANDROID_UNPLUGGED,
ClientType.IOS,
};

private static final ClientType[] clientTypesToUse;
Expand Down Expand Up @@ -100,6 +107,59 @@ private static void handleConnectionError(String toastMessage, @Nullable Excepti
Logger.printInfo(() -> toastMessage, ex);
}

private static boolean isUnplayableOrLiveStream(ClientType clientType, String videoId) {
if (!SPOOF_STREAMING_DATA_IOS_COMPATIBILITY || clientType != ClientType.IOS) {
return false;
}
Objects.requireNonNull(videoId);
try {
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_LIVE_STREAM_RENDERER, clientType);
String innerTubeBody = PlayerRoutes.createInnertubeBody(clientType, videoId);
byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(requestBody.length);
connection.getOutputStream().write(requestBody);

final int responseCode = connection.getResponseCode();
if (responseCode == 200) {
JSONObject playerResponse = Requester.parseJSONObject(connection);
final boolean isPlayabilityOk = isPlayabilityStatusOk(playerResponse);
final boolean isLiveStream = isLiveStream(playerResponse);
return !isPlayabilityOk || isLiveStream;
}

// Always show a toast for this, as a non 200 response means something is broken.
handleConnectionError("Fetch livestreams not available: " + responseCode, null);
} catch (SocketTimeoutException ex) {
handleConnectionError("Fetch livestreams temporarily not available (API timed out)", ex);
} catch (IOException ex) {
handleConnectionError("Fetch livestreams temporarily not available: " + ex.getMessage(), ex);
} catch (Exception ex) {
Logger.printException(() -> "Fetch livestreams failed", ex); // Should never happen.
}

return true;
}

private static boolean isPlayabilityStatusOk(@NonNull JSONObject playerResponse) {
try {
return playerResponse.getJSONObject("playabilityStatus").getString("status").equals("OK");
} catch (JSONException e) {
Logger.printDebug(() -> "Failed to get playabilityStatus for response: " + playerResponse);
}

return false;
}

private static boolean isLiveStream(@NonNull JSONObject playerResponse) {
try {
return playerResponse.getJSONObject("videoDetails").getBoolean("isLiveContent");
} catch (JSONException e) {
Logger.printDebug(() -> "Failed to get videoDetails for response: " + playerResponse);
}

return false;
}

private static final String[] REQUEST_HEADER_KEYS = {
"Authorization", // Available only to logged in users.
"X-GOOG-API-FORMAT-VERSION",
Expand Down Expand Up @@ -158,6 +218,11 @@ private static ByteBuffer fetch(@NonNull String videoId, Map<String, String> pla

// Retry with different client if empty response body is received.
for (ClientType clientType : clientTypesToUse) {
if (isUnplayableOrLiveStream(clientType, videoId)) {
Logger.printDebug(() -> "Ignore IOS spoofing as it is unplayable or a live stream (video: " + videoId + ")");
continue;
}

HttpURLConnection connection = send(clientType, videoId, playerHeaders);
if (connection != null) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,16 @@
import static app.revanced.integrations.shared.utils.Utils.runOnMainThreadDelayed;

import android.app.Activity;
import android.app.AlertDialog;

import androidx.annotation.NonNull;

import app.revanced.integrations.shared.settings.BaseSettings;
import app.revanced.integrations.shared.settings.BooleanSetting;
import app.revanced.integrations.shared.utils.Utils;
import app.revanced.integrations.youtube.settings.Settings;
import app.revanced.integrations.youtube.utils.ExtendedUtils;

@SuppressWarnings("unused")
public class InitializationPatch {
private static final BooleanSetting SETTINGS_INITIALIZED = BaseSettings.SETTINGS_INITIALIZED;
private static final BooleanSetting SPOOF_APP_VERSION = Settings.SPOOF_APP_VERSION;
private static final boolean hasRollingNumberIssue = Settings.SPOOF_APP_VERSION_TARGET.defaultValue.startsWith("19");

/**
* Some layouts that depend on litho do not load when the app is first installed.
Expand All @@ -31,13 +26,7 @@ public static void onCreate(@NonNull Activity mActivity) {
if (SETTINGS_INITIALIZED.get()) {
return;
}
runOnMainThreadDelayed(() -> {
if (hasRollingNumberIssue) {
showSpoofAppVersionDialog(mActivity);
} else {
showRestartDialog(mActivity, str("revanced_extended_restart_first_run"), 3500);
}
}, 500);
runOnMainThreadDelayed(() -> showRestartDialog(mActivity, str("revanced_extended_restart_first_run"), 3500), 500);
runOnMainThreadDelayed(() -> SETTINGS_INITIALIZED.save(true), 1000);
}

Expand All @@ -47,18 +36,4 @@ public static void setExtendedUtils(@NonNull Activity mActivity) {
ExtendedUtils.setVersionName();
ExtendedUtils.setPlayerFlyoutMenuAdditionalSettings();
}

private static void showSpoofAppVersionDialog(@NonNull Activity mActivity) {
new AlertDialog.Builder(mActivity)
.setMessage(str("revanced_extended_restart_first_run_rolling_number"))
.setPositiveButton(android.R.string.ok, (dialog, id)
-> Utils.runOnMainThreadDelayed(() -> {
SPOOF_APP_VERSION.save(true);
Utils.restartApp(mActivity);
}, 3500))
.setNegativeButton(android.R.string.cancel, (dialog, id)
-> Utils.runOnMainThreadDelayed(() -> Utils.restartApp(mActivity), 3500))
.setCancelable(false)
.show();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,4 @@ public static boolean OldSeekbarThumbnailsDefaultBoolean() {
return false;
}

// Modified by a patch. Do not touch.
public static String SpoofAppVersionDefaultString() {
return "18.17.43";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ public class Settings extends BaseSettings {

public static final EnumSetting<FormFactor> CHANGE_LAYOUT = new EnumSetting<>("revanced_change_layout", FormFactor.ORIGINAL, true);
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", false, true, "revanced_spoof_app_version_user_dialog_message");
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target",
PatchStatus.SpoofAppVersionDefaultString(), true, parent(SPOOF_APP_VERSION));
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "18.17.43", true, parent(SPOOF_APP_VERSION));

// PreferenceScreen: General - Account menu
public static final BooleanSetting HIDE_ACCOUNT_MENU = new BooleanSetting("revanced_hide_account_menu", FALSE);
Expand Down Expand Up @@ -544,10 +543,12 @@ public class Settings extends BaseSettings {
public static final EnumSetting<WatchHistoryType> WATCH_HISTORY_TYPE = new EnumSetting<>("revanced_watch_history_type", WatchHistoryType.REPLACE);

// PreferenceScreen: Miscellaneous - Spoof streaming data
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", FALSE, true, "revanced_spoof_streaming_data_user_dialog_message");
// The order of the settings should not be changed otherwise the app may crash
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message");
public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
"revanced_spoof_streaming_data_ios_force_avc_user_dialog_message", new SpoofStreamingDataPatch.ForceiOSAVCAvailability());
public static final EnumSetting<ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true, parent(SPOOF_STREAMING_DATA));
public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_COMPATIBILITY = new BooleanSetting("revanced_spoof_streaming_data_ios_compatibility", TRUE, true, new SpoofStreamingDataPatch.ForceiOSAVCAvailability());
public static final EnumSetting<ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.IOS, true, parent(SPOOF_STREAMING_DATA));
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_STREAMING_DATA));

// PreferenceScreen: Return YouTube Dislike
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ private void updateUI() {

final String summaryTextKey;
if (selectableClientTypes.contains(clientType)) {
summaryTextKey = "revanced_spoof_streaming_data_side_effects_" + clientType.name().toLowerCase();
if (clientType == ClientType.IOS && Settings.SPOOF_STREAMING_DATA_IOS_COMPATIBILITY.get()) {
summaryTextKey = "revanced_spoof_streaming_data_side_effects_ios_compatibility";
} else {
summaryTextKey = "revanced_spoof_streaming_data_side_effects_" + clientType.name().toLowerCase();
}
} else {
summaryTextKey = "revanced_spoof_streaming_data_side_effects_unknown";
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
android.useAndroidX = true
version = 0.158.0
version = 0.159.0-dev.1