mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-11-09 05:37:04 +01:00
chore: merge branch dev
to main
(#446)
This commit is contained in:
commit
2ab34fce31
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build
|
run: ./gradlew build clean
|
||||||
- name: Setup semantic-release
|
- name: Setup semantic-release
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Release
|
- name: Release
|
||||||
|
49
CHANGELOG.md
49
CHANGELOG.md
@ -1,3 +1,52 @@
|
|||||||
|
# [0.115.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.6...v0.115.0-dev.7) (2023-08-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Tiktok - Feed filter:** Add more filters ([#445](https://github.com/ReVanced/revanced-integrations/issues/445)) ([c16c038](https://github.com/ReVanced/revanced-integrations/commit/c16c038396bde365ce1eec7b385eeb1f485efe99))
|
||||||
|
|
||||||
|
# [0.115.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.5...v0.115.0-dev.6) (2023-08-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** fix potential litho filter thread race ([d3f523f](https://github.com/ReVanced/revanced-integrations/commit/d3f523f9dd5c3638cf9e2ba849b91e446daa99fa))
|
||||||
|
|
||||||
|
# [0.115.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.4...v0.115.0-dev.5) (2023-08-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **YouTube:** Filter litho components using prefix tree ([#447](https://github.com/ReVanced/revanced-integrations/issues/447)) ([18f2900](https://github.com/ReVanced/revanced-integrations/commit/18f29004b8d570915a0228e292f1256785ac2cab))
|
||||||
|
|
||||||
|
# [0.115.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.3...v0.115.0-dev.4) (2023-07-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** add `Player Flyout Menu` patch ([#416](https://github.com/ReVanced/revanced-integrations/issues/416)) ([b2085ae](https://github.com/ReVanced/revanced-integrations/commit/b2085aeebf4cfc21d69c0abfe545b382e2c82abe))
|
||||||
|
|
||||||
|
# [0.115.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.2...v0.115.0-dev.3) (2023-07-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof App Version:** Remove 17.30.35 target ([e906c6d](https://github.com/ReVanced/revanced-integrations/commit/e906c6d3a2195c94c926f9903f1e4e66c3714e3e))
|
||||||
|
|
||||||
|
# [0.115.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v0.115.0-dev.1...v0.115.0-dev.2) (2023-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Fix skip highlight button showing when set to 'show in seekbar' ([ea7bc57](https://github.com/ReVanced/revanced-integrations/commit/ea7bc5737584ce967becd5b9e5d0d57f5d2fbc49))
|
||||||
|
|
||||||
|
# [0.115.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.114.0...v0.115.0-dev.1) (2023-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide `chips shelf` ([#448](https://github.com/ReVanced/revanced-integrations/issues/448)) ([c7d2a9b](https://github.com/ReVanced/revanced-integrations/commit/c7d2a9b3101439b241218c45d0c4b64012adab65))
|
||||||
|
|
||||||
# [0.114.0](https://github.com/ReVanced/revanced-integrations/compare/v0.113.0...v0.114.0) (2023-07-21)
|
# [0.114.0](https://github.com/ReVanced/revanced-integrations/compare/v0.113.0...v0.114.0) (2023-07-21)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideWatchInVRPatch {
|
|
||||||
public static boolean hideWatchInVR() {
|
|
||||||
return SettingsEnum.HIDE_WATCH_IN_VR.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,21 +2,25 @@ package app.revanced.integrations.patches.components;
|
|||||||
|
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
import app.revanced.integrations.utils.StringTrieSearch;
|
||||||
|
|
||||||
|
|
||||||
public final class AdsFilter extends Filter {
|
public final class AdsFilter extends Filter {
|
||||||
private final String[] exceptions;
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
|
|
||||||
public AdsFilter() {
|
public AdsFilter() {
|
||||||
exceptions = new String[]{
|
exceptions.addPatterns(
|
||||||
"home_video_with_context", // Don't filter anything in the home page video component.
|
"home_video_with_context", // Don't filter anything in the home page video component.
|
||||||
"related_video_with_context", // Don't filter anything in the related video component.
|
"related_video_with_context", // Don't filter anything in the related video component.
|
||||||
"comment_thread", // Don't filter anything in the comments.
|
"comment_thread", // Don't filter anything in the comments.
|
||||||
"|comment.", // Don't filter anything in the comments replies.
|
"|comment.", // Don't filter anything in the comments replies.
|
||||||
"library_recent_shelf",
|
"library_recent_shelf"
|
||||||
};
|
);
|
||||||
|
|
||||||
final var buttonedAd = new StringFilterGroup(
|
final var buttonedAd = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_BUTTONED_ADS,
|
SettingsEnum.HIDE_BUTTONED_ADS,
|
||||||
@ -95,11 +99,12 @@ public final class AdsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) {
|
public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
if (ReVancedUtils.containsAny(path, exceptions))
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
|
if (exceptions.matches(path))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return super.isFiltered(path, identifier, _protobufBufferArray);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
final class ButtonsFilter extends Filter {
|
final class ButtonsFilter extends Filter {
|
||||||
@ -33,7 +35,8 @@ final class ButtonsFilter extends Filter {
|
|||||||
SettingsEnum.HIDE_ACTION_BUTTONS,
|
SettingsEnum.HIDE_ACTION_BUTTONS,
|
||||||
"ContainerType|video_action_button",
|
"ContainerType|video_action_button",
|
||||||
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
||||||
)
|
),
|
||||||
|
actionBarRule
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,10 +48,12 @@ final class ButtonsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) {
|
public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
if (isEveryFilterGroupEnabled())
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
if (actionBarRule.check(identifier).isFiltered()) return true;
|
if (matchedGroup == actionBarRule) {
|
||||||
|
return isEveryFilterGroupEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
return super.isFiltered(path, identifier, _protobufBufferArray);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,16 @@ package app.revanced.integrations.patches.components;
|
|||||||
|
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.StringTrieSearch;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public final class LayoutComponentsFilter extends Filter {
|
public final class LayoutComponentsFilter extends Filter {
|
||||||
private final String[] exceptions;
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
|
|
||||||
private final CustomFilterGroup custom;
|
private final CustomFilterGroup custom;
|
||||||
|
|
||||||
private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup(
|
private static final ByteArrayAsStringFilterGroup mixPlaylists = new ByteArrayAsStringFilterGroup(
|
||||||
@ -20,13 +21,13 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public LayoutComponentsFilter() {
|
public LayoutComponentsFilter() {
|
||||||
exceptions = new String[]{
|
exceptions.addPatterns(
|
||||||
"home_video_with_context",
|
"home_video_with_context",
|
||||||
"related_video_with_context",
|
"related_video_with_context",
|
||||||
"comment_thread", // skip filtering anything in the comments
|
"comment_thread", // skip filtering anything in the comments
|
||||||
"|comment.", // skip filtering anything in the comments replies
|
"|comment.", // skip filtering anything in the comments replies
|
||||||
"library_recent_shelf",
|
"library_recent_shelf"
|
||||||
};
|
);
|
||||||
|
|
||||||
custom = new CustomFilterGroup(
|
custom = new CustomFilterGroup(
|
||||||
SettingsEnum.CUSTOM_FILTER,
|
SettingsEnum.CUSTOM_FILTER,
|
||||||
@ -136,6 +137,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"cell_divider" // layout residue (gray line above the buttoned ad),
|
"cell_divider" // layout residue (gray line above the buttoned ad),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final var chipsShelf = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CHIPS_SHELF,
|
||||||
|
"chips_shelf"
|
||||||
|
);
|
||||||
|
|
||||||
this.pathFilterGroups.addAll(
|
this.pathFilterGroups.addAll(
|
||||||
channelBar,
|
channelBar,
|
||||||
communityPosts,
|
communityPosts,
|
||||||
@ -155,26 +161,32 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
artistCard,
|
artistCard,
|
||||||
imageShelf,
|
imageShelf,
|
||||||
subscribersCommunityGuidelines,
|
subscribersCommunityGuidelines,
|
||||||
channelMemberShelf
|
channelMemberShelf,
|
||||||
|
custom
|
||||||
);
|
);
|
||||||
|
|
||||||
this.identifierFilterGroups.addAll(graySeparator);
|
this.identifierFilterGroups.addAll(
|
||||||
|
graySeparator,
|
||||||
|
chipsShelf
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) {
|
public boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
if (custom.isEnabled() && custom.check(path).isFiltered())
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
return true;
|
if (matchedGroup != custom && exceptions.matches(path))
|
||||||
|
|
||||||
if (ReVancedUtils.containsAny(path, exceptions))
|
|
||||||
return false; // Exceptions are not filtered.
|
return false; // Exceptions are not filtered.
|
||||||
|
|
||||||
return super.isFiltered(path, identifier, _protobufBufferArray);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Called from a different place then the other filters.
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Called from a different place then the other filters.
|
||||||
|
*/
|
||||||
public static boolean filterMixPlaylists(final byte[] bytes) {
|
public static boolean filterMixPlaylists(final byte[] bytes) {
|
||||||
return mixPlaylists.isEnabled() && mixPlaylists.check(bytes).isFiltered();
|
return mixPlaylists.check(bytes).isFiltered();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,30 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.*;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Spliterator;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
abstract class FilterGroup<T> {
|
abstract class FilterGroup<T> {
|
||||||
final static class FilterGroupResult {
|
final static class FilterGroupResult {
|
||||||
private final boolean filtered;
|
SettingsEnum setting;
|
||||||
private final SettingsEnum setting;
|
boolean filtered;
|
||||||
|
|
||||||
public FilterGroupResult(final SettingsEnum setting, final boolean filtered) {
|
FilterGroupResult(SettingsEnum setting, boolean filtered) {
|
||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
this.filtered = filtered;
|
this.filtered = filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A null value if the group has no setting,
|
||||||
|
* or if no match is returned from {@link FilterGroupList#check(Object)}.
|
||||||
|
*/
|
||||||
public SettingsEnum getSetting() {
|
public SettingsEnum getSetting() {
|
||||||
return setting;
|
return setting;
|
||||||
}
|
}
|
||||||
@ -48,51 +47,87 @@ abstract class FilterGroup<T> {
|
|||||||
public FilterGroup(final SettingsEnum setting, final T... filters) {
|
public FilterGroup(final SettingsEnum setting, final T... filters) {
|
||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
|
if (filters.length == 0) {
|
||||||
|
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return setting == null || setting.getBoolean();
|
return setting == null || setting.getBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If {@link FilterGroupList} should include this group when searching.
|
||||||
|
* By default, all filters are included except non enabled settings that require reboot.
|
||||||
|
*/
|
||||||
|
public boolean includeInSearch() {
|
||||||
|
return isEnabled() || !setting.rebootApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract FilterGroupResult check(final T stack);
|
public abstract FilterGroupResult check(final T stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringFilterGroup extends FilterGroup<String> {
|
class StringFilterGroup extends FilterGroup<String> {
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link FilterGroup#FilterGroup(SettingsEnum, Object[])}
|
|
||||||
*/
|
|
||||||
public StringFilterGroup(final SettingsEnum setting, final String... filters) {
|
public StringFilterGroup(final SettingsEnum setting, final String... filters) {
|
||||||
super(setting, filters);
|
super(setting, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FilterGroupResult check(final String string) {
|
public FilterGroupResult check(final String string) {
|
||||||
return new FilterGroupResult(setting, string != null && ReVancedUtils.containsAny(string, filters));
|
return new FilterGroupResult(setting,
|
||||||
|
(setting == null || setting.getBoolean()) && ReVancedUtils.containsAny(string, filters));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CustomFilterGroup extends StringFilterGroup {
|
final class CustomFilterGroup extends StringFilterGroup {
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link FilterGroup#FilterGroup(SettingsEnum, Object[])}
|
|
||||||
*/
|
|
||||||
public CustomFilterGroup(final SettingsEnum setting, final SettingsEnum filter) {
|
public CustomFilterGroup(final SettingsEnum setting, final SettingsEnum filter) {
|
||||||
super(setting, filter.getString().split(","));
|
super(setting, filter.getString().split(","));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you have more than 1 filter patterns, then all instances of
|
||||||
|
* this class should filtered using {@link ByteArrayFilterGroupList#check(byte[])},
|
||||||
|
* which uses a prefix tree to give better performance.
|
||||||
|
*/
|
||||||
class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||||
|
|
||||||
|
private volatile int[][] failurePatterns;
|
||||||
|
|
||||||
// Modified implementation from https://stackoverflow.com/a/1507813
|
// Modified implementation from https://stackoverflow.com/a/1507813
|
||||||
private int indexOf(final byte[] data, final byte[] pattern) {
|
private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) {
|
||||||
if (data.length == 0)
|
// Finds the first occurrence of the pattern in the byte array using
|
||||||
return -1;
|
// KMP matching algorithm.
|
||||||
|
int patternLength = pattern.length;
|
||||||
|
for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) {
|
||||||
|
while (j > 0 && pattern[j] != data[i]) {
|
||||||
|
j = failure[j - 1];
|
||||||
|
}
|
||||||
|
if (pattern[j] == data[i]) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == patternLength) {
|
||||||
|
return i - patternLength + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] createFailurePattern(byte[] pattern) {
|
||||||
// Computes the failure function using a boot-strapping process,
|
// Computes the failure function using a boot-strapping process,
|
||||||
// where the pattern is matched against itself.
|
// where the pattern is matched against itself.
|
||||||
final int[] failure = new int[pattern.length];
|
final int patternLength = pattern.length;
|
||||||
|
final int[] failure = new int[patternLength];
|
||||||
|
|
||||||
int j = 0;
|
for (int i = 1, j = 0; i < patternLength; i++) {
|
||||||
for (int i = 1; i < pattern.length; i++) {
|
|
||||||
while (j > 0 && pattern[j] != pattern[i]) {
|
while (j > 0 && pattern[j] != pattern[i]) {
|
||||||
j = failure[j - 1];
|
j = failure[j - 1];
|
||||||
}
|
}
|
||||||
@ -101,54 +136,45 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
|||||||
}
|
}
|
||||||
failure[i] = j;
|
failure[i] = j;
|
||||||
}
|
}
|
||||||
|
return failure;
|
||||||
// Finds the first occurrence of the pattern in the byte array using
|
|
||||||
// KMP matching algorithm.
|
|
||||||
|
|
||||||
j = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
while (j > 0 && pattern[j] != data[i]) {
|
|
||||||
j = failure[j - 1];
|
|
||||||
}
|
|
||||||
if (pattern[j] == data[i]) {
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
if (j == pattern.length) {
|
|
||||||
return i - pattern.length + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public ByteArrayFilterGroup(SettingsEnum setting, byte[]... filters) {
|
||||||
* {@link FilterGroup#FilterGroup(SettingsEnum, Object[])}
|
|
||||||
*/
|
|
||||||
public ByteArrayFilterGroup(final SettingsEnum setting, final byte[]... filters) {
|
|
||||||
super(setting, filters);
|
super(setting, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void buildFailurePatterns() {
|
||||||
|
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
|
||||||
|
LogHelper.printDebug(() -> "Building failure array for: " + this);
|
||||||
|
int[][] failurePatterns = new int[filters.length][];
|
||||||
|
int i = 0;
|
||||||
|
for (byte[] pattern : filters) {
|
||||||
|
failurePatterns[i++] = createFailurePattern(pattern);
|
||||||
|
}
|
||||||
|
this.failurePatterns = failurePatterns; // Must set after initialization finishes.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FilterGroupResult check(final byte[] bytes) {
|
public FilterGroupResult check(final byte[] bytes) {
|
||||||
var matched = false;
|
var matched = false;
|
||||||
for (byte[] filter : filters) {
|
if (isEnabled()) {
|
||||||
if (indexOf(bytes, filter) == -1)
|
if (failurePatterns == null) {
|
||||||
continue;
|
buildFailurePatterns(); // Lazy load.
|
||||||
|
}
|
||||||
matched = true;
|
for (int i = 0, length = filters.length; i < length; i++) {
|
||||||
break;
|
if (indexOf(bytes, filters[i], failurePatterns[i]) >= 0) {
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return new FilterGroupResult(setting, matched);
|
||||||
final var filtered = matched;
|
|
||||||
return new FilterGroupResult(setting, filtered);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup {
|
final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup {
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link ByteArrayFilterGroup#ByteArrayFilterGroup(SettingsEnum, byte[]...)}
|
|
||||||
*/
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) {
|
public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) {
|
||||||
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
||||||
@ -156,11 +182,41 @@ final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
||||||
private final ArrayList<T> filterGroups = new ArrayList<>();
|
|
||||||
|
private final List<T> filterGroups = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* Search graph. Created only if needed.
|
||||||
|
*/
|
||||||
|
private volatile TrieSearch<V> search;
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
protected final void addAll(final T... filterGroups) {
|
protected final void addAll(final T... groups) {
|
||||||
this.filterGroups.addAll(Arrays.asList(filterGroups));
|
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) {
|
||||||
|
if (!group.includeInSearch()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (V pattern : group.filters) {
|
||||||
|
search.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> {
|
||||||
|
if (group.isEnabled()) {
|
||||||
|
FilterGroup.FilterGroupResult result = (FilterGroup.FilterGroupResult) callbackParameter;
|
||||||
|
result.setting = group.setting;
|
||||||
|
result.filtered = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.search = search; // Must set after it's completely initialized.
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -182,94 +238,207 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
return filterGroups.spliterator();
|
return filterGroups.spliterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean contains(final V stack) {
|
protected FilterGroup.FilterGroupResult check(V stack) {
|
||||||
for (T filterGroup : this) {
|
if (search == null) {
|
||||||
if (!filterGroup.isEnabled())
|
buildSearch(); // Lazy load.
|
||||||
continue;
|
|
||||||
|
|
||||||
var result = filterGroup.check(stack);
|
|
||||||
if (result.isFiltered()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(null, false);
|
||||||
return false;
|
search.matches(stack, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract TrieSearch<V> createSearchGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
||||||
|
protected StringTrieSearch createSearchGraph() {
|
||||||
|
return new StringTrieSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If searching for a single byte pattern, then it is slightly better to use
|
||||||
|
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
|
||||||
|
* than a prefix tree to search for only 1 pattern.
|
||||||
|
*/
|
||||||
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
||||||
|
protected ByteTrieSearch createSearchGraph() {
|
||||||
|
return new ByteTrieSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Filter {
|
abstract class Filter {
|
||||||
final protected StringFilterGroupList pathFilterGroups = new StringFilterGroupList();
|
/**
|
||||||
final protected StringFilterGroupList identifierFilterGroups = new StringFilterGroupList();
|
* All group filters must be set before the constructor call completes.
|
||||||
final protected ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList();
|
* Otherwise {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)}
|
||||||
|
* will never be called for any matches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected final StringFilterGroupList pathFilterGroups = new StringFilterGroupList();
|
||||||
|
protected final StringFilterGroupList identifierFilterGroups = new StringFilterGroupList();
|
||||||
|
/**
|
||||||
|
* A collection of {@link ByteArrayFilterGroup} that are always searched for (no matter what).
|
||||||
|
*
|
||||||
|
* If possible, avoid adding values to this list and instead use a path or identifier filter
|
||||||
|
* for the item you are looking for. Then inside
|
||||||
|
* {@link #isFiltered(String, String, byte[], FilterGroupList, FilterGroup, int)},
|
||||||
|
* the buffer can then be searched using using a different
|
||||||
|
* {@link ByteArrayFilterGroupList} or a {@link ByteArrayFilterGroup}.
|
||||||
|
* This way, the expensive buffer searching only occurs if the cheap and fast path/identifier is already found.
|
||||||
|
*/
|
||||||
|
protected final ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given path, identifier or protobuf buffer is filtered by any
|
* Called after an enabled filter has been matched.
|
||||||
* {@link FilterGroup}. Method is called off the main thread.
|
* Default implementation is to always filter the matched item.
|
||||||
|
* Subclasses can perform additional or different checks if needed.
|
||||||
*
|
*
|
||||||
* @return True if filtered, false otherwise.
|
* 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.
|
||||||
*/
|
*/
|
||||||
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
|
@SuppressWarnings("rawtypes")
|
||||||
if (pathFilterGroups.contains(path)) {
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
LogHelper.printDebug(() -> String.format("Filtered path: %s", path));
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
return true;
|
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||||
|
if (pathFilterGroups == matchedList) {
|
||||||
|
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered path: " + path);
|
||||||
|
} else if (identifierFilterGroups == matchedList) {
|
||||||
|
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered identifier: " + identifier);
|
||||||
|
} else if (protobufBufferFilterGroups == matchedList) {
|
||||||
|
LogHelper.printDebug(() -> getClass().getSimpleName() + " Filtered from protobuf-buffer");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
if (identifierFilterGroups.contains(identifier)) {
|
|
||||||
LogHelper.printDebug(() -> String.format("Filtered identifier: %s", identifier));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protobufBufferFilterGroups.contains(protobufBufferArray)) {
|
|
||||||
LogHelper.printDebug(() -> "Filtered from protobuf-buffer");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class LithoFilterPatch {
|
public final class LithoFilterPatch {
|
||||||
|
/**
|
||||||
|
* Simple wrapper to pass the litho parameters through the prefix search.
|
||||||
|
*/
|
||||||
|
private static final class LithoFilterParameters {
|
||||||
|
final String path;
|
||||||
|
final String identifier;
|
||||||
|
final byte[] protoBuffer;
|
||||||
|
|
||||||
|
LithoFilterParameters(StringBuilder lithoPath, String lithoIdentifier, ByteBuffer protoBuffer) {
|
||||||
|
this.path = lithoPath.toString();
|
||||||
|
this.identifier = lithoIdentifier;
|
||||||
|
this.protoBuffer = protoBuffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// Estimate the percentage of the buffer that are Strings.
|
||||||
|
StringBuilder builder = new StringBuilder(protoBuffer.length / 2);
|
||||||
|
builder.append( "ID: ");
|
||||||
|
builder.append(identifier);
|
||||||
|
builder.append(" Path: ");
|
||||||
|
builder.append(path);
|
||||||
|
// TODO: allow turning on/off buffer logging with a debug setting?
|
||||||
|
builder.append(" BufferStrings: ");
|
||||||
|
findAsciiStrings(builder, protoBuffer);
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search through a byte array for all ASCII strings.
|
||||||
|
*/
|
||||||
|
private static void findAsciiStrings(StringBuilder builder, byte[] buffer) {
|
||||||
|
// Valid ASCII values (ignore control characters).
|
||||||
|
final int minimumAscii = 32; // 32 = space character
|
||||||
|
final int maximumAscii = 126; // 127 = delete character
|
||||||
|
final int minimumAsciiStringLength = 4; // Minimum length of an ASCII string to include.
|
||||||
|
String delimitingCharacter = "❙"; // Non ascii character, to allow easier log filtering.
|
||||||
|
|
||||||
|
final int length = buffer.length;
|
||||||
|
int start = 0;
|
||||||
|
int end = 0;
|
||||||
|
while (end < length) {
|
||||||
|
int value = buffer[end];
|
||||||
|
if (value < minimumAscii || value > maximumAscii || end == length - 1) {
|
||||||
|
if (end - start >= minimumAsciiStringLength) {
|
||||||
|
builder.append(new String(buffer, start, end - start));
|
||||||
|
builder.append(delimitingCharacter);
|
||||||
|
}
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final Filter[] filters = new Filter[] {
|
private static final Filter[] filters = new Filter[] {
|
||||||
new DummyFilter() // Replaced by patch.
|
new DummyFilter() // Replaced by patch.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
||||||
|
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
|
||||||
|
private static final ByteTrieSearch protoSearchTree = new ByteTrieSearch();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (Filter filter : filters) {
|
||||||
|
filterGroupLists(pathSearchTree, filter, filter.pathFilterGroups);
|
||||||
|
filterGroupLists(identifierSearchTree, filter, filter.identifierFilterGroups);
|
||||||
|
filterGroupLists(protoSearchTree, filter, filter.protobufBufferFilterGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> "Using: "
|
||||||
|
+ pathSearchTree.numberOfPatterns() + " path filters"
|
||||||
|
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB), "
|
||||||
|
+ identifierSearchTree.numberOfPatterns() + " identifier filters"
|
||||||
|
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), "
|
||||||
|
+ protoSearchTree.numberOfPatterns() + " buffer filters"
|
||||||
|
+ " (" + protoSearchTree.getEstimatedMemorySize() + " KB)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void filterGroupLists(TrieSearch<T> pathSearchTree,
|
||||||
|
Filter filter, FilterGroupList<T, ? extends FilterGroup<T>> list) {
|
||||||
|
for (FilterGroup<T> group : list) {
|
||||||
|
if (!group.includeInSearch()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (T pattern : group.filters) {
|
||||||
|
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, callbackParameter) -> {
|
||||||
|
if (!group.isEnabled()) return false;
|
||||||
|
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||||
|
return filter.isFiltered(parameters.path, parameters.identifier, parameters.protoBuffer,
|
||||||
|
list, group, matchedStartIndex);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread.
|
* Injection point. Called off the main thread.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static boolean filter(final StringBuilder pathBuilder, final String identifier,
|
public static boolean filter(@NonNull StringBuilder pathBuilder, @Nullable String lithoIdentifier,
|
||||||
final ByteBuffer protobufBuffer) {
|
@NonNull ByteBuffer protobufBuffer) {
|
||||||
// TODO: Maybe this can be moved to the Filter class, to prevent unnecessary
|
try {
|
||||||
// string creation
|
// It is assumed that protobufBuffer is empty as well in this case.
|
||||||
// because some filters might not need the path.
|
if (pathBuilder.length() == 0)
|
||||||
var path = pathBuilder.toString();
|
return false;
|
||||||
|
|
||||||
// It is assumed that protobufBuffer is empty as well in this case.
|
LithoFilterParameters parameter = new LithoFilterParameters(pathBuilder, lithoIdentifier, protobufBuffer);
|
||||||
if (path.isEmpty())
|
LogHelper.printDebug(() -> "Searching " + parameter);
|
||||||
return false;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> String.format(
|
if (pathSearchTree.matches(parameter.path, parameter)) return true;
|
||||||
"Searching (ID: %s, Buffer-size: %s): %s",
|
if (parameter.identifier != null) {
|
||||||
identifier, protobufBuffer.remaining(), path));
|
if (identifierSearchTree.matches(parameter.identifier, parameter)) return true;
|
||||||
|
}
|
||||||
var protobufBufferArray = protobufBuffer.array();
|
if (protoSearchTree.matches(parameter.protoBuffer, parameter)) return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
for (var filter : filters) {
|
LogHelper.printException(() -> "Litho filter failure", ex);
|
||||||
var filtered = filter.isFiltered(path, identifier, protobufBufferArray);
|
|
||||||
|
|
||||||
LogHelper.printDebug(
|
|
||||||
() -> String.format("%s (ID: %s): %s", filtered ? "Filtered" : "Unfiltered", identifier, path));
|
|
||||||
|
|
||||||
if (filtered)
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
// Abuse LithoFilter for CustomPlaybackSpeedPatch.
|
// Abuse LithoFilter for CustomPlaybackSpeedPatch.
|
||||||
public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
public final class PlaybackSpeedMenuFilterPatch 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.
|
||||||
@ -13,8 +15,9 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
isPlaybackSpeedMenuVisible = super.isFiltered(path, identifier, protobufBufferArray);
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
|
isPlaybackSpeedMenuVisible = true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||||
|
|
||||||
|
// Search the buffer only if the flyout menu identifier 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();
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
public PlayerFlyoutMenuItemsFilter() {
|
||||||
|
identifierFilterGroups.addAll(new StringFilterGroup(null, "overflow_menu_item.eml|"));
|
||||||
|
|
||||||
|
flyoutFilterGroupList.addAll(
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_QUALITY_MENU,
|
||||||
|
"yt_outline_gear"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CAPTIONS_MENU,
|
||||||
|
"closed_caption"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_LOOP_VIDEO_MENU,
|
||||||
|
"yt_outline_arrow_repeat_1_"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_AMBIENT_MODE_MENU,
|
||||||
|
"yt_outline_screen_light"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_REPORT_MENU,
|
||||||
|
"yt_outline_flag"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_HELP_MENU,
|
||||||
|
"yt_outline_question_circle"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_MORE_INFO_MENU,
|
||||||
|
"yt_outline_info_circle"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SPEED_MENU,
|
||||||
|
"yt_outline_play_arrow_half_circle"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_AUDIO_TRACK_MENU,
|
||||||
|
"yt_outline_person_radar"
|
||||||
|
),
|
||||||
|
new ByteArrayAsStringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_WATCH_IN_VR_MENU,
|
||||||
|
"yt_outline_vr"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
|
// Only 1 group is added to the parent class, so the matched group must be the overflow menu.
|
||||||
|
if (matchedIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||||
|
// Super class handles logging.
|
||||||
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedList, matchedGroup, matchedIndex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,40 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
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.hideViewBy1dpUnderCondition;
|
||||||
import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition;
|
import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
public final class ShortsFilter extends Filter {
|
public final class ShortsFilter extends Filter {
|
||||||
// Set by patch.
|
private static final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar";
|
||||||
public static PivotBar pivotBar;
|
public static PivotBar pivotBar; // Set by patch.
|
||||||
final StringFilterGroupList shortsFilterGroup = new StringFilterGroupList();
|
|
||||||
private final StringFilterGroup reelChannelBar = new StringFilterGroup(
|
private final StringFilterGroup channelBar;
|
||||||
null,
|
private final StringFilterGroup soundButton;
|
||||||
"reel_channel_bar"
|
private final StringFilterGroup infoPanel;
|
||||||
);
|
|
||||||
|
|
||||||
public ShortsFilter() {
|
public ShortsFilter() {
|
||||||
|
channelBar = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_CHANNEL_BAR,
|
||||||
|
REEL_CHANNEL_BAR_PATH
|
||||||
|
);
|
||||||
|
|
||||||
|
soundButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_SOUND_BUTTON,
|
||||||
|
"reel_pivot_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
infoPanel = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_INFO_PANEL,
|
||||||
|
"shorts_info_panel_overview"
|
||||||
|
);
|
||||||
|
|
||||||
final var thanksButton = new StringFilterGroup(
|
final var thanksButton = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS_THANKS_BUTTON,
|
SettingsEnum.HIDE_SHORTS_THANKS_BUTTON,
|
||||||
"suggested_action"
|
"suggested_action"
|
||||||
@ -32,21 +50,6 @@ public final class ShortsFilter extends Filter {
|
|||||||
"sponsor_button"
|
"sponsor_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var soundButton = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_SOUND_BUTTON,
|
|
||||||
"reel_pivot_button"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var infoPanel = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_INFO_PANEL,
|
|
||||||
"shorts_info_panel_overview"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var channelBar = new StringFilterGroup(
|
|
||||||
SettingsEnum.HIDE_SHORTS_CHANNEL_BAR,
|
|
||||||
"reel_channel_bar"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var shorts = new StringFilterGroup(
|
final var shorts = new StringFilterGroup(
|
||||||
SettingsEnum.HIDE_SHORTS,
|
SettingsEnum.HIDE_SHORTS,
|
||||||
"shorts_shelf",
|
"shorts_shelf",
|
||||||
@ -55,22 +58,21 @@ public final class ShortsFilter extends Filter {
|
|||||||
"shorts_video_cell"
|
"shorts_video_cell"
|
||||||
);
|
);
|
||||||
|
|
||||||
shortsFilterGroup.addAll(soundButton, infoPanel);
|
pathFilterGroups.addAll(joinButton, subscribeButton, channelBar, soundButton, infoPanel);
|
||||||
pathFilterGroups.addAll(joinButton, subscribeButton, channelBar);
|
|
||||||
identifierFilterGroups.addAll(shorts, thanksButton);
|
identifierFilterGroups.addAll(shorts, thanksButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(final String path, final String identifier,
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
final byte[] protobufBufferArray) {
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
|
if (matchedGroup == soundButton || matchedGroup == infoPanel || matchedGroup == channelBar) return true;
|
||||||
|
|
||||||
// Filter the path only when reelChannelBar is visible.
|
// Filter the path only when reelChannelBar is visible.
|
||||||
if (reelChannelBar.check(path).isFiltered())
|
if (pathFilterGroups == matchedList) {
|
||||||
if (this.pathFilterGroups.contains(path)) return true;
|
return path.contains(REEL_CHANNEL_BAR_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
if (shortsFilterGroup.contains(path)) return true;
|
return identifierFilterGroups == matchedList;
|
||||||
|
|
||||||
return this.identifierFilterGroups.contains(identifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void hideShortsShelf(final View shortsShelfView) {
|
public static void hideShortsShelf(final View shortsShelfView) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package app.revanced.integrations.patches.components;
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
// Abuse LithoFilter for OldVideoQualityMenuPatch.
|
// Abuse LithoFilter for OldVideoQualityMenuPatch.
|
||||||
@ -15,8 +17,9 @@ public final class VideoQualityMenuFilterPatch extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
|
boolean isFiltered(String path, @Nullable String identifier, byte[] protobufBufferArray,
|
||||||
isVideoQualityMenuVisible = super.isFiltered(path, identifier, protobufBufferArray);
|
FilterGroupList matchedList, FilterGroup matchedGroup, int matchedIndex) {
|
||||||
|
isVideoQualityMenuVisible = true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,8 @@ public final class OldVideoQualityMenuPatch {
|
|||||||
// The quality menu is a RecyclerView with 3 children. The third child is the "Advanced" quality menu.
|
// The quality menu is a RecyclerView with 3 children. The third child is the "Advanced" quality menu.
|
||||||
addRecyclerListener(linearLayout, 3, 2, recyclerView -> {
|
addRecyclerListener(linearLayout, 3, 2, recyclerView -> {
|
||||||
// Check if the current view is the quality menu.
|
// Check if the current view is the quality menu.
|
||||||
if (VideoQualityMenuFilterPatch.isVideoQualityMenuVisible) {// Hide the video quality menu.
|
if (VideoQualityMenuFilterPatch.isVideoQualityMenuVisible) {
|
||||||
|
VideoQualityMenuFilterPatch.isVideoQualityMenuVisible = false;
|
||||||
linearLayout.setVisibility(View.GONE);
|
linearLayout.setVisibility(View.GONE);
|
||||||
|
|
||||||
// Click the "Advanced" quality menu to show the "old" quality menu.
|
// Click the "Advanced" quality menu to show the "old" quality menu.
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
package app.revanced.integrations.patches.playback.speed;
|
package app.revanced.integrations.patches.playback.speed;
|
||||||
|
|
||||||
import static app.revanced.integrations.patches.playback.quality.OldVideoQualityMenuPatch.addRecyclerListener;
|
|
||||||
|
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.facebook.litho.ComponentHost;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import app.revanced.integrations.patches.components.PlaybackSpeedMenuFilterPatch;
|
import app.revanced.integrations.patches.components.PlaybackSpeedMenuFilterPatch;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
import com.facebook.litho.ComponentHost;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.patches.playback.quality.OldVideoQualityMenuPatch.addRecyclerListener;
|
||||||
|
|
||||||
public class CustomPlaybackSpeedPatch {
|
public class CustomPlaybackSpeedPatch {
|
||||||
/**
|
/**
|
||||||
@ -110,23 +107,24 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* To reduce copy paste between two similar code paths.
|
* To reduce copy and paste between two similar code paths.
|
||||||
*/
|
*/
|
||||||
public static void onFlyoutMenuCreate(final LinearLayout linearLayout) {
|
public static void onFlyoutMenuCreate(final LinearLayout linearLayout) {
|
||||||
// The playback rate menu is a RecyclerView with 2 children. The third child is the "Advanced" quality menu.
|
// The playback rate menu is a RecyclerView with 2 children. The third child is the "Advanced" quality menu.
|
||||||
addRecyclerListener(linearLayout, 2, 1, recyclerView -> {
|
addRecyclerListener(linearLayout, 2, 1, recyclerView -> {
|
||||||
if (PlaybackSpeedMenuFilterPatch.isPlaybackSpeedMenuVisible &&
|
if (PlaybackSpeedMenuFilterPatch.isPlaybackSpeedMenuVisible) {
|
||||||
recyclerView.getChildCount() == 1 &&
|
PlaybackSpeedMenuFilterPatch.isPlaybackSpeedMenuVisible = false;
|
||||||
recyclerView.getChildAt(0) instanceof ComponentHost
|
|
||||||
) {
|
|
||||||
linearLayout.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// Close the new Playback speed menu and instead show the old one.
|
if (recyclerView.getChildCount() == 1 && recyclerView.getChildAt(0) instanceof ComponentHost) {
|
||||||
showOldPlaybackSpeedMenu();
|
linearLayout.setVisibility(View.GONE);
|
||||||
|
|
||||||
// DismissView [R.id.touch_outside] is the 1st ChildView of the 3rd ParentView.
|
// Close the new Playback speed menu and instead show the old one.
|
||||||
((ViewGroup) linearLayout.getParent().getParent().getParent())
|
showOldPlaybackSpeedMenu();
|
||||||
.getChildAt(0).performClick();
|
|
||||||
|
// DismissView [R.id.touch_outside] is the 1st ChildView of the 3rd ParentView.
|
||||||
|
((ViewGroup) linearLayout.getParent().getParent().getParent())
|
||||||
|
.getChildAt(0).performClick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,7 @@ public enum SettingsEnum {
|
|||||||
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
|
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
|
||||||
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true),
|
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true),
|
||||||
HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
|
HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
|
||||||
|
HIDE_CHIPS_SHELF("revanced_hide_chips_shelf", BOOLEAN, TRUE),
|
||||||
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
|
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
|
||||||
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
|
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
|
||||||
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
|
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
|
||||||
@ -123,11 +124,10 @@ public enum SettingsEnum {
|
|||||||
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
|
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
|
||||||
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
|
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
|
||||||
HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
|
HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
|
||||||
HIDE_WATCH_IN_VR("revanced_hide_watch_in_vr", BOOLEAN, FALSE, true),
|
|
||||||
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
|
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
|
||||||
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
|
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
|
||||||
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
||||||
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.30.35", true, parents(SPOOF_APP_VERSION)),
|
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.08.35", true, parents(SPOOF_APP_VERSION)),
|
||||||
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
||||||
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -149,6 +149,18 @@ public enum SettingsEnum {
|
|||||||
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),
|
||||||
|
|
||||||
|
//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_LOOP_VIDEO_MENU("revanced_hide_player_flyout_loop_video", 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_HELP_MENU("revanced_hide_player_flyout_help", BOOLEAN, TRUE),
|
||||||
|
HIDE_SPEED_MENU("revanced_hide_player_flyout_speed", BOOLEAN, FALSE),
|
||||||
|
HIDE_MORE_INFO_MENU("revanced_hide_player_flyout_more_info", BOOLEAN, TRUE),
|
||||||
|
HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, TRUE),
|
||||||
|
HIDE_WATCH_IN_VR_MENU("revanced_hide_player_flyout_watch_in_vr", BOOLEAN, TRUE),
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
AUTO_CAPTIONS("revanced_auto_captions", BOOLEAN, FALSE),
|
AUTO_CAPTIONS("revanced_auto_captions", BOOLEAN, FALSE),
|
||||||
DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE),
|
DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE),
|
||||||
|
@ -50,7 +50,7 @@ public class SegmentPlaybackController {
|
|||||||
private static SponsorSegment[] segments;
|
private static SponsorSegment[] segments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight segment, if one exists.
|
* Highlight segment, if one exists and the skip behavior is not set to {@link CategoryBehaviour#SHOW_IN_SEEKBAR}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static SponsorSegment highlightSegment;
|
private static SponsorSegment highlightSegment;
|
||||||
@ -112,10 +112,13 @@ public class SegmentPlaybackController {
|
|||||||
segments = videoSegments;
|
segments = videoSegments;
|
||||||
calculateTimeWithoutSegments();
|
calculateTimeWithoutSegments();
|
||||||
|
|
||||||
for (SponsorSegment segment : videoSegments) {
|
if (SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY
|
||||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
|| SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) {
|
||||||
highlightSegment = segment;
|
for (SponsorSegment segment : videoSegments) {
|
||||||
return;
|
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
|
highlightSegment = segment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
highlightSegment = null;
|
highlightSegment = null;
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package app.revanced.integrations.utils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
||||||
|
|
||||||
|
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
||||||
|
TrieNode<byte[]> createNode() {
|
||||||
|
return new ByteTrieNode();
|
||||||
|
}
|
||||||
|
char getCharValue(byte[] text, int index) {
|
||||||
|
return (char) text[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteTrieSearch() {
|
||||||
|
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, @Nullable Object callbackParameter) {
|
||||||
|
return super.matches(textToSearch, textToSearch.length, callbackParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
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).
|
||||||
|
*/
|
||||||
|
public final class StringTrieSearch extends TrieSearch<String> {
|
||||||
|
|
||||||
|
private static final class StringTrieNode extends TrieNode<String> {
|
||||||
|
TrieNode<String> createNode() {
|
||||||
|
return new StringTrieNode();
|
||||||
|
}
|
||||||
|
char getCharValue(String text, int index) {
|
||||||
|
return text.charAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringTrieSearch() {
|
||||||
|
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, @Nullable Object callbackParameter) {
|
||||||
|
return super.matches(textToSearch, textToSearch.length(), callbackParameter);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,305 @@
|
|||||||
|
package app.revanced.integrations.utils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a group of different patterns using a trie (prefix tree).
|
||||||
|
* Can significantly speed up searching for multiple patterns.
|
||||||
|
*
|
||||||
|
* Currently only supports ASCII non-control characters (letters/numbers/symbols).
|
||||||
|
* But could be modified to also support UTF-8 unicode.
|
||||||
|
*/
|
||||||
|
public abstract class TrieSearch<T> {
|
||||||
|
|
||||||
|
public interface TriePatternMatchedCallback<T> {
|
||||||
|
/**
|
||||||
|
* Called when a pattern is matched.
|
||||||
|
*
|
||||||
|
* @param textSearched Text that was searched.
|
||||||
|
* @param matchedStartIndex Start index of the search text, where the pattern was matched.
|
||||||
|
* @param callbackParameter Optional parameter passed into {@link TrieSearch#matches(Object, Object)}.
|
||||||
|
* @return True, if the search should stop here.
|
||||||
|
* If false, searching will continue to look for other matches.
|
||||||
|
*/
|
||||||
|
boolean patternMatched(T textSearched, int matchedStartIndex, Object callbackParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a compressed tree path for a single pattern that shares no sibling nodes.
|
||||||
|
*
|
||||||
|
* For example, if a tree contains the patterns: "foobar", "football", "feet",
|
||||||
|
* it would contain 3 compressed paths of: "bar", "tball", "eet".
|
||||||
|
*
|
||||||
|
* And the tree would contain children arrays only for the first level containing 'f',
|
||||||
|
* the second level containing 'o',
|
||||||
|
* and the third level containing 'o'.
|
||||||
|
*
|
||||||
|
* This is done to reduce memory usage, which can be substantial if many long patterns are used.
|
||||||
|
*/
|
||||||
|
private static final class TrieCompressedPath<T> {
|
||||||
|
final T pattern;
|
||||||
|
final int patternLength;
|
||||||
|
final int patternStartIndex;
|
||||||
|
final TriePatternMatchedCallback<T> callback;
|
||||||
|
|
||||||
|
TrieCompressedPath(T pattern, int patternLength, int patternStartIndex, TriePatternMatchedCallback<T> callback) {
|
||||||
|
this.pattern = pattern;
|
||||||
|
this.patternLength = patternLength;
|
||||||
|
this.patternStartIndex = patternStartIndex;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
boolean matches(TrieNode<T> enclosingNode, // Used only for the get character method.
|
||||||
|
T searchText, int searchTextLength, int searchTextIndex, Object callbackParameter) {
|
||||||
|
if (searchTextLength - searchTextIndex < patternLength - patternStartIndex) {
|
||||||
|
return false; // Remaining search text is shorter than the remaining leaf pattern and they cannot match.
|
||||||
|
}
|
||||||
|
for (int i = searchTextIndex, j = patternStartIndex; j < patternLength; i++, j++) {
|
||||||
|
if (enclosingNode.getCharValue(searchText, i) != enclosingNode.getCharValue(pattern, j)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return callback == null
|
||||||
|
|| callback.patternMatched(searchText, searchTextIndex - patternStartIndex, callbackParameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static abstract class TrieNode<T> {
|
||||||
|
// Support only ASCII letters/numbers/symbols and filter out all control characters.
|
||||||
|
private static final char MIN_VALID_CHAR = 32; // Space character.
|
||||||
|
private static final char MAX_VALID_CHAR = 126; // 127 = delete character.
|
||||||
|
private static final int NUMBER_OF_CHILDREN = MAX_VALID_CHAR - MIN_VALID_CHAR + 1;
|
||||||
|
|
||||||
|
private static boolean isInvalidRange(char character) {
|
||||||
|
return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A compressed graph path that represents the remaining pattern characters of a single child node.
|
||||||
|
*
|
||||||
|
* If present then child array is always null, although callbacks for other
|
||||||
|
* end of patterns can also exist on this same node.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private TrieCompressedPath<T> leaf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All child nodes. Only present if no compressed leaf exist.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private TrieNode<T>[] children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks for all patterns that end at this node.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private List<TriePatternMatchedCallback<T>> endOfPatternCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pattern Pattern to add.
|
||||||
|
* @param patternLength Length of the pattern.
|
||||||
|
* @param patternIndex Current recursive index of the pattern.
|
||||||
|
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
|
||||||
|
*/
|
||||||
|
private void addPattern(@NonNull T pattern, int patternLength, int patternIndex,
|
||||||
|
@Nullable TriePatternMatchedCallback<T> callback) {
|
||||||
|
if (patternIndex == patternLength) { // Reached the end of the pattern.
|
||||||
|
if (endOfPatternCallback == null) {
|
||||||
|
endOfPatternCallback = new ArrayList<>(1);
|
||||||
|
}
|
||||||
|
endOfPatternCallback.add(callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (leaf != null) {
|
||||||
|
// Reached end of the graph and a leaf exist.
|
||||||
|
// Recursively call back into this method and push the existing leaf down 1 level.
|
||||||
|
if (children != null) throw new IllegalStateException();
|
||||||
|
//noinspection unchecked
|
||||||
|
children = new TrieNode[NUMBER_OF_CHILDREN];
|
||||||
|
TrieCompressedPath<T> temp = leaf;
|
||||||
|
leaf = null;
|
||||||
|
addPattern(temp.pattern, temp.patternLength, temp.patternStartIndex, temp.callback);
|
||||||
|
// Continue onward and add the parameter pattern.
|
||||||
|
} else if (children == null) {
|
||||||
|
leaf = new TrieCompressedPath<>(pattern, patternLength, patternIndex, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char character = getCharValue(pattern, patternIndex);
|
||||||
|
if (isInvalidRange(character)) {
|
||||||
|
throw new IllegalArgumentException("invalid character at index " + patternIndex + ": " + pattern);
|
||||||
|
}
|
||||||
|
character -= MIN_VALID_CHAR; // Adjust to the array range.
|
||||||
|
TrieNode<T> child = children[character];
|
||||||
|
if (child == null) {
|
||||||
|
child = createNode();
|
||||||
|
children[character] = child;
|
||||||
|
}
|
||||||
|
child.addPattern(pattern, patternLength, patternIndex + 1, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param searchText Text to search for patterns in.
|
||||||
|
* @param searchTextLength Length of the search text.
|
||||||
|
* @param searchTextIndex Current recursive search text index. Also, the end index of the current pattern match.
|
||||||
|
* @param currentMatchLength current search depth, and also the length of the current pattern match.
|
||||||
|
* @return If any pattern matches, and it's associated callback halted the search.
|
||||||
|
*/
|
||||||
|
private boolean matches(T searchText, int searchTextLength, int searchTextIndex, int currentMatchLength,
|
||||||
|
Object callbackParameter) {
|
||||||
|
if (leaf != null && leaf.matches(this,
|
||||||
|
searchText, searchTextLength, searchTextIndex, callbackParameter)) {
|
||||||
|
return true; // Leaf exists and it matched the search text.
|
||||||
|
}
|
||||||
|
if (endOfPatternCallback != null) {
|
||||||
|
final int matchStartIndex = searchTextIndex - currentMatchLength;
|
||||||
|
for (@Nullable TriePatternMatchedCallback<T> callback : endOfPatternCallback) {
|
||||||
|
if (callback == null) {
|
||||||
|
return true; // No callback and all matches are valid.
|
||||||
|
}
|
||||||
|
if (callback.patternMatched(searchText, matchStartIndex, callbackParameter)) {
|
||||||
|
return true; // Callback confirmed the match.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (children == null) {
|
||||||
|
return false; // Reached a graph end point and there's no further patterns to search.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchTextIndex == searchTextLength) {
|
||||||
|
return false; // Reached end of the search text and found no matches.
|
||||||
|
}
|
||||||
|
|
||||||
|
char character = getCharValue(searchText, searchTextIndex);
|
||||||
|
if (isInvalidRange(character)) {
|
||||||
|
return false; // Not an ASCII letter/number/symbol.
|
||||||
|
}
|
||||||
|
character -= MIN_VALID_CHAR; // Adjust to the array range.
|
||||||
|
TrieNode<T> child = children[character];
|
||||||
|
if (child == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return child.matches(searchText, searchTextLength, searchTextIndex + 1,
|
||||||
|
currentMatchLength + 1, callbackParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives an approximate memory usage.
|
||||||
|
*
|
||||||
|
* @return Estimated number of memory pointers used, starting from this node and including all children.
|
||||||
|
*/
|
||||||
|
private int estimatedNumberOfPointersUsed() {
|
||||||
|
int numberOfPointers = 3; // Number of fields in this class.
|
||||||
|
if (leaf != null) {
|
||||||
|
numberOfPointers += 4; // Number of fields in leaf node.
|
||||||
|
}
|
||||||
|
if (endOfPatternCallback != null) {
|
||||||
|
numberOfPointers += endOfPatternCallback.size();
|
||||||
|
}
|
||||||
|
if (children != null) {
|
||||||
|
numberOfPointers += NUMBER_OF_CHILDREN;
|
||||||
|
for (TrieNode<T> child : children) {
|
||||||
|
if (child != null) {
|
||||||
|
numberOfPointers += child.estimatedNumberOfPointersUsed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numberOfPointers;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract TrieNode<T> createNode();
|
||||||
|
abstract char getCharValue(T text, int index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root node, and it's children represent the first pattern characters.
|
||||||
|
*/
|
||||||
|
private final TrieNode<T> root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patterns to match.
|
||||||
|
*/
|
||||||
|
private final List<T> patterns = new ArrayList<>();
|
||||||
|
|
||||||
|
TrieSearch(@NonNull TrieNode<T> root) {
|
||||||
|
this.root = Objects.requireNonNull(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final void addPatterns(@NonNull T... patterns) {
|
||||||
|
for (T pattern : patterns) {
|
||||||
|
addPattern(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
|
||||||
|
if (patternLength == 0) return; // Nothing to match
|
||||||
|
|
||||||
|
patterns.add(pattern);
|
||||||
|
root.addPattern(pattern, patternLength, 0, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean matches(@NonNull T textToSearch, int textToSearchLength, @Nullable Object callbackParameter) {
|
||||||
|
if (patterns.size() == 0) {
|
||||||
|
return false; // No patterns were added.
|
||||||
|
}
|
||||||
|
for (int i = 0; i < textToSearchLength; i++) {
|
||||||
|
if (root.matches(textToSearch, textToSearchLength, i, 0, callbackParameter)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Estimated memory size (in kilobytes) of this instance.
|
||||||
|
*/
|
||||||
|
public int getEstimatedMemorySize() {
|
||||||
|
if (patterns.size() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Assume the device has less than 32GB of ram (and can use pointer compression),
|
||||||
|
// or the device is 32-bit.
|
||||||
|
final int numberOfBytesPerPointer = 4;
|
||||||
|
return (int) Math.ceil((numberOfBytesPerPointer * root.estimatedNumberOfPointersUsed()) / 1024.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int numberOfPatterns() {
|
||||||
|
return patterns.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> getPatterns() {
|
||||||
|
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 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, @Nullable Object callbackParameter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identical to {@link #matches(Object, Object)} but with a null callback parameter.
|
||||||
|
*/
|
||||||
|
public final boolean matches(@NonNull T textToSearch) {
|
||||||
|
return matches(textToSearch, null);
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,10 @@ import app.revanced.tiktok.settings.SettingsEnum;
|
|||||||
|
|
||||||
public class DownloadsPatch {
|
public class DownloadsPatch {
|
||||||
public static String getDownloadPath() {
|
public static String getDownloadPath() {
|
||||||
return SettingsEnum.TIK_DOWN_PATH.getString();
|
return SettingsEnum.DOWNLOAD_PATH.getString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldRemoveWatermark() {
|
public static boolean shouldRemoveWatermark() {
|
||||||
return SettingsEnum.TIK_DOWN_WATERMARK.getBoolean();
|
return SettingsEnum.DOWNLOAD_WATERMARK.getBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public class AdsFilter implements IFilter {
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return SettingsEnum.REMOVE_ADS.getBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
return item.isAd() || item.isWithPromotionalMusic();
|
||||||
|
}
|
||||||
|
}
|
@ -6,33 +6,28 @@ import com.ss.android.ugc.aweme.feed.model.FeedItemList;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
public final class FeedItemsFilter {
|
||||||
|
private static final List<IFilter> FILTERS = List.of(
|
||||||
public class FeedItemsFilter {
|
new AdsFilter(),
|
||||||
|
new LiveFilter(),
|
||||||
|
new StoryFilter(),
|
||||||
|
new ImageVideoFilter(),
|
||||||
|
new ViewCountFilter(),
|
||||||
|
new LikeCountFilter()
|
||||||
|
);
|
||||||
|
|
||||||
public static void filter(FeedItemList feedItemList) {
|
public static void filter(FeedItemList feedItemList) {
|
||||||
if (SettingsEnum.TIK_REMOVE_ADS.getBoolean()) removeAds(feedItemList);
|
Iterator<Aweme> feedItemListIterator = feedItemList.items.iterator();
|
||||||
if (SettingsEnum.TIK_HIDE_LIVE.getBoolean()) removeLive(feedItemList);
|
while (feedItemListIterator.hasNext()) {
|
||||||
}
|
Aweme item = feedItemListIterator.next();
|
||||||
|
if (item == null) continue;
|
||||||
|
|
||||||
private static void removeAds(FeedItemList feedItemList) {
|
for (IFilter filter : FILTERS) {
|
||||||
List<Aweme> items = feedItemList.items;
|
boolean enabled = filter.getEnabled();
|
||||||
Iterator<Aweme> it = items.iterator();
|
if (enabled && filter.getFiltered(item)) {
|
||||||
while (it.hasNext()) {
|
feedItemListIterator.remove();
|
||||||
Aweme item = it.next();
|
break;
|
||||||
if (item != null && (item.isAd() || item.isWithPromotionalMusic())) {
|
}
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void removeLive(FeedItemList feedItemList) {
|
|
||||||
List<Aweme> items = feedItemList.items;
|
|
||||||
Iterator<Aweme> it = items.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
Aweme item = it.next();
|
|
||||||
if (item != null && (item.isLive() || item.isLiveReplay())) {
|
|
||||||
it.remove();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public interface IFilter {
|
||||||
|
boolean getEnabled();
|
||||||
|
|
||||||
|
boolean getFiltered(Aweme item);
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public class ImageVideoFilter implements IFilter {
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return SettingsEnum.HIDE_IMAGE.getBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
return item.isImage() || item.isPhotoMode();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
||||||
|
|
||||||
|
import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax;
|
||||||
|
|
||||||
|
public final class LikeCountFilter implements IFilter {
|
||||||
|
final long minLike;
|
||||||
|
final long maxLike;
|
||||||
|
|
||||||
|
LikeCountFilter() {
|
||||||
|
long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_LIKES);
|
||||||
|
minLike = minMax[0];
|
||||||
|
maxLike = minMax[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
AwemeStatistics statistics = item.getStatistics();
|
||||||
|
if (statistics == null) return false;
|
||||||
|
|
||||||
|
long likeCount = statistics.getDiggCount();
|
||||||
|
return likeCount < minLike || likeCount > maxLike;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public class LiveFilter implements IFilter {
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return SettingsEnum.HIDE_LIVE.getBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
return item.isLive() || item.isLiveReplay();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
|
public class StoryFilter implements IFilter {
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return SettingsEnum.HIDE_STORY.getBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
return item.getIsTikTokStory();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package app.revanced.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
||||||
|
|
||||||
|
import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax;
|
||||||
|
|
||||||
|
public class ViewCountFilter implements IFilter {
|
||||||
|
final long minView;
|
||||||
|
final long maxView;
|
||||||
|
|
||||||
|
ViewCountFilter() {
|
||||||
|
long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_VIEWS);
|
||||||
|
minView = minMax[0];
|
||||||
|
maxView = minMax[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getFiltered(Aweme item) {
|
||||||
|
AwemeStatistics statistics = item.getStatistics();
|
||||||
|
if (statistics == null) return false;
|
||||||
|
|
||||||
|
long playCount = statistics.getPlayCount();
|
||||||
|
return playCount < minView || playCount > maxView;
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,42 @@
|
|||||||
package app.revanced.tiktok.settings;
|
package app.revanced.tiktok.settings;
|
||||||
|
|
||||||
import static java.lang.Boolean.FALSE;
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.BOOLEAN;
|
|
||||||
import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.STRING;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.tiktok.utils.LogHelper;
|
import app.revanced.tiktok.utils.LogHelper;
|
||||||
import app.revanced.tiktok.utils.ReVancedUtils;
|
import app.revanced.tiktok.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.BOOLEAN;
|
||||||
|
import static app.revanced.tiktok.settings.SettingsEnum.ReturnType.STRING;
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
|
||||||
public enum SettingsEnum {
|
public enum SettingsEnum {
|
||||||
//TikTok Settings
|
DEBUG("debug", BOOLEAN, FALSE), // Must be first value, otherwise logging during loading will not work.
|
||||||
TIK_DEBUG("tik_debug", BOOLEAN, FALSE), // must be first value, otherwise logging during loading will not work
|
REMOVE_ADS("remove_ads", BOOLEAN, TRUE, true),
|
||||||
TIK_REMOVE_ADS("tik_remove_ads", BOOLEAN, TRUE, true),
|
HIDE_LIVE("hide_live", BOOLEAN, FALSE, true),
|
||||||
TIK_HIDE_LIVE("tik_hide_live", BOOLEAN, FALSE, true),
|
HIDE_STORY("hide_story", BOOLEAN, FALSE, true),
|
||||||
TIK_DOWN_PATH("tik_down_path", STRING, "DCIM/TikTok"),
|
HIDE_IMAGE("hide_image", BOOLEAN, FALSE, true),
|
||||||
TIK_DOWN_WATERMARK("tik_down_watermark", BOOLEAN, TRUE),
|
MIN_MAX_VIEWS("min_max_views", STRING, "0-" + Long.MAX_VALUE, true),
|
||||||
TIK_SIMSPOOF("tik_simspoof", BOOLEAN, TRUE, true),
|
MIN_MAX_LIKES("min_max_likes", STRING, "0-" + Long.MAX_VALUE, true),
|
||||||
TIK_SIMSPOOF_ISO("tik_simspoof_iso", STRING, "us"),
|
DOWNLOAD_PATH("down_path", STRING, "DCIM/TikTok"),
|
||||||
TIK_SIMSPOOF_MCCMNC("tik_simspoof_mccmnc", STRING, "310160"),
|
DOWNLOAD_WATERMARK("down_watermark", BOOLEAN, TRUE),
|
||||||
TIK_SIMSPOOF_OP_NAME("tik_simspoof_op_name", STRING, "T-Mobile");
|
SIM_SPOOF("simspoof", BOOLEAN, TRUE, true),
|
||||||
|
SIM_SPOOF_ISO("simspoof_iso", STRING, "us"),
|
||||||
|
SIMSPOOF_MCCMNC("simspoof_mccmnc", STRING, "310160"),
|
||||||
|
SIMSPOOF_OP_NAME("simspoof_op_name", STRING, "T-Mobile");
|
||||||
|
|
||||||
|
private static final Map<String, SettingsEnum> pathToSetting = new HashMap<>(2 * values().length);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
loadAllSettings();
|
loadAllSettings();
|
||||||
|
for (SettingsEnum setting : values()) {
|
||||||
|
pathToSetting.put(setting.path, setting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -47,11 +57,12 @@ public enum SettingsEnum {
|
|||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue) {
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.TIKTOK_PREFS, false);
|
this(path, returnType, defaultValue, SharedPrefCategory.TIKTOK_PREFS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, boolean rebootApp) {
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue, boolean rebootApp) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.TIKTOK_PREFS, rebootApp);
|
this(path, returnType, defaultValue, SharedPrefCategory.TIKTOK_PREFS, rebootApp);
|
||||||
}
|
}
|
||||||
SettingsEnum(@NonNull String path, @NonNull ReturnType returnType, @NonNull Object defaultValue,
|
|
||||||
@NonNull SharedPrefCategory prefName, boolean rebootApp) {
|
SettingsEnum(@NonNull String path, @NonNull ReturnType returnType, @NonNull Object defaultValue, @NonNull SharedPrefCategory prefName, boolean rebootApp) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.returnType = returnType;
|
this.returnType = returnType;
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
@ -59,6 +70,11 @@ public enum SettingsEnum {
|
|||||||
this.rebootApp = rebootApp;
|
this.rebootApp = rebootApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static SettingsEnum getSettingsFromPath(@NonNull String str) {
|
||||||
|
return pathToSetting.get(str);
|
||||||
|
}
|
||||||
|
|
||||||
private static void loadAllSettings() {
|
private static void loadAllSettings() {
|
||||||
try {
|
try {
|
||||||
Context context = ReVancedUtils.getAppContext();
|
Context context = ReVancedUtils.getAppContext();
|
||||||
@ -144,11 +160,15 @@ public enum SettingsEnum {
|
|||||||
return (String) value;
|
return (String) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value of this setting as as generic object type.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Object getObjectValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public enum ReturnType {
|
public enum ReturnType {
|
||||||
BOOLEAN,
|
BOOLEAN, INTEGER, LONG, FLOAT, STRING,
|
||||||
INTEGER,
|
|
||||||
LONG,
|
|
||||||
FLOAT,
|
|
||||||
STRING,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.preference.*;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import app.revanced.tiktok.settings.SharedPrefCategory;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.RangeValuePreference;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.categories.DownloadsPreferenceCategory;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.categories.FeedFilterPreferenceCategory;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.categories.IntegrationsPreferenceCategory;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.categories.SimSpoofPreferenceCategory;
|
||||||
|
import app.revanced.tiktok.utils.ReVancedUtils;
|
||||||
|
import com.ss.android.ugc.aweme.splash.SplashActivity;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class ReVancedPreferenceFragment extends PreferenceFragment {
|
||||||
|
private boolean registered = false;
|
||||||
|
private boolean settingsInitialized = false;
|
||||||
|
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
|
try {
|
||||||
|
SettingsEnum setting = SettingsEnum.getSettingsFromPath(str);
|
||||||
|
if (setting == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Preference pref = findPreference(str);
|
||||||
|
if (pref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pref instanceof SwitchPreference) {
|
||||||
|
SwitchPreference switchPref = (SwitchPreference) pref;
|
||||||
|
SettingsEnum.setValue(setting, switchPref.isChecked());
|
||||||
|
} else if (pref instanceof EditTextPreference) {
|
||||||
|
EditTextPreference editPreference = (EditTextPreference) pref;
|
||||||
|
SettingsEnum.setValue(setting, editPreference.getText());
|
||||||
|
} else if (pref instanceof ListPreference) {
|
||||||
|
ListPreference listPref = (ListPreference) pref;
|
||||||
|
SettingsEnum.setValue(setting, listPref.getValue());
|
||||||
|
updateListPreferenceSummary((ListPreference) pref, setting);
|
||||||
|
} else if (pref instanceof RangeValuePreference) {
|
||||||
|
RangeValuePreference rangeValuePref = (RangeValuePreference) pref;
|
||||||
|
SettingsEnum.setValue(setting, rangeValuePref.getValue());
|
||||||
|
} else if (pref instanceof DownloadPathPreference) {
|
||||||
|
DownloadPathPreference downloadPathPref = (DownloadPathPreference) pref;
|
||||||
|
SettingsEnum.setValue(setting, downloadPathPref.getValue());
|
||||||
|
} else {
|
||||||
|
LogHelper.printException(() -> "Setting cannot be handled: " + pref.getClass() + " " + pref);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ReVancedUtils.getAppContext() != null && this.settingsInitialized && setting.rebootApp) {
|
||||||
|
rebootDialog(getActivity());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void updateListPreferenceSummary(ListPreference listPreference, SettingsEnum setting) {
|
||||||
|
String objectStringValue = setting.getObjectValue().toString();
|
||||||
|
final int entryIndex = listPreference.findIndexOfValue(objectStringValue);
|
||||||
|
if (entryIndex >= 0) {
|
||||||
|
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
|
||||||
|
listPreference.setValue(objectStringValue);
|
||||||
|
} else {
|
||||||
|
listPreference.setSummary(objectStringValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
this.registered = true;
|
||||||
|
|
||||||
|
getPreferenceManager().setSharedPreferencesName(SharedPrefCategory.TIKTOK_PREFS.prefName);
|
||||||
|
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this.listener);
|
||||||
|
|
||||||
|
final Activity context = this.getActivity();
|
||||||
|
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
|
||||||
|
setPreferenceScreen(preferenceScreen);
|
||||||
|
|
||||||
|
new FeedFilterPreferenceCategory(context, preferenceScreen);
|
||||||
|
new DownloadsPreferenceCategory(context, preferenceScreen);
|
||||||
|
new SimSpoofPreferenceCategory(context, preferenceScreen);
|
||||||
|
new IntegrationsPreferenceCategory(context, preferenceScreen);
|
||||||
|
|
||||||
|
this.settingsInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
if (this.registered) {
|
||||||
|
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this.listener);
|
||||||
|
this.registered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reboot(Activity activity) {
|
||||||
|
int intent = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
|
||||||
|
((AlarmManager) activity.getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.ELAPSED_REALTIME, 1500L, PendingIntent.getActivity(activity, 0, new Intent(activity, SplashActivity.class), intent));
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebootDialog(final Activity activity) {
|
||||||
|
new AlertDialog.Builder(activity).setMessage("Refresh and restart").setPositiveButton("Restart", (dialog, i) -> reboot(activity)).setNegativeButton("Cancel", null).show();
|
||||||
|
}
|
||||||
|
}
|
@ -1,242 +0,0 @@
|
|||||||
package app.revanced.tiktok.settingsmenu;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlarmManager;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.preference.EditTextPreference;
|
|
||||||
import android.preference.PreferenceCategory;
|
|
||||||
import android.preference.PreferenceFragment;
|
|
||||||
import android.preference.PreferenceScreen;
|
|
||||||
import android.preference.SwitchPreference;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.ss.android.ugc.aweme.splash.SplashActivity;
|
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
|
||||||
import app.revanced.tiktok.settings.SharedPrefCategory;
|
|
||||||
import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference;
|
|
||||||
import app.revanced.tiktok.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public class ReVancedSettingsFragment extends PreferenceFragment {
|
|
||||||
|
|
||||||
private boolean Registered = false;
|
|
||||||
private boolean settingsInitialized = false;
|
|
||||||
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
|
||||||
for (SettingsEnum setting : SettingsEnum.values()) {
|
|
||||||
if (!setting.path.equals(str)) continue;
|
|
||||||
|
|
||||||
if (ReVancedUtils.getAppContext() != null && this.settingsInitialized && setting.rebootApp) {
|
|
||||||
rebootDialog(getActivity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getPreferenceManager().setSharedPreferencesName(SharedPrefCategory.TIKTOK_PREFS.prefName);
|
|
||||||
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this.listener);
|
|
||||||
this.Registered = true;
|
|
||||||
|
|
||||||
final Activity context = this.getActivity();
|
|
||||||
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
|
|
||||||
setPreferenceScreen(preferenceScreen);
|
|
||||||
|
|
||||||
//Feed filter
|
|
||||||
if (SettingsStatus.feedFilter) {
|
|
||||||
PreferenceCategory feedFilter = new PreferenceCategory(context);
|
|
||||||
feedFilter.setTitle("Feed filter");
|
|
||||||
preferenceScreen.addPreference(feedFilter);
|
|
||||||
|
|
||||||
//Remove ads toggle
|
|
||||||
{
|
|
||||||
SwitchPreference preference = new SwitchPreference(context);
|
|
||||||
feedFilter.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_REMOVE_ADS.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_REMOVE_ADS.defaultValue);
|
|
||||||
preference.setChecked(SettingsEnum.TIK_REMOVE_ADS.getBoolean());
|
|
||||||
preference.setTitle("Remove feed ads");
|
|
||||||
preference.setSummary("Remove ads from feed.");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
// FIXME: the value is already saved in the preferences.
|
|
||||||
// instead of saving again, simple call SettingsEnum#setValue()
|
|
||||||
final boolean value = (Boolean) newValue;
|
|
||||||
SettingsEnum.TIK_REMOVE_ADS.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Hide LiveStreams toggle
|
|
||||||
{
|
|
||||||
SwitchPreference preference = new SwitchPreference(context);
|
|
||||||
feedFilter.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_HIDE_LIVE.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_HIDE_LIVE.defaultValue);
|
|
||||||
preference.setChecked(SettingsEnum.TIK_HIDE_LIVE.getBoolean());
|
|
||||||
preference.setTitle("Hide livestreams");
|
|
||||||
preference.setSummary("Hide livestreams from feed.");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final boolean value = (Boolean) newValue;
|
|
||||||
SettingsEnum.TIK_HIDE_LIVE.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Download
|
|
||||||
if (SettingsStatus.download) {
|
|
||||||
PreferenceCategory download = new PreferenceCategory(context);
|
|
||||||
download.setTitle("Download");
|
|
||||||
preferenceScreen.addPreference(download);
|
|
||||||
//Download path
|
|
||||||
{
|
|
||||||
DownloadPathPreference preference = new DownloadPathPreference(context);
|
|
||||||
download.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_DOWN_PATH.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_DOWN_PATH.defaultValue);
|
|
||||||
preference.setValue(SettingsEnum.TIK_DOWN_PATH.getString());
|
|
||||||
preference.setTitle("Download path");
|
|
||||||
preference.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + preference.getValue());
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final String value = (String) newValue;
|
|
||||||
SettingsEnum.TIK_DOWN_PATH.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Download watermark
|
|
||||||
{
|
|
||||||
SwitchPreference preference = new SwitchPreference(context);
|
|
||||||
download.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_DOWN_WATERMARK.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_DOWN_WATERMARK.defaultValue);
|
|
||||||
preference.setChecked(SettingsEnum.TIK_DOWN_WATERMARK.getBoolean());
|
|
||||||
preference.setTitle("Remove watermark");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final boolean value = (Boolean) newValue;
|
|
||||||
SettingsEnum.TIK_DOWN_WATERMARK.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpoofSimPatch
|
|
||||||
if(SettingsStatus.simSpoof) {
|
|
||||||
PreferenceCategory simSpoof = new PreferenceCategory(context);
|
|
||||||
simSpoof.setTitle("Bypass regional restriction");
|
|
||||||
preferenceScreen.addPreference(simSpoof);
|
|
||||||
//Global Switch
|
|
||||||
{
|
|
||||||
SwitchPreference preference = new SwitchPreference(context);
|
|
||||||
simSpoof.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_SIMSPOOF.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF.defaultValue);
|
|
||||||
preference.setChecked(SettingsEnum.TIK_SIMSPOOF.getBoolean());
|
|
||||||
preference.setTitle("Fake sim card info");
|
|
||||||
preference.setSummary("Bypass regional restriction by fake sim card information.");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final boolean value = (Boolean) newValue;
|
|
||||||
SettingsEnum.TIK_SIMSPOOF.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Country ISO
|
|
||||||
{
|
|
||||||
EditTextPreference preference = new EditTextPreference(context);
|
|
||||||
simSpoof.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_SIMSPOOF_ISO.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF_ISO.defaultValue);
|
|
||||||
preference.setText(SettingsEnum.TIK_SIMSPOOF_ISO.getString());
|
|
||||||
preference.setTitle("Country ISO");
|
|
||||||
preference.setSummary("us, uk, jp, ...");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final String value = (String) newValue;
|
|
||||||
SettingsEnum.TIK_SIMSPOOF_ISO.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Operator mcc+mnc
|
|
||||||
{
|
|
||||||
EditTextPreference preference = new EditTextPreference(context);
|
|
||||||
simSpoof.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_SIMSPOOF_MCCMNC.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF_MCCMNC.defaultValue);
|
|
||||||
preference.setText(SettingsEnum.TIK_SIMSPOOF_MCCMNC.getString());
|
|
||||||
preference.setTitle("Operator mcc+mnc");
|
|
||||||
preference.setSummary("mcc+mnc");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final String value = (String) newValue;
|
|
||||||
SettingsEnum.TIK_SIMSPOOF_MCCMNC.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//Operator name
|
|
||||||
{
|
|
||||||
EditTextPreference preference = new EditTextPreference(context);
|
|
||||||
simSpoof.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_SIMSPOOF_OP_NAME.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_SIMSPOOF_OP_NAME.defaultValue);
|
|
||||||
preference.setText(SettingsEnum.TIK_SIMSPOOF_OP_NAME.getString());
|
|
||||||
preference.setTitle("Operator name");
|
|
||||||
preference.setSummary("Name of the operator");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final String value = (String) newValue;
|
|
||||||
SettingsEnum.TIK_SIMSPOOF_OP_NAME.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Integration
|
|
||||||
PreferenceCategory integration = new PreferenceCategory(context);
|
|
||||||
integration.setTitle("Integration");
|
|
||||||
preferenceScreen.addPreference(integration);
|
|
||||||
//Enable DebugLog toggle
|
|
||||||
{
|
|
||||||
SwitchPreference preference = new SwitchPreference(context);
|
|
||||||
integration.addPreference(preference);
|
|
||||||
preference.setKey(SettingsEnum.TIK_DEBUG.path);
|
|
||||||
preference.setDefaultValue(SettingsEnum.TIK_DEBUG.defaultValue);
|
|
||||||
preference.setChecked(SettingsEnum.TIK_DEBUG.getBoolean());
|
|
||||||
preference.setTitle("Enable debug log");
|
|
||||||
preference.setSummary("Show integration debug log.");
|
|
||||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final boolean value = (Boolean) newValue;
|
|
||||||
SettingsEnum.TIK_DEBUG.saveValue(value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.settingsInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.preference.PreferenceFragment, android.app.Fragment
|
|
||||||
public void onDestroy() {
|
|
||||||
if (this.Registered) {
|
|
||||||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this.listener);
|
|
||||||
this.Registered = false;
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reboot(Activity activity) {
|
|
||||||
int intent;
|
|
||||||
intent = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
|
|
||||||
((AlarmManager) activity.getSystemService(Context.ALARM_SERVICE)).setExact(AlarmManager.ELAPSED_REALTIME, 1500L, PendingIntent.getActivity(activity, 0, new Intent(activity, SplashActivity.class), intent));
|
|
||||||
Process.killProcess(Process.myPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rebootDialog(final Activity activity) {
|
|
||||||
new AlertDialog.Builder(activity).
|
|
||||||
setMessage("Refresh and restart").
|
|
||||||
setPositiveButton("RESTART", (dialog, i) -> reboot(activity))
|
|
||||||
.setNegativeButton("CANCEL", null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,7 +55,7 @@ public class SettingsMenu {
|
|||||||
linearLayout.addView(fragment);
|
linearLayout.addView(fragment);
|
||||||
base.setContentView(linearLayout);
|
base.setContentView(linearLayout);
|
||||||
|
|
||||||
PreferenceFragment preferenceFragment = new ReVancedSettingsFragment();
|
PreferenceFragment preferenceFragment = new ReVancedPreferenceFragment();
|
||||||
base.getFragmentManager().beginTransaction().replace(fragmentId, preferenceFragment).commit();
|
base.getFragmentManager().beginTransaction().replace(fragmentId, preferenceFragment).commit();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package app.revanced.tiktok.settingsmenu;
|
package app.revanced.tiktok.settingsmenu;
|
||||||
|
|
||||||
public class SettingsStatus {
|
public class SettingsStatus {
|
||||||
public static boolean feedFilter = false;
|
public static boolean feedFilterEnabled = false;
|
||||||
public static boolean download = false;
|
public static boolean downloadEnabled = false;
|
||||||
public static boolean simSpoof = false;
|
public static boolean simSpoofEnabled = false;
|
||||||
|
|
||||||
public static void enableFeedFilter() {
|
public static void enableFeedFilter() {
|
||||||
feedFilter = true;
|
feedFilterEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enableDownload() {
|
public static void enableDownload() {
|
||||||
download = true;
|
downloadEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void enableSimSpoof() {
|
public static void enableSimSpoof() {
|
||||||
simSpoof = true;
|
simSpoofEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void load() {
|
public static void load() {
|
||||||
|
@ -7,6 +7,7 @@ import android.os.Environment;
|
|||||||
import android.preference.DialogPreference;
|
import android.preference.DialogPreference;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@ -14,23 +15,48 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
import android.widget.RadioGroup;
|
import android.widget.RadioGroup;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public class DownloadPathPreference extends DialogPreference {
|
public class DownloadPathPreference extends DialogPreference {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final String[] entryValues = {"DCIM", "Movies", "Pictures"};
|
private final String[] entryValues = {"DCIM", "Movies", "Pictures"};
|
||||||
private String value;
|
private String mValue;
|
||||||
|
|
||||||
|
private boolean mValueSet;
|
||||||
private int mediaPathIndex;
|
private int mediaPathIndex;
|
||||||
private String childDownloadPath;
|
private String childDownloadPath;
|
||||||
|
|
||||||
public DownloadPathPreference(Context context) {
|
public DownloadPathPreference(Context context, String title, SettingsEnum setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.setTitle(title);
|
||||||
|
this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.getString());
|
||||||
|
this.setKey(setting.path);
|
||||||
|
this.setValue(setting.getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return this.mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
final boolean changed = !TextUtils.equals(mValue, value);
|
||||||
|
if (changed || !mValueSet) {
|
||||||
|
mValue = value;
|
||||||
|
mValueSet = true;
|
||||||
|
persistString(value);
|
||||||
|
if (changed) {
|
||||||
|
notifyDependencyChange(shouldDisableDependents());
|
||||||
|
notifyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View onCreateDialogView() {
|
protected View onCreateDialogView() {
|
||||||
String currentMedia = getValue().split("/")[0];
|
String currentMedia = getValue().split("/")[0];
|
||||||
childDownloadPath = getValue().substring(getValue().indexOf("/") + 1);
|
childDownloadPath = getValue().substring(getValue().indexOf("/") + 1);
|
||||||
|
|
||||||
mediaPathIndex = findIndexOf(currentMedia);
|
mediaPathIndex = findIndexOf(currentMedia);
|
||||||
|
|
||||||
LinearLayout dialogView = new LinearLayout(context);
|
LinearLayout dialogView = new LinearLayout(context);
|
||||||
@ -84,10 +110,8 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
protected void onDialogClosed(boolean positiveResult) {
|
protected void onDialogClosed(boolean positiveResult) {
|
||||||
if (positiveResult && mediaPathIndex >= 0) {
|
if (positiveResult && mediaPathIndex >= 0) {
|
||||||
String newValue = entryValues[mediaPathIndex] + "/" + childDownloadPath;
|
String newValue = entryValues[mediaPathIndex] + "/" + childDownloadPath;
|
||||||
if (callChangeListener(newValue)) {
|
setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + newValue);
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + newValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +121,4 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(String value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class InputTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
|
public InputTextPreference(Context context, String title, String summary, SettingsEnum setting) {
|
||||||
|
super(context);
|
||||||
|
this.setTitle(title);
|
||||||
|
this.setSummary(summary);
|
||||||
|
this.setKey(setting.path);
|
||||||
|
this.setText(setting.getString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.preference.DialogPreference;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class RangeValuePreference extends DialogPreference {
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private String minValue;
|
||||||
|
|
||||||
|
private String maxValue;
|
||||||
|
|
||||||
|
private String mValue;
|
||||||
|
|
||||||
|
private boolean mValueSet;
|
||||||
|
|
||||||
|
public RangeValuePreference(Context context, String title, String summary, SettingsEnum setting) {
|
||||||
|
super(context);
|
||||||
|
this.context = context;
|
||||||
|
setTitle(title);
|
||||||
|
setSummary(summary);
|
||||||
|
setKey(setting.path);
|
||||||
|
setValue(setting.getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
final boolean changed = !TextUtils.equals(mValue, value);
|
||||||
|
if (changed || !mValueSet) {
|
||||||
|
mValue = value;
|
||||||
|
mValueSet = true;
|
||||||
|
persistString(value);
|
||||||
|
if (changed) {
|
||||||
|
notifyDependencyChange(shouldDisableDependents());
|
||||||
|
notifyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View onCreateDialogView() {
|
||||||
|
minValue = getValue().split("-")[0];
|
||||||
|
maxValue = getValue().split("-")[1];
|
||||||
|
LinearLayout dialogView = new LinearLayout(context);
|
||||||
|
dialogView.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
LinearLayout minView = new LinearLayout(context);
|
||||||
|
minView.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
TextView min = new TextView(context);
|
||||||
|
min.setText("Min: ");
|
||||||
|
minView.addView(min);
|
||||||
|
EditText minEditText = new EditText(context);
|
||||||
|
minEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
|
minEditText.setText(minValue);
|
||||||
|
minView.addView(minEditText);
|
||||||
|
dialogView.addView(minView);
|
||||||
|
LinearLayout maxView = new LinearLayout(context);
|
||||||
|
maxView.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
TextView max = new TextView(context);
|
||||||
|
max.setText("Max: ");
|
||||||
|
maxView.addView(max);
|
||||||
|
EditText maxEditText = new EditText(context);
|
||||||
|
maxEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
|
maxEditText.setText(maxValue);
|
||||||
|
maxView.addView(maxEditText);
|
||||||
|
dialogView.addView(maxView);
|
||||||
|
minEditText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable editable) {
|
||||||
|
minValue = editable.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
maxEditText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable editable) {
|
||||||
|
maxValue = editable.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dialogView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
|
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> this.onClick(dialog, DialogInterface.BUTTON_POSITIVE));
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDialogClosed(boolean positiveResult) {
|
||||||
|
if (positiveResult) {
|
||||||
|
String newValue = minValue + "-" + maxValue;
|
||||||
|
setValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class TogglePreference extends SwitchPreference {
|
||||||
|
public TogglePreference(Context context, String title, String summary, SettingsEnum setting) {
|
||||||
|
super(context);
|
||||||
|
this.setTitle(title);
|
||||||
|
this.setSummary(summary);
|
||||||
|
this.setKey(setting.path);
|
||||||
|
this.setChecked(setting.getBoolean());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference.categories;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public abstract class ConditionalPreferenceCategory extends PreferenceCategory {
|
||||||
|
public ConditionalPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
if (getSettingsStatus()) {
|
||||||
|
screen.addPreference(this);
|
||||||
|
addPreferences(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean getSettingsStatus();
|
||||||
|
|
||||||
|
public abstract void addPreferences(Context context);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference.categories;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
|
public DownloadsPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||||
|
super(context, screen);
|
||||||
|
setTitle("Downloads");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getSettingsStatus() {
|
||||||
|
return SettingsStatus.downloadEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreferences(Context context) {
|
||||||
|
addPreference(new DownloadPathPreference(
|
||||||
|
context,
|
||||||
|
"Download path",
|
||||||
|
SettingsEnum.DOWNLOAD_PATH
|
||||||
|
));
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Remove watermark", "",
|
||||||
|
SettingsEnum.DOWNLOAD_WATERMARK
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference.categories;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.RangeValuePreference;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
|
public FeedFilterPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||||
|
super(context, screen);
|
||||||
|
setTitle("Feed filter");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getSettingsStatus() {
|
||||||
|
return SettingsStatus.feedFilterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreferences(Context context) {
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Remove feed ads", "Remove ads from feed.",
|
||||||
|
SettingsEnum.REMOVE_ADS
|
||||||
|
));
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Hide livestreams", "Hide livestreams from feed.",
|
||||||
|
SettingsEnum.HIDE_LIVE
|
||||||
|
));
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Hide story", "Hide story from feed.",
|
||||||
|
SettingsEnum.HIDE_STORY
|
||||||
|
));
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Hide image video", "Hide image video from feed.",
|
||||||
|
SettingsEnum.HIDE_IMAGE
|
||||||
|
));
|
||||||
|
addPreference(new RangeValuePreference(
|
||||||
|
context,
|
||||||
|
"Min/Max views", "The minimum or maximum views of a video to show.",
|
||||||
|
SettingsEnum.MIN_MAX_VIEWS
|
||||||
|
));
|
||||||
|
addPreference(new RangeValuePreference(
|
||||||
|
context,
|
||||||
|
"Min/Max likes", "The minimum or maximum likes of a video to show.",
|
||||||
|
SettingsEnum.MIN_MAX_LIKES
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference.categories;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
|
public IntegrationsPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||||
|
super(context, screen);
|
||||||
|
setTitle("Integrations");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getSettingsStatus() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreferences(Context context) {
|
||||||
|
addPreference(new TogglePreference(context,
|
||||||
|
"Enable debug log",
|
||||||
|
"Show integration debug log.",
|
||||||
|
SettingsEnum.DEBUG
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package app.revanced.tiktok.settingsmenu.preference.categories;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.InputTextPreference;
|
||||||
|
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
|
public SimSpoofPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||||
|
super(context, screen);
|
||||||
|
setTitle("Bypass regional restriction");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getSettingsStatus() {
|
||||||
|
return SettingsStatus.simSpoofEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPreferences(Context context) {
|
||||||
|
addPreference(new TogglePreference(
|
||||||
|
context,
|
||||||
|
"Fake sim card info",
|
||||||
|
"Bypass regional restriction by fake sim card information.",
|
||||||
|
SettingsEnum.SIM_SPOOF
|
||||||
|
));
|
||||||
|
addPreference(new InputTextPreference(
|
||||||
|
context,
|
||||||
|
"Country ISO", "us, uk, jp, ...",
|
||||||
|
SettingsEnum.SIM_SPOOF_ISO
|
||||||
|
));
|
||||||
|
addPreference(new InputTextPreference(
|
||||||
|
context,
|
||||||
|
"Operator mcc+mnc", "mcc+mnc",
|
||||||
|
SettingsEnum.SIMSPOOF_MCCMNC
|
||||||
|
));
|
||||||
|
addPreference(new InputTextPreference(
|
||||||
|
context,
|
||||||
|
"Operator name", "Name of the operator.",
|
||||||
|
SettingsEnum.SIMSPOOF_OP_NAME
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,11 @@ import app.revanced.tiktok.settings.SettingsEnum;
|
|||||||
|
|
||||||
public class SpoofSimPatch {
|
public class SpoofSimPatch {
|
||||||
public static boolean isEnable() {
|
public static boolean isEnable() {
|
||||||
return SettingsEnum.TIK_SIMSPOOF.getBoolean();
|
return SettingsEnum.SIM_SPOOF.getBoolean();
|
||||||
}
|
}
|
||||||
public static String getCountryIso(String value) {
|
public static String getCountryIso(String value) {
|
||||||
if (isEnable()) {
|
if (isEnable()) {
|
||||||
return SettingsEnum.TIK_SIMSPOOF_ISO.getString();
|
return SettingsEnum.SIM_SPOOF_ISO.getString();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -16,14 +16,14 @@ public class SpoofSimPatch {
|
|||||||
}
|
}
|
||||||
public static String getOperator(String value) {
|
public static String getOperator(String value) {
|
||||||
if (isEnable()) {
|
if (isEnable()) {
|
||||||
return SettingsEnum.TIK_SIMSPOOF_MCCMNC.getString();
|
return SettingsEnum.SIMSPOOF_MCCMNC.getString();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static String getOperatorName(String value) {
|
public static String getOperatorName(String value) {
|
||||||
if (isEnable()) {
|
if (isEnable()) {
|
||||||
return SettingsEnum.TIK_SIMSPOOF_OP_NAME.getString();
|
return SettingsEnum.SIMSPOOF_OP_NAME.getString();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import app.revanced.tiktok.settings.SettingsEnum;
|
|||||||
public class LogHelper {
|
public class LogHelper {
|
||||||
|
|
||||||
public static void debug(Class clazz, String message) {
|
public static void debug(Class clazz, String message) {
|
||||||
if (SettingsEnum.TIK_DEBUG.getBoolean()) {
|
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||||
Log.d("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message);
|
Log.d("revanced: " + (clazz != null ? clazz.getSimpleName() : ""), message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package app.revanced.tiktok.utils;
|
package app.revanced.tiktok.utils;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import app.revanced.tiktok.settings.SettingsEnum;
|
||||||
|
|
||||||
public class ReVancedUtils {
|
public class ReVancedUtils {
|
||||||
|
|
||||||
//Used by TiktokIntegrations patch
|
@SuppressLint("StaticFieldLeak")
|
||||||
public static Context context;
|
public static Context context;
|
||||||
|
|
||||||
//Used by TiktokIntegrations patch
|
|
||||||
public static Context getAppContext() {
|
public static Context getAppContext() {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
return context;
|
return context;
|
||||||
@ -15,4 +16,23 @@ public class ReVancedUtils {
|
|||||||
LogHelper.printException(ReVancedUtils.class, "Context is null!");
|
LogHelper.printException(ReVancedUtils.class, "Context is null!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long[] parseMinMax(SettingsEnum setting) {
|
||||||
|
if (setting.returnType == SettingsEnum.ReturnType.STRING) {
|
||||||
|
final String[] minMax = setting.getString().split("-");
|
||||||
|
|
||||||
|
if (minMax.length == 2)
|
||||||
|
try {
|
||||||
|
final long min = Long.parseLong(minMax[0]);
|
||||||
|
final long max = Long.parseLong(minMax[1]);
|
||||||
|
|
||||||
|
if (min <= max && min >= 0) return new long[]{min, max};
|
||||||
|
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.saveValue("0-" + Long.MAX_VALUE);
|
||||||
|
return new long[]{0L, Long.MAX_VALUE};
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,16 +3,34 @@ package com.ss.android.ugc.aweme.feed.model;
|
|||||||
//Dummy class
|
//Dummy class
|
||||||
public class Aweme {
|
public class Aweme {
|
||||||
public boolean isAd() {
|
public boolean isAd() {
|
||||||
return true;
|
throw new UnsupportedOperationException("Stub");
|
||||||
}
|
|
||||||
public boolean isLive() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public boolean isLiveReplay() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public boolean isWithPromotionalMusic() {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLive() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLiveReplay() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWithPromotionalMusic() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getIsTikTokStory() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImage() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPhotoMode() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
public AwemeStatistics getStatistics() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.ss.android.ugc.aweme.feed.model;
|
||||||
|
|
||||||
|
public class AwemeStatistics {
|
||||||
|
public long getPlayCount() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
public long getDiggCount() {
|
||||||
|
throw new UnsupportedOperationException("Stub");
|
||||||
|
}
|
||||||
|
}
|
@ -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.114.0
|
version = 0.115.0-dev.7
|
||||||
|
Loading…
Reference in New Issue
Block a user