damus

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

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 }