Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ boolean isFiltered(ContextInterface contextInterface,
}

// Check buffer if specified.
if (custom.bufferSearch != null && !custom.bufferSearch.matches(buffer)) {
return false;
}

return true; // All custom filter conditions passed.
return custom.bufferSearch == null || custom.bufferSearch.matches(buffer); // All custom filter conditions passed.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,17 @@
@SuppressWarnings("unused")
public final class LithoFilterPatch {
/**
* Simple wrapper to pass the litho parameters through the prefix search.
*/
private static final class LithoFilterParameters {
final ContextInterface contextInterface;
final String identifier;
final String path;
final String accessibility;
final byte[] buffer;

LithoFilterParameters(ContextInterface contextInterface, String identifier,
String path, String accessibility, byte[] buffer) {
this.contextInterface = contextInterface;
this.identifier = identifier;
this.path = path;
this.accessibility = accessibility;
this.buffer = buffer;
}
* Simple wrapper to pass the litho parameters through the prefix search.
*/
private record LithoFilterParameters(ContextInterface contextInterface, String identifier,
String path, String accessibility, byte[] buffer) {

@NonNull
@Override
public String toString() {
// Estimate the percentage of the buffer that are Strings.
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
builder.append( "ID: ");
builder.append("ID: ");
builder.append(identifier);
if (!accessibility.isEmpty()) {
// AccessibilityId and AccessibilityText are pieces of BufferStrings.
Expand Down Expand Up @@ -233,6 +220,7 @@ public static boolean isFiltered(ContextInterface contextInterface, @Nullable by
try {
String identifier = contextInterface.patch_getIdentifier();
StringBuilder pathBuilder = contextInterface.patch_getPathBuilder();
//noinspection SizeReplaceableByIsEmpty
if (identifier.isEmpty() || pathBuilder.length() == 0) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* Original hard forked code:
* https://github.com/ReVanced/revanced-patches/commit/724e6d61b2ecd868c1a9a37d465a688e83a74799
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to Morphe contributions.
*/

package app.morphe.extension.youtube.patches.spans;

import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.text.style.RelativeSizeSpan;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* Filters litho based components.
* <p>
* All callbacks must be registered before the constructor completes.
*/
abstract class Filter {
Comment thread
ILoveOpenSourceApplications marked this conversation as resolved.
Outdated
private static final RelativeSizeSpan relativeSizeSpanDummy = new RelativeSizeSpan(0f);
private static final Drawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
private static final ImageSpan imageSpanDummy = new ImageSpan(transparentDrawable);

/**
* Path callbacks. Do not add to this instance,
* and instead use {@link #addCallbacks(StringFilterGroup...)}.
*/
protected final List<StringFilterGroup> callbacks = new ArrayList<>();

/**
* Adds callbacks to {@link #skip(String, SpannableString, Object, int, int, int, boolean, SpanType, StringFilterGroup)}
* if any of the groups are found.
*/
protected final void addCallbacks(StringFilterGroup... groups) {
callbacks.addAll(Arrays.asList(groups));
}

protected final void hideSpan(SpannableString spannableString, int start, int end, int flags) {
spannableString.setSpan(relativeSizeSpanDummy, start, end, flags);
}

protected final void hideImageSpan(SpannableString spannableString, int start, int end, int flags) {
spannableString.setSpan(imageSpanDummy, start, end, flags);
}

/**
* Called after an enabled filter has been matched.
* Default implementation is to always filter the matched component and log the action.
* Subclasses can perform additional or different checks if needed.
* <p>
* Method is called off the main thread.
*
* @param matchedGroup The actual filter that matched.
*/
boolean skip(String conversionContext, SpannableString spannableString, Object span, int start, int end,
int flags, boolean isWord, SpanType spanType, StringFilterGroup matchedGroup) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* Original hard forked code:
* https://github.com/ReVanced/revanced-patches/commit/724e6d61b2ecd868c1a9a37d465a688e83a74799
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to Morphe contributions.
*/

package app.morphe.extension.youtube.patches.spans;

import androidx.annotation.NonNull;

import app.morphe.extension.shared.settings.BooleanSetting;

abstract class FilterGroup<T> {
final static class FilterGroupResult {
private BooleanSetting setting;
private int matchedIndex;

FilterGroupResult() {
this(null, -1);
}

FilterGroupResult(BooleanSetting setting, int matchedIndex) {
setValues(setting, matchedIndex);
}

public void setValues(BooleanSetting setting, int matchedIndex) {
this.setting = setting;
this.matchedIndex = matchedIndex;
}

public BooleanSetting getSetting() {
return setting;
}

public boolean isFiltered() {
return matchedIndex >= 0;
}
}

protected final BooleanSetting setting;
protected final T[] filters;

@SafeVarargs
public FilterGroup(final BooleanSetting setting, final T... filters) {
this.setting = setting;
this.filters = filters;
if (filters.length == 0) {
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
}
}

public boolean isEnabled() {
return setting == null || setting.get();
}

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

class StringFilterGroup extends FilterGroup<String> {

public StringFilterGroup(final BooleanSetting setting, final String... filters) {
super(setting, filters);
}

@Override
public FilterGroupResult check(final String string) {
int matchedIndex = -1;
if (isEnabled()) {
for (String pattern : filters) {
if (!string.isEmpty()) {
final int indexOf = string.indexOf(pattern);
if (indexOf >= 0) {
matchedIndex = indexOf;
break;
}
}
}
}
return new FilterGroupResult(setting, matchedIndex);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* Original hard forked code:
* https://github.com/ReVanced/revanced-patches/commit/724e6d61b2ecd868c1a9a37d465a688e83a74799
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to Morphe contributions.
*/

package app.morphe.extension.youtube.patches.spans;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.function.Consumer;

import app.morphe.extension.shared.StringTrieSearch;
import app.morphe.extension.shared.TrieSearch;

abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {

private final List<T> filterGroups = new ArrayList<>();
private final TrieSearch<V> search = createSearchGraph();

@SafeVarargs
protected final void addAll(final T... groups) {
filterGroups.addAll(Arrays.asList(groups));

for (T group : groups) {
if (!group.includeInSearch()) {
continue;
}
for (V pattern : group.filters) {
search.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
if (group.isEnabled()) {
FilterGroup.FilterGroupResult result = (FilterGroup.FilterGroupResult) callbackParameter;
result.setValues(group.setting, matchedStartIndex);
return true;
}
return false;
});
}
}
}

@NonNull
@Override
public Iterator<T> iterator() {
return filterGroups.iterator();
}

@Override
public void forEach(@NonNull Consumer<? super T> action) {
filterGroups.forEach(action);
}

@NonNull
@Override
public Spliterator<T> spliterator() {
return filterGroups.spliterator();
}

protected FilterGroup.FilterGroupResult check(V stack) {
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
search.matches(stack, result);
return result;
}

protected abstract TrieSearch<V> createSearchGraph();
}

final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
@Override
protected StringTrieSearch createSearchGraph() {
return new StringTrieSearch();
}
}
Loading
Loading