damus

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

SearchModel.swift (3851B)


      1 //
      2 //  Timeline.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2022-05-09.
      6 //
      7 
      8 import Foundation
      9 
     10 
     11 class SearchModel: ObservableObject {
     12     let state: DamusState
     13     var events: EventHolder
     14     @Published var loading: Bool = false
     15     
     16     var search: NostrFilter
     17     let sub_id = UUID().description
     18     let profiles_subid = UUID().description
     19     let limit: UInt32 = 500
     20     
     21     init(state: DamusState, search: NostrFilter) {
     22         self.state = state
     23         self.search = search
     24         self.events = EventHolder(on_queue: { ev in
     25             preload_events(state: state, events: [ev])
     26         })
     27     }
     28     
     29     func filter_muted()  {
     30         self.events.filter {
     31             should_show_event(state: state, ev: $0)
     32         }
     33         self.objectWillChange.send()
     34     }
     35     
     36     func subscribe() {
     37         // since 1 month
     38         search.limit = self.limit
     39         search.kinds = [.text, .like, .longform]
     40 
     41         //likes_filter.ids = ref_events.referenced_ids!
     42 
     43         print("subscribing to search '\(search)' with sub_id \(sub_id)")
     44         state.pool.register_handler(sub_id: sub_id, handler: handle_event)
     45         loading = true
     46         state.pool.send(.subscribe(.init(filters: [search], sub_id: sub_id)))
     47     }
     48     
     49     func unsubscribe() {
     50         state.pool.unsubscribe(sub_id: sub_id)
     51         loading = false
     52         print("unsubscribing from search '\(search)' with sub_id \(sub_id)")
     53     }
     54     
     55     func add_event(_ ev: NostrEvent) {
     56         if !event_matches_filter(ev, filter: search) {
     57             return
     58         }
     59         
     60         guard should_show_event(state: state, ev: ev) else {
     61             return
     62         }
     63         
     64         if self.events.insert(ev) {
     65             objectWillChange.send()
     66         }
     67     }
     68 
     69     func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
     70         let (sub_id, done) = handle_subid_event(pool: state.pool, relay_id: relay_id, ev: ev) { sub_id, ev in
     71             if ev.is_textlike && ev.should_show_event {
     72                 self.add_event(ev)
     73             }
     74         }
     75         
     76         guard done else {
     77             return
     78         }
     79         
     80         self.loading = false
     81         
     82         if sub_id == self.sub_id {
     83             guard let txn = NdbTxn(ndb: state.ndb) else { return }
     84             load_profiles(context: "search", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state, txn: txn)
     85         }
     86     }
     87 }
     88 
     89 func event_matches_hashtag(_ ev: NostrEvent, hashtags: [String]) -> Bool {
     90     for tag in ev.tags {
     91         if tag_is_hashtag(tag) && hashtags.contains(tag[1].string()) {
     92             return true
     93         }
     94     }
     95     return false
     96 }
     97 
     98 func tag_is_hashtag(_ tag: Tag) -> Bool {
     99     // "hashtag" is deprecated, will remove in the future
    100     return tag.count >= 2 && tag[0].matches_char("t")
    101 }
    102 
    103 func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
    104     if let hashtags = filter.hashtag {
    105         return event_matches_hashtag(ev, hashtags: hashtags)
    106     }
    107     return true
    108 }
    109 
    110 func handle_subid_event(pool: RelayPool, relay_id: RelayURL, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) {
    111     switch ev {
    112     case .ws_event:
    113         return (nil, false)
    114         
    115     case .nostr_event(let res):
    116         switch res {
    117         case .event(let ev_subid, let ev):
    118             handle(ev_subid, ev)
    119             return (ev_subid, false)
    120         
    121         case .ok:
    122             return (nil, false)
    123 
    124         case .notice(let note):
    125             if note.contains("Too many subscription filters") {
    126                 // TODO: resend filters?
    127                 pool.reconnect(to: [relay_id])
    128             }
    129             return (nil, false)
    130             
    131         case .eose(let subid):
    132             return (subid, true)
    133 
    134         case .auth:
    135             return (nil, false)
    136         }
    137     }
    138 }