Skip to content

Commit be7a23b

Browse files
alltheseasclaudejb55
committed
filter: option to 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: #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]>
1 parent f7fcb2c commit be7a23b

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)