diff --git a/MastodonKit/Sources/MastodonKit/Entities/AccessToken.swift b/MastodonKit/Sources/MastodonKit/Entities/AccessToken.swift index d4d08e4..d9da19b 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/AccessToken.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/AccessToken.swift @@ -7,7 +7,10 @@ import Foundation import OAuthSwift +/// Access token returned by the server. public struct AccessToken: Codable { + + /// Access token. public let token: String private enum CodingKeys: String, CodingKey { diff --git a/MastodonKit/Sources/MastodonKit/Entities/Account.swift b/MastodonKit/Sources/MastodonKit/Entities/Account.swift index c8c6ce9..0f69fb0 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Account.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Account.swift @@ -6,21 +6,84 @@ import Foundation +/// Represents a user of Mastodon and their associated profile. public struct Account: Codable { + + /// The account id. public let id: String + + /// The username of the account, not including domain. public let username: String + + /// The Webfinger account URI. Equal to username for local users, or username@domain for remote users. public let acct: String + + /// The profile’s display name. public let displayName: String? + + /// The profile’s bio or description. public let note: String? + + /// The location of the user’s profile page. public let url: URL? + + /// An image icon that is shown next to statuses and in the profile. public let avatar: URL? + + /// A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF. + public let avatarStatic: URL? + + /// An image banner that is shown above the profile and in profile cards. public let header: URL? + + /// A static version of the header. Equal to header if its value is a static image; different if header is an animated GIF. + public let headerStatic: URL? + + /// Whether the account manually approves follow requests. public let locked: Bool + + /// Additional metadata attached to a profile as name-value pairs. + public let fields: [Field] = [] + + /// When the account was created. String (ISO 8601 Datetime). public let createdAt: String + + /// The reported followers of this profile. public let followersCount: Int + + /// The reported follows of this profile. public let followingCount: Int + + /// How many statuses are attached to this account. public let statusesCount: Int - public let emojis: [Emoji] = [] + + /// Custom emoji entities to be used when rendering the profile. + public let emojis: [CustomEmoji] = [] + + /// Indicates that the account may perform automated actions, may not be monitored, or identifies as a robot. + public let bot: Bool = false + + /// Indicates that the account represents a Group actor. + public let group: Bool = false + + /// Whether the account has opted into discovery features such as the profile directory. + public let discoverable: Bool? + + /// Whether the local user has opted out of being indexed by search engines. + public let noindex: Bool? + + /// Indicates that the profile is currently inactive and that its user has moved to a new account. + public let moved: Bool? + + /// An extra attribute returned only when an account is suspended. + public let suspended: Bool? + + /// An extra attribute returned only when an account is silenced. If true, indicates that the account should be hidden behind a warning screen. + public let limited: Bool? + + /// When the most recent status was posted. + /// NULLABLE String (ISO 8601 Date), or null if no statuses + public let lastStatusAt: String? private enum CodingKeys: String, CodingKey { case id @@ -32,17 +95,32 @@ public struct Account: Codable { case followingCount = "following_count" case statusesCount = "statuses_count" case displayName = "display_name" + case avatarStatic = "avatar_static" + case headerStatic = "header_static" case note case url case avatar case header case emojis + case fields + case bot + case group + case discoverable + case noindex + case moved + case suspended + case limited + case lastStatusAt = "last_status_at" } } extension Account { public var safeDisplayName: String { - return self.displayName ?? self.acct + if let trimmed = self.displayName?.trimmingCharacters(in: .whitespacesAndNewlines), trimmed.count > 0 { + return trimmed + } + + return "@\(self.acct)" } public var displayNameWithoutEmojis: String { diff --git a/MastodonKit/Sources/MastodonKit/Entities/App.swift b/MastodonKit/Sources/MastodonKit/Entities/App.swift deleted file mode 100644 index 3659153..0000000 --- a/MastodonKit/Sources/MastodonKit/Entities/App.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2022 Marcin Czachurski and the repository contributors. -// Licensed under the MIT License. -// - -import Foundation - -public struct App: Codable { - public let id: String - public let name: String - public let redirectUri: String - public let clientId: String - public let clientSecret: String - public let website: String? - public let vapidKey: String? - - public init(clientId: String, clientSecret: String, vapidKey: String = "") { - self.id = "" - self.name = "" - self.redirectUri = "urn:ietf:wg:oauth:2.0:oob" - self.clientId = clientId - self.clientSecret = clientSecret - self.website = nil - self.vapidKey = vapidKey - } - - private enum CodingKeys: String, CodingKey { - case id - case name - case redirectUri = "redirect_uri" - case clientId = "client_id" - case clientSecret = "client_secret" - case website - case vapidKey = "vapid_key" - } -} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Application.swift b/MastodonKit/Sources/MastodonKit/Entities/Application.swift index 3b57cc9..d48ea26 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Application.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Application.swift @@ -6,7 +6,68 @@ import Foundation -public struct Application: Codable { - public let name: String - public let website: URL? +/// Represents an application that interfaces with the REST API to access accounts or post statuses. +public class Application: BaseApplication { + + /// The application id. + public let id: String + + /// Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use urn:ietf:wg:oauth:2.0:oob in this parameter. + public let redirectUri: String + + /// Client ID key, to be used for obtaining OAuth tokens. + public let clientId: String + + /// Client secret key, to be used for obtaining OAuth tokens. + public let clientSecret: String + + /// Used for Push Streaming API. Returned with POST /api/v1/apps. Equivalent to WebPushSubscription#server_key. + public let vapidKey: String? + + private enum CodingKeys: String, CodingKey { + case id + case redirectUri = "redirect_uri" + case clientId = "client_id" + case clientSecret = "client_secret" + case vapidKey = "vapid_key" + } + + public init(clientId: String, clientSecret: String, vapidKey: String = "") { + self.id = "" + self.redirectUri = "urn:ietf:wg:oauth:2.0:oob" + self.clientId = clientId + self.clientSecret = clientSecret + self.vapidKey = vapidKey + + super.init(name: "", website: nil) + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.id = try container.decode(String.self, forKey: .id) + self.redirectUri = try container.decode(String.self, forKey: .redirectUri) + self.clientId = try container.decode(String.self, forKey: .clientId) + self.clientSecret = try container.decode(String.self, forKey: .clientSecret) + self.vapidKey = try? container.decode(String.self, forKey: .vapidKey) + + let superDecoder = try container.superDecoder() + try super.init(from: superDecoder) + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(id, forKey: .id) + try container.encode(redirectUri, forKey: .redirectUri) + try container.encode(clientId, forKey: .clientId) + try container.encode(clientSecret, forKey: .clientSecret) + + if let vapidKey { + try container.encode(vapidKey, forKey: .vapidKey) + } + + let superEncoder = container.superEncoder() + try super.encode(to: superEncoder) + } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/BaseApplication.swift b/MastodonKit/Sources/MastodonKit/Entities/BaseApplication.swift new file mode 100644 index 0000000..7faa911 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/BaseApplication.swift @@ -0,0 +1,45 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents an application that interfaces with the REST API to access accounts or post statuses. +/// Base object is used in statuses etc. +public class BaseApplication: Codable { + + /// The name of your application. + public let name: String + + // The website associated with your application. + public let website: URL? + + init(name: String, website: URL?) { + self.name = name + self.website = website + } + + private enum CodingKeys: String, CodingKey { + case name + case website + } + + public required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.name = try container.decode(String.self, forKey: .name) + self.website = try? container.decode(URL.self, forKey: .website) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(name, forKey: .name) + + if let website { + try container.encode(website, forKey: .website) + } + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Card.swift b/MastodonKit/Sources/MastodonKit/Entities/Card.swift deleted file mode 100644 index 7a21d22..0000000 --- a/MastodonKit/Sources/MastodonKit/Entities/Card.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2022 Marcin Czachurski and the repository contributors. -// Licensed under the MIT License. -// - -import Foundation - -public struct Card: Codable { - public enum CardType: String, Codable { - case link = "link" // Link OEmbed - case photo = "photo" // Photo OEmbed - case video = "video" // Video OEmbed - case rich = "rich" // iframe OEmbed. Not currently accepted, so won't show up in practice. - } - - public let url: URL - public let title: String - public let description: String - public let type: CardType - - public let authorName: String? - public let authorUrl: String? - public let providerName: String? - public let providerUrl: String? - public let html: String? - public let width: Int? - public let height: Int? - public let image: String? - public let embedUrl: String? - public let blurhash: String? - - private enum CodingKeys: String, CodingKey { - case url - case title - case description - case type - - case authorName = "author_name" - case authorUrl = "author_url" - case providerName = "provider_name" - case providerUrl = "provider_url" - case html - case width - case height - case image - case embedUrl = "embed_url" - case blurhash - } -} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Configuration.swift b/MastodonKit/Sources/MastodonKit/Entities/Configuration.swift new file mode 100644 index 0000000..fb67138 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Configuration.swift @@ -0,0 +1,144 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Configured values and limits for this website. +public struct Configuration: Codable { + + /// URLs of interest for clients apps. + public let urls: ConfigurationUrls? + + /// Limits related to accounts. + public let accounts: ConfigurationAccounts? + + /// Limits related to authoring statuses. + public let statuses: ConfigurationStatuses? + + /// Hints for which attachments will be accepted. + public let mediaAttachments: ConfigurationMediaAttachments? + + /// Limits related to polls. + public let polls: ConfigurationPolls? + + /// Hints related to translation. + public let translation: ConfigurationTranslation? + + private enum CodingKeys: String, CodingKey { + case urls + case accounts + case statuses + case mediaAttachments = "media_attachments" + case polls + case translation + } +} + +/// URLs of interest for clients apps. +public struct ConfigurationUrls: Codable { + + /// The Websockets URL for connecting to the streaming API. + public let streaming: String? + + private enum CodingKeys: String, CodingKey { + case streaming + } +} + +/// Limits related to accounts. +public struct ConfigurationAccounts: Codable { + + /// The maximum number of featured tags allowed for each account. + public let maxFeaturedTags: Int? + + private enum CodingKeys: String, CodingKey { + case maxFeaturedTags = "max_featured_tags" + } +} + +/// Limits related to authoring statuses. +public struct ConfigurationStatuses: Codable { + + /// The maximum number of allowed characters per status. + public let maxCharacters: Int + + /// The maximum number of media attachments that can be added to a status. + public let maxMediaAttachments: Int + + /// Each URL in a status will be assumed to be exactly this many characters. + public let charactersReservedPerUrl: Int + + private enum CodingKeys: String, CodingKey { + case maxCharacters = "max_characters" + case maxMediaAttachments = "max_media_attachments" + case charactersReservedPerUrl = "characters_reserved_per_url" + } +} + +/// Hints for which attachments will be accepted. +public struct ConfigurationMediaAttachments: Codable { + + /// Contains MIME types that can be uploaded. + public let supportedMimeTypes: [String]? + + /// The maximum size of any uploaded image, in bytes. + public let imageSizeLimit: Int + + /// The maximum number of pixels (width times height) for image uploads. + public let imageMatrixLimit: Int + + /// The maximum size of any uploaded video, in bytes. + public let videoSizeLimit: Int + + /// The maximum frame rate for any uploaded video. + public let videoFrameRateLimit: Int + + /// The maximum number of pixels (width times height) for video uploads. + 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" + } +} + +/// Limits related to polls. +public struct ConfigurationPolls: Codable { + + /// Each poll is allowed to have up to this many options. + public let maxOptions: Int + + /// Each poll option is allowed to have this many characters. + public let maxCharactersPerOption: Int + + /// The shortest allowed poll duration, in seconds. + public let minExpiration: Int + + /// The longest allowed poll duration, in seconds. + public let maxExpiration: Int + + private enum CodingKeys: String, CodingKey { + case maxOptions = "max_options" + case maxCharactersPerOption = "max_characters_per_option" + case minExpiration = "min_expiration" + case maxExpiration = "max_expiration" + } +} + +/// Hints related to translation. +public struct ConfigurationTranslation: Codable { + + /// Whether the Translations API is available on this instance. + public let enabled: Bool + + private enum CodingKeys: String, CodingKey { + case enabled + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Contact.swift b/MastodonKit/Sources/MastodonKit/Entities/Contact.swift new file mode 100644 index 0000000..e13c633 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Contact.swift @@ -0,0 +1,23 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + + +import Foundation + +/// Hints related to contacting a representative of the website. +public struct Contact: Codable { + + /// An email address that can be messaged regarding inquiries or issues. + public let email: String; + + /// An account that can be contacted natively over the network regarding inquiries or issues. + public let account: Account + + private enum CodingKeys: String, CodingKey { + case email + case account + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Context.swift b/MastodonKit/Sources/MastodonKit/Entities/Context.swift index 886e58b..81c1b41 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Context.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Context.swift @@ -6,8 +6,13 @@ import Foundation +/// Represents the tree around a given status. Used for reconstructing threads of statuses. public struct Context: Codable { + + /// Parents in the thread. public let ancestors: [Status] + + /// Children in the thread. public let descendants: [Status] public enum CodingKeys: CodingKey { diff --git a/MastodonKit/Sources/MastodonKit/Entities/Emoji.swift b/MastodonKit/Sources/MastodonKit/Entities/CustomEmoji.swift similarity index 54% rename from MastodonKit/Sources/MastodonKit/Entities/Emoji.swift rename to MastodonKit/Sources/MastodonKit/Entities/CustomEmoji.swift index 0c6b2aa..b107ac8 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Emoji.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/CustomEmoji.swift @@ -6,16 +6,29 @@ import Foundation -public struct Emoji: Codable { +/// Represents a custom emoji. +public struct CustomEmoji: Codable { + + /// The name of the custom emoji. public let shortcode: String + + /// A link to the custom emoji. public let url: URL + + /// A link to a static copy of the custom emoji. public let staticUrl: URL + + /// Whether this Emoji should be visible in the picker or unlisted. public let visibleInPicker: Bool + + /// Used for sorting custom emoji in the picker. + public let category: String? private enum CodingKeys: String, CodingKey { case shortcode case url case staticUrl = "static_url" case visibleInPicker = "visible_in_picker" + case category } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/ErrorMessage.swift b/MastodonKit/Sources/MastodonKit/Entities/ErrorMessage.swift deleted file mode 100644 index 91032a5..0000000 --- a/MastodonKit/Sources/MastodonKit/Entities/ErrorMessage.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2022 Marcin Czachurski and the repository contributors. -// Licensed under the MIT License. -// - -import Foundation - -public struct ErrorMessage: Codable { - public let error: String -} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Field.swift b/MastodonKit/Sources/MastodonKit/Entities/Field.swift new file mode 100644 index 0000000..706309c --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Field.swift @@ -0,0 +1,27 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Additional metadata attached to a profile as name-value pairs. +public struct Field: Codable { + + /// The key of a given field’s key-value pair. + public let name: String + + /// The value associated with the name key. Type: String (HTML). + public let value: String + + /// Timestamp of when the server verified a URL value for a rel=“me” link. + /// NULLABLE String (ISO 8601 Datetime) if value is a verified URL. Otherwise, null. + public let verifiedAt: String? + + private enum CodingKeys: String, CodingKey { + case name + case value + case verifiedAt = "verified_at" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Filter.swift b/MastodonKit/Sources/MastodonKit/Entities/Filter.swift new file mode 100644 index 0000000..7efe655 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Filter.swift @@ -0,0 +1,62 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a user-defined filter for determining which statuses should not be shown to the user. +public struct Filter: Codable { + public enum FilterContext: String, Codable { + /// Home timeline and lists. + case home = "home" + /// Notifications timeline. + case notifications = "notifications" + /// Public timelines. + case `public` = "public" + /// Expanded thread of a detailed status. + case thread = "thread" + /// When viewing a profile. + case account = "account" + } + + public enum FilterAction: String, Codable { + /// Show a warning that identifies the matching filter by title, and allow the user to expand the filtered status. + /// This is the default (and unknown values should be treated as equivalent to warn). + case warn = "warn" + /// do not show this status if it is received + case hide = "hide" + } + + /// The ID of the Filter in the database. + public let id: EntityId + + /// A title given by the user to name the filter. + public let title: String + + /// The contexts in which the filter should be applied. + public let context: FilterContext + + /// When the filter should no longer be applied. NULLABLE String (ISO 8601 Datetime), or null if the filter does not expire. + public let expiresAt: String? + + /// The action to be taken when a status matches this filter. + public let filterAction: FilterAction + + /// The keywords grouped under this filter. + public let keywords: [FilterKeyword] + + /// The statuses grouped under this filter. + public let statuses: [FilterStatus] + + private enum CodingKeys: String, CodingKey { + case id + case title + case context + case expiresAt = "expires_at" + case filterAction = "filter_action" + case keywords + case statuses + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/FilterKeyword.swift b/MastodonKit/Sources/MastodonKit/Entities/FilterKeyword.swift new file mode 100644 index 0000000..7eec673 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/FilterKeyword.swift @@ -0,0 +1,26 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a keyword that, if matched, should cause the filter action to be taken. +public struct FilterKeyword: Codable { + + /// The ID of the FilterKeyword in the database. + public let id: EntityId + + /// The phrase to be matched against. + public let keyword: String + + /// Should the filter consider word boundaries? See [implementation guidelines for filters](https://docs.joinmastodon.org/api/guidelines/#filters). + public let wholeWord: Bool + + private enum CodingKeys: String, CodingKey { + case id + case keyword + case wholeWord = "whole_word" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/FilterResult.swift b/MastodonKit/Sources/MastodonKit/Entities/FilterResult.swift new file mode 100644 index 0000000..447f84d --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/FilterResult.swift @@ -0,0 +1,26 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a filter whose keywords matched a given status. +public struct FilterResult: Codable { + + /// The filter that was matched. + public let filter: Filter + + /// The keyword within the filter that was matched. + public let keywordMatches: [String]? + + /// The status ID within the filter that was matched. + public let statusMatches: [EntityId]? + + private enum CodingKeys: String, CodingKey { + case filter + case keywordMatches = "keyword_matches" + case statusMatches = "status_matches" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/FilterStatus.swift b/MastodonKit/Sources/MastodonKit/Entities/FilterStatus.swift new file mode 100644 index 0000000..1ae48ad --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/FilterStatus.swift @@ -0,0 +1,22 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a status ID that, if matched, should cause the filter action to be taken. +public struct FilterStatus: Codable { + + /// The ID of the FilterStatus in the database. + public let id: EntityId + + /// The ID of the filtered Status in the database. + public let statusId: EntityId + + private enum CodingKeys: String, CodingKey { + case id + case statusId = "status_id" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Focus.swift b/MastodonKit/Sources/MastodonKit/Entities/Focus.swift index 0f88287..2625075 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Focus.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Focus.swift @@ -6,8 +6,14 @@ import Foundation +/// Focal points for cropping media thumbnails. +/// https://docs.joinmastodon.org/api/guidelines/#focal-points public struct Focus: Codable { + + /// X position int he image. public let x: Int + + /// Y position in the image. public let y: Int private enum CodingKeys: String, CodingKey { diff --git a/MastodonKit/Sources/MastodonKit/Entities/ImageInfo.swift b/MastodonKit/Sources/MastodonKit/Entities/ImageInfo.swift index 67f79b2..3f3cf77 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/ImageInfo.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/ImageInfo.swift @@ -6,10 +6,19 @@ import Foundation +/// Infor about image stored in metadata. public struct ImageInfo: Codable { + + /// Width of the image. public let width: Int + + /// Height of the image. public let height: Int + + /// Size of the image. public let size: String + + /// Aspect ratio of the image. public let aspect: Double private enum CodingKeys: String, CodingKey { diff --git a/MastodonKit/Sources/MastodonKit/Entities/ImageMetadata.swift b/MastodonKit/Sources/MastodonKit/Entities/ImageMetadata.swift index 7708653..06d0c8d 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/ImageMetadata.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/ImageMetadata.swift @@ -6,9 +6,18 @@ import Foundation +/// Metadata returned by Paperclip. May contain subtrees small and original, as well as various other top-level properties. +/// More importantly, there may be another topl-level focus Hash object on images as of 2.3.0, +/// with coordinates can be used for smart thumbnail cropping – see Focal points for cropped media thumbnails for more. public struct ImageMetadata: Metadata { + + /// Metadata about orginal image. public let original: ImageInfo? + + /// Metadata about small version of image. public let small: ImageInfo? + + /// Focal points for cropping media thumbnails. public let focus: Focus? private enum CodingKeys: String, CodingKey { diff --git a/MastodonKit/Sources/MastodonKit/Entities/Instance.swift b/MastodonKit/Sources/MastodonKit/Entities/Instance.swift index 6c17bd3..298a696 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Instance.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Instance.swift @@ -6,27 +6,72 @@ import Foundation +/// Represents the software instance of Mastodon running on this domain. public struct Instance: Codable { - public let uri: String + /// The domain name of the instance. + public let domain: String + + /// The title of the website. public let title: String? - public let description: String? - public let email: String? - public let thumbnail: String? - enum CodingKeys: CodingKey { - case uri + /// The version of Mastodon installed on the instance. + public let version: String + + /// The URL for the source code of the software running on this instance, in keeping with AGPL license requirements. + public let sourceUrl: URL? + + /// A short, plain-text description defined by the admin. + public let description: String? + + /// Usage data for this instance. + public let usage: Usage? + + /// The URL for the thumbnail image. + public let thumbnail: Thumbnail? + + /// Primary languages of the website and its staff. Array of String (ISO 639-1 two-letter code). + public let languages: [String]? + + /// Configured values and limits for this website. + public let configuration: Configuration? + + /// Information about registering for this website. + public let registrations: Registration? + + /// Hints related to contacting a representative of the website. + public let contact: Contact? + + /// An itemized list of rules for this website. + public let rules: [Rule]? + + enum CodingKeys: String, CodingKey { + case domain case title + case version + case sourceUrl = "source_url" case description - case email + case usage case thumbnail + case languages + case configuration + case registrations + case contact + case rules } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.uri = try container.decode(String.self, forKey: .uri) + self.domain = try container.decode(String.self, forKey: .domain) self.title = try? container.decodeIfPresent(String.self, forKey: .title) + self.version = try container.decode(String.self, forKey: .version) + self.sourceUrl = try? container.decodeIfPresent(URL.self, forKey: .sourceUrl) self.description = try? container.decodeIfPresent(String.self, forKey: .description) - self.email = try? container.decodeIfPresent(String.self, forKey: .email) - self.thumbnail = try? container.decodeIfPresent(String.self, forKey: .thumbnail) + self.usage = try? container.decodeIfPresent(Usage.self, forKey: .usage) + self.thumbnail = try? container.decodeIfPresent(Thumbnail.self, forKey: .thumbnail) + self.languages = try? container.decodeIfPresent([String].self, forKey: .languages) + self.configuration = try? container.decodeIfPresent(Configuration.self, forKey: .configuration) + self.registrations = try? container.decodeIfPresent(Registration.self, forKey: .registrations) + self.contact = try? container.decodeIfPresent(Contact.self, forKey: .contact) + self.rules = try? container.decodeIfPresent([Rule].self, forKey: .rules) } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Link.swift b/MastodonKit/Sources/MastodonKit/Entities/Link.swift index 851dbc3..be058a0 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Link.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Link.swift @@ -7,7 +7,10 @@ import Foundation import RegexBuilder +/// Link returned in header for paging feature/ public struct Link { + + /// Raw value of header link. public let rawLink: String } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Linkable.swift b/MastodonKit/Sources/MastodonKit/Entities/Linkable.swift index 7836d8c..58ca040 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Linkable.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Linkable.swift @@ -6,8 +6,13 @@ import Foundation +/// Some of endpoint returns JSON data and additional information in header, like link for paging functionality. public struct Linkable where T: Codable { + + /// Data retunred in HTTP reponse body (mostly JSON data/entities). public let data: T + + /// Link returned in the HTTP header. public let link: Link? public init(data: T, link: Link? = nil) where T: Codable { diff --git a/MastodonKit/Sources/MastodonKit/Entities/Markers.swift b/MastodonKit/Sources/MastodonKit/Entities/Markers.swift index 8a1c8ed..aa96614 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Markers.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Markers.swift @@ -6,9 +6,16 @@ import Foundation +/// Represents the last read position within a user's timelines. public struct Marker: Codable { + + /// The ID of the most recently viewed entity. public let lastReadId: EntityId + + /// An incrementing counter, used for locking to prevent write conflicts. public let version: Int64 + + /// The timestamp of when the marker was set. String (ISO 8601 Datetime). public let updatedAt: String private enum CodingKeys: String, CodingKey { diff --git a/MastodonKit/Sources/MastodonKit/Entities/Attachment.swift b/MastodonKit/Sources/MastodonKit/Entities/MediaAttachment.swift similarity index 69% rename from MastodonKit/Sources/MastodonKit/Entities/Attachment.swift rename to MastodonKit/Sources/MastodonKit/Entities/MediaAttachment.swift index e64804d..f9ce683 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Attachment.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/MediaAttachment.swift @@ -6,8 +6,10 @@ import Foundation -public class Attachment: Codable { - public enum AttachmentType: String, Codable { +/// Represents a file or media attachment that can be added to a status. +public class MediaAttachment: Codable { + + public enum MediaAttachmentType: String, Codable { case unknown = "unknown" case image = "image" case gifv = "gifv" @@ -15,14 +17,29 @@ public class Attachment: Codable { case audio = "audio" } + /// The ID of the attachment in the database. public let id: String - public let type: AttachmentType + + /// The type of the attachment. + public let type: MediaAttachmentType + + /// The location of the original full-size attachment. public let url: URL + + /// The location of a scaled-down preview of the attachment. public let previewUrl: URL? + /// The location of the full-size original attachment on the remote website. public let remoteUrl: URL? + + /// Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load. public let description: String? + + /// A hash computed by the [BlurHash](https://github.com/woltapp/blurhash) algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. public let blurhash: String? + + /// Metadata returned by Paperclip. + /// May contain subtrees small and original, as well as various other top-level properties. public let meta: Metadata? private enum CodingKeys: String, CodingKey { @@ -41,7 +58,7 @@ public class Attachment: Codable { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(EntityId.self, forKey: .id) - self.type = try container.decode(AttachmentType.self, forKey: .type) + self.type = try container.decode(MediaAttachmentType.self, forKey: .type) self.url = try container.decode(URL.self, forKey: .url) self.previewUrl = try? container.decode(URL.self, forKey: .previewUrl) self.remoteUrl = try? container.decode(URL.self, forKey: .remoteUrl) diff --git a/MastodonKit/Sources/MastodonKit/Entities/Mention.swift b/MastodonKit/Sources/MastodonKit/Entities/Mention.swift index c6974ce..3848a09 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Mention.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Mention.swift @@ -6,9 +6,18 @@ import Foundation +/// Mentions of users within the status content. public struct Mention: Codable { - public let url: String - public let username: String - public let acct: String + + /// The account ID of the mentioned user. public let id: String + + /// The location of the mentioned user’s profile. + public let url: String + + /// The username of the mentioned user. + public let username: String + + /// The webfinger acct: URI of the mentioned user. Equivalent to username for local users, or username@domain for remote users. + public let acct: String } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Meta.swift b/MastodonKit/Sources/MastodonKit/Entities/Meta.swift index 5a242cc..ac1a19e 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Meta.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Meta.swift @@ -4,6 +4,6 @@ // Licensed under the MIT License. // +/// Generic protocol for different kind of media attachment metadata types. public protocol Metadata: Codable { - } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Notification.swift b/MastodonKit/Sources/MastodonKit/Entities/Notification.swift index 6c2cf0e..1e5bd33 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Notification.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Notification.swift @@ -6,34 +6,75 @@ import Foundation +/// Represents a notification of an event relevant to the user. public struct Notification: Codable { public enum NotificationType: String, Codable { + /// Someone mentioned you in their status. case mention = "mention" + + /// Someone boosted one of your statuses. case reblog = "reblog" + + /// Someone favourited one of your statuses. case favourite = "favourite" + + /// Someone followed you. case follow = "follow" + + /// Someone you enabled notifications for has posted a status. + case status = "status" + + /// Someone requested to follow you. + case followRequest = "follow_request" + + /// A poll you have voted in or created has ended. + case poll = "poll" + + /// A status you interacted with has been edited. + case update = "update" + + /// Someone signed up (optionally sent to admins). + case adminSignUp = "admin.sign_up" + + /// A new report has been filed. + case adminReport = "admin.report" } - public let id: String + + /// The id of the notification in the database. + public let id: EntityId + + /// The type of event that resulted in the notification. public let type: NotificationType + + /// The timestamp of the notification. String (ISO 8601 Datetime). public let createdAt: String + + /// The account that performed the action that generated the notification. public let account: Account + + /// Status that was the object of the notification. Attached when type of the notification is favourite, reblog, status, mention, poll, or update. public let status: Status? + /// Report that was the object of the notification. Attached when type of the notification is admin.report. + public let report: Report? + private enum CodingKeys: String, CodingKey { case id case type case createdAat = "created_at" case account case status + case report } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) + self.id = try container.decode(EntityId.self, forKey: .id) self.type = try container.decode(NotificationType.self, forKey: .type) self.createdAt = try container.decode(String.self, forKey: .createdAat) self.account = try container.decode(Account.self, forKey: .account) - self.status = try? container.decode(Status.self, forKey: .status) + self.status = try? container.decodeIfPresent(Status.self, forKey: .status) + self.report = try? container.decodeIfPresent(Report.self, forKey: .report) } public func encode(to encoder: Encoder) throws { @@ -46,5 +87,9 @@ public struct Notification: Codable { if let status { try container.encode(status, forKey: .status) } + + if let report { + try container.encode(report, forKey: .report) + } } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Place.swift b/MastodonKit/Sources/MastodonKit/Entities/Place.swift index fc1f295..2592a4c 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Place.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Place.swift @@ -6,9 +6,19 @@ import Foundation +/// Location where image has been taken. +/// Entity specific for Pixelfed. public struct Place: Codable { + + /// Id of the entity. public let id: Int32 + + /// City where picture has been taken. public let slug: String? + + /// City where picture has been taken. public let name: String? + + /// Country where picture has been taken. public let country: String? } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Poll.swift b/MastodonKit/Sources/MastodonKit/Entities/Poll.swift new file mode 100644 index 0000000..085dc89 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Poll.swift @@ -0,0 +1,54 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a poll attached to a status. +public struct Poll: Codable { + + /// The ID of the poll in the database. + public let id: EntityId + + /// When the poll ends. NULLABLE String (ISO 8601 Datetime), or null if the poll does not end. + public let expiresAt: String? + + /// Is the poll currently expired? + public let expired: Bool + + /// Does the poll allow multiple-choice answers? + public let multiple: Bool + + /// How many votes have been received. + public let votesCount: Int + + /// How many unique accounts have voted on a multiple-choice poll. + public let votersCount: Int? + + /// Possible answers for the poll. + public let options: [PollOption] + + /// Custom emoji to be used for rendering poll options. + public let emojis: [CustomEmoji]? + + /// When called with a user token, has the authorized user voted? + public let voted: Bool? + + /// When called with a user token, which options has the authorized user chosen? Contains an array of index values for options. + public let ownVotes: [Int]? + + private enum CodingKeys: String, CodingKey { + case id + case expiresAt = "expires_at" + case expired + case multiple + case votesCount = "votes_count" + case votersCount = "voters_count" + case options + case emojis + case voted + case ownVotes = "own_votes" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/PollOption.swift b/MastodonKit/Sources/MastodonKit/Entities/PollOption.swift new file mode 100644 index 0000000..321324a --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/PollOption.swift @@ -0,0 +1,22 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Possible answers for the poll. +public struct PollOption: Codable { + + /// The text value of the poll option. + public let title: String + + /// he total number of received votes for this option. NULLABLE Integer, or null if results are not published yet. + public let votesCount: Int? + + private enum CodingKeys: String, CodingKey { + case title + case votesCount = "votes_count" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/PreviewCard.swift b/MastodonKit/Sources/MastodonKit/Entities/PreviewCard.swift new file mode 100644 index 0000000..2c6d3a5 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/PreviewCard.swift @@ -0,0 +1,77 @@ +// +// https://mczachurski.dev +// Copyright © 2022 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a rich preview card that is generated using OpenGraph tags from a URL. +public struct PreviewCard: Codable { + public enum PreviewCardType: String, Codable { + case link = "link" + case photo = "photo" + case video = "video" + case rich = "rich" + } + + /// Location of linked resource. + public let url: URL + + /// Title of linked resource. + public let title: String + + /// Description of preview. + public let description: String + + /// The type of the preview card. + public let type: PreviewCardType + + /// The author of the original resource. + public let authorName: String? + + /// A link to the author of the original resource. + public let authorUrl: String? + + /// The provider of the original resource. + public let providerName: String? + + /// A link to the provider of the original resource. + public let providerUrl: String? + + /// HTML to be used for generating the preview card. + public let html: String? + + /// Width of preview, in pixels. + public let width: Int? + + /// Height of preview, in pixels. + public let height: Int? + + /// Preview thumbnail. + public let image: URL? + + /// Used for photo embeds, instead of custom html. + public let embedUrl: URL? + + /// A hash computed by the [BlurHash](https://github.com/woltapp/blurhash) algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. + public let blurhash: String? + + private enum CodingKeys: String, CodingKey { + case url + case title + case description + case type + + case authorName = "author_name" + case authorUrl = "author_url" + case providerName = "provider_name" + case providerUrl = "provider_url" + case html + case width + case height + case image + case embedUrl = "embed_url" + case blurhash + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Registration.swift b/MastodonKit/Sources/MastodonKit/Entities/Registration.swift new file mode 100644 index 0000000..43a0b8b --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Registration.swift @@ -0,0 +1,26 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Information about registering for this website. +public struct Registration: Codable { + + /// Whether registrations are enabled. + public let enabled: Bool + + /// Whether registrations require moderator approval. + public let approvalRequired: Bool + + /// A custom message to be shown when registrations are closed. String (HTML) or null. + public let message: String? + + private enum CodingKeys: String, CodingKey { + case enabled + case approvalRequired = "approval_required" + case message + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Relationship.swift b/MastodonKit/Sources/MastodonKit/Entities/Relationship.swift index 4807250..d8dd71a 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Relationship.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Relationship.swift @@ -6,20 +6,50 @@ import Foundation +/// Represents the relationship between accounts, such as following / blocking / muting / etc. public struct Relationship: Codable { - public let id: String + + /// The account ID. + public let id: EntityId + + /// Are you following this user? public let following: Bool + + /// Are you followed by this user? public let followedBy: Bool + + /// Are you blocking this user? public let blocking: Bool + + /// Is this user blocking you? public let blockedBy: Bool + + /// Are you muting this user? public let muting: Bool + + /// Are you muting notifications from this user? public let mutingNotifications: Bool + + /// Do you have a pending follow request for this user? public let requested: Bool + + /// Are you receiving this user’s boosts in your home timeline? public let showingReblogs: Bool + + /// Have you enabled notifications for this user? public let notifying: Bool + + /// Are you blocking this user’s domain? public let domainBlocking: Bool + + /// Are you featuring this user on your profile? public let endorsed: Bool + /// Which languages are you following from this user? Array of String (ISO 639-1 language two-letter code). + public let languages: [String]? + + /// This user’s profile bio. + public let note: String? private enum CodingKeys: String, CodingKey { case id @@ -34,6 +64,8 @@ public struct Relationship: Codable { case notifying case domainBlocking = "domain_blocking" case endorsed + case languages + case note } public init(from decoder: Decoder) throws { @@ -50,6 +82,8 @@ public struct Relationship: Codable { self.notifying = (try? container.decode(Bool.self, forKey: .notifying)) ?? false self.domainBlocking = (try? container.decode(Bool.self, forKey: .domainBlocking)) ?? false self.endorsed = (try? container.decode(Bool.self, forKey: .endorsed)) ?? false + self.languages = try? container.decodeIfPresent([String].self, forKey: .languages) + self.note = try? container.decodeIfPresent(String.self, forKey: .note) } public func encode(to encoder: Encoder) throws { @@ -66,5 +100,13 @@ public struct Relationship: Codable { try container.encode(notifying, forKey: .notifying) try container.encode(domainBlocking, forKey: .domainBlocking) try container.encode(endorsed, forKey: .endorsed) + + if let languages { + try container.encode(languages, forKey: .languages) + } + + if let note { + try container.encode(note, forKey: .note) + } } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Report.swift b/MastodonKit/Sources/MastodonKit/Entities/Report.swift index 00bd301..44120f5 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Report.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Report.swift @@ -6,18 +6,57 @@ import Foundation +/// Reports filed against users and/or statuses, to be taken action on by moderators. public struct Report: Codable { - public let id: String - public let actionTaken: String? - - public enum CodingKeys: CodingKey { - case id - case actionTaken + public enum ReportCategoryTye: String, Codable { + /// Unwanted or repetitive content + case spam = "spam" + /// A specific rule was violated + case violation = "violation" + /// Some other reason + case other = "other" } - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.id = try container.decode(String.self, forKey: .id) - self.actionTaken = try? container.decodeIfPresent(String.self, forKey: .actionTaken) + /// The ID of the report in the database. + public let id: EntityId + + /// Whether an action was taken yet. + public let actionTaken: String? + + /// When an action was taken against the report. NULLABLE String (ISO 8601 Datetime) or null. + public let actionTakenAt: String? + + /// The generic reason for the report. + public let category: ReportCategoryTye + + /// The reason for the report. + public let comment: String + + /// Whether the report was forwarded to a remote domain. + public let forwarded: Bool + + /// When the report was created. String (ISO 8601 Datetime). + public let createdAt: String + + /// List od statuses in the report. + public let statusIds: [EntityId]? + + /// List of the rules in ther report. + public let ruleIds: [EntityId]? + + /// The account that was reported. + public let targetAccount: Account + + public enum CodingKeys: String, CodingKey { + case id + case actionTaken + case actionTakenAt = "action_taken_at" + case category + case comment + case forwarded + case createdAt = "created_at" + case statusIds = "status_ids" + case ruleIds = "rule_ids" + case targetAccount = "target_account" } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Result.swift b/MastodonKit/Sources/MastodonKit/Entities/Result.swift index c25ee3d..96220bc 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Result.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Result.swift @@ -6,12 +6,17 @@ import Foundation -public typealias Hashtag = String - +/// Search results. public struct Result: Codable { + + /// List of accoutns. public let accounts: [Account] + + /// List od statuses. public let statuses: [Status] - public let hashtags: [Hashtag] + + + public let hashtags: [Tag] public enum CodingKeys: CodingKey { case accounts @@ -23,6 +28,6 @@ public struct Result: Codable { let container = try decoder.container(keyedBy: CodingKeys.self) self.accounts = (try? container.decode([Account].self, forKey: .accounts)) ?? [] self.statuses = (try? container.decode([Status].self, forKey: .statuses)) ?? [] - self.hashtags = (try? container.decode([Hashtag].self, forKey: .hashtags)) ?? [] + self.hashtags = (try? container.decode([Tag].self, forKey: .hashtags)) ?? [] } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Rule.swift b/MastodonKit/Sources/MastodonKit/Entities/Rule.swift new file mode 100644 index 0000000..b080cd8 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Rule.swift @@ -0,0 +1,22 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Represents a rule that server users should follow. +public struct Rule: Codable { + + /// An identifier for the rule. + public let id: EntityId + + /// The rule to be followed. + public let text: String + + private enum CodingKeys: String, CodingKey { + case id + case text + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Status.swift b/MastodonKit/Sources/MastodonKit/Entities/Status.swift index 8dc36d4..2739a12 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Status.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Status.swift @@ -6,9 +6,7 @@ import Foundation -public typealias EntityId = String -public typealias Html = String - +/// Represents a status posted by an account. public class Status: Codable { public enum Visibility: String, Codable { case pub = "public" @@ -17,34 +15,103 @@ public class Status: Codable { case direct = "direct" } + /// ID of the status in the database. public let id: EntityId + + /// HTML-encoded status content. public let content: Html + /// URI of the status used for federation. public let uri: String? + + /// A link to the status’s HTML representation. public let url: URL? - public let account: Account - public let inReplyToId: AccountId? - public let inReplyToAccount: EntityId? - public let reblog: Status? - public let createdAt: String - public let reblogsCount: Int - public let favouritesCount: Int - public let repliesCount: Int - public let reblogged: Bool - public let favourited: Bool - public let sensitive: Bool - public let bookmarked: Bool - public let pinned: Bool - public let muted: Bool - public let spoilerText: String? - public let visibility: Visibility - public let mediaAttachments: [Attachment] - public let card: Card? - public let mentions: [Mention] - public let tags: [Tag] - public let application: Application? - public let place: Place? + /// The account that authored this status. + public let account: Account + + /// ID of the status being replied to. + public let inReplyToId: EntityId? + + /// ID of the account that authored the status being replied to. + public let inReplyToAccount: EntityId? + + /// The status being reblogged. + public let reblog: Status? + + /// The date when this status was created. String (ISO 8601 Datetime). + public let createdAt: String + + /// How many boosts this status has received. + public let reblogsCount: Int + + /// How many favourites this status has received. + public let favouritesCount: Int + + /// How many replies this status has received. + public let repliesCount: Int + + /// If the current token has an authorized user: Have you boosted this status? + public let reblogged: Bool + + /// If the current token has an authorized user: Have you favourited this status? + public let favourited: Bool + + /// Is this status marked as sensitive content? + public let sensitive: Bool + + /// If the current token has an authorized user: Have you bookmarked this status? + public let bookmarked: Bool + + /// If the current token has an authorized user: Have you pinned this status? Only appears if the status is pinnable. + public let pinned: Bool + + /// If the current token has an authorized user: Have you muted notifications for this status’s conversation? + public let muted: Bool + + /// Subject or summary line, below which status content is collapsed until expanded. + public let spoilerText: String? + + /// Visibility of this status. + public let visibility: Visibility + + /// Media that is attached to this status. + public let mediaAttachments: [MediaAttachment] + + /// Preview card for links included within status content. + public let card: PreviewCard? + + /// Mentions of users within the status content. + public let mentions: [Mention] + + /// Hashtags used within the status content. + public let tags: [Tag] + + /// The application used to post this status. + public let application: BaseApplication? + + /// Place where photo has been taken (specific for Pixelfed). + public let place: Place? + + /// Custom emoji to be used when rendering status content. + public let emojis: [CustomEmoji]? + + /// The poll attached to the status. + public let poll: Poll? + + /// Primary language of this status. NULLABLE String (ISO 639 Part 1 two-letter language code) or null. + public let language: String? + + /// Plain-text source of a status. Returned instead of content when status is deleted, so the user may redraft from the source text + /// without the client having to reverse-engineer the original text from the HTML content. + public let text: String? + + /// Timestamp of when the status was last edited. NULLABLE String (ISO 8601 Datetime). + public let editedAt: String? + + /// If the current token has an authorized user: The filter and keywords that matched this status. + public let filtered: FilterResult? + private enum CodingKeys: String, CodingKey { case id case uri @@ -72,6 +139,12 @@ public class Status: Codable { case tags case application case place + case emojis + case poll + case language + case text + case editedAt = "edited_at" + case filtered } public required init(from decoder: Decoder) throws { @@ -83,7 +156,7 @@ public class Status: Codable { self.account = try container.decode(Account.self, forKey: .account) self.content = try container.decode(Html.self, forKey: .content) self.createdAt = try container.decode(String.self, forKey: .createdAt) - self.inReplyToId = try? container.decode(AccountId.self, forKey: .inReplyToId) + self.inReplyToId = try? container.decode(EntityId.self, forKey: .inReplyToId) self.inReplyToAccount = try? container.decode(EntityId.self, forKey: .inReplyToAccount) self.reblog = try? container.decode(Status.self, forKey: .reblog) self.spoilerText = try? container.decode(String.self, forKey: .spoilerText) @@ -97,12 +170,18 @@ public class Status: Codable { self.pinned = (try? container.decode(Bool.self, forKey: .pinned)) ?? false self.muted = (try? container.decode(Bool.self, forKey: .muted)) ?? false self.visibility = try container.decode(Visibility.self, forKey: .visibility) - self.mediaAttachments = (try? container.decode([Attachment].self, forKey: .mediaAttachments)) ?? [] - self.card = try? container.decode(Card.self, forKey: .card) + self.mediaAttachments = (try? container.decode([MediaAttachment].self, forKey: .mediaAttachments)) ?? [] + self.card = try? container.decode(PreviewCard.self, forKey: .card) self.mentions = (try? container.decode([Mention].self, forKey: .mentions)) ?? [] self.tags = (try? container.decode([Tag].self, forKey: .tags)) ?? [] - self.application = try? container.decode(Application.self, forKey: .application) - self.place = try? container.decode(Place.self, forKey: .place) + self.application = try? container.decode(BaseApplication.self, forKey: .application) + self.place = try? container.decodeIfPresent(Place.self, forKey: .place) + self.emojis = try? container.decodeIfPresent([CustomEmoji].self, forKey: .emojis) + self.poll = try? container.decodeIfPresent(Poll.self, forKey: .poll) + self.language = try? container.decodeIfPresent(String.self, forKey: .language) + self.text = try? container.decodeIfPresent(String.self, forKey: .text) + self.editedAt = try? container.decodeIfPresent(String.self, forKey: .editedAt) + self.filtered = try? container.decodeIfPresent(FilterResult.self, forKey: .filtered) } public func encode(to encoder: Encoder) throws { @@ -110,6 +189,7 @@ public class Status: Codable { try container.encode(id, forKey: .id) try container.encode(uri, forKey: .uri) + if let url { try container.encode(url, forKey: .url) } @@ -117,18 +197,23 @@ public class Status: Codable { try container.encode(account, forKey: .account) try container.encode(content, forKey: .content) try container.encode(createdAt, forKey: .createdAt) + if let inReplyToId { try container.encode(inReplyToId, forKey: .inReplyToId) } + if let inReplyToAccount { try container.encode(inReplyToAccount, forKey: .inReplyToAccount) } + if let reblog { try container.encode(reblog, forKey: .reblog) } + if let spoilerText { try container.encode(spoilerText, forKey: .spoilerText) } + try container.encode(reblogsCount, forKey: .reblogsCount) try container.encode(favouritesCount, forKey: .favouritesCount) try container.encode(repliesCount, forKey: .repliesCount) @@ -140,16 +225,44 @@ public class Status: Codable { try container.encode(sensitive, forKey: .sensitive) try container.encode(visibility, forKey: .visibility) try container.encode(mediaAttachments, forKey: .mediaAttachments) + if let card { try container.encode(card, forKey: .card) } + try container.encode(mentions, forKey: .mentions) try container.encode(tags, forKey: .tags) + if let application { try container.encode(application, forKey: .application) } + if let place { try container.encode(place, forKey: .place) } + + if let emojis { + try container.encode(emojis, forKey: .emojis) + } + + if let poll { + try container.encode(poll, forKey: .poll) + } + + if let language { + try container.encode(language, forKey: .language) + } + + if let text { + try container.encode(text, forKey: .text) + } + + if let editedAt { + try container.encode(editedAt, forKey: .editedAt) + } + + if let filtered { + try container.encode(filtered, forKey: .filtered) + } } } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Tag.swift b/MastodonKit/Sources/MastodonKit/Entities/Tag.swift index 53266a4..2e8088f 100644 --- a/MastodonKit/Sources/MastodonKit/Entities/Tag.swift +++ b/MastodonKit/Sources/MastodonKit/Entities/Tag.swift @@ -6,7 +6,31 @@ import Foundation +/// Represents a hashtag used within the content of a status. public struct Tag: Codable { + + /// The value of the hashtag after the # sign. public let name: String + + /// A link to the hashtag on the instance. public let url: URL? + + /// Usage statistics for given days (typically the past week). + public let history: [TagHistory]? + + /// Whether the current token’s authorized user is following this tag. + public let following: Bool? +} + +/// Usage statistics for given days. +public struct TagHistory: Codable { + + /// UNIX timestamp on midnight of the given day. + public let day: String + + /// The counted usage of the tag within that day. + public let uses: String + + /// he total of accounts using the tag within that day (cast from an integer). + public let accounts: String } diff --git a/MastodonKit/Sources/MastodonKit/Entities/Thumbnail.swift b/MastodonKit/Sources/MastodonKit/Entities/Thumbnail.swift new file mode 100644 index 0000000..728f410 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Thumbnail.swift @@ -0,0 +1,41 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// An image used to represent this instance. +public struct Thumbnail: Codable { + + /// The URL for the thumbnail image. + public let url: URL + + /// A hash computed by the [BlurHash](https://github.com/woltapp/blurhash) algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. + public let blurhash: String? + + /// Links to scaled resolution images, for high DPI screens. + public let versions: ThumbnailVersions? + + private enum CodingKeys: String, CodingKey { + case url + case blurhash + case versions + } +} + +/// Links to scaled resolution images, for high DPI screens. +public struct ThumbnailVersions: Codable { + + /// The URL for the thumbnail image at 1x resolution. + public let x1: URL? + + /// The URL for the thumbnail image at 2x resolution. + public let x2: URL? + + private enum CodingKeys: String, CodingKey { + case x1 = "@1x" + case x2 = "@2x" + } +} diff --git a/MastodonKit/Sources/MastodonKit/Entities/Types.swift b/MastodonKit/Sources/MastodonKit/Entities/Types.swift new file mode 100644 index 0000000..9336ed5 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Types.swift @@ -0,0 +1,26 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +public typealias EntityId = String +public typealias Html = String +public typealias SearchQuery = String + +public typealias ClientId = String +public typealias ClientSecret = String +public typealias UsernameType = String +public typealias PasswordType = String + +public typealias SinceId = EntityId +public typealias MaxId = EntityId +public typealias MinId = EntityId +public typealias Limit = Int +public typealias Page = Int + +public typealias Scope = String +public typealias Scopes = [Scope] +public typealias Token = String diff --git a/MastodonKit/Sources/MastodonKit/Entities/Usage.swift b/MastodonKit/Sources/MastodonKit/Entities/Usage.swift new file mode 100644 index 0000000..4fae5e7 --- /dev/null +++ b/MastodonKit/Sources/MastodonKit/Entities/Usage.swift @@ -0,0 +1,29 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import Foundation + +/// Usage data for this instance. +public struct Usage: Codable { + + /// Usage data related to users on this instance. + public let users: UsageUsers + + private enum CodingKeys: String, CodingKey { + case users + } +} + +// Usage data related to users on this instance. +public struct UsageUsers: Codable { + + /// The number of active users in the past 4 weeks. + public let activeMonth: Int + + private enum CodingKeys: String, CodingKey { + case activeMonth = "active_month" + } +} diff --git a/MastodonKit/Sources/MastodonKit/MastodonClient+Convenience.swift b/MastodonKit/Sources/MastodonKit/MastodonClient+Convenience.swift index ac69296..d268511 100644 --- a/MastodonKit/Sources/MastodonKit/MastodonClient+Convenience.swift +++ b/MastodonKit/Sources/MastodonKit/MastodonClient+Convenience.swift @@ -11,7 +11,7 @@ public extension MastodonClient { func createApp(named name: String, redirectUri: String = "urn:ietf:wg:oauth:2.0:oob", scopes: Scopes, - website: URL) async throws -> App { + website: URL) async throws -> Application { let request = try Self.request( for: baseURL, @@ -23,10 +23,10 @@ public extension MastodonClient { ) ) - return try await downloadJson(App.self, request: request) + return try await downloadJson(Application.self, request: request) } - func authenticate(app: App, scope: Scopes) async throws -> OAuthSwiftCredential { // todo: we should not load OAuthSwift objects here + func authenticate(app: Application, scope: Scopes) async throws -> OAuthSwiftCredential { // todo: we should not load OAuthSwift objects here oauthClient = OAuth2Swift( consumerKey: app.clientId, consumerSecret: app.clientSecret, @@ -59,7 +59,7 @@ public extension MastodonClient { } @available(*, deprecated, message: "The password flow is discoured and won't support 2FA. Please use authentiate(app:, scope:)") - func getToken(withApp app: App, + func getToken(withApp app: Application, username: String, password: String, scope: Scopes) async throws -> AccessToken { diff --git a/MastodonKit/Sources/MastodonKit/MastodonClient.swift b/MastodonKit/Sources/MastodonKit/MastodonClient.swift index 11b5747..9cd0779 100644 --- a/MastodonKit/Sources/MastodonKit/MastodonClient.swift +++ b/MastodonKit/Sources/MastodonKit/MastodonClient.swift @@ -7,10 +7,6 @@ import Foundation import OAuthSwift -public typealias Scope = String -public typealias Scopes = [Scope] -public typealias Token = String - public enum MastodonClientError: Swift.Error { case oAuthCancelled } diff --git a/MastodonKit/Sources/MastodonKit/Targets/Accounts.swift b/MastodonKit/Sources/MastodonKit/Targets/Accounts.swift index 93951a9..4305103 100644 --- a/MastodonKit/Sources/MastodonKit/Targets/Accounts.swift +++ b/MastodonKit/Sources/MastodonKit/Targets/Accounts.swift @@ -8,18 +8,18 @@ import Foundation extension Mastodon { public enum Account { - case account(AccountId) + case account(EntityId) case verifyCredentials - case followers(AccountId, MaxId?, SinceId?, MinId?, Limit?, Page?) - case following(AccountId, MaxId?, SinceId?, MinId?, Limit?, Page?) - case statuses(AccountId, Bool, Bool, MaxId?, SinceId?, MinId?, Limit?) - case follow(AccountId) - case unfollow(AccountId) - case block(AccountId) - case unblock(AccountId) - case mute(AccountId) - case unmute(AccountId) - case relationships([AccountId]) + case followers(EntityId, MaxId?, SinceId?, MinId?, Limit?, Page?) + case following(EntityId, MaxId?, SinceId?, MinId?, Limit?, Page?) + case statuses(EntityId, Bool, Bool, MaxId?, SinceId?, MinId?, Limit?) + case follow(EntityId) + case unfollow(EntityId) + case block(EntityId) + case unblock(EntityId) + case mute(EntityId) + case unmute(EntityId) + case relationships([EntityId]) case search(SearchQuery, Int) } } diff --git a/MastodonKit/Sources/MastodonKit/Targets/Mastodon.swift b/MastodonKit/Sources/MastodonKit/Targets/Mastodon.swift index c8c1442..0c2f24d 100644 --- a/MastodonKit/Sources/MastodonKit/Targets/Mastodon.swift +++ b/MastodonKit/Sources/MastodonKit/Targets/Mastodon.swift @@ -6,9 +6,5 @@ import Foundation -public typealias AccountId = String -public typealias SearchQuery = String - -public class Mastodon { - +public class Mastodon { } diff --git a/MastodonKit/Sources/MastodonKit/Targets/OAuth.swift b/MastodonKit/Sources/MastodonKit/Targets/OAuth.swift index 2674ad4..018fd9b 100644 --- a/MastodonKit/Sources/MastodonKit/Targets/OAuth.swift +++ b/MastodonKit/Sources/MastodonKit/Targets/OAuth.swift @@ -6,14 +6,9 @@ import Foundation -public typealias ClientId = String -public typealias ClientSecret = String -public typealias UsernameType = String -public typealias PasswordType = String - extension Mastodon { public enum OAuth { - case authenticate(App, UsernameType, PasswordType, String?) + case authenticate(Application, UsernameType, PasswordType, String?) } } diff --git a/MastodonKit/Sources/MastodonKit/Targets/Timelines.swift b/MastodonKit/Sources/MastodonKit/Targets/Timelines.swift index 86acb7a..ea9c9a4 100644 --- a/MastodonKit/Sources/MastodonKit/Targets/Timelines.swift +++ b/MastodonKit/Sources/MastodonKit/Targets/Timelines.swift @@ -6,12 +6,6 @@ import Foundation -public typealias SinceId = EntityId -public typealias MaxId = EntityId -public typealias MinId = EntityId -public typealias Limit = Int -public typealias Page = Int - extension Mastodon { public enum Timelines { case home(MaxId?, SinceId?, MinId?, Limit?) diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 406eb47..c3fbc9d 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -96,7 +96,6 @@ F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4329718092001DA3D4 /* AccentsSection.swift */; }; F89D6C4629718193001DA3D4 /* ThemeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4529718193001DA3D4 /* ThemeSection.swift */; }; F89D6C4A297196FF001DA3D4 /* ImagesViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C49297196FF001DA3D4 /* ImagesViewer.swift */; }; - F89D6C4C297197FE001DA3D4 /* ImageViewerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4B297197FE001DA3D4 /* ImageViewerViewModel.swift */; }; F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; }; F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; }; F8B1E64F2973F61400EE0D10 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = F8B1E64E2973F61400EE0D10 /* Drops */; }; @@ -196,7 +195,6 @@ F89D6C4329718092001DA3D4 /* AccentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentsSection.swift; sourceTree = ""; }; F89D6C4529718193001DA3D4 /* ThemeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSection.swift; sourceTree = ""; }; F89D6C49297196FF001DA3D4 /* ImagesViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesViewer.swift; sourceTree = ""; }; - F89D6C4B297197FE001DA3D4 /* ImageViewerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewerViewModel.swift; sourceTree = ""; }; F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = ""; }; F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = ""; }; F8AF2A61297073FE00D2DA3F /* Vernissage20230112-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage20230112-001.xcdatamodel"; sourceTree = ""; }; @@ -457,7 +455,6 @@ children = ( F89992CB296D9231005994BF /* StatusViewModel.swift */, F89992CD296D92E7005994BF /* AttachmentViewModel.swift */, - F89D6C4B297197FE001DA3D4 /* ImageViewerViewModel.swift */, F898DE7129728CB2004B4A6A /* CommentViewModel.swift */, ); path = ViewModels; @@ -650,7 +647,6 @@ F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */, F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */, F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */, - F89D6C4C297197FE001DA3D4 /* ImageViewerViewModel.swift in Sources */, F86B7216296BFFDA00EE59EC /* UserProfileStatuses.swift in Sources */, F897978F29684BCB00B22335 /* LoadingView.swift in Sources */, F89992C9296D6DC7005994BF /* CommentBody.swift in Sources */, diff --git a/Vernissage/CoreData/AttachmentData+Attachment.swift b/Vernissage/CoreData/AttachmentData+Attachment.swift index 9a477bc..653efae 100644 --- a/Vernissage/CoreData/AttachmentData+Attachment.swift +++ b/Vernissage/CoreData/AttachmentData+Attachment.swift @@ -8,7 +8,7 @@ import Foundation import MastodonKit extension AttachmentData { - func copyFrom(_ attachment: Attachment) { + func copyFrom(_ attachment: MediaAttachment) { self.id = attachment.id self.url = attachment.url self.blurhash = attachment.blurhash diff --git a/Vernissage/Services/AuthorizationService.swift b/Vernissage/Services/AuthorizationService.swift index fe05fda..9854607 100644 --- a/Vernissage/Services/AuthorizationService.swift +++ b/Vernissage/Services/AuthorizationService.swift @@ -112,7 +112,7 @@ public class AuthorizationService { let client = MastodonClient(baseURL: accountData.serverUrl) // Create application (we will get clientId amd clientSecret). - let oAuthApp = App(clientId: accountData.clientId, clientSecret: accountData.clientSecret) + let oAuthApp = Application(clientId: accountData.clientId, clientSecret: accountData.clientSecret) // Authorize a user (browser, we will get clientCode). let oAuthSwiftCredential = try await client.authenticate(app: oAuthApp, scope: Scopes(["read", "write", "follow", "push"])) diff --git a/Vernissage/ViewModels/AttachmentViewModel.swift b/Vernissage/ViewModels/AttachmentViewModel.swift index ce6df0a..a52cc0b 100644 --- a/Vernissage/ViewModels/AttachmentViewModel.swift +++ b/Vernissage/ViewModels/AttachmentViewModel.swift @@ -9,7 +9,7 @@ import MastodonKit public class AttachmentViewModel { public let id: String - public let type: Attachment.AttachmentType + public let type: MediaAttachment.MediaAttachmentType public let url: URL public let previewUrl: URL? @@ -28,7 +28,7 @@ public class AttachmentViewModel { public var exifLens: String? init(id: String, - type: Attachment.AttachmentType, + type: MediaAttachment.MediaAttachmentType, url: URL, previewUrl: URL? = nil, remoteUrl: URL? = nil, @@ -60,7 +60,7 @@ public class AttachmentViewModel { self.data = data } - init(attachment: Attachment) { + init(attachment: MediaAttachment) { self.id = attachment.id self.type = attachment.type self.url = attachment.url diff --git a/Vernissage/ViewModels/ImageViewerViewModel.swift b/Vernissage/ViewModels/ImageViewerViewModel.swift deleted file mode 100644 index d3ec71d..0000000 --- a/Vernissage/ViewModels/ImageViewerViewModel.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2023 Marcin Czachurski and the repository contributors. -// Licensed under the MIT License. -// - - -import Foundation - -public struct ImageViewerViewModel { - // public var images: [] -} diff --git a/Vernissage/ViewModels/StatusViewModel.swift b/Vernissage/ViewModels/StatusViewModel.swift index c5e442c..99f2772 100644 --- a/Vernissage/ViewModels/StatusViewModel.swift +++ b/Vernissage/ViewModels/StatusViewModel.swift @@ -16,7 +16,7 @@ public class StatusViewModel { public let uri: String? public let url: URL? public let account: Account - public let inReplyToId: AccountId? + public let inReplyToId: EntityId? public let inReplyToAccount: EntityId? public let reblog: Status? public let createdAt: String @@ -32,10 +32,10 @@ public class StatusViewModel { public let spoilerText: String? public let visibility: Status.Visibility public let mediaAttachments: [AttachmentViewModel] - public let card: Card? + public let card: PreviewCard? public let mentions: [Mention] public let tags: [Tag] - public let application: Application? + public let application: BaseApplication? public let place: Place? public init( @@ -43,9 +43,9 @@ public class StatusViewModel { content: Html, uri: String, account: Account, - application: Application, + application: BaseApplication, url: URL? = nil, - inReplyToId: AccountId? = nil, + inReplyToId: EntityId? = nil, inReplyToAccount: EntityId? = nil, reblog: Status? = nil, createdAt: String? = nil, @@ -61,7 +61,7 @@ public class StatusViewModel { spoilerText: String? = nil, visibility: Status.Visibility = Status.Visibility.pub, mediaAttachments: [AttachmentViewModel] = [], - card: Card? = nil, + card: PreviewCard? = nil, mentions: [Mention] = [], tags: [Tag] = [], place: Place? = nil diff --git a/Vernissage/Views/FollowersView.swift b/Vernissage/Views/FollowersView.swift index ba1ff5a..8cc75c2 100644 --- a/Vernissage/Views/FollowersView.swift +++ b/Vernissage/Views/FollowersView.swift @@ -26,7 +26,7 @@ struct FollowersView: View { .environmentObject(applicationState)) { UsernameRow(accountId: account.id, accountAvatar: account.avatar, - accountDisplayName: account.displayName, + accountDisplayName: account.displayNameWithoutEmojis, accountUsername: account.acct) } } diff --git a/Vernissage/Views/FollowingView.swift b/Vernissage/Views/FollowingView.swift index 98564bb..58c1de9 100644 --- a/Vernissage/Views/FollowingView.swift +++ b/Vernissage/Views/FollowingView.swift @@ -26,7 +26,7 @@ struct FollowingView: View { .environmentObject(applicationState)) { UsernameRow(accountId: account.id, accountAvatar: account.avatar, - accountDisplayName: account.displayName, + accountDisplayName: account.displayNameWithoutEmojis, accountUsername: account.acct) } } diff --git a/Vernissage/Views/NotificationsView.swift b/Vernissage/Views/NotificationsView.swift index 0243d0b..e299ffe 100644 --- a/Vernissage/Views/NotificationsView.swift +++ b/Vernissage/Views/NotificationsView.swift @@ -24,21 +24,31 @@ struct NotificationsView: View { List { ForEach(notifications, id: \.id) { notification in switch notification.type { - case .mention, .reblog, .favourite: + case .favourite, .reblog, .mention, .status, .poll, .update: if let status = notification.status { NavigationLink(destination: StatusView(statusId: status.id) .environmentObject(applicationState)) { NotificationRow(notification: notification) } } - case .follow: + case .follow, .followRequest, .adminSignUp: NavigationLink(destination: UserProfileView( accountId: notification.account.id, - accountDisplayName: notification.account.displayName, + accountDisplayName: notification.account.displayNameWithoutEmojis, accountUserName: notification.account.acct) .environmentObject(applicationState)) { NotificationRow(notification: notification) } + case .adminReport: + if let targetAccount = notification.report?.targetAccount { + NavigationLink(destination: UserProfileView( + accountId: targetAccount.id, + accountDisplayName: targetAccount.displayNameWithoutEmojis, + accountUserName: targetAccount.acct) + .environmentObject(applicationState)) { + NotificationRow(notification: notification) + } + } } } @@ -164,7 +174,7 @@ struct NotificationsView: View { images.append(contentsOf: mediaAttachment .filter({ attachment in - attachment.type == Attachment.AttachmentType.image + attachment.type == MediaAttachment.MediaAttachmentType.image }) .map({ attachment in (id: attachment.id, url: attachment.url) diff --git a/Vernissage/Widgets/NotificationsView/NotificationRow.swift b/Vernissage/Widgets/NotificationsView/NotificationRow.swift index 710bb9d..ec3e0fb 100644 --- a/Vernissage/Widgets/NotificationsView/NotificationRow.swift +++ b/Vernissage/Widgets/NotificationsView/NotificationRow.swift @@ -47,12 +47,12 @@ struct NotificationRow: View { switch self.notification.type { - case .favourite, .reblog, .mention: + case .favourite, .reblog, .mention, .status, .poll, .update: if let status = self.notification.status, let statusViewModel = StatusViewModel(status: status) { HStack(alignment: .top) { Spacer() if let attachment = statusViewModel.mediaAttachments.filter({ attachment in - attachment.type == Attachment.AttachmentType.image + attachment.type == MediaAttachment.MediaAttachmentType.image }).first { if let cachedImage = CacheImageService.shared.getImage(for: attachment.id) { cachedImage @@ -67,7 +67,7 @@ struct NotificationRow: View { } } } - case .follow: + case .follow, .followRequest, .adminSignUp: if let note = self.notification.account.note { HTMLFormattedText(note, withFontSize: 12, andWidth: contentWidth) .padding(.top, -4) @@ -75,6 +75,9 @@ struct NotificationRow: View { } else { EmptyView() } + case .adminReport: + Text(self.notification.report?.comment ?? "") + .multilineTextAlignment(.leading) } } } @@ -109,6 +112,18 @@ struct NotificationRow: View { return "boosted" case .favourite: return "favourited" + case .status: + return "posted status" + case .followRequest: + return "follow request" + case .poll: + return "poll" + case .update: + return "updated post" + case .adminSignUp: + return "signed up" + case .adminReport: + return "new report" } } @@ -122,6 +137,18 @@ struct NotificationRow: View { return "paperplane" case .favourite: return "hand.thumbsup" + case .status: + return "photo.on.rectangle.angled" + case .followRequest: + return "person.badge.clock" + case .poll: + return "checklist" + case .update: + return "text.below.photo" + case .adminSignUp: + return "person.badge.key" + case .adminReport: + return "exclamationmark.bubble" } } @@ -135,6 +162,18 @@ struct NotificationRow: View { return Color.accentColor5 case .favourite: return Color.accentColor6 + case .status: + return Color.accentColor1 + case .followRequest: + return Color.accentColor2 + case .poll: + return Color.accentColor7 + case .update: + return Color.accentColor8 + case .adminSignUp: + return Color.accentColor9 + case .adminReport: + return Color.accentColor10 } } }