mastodon-ios/MastodonIntent/Handler/SendPostIntentHandler.swift

162 lines
6.0 KiB
Swift

//
// SendPostIntentHandler.swift
// MastodonIntent
//
// Created by Cirno MainasuK on 2021-7-26.
//
import Foundation
import Intents
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
import MastodonCore
final class SendPostIntentHandler: NSObject {
var disposeBag = Set<AnyCancellable>()
let coreDataStack = CoreDataStack()
lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext
lazy var api: APIService = {
let backgroundManagedObjectContext = coreDataStack.newTaskContext()
return APIService(
backgroundManagedObjectContext: backgroundManagedObjectContext
)
}()
}
// 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,
language: nil
),
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) }
)
if let urlString = response.value.url, let url = URL(string: urlString) {
post.url = url
}
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))
}
}