From dc955d1bc2db63efc83d4fd5c7e076ed0867d48b Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 27 Aug 2023 21:40:07 +0200 Subject: [PATCH] feat: Restore previous release --- app/build.gradle.kts | 3 +- .../patches/AlternativeThumbnailsPatch.java | 409 ++++++++++++++++++ .../CustomPlayerOverlayOpacityPatch.java | 22 + .../patches/EnableTabletLayoutPatch.java | 9 + .../patches/HidePlayerOverlayPatch.java | 12 - .../patches/OpenLinksExternallyPatch.java | 16 +- .../patches/components/AdsFilter.java | 8 +- .../patches/components/ButtonsFilter.java | 93 +++- .../patches/components/CommentsFilter.java | 2 +- .../components/LayoutComponentsFilter.java | 8 +- .../patches/components/LithoFilterPatch.java | 89 ++-- .../PlaybackSpeedMenuFilterPatch.java | 4 +- .../PlayerFlyoutMenuItemsFilter.java | 6 +- .../patches/components/ShortsFilter.java | 86 ++-- .../VideoQualityMenuFilterPatch.java | 4 +- .../integrations/settings/SettingsEnum.java | 52 +-- .../settings/SharedPrefCategory.java | 16 +- .../integrations/utils/ByteTrieSearch.java | 12 +- .../integrations/utils/StringTrieSearch.java | 12 +- .../integrations/utils/TrieSearch.java | 27 +- .../patches/hook/twifucker/TwiFucker.kt | 16 +- build.gradle.kts | 2 +- 22 files changed, 723 insertions(+), 185 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java create mode 100644 app/src/main/java/app/revanced/integrations/patches/CustomPlayerOverlayOpacityPatch.java create mode 100644 app/src/main/java/app/revanced/integrations/patches/EnableTabletLayoutPatch.java delete mode 100644 app/src/main/java/app/revanced/integrations/patches/HidePlayerOverlayPatch.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6e77d08b..0e98a7b6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,7 +5,6 @@ plugins { android { compileSdk = 33 - buildToolsVersion = "33.0.1" namespace = "app.revanced.integrations" defaultConfig { @@ -44,7 +43,7 @@ android { dependencies { compileOnly(project(mapOf("path" to ":dummy"))) compileOnly("androidx.annotation:annotation:1.6.0") - compileOnly("androidx.appcompat:appcompat:1.7.0-alpha02") + compileOnly("androidx.appcompat:appcompat:1.7.0-alpha03") compileOnly("com.squareup.okhttp3:okhttp:5.0.0-alpha.11") compileOnly("com.squareup.retrofit2:retrofit:2.9.0") } diff --git a/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java b/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java new file mode 100644 index 00000000..1e4d3f05 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java @@ -0,0 +1,409 @@ +package app.revanced.integrations.patches; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.chromium.net.UrlResponseInfo; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; + +/** + * Alternative YouTube thumbnails, showing the beginning/middle/end of the video. + * (ie: sd1.jpg, sd2.jpg, sd3.jpg). + * + * Has an additional option to use 'fast' thumbnails, + * where it forces sd thumbnail quality and skips verifying if the alt thumbnail image exists. + * The UI loading time will be the same or better than using the the original thumbnails, + * but thumbnails will initially fail to load for all live streams, unreleased, and occasionally very old videos. + * If a failed thumbnail load is reloaded (ie: scroll off, then on screen), then the original thumbnail + * is reloaded instead. Fast thumbnails requires using SD or lower thumbnail resolution, + * because a noticeable number of videos do not have hq720 and too many fail to load. + * + * Ideas for improvements: + * - Selectively allow using original thumbnails in some situations, + * such as videos subscription feed, watch history, or in search results. + * - Save to a temporary file the video id's verified to have alt thumbnails. + * This would speed up loading the watch history and users saved playlists. + */ +public final class AlternativeThumbnailsPatch { + + private enum ThumbnailQuality { + // In order of lowest to highest resolution. + DEFAULT("default", ""), // effective alt name is 1.jpg, 2.jpg, 3.jpg + MQDEFAULT("mqdefault", "mq"), + HQDEFAULT("hqdefault", "hq"), + SDDEFAULT("sddefault", "sd"), + HQ720("hq720", "hq720_"), + MAXRESDEFAULT("maxresdefault", "maxres"); + + /** + * Lookup map of original name to enum. + */ + private static final Map originalNameToEnum = new HashMap<>(); + + /** + * Lookup map of alt name to enum. ie: "hq720_1" to {@link #HQ720}. + */ + private static final Map altNameToEnum = new HashMap<>(); + + static { + for (ThumbnailQuality quality : values()) { + originalNameToEnum.put(quality.originalName, quality); + + for (int i = 1; i <= 3; i++) { + altNameToEnum.put(quality.altImageName + i, quality); + } + } + } + + /** + * Convert an alt image name to enum. + * ie: "hq720_2" returns {@link #HQ720}. + */ + @Nullable + static ThumbnailQuality altImageNameToQuality(@NonNull String altImageName) { + return altNameToEnum.get(altImageName); + } + + /** + * Original quality to effective alt quality to use. + * ie: If fast alt image is enabled, then "hq720" returns {@link #SDDEFAULT}. + */ + @Nullable + static ThumbnailQuality getQualityToUse(@NonNull String originalSize) { + ThumbnailQuality quality = originalNameToEnum.get(originalSize); + if (quality == null) { + return null; // Not a thumbnail for a regular video. + } + + final boolean useFastQuality = SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean(); + switch (quality) { + case SDDEFAULT: + // SD alt images have somewhat worse quality with washed out color and poor contrast. + // But the 720 images look much better and don't suffer from these issues. + // For unknown reasons, the 720 thumbnails are used only for the home feed, + // while SD is used for the search and subscription feed + // (even though search and subscriptions use the exact same layout as the home feed). + // Of note, this image quality issue only appears with the alt thumbnail images, + // and the regular thumbnails have identical color/contrast quality for all sizes. + // Fix this by falling thru and upgrading SD to 720. + case HQ720: + if (useFastQuality) { + return SDDEFAULT; // SD is max resolution for fast alt images. + } + return HQ720; + case MAXRESDEFAULT: + if (useFastQuality) { + return SDDEFAULT; + } + return MAXRESDEFAULT; + default: + return quality; + } + } + + final String originalName; + final String altImageName; + + ThumbnailQuality(String originalName, String altImageName) { + this.originalName = originalName; + this.altImageName = altImageName; + } + + String getAltImageNameToUse() { + return altImageName + SettingsEnum.ALT_THUMBNAIL_TYPE.getInt(); + } + } + + /** + * Uses HTTP HEAD requests to verify and keep track of which thumbnail sizes + * are available and not available. + */ + private static class VerifiedQualities { + /** + * After a quality is verified as not available, how long until the quality is re-verified again. + * Used only if fast mode is not enabled. Intended for live streams and unreleased videos + * that are now finished and available (and thus, the alt thumbnails are also now available). + */ + private static final long NOT_AVAILABLE_TIMEOUT_MILLISECONDS = 10 * 60 * 1000; // 10 minutes. + + /** + * Cache used to verify if an alternative thumbnails exists for a given video id. + */ + @GuardedBy("itself") + private static final Map altVideoIdLookup = new LinkedHashMap<>(100) { + private static final int CACHE_LIMIT = 1000; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > CACHE_LIMIT; // Evict oldest entry if over the cache limit. + } + }; + + private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) { + synchronized (altVideoIdLookup) { + VerifiedQualities verified = altVideoIdLookup.get(videoId); + if (verified == null) { + if (returnNullIfDoesNotExist) { + return null; + } + verified = new VerifiedQualities(); + altVideoIdLookup.put(videoId, verified); + } + return verified; + } + } + + static boolean verifyAltThumbnailExist(@NonNull String videoId, @NonNull ThumbnailQuality quality, + @NonNull String imageUrl) { + VerifiedQualities verified = getVerifiedQualities(videoId, SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean()); + if (verified == null) return true; // Fast alt thumbnails is enabled. + return verified.verifyYouTubeThumbnailExists(videoId, quality, imageUrl); + } + + static void setAltThumbnailDoesNotExist(@NonNull String videoId, @NonNull ThumbnailQuality quality) { + VerifiedQualities verified = getVerifiedQualities(videoId, false); + verified.setQualityVerified(videoId, quality, false); + } + + /** + * Highest quality verified as existing. + */ + @Nullable + ThumbnailQuality highestQualityVerified; + /** + * Lowest quality verified as not existing. + */ + @Nullable + ThumbnailQuality lowestQualityNotAvailable; + + /** + * System time, of when to invalidate {@link #lowestQualityNotAvailable}. + * Used only if fast mode is not enabled. + */ + long timeToReVerifyLowestQuality; + + synchronized void setQualityVerified(String videoId, ThumbnailQuality quality, boolean isVerified) { + if (isVerified) { + if (highestQualityVerified == null || highestQualityVerified.ordinal() < quality.ordinal()) { + highestQualityVerified = quality; + } + } else { + if (lowestQualityNotAvailable == null || lowestQualityNotAvailable.ordinal() > quality.ordinal()) { + lowestQualityNotAvailable = quality; + timeToReVerifyLowestQuality = System.currentTimeMillis() + NOT_AVAILABLE_TIMEOUT_MILLISECONDS; + } + LogHelper.printDebug(() -> quality + " not available for video: " + videoId); + } + } + + /** + * Verify if a video alt thumbnail exists. Does so by making a minimal HEAD http request. + */ + synchronized boolean verifyYouTubeThumbnailExists(@NonNull String videoId, @NonNull ThumbnailQuality quality, + @NonNull String imageUrl) { + if (highestQualityVerified != null && highestQualityVerified.ordinal() >= quality.ordinal()) { + return true; // Previously verified as existing. + } + + final boolean fastQuality = SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean(); + if (lowestQualityNotAvailable != null && lowestQualityNotAvailable.ordinal() <= quality.ordinal()) { + if (fastQuality || System.currentTimeMillis() < timeToReVerifyLowestQuality) { + return false; // Previously verified as not existing. + } + // Enough time has passed, and should re-verify again. + LogHelper.printDebug(() -> "Resetting lowest verified quality for: " + videoId); + lowestQualityNotAvailable = null; + } + + if (fastQuality) { + return true; // Unknown if it exists or not. Use the URL anyways and update afterwards if loading fails. + } + + boolean imageFileFound; + try { + LogHelper.printDebug(() -> "Verifying image: " + imageUrl); + // This hooked code is running on a low priority thread, and it's slightly faster + // to run the url connection thru the integrations thread pool which runs at the highest priority. + final long start = System.currentTimeMillis(); + imageFileFound = ReVancedUtils.submitOnBackgroundThread(() -> { + final int connectionTimeoutMillis = 5000; + HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection(); + connection.setConnectTimeout(connectionTimeoutMillis); + connection.setReadTimeout(connectionTimeoutMillis); + connection.setRequestMethod("HEAD"); + // Even with a HEAD request, the response is the same size as a full GET request. + // Using an empty range fixes this. + connection.setRequestProperty("Range", "bytes=0-0"); + final int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_PARTIAL) { + String contentType = connection.getContentType(); + return (contentType != null && contentType.startsWith("image")); + } + if (responseCode != HttpURLConnection.HTTP_NOT_FOUND) { + LogHelper.printDebug(() -> "Unexpected response code: " + responseCode + " for url: " + imageUrl); + } + return false; + }).get(); + LogHelper.printDebug(() -> "Alt verification took: " + (System.currentTimeMillis() - start) + "ms"); + } catch (ExecutionException | InterruptedException ex) { + LogHelper.printInfo(() -> "Could not verify alt url: " + imageUrl, ex); + imageFileFound = false; + } + + setQualityVerified(videoId, quality, imageFileFound); + return imageFileFound; + } + } + + /** + * YouTube video thumbnail url, decoded into it's relevant parts. + */ + private static class DecodedThumbnailUrl { + /** + * YouTube thumbnail URL prefix. Can be '/vi/' or '/vi_webp/' + */ + private static final String YOUTUBE_THUMBNAIL_PREFIX = "https://i.ytimg.com/vi"; + + @Nullable + static DecodedThumbnailUrl decodeImageUrl(String url) { + final int videoIdStartIndex = url.indexOf('/', YOUTUBE_THUMBNAIL_PREFIX.length()) + 1; + if (videoIdStartIndex <= 0) return null; + final int videoIdEndIndex = url.indexOf('/', videoIdStartIndex); + if (videoIdEndIndex < 0) return null; + final int imageSizeStartIndex = videoIdEndIndex + 1; + final int imageSizeEndIndex = url.indexOf('.', imageSizeStartIndex); + if (imageSizeEndIndex < 0) return null; + int imageExtensionEndIndex = url.indexOf('?', imageSizeEndIndex); + if (imageExtensionEndIndex < 0) imageExtensionEndIndex = url.length(); + return new DecodedThumbnailUrl(url, videoIdStartIndex, videoIdEndIndex, + imageSizeStartIndex, imageSizeEndIndex, imageExtensionEndIndex); + } + + /** Full usable url, but stripped of any tracking information. */ + final String sanitizedUrl; + /** Url up to the video id. */ + final String urlPrefix; + final String videoId; + /** Quality, such as hq720 or sddefault. */ + final String imageQuality; + /** jpg or webp */ + final String imageExtension; + /** User view tracking parameters, only present on some images. */ + final String urlTrackingParameters; + + private DecodedThumbnailUrl(String fullUrl, int videoIdStartIndex, int videoIdEndIndex, + int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) { + sanitizedUrl = fullUrl.substring(0, imageExtensionEndIndex); + urlPrefix = fullUrl.substring(0, videoIdStartIndex); + videoId = fullUrl.substring(videoIdStartIndex, videoIdEndIndex); + imageQuality = fullUrl.substring(imageSizeStartIndex, imageSizeEndIndex); + imageExtension = fullUrl.substring(imageSizeEndIndex + 1, imageExtensionEndIndex); + urlTrackingParameters = (imageExtensionEndIndex == fullUrl.length()) + ? "" : fullUrl.substring(imageExtensionEndIndex); + } + } + + static { + // Fix any bad imported data. + final int altThumbnailType = SettingsEnum.ALT_THUMBNAIL_TYPE.getInt(); + if (altThumbnailType < 1 || altThumbnailType > 3) { + LogHelper.printException(() -> "Invalid alt thumbnail type: " + altThumbnailType); + SettingsEnum.ALT_THUMBNAIL_TYPE.saveValue(SettingsEnum.ALT_THUMBNAIL_TYPE.defaultValue); + } + } + + /** + * Injection point. Called off the main thread and by multiple threads at the same time. + * + * @param originalUrl Image url for all url images loaded, including video thumbnails. + */ + public static String overrideImageURL(String originalUrl) { + try { + if (!SettingsEnum.ALT_THUMBNAIL.getBoolean()) { + return originalUrl; + } + DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(originalUrl); + if (decodedUrl == null) { + return originalUrl; // Not a thumbnail. + } + + // Keep any tracking parameters out of the logs, and log only the base url. + LogHelper.printDebug(() -> "Original url: " + decodedUrl.sanitizedUrl); + + ThumbnailQuality qualityToUse = ThumbnailQuality.getQualityToUse(decodedUrl.imageQuality); + if (qualityToUse == null) return originalUrl; // Video is a short. + + // Images could be upgraded to webp if they are not already, but this fails quite often, + // especially for new videos uploaded in the last hour. + // And even if alt webp images do exist, sometimes they can load much slower than the original jpg alt images. + // (as much as 4x slower has been observed, despite the alt webp image being a smaller file). + + StringBuilder builder = new StringBuilder(originalUrl.length() + 2); + builder.append(decodedUrl.urlPrefix); + builder.append(decodedUrl.videoId).append('/'); + builder.append(qualityToUse.getAltImageNameToUse()); + builder.append('.').append(decodedUrl.imageExtension); + + String sanitizedReplacement = builder.toString(); + if (!VerifiedQualities.verifyAltThumbnailExist(decodedUrl.videoId, qualityToUse, sanitizedReplacement)) { + return originalUrl; + } + + LogHelper.printDebug(() -> "Replaced url: " + sanitizedReplacement); + + // URL tracking parameters. Presumably they are to determine if a user has viewed a thumbnail. + // This likely is used for recommendations, so they are retained if present. + builder.append(decodedUrl.urlTrackingParameters); + return builder.toString(); + } catch (Exception ex) { + LogHelper.printException(() -> "Alt thumbnails failure", ex); + return originalUrl; + } + } + + /** + * Injection point. + * + * Cronet considers all completed connections as a success, even if the response is 404 or 5xx. + */ + public static void handleCronetSuccess(@NonNull UrlResponseInfo responseInfo) { + try { + if (responseInfo.getHttpStatusCode() == 404 && SettingsEnum.ALT_THUMBNAIL.getBoolean()) { + // Fast alt thumbnails is enabled and the thumbnail is not available. + // The video is: + // - live stream + // - upcoming unreleased video + // - very old + // - very low view count + // Take note of this, so if the image reloads the original thumbnail will be used. + DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(responseInfo.getUrl()); + if (decodedUrl == null) { + return; // Not a thumbnail. + } + + ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedUrl.imageQuality); + if (quality == null) { + // Video is a short or unknown quality, but the url returned 404. Should never happen. + LogHelper.printDebug(() -> "Failed to load unknown url: " + decodedUrl.sanitizedUrl); + return; + } + + VerifiedQualities.setAltThumbnailDoesNotExist(decodedUrl.videoId, quality); + } + } catch (Exception ex) { + LogHelper.printException(() -> "Alt thumbnails callback failure", ex); + } + } + +} diff --git a/app/src/main/java/app/revanced/integrations/patches/CustomPlayerOverlayOpacityPatch.java b/app/src/main/java/app/revanced/integrations/patches/CustomPlayerOverlayOpacityPatch.java new file mode 100644 index 00000000..a2b4a94c --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/CustomPlayerOverlayOpacityPatch.java @@ -0,0 +1,22 @@ +package app.revanced.integrations.patches; + +import android.widget.ImageView; + +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.ReVancedUtils; + +public class CustomPlayerOverlayOpacityPatch { + private static final int DEFAULT_OPACITY = (int) SettingsEnum.PLAYER_OVERLAY_OPACITY.defaultValue; + + public static void changeOpacity(ImageView imageView) { + int opacity = SettingsEnum.PLAYER_OVERLAY_OPACITY.getInt(); + + if (opacity < 0 || opacity > 100) { + ReVancedUtils.showToastLong("Player overlay opacity must be between 0-100"); + SettingsEnum.PLAYER_OVERLAY_OPACITY.saveValue(DEFAULT_OPACITY); + opacity = DEFAULT_OPACITY; + } + + imageView.setImageAlpha((opacity * 255) / 100); + } +} diff --git a/app/src/main/java/app/revanced/integrations/patches/EnableTabletLayoutPatch.java b/app/src/main/java/app/revanced/integrations/patches/EnableTabletLayoutPatch.java new file mode 100644 index 00000000..638abd23 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/EnableTabletLayoutPatch.java @@ -0,0 +1,9 @@ +package app.revanced.integrations.patches; + +import app.revanced.integrations.settings.SettingsEnum; + +public final class EnableTabletLayoutPatch { + public static boolean enableTabletLayout() { + return SettingsEnum.TABLET_LAYOUT.getBoolean(); + } +} diff --git a/app/src/main/java/app/revanced/integrations/patches/HidePlayerOverlayPatch.java b/app/src/main/java/app/revanced/integrations/patches/HidePlayerOverlayPatch.java deleted file mode 100644 index e734fb24..00000000 --- a/app/src/main/java/app/revanced/integrations/patches/HidePlayerOverlayPatch.java +++ /dev/null @@ -1,12 +0,0 @@ -package app.revanced.integrations.patches; - -import android.widget.ImageView; - -import app.revanced.integrations.settings.SettingsEnum; - -public class HidePlayerOverlayPatch { - public static void hidePlayerOverlay(ImageView view) { - if (!SettingsEnum.HIDE_PLAYER_OVERLAY.getBoolean()) return; - view.setImageResource(android.R.color.transparent); - } -} diff --git a/app/src/main/java/app/revanced/integrations/patches/OpenLinksExternallyPatch.java b/app/src/main/java/app/revanced/integrations/patches/OpenLinksExternallyPatch.java index 1640c7bf..bc23e1bd 100644 --- a/app/src/main/java/app/revanced/integrations/patches/OpenLinksExternallyPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/OpenLinksExternallyPatch.java @@ -4,16 +4,14 @@ import app.revanced.integrations.settings.SettingsEnum; public class OpenLinksExternallyPatch { /** - * Override 'android.support.customtabs.action.CustomTabsService', - * in order to open links in the default browser. This is done by returning an empty string, - * for the service that handles custom tabs in the Android support library - * which opens links in the default service instead. + * Return the intent to open links with. If empty, the link will be opened with the default browser. * - * @param original The original custom tabs service. - * @return The new, default service to open links with or the original service. + * @param originalIntent The original intent to open links with. + * @return The intent to open links with. Empty means the link will be opened with the default browser. */ - public static String enableExternalBrowser(String original) { - if (SettingsEnum.EXTERNAL_BROWSER.getBoolean()) original = ""; - return original; + public static String getIntent(String originalIntent) { + if (SettingsEnum.EXTERNAL_BROWSER.getBoolean()) return ""; + + return originalIntent; } } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java index 3797f53a..40192d0d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/AdsFilter.java @@ -86,7 +86,7 @@ public final class AdsFilter extends Filter { "cta_shelf_card" ); - this.pathFilterGroups.addAll( + this.pathFilterGroupList.addAll( generalAds, buttonedAd, merchandise, @@ -95,16 +95,16 @@ public final class AdsFilter extends Filter { webLinkPanel, movieAds ); - this.identifierFilterGroups.addAll(carouselAd); + this.identifierFilterGroupList.addAll(carouselAd); } @Override - public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (exceptions.matches(path)) return false; - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } /** diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java index 5722e640..19cddf3f 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ButtonsFilter.java @@ -1,59 +1,114 @@ package app.revanced.integrations.patches.components; +import android.os.Build; + import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import app.revanced.integrations.settings.SettingsEnum; +@RequiresApi(api = Build.VERSION_CODES.N) final class ButtonsFilter extends Filter { - private final StringFilterGroup actionBarRule; + private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml"; + + private final StringFilterGroup actionBarGroup; + private final StringFilterGroup bufferFilterPathGroup; + private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList(); public ButtonsFilter() { - actionBarRule = new StringFilterGroup( + actionBarGroup = new StringFilterGroup( null, - "video_action_bar" + VIDEO_ACTION_BAR_PATH ); + identifierFilterGroupList.addAll(actionBarGroup); - pathFilterGroups.addAll( + + bufferFilterPathGroup = new StringFilterGroup( + null, + "|CellType|CollectionType|CellType|ContainerType|button.eml|" + ); + pathFilterGroupList.addAll( new StringFilterGroup( SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON, - "|like_button", - "dislike_button" + "|segmented_like_dislike_button" ), new StringFilterGroup( SettingsEnum.HIDE_DOWNLOAD_BUTTON, - "download_button" + "|download_button.eml|" ), new StringFilterGroup( SettingsEnum.HIDE_PLAYLIST_BUTTON, - "save_to_playlist_button" + "|save_to_playlist_button" ), new StringFilterGroup( SettingsEnum.HIDE_CLIP_BUTTON, "|clip_button.eml|" ), - new StringFilterGroup( - SettingsEnum.HIDE_ACTION_BUTTONS, - "ContainerType|video_action_button", - "|CellType|CollectionType|CellType|ContainerType|button.eml|" + bufferFilterPathGroup + ); + + bufferButtonsGroupList.addAll( + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_LIVE_CHAT_BUTTON, + "yt_outline_message_bubble_overlap" ), - actionBarRule + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_REPORT_BUTTON, + "yt_outline_flag" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_SHARE_BUTTON, + "yt_outline_share" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_REMIX_BUTTON, + "yt_outline_youtube_shorts_plus" + ), + // Check for clip button both here and using a path filter, + // as there's a chance the path is a generic action button and won't contain 'clip_button' + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_CLIP_BUTTON, + "yt_outline_scissors" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_SHOP_BUTTON, + "yt_outline_bag" + ), + new ByteArrayAsStringFilterGroup( + SettingsEnum.HIDE_THANKS_BUTTON, + "yt_outline_dollar_sign_heart" + ) ); } private boolean isEveryFilterGroupEnabled() { - for (StringFilterGroup rule : pathFilterGroups) - if (!rule.isEnabled()) return false; + for (var group : pathFilterGroupList) + if (!group.isEnabled()) return false; + + for (var group : bufferButtonsGroupList) + if (!group.isEnabled()) return false; return true; } @Override - public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { - if (matchedGroup == actionBarRule) { - return isEveryFilterGroupEnabled(); + // If the current matched group is the action bar group, + // in case every filter group is enabled, hide the action bar. + if (matchedGroup == actionBarGroup) { + if (!isEveryFilterGroupEnabled()) { + return false; + } + } else if (matchedGroup == bufferFilterPathGroup) { + // Make sure the current path is the right one + // to avoid false positives. + if (!path.startsWith(VIDEO_ACTION_BAR_PATH)) return false; + + // In case the group list has no match, return false. + if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false; } - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java index 22a9cba6..089fb948 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/CommentsFilter.java @@ -18,7 +18,7 @@ final class CommentsFilter extends Filter { "comments_entry_point_simplebox" ); - this.pathFilterGroups.addAll( + this.pathFilterGroupList.addAll( comments, previewComment ); diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java index 53860b9e..67811a89 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LayoutComponentsFilter.java @@ -142,7 +142,7 @@ public final class LayoutComponentsFilter extends Filter { "chips_shelf" ); - this.pathFilterGroups.addAll( + this.pathFilterGroupList.addAll( channelBar, communityPosts, paidContent, @@ -165,19 +165,19 @@ public final class LayoutComponentsFilter extends Filter { custom ); - this.identifierFilterGroups.addAll( + this.identifierFilterGroupList.addAll( graySeparator, chipsShelf ); } @Override - public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (matchedGroup != custom && exceptions.matches(path)) return false; // Exceptions are not filtered. - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java index b61c9df6..90aa6781 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java @@ -81,8 +81,7 @@ class StringFilterGroup extends FilterGroup { @Override public FilterGroupResult check(final String string) { - return new FilterGroupResult(setting, - (setting == null || setting.getBoolean()) && ReVancedUtils.containsAny(string, filters)); + return new FilterGroupResult(setting, isEnabled() && ReVancedUtils.containsAny(string, filters)); } } @@ -274,25 +273,14 @@ abstract class Filter { * will never be called for any matches. */ - protected final StringFilterGroupList pathFilterGroups = new StringFilterGroupList(); - protected final StringFilterGroupList identifierFilterGroups = new StringFilterGroupList(); - /** - * A collection of {@link ByteArrayFilterGroup} that are always searched for (no matter what). - * - * If possible, avoid adding values to this list and instead use a path or identifier filter - * for the item you are looking for. Then inside - * {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)}, - * the buffer can then be searched using using a different - * {@link ByteArrayFilterGroupList} or a {@link ByteArrayFilterGroup}. - * This way, the expensive buffer searching only occurs if the cheap and fast path/identifier is already found. - */ - protected final ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList(); + protected final StringFilterGroupList pathFilterGroupList = new StringFilterGroupList(); + protected final StringFilterGroupList identifierFilterGroupList = new StringFilterGroupList(); /** * Called after an enabled filter has been matched. * Default implementation is to always filter the matched item. * Subclasses can perform additional or different checks if needed. - * + *

* Method is called off the main thread. * * @param matchedList The list the group filter belongs to. @@ -301,15 +289,13 @@ abstract class Filter { * @return True if the litho item should be filtered out. */ @SuppressWarnings("rawtypes") - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { if (SettingsEnum.DEBUG.getBoolean()) { - if (pathFilterGroups == matchedList) { + if (pathFilterGroupList == matchedList) { LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path); - } else if (identifierFilterGroups == matchedList) { + } else if (identifierFilterGroupList == matchedList) { LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier); - } else if (protobufBufferFilterGroups == matchedList) { - LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered from protobuf-buffer"); } } return true; @@ -323,13 +309,14 @@ public final class LithoFilterPatch { * Simple wrapper to pass the litho parameters through the prefix search. */ private static final class LithoFilterParameters { - final String path; + @Nullable final String identifier; + final String path; final byte[] protoBuffer; - LithoFilterParameters(StringBuilder lithoPath, String lithoIdentifier, ByteBuffer protoBuffer) { - this.path = lithoPath.toString(); + LithoFilterParameters(@Nullable String lithoIdentifier, StringBuilder lithoPath, ByteBuffer protoBuffer) { this.identifier = lithoIdentifier; + this.path = lithoPath.toString(); this.protoBuffer = protoBuffer.array(); } @@ -342,9 +329,10 @@ public final class LithoFilterPatch { builder.append(identifier); builder.append(" Path: "); builder.append(path); - // TODO: allow turning on/off buffer logging with a debug setting? - builder.append(" BufferStrings: "); - findAsciiStrings(builder, protoBuffer); + if (SettingsEnum.DEBUG_PROTOBUFFER.getBoolean()) { + builder.append(" BufferStrings: "); + findAsciiStrings(builder, protoBuffer); + } return builder.toString(); } @@ -366,7 +354,9 @@ public final class LithoFilterPatch { int value = buffer[end]; if (value < minimumAscii || value > maximumAscii || end == length - 1) { if (end - start >= minimumAsciiStringLength) { - builder.append(new String(buffer, start, end - start)); + for (int i = start; i < end; i++) { + builder.append((char) buffer[i]); + } builder.append(delimitingCharacter); } start = end + 1; @@ -382,22 +372,24 @@ public final class LithoFilterPatch { private static final StringTrieSearch pathSearchTree = new StringTrieSearch(); private static final StringTrieSearch identifierSearchTree = new StringTrieSearch(); - private static final ByteTrieSearch protoSearchTree = new ByteTrieSearch(); + + /** + * Because litho filtering is multi-threaded and the buffer is passed in from a different injection point, + * the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads. + */ + private static final ThreadLocal bufferThreadLocal = new ThreadLocal<>(); static { for (Filter filter : filters) { - filterGroupLists(pathSearchTree, filter, filter.pathFilterGroups); - filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroups); - filterGroupLists(protoSearchTree, filter, filter.protobufBufferFilterGroups); + filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList); + filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList); } LogHelper.printDebug(() -> "Using: " + pathSearchTree.numberOfPatterns() + " path filters" + " (" + pathSearchTree.getEstimatedMemorySize() + " KB), " + identifierSearchTree.numberOfPatterns() + " identifier filters" - + " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), " - + protoSearchTree.numberOfPatterns() + " buffer filters" - + " (" + protoSearchTree.getEstimatedMemorySize() + " KB)"); + + " (" + identifierSearchTree.getEstimatedMemorySize() + " KB)"); } private static void filterGroupLists(TrieSearch pathSearchTree, @@ -410,7 +402,7 @@ public final class LithoFilterPatch { pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> { if (!group.isEnabled()) return false; LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter; - return filter.isFiltered(parameters.path, parameters.identifier, parameters.protoBuffer, + return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer, list, group, matchedStartIndex); } ); @@ -422,21 +414,36 @@ public final class LithoFilterPatch { * Injection point. Called off the main thread. */ @SuppressWarnings("unused") - public static boolean filter(@NonNull StringBuilder pathBuilder, @Nullable String lithoIdentifier, - @NonNull ByteBuffer protobufBuffer) { + public static void setProtoBuffer(@NonNull ByteBuffer protobufBuffer) { + // Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes. + // This is intentional, as it appears the buffer can be set once and then filtered multiple times. + // The buffer will be cleared from memory after a new buffer is set by the same thread, + // or when the calling thread eventually dies. + bufferThreadLocal.set(protobufBuffer); + } + + /** + * Injection point. Called off the main thread, and commonly called by multiple threads at the same time. + */ + @SuppressWarnings("unused") + public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) { try { // It is assumed that protobufBuffer is empty as well in this case. if (pathBuilder.length() == 0) return false; - LithoFilterParameters parameter = new LithoFilterParameters(pathBuilder, lithoIdentifier, protobufBuffer); + ByteBuffer protobufBuffer = bufferThreadLocal.get(); + if (protobufBuffer == null) { + LogHelper.printException(() -> "Proto buffer is null"); // Should never happen + return false; + } + LithoFilterParameters parameter = new LithoFilterParameters(lithoIdentifier, pathBuilder, protobufBuffer); LogHelper.printDebug(() -> "Searching " + parameter); - if (pathSearchTree.matches(parameter.path, parameter)) return true; if (parameter.identifier != null) { if (identifierSearchTree.matches(parameter.identifier, parameter)) return true; } - if (protoSearchTree.matches(parameter.protoBuffer, parameter)) return true; + if (pathSearchTree.matches(parameter.path, parameter)) return true; } catch (Exception ex) { LogHelper.printException(() -> "Litho filter failure", ex); } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java index ab2e63b4..00b11af3 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlaybackSpeedMenuFilterPatch.java @@ -8,14 +8,14 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter { public static volatile boolean isPlaybackSpeedMenuVisible; public PlaybackSpeedMenuFilterPatch() { - pathFilterGroups.addAll(new StringFilterGroup( + pathFilterGroupList.addAll(new StringFilterGroup( null, "playback_speed_sheet_content.eml-js" )); } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { isPlaybackSpeedMenuVisible = true; diff --git a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java index 3f435e08..c394f84f 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/PlayerFlyoutMenuItemsFilter.java @@ -15,7 +15,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter { @RequiresApi(api = Build.VERSION_CODES.N) public PlayerFlyoutMenuItemsFilter() { - identifierFilterGroups.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|")); + identifierFilterGroupList.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|")); flyoutFilterGroupList.addAll( new ByteArrayAsStringFilterGroup( @@ -62,12 +62,12 @@ public class PlayerFlyoutMenuItemsFilter extends Filter { } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { // Only 1 group is added to the parent class, so the matched group must be the overflow menu. if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) { // Super class handles logging. - return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); + return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } return false; } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java b/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java index 48542a06..b6e1f3fa 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ShortsFilter.java @@ -12,45 +12,21 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; import app.revanced.integrations.settings.SettingsEnum; public final class ShortsFilter extends Filter { - private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar"; + private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml"; public static PivotBar pivotBar; // Set by patch. private final StringFilterGroup channelBar; private final StringFilterGroup soundButton; private final StringFilterGroup infoPanel; + private final StringFilterGroup shortsShelfHeader; public ShortsFilter() { - channelBar = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_CHANNEL_BAR, - REEL_CHANNEL_BAR_PATH - ); - - soundButton = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_SOUND_BUTTON, - "reel_pivot_button" - ); - - infoPanel = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_INFO_PANEL, - "shorts_info_panel_overview" - ); - - final var thanksButton = new StringFilterGroup( + // Home / subscription feed components. + var thanksButton = new StringFilterGroup( SettingsEnum.HIDE_SHORTS_THANKS_BUTTON, "suggested_action" ); - - final var subscribeButton = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_SUBSCRIBE_BUTTON, - "subscribe_button" - ); - - final var joinButton = new StringFilterGroup( - SettingsEnum.HIDE_SHORTS_JOIN_BUTTON, - "sponsor_button" - ); - - final var shorts = new StringFilterGroup( + var shorts = new StringFilterGroup( SettingsEnum.HIDE_SHORTS, "shorts_shelf", "inline_shorts", @@ -58,22 +34,58 @@ public final class ShortsFilter extends Filter { "shorts_video_cell", "shorts_pivot_item" ); + // Use a different filter group for this pattern, as it requires an additional check after matching. + shortsShelfHeader = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS, + "shelf_header.eml" + ); + identifierFilterGroupList.addAll(shorts, shortsShelfHeader, thanksButton); - pathFilterGroups.addAll(joinButton, subscribeButton, channelBar, soundButton, infoPanel); - identifierFilterGroups.addAll(shorts, thanksButton); + + // Shorts player components. + var joinButton = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_JOIN_BUTTON, + "sponsor_button" + ); + var subscribeButton = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_SUBSCRIBE_BUTTON, + "subscribe_button" + ); + channelBar = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_CHANNEL_BAR, + REEL_CHANNEL_BAR_PATH + ); + soundButton = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_SOUND_BUTTON, + "reel_pivot_button" + ); + infoPanel = new StringFilterGroup( + SettingsEnum.HIDE_SHORTS_INFO_PANEL, + "shorts_info_panel_overview" + ); + pathFilterGroupList.addAll(joinButton, subscribeButton, channelBar, soundButton, infoPanel); } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { - if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar) return true; + if (matchedList == pathFilterGroupList) { + // Always filter if matched. + if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar) + return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); - // Filter the path only when reelChannelBar is visible. - if (pathFilterGroups == matchedList) { - return path.contains(REEL_CHANNEL_BAR_PATH); + // Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible + // to avoid false positives. + if (!path.startsWith(REEL_CHANNEL_BAR_PATH)) + return false; + } else if (matchedGroup == shortsShelfHeader) { + // Because the header is used in watch history and possibly other places, check for the index, + // which is 0 when the shelf header is used for Shorts. + if (matchedIndex != 0) return false; } - return identifierFilterGroups == matchedList; + // Super class handles logging. + return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex); } public static void hideShortsShelf(final View shortsShelfView) { diff --git a/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java index fb94de05..8b5d9643 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/VideoQualityMenuFilterPatch.java @@ -10,14 +10,14 @@ public final class VideoQualityMenuFilterPatch extends Filter { public static volatile boolean isVideoQualityMenuVisible; public VideoQualityMenuFilterPatch() { - pathFilterGroups.addAll(new StringFilterGroup( + pathFilterGroupList.addAll(new StringFilterGroup( SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU, "quick_quality_sheet_content.eml-js" )); } @Override - boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray, + boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) { isVideoQualityMenuVisible = true; diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index ef07eff4..c7e6c432 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -1,26 +1,12 @@ package app.revanced.integrations.settings; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; -import static app.revanced.integrations.settings.SettingsEnum.ReturnType.BOOLEAN; -import static app.revanced.integrations.settings.SettingsEnum.ReturnType.FLOAT; -import static app.revanced.integrations.settings.SettingsEnum.ReturnType.INTEGER; -import static app.revanced.integrations.settings.SettingsEnum.ReturnType.LONG; -import static app.revanced.integrations.settings.SettingsEnum.ReturnType.STRING; -import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE; -import static app.revanced.integrations.settings.SharedPrefCategory.SPONSOR_BLOCK; -import static app.revanced.integrations.utils.StringRef.str; - import android.content.Context; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import app.revanced.integrations.sponsorblock.SponsorBlockSettings; +import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.ReVancedUtils; import app.revanced.integrations.utils.StringRef; -import app.revanced.integrations.utils.LogHelper; - import org.json.JSONException; import org.json.JSONObject; @@ -29,6 +15,13 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import static app.revanced.integrations.settings.SettingsEnum.ReturnType.*; +import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE; +import static app.revanced.integrations.settings.SharedPrefCategory.SPONSOR_BLOCK; +import static app.revanced.integrations.utils.StringRef.str; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + public enum SettingsEnum { // External downloader @@ -89,12 +82,18 @@ public enum SettingsEnum { // Action buttons HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE), + HIDE_LIVE_CHAT_BUTTON("revanced_hide_live_chat_button", BOOLEAN, FALSE), + HIDE_SHARE_BUTTON("revanced_hide_share_button", BOOLEAN, FALSE), + HIDE_REPORT_BUTTON("revanced_hide_report_button", BOOLEAN, FALSE), + HIDE_REMIX_BUTTON("revanced_hide_remix_button", BOOLEAN, TRUE), HIDE_DOWNLOAD_BUTTON("revanced_hide_download_button", BOOLEAN, FALSE), + HIDE_THANKS_BUTTON("revanced_hide_thanks_button", BOOLEAN, TRUE), + HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, TRUE), HIDE_PLAYLIST_BUTTON("revanced_hide_playlist_button", BOOLEAN, FALSE), - HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, FALSE, "revanced_hide_clip_button_user_dialog_message"), - HIDE_ACTION_BUTTONS("revanced_hide_action_buttons", BOOLEAN, FALSE), + HIDE_SHOP_BUTTON("revanced_hide_shop_button", BOOLEAN, TRUE), // Layout + PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true), DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE), HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true), HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE), @@ -115,7 +114,6 @@ public enum SettingsEnum { HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE), HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true), HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE), - HIDE_PLAYER_OVERLAY("revanced_hide_player_overlay", BOOLEAN, FALSE, true), HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true), HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE), HIDE_SEEKBAR_THUMBNAIL("revanced_hide_seekbar_thumbnail", BOOLEAN, FALSE), @@ -129,9 +127,8 @@ public enum SettingsEnum { SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"), SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)), USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true), + TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"), WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true), - @Deprecated - DEPRECATED_SEEKBAR_COLOR("revanced_seekbar_color", STRING, "#FF0000"), // TODO: delete this SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true), SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)), HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true), @@ -149,6 +146,10 @@ public enum SettingsEnum { HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true), HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true), + ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE), + ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)), + ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)), + //Player flyout menu items HIDE_QUALITY_MENU("revanced_hide_player_flyout_quality", BOOLEAN, FALSE), HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE), @@ -158,7 +159,7 @@ public enum SettingsEnum { HIDE_HELP_MENU("revanced_hide_player_flyout_help", BOOLEAN, TRUE), HIDE_SPEED_MENU("revanced_hide_player_flyout_speed", BOOLEAN, FALSE), HIDE_MORE_INFO_MENU("revanced_hide_player_flyout_more_info", BOOLEAN, TRUE), - HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, TRUE), + HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, FALSE), HIDE_WATCH_IN_VR_MENU("revanced_hide_player_flyout_watch_in_vr", BOOLEAN, TRUE), // Misc @@ -191,6 +192,7 @@ public enum SettingsEnum { // Debugging DEBUG("revanced_debug", BOOLEAN, FALSE), DEBUG_STACKTRACE("revanced_debug_stacktrace", BOOLEAN, FALSE, parents(DEBUG)), + DEBUG_PROTOBUFFER("revanced_debug_protobuffer", BOOLEAN, FALSE, parents(DEBUG)), DEBUG_TOAST_ON_ERROR("revanced_debug_toast_on_error", BOOLEAN, TRUE, "revanced_debug_toast_on_error_user_dialog_message"), // ReturnYoutubeDislike @@ -374,14 +376,6 @@ public enum SettingsEnum { // TODO: delete DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU (When? anytime). migrateOldSettingToNew(DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU, SHOW_OLD_VIDEO_QUALITY_MENU); - // TODO: delete this seekbar color migration code - String oldSeekbarColorValue = DEPRECATED_SEEKBAR_COLOR.getString(); - if (!oldSeekbarColorValue.equalsIgnoreCase((String) DEPRECATED_SEEKBAR_COLOR.defaultValue)) { - SEEKBAR_CUSTOM_COLOR_VALUE.saveValue(oldSeekbarColorValue); - SEEKBAR_CUSTOM_COLOR.saveValue(true); - DEPRECATED_SEEKBAR_COLOR.saveValue(DEPRECATED_SEEKBAR_COLOR.defaultValue); - } - // endregion } diff --git a/app/src/main/java/app/revanced/integrations/settings/SharedPrefCategory.java b/app/src/main/java/app/revanced/integrations/settings/SharedPrefCategory.java index bded5469..17799e25 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SharedPrefCategory.java +++ b/app/src/main/java/app/revanced/integrations/settings/SharedPrefCategory.java @@ -80,12 +80,24 @@ public enum SharedPrefCategory { @NonNull public String getString(@NonNull String key, @NonNull String _default) { Objects.requireNonNull(_default); - return preferences.getString(key, _default); + try { + return preferences.getString(key, _default); + } catch (ClassCastException ex) { + // Value stored is a completely different type (should never happen). + removeConflictingPreferenceKeyValue(key); + return _default; + } } public boolean getBoolean(@NonNull String key, boolean _default) { - return preferences.getBoolean(key, _default); + try { + return preferences.getBoolean(key, _default); + } catch (ClassCastException ex) { + // Value stored is a completely different type (should never happen). + removeConflictingPreferenceKeyValue(key); + return _default; + } } @NonNull diff --git a/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java b/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java index e83a921c..807f3f8a 100644 --- a/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java +++ b/app/src/main/java/app/revanced/integrations/utils/ByteTrieSearch.java @@ -30,9 +30,19 @@ public final class ByteTrieSearch extends TrieSearch { super.addPattern(pattern, pattern.length, Objects.requireNonNull(callback)); } + @Override + public boolean matches(@NonNull byte[] textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) { + return super.matches(textToSearch, textToSearch.length, startIndex, endIndex, callbackParameter); + } + + @Override + public boolean matches(@NonNull byte[] textToSearch, int startIndex) { + return matches(textToSearch, startIndex, textToSearch.length, null); + } + @Override public boolean matches(@NonNull byte[] textToSearch, @Nullable Object callbackParameter) { - return super.matches(textToSearch, textToSearch.length, callbackParameter); + return matches(textToSearch,0, textToSearch.length, callbackParameter); } } diff --git a/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java b/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java index 7afafc60..1a1a0a9e 100644 --- a/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java +++ b/app/src/main/java/app/revanced/integrations/utils/StringTrieSearch.java @@ -33,8 +33,18 @@ public final class StringTrieSearch extends TrieSearch { super.addPattern(pattern, pattern.length(), Objects.requireNonNull(callback)); } + @Override + public boolean matches(@NonNull String textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) { + return super.matches(textToSearch, textToSearch.length(), startIndex, endIndex, callbackParameter); + } + @Override public boolean matches(@NonNull String textToSearch, @Nullable Object callbackParameter) { - return super.matches(textToSearch, textToSearch.length(), callbackParameter); + return matches(textToSearch, 0, textToSearch.length(), callbackParameter); + } + + @Override + public boolean matches(@NonNull String textToSearch, int startIndex) { + return matches(textToSearch, startIndex, textToSearch.length(), null); } } diff --git a/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java b/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java index 9d2db167..6458f07c 100644 --- a/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java +++ b/app/src/main/java/app/revanced/integrations/utils/TrieSearch.java @@ -243,12 +243,17 @@ public abstract class TrieSearch { root.addPattern(pattern, patternLength, 0, callback); } - boolean matches(@NonNull T textToSearch, int textToSearchLength, @Nullable Object callbackParameter) { + final boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex, + @Nullable Object callbackParameter) { + if (endIndex > textToSearchLength) { + throw new IllegalArgumentException("endIndex: " + endIndex + + " is greater than texToSearchLength: " + textToSearchLength); + } if (patterns.size() == 0) { return false; // No patterns were added. } - for (int i = 0; i < textToSearchLength; i++) { - if (root.matches(textToSearch, textToSearchLength, i, 0, callbackParameter)) return true; + for (int i = startIndex; i < endIndex; i++) { + if (root.matches(textToSearch, endIndex, i, 0, callbackParameter)) return true; } return false; } @@ -287,19 +292,27 @@ public abstract class TrieSearch { */ public abstract void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback callback); + /** * Searches through text, looking for any substring that matches any pattern in this tree. * * @param textToSearch Text to search through. + * @param startIndex Index to start searching, inclusive value. + * @param endIndex Index to stop matching, exclusive value. * @param callbackParameter Optional parameter passed to the callbacks. * @return If any pattern matched, and it's callback halted searching. */ + public abstract boolean matches(@NonNull T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter); + + public abstract boolean matches(@NonNull T textToSearch, int startIndex); + public abstract boolean matches(@NonNull T textToSearch, @Nullable Object callbackParameter); - /** - * Identical to {@link #matches(Object, Object)} but with a null callback parameter. - */ + public final boolean matches(@NonNull T textToSearch, int startIndex, int endIndex) { + return matches(textToSearch, startIndex, endIndex, null); + } + public final boolean matches(@NonNull T textToSearch) { - return matches(textToSearch, null); + return matches(textToSearch, 0); } } diff --git a/app/src/main/java/app/revanced/twitter/patches/hook/twifucker/TwiFucker.kt b/app/src/main/java/app/revanced/twitter/patches/hook/twifucker/TwiFucker.kt index 9d47bc8f..5c4323e3 100644 --- a/app/src/main/java/app/revanced/twitter/patches/hook/twifucker/TwiFucker.kt +++ b/app/src/main/java/app/revanced/twitter/patches/hook/twifucker/TwiFucker.kt @@ -22,7 +22,7 @@ internal object TwiFucker { private fun JSONObject.jsonCheckAndRemoveRecommendedUsers() { if (jsonHasRecommendedUsers()) { - Log.d("revanced", "Handle recommended users: $this") + Log.d("ReVanced", "Handle recommended users: $this") jsonRemoveRecommendedUsers() } } @@ -35,7 +35,7 @@ internal object TwiFucker { private fun JSONObject.jsonCheckAndRemoveThreads() { if (jsonHasThreads()) { - Log.d("revabced", "Handle threads: $this") + Log.d("ReVanced", "Handle threads: $this") jsonRemoveThreads() } } @@ -92,7 +92,7 @@ internal object TwiFucker { val trendRemoveIndex = mutableListOf() forEachIndexed { trendIndex, trend -> if (trend.trendHasPromotedMetadata()) { - Log.d("revanced", "Handle trends ads $trendIndex $trend") + Log.d("ReVanced", "Handle trends ads $trendIndex $trend") trendRemoveIndex.add(trendIndex) } } @@ -119,7 +119,7 @@ internal object TwiFucker { entry.entryGetTrends()?.trendRemoveAds() if (entry.entryHasPromotedMetadata()) { - Log.d("revanced", "Handle timeline ads $entryIndex $entry") + Log.d("ReVanced", "Handle timeline ads $entryIndex $entry") removeIndex.add(entryIndex) } @@ -127,7 +127,7 @@ internal object TwiFucker { val contentItems = entry.entryGetContentItems() contentItems?.forEachIndexed inner@{ itemIndex, item -> if (item.entryHasPromotedMetadata()) { - Log.d("revanced", "Handle timeline replies ads $entryIndex $entry") + Log.d("ReVanced", "Handle timeline replies ads $entryIndex $entry") if (contentItems.length() == 1) { removeIndex.add(entryIndex) } else { @@ -150,7 +150,7 @@ internal object TwiFucker { forEachIndexed { entryIndex, entry -> if (entry.entryIsTweetDetailRelatedTweets()) { - Log.d("revanced", "Handle tweet detail related tweets $entryIndex $entry") + Log.d("ReVanced", "Handle tweet detail related tweets $entryIndex $entry") removeIndex.add(entryIndex) } } @@ -180,7 +180,7 @@ internal object TwiFucker { forEachIndexed { entryIndex, entry -> if (!entry.entryIsWhoToFollow()) return@forEachIndexed - Log.d("revanced", "Handle whoToFollow $entryIndex $entry") + Log.d("ReVanced", "Handle whoToFollow $entryIndex $entry") entryRemoveIndex.add(entryIndex) val items = entry.entryGetContentItems() @@ -188,7 +188,7 @@ internal object TwiFucker { items?.forEachIndexed { index, item -> item.itemContainsPromotedUser().let { if (it) { - Log.d("revanced", "Handle whoToFollow promoted user $index $item") + Log.d("ReVanced", "Handle whoToFollow promoted user $index $item") userRemoveIndex.add(index) } } diff --git a/build.gradle.kts b/build.gradle.kts index be6fa2c9..7f2b9a0f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.4.2") + classpath("com.android.tools.build:gradle:8.0.2") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20") } }