diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index efe519f74..e0c1d19ab 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -108,7 +108,7 @@ 5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; }; 5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; }; 87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; }; - DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; + DB0009A626AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; settings = {ATTRIBUTES = (codegen, ); }; }; DB0009A726AEE5DC009B9D2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = DB0009A926AEE5DC009B9D2D /* Intents.intentdefinition */; }; DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; DB023D26279FFB0A005AC798 /* ShareActivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB023D25279FFB0A005AC798 /* ShareActivityProvider.swift */; }; @@ -346,6 +346,8 @@ DB63F779279ABF9C00455B82 /* DataSourceFacade+Reblog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */; }; DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */; }; DB647C5926F1EA2700F7F82C /* WizardPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB647C5826F1EA2700F7F82C /* WizardPreference.swift */; }; + DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */; }; + DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */; }; DB65C63727A2AF6C008BAC2E /* ReportItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB65C63627A2AF6C008BAC2E /* ReportItem.swift */; }; DB66728C25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */; }; DB66729625F9F91600D60309 /* ComposeStatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB66729525F9F91600D60309 /* ComposeStatusSection.swift */; }; @@ -649,13 +651,6 @@ remoteGlobalIDString = DB68047E2637CD4C00430867; remoteInfo = AppShared; }; - DB6804C92637CE3000430867 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; - proxyType = 1; - remoteGlobalIDString = DB68047E2637CD4C00430867; - remoteInfo = AppShared; - }; DB8FABCC26AEC7B2008E5AF4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DB427DCA25BAA00100D1B89D /* Project object */; @@ -1112,6 +1107,8 @@ DB63F778279ABF9C00455B82 /* DataSourceFacade+Reblog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Reblog.swift"; sourceTree = ""; }; DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Favorite.swift"; sourceTree = ""; }; DB647C5826F1EA2700F7F82C /* WizardPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardPreference.swift; sourceTree = ""; }; + DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAuthentication+Fetch.swift"; sourceTree = ""; }; + DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Fetch.swift"; sourceTree = ""; }; DB65C63627A2AF6C008BAC2E /* ReportItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportItem.swift; sourceTree = ""; }; DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = ""; }; DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = ""; }; @@ -2601,6 +2598,23 @@ path = NotificationTimeline; sourceTree = ""; }; + DB64BA462851F23300ADF1B7 /* Model */ = { + isa = PBXGroup; + children = ( + DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */, + DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */, + ); + path = Model; + sourceTree = ""; + }; + DB64BA492851F65F00ADF1B7 /* Handler */ = { + isa = PBXGroup; + children = ( + DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */, + ); + path = Handler; + sourceTree = ""; + }; DB65C63527A2AF52008BAC2E /* Report */ = { isa = PBXGroup; children = ( @@ -2892,7 +2906,8 @@ DBA4B0F926C269880077136E /* Intents.stringsdict */, DB8FABD626AEC864008E5AF4 /* MastodonIntent.entitlements */, DB8FABC926AEC7B2008E5AF4 /* IntentHandler.swift */, - DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */, + DB64BA462851F23300ADF1B7 /* Model */, + DB64BA492851F65F00ADF1B7 /* Handler */, DBB8AB4B26AED0B800F6D281 /* Service */, DB8FABCB26AEC7B2008E5AF4 /* Info.plist */, ); @@ -3413,13 +3428,13 @@ buildConfigurationList = DB427DFC25BAA00100D1B89D /* Build configuration list for PBXNativeTarget "Mastodon" */; buildPhases = ( 7A04933A2AB1D5B758D4F908 /* [CP] Check Pods Manifest.lock */, - DB3D100425BAA71500EAA174 /* ShellScript */, + 5532CB85BBE168B25B20720B /* [CP] Embed Pods Frameworks */, + DB427DD025BAA00100D1B89D /* Resources */, DB427DCE25BAA00100D1B89D /* Sources */, DB427DCF25BAA00100D1B89D /* Frameworks */, - DB427DD025BAA00100D1B89D /* Resources */, - 5532CB85BBE168B25B20720B /* [CP] Embed Pods Frameworks */, DB89BA0825C10FD0008580ED /* Embed Frameworks */, DBF8AE1B263293E400C9C23C /* Embed App Extensions */, + DB3D100425BAA71500EAA174 /* ShellScript */, DB025B8E278D6448002F581E /* ShellScript */, DB697DD2278F48D5004EF2F7 /* ShellScript */, ); @@ -3428,7 +3443,6 @@ dependencies = ( DBF8AE19263293E400C9C23C /* PBXTargetDependency */, DB6804852637CD4C00430867 /* PBXTargetDependency */, - DB6804CA2637CE3000430867 /* PBXTargetDependency */, DBC6461B26A170AB00B0E31B /* PBXTargetDependency */, DB8FABCD26AEC7B2008E5AF4 /* PBXTargetDependency */, ); @@ -4487,6 +4501,8 @@ DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */, DBB8AB4A26AED0B500F6D281 /* APIService.swift in Sources */, DBB8AB4C26AED11300F6D281 /* APIService+APIError.swift in Sources */, + DB64BA452851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift in Sources */, + DB64BA482851F29300ADF1B7 /* Account+Fetch.swift in Sources */, DB6746E9278ED63F008A6B94 /* MastodonAuthenticationBox.swift in Sources */, DBB8AB5226AED1B300F6D281 /* APIService+Status+Publish.swift in Sources */, DB8FABCA26AEC7B2008E5AF4 /* IntentHandler.swift in Sources */, @@ -4555,11 +4571,6 @@ target = DB68047E2637CD4C00430867 /* AppShared */; targetProxy = DB6804A72637CDCC00430867 /* PBXContainerItemProxy */; }; - DB6804CA2637CE3000430867 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DB68047E2637CD4C00430867 /* AppShared */; - targetProxy = DB6804C92637CE3000430867 /* PBXContainerItemProxy */; - }; DB8FABCD26AEC7B2008E5AF4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DB8FABC526AEC7B2008E5AF4 /* MastodonIntent */; diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index 1c922a0b5..10b782b15 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -24,17 +24,17 @@ Mastodon - RTL.xcscheme_^#shared#^_ orderHint - 11 + 12 Mastodon - Release.xcscheme_^#shared#^_ orderHint - 4 + 5 Mastodon - Snapshot.xcscheme_^#shared#^_ orderHint - 6 + 7 Mastodon - ar.xcscheme @@ -114,7 +114,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 30 + 23 MastodonIntents.xcscheme_^#shared#^_ @@ -129,12 +129,12 @@ NotificationService.xcscheme_^#shared#^_ orderHint - 29 + 24 ShareActionExtension.xcscheme_^#shared#^_ orderHint - 31 + 22 SuppressBuildableAutocreation diff --git a/Mastodon/Info.plist b/Mastodon/Info.plist index 0407f6502..1b774cbc6 100644 --- a/Mastodon/Info.plist +++ b/Mastodon/Info.plist @@ -2,19 +2,6 @@ - NSAppTransportSecurity - - NSExceptionDomains - - onion - - NSExceptionAllowsInsecureHTTPLoads - - NSIncludesSubdomains - - - - CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -59,6 +46,19 @@ LSRequiresIPhoneOS + NSAppTransportSecurity + + NSExceptionDomains + + onion + + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + + + + NSUserActivityTypes SendPostIntent diff --git a/MastodonIntent/Base.lproj/Intents.intentdefinition b/MastodonIntent/Base.lproj/Intents.intentdefinition index 6ccb0318c..509591d5c 100644 --- a/MastodonIntent/Base.lproj/Intents.intentdefinition +++ b/MastodonIntent/Base.lproj/Intents.intentdefinition @@ -53,11 +53,11 @@ INIntentDefinitionNamespace BvMBE4 INIntentDefinitionSystemVersion - 20G80 + 21F79 INIntentDefinitionToolsBuildVersion - 12E507 + 13F100 INIntentDefinitionToolsVersion - 12.5.1 + 13.4.1 INIntents @@ -74,10 +74,10 @@ INIntentKeyParameter content INIntentLastParameterTag - 3 + 6 INIntentManagedParameterCombinations - content,visibility + content,visibility,accounts INIntentParameterCombinationSupportsBackgroundExecution @@ -91,6 +91,19 @@ SendPost INIntentParameterCombinations + accounts,visibility,content + + INIntentParameterCombinationSubtitle + ${content}. Post via ${accounts}. (${visibility}) + INIntentParameterCombinationSubtitleID + YMuITB + INIntentParameterCombinationSupportsBackgroundExecution + + INIntentParameterCombinationTitle + Post on Mastodon + INIntentParameterCombinationTitleID + D2h76g + content INIntentParameterCombinationSubtitle @@ -109,7 +122,7 @@ INIntentParameterCombinationIsPrimary INIntentParameterCombinationSubtitle - ${content}, ${visibility} + ${content}. (${visibility}) INIntentParameterCombinationSubtitleID ayoYEb INIntentParameterCombinationSupportsBackgroundExecution @@ -170,6 +183,69 @@ INIntentParameterType String + + INIntentParameterConfigurable + + INIntentParameterCustomDisambiguation + + INIntentParameterDisplayName + Accounts + INIntentParameterDisplayNameID + nQtHsT + INIntentParameterDisplayPriority + 2 + INIntentParameterName + accounts + INIntentParameterObjectType + Account + INIntentParameterObjectTypeNamespace + BvMBE4 + INIntentParameterPromptDialogs + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Configuration + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogType + Primary + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + There are ${count} options matching ‘${accounts}’. + INIntentParameterPromptDialogFormatStringID + ciITyC + INIntentParameterPromptDialogType + DisambiguationIntroduction + + + INIntentParameterPromptDialogCustom + + INIntentParameterPromptDialogFormatString + Just to confirm, you wanted ‘${accounts}’? + INIntentParameterPromptDialogFormatStringID + 4kVQfr + INIntentParameterPromptDialogType + Confirmation + + + INIntentParameterSupportsDynamicEnumeration + + INIntentParameterSupportsMultipleValues + + INIntentParameterSupportsResolution + + INIntentParameterTag + 6 + INIntentParameterType + Object + INIntentParameterConfigurable @@ -180,7 +256,7 @@ INIntentParameterDisplayNameID ZbSjzC INIntentParameterDisplayPriority - 2 + 3 INIntentParameterEnumType PostVisibility INIntentParameterEnumTypeNamespace @@ -267,26 +343,28 @@ INIntentResponseLastParameterTag - 7 + 8 INIntentResponseOutput - post + posts INIntentResponseParameters INIntentResponseParameterDisplayName - Post + Posts INIntentResponseParameterDisplayNameID ZKJSNu INIntentResponseParameterDisplayPriority 1 INIntentResponseParameterName - post + posts INIntentResponseParameterObjectType Post INIntentResponseParameterObjectTypeNamespace BvMBE4 + INIntentResponseParameterSupportsMultipleValues + INIntentResponseParameterTag - 6 + 8 INIntentResponseParameterType Object @@ -395,6 +473,97 @@ + + INTypeDisplayName + Account + INTypeDisplayNameID + Gcmnot + INTypeLastPropertyTag + 101 + INTypeName + Account + INTypeProperties + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 1 + INTypePropertyName + identifier + INTypePropertyTag + 1 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 2 + INTypePropertyName + displayString + INTypePropertyTag + 2 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 3 + INTypePropertyName + pronunciationHint + INTypePropertyTag + 3 + INTypePropertyType + String + + + INTypePropertyDefault + + INTypePropertyDisplayPriority + 4 + INTypePropertyName + alternativeSpeakableMatches + INTypePropertySupportsMultipleValues + + INTypePropertyTag + 4 + INTypePropertyType + SpeakableString + + + INTypePropertyDisplayName + Name + INTypePropertyDisplayNameID + Zim0Js + INTypePropertyDisplayPriority + 5 + INTypePropertyName + name + INTypePropertyTag + 100 + INTypePropertyType + String + + + INTypePropertyDisplayName + Username + INTypePropertyDisplayNameID + 3sNRTG + INTypePropertyDisplayPriority + 6 + INTypePropertyName + username + INTypePropertyTag + 101 + INTypePropertyType + String + + + diff --git a/MastodonIntent/Handler/SendPostIntentHandler.swift b/MastodonIntent/Handler/SendPostIntentHandler.swift new file mode 100644 index 000000000..0da4e113b --- /dev/null +++ b/MastodonIntent/Handler/SendPostIntentHandler.swift @@ -0,0 +1,152 @@ +// +// SendPostIntentHandler.swift +// MastodonIntent +// +// Created by Cirno MainasuK on 2021-7-26. +// + +import Foundation +import Intents +import Combine +import CoreData +import CoreDataStack +import MastodonSDK + +final class SendPostIntentHandler: NSObject { + + var disposeBag = Set() + + let coreDataStack = CoreDataStack() + lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext + lazy var api = APIService.shared + +} + +// MARK: - SendPostIntentHandling +extension SendPostIntentHandler: SendPostIntentHandling { + + func handle(intent: SendPostIntent) async -> SendPostIntentResponse { + guard let content = intent.content else { + return SendPostIntentResponse(code: .failure, userActivity: nil) + } + + let visibility: Mastodon.Entity.Status.Visibility = { + switch intent.visibility { + case .unknown: return .public + case .public: return .public + case .followersOnly: return .private + } + }() + + do { + // fetch authentications from + // user pick accounts + // or fallback to active account + let mastodonAuthentications: [MastodonAuthentication] + let accounts = intent.accounts ?? [] + if accounts.isEmpty { + let request = MastodonAuthentication.sortedFetchRequest + let authentications = try managedObjectContext.fetch(request) + let _authentication = authentications.sorted(by: { $0.activedAt > $1.activedAt }).first + + guard let authentication = _authentication else { + let failureReason = APIService.APIError.implicit(.authenticationMissing).errorDescription ?? "Fail to Send Post" + return SendPostIntentResponse.failure(failureReason: failureReason) + } + mastodonAuthentications = [authentication] + } else { + mastodonAuthentications = try accounts.mastodonAuthentication(in: managedObjectContext) + } + + let authenticationBoxes = mastodonAuthentications.map { authentication in + MastodonAuthenticationBox( + authenticationRecord: .init(objectID: authentication.objectID), + domain: authentication.domain, + userID: authentication.userID, + appAuthorization: .init(accessToken: authentication.appAccessToken), + userAuthorization: .init(accessToken: authentication.userAccessToken) + ) + } + + var posts: [Post] = [] + for authenticationBox in authenticationBoxes { + let idempotencyKey = UUID().uuidString + let response = try await api.publishStatus( + domain: authenticationBox.domain, + idempotencyKey: idempotencyKey, + query: .init( + status: content, + mediaIDs: nil, + pollOptions: nil, + pollExpiresIn: nil, + inReplyToID: nil, + sensitive: nil, + spoilerText: nil, + visibility: visibility + ), + authenticationBox: authenticationBox + ) + let post = Post( + identifier: response.value.id, + display: response.value.account.acct, + subtitle: content, + image: response.value.account.avatarImageURL().flatMap { INImage(url: $0) } + ) + posts.append(post) + } // end for in + + let intentResponse = SendPostIntentResponse(code: .success, userActivity: nil) + intentResponse.posts = posts + + return intentResponse + } catch { + let intentResponse = SendPostIntentResponse(code: .failure, userActivity: nil) + if let error = error as? LocalizedError { + intentResponse.failureReason = [ + error.errorDescription, + error.failureReason, + error.recoverySuggestion + ] + .compactMap { $0 } + .joined(separator: ", ") + } else { + intentResponse.failureReason = error.localizedDescription + } + return intentResponse + } + } // end func + + // content + func resolveContent(for intent: SendPostIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + guard let content = intent.content, !content.isEmpty else { + completion(.needsValue()) + return + } + + completion(.success(with: content)) + } + + // accounts + func resolveAccounts(for intent: SendPostIntent) async -> [AccountResolutionResult] { + guard let accounts = intent.accounts, !accounts.isEmpty else { + return [AccountResolutionResult.needsValue()] + } + + let results = accounts.map { account in + AccountResolutionResult.success(with: account) + } + + return results + } + + func provideAccountsOptionsCollection(for intent: SendPostIntent) async throws -> INObjectCollection { + let accounts = try await Account.fetch(in: managedObjectContext) + return .init(items: accounts) + } + + // visibility + func resolveVisibility(for intent: SendPostIntent, with completion: @escaping (PostVisibilityResolutionResult) -> Void) { + completion(.success(with: intent.visibility)) + } + +} diff --git a/MastodonIntent/Model/Account+Fetch.swift b/MastodonIntent/Model/Account+Fetch.swift new file mode 100644 index 000000000..065ccac12 --- /dev/null +++ b/MastodonIntent/Model/Account+Fetch.swift @@ -0,0 +1,51 @@ +// +// Account.swift +// MastodonIntent +// +// Created by MainasuK on 2022-6-9. +// + +import Foundation +import CoreData +import CoreDataStack +import Intents + +extension Account { + + @MainActor + static func fetch(in managedObjectContext: NSManagedObjectContext) async throws -> [Account] { + // get accounts + let accounts: [Account] = try await managedObjectContext.perform { + let results = try MastodonAuthentication.fetch(in: managedObjectContext) + let accounts = results.compactMap { mastodonAuthentication -> Account? in + let user = mastodonAuthentication.user + let account = Account( + identifier: mastodonAuthentication.identifier.uuidString, + display: user.displayNameWithFallback, + subtitle: user.acctWithDomain, + image: user.avatarImageURL().flatMap { INImage(url: $0) } + ) + account.name = user.displayNameWithFallback + account.username = user.acctWithDomain + return account + } + return accounts + } // end managedObjectContext.perform + + return accounts + } + +} + +extension Array where Element == Account { + func mastodonAuthentication(in managedObjectContext: NSManagedObjectContext) throws -> [MastodonAuthentication] { + let identifiers = self + .compactMap { $0.identifier } + .compactMap { UUID(uuidString: $0) } + let request = MastodonAuthentication.sortedFetchRequest + request.predicate = MastodonAuthentication.predicate(identifiers: identifiers) + let results = try managedObjectContext.fetch(request) + return results + } + +} diff --git a/MastodonIntent/Model/MastodonAuthentication+Fetch.swift b/MastodonIntent/Model/MastodonAuthentication+Fetch.swift new file mode 100644 index 000000000..9d1201000 --- /dev/null +++ b/MastodonIntent/Model/MastodonAuthentication+Fetch.swift @@ -0,0 +1,20 @@ +// +// MastodonAuthentication.swift +// MastodonIntent +// +// Created by MainasuK on 2022-6-9. +// + +import Foundation +import CoreData +import CoreDataStack + +extension MastodonAuthentication { + + static func fetch(in managedObjectContext: NSManagedObjectContext) throws -> [MastodonAuthentication] { + let request = MastodonAuthentication.sortedFetchRequest + let results = try managedObjectContext.fetch(request) + return results + } + +} diff --git a/MastodonIntent/SendPostIntentHandler.swift b/MastodonIntent/SendPostIntentHandler.swift deleted file mode 100644 index 1ad843088..000000000 --- a/MastodonIntent/SendPostIntentHandler.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// SendPostIntentHandler.swift -// MastodonIntent -// -// Created by Cirno MainasuK on 2021-7-26. -// - -import Foundation -import Intents -import Combine -import CoreData -import CoreDataStack -import MastodonSDK - -final class SendPostIntentHandler: NSObject, SendPostIntentHandling { - - var disposeBag = Set() - - lazy var coreDataStack = CoreDataStack() - lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext - - func handle(intent: SendPostIntent, completion: @escaping (SendPostIntentResponse) -> Void) { - managedObjectContext.performAndWait { - let request = MastodonAuthentication.sortedFetchRequest - let authentications = (try? self.managedObjectContext.fetch(request)) ?? [] - let _authentication = authentications.sorted(by: { $0.activedAt > $1.activedAt }).first - - guard let authentication = _authentication else { - let failureReason = APIService.APIError.implicit(.authenticationMissing).errorDescription ?? "Fail to Send Post" - completion(SendPostIntentResponse.failure(failureReason: failureReason)) - return - } - - let box = MastodonAuthenticationBox( - authenticationRecord: .init(objectID: authentication.objectID), - domain: authentication.domain, - userID: authentication.userID, - appAuthorization: .init(accessToken: authentication.appAccessToken), - userAuthorization: .init(accessToken: authentication.userAccessToken) - ) - - let visibility: Mastodon.Entity.Status.Visibility = { - switch intent.visibility { - case .unknown: return .public - case .public: return .public - case .followersOnly: return .private - } - }() - let query = Mastodon.API.Statuses.PublishStatusQuery( - status: intent.content, - mediaIDs: nil, - pollOptions: nil, - pollExpiresIn: nil, - inReplyToID: nil, - sensitive: nil, - spoilerText: nil, - visibility: visibility - ) - - let idempotencyKey = UUID().uuidString - - Just(Void()) - .asyncMap { - try await APIService.shared.publishStatus( - domain: box.domain, - idempotencyKey: idempotencyKey, - query: query, - authenticationBox: box - ) - } - .sink { _completion in - switch _completion { - case .failure(let error): - let failureReason = error.localizedDescription - completion(SendPostIntentResponse.failure(failureReason: failureReason)) - case .finished: - break - } - } receiveValue: { response in - let post = Post(identifier: response.value.id, display: intent.content ?? "") - post.url = URL(string: response.value.url ?? response.value.uri) - let result = SendPostIntentResponse(code: .success, userActivity: nil) - result.post = post - completion(result) - } - .store(in: &disposeBag) - } - - } - - func resolveContent(for intent: SendPostIntent, with completion: @escaping (INStringResolutionResult) -> Void) { - guard let content = intent.content, !content.isEmpty else { - completion(.needsValue()) - return - } - - completion(.success(with: content)) - } - - func resolveVisibility(for intent: SendPostIntent, with completion: @escaping (PostVisibilityResolutionResult) -> Void) { - completion(.success(with: intent.visibility)) - } - - - -} diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift index 7aafd65a4..1c5c40851 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonAuthentication.swift @@ -171,4 +171,12 @@ extension MastodonAuthentication { return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.userAccessToken), userAccessToken) } + public static func predicate(identifier: UUID) -> NSPredicate { + return NSPredicate(format: "%K == %@", #keyPath(MastodonAuthentication.identifier), identifier as NSUUID) + } + + public static func predicate(identifiers: [UUID]) -> NSPredicate { + return NSPredicate(format: "%K IN %@", #keyPath(MastodonAuthentication.identifier), identifiers as [NSUUID]) + } + }