Skip to content

Improve message filtering#371

Closed
KMohZaid wants to merge 6 commits into
risin42:devfrom
KMohZaid:feature/better-message-filtering
Closed

Improve message filtering#371
KMohZaid wants to merge 6 commits into
risin42:devfrom
KMohZaid:feature/better-message-filtering

Conversation

@KMohZaid
Copy link
Copy Markdown

What is this?

This PR implement a masking feature for message filters, before user can hide messages completely that matches regex flters but now they can mask them with spoiler tag. Not only that but with a different spoiler tag color to identify what filter is hiding it and also know that its not normal telegram spoiler tag.

Additionally, instead of hiding blocked users messages. User can choose to mask them. Similarly for shadow ban list, they can manually change hide to mask and select a color of masking.

Why this?

I have been trying to filter out seeing some particular kind of text but i didnt wanted to hide whole message (what if a word is mentioned in context and filter hides it). So for my use case i worked on this feature

Note

I am not sure how good the code quality is, i am not yet familiar with this codebase and i didn't work on much of android stuff (i did learn basic of android app in past but not much). So for understanding files and getting help with this feature implementation i used chatbot. But i still added some redundant code block (in checkSpoilerMotionEvent method)

Also, sorry if PR is too messy to review (i should have committed step by step)

…iding (also option to only mask matched word)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 16b197d6-9708-4a2f-9396-70cb0b059cf8

📥 Commits

Reviewing files that changed from the base of the PR and between 082ef7d and 438b3ca.

📒 Files selected for processing (2)
  • TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java
  • TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added spoiler masking support for regex filters with customizable mask colors as an alternative to hiding messages
    • Added option to mask blocked user messages instead of hiding them
    • Enhanced visual spoiler rendering with improved masking effects
  • Improvements

    • Extended filter actions to support hide, mask all, or mask matched text modes
    • Updated blocked peer message handling for consistent filtering behavior

Walkthrough

This PR adds Ayu spoiler masking (actions: hide, spoiler-all, spoiler-match) with per-filter colors, integrates it into message layout/rendering and hit-testing, updates AyuFilter to action semantics, and adds UI/config for editing filters, masked blocked-user settings, and per-user color/action controls.

Changes

Ayu Spoiler Masking Implementation

Layer / File(s) Summary
Ayu Spoiler Data Model and Text Layout Support
TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java
MessageObject adds ayuMediaSpoiler, ayuSpoilerText, ayuSpoilerRevealed; TextLayoutBlock gains ayuSpoilersPatchedTextLayout and ayuSpoilerGroups. New AyuSpoilerSpan and helpers build grouped spoiler effects and a patched StaticLayout that masks emoji and applies transparent masks; layout generation and TextLayoutBlocks construction run the Ayu pipeline when applicable.
AyuFilter Action-Based Filtering Model
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/AyuFilter.java
Replaces boolean filtering with an integer filterAction model (constants ACTION_HIDE, ACTION_SPOILER_ALL, ACTION_SPOILER_MATCH). Per-filter spoilerColor persisted, matching APIs added, cache type changed to int, isFiltered() derives from ACTION_HIDE, and custom filtered-user APIs expose action/color and allow setting them. getMaskColor() resolves color from the winning action with overrides for custom/blocked users.
ChatActivity Blocked User Masking
TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
Blocked-peer detection now checks NaConfig.INSTANCE.getMaskBlockedUserMessages(); when enabled, blocked senders are masked (method returns false earlier), and custom-filter checks use AyuFilter.isCustomFilteredPeerHidden().
ChatMessageCell Ayu Spoiler Interaction and Rendering
TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java
Hit-testing uses per-block yOff and checks block.ayuSpoilerGroups before block.spoilers. Ripple region construction, ripple start/end, and reveal flow branch on currentMessageObject.ayuSpoilerText to handle Ayu vs legacy spoilers. New applyAyuFilterSpoiler() applies/clears Ayu mask spans (full-message vs match-only), toggles ayuMediaSpoiler, regenerates layout, and rendering prefers ayuSpoilerGroups when present. Emoji drawing clips out Ayu spoiler regions.
RegexFilterEditActivity Action and Color Selection
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFilterEditActivity.java
Adds filterAction and spoilerColor state, initializes them from constructors/edited filters, adds UI toggles for hide vs spoiler and match-only option, and a color picker; save paths pass filterAction and computed savedColor into AyuFilter add/edit APIs.
RegexFiltersSettingActivity Blocked User Masking Config
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFiltersSettingActivity.java
Adds "mask blocked user messages" toggle and dependent "blocked user mask color" row (visible when masking enabled), click handlers that sync NaConfig/NekoConfig.ignoreBlocked, color picker integration, and a circular color preview via ShapeDrawable/OvalShape.
ShadowBanListActivity Custom Filtered User Action and Color Display
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/ShadowBanListActivity.java
Renders custom-filtered-user subtitles with a colored circular bullet for spoiler-mode entries, and expands the user options menu to choose hide/spoiler and open a color picker; updates AyuFilter and refreshes rows on changes.
Configuration Items and String Resources
TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt, TMessagesProj/src/main/res/values/strings_na.xml
Adds maskBlockedUserMessages (boolean) and blockedUserMaskColor (int, default 0xFF888888) to NaConfig. Adds string resources for spoiler-mask labels, match-only option, color selection, and blocked-user masking.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Improve message filtering' is too vague and generic; it does not convey the specific masking feature or spoiler-tag enhancement that represents the main change. Use a more descriptive title such as 'Add spoiler masking for message filters' or 'Implement masking feature for regex and blocked-user filters'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the masking feature, its motivation, implementation scope (regex filters, blocked users, shadow ban list), and code quality caveats, which aligns well with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.42.3)
TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java
TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9a4744a7-52b2-46cf-9fbb-7cdb46ca2322

📥 Commits

Reviewing files that changed from the base of the PR and between 918dd4e and 16d4a3b.

📒 Files selected for processing (9)
  • TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java
  • TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java
  • TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
  • TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/AyuFilter.java
  • TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFilterEditActivity.java
  • TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFiltersSettingActivity.java
  • TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/ShadowBanListActivity.java
  • TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt
  • TMessagesProj/src/main/res/values/strings_na.xml

Comment thread TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java Outdated
Comment thread TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java Outdated
Comment thread TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java Outdated
Comment thread TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/AyuFilter.java Outdated
Comment thread TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt
Comment thread TMessagesProj/src/main/res/values/strings_na.xml
@KMohZaid
Copy link
Copy Markdown
Author

hmm, i will work on those suggestion when i get time

KMohZaid added 2 commits May 25, 2026 18:41
1. make ayu media and text spoiler mask independent from telegram native
spoiler reveal setting
2. make sure AnimatedEmojiSpans are also handled and masked
3. move ayuClipPath to class field and rewind instead of allocating new
Path each draw frame
4. fix cache issue of blocked user after caching
5. make entire spoiler color row clickable for accessibility
6. change default blocked user mask color from white to gray for
visibility on light backgrounds
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java (1)

8391-8416: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mask Telegram spoiler emoji in the shared patched layout too.

This patched layout is reused for both Ayu masks and native Telegram spoilers, but only the Ayu branch strips self-drawing emoji spans. If a block contains both spoiler types, animated/custom emoji inside the Telegram spoiler range can still draw through because the Telegram branch only adds a transparent foreground span.

💡 Suggested fix
         if (block.textLayout.getText() instanceof Spanned) {
             Spanned sp = (Spanned) block.textLayout.getText();
             TextStyleSpan[] tgSpans = sp.getSpans(0, sp.length(), TextStyleSpan.class);
             for (TextStyleSpan span : tgSpans) {
                 if (span.isSpoiler()) {
                     int start = sp.getSpanStart(span);
                     int end = sp.getSpanEnd(span);
                     if (start >= 0 && end > start && end <= sb.length()) {
+                        for (Emoji.EmojiSpan e : sb.getSpans(start, end, Emoji.EmojiSpan.class)) {
+                            final Emoji.EmojiSpan captured = e;
+                            sb.setSpan(new ReplacementSpan() {
+                                `@Override`
+                                public int getSize(`@NonNull` Paint paint, CharSequence text, int s, int e2, `@Nullable` Paint.FontMetricsInt fm) {
+                                    return captured.getSize(paint, text, s, e2, fm);
+                                }
+                                `@Override`
+                                public void draw(`@NonNull` Canvas canvas, CharSequence text, int s, int e2, float x, int top, int y, int bottom, `@NonNull` Paint paint) {
+                                }
+                            }, sb.getSpanStart(e), sb.getSpanEnd(e), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                            sb.removeSpan(e);
+                        }
+                        for (AnimatedEmojiSpan e : sb.getSpans(start, end, AnimatedEmojiSpan.class)) {
+                            final AnimatedEmojiSpan captured = e;
+                            sb.setSpan(new ReplacementSpan() {
+                                `@Override`
+                                public int getSize(`@NonNull` Paint paint, CharSequence text, int s, int e2, `@Nullable` Paint.FontMetricsInt fm) {
+                                    return captured.getSize(paint, text, s, e2, fm);
+                                }
+                                `@Override`
+                                public void draw(`@NonNull` Canvas canvas, CharSequence text, int s, int e2, float x, int top, int y, int bottom, `@NonNull` Paint paint) {
+                                }
+                            }, sb.getSpanStart(e), sb.getSpanEnd(e), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                            sb.removeSpan(e);
+                        }
                         sb.setSpan(new ForegroundColorSpan(Color.TRANSPARENT), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                     }
                 }
             }
         }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94aae10a-4811-4400-b225-5a0fc0e222ec

📥 Commits

Reviewing files that changed from the base of the PR and between 16d4a3b and e9996c8.

📒 Files selected for processing (5)
  • TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java
  • TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java
  • TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/AyuFilter.java
  • TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFilterEditActivity.java
  • TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b01b4f3-c1cf-445a-9889-c9ece3e5b108

📥 Commits

Reviewing files that changed from the base of the PR and between e9996c8 and 648d4d2.

📒 Files selected for processing (1)
  • TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java

Comment thread TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java (3)

8411-8417: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Define suppressSelfDrawingSpans(...) before calling it.

Line 8416 invokes suppressSelfDrawingSpans(sb, start, end), but this helper is not declared anywhere in MessageObject.java. As written, this file will not compile.


271-273: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve ayuSpoilerRevealed across MessageObject replacement.

The new reveal flag is transient, but unlike isSpoilersRevealed it is never copied in copyStableParams(). Any refresh that swaps in a new MessageObject will re-mask content the user already revealed.

💡 Suggested fix
         isSpoilersRevealed = old.isSpoilersRevealed;
+        ayuSpoilerRevealed = old.ayuSpoilerRevealed;
         messageOwner.replyStory = old.messageOwner.replyStory;
         if (messageOwner.media != null && old.messageOwner.media != null) {
             messageOwner.media.storyItem = old.messageOwner.media.storyItem;
         }
         if (isSpoilersRevealed && textLayoutBlocks != null) {
             for (TextLayoutBlock block : textLayoutBlocks) {
                 block.spoilers.clear();
             }
         }
+        if (ayuSpoilerRevealed && textLayoutBlocks != null) {
+            for (TextLayoutBlock block : textLayoutBlocks) {
+                block.ayuSpoilerGroups = null;
+                block.ayuSpoilersPatchedTextLayout.set(null);
+            }
+        }

8434-8434: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix Ayu patched spoiler layout caching to use ayuSpoilersPatchedTextLayout

  • MessageObject.ayuPreBuildPatchedLayout() stores into block.spoilersPatchedTextLayout (8434), never into block.ayuSpoilersPatchedTextLayout (declared but not written).
  • ChatMessageCell renders Ayu spoilers using block.spoilersPatchedTextLayout (e.g., ~17230), so block.ayuSpoilersPatchedTextLayout is effectively unused.
  • Ayu spoiler invalidation clears only block.spoilersPatchedTextLayout (8857-8859, 9319-9322) and block.ayuSpoilerGroups, leaving block.ayuSpoilersPatchedTextLayout stale/not cleared.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c0a1b82-8733-4a61-a31b-3bc428d3bd37

📥 Commits

Reviewing files that changed from the base of the PR and between 648d4d2 and 082ef7d.

📒 Files selected for processing (1)
  • TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java

@KMohZaid
Copy link
Copy Markdown
Author

this coderabbit ...

@KMohZaid KMohZaid marked this pull request as draft May 27, 2026 22:51
@KMohZaid KMohZaid marked this pull request as ready for review May 28, 2026 01:08
@KMohZaid
Copy link
Copy Markdown
Author

@coderabbitai Do a proper review for once ok, include all suggestions at once if there is any left

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh pr view 371 --repo risin42/NagramX --json files,number,title

Length of output: 1408


🏁 Script executed:

#!/bin/bash
gh pr diff 371 --repo risin42/NagramX 2>/dev/null | head -4000

Length of output: 88389



@KMohZaid Here is a comprehensive review covering all findings across every file in the PR.


🐛 Critical / Correctness

1. canvas.clipOutPath() requires API 26+ — no version guard

ChatMessageCell.javadrawAnimatedEmojiMessageText

// current code — crashes on API < 26
ayuClipSave = canvas.save();
canvas.clipOutPath(ayuClipPath);

Canvas.clipOutPath() was added in API 26. NagramX's minSdk is lower than 26. Add a guard:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    ayuClipSave = canvas.save();
    canvas.clipOutPath(ayuClipPath);
}

2. getMatchingFilterModels runs all regexes every call with no cache

AyuFilter.java

getMatchingFilterModels is called from applyAyuFilterSpoiler during ACTION_SPOILER_MATCH rendering and also from getMaskColor. It iterates every filter and calls pattern.matcher(text).find() each time — there is no caching at all. For a busy chat list this will run regex matching on every layout pass.

Suggestion: Reuse the existing filteredCache (LruCache<Integer, Integer>) keyed by message ID, or add a parallel LruCache<Integer, List<FilterModel>> that stores matching models alongside the action result.


3. Stale ayuSpoilerText when applyAyuFilterSpoiler finds no match

ChatMessageCell.javaapplyAyuFilterSpoiler

if (action == ACTION_SPOILER_ALL || action == ACTION_SPOILER_MATCH) {
    ...
    if (!(original instanceof android.text.Spannable)) {
        msg.ayuSpoilerText = null;
        msg.ayuMediaSpoiler = false;
        return;           // ← generateLayout is NOT called here
    }

If a message previously had ayuSpoilerText set (and a layout was built around it) and then re-enters this branch, returning early without generateLayout leaves the old spoiler layout alive. Add msg.generateLayout(null) before the return.


4. savedColor always overwrites the stored color with white on hide-action edits

RegexFilterEditActivity.java

int savedColor = filterAction != AyuFilter.FilterModel.ACTION_HIDE ? spoilerColor : 0xFFFFFFFF;

When a user edits an existing spoiler filter and only changes the regex (not the action), filterAction remains ACTION_SPOILER_*, so savedColor = spoilerColor which is correct. But if the user switches back to hide while editing, the stored spoilerColor is silently reset to white. If the user later switches back to spoiler mode, their previously chosen color is gone. Consider keeping the last colour in the model instead:

int savedColor = filterAction != AyuFilter.FilterModel.ACTION_HIDE
        ? spoilerColor
        : (filterModel != null ? filterModel.spoilerColor : 0xFFFFFFFF);

⚠️ Design / Performance

5. applyAyuFilterSpoiler called unconditionally on every setMessageContent

ChatMessageCell.java

private void setMessageContent(...) {
    applyAyuFilterSpoiler(messageObject, groupedMessages);
    ...

setMessageContent is called on every rebind (scroll, update, etc.). Inside, if ayuSpoilerText != null and ayuSpoilerRevealed, it calls msg.generateLayout(null) — a full layout rebuild — on every bind. Consider gating on whether the message/filter state actually changed:

if (msg.ayuSpoilerText != null && msg.ayuSpoilerRevealed) {
    // only rebuild once
}

Or cache the filter action per message object and skip applyAyuFilterSpoiler when neither the message content nor filter configuration changed.


6. ayuBuildSpoilerGroups allocates a new SpannableString per span

MessageObject.java

for (AyuSpoilerSpan span : spans) {
    SpannableString ss = new SpannableString(block.textLayout.getText().toString());
    ss.setSpan(span, start, end, ...);
    ...
    SpoilerEffect.addSpoilers(null, block.textLayout, -1, right, ss, null, group, null);
}

A new SpannableString (full copy of the block text) is created for every AyuSpoilerSpan. This is O(spans × text_length) allocation. Reuse a single SpannableStringBuilder and replace the span in place before each call.


7. Magic value -1 returned from getFilterAction — use a named constant

AyuFilter.java

getFilterAction returns -1 to mean "no match / no action". This raw magic value propagates to callers. Define and use:

public static final int ACTION_NONE = -1;

This makes intent clear and prevents future off-by-one mistakes.


8. computeCustomResult ignores blocked-peer mask when custom filter also present

AyuFilter.java

private static int computeCustomResult(MessageObject msg) {
    int customResult = -1;
    long senderId = msg.getFromChatId();
    if (senderId > 0L) {
        if (NekoConfig.ignoreBlocked.Bool() && getCustomFilteredUsers().contains(senderId)) {
            CustomFilteredUser cfu = getCustomFilteredUser(senderId);
            if (cfu != null && cfu.filterAction != FilterModel.ACTION_HIDE) {
                customResult = cfu.filterAction;   // sets customResult to spoiler
            }
        }
        if (customResult == -1 && NaConfig.INSTANCE.getMaskBlockedUserMessages().Bool()) {

If a user is in the shadow-ban list with ACTION_HIDE, customResult stays at -1, and the code falls through to the blocked-peer mask check — meaning a shadow-banned + blocked user could end up masked instead of hidden, even if the shadow-ban entry explicitly says hide. The inner if (cfu.filterAction != ACTION_HIDE) should probably be if (cfu != null) { customResult = cfu.filterAction; } to honour the shadow-ban setting regardless.


🔧 Minor / Polish

9. String resource missing space

strings_na.xml

<string name="RegexFiltersSpoilerInsteadOfHide">Use mask(spoiler tag) instead of hiding message</string>

Should be "Use mask (spoiler tag) instead of hiding message" (space before ().


10. Import ordering

Several files have out-of-order imports:

  • MessageObject.java: android.graphics.Color inserted before android.graphics.Canvas (should be after).
  • MessageObject.java: android.text.style.ForegroundColorSpan and ReplacementSpan inserted between CharacterStyle and ClickableSpan — should follow alphabetical order.
  • ShadowBanListActivity.java: android.text.style.ImageSpan is added after android.text.InputType — should come before it alphabetically.

11. Redundant messageObject != null guard in TextLayoutBlocks constructor

MessageObject.java

if ((messageObject != null && !messageObject.isSpoilersRevealed ...) || hasUnrevealedAyu) {
    ...
    if (messageObject != null && !messageObject.isSpoilersRevealed ...) {  // ← redundant inner check

The inner messageObject != null is already implied by the outer condition. Remove it:

if (messageObject != null && !messageObject.isSpoilersRevealed && !messageObject.spoiledLoginCode) {
    SpoilerEffect.addSpoilers(...);
}

12. Duplicate hit-testing logic in checkSpoilersMotionEvent (acknowledged)

The three nearly-identical loops (text / caption / explanation) for both Ayu groups and standard spoilers could be extracted to a helper method, e.g.:

private boolean checkSpoilerHit(List<TextLayoutBlock> blocks, float bx, float by,
                                  List<TextLayoutBlock> allBlocks, TransitionParams tp) { ... }

This would significantly reduce the method length and risk of divergence.


13. ColorPickerBottomSheet.PipetteDelegate boilerplate repeated three times

RegexFilterEditActivity.java, RegexFiltersSettingActivity.java, ShadowBanListActivity.java

The same no-op PipetteDelegate is copy-pasted in all three places. Extract it into a small utility:

// in a shared util class
public static ColorPickerBottomSheet.PipetteDelegate noOpPipetteDelegate() {
    return new ColorPickerBottomSheet.PipetteDelegate() { /* all no-ops */ };
}

Summary table:

# Severity File Issue
1 🔴 Critical ChatMessageCell.java clipOutPath crashes API < 26
2 🔴 Critical AyuFilter.java getMatchingFilterModels uncached regex scan per render
3 🟠 Bug ChatMessageCell.java Early return without generateLayout leaves stale layout
4 🟠 Bug RegexFilterEditActivity.java Stored spoiler color silently reset on hide-action edit
5 🟡 Perf ChatMessageCell.java applyAyuFilterSpoiler + generateLayout on every bind
6 🟡 Perf MessageObject.java O(spans × text) allocation in ayuBuildSpoilerGroups
7 🟡 Design AyuFilter.java Magic -1 return value needs named constant
8 🟡 Logic AyuFilter.java Shadow-ban hide vs blocked-peer mask priority edge case
9 🔵 Minor strings_na.xml Missing space in string
10 🔵 Minor Multiple Import ordering
11 🔵 Minor MessageObject.java Redundant null check
12 🔵 Minor ChatMessageCell.java Duplicate hit-test loops (self-acknowledged)
13 🔵 Minor Multiple PipetteDelegate no-op boilerplate

The most important fixes to land before merge are #1 (crashes on API < 26), #2 (regex perf), and #3 (stale layout). Take your time on the rest.

(ง •̀_•́)ง

@risin42
Copy link
Copy Markdown
Owner

risin42 commented May 30, 2026

Thanks for your work, but I do not plan to add a masking option to the message filters. And bro...No AI Slop, thanks.

@risin42 risin42 closed this May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants