// // Instance.swift // Pachyderm // // Created by Shadowfacts on 9/9/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import Foundation public struct Instance: Decodable, Sendable { public let uri: String public let title: String public let description: String public let shortDescription: String? public let email: String? public let version: String public let urls: [String: URL] public let thumbnail: URL? public let languages: [String]? public let stats: Stats? public let configuration: Configuration? public let rules: [Rule]? // pleroma doesn't currently implement these public let contactAccount: Account? // superseded by mastodon's configuration.statuses.max_characters, still used by older instances & pleroma let maxTootCharacters: Int? let pollLimits: PollsConfiguration? public var maxStatusCharacters: Int? { configuration?.statuses.maxCharacters ?? maxTootCharacters } public var pollsConfiguration: PollsConfiguration? { configuration?.polls ?? pollLimits } // we need a custom decoder, because all API-compatible implementations don't return some data in the same format public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.uri = try container.decode(String.self, forKey: .uri) self.title = try container.decode(String.self, forKey: .title) self.description = try container.decode(String.self, forKey: .description) self.shortDescription = try container.decodeIfPresent(String.self, forKey: .shortDescription) self.email = try container.decodeIfPresent(String.self, forKey: .email) self.version = try container.decode(String.self, forKey: .version) if let urls = try? container.decodeIfPresent([String: URL].self, forKey: .urls) { self.urls = urls } else { self.urls = [:] } self.languages = try? container.decodeIfPresent([String].self, forKey: .languages) self.contactAccount = try? container.decodeIfPresent(Account.self, forKey: .contactAccount) self.stats = try? container.decodeIfPresent(Stats.self, forKey: .stats) self.thumbnail = try? container.decodeIfPresent(URL.self, forKey: .thumbnail) self.configuration = try? container.decodeIfPresent(Configuration.self, forKey: .configuration) self.rules = try? container.decodeIfPresent([Rule].self, forKey: .rules) if let maxTootCharacters = try? container.decodeIfPresent(Int.self, forKey: .maxTootCharacters) { self.maxTootCharacters = maxTootCharacters } else if let str = try? container.decodeIfPresent(String.self, forKey: .maxTootCharacters), let maxTootCharacters = Int(str, radix: 10) { self.maxTootCharacters = maxTootCharacters } else { self.maxTootCharacters = nil } self.pollLimits = try? container.decodeIfPresent(PollsConfiguration.self, forKey: .pollLimits) } private enum CodingKeys: String, CodingKey { case uri case title case description case shortDescription = "short_description" case email case version case urls case thumbnail case languages case stats case configuration case contactAccount = "contact_account" case rules case maxTootCharacters = "max_toot_chars" case pollLimits = "poll_limits" } } extension Instance { public struct Stats: Decodable, Sendable { public let domainCount: Int? public let statusCount: Int? public let userCount: Int? private enum CodingKeys: String, CodingKey { case domainCount = "domain_count" case statusCount = "status_count" case userCount = "user_count" } } } extension Instance { public struct Configuration: Codable, Sendable { public let statuses: StatusesConfiguration public let mediaAttachments: MediaAttachmentsConfiguration /// Use Instance.pollsConfiguration to support older instance that don't have this nested let polls: PollsConfiguration private enum CodingKeys: String, CodingKey { case statuses case mediaAttachments = "media_attachments" case polls } } } extension Instance { public struct StatusesConfiguration: Codable, Sendable { public let maxCharacters: Int public let maxMediaAttachments: Int public let charactersReservedPerURL: Int private enum CodingKeys: String, CodingKey { case maxCharacters = "max_characters" case maxMediaAttachments = "max_media_attachments" case charactersReservedPerURL = "characters_reserved_per_url" } } } extension Instance { public struct MediaAttachmentsConfiguration: Codable, Sendable { public let supportedMIMETypes: [String] public let imageSizeLimit: Int public let imageMatrixLimit: Int public let videoSizeLimit: Int public let videoFrameRateLimit: Int public let videoMatrixLimit: Int private enum CodingKeys: String, CodingKey { case supportedMIMETypes = "supported_mime_types" case imageSizeLimit = "image_size_limit" case imageMatrixLimit = "image_matrix_limit" case videoSizeLimit = "video_size_limit" case videoFrameRateLimit = "video_frame_rate_limit" case videoMatrixLimit = "video_matrix_limit" } } } extension Instance { public struct PollsConfiguration: Codable, Sendable { public let maxOptions: Int public let maxCharactersPerOption: Int public let minExpiration: TimeInterval public let maxExpiration: TimeInterval private enum CodingKeys: String, CodingKey { case maxOptions = "max_options" case maxCharactersPerOption = "max_characters_per_option" case minExpiration = "min_expiration" case maxExpiration = "max_expiration" } } } extension Instance { public struct Rule: Decodable, Identifiable, Sendable { public let id: String public let text: String } }