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