mirror of
https://github.com/Finb/Bark.git
synced 2025-12-08 21:36:01 +00:00
重构NotificationService
This commit is contained in:
parent
f509cbd5d4
commit
0238cacf5d
@ -126,6 +126,17 @@
|
|||||||
06C595362481160F006B98F3 /* BKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C595352481160F006B98F3 /* BKLabel.swift */; };
|
06C595362481160F006B98F3 /* BKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C595352481160F006B98F3 /* BKLabel.swift */; };
|
||||||
06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
06CF784721C7A50300A052D7 /* NotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CF784B21C7A51200A052D7 /* NotificationService.swift */; };
|
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CF784B21C7A51200A052D7 /* NotificationService.swift */; };
|
||||||
|
06E944682C06E40600AC86AB /* NotificationContentProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */; };
|
||||||
|
06E9446A2C06E4A200AC86AB /* CiphertextProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */; };
|
||||||
|
06E9446D2C06FEC900AC86AB /* LevelProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */; };
|
||||||
|
06E9446F2C06FF1E00AC86AB /* BadgeProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E9446E2C06FF1E00AC86AB /* BadgeProcessor.swift */; };
|
||||||
|
06E944712C06FF4C00AC86AB /* AutoCopyProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944702C06FF4C00AC86AB /* AutoCopyProcessor.swift */; };
|
||||||
|
06E944732C06FF9200AC86AB /* ArchiveProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944722C06FF9200AC86AB /* ArchiveProcessor.swift */; };
|
||||||
|
06E944752C07012E00AC86AB /* RealmConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944742C07012E00AC86AB /* RealmConfiguration.swift */; };
|
||||||
|
06E944762C07013000AC86AB /* RealmConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944742C07012E00AC86AB /* RealmConfiguration.swift */; };
|
||||||
|
06E944782C0701F300AC86AB /* IconProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944772C0701F300AC86AB /* IconProcessor.swift */; };
|
||||||
|
06E9447A2C0704E500AC86AB /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E944792C0704E500AC86AB /* ImageDownloader.swift */; };
|
||||||
|
06E9447C2C07052F00AC86AB /* ImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E9447B2C07052F00AC86AB /* ImageProcessor.swift */; };
|
||||||
06EE1FD326843E9300586708 /* BarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EE1FD226843E9300586708 /* BarkTests.swift */; };
|
06EE1FD326843E9300586708 /* BarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EE1FD226843E9300586708 /* BarkTests.swift */; };
|
||||||
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF332291CCFF400CA228A /* CryptoSettingController.swift */; };
|
06EEF333291CCFF400CA228A /* CryptoSettingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF332291CCFF400CA228A /* CryptoSettingController.swift */; };
|
||||||
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */; };
|
06EEF335291CD00000CA228A /* CryptoSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06EEF334291CD00000CA228A /* CryptoSettingViewModel.swift */; };
|
||||||
@ -305,6 +316,16 @@
|
|||||||
06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
06CF784021C7A50300A052D7 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
06CF784421C7A50300A052D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
06CF784421C7A50300A052D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
06CF784B21C7A51200A052D7 /* NotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
06CF784B21C7A51200A052D7 /* NotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||||
|
06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CiphertextProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E9446E2C06FF1E00AC86AB /* BadgeProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E944702C06FF4C00AC86AB /* AutoCopyProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCopyProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E944722C06FF9200AC86AB /* ArchiveProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchiveProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E944742C07012E00AC86AB /* RealmConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmConfiguration.swift; sourceTree = "<group>"; };
|
||||||
|
06E944772C0701F300AC86AB /* IconProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconProcessor.swift; sourceTree = "<group>"; };
|
||||||
|
06E944792C0704E500AC86AB /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
|
||||||
|
06E9447B2C07052F00AC86AB /* ImageProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessor.swift; sourceTree = "<group>"; };
|
||||||
06EE1FD026843E9300586708 /* BarkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BarkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
06EE1FD026843E9300586708 /* BarkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BarkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
06EE1FD226843E9300586708 /* BarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarkTests.swift; sourceTree = "<group>"; };
|
06EE1FD226843E9300586708 /* BarkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarkTests.swift; sourceTree = "<group>"; };
|
||||||
06EE1FD426843E9300586708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
06EE1FD426843E9300586708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@ -512,6 +533,7 @@
|
|||||||
0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */,
|
0653677729B727A60038BDB8 /* CryptoSettingRelay.swift */,
|
||||||
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */,
|
06F08EA629B1DDFE006AB9CA /* Error+Extension.swift */,
|
||||||
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */,
|
06F08EAB29B1DECD006AB9CA /* NSLocalizedString+Extension.swift */,
|
||||||
|
06E944742C07012E00AC86AB /* RealmConfiguration.swift */,
|
||||||
);
|
);
|
||||||
path = Common;
|
path = Common;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -571,6 +593,7 @@
|
|||||||
06CF784121C7A50300A052D7 /* NotificationServiceExtension */ = {
|
06CF784121C7A50300A052D7 /* NotificationServiceExtension */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
06E9446B2C06E4A800AC86AB /* Processor */,
|
||||||
06B11590247BBC15006D91FB /* NotificationServiceExtension.entitlements */,
|
06B11590247BBC15006D91FB /* NotificationServiceExtension.entitlements */,
|
||||||
06CF784B21C7A51200A052D7 /* NotificationService.swift */,
|
06CF784B21C7A51200A052D7 /* NotificationService.swift */,
|
||||||
06CF784421C7A50300A052D7 /* Info.plist */,
|
06CF784421C7A50300A052D7 /* Info.plist */,
|
||||||
@ -578,6 +601,22 @@
|
|||||||
path = NotificationServiceExtension;
|
path = NotificationServiceExtension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
06E9446B2C06E4A800AC86AB /* Processor */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
06E944672C06E40600AC86AB /* NotificationContentProcessor.swift */,
|
||||||
|
06E944692C06E4A200AC86AB /* CiphertextProcessor.swift */,
|
||||||
|
06E9446C2C06FEC900AC86AB /* LevelProcessor.swift */,
|
||||||
|
06E9446E2C06FF1E00AC86AB /* BadgeProcessor.swift */,
|
||||||
|
06E944702C06FF4C00AC86AB /* AutoCopyProcessor.swift */,
|
||||||
|
06E944722C06FF9200AC86AB /* ArchiveProcessor.swift */,
|
||||||
|
06E944772C0701F300AC86AB /* IconProcessor.swift */,
|
||||||
|
06E9447B2C07052F00AC86AB /* ImageProcessor.swift */,
|
||||||
|
06E944792C0704E500AC86AB /* ImageDownloader.swift */,
|
||||||
|
);
|
||||||
|
path = Processor;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
06EE1FD126843E9300586708 /* BarkTests */ = {
|
06EE1FD126843E9300586708 /* BarkTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -981,6 +1020,7 @@
|
|||||||
0667D194247D1BA0005DE2ED /* Date+Extension.swift in Sources */,
|
0667D194247D1BA0005DE2ED /* Date+Extension.swift in Sources */,
|
||||||
0604F7DF20620D4900B32F09 /* ServerManager.swift in Sources */,
|
0604F7DF20620D4900B32F09 /* ServerManager.swift in Sources */,
|
||||||
0667D192247D162C005DE2ED /* MessageTableViewCell.swift in Sources */,
|
0667D192247D162C005DE2ED /* MessageTableViewCell.swift in Sources */,
|
||||||
|
06E944762C07013000AC86AB /* RealmConfiguration.swift in Sources */,
|
||||||
0603706720E1E31600F4CA05 /* Defines.swift in Sources */,
|
0603706720E1E31600F4CA05 /* Defines.swift in Sources */,
|
||||||
06787C3B2AB82BDB008ABDD7 /* CrashReportViewController.swift in Sources */,
|
06787C3B2AB82BDB008ABDD7 /* CrashReportViewController.swift in Sources */,
|
||||||
064CAB9E256BE9090018155C /* PreviewCardCellViewModel.swift in Sources */,
|
064CAB9E256BE9090018155C /* PreviewCardCellViewModel.swift in Sources */,
|
||||||
@ -1015,12 +1055,22 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
06E944752C07012E00AC86AB /* RealmConfiguration.swift in Sources */,
|
||||||
|
06E944732C06FF9200AC86AB /* ArchiveProcessor.swift in Sources */,
|
||||||
|
06E9447A2C0704E500AC86AB /* ImageDownloader.swift in Sources */,
|
||||||
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */,
|
06CF784C21C7A51200A052D7 /* NotificationService.swift in Sources */,
|
||||||
06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
|
06F08EAD29B1DED6006AB9CA /* NSLocalizedString+Extension.swift in Sources */,
|
||||||
0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */,
|
0653677629B719BC0038BDB8 /* CryptoSettingManager.swift in Sources */,
|
||||||
|
06E9446F2C06FF1E00AC86AB /* BadgeProcessor.swift in Sources */,
|
||||||
|
06E9446D2C06FEC900AC86AB /* LevelProcessor.swift in Sources */,
|
||||||
|
06E944682C06E40600AC86AB /* NotificationContentProcessor.swift in Sources */,
|
||||||
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */,
|
06F08EA529B1DDA7006AB9CA /* Algorithm.swift in Sources */,
|
||||||
|
06E944782C0701F300AC86AB /* IconProcessor.swift in Sources */,
|
||||||
06BBB89125650CCF0076F63E /* ArchiveSettingManager.swift in Sources */,
|
06BBB89125650CCF0076F63E /* ArchiveSettingManager.swift in Sources */,
|
||||||
06B11591247BC132006D91FB /* Message.swift in Sources */,
|
06B11591247BC132006D91FB /* Message.swift in Sources */,
|
||||||
|
06E9447C2C07052F00AC86AB /* ImageProcessor.swift in Sources */,
|
||||||
|
06E9446A2C06E4A200AC86AB /* CiphertextProcessor.swift in Sources */,
|
||||||
|
06E944712C06FF4C00AC86AB /* AutoCopyProcessor.swift in Sources */,
|
||||||
06F08EA829B1DE0A006AB9CA /* Error+Extension.swift in Sources */,
|
06F08EA829B1DE0A006AB9CA /* Error+Extension.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import CrashReporter
|
|||||||
import IceCream
|
import IceCream
|
||||||
import IQKeyboardManagerSwift
|
import IQKeyboardManagerSwift
|
||||||
import Material
|
import Material
|
||||||
import RealmSwift
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
@ -20,22 +19,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
var syncEngine: SyncEngine?
|
var syncEngine: SyncEngine?
|
||||||
func setupRealm() {
|
func setupRealm() {
|
||||||
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
|
|
||||||
let fileUrl = groupUrl?.appendingPathComponent("bark.realm")
|
|
||||||
let config = Realm.Configuration(
|
|
||||||
fileURL: fileUrl,
|
|
||||||
schemaVersion: 13,
|
|
||||||
migrationBlock: { _, oldSchemaVersion in
|
|
||||||
// We haven’t migrated anything yet, so oldSchemaVersion == 0
|
|
||||||
if oldSchemaVersion < 1 {
|
|
||||||
// Nothing to do!
|
|
||||||
// Realm will automatically detect new properties and removed properties
|
|
||||||
// And will update the schema on disk automatically
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
// Tell Realm to use this new configuration object for the default Realm
|
// Tell Realm to use this new configuration object for the default Realm
|
||||||
Realm.Configuration.defaultConfiguration = config
|
Realm.Configuration.defaultConfiguration = kRealmDefaultConfiguration
|
||||||
|
|
||||||
// iCloud 同步
|
// iCloud 同步
|
||||||
syncEngine = SyncEngine(objects: [
|
syncEngine = SyncEngine(objects: [
|
||||||
|
|||||||
28
Common/RealmConfiguration.swift
Normal file
28
Common/RealmConfiguration.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// RealmConfiguration.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
@_exported import RealmSwift
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
let kRealmDefaultConfiguration = {
|
||||||
|
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
|
||||||
|
let fileUrl = groupUrl?.appendingPathComponent("bark.realm")
|
||||||
|
let config = Realm.Configuration(
|
||||||
|
fileURL: fileUrl,
|
||||||
|
schemaVersion: 13,
|
||||||
|
migrationBlock: { _, oldSchemaVersion in
|
||||||
|
// We haven’t migrated anything yet, so oldSchemaVersion == 0
|
||||||
|
if oldSchemaVersion < 1 {
|
||||||
|
// Nothing to do!
|
||||||
|
// Realm will automatically detect new properties and removed properties
|
||||||
|
// And will update the schema on disk automatically
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
}()
|
||||||
@ -6,334 +6,36 @@
|
|||||||
// Copyright © 2018 Fin. All rights reserved.
|
// Copyright © 2018 Fin. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Intents
|
|
||||||
import Kingfisher
|
|
||||||
import MobileCoreServices
|
|
||||||
import RealmSwift
|
|
||||||
import SwiftyJSON
|
|
||||||
import UIKit
|
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
class NotificationService: UNNotificationServiceExtension {
|
class NotificationService: UNNotificationServiceExtension {
|
||||||
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
lazy var realm: Realm? = {
|
Task {
|
||||||
let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark")
|
guard var bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
|
||||||
let fileUrl = groupUrl?.appendingPathComponent("bark.realm")
|
contentHandler(request.content)
|
||||||
let config = Realm.Configuration(
|
|
||||||
fileURL: fileUrl,
|
|
||||||
schemaVersion: 13,
|
|
||||||
migrationBlock: { _, oldSchemaVersion in
|
|
||||||
// We haven’t migrated anything yet, so oldSchemaVersion == 0
|
|
||||||
if oldSchemaVersion < 1 {
|
|
||||||
// Nothing to do!
|
|
||||||
// Realm will automatically detect new properties and removed properties
|
|
||||||
// And will update the schema on disk automatically
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tell Realm to use this new configuration object for the default Realm
|
|
||||||
Realm.Configuration.defaultConfiguration = config
|
|
||||||
return try? Realm()
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// 自动保存推送
|
|
||||||
/// - Parameters:
|
|
||||||
/// - userInfo: 推送参数
|
|
||||||
/// - bestAttemptContentBody: 推送body,如果用户`没有指定要复制的值` ,默认复制 `推送正文`
|
|
||||||
fileprivate func autoCopy(_ userInfo: [AnyHashable: Any], defaultCopy: String) {
|
|
||||||
if userInfo["autocopy"] as? String == "1"
|
|
||||||
|| userInfo["automaticallycopy"] as? String == "1"
|
|
||||||
{
|
|
||||||
if let copy = userInfo["copy"] as? String {
|
|
||||||
UIPasteboard.general.string = copy
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
UIPasteboard.general.string = defaultCopy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 保存推送
|
|
||||||
/// - Parameter userInfo: 推送参数
|
|
||||||
/// 如果用户携带了 `isarchive` 参数,则以 `isarchive` 参数值为准
|
|
||||||
/// 否则,以用户`应用内设置`为准
|
|
||||||
fileprivate func archive(_ userInfo: [AnyHashable: Any]) {
|
|
||||||
var isArchive: Bool?
|
|
||||||
if let archive = userInfo["isarchive"] as? String {
|
|
||||||
isArchive = archive == "1" ? true : false
|
|
||||||
}
|
|
||||||
if isArchive == nil {
|
|
||||||
isArchive = ArchiveSettingManager.shared.isArchive
|
|
||||||
}
|
|
||||||
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
|
|
||||||
let title = alert?["title"] as? String
|
|
||||||
let body = alert?["body"] as? String
|
|
||||||
|
|
||||||
let url = userInfo["url"] as? String
|
|
||||||
let group = userInfo["group"] as? String
|
|
||||||
|
|
||||||
if isArchive == true {
|
|
||||||
try? realm?.write {
|
|
||||||
let message = Message()
|
|
||||||
message.title = title
|
|
||||||
message.body = body
|
|
||||||
message.url = url
|
|
||||||
message.group = group
|
|
||||||
message.createDate = Date()
|
|
||||||
realm?.add(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 保存图片到缓存中
|
|
||||||
/// - Parameters:
|
|
||||||
/// - cache: 使用的缓存
|
|
||||||
/// - data: 图片 Data 数据
|
|
||||||
/// - key: 缓存 Key
|
|
||||||
func storeImage(cache: ImageCache, data: Data, key: String) async {
|
|
||||||
return await withCheckedContinuation { continuation in
|
|
||||||
cache.storeToDisk(data, forKey: key, expiration: StorageExpiration.never) { _ in
|
|
||||||
continuation.resume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 使用 Kingfisher.ImageDownloader 下载图片
|
|
||||||
/// - Parameter url: 下载的图片URL
|
|
||||||
/// - Returns: 返回 Result
|
|
||||||
func downloadImage(url: URL) async -> Result<ImageLoadingResult, KingfisherError> {
|
|
||||||
return await withCheckedContinuation { continuation in
|
|
||||||
Kingfisher.ImageDownloader.default.downloadImage(with: url, options: nil) { result in
|
|
||||||
continuation.resume(returning: result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 下载推送图片
|
|
||||||
/// - Parameter imageUrl: 图片URL字符串
|
|
||||||
/// - Returns: 保存在本地中的`图片 File URL`
|
|
||||||
fileprivate func downloadImage(_ imageUrl: String) async -> String? {
|
|
||||||
guard let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark"),
|
|
||||||
let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl),
|
|
||||||
let imageResource = URL(string: imageUrl)
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先查看图片缓存
|
|
||||||
if cache.diskStorage.isCached(forKey: imageResource.cacheKey) {
|
|
||||||
return cache.cachePath(forKey: imageResource.cacheKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载图片
|
|
||||||
guard let result = try? await downloadImage(url: imageResource).get() else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// 缓存图片
|
|
||||||
await storeImage(cache: cache, data: result.originalData, key: imageResource.cacheKey)
|
|
||||||
|
|
||||||
return cache.cachePath(forKey: imageResource.cacheKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 为 Notification Content 设置图片
|
|
||||||
/// - Parameter bestAttemptContent: 要设置的 Notification Content
|
|
||||||
/// - Returns: 返回设置图片后的 Notification Content
|
|
||||||
fileprivate func setImage(content bestAttemptContent: UNMutableNotificationContent) async -> UNMutableNotificationContent {
|
|
||||||
let userInfo = bestAttemptContent.userInfo
|
|
||||||
guard let imageUrl = userInfo["image"] as? String,
|
|
||||||
let imageFileUrl = await downloadImage(imageUrl)
|
|
||||||
else {
|
|
||||||
return bestAttemptContent
|
|
||||||
}
|
|
||||||
|
|
||||||
let copyDestUrl = URL(fileURLWithPath: imageFileUrl).appendingPathExtension(".tmp")
|
|
||||||
// 将图片缓存复制一份,推送使用完后会自动删除,但图片缓存需要留着以后在历史记录里查看
|
|
||||||
try? FileManager.default.copyItem(
|
|
||||||
at: URL(fileURLWithPath: imageFileUrl),
|
|
||||||
to: copyDestUrl
|
|
||||||
)
|
|
||||||
|
|
||||||
if let attachment = try? UNNotificationAttachment(
|
|
||||||
identifier: "image",
|
|
||||||
url: copyDestUrl,
|
|
||||||
options: [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]
|
|
||||||
) {
|
|
||||||
bestAttemptContent.attachments = [attachment]
|
|
||||||
}
|
|
||||||
return bestAttemptContent
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 为 Notification Content 设置ICON
|
|
||||||
/// - Parameter bestAttemptContent: 要设置的 Notification Content
|
|
||||||
/// - Returns: 返回设置ICON后的 Notification Content
|
|
||||||
fileprivate func setIcon(content bestAttemptContent: UNMutableNotificationContent) async -> UNMutableNotificationContent {
|
|
||||||
if #available(iOSApplicationExtension 15.0, *) {
|
|
||||||
|
|
||||||
let userInfo = bestAttemptContent.userInfo
|
|
||||||
|
|
||||||
guard let imageUrl = userInfo["icon"] as? String,
|
|
||||||
let imageFileUrl = await downloadImage(imageUrl)
|
|
||||||
else {
|
|
||||||
return bestAttemptContent
|
|
||||||
}
|
|
||||||
|
|
||||||
var personNameComponents = PersonNameComponents()
|
|
||||||
personNameComponents.nickname = bestAttemptContent.title
|
|
||||||
|
|
||||||
let avatar = INImage(imageData: NSData(contentsOfFile: imageFileUrl)! as Data)
|
|
||||||
let senderPerson = INPerson(
|
|
||||||
personHandle: INPersonHandle(value: "", type: .unknown),
|
|
||||||
nameComponents: personNameComponents,
|
|
||||||
displayName: personNameComponents.nickname,
|
|
||||||
image: avatar,
|
|
||||||
contactIdentifier: nil,
|
|
||||||
customIdentifier: nil,
|
|
||||||
isMe: false,
|
|
||||||
suggestionType: .none
|
|
||||||
)
|
|
||||||
let mePerson = INPerson(
|
|
||||||
personHandle: INPersonHandle(value: "", type: .unknown),
|
|
||||||
nameComponents: nil,
|
|
||||||
displayName: nil,
|
|
||||||
image: nil,
|
|
||||||
contactIdentifier: nil,
|
|
||||||
customIdentifier: nil,
|
|
||||||
isMe: true,
|
|
||||||
suggestionType: .none
|
|
||||||
)
|
|
||||||
|
|
||||||
let intent = INSendMessageIntent(
|
|
||||||
recipients: [mePerson],
|
|
||||||
outgoingMessageType: .outgoingMessageText,
|
|
||||||
content: bestAttemptContent.body,
|
|
||||||
speakableGroupName: INSpeakableString(spokenPhrase: personNameComponents.nickname ?? ""),
|
|
||||||
conversationIdentifier: bestAttemptContent.threadIdentifier,
|
|
||||||
serviceName: nil,
|
|
||||||
sender: senderPerson,
|
|
||||||
attachments: nil
|
|
||||||
)
|
|
||||||
|
|
||||||
intent.setImage(avatar, forParameterNamed: \.sender)
|
|
||||||
|
|
||||||
let interaction = INInteraction(intent: intent, response: nil)
|
|
||||||
interaction.direction = .incoming
|
|
||||||
|
|
||||||
try? await interaction.donate()
|
|
||||||
|
|
||||||
do {
|
|
||||||
let content = try bestAttemptContent.updating(from: intent) as! UNMutableNotificationContent
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
catch {}
|
|
||||||
|
|
||||||
return bestAttemptContent
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return bestAttemptContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decrypt(ciphertext: String, iv: String? = nil) throws -> [String: Any] {
|
|
||||||
guard var fields = CryptoSettingManager.shared.fields else {
|
|
||||||
throw "No encryption key set"
|
|
||||||
}
|
|
||||||
if let iv = iv {
|
|
||||||
// Support using specified IV parameter for decryption
|
|
||||||
fields.iv = iv
|
|
||||||
}
|
|
||||||
|
|
||||||
let aes = try AESCryptoModel(cryptoFields: fields)
|
|
||||||
|
|
||||||
let json = try aes.decrypt(ciphertext: ciphertext)
|
|
||||||
|
|
||||||
guard let data = json.data(using: .utf8), let map = JSON(data).dictionaryObject else {
|
|
||||||
throw "JSON parsing failed"
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> ()) {
|
|
||||||
guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
|
|
||||||
contentHandler(request.content)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var userInfo = bestAttemptContent.userInfo
|
|
||||||
// 如果是加密推送,则使用密文配置 bestAttemptContent
|
|
||||||
if let ciphertext = userInfo["ciphertext"] as? String {
|
|
||||||
do {
|
|
||||||
var map = try decrypt(ciphertext: ciphertext, iv: userInfo["iv"] as? String)
|
|
||||||
for (key, val) in map {
|
|
||||||
// 将key重写为小写
|
|
||||||
map[key.lowercased()] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
var alert = [String: Any]()
|
|
||||||
if let title = map["title"] as? String {
|
|
||||||
bestAttemptContent.title = title
|
|
||||||
alert["title"] = title
|
|
||||||
}
|
|
||||||
if let body = map["body"] as? String {
|
|
||||||
bestAttemptContent.body = body
|
|
||||||
alert["body"] = body
|
|
||||||
}
|
|
||||||
if let group = map["group"] as? String {
|
|
||||||
bestAttemptContent.threadIdentifier = group
|
|
||||||
}
|
|
||||||
if var sound = map["sound"] as? String {
|
|
||||||
if !sound.hasSuffix(".caf") {
|
|
||||||
sound = "\(sound).caf"
|
|
||||||
}
|
|
||||||
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: sound))
|
|
||||||
}
|
|
||||||
if let badge = map["badge"] as? Int {
|
|
||||||
bestAttemptContent.badge = badge as NSNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
map["aps"] = ["alert": alert]
|
|
||||||
userInfo = map
|
|
||||||
bestAttemptContent.userInfo = userInfo
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
bestAttemptContent.body = "Decryption Failed"
|
|
||||||
bestAttemptContent.userInfo = ["aps": ["alert": ["body": bestAttemptContent.body]]]
|
|
||||||
contentHandler(bestAttemptContent)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let processors: [NotificationContentProcessorItem] = [
|
||||||
// 通知中断级别
|
.ciphertext,
|
||||||
if #available(iOSApplicationExtension 15.0, *) {
|
.level,
|
||||||
if let level = userInfo["level"] as? String {
|
.badge,
|
||||||
let interruptionLevels: [String: UNNotificationInterruptionLevel] = [
|
.autoCopy,
|
||||||
"passive": UNNotificationInterruptionLevel.passive,
|
.archive,
|
||||||
"active": UNNotificationInterruptionLevel.active,
|
.setIcon,
|
||||||
"timeSensitive": UNNotificationInterruptionLevel.timeSensitive,
|
.setImage
|
||||||
"timesensitive": UNNotificationInterruptionLevel.timeSensitive,
|
]
|
||||||
"critical": UNNotificationInterruptionLevel.critical,
|
|
||||||
]
|
for item in processors {
|
||||||
bestAttemptContent.interruptionLevel = interruptionLevels[level] ?? .active
|
do {
|
||||||
|
bestAttemptContent = try await item.processor.process(content: bestAttemptContent)
|
||||||
|
} catch NotificationContentProcessorError.error(let content) {
|
||||||
|
contentHandler(content)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
contentHandler(bestAttemptContent)
|
||||||
// 通知角标
|
|
||||||
if let badgeStr = userInfo["badge"] as? String, let badge = Int(badgeStr) {
|
|
||||||
bestAttemptContent.badge = NSNumber(value: badge)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动复制
|
|
||||||
autoCopy(userInfo, defaultCopy: bestAttemptContent.body)
|
|
||||||
|
|
||||||
// 保存推送
|
|
||||||
archive(userInfo)
|
|
||||||
|
|
||||||
Task.init {
|
|
||||||
// 设置推送图标
|
|
||||||
let iconResult = await setIcon(content: bestAttemptContent)
|
|
||||||
// 设置推送图片
|
|
||||||
let imageResult = await self.setImage(content: iconResult)
|
|
||||||
contentHandler(imageResult)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// ArchiveProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RealmSwift
|
||||||
|
|
||||||
|
class ArchiveProcessor: NotificationContentProcessor {
|
||||||
|
private lazy var realm: Realm? = {
|
||||||
|
Realm.Configuration.defaultConfiguration = kRealmDefaultConfiguration
|
||||||
|
return try? Realm()
|
||||||
|
}()
|
||||||
|
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
let userInfo = bestAttemptContent.userInfo
|
||||||
|
|
||||||
|
var isArchive: Bool = ArchiveSettingManager.shared.isArchive
|
||||||
|
if let archive = userInfo["isarchive"] as? String {
|
||||||
|
isArchive = archive == "1" ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isArchive {
|
||||||
|
let alert = (userInfo["aps"] as? [String: Any])?["alert"] as? [String: Any]
|
||||||
|
let title = alert?["title"] as? String
|
||||||
|
let body = alert?["body"] as? String
|
||||||
|
let url = userInfo["url"] as? String
|
||||||
|
let group = userInfo["group"] as? String
|
||||||
|
|
||||||
|
try? realm?.write {
|
||||||
|
let message = Message()
|
||||||
|
message.title = title
|
||||||
|
message.body = body
|
||||||
|
message.url = url
|
||||||
|
message.group = group
|
||||||
|
message.createDate = Date()
|
||||||
|
realm?.add(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// AutoCopyProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class AutoCopyProcessor: NotificationContentProcessor {
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
let userInfo = bestAttemptContent.userInfo
|
||||||
|
if userInfo["autocopy"] as? String == "1"
|
||||||
|
|| userInfo["automaticallycopy"] as? String == "1"
|
||||||
|
{
|
||||||
|
if let copy = userInfo["copy"] as? String {
|
||||||
|
UIPasteboard.general.string = copy
|
||||||
|
} else {
|
||||||
|
UIPasteboard.general.string = bestAttemptContent.body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
}
|
||||||
19
NotificationServiceExtension/Processor/BadgeProcessor.swift
Normal file
19
NotificationServiceExtension/Processor/BadgeProcessor.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// BadgeProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// 通知角标
|
||||||
|
class BadgeProcessor: NotificationContentProcessor {
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
if let badgeStr = bestAttemptContent.userInfo["badge"] as? String, let badge = Int(badgeStr) {
|
||||||
|
bestAttemptContent.badge = NSNumber(value: badge)
|
||||||
|
}
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// CiphertextProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
/// 加密推送
|
||||||
|
class CiphertextProcessor: NotificationContentProcessor {
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
var userInfo = bestAttemptContent.userInfo
|
||||||
|
guard let ciphertext = userInfo["ciphertext"] as? String else {
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是加密推送,则使用密文配置 bestAttemptContent
|
||||||
|
do {
|
||||||
|
var map = try decrypt(ciphertext: ciphertext, iv: userInfo["iv"] as? String)
|
||||||
|
|
||||||
|
var alert = [String: Any]()
|
||||||
|
if let title = map["title"] as? String {
|
||||||
|
bestAttemptContent.title = title
|
||||||
|
alert["title"] = title
|
||||||
|
}
|
||||||
|
if let body = map["body"] as? String {
|
||||||
|
bestAttemptContent.body = body
|
||||||
|
alert["body"] = body
|
||||||
|
}
|
||||||
|
if let group = map["group"] as? String {
|
||||||
|
bestAttemptContent.threadIdentifier = group
|
||||||
|
}
|
||||||
|
if var sound = map["sound"] as? String {
|
||||||
|
if !sound.hasSuffix(".caf") {
|
||||||
|
sound = "\(sound).caf"
|
||||||
|
}
|
||||||
|
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: sound))
|
||||||
|
}
|
||||||
|
if let badge = map["badge"] as? Int {
|
||||||
|
bestAttemptContent.badge = badge as NSNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
map["aps"] = ["alert": alert]
|
||||||
|
userInfo = map
|
||||||
|
bestAttemptContent.userInfo = userInfo
|
||||||
|
return bestAttemptContent
|
||||||
|
} catch {
|
||||||
|
bestAttemptContent.body = "Decryption Failed"
|
||||||
|
bestAttemptContent.userInfo = ["aps": ["alert": ["body": bestAttemptContent.body]]]
|
||||||
|
throw NotificationContentProcessorError.error(content: bestAttemptContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解密文本
|
||||||
|
/// - Parameters:
|
||||||
|
/// - ciphertext: 密文
|
||||||
|
/// - iv: iv 如果不传就用配置保存的,传了就以传的 iv 为准
|
||||||
|
/// - Returns: 解密后的 json 数据
|
||||||
|
private func decrypt(ciphertext: String, iv: String? = nil) throws -> [AnyHashable: Any] {
|
||||||
|
guard var fields = CryptoSettingManager.shared.fields else {
|
||||||
|
throw "No encryption key set"
|
||||||
|
}
|
||||||
|
if let iv = iv {
|
||||||
|
// Support using specified IV parameter for decryption
|
||||||
|
fields.iv = iv
|
||||||
|
}
|
||||||
|
|
||||||
|
let aes = try AESCryptoModel(cryptoFields: fields)
|
||||||
|
|
||||||
|
let json = try aes.decrypt(ciphertext: ciphertext)
|
||||||
|
|
||||||
|
guard let data = json.data(using: .utf8), let map = JSON(data).dictionaryObject else {
|
||||||
|
throw "JSON parsing failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
var result: [AnyHashable: Any] = [:]
|
||||||
|
for (key, val) in map {
|
||||||
|
// 将key重写为小写
|
||||||
|
result[key.lowercased()] = val
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
75
NotificationServiceExtension/Processor/IconProcessor.swift
Normal file
75
NotificationServiceExtension/Processor/IconProcessor.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// IconProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Intents
|
||||||
|
|
||||||
|
class IconProcessor: NotificationContentProcessor {
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
if #available(iOSApplicationExtension 15.0, *) {
|
||||||
|
let userInfo = bestAttemptContent.userInfo
|
||||||
|
|
||||||
|
guard let imageUrl = userInfo["icon"] as? String,
|
||||||
|
let imageFileUrl = await ImageDownloader.downloadImage(imageUrl)
|
||||||
|
else {
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
|
||||||
|
var personNameComponents = PersonNameComponents()
|
||||||
|
personNameComponents.nickname = bestAttemptContent.title
|
||||||
|
|
||||||
|
let avatar = INImage(imageData: NSData(contentsOfFile: imageFileUrl)! as Data)
|
||||||
|
let senderPerson = INPerson(
|
||||||
|
personHandle: INPersonHandle(value: "", type: .unknown),
|
||||||
|
nameComponents: personNameComponents,
|
||||||
|
displayName: personNameComponents.nickname,
|
||||||
|
image: avatar,
|
||||||
|
contactIdentifier: nil,
|
||||||
|
customIdentifier: nil,
|
||||||
|
isMe: false,
|
||||||
|
suggestionType: .none
|
||||||
|
)
|
||||||
|
let mePerson = INPerson(
|
||||||
|
personHandle: INPersonHandle(value: "", type: .unknown),
|
||||||
|
nameComponents: nil,
|
||||||
|
displayName: nil,
|
||||||
|
image: nil,
|
||||||
|
contactIdentifier: nil,
|
||||||
|
customIdentifier: nil,
|
||||||
|
isMe: true,
|
||||||
|
suggestionType: .none
|
||||||
|
)
|
||||||
|
|
||||||
|
let intent = INSendMessageIntent(
|
||||||
|
recipients: [mePerson],
|
||||||
|
outgoingMessageType: .outgoingMessageText,
|
||||||
|
content: bestAttemptContent.body,
|
||||||
|
speakableGroupName: INSpeakableString(spokenPhrase: personNameComponents.nickname ?? ""),
|
||||||
|
conversationIdentifier: bestAttemptContent.threadIdentifier,
|
||||||
|
serviceName: nil,
|
||||||
|
sender: senderPerson,
|
||||||
|
attachments: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
intent.setImage(avatar, forParameterNamed: \.sender)
|
||||||
|
|
||||||
|
let interaction = INInteraction(intent: intent, response: nil)
|
||||||
|
interaction.direction = .incoming
|
||||||
|
|
||||||
|
do {
|
||||||
|
try await interaction.donate()
|
||||||
|
let content = try bestAttemptContent.updating(from: intent) as! UNMutableNotificationContent
|
||||||
|
return content
|
||||||
|
} catch {
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
NotificationServiceExtension/Processor/ImageDownloader.swift
Normal file
62
NotificationServiceExtension/Processor/ImageDownloader.swift
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// ImageDownloader.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
class ImageDownloader {
|
||||||
|
/// 保存图片到缓存中
|
||||||
|
/// - Parameters:
|
||||||
|
/// - cache: 使用的缓存
|
||||||
|
/// - data: 图片 Data 数据
|
||||||
|
/// - key: 缓存 Key
|
||||||
|
class func storeImage(cache: ImageCache, data: Data, key: String) async {
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
cache.storeToDisk(data, forKey: key, expiration: StorageExpiration.never) { _ in
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用 Kingfisher.ImageDownloader 下载图片
|
||||||
|
/// - Parameter url: 下载的图片URL
|
||||||
|
/// - Returns: 返回 Result
|
||||||
|
class func downloadImage(url: URL) async -> Result<ImageLoadingResult, KingfisherError> {
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
Kingfisher.ImageDownloader.default.downloadImage(with: url, options: nil) { result in
|
||||||
|
continuation.resume(returning: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 下载推送图片
|
||||||
|
/// - Parameter imageUrl: 图片URL字符串
|
||||||
|
/// - Returns: 保存在本地中的`图片 File URL`
|
||||||
|
class func downloadImage(_ imageUrl: String) async -> String? {
|
||||||
|
guard let groupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.bark"),
|
||||||
|
let cache = try? ImageCache(name: "shared", cacheDirectoryURL: groupUrl),
|
||||||
|
let imageResource = URL(string: imageUrl)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先查看图片缓存
|
||||||
|
if cache.diskStorage.isCached(forKey: imageResource.cacheKey) {
|
||||||
|
return cache.cachePath(forKey: imageResource.cacheKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载图片
|
||||||
|
guard let result = try? await downloadImage(url: imageResource).get() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 缓存图片
|
||||||
|
await storeImage(cache: cache, data: result.originalData, key: imageResource.cacheKey)
|
||||||
|
|
||||||
|
return cache.cachePath(forKey: imageResource.cacheKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
NotificationServiceExtension/Processor/ImageProcessor.swift
Normal file
37
NotificationServiceExtension/Processor/ImageProcessor.swift
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// ImageProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MobileCoreServices
|
||||||
|
|
||||||
|
class ImageProcessor: NotificationContentProcessor {
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
let userInfo = bestAttemptContent.userInfo
|
||||||
|
guard let imageUrl = userInfo["image"] as? String,
|
||||||
|
let imageFileUrl = await ImageDownloader.downloadImage(imageUrl)
|
||||||
|
else {
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
|
||||||
|
let copyDestUrl = URL(fileURLWithPath: imageFileUrl).appendingPathExtension(".tmp")
|
||||||
|
// 将图片缓存复制一份,推送使用完后会自动删除,但图片缓存需要留着以后在历史记录里查看
|
||||||
|
try? FileManager.default.copyItem(
|
||||||
|
at: URL(fileURLWithPath: imageFileUrl),
|
||||||
|
to: copyDestUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
if let attachment = try? UNNotificationAttachment(
|
||||||
|
identifier: "image",
|
||||||
|
url: copyDestUrl,
|
||||||
|
options: [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]
|
||||||
|
) {
|
||||||
|
bestAttemptContent.attachments = [attachment]
|
||||||
|
}
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
}
|
||||||
28
NotificationServiceExtension/Processor/LevelProcessor.swift
Normal file
28
NotificationServiceExtension/Processor/LevelProcessor.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// LevelProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// 通知中断级别
|
||||||
|
class LevelProcessor: NotificationContentProcessor {
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent {
|
||||||
|
if #available(iOSApplicationExtension 15.0, *) {
|
||||||
|
if let level = bestAttemptContent.userInfo["level"] as? String {
|
||||||
|
let interruptionLevels: [String: UNNotificationInterruptionLevel] = [
|
||||||
|
"passive": UNNotificationInterruptionLevel.passive,
|
||||||
|
"active": UNNotificationInterruptionLevel.active,
|
||||||
|
"timeSensitive": UNNotificationInterruptionLevel.timeSensitive,
|
||||||
|
"timesensitive": UNNotificationInterruptionLevel.timeSensitive,
|
||||||
|
"critical": UNNotificationInterruptionLevel.critical
|
||||||
|
]
|
||||||
|
bestAttemptContent.interruptionLevel = interruptionLevels[level] ?? .active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestAttemptContent
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// NotificationContentProcessor.swift
|
||||||
|
// NotificationServiceExtension
|
||||||
|
//
|
||||||
|
// Created by huangfeng on 2024/5/29.
|
||||||
|
// Copyright © 2024 Fin. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
@_exported import UserNotifications
|
||||||
|
|
||||||
|
enum NotificationContentProcessorItem {
|
||||||
|
case ciphertext
|
||||||
|
case level
|
||||||
|
case badge
|
||||||
|
case autoCopy
|
||||||
|
case archive
|
||||||
|
case setIcon
|
||||||
|
case setImage
|
||||||
|
|
||||||
|
var processor: NotificationContentProcessor {
|
||||||
|
switch self {
|
||||||
|
case .ciphertext:
|
||||||
|
return CiphertextProcessor()
|
||||||
|
case .level:
|
||||||
|
return LevelProcessor()
|
||||||
|
case .badge:
|
||||||
|
return BadgeProcessor()
|
||||||
|
case .autoCopy:
|
||||||
|
return AutoCopyProcessor()
|
||||||
|
case .archive:
|
||||||
|
return ArchiveProcessor()
|
||||||
|
case .setIcon:
|
||||||
|
return IconProcessor()
|
||||||
|
case .setImage:
|
||||||
|
return ImageProcessor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationContentProcessorError: Swift.Error {
|
||||||
|
case error(content: UNMutableNotificationContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol NotificationContentProcessor {
|
||||||
|
/// 处理 UNMutableNotificationContent
|
||||||
|
/// - Parameter bestAttemptContent: 需要处理的 UNMutableNotificationContent
|
||||||
|
/// - Returns: 处理成功后的 UNMutableNotificationContent
|
||||||
|
/// - Throws: 处理失败后,应该中断处理
|
||||||
|
func process(content bestAttemptContent: UNMutableNotificationContent) async throws -> UNMutableNotificationContent
|
||||||
|
}
|
||||||
@ -153,4 +153,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: 73cfda85b7a345d896330d72bc61822356f98586
|
PODFILE CHECKSUM: 73cfda85b7a345d896330d72bc61822356f98586
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user