damus

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

MuteItem.swift (6825B)


      1 //
      2 //  MuteItem.swift
      3 //  damus
      4 //
      5 //  Created by Charlie Fish on 1/13/24.
      6 //
      7 
      8 import Foundation
      9 
     10 /// Represents an item that is muted.
     11 enum MuteItem: Hashable, Equatable {
     12     /// A user that is muted.
     13     ///
     14     /// The associated type is the ``Pubkey`` that is muted. The second associated type is the date that the item should expire at. If no date is supplied, assume the muted item should remain active until it expires.
     15     case user(Pubkey, Date?)
     16 
     17     /// A hashtag that is muted.
     18     ///
     19     /// The associated type is the hashtag string that is muted. The second associated type is the date that the item should expire at. If no date is supplied, assume the muted item should remain active until it expires.
     20     case hashtag(Hashtag, Date?)
     21 
     22     /// A word/phrase that is muted.
     23     ///
     24     /// The associated type is the word/phrase that is muted. The second associated type is the date that the item should expire at. If no date is supplied, assume the muted item should remain active until it expires.
     25     case word(String, Date?)
     26 
     27     /// A thread that is muted.
     28     ///
     29     /// The associated type is the `id` of the note that is muted. The second associated type is the date that the item should expire at. If no date is supplied, assume the muted item should remain active until it expires.
     30     case thread(NoteId, Date?)
     31 
     32     func is_expired() -> Bool {
     33         switch self {
     34         case .user(_, let expiration_date):
     35             return expiration_date ?? .distantFuture < Date()
     36         case .hashtag(_, let expiration_date):
     37             return expiration_date ?? .distantFuture < Date()
     38         case .word(_, let expiration_date):
     39             return expiration_date ?? .distantFuture < Date()
     40         case .thread(_, let expiration_date):
     41             return expiration_date ?? .distantFuture < Date()
     42         }
     43     }
     44 
     45     static func == (lhs: MuteItem, rhs: MuteItem) -> Bool {
     46         // lhs is the item we want to check (ie. the item the user is attempting to display)
     47         // rhs is the item we want to check against (ie. the item in the mute list)
     48 
     49         switch (lhs, rhs) {
     50             case (.user(let lhs_pubkey, _), .user(let rhs_pubkey, let rhs_expiration_date)):
     51                 return lhs_pubkey == rhs_pubkey && !rhs.is_expired()
     52             case (.hashtag(let lhs_hashtag, _), .hashtag(let rhs_hashtag, let rhs_expiration_date)):
     53                 return lhs_hashtag == rhs_hashtag && !rhs.is_expired()
     54             case (.word(let lhs_word, _), .word(let rhs_word, let rhs_expiration_date)):
     55                 return lhs_word == rhs_word && !rhs.is_expired()
     56             case (.thread(let lhs_thread, _), .thread(let rhs_thread, let rhs_expiration_date)):
     57                 return lhs_thread == rhs_thread && !rhs.is_expired()
     58             default:
     59                 return false
     60         }
     61     }
     62 
     63     private var refTags: [String] {
     64         switch self {
     65         case .user(let pubkey, _):
     66             return RefId.pubkey(pubkey).tag
     67         case .hashtag(let hashtag, _):
     68             return RefId.hashtag(hashtag).tag
     69         case .word(let string, _):
     70             return ["word", string]
     71         case .thread(let noteId, _):
     72             return RefId.event(noteId).tag
     73         }
     74     }
     75 
     76     var tag: [String] {
     77         var tag = self.refTags
     78 
     79         switch self {
     80         case .user(_, let date):
     81             if let date {
     82                 tag.append("\(Int(date.timeIntervalSince1970))")
     83             }
     84         case .hashtag(_, let date):
     85             if let date {
     86                 tag.append("\(Int(date.timeIntervalSince1970))")
     87             }
     88         case .word(_, let date):
     89             if let date {
     90                 tag.append("\(Int(date.timeIntervalSince1970))")
     91             }
     92         case .thread(_, let date):
     93             if let date {
     94                 tag.append("\(Int(date.timeIntervalSince1970))")
     95             }
     96         }
     97 
     98         return tag
     99     }
    100 
    101     var title: String {
    102         switch self {
    103         case .user:
    104             return "user"
    105         case .hashtag:
    106             return "hashtag"
    107         case .word:
    108             return "word"
    109         case .thread:
    110             return "thread"
    111         }
    112     }
    113 
    114     init?(_ tag: [String]) {
    115         guard let tag_id = tag.first else { return nil }
    116         guard let tag_content = tag[safe: 1] else { return nil }
    117 
    118         let tag_expiration_date: Date? = {
    119             if let tag_expiration_string: String = tag[safe: 2],
    120                let tag_expiration_number: TimeInterval = Double(tag_expiration_string) {
    121                 return Date(timeIntervalSince1970: tag_expiration_number)
    122             } else {
    123                 return nil
    124             }
    125         }()
    126 
    127         switch tag_id {
    128         case "p":
    129             guard let pubkey = Pubkey(hex: tag_content) else { return nil }
    130             self = MuteItem.user(pubkey, tag_expiration_date)
    131             break
    132         case "t":
    133             self = MuteItem.hashtag(Hashtag(hashtag: tag_content), tag_expiration_date)
    134             break
    135         case "word":
    136             self = MuteItem.word(tag_content, tag_expiration_date)
    137             break
    138         case "thread":
    139             guard let note_id = NoteId(hex: tag_content) else { return nil }
    140             self = MuteItem.thread(note_id, tag_expiration_date)
    141             break
    142         default:
    143             return nil
    144         }
    145     }
    146 }
    147 
    148 // - MARK: TagConvertible
    149 extension MuteItem: TagConvertible {
    150     enum MuteKeys: String {
    151         case p, t, word, e
    152 
    153         init?(tag: NdbTagElem) {
    154             let len = tag.count
    155             if len == 1 {
    156                 switch tag.single_char {
    157                 case "p": self = .p
    158                 case "t": self = .t
    159                 case "e": self = .e
    160                 default: return nil
    161                 }
    162             } else if len == 4 && tag.matches_str("word", tag_len: 4) {
    163                 self = .word
    164             } else {
    165                 return nil
    166             }
    167         }
    168 
    169         var description: String { self.rawValue }
    170     }
    171 
    172     static func from_tag(tag: TagSequence) -> MuteItem? {
    173         guard tag.count >= 2 else { return nil }
    174 
    175         var i = tag.makeIterator()
    176 
    177         guard let t0   = i.next(),
    178               let mkey = MuteKeys(tag: t0),
    179               let t1   = i.next()
    180         else {
    181             return nil
    182         }
    183 
    184         var expiry: Date? = nil
    185         if let expiry_str = i.next(), let ts = expiry_str.u64() {
    186             expiry = Date(timeIntervalSince1970: Double(ts))
    187         }
    188 
    189         switch mkey {
    190         case .p:
    191             return t1.id().map({ .user(Pubkey($0), expiry) })
    192         case .t:
    193             return .hashtag(Hashtag(hashtag: t1.string()), expiry)
    194         case .word:
    195             return .word(t1.string(), expiry)
    196         case .e:
    197             guard let id = t1.id() else { return nil }
    198             return .thread(NoteId(id), expiry)
    199         }
    200     }
    201 }
    202