diff --git a/integrations/AndroidManifest.xml b/integrations/AndroidManifest.xml
index 431b54ad6..e52a1167f 100644
--- a/integrations/AndroidManifest.xml
+++ b/integrations/AndroidManifest.xml
@@ -1,5 +1,5 @@
+
-
diff --git a/integrations/java/app/revanced/integrations/shared/Utils.java b/integrations/java/app/revanced/integrations/shared/Utils.java
index 279abdead..c2aa2f6bd 100644
--- a/integrations/java/app/revanced/integrations/shared/Utils.java
+++ b/integrations/java/app/revanced/integrations/shared/Utils.java
@@ -12,6 +12,7 @@ import android.os.Handler;
import android.os.Looper;
import android.preference.Preference;
import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
@@ -26,10 +27,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.Bidi;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.SortedMap;
-import java.util.TreeMap;
+import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
@@ -102,7 +100,6 @@ public class Utils {
view.setVisibility(View.GONE);
}
-
/**
* General purpose pool for network calls and other background tasks.
* All tasks run at max thread priority.
@@ -205,7 +202,9 @@ public class Utils {
public static T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) {
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
View childAt = viewGroup.getChildAt(i);
+ //noinspection unchecked
if (filter.matches(childAt)) {
+ //noinspection unchecked
return (T) childAt;
}
}
@@ -345,6 +344,12 @@ public class Utils {
}
}
+ public enum NetworkType {
+ NONE,
+ MOBILE,
+ OTHER,
+ }
+
public static boolean isNetworkConnected() {
NetworkType networkType = getNetworkType();
return networkType == NetworkType.MOBILE
@@ -393,48 +398,104 @@ public class Utils {
}
}
+ /**
+ * {@link PreferenceScreen} and {@link PreferenceGroup} sorting styles.
+ */
+ private enum Sort {
+ /**
+ * Sort by the localized preference title.
+ */
+ BY_TITLE("_sort_by_title"),
+
+ /**
+ * Sort by the preference keys.
+ */
+ BY_KEY("_sort_by_key"),
+
+ /**
+ * Unspecified sorting.
+ */
+ UNSORTED("_sort_by_unsorted");
+
+ final String keySuffix;
+
+ Sort(String keySuffix) {
+ this.keySuffix = keySuffix;
+ }
+
+ /**
+ * Defaults to {@link #UNSORTED} if key is null or has no sort suffix.
+ */
+ @NonNull
+ static Sort fromKey(@Nullable String key) {
+ if (key != null) {
+ for (Sort sort : values()) {
+ if (key.endsWith(sort.keySuffix)) {
+ return sort;
+ }
+ }
+ }
+ return UNSORTED;
+ }
+ }
+
private static final Regex punctuationRegex = new Regex("\\p{P}+");
/**
- * Sort the preferences by title and ignore the casing.
- *
- * Android Preferences are automatically sorted by title,
- * but if using a localized string key it sorts on the key and not the actual title text that's used at runtime.
- *
- * @param menuDepthToSort Maximum menu depth to sort. Menus deeper than this value
- * will show preferences in the order created in patches.
+ * Strips all punctuation and converts to lower case. A null parameter returns an empty string.
*/
- public static void sortPreferenceGroupByTitle(PreferenceGroup group, int menuDepthToSort) {
- if (menuDepthToSort == 0) return;
-
- SortedMap preferences = new TreeMap<>();
- for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
- Preference preference = group.getPreference(i);
- if (preference instanceof PreferenceGroup) {
- sortPreferenceGroupByTitle((PreferenceGroup) preference, menuDepthToSort - 1);
- }
- preferences.put(removePunctuationConvertToLowercase(preference.getTitle()), preference);
- }
-
- int prefIndex = 0;
- for (Preference pref : preferences.values()) {
- int indexToSet = prefIndex++;
- if (pref instanceof PreferenceGroup || pref.getIntent() != null) {
- // Place preference groups last.
- // Use an offset to push the group to the end.
- indexToSet += 1000;
- }
- pref.setOrder(indexToSet);
- }
- }
-
- public static String removePunctuationConvertToLowercase(CharSequence original) {
+ public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) {
+ if (original == null) return "";
return punctuationRegex.replace(original, "").toLowerCase();
}
- public enum NetworkType {
- NONE,
- MOBILE,
- OTHER,
+ /**
+ * Sort a PreferenceGroup and all it's sub groups by title or key.
+ *
+ * Sort order is determined by the preferences key {@link Sort} suffix.
+ *
+ * If a preference has no key or no {@link Sort} suffix,
+ * then the preferences are left unsorted.
+ */
+ public static void sortPreferenceGroups(@NonNull PreferenceGroup group) {
+ Sort sort = Sort.fromKey(group.getKey());
+ SortedMap preferences = new TreeMap<>();
+
+ for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
+ Preference preference = group.getPreference(i);
+
+ if (preference instanceof PreferenceGroup) {
+ sortPreferenceGroups((PreferenceGroup) preference);
+ }
+
+ final String sortValue;
+ switch (sort) {
+ case BY_TITLE:
+ sortValue = removePunctuationConvertToLowercase(preference.getTitle());
+ break;
+ case BY_KEY:
+ sortValue = preference.getKey();
+ break;
+ case UNSORTED:
+ continue; // Keep original sorting.
+ default:
+ throw new IllegalStateException();
+ }
+
+ preferences.put(sortValue, preference);
+ }
+
+ int index = 0;
+ for (Preference pref : preferences.values()) {
+ int order = index++;
+
+ // If the preference is a PreferenceScreen or is an intent preference, move to the top.
+ if (pref instanceof PreferenceScreen || pref.getIntent() != null) {
+ // Arbitrary high number.
+ order -= 1000;
+ }
+
+ pref.setOrder(order);
+ }
}
}
diff --git a/integrations/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java b/integrations/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java
index 6ef0c7785..dc1fcbd93 100644
--- a/integrations/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java
+++ b/integrations/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java
@@ -85,7 +85,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (identifier == 0) return;
addPreferencesFromResource(identifier);
- Utils.sortPreferenceGroupByTitle(getPreferenceScreen(), 2);
+ Utils.sortPreferenceGroups(getPreferenceScreen());
}
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
diff --git a/integrations/java/app/revanced/integrations/syncforreddit/FixSLinksPatch.java b/integrations/java/app/revanced/integrations/syncforreddit/FixSLinksPatch.java
new file mode 100644
index 000000000..a3c04ad61
--- /dev/null
+++ b/integrations/java/app/revanced/integrations/syncforreddit/FixSLinksPatch.java
@@ -0,0 +1,42 @@
+package app.revanced.integrations.syncforreddit;
+
+import android.os.StrictMode;
+import app.revanced.integrations.shared.Logger;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public final class FixSLinksPatch {
+ public static String resolveSLink(String link) {
+ if (link.matches(".*reddit\\.com/r/[^/]+/s/[^/]+")) {
+ Logger.printInfo(() -> "Resolving " + link);
+ try {
+ URL url = new URL(link);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setInstanceFollowRedirects(false);
+ connection.setRequestMethod("HEAD");
+
+ // Disable strict mode in order to allow network access on the main thread.
+ // This is not ideal, but it's the easiest solution for now.
+ final var currentPolicy = StrictMode.getThreadPolicy();
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+
+ connection.connect();
+ String location = connection.getHeaderField("location");
+ connection.disconnect();
+
+ // Restore the original strict mode policy.
+ StrictMode.setThreadPolicy(currentPolicy);
+
+ Logger.printInfo(() -> "Resolved " + link + " -> " + location);
+
+ return location;
+ } catch (Exception e) {
+ Logger.printException(() -> "Failed to resolve " + link, e);
+ }
+ }
+
+ return link;
+ }
+}
diff --git a/integrations/java/app/revanced/integrations/twitch/settings/AppCompatActivityHook.java b/integrations/java/app/revanced/integrations/twitch/settings/AppCompatActivityHook.java
index f08fec13b..42da77984 100644
--- a/integrations/java/app/revanced/integrations/twitch/settings/AppCompatActivityHook.java
+++ b/integrations/java/app/revanced/integrations/twitch/settings/AppCompatActivityHook.java
@@ -28,7 +28,7 @@ public class AppCompatActivityHook {
public static void startSettingsActivity() {
Logger.printDebug(() -> "Launching ReVanced settings");
- final var context = app.revanced.integrations.shared.Utils.getContext();
+ final var context = Utils.getContext();
if (context != null) {
Intent intent = new Intent(context, SettingsActivity.class);
diff --git a/integrations/java/app/revanced/integrations/twitter/patches/hook/twifucker/TwiFucker.kt b/integrations/java/app/revanced/integrations/twitter/patches/hook/twifucker/TwiFucker.kt
index 247ef0a34..3e0f7550b 100644
--- a/integrations/java/app/revanced/integrations/twitter/patches/hook/twifucker/TwiFucker.kt
+++ b/integrations/java/app/revanced/integrations/twitter/patches/hook/twifucker/TwiFucker.kt
@@ -9,8 +9,7 @@ import org.json.JSONObject
// https://raw.githubusercontent.com/Dr-TSNG/TwiFucker/880cdf1c1622e54ab45561ffcb4f53d94ed97bae/app/src/main/java/icu/nullptr/twifucker/hook/JsonHook.kt
internal object TwiFucker {
// root
- private fun JSONObject.jsonGetInstructions(): JSONArray? =
- optJSONObject("timeline")?.optJSONArray("instructions")
+ private fun JSONObject.jsonGetInstructions(): JSONArray? = optJSONObject("timeline")?.optJSONArray("instructions")
private fun JSONObject.jsonGetData(): JSONObject? = optJSONObject("data")
@@ -42,10 +41,12 @@ internal object TwiFucker {
// data
private fun JSONObject.dataGetInstructions(): JSONArray? {
- val timeline = optJSONObject("user_result")?.optJSONObject("result")
- ?.optJSONObject("timeline_response")?.optJSONObject("timeline")
- ?: optJSONObject("timeline_response")?.optJSONObject("timeline")
- ?: optJSONObject("timeline_response")
+ val timeline =
+ optJSONObject("user_result")?.optJSONObject("result")
+ ?.optJSONObject("timeline_response")?.optJSONObject("timeline")
+ ?: optJSONObject("timeline_response")?.optJSONObject("timeline")
+ ?: optJSONObject("search")?.optJSONObject("timeline_response")?.optJSONObject("timeline")
+ ?: optJSONObject("timeline_response")
return timeline?.optJSONArray("instructions")
}
@@ -64,7 +65,6 @@ internal object TwiFucker {
}
}?.optJSONObject("legacy")
-
// entry
private fun JSONObject.entryHasPromotedMetadata(): Boolean =
optJSONObject("content")?.optJSONObject("item")?.optJSONObject("content")
@@ -77,11 +77,9 @@ internal object TwiFucker {
optJSONObject("content")?.optJSONArray("items")
?: optJSONObject("content")?.optJSONObject("timelineModule")?.optJSONArray("items")
- private fun JSONObject.entryIsTweetDetailRelatedTweets(): Boolean =
- optString("entryId").startsWith("tweetdetailrelatedtweets-")
+ private fun JSONObject.entryIsTweetDetailRelatedTweets(): Boolean = optString("entryId").startsWith("tweetdetailrelatedtweets-")
- private fun JSONObject.entryGetTrends(): JSONArray? =
- optJSONObject("content")?.optJSONObject("timelineModule")?.optJSONArray("items")
+ private fun JSONObject.entryGetTrends(): JSONArray? = optJSONObject("content")?.optJSONObject("timelineModule")?.optJSONArray("items")
// trend
private fun JSONObject.trendHasPromotedMetadata(): Boolean =
@@ -104,8 +102,7 @@ internal object TwiFucker {
// instruction
private fun JSONObject.instructionTimelineAddEntries(): JSONArray? = optJSONArray("entries")
- private fun JSONObject.instructionGetAddEntries(): JSONArray? =
- optJSONObject("addEntries")?.optJSONArray("entries")
+ private fun JSONObject.instructionGetAddEntries(): JSONArray? = optJSONObject("addEntries")?.optJSONArray("entries")
private fun JSONObject.instructionCheckAndRemove(action: (JSONArray) -> Unit) {
instructionTimelineAddEntries()?.let(action)
@@ -164,9 +161,10 @@ internal object TwiFucker {
entriesRemoveTweetDetailRelatedTweets()
}
- private fun JSONObject.entryIsWhoToFollow(): Boolean = optString("entryId").let {
- it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-")
- }
+ private fun JSONObject.entryIsWhoToFollow(): Boolean =
+ optString("entryId").let {
+ it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-")
+ }
private fun JSONObject.itemContainsPromotedUser(): Boolean =
optJSONObject("item")?.optJSONObject("content")
@@ -217,4 +215,4 @@ internal object TwiFucker {
instruction.instructionCheckAndRemove(action)
}
}
-}
\ No newline at end of file
+}
diff --git a/integrations/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java b/integrations/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java
new file mode 100644
index 000000000..e781f19af
--- /dev/null
+++ b/integrations/java/app/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch.java
@@ -0,0 +1,12 @@
+package app.revanced.integrations.twitter.patches.links;
+
+import android.content.Context;
+import android.content.Intent;
+
+public final class OpenLinksWithAppChooserPatch {
+ public static void openWithChooser(final Context context, final Intent intent) {
+ intent.setAction("android.intent.action.VIEW");
+
+ context.startActivity(Intent.createChooser(intent, null));
+ }
+}
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/AlternativeThumbnailsPatch.java b/integrations/java/app/revanced/integrations/youtube/patches/AlternativeThumbnailsPatch.java
index 9cedb96e2..a16d481ae 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/AlternativeThumbnailsPatch.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/AlternativeThumbnailsPatch.java
@@ -422,7 +422,7 @@ public final class AlternativeThumbnailsPatch {
private static final int CACHE_LIMIT = 1000;
@Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
+ protected boolean removeEldestEntry(Entry eldest) {
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
}
};
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/ChangeStartPagePatch.java b/integrations/java/app/revanced/integrations/youtube/patches/ChangeStartPagePatch.java
index 2e3f007a4..f31af61b1 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/ChangeStartPagePatch.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/ChangeStartPagePatch.java
@@ -1,9 +1,9 @@
package app.revanced.integrations.youtube.patches;
import android.content.Intent;
-
-import app.revanced.integrations.youtube.settings.Settings;
+import android.net.Uri;
import app.revanced.integrations.shared.Logger;
+import app.revanced.integrations.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class ChangeStartPagePatch {
@@ -12,6 +12,10 @@ public final class ChangeStartPagePatch {
if (startPage.isEmpty()) return;
Logger.printDebug(() -> "Changing start page to " + startPage);
- intent.setAction("com.google.android.youtube.action." + startPage);
+
+ if (startPage.startsWith("www"))
+ intent.setData(Uri.parse(startPage));
+ else
+ intent.setAction("com.google.android.youtube.action." + startPage);
}
}
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/DisableSuggestedVideoEndScreenPatch.java b/integrations/java/app/revanced/integrations/youtube/patches/DisableSuggestedVideoEndScreenPatch.java
index 3ceea0cc1..0b7897bac 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/DisableSuggestedVideoEndScreenPatch.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/DisableSuggestedVideoEndScreenPatch.java
@@ -3,29 +3,27 @@ package app.revanced.integrations.youtube.patches;
import android.annotation.SuppressLint;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.youtube.settings.Settings;
/** @noinspection unused*/
public final class DisableSuggestedVideoEndScreenPatch {
@SuppressLint("StaticFieldLeak")
- private static View lastView;
+ private static ImageView lastView;
public static void closeEndScreen(final ImageView imageView) {
if (!Settings.DISABLE_SUGGESTED_VIDEO_END_SCREEN.get()) return;
- // Get a parent view which can be listened to for layout changes.
- final var parent = imageView.getParent().getParent();
-
// Prevent adding the listener multiple times.
- if (lastView == parent) return;
+ if (lastView == imageView) return;
+ lastView = imageView;
- lastView = (ViewGroup)parent;
- lastView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- // Disable sound effects to prevent the click sound.
- imageView.setSoundEffectsEnabled(false);
- imageView.performClick();
+ imageView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ if (imageView.isShown()) imageView.callOnClick();
});
}
}
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java b/integrations/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java
index 51774f04e..0cb687f63 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/ReturnYouTubeDislikePatch.java
@@ -667,7 +667,7 @@ public class ReturnYouTubeDislikePatch {
*
* Called when the user likes or dislikes.
*
- * @param vote int that matches {@link ReturnYouTubeDislike.Vote#value}
+ * @param vote int that matches {@link Vote#value}
*/
public static void sendVote(int vote) {
try {
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java b/integrations/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java
index b1d38c6eb..c122cd914 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/components/AdsFilter.java
@@ -14,7 +14,7 @@ import app.revanced.integrations.youtube.StringTrieSearch;
@SuppressWarnings("unused")
public final class AdsFilter extends Filter {
// region Fullscreen ad
- private static long lastTimeClosedFullscreenAd = 0;
+ private static volatile long lastTimeClosedFullscreenAd;
private static final Instrumentation instrumentation = new Instrumentation();
private final StringFilterGroup fullscreenAd;
@@ -168,6 +168,9 @@ public final class AdsFilter extends Filter {
Logger.printDebug(() -> "Closing fullscreen ad");
- Utils.runOnMainThreadDelayed(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK), 1000);
+ Utils.runOnMainThreadDelayed(() -> {
+ // Must run off main thread (Odd, but whatever).
+ Utils.runOnBackgroundThread(() -> instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK));
+ }, 1000);
}
}
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/components/ButtonsFilter.java b/integrations/java/app/revanced/integrations/youtube/patches/components/ButtonsFilter.java
index 5db9a8a84..c92cba972 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/components/ButtonsFilter.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/components/ButtonsFilter.java
@@ -49,10 +49,6 @@ final class ButtonsFilter extends Filter {
);
bufferButtonsGroupList.addAll(
- new ByteArrayFilterGroup(
- Settings.HIDE_LIVE_CHAT_BUTTON,
- "yt_outline_message_bubble_overlap"
- ),
new ByteArrayFilterGroup(
Settings.HIDE_REPORT_BUTTON,
"yt_outline_flag"
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java b/integrations/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java
index f8ac724b7..605b48bea 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/components/ReturnYouTubeDislikeFilterPatch.java
@@ -45,7 +45,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
@Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
+ protected boolean removeEldestEntry(Entry eldest) {
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
}
};
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java b/integrations/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java
index 6782223c9..c0c8d8c51 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/components/ShortsFilter.java
@@ -17,6 +17,9 @@ public final class ShortsFilter extends Filter {
public static PivotBar pivotBar; // Set by patch.
private final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
+ private final StringFilterGroup shortsCompactFeedVideoPath;
+ private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
+
private final StringFilterGroup channelBar;
private final StringFilterGroup subscribeButton;
private final StringFilterGroup subscribeButtonPaused;
@@ -35,7 +38,6 @@ public final class ShortsFilter extends Filter {
"shorts_grid",
"shorts_video_cell",
"shorts_pivot_item"
-
);
// Feed Shorts shelf header.
// Use a different filter group for this pattern, as it requires an additional check after matching.
@@ -52,6 +54,15 @@ public final class ShortsFilter extends Filter {
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
+ // Shorts that appear in the feed/search when the device is using tablet layout.
+ shortsCompactFeedVideoPath = new StringFilterGroup(Settings.HIDE_SHORTS,
+ "compact_video.eml");
+ // Filter out items that use the 'frame0' thumbnail.
+ // This is a valid thumbnail for both regular videos and Shorts,
+ // but it appears these thumbnails are used only for Shorts.
+ shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(Settings.HIDE_SHORTS,
+ "/frame0.jpg");
+
// Shorts player components.
var joinButton = new StringFilterGroup(
Settings.HIDE_SHORTS_JOIN_BUTTON,
@@ -89,6 +100,7 @@ public final class ShortsFilter extends Filter {
);
addPathCallbacks(
+ shortsCompactFeedVideoPath,
joinButton, subscribeButton, subscribeButtonPaused,
channelBar, soundButton, infoPanel, videoActionButton
);
@@ -122,6 +134,13 @@ public final class ShortsFilter extends Filter {
matchedGroup == subscribeButtonPaused
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
+ if (matchedGroup == shortsCompactFeedVideoPath) {
+ if (contentIndex == 0 && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
+ return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
+ }
+ return false;
+ }
+
// Video action buttons (comment, share, remix) have the same path.
if (matchedGroup == videoActionButton) {
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
@@ -132,10 +151,11 @@ public final class ShortsFilter extends Filter {
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
// to avoid false positives.
- if (path.startsWith(REEL_CHANNEL_BAR_PATH))
- if (matchedGroup == subscribeButton) return super.isFiltered(
+ if (matchedGroup == subscribeButton) {
+ if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
);
+ }
return false;
} else if (matchedGroup == shelfHeader) {
diff --git a/integrations/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java b/integrations/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java
index c57bf0fc6..93219797d 100644
--- a/integrations/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java
+++ b/integrations/java/app/revanced/integrations/youtube/patches/spoof/SpoofAppVersionPatch.java
@@ -1,12 +1,26 @@
package app.revanced.integrations.youtube.patches.spoof;
+import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.youtube.settings.Settings;
@SuppressWarnings("unused")
public class SpoofAppVersionPatch {
- private static final boolean SPOOF_APP_VERSION_ENABLED = Settings.SPOOF_APP_VERSION.get();
- private static final String SPOOF_APP_VERSION_TARGET = Settings.SPOOF_APP_VERSION_TARGET.get();
+ private static final boolean SPOOF_APP_VERSION_ENABLED;
+ private static final String SPOOF_APP_VERSION_TARGET;
+
+ static {
+ // TODO: remove this migration code
+ // Spoof targets below 17.33 that no longer reliably work.
+ if (Settings.SPOOF_APP_VERSION_TARGET.get().compareTo("17.33.01") < 0) {
+ Logger.printInfo(() -> "Resetting spoof app version target");
+ Settings.SPOOF_APP_VERSION_TARGET.resetToDefault();
+ }
+ // End migration
+
+ SPOOF_APP_VERSION_ENABLED = Settings.SPOOF_APP_VERSION.get();
+ SPOOF_APP_VERSION_TARGET = Settings.SPOOF_APP_VERSION_TARGET.get();
+ }
/**
* Injection point
diff --git a/integrations/java/app/revanced/integrations/youtube/requests/Route.java b/integrations/java/app/revanced/integrations/youtube/requests/Route.java
index 877a364c9..afbc24506 100644
--- a/integrations/java/app/revanced/integrations/youtube/requests/Route.java
+++ b/integrations/java/app/revanced/integrations/youtube/requests/Route.java
@@ -2,10 +2,10 @@ package app.revanced.integrations.youtube.requests;
public class Route {
private final String route;
- private final Route.Method method;
+ private final Method method;
private final int paramCount;
- public Route(Route.Method method, String route) {
+ public Route(Method method, String route) {
this.method = method;
this.route = route;
this.paramCount = countMatches(route, '{');
@@ -14,11 +14,11 @@ public class Route {
throw new IllegalArgumentException("Not enough parameters");
}
- public Route.Method getMethod() {
+ public Method getMethod() {
return method;
}
- public Route.CompiledRoute compile(String... params) {
+ public CompiledRoute compile(String... params) {
if (params.length != paramCount)
throw new IllegalArgumentException("Error compiling route [" + route + "], incorrect amount of parameters provided. " +
"Expected: " + paramCount + ", provided: " + params.length);
@@ -29,7 +29,7 @@ public class Route {
int paramEnd = compiledRoute.indexOf("}");
compiledRoute.replace(paramStart, paramEnd + 1, params[i]);
}
- return new Route.CompiledRoute(this, compiledRoute.toString());
+ return new CompiledRoute(this, compiledRoute.toString());
}
public static class CompiledRoute {
@@ -45,7 +45,7 @@ public class Route {
return compiledRoute;
}
- public Route.Method getMethod() {
+ public Method getMethod() {
return baseRoute.method;
}
}
diff --git a/integrations/java/app/revanced/integrations/youtube/settings/Settings.java b/integrations/java/app/revanced/integrations/youtube/settings/Settings.java
index bbf11fba3..e011f941f 100644
--- a/integrations/java/app/revanced/integrations/youtube/settings/Settings.java
+++ b/integrations/java/app/revanced/integrations/youtube/settings/Settings.java
@@ -15,15 +15,14 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
-import app.revanced.integrations.shared.Logger;
+import app.revanced.integrations.shared.settings.BaseSettings;
import app.revanced.integrations.shared.settings.BooleanSetting;
import app.revanced.integrations.shared.settings.FloatSetting;
import app.revanced.integrations.shared.settings.IntegerSetting;
import app.revanced.integrations.shared.settings.LongSetting;
import app.revanced.integrations.shared.settings.Setting;
-import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
-import app.revanced.integrations.shared.settings.BaseSettings;
import app.revanced.integrations.shared.settings.StringSetting;
+import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
public class Settings extends BaseSettings {
@@ -129,7 +128,7 @@ public class Settings extends BaseSettings {
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity",100, true);
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
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", "17.08.35", true, parent(SPOOF_APP_VERSION));
+ public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "17.33.42", true, parent(SPOOF_APP_VERSION));
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true);
public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
public static final BooleanSetting USE_TABLET_MINIPLAYER = new BooleanSetting("revanced_tablet_miniplayer", FALSE, true);
@@ -167,7 +166,6 @@ public class Settings extends BaseSettings {
// Action buttons
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
- public static final BooleanSetting HIDE_LIVE_CHAT_BUTTON = new BooleanSetting("revanced_hide_live_chat_button", FALSE);
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", TRUE);
diff --git a/integrations/java/app/revanced/integrations/youtube/settings/preference/ReturnYouTubeDislikePreferenceFragment.java b/integrations/java/app/revanced/integrations/youtube/settings/preference/ReturnYouTubeDislikePreferenceFragment.java
index 50b0a78bb..117c2f994 100644
--- a/integrations/java/app/revanced/integrations/youtube/settings/preference/ReturnYouTubeDislikePreferenceFragment.java
+++ b/integrations/java/app/revanced/integrations/youtube/settings/preference/ReturnYouTubeDislikePreferenceFragment.java
@@ -150,7 +150,7 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
pref.getContext().startActivity(i);
return false;
});
- preferenceScreen.addPreference(aboutWebsitePreference);
+ aboutCategory.addPreference(aboutWebsitePreference);
// RYD API connection statistics