chore: Merge branch dev to main (#505)

This commit is contained in:
oSumAtrIX 2023-11-04 22:11:22 +01:00 committed by GitHub
commit 3344375fb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 304 additions and 173 deletions

View File

@ -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)

View File

@ -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();
}
}

View File

@ -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);
} }

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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));
} }
} }

View File

@ -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.

View File

@ -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) {

View File

@ -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);

View File

@ -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.

View File

@ -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");

View File

@ -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,

View File

@ -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);
}
} }

View File

@ -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);
}
} }

View File

@ -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);
}
} }

View File

@ -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