// // Status.swift // Pachyderm // // Created by Shadowfacts on 9/9/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import Foundation import WebURL public final class Status: StatusProtocol, Decodable { public let id: String public let uri: String public let url: WebURL? public let account: Account public let inReplyToID: String? public let inReplyToAccountID: String? public let reblog: Status? public let content: String public let createdAt: Date public let emojis: [Emoji] // TODO: missing from pleroma // public let repliesCount: Int public let reblogsCount: Int public let favouritesCount: Int public let reblogged: Bool? public let favourited: Bool? public let muted: Bool? public let sensitive: Bool public let spoilerText: String public let visibility: Visibility public let attachments: [Attachment] public let mentions: [Mention] public let hashtags: [Hashtag] public let application: Application? public let language: String? public let pinned: Bool? public let bookmarked: Bool? public let card: Card? public let poll: Poll? // Hometown, Glitch only public let localOnly: Bool? public var applicationName: String? { application?.name } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(String.self, forKey: .id) self.uri = try container.decode(String.self, forKey: .uri) self.url = try container.decodeIfPresent(WebURL.self, forKey: .url) self.account = try container.decode(Account.self, forKey: .account) self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID) self.inReplyToAccountID = try container.decodeIfPresent(String.self, forKey: .inReplyToAccountID) self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog) self.content = try container.decode(String.self, forKey: .content) self.createdAt = try container.decode(Date.self, forKey: .createdAt) self.emojis = try container.decode([Emoji].self, forKey: .emojis) self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount) self.favouritesCount = try container.decode(Int.self, forKey: .favouritesCount) self.reblogged = try container.decodeIfPresent(Bool.self, forKey: .reblogged) self.favourited = try container.decodeIfPresent(Bool.self, forKey: .favourited) self.muted = try container.decodeIfPresent(Bool.self, forKey: .muted) self.sensitive = try container.decode(Bool.self, forKey: .sensitive) self.spoilerText = try container.decode(String.self, forKey: .spoilerText) if let visibility = try? container.decode(Status.Visibility.self, forKey: .visibility) { self.visibility = visibility self.localOnly = try container.decodeIfPresent(Bool.self, forKey: .localOnly) } else if let s = try? container.decode(String.self, forKey: .visibility), s == "local" { // hacky workaround for #332, akkoma describes local posts with a separate visibility self.visibility = .public self.localOnly = true } else { throw DecodingError.dataCorruptedError(forKey: .visibility, in: container, debugDescription: "Could not decode visibility") } self.attachments = try container.decode([Attachment].self, forKey: .attachments) self.mentions = try container.decode([Mention].self, forKey: .mentions) self.hashtags = try container.decode([Hashtag].self, forKey: .hashtags) self.application = try container.decodeIfPresent(Application.self, forKey: .application) self.language = try container.decodeIfPresent(String.self, forKey: .language) self.pinned = try container.decodeIfPresent(Bool.self, forKey: .pinned) self.bookmarked = try container.decodeIfPresent(Bool.self, forKey: .bookmarked) self.card = try container.decodeIfPresent(Card.self, forKey: .card) self.poll = try container.decodeIfPresent(Poll.self, forKey: .poll) } public static func getContext(_ statusID: String) -> Request { return Request(method: .get, path: "/api/v1/statuses/\(statusID)/context") } public static func getCard(_ status: Status) -> Request { return Request(method: .get, path: "/api/v1/statuses/\(status.id)/card") } public static func getFavourites(_ statusID: String, range: RequestRange = .default) -> Request<[Account]> { var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/favourited_by") request.range = range return request } public static func getReblogs(_ statusID: String, range: RequestRange = .default) -> Request<[Account]> { var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/reblogged_by") request.range = range return request } public static func delete(_ statusID: String) -> Request { return Request(method: .delete, path: "/api/v1/statuses/\(statusID)") } public static func reblog(_ statusID: String, visibility: Visibility? = nil) -> Request { var params: [Parameter] = [] if let visibility { assert([.public, .unlisted, .private].contains(visibility)) params.append("visibility" => visibility.rawValue) } return Request(method: .post, path: "/api/v1/statuses/\(statusID)/reblog", queryParameters: params) } public static func unreblog(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/unreblog") } public static func favourite(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/favourite") } public static func unfavourite(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/unfavourite") } public static func pin(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/pin") } public static func unpin(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/unpin") } public static func bookmark(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/bookmark") } public static func unbookmark(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/unbookmark") } public static func muteConversation(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/mute") } public static func unmuteConversation(_ statusID: String) -> Request { return Request(method: .post, path: "/api/v1/statuses/\(statusID)/unmute") } private enum CodingKeys: String, CodingKey { case id case uri case url case account case inReplyToID = "in_reply_to_id" case inReplyToAccountID = "in_reply_to_account_id" case reblog case content case createdAt = "created_at" case emojis // case repliesCount = "replies_count" case reblogsCount = "reblogs_count" case favouritesCount = "favourites_count" case reblogged case favourited case muted case sensitive case spoilerText = "spoiler_text" case visibility case attachments = "media_attachments" case mentions case hashtags = "tags" case application case language case pinned case bookmarked case card case poll case localOnly = "local_only" } } extension Status { public enum Visibility: String, Codable, CaseIterable { case `public` case unlisted case `private` case direct } } extension Status: Identifiable {}