mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-04-30 14:44:29 +02:00
chore: Merge branch dev
to main
(#505)
This commit is contained in:
commit
3344375fb9
71
CHANGELOG.md
71
CHANGELOG.md
@ -1,3 +1,74 @@
|
|||||||
|
# [0.121.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0-dev.6...v0.121.0-dev.7) (2023-11-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Player flyout menu:** Restore functionality ([#502](https://github.com/ReVanced/revanced-integrations/issues/502)) ([c048527](https://github.com/ReVanced/revanced-integrations/commit/c048527dc0bc6b1b3de9c451ca909aab7ad93d0f))
|
||||||
|
|
||||||
|
# [0.121.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0-dev.5...v0.121.0-dev.6) (2023-10-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Client spoof:** Set the client version correctly ([f203731](https://github.com/ReVanced/revanced-integrations/commit/f2037316d36f99ef79ae5792e34d8616ecd31c80))
|
||||||
|
|
||||||
|
# [0.121.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0-dev.4...v0.121.0-dev.5) (2023-10-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Disable suggested video end screen:** Hide the view once possible ([df27822](https://github.com/ReVanced/revanced-integrations/commit/df278222e8814612797e55e616d4ebc075cafb92))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Disable precise seeking gesture:** Use better patch name ([2453d30](https://github.com/ReVanced/revanced-integrations/commit/2453d30970ac59d78647386f4fe5d904dbc145e4))
|
||||||
|
* **YouTube:** Add `Enable old seekbar thumbnails` patch ([75297a5](https://github.com/ReVanced/revanced-integrations/commit/75297a52c1c5f7c2b928964d08b055380f4a08fe))
|
||||||
|
|
||||||
|
# [0.121.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0-dev.3...v0.121.0-dev.4) (2023-10-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - ReturnYouTubeDislike:** Use API back off if client connection fails for any reason ([#509](https://github.com/ReVanced/revanced-integrations/issues/509)) ([40cfa1e](https://github.com/ReVanced/revanced-integrations/commit/40cfa1e9af2b064b464c4d03d5c28b5932621d62))
|
||||||
|
|
||||||
|
# [0.121.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0-dev.2...v0.121.0-dev.3) (2023-10-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Disable fullscreen ambient mode` patch ([bf50711](https://github.com/ReVanced/revanced-integrations/commit/bf5071107b8bc88ac6562d45bfa28bdab8e566c7))
|
||||||
|
* **YouTube:** Add `Disable suggested video end screen` patch ([6bd5aae](https://github.com/ReVanced/revanced-integrations/commit/6bd5aae9772e80809dbee9f8fffc1247364a9a13))
|
||||||
|
|
||||||
|
# [0.121.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.121.0-dev.1...v0.121.0-dev.2) (2023-10-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - ReturnYouTubeDislike:** Fix RYD prefetching home feed Shorts ([#508](https://github.com/ReVanced/revanced-integrations/issues/508)) ([98c91af](https://github.com/ReVanced/revanced-integrations/commit/98c91af130b57322aa98a3e66ec0acad26bfc7d6))
|
||||||
|
|
||||||
|
# [0.121.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.120.1-dev.3...v0.121.0-dev.1) (2023-10-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide video quality menu footer ([04608d3](https://github.com/ReVanced/revanced-integrations/commit/04608d32e88d184e4662a71498d7a49c1fbbdb25))
|
||||||
|
|
||||||
|
## [0.120.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.120.1-dev.2...v0.120.1-dev.3) (2023-10-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Custom filter:** Fix app crash if invalid character is used in custom filter ([#506](https://github.com/ReVanced/revanced-integrations/issues/506)) ([debd0a2](https://github.com/ReVanced/revanced-integrations/commit/debd0a2e1101e543161390fd3ced6bda19030155))
|
||||||
|
|
||||||
|
## [0.120.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.120.1-dev.1...v0.120.1-dev.2) (2023-10-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* Revert "fix(YouTube - Minimized playback): Fix pip incorrectly showing for Short playback (#504)" ([c1c7e3b](https://github.com/ReVanced/revanced-integrations/commit/c1c7e3b5964394de6af39f6fb83d667eba174f0a)), closes [#504](https://github.com/ReVanced/revanced-integrations/issues/504)
|
||||||
|
* Revert "chore(release): 0.120.1-dev.1 [skip ci]" ([e68f558](https://github.com/ReVanced/revanced-integrations/commit/e68f558e9ccac4c6e0d9113b3f134e89edd2233f))
|
||||||
|
|
||||||
# [0.120.0](https://github.com/ReVanced/revanced-integrations/compare/v0.119.2...v0.120.0) (2023-10-20)
|
# [0.120.0](https://github.com/ReVanced/revanced-integrations/compare/v0.119.2...v0.120.0) (2023-10-20)
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
/** @noinspection unused*/
|
||||||
|
public final class DisableFullscreenAmbientModePatch {
|
||||||
|
public static boolean enableFullScreenAmbientMode() {
|
||||||
|
return !SettingsEnum.DISABLE_FULLSCREEN_AMBIENT_MODE.getBoolean();
|
||||||
|
}
|
||||||
|
}
|
@ -4,14 +4,14 @@ import android.view.MotionEvent;
|
|||||||
import android.view.VelocityTracker;
|
import android.view.VelocityTracker;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
public final class DisableFineScrubbingGesturePatch {
|
public final class DisablePreciseSeekingGesturePatch {
|
||||||
/**
|
/**
|
||||||
* Disables the fine scrubbing gesture.
|
* Disables the gesture that is used to seek precisely.
|
||||||
* @param tracker The velocity tracker that is used to determine the gesture.
|
* @param tracker The velocity tracker that is used to determine the gesture.
|
||||||
* @param event The motion event that is used to determine the gesture.
|
* @param event The motion event that is used to determine the gesture.
|
||||||
*/
|
*/
|
||||||
public static void disableGesture(VelocityTracker tracker, MotionEvent event) {
|
public static void disableGesture(VelocityTracker tracker, MotionEvent event) {
|
||||||
if (SettingsEnum.DISABLE_FINE_SCRUBBING_GESTURE.getBoolean()) return;
|
if (SettingsEnum.DISABLE_PRECISE_SEEKING_GESTURE.getBoolean()) return;
|
||||||
|
|
||||||
tracker.addMovement(event);
|
tracker.addMovement(event);
|
||||||
}
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
/** @noinspection unused*/
|
||||||
|
public final class DisableSuggestedVideoEndScreenPatch {
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private static View lastView;
|
||||||
|
|
||||||
|
public static void closeEndScreen(final ImageView imageView) {
|
||||||
|
if (!SettingsEnum.DISABLE_SUGGESTED_VIDEO_END_SCREEN.getBoolean()) return;
|
||||||
|
|
||||||
|
// Get the 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;
|
||||||
|
|
||||||
|
lastView = (ViewGroup)parent;
|
||||||
|
lastView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> imageView.performClick());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public final class EnableOldSeekbarThumbnailsPatch {
|
||||||
|
public static boolean enableOldSeekbarThumbnails() {
|
||||||
|
return !SettingsEnum.ENABLE_OLD_SEEKBAR_THUMBNAILS.getBoolean();
|
||||||
|
}
|
||||||
|
}
|
@ -404,11 +404,14 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
* Injection point. Uses 'playback response' video id hook to preload RYD.
|
||||||
*/
|
*/
|
||||||
public static void preloadVideoId(@NonNull String videoId) {
|
public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) {
|
||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
// Shorts shelf in home and subscription feed causes player response hook to be called,
|
||||||
|
// and the 'is opening/playing' parameter will be false.
|
||||||
|
// This hook will be called again when the Short is actually opened.
|
||||||
|
if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneOrHidden()) {
|
if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (videoId.equals(lastPrefetchedVideoId)) {
|
if (videoId.equals(lastPrefetchedVideoId)) {
|
||||||
@ -471,12 +474,13 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (videoIdIsSame(currentVideoData, videoId)) {
|
if (videoIdIsSame(currentVideoData, videoId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentVideoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
ReturnYouTubeDislike data = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||||
// Pre-emptively set the data to short status.
|
// Pre-emptively set the data to short status.
|
||||||
// Required to prevent Shorts data from being used on a minimized video in incognito mode.
|
// Required to prevent Shorts data from being used on a minimized video in incognito mode.
|
||||||
if (isNoneHiddenOrSlidingMinimized) {
|
if (isNoneHiddenOrSlidingMinimized) {
|
||||||
currentVideoData.setVideoIdIsShort(true);
|
data.setVideoIdIsShort(true);
|
||||||
}
|
}
|
||||||
|
currentVideoData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType
|
LogHelper.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType
|
||||||
|
@ -69,7 +69,7 @@ public final class VideoInformation {
|
|||||||
*
|
*
|
||||||
* @param videoId The id of the last video loaded.
|
* @param videoId The id of the last video loaded.
|
||||||
*/
|
*/
|
||||||
public static void setPlayerResponseVideoId(@NonNull String videoId) {
|
public static void setPlayerResponseVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) {
|
||||||
if (!playerResponseVideoId.equals(videoId)) {
|
if (!playerResponseVideoId.equals(videoId)) {
|
||||||
LogHelper.printDebug(() -> "New player response video id: " + videoId);
|
LogHelper.printDebug(() -> "New player response video id: " + videoId);
|
||||||
playerResponseVideoId = videoId;
|
playerResponseVideoId = videoId;
|
||||||
|
@ -120,6 +120,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"inline_expander"
|
"inline_expander"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var videoQualityMenuFooter = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_VIDEO_QUALITY_MENU_FOOTER,
|
||||||
|
"quality_sheet_footer"
|
||||||
|
);
|
||||||
|
|
||||||
final var chapters = new StringFilterGroup(
|
final var chapters = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_CHAPTERS,
|
SettingsEnum.HIDE_CHAPTERS,
|
||||||
"macro_markers_carousel"
|
"macro_markers_carousel"
|
||||||
@ -196,6 +201,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
joinMembership,
|
joinMembership,
|
||||||
medicalPanel,
|
medicalPanel,
|
||||||
notifyMe,
|
notifyMe,
|
||||||
|
videoQualityMenuFooter,
|
||||||
infoPanel,
|
infoPanel,
|
||||||
subscribersCommunityGuidelines,
|
subscribersCommunityGuidelines,
|
||||||
channelGuidelines,
|
channelGuidelines,
|
||||||
|
@ -128,8 +128,20 @@ class StringFilterGroup extends FilterGroup<String> {
|
|||||||
|
|
||||||
final class CustomFilterGroup extends StringFilterGroup {
|
final class CustomFilterGroup extends StringFilterGroup {
|
||||||
|
|
||||||
public CustomFilterGroup(final SettingsEnum setting, final SettingsEnum filter) {
|
private static String[] getFilterPatterns(SettingsEnum setting) {
|
||||||
super(setting, filter.getString().split("\\s+"));
|
String[] patterns = setting.getString().split("\\s+");
|
||||||
|
for (String pattern : patterns) {
|
||||||
|
if (!StringTrieSearch.isValidPattern(pattern)) {
|
||||||
|
ReVancedUtils.showToastLong("Invalid custom filter, resetting to default");
|
||||||
|
setting.saveValue(setting.defaultValue);
|
||||||
|
return getFilterPatterns(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomFilterGroup(SettingsEnum setting, SettingsEnum filter) {
|
||||||
|
super(setting, getFilterPatterns(filter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,26 +6,38 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
|
|
||||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||||
|
|
||||||
// Search the buffer only if the flyout menu identifier is found.
|
// Search the buffer only if the flyout menu path is found.
|
||||||
// Handle the searching in this class instead of adding to the global filter group (which searches all the time)
|
// Handle the searching in this class instead of adding to the global filter group (which searches all the time)
|
||||||
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
|
private final ByteArrayFilterGroup exception;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public PlayerFlyoutMenuItemsFilter() {
|
public PlayerFlyoutMenuItemsFilter() {
|
||||||
identifierFilterGroupList.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|"));
|
exception = new ByteArrayAsStringFilterGroup(
|
||||||
|
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled
|
||||||
|
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
|
||||||
|
"quality_sheet"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Using pathFilterGroupList due to new flyout panel(A/B)
|
||||||
|
pathFilterGroupList.addAll(
|
||||||
|
new StringFilterGroup(null, "overflow_menu_item.eml|")
|
||||||
|
);
|
||||||
|
|
||||||
flyoutFilterGroupList.addAll(
|
flyoutFilterGroupList.addAll(
|
||||||
new ByteArrayAsStringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_QUALITY_MENU,
|
|
||||||
"yt_outline_gear"
|
|
||||||
),
|
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayAsStringFilterGroup(
|
||||||
SettingsEnum.HIDE_CAPTIONS_MENU,
|
SettingsEnum.HIDE_CAPTIONS_MENU,
|
||||||
"closed_caption"
|
"closed_caption"
|
||||||
),
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
|
||||||
|
"yt_outline_gear"
|
||||||
|
),
|
||||||
new ByteArrayAsStringFilterGroup(
|
new ByteArrayAsStringFilterGroup(
|
||||||
SettingsEnum.HIDE_LOOP_VIDEO_MENU,
|
SettingsEnum.HIDE_LOOP_VIDEO_MENU,
|
||||||
"yt_outline_arrow_repeat_1_"
|
"yt_outline_arrow_repeat_1_"
|
||||||
@ -64,6 +76,10 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
|
// Shorts also use this player flyout panel
|
||||||
|
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered())
|
||||||
|
return false;
|
||||||
|
|
||||||
// Only 1 group is added to the parent class, so the matched group must be the overflow menu.
|
// 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()) {
|
if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
// Super class handles logging.
|
// Super class handles logging.
|
||||||
|
@ -53,9 +53,9 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void newPlayerResponseVideoId(String videoId) {
|
public static void newPlayerResponseVideoId(String videoId, boolean videoIsOpeningOrPlaying) {
|
||||||
try {
|
try {
|
||||||
if (!SettingsEnum.RYD_SHORTS.getBoolean()) {
|
if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (lastVideoIds) {
|
synchronized (lastVideoIds) {
|
||||||
|
@ -31,7 +31,7 @@ final class PlayerRoutes {
|
|||||||
|
|
||||||
JSONObject client = new JSONObject();
|
JSONObject client = new JSONObject();
|
||||||
client.put("clientName", "ANDROID");
|
client.put("clientName", "ANDROID");
|
||||||
client.put("clientVersion", "18.37.36");
|
client.put("clientVersion", ReVancedUtils.getVersionName());
|
||||||
client.put("androidSdkVersion", 34);
|
client.put("androidSdkVersion", 34);
|
||||||
|
|
||||||
context.put("client", client);
|
context.put("client", client);
|
||||||
|
@ -80,7 +80,7 @@ public class ReturnYouTubeDislike {
|
|||||||
* How long to retain unsuccessful RYD fetches,
|
* How long to retain unsuccessful RYD fetches,
|
||||||
* and also the minimum time before retrying again.
|
* and also the minimum time before retrying again.
|
||||||
*/
|
*/
|
||||||
private static final long CACHE_TIMEOUT_FAILURE_MILLISECONDS = 2 * 60 * 1000; // 2 Minutes
|
private static final long CACHE_TIMEOUT_FAILURE_MILLISECONDS = 3 * 60 * 1000; // 3 Minutes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
|
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
|
||||||
|
@ -32,13 +32,13 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
/**
|
/**
|
||||||
* {@link #fetchVotes(String)} TCP connection timeout
|
* {@link #fetchVotes(String)} TCP connection timeout
|
||||||
*/
|
*/
|
||||||
private static final int API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS = 2000;
|
private static final int API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS = 2 * 1000; // 2 Seconds.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link #fetchVotes(String)} HTTP read timeout.
|
* {@link #fetchVotes(String)} HTTP read timeout.
|
||||||
* To locally debug and force timeouts, change this to a very small number (ie: 100)
|
* To locally debug and force timeouts, change this to a very small number (ie: 100)
|
||||||
*/
|
*/
|
||||||
private static final int API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS = 5000;
|
private static final int API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS = 5 * 1000; // 5 Seconds.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default connection and response timeout for voting and registration.
|
* Default connection and response timeout for voting and registration.
|
||||||
@ -46,7 +46,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
* Voting and user registration runs in the background and has has no urgency
|
* Voting and user registration runs in the background and has has no urgency
|
||||||
* so this can be a larger value.
|
* so this can be a larger value.
|
||||||
*/
|
*/
|
||||||
private static final int API_REGISTER_VOTE_TIMEOUT_MILLISECONDS = 90000;
|
private static final int API_REGISTER_VOTE_TIMEOUT_MILLISECONDS = 60 * 1000; // 60 Seconds.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response code of a successful API call
|
* Response code of a successful API call
|
||||||
@ -54,31 +54,30 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
private static final int HTTP_STATUS_CODE_SUCCESS = 200;
|
private static final int HTTP_STATUS_CODE_SUCCESS = 200;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response code indicating the video id is not for a video that can be voted for.
|
* Indicates a client rate limit has been reached and the client must back off.
|
||||||
* (it's not a Short or a regular video, and it's likely a YouTube Story)
|
|
||||||
*/
|
*/
|
||||||
private static final int HTTP_STATUS_CODE_NOT_FOUND = 404;
|
private static final int HTTP_STATUS_CODE_RATE_LIMIT = 429;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates a client rate limit has been reached
|
* How long to wait until API calls are resumed, if the API requested a back off.
|
||||||
|
* No clear guideline of how long to wait until resuming.
|
||||||
*/
|
*/
|
||||||
private static final int RATE_LIMIT_HTTP_STATUS_CODE = 429;
|
private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 4 * 60 * 1000; // 4 Minutes.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How long to wait until API calls are resumed, if a rate limit is hit.
|
* How long to wait until API calls are resumed, if any connection error occurs.
|
||||||
* No clear guideline of how long to backoff. Using 2 minutes for now.
|
|
||||||
*/
|
*/
|
||||||
private static final int RATE_LIMIT_BACKOFF_SECONDS = 120;
|
private static final int BACKOFF_CONNECTION_ERROR_MILLISECONDS = 60 * 1000; // 60 Seconds.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last time a {@link #RATE_LIMIT_HTTP_STATUS_CODE} was reached.
|
* If non zero, then the system time of when API calls can resume.
|
||||||
* zero if has not been reached.
|
|
||||||
*/
|
*/
|
||||||
private static volatile long lastTimeRateLimitWasHit; // must be volatile, since different threads read/write to this
|
private static volatile long timeToResumeAPICalls; // must be volatile, since different threads read/write to this
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of times {@link #RATE_LIMIT_HTTP_STATUS_CODE} was requested by RYD api.
|
* Number of times {@link #HTTP_STATUS_CODE_RATE_LIMIT} was requested by RYD api.
|
||||||
* Does not include network calls attempted while rate limit is in effect
|
* Does not include network calls attempted while rate limit is in effect,
|
||||||
|
* and does not include rate limit imposed if a fetch fails.
|
||||||
*/
|
*/
|
||||||
private static volatile int numberOfRateLimitRequestsEncountered;
|
private static volatile int numberOfRateLimitRequestsEncountered;
|
||||||
|
|
||||||
@ -165,16 +164,16 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
* @return True, if api rate limit is in effect.
|
* @return True, if api rate limit is in effect.
|
||||||
*/
|
*/
|
||||||
private static boolean checkIfRateLimitInEffect(String apiEndPointName) {
|
private static boolean checkIfRateLimitInEffect(String apiEndPointName) {
|
||||||
if (lastTimeRateLimitWasHit == 0) {
|
if (timeToResumeAPICalls == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final long numberOfSecondsSinceLastRateLimit = (System.currentTimeMillis() - lastTimeRateLimitWasHit) / 1000;
|
final long now = System.currentTimeMillis();
|
||||||
if (numberOfSecondsSinceLastRateLimit < RATE_LIMIT_BACKOFF_SECONDS) {
|
if (now > timeToResumeAPICalls) {
|
||||||
LogHelper.printDebug(() -> "Ignoring api call " + apiEndPointName + " as only "
|
timeToResumeAPICalls = 0;
|
||||||
+ numberOfSecondsSinceLastRateLimit + " seconds has passed since last rate limit.");
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
LogHelper.printDebug(() -> "Ignoring api call " + apiEndPointName + " as rate limit is in effect");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,37 +185,33 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
final double RANDOM_RATE_LIMIT_PERCENTAGE = 0.2; // 20% chance of a triggering a rate limit
|
final double RANDOM_RATE_LIMIT_PERCENTAGE = 0.2; // 20% chance of a triggering a rate limit
|
||||||
if (Math.random() < RANDOM_RATE_LIMIT_PERCENTAGE) {
|
if (Math.random() < RANDOM_RATE_LIMIT_PERCENTAGE) {
|
||||||
LogHelper.printDebug(() -> "Artificially triggering rate limit for debug purposes");
|
LogHelper.printDebug(() -> "Artificially triggering rate limit for debug purposes");
|
||||||
httpResponseCode = RATE_LIMIT_HTTP_STATUS_CODE;
|
httpResponseCode = HTTP_STATUS_CODE_RATE_LIMIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return httpResponseCode == HTTP_STATUS_CODE_RATE_LIMIT;
|
||||||
if (httpResponseCode == RATE_LIMIT_HTTP_STATUS_CODE) {
|
|
||||||
lastTimeRateLimitWasHit = System.currentTimeMillis();
|
|
||||||
//noinspection NonAtomicOperationOnVolatileField // don't care, field is used only as an estimate
|
|
||||||
numberOfRateLimitRequestsEncountered++;
|
|
||||||
LogHelper.printDebug(() -> "API rate limit was hit. Stopping API calls for the next "
|
|
||||||
+ RATE_LIMIT_BACKOFF_SECONDS + " seconds");
|
|
||||||
ReVancedUtils.showToastLong(str("revanced_ryd_failure_client_rate_limit_requested"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("NonAtomicOperationOnVolatileField") // do not want to pay performance cost of full synchronization for debug fields that are only estimates anyways
|
@SuppressWarnings("NonAtomicOperationOnVolatileField") // Don't care, fields are estimates.
|
||||||
private static void updateStatistics(long timeNetworkCallStarted, long timeNetworkCallEnded, boolean connectionError, boolean rateLimitHit) {
|
private static void updateRateLimitAndStats(long timeNetworkCallStarted, boolean connectionError, boolean rateLimitHit) {
|
||||||
if (connectionError && rateLimitHit) {
|
if (connectionError && rateLimitHit) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
final long responseTimeOfFetchCall = timeNetworkCallEnded - timeNetworkCallStarted;
|
final long responseTimeOfFetchCall = System.currentTimeMillis() - timeNetworkCallStarted;
|
||||||
fetchCallResponseTimeTotal += responseTimeOfFetchCall;
|
fetchCallResponseTimeTotal += responseTimeOfFetchCall;
|
||||||
fetchCallResponseTimeMin = (fetchCallResponseTimeMin == 0) ? responseTimeOfFetchCall : Math.min(responseTimeOfFetchCall, fetchCallResponseTimeMin);
|
fetchCallResponseTimeMin = (fetchCallResponseTimeMin == 0) ? responseTimeOfFetchCall : Math.min(responseTimeOfFetchCall, fetchCallResponseTimeMin);
|
||||||
fetchCallResponseTimeMax = Math.max(responseTimeOfFetchCall, fetchCallResponseTimeMax);
|
fetchCallResponseTimeMax = Math.max(responseTimeOfFetchCall, fetchCallResponseTimeMax);
|
||||||
fetchCallCount++;
|
fetchCallCount++;
|
||||||
if (connectionError) {
|
if (connectionError) {
|
||||||
|
timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_CONNECTION_ERROR_MILLISECONDS;
|
||||||
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
||||||
fetchCallNumberOfFailures++;
|
fetchCallNumberOfFailures++;
|
||||||
} else if (rateLimitHit) {
|
} else if (rateLimitHit) {
|
||||||
|
LogHelper.printDebug(() -> "API rate limit was hit. Stopping API calls for the next "
|
||||||
|
+ BACKOFF_RATE_LIMIT_MILLISECONDS + " seconds");
|
||||||
|
timeToResumeAPICalls = System.currentTimeMillis() + BACKOFF_RATE_LIMIT_MILLISECONDS;
|
||||||
|
numberOfRateLimitRequestsEncountered++;
|
||||||
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT;
|
fetchCallResponseTimeLast = FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT;
|
||||||
|
ReVancedUtils.showToastLong(str("revanced_ryd_failure_client_rate_limit_requested"));
|
||||||
} else {
|
} else {
|
||||||
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
fetchCallResponseTimeLast = responseTimeOfFetchCall;
|
||||||
}
|
}
|
||||||
@ -262,27 +257,22 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
final int responseCode = connection.getResponseCode();
|
final int responseCode = connection.getResponseCode();
|
||||||
if (checkIfRateLimitWasHit(responseCode)) {
|
if (checkIfRateLimitWasHit(responseCode)) {
|
||||||
connection.disconnect(); // rate limit hit, should disconnect
|
connection.disconnect(); // rate limit hit, should disconnect
|
||||||
updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(),false, true);
|
updateRateLimitAndStats(timeNetworkCallStarted, false, true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
final long timeNetworkCallEnded = System.currentTimeMillis(); // record end time before parsing
|
|
||||||
// do not disconnect, the same server connection will likely be used again soon
|
// do not disconnect, the same server connection will likely be used again soon
|
||||||
JSONObject json = Requester.parseJSONObject(connection);
|
JSONObject json = Requester.parseJSONObject(connection);
|
||||||
try {
|
try {
|
||||||
RYDVoteData votingData = new RYDVoteData(json);
|
RYDVoteData votingData = new RYDVoteData(json);
|
||||||
updateStatistics(timeNetworkCallStarted, timeNetworkCallEnded, false, false);
|
updateRateLimitAndStats(timeNetworkCallStarted, false, false);
|
||||||
LogHelper.printDebug(() -> "Voting data fetched: " + votingData);
|
LogHelper.printDebug(() -> "Voting data fetched: " + votingData);
|
||||||
return votingData;
|
return votingData;
|
||||||
} catch (JSONException ex) {
|
} catch (JSONException ex) {
|
||||||
LogHelper.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex);
|
LogHelper.printException(() -> "Failed to parse video: " + videoId + " json: " + json, ex);
|
||||||
// fall thru to update statistics
|
// fall thru to update statistics
|
||||||
}
|
}
|
||||||
} else if (responseCode == HTTP_STATUS_CODE_NOT_FOUND) {
|
|
||||||
// normal response when viewing YouTube Stories (cannot vote for these)
|
|
||||||
LogHelper.printDebug(() -> "Video has no like/dislikes (video is a YouTube Story?): " + videoId);
|
|
||||||
return null; // do not updated connection statistics
|
|
||||||
} else {
|
} else {
|
||||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||||
}
|
}
|
||||||
@ -296,7 +286,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage()));
|
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatistics(timeNetworkCallStarted, System.currentTimeMillis(), true, false);
|
updateRateLimitAndStats(timeNetworkCallStarted, true, false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,7 +301,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String userId = randomString(36);
|
String userId = randomString(36);
|
||||||
LogHelper.printDebug(() -> "Trying to register new user: " + userId);
|
LogHelper.printDebug(() -> "Trying to register new user");
|
||||||
|
|
||||||
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_REGISTRATION, userId);
|
HttpURLConnection connection = getRYDConnectionFromRoute(ReturnYouTubeDislikeRoutes.GET_REGISTRATION, userId);
|
||||||
connection.setRequestProperty("Accept", "application/json");
|
connection.setRequestProperty("Accept", "application/json");
|
||||||
|
@ -55,6 +55,7 @@ public enum SettingsEnum {
|
|||||||
HIDE_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
HIDE_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
||||||
HIDE_CHANNEL_MEMBER_SHELF("revanced_hide_channel_member_shelf", BOOLEAN, TRUE),
|
HIDE_CHANNEL_MEMBER_SHELF("revanced_hide_channel_member_shelf", BOOLEAN, TRUE),
|
||||||
HIDE_EXPANDABLE_CHIP("revanced_hide_expandable_chip", BOOLEAN, TRUE),
|
HIDE_EXPANDABLE_CHIP("revanced_hide_expandable_chip", BOOLEAN, TRUE),
|
||||||
|
HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE),
|
||||||
HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE),
|
HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE),
|
||||||
HIDE_COMMUNITY_GUIDELINES("revanced_hide_community_guidelines", BOOLEAN, TRUE),
|
HIDE_COMMUNITY_GUIDELINES("revanced_hide_community_guidelines", BOOLEAN, TRUE),
|
||||||
HIDE_COMMUNITY_POSTS("revanced_hide_community_posts", BOOLEAN, FALSE),
|
HIDE_COMMUNITY_POSTS("revanced_hide_community_posts", BOOLEAN, FALSE),
|
||||||
@ -146,14 +147,16 @@ public enum SettingsEnum {
|
|||||||
HIDE_SHORTS_CHANNEL_BAR("revanced_hide_shorts_channel_bar", BOOLEAN, FALSE),
|
HIDE_SHORTS_CHANNEL_BAR("revanced_hide_shorts_channel_bar", BOOLEAN, FALSE),
|
||||||
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
|
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
|
||||||
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
|
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
|
||||||
|
DISABLE_SUGGESTED_VIDEO_END_SCREEN("revanced_disable_suggested_video_end_screen", BOOLEAN, TRUE),
|
||||||
|
ENABLE_OLD_SEEKBAR_THUMBNAILS("revanced_enable_old_seekbar_thumbnails", BOOLEAN, TRUE),
|
||||||
|
DISABLE_FULLSCREEN_AMBIENT_MODE("revanced_disable_fullscreen_ambient_mode", BOOLEAN, TRUE, true),
|
||||||
ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE),
|
ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE),
|
||||||
ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)),
|
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)),
|
ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)),
|
||||||
|
|
||||||
//Player flyout menu items
|
//Player flyout menu items
|
||||||
HIDE_QUALITY_MENU("revanced_hide_player_flyout_quality", BOOLEAN, FALSE),
|
|
||||||
HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE),
|
HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE),
|
||||||
|
HIDE_ADDITIONAL_SETTINGS_MENU("revanced_hide_player_flyout_additional_settings", BOOLEAN, FALSE),
|
||||||
HIDE_LOOP_VIDEO_MENU("revanced_hide_player_flyout_loop_video", BOOLEAN, FALSE),
|
HIDE_LOOP_VIDEO_MENU("revanced_hide_player_flyout_loop_video", BOOLEAN, FALSE),
|
||||||
HIDE_AMBIENT_MODE_MENU("revanced_hide_player_flyout_ambient_mode", BOOLEAN, FALSE),
|
HIDE_AMBIENT_MODE_MENU("revanced_hide_player_flyout_ambient_mode", BOOLEAN, FALSE),
|
||||||
HIDE_REPORT_MENU("revanced_hide_player_flyout_report", BOOLEAN, TRUE),
|
HIDE_REPORT_MENU("revanced_hide_player_flyout_report", BOOLEAN, TRUE),
|
||||||
@ -169,6 +172,7 @@ public enum SettingsEnum {
|
|||||||
EXTERNAL_BROWSER("revanced_external_browser", BOOLEAN, TRUE, true),
|
EXTERNAL_BROWSER("revanced_external_browser", BOOLEAN, TRUE, true),
|
||||||
AUTO_REPEAT("revanced_auto_repeat", BOOLEAN, FALSE),
|
AUTO_REPEAT("revanced_auto_repeat", BOOLEAN, FALSE),
|
||||||
SEEKBAR_TAPPING("revanced_seekbar_tapping", BOOLEAN, TRUE),
|
SEEKBAR_TAPPING("revanced_seekbar_tapping", BOOLEAN, TRUE),
|
||||||
|
DISABLE_PRECISE_SEEKING_GESTURE("revanced_disable_precise_seeking_gesture", BOOLEAN, TRUE),
|
||||||
DISABLE_FINE_SCRUBBING_GESTURE("revanced_disable_fine_scrubbing_gesture", BOOLEAN, TRUE),
|
DISABLE_FINE_SCRUBBING_GESTURE("revanced_disable_fine_scrubbing_gesture", BOOLEAN, TRUE),
|
||||||
SPOOF_SIGNATURE("revanced_spoof_signature_verification_enabled", BOOLEAN, TRUE, true,
|
SPOOF_SIGNATURE("revanced_spoof_signature_verification_enabled", BOOLEAN, TRUE, true,
|
||||||
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
|
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
|
||||||
@ -378,6 +382,7 @@ public enum SettingsEnum {
|
|||||||
// region Migration
|
// region Migration
|
||||||
|
|
||||||
migrateOldSettingToNew(HIDE_VIDEO_WATERMARK, HIDE_VIDEO_CHANNEL_WATERMARK);
|
migrateOldSettingToNew(HIDE_VIDEO_WATERMARK, HIDE_VIDEO_CHANNEL_WATERMARK);
|
||||||
|
migrateOldSettingToNew(DISABLE_FINE_SCRUBBING_GESTURE, DISABLE_PRECISE_SEEKING_GESTURE);
|
||||||
|
|
||||||
// Do _not_ delete this SB private user id migration property until sometime in 2024.
|
// Do _not_ delete this SB private user id migration property until sometime in 2024.
|
||||||
// This is the only setting that cannot be reconfigured if lost,
|
// This is the only setting that cannot be reconfigured if lost,
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
package app.revanced.integrations.utils;
|
package app.revanced.integrations.utils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
||||||
|
|
||||||
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
||||||
@ -22,35 +17,25 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
|||||||
char getCharValue(byte[] text, int index) {
|
char getCharValue(byte[] text, int index) {
|
||||||
return (char) text[index];
|
return (char) text[index];
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
int getTextLength(byte[] text) {
|
||||||
|
return text.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the pattern is valid to add to this instance.
|
||||||
|
*/
|
||||||
|
public static boolean isValidPattern(byte[] pattern) {
|
||||||
|
for (byte b : pattern) {
|
||||||
|
if (TrieNode.isInvalidRange((char) b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteTrieSearch() {
|
public ByteTrieSearch() {
|
||||||
super(new ByteTrieNode());
|
super(new ByteTrieNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPattern(@NonNull byte[] pattern) {
|
|
||||||
super.addPattern(pattern, pattern.length, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPattern(@NonNull byte[] pattern, @NonNull TriePatternMatchedCallback<byte[]> callback) {
|
|
||||||
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 matches(textToSearch,0, textToSearch.length, callbackParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
package app.revanced.integrations.utils;
|
package app.revanced.integrations.utils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text pattern searching using a prefix tree (trie).
|
* Text pattern searching using a prefix tree (trie).
|
||||||
*/
|
*/
|
||||||
@ -25,34 +20,25 @@ public final class StringTrieSearch extends TrieSearch<String> {
|
|||||||
char getCharValue(String text, int index) {
|
char getCharValue(String text, int index) {
|
||||||
return text.charAt(index);
|
return text.charAt(index);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
int getTextLength(String text) {
|
||||||
|
return text.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the pattern is valid to add to this instance.
|
||||||
|
*/
|
||||||
|
public static boolean isValidPattern(String pattern) {
|
||||||
|
for (int i = 0, length = pattern.length(); i < length; i++) {
|
||||||
|
if (TrieNode.isInvalidRange(pattern.charAt(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringTrieSearch() {
|
public StringTrieSearch() {
|
||||||
super(new StringTrieNode());
|
super(new StringTrieNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPattern(@NonNull String pattern) {
|
|
||||||
super.addPattern(pattern, pattern.length(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPattern(@NonNull String pattern, @NonNull TriePatternMatchedCallback<String> callback) {
|
|
||||||
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 matches(textToSearch, 0, textToSearch.length(), callbackParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(@NonNull String textToSearch, int startIndex) {
|
|
||||||
return matches(textToSearch, startIndex, textToSearch.length(), null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ public abstract class TrieSearch<T> {
|
|||||||
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
||||||
private static final int CHILDREN_ARRAY_MAX_SIZE = MAX_VALID_CHAR - MIN_VALID_CHAR + 1;
|
private static final int CHILDREN_ARRAY_MAX_SIZE = MAX_VALID_CHAR - MIN_VALID_CHAR + 1;
|
||||||
|
|
||||||
private static boolean isInvalidRange(char character) {
|
static boolean isInvalidRange(char character) {
|
||||||
return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR;
|
return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ public abstract class TrieSearch<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int hashIndexForTableSize(int arraySize, char nodeValue) {
|
private static int hashIndexForTableSize(int arraySize, char nodeValue) {
|
||||||
return (nodeValue - MIN_VALID_CHAR) % arraySize;
|
return nodeValue % arraySize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,6 +300,7 @@ public abstract class TrieSearch<T> {
|
|||||||
|
|
||||||
abstract TrieNode<T> createNode(char nodeValue);
|
abstract TrieNode<T> createNode(char nodeValue);
|
||||||
abstract char getCharValue(T text, int index);
|
abstract char getCharValue(T text, int index);
|
||||||
|
abstract int getTextLength(T text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -323,6 +324,23 @@ public abstract class TrieSearch<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a pattern that will always return a positive match if found.
|
||||||
|
*
|
||||||
|
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
|
||||||
|
*/
|
||||||
|
public void addPattern(@NonNull T pattern) {
|
||||||
|
addPattern(pattern, root.getTextLength(pattern), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
|
||||||
|
* @param callback Callback to determine if searching should halt when a match is found.
|
||||||
|
*/
|
||||||
|
public void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> callback) {
|
||||||
|
addPattern(pattern, root.getTextLength(pattern), Objects.requireNonNull(callback));
|
||||||
|
}
|
||||||
|
|
||||||
void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
|
void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
|
||||||
if (patternLength == 0) return; // Nothing to match
|
if (patternLength == 0) return; // Nothing to match
|
||||||
|
|
||||||
@ -330,8 +348,38 @@ public abstract class TrieSearch<T> {
|
|||||||
root.addPattern(pattern, patternLength, 0, callback);
|
root.addPattern(pattern, patternLength, 0, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex,
|
public final boolean matches(@NonNull T textToSearch) {
|
||||||
@Nullable Object callbackParameter) {
|
return matches(textToSearch, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(@NonNull T textToSearch, @NonNull Object callbackParameter) {
|
||||||
|
return matches(textToSearch, 0, root.getTextLength(textToSearch),
|
||||||
|
Objects.requireNonNull(callbackParameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(@NonNull T textToSearch, int startIndex) {
|
||||||
|
return matches(textToSearch, startIndex, root.getTextLength(textToSearch));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean matches(@NonNull T textToSearch, int startIndex, int endIndex) {
|
||||||
|
return matches(textToSearch, startIndex, endIndex, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 boolean matches(@NonNull T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
|
||||||
|
return matches(textToSearch, root.getTextLength(textToSearch), startIndex, endIndex, callbackParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex,
|
||||||
|
@Nullable Object callbackParameter) {
|
||||||
if (endIndex > textToSearchLength) {
|
if (endIndex > textToSearchLength) {
|
||||||
throw new IllegalArgumentException("endIndex: " + endIndex
|
throw new IllegalArgumentException("endIndex: " + endIndex
|
||||||
+ " is greater than texToSearchLength: " + textToSearchLength);
|
+ " is greater than texToSearchLength: " + textToSearchLength);
|
||||||
@ -365,41 +413,4 @@ public abstract class TrieSearch<T> {
|
|||||||
public List<T> getPatterns() {
|
public List<T> getPatterns() {
|
||||||
return Collections.unmodifiableList(patterns);
|
return Collections.unmodifiableList(patterns);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a pattern that will always return a positive match if found.
|
|
||||||
*
|
|
||||||
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
|
|
||||||
*/
|
|
||||||
public abstract void addPattern(@NonNull T pattern);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
|
|
||||||
* @param callback Callback to determine if searching should halt when a match is found.
|
|
||||||
*/
|
|
||||||
public abstract void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> 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);
|
|
||||||
|
|
||||||
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, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
version = 0.120.0
|
version = 0.121.0-dev.7
|
||||||
|
Loading…
x
Reference in New Issue
Block a user