Skip to content

Conversation

@alltheseas
Copy link
Collaborator

@alltheseas alltheseas commented Dec 15, 2025

Summary

Add a hashtag spam filter that automatically hides posts with too many hashtags from timelines. Posts with more than the configured number of hashtags (default: 3) are filtered out, helping reduce hashtag spam.

Changes:

  • Add hide_hashtag_spam setting (default: enabled) and max_hashtags setting (default: 3) to UserSettingsStore
  • Add hashtag_spam_filter function that counts hashtags in content text (works regardless of whether clients add proper ["t", "tag"] entries)
  • Add toggle and slider UI in Settings → Appearance → Content filters

Checklist

Standard PR Checklist

  • I have read (or I am familiar with) the Contribution Guidelines
  • I have tested the changes in this PR
  • I have profiled the changes to ensure there are no performance regressions, or I do not need to profile the changes.
    • If not needed, provide reason: Simple string iteration with early return; negligible performance impact
  • I have opened or referred to an existing github issue related to this change: Hashtag spamming #1677
  • My PR is either small, or I have split it into smaller logical commits that are easier to review
  • I have added the signoff line to all my commits. See Signing off your work
  • I have added appropriate changelog entries for the changes in this PR. See Adding changelog entries
  • I have added appropriate Closes: or Fixes: tags in the commit messages wherever applicable, or made sure those are not needed. See Submitting patches

Test report

Device: iPhone 16 Pro Simulator

iOS: 18.3.1

Damus: ed00301

Setup: Default settings with hashtag spam filter enabled (default)

Steps:

  1. Open Universe view
  2. Verify posts with >3 hashtags are filtered
  3. Go to Settings → Appearance → Content filters
  4. Toggle "Hide posts with too many hashtags" off/on
  5. Adjust "Maximum hashtags" slider (1-20)
  6. Verify filter respects settings changes

Results:

  • PASS

Other notes

Updated logic now checks both:

  1. "t" tags (ev.referenced_hashtags) - if count > threshold → filter
  2. Content text (scanning for # patterns) - if count > threshold → filter

Some Nostr clients don't properly populate ["t", "hashtag"] tags.

Closes #1677

Summary by CodeRabbit

Release Notes

New Features

  • Added hashtag spam filtering to content filter settings
  • Toggle option to hide posts with excessive hashtags
  • Configurable maximum hashtag threshold (adjustable 1-20) to customize filtering sensitivity

✏️ Tip: You can customize this high-level summary in your review settings.

@alltheseas
Copy link
Collaborator Author

smol update @jb55 - this one is tested and seems to work well.

this is similar to the notedeck hashtag filter you merged damus-io/notedeck#1202

@danieldaquino danieldaquino added the pr-in-queue This PR is waiting in a queue behind their other PRs marked with the label `pr-active-review`. label Dec 15, 2025
@alltheseas alltheseas mentioned this pull request Dec 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Two new user-configurable settings for hashtag spam filtering were added: a toggle to enable the filter and a parameter for maximum allowed hashtags per post. The new filter function integrates into the content filtering pipeline to exclude posts exceeding the threshold, and UI controls were added to the appearance settings to manage these parameters.

Changes

Cohort / File(s) Summary
Settings Model
damus/Features/Settings/Models/UserSettingsStore.swift
Added two new @Setting properties: hide_hashtag_spam: Bool (default true) and max_hashtags: Int (default 3) to store user preferences for hashtag spam filtering.
Settings UI
damus/Features/Settings/Views/AppearanceSettingsView.swift
Added computed binding max_hashtags_binding to convert Double slider values to Int. Inserted "Hide posts with too many hashtags" toggle under Content filters; when enabled, reveals a slider (range 1–20) to set the maximum hashtags threshold.
Content Filtering
damus/Features/Timeline/Models/ContentFilters.swift
Implemented hashtag_spam_filter() function that counts referenced hashtags and returns false when count exceeds the configured threshold. Integrated the filter into ContentFilters.defaults when the feature is enabled.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A fluffy-tailed dev hops with glee,
"No more spam hashtags we'll see!"
Three tags by default, a slider so bright,
Damus now filters with all of its might. 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive The raw summary indicates the implementation only counts ev.referenced_hashtags despite comments claiming it checks content text, creating a discrepancy with the PR description. Verify whether the hashtag_spam_filter function checks both ev.referenced_hashtags and content text patterns as described, or confirm if only ev.referenced_hashtags is implemented.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding a hashtag spam filter that automatically mutes posts with too many hashtags.
Description check ✅ Passed The PR description includes all required sections: summary, comprehensive checklist completion, detailed test report with device/OS/version info, and appropriate notes referencing the issue.
Linked Issues check ✅ Passed The PR successfully addresses issue #1677 by implementing a configurable hashtag spam filter that manages signal-to-noise by hiding posts exceeding a threshold of hashtags.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf73bf9 and 143bc62.

📒 Files selected for processing (3)
  • damus/Features/Settings/Models/UserSettingsStore.swift
  • damus/Features/Settings/Views/AppearanceSettingsView.swift
  • damus/Features/Timeline/Models/ContentFilters.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • damus/Features/Settings/Views/AppearanceSettingsView.swift
  • damus/Features/Settings/Models/UserSettingsStore.swift
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Maximize usage of nostrdb facilities (Ndb, NdbNote, iterators) whenever possible for persistence and queries in the Damus iOS app
Favor Swift-first solutions that lean on nostrdb types (Ndb, NdbNote, iterators) before introducing new storage mechanisms
Ensure docstring coverage for any code added, or modified
Ensure nevernesting: favor early returns and guard clauses over deeply nested conditionals; simplify control flow by exiting early instead of wrapping logic in multiple layers of if statements

Files:

  • damus/Features/Timeline/Models/ContentFilters.swift
🔇 Additional comments (1)
damus/Features/Timeline/Models/ContentFilters.swift (1)

116-119: LGTM!

The integration correctly checks the hide_hashtag_spam setting, captures the max_hashtags value, and appends the filter to the array. The closure properly passes both parameters to hashtag_spam_filter, and the implementation follows the established pattern used for other filters in this file.


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

@jb55 jb55 force-pushed the hashtag-spam-filter branch from bf73bf9 to fc1501c Compare January 6, 2026 19:31
…htags

Posts with more than the configured number of hashtags (default: 3) are
now automatically filtered from timelines. This helps reduce hashtag spam.

- Add hide_hashtag_spam and max_hashtags settings to UserSettingsStore
- Add hashtag_spam_filter that counts hashtags in content text
- Add toggle and slider UI in Appearance > Content filters settings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changelog-Added: Added hashtag spam filter setting to hide posts with too many hashtags
Closes: damus-io#1677
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: William Casarin <[email protected]>
Signed-off-by: alltheseas
Signed-off-by: William Casarin <[email protected]>
@jb55 jb55 force-pushed the hashtag-spam-filter branch from fc1501c to 143bc62 Compare January 6, 2026 19:31
Copy link

@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

🤖 Fix all issues with AI Agents
In @damus/Features/Timeline/Models/ContentFilters.swift:
- Around line 57-71: The doc comment for hashtag_spam_filter is inaccurate: the
function only checks ev.referenced_hashtags (the "t" tags) against max_hashtags
and does not scan content text; update the documentation comment above func
hashtag_spam_filter(ev: NostrEvent, max_hashtags: Int) -> Bool to state that it
only enforces a tag-count limit (spam detection based solely on "t"
tags/referenced_hashtags) and remove any mention of scanning hashtags in the
event content. Ensure the comment references ev.referenced_hashtags and
max_hashtags so readers understand the check being performed.
🧹 Nitpick comments (3)
damus/Features/Settings/Models/UserSettingsStore.swift (1)

142-146: Consider adding docstrings for the new settings.

Per the coding guidelines, "Ensure docstring coverage for any code added, or modified." While the existing settings in this file lack docstrings, adding them for new properties would improve maintainability and help future developers understand the purpose and expected values.

📝 Suggested docstrings
+    /// Whether to hide posts that contain too many hashtags (spam detection)
     @Setting(key: "hide_hashtag_spam", default_value: true)
     var hide_hashtag_spam: Bool

+    /// Maximum number of hashtags allowed in a post before it's hidden (when hide_hashtag_spam is enabled)
     @Setting(key: "max_hashtags", default_value: 3)
     var max_hashtags: Int
damus/Features/Timeline/Models/ContentFilters.swift (1)

60-71: Add docstring for new function.

The hashtag_spam_filter function lacks documentation. Per the coding guidelines, "Ensure docstring coverage for any code added, or modified."

📝 Suggested docstring
+/// Filters posts with too many hashtags for spam detection.
+///
+/// - Parameters:
+///   - ev: The NostrEvent to check
+///   - max_hashtags: Maximum number of hashtags allowed
+/// - Returns: `true` if the post should be shown (hashtag count is within limit), `false` if it should be filtered
 func hashtag_spam_filter(ev: NostrEvent, max_hashtags: Int) -> Bool {
damus/Features/Settings/Views/AppearanceSettingsView.swift (1)

39-45: Consider adding documentation for the binding property.

While the binding implementation is correct, adding a brief docstring would improve code clarity, especially since this property bridges between different numeric types (Double ↔ Int).

📝 Suggested docstring
+    /// Binding that bridges the Double-based Slider to the Int-based max_hashtags setting
     var max_hashtags_binding: Binding<Double> {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3a5445 and bf73bf9.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • damus/Features/Settings/Models/UserSettingsStore.swift
  • damus/Features/Settings/Views/AppearanceSettingsView.swift
  • damus/Features/Timeline/Models/ContentFilters.swift
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Maximize usage of nostrdb facilities (Ndb, NdbNote, iterators) whenever possible for persistence and queries in the Damus iOS app
Favor Swift-first solutions that lean on nostrdb types (Ndb, NdbNote, iterators) before introducing new storage mechanisms
Ensure docstring coverage for any code added, or modified
Ensure nevernesting: favor early returns and guard clauses over deeply nested conditionals; simplify control flow by exiting early instead of wrapping logic in multiple layers of if statements

Files:

  • damus/Features/Timeline/Models/ContentFilters.swift
  • damus/Features/Settings/Models/UserSettingsStore.swift
  • damus/Features/Settings/Views/AppearanceSettingsView.swift
🔇 Additional comments (2)
damus/Features/Timeline/Models/ContentFilters.swift (1)

116-119: LGTM! Hashtag spam filter integration is correct.

The integration of the hashtag spam filter into the content filtering pipeline follows the same pattern as the NSFW filter. The closure correctly captures max_hashtags from settings and passes it to the filter function.

damus/Features/Settings/Views/AppearanceSettingsView.swift (1)

115-122: LGTM! Hashtag spam filter UI implementation is well-structured.

The toggle and conditional slider provide a clean, intuitive interface for configuring the hashtag spam filter. The conditional reveal pattern (showing the slider only when the toggle is enabled) is good UX, and the slider range of 1-20 with step 1 is appropriate for the use case.

Comment on lines +57 to +71
/// Filter to hide posts with too many hashtags (spam detection)
/// Checks both the event's "t" tags and hashtags in content text.
/// If either exceeds the threshold, the post is filtered.
func hashtag_spam_filter(ev: NostrEvent, max_hashtags: Int) -> Bool {
// Check "t" tags count
var tag_count = 0
for _ in ev.referenced_hashtags {
tag_count += 1
if tag_count > max_hashtags {
return false
}
}

return true
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update misleading documentation comment.

The documentation comment at lines 58-59 states that the function "Checks both the event's 't' tags and hashtags in content text," but the implementation only checks ev.referenced_hashtags (the "t" tags). Content text scanning is not performed.

According to the PR description, a follow-up commit by jb55 removed the content filter, noting that "only spam in tags is relevant." The comment should be updated to reflect the actual implementation.

🔎 Proposed fix
 /// Filter to hide posts with too many hashtags (spam detection)
-/// Checks both the event's "t" tags and hashtags in content text.
-/// If either exceeds the threshold, the post is filtered.
+/// Checks the event's "t" tags (referenced_hashtags).
+/// If the count exceeds the threshold, the post is filtered.
 func hashtag_spam_filter(ev: NostrEvent, max_hashtags: Int) -> Bool {
🤖 Prompt for AI Agents
In @damus/Features/Timeline/Models/ContentFilters.swift around lines 57 - 71,
The doc comment for hashtag_spam_filter is inaccurate: the function only checks
ev.referenced_hashtags (the "t" tags) against max_hashtags and does not scan
content text; update the documentation comment above func
hashtag_spam_filter(ev: NostrEvent, max_hashtags: Int) -> Bool to state that it
only enforces a tag-count limit (spam detection based solely on "t"
tags/referenced_hashtags) and remove any mention of scanning hashtags in the
event content. Ensure the comment references ev.referenced_hashtags and
max_hashtags so readers understand the check being performed.

Copy link
Collaborator

jb55 commented Jan 6, 2026

thanks! i've cherry-picked this into my integration branch

@jb55 jb55 added pr-in-merge-queue The PR has been reviewed, tested, and added to an integration branch. Will be merged soon. and removed pr-in-queue This PR is waiting in a queue behind their other PRs marked with the label `pr-active-review`. labels Jan 6, 2026
jb55 added a commit that referenced this pull request Jan 6, 2026
Posts with more than the configured number of hashtags (default: 3) are
now automatically filtered from timelines. This helps reduce hashtag spam.

- Add hide_hashtag_spam and max_hashtags settings to UserSettingsStore
- Add hashtag_spam_filter that counts hashtags in content text
- Add toggle and slider UI in Appearance > Content filters settings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changelog-Added: Added hashtag spam filter setting to hide posts with too many hashtags
Closes: #3425
Closes: #1677
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: William Casarin <[email protected]>
Signed-off-by: alltheseas
Signed-off-by: William Casarin <[email protected]>
@jb55 jb55 closed this in be7a23b Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hashtags pr-in-merge-queue The PR has been reviewed, tested, and added to an integration branch. Will be merged soon. spam

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hashtag spamming

3 participants