Files
damus/damus/Features/Search/Models/SearchModel.swift
Daniel D’Aquino 3290e1f9d2 Improve NostrNetworkManager interfaces
This commit improves NostrNetworkManager interfaces to be easier to use,
and with more options on how to read data from the Nostr network

This reduces the amount of duplicate logic in handling streams, and also
prevents possible common mistakes when using the standard subscribe method.

This fixes an issue with the mute list manager (which prompted for this
interface improvement, as the root cause is similar to other similar
issues).

Closes: https://github.com/damus-io/damus/issues/3221
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:02 -07:00

107 lines
2.8 KiB
Swift

//
// Timeline.swift
// damus
//
// Created by William Casarin on 2022-05-09.
//
import Foundation
class SearchModel: ObservableObject {
let state: DamusState
var events: EventHolder
@Published var loading: Bool = false
var search: NostrFilter
let profiles_subid = UUID().description
var listener: Task<Void, any Error>? = nil
let limit: UInt32 = 500
init(state: DamusState, search: NostrFilter) {
self.state = state
self.search = search
self.events = EventHolder(on_queue: { ev in
preload_events(state: state, events: [ev])
})
}
@MainActor
func filter_muted() {
self.events.filter {
should_show_event(state: state, ev: $0)
}
self.objectWillChange.send()
}
func subscribe() {
// since 1 month
search.limit = self.limit
search.kinds = [.text, .like, .longform, .highlight, .follow_list]
//likes_filter.ids = ref_events.referenced_ids!
listener?.cancel()
listener = Task {
DispatchQueue.main.async {
self.loading = true
}
print("subscribing to search")
try Task.checkCancellation()
let events = await state.nostrNetwork.reader.query(filters: [search])
for event in events {
if event.is_textlike && event.should_show_event {
await self.add_event(event)
}
}
guard let txn = NdbTxn(ndb: state.ndb) else { return }
try Task.checkCancellation()
load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn)
DispatchQueue.main.async {
self.loading = false
}
}
}
func unsubscribe() {
listener?.cancel()
listener = nil
}
@MainActor
func add_event(_ ev: NostrEvent) {
if !event_matches_filter(ev, filter: search) {
return
}
guard should_show_event(state: state, ev: ev) else {
return
}
if self.events.insert(ev) {
objectWillChange.send()
}
}
}
func event_matches_hashtag(_ ev: NostrEvent, hashtags: [String]) -> Bool {
for tag in ev.tags {
if tag_is_hashtag(tag) && hashtags.contains(tag[1].string()) {
return true
}
}
return false
}
func tag_is_hashtag(_ tag: Tag) -> Bool {
// "hashtag" is deprecated, will remove in the future
return tag.count >= 2 && tag[0].matches_char("t")
}
func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
if let hashtags = filter.hashtag {
return event_matches_hashtag(ev, hashtags: hashtags)
}
return true
}