Skip to content

Commit 143bc62

Browse files
alltheseasclaudejb55
committed
Add hashtag spam filter to automatically mute posts with too many hashtags
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: #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]>
1 parent d3a5445 commit 143bc62

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

damus/Features/Settings/Models/UserSettingsStore.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,13 @@ class UserSettingsStore: ObservableObject {
138138

139139
@Setting(key: "hide_nsfw_tagged_content", default_value: false)
140140
var hide_nsfw_tagged_content: Bool
141-
141+
142+
@Setting(key: "hide_hashtag_spam", default_value: true)
143+
var hide_hashtag_spam: Bool
144+
145+
@Setting(key: "max_hashtags", default_value: 3)
146+
var max_hashtags: Int
147+
142148
@Setting(key: "reduce_bitcoin_content", default_value: false)
143149
var reduce_bitcoin_content: Bool
144150

damus/Features/Settings/Views/AppearanceSettingsView.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ struct AppearanceSettingsView: View {
3636
@State var showing_enable_animation_alert: Bool = false
3737
@State var enable_animation_toggle_is_user_initiated: Bool = true
3838

39+
var max_hashtags_binding: Binding<Double> {
40+
Binding<Double>(get: {
41+
return Double(settings.max_hashtags)
42+
}, set: {
43+
settings.max_hashtags = Int($0)
44+
})
45+
}
46+
3947
var FontSize: some View {
4048
VStack(alignment: .leading) {
4149
Slider(value: $settings.font_size, in: 0.5...2.0, step: 0.1)
@@ -104,6 +112,14 @@ struct AppearanceSettingsView: View {
104112
.toggleStyle(.switch)
105113
Toggle(NSLocalizedString("Hide notes with #nsfw tags", comment: "Setting to hide notes with the #nsfw (not safe for work) tags"), isOn: $settings.hide_nsfw_tagged_content)
106114
.toggleStyle(.switch)
115+
Toggle(NSLocalizedString("Hide posts with too many hashtags", comment: "Setting to hide notes that contain too many hashtags (spam)"), isOn: $settings.hide_hashtag_spam)
116+
.toggleStyle(.switch)
117+
if settings.hide_hashtag_spam {
118+
VStack(alignment: .leading) {
119+
Text(String(format: NSLocalizedString("Maximum hashtags: %d", comment: "Label showing the maximum number of hashtags allowed before a post is hidden"), settings.max_hashtags))
120+
Slider(value: max_hashtags_binding, in: 1...20, step: 1)
121+
}
122+
}
107123
}
108124

109125
// MARK: - Profiles

damus/Features/Timeline/Models/ContentFilters.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ func nsfw_tag_filter(ev: NostrEvent) -> Bool {
5454
return ev.referenced_hashtags.first(where: { t in t.hashtag.caseInsensitiveCompare("nsfw") == .orderedSame }) == nil
5555
}
5656

57+
/// Filter to hide posts with too many hashtags (spam detection)
58+
/// Checks both the event's "t" tags and hashtags in content text.
59+
/// If either exceeds the threshold, the post is filtered.
60+
func hashtag_spam_filter(ev: NostrEvent, max_hashtags: Int) -> Bool {
61+
// Check "t" tags count
62+
var tag_count = 0
63+
for _ in ev.referenced_hashtags {
64+
tag_count += 1
65+
if tag_count > max_hashtags {
66+
return false
67+
}
68+
}
69+
70+
return true
71+
}
72+
5773
@MainActor
5874
func get_repost_of_muted_user_filter(damus_state: DamusState) -> ((_ ev: NostrEvent) -> Bool) {
5975
return { ev in
@@ -97,6 +113,10 @@ extension ContentFilters {
97113
if damus_state.settings.hide_nsfw_tagged_content {
98114
filters.append(nsfw_tag_filter)
99115
}
116+
if damus_state.settings.hide_hashtag_spam {
117+
let max_hashtags = damus_state.settings.max_hashtags
118+
filters.append({ ev in hashtag_spam_filter(ev: ev, max_hashtags: max_hashtags) })
119+
}
100120
filters.append(get_repost_of_muted_user_filter(damus_state: damus_state))
101121
filters.append(timestamp_filter)
102122
return filters

0 commit comments

Comments
 (0)