1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

filters: add "Do not show #nsfw tagged posts" setting

This commit adds a setting where the user can choose to hide notes with
a #nsfw hashtag. This setting was implemented to allow users to filter
out adult or other unsafe content.

I moved the code logic for content filtering into a new file, and
defined a protocol for content filters. Although the logic is still
simple, this might help in developing a flexible API in case we have
more complex filtering needs in the future.

I also modified the name of the "Appearance" setting to "Appearance and
filters", to make it easier for users to intuitively find this setting.
(Note: Re-translations of this string might be necessary)

**PASS**
**iOS:**
- iOS 17.0 (iPhone 14 Pro)

**Damus:** (This commit)
**Steps:**
1. Follow another account that you control (Account B)
2. On account B, post a note saying "#test this is a test". This note should show up on the home feed.
3. On account B, post a note saying "#nsfw this is a test". This note should NOT show up on the home feed
4. Go to settings and disable the NSFW filter. Go back to the home view. The #nsfw post should now show up.
5. Close app and reopen. NSFW post should still show up (i.e. Setting should be persistent)
6. Unfollow account B
7. Close app and reopen.
8. Follow the "#grownostr" hashtag
9. Turn on the NSFW filter
10. On account B, post a note saying "#grownostr this is a test". This note should show up on the home view.
11. On account B, post a note saying "#grownostr #nsfw this is a test". This note should NOT show up.
12. Double-check the "notes and replies" tab. Note should NOT show up there either.
12. Turn off NSFW filter
13. Note from step 11 should now show up.
14. Go to Universe view and find a post with a hashtag. Remember where the post is.
14. Locally change the tag keyword from "nsfw" to that hashtag (Note: I had to test this way because my posts were not showing up in the Universe view)
15. Turn off the filter. Check post is there, in the Universe view.
16. Turn on the filter. Check post is no longer there in the Universe view. (Check the neighboring posts are the same, to make sure)
17. Bring back the code to its normal state.
18. Search for "#nsfw". Make sure that #nsfw appears (I believe this is ok, because it means the person is purposefully searching for it)

Closes: https://github.com/damus-io/damus/issues/1412
Changelog-Added: Add "Do not show #nsfw tagged posts" setting
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
Daniel D’Aquino 2023-09-18 23:27:15 +00:00 committed by William Casarin
parent 305ee03b0e
commit 49283f2bb2
7 changed files with 91 additions and 17 deletions

View File

@ -423,6 +423,7 @@
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; };
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; };
D78525252A7B2EA4002FA637 /* NoteContentViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */; };
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
@ -1104,6 +1105,7 @@
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; };
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContentViewTests.swift; sourceTree = "<group>"; };
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
@ -1287,6 +1289,7 @@
3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */,
3A5E47C42A4A6CF400C0D090 /* Trie.swift */,
3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */,
D723C38D2AB8D83400065664 /* ContentFilters.swift */,
);
path = Models;
sourceTree = "<group>";
@ -2861,6 +2864,7 @@
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */,
4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */,
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,

View File

@ -56,20 +56,6 @@ enum Sheets: Identifiable {
}
}
enum FilterState : Int {
case posts_and_replies = 1
case posts = 0
func filter(ev: NostrEvent) -> Bool {
switch self {
case .posts:
return ev.known_kind == .boost || !ev.is_reply(.empty)
case .posts_and_replies:
return true
}
}
}
struct ContentView: View {
let keypair: Keypair
@ -96,6 +82,11 @@ struct ContentView: View {
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
@AppStorage("has_seen_suggested_users") private var hasSeenSuggestedUsers = false
let sub_id = UUID().description
var damus_filter: DamusFilter {
get {
return DamusFilter(hide_nsfw_tagged_content: self.damus_state?.settings.hide_nsfw_tagged_content ?? true)
}
}
@Environment(\.colorScheme) var colorScheme
@ -114,10 +105,10 @@ struct ContentView: View {
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
mystery
contentTimelineView(filter: FilterState.posts.filter)
contentTimelineView(filter: damus_filter.get_filter(.posts))
.tag(FilterState.posts)
.id(FilterState.posts)
contentTimelineView(filter: FilterState.posts_and_replies.filter)
contentTimelineView(filter: damus_filter.get_filter(.posts_and_replies))
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
}

View File

@ -0,0 +1,58 @@
//
// ContentFilters.swift
// damus
//
// Created by Daniel DAquino on 2023-09-18.
//
import Foundation
protocol ContentFilter {
/// Function that implements the content filtering logic
/// - Parameter ev: The nostr event to be processed
/// - Returns: Must return `true` to show events, and return `false` to hide/filter events
func filter(ev: NostrEvent) -> Bool
}
/// Simple filter to determine whether to show posts or all posts and replies.
enum FilterState : Int, ContentFilter {
case posts_and_replies = 1
case posts = 0
func filter(ev: NostrEvent) -> Bool {
switch self {
case .posts:
return ev.known_kind == .boost || !ev.is_reply(.empty)
case .posts_and_replies:
return true
}
}
}
/// Simple filter to determine whether to show posts with #nsfw tags
struct NSFWTagFilter: ContentFilter {
func filter(ev: NostrEvent) -> Bool {
return ev.referenced_hashtags.first(where: { t in t.hashtag == "nsfw" }) == nil
}
}
/// Generic filter with various tweakable settings
struct DamusFilter: ContentFilter {
let hide_nsfw_tagged_content: Bool
func filter(ev: NostrEvent) -> Bool {
if self.hide_nsfw_tagged_content {
return NSFWTagFilter().filter(ev: ev)
}
else {
return true
}
}
func get_filter(_ filter_state: FilterState) -> ((NostrEvent) -> Bool) {
return { ev in
return filter_state.filter(ev: ev) && self.filter(ev: ev)
}
}
}

View File

@ -110,6 +110,9 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "always_show_images", default_value: false)
var always_show_images: Bool
@Setting(key: "hide_nsfw_tagged_content", default_value: false)
var hide_nsfw_tagged_content: Bool
@Setting(key: "zap_vibration", default_value: true)
var zap_vibration: Bool

View File

@ -41,7 +41,7 @@ struct ConfigView: View {
}
NavigationLink(value: Route.AppearanceSettings(settings: settings)) {
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "eye", color: .red)
IconLabel(NSLocalizedString("Appearance and filters", comment: "Section header for text, appearance, and content filter settings"), img_name: "eye", color: .red)
}
NavigationLink(value: Route.SearchSettings(settings: settings)) {

View File

@ -14,6 +14,11 @@ struct SearchHomeView: View {
@StateObject var model: SearchHomeModel
@State var search: String = ""
@FocusState private var isFocused: Bool
var damus_filter: DamusFilter {
get {
return DamusFilter(hide_nsfw_tagged_content: self.damus_state.settings.hide_nsfw_tagged_content)
}
}
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
@ -50,6 +55,10 @@ struct SearchHomeView: View {
damus: damus_state,
show_friend_icon: true,
filter: { ev in
if !damus_filter.filter(ev: ev) {
return false
}
if damus_state.muted_threads.isMutedThread(ev, keypair: self.damus_state.keypair) {
return false
}

View File

@ -86,6 +86,15 @@ struct AppearanceSettingsView: View {
}
}
// MARK: - Content filters and moderation
Section(
header: Text(NSLocalizedString("Content filters", comment: "Section title for content filtering/moderation configuration.")),
footer: Text(NSLocalizedString("Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content", comment: "Section footer clarifying what #nsfw (not safe for work) tags mean"))
) {
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)
.toggleStyle(.switch)
}
}
.navigationTitle(NSLocalizedString("Appearance", comment: "Navigation title for text and appearance settings."))