From a9baef7a213eaa0f10b80d658e61b08fb95e7d1d Mon Sep 17 00:00:00 2001 From: Charlie Fish Date: Sat, 10 Feb 2024 09:36:47 -0700 Subject: [PATCH] mute: adding MutelistManager.swift This patch adds MutelistManager which contains separate sets for each type of muted item. Whenever we receive a new event we parse it into those sets. When checking to see if an event is muted, we simply check in each of those sets if it contains the given mute. For words/phrases we have to loop through each item in the set to see if the event contains the given word. In future patches I will be implementing and integrating this new MutelistManager. Lighting-Address: fishcharlie@strike.me Signed-off-by: Charlie Fish Reviewed-by: William Casarin Link: 20240210163650.42884-3-contact@charlie.fish Signed-off-by: William Casarin --- damus.xcodeproj/project.pbxproj | 6 ++ damus/Models/MutelistManager.swift | 162 +++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 damus/Models/MutelistManager.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 90cc34f9..15da775b 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -423,9 +423,11 @@ B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501062C2B363036003874F5 /* AuthIntegrationTests.swift */; }; B51C1CEA2B55A60A00E312A9 /* AddMuteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51C1CE82B55A60A00E312A9 /* AddMuteItemView.swift */; }; B51C1CEB2B55A60A00E312A9 /* MuteDurationMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51C1CE92B55A60A00E312A9 /* MuteDurationMenu.swift */; }; + B533694E2B66D791008A805E /* MutelistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533694D2B66D791008A805E /* MutelistManager.swift */; }; B57B4C622B312BD700A232C0 /* ReconnectRelaysNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */; }; B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */; }; B57B4C662B312C3700A232C0 /* NostrAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C652B312C3700A232C0 /* NostrAuth.swift */; }; + B59CAD4D2B688D1000677E8B /* MutelistManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533694D2B66D791008A805E /* MutelistManager.swift */; }; B5A75C2A2B546D94007AFBC0 /* MuteItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A75C292B546D94007AFBC0 /* MuteItemTests.swift */; }; B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B4D1422B37D47600844320 /* NdbExtensions.swift */; }; BA0F0A6F2B36207E001641B2 /* CameraMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA0F0A6E2B36207E001641B2 /* CameraMediaView.swift */; }; @@ -1322,6 +1324,7 @@ B501062C2B363036003874F5 /* AuthIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthIntegrationTests.swift; sourceTree = ""; usesTabs = 0; }; B51C1CE82B55A60A00E312A9 /* AddMuteItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddMuteItemView.swift; sourceTree = ""; }; B51C1CE92B55A60A00E312A9 /* MuteDurationMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MuteDurationMenu.swift; sourceTree = ""; }; + B533694D2B66D791008A805E /* MutelistManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutelistManager.swift; sourceTree = ""; usesTabs = 0; }; B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReconnectRelaysNotify.swift; sourceTree = ""; }; B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelayAuthenticationDetail.swift; sourceTree = ""; }; B57B4C652B312C3700A232C0 /* NostrAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NostrAuth.swift; sourceTree = ""; }; @@ -1602,6 +1605,7 @@ D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */, D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */, B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */, + B533694D2B66D791008A805E /* MutelistManager.swift */, ); path = Models; sourceTree = ""; @@ -3274,6 +3278,7 @@ D7CB5D5C2B1176B200AD4105 /* MediaUploader.swift in Sources */, 4C1253562A76C8C60004F4B8 /* BroadcastNotify.swift in Sources */, 4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */, + B533694E2B66D791008A805E /* MutelistManager.swift in Sources */, 4C32B9532A9AD44700DC3548 /* Verifier.swift in Sources */, BA10192F2B449556009C57DA /* CameraPreview.swift in Sources */, 4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */, @@ -3575,6 +3580,7 @@ D7CE1B1E2B0BE190002EDAD4 /* midl.c in Sources */, D7CB5D3C2B1130C600AD4105 /* LocalNotification.swift in Sources */, D7CE1B2D2B0BE250002EDAD4 /* take.c in Sources */, + B59CAD4D2B688D1000677E8B /* MutelistManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/damus/Models/MutelistManager.swift b/damus/Models/MutelistManager.swift new file mode 100644 index 00000000..90a640fe --- /dev/null +++ b/damus/Models/MutelistManager.swift @@ -0,0 +1,162 @@ +// +// MutelistManager.swift +// damus +// +// Created by Charlie Fish on 1/28/24. +// + +import Foundation + +class MutelistManager { + private(set) var event: NostrEvent? = nil + + var users: Set = [] + var hashtags: Set = [] + var threads: Set = [] + var words: Set = [] + + func refresh_sets() { + guard let referenced_mute_items = event?.referenced_mute_items else { return } + + var new_users: Set = [] + var new_hashtags: Set = [] + var new_threads: Set = [] + var new_words: Set = [] + + for mute_item in referenced_mute_items { + switch mute_item { + case .user: + new_users.insert(mute_item) + case .hashtag: + new_hashtags.insert(mute_item) + case .word: + new_words.insert(mute_item) + case .thread: + new_threads.insert(mute_item) + } + } + + users = new_users + hashtags = new_hashtags + threads = new_threads + words = new_words + } + + func is_muted(_ item: MuteItem) -> Bool { + switch item { + case .user(_, _): + return users.contains(item) + case .hashtag(_, _): + return hashtags.contains(item) + case .word(_, _): + return words.contains(item) + case .thread(_, _): + return threads.contains(item) + } + } + + func is_event_muted(_ ev: NostrEvent, keypair: Keypair? = nil) -> Bool { + return event_muted_reason(ev, keypair: keypair) != nil + } + + func set_mutelist(_ ev: NostrEvent) { + let oldlist = self.event + self.event = ev + + let old: Set = oldlist?.mute_list ?? Set() + let new: Set = ev.mute_list ?? Set() + let diff = old.symmetricDifference(new) + + var new_mutes = Set() + var new_unmutes = Set() + + for d in diff { + if new.contains(d) { + add_mute_item(d) + new_mutes.insert(d) + } else { + remove_mute_item(d) + new_unmutes.insert(d) + } + } + + if new_mutes.count > 0 { + notify(.new_mutes(new_mutes)) + } + + if new_unmutes.count > 0 { + notify(.new_unmutes(new_unmutes)) + } + } + + private func add_mute_item(_ item: MuteItem) { + switch item { + case .user(_, _): + users.insert(item) + case .hashtag(_, _): + hashtags.insert(item) + case .word(_, _): + words.insert(item) + case .thread(_, _): + threads.insert(item) + } + } + + private func remove_mute_item(_ item: MuteItem) { + switch item { + case .user(_, _): + users.remove(item) + case .hashtag(_, _): + hashtags.remove(item) + case .word(_, _): + words.remove(item) + case .thread(_, _): + threads.remove(item) + } + } + + + /// Check if an event is muted given a collection of ``MutedItem``. + /// + /// - Parameter ev: The ``NostrEvent`` that you want to check the muted reason for. + /// - Returns: The ``MuteItem`` that matched the event. Or `nil` if the event is not muted. + func event_muted_reason(_ ev: NostrEvent, keypair: Keypair? = nil) -> MuteItem? { + // Events from the current user should not be muted. + guard keypair?.pubkey != ev.pubkey else { return nil } + + // Check if user is muted + let check_user_item = MuteItem.user(ev.pubkey, nil) + if users.contains(check_user_item) { + return check_user_item + } + + // Check if hashtag is muted + for hashtag in ev.referenced_hashtags { + let check_hashtag_item = MuteItem.hashtag(hashtag, nil) + if hashtags.contains(check_hashtag_item) { + return check_hashtag_item + } + } + + // Check if thread is muted + for thread_id in ev.referenced_ids { + let check_thread_item = MuteItem.thread(thread_id, nil) + if threads.contains(check_thread_item) { + return check_thread_item + } + } + + // Check if word is muted + if let keypair, let content: String = ev.maybe_get_content(keypair)?.lowercased() { + for word in words { + if case .word(let string, _) = word { + if content.contains(string.lowercased()) { + return word + } + } + } + } + + return nil + } +}