feat: allow select accounts for SendPost Siri shortcut intent. resolve #446

This commit is contained in:
CMK 2022-06-09 18:09:28 +08:00
parent 013a901752
commit 69d790c45d
9 changed files with 460 additions and 155 deletions

View File

@ -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 = "<group>"; };
DB63F77A279ACAE500455B82 /* DataSourceFacade+Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Favorite.swift"; sourceTree = "<group>"; };
DB647C5826F1EA2700F7F82C /* WizardPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardPreference.swift; sourceTree = "<group>"; };
DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonAuthentication+Fetch.swift"; sourceTree = "<group>"; };
DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Fetch.swift"; sourceTree = "<group>"; };
DB65C63627A2AF6C008BAC2E /* ReportItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportItem.swift; sourceTree = "<group>"; };
DB66728B25F9F8DC00D60309 /* ComposeViewModel+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ComposeViewModel+DataSource.swift"; sourceTree = "<group>"; };
DB66729525F9F91600D60309 /* ComposeStatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusSection.swift; sourceTree = "<group>"; };
@ -2601,6 +2598,23 @@
path = NotificationTimeline;
sourceTree = "<group>";
};
DB64BA462851F23300ADF1B7 /* Model */ = {
isa = PBXGroup;
children = (
DB64BA442851F23000ADF1B7 /* MastodonAuthentication+Fetch.swift */,
DB64BA472851F29300ADF1B7 /* Account+Fetch.swift */,
);
path = Model;
sourceTree = "<group>";
};
DB64BA492851F65F00ADF1B7 /* Handler */ = {
isa = PBXGroup;
children = (
DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */,
);
path = Handler;
sourceTree = "<group>";
};
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 */;

View File

@ -24,17 +24,17 @@
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>11</integer>
<integer>12</integer>
</dict>
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
<integer>5</integer>
</dict>
<key>Mastodon - Snapshot.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>6</integer>
<integer>7</integer>
</dict>
<key>Mastodon - ar.xcscheme</key>
<dict>
@ -114,7 +114,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>30</integer>
<integer>23</integer>
</dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict>
@ -129,12 +129,12 @@
<key>NotificationService.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>29</integer>
<integer>24</integer>
</dict>
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>31</integer>
<integer>22</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -2,19 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>onion</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
@ -59,6 +46,19 @@
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>onion</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSUserActivityTypes</key>
<array>
<string>SendPostIntent</string>

View File

@ -53,11 +53,11 @@
<key>INIntentDefinitionNamespace</key>
<string>BvMBE4</string>
<key>INIntentDefinitionSystemVersion</key>
<string>20G80</string>
<string>21F79</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>12E507</string>
<string>13F100</string>
<key>INIntentDefinitionToolsVersion</key>
<string>12.5.1</string>
<string>13.4.1</string>
<key>INIntents</key>
<array>
<dict>
@ -74,10 +74,10 @@
<key>INIntentKeyParameter</key>
<string>content</string>
<key>INIntentLastParameterTag</key>
<integer>3</integer>
<integer>6</integer>
<key>INIntentManagedParameterCombinations</key>
<dict>
<key>content,visibility</key>
<key>content,visibility,accounts</key>
<dict>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
@ -91,6 +91,19 @@
<string>SendPost</string>
<key>INIntentParameterCombinations</key>
<dict>
<key>accounts,visibility,content</key>
<dict>
<key>INIntentParameterCombinationSubtitle</key>
<string>${content}. Post via ${accounts}. (${visibility})</string>
<key>INIntentParameterCombinationSubtitleID</key>
<string>YMuITB</string>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
<true/>
<key>INIntentParameterCombinationTitle</key>
<string>Post on Mastodon</string>
<key>INIntentParameterCombinationTitleID</key>
<string>D2h76g</string>
</dict>
<key>content</key>
<dict>
<key>INIntentParameterCombinationSubtitle</key>
@ -109,7 +122,7 @@
<key>INIntentParameterCombinationIsPrimary</key>
<true/>
<key>INIntentParameterCombinationSubtitle</key>
<string>${content}, ${visibility}</string>
<string>${content}. (${visibility})</string>
<key>INIntentParameterCombinationSubtitleID</key>
<string>ayoYEb</string>
<key>INIntentParameterCombinationSupportsBackgroundExecution</key>
@ -170,6 +183,69 @@
<key>INIntentParameterType</key>
<string>String</string>
</dict>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterCustomDisambiguation</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>Accounts</string>
<key>INIntentParameterDisplayNameID</key>
<string>nQtHsT</string>
<key>INIntentParameterDisplayPriority</key>
<integer>2</integer>
<key>INIntentParameterName</key>
<string>accounts</string>
<key>INIntentParameterObjectType</key>
<string>Account</string>
<key>INIntentParameterObjectTypeNamespace</key>
<string>BvMBE4</string>
<key>INIntentParameterPromptDialogs</key>
<array>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Primary</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>There are ${count} options matching ${accounts}.</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>ciITyC</string>
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationIntroduction</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Just to confirm, you wanted ${accounts}?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>4kVQfr</string>
<key>INIntentParameterPromptDialogType</key>
<string>Confirmation</string>
</dict>
</array>
<key>INIntentParameterSupportsDynamicEnumeration</key>
<true/>
<key>INIntentParameterSupportsMultipleValues</key>
<true/>
<key>INIntentParameterSupportsResolution</key>
<true/>
<key>INIntentParameterTag</key>
<integer>6</integer>
<key>INIntentParameterType</key>
<string>Object</string>
</dict>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
@ -180,7 +256,7 @@
<key>INIntentParameterDisplayNameID</key>
<string>ZbSjzC</string>
<key>INIntentParameterDisplayPriority</key>
<integer>2</integer>
<integer>3</integer>
<key>INIntentParameterEnumType</key>
<string>PostVisibility</string>
<key>INIntentParameterEnumTypeNamespace</key>
@ -267,26 +343,28 @@
</dict>
</array>
<key>INIntentResponseLastParameterTag</key>
<integer>7</integer>
<integer>8</integer>
<key>INIntentResponseOutput</key>
<string>post</string>
<string>posts</string>
<key>INIntentResponseParameters</key>
<array>
<dict>
<key>INIntentResponseParameterDisplayName</key>
<string>Post</string>
<string>Posts</string>
<key>INIntentResponseParameterDisplayNameID</key>
<string>ZKJSNu</string>
<key>INIntentResponseParameterDisplayPriority</key>
<integer>1</integer>
<key>INIntentResponseParameterName</key>
<string>post</string>
<string>posts</string>
<key>INIntentResponseParameterObjectType</key>
<string>Post</string>
<key>INIntentResponseParameterObjectTypeNamespace</key>
<string>BvMBE4</string>
<key>INIntentResponseParameterSupportsMultipleValues</key>
<true/>
<key>INIntentResponseParameterTag</key>
<integer>6</integer>
<integer>8</integer>
<key>INIntentResponseParameterType</key>
<string>Object</string>
</dict>
@ -395,6 +473,97 @@
</dict>
</array>
</dict>
<dict>
<key>INTypeDisplayName</key>
<string>Account</string>
<key>INTypeDisplayNameID</key>
<string>Gcmnot</string>
<key>INTypeLastPropertyTag</key>
<integer>101</integer>
<key>INTypeName</key>
<string>Account</string>
<key>INTypeProperties</key>
<array>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>1</integer>
<key>INTypePropertyName</key>
<string>identifier</string>
<key>INTypePropertyTag</key>
<integer>1</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>2</integer>
<key>INTypePropertyName</key>
<string>displayString</string>
<key>INTypePropertyTag</key>
<integer>2</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>3</integer>
<key>INTypePropertyName</key>
<string>pronunciationHint</string>
<key>INTypePropertyTag</key>
<integer>3</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>4</integer>
<key>INTypePropertyName</key>
<string>alternativeSpeakableMatches</string>
<key>INTypePropertySupportsMultipleValues</key>
<true/>
<key>INTypePropertyTag</key>
<integer>4</integer>
<key>INTypePropertyType</key>
<string>SpeakableString</string>
</dict>
<dict>
<key>INTypePropertyDisplayName</key>
<string>Name</string>
<key>INTypePropertyDisplayNameID</key>
<string>Zim0Js</string>
<key>INTypePropertyDisplayPriority</key>
<integer>5</integer>
<key>INTypePropertyName</key>
<string>name</string>
<key>INTypePropertyTag</key>
<integer>100</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDisplayName</key>
<string>Username</string>
<key>INTypePropertyDisplayNameID</key>
<string>3sNRTG</string>
<key>INTypePropertyDisplayPriority</key>
<integer>6</integer>
<key>INTypePropertyName</key>
<string>username</string>
<key>INTypePropertyTag</key>
<integer>101</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -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<AnyCancellable>()
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<Account> {
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))
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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<AnyCancellable>()
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))
}
}

View File

@ -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])
}
}