From 6d9107f6622fba8b18abc580553241bc7b25f9e8 Mon Sep 17 00:00:00 2001 From: alltheseas <64376233+alltheseas@users.noreply.github.com> Date: Sat, 25 Oct 2025 08:46:11 -0500 Subject: [PATCH] Persist mute list across cold start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: https://github.com/damus-io/damus/issues/3389 Changelog-Fixed: Added more guards to prevent accidental overrides of the user's mutelist Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com> Co-authored-by: Daniel D’Aquino --- damus/ContentView.swift | 4 ++ .../Features/Profile/Views/ProfileView.swift | 1 - .../Features/Timeline/Models/HomeModel.swift | 53 ++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 88260424..b8516338 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -621,6 +621,10 @@ struct ContentView: View { return } + if ds.mutelist_manager.event == nil { + home.load_latest_mutelist_event_from_damus_state() + } + if ds.mutelist_manager.event == nil { confirm_overwrite_mutelist = true } else { diff --git a/damus/Features/Profile/Views/ProfileView.swift b/damus/Features/Profile/Views/ProfileView.swift index c5780113..df7ec41b 100644 --- a/damus/Features/Profile/Views/ProfileView.swift +++ b/damus/Features/Profile/Views/ProfileView.swift @@ -591,4 +591,3 @@ func check_nip05_validity(pubkey: Pubkey, damus_state: DamusState) { } } } - diff --git a/damus/Features/Timeline/Models/HomeModel.swift b/damus/Features/Timeline/Models/HomeModel.swift index 98929186..482f075b 100644 --- a/damus/Features/Timeline/Models/HomeModel.swift +++ b/damus/Features/Timeline/Models/HomeModel.swift @@ -41,6 +41,7 @@ enum HomeResubFilter { } } +@MainActor class HomeModel: ContactsDelegate, ObservableObject { // The maximum amount of contacts placed on a home feed subscription filter. // If the user has more contacts, chunking or other techniques will be used to avoid sending huge filters @@ -109,13 +110,16 @@ class HomeModel: ContactsDelegate, ObservableObject { // MARK: - Loading items from DamusState /// This is called whenever DamusState gets set. This function is used to load or setup anything we need from the new DamusState + @MainActor func load_our_stuff_from_damus_state() { self.load_latest_contact_event_from_damus_state() + self.load_latest_mutelist_event_from_damus_state() self.load_drafts_from_damus_state() } /// This loads the latest contact event we have on file from NostrDB. This should be called as soon as we get the new DamusState /// Loading the latest contact list event into our `Contacts` instance from storage is important to avoid getting into weird states when the network is unreliable or when relays delete such information + @MainActor func load_latest_contact_event_from_damus_state() { damus_state.contacts.delegate = self guard let latest_contact_event_id_hex = damus_state.settings.latest_contact_event_id_hex else { return } @@ -124,6 +128,53 @@ class HomeModel: ContactsDelegate, ObservableObject { process_contact_event(state: damus_state, ev: latest_contact_event) } + /// Loads the latest mute list event we have stored locally so that the mutelist manager is immediately aware of previous mutes. + @MainActor + func load_latest_mutelist_event_from_damus_state() { + if damus_state.mutelist_manager.event != nil { + return + } + + if let latest_event = load_latest_mutelist_event_from_db() { + damus_state.mutelist_manager.set_mutelist(latest_event) + return + } + + if let legacy_event = load_latest_legacy_mutelist_event_from_db() { + damus_state.mutelist_manager.set_mutelist(legacy_event) + } + } + + @MainActor + private func load_latest_mutelist_event_from_db(limit: Int = 5) -> NostrEvent? { + guard let filter = try? NdbFilter(from: NostrFilter(kinds: [.mute_list], limit: UInt32(limit), authors: [damus_state.pubkey])) else { return nil } + + guard let note_keys = try? damus_state.ndb.query(filters: [filter], maxResults: limit) else { return nil } + + var candidates: [NostrEvent] = [] + for key in note_keys { + guard let note = damus_state.ndb.lookup_note_by_key_and_copy(key) else { continue } + candidates.append(note) + } + return candidates.max(by: { $0.created_at < $1.created_at }) + } + + @MainActor + private func load_latest_legacy_mutelist_event_from_db(limit: Int = 20) -> NostrEvent? { + guard let filter = try? NdbFilter(from: NostrFilter(kinds: [.list_deprecated], limit: UInt32(limit), authors: [damus_state.pubkey])) else { return nil } + guard let note_keys = try? damus_state.ndb.query(filters: [filter], maxResults: limit) else { return nil } + + var candidates: [NostrEvent] = [] + for key in note_keys { + guard let note = damus_state.ndb.lookup_note_by_key_and_copy(key) else { continue } + if note.referenced_params.contains(where: { $0.param.matches_str("mute") }) { + candidates.append(note) + } + } + + return candidates.max(by: { $0.created_at < $1.created_at }) + } + func load_drafts_from_damus_state() { damus_state.drafts.load(from: damus_state) } @@ -706,7 +757,6 @@ class HomeModel: ContactsDelegate, ObservableObject { } damus_state.mutelist_manager.set_mutelist(ev) - migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state) } @@ -729,7 +779,6 @@ class HomeModel: ContactsDelegate, ObservableObject { } damus_state.mutelist_manager.set_mutelist(ev) - migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state) }