MutelistManager.swift (5855B)
1 // 2 // MutelistManager.swift 3 // damus 4 // 5 // Created by Charlie Fish on 1/28/24. 6 // 7 8 import Foundation 9 10 class MutelistManager { 11 let user_keypair: Keypair 12 private(set) var event: NostrEvent? = nil 13 14 var users: Set<MuteItem> = [] { 15 didSet { self.reset_cache() } 16 } 17 var hashtags: Set<MuteItem> = [] { 18 didSet { self.reset_cache() } 19 } 20 var threads: Set<MuteItem> = [] { 21 didSet { self.reset_cache() } 22 } 23 var words: Set<MuteItem> = [] { 24 didSet { self.reset_cache() } 25 } 26 27 var muted_notes_cache: [NoteId: EventMuteStatus] = [:] 28 29 init(user_keypair: Keypair) { 30 self.user_keypair = user_keypair 31 } 32 33 func refresh_sets() { 34 guard let referenced_mute_items = event?.referenced_mute_items else { return } 35 36 var new_users: Set<MuteItem> = [] 37 var new_hashtags: Set<MuteItem> = [] 38 var new_threads: Set<MuteItem> = [] 39 var new_words: Set<MuteItem> = [] 40 41 for mute_item in referenced_mute_items { 42 switch mute_item { 43 case .user: 44 new_users.insert(mute_item) 45 case .hashtag: 46 new_hashtags.insert(mute_item) 47 case .word: 48 new_words.insert(mute_item) 49 case .thread: 50 new_threads.insert(mute_item) 51 } 52 } 53 54 users = new_users 55 hashtags = new_hashtags 56 threads = new_threads 57 words = new_words 58 } 59 60 func reset_cache() { 61 self.muted_notes_cache = [:] 62 } 63 64 func is_muted(_ item: MuteItem) -> Bool { 65 switch item { 66 case .user(_, _): 67 return users.contains(item) 68 case .hashtag(_, _): 69 return hashtags.contains(item) 70 case .word(_, _): 71 return words.contains(item) 72 case .thread(_, _): 73 return threads.contains(item) 74 } 75 } 76 77 func is_event_muted(_ ev: NostrEvent) -> Bool { 78 return self.event_muted_reason(ev) != nil 79 } 80 81 func set_mutelist(_ ev: NostrEvent) { 82 let oldlist = self.event 83 self.event = ev 84 85 let old: Set<MuteItem> = oldlist?.mute_list ?? Set<MuteItem>() 86 let new: Set<MuteItem> = ev.mute_list ?? Set<MuteItem>() 87 let diff = old.symmetricDifference(new) 88 89 var new_mutes = Set<MuteItem>() 90 var new_unmutes = Set<MuteItem>() 91 92 for d in diff { 93 if new.contains(d) { 94 add_mute_item(d) 95 new_mutes.insert(d) 96 } else { 97 remove_mute_item(d) 98 new_unmutes.insert(d) 99 } 100 } 101 102 if new_mutes.count > 0 { 103 notify(.new_mutes(new_mutes)) 104 } 105 106 if new_unmutes.count > 0 { 107 notify(.new_unmutes(new_unmutes)) 108 } 109 } 110 111 private func add_mute_item(_ item: MuteItem) { 112 switch item { 113 case .user(_, _): 114 users.insert(item) 115 case .hashtag(_, _): 116 hashtags.insert(item) 117 case .word(_, _): 118 words.insert(item) 119 case .thread(_, _): 120 threads.insert(item) 121 } 122 } 123 124 private func remove_mute_item(_ item: MuteItem) { 125 switch item { 126 case .user(_, _): 127 users.remove(item) 128 case .hashtag(_, _): 129 hashtags.remove(item) 130 case .word(_, _): 131 words.remove(item) 132 case .thread(_, _): 133 threads.remove(item) 134 } 135 } 136 137 func event_muted_reason(_ ev: NostrEvent) -> MuteItem? { 138 if let cached_mute_status = self.muted_notes_cache[ev.id] { 139 return cached_mute_status.mute_reason() 140 } 141 if let reason = self.compute_event_muted_reason(ev) { 142 self.muted_notes_cache[ev.id] = .muted(reason: reason) 143 return reason 144 } 145 self.muted_notes_cache[ev.id] = .not_muted 146 return nil 147 } 148 149 150 /// Check if an event is muted given a collection of ``MutedItem``. 151 /// 152 /// - Parameter ev: The ``NostrEvent`` that you want to check the muted reason for. 153 /// - Returns: The ``MuteItem`` that matched the event. Or `nil` if the event is not muted. 154 func compute_event_muted_reason(_ ev: NostrEvent) -> MuteItem? { 155 // Events from the current user should not be muted. 156 guard self.user_keypair.pubkey != ev.pubkey else { return nil } 157 158 // Check if user is muted 159 let check_user_item = MuteItem.user(ev.pubkey, nil) 160 if users.contains(check_user_item) { 161 return check_user_item 162 } 163 164 // Check if hashtag is muted 165 for hashtag in ev.referenced_hashtags { 166 let check_hashtag_item = MuteItem.hashtag(hashtag, nil) 167 if hashtags.contains(check_hashtag_item) { 168 return check_hashtag_item 169 } 170 } 171 172 // Check if thread is muted 173 for thread_id in ev.referenced_ids { 174 let check_thread_item = MuteItem.thread(thread_id, nil) 175 if threads.contains(check_thread_item) { 176 return check_thread_item 177 } 178 } 179 180 // Check if word is muted 181 if let content: String = ev.maybe_get_content(self.user_keypair)?.lowercased() { 182 for word in words { 183 if case .word(let string, _) = word { 184 if content.contains(string.lowercased()) { 185 return word 186 } 187 } 188 } 189 } 190 191 return nil 192 } 193 194 enum EventMuteStatus { 195 case muted(reason: MuteItem) 196 case not_muted 197 198 func mute_reason() -> MuteItem? { 199 switch self { 200 case .muted(reason: let reason): 201 return reason 202 case .not_muted: 203 return nil 204 } 205 } 206 } 207 }