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 OAuthSwift
/// Access token returned by the server.
public struct AccessToken: Codable {
/// Access token.
public let token: String
private enum CodingKeys: String, CodingKey {

View File

@ -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 profiles display name.
public let displayName: String?
/// The profiles bio or description.
public let note: String?
/// The location of the users 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 {

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

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
/// 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 {

View File

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

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
/// 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 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.
//
/// Generic protocol for different kind of media attachment metadata types.
public protocol Metadata: Codable {
}

View File

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

View File

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

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
/// 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 users boosts in your home timeline?
public let showingReblogs: Bool
/// Have you enabled notifications for this user?
public let notifying: Bool
/// Are you blocking this users 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 users 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)
}
}
}

View File

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

View File

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

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
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 statuss 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 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 {
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)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = "<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>"; };
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>"; };
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>"; };
@ -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 */,

View File

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

View File

@ -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"]))

View File

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

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

View File

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

View File

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

View File

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

View File

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