1
mirror of https://github.com/revanced/revanced-integrations synced 2025-11-19 03:23:27 +01:00

Compare commits

...

22 Commits

Author SHA1 Message Date
semantic-release-bot
869b209c4b chore(release): 1.0.1-dev.1 [skip ci]
## [1.0.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0...v1.0.1-dev.1) (2023-12-16)

### Bug Fixes

* **YouTube - SponsorBlock:** Export local statistics with saved settings ([#542](https://github.com/ReVanced/revanced-integrations/issues/542)) ([0ed8e5a](0ed8e5a298))
2023-12-16 11:25:35 +00:00
LisoUseInAIKyrios
0ed8e5a298 fix(YouTube - SponsorBlock): Export local statistics with saved settings (#542) 2023-12-16 15:22:59 +04:00
LisoUseInAIKyrios
aedb3eddd6 refactor(YouTube - Litho filter): Simplify filtering callbacks (#539) 2023-12-13 02:40:28 +04:00
semantic-release-bot
56cf001964 chore(release): 1.0.0 [skip ci]
# [1.0.0](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v1.0.0) (2023-12-12)

### Bug Fixes

* **YouTube - Announcements:** Don't show error toast if there is no internet connection ([#537](https://github.com/ReVanced/revanced-integrations/issues/537)) ([0ce92c2](0ce92c284d))
* **YouTube - Client spoof:** Do not break clips ([f9102fa](f9102fa83b))
* **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#533](https://github.com/ReVanced/revanced-integrations/issues/533)) ([fb433da](fb433da6ad))
* **YouTube - Return YouTube Dislike:** Fix dislikes sometimes not showing for non English language ([5d4c8b0](5d4c8b0a1b))
* **YouTube - Return YouTube Dislike:** Prevent the first Short opened from freezing the UI ([#532](https://github.com/ReVanced/revanced-integrations/issues/532)) ([0bb8669](0bb86694e2))
* **YouTube - Return YouTube Dislike:** Wait until fetch is complete before allowing the first Short to start playback ([#538](https://github.com/ReVanced/revanced-integrations/issues/538)) ([1c9c51c](1c9c51ca5f))
* **YouTube - SponsorBlock:** Allow autoplay when skipping to the end of the video ([3d660e1](3d660e1b5e))
* **YouTube - SponsorBlock:** Prevent autoplay from stopping to work ([f4e2d56](f4e2d56b18))
* **YouTube - Spoof signature:** Wait until storyboard fetch is done ([#535](https://github.com/ReVanced/revanced-integrations/issues/535)) ([92e8619](92e8619cd7))

### Features

* Allow choosing the vendor of GmsCore via patch options ([#529](https://github.com/ReVanced/revanced-integrations/issues/529)) ([fba7181](fba7181e70))
* **Tiktok:** Bump compatibility to `32.5.3` ([#536](https://github.com/ReVanced/revanced-integrations/issues/536)) ([10a1e16](10a1e168d0))
* **YouTube - Alternative Thumbnails:** Add option to use DeArrow ([#534](https://github.com/ReVanced/revanced-integrations/issues/534)) ([c4ee6ca](c4ee6ca4dd))
* **YouTube:** Add `Change start page` patch ([792dc0c](792dc0c522))

### BREAKING CHANGES

* The class `MicroGSupport` has been renamed to `GmsCoreSupport`
2023-12-12 00:13:59 +00:00
oSumAtrIX
69c5028661 chore: Merge branch dev to main (#530) 2023-12-12 01:11:30 +01:00
semantic-release-bot
30f2c4f4c9 chore(release): 1.0.0-dev.12 [skip ci]
# [1.0.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2023-12-12)

### Features

* **Tiktok:** Bump compatibility to `32.5.3` ([#536](https://github.com/ReVanced/revanced-integrations/issues/536)) ([10a1e16](10a1e168d0))
2023-12-12 00:09:33 +00:00
d4rkk3y
10a1e168d0 feat(Tiktok): Bump compatibility to 32.5.3 (#536)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-12-12 01:07:08 +01:00
semantic-release-bot
e79ebb26ec chore(release): 1.0.0-dev.11 [skip ci]
# [1.0.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2023-12-11)

### Features

* **YouTube:** Add `Change start page` patch ([792dc0c](792dc0c522))
2023-12-11 23:44:14 +00:00
oSumAtrIX
792dc0c522 feat(YouTube): Add Change start page patch 2023-12-12 00:38:04 +01:00
semantic-release-bot
8870f7ab0c chore(release): 1.0.0-dev.10 [skip ci]
# [1.0.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2023-12-11)

### Bug Fixes

* **YouTube - Return YouTube Dislike:** Wait until fetch is complete before allowing the first Short to start playback ([#538](https://github.com/ReVanced/revanced-integrations/issues/538)) ([1c9c51c](1c9c51ca5f))
2023-12-11 18:33:07 +00:00
LisoUseInAIKyrios
1c9c51ca5f fix(YouTube - Return YouTube Dislike): Wait until fetch is complete before allowing the first Short to start playback (#538) 2023-12-11 22:30:36 +04:00
semantic-release-bot
6f7a7b825e chore(release): 1.0.0-dev.9 [skip ci]
# [1.0.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2023-12-11)

### Features

* **YouTube - Alternative Thumbnails:** Add option to use DeArrow ([#534](https://github.com/ReVanced/revanced-integrations/issues/534)) ([c4ee6ca](c4ee6ca4dd))
2023-12-11 01:09:29 +00:00
Chris
c4ee6ca4dd feat(YouTube - Alternative Thumbnails): Add option to use DeArrow (#534)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2023-12-11 05:06:55 +04:00
semantic-release-bot
46dbbf5f86 chore(release): 1.0.0-dev.8 [skip ci]
# [1.0.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2023-12-10)

### Bug Fixes

* **YouTube - Announcements:** Don't show error toast if there is no internet connection ([#537](https://github.com/ReVanced/revanced-integrations/issues/537)) ([0ce92c2](0ce92c284d))
2023-12-10 22:46:37 +00:00
nullptr
0ce92c284d fix(YouTube - Announcements): Don't show error toast if there is no internet connection (#537) 2023-12-10 23:44:10 +01:00
oSumAtrIX
fb56e9a362 build: Simplify enabling local build cache 2023-12-10 22:21:40 +01:00
semantic-release-bot
fa5713b4a0 chore(release): 1.0.0-dev.7 [skip ci]
# [1.0.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2023-12-09)

### Bug Fixes

* **YouTube - Spoof signature:** Wait until storyboard fetch is done ([#535](https://github.com/ReVanced/revanced-integrations/issues/535)) ([92e8619](92e8619cd7))
2023-12-09 10:33:48 +00:00
LisoUseInAIKyrios
92e8619cd7 fix(YouTube - Spoof signature): Wait until storyboard fetch is done (#535) 2023-12-09 14:31:20 +04:00
semantic-release-bot
9c635e5f91 chore(release): 1.0.0-dev.6 [skip ci]
# [1.0.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2023-12-07)

### Bug Fixes

* **YouTube - Client spoof:** Do not break clips ([f9102fa](f9102fa83b))
2023-12-07 19:03:21 +00:00
oSumAtrIX
f9102fa83b fix(YouTube - Client spoof): Do not break clips 2023-12-07 23:00:36 +04:00
semantic-release-bot
e7758a7ac4 chore(release): 1.0.0-dev.5 [skip ci]
# [1.0.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2023-12-04)

### Bug Fixes

* **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#533](https://github.com/ReVanced/revanced-integrations/issues/533)) ([fb433da](fb433da6ad))
2023-12-04 23:50:11 +00:00
LisoUseInAIKyrios
fb433da6ad fix(YouTube - Minimized playback): Fix PIP incorrectly shown for some Shorts playback (#533) 2023-12-05 03:47:31 +04:00
32 changed files with 899 additions and 315 deletions

View File

@@ -1,3 +1,94 @@
## [1.0.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0...v1.0.1-dev.1) (2023-12-16)
### Bug Fixes
* **YouTube - SponsorBlock:** Export local statistics with saved settings ([#542](https://github.com/ReVanced/revanced-integrations/issues/542)) ([0ed8e5a](https://github.com/ReVanced/revanced-integrations/commit/0ed8e5a2988c07f3dfbd5dd4e9ef8ed53378fbbe))
# [1.0.0](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v1.0.0) (2023-12-12)
### Bug Fixes
* **YouTube - Announcements:** Don't show error toast if there is no internet connection ([#537](https://github.com/ReVanced/revanced-integrations/issues/537)) ([0ce92c2](https://github.com/ReVanced/revanced-integrations/commit/0ce92c284d08a1c6bffba976e9cf208e82288ddf))
* **YouTube - Client spoof:** Do not break clips ([f9102fa](https://github.com/ReVanced/revanced-integrations/commit/f9102fa83bdb2b147543882cb8ebb80b5985ad3e))
* **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#533](https://github.com/ReVanced/revanced-integrations/issues/533)) ([fb433da](https://github.com/ReVanced/revanced-integrations/commit/fb433da6ad652aee48fc92794de82bb914ab80ca))
* **YouTube - Return YouTube Dislike:** Fix dislikes sometimes not showing for non English language ([5d4c8b0](https://github.com/ReVanced/revanced-integrations/commit/5d4c8b0a1b77e97c7c0c02288927e92f3c9765ce))
* **YouTube - Return YouTube Dislike:** Prevent the first Short opened from freezing the UI ([#532](https://github.com/ReVanced/revanced-integrations/issues/532)) ([0bb8669](https://github.com/ReVanced/revanced-integrations/commit/0bb86694e24a6a41edee62f5ef1bb80fe7bc3f19))
* **YouTube - Return YouTube Dislike:** Wait until fetch is complete before allowing the first Short to start playback ([#538](https://github.com/ReVanced/revanced-integrations/issues/538)) ([1c9c51c](https://github.com/ReVanced/revanced-integrations/commit/1c9c51ca5f7970774d4e0b5aad5ebcd064cac716))
* **YouTube - SponsorBlock:** Allow autoplay when skipping to the end of the video ([3d660e1](https://github.com/ReVanced/revanced-integrations/commit/3d660e1b5eeab9771f96bd2d26a222b835e2485c))
* **YouTube - SponsorBlock:** Prevent autoplay from stopping to work ([f4e2d56](https://github.com/ReVanced/revanced-integrations/commit/f4e2d56b181fee4d693dea1dfe81974237e4eff7))
* **YouTube - Spoof signature:** Wait until storyboard fetch is done ([#535](https://github.com/ReVanced/revanced-integrations/issues/535)) ([92e8619](https://github.com/ReVanced/revanced-integrations/commit/92e8619cd7bbcf82f27e9407e18c30d65214e31c))
### Features
* Allow choosing the vendor of GmsCore via patch options ([#529](https://github.com/ReVanced/revanced-integrations/issues/529)) ([fba7181](https://github.com/ReVanced/revanced-integrations/commit/fba7181e70d695d7fb13c530754dc1db99b87216))
* **Tiktok:** Bump compatibility to `32.5.3` ([#536](https://github.com/ReVanced/revanced-integrations/issues/536)) ([10a1e16](https://github.com/ReVanced/revanced-integrations/commit/10a1e168d062adc3c979de17738d8cf1b8f25d63))
* **YouTube - Alternative Thumbnails:** Add option to use DeArrow ([#534](https://github.com/ReVanced/revanced-integrations/issues/534)) ([c4ee6ca](https://github.com/ReVanced/revanced-integrations/commit/c4ee6ca4dde13ab8ce6f9cf94f1910455f9d9ecc))
* **YouTube:** Add `Change start page` patch ([792dc0c](https://github.com/ReVanced/revanced-integrations/commit/792dc0c52210dc9b1560290b0cc2afad572c584c))
### BREAKING CHANGES
* The class `MicroGSupport` has been renamed to `GmsCoreSupport`
# [1.0.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2023-12-12)
### Features
* **Tiktok:** Bump compatibility to `32.5.3` ([#536](https://github.com/ReVanced/revanced-integrations/issues/536)) ([10a1e16](https://github.com/ReVanced/revanced-integrations/commit/10a1e168d062adc3c979de17738d8cf1b8f25d63))
# [1.0.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2023-12-11)
### Features
* **YouTube:** Add `Change start page` patch ([792dc0c](https://github.com/ReVanced/revanced-integrations/commit/792dc0c52210dc9b1560290b0cc2afad572c584c))
# [1.0.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2023-12-11)
### Bug Fixes
* **YouTube - Return YouTube Dislike:** Wait until fetch is complete before allowing the first Short to start playback ([#538](https://github.com/ReVanced/revanced-integrations/issues/538)) ([1c9c51c](https://github.com/ReVanced/revanced-integrations/commit/1c9c51ca5f7970774d4e0b5aad5ebcd064cac716))
# [1.0.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2023-12-11)
### Features
* **YouTube - Alternative Thumbnails:** Add option to use DeArrow ([#534](https://github.com/ReVanced/revanced-integrations/issues/534)) ([c4ee6ca](https://github.com/ReVanced/revanced-integrations/commit/c4ee6ca4dde13ab8ce6f9cf94f1910455f9d9ecc))
# [1.0.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2023-12-10)
### Bug Fixes
* **YouTube - Announcements:** Don't show error toast if there is no internet connection ([#537](https://github.com/ReVanced/revanced-integrations/issues/537)) ([0ce92c2](https://github.com/ReVanced/revanced-integrations/commit/0ce92c284d08a1c6bffba976e9cf208e82288ddf))
# [1.0.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2023-12-09)
### Bug Fixes
* **YouTube - Spoof signature:** Wait until storyboard fetch is done ([#535](https://github.com/ReVanced/revanced-integrations/issues/535)) ([92e8619](https://github.com/ReVanced/revanced-integrations/commit/92e8619cd7bbcf82f27e9407e18c30d65214e31c))
# [1.0.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2023-12-07)
### Bug Fixes
* **YouTube - Client spoof:** Do not break clips ([f9102fa](https://github.com/ReVanced/revanced-integrations/commit/f9102fa83bdb2b147543882cb8ebb80b5985ad3e))
# [1.0.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2023-12-04)
### Bug Fixes
* **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#533](https://github.com/ReVanced/revanced-integrations/issues/533)) ([fb433da](https://github.com/ReVanced/revanced-integrations/commit/fb433da6ad652aee48fc92794de82bb914ab80ca))
# [1.0.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2023-12-04)

View File

@@ -0,0 +1,16 @@
package app.revanced.integrations.patches;
import android.content.Intent;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
@SuppressWarnings("unused")
public final class ChangeStartPagePatch {
public static void changeIntent(Intent intent) {
final var startPage = SettingsEnum.START_PAGE.getString();
if (startPage.isEmpty()) return;
LogHelper.printDebug(() -> "Changing start page to " + startPage);
intent.setAction("com.google.android.youtube.action." + startPage);
}
}

View File

@@ -2,12 +2,39 @@ package app.revanced.integrations.patches;
import app.revanced.integrations.shared.PlayerType;
@SuppressWarnings("unused")
public class MinimizedPlaybackPatch {
public static boolean isPlaybackNotShort() {
return !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized();
/**
* Injection point.
*/
public static boolean playbackIsNotShort() {
// Steps to verify most edge cases:
// 1. Open a regular video
// 2. Minimize app (PIP should appear)
// 3. Reopen app
// 4. Open a Short (without closing the regular video)
// (try opening both Shorts in the video player suggestions AND Shorts from the home feed)
// 5. Minimize the app (PIP should not appear)
// 6. Reopen app
// 7. Close the Short
// 8. Resume playing the regular video
// 9. Minimize the app (PIP should appear)
if (!VideoInformation.lastVideoIdIsShort()) {
return true; // Definitely is not a Short.
}
// Might be a Short, or might be a prior regular video on screen again after a Short was closed.
// This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing a Short,
// But there's no way around this unless an additional hook is added to definitively detect
// the Shorts player is on screen. This use case is unusual anyways so it's not a huge concern.
return !PlayerType.getCurrent().isNoneHiddenOrMinimized();
}
/**
* Injection point.
*/
public static boolean overrideMinimizedPlaybackAvailable() {
// This could be done entirely in the patch,
// but having a unique method to search for makes manually inspecting the patched apk much easier.

View File

@@ -579,8 +579,6 @@ public class ReturnYouTubeDislikePatch {
}
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
&& videoIdIsShort && !lastPlayerResponseWasShort;
lastPlayerResponseWasShort = videoIdIsShort;
lastPrefetchedVideoId = videoId;
LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId);
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
@@ -594,8 +592,11 @@ public class ReturnYouTubeDislikePatch {
//
// If an asynchronous litho Shorts solution is found, then this blocking call should be removed.
LogHelper.printDebug(() -> "Waiting for prefetch to complete: " + videoId);
fetch.getFetchData(10000); // Use any arbitrarily large max wait time.
fetch.getFetchData(20000); // Any arbitrarily large max wait time.
}
// Set the fields after the fetch completes, so any concurrent calls will also wait.
lastPlayerResponseWasShort = videoIdIsShort;
lastPrefetchedVideoId = videoId;
} catch (Exception ex) {
LogHelper.printException(() -> "preloadVideoId failure", ex);
}

View File

@@ -32,6 +32,9 @@ public final class AnnouncementsPatch {
public static void showAnnouncement(final Activity context) {
if (!SettingsEnum.ANNOUNCEMENTS.getBoolean()) return;
// Check if there is internet connection
if (!ReVancedUtils.isNetworkConnected()) return;
ReVancedUtils.runOnBackgroundThread(() -> {
try {
HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT, CONSUMER);
@@ -43,7 +46,7 @@ public final class AnnouncementsPatch {
if (connection.getResponseCode() != 200) {
if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return;
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue("");
SettingsEnum.ANNOUNCEMENT_LAST_HASH.resetToDefault();
ReVancedUtils.showToastLong("Failed to get announcement");
return;
@@ -118,7 +121,7 @@ public final class AnnouncementsPatch {
*/
private static boolean emptyLastAnnouncementHash() {
if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return true;
SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue("");
SettingsEnum.ANNOUNCEMENT_LAST_HASH.resetToDefault();
return false;
}

View File

@@ -1,6 +1,5 @@
package app.revanced.integrations.patches.components;
import android.view.View;
import androidx.annotation.Nullable;
@@ -9,7 +8,7 @@ import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.ReVancedUtils;
import app.revanced.integrations.utils.StringTrieSearch;
@SuppressWarnings("unused")
public final class AdsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
private final StringFilterGroup shoppingLinks;
@@ -23,6 +22,16 @@ public final class AdsFilter extends Filter {
"library_recent_shelf"
);
// Identifiers.
final var carouselAd = new StringFilterGroup(
SettingsEnum.HIDE_GENERAL_ADS,
"carousel_ad"
);
addIdentifierCallbacks(carouselAd);
// Paths.
final var buttonedAd = new StringFilterGroup(
SettingsEnum.HIDE_BUTTONED_ADS,
"_buttoned_layout",
@@ -61,11 +70,6 @@ public final class AdsFilter extends Filter {
"offer_module_root"
);
final var carouselAd = new StringFilterGroup(
SettingsEnum.HIDE_GENERAL_ADS,
"carousel_ad"
);
final var viewProducts = new StringFilterGroup(
SettingsEnum.HIDE_PRODUCTS_BANNER,
"product_item",
@@ -92,7 +96,7 @@ public final class AdsFilter extends Filter {
"cta_shelf_card"
);
this.pathFilterGroupList.addAll(
addPathCallbacks(
generalAds,
buttonedAd,
merchandise,
@@ -102,20 +106,19 @@ public final class AdsFilter extends Filter {
shoppingLinks,
movieAds
);
this.identifierFilterGroupList.addAll(carouselAd);
}
@Override
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (exceptions.matches(path))
return false;
// Check for the index because of likelihood of false positives.
if (matchedGroup == shoppingLinks && matchedIndex != 0)
if (matchedGroup == shoppingLinks && contentIndex != 0)
return false;
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
/**

View File

@@ -7,6 +7,7 @@ import androidx.annotation.RequiresApi;
import app.revanced.integrations.settings.SettingsEnum;
@SuppressWarnings("unused")
@RequiresApi(api = Build.VERSION_CODES.N)
final class ButtonsFilter extends Filter {
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
@@ -20,14 +21,14 @@ final class ButtonsFilter extends Filter {
null,
VIDEO_ACTION_BAR_PATH
);
identifierFilterGroupList.addAll(actionBarGroup);
addIdentifierCallbacks(actionBarGroup);
bufferFilterPathGroup = new StringFilterGroup(
null,
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
);
pathFilterGroupList.addAll(
addPathCallbacks(
new StringFilterGroup(
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
"|segmented_like_dislike_button"
@@ -48,33 +49,33 @@ final class ButtonsFilter extends Filter {
);
bufferButtonsGroupList.addAll(
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_LIVE_CHAT_BUTTON,
"yt_outline_message_bubble_overlap"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_REPORT_BUTTON,
"yt_outline_flag"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHARE_BUTTON,
"yt_outline_share"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_REMIX_BUTTON,
"yt_outline_youtube_shorts_plus"
),
// Check for clip button both here and using a path filter,
// as there's a chance the path is a generic action button and won't contain 'clip_button'
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_CLIP_BUTTON,
"yt_outline_scissors"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHOP_BUTTON,
"yt_outline_bag"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_THANKS_BUTTON,
"yt_outline_dollar_sign_heart"
)
@@ -82,7 +83,7 @@ final class ButtonsFilter extends Filter {
}
private boolean isEveryFilterGroupEnabled() {
for (var group : pathFilterGroupList)
for (var group : pathCallbacks)
if (!group.isEnabled()) return false;
for (var group : bufferButtonsGroupList)
@@ -93,7 +94,7 @@ final class ButtonsFilter extends Filter {
@Override
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// If the current matched group is the action bar group,
// in case every filter group is enabled, hide the action bar.
if (matchedGroup == actionBarGroup) {
@@ -109,6 +110,6 @@ final class ButtonsFilter extends Filter {
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
}
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}

View File

@@ -2,6 +2,7 @@ package app.revanced.integrations.patches.components;
import app.revanced.integrations.settings.SettingsEnum;
@SuppressWarnings("unused")
final class CommentsFilter extends Filter {
public CommentsFilter() {
@@ -18,7 +19,7 @@ final class CommentsFilter extends Filter {
"comments_entry_point_simplebox"
);
this.pathFilterGroupList.addAll(
addPathCallbacks(
comments,
previewComment
);

View File

@@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.StringTrieSearch;
@SuppressWarnings("unused")
final class DescriptionComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
@@ -48,7 +49,7 @@ final class DescriptionComponentsFilter extends Filter {
"transcript_section"
);
pathFilterGroupList.addAll(
addPathCallbacks(
chapterSection,
infoCardsSection,
gameSection,
@@ -61,9 +62,9 @@ final class DescriptionComponentsFilter extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (exceptions.matches(path)) return false;
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}

View File

@@ -1,3 +0,0 @@
package app.revanced.integrations.patches.components;
final class DummyFilter extends Filter { }

View File

@@ -2,10 +2,11 @@ package app.revanced.integrations.patches.components;
import app.revanced.integrations.settings.SettingsEnum;
@SuppressWarnings("unused")
public final class HideInfoCardsFilterPatch extends Filter {
public HideInfoCardsFilterPatch() {
identifierFilterGroupList.addAll(
addIdentifierCallbacks(
new StringFilterGroup(
SettingsEnum.HIDE_INFO_CARDS,
"info_card_teaser_overlay.eml"

View File

@@ -1,24 +1,26 @@
package app.revanced.integrations.patches.components;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.StringTrieSearch;
@SuppressWarnings("unused")
@RequiresApi(api = Build.VERSION_CODES.N)
public final class LayoutComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
private static final ByteArrayAsStringFilterGroup mixPlaylistsExceptions2 = new ByteArrayAsStringFilterGroup(
private static final ByteArrayFilterGroup mixPlaylistsExceptions2 = new ByteArrayFilterGroup(
null,
"cell_description_body"
);
private final CustomFilterGroup custom;
private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup(
private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup(
SettingsEnum.HIDE_MIX_PLAYLISTS,
"&list="
);
@@ -44,6 +46,25 @@ public final class LayoutComponentsFilter extends Filter {
"library_recent_shelf"
);
// Identifiers.
final var graySeparator = new StringFilterGroup(
SettingsEnum.HIDE_GRAY_SEPARATOR,
"cell_divider" // layout residue (gray line above the buttoned ad),
);
final var chipsShelf = new StringFilterGroup(
SettingsEnum.HIDE_CHIPS_SHELF,
"chips_shelf"
);
addIdentifierCallbacks(
graySeparator,
chipsShelf
);
// Paths.
custom = new CustomFilterGroup(
SettingsEnum.CUSTOM_FILTER,
SettingsEnum.CUSTOM_FILTER_STRINGS
@@ -155,10 +176,6 @@ public final class LayoutComponentsFilter extends Filter {
"image_shelf"
);
final var graySeparator = new StringFilterGroup(
SettingsEnum.HIDE_GRAY_SEPARATOR,
"cell_divider" // layout residue (gray line above the buttoned ad),
);
final var timedReactions = new StringFilterGroup(
SettingsEnum.HIDE_TIMED_REACTIONS,
@@ -181,11 +198,6 @@ public final class LayoutComponentsFilter extends Filter {
"compact_sponsor_button"
);
final var chipsShelf = new StringFilterGroup(
SettingsEnum.HIDE_CHIPS_SHELF,
"chips_shelf"
);
final var channelWatermark = new StringFilterGroup(
SettingsEnum.HIDE_VIDEO_CHANNEL_WATERMARK,
"featured_channel_watermark_overlay"
@@ -196,7 +208,11 @@ public final class LayoutComponentsFilter extends Filter {
"mixed_content_shelf"
);
this.pathFilterGroupList.addAll(
addPathCallbacks(
custom,
expandableMetadata,
inFeedSurvey,
notifyMe,
channelBar,
communityPosts,
paidContent,
@@ -204,13 +220,10 @@ public final class LayoutComponentsFilter extends Filter {
channelWatermark,
communityGuidelines,
quickActions,
expandableMetadata,
relatedVideos,
compactBanner,
inFeedSurvey,
joinMembership,
medicalPanel,
notifyMe,
videoQualityMenuFooter,
infoPanel,
subscribersCommunityGuidelines,
@@ -220,32 +233,26 @@ public final class LayoutComponentsFilter extends Filter {
timedReactions,
imageShelf,
channelMemberShelf,
forYouShelf,
custom
);
this.identifierFilterGroupList.addAll(
graySeparator,
chipsShelf
forYouShelf
);
}
@Override
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here.
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
if (matchedGroup != custom && exceptions.matches(path))
return false; // Exceptions are not filtered.
// TODO: This also hides the feed Shorts shelf header
if (matchedGroup == searchResultShelfHeader && matchedIndex != 0) return false;
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
/**

View File

@@ -133,7 +133,7 @@ final class CustomFilterGroup extends StringFilterGroup {
for (String pattern : patterns) {
if (!StringTrieSearch.isValidPattern(pattern)) {
ReVancedUtils.showToastLong("Invalid custom filter, resetting to default");
setting.saveValue(setting.defaultValue);
setting.resetToDefault();
return getFilterPatterns(setting);
}
}
@@ -195,6 +195,14 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
super(setting, filters);
}
/**
* Converts the Strings into byte arrays. Used to search for text in binary data.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public ByteArrayFilterGroup(SettingsEnum setting, String... filters) {
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
}
private synchronized void buildFailurePatterns() {
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
LogHelper.printDebug(() -> "Building failure array for: " + this);
@@ -211,12 +219,14 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
int matchedLength = 0;
int matchedIndex = -1;
if (isEnabled()) {
if (failurePatterns == null) {
int[][] failures = failurePatterns;
if (failures == null) {
buildFailurePatterns(); // Lazy load.
failures = failurePatterns;
}
for (int i = 0, length = filters.length; i < length; i++) {
byte[] filter = filters[i];
matchedIndex = indexOf(bytes, filter, failurePatterns[i]);
matchedIndex = indexOf(bytes, filter, failures[i]);
if (matchedIndex >= 0) {
matchedLength = filter.length;
break;
@@ -228,34 +238,16 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
}
final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup {
@RequiresApi(api = Build.VERSION_CODES.N)
public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) {
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
}
}
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
private final List<T> filterGroups = new ArrayList<>();
/**
* Search graph. Created only if needed.
*/
private volatile TrieSearch<V> search;
private final TrieSearch<V> search = createSearchGraph();
@SafeVarargs
protected final void addAll(final T... groups) {
filterGroups.addAll(Arrays.asList(groups));
search = null; // Rebuild, if already created.
}
protected final synchronized void buildSearch() {
// Since litho filtering is multi-threaded, this method can be concurrently called by multiple threads.
if (search != null) return; // Thread race and another thread already initialized the search.
LogHelper.printDebug(() -> "Creating prefix search tree for: " + this);
TrieSearch<V> search = createSearchGraph();
for (T group : filterGroups) {
for (T group : groups) {
if (!group.includeInSearch()) {
continue;
}
@@ -270,7 +262,6 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
});
}
}
this.search = search; // Must set after it's completely initialized.
}
@NonNull
@@ -293,9 +284,6 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
}
protected FilterGroup.FilterGroupResult check(V stack) {
if (search == null) {
buildSearch(); // Lazy load.
}
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
search.matches(stack, result);
return result;
@@ -322,42 +310,90 @@ final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFi
}
}
/**
* Filters litho based components.
*
* Callbacks to filter content are added using {@link #addIdentifierCallbacks(StringFilterGroup...)}
* and {@link #addPathCallbacks(StringFilterGroup...)}.
*
* To filter {@link FilterContentType#PROTOBUFFER}, first add a callback to
* either an identifier or a path.
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
* or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern).
*
* All callbacks must be registered before the constructor completes.
*/
abstract class Filter {
/**
* All group filters must be set before the constructor call completes.
* Otherwise {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)}
* will never be called for any matches.
*/
protected final StringFilterGroupList pathFilterGroupList = new StringFilterGroupList();
protected final StringFilterGroupList identifierFilterGroupList = new StringFilterGroupList();
public enum FilterContentType {
IDENTIFIER,
PATH,
PROTOBUFFER
}
/**
* Identifier callbacks. Do not add to this instance,
* and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}.
*/
protected final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
/**
* Path callbacks. Do not add to this instance,
* and instead use {@link #addPathCallbacks(StringFilterGroup...)}.
*/
protected final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
/**
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
* if any of the groups are found.
*/
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
identifierCallbacks.addAll(Arrays.asList(groups));
}
/**
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
* if any of the groups are found.
*/
protected final void addPathCallbacks(StringFilterGroup... groups) {
pathCallbacks.addAll(Arrays.asList(groups));
}
/**
* Called after an enabled filter has been matched.
* Default implementation is to always filter the matched item.
* Default implementation is to always filter the matched component and log the action.
* Subclasses can perform additional or different checks if needed.
* <p>
* If the content is to be filtered, subclasses should always
* call this method (and never return a plain 'true').
* That way the logs will always show when a component was filtered and which filter hide it.
* <p>
* Method is called off the main thread.
*
* @param matchedList The list the group filter belongs to.
* @param matchedGroup The actual filter that matched.
* @param matchedIndex Matched index of string/array.
* @return True if the litho item should be filtered out.
* @param contentType The type of content matched.
* @param contentIndex Matched index of the identifier or path.
* @return True if the litho component should be filtered out.
*/
@SuppressWarnings("rawtypes")
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (SettingsEnum.DEBUG.getBoolean()) {
if (matchedList == identifierFilterGroupList) {
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier);
String filterSimpleName = getClass().getSimpleName();
if (contentType == FilterContentType.IDENTIFIER) {
LogHelper.printDebug(() -> filterSimpleName + " Filtered identifier: " + identifier);
} else {
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path);
LogHelper.printDebug(() -> filterSimpleName + " Filtered path: " + path);
}
}
return true;
}
}
/**
* Placeholder for actual filters.
*/
final class DummyFilter extends Filter { }
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressWarnings("unused")
public final class LithoFilterPatch {
@@ -437,8 +473,10 @@ public final class LithoFilterPatch {
static {
for (Filter filter : filters) {
filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroupList);
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroupList);
filterUsingCallbacks(identifierSearchTree, filter,
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
filterUsingCallbacks(pathSearchTree, filter,
filter.pathCallbacks, Filter.FilterContentType.PATH);
}
LogHelper.printDebug(() -> "Using: "
@@ -448,18 +486,19 @@ public final class LithoFilterPatch {
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
}
private static <T> void filterGroupLists(TrieSearch<T> pathSearchTree,
Filter filter, FilterGroupList<T, ? extends FilterGroup<T>> list) {
for (FilterGroup<T> group : list) {
private static void filterUsingCallbacks(StringTrieSearch pathSearchTree,
Filter filter, List<StringFilterGroup> groups,
Filter.FilterContentType type) {
for (StringFilterGroup group : groups) {
if (!group.includeInSearch()) {
continue;
}
for (T pattern : group.filters) {
for (String pattern : group.filters) {
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
if (!group.isEnabled()) return false;
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer,
list, group, matchedStartIndex);
group, type, matchedStartIndex);
}
);
}

View File

@@ -12,7 +12,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
public static volatile boolean isPlaybackSpeedMenuVisible;
public PlaybackSpeedMenuFilterPatch() {
pathFilterGroupList.addAll(new StringFilterGroup(
addPathCallbacks(new StringFilterGroup(
null,
"playback_speed_sheet_content.eml-js"
));
@@ -20,7 +20,7 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isPlaybackSpeedMenuVisible = true;
return false;

View File

@@ -8,65 +8,64 @@ import androidx.annotation.RequiresApi;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.shared.PlayerType;
@SuppressWarnings("unused")
public class PlayerFlyoutMenuItemsFilter extends Filter {
// 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)
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
private final ByteArrayFilterGroup exception;
@RequiresApi(api = Build.VERSION_CODES.N)
public PlayerFlyoutMenuItemsFilter() {
exception = new ByteArrayAsStringFilterGroup(
exception = new ByteArrayFilterGroup(
// 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(
addPathCallbacks(
new StringFilterGroup(null, "overflow_menu_item.eml|")
);
flyoutFilterGroupList.addAll(
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_CAPTIONS_MENU,
"closed_caption"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_ADDITIONAL_SETTINGS_MENU,
"yt_outline_gear"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_LOOP_VIDEO_MENU,
"yt_outline_arrow_repeat_1_"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_AMBIENT_MODE_MENU,
"yt_outline_screen_light"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_REPORT_MENU,
"yt_outline_flag"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_HELP_MENU,
"yt_outline_question_circle"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_MORE_INFO_MENU,
"yt_outline_info_circle"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_SPEED_MENU,
"yt_outline_play_arrow_half_circle"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_AUDIO_TRACK_MENU,
"yt_outline_person_radar"
),
new ByteArrayAsStringFilterGroup(
new ByteArrayFilterGroup(
SettingsEnum.HIDE_WATCH_IN_VR_MENU,
"yt_outline_vr"
)
@@ -75,15 +74,15 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// 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.
if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
// Only 1 path callback was added, so the matched group must be the overflow menu.
if (contentIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
// Super class handles logging.
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}

View File

@@ -71,21 +71,21 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
public ReturnYouTubeDislikeFilterPatch() {
pathFilterGroupList.addAll(
addPathCallbacks(
new StringFilterGroup(SettingsEnum.RYD_SHORTS, "|shorts_dislike_button.eml|")
);
// After the dislikes icon name is some binary data and then the video id for that specific short.
videoIdFilterGroup.addAll(
// Video was previously disliked before video was opened.
new ByteArrayAsStringFilterGroup(null, "ic_right_dislike_on_shadowed"),
new ByteArrayFilterGroup(null, "ic_right_dislike_on_shadowed"),
// Video was not already disliked.
new ByteArrayAsStringFilterGroup(null, "ic_right_dislike_off_shadowed")
new ByteArrayFilterGroup(null, "ic_right_dislike_off_shadowed")
);
}
@Override
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray);
if (result.isFiltered()) {
String matchedVideoId = findVideoId(protobufBufferArray);
@@ -112,7 +112,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
}
/**
* This could use {@link TrieSearch}, but since the video ids are constantly changing
* This could use {@link TrieSearch}, but since the patterns are constantly changing
* the overhead of updating the Trie might negate the search performance gain.
*/
private static boolean byteArrayContainsString(@NonNull byte[] array, @NonNull String text) {

View File

@@ -10,7 +10,7 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
import static app.revanced.integrations.utils.ReVancedUtils.hideViewBy1dpUnderCondition;
import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition;
/** @noinspection unused*/
@SuppressWarnings("unused")
@RequiresApi(api = Build.VERSION_CODES.N)
public final class ShortsFilter extends Filter {
public static PivotBar pivotBar; // Set by patch.
@@ -49,7 +49,7 @@ public final class ShortsFilter extends Filter {
"suggested_action"
);
identifierFilterGroupList.addAll(shorts, shelfHeader, thanksButton);
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
// Shorts player components.
var joinButton = new StringFilterGroup(
@@ -87,22 +87,22 @@ public final class ShortsFilter extends Filter {
"ContainerType|shorts_video_action_button"
);
pathFilterGroupList.addAll(
addPathCallbacks(
joinButton, subscribeButton, subscribeButtonPaused,
channelBar, soundButton, infoPanel, videoActionButton
);
var shortsCommentButton = new ByteArrayAsStringFilterGroup(
var shortsCommentButton = new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHORTS_COMMENTS_BUTTON,
"reel_comment_button"
);
var shortsShareButton = new ByteArrayAsStringFilterGroup(
var shortsShareButton = new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHORTS_SHARE_BUTTON,
"reel_share_button"
);
var shortsRemixButton = new ByteArrayAsStringFilterGroup(
var shortsRemixButton = new ByteArrayFilterGroup(
SettingsEnum.HIDE_SHORTS_REMIX_BUTTON,
"reel_remix_button"
);
@@ -112,19 +112,19 @@ public final class ShortsFilter extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
if (matchedList == pathFilterGroupList) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentType == FilterContentType.PATH) {
// Always filter if matched.
if (matchedGroup == soundButton ||
matchedGroup == infoPanel ||
matchedGroup == channelBar ||
matchedGroup == subscribeButtonPaused
) return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
// Video action buttons (comment, share, remix) have the same path.
if (matchedGroup == videoActionButton) {
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
);
return false;
}
@@ -133,18 +133,18 @@ public final class ShortsFilter extends Filter {
// to avoid false positives.
if (path.startsWith(REEL_CHANNEL_BAR_PATH))
if (matchedGroup == subscribeButton) return super.isFiltered(
identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
);
return false;
} else if (matchedGroup == shelfHeader) {
// Because the header is used in watch history and possibly other places, check for the index,
// which is 0 when the shelf header is used for Shorts.
if (matchedIndex != 0) return false;
if (contentIndex != 0) return false;
}
// Super class handles logging.
return super.isFiltered(identifier, path, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
public static void hideShortsShelf(final View shortsShelfView) {

View File

@@ -9,11 +9,12 @@ import app.revanced.integrations.settings.SettingsEnum;
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
*/
public final class VideoQualityMenuFilterPatch extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
// Must be volatile or synchronized, as litho filtering runs off main thread
// and this field is then access from the main thread.
public static volatile boolean isVideoQualityMenuVisible;
public VideoQualityMenuFilterPatch() {
pathFilterGroupList.addAll(new StringFilterGroup(
addPathCallbacks(new StringFilterGroup(
SettingsEnum.RESTORE_OLD_VIDEO_QUALITY_MENU,
"quick_quality_sheet_content.eml-js"
));
@@ -21,7 +22,7 @@ public final class VideoQualityMenuFilterPatch extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isVideoQualityMenuVisible = true;
return false;

View File

@@ -43,7 +43,7 @@ public class CustomPlaybackSpeedPatch {
private static void resetCustomSpeeds(@NonNull String toastMessage) {
ReVancedUtils.showToastLong(toastMessage);
SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.saveValue(SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.defaultValue);
SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
}
private static void loadCustomSpeeds() {

View File

@@ -1,5 +1,6 @@
package app.revanced.integrations.patches.spoof;
import static app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
import static app.revanced.integrations.utils.ReVancedUtils.containsAny;
import android.view.View;
@@ -8,11 +9,16 @@ import android.widget.ImageView;
import androidx.annotation.Nullable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import app.revanced.integrations.patches.VideoInformation;
import app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.shared.PlayerType;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
/** @noinspection unused*/
public class SpoofSignaturePatch {
@@ -23,6 +29,11 @@ public class SpoofSignaturePatch {
*/
private static final String INCOGNITO_PARAMETERS = "CgIQBg==";
/**
* Parameters used when playing clips.
*/
private static final String CLIPS_PARAMETERS = "kAIB";
/**
* Parameters causing playback issues.
*/
@@ -44,12 +55,30 @@ public class SpoofSignaturePatch {
private static volatile String lastPlayerResponseVideoId;
@Nullable
private static volatile StoryboardRenderer videoRenderer;
private static volatile Future<StoryboardRenderer> rendererFuture;
private static volatile boolean useOriginalStoryboardRenderer;
private static volatile boolean isPlayingShorts;
@Nullable
private static StoryboardRenderer getRenderer(boolean waitForCompletion) {
Future<StoryboardRenderer> future = rendererFuture;
if (future != null) {
try {
if (waitForCompletion || future.isDone()) {
return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout.
} // else, return null.
} catch (TimeoutException ex) {
LogHelper.printDebug(() -> "Could not get renderer (get timed out)");
} catch (ExecutionException | InterruptedException ex) {
// Should never happen.
LogHelper.printException(() -> "Could not get renderer", ex);
}
}
return null;
}
/**
* Injection point.
*
@@ -61,14 +90,20 @@ public class SpoofSignaturePatch {
try {
LogHelper.printDebug(() -> "Original protobuf parameter value: " + parameters);
if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) return parameters;
if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) {
return parameters;
}
// Clip's player parameters contain a lot of information (e.g. video start and end time or whether it loops)
// For this reason, the player parameters of a clip are usually very long (150~300 characters).
// Clips are 60 seconds or less in length, so no spoofing.
if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters;
//noinspection AssignmentUsedAsCondition
if (useOriginalStoryboardRenderer = parameters.length() > 150 || containsAny(parameters, CLIPS_PARAMETERS)) {
return parameters;
}
// Shorts do not need to be spoofed.
//noinspection AssignmentUsedAsCondition
if (useOriginalStoryboardRenderer = VideoInformation.playerParametersAreShort(parameters)) {
isPlayingShorts = true;
return parameters;
@@ -78,6 +113,7 @@ public class SpoofSignaturePatch {
boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL
&& containsAny(parameters, AUTOPLAY_PARAMETERS);
if (isPlayingFeed) {
//noinspection AssignmentUsedAsCondition
if (useOriginalStoryboardRenderer = !SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean()) {
// Don't spoof the feed video playback. This will cause video playback issues,
// but only if user continues watching for more than 1 minute.
@@ -98,27 +134,32 @@ public class SpoofSignaturePatch {
private static void fetchStoryboardRenderer() {
if (!SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) {
lastPlayerResponseVideoId = null;
videoRenderer = null;
rendererFuture = null;
return;
}
String videoId = VideoInformation.getPlayerResponseVideoId();
if (!videoId.equals(lastPlayerResponseVideoId)) {
rendererFuture = ReVancedUtils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId));
lastPlayerResponseVideoId = videoId;
// This will block starting video playback until the fetch completes.
// This is desired because if this returns without finishing the fetch,
// then video will start playback but the image will be frozen
// while the main thread call for the renderer waits for the fetch to complete.
videoRenderer = StoryboardRendererRequester.getStoryboardRenderer(videoId);
}
// Block until the renderer fetch completes.
// This is desired because if this returns without finishing the fetch
// then video will start playback but the storyboard is not ready yet.
getRenderer(true);
}
private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec,
boolean returnNullIfLiveStream) {
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) {
StoryboardRenderer renderer = videoRenderer;
StoryboardRenderer renderer = getRenderer(false);
if (renderer != null) {
if (returnNullIfLiveStream && renderer.isLiveStream()) return null;
return renderer.getSpec();
if (returnNullIfLiveStream && renderer.isLiveStream()) {
return null;
}
String spec = renderer.getSpec();
if (spec != null) {
return spec;
}
}
}
@@ -149,7 +190,7 @@ public class SpoofSignaturePatch {
*/
public static int getRecommendedLevel(int originalLevel) {
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) {
StoryboardRenderer renderer = videoRenderer;
StoryboardRenderer renderer = getRenderer(false);
if (renderer != null) {
Integer recommendedLevel = renderer.getRecommendedLevel();
if (recommendedLevel != null) return recommendedLevel;
@@ -167,7 +208,7 @@ public class SpoofSignaturePatch {
if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) {
return false;
}
StoryboardRenderer renderer = videoRenderer;
StoryboardRenderer renderer = getRenderer(false);
if (renderer == null) {
// Spoof storyboard renderer is turned off,
// video is paid, or the storyboard fetch timed out.

View File

@@ -48,7 +48,7 @@ public final class SeekbarColorPatch {
Color.colorToHSV(seekbarColor, customSeekbarColorHSV);
} catch (Exception ex) {
ReVancedUtils.showToastShort("Invalid seekbar color value. Using default value.");
SettingsEnum.SEEKBAR_CUSTOM_COLOR_VALUE.saveValue(SettingsEnum.SEEKBAR_CUSTOM_COLOR_VALUE.defaultValue);
SettingsEnum.SEEKBAR_CUSTOM_COLOR_VALUE.resetToDefault();
loadCustomSeekbarColor();
}
}

View File

@@ -56,9 +56,13 @@ public enum SettingsEnum {
HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE),
// Layout
ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE),
ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)),
ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)),
ALT_THUMBNAIL_STILLS("revanced_alt_thumbnail_stills", BOOLEAN, FALSE),
ALT_THUMBNAIL_STILLS_TIME("revanced_alt_thumbnail_stills_time", INTEGER, 2, parents(ALT_THUMBNAIL_STILLS)),
ALT_THUMBNAIL_STILLS_FAST("revanced_alt_thumbnail_stills_fast", BOOLEAN, FALSE, parents(ALT_THUMBNAIL_STILLS)),
ALT_THUMBNAIL_DEARROW("revanced_alt_thumbnail_dearrow", BOOLEAN, false),
ALT_THUMBNAIL_DEARROW_API_URL("revanced_alt_thumbnail_dearrow_api_url", STRING,
"https://dearrow-thumb.ajay.app/api/v1/getThumbnail", true, parents(ALT_THUMBNAIL_DEARROW)),
ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST("revanced_alt_thumbnail_dearrow_connection_toast", BOOLEAN, TRUE, parents(ALT_THUMBNAIL_DEARROW)),
CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE),
CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)),
DISABLE_FULLSCREEN_AMBIENT_MODE("revanced_disable_fullscreen_ambient_mode", BOOLEAN, TRUE, true),
@@ -125,6 +129,8 @@ public enum SettingsEnum {
TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"),
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
START_PAGE("revanced_start_page", STRING, ""),
// Description
HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE),
HIDE_INFO_CARDS_SECTION("revanced_hide_info_cards_section", BOOLEAN, TRUE),
@@ -430,7 +436,7 @@ public enum SettingsEnum {
LogHelper.printInfo(() -> "Migrating old setting of '" + oldSetting.value
+ "' from: " + oldSetting + " into replacement setting: " + newSetting);
newSetting.saveValue(oldSetting.value);
oldSetting.saveValue(oldSetting.defaultValue); // reset old value
oldSetting.resetToDefault();
}
}
@@ -522,6 +528,13 @@ public enum SettingsEnum {
}
}
/**
* Identical to calling {@link #saveValue(Object)} using {@link #defaultValue}.
*/
public void resetToDefault() {
saveValue(defaultValue);
}
/**
* @return if this setting can be configured and used.
* <p>
@@ -584,8 +597,6 @@ public enum SettingsEnum {
case SB_LAST_VIP_CHECK:
case SB_HIDE_EXPORT_WARNING:
case SB_SEEN_GUIDELINES:
case SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS:
case SB_LOCAL_TIME_SAVED_MILLISECONDS:
return false;
}
return true;
@@ -694,7 +705,7 @@ public enum SettingsEnum {
} else if (setting.includeWithImportExport() && !setting.isSetToDefault()) {
LogHelper.printDebug(() -> "Resetting to default: " + setting);
rebootSettingChanged |= setting.rebootApp;
setting.saveValue(setting.defaultValue);
setting.resetToDefault();
}
}
numberOfSettingsImported += SponsorBlockSettings.importCategoriesFromFlatJson(json);

View File

@@ -0,0 +1,35 @@
package app.revanced.integrations.settingsmenu;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.Preference;
import android.util.AttributeSet;
/**
* Allows tapping the DeArrow about preference to open the DeArrow website.
*/
@SuppressWarnings("unused")
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference {
{
setOnPreferenceClickListener(pref -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://dearrow.ajay.app"));
pref.getContext().startActivity(i);
return false;
});
}
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlternativeThumbnailsAboutDeArrowPreference(Context context) {
super(context);
}
}

View File

@@ -0,0 +1,85 @@
package app.revanced.integrations.settingsmenu;
import static app.revanced.integrations.utils.StringRef.str;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.settings.SharedPrefCategory;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
/**
* Shows what thumbnails will be used based on the current settings.
*/
@SuppressWarnings("unused")
public class AlternativeThumbnailsStatusPreference extends Preference {
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
// Because this listener may run before the ReVanced settings fragment updates SettingsEnum,
// this could show the prior config and not the current.
//
// Push this call to the end of the main run queue,
// so all other listeners are done and SettingsEnum is up to date.
ReVancedUtils.runOnMainThread(this::updateUI);
};
public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlternativeThumbnailsStatusPreference(Context context) {
super(context);
}
private void addChangeListener() {
LogHelper.printDebug(() -> "addChangeListener");
SharedPrefCategory.YOUTUBE.preferences.registerOnSharedPreferenceChangeListener(listener);
}
private void removeChangeListener() {
LogHelper.printDebug(() -> "removeChangeListener");
SharedPrefCategory.YOUTUBE.preferences.unregisterOnSharedPreferenceChangeListener(listener);
}
@Override
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
super.onAttachedToHierarchy(preferenceManager);
updateUI();
addChangeListener();
}
@Override
protected void onPrepareForRemoval() {
super.onPrepareForRemoval();
removeChangeListener();
}
private void updateUI() {
LogHelper.printDebug(() -> "updateUI");
final boolean usingDeArrow = SettingsEnum.ALT_THUMBNAIL_DEARROW.getBoolean();
final boolean usingVideoStills = SettingsEnum.ALT_THUMBNAIL_STILLS.getBoolean();
final String summaryTextKey;
if (usingDeArrow && usingVideoStills) {
summaryTextKey = "revanced_alt_thumbnail_about_status_dearrow_stills";
} else if (usingDeArrow) {
summaryTextKey = "revanced_alt_thumbnail_about_status_dearrow";
} else if (usingVideoStills) {
summaryTextKey = "revanced_alt_thumbnail_about_status_stills";
} else {
summaryTextKey = "revanced_alt_thumbnail_about_status_disabled";
}
setSummary(str(summaryTextKey));
}
}

View File

@@ -26,8 +26,6 @@ import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.DecimalFormat;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.settings.SharedPrefCategory;
import app.revanced.integrations.sponsorblock.SegmentPlaybackController;
@@ -351,7 +349,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> {
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) {
SettingsEnum.SB_API_URL.saveValue(SettingsEnum.SB_API_URL.defaultValue);
SettingsEnum.SB_API_URL.resetToDefault();
ReVancedUtils.showToastLong(str("sb_api_url_reset"));
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) {
String serverAddress = editText.getText().toString();
@@ -471,8 +469,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
}
}
private static final DecimalFormat statsNumberOfSegmentsSkippedFormatter = new DecimalFormat("#,###,###");
private void addUserStats(@NonNull Preference loadingPlaceholder, @Nullable UserStats stats) {
ReVancedUtils.verifyOnMainThread();
try {
@@ -514,7 +510,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
// number of segment submissions (does not include ignored segments)
Preference preference = new Preference(context);
statsCategory.addPreference(preference);
String formatted = statsNumberOfSegmentsSkippedFormatter.format(stats.segmentCount);
String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount);
preference.setTitle(fromHtml(str("sb_stats_submissions", formatted)));
if (stats.totalSegmentCountIncludingIgnored == 0) {
preference.setSelectable(false);
@@ -550,7 +546,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
stats_saved = str("sb_stats_saved_zero");
stats_saved_sum = str("sb_stats_saved_sum_zero");
} else {
stats_saved = str("sb_stats_saved", statsNumberOfSegmentsSkippedFormatter.format(stats.viewCount));
stats_saved = str("sb_stats_saved",
SponsorBlockUtils.getNumberOfSkipsString(stats.viewCount));
stats_saved_sum = str("sb_stats_saved_sum", SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
}
preference.setTitle(fromHtml(stats_saved));
@@ -573,7 +570,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
statsCategory.addPreference(preference);
Runnable updateStatsSelfSaved = () -> {
String formatted = statsNumberOfSegmentsSkippedFormatter.format(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
String formatted = SponsorBlockUtils.getNumberOfSkipsString(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
preference.setTitle(fromHtml(str("sb_stats_self_saved", formatted)));
String formattedSaved = SponsorBlockUtils.getTimeSavedString(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / 1000);
preference.setSummary(fromHtml(str("sb_stats_self_saved_sum", formattedSaved)));
@@ -583,8 +580,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("sb_stats_self_saved_reset_title"))
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.defaultValue);
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.defaultValue);
SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
updateStatsSelfSaved.run();
})
.setNegativeButton(android.R.string.no, null).show();

View File

@@ -12,6 +12,7 @@ import android.widget.EditText;
import androidx.annotation.NonNull;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Duration;
@@ -39,6 +40,7 @@ public class SponsorBlockUtils {
private static final SimpleDateFormat manualEditTimeFormatter = new SimpleDateFormat(MANUAL_EDIT_TIME_FORMAT);
@SuppressLint("SimpleDateFormat")
private static final SimpleDateFormat voteSegmentTimeFormatter = new SimpleDateFormat();
private static final NumberFormat statsNumberFormatter = NumberFormat.getNumberInstance();
static {
TimeZone utc = TimeZone.getTimeZone("UTC");
manualEditTimeFormatter.setTimeZone(utc);
@@ -402,19 +404,27 @@ public class SponsorBlockUtils {
}
}
public static String getNumberOfSkipsString(int viewCount) {
return statsNumberFormatter.format(viewCount);
}
public static String getTimeSavedString(long totalSecondsSaved) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Duration duration = Duration.ofSeconds(totalSecondsSaved);
final long hoursSaved = duration.toHours();
final long minutesSaved = duration.toMinutes() % 60;
if (hoursSaved > 0) {
return str("sb_stats_saved_hour_format", hoursSaved, minutesSaved);
final long hours = duration.toHours();
final long minutes = duration.toMinutes() % 60;
// Format all numbers so non-western numbers use a consistent appearance.
String minutesFormatted = statsNumberFormatter.format(minutes);
if (hours > 0) {
String hoursFormatted = statsNumberFormatter.format(hours);
return str("sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
}
final long secondsSaved = duration.getSeconds() % 60;
if (minutesSaved > 0) {
return str("sb_stats_saved_minute_format", minutesSaved, secondsSaved);
final long seconds = duration.getSeconds() % 60;
String secondsFormatted = statsNumberFormatter.format(seconds);
if (minutes > 0) {
return str("sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
}
return str("sb_stats_saved_second_format", secondsSaved);
return str("sb_stats_saved_second_format", secondsFormatted);
}
return "error"; // will never be reached. YouTube requires Android O or greater
}

View File

@@ -7,13 +7,15 @@ import android.preference.PreferenceFragment;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import app.revanced.tiktok.utils.LogHelper;
import app.revanced.tiktok.utils.ReVancedUtils;
import com.bytedance.ies.ugc.aweme.commercialize.compliance.personalization.AdPersonalizationActivity;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import app.revanced.tiktok.utils.LogHelper;
import app.revanced.tiktok.utils.ReVancedUtils;
public class SettingsMenu {
public static Object createSettingsEntry(String entryClazzName, String entryInfoClazzName) {
@@ -22,10 +24,9 @@ public class SettingsMenu {
Class<?> entryInfoClazz = Class.forName(entryInfoClazzName);
Constructor<?> entryConstructor = entryClazz.getConstructor(entryInfoClazz);
Constructor<?> entryInfoConstructor = entryInfoClazz.getDeclaredConstructors()[0];
Object buttonInfo = entryInfoConstructor.newInstance("Revanced settings", null, (View.OnClickListener) view -> startSettingsActivity());
Object buttonInfo = entryInfoConstructor.newInstance("ReVanced settings", null, (View.OnClickListener) view -> startSettingsActivity(), "revanced");
return entryConstructor.newInstance(buttonInfo);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException |
InstantiationException e) {
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}

View File

@@ -0,0 +1,4 @@
package org.chromium.net;
public abstract class UrlRequest {
}

View File

@@ -0,0 +1,11 @@
package org.chromium.net.impl;
import org.chromium.net.UrlRequest;
public abstract class CronetUrlRequest extends UrlRequest {
/**
* Method is added by patch.
*/
public abstract String getHookedUrl();
}

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
android.useAndroidX = true
version = 1.0.0-dev.4
version = 1.0.1-dev.1

View File

@@ -2,7 +2,7 @@ rootProject.name = "revanced-integrations"
buildCache {
local {
isEnabled = !System.getenv().containsKey("CI")
isEnabled = "CI" !in System.getenv()
}
}