This commit is contained in:
Marcin Czachursk 2023-01-22 13:49:19 +01:00
parent c0deced651
commit ae74c36350
57 changed files with 1378 additions and 242 deletions

View File

@ -7,7 +7,10 @@
import Foundation import Foundation
import OAuthSwift import OAuthSwift
/// Access token returned by the server.
public struct AccessToken: Codable { public struct AccessToken: Codable {
/// Access token.
public let token: String public let token: String
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {

View File

@ -6,21 +6,84 @@
import Foundation import Foundation
/// Represents a user of Mastodon and their associated profile.
public struct Account: Codable { public struct Account: Codable {
/// The account id.
public let id: String public let id: String
/// The username of the account, not including domain.
public let username: String public let username: String
/// The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
public let acct: String public let acct: String
/// The profiles display name.
public let displayName: String? public let displayName: String?
/// The profiles bio or description.
public let note: String? public let note: String?
/// The location of the users profile page.
public let url: URL? public let url: URL?
/// An image icon that is shown next to statuses and in the profile.
public let avatar: URL? 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? 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 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 public let createdAt: String
/// The reported followers of this profile.
public let followersCount: Int public let followersCount: Int
/// The reported follows of this profile.
public let followingCount: Int public let followingCount: Int
/// How many statuses are attached to this account.
public let statusesCount: Int 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 { private enum CodingKeys: String, CodingKey {
case id case id
@ -32,17 +95,32 @@ public struct Account: Codable {
case followingCount = "following_count" case followingCount = "following_count"
case statusesCount = "statuses_count" case statusesCount = "statuses_count"
case displayName = "display_name" case displayName = "display_name"
case avatarStatic = "avatar_static"
case headerStatic = "header_static"
case note case note
case url case url
case avatar case avatar
case header case header
case emojis 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 { extension Account {
public var safeDisplayName: String { 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 { public var displayNameWithoutEmojis: String {

View File

@ -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"
}
}

View File

@ -6,7 +6,68 @@
import Foundation import Foundation
public struct Application: Codable { /// Represents an application that interfaces with the REST API to access accounts or post statuses.
public let name: String public class Application: BaseApplication {
public let website: URL?
/// 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)
}
} }

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -6,8 +6,13 @@
import Foundation import Foundation
/// Represents the tree around a given status. Used for reconstructing threads of statuses.
public struct Context: Codable { public struct Context: Codable {
/// Parents in the thread.
public let ancestors: [Status] public let ancestors: [Status]
/// Children in the thread.
public let descendants: [Status] public let descendants: [Status]
public enum CodingKeys: CodingKey { public enum CodingKeys: CodingKey {

View File

@ -6,16 +6,29 @@
import Foundation import Foundation
public struct Emoji: Codable { /// Represents a custom emoji.
public struct CustomEmoji: Codable {
/// The name of the custom emoji.
public let shortcode: String public let shortcode: String
/// A link to the custom emoji.
public let url: URL public let url: URL
/// A link to a static copy of the custom emoji.
public let staticUrl: URL public let staticUrl: URL
/// Whether this Emoji should be visible in the picker or unlisted.
public let visibleInPicker: Bool public let visibleInPicker: Bool
/// Used for sorting custom emoji in the picker.
public let category: String?
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case shortcode case shortcode
case url case url
case staticUrl = "static_url" case staticUrl = "static_url"
case visibleInPicker = "visible_in_picker" case visibleInPicker = "visible_in_picker"
case category
} }
} }

View File

@ -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
}

View File

@ -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 fields 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"
}
}

View File

@ -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
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -6,8 +6,14 @@
import Foundation import Foundation
/// Focal points for cropping media thumbnails.
/// https://docs.joinmastodon.org/api/guidelines/#focal-points
public struct Focus: Codable { public struct Focus: Codable {
/// X position int he image.
public let x: Int public let x: Int
/// Y position in the image.
public let y: Int public let y: Int
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {

View File

@ -6,10 +6,19 @@
import Foundation import Foundation
/// Infor about image stored in metadata.
public struct ImageInfo: Codable { public struct ImageInfo: Codable {
/// Width of the image.
public let width: Int public let width: Int
/// Height of the image.
public let height: Int public let height: Int
/// Size of the image.
public let size: String public let size: String
/// Aspect ratio of the image.
public let aspect: Double public let aspect: Double
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {

View File

@ -6,9 +6,18 @@
import Foundation 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 { public struct ImageMetadata: Metadata {
/// Metadata about orginal image.
public let original: ImageInfo? public let original: ImageInfo?
/// Metadata about small version of image.
public let small: ImageInfo? public let small: ImageInfo?
/// Focal points for cropping media thumbnails.
public let focus: Focus? public let focus: Focus?
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {

View File

@ -6,27 +6,72 @@
import Foundation import Foundation
/// Represents the software instance of Mastodon running on this domain.
public struct Instance: Codable { 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 title: String?
public let description: String?
public let email: String?
public let thumbnail: String?
enum CodingKeys: CodingKey { /// The version of Mastodon installed on the instance.
case uri 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 title
case version
case sourceUrl = "source_url"
case description case description
case email case usage
case thumbnail case thumbnail
case languages
case configuration
case registrations
case contact
case rules
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) 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.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.description = try? container.decodeIfPresent(String.self, forKey: .description)
self.email = try? container.decodeIfPresent(String.self, forKey: .email) self.usage = try? container.decodeIfPresent(Usage.self, forKey: .usage)
self.thumbnail = try? container.decodeIfPresent(String.self, forKey: .thumbnail) 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)
} }
} }

View File

@ -7,7 +7,10 @@
import Foundation import Foundation
import RegexBuilder import RegexBuilder
/// Link returned in header for paging feature/
public struct Link { public struct Link {
/// Raw value of header link.
public let rawLink: String public let rawLink: String
} }

View File

@ -6,8 +6,13 @@
import Foundation import Foundation
/// Some of endpoint returns JSON data and additional information in header, like link for paging functionality.
public struct Linkable<T> where T: Codable { public struct Linkable<T> where T: Codable {
/// Data retunred in HTTP reponse body (mostly JSON data/entities).
public let data: T public let data: T
/// Link returned in the HTTP header.
public let link: Link? public let link: Link?
public init(data: T, link: Link? = nil) where T: Codable { public init(data: T, link: Link? = nil) where T: Codable {

View File

@ -6,9 +6,16 @@
import Foundation import Foundation
/// Represents the last read position within a user's timelines.
public struct Marker: Codable { public struct Marker: Codable {
/// The ID of the most recently viewed entity.
public let lastReadId: EntityId public let lastReadId: EntityId
/// An incrementing counter, used for locking to prevent write conflicts.
public let version: Int64 public let version: Int64
/// The timestamp of when the marker was set. String (ISO 8601 Datetime).
public let updatedAt: String public let updatedAt: String
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {

View File

@ -6,8 +6,10 @@
import Foundation import Foundation
public class Attachment: Codable { /// Represents a file or media attachment that can be added to a status.
public enum AttachmentType: String, Codable { public class MediaAttachment: Codable {
public enum MediaAttachmentType: String, Codable {
case unknown = "unknown" case unknown = "unknown"
case image = "image" case image = "image"
case gifv = "gifv" case gifv = "gifv"
@ -15,14 +17,29 @@ public class Attachment: Codable {
case audio = "audio" case audio = "audio"
} }
/// The ID of the attachment in the database.
public let id: String 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 public let url: URL
/// The location of a scaled-down preview of the attachment.
public let previewUrl: URL? public let previewUrl: URL?
/// The location of the full-size original attachment on the remote website.
public let remoteUrl: URL? 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? 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? 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? public let meta: Metadata?
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
@ -41,7 +58,7 @@ public class Attachment: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(EntityId.self, forKey: .id) 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.url = try container.decode(URL.self, forKey: .url)
self.previewUrl = try? container.decode(URL.self, forKey: .previewUrl) self.previewUrl = try? container.decode(URL.self, forKey: .previewUrl)
self.remoteUrl = try? container.decode(URL.self, forKey: .remoteUrl) self.remoteUrl = try? container.decode(URL.self, forKey: .remoteUrl)

View File

@ -6,9 +6,18 @@
import Foundation import Foundation
/// Mentions of users within the status content.
public struct Mention: Codable { public struct Mention: Codable {
public let url: String
public let username: String /// The account ID of the mentioned user.
public let acct: String
public let id: String public let id: String
/// The location of the mentioned users 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
} }

View File

@ -4,6 +4,6 @@
// Licensed under the MIT License. // Licensed under the MIT License.
// //
/// Generic protocol for different kind of media attachment metadata types.
public protocol Metadata: Codable { public protocol Metadata: Codable {
} }

View File

@ -6,34 +6,75 @@
import Foundation import Foundation
/// Represents a notification of an event relevant to the user.
public struct Notification: Codable { public struct Notification: Codable {
public enum NotificationType: String, Codable { public enum NotificationType: String, Codable {
/// Someone mentioned you in their status.
case mention = "mention" case mention = "mention"
/// Someone boosted one of your statuses.
case reblog = "reblog" case reblog = "reblog"
/// Someone favourited one of your statuses.
case favourite = "favourite" case favourite = "favourite"
/// Someone followed you.
case follow = "follow" 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 public let type: NotificationType
/// The timestamp of the notification. String (ISO 8601 Datetime).
public let createdAt: String public let createdAt: String
/// The account that performed the action that generated the notification.
public let account: Account 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? 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 { private enum CodingKeys: String, CodingKey {
case id case id
case type case type
case createdAat = "created_at" case createdAat = "created_at"
case account case account
case status case status
case report
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) 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.type = try container.decode(NotificationType.self, forKey: .type)
self.createdAt = try container.decode(String.self, forKey: .createdAat) self.createdAt = try container.decode(String.self, forKey: .createdAat)
self.account = try container.decode(Account.self, forKey: .account) 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 { public func encode(to encoder: Encoder) throws {
@ -46,5 +87,9 @@ public struct Notification: Codable {
if let status { if let status {
try container.encode(status, forKey: .status) try container.encode(status, forKey: .status)
} }
if let report {
try container.encode(report, forKey: .report)
}
} }
} }

View File

@ -6,9 +6,19 @@
import Foundation import Foundation
/// Location where image has been taken.
/// Entity specific for Pixelfed.
public struct Place: Codable { public struct Place: Codable {
/// Id of the entity.
public let id: Int32 public let id: Int32
/// City where picture has been taken.
public let slug: String? public let slug: String?
/// City where picture has been taken.
public let name: String? public let name: String?
/// Country where picture has been taken.
public let country: String? public let country: String?
} }

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -6,20 +6,50 @@
import Foundation import Foundation
/// Represents the relationship between accounts, such as following / blocking / muting / etc.
public struct Relationship: Codable { public struct Relationship: Codable {
public let id: String
/// The account ID.
public let id: EntityId
/// Are you following this user?
public let following: Bool public let following: Bool
/// Are you followed by this user?
public let followedBy: Bool public let followedBy: Bool
/// Are you blocking this user?
public let blocking: Bool public let blocking: Bool
/// Is this user blocking you?
public let blockedBy: Bool public let blockedBy: Bool
/// Are you muting this user?
public let muting: Bool public let muting: Bool
/// Are you muting notifications from this user?
public let mutingNotifications: Bool public let mutingNotifications: Bool
/// Do you have a pending follow request for this user?
public let requested: Bool public let requested: Bool
/// Are you receiving this users boosts in your home timeline?
public let showingReblogs: Bool public let showingReblogs: Bool
/// Have you enabled notifications for this user?
public let notifying: Bool public let notifying: Bool
/// Are you blocking this users domain?
public let domainBlocking: Bool public let domainBlocking: Bool
/// Are you featuring this user on your profile?
public let endorsed: Bool 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 users profile bio.
public let note: String?
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case id case id
@ -34,6 +64,8 @@ public struct Relationship: Codable {
case notifying case notifying
case domainBlocking = "domain_blocking" case domainBlocking = "domain_blocking"
case endorsed case endorsed
case languages
case note
} }
public init(from decoder: Decoder) throws { 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.notifying = (try? container.decode(Bool.self, forKey: .notifying)) ?? false
self.domainBlocking = (try? container.decode(Bool.self, forKey: .domainBlocking)) ?? false self.domainBlocking = (try? container.decode(Bool.self, forKey: .domainBlocking)) ?? false
self.endorsed = (try? container.decode(Bool.self, forKey: .endorsed)) ?? 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 { public func encode(to encoder: Encoder) throws {
@ -66,5 +100,13 @@ public struct Relationship: Codable {
try container.encode(notifying, forKey: .notifying) try container.encode(notifying, forKey: .notifying)
try container.encode(domainBlocking, forKey: .domainBlocking) try container.encode(domainBlocking, forKey: .domainBlocking)
try container.encode(endorsed, forKey: .endorsed) try container.encode(endorsed, forKey: .endorsed)
if let languages {
try container.encode(languages, forKey: .languages)
}
if let note {
try container.encode(note, forKey: .note)
}
} }
} }

View File

@ -6,18 +6,57 @@
import Foundation import Foundation
/// Reports filed against users and/or statuses, to be taken action on by moderators.
public struct Report: Codable { public struct Report: Codable {
public let id: String public enum ReportCategoryTye: String, Codable {
public let actionTaken: String? /// Unwanted or repetitive content
case spam = "spam"
public enum CodingKeys: CodingKey { /// A specific rule was violated
case id case violation = "violation"
case actionTaken /// Some other reason
case other = "other"
} }
public init(from decoder: Decoder) throws { /// The ID of the report in the database.
let container = try decoder.container(keyedBy: CodingKeys.self) public let id: EntityId
self.id = try container.decode(String.self, forKey: .id)
self.actionTaken = try? container.decodeIfPresent(String.self, forKey: .actionTaken) /// 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"
} }
} }

View File

@ -6,12 +6,17 @@
import Foundation import Foundation
public typealias Hashtag = String /// Search results.
public struct Result: Codable { public struct Result: Codable {
/// List of accoutns.
public let accounts: [Account] public let accounts: [Account]
/// List od statuses.
public let statuses: [Status] public let statuses: [Status]
public let hashtags: [Hashtag]
public let hashtags: [Tag]
public enum CodingKeys: CodingKey { public enum CodingKeys: CodingKey {
case accounts case accounts
@ -23,6 +28,6 @@ public struct Result: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.accounts = (try? container.decode([Account].self, forKey: .accounts)) ?? [] self.accounts = (try? container.decode([Account].self, forKey: .accounts)) ?? []
self.statuses = (try? container.decode([Status].self, forKey: .statuses)) ?? [] 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)) ?? []
} }
} }

View File

@ -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
}
}

View File

@ -6,9 +6,7 @@
import Foundation import Foundation
public typealias EntityId = String /// Represents a status posted by an account.
public typealias Html = String
public class Status: Codable { public class Status: Codable {
public enum Visibility: String, Codable { public enum Visibility: String, Codable {
case pub = "public" case pub = "public"
@ -17,34 +15,103 @@ public class Status: Codable {
case direct = "direct" case direct = "direct"
} }
/// ID of the status in the database.
public let id: EntityId public let id: EntityId
/// HTML-encoded status content.
public let content: Html public let content: Html
/// URI of the status used for federation.
public let uri: String? public let uri: String?
/// A link to the statuss HTML representation.
public let url: URL? 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 statuss 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 { private enum CodingKeys: String, CodingKey {
case id case id
case uri case uri
@ -72,6 +139,12 @@ public class Status: Codable {
case tags case tags
case application case application
case place case place
case emojis
case poll
case language
case text
case editedAt = "edited_at"
case filtered
} }
public required init(from decoder: Decoder) throws { 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.account = try container.decode(Account.self, forKey: .account)
self.content = try container.decode(Html.self, forKey: .content) self.content = try container.decode(Html.self, forKey: .content)
self.createdAt = try container.decode(String.self, forKey: .createdAt) 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.inReplyToAccount = try? container.decode(EntityId.self, forKey: .inReplyToAccount)
self.reblog = try? container.decode(Status.self, forKey: .reblog) self.reblog = try? container.decode(Status.self, forKey: .reblog)
self.spoilerText = try? container.decode(String.self, forKey: .spoilerText) 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.pinned = (try? container.decode(Bool.self, forKey: .pinned)) ?? false
self.muted = (try? container.decode(Bool.self, forKey: .muted)) ?? false self.muted = (try? container.decode(Bool.self, forKey: .muted)) ?? false
self.visibility = try container.decode(Visibility.self, forKey: .visibility) self.visibility = try container.decode(Visibility.self, forKey: .visibility)
self.mediaAttachments = (try? container.decode([Attachment].self, forKey: .mediaAttachments)) ?? [] self.mediaAttachments = (try? container.decode([MediaAttachment].self, forKey: .mediaAttachments)) ?? []
self.card = try? container.decode(Card.self, forKey: .card) self.card = try? container.decode(PreviewCard.self, forKey: .card)
self.mentions = (try? container.decode([Mention].self, forKey: .mentions)) ?? [] self.mentions = (try? container.decode([Mention].self, forKey: .mentions)) ?? []
self.tags = (try? container.decode([Tag].self, forKey: .tags)) ?? [] self.tags = (try? container.decode([Tag].self, forKey: .tags)) ?? []
self.application = try? container.decode(Application.self, forKey: .application) self.application = try? container.decode(BaseApplication.self, forKey: .application)
self.place = try? container.decode(Place.self, forKey: .place) 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 { public func encode(to encoder: Encoder) throws {
@ -110,6 +189,7 @@ public class Status: Codable {
try container.encode(id, forKey: .id) try container.encode(id, forKey: .id)
try container.encode(uri, forKey: .uri) try container.encode(uri, forKey: .uri)
if let url { if let url {
try container.encode(url, forKey: .url) try container.encode(url, forKey: .url)
} }
@ -117,18 +197,23 @@ public class Status: Codable {
try container.encode(account, forKey: .account) try container.encode(account, forKey: .account)
try container.encode(content, forKey: .content) try container.encode(content, forKey: .content)
try container.encode(createdAt, forKey: .createdAt) try container.encode(createdAt, forKey: .createdAt)
if let inReplyToId { if let inReplyToId {
try container.encode(inReplyToId, forKey: .inReplyToId) try container.encode(inReplyToId, forKey: .inReplyToId)
} }
if let inReplyToAccount { if let inReplyToAccount {
try container.encode(inReplyToAccount, forKey: .inReplyToAccount) try container.encode(inReplyToAccount, forKey: .inReplyToAccount)
} }
if let reblog { if let reblog {
try container.encode(reblog, forKey: .reblog) try container.encode(reblog, forKey: .reblog)
} }
if let spoilerText { if let spoilerText {
try container.encode(spoilerText, forKey: .spoilerText) try container.encode(spoilerText, forKey: .spoilerText)
} }
try container.encode(reblogsCount, forKey: .reblogsCount) try container.encode(reblogsCount, forKey: .reblogsCount)
try container.encode(favouritesCount, forKey: .favouritesCount) try container.encode(favouritesCount, forKey: .favouritesCount)
try container.encode(repliesCount, forKey: .repliesCount) try container.encode(repliesCount, forKey: .repliesCount)
@ -140,16 +225,44 @@ public class Status: Codable {
try container.encode(sensitive, forKey: .sensitive) try container.encode(sensitive, forKey: .sensitive)
try container.encode(visibility, forKey: .visibility) try container.encode(visibility, forKey: .visibility)
try container.encode(mediaAttachments, forKey: .mediaAttachments) try container.encode(mediaAttachments, forKey: .mediaAttachments)
if let card { if let card {
try container.encode(card, forKey: .card) try container.encode(card, forKey: .card)
} }
try container.encode(mentions, forKey: .mentions) try container.encode(mentions, forKey: .mentions)
try container.encode(tags, forKey: .tags) try container.encode(tags, forKey: .tags)
if let application { if let application {
try container.encode(application, forKey: .application) try container.encode(application, forKey: .application)
} }
if let place { if let place {
try container.encode(place, forKey: .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)
}
} }
} }

View File

@ -6,7 +6,31 @@
import Foundation import Foundation
/// Represents a hashtag used within the content of a status.
public struct Tag: Codable { public struct Tag: Codable {
/// The value of the hashtag after the # sign.
public let name: String public let name: String
/// A link to the hashtag on the instance.
public let url: URL? public let url: URL?
/// Usage statistics for given days (typically the past week).
public let history: [TagHistory]?
/// Whether the current tokens 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
} }

View File

@ -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"
}
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -11,7 +11,7 @@ public extension MastodonClient {
func createApp(named name: String, func createApp(named name: String,
redirectUri: String = "urn:ietf:wg:oauth:2.0:oob", redirectUri: String = "urn:ietf:wg:oauth:2.0:oob",
scopes: Scopes, scopes: Scopes,
website: URL) async throws -> App { website: URL) async throws -> Application {
let request = try Self.request( let request = try Self.request(
for: baseURL, 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( oauthClient = OAuth2Swift(
consumerKey: app.clientId, consumerKey: app.clientId,
consumerSecret: app.clientSecret, 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:)") @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, username: String,
password: String, password: String,
scope: Scopes) async throws -> AccessToken { scope: Scopes) async throws -> AccessToken {

View File

@ -7,10 +7,6 @@
import Foundation import Foundation
import OAuthSwift import OAuthSwift
public typealias Scope = String
public typealias Scopes = [Scope]
public typealias Token = String
public enum MastodonClientError: Swift.Error { public enum MastodonClientError: Swift.Error {
case oAuthCancelled case oAuthCancelled
} }

View File

@ -8,18 +8,18 @@ import Foundation
extension Mastodon { extension Mastodon {
public enum Account { public enum Account {
case account(AccountId) case account(EntityId)
case verifyCredentials case verifyCredentials
case followers(AccountId, MaxId?, SinceId?, MinId?, Limit?, Page?) case followers(EntityId, MaxId?, SinceId?, MinId?, Limit?, Page?)
case following(AccountId, MaxId?, SinceId?, MinId?, Limit?, Page?) case following(EntityId, MaxId?, SinceId?, MinId?, Limit?, Page?)
case statuses(AccountId, Bool, Bool, MaxId?, SinceId?, MinId?, Limit?) case statuses(EntityId, Bool, Bool, MaxId?, SinceId?, MinId?, Limit?)
case follow(AccountId) case follow(EntityId)
case unfollow(AccountId) case unfollow(EntityId)
case block(AccountId) case block(EntityId)
case unblock(AccountId) case unblock(EntityId)
case mute(AccountId) case mute(EntityId)
case unmute(AccountId) case unmute(EntityId)
case relationships([AccountId]) case relationships([EntityId])
case search(SearchQuery, Int) case search(SearchQuery, Int)
} }
} }

View File

@ -6,9 +6,5 @@
import Foundation import Foundation
public typealias AccountId = String public class Mastodon {
public typealias SearchQuery = String
public class Mastodon {
} }

View File

@ -6,14 +6,9 @@
import Foundation import Foundation
public typealias ClientId = String
public typealias ClientSecret = String
public typealias UsernameType = String
public typealias PasswordType = String
extension Mastodon { extension Mastodon {
public enum OAuth { public enum OAuth {
case authenticate(App, UsernameType, PasswordType, String?) case authenticate(Application, UsernameType, PasswordType, String?)
} }
} }

View File

@ -6,12 +6,6 @@
import Foundation import Foundation
public typealias SinceId = EntityId
public typealias MaxId = EntityId
public typealias MinId = EntityId
public typealias Limit = Int
public typealias Page = Int
extension Mastodon { extension Mastodon {
public enum Timelines { public enum Timelines {
case home(MaxId?, SinceId?, MinId?, Limit?) case home(MaxId?, SinceId?, MinId?, Limit?)

View File

@ -96,7 +96,6 @@
F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4329718092001DA3D4 /* AccentsSection.swift */; }; F89D6C4429718092001DA3D4 /* AccentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4329718092001DA3D4 /* AccentsSection.swift */; };
F89D6C4629718193001DA3D4 /* ThemeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4529718193001DA3D4 /* ThemeSection.swift */; }; F89D6C4629718193001DA3D4 /* ThemeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4529718193001DA3D4 /* ThemeSection.swift */; };
F89D6C4A297196FF001DA3D4 /* ImagesViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C49297196FF001DA3D4 /* ImagesViewer.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 */; }; F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; }; F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; };
F8B1E64F2973F61400EE0D10 /* Drops in Frameworks */ = {isa = PBXBuildFile; productRef = F8B1E64E2973F61400EE0D10 /* Drops */; }; 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 = "<group>"; }; F89D6C4329718092001DA3D4 /* AccentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccentsSection.swift; sourceTree = "<group>"; };
F89D6C4529718193001DA3D4 /* ThemeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSection.swift; sourceTree = "<group>"; }; F89D6C4529718193001DA3D4 /* ThemeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeSection.swift; sourceTree = "<group>"; };
F89D6C49297196FF001DA3D4 /* ImagesViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesViewer.swift; sourceTree = "<group>"; }; F89D6C49297196FF001DA3D4 /* ImagesViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesViewer.swift; sourceTree = "<group>"; };
F89D6C4B297197FE001DA3D4 /* ImageViewerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewerViewModel.swift; sourceTree = "<group>"; };
F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; }; F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = "<group>"; };
F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = "<group>"; }; F8A93D7F2965FED4001D8331 /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = "<group>"; };
F8AF2A61297073FE00D2DA3F /* Vernissage20230112-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage20230112-001.xcdatamodel"; sourceTree = "<group>"; }; F8AF2A61297073FE00D2DA3F /* Vernissage20230112-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage20230112-001.xcdatamodel"; sourceTree = "<group>"; };
@ -457,7 +455,6 @@
children = ( children = (
F89992CB296D9231005994BF /* StatusViewModel.swift */, F89992CB296D9231005994BF /* StatusViewModel.swift */,
F89992CD296D92E7005994BF /* AttachmentViewModel.swift */, F89992CD296D92E7005994BF /* AttachmentViewModel.swift */,
F89D6C4B297197FE001DA3D4 /* ImageViewerViewModel.swift */,
F898DE7129728CB2004B4A6A /* CommentViewModel.swift */, F898DE7129728CB2004B4A6A /* CommentViewModel.swift */,
); );
path = ViewModels; path = ViewModels;
@ -650,7 +647,6 @@
F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */, F88FAD25295F3FF7009B20C9 /* FederatedFeedView.swift in Sources */,
F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */, F88FAD32295F5029009B20C9 /* RemoteFileService.swift in Sources */,
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */, F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */,
F89D6C4C297197FE001DA3D4 /* ImageViewerViewModel.swift in Sources */,
F86B7216296BFFDA00EE59EC /* UserProfileStatuses.swift in Sources */, F86B7216296BFFDA00EE59EC /* UserProfileStatuses.swift in Sources */,
F897978F29684BCB00B22335 /* LoadingView.swift in Sources */, F897978F29684BCB00B22335 /* LoadingView.swift in Sources */,
F89992C9296D6DC7005994BF /* CommentBody.swift in Sources */, F89992C9296D6DC7005994BF /* CommentBody.swift in Sources */,

View File

@ -8,7 +8,7 @@ import Foundation
import MastodonKit import MastodonKit
extension AttachmentData { extension AttachmentData {
func copyFrom(_ attachment: Attachment) { func copyFrom(_ attachment: MediaAttachment) {
self.id = attachment.id self.id = attachment.id
self.url = attachment.url self.url = attachment.url
self.blurhash = attachment.blurhash self.blurhash = attachment.blurhash

View File

@ -112,7 +112,7 @@ public class AuthorizationService {
let client = MastodonClient(baseURL: accountData.serverUrl) let client = MastodonClient(baseURL: accountData.serverUrl)
// Create application (we will get clientId amd clientSecret). // 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). // Authorize a user (browser, we will get clientCode).
let oAuthSwiftCredential = try await client.authenticate(app: oAuthApp, scope: Scopes(["read", "write", "follow", "push"])) let oAuthSwiftCredential = try await client.authenticate(app: oAuthApp, scope: Scopes(["read", "write", "follow", "push"]))

View File

@ -9,7 +9,7 @@ import MastodonKit
public class AttachmentViewModel { public class AttachmentViewModel {
public let id: String public let id: String
public let type: Attachment.AttachmentType public let type: MediaAttachment.MediaAttachmentType
public let url: URL public let url: URL
public let previewUrl: URL? public let previewUrl: URL?
@ -28,7 +28,7 @@ public class AttachmentViewModel {
public var exifLens: String? public var exifLens: String?
init(id: String, init(id: String,
type: Attachment.AttachmentType, type: MediaAttachment.MediaAttachmentType,
url: URL, url: URL,
previewUrl: URL? = nil, previewUrl: URL? = nil,
remoteUrl: URL? = nil, remoteUrl: URL? = nil,
@ -60,7 +60,7 @@ public class AttachmentViewModel {
self.data = data self.data = data
} }
init(attachment: Attachment) { init(attachment: MediaAttachment) {
self.id = attachment.id self.id = attachment.id
self.type = attachment.type self.type = attachment.type
self.url = attachment.url self.url = attachment.url

View File

@ -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: []
}

View File

@ -16,7 +16,7 @@ public class StatusViewModel {
public let uri: String? public let uri: String?
public let url: URL? public let url: URL?
public let account: Account public let account: Account
public let inReplyToId: AccountId? public let inReplyToId: EntityId?
public let inReplyToAccount: EntityId? public let inReplyToAccount: EntityId?
public let reblog: Status? public let reblog: Status?
public let createdAt: String public let createdAt: String
@ -32,10 +32,10 @@ public class StatusViewModel {
public let spoilerText: String? public let spoilerText: String?
public let visibility: Status.Visibility public let visibility: Status.Visibility
public let mediaAttachments: [AttachmentViewModel] public let mediaAttachments: [AttachmentViewModel]
public let card: Card? public let card: PreviewCard?
public let mentions: [Mention] public let mentions: [Mention]
public let tags: [Tag] public let tags: [Tag]
public let application: Application? public let application: BaseApplication?
public let place: Place? public let place: Place?
public init( public init(
@ -43,9 +43,9 @@ public class StatusViewModel {
content: Html, content: Html,
uri: String, uri: String,
account: Account, account: Account,
application: Application, application: BaseApplication,
url: URL? = nil, url: URL? = nil,
inReplyToId: AccountId? = nil, inReplyToId: EntityId? = nil,
inReplyToAccount: EntityId? = nil, inReplyToAccount: EntityId? = nil,
reblog: Status? = nil, reblog: Status? = nil,
createdAt: String? = nil, createdAt: String? = nil,
@ -61,7 +61,7 @@ public class StatusViewModel {
spoilerText: String? = nil, spoilerText: String? = nil,
visibility: Status.Visibility = Status.Visibility.pub, visibility: Status.Visibility = Status.Visibility.pub,
mediaAttachments: [AttachmentViewModel] = [], mediaAttachments: [AttachmentViewModel] = [],
card: Card? = nil, card: PreviewCard? = nil,
mentions: [Mention] = [], mentions: [Mention] = [],
tags: [Tag] = [], tags: [Tag] = [],
place: Place? = nil place: Place? = nil

View File

@ -26,7 +26,7 @@ struct FollowersView: View {
.environmentObject(applicationState)) { .environmentObject(applicationState)) {
UsernameRow(accountId: account.id, UsernameRow(accountId: account.id,
accountAvatar: account.avatar, accountAvatar: account.avatar,
accountDisplayName: account.displayName, accountDisplayName: account.displayNameWithoutEmojis,
accountUsername: account.acct) accountUsername: account.acct)
} }
} }

View File

@ -26,7 +26,7 @@ struct FollowingView: View {
.environmentObject(applicationState)) { .environmentObject(applicationState)) {
UsernameRow(accountId: account.id, UsernameRow(accountId: account.id,
accountAvatar: account.avatar, accountAvatar: account.avatar,
accountDisplayName: account.displayName, accountDisplayName: account.displayNameWithoutEmojis,
accountUsername: account.acct) accountUsername: account.acct)
} }
} }

View File

@ -24,21 +24,31 @@ struct NotificationsView: View {
List { List {
ForEach(notifications, id: \.id) { notification in ForEach(notifications, id: \.id) { notification in
switch notification.type { switch notification.type {
case .mention, .reblog, .favourite: case .favourite, .reblog, .mention, .status, .poll, .update:
if let status = notification.status { if let status = notification.status {
NavigationLink(destination: StatusView(statusId: status.id) NavigationLink(destination: StatusView(statusId: status.id)
.environmentObject(applicationState)) { .environmentObject(applicationState)) {
NotificationRow(notification: notification) NotificationRow(notification: notification)
} }
} }
case .follow: case .follow, .followRequest, .adminSignUp:
NavigationLink(destination: UserProfileView( NavigationLink(destination: UserProfileView(
accountId: notification.account.id, accountId: notification.account.id,
accountDisplayName: notification.account.displayName, accountDisplayName: notification.account.displayNameWithoutEmojis,
accountUserName: notification.account.acct) accountUserName: notification.account.acct)
.environmentObject(applicationState)) { .environmentObject(applicationState)) {
NotificationRow(notification: notification) 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: images.append(contentsOf:
mediaAttachment mediaAttachment
.filter({ attachment in .filter({ attachment in
attachment.type == Attachment.AttachmentType.image attachment.type == MediaAttachment.MediaAttachmentType.image
}) })
.map({ .map({
attachment in (id: attachment.id, url: attachment.url) attachment in (id: attachment.id, url: attachment.url)

View File

@ -47,12 +47,12 @@ struct NotificationRow: View {
switch self.notification.type { 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) { if let status = self.notification.status, let statusViewModel = StatusViewModel(status: status) {
HStack(alignment: .top) { HStack(alignment: .top) {
Spacer() Spacer()
if let attachment = statusViewModel.mediaAttachments.filter({ attachment in if let attachment = statusViewModel.mediaAttachments.filter({ attachment in
attachment.type == Attachment.AttachmentType.image attachment.type == MediaAttachment.MediaAttachmentType.image
}).first { }).first {
if let cachedImage = CacheImageService.shared.getImage(for: attachment.id) { if let cachedImage = CacheImageService.shared.getImage(for: attachment.id) {
cachedImage cachedImage
@ -67,7 +67,7 @@ struct NotificationRow: View {
} }
} }
} }
case .follow: case .follow, .followRequest, .adminSignUp:
if let note = self.notification.account.note { if let note = self.notification.account.note {
HTMLFormattedText(note, withFontSize: 12, andWidth: contentWidth) HTMLFormattedText(note, withFontSize: 12, andWidth: contentWidth)
.padding(.top, -4) .padding(.top, -4)
@ -75,6 +75,9 @@ struct NotificationRow: View {
} else { } else {
EmptyView() EmptyView()
} }
case .adminReport:
Text(self.notification.report?.comment ?? "")
.multilineTextAlignment(.leading)
} }
} }
} }
@ -109,6 +112,18 @@ struct NotificationRow: View {
return "boosted" return "boosted"
case .favourite: case .favourite:
return "favourited" 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" return "paperplane"
case .favourite: case .favourite:
return "hand.thumbsup" 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 return Color.accentColor5
case .favourite: case .favourite:
return Color.accentColor6 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
} }
} }
} }