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 }