2023-12-29 11:17:37 +01:00
|
|
|
//Made by Lumaa
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
public final class Status: AnyStatus, Codable, Identifiable, Equatable, Hashable {
|
|
|
|
public static func == (lhs: Status, rhs: Status) -> Bool {
|
|
|
|
lhs.id == rhs.id
|
|
|
|
}
|
|
|
|
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
|
|
hasher.combine(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
public let id: String
|
|
|
|
public let content: HTMLString
|
|
|
|
public let account: Account
|
|
|
|
public let createdAt: ServerDate
|
|
|
|
public let editedAt: ServerDate?
|
|
|
|
public let reblog: ReblogStatus?
|
|
|
|
public let mediaAttachments: [MediaAttachment]
|
|
|
|
public let mentions: [Mention]
|
|
|
|
public let repliesCount: Int
|
|
|
|
public let reblogsCount: Int
|
|
|
|
public let favouritesCount: Int
|
|
|
|
public let card: Card?
|
|
|
|
public let favourited: Bool?
|
|
|
|
public let reblogged: Bool?
|
|
|
|
public let pinned: Bool?
|
|
|
|
public let bookmarked: Bool?
|
|
|
|
public let emojis: [Emoji]
|
|
|
|
public let url: String?
|
|
|
|
public let application: Application?
|
|
|
|
public let inReplyToId: String?
|
|
|
|
public let inReplyToAccountId: String?
|
|
|
|
public let visibility: Visibility
|
|
|
|
public let poll: Poll?
|
|
|
|
public let spoilerText: HTMLString
|
|
|
|
public let filtered: [Filtered]?
|
|
|
|
public let sensitive: Bool
|
|
|
|
public let language: String?
|
|
|
|
|
|
|
|
public init(id: String, content: HTMLString, account: Account, createdAt: ServerDate, editedAt: ServerDate?, reblog: ReblogStatus?, mediaAttachments: [MediaAttachment], mentions: [Mention], repliesCount: Int, reblogsCount: Int, favouritesCount: Int, card: Card?, favourited: Bool?, reblogged: Bool?, pinned: Bool?, bookmarked: Bool?, emojis: [Emoji], url: String?, application: Application?, inReplyToId: String?, inReplyToAccountId: String?, visibility: Visibility, poll: Poll?, spoilerText: HTMLString, filtered: [Filtered]?, sensitive: Bool, language: String?) {
|
|
|
|
self.id = id
|
|
|
|
self.content = content
|
|
|
|
self.account = account
|
|
|
|
self.createdAt = createdAt
|
|
|
|
self.editedAt = editedAt
|
|
|
|
self.reblog = reblog
|
|
|
|
self.mediaAttachments = mediaAttachments
|
|
|
|
self.mentions = mentions
|
|
|
|
self.repliesCount = repliesCount
|
|
|
|
self.reblogsCount = reblogsCount
|
|
|
|
self.favouritesCount = favouritesCount
|
|
|
|
self.card = card
|
|
|
|
self.favourited = favourited
|
|
|
|
self.reblogged = reblogged
|
|
|
|
self.pinned = pinned
|
|
|
|
self.bookmarked = bookmarked
|
|
|
|
self.emojis = emojis
|
|
|
|
self.url = url
|
|
|
|
self.application = application
|
|
|
|
self.inReplyToId = inReplyToId
|
|
|
|
self.inReplyToAccountId = inReplyToAccountId
|
|
|
|
self.visibility = visibility
|
|
|
|
self.poll = poll
|
|
|
|
self.spoilerText = spoilerText
|
|
|
|
self.filtered = filtered
|
|
|
|
self.sensitive = sensitive
|
|
|
|
self.language = language
|
|
|
|
}
|
|
|
|
|
|
|
|
public static func placeholder(forSettings: Bool = false, language: String? = nil) -> Status {
|
|
|
|
.init(id: UUID().uuidString,
|
|
|
|
content: .init(stringValue: "Here's to the [#crazy](#) ones",
|
|
|
|
parseMarkdown: forSettings),
|
|
|
|
|
|
|
|
account: .placeholder(),
|
|
|
|
createdAt: ServerDate(),
|
|
|
|
editedAt: nil,
|
|
|
|
reblog: nil,
|
|
|
|
mediaAttachments: [],
|
|
|
|
mentions: [],
|
|
|
|
repliesCount: 2,
|
|
|
|
reblogsCount: 1,
|
|
|
|
favouritesCount: 3,
|
|
|
|
card: nil,
|
|
|
|
favourited: false,
|
|
|
|
reblogged: false,
|
|
|
|
pinned: false,
|
|
|
|
bookmarked: false,
|
|
|
|
emojis: [],
|
|
|
|
url: "https://example.com",
|
|
|
|
application: nil,
|
|
|
|
inReplyToId: nil,
|
|
|
|
inReplyToAccountId: nil,
|
|
|
|
visibility: .pub,
|
|
|
|
poll: nil,
|
|
|
|
spoilerText: .init(stringValue: ""),
|
|
|
|
filtered: [],
|
|
|
|
sensitive: false,
|
|
|
|
language: language)
|
|
|
|
}
|
|
|
|
|
|
|
|
public static func placeholders() -> [Status] {
|
|
|
|
[.placeholder(), .placeholder(), .placeholder(), .placeholder(), .placeholder()]
|
|
|
|
}
|
|
|
|
|
|
|
|
public var reblogAsAsStatus: Status? {
|
|
|
|
if let reblog {
|
|
|
|
return .init(id: reblog.id,
|
|
|
|
content: reblog.content,
|
|
|
|
account: reblog.account,
|
|
|
|
createdAt: reblog.createdAt,
|
|
|
|
editedAt: reblog.editedAt,
|
|
|
|
reblog: nil,
|
|
|
|
mediaAttachments: reblog.mediaAttachments,
|
|
|
|
mentions: reblog.mentions,
|
|
|
|
repliesCount: reblog.repliesCount,
|
|
|
|
reblogsCount: reblog.reblogsCount,
|
|
|
|
favouritesCount: reblog.favouritesCount,
|
|
|
|
card: reblog.card,
|
|
|
|
favourited: reblog.favourited,
|
|
|
|
reblogged: reblog.reblogged,
|
|
|
|
pinned: reblog.pinned,
|
|
|
|
bookmarked: reblog.bookmarked,
|
|
|
|
emojis: reblog.emojis,
|
|
|
|
url: reblog.url,
|
|
|
|
application: reblog.application,
|
|
|
|
inReplyToId: reblog.inReplyToId,
|
|
|
|
inReplyToAccountId: reblog.inReplyToAccountId,
|
|
|
|
visibility: reblog.visibility,
|
|
|
|
poll: reblog.poll,
|
|
|
|
spoilerText: reblog.spoilerText,
|
|
|
|
filtered: reblog.filtered,
|
|
|
|
sensitive: reblog.sensitive,
|
|
|
|
language: reblog.language)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public final class ReblogStatus: AnyStatus, Codable, Identifiable, Equatable, Hashable {
|
|
|
|
public static func == (lhs: ReblogStatus, rhs: ReblogStatus) -> Bool {
|
|
|
|
lhs.id == rhs.id
|
|
|
|
}
|
|
|
|
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
|
|
hasher.combine(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
public let id: String
|
|
|
|
public let content: HTMLString
|
|
|
|
public let account: Account
|
|
|
|
public let createdAt: ServerDate
|
|
|
|
public let editedAt: ServerDate?
|
|
|
|
public let mediaAttachments: [MediaAttachment]
|
|
|
|
public let mentions: [Mention]
|
|
|
|
public let repliesCount: Int
|
|
|
|
public let reblogsCount: Int
|
|
|
|
public let favouritesCount: Int
|
|
|
|
public let card: Card?
|
|
|
|
public let favourited: Bool?
|
|
|
|
public let reblogged: Bool?
|
|
|
|
public let pinned: Bool?
|
|
|
|
public let bookmarked: Bool?
|
|
|
|
public let emojis: [Emoji]
|
|
|
|
public let url: String?
|
|
|
|
public let application: Application?
|
|
|
|
public let inReplyToId: String?
|
|
|
|
public let inReplyToAccountId: String?
|
|
|
|
public let visibility: Visibility
|
|
|
|
public let poll: Poll?
|
|
|
|
public let spoilerText: HTMLString
|
|
|
|
public let filtered: [Filtered]?
|
|
|
|
public let sensitive: Bool
|
|
|
|
public let language: String?
|
|
|
|
|
|
|
|
public init(id: String, content: HTMLString, account: Account, createdAt: ServerDate, editedAt: ServerDate?, mediaAttachments: [MediaAttachment], mentions: [Mention], repliesCount: Int, reblogsCount: Int, favouritesCount: Int, card: Card?, favourited: Bool?, reblogged: Bool?, pinned: Bool?, bookmarked: Bool?, emojis: [Emoji], url: String?, application: Application? = nil, inReplyToId: String?, inReplyToAccountId: String?, visibility: Visibility, poll: Poll?, spoilerText: HTMLString, filtered: [Filtered]?, sensitive: Bool, language: String?) {
|
|
|
|
self.id = id
|
|
|
|
self.content = content
|
|
|
|
self.account = account
|
|
|
|
self.createdAt = createdAt
|
|
|
|
self.editedAt = editedAt
|
|
|
|
self.mediaAttachments = mediaAttachments
|
|
|
|
self.mentions = mentions
|
|
|
|
self.repliesCount = repliesCount
|
|
|
|
self.reblogsCount = reblogsCount
|
|
|
|
self.favouritesCount = favouritesCount
|
|
|
|
self.card = card
|
|
|
|
self.favourited = favourited
|
|
|
|
self.reblogged = reblogged
|
|
|
|
self.pinned = pinned
|
|
|
|
self.bookmarked = bookmarked
|
|
|
|
self.emojis = emojis
|
|
|
|
self.url = url
|
|
|
|
self.application = application
|
|
|
|
self.inReplyToId = inReplyToId
|
|
|
|
self.inReplyToAccountId = inReplyToAccountId
|
|
|
|
self.visibility = visibility
|
|
|
|
self.poll = poll
|
|
|
|
self.spoilerText = spoilerText
|
|
|
|
self.filtered = filtered
|
|
|
|
self.sensitive = sensitive
|
|
|
|
self.language = language
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Every property in Status is immutable.
|
|
|
|
extension Status: Sendable {}
|
|
|
|
|
|
|
|
// Every property in ReblogStatus is immutable.
|
|
|
|
extension ReblogStatus: Sendable {}
|
|
|
|
|
|
|
|
public protocol AnyStatus {
|
|
|
|
var id: String { get }
|
|
|
|
var content: HTMLString { get }
|
|
|
|
var account: Account { get }
|
|
|
|
var createdAt: ServerDate { get }
|
|
|
|
var editedAt: ServerDate? { get }
|
|
|
|
var mediaAttachments: [MediaAttachment] { get }
|
|
|
|
var mentions: [Mention] { get }
|
|
|
|
var repliesCount: Int { get }
|
|
|
|
var reblogsCount: Int { get }
|
|
|
|
var favouritesCount: Int { get }
|
|
|
|
var card: Card? { get }
|
|
|
|
var favourited: Bool? { get }
|
|
|
|
var reblogged: Bool? { get }
|
|
|
|
var pinned: Bool? { get }
|
|
|
|
var bookmarked: Bool? { get }
|
|
|
|
var emojis: [Emoji] { get }
|
|
|
|
var url: String? { get }
|
|
|
|
var application: Application? { get }
|
|
|
|
var inReplyToId: String? { get }
|
|
|
|
var inReplyToAccountId: String? { get }
|
|
|
|
var visibility: Visibility { get }
|
|
|
|
var poll: Poll? { get }
|
|
|
|
var spoilerText: HTMLString { get }
|
|
|
|
var filtered: [Filtered]? { get }
|
|
|
|
var sensitive: Bool { get }
|
|
|
|
var language: String? { get }
|
|
|
|
}
|
|
|
|
|
2024-01-10 17:46:44 +01:00
|
|
|
public struct StatusContext: Decodable {
|
|
|
|
public let ancestors: [Status]
|
|
|
|
public let descendants: [Status]
|
|
|
|
|
|
|
|
public static func empty() -> StatusContext {
|
|
|
|
.init(ancestors: [], descendants: [])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension StatusContext: Sendable {}
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
public struct MediaAttachment: Codable, Identifiable, Hashable, Equatable {
|
|
|
|
public struct MetaContainer: Codable, Equatable {
|
|
|
|
public struct Meta: Codable, Equatable {
|
|
|
|
public let width: Int?
|
|
|
|
public let height: Int?
|
|
|
|
}
|
|
|
|
|
|
|
|
public let original: Meta?
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum SupportedType: String {
|
|
|
|
case image, gifv, video, audio
|
|
|
|
}
|
|
|
|
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
|
|
hasher.combine(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
public let id: String
|
|
|
|
public let type: String
|
|
|
|
public var supportedType: SupportedType? {
|
|
|
|
SupportedType(rawValue: type)
|
|
|
|
}
|
|
|
|
|
|
|
|
public var localizedTypeDescription: String? {
|
|
|
|
if let supportedType {
|
|
|
|
switch supportedType {
|
|
|
|
case .image:
|
|
|
|
return NSLocalizedString("accessibility.media.supported-type.image.label", bundle: .main, comment: "A localized description of SupportedType.image")
|
|
|
|
case .gifv:
|
|
|
|
return NSLocalizedString("accessibility.media.supported-type.gifv.label", bundle: .main, comment: "A localized description of SupportedType.gifv")
|
|
|
|
case .video:
|
|
|
|
return NSLocalizedString("accessibility.media.supported-type.video.label", bundle: .main, comment: "A localized description of SupportedType.video")
|
|
|
|
case .audio:
|
|
|
|
return NSLocalizedString("accessibility.media.supported-type.audio.label", bundle: .main, comment: "A localized description of SupportedType.audio")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
public let url: URL?
|
|
|
|
public let previewUrl: URL?
|
|
|
|
public let description: String?
|
|
|
|
public let meta: MetaContainer?
|
|
|
|
|
|
|
|
public static func imageWith(url: URL) -> MediaAttachment {
|
|
|
|
.init(id: UUID().uuidString,
|
|
|
|
type: "image",
|
|
|
|
url: url,
|
|
|
|
previewUrl: url,
|
|
|
|
description: "Alternative text",
|
|
|
|
meta: nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension MediaAttachment: Sendable {}
|
|
|
|
extension MediaAttachment.MetaContainer: Sendable {}
|
|
|
|
extension MediaAttachment.MetaContainer.Meta: Sendable {}
|
|
|
|
extension MediaAttachment.SupportedType: Sendable {}
|
|
|
|
|
|
|
|
public struct Mention: Codable, Equatable, Hashable {
|
|
|
|
public let id: String
|
|
|
|
public let username: String
|
|
|
|
public let url: URL
|
|
|
|
public let acct: String
|
|
|
|
}
|
|
|
|
|
|
|
|
extension Mention: Sendable {}
|
|
|
|
|
|
|
|
public struct Card: Codable, Identifiable, Equatable, Hashable {
|
|
|
|
public var id: String {
|
|
|
|
url
|
|
|
|
}
|
|
|
|
|
|
|
|
public let url: String
|
|
|
|
public let title: String?
|
|
|
|
public let description: String?
|
|
|
|
public let type: String
|
|
|
|
public let image: URL?
|
|
|
|
}
|
|
|
|
|
|
|
|
extension Card: Sendable {}
|
|
|
|
|
|
|
|
public struct Application: Codable, Identifiable, Hashable, Equatable, Sendable {
|
|
|
|
public var id: String {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
|
|
|
|
public let name: String
|
|
|
|
public let website: URL?
|
|
|
|
}
|
|
|
|
|
|
|
|
public extension Application {
|
|
|
|
init(from decoder: Decoder) throws {
|
|
|
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
|
|
|
|
name = try values.decodeIfPresent(String.self, forKey: .name) ?? ""
|
|
|
|
website = try? values.decodeIfPresent(URL.self, forKey: .website)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct Filtered: Codable, Equatable, Hashable {
|
|
|
|
public let filter: Filter
|
|
|
|
public let keywordMatches: [String]?
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct Filter: Codable, Identifiable, Equatable, Hashable {
|
|
|
|
public enum Action: String, Codable, Equatable {
|
|
|
|
case warn, hide
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum Context: String, Codable {
|
|
|
|
case home, notifications, account, thread
|
|
|
|
case pub = "public"
|
|
|
|
}
|
|
|
|
|
|
|
|
public let id: String
|
|
|
|
public let title: String
|
|
|
|
public let context: [String]
|
|
|
|
public let filterAction: Action
|
|
|
|
}
|
|
|
|
|
|
|
|
extension Filtered: Sendable {}
|
|
|
|
extension Filter: Sendable {}
|
|
|
|
extension Filter.Action: Sendable {}
|
|
|
|
extension Filter.Context: Sendable {}
|
|
|
|
|
|
|
|
public struct Poll: Codable, Equatable, Hashable {
|
|
|
|
public static func == (lhs: Poll, rhs: Poll) -> Bool {
|
|
|
|
lhs.id == rhs.id
|
|
|
|
}
|
|
|
|
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
|
|
hasher.combine(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct Option: Identifiable, Codable {
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
|
|
case title, votesCount
|
|
|
|
}
|
|
|
|
|
|
|
|
public var id = UUID().uuidString
|
|
|
|
public let title: String
|
|
|
|
public let votesCount: Int?
|
|
|
|
}
|
|
|
|
|
|
|
|
public let id: String
|
|
|
|
public let expiresAt: NullableString
|
|
|
|
public let expired: Bool
|
|
|
|
public let multiple: Bool
|
|
|
|
public let votesCount: Int
|
|
|
|
public let votersCount: Int?
|
|
|
|
public let voted: Bool?
|
|
|
|
public let ownVotes: [Int]?
|
|
|
|
public let options: [Option]
|
|
|
|
|
|
|
|
// the votersCount can be null according to the docs when multiple is false.
|
|
|
|
// Didn't find that to be true, but we make sure
|
|
|
|
public var safeVotersCount: Int {
|
|
|
|
votersCount ?? votesCount
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct NullableString: Codable, Equatable, Hashable {
|
|
|
|
public let value: ServerDate?
|
|
|
|
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
|
|
do {
|
|
|
|
let container = try decoder.singleValueContainer()
|
|
|
|
value = try container.decode(ServerDate.self)
|
|
|
|
} catch {
|
|
|
|
value = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension Poll: Sendable {}
|
|
|
|
extension Poll.Option: Sendable {}
|
|
|
|
extension NullableString: Sendable {}
|