damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }