robust implementation of feed filter + bloat filter

This commit is contained in:
lbux 2026-02-11 20:18:22 -08:00
parent ff16079369
commit c17c4e9050
17 changed files with 209 additions and 128 deletions

View file

@ -2,7 +2,6 @@ package app.revanced.extension.tiktok.feedfilter;
import app.revanced.extension.tiktok.settings.Settings;
import com.ss.android.ugc.aweme.feed.model.Aweme;
import com.ss.android.ugc.aweme.commerce.AwemeCommerceStruct;
public class AdsFilter implements IFilter {
@Override
@ -13,21 +12,29 @@ public class AdsFilter implements IFilter {
@Override
public boolean getFiltered(Aweme item) {
try {
// Standard Ads & Promotional Music
if (item.isAd() || item.isWithPromotionalMusic()) {
return true;
}
if (item == null) return false;
// Paid Partnerships (Branded Content)
if (item.mCommerceVideoAuthInfo != null) {
if (item.mCommerceVideoAuthInfo.isBrandedContent()) {
return true;
}
}
} catch (Throwable t) {
return false;
// TikTok's Internal Commercial Types
// Verified in AwemeExtKt: 1, 29, 30, 32, 33, 201 are commercial
int type = item.getAwemeType();
if (type == 1 || type == 29 || type == 30 || type == 32 || type == 33 || type == 201) {
return true;
}
// Ad Flags (Hard and Soft/Sponsored)
if (item.isAd || item.isSoftAd || item.awemeRawAd != null) {
return true;
}
// Music Marketing
if (item.isWithPromotionalMusic()) return true;
if (item.mCommerceVideoAuthInfo != null) {
// PseudoAds (Spark Ads) and Branded Content
return item.mCommerceVideoAuthInfo.isBrandedContent() ||
item.mCommerceVideoAuthInfo.isPseudoAd();
}
return false;
}
}

View file

@ -0,0 +1,28 @@
package app.revanced.extension.tiktok.feedfilter;
import com.ss.android.ugc.aweme.feed.model.Aweme;
public class BloatFilter implements IFilter {
@Override
public boolean getEnabled() {
return true;
}
@Override
public boolean getFiltered(Aweme item) {
if (item == null) return false;
// Full screen promos
if (item.isReferralFakeAweme || item.isRecBigCardFakeAweme) {
return true;
}
// System cards (non video interrupts)
if (item.awemeType == 104 || item.awemeType == 105) return true;
// Accounts to follow recs and overlays
if (item.recommendCardType != 0) return true;
return false;
}
}

View file

@ -1,7 +1,5 @@
package app.revanced.extension.tiktok.feedfilter;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
import com.ss.android.ugc.aweme.feed.model.Aweme;
import com.ss.android.ugc.aweme.feed.model.FeedItemList;
import com.ss.android.ugc.aweme.follow.presenter.FollowFeedList;
@ -10,73 +8,53 @@ import java.util.Iterator;
import java.util.List;
public final class FeedItemsFilter {
public static void filter(FeedItemList feedItemList) {
boolean verbose = BaseSettings.DEBUG.get();
if (feedItemList == null || feedItemList.items == null) return;
filterFeedList("FeedItemList", feedItemList.items, item -> item, verbose);
private static final IFilter[] FILTERS = new IFilter[] {
new AdsFilter(),
new LiveFilter(),
new ShopFilter(),
new StoryFilter(),
new ImageVideoFilter(),
new BloatFilter()
};
public static void filter(FeedItemList feedItemList) {
if (feedItemList == null || feedItemList.items == null) return;
filterFeedList(feedItemList.items, item -> item);
}
public static void filter(FollowFeedList followFeedList) {
boolean verbose = BaseSettings.DEBUG.get();
if (followFeedList == null || followFeedList.mItems == null) return;
filterFeedList("FollowFeedList", followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null, verbose);
filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null);
}
private static <T> void filterFeedList(
String source,
List<T> list,
AwemeExtractor<T> extractor,
boolean verbose
AwemeExtractor<T> extractor
) {
if (list == null) return;
int initialSize = list.size();
int removed = 0;
Iterator<T> iterator = list.iterator();
while (iterator.hasNext()) {
T container = iterator.next();
Aweme item = extractor.extract(container);
if (item == null) continue;
String reason = getInternalizedFilterReason(item);
if (reason != null) {
removed++;
if (shouldFilter(item)) {
iterator.remove();
if (verbose) {
logItem(item, reason);
}
}
}
if (verbose && removed > 0) {
int finalRemoved = removed;
Logger.printInfo(() -> "[ReVanced FeedFilter] " + source + ": removed " + finalRemoved + " items.");
}
}
private static String getInternalizedFilterReason(Aweme item) {
if (item.isAd() || item.isWithPromotionalMusic()) {
return "AdsFilter";
private static boolean shouldFilter(Aweme item) {
for (IFilter filter : FILTERS) {
if (filter.getEnabled() && filter.getFiltered(item)) {
return true;
}
}
if (item.getLiveId() != 0 || item.getLiveType() != null || item.isLiveReplay()) {
return "LiveFilter";
}
String shareUrl = item.getShareUrl();
if (shareUrl != null && shareUrl.contains("placeholder_product_id")) {
return "ShopFilter";
}
return null;
}
private static void logItem(Aweme item, String reason) {
Logger.printInfo(() -> "[ReVanced FeedFilter] FILTERED: aid=" + item.getAid() + " Reason=" + reason);
return false;
}
@FunctionalInterface

View file

@ -11,9 +11,17 @@ public class ImageVideoFilter implements IFilter {
@Override
public boolean getFiltered(Aweme item) {
if (item == null) return false;
int type = item.getAwemeType();
// 2 = Standard Image, 150 = Photo Mode, 160 = Text Mode
if (type == 2 || type == 150 || type == 160) {
return true;
}
// Fallback checks
var imageInfos = item.getImageInfos();
boolean isImage = imageInfos != null && !imageInfos.isEmpty();
boolean isPhotoMode = item.getPhotoModeImageInfo() != null || item.getPhotoModeTextInfo() != null;
return isImage || isPhotoMode;
return imageInfos != null && !imageInfos.isEmpty();
}
}

View file

@ -24,7 +24,10 @@ public final class LikeCountFilter implements IFilter {
@Override
public boolean getFiltered(Aweme item) {
AwemeStatistics statistics = item.getStatistics();
AwemeStatistics statistics = item.statistics;
if (statistics == null) statistics = item.getStatistics();
if (statistics == null) return false;
long likeCount = statistics.getDiggCount();

View file

@ -6,12 +6,19 @@ import com.ss.android.ugc.aweme.feed.model.Aweme;
public class LiveFilter implements IFilter {
@Override
public boolean getEnabled() {
// HARDCODED: Always filter live streams
return true;
}
@Override
public boolean getFiltered(Aweme item) {
return item.getLiveId() != 0 || item.isLiveReplay() || item.getLiveType() != null;
if (item == null) return false;
// awemeType 101 is the 'isLive' check in code
if (item.getAwemeType() == 101 || item.getRoom() != null) {
return true;
}
// Fallbacks
return item.isLiveReplay() || item.getLiveId() != 0 || item.getLiveType() != null;
}
}

View file

@ -4,7 +4,6 @@ import app.revanced.extension.tiktok.settings.Settings;
import com.ss.android.ugc.aweme.feed.model.Aweme;
public class ShopFilter implements IFilter {
private static final String SHOP_INFO = "placeholder_product_id";
@Override
public boolean getEnabled() {
return true;
@ -13,7 +12,25 @@ public class ShopFilter implements IFilter {
@Override
public boolean getFiltered(Aweme item) {
if (item == null) return false;
// Attached Products (TikTok Shop)
if (item.productsInfo != null && !item.productsInfo.isEmpty()) {
return true;
}
// Simple Promotions (Banner links)
if (item.simplePromotions != null && !item.simplePromotions.isEmpty()) {
return true;
}
// Shop Ads
if (item.shopAdStruct != null) {
return true;
}
// Fallback (URL check)
String shareUrl = item.getShareUrl();
return shareUrl != null && shareUrl.contains(SHOP_INFO);
return shareUrl != null && shareUrl.contains("placeholder_product_id");
}
}

View file

@ -9,8 +9,17 @@ public class StoryFilter implements IFilter {
return Settings.HIDE_STORY.get();
}
@Override
public boolean getFiltered(Aweme item) {
return item.getIsTikTokStory();
if (item == null) return false;
if (item.isTikTokStory) return true;
// Type 40 = Standard Story, 11 = Legacy/Region Story
int type = item.getAwemeType();
if (type == 40 || type == 11 || item.isTikTokStory) {
return true;
}
return false;
}
}

View file

@ -23,7 +23,11 @@ public class ViewCountFilter implements IFilter {
@Override
public boolean getFiltered(Aweme item) {
AwemeStatistics statistics = item.getStatistics();
AwemeStatistics statistics = item.statistics;
// Fallback to getter if field is null
if (statistics == null) statistics = item.getStatistics();
if (statistics == null) return false;
long playCount = statistics.getPlayCount();

View file

@ -1,16 +1,17 @@
package com.ss.android.ugc.aweme.commerce;
import java.io.Serializable;
public class AwemeCommerceStruct implements Serializable {
public long brandedContentType;
public long brandOrganicType;
import com.ss.android.ugc.aweme.feed.model.AwemeRawAd;
public class AwemeCommerceStruct {
public boolean isBrandedContent() {
return this.brandedContentType > 0;
throw new UnsupportedOperationException("Stub");
}
public boolean isBrandOrganicContent() {
return this.brandOrganicType > 0;
public boolean isPseudoAd() {
throw new UnsupportedOperationException("Stub");
}
public AwemeRawAd getPseudoAdData() {
throw new UnsupportedOperationException("Stub");
}
}

View file

@ -0,0 +1,4 @@
package com.ss.android.ugc.aweme.commerce.model;
public class ShopAdStruct {
}

View file

@ -0,0 +1,4 @@
package com.ss.android.ugc.aweme.commerce.model;
public class SimplePromotion {
}

View file

@ -1,61 +1,57 @@
package com.ss.android.ugc.aweme.feed.model;
import com.ss.android.ugc.aweme.commerce.AwemeCommerceStruct;
import com.ss.android.ugc.aweme.commerce.model.ShopAdStruct;
import com.ss.android.ugc.aweme.commerce.model.SimplePromotion;
import com.ss.android.ugc.aweme.search.ecom.data.Product;
import com.ss.android.ugc.aweme.commerce.AwemeCommerceStruct;
import java.util.List;
public class Aweme {
// Internal Feed Type Identifiers
public int awemeType;
public int adLinkType;
// Live Stream Data
public RoomStruct room;
// Monetization & Sponsored Traffic
public boolean isAd;
public boolean isSoftAd;
public AwemeRawAd awemeRawAd;
public AwemeCommerceStruct mCommerceVideoAuthInfo;
public String getAid() {
throw new UnsupportedOperationException("Stub");
}
// E-Commerce / Shop Data
public List<Object> productsInfo;
public List<Object> simplePromotions;
public ShopAdStruct shopAdStruct;
// Non-Video Feed Injections (Fake Awemes)
public boolean isReferralFakeAweme;
public boolean isRecBigCardFakeAweme;
public boolean isAd() {
throw new UnsupportedOperationException("Stub");
}
// Social & Follow Recommendations
public int recommendCardType;
public List<Object> familiarRecommendUser;
public boolean isLiveReplay() {
throw new UnsupportedOperationException("Stub");
}
// Content Engagement Statistics
public AwemeStatistics statistics;
public long getLiveId() {
throw new UnsupportedOperationException("Stub");
}
// Story Metadata
public boolean isTikTokStory;
public String getLiveType() {
throw new UnsupportedOperationException("Stub");
}
public boolean isWithPromotionalMusic() {
throw new UnsupportedOperationException("Stub");
}
public boolean getIsTikTokStory() {
throw new UnsupportedOperationException("Stub");
}
public List getImageInfos() {
throw new UnsupportedOperationException("Stub");
}
public PhotoModeImageInfo getPhotoModeImageInfo() {
throw new UnsupportedOperationException("Stub");
}
public PhotoModeTextInfo getPhotoModeTextInfo() {
throw new UnsupportedOperationException("Stub");
}
public AwemeStatistics getStatistics() {
throw new UnsupportedOperationException("Stub");
}
public String getShareUrl() {
throw new UnsupportedOperationException("Stub");
}
public AwemeCommerceStruct getCommerceAndAdSettingsStruct() {
throw new UnsupportedOperationException("Stub");
}
public int getAwemeType() { throw new UnsupportedOperationException("Stub"); }
public RoomStruct getRoom() { throw new UnsupportedOperationException("Stub"); }
public boolean isAd() { throw new UnsupportedOperationException("Stub"); }
public boolean isSoftAd() { throw new UnsupportedOperationException("Stub"); }
public AwemeStatistics getStatistics() { throw new UnsupportedOperationException("Stub"); }
// Stub methods for legacy compatibility
public String getAid() { throw new UnsupportedOperationException("Stub"); }
public boolean isLiveReplay() { throw new UnsupportedOperationException("Stub"); }
public long getLiveId() { throw new UnsupportedOperationException("Stub"); }
public String getLiveType() { throw new UnsupportedOperationException("Stub"); }
public boolean isWithPromotionalMusic() { throw new UnsupportedOperationException("Stub"); }
public String getShareUrl() { throw new UnsupportedOperationException("Stub"); }
public List getImageInfos() { throw new UnsupportedOperationException("Stub"); }
}

View file

@ -0,0 +1,4 @@
package com.ss.android.ugc.aweme.feed.model;
public class AwemeRawAd {
}

View file

@ -1,10 +1,13 @@
package com.ss.android.ugc.aweme.feed.model;
public class AwemeStatistics {
// Used by ViewCountFilter
public long getPlayCount() {
throw new UnsupportedOperationException("Stub");
}
public long getDiggCount() {
throw new UnsupportedOperationException("Stub");
}
}
}

View file

@ -0,0 +1,4 @@
package com.ss.android.ugc.aweme.feed.model;
public class RoomStruct {
}

View file

@ -0,0 +1,4 @@
package com.ss.android.ugc.aweme.search.ecom.data;
public class Product {
}