damus

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

commit 379de6ff8e3ae921a92502fc1e98da57cf81f635
parent d2acf61e5a591c86630be730efa6b99d16ccba74
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Thu,  5 Sep 2024 20:08:51 -0700

Merge branch 'release_1.10'

Diffstat:
MDamusNotificationService/NotificationFormatter.swift | 6+++++-
MDamusNotificationService/NotificationService.swift | 22++++++++++++++++------
Mdamus/ContentView.swift | 2+-
Mdamus/Models/NotificationsManager.swift | 36+++++++++++++++++++++++-------------
Mdamus/Util/LocalNotification.swift | 17++++++++++++++++-
Mnostrdb/Ndb.swift | 4+++-
6 files changed, 64 insertions(+), 23 deletions(-)

diff --git a/DamusNotificationService/NotificationFormatter.swift b/DamusNotificationService/NotificationFormatter.swift @@ -55,6 +55,9 @@ struct NotificationFormatter { var identifier = "" switch notify.type { + case .tagged: + title = String(format: NSLocalizedString("Tagged by %@", comment: "Tagged by heading in local notification"), displayName) + identifier = "myMentionNotification" case .mention: title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName) identifier = "myMentionNotification" @@ -90,10 +93,11 @@ struct NotificationFormatter { // If it does not work, try async formatting methods let content = UNMutableNotificationContent() - + switch notify.type { case .zap, .profile_zap: guard let zap = await get_zap(from: notify.event, state: state) else { + Log.debug("format_message: async get_zap failed", for: .push_notifications) return nil } content.title = Self.zap_notification_title(zap) diff --git a/DamusNotificationService/NotificationService.swift b/DamusNotificationService/NotificationService.swift @@ -27,9 +27,9 @@ class NotificationService: UNNotificationServiceExtension { // Log that we got a push notification Log.debug("Got nostr event push notification from pubkey %s", for: .push_notifications, nostr_event.pubkey.hex()) - guard let state = NotificationExtensionState(), - let display_name = state.ndb.lookup_profile(nostr_event.pubkey)?.unsafeUnownedValue?.profile?.display_name // We are not holding the txn here. - else { + guard let state = NotificationExtensionState() else { + Log.debug("Failed to open nostrdb", for: .push_notifications) + // Something failed to initialize so let's go for the next best thing guard let improved_content = NotificationFormatter.shared.format_message(event: nostr_event) else { // We cannot format this nostr event. Suppress notification. @@ -39,7 +39,11 @@ class NotificationService: UNNotificationServiceExtension { contentHandler(improved_content) return } - + + let txn = state.ndb.lookup_profile(nostr_event.pubkey) + let profile = txn?.unsafeUnownedValue?.profile + let name = Profile.displayName(profile: profile, pubkey: nostr_event.pubkey).displayName + // Don't show notification details that match mute list. // TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block if state.mutelist_manager.is_event_muted(nostr_event) { @@ -54,6 +58,7 @@ class NotificationService: UNNotificationServiceExtension { } guard should_display_notification(state: state, event: nostr_event, mode: .push) else { + Log.debug("should_display_notification failed", for: .push_notifications) // We should not display notification for this event. Suppress notification. // contentHandler(UNNotificationContent()) // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification @@ -62,6 +67,7 @@ class NotificationService: UNNotificationServiceExtension { } guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else { + Log.debug("generate_local_notification_object failed", for: .push_notifications) // We could not process this notification. Probably an unsupported nostr event kind. Suppress. // contentHandler(UNNotificationContent()) // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification @@ -70,9 +76,13 @@ class NotificationService: UNNotificationServiceExtension { } Task { - if let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: display_name, notify: notification_object, state: state) { - contentHandler(improvedContent) + guard let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: name, notify: notification_object, state: state) else { + + Log.debug("NotificationFormatter.format_message failed", for: .push_notifications) + return } + + contentHandler(improvedContent) } } diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -722,7 +722,7 @@ struct ContentView: View { selected_timeline = .dms damus_state.dms.set_active_dm(target.pubkey) navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model)) - case .like, .zap, .mention, .repost, .reply: + case .like, .zap, .mention, .repost, .reply, .tagged: open_event(ev: target) case .profile_zap: break diff --git a/damus/Models/NotificationsManager.swift b/damus/Models/NotificationsManager.swift @@ -61,13 +61,15 @@ func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamu if type == .text, state.settings.mention_notification { let blocks = ev.blocks(state.keypair).blocks + for case .mention(let mention) in blocks { guard case .pubkey(let pk) = mention.ref, pk == state.keypair.pubkey else { continue } let content_preview = render_notification_content_preview(ev: ev, profiles: state.profiles, keypair: state.keypair) - return LocalNotification(type: .mention, event: ev, target: ev, content: content_preview) + return LocalNotification(type: .mention, event: ev, target: .note(ev), content: content_preview) } + if ev.referenced_ids.contains(where: { note_id in guard let note_author: Pubkey = state.ndb.lookup_note(note_id)?.unsafeUnownedValue?.pubkey else { return false } guard note_author == state.keypair.pubkey else { return false } @@ -75,31 +77,39 @@ func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamu }) { // This is a reply to one of our posts let content_preview = render_notification_content_preview(ev: ev, profiles: state.profiles, keypair: state.keypair) - return LocalNotification(type: .reply, event: ev, target: ev, content: content_preview) + return LocalNotification(type: .reply, event: ev, target: .note(ev), content: content_preview) + } + + if ev.referenced_pubkeys.contains(state.keypair.pubkey) { + // not mentioned or replied to, just tagged + let content_preview = render_notification_content_preview(ev: ev, profiles: state.profiles, keypair: state.keypair) + return LocalNotification(type: .tagged, event: ev, target: .note(ev), content: content_preview) } + } else if type == .boost, state.settings.repost_notification, let inner_ev = ev.get_inner_event() { let content_preview = render_notification_content_preview(ev: inner_ev, profiles: state.profiles, keypair: state.keypair) - return LocalNotification(type: .repost, event: ev, target: inner_ev, content: content_preview) - } else if type == .like, - state.settings.like_notification, - let evid = ev.referenced_ids.last, - let txn = state.ndb.lookup_note(evid, txn_name: "local_notification_like"), - let liked_event = txn.unsafeUnownedValue?.to_owned() - { - let content_preview = render_notification_content_preview(ev: liked_event, profiles: state.profiles, keypair: state.keypair) - return LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview) + return LocalNotification(type: .repost, event: ev, target: .note(inner_ev), content: content_preview) + } else if type == .like, state.settings.like_notification, let evid = ev.referenced_ids.last { + if let txn = state.ndb.lookup_note(evid, txn_name: "local_notification_like"), + let liked_event = txn.unsafeUnownedValue + { + let content_preview = render_notification_content_preview(ev: liked_event, profiles: state.profiles, keypair: state.keypair) + return LocalNotification(type: .like, event: ev, target: .note(liked_event), content: content_preview) + } else { + return LocalNotification(type: .like, event: ev, target: .note_id(evid), content: "") + } } else if type == .dm, state.settings.dm_notification { let convo = ev.decrypted(keypair: state.keypair) ?? NSLocalizedString("New encrypted direct message", comment: "Notification that the user has received a new direct message") - return LocalNotification(type: .dm, event: ev, target: ev, content: convo) + return LocalNotification(type: .dm, event: ev, target: .note(ev), content: convo) } else if type == .zap, state.settings.zap_notification { - return LocalNotification(type: .zap, event: ev, target: ev, content: ev.content) + return LocalNotification(type: .zap, event: ev, target: .note(ev), content: ev.content) } return nil diff --git a/damus/Util/LocalNotification.swift b/damus/Util/LocalNotification.swift @@ -48,10 +48,24 @@ struct LossyLocalNotification { } } +enum NotificationTarget { + case note(NostrEvent) + case note_id(NoteId) + + var id: NoteId { + switch self { + case .note(let note): + return note.id + case .note_id(let id): + return id + } + } +} + struct LocalNotification { let type: LocalNotificationType let event: NostrEvent - let target: NostrEvent + let target: NotificationTarget let content: String func to_lossy() -> LossyLocalNotification { @@ -64,6 +78,7 @@ enum LocalNotificationType: String { case like case mention case reply + case tagged case repost case zap case profile_zap diff --git a/nostrdb/Ndb.swift b/nostrdb/Ndb.swift @@ -111,8 +111,10 @@ class Ndb { var ok = false while !ok && mapsize > 1024 * 1024 * 700 { var cfg = ndb_config(flags: 0, ingester_threads: ingest_threads, mapsize: mapsize, filter_context: nil, ingest_filter: nil) - ok = ndb_init(&ndb_p, testdir, &cfg) != 0 + let res = ndb_init(&ndb_p, testdir, &cfg) + ok = res != 0; if !ok { + Log.error("ndb_init failed: %d, reducing mapsize from %d to %d", for: .storage, res, mapsize, mapsize / 2) mapsize /= 2 } }