Skip to content

Improve message filtering#371

Open
KMohZaid wants to merge 3 commits into
risin42:devfrom
KMohZaid:feature/better-message-filtering
Open

Improve message filtering#371
KMohZaid wants to merge 3 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

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added "Ayu" spoiler masking as an alternative visual spoiler treatment (tap-to-reveal) alongside existing spoilers.
    • Regex filters now support actions: hide, mask whole message, or mask only matched spans.
    • Per-filter and blocked-user mask colors with an integrated color picker.
    • Option to mask messages from blocked users and improved shadow-ban user options (choose hide vs. mask + color).
  • Localization
    • New strings for spoiler/mask options and blocked-user masking.

Walkthrough

This PR introduces Ayu spoiler masking, an alternative content-filtering mechanism that masks messages with visual spoiler effects instead of hiding them. It refactors regex filtering from a boolean model to an action-based approach (hide, mask all, mask matched spans) with configurable colors, and extends it to blocked users.

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 gains three transient ayu spoiler state fields (ayuMediaSpoiler, ayuSpoilerText, ayuSpoilerRevealed) and hasMediaSpoilers() incorporates ayu media spoiler detection. TextLayoutBlock adds an AtomicReference for a prebuilt patched layout and a list grouping spoiler effects by color. New AyuSpoilerSpan class and three helper methods (ayuSpoilerBlockSpannable, ayuBuildSpoilerGroups, ayuPreBuildPatchedLayout) build patched layouts with emoji and Telegram spoiler ranges masked. Layout generation in both generateLayout and the TextLayoutBlocks constructor now use these helpers instead of direct SpoilerEffect.addSpoilers calls.
AyuFilter Action-Based Filtering Model
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/AyuFilter.java
AyuFilter switches from boolean "matched/hidden" filtering to an integer "best action" model with three actions: ACTION_HIDE, ACTION_SPOILER_ALL, ACTION_SPOILER_MATCH. Filter add/edit methods now accept filterAction and optional spoilerColor parameters. New getMatchingFilterModels() returns all enabled matching filters; isFiltered() now derives from whether action equals ACTION_HIDE. getMaskColor() computes spoiler color from the winning action. Custom filtered peer APIs extend to isCustomFilteredPeerHidden() and setCustomFilteredUserAction(). Both FilterModel and CustomFilteredUser gain filterAction and spoilerColor fields with defaults.
ChatActivity Blocked User Masking
TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java
Blocked-peer detection logic branches on whether masking is enabled: if a sender is blocked and NaConfig.INSTANCE.getMaskBlockedUserMessages() is true, the message is masked rather than hidden. The final return also uses AyuFilter.isCustomFilteredPeerHidden() for custom filtering evaluation.
ChatMessageCell Ayu Spoiler Interaction and Rendering
TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java
Spoiler hit-testing computes adjusted yOff bounds and checks ayuSpoilerGroups for ayu spoilers separately from standard spoilers. On click release, logic branches on whether ayuSpoilerText is active: ayu path builds ripple from ayuSpoilerGroups, calls applyAyuFilterSpoiler() to update mask state, and clears ayu groups; standard path uses block.spoilers. A new applyAyuFilterSpoiler() method computes mask spans via AyuFilter, manages revealed state, and regenerates layout. Text rendering conditionally draws ayu groups with ripple effects. Emoji drawing clips out ayu spoiler regions via canvas.clipOutPath().
RegexFilterEditActivity Action and Color Selection
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFilterEditActivity.java
Introduces filterAction and spoilerColor state fields, initialized across all constructors and loaded from existing filters. createView now includes a toggle for hide vs spoiler mode, a conditional "match only" option, and a spoiler color picker (custom swatch + ColorPickerBottomSheet) whose visibility depends on the selected action. Save logic computes savedColor based on action and passes both into updated AyuFilter add/edit calls.
RegexFiltersSettingActivity Blocked User Masking Config
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/RegexFiltersSettingActivity.java
Adds rows for "mask blocked user messages" toggle and "blocked user mask color" picker (visible only when masking is enabled). Toggles update both NaConfig and NekoConfig.ignoreBlocked. Color picker UI draws a circular preview via ShapeDrawable with OvalShape. Row rendering adjusts "IgnoreBlocked" checkbox state when masking is active. Adapter view-type selection correctly assigns the color row.
ShadowBanListActivity Custom Filtered User Action and Color Display
TMessagesProj/src/main/java/tw/nekomimi/nekogram/filters/ShadowBanListActivity.java
getCustomFilteredUserRowSubtitle now returns a styled CharSequence prefixing spoiler-mode labels with a colored circular bullet via SpannableStringBuilder and ImageSpan. The user options menu replaces a compact menu with an action-aware builder showing hide/spoiler toggles and a "spoiler color" option, updating AyuFilter state and refreshing rows on selection.
Configuration Items and String Resources
TMessagesProj/src/main/kotlin/xyz/nextalone/nagram/NaConfig.kt, TMessagesProj/src/main/res/values/strings_na.xml
NaConfig defines maskBlockedUserMessages (boolean) and blockedUserMaskColor (integer, default 0xFF888888) configuration items. String resources add labels and descriptions for spoiler-instead-of-hide filter behavior, matched-regex-only masking, 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 vague and overly broad, not clearly summarizing the specific main change of adding spoiler masking for filtered messages. Use a more specific title like 'Add spoiler masking for message filters and blocked users' to clearly convey the core feature being added.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the masking feature for filters, blocked users, and shadow ban list with motivation and context about the changes.
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

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.

1 participant