diff --git a/IceCubesApp/IceCubesApp.entitlements b/IceCubesApp/IceCubesApp.entitlements index 34019c5a..cb44e4cc 100644 --- a/IceCubesApp/IceCubesApp.entitlements +++ b/IceCubesApp/IceCubesApp.entitlements @@ -8,6 +8,8 @@ app-usage + com.apple.developer.usernotifications.communication + com.apple.security.app-sandbox com.apple.security.application-groups diff --git a/IceCubesApp/Info.plist b/IceCubesApp/Info.plist index ce5b4fc9..996b6f26 100644 --- a/IceCubesApp/Info.plist +++ b/IceCubesApp/Info.plist @@ -17,6 +17,10 @@ ITSAppUsesNonExemptEncryption + NSUserActivityTypes + + INSendMessageIntent + UIBackgroundModes remote-notification diff --git a/IceCubesNotifications/Info.plist b/IceCubesNotifications/Info.plist index 57421ebf..63aff942 100644 --- a/IceCubesNotifications/Info.plist +++ b/IceCubesNotifications/Info.plist @@ -4,6 +4,13 @@ NSExtension + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + NSExtensionPointIdentifier com.apple.usernotifications.service NSExtensionPrincipalClass diff --git a/IceCubesNotifications/NotificationService.swift b/IceCubesNotifications/NotificationService.swift index 32193399..c878c309 100644 --- a/IceCubesNotifications/NotificationService.swift +++ b/IceCubesNotifications/NotificationService.swift @@ -5,6 +5,8 @@ import KeychainSwift import Models import UIKit import UserNotifications +import Intents +import Network @MainActor class NotificationService: UNNotificationServiceExtension { @@ -15,7 +17,7 @@ class NotificationService: UNNotificationServiceExtension { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - if let bestAttemptContent { + if var bestAttemptContent { let privateKey = PushNotificationsService.shared.notificationsPrivateKeyAsKey let auth = PushNotificationsService.shared.notificationsAuthKeyAsKey @@ -57,12 +59,13 @@ class NotificationService: UNNotificationServiceExtension { bestAttemptContent.body = notification.body.escape() bestAttemptContent.userInfo["plaintext"] = plaintextData bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "glass.caf")) + let preferences = UserPreferences.shared preferences.pushNotificationsCount += 1 bestAttemptContent.badge = .init(integerLiteral: preferences.pushNotificationsCount) - + if let urlString = notification.icon, let url = URL(string: urlString) { @@ -75,8 +78,16 @@ class NotificationService: UNNotificationServiceExtension { if let (data, _) = try? await URLSession.shared.data(for: .init(url: url)) { if let image = UIImage(data: data) { try? image.pngData()?.write(to: fileURL) - if let attachment = try? UNNotificationAttachment(identifier: filename, url: fileURL, options: nil) { - bestAttemptContent.attachments = [attachment] + + if let remoteNotification = await toRemoteNotification(localNotification: notification) { + let intent = buildMessageIntent(remoteNotification: remoteNotification, avatarURL: fileURL) + bestAttemptContent = try bestAttemptContent.updating(from: intent) as! UNMutableNotificationContent + let newBody = "\(bestAttemptContent.userInfo["i"] as? String ?? "") \n\(notification.title)\n\(notification.body.escape())" + bestAttemptContent.body = newBody + } else { + if let attachment = try? UNNotificationAttachment(identifier: filename, url: fileURL, options: nil) { + bestAttemptContent.attachments = [attachment] + } } } contentHandler(bestAttemptContent) @@ -89,4 +100,37 @@ class NotificationService: UNNotificationServiceExtension { } } } + + private func toRemoteNotification(localNotification: MastodonPushNotification) async -> Models.Notification? { + do { + if let account = AppAccountsManager.shared.availableAccounts.first(where: { $0.oauthToken?.accessToken == localNotification.accessToken }) { + let client = Client(server: account.server, oauthToken: account.oauthToken) + let remoteNotification: Models.Notification = try await client.get(endpoint: Notifications.notification(id: String(localNotification.notificationID))) + return remoteNotification + } + } catch { + return nil + } + return nil + } + + private func buildMessageIntent(remoteNotification: Models.Notification, avatarURL: URL) -> INSendMessageIntent { + let handle = INPersonHandle(value: remoteNotification.account.id, type: .unknown) + let avatar = INImage(url: avatarURL) + let sender = INPerson(personHandle: handle, + nameComponents: nil, + displayName: remoteNotification.account.safeDisplayName, + image: avatar, + contactIdentifier: nil, + customIdentifier: nil) + let intent = INSendMessageIntent(recipients: nil, + outgoingMessageType: .outgoingMessageText, + content: nil, + speakableGroupName: nil, + conversationIdentifier: remoteNotification.account.id, + serviceName: nil, + sender: sender, + attachments: nil) + return intent + } }