ProfileModel.swift (5532B)
1 // 2 // ProfileModel.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-04-27. 6 // 7 8 import Foundation 9 10 class ProfileModel: ObservableObject, Equatable { 11 @Published var contacts: NostrEvent? = nil 12 @Published var following: Int = 0 13 @Published var relays: [RelayURL: RelayInfo]? = nil 14 @Published var progress: Int = 0 15 16 private let MAX_SHARE_RELAYS = 4 17 18 var events: EventHolder 19 let pubkey: Pubkey 20 let damus: DamusState 21 22 var seen_event: Set<NoteId> = Set() 23 var sub_id = UUID().description 24 var prof_subid = UUID().description 25 var findRelay_subid = UUID().description 26 27 init(pubkey: Pubkey, damus: DamusState) { 28 self.pubkey = pubkey 29 self.damus = damus 30 self.events = EventHolder(on_queue: { ev in 31 preload_events(state: damus, events: [ev]) 32 }) 33 } 34 35 func follows(pubkey: Pubkey) -> Bool { 36 guard let contacts = self.contacts else { 37 return false 38 } 39 40 return contacts.referenced_pubkeys.contains(pubkey) 41 } 42 43 func get_follow_target() -> FollowTarget { 44 if let contacts = contacts { 45 return .contact(contacts) 46 } 47 return .pubkey(pubkey) 48 } 49 50 static func == (lhs: ProfileModel, rhs: ProfileModel) -> Bool { 51 return lhs.pubkey == rhs.pubkey 52 } 53 54 func hash(into hasher: inout Hasher) { 55 hasher.combine(pubkey) 56 } 57 58 func unsubscribe() { 59 print("unsubscribing from profile \(pubkey) with sub_id \(sub_id)") 60 damus.pool.unsubscribe(sub_id: sub_id) 61 damus.pool.unsubscribe(sub_id: prof_subid) 62 } 63 64 func subscribe() { 65 var text_filter = NostrFilter(kinds: [.text, .longform]) 66 var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost]) 67 68 profile_filter.authors = [pubkey] 69 70 text_filter.authors = [pubkey] 71 text_filter.limit = 500 72 73 print("subscribing to profile \(pubkey) with sub_id \(sub_id)") 74 //print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]]) 75 damus.pool.subscribe(sub_id: sub_id, filters: [text_filter], handler: handle_event) 76 damus.pool.subscribe(sub_id: prof_subid, filters: [profile_filter], handler: handle_event) 77 } 78 79 func handle_profile_contact_event(_ ev: NostrEvent) { 80 process_contact_event(state: damus, ev: ev) 81 82 // only use new stuff 83 if let current_ev = self.contacts { 84 guard ev.created_at > current_ev.created_at else { 85 return 86 } 87 } 88 89 self.contacts = ev 90 self.following = count_pubkeys(ev.tags) 91 self.relays = decode_json_relays(ev.content) 92 } 93 94 func add_event(_ ev: NostrEvent) { 95 guard ev.should_show_event else { 96 return 97 } 98 99 if seen_event.contains(ev.id) { 100 return 101 } 102 if ev.is_textlike || ev.known_kind == .boost { 103 if self.events.insert(ev) { 104 self.objectWillChange.send() 105 } 106 } else if ev.known_kind == .contacts { 107 handle_profile_contact_event(ev) 108 } 109 seen_event.insert(ev.id) 110 } 111 112 private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { 113 switch ev { 114 case .ws_event: 115 return 116 case .nostr_event(let resp): 117 guard resp.subid == self.sub_id || resp.subid == self.prof_subid else { 118 return 119 } 120 switch resp { 121 case .ok: 122 break 123 case .event(_, let ev): 124 // Ensure the event public key matches this profiles public key 125 // This is done to protect against a relay not properly filtering events by the pubkey 126 // See https://github.com/damus-io/damus/issues/1846 for more information 127 guard self.pubkey == ev.pubkey else { break } 128 129 add_event(ev) 130 case .notice: 131 break 132 //notify(.notice, notice) 133 case .eose: 134 guard let txn = NdbTxn(ndb: damus.ndb) else { return } 135 if resp.subid == sub_id { 136 load_profiles(context: "profile", profiles_subid: prof_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus, txn: txn) 137 } 138 progress += 1 139 break 140 case .auth: 141 break 142 } 143 } 144 } 145 146 private func findRelaysHandler(relay_id: RelayURL, ev: NostrConnectionEvent) { 147 if case .nostr_event(let resp) = ev, case .event(_, let event) = resp, case .contacts = event.known_kind { 148 self.relays = decode_json_relays(event.content) 149 } 150 } 151 152 func subscribeToFindRelays() { 153 var profile_filter = NostrFilter(kinds: [.contacts]) 154 profile_filter.authors = [pubkey] 155 156 damus.pool.subscribe(sub_id: findRelay_subid, filters: [profile_filter], handler: findRelaysHandler) 157 } 158 159 func unsubscribeFindRelays() { 160 damus.pool.unsubscribe(sub_id: findRelay_subid) 161 } 162 163 func getCappedRelayStrings() -> [String] { 164 return relays?.keys.prefix(MAX_SHARE_RELAYS).map { $0.absoluteString } ?? [] 165 } 166 } 167 168 169 func count_pubkeys(_ tags: Tags) -> Int { 170 var c: Int = 0 171 for tag in tags { 172 if tag.count >= 2 && tag[0].matches_char("p") { 173 c += 1 174 } 175 } 176 177 return c 178 }