// Copyright © 2023 Mastodon gGmbH. All rights reserved. import Foundation import CoreDataStack import MastodonSDK public struct MastodonAuthentication: Codable, Hashable, UserIdentifier { public static let fallbackCharactersReservedPerURL = 23 public enum InstanceConfiguration: Codable, Hashable { case v1(Mastodon.Entity.Instance) case v2(Mastodon.Entity.V2.Instance, TranslationLanguages) public func canTranslateFrom(_ sourceLocale: String, to targetLanguage: String) -> Bool { switch self { case .v1: return false case let .v2(instance, translationLanguages): guard instance.configuration?.translation?.enabled == true else { return false } return translationLanguages[sourceLocale]?.contains(targetLanguage) == true } } public var instanceConfigLimitingProperties: InstanceConfigLimitingPropertyContaining? { switch self { case let .v1(instance): return instance.configuration case let .v2(instance, _): return instance.configuration } } public var canFollowTags: Bool { let version: String? switch self { case let .v1(instance): version = instance.version case let .v2(instance, _): version = instance.version } return version?.majorServerVersion(greaterThanOrEquals: 4) ?? false // following Tags is support beginning with Mastodon v4.0.0 } public var charactersReservedPerURL: Int { switch self { case let .v1(instance): return instance.configuration?.statuses?.charactersReservedPerURL ?? fallbackCharactersReservedPerURL case let .v2(instance, _): return instance.configuration?.statuses?.charactersReservedPerURL ?? fallbackCharactersReservedPerURL } } } public typealias ID = UUID public private(set) var identifier: ID public private(set) var domain: String public private(set) var username: String public private(set) var appAccessToken: String public private(set) var userAccessToken: String public private(set) var clientID: String public private(set) var clientSecret: String public private(set) var createdAt: Date public private(set) var updatedAt: Date public private(set) var activedAt: Date public private(set) var userID: String public private(set) var instanceConfiguration: InstanceConfiguration? public var persistenceIdentifier: String { "\(username)@\(domain)" } public static func createFrom( domain: String, userID: String, username: String, appAccessToken: String, userAccessToken: String, clientID: String, clientSecret: String ) -> Self { let now = Date() return MastodonAuthentication( identifier: .init(), domain: domain, username: username, appAccessToken: appAccessToken, userAccessToken: userAccessToken, clientID: clientID, clientSecret: clientSecret, createdAt: now, updatedAt: now, activedAt: now, userID: userID, instanceConfiguration: nil ) } func copy( identifier: ID? = nil, domain: String? = nil, username: String? = nil, appAccessToken: String? = nil, userAccessToken: String? = nil, clientID: String? = nil, clientSecret: String? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, activedAt: Date? = nil, userID: String? = nil, instanceConfiguration: InstanceConfiguration? = nil ) -> Self { MastodonAuthentication( identifier: identifier ?? self.identifier, domain: domain ?? self.domain, username: username ?? self.username, appAccessToken: appAccessToken ?? self.appAccessToken, userAccessToken: userAccessToken ?? self.userAccessToken, clientID: clientID ?? self.clientID, clientSecret: clientSecret ?? self.clientSecret, createdAt: createdAt ?? self.createdAt, updatedAt: updatedAt ?? self.updatedAt, activedAt: activedAt ?? self.activedAt, userID: userID ?? self.userID, instanceConfiguration: instanceConfiguration ?? self.instanceConfiguration ) } public func account() -> Mastodon.Entity.Account? { let account = FileManager .default .accounts(for: self.userIdentifier()) .first(where: { $0.id == userID }) return account } public func userIdentifier() -> MastodonUserIdentifier { MastodonUserIdentifier(domain: domain, userID: userID) } @MainActor func updating(instanceV1 instance: Mastodon.Entity.Instance) -> Self { return copy(instanceConfiguration: .v1(instance)) } @MainActor func updating(instanceV2 instance: Mastodon.Entity.V2.Instance) -> Self { guard let instanceConfiguration, case let InstanceConfiguration.v2(_, translationLanguages) = instanceConfiguration else { return copy(instanceConfiguration: .v2(instance, [:])) } return copy(instanceConfiguration: .v2(instance, translationLanguages)) } @MainActor func updating(translationLanguages: TranslationLanguages) -> Self { switch self.instanceConfiguration { case .v1(let instance): return copy(instanceConfiguration: .v1(instance)) case .v2(let instance, _): return copy(instanceConfiguration: .v2(instance, translationLanguages)) case .none: return self } } func updating(activatedAt: Date) -> Self { copy(activedAt: activatedAt) } var authorization: Mastodon.API.OAuth.Authorization { .init(accessToken: userAccessToken) } }