Create MastodonKit
This commit is contained in:
parent
476e515423
commit
44f224768c
|
@ -0,0 +1,37 @@
|
|||
// swift-tools-version: 5.7
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MastodonKit",
|
||||
platforms: [
|
||||
.iOS(.v13),
|
||||
.macOS(.v12),
|
||||
.watchOS(.v8)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "MastodonKit",
|
||||
targets: ["MastodonKit"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(
|
||||
url: "https://github.com/OAuthSwift/OAuthSwift.git",
|
||||
.upToNextMajor(from: "2.2.0")
|
||||
)
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "MastodonKit",
|
||||
dependencies: ["OAuthSwift"]),
|
||||
.testTarget(
|
||||
name: "MastodonKitTests",
|
||||
dependencies: ["MastodonKit"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
import Foundation
|
||||
import OAuthSwift
|
||||
|
||||
public struct AccessToken: Codable {
|
||||
public let token: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case token = "access_token"
|
||||
}
|
||||
|
||||
#warning("This needs to be refactored, refresh token and other properties need to be available")
|
||||
public init(credential: OAuthSwiftCredential) {
|
||||
self.token = credential.oauthToken
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import Foundation
|
||||
|
||||
public struct Account: Codable {
|
||||
public let id: String
|
||||
public let username: String
|
||||
public let acct: String
|
||||
public let displayName: String?
|
||||
public let note: String?
|
||||
public let url: URL?
|
||||
public let avatar: URL?
|
||||
public let header: URL?
|
||||
public let locked: Bool
|
||||
public let createdAt: String
|
||||
public let followersCount: Int
|
||||
public let followingCount: Int
|
||||
public let statusesCount: Int
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case username
|
||||
case acct
|
||||
case locked
|
||||
case createdAt = "created_at"
|
||||
case followersCount = "followers_count"
|
||||
case followingCount = "following_count"
|
||||
case statusesCount = "statuses_count"
|
||||
case displayName = "display_name"
|
||||
case note
|
||||
case url
|
||||
case avatar
|
||||
case header
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
public struct Application: Codable {
|
||||
public let name: String
|
||||
public let website: URL?
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import Foundation
|
||||
|
||||
public class Attachment: Codable {
|
||||
public enum AttachmentType: String, Codable {
|
||||
case unknown = "unknown"
|
||||
case image = "image"
|
||||
case gifv = "gifv"
|
||||
case video = "video"
|
||||
case audio = "audio"
|
||||
}
|
||||
|
||||
public let id: String
|
||||
public let type: AttachmentType
|
||||
public let url: URL
|
||||
public let previewUrl: URL?
|
||||
|
||||
public let remoteUrl: URL?
|
||||
public let description: String?
|
||||
public let blurhash: String?
|
||||
public let meta: Metadata?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case type
|
||||
case url
|
||||
case previewUrl = "preview_url"
|
||||
|
||||
case remoteUrl = "remote_url"
|
||||
case description
|
||||
case blurhash
|
||||
case meta
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.id = try container.decode(StatusId.self, forKey: .id)
|
||||
self.type = try container.decode(AttachmentType.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)
|
||||
self.description = try? container.decode(String.self, forKey: .description)
|
||||
self.blurhash = try? container.decode(String.self, forKey: .blurhash)
|
||||
|
||||
switch self.type {
|
||||
case .image:
|
||||
self.meta = try? container.decode(ImageMetadata.self, forKey: .meta)
|
||||
default:
|
||||
self.meta = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encode(url, forKey: .url)
|
||||
|
||||
if let previewUrl {
|
||||
try container.encode(previewUrl, forKey: .previewUrl)
|
||||
}
|
||||
|
||||
if let remoteUrl {
|
||||
try container.encode(remoteUrl, forKey: .remoteUrl)
|
||||
}
|
||||
|
||||
if let description {
|
||||
try container.encode(description, forKey: .description)
|
||||
}
|
||||
|
||||
if let blurhash {
|
||||
try container.encode(blurhash, forKey: .blurhash)
|
||||
}
|
||||
|
||||
if let meta {
|
||||
try container.encode(meta, forKey: .meta)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import Foundation
|
||||
|
||||
public struct Context: Codable {
|
||||
public let ancestors: [Status]
|
||||
public let descendants: [Status]
|
||||
|
||||
public enum CodingKeys: CodingKey {
|
||||
case ancestors
|
||||
case descendants
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.ancestors = try container.decode([Status].self, forKey: .ancestors)
|
||||
self.descendants = try container.decode([Status].self, forKey: .descendants)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import Foundation
|
||||
|
||||
public struct ErrorMessage: Codable {
|
||||
public let error: String
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import Foundation
|
||||
|
||||
public struct Focus: Codable {
|
||||
public let x: Int
|
||||
public let y: Int
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case x
|
||||
case y
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import Foundation
|
||||
|
||||
public struct ImageInfo: Codable {
|
||||
public let width: Int
|
||||
public let height: Int
|
||||
public let size: String
|
||||
public let aspect: Double
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case width
|
||||
case height
|
||||
case size
|
||||
case aspect
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
public struct ImageMetadata: Metadata {
|
||||
public let original: ImageInfo?
|
||||
public let small: ImageInfo?
|
||||
public let focus: Focus?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case original
|
||||
case small
|
||||
case focus
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import Foundation
|
||||
|
||||
public struct Instance: Codable {
|
||||
public let uri: String
|
||||
public let title: String?
|
||||
public let description: String?
|
||||
public let email: String?
|
||||
public let thumbnail: String?
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case uri
|
||||
case title
|
||||
case description
|
||||
case email
|
||||
case thumbnail
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.uri = try container.decode(String.self, forKey: .uri)
|
||||
self.title = try? container.decodeIfPresent(String.self, forKey: .title)
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import Foundation
|
||||
|
||||
public struct Marker: Codable {
|
||||
public let lastReadId: StatusId
|
||||
public let version: Int64
|
||||
public let updatedAt: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case lastReadId = "last_read_id"
|
||||
case version
|
||||
case updatedAt = "updated_at"
|
||||
}
|
||||
}
|
||||
|
||||
public struct Markers: Codable {
|
||||
public let home: Marker?
|
||||
public let notifications: Marker?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case home
|
||||
case notifications
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
|
||||
public struct Mention: Codable {
|
||||
public let url: String
|
||||
public let username: String
|
||||
public let acct: String
|
||||
public let id: String
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
public protocol Metadata: Codable {
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import Foundation
|
||||
|
||||
public struct Notification: Codable {
|
||||
public enum NotificationType: String, Codable {
|
||||
case mention = "mention"
|
||||
case reblog = "reblog"
|
||||
case favourite = "favourite"
|
||||
case follow = "follow"
|
||||
}
|
||||
public let id: String
|
||||
public let type: NotificationType
|
||||
public let createdAt: String
|
||||
public let account: Account
|
||||
public let status: Status
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case type
|
||||
case createdAat = "created_at"
|
||||
case account
|
||||
case status
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try container.decode(String.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)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encode(createdAt, forKey: .createdAat)
|
||||
try container.encode(account, forKey: .account)
|
||||
try container.encode(status, forKey: .status)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import Foundation
|
||||
|
||||
public struct Relationship: Codable {
|
||||
public let id: String
|
||||
public let following: Bool
|
||||
public let followedBy: Bool
|
||||
public let blocking: Bool
|
||||
public let blockedBy: Bool
|
||||
public let muting: Bool
|
||||
public let mutingNotifications: Bool
|
||||
public let requested: Bool
|
||||
public let showingReblogs: Bool
|
||||
public let notifying: Bool
|
||||
public let domainBlocking: Bool
|
||||
public let endorsed: Bool
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case following
|
||||
case followedBy = "followed_by"
|
||||
case blocking
|
||||
case blockedBy = "blocked_by"
|
||||
case muting
|
||||
case mutingNotifications = "muting_notifications"
|
||||
case requested
|
||||
case showingReblogs = "showing_reblogs"
|
||||
case notifying
|
||||
case domainBlocking = "domain_blocking"
|
||||
case endorsed
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try container.decode(String.self, forKey: .id)
|
||||
self.following = (try? container.decode(Bool.self, forKey: .following)) ?? false
|
||||
self.followedBy = (try? container.decode(Bool.self, forKey: .followedBy)) ?? false
|
||||
self.blocking = (try? container.decode(Bool.self, forKey: .blocking)) ?? false
|
||||
self.blockedBy = (try? container.decode(Bool.self, forKey: .blockedBy)) ?? false
|
||||
self.muting = (try? container.decode(Bool.self, forKey: .muting)) ?? false
|
||||
self.mutingNotifications = (try? container.decode(Bool.self, forKey: .mutingNotifications)) ?? false
|
||||
self.requested = (try? container.decode(Bool.self, forKey: .requested)) ?? false
|
||||
self.showingReblogs = (try? container.decode(Bool.self, forKey: .showingReblogs)) ?? false
|
||||
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
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(following, forKey: .following)
|
||||
try container.encode(followedBy, forKey: .followedBy)
|
||||
try container.encode(blocking, forKey: .blocking)
|
||||
try container.encode(blockedBy, forKey: .blockedBy)
|
||||
try container.encode(muting, forKey: .muting)
|
||||
try container.encode(mutingNotifications, forKey: .mutingNotifications)
|
||||
try container.encode(requested, forKey: .requested)
|
||||
try container.encode(showingReblogs, forKey: .showingReblogs)
|
||||
try container.encode(notifying, forKey: .notifying)
|
||||
try container.encode(domainBlocking, forKey: .domainBlocking)
|
||||
try container.encode(endorsed, forKey: .endorsed)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import Foundation
|
||||
|
||||
public struct Report: Codable {
|
||||
public let id: String
|
||||
public let actionTaken: String?
|
||||
|
||||
public enum CodingKeys: CodingKey {
|
||||
case id
|
||||
case actionTaken
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import Foundation
|
||||
|
||||
public typealias Hashtag = String
|
||||
|
||||
public struct Result: Codable {
|
||||
public let accounts: [Account]
|
||||
public let statuses: [Status]
|
||||
public let hashtags: [Hashtag]
|
||||
|
||||
public enum CodingKeys: CodingKey {
|
||||
case accounts
|
||||
case statuses
|
||||
case hashtags
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
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)) ?? []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
import Foundation
|
||||
|
||||
public typealias StatusId = String
|
||||
public typealias Html = String
|
||||
|
||||
public class Status: Codable {
|
||||
public enum Visibility: String, Codable {
|
||||
case pub = "public"
|
||||
case unlisted = "unlisted"
|
||||
case priv = "private"
|
||||
case direct = "direct"
|
||||
}
|
||||
public let id: StatusId
|
||||
public let uri: String
|
||||
public let url: URL?
|
||||
public let account: Account?
|
||||
public let inReplyToId: AccountId?
|
||||
public let inReplyToAccount: StatusId?
|
||||
public let reblog: Status?
|
||||
public let content: Html
|
||||
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?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case uri
|
||||
case url
|
||||
case account
|
||||
case inReplyToId = "in_reply_to_id"
|
||||
case inReplyToAccount = "in_reply_to_account_id"
|
||||
case reblog
|
||||
case content
|
||||
case createdAt = "created_at"
|
||||
case reblogsCount = "reblogs_count"
|
||||
case favouritesCount = "favourites_count"
|
||||
case repliesCount = "replies_count"
|
||||
case reblogged
|
||||
case favourited
|
||||
case sensitive
|
||||
case bookmarked
|
||||
case pinned
|
||||
case muted
|
||||
case spoilerText = "spoiler_text"
|
||||
case visibility
|
||||
case mediaAttachments = "media_attachments"
|
||||
case card
|
||||
case mentions
|
||||
case tags
|
||||
case application
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.id = try container.decode(StatusId.self, forKey: .id)
|
||||
self.uri = try container.decode(String.self, forKey: .uri)
|
||||
self.url = try? container.decode(URL.self, forKey: .url)
|
||||
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.inReplyToAccount = try? container.decode(StatusId.self, forKey: .inReplyToAccount)
|
||||
self.reblog = try? container.decode(Status.self, forKey: .reblog)
|
||||
self.spoilerText = try? container.decode(String.self, forKey: .spoilerText)
|
||||
self.reblogsCount = (try? container.decode(Int.self, forKey: .reblogsCount)) ?? 0
|
||||
self.repliesCount = (try? container.decode(Int.self, forKey: .repliesCount)) ?? 0
|
||||
self.favouritesCount = (try? container.decode(Int.self, forKey: .favouritesCount)) ?? 0
|
||||
self.reblogged = (try? container.decode(Bool.self, forKey: .reblogged)) ?? false
|
||||
self.favourited = (try? container.decode(Bool.self, forKey: .favourited)) ?? false
|
||||
self.sensitive = (try? container.decode(Bool.self, forKey: .sensitive)) ?? false
|
||||
self.bookmarked = (try? container.decode(Bool.self, forKey: .bookmarked)) ?? false
|
||||
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.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)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(uri, forKey: .uri)
|
||||
if let url {
|
||||
try container.encode(url, forKey: .url)
|
||||
}
|
||||
if let account {
|
||||
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)
|
||||
try container.encode(reblogged, forKey: .reblogged)
|
||||
try container.encode(favourited, forKey: .favourited)
|
||||
try container.encode(bookmarked, forKey: .bookmarked)
|
||||
try container.encode(pinned, forKey: .pinned)
|
||||
try container.encode(muted, forKey: .muted)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
public struct Tag: Codable {
|
||||
public let name: String
|
||||
public let url: URL?
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public enum NetworkError: Error {
|
||||
public enum NetworkError: Error {
|
||||
case notSuccessResponse(URLResponse)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
|
||||
extension Bool {
|
||||
var asString: String {
|
||||
return self == true ? "true" : "false"
|
||||
}
|
||||
}
|
||||
|
||||
extension Int {
|
||||
var asString: String {
|
||||
return "\(self)"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import Foundation
|
||||
|
||||
extension String {
|
||||
func asURL() -> URL? {
|
||||
return URL(string: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
static let showTimeline = "ShowTimeline"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import Foundation
|
||||
|
||||
extension URL {
|
||||
static func fromOptional(string: String?) -> URL? {
|
||||
guard let string = string else {
|
||||
return nil
|
||||
}
|
||||
return URL(string: string)
|
||||
}
|
||||
}
|
|
@ -5,9 +5,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
extension MastodonClientAuthenticated {
|
||||
public extension MastodonClientAuthenticated {
|
||||
func getAccount(for accountId: String) async throws -> Account {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
|
@ -0,0 +1,15 @@
|
|||
import Foundation
|
||||
|
||||
public extension MastodonClientAuthenticated {
|
||||
func verifyCredentials() async throws -> Account {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Account.verifyCredentials,
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Account.self, from: data)
|
||||
}
|
||||
}
|
|
@ -5,9 +5,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
|
||||
extension MastodonClientAuthenticated {
|
||||
public extension MastodonClientAuthenticated {
|
||||
func getContext(for statusId: String) async throws -> Context {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
|
@ -0,0 +1,78 @@
|
|||
import Foundation
|
||||
import OAuthSwift
|
||||
|
||||
public extension MastodonClient {
|
||||
func createApp(named name: String,
|
||||
redirectUri: String = "urn:ietf:wg:oauth:2.0:oob",
|
||||
scopes: Scopes,
|
||||
website: URL) async throws -> App {
|
||||
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Apps.register(
|
||||
clientName: name,
|
||||
redirectUris: redirectUri,
|
||||
scopes: scopes.reduce("") { $0 == "" ? $1 : $0 + " " + $1},
|
||||
website: website.absoluteString
|
||||
)
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(App.self, from: data)
|
||||
}
|
||||
|
||||
func authenticate(app: App, scope: Scopes) async throws -> OAuthSwiftCredential { // todo: we should not load OAuthSwift objects here
|
||||
oauthClient = OAuth2Swift(
|
||||
consumerKey: app.clientId,
|
||||
consumerSecret: app.clientSecret,
|
||||
authorizeUrl: baseURL.appendingPathComponent("oauth/authorize"),
|
||||
accessTokenUrl: baseURL.appendingPathComponent("oauth/token"),
|
||||
responseType: "code"
|
||||
)
|
||||
|
||||
return try await withCheckedThrowingContinuation { [weak self] continuation in
|
||||
self?.oAuthContinuation = continuation
|
||||
oAuthHandle = oauthClient?.authorize(
|
||||
withCallbackURL: app.redirectUri,
|
||||
scope: scope.asScopeString,
|
||||
state: "MASToDON_AUTH",
|
||||
completionHandler: { result in
|
||||
switch result {
|
||||
case let .success((credentials, _, _)):
|
||||
continuation.resume(with: .success(credentials))
|
||||
case let .failure(error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
self?.oAuthContinuation = nil
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static func handleOAuthResponse(url: URL) {
|
||||
OAuthSwift.handle(url: url)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "The password flow is discoured and won't support 2FA. Please use authentiate(app:, scope:)")
|
||||
func getToken(withApp app: App,
|
||||
username: String,
|
||||
password: String,
|
||||
scope: Scopes) async throws -> AccessToken {
|
||||
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.OAuth.authenticate(app, username, password, scope.asScopeString)
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(AccessToken.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
private extension [String] {
|
||||
var asScopeString: String {
|
||||
joined(separator: " ")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import Foundation
|
||||
|
||||
public extension MastodonClient {
|
||||
func readInstanceInformation() async throws -> Instance {
|
||||
let request = try Self.request(for: baseURL, target: Mastodon.Instances.instance )
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Instance.self, from: data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
import Foundation
|
||||
|
||||
public extension MastodonClientAuthenticated {
|
||||
func read(statusId: StatusId) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.status(statusId),
|
||||
withBearerToken: token)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func boost(statusId: StatusId) async throws -> Status {
|
||||
// TODO: Check whether the current user already boosted the status
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.reblog(statusId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func unboost(statusId: StatusId) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.unreblog(statusId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func bookmark(statusId: StatusId) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.bookmark(statusId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func unbookmark(statusId: StatusId) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.unbookmark(statusId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func favourite(statusId: StatusId) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.favourite(statusId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func unfavourite(statusId: StatusId) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.unfavourite(statusId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
|
||||
func new(statusComponents: Mastodon.Statuses.Components) async throws -> Status {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Statuses.new(statusComponents),
|
||||
withBearerToken: token)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Status.self, from: data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
import Foundation
|
||||
import OAuthSwift
|
||||
|
||||
public typealias Scope = String
|
||||
public typealias Scopes = [Scope]
|
||||
public typealias Token = String
|
||||
|
||||
public enum MastodonClientError: Swift.Error {
|
||||
case oAuthCancelled
|
||||
}
|
||||
|
||||
public protocol MastodonClientProtocol {
|
||||
static func request(for baseURL: URL, target: TargetType, withBearerToken token: String?) throws -> URLRequest
|
||||
}
|
||||
|
||||
public extension MastodonClientProtocol {
|
||||
static func request(for baseURL: URL, target: TargetType, withBearerToken token: String? = nil) throws -> URLRequest {
|
||||
|
||||
var urlComponents = URLComponents(url: baseURL.appendingPathComponent(target.path), resolvingAgainstBaseURL: false)
|
||||
urlComponents?.queryItems = target.queryItems?.map { URLQueryItem(name: $0.0, value: $0.1) }
|
||||
|
||||
guard let url = urlComponents?.url else { throw NetworkingError.cannotCreateUrlRequest }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
|
||||
target.headers?.forEach { header in
|
||||
request.setValue(header.1, forHTTPHeaderField: header.0)
|
||||
}
|
||||
|
||||
if let token = token {
|
||||
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
|
||||
request.httpMethod = target.method.rawValue
|
||||
request.httpBody = target.httpBody
|
||||
|
||||
return request
|
||||
}
|
||||
}
|
||||
|
||||
public class MastodonClient: MastodonClientProtocol {
|
||||
|
||||
let urlSession: URLSession
|
||||
let baseURL: URL
|
||||
|
||||
/// oAuth
|
||||
var oauthClient: OAuth2Swift?
|
||||
var oAuthHandle: OAuthSwiftRequestHandle?
|
||||
var oAuthContinuation: CheckedContinuation<OAuthSwiftCredential, Swift.Error>?
|
||||
|
||||
public init(baseURL: URL, urlSession: URLSession = .shared) {
|
||||
self.baseURL = baseURL
|
||||
self.urlSession = urlSession
|
||||
}
|
||||
|
||||
public func getAuthenticated(token: Token) -> MastodonClientAuthenticated {
|
||||
MastodonClientAuthenticated(baseURL: baseURL, urlSession: urlSession, token: token)
|
||||
}
|
||||
|
||||
deinit {
|
||||
oAuthContinuation?.resume(throwing: MastodonClientError.oAuthCancelled)
|
||||
oAuthHandle?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
public class MastodonClientAuthenticated: MastodonClientProtocol {
|
||||
|
||||
public let token: Token
|
||||
public let baseURL: URL
|
||||
public let urlSession: URLSession
|
||||
|
||||
init(baseURL: URL, urlSession: URLSession, token: Token) {
|
||||
self.token = token
|
||||
self.baseURL = baseURL
|
||||
self.urlSession = urlSession
|
||||
}
|
||||
|
||||
public func getHomeTimeline(
|
||||
maxId: StatusId? = nil,
|
||||
sinceId: StatusId? = nil,
|
||||
minId: StatusId? = nil,
|
||||
limit: Int? = nil) async throws -> [Status] {
|
||||
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Timelines.home(maxId, sinceId, minId, limit),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode([Status].self, from: data)
|
||||
}
|
||||
|
||||
public func getPublicTimeline(isLocal: Bool = false,
|
||||
maxId: StatusId? = nil,
|
||||
sinceId: StatusId? = nil) async throws -> [Status] {
|
||||
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Timelines.pub(isLocal, maxId, sinceId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode([Status].self, from: data)
|
||||
}
|
||||
|
||||
public func getTagTimeline(tag: String,
|
||||
isLocal: Bool = false,
|
||||
maxId: StatusId? = nil,
|
||||
sinceId: StatusId? = nil) async throws -> [Status] {
|
||||
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Timelines.tag(tag, isLocal, maxId, sinceId),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode([Status].self, from: data)
|
||||
}
|
||||
|
||||
public func saveMarkers(_ markers: [Mastodon.Markers.Timeline: StatusId]) async throws -> Markers {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Markers.set(markers),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Markers.self, from: data)
|
||||
}
|
||||
|
||||
public func readMarkers(_ markers: Set<Mastodon.Markers.Timeline>) async throws -> Markers {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Mastodon.Markers.read(markers),
|
||||
withBearerToken: token
|
||||
)
|
||||
|
||||
let (data, _) = try await urlSession.data(for: request)
|
||||
|
||||
return try JSONDecoder().decode(Markers.self, from: data)
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import Foundation
|
|||
public enum HTTPStatusCode: Int, Error {
|
||||
|
||||
/// The response class representation of status codes, these get grouped by their first digit.
|
||||
enum ResponseType {
|
||||
public enum ResponseType {
|
||||
|
||||
/// - informational: This class of status code indicates a provisional response, consisting only of the Status-Line and optional headers, and is terminated by an empty line.
|
||||
case informational
|
||||
|
@ -253,7 +253,7 @@ public enum HTTPStatusCode: Int, Error {
|
|||
case networkAuthenticationRequired = 511
|
||||
|
||||
/// The class (or group) which the status code belongs to.
|
||||
var responseType: ResponseType {
|
||||
public var responseType: ResponseType {
|
||||
|
||||
switch self.rawValue {
|
||||
|
||||
|
@ -281,7 +281,7 @@ public enum HTTPStatusCode: Int, Error {
|
|||
|
||||
}
|
||||
|
||||
extension HTTPURLResponse {
|
||||
public extension HTTPURLResponse {
|
||||
var status: HTTPStatusCode? {
|
||||
return HTTPStatusCode(rawValue: statusCode)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import Foundation
|
||||
|
||||
extension Data {
|
||||
|
||||
func getMultipartFormDataBuilder(withBoundary boundary: String) -> MultipartFormDataBuilder? {
|
||||
return MultipartFormDataBuilder(data: self, boundary: boundary)
|
||||
}
|
||||
|
||||
struct MultipartFormDataBuilder {
|
||||
private let boundary: String
|
||||
private var httpBody = NSMutableData()
|
||||
|
||||
private let data: Data
|
||||
|
||||
fileprivate init(data: Data, boundary: String) {
|
||||
self.data = data
|
||||
self.boundary = boundary
|
||||
}
|
||||
|
||||
func addTextField(named name: String, value: String) -> Self {
|
||||
httpBody.append(textFormField(named: name, value: value))
|
||||
return self
|
||||
}
|
||||
|
||||
private func textFormField(named name: String, value: String) -> String {
|
||||
var fieldString = "--\(boundary)\r\n"
|
||||
fieldString += "Content-Disposition: form-data; name=\"\(name)\"\r\n"
|
||||
fieldString += "Content-Type: text/plain; charset=UTF-8\r\n"
|
||||
fieldString += "\r\n"
|
||||
fieldString += "\(value)\r\n"
|
||||
|
||||
return fieldString
|
||||
}
|
||||
|
||||
func addDataField(named name: String, data: Data, mimeType: String) -> Self {
|
||||
httpBody.append(dataFormField(named: name, data: data, mimeType: mimeType))
|
||||
return self
|
||||
}
|
||||
|
||||
private func dataFormField(named name: String, data: Data, mimeType: String) -> Data {
|
||||
let fieldData = NSMutableData()
|
||||
|
||||
fieldData.append("--\(boundary)\r\n")
|
||||
fieldData.append("Content-Disposition: form-data; name=\"\(name)\"\r\n")
|
||||
fieldData.append("Content-Type: \(mimeType)\r\n")
|
||||
fieldData.append("\r\n")
|
||||
fieldData.append(data)
|
||||
fieldData.append("\r\n")
|
||||
|
||||
return fieldData as Data
|
||||
}
|
||||
|
||||
func build() -> Data {
|
||||
return httpBody as Data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSMutableData {
|
||||
func append(_ string: String) {
|
||||
if let data = string.data(using: .utf8) {
|
||||
self.append(data)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import Foundation
|
||||
|
||||
public enum NetworkingError: String, Swift.Error {
|
||||
case cannotCreateUrlRequest
|
||||
}
|
||||
|
||||
public enum Method: String {
|
||||
case delete = "DELETE", get = "GET", head = "HEAD", patch = "PATCH", post = "POST", put = "PUT"
|
||||
}
|
||||
|
||||
public protocol TargetType {
|
||||
var path: String { get }
|
||||
var method: Method { get }
|
||||
var headers: [String: String]? { get }
|
||||
var queryItems: [(String, String)]? { get }
|
||||
var httpBody: Data? { get }
|
||||
}
|
||||
|
||||
extension [String: String] {
|
||||
var contentTypeApplicationJson: [String: String] {
|
||||
var selfCopy = self
|
||||
selfCopy["content-type"] = "application/json"
|
||||
return selfCopy
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Account {
|
||||
case account(AccountId)
|
||||
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 search(SearchQuery, Int)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Account: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/accounts" }
|
||||
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .account(let id):
|
||||
return "\(apiPath)/\(id)"
|
||||
case .verifyCredentials:
|
||||
return "\(apiPath)/verify_credentials"
|
||||
case .followers(let id, _, _, _, _, _):
|
||||
return "\(apiPath)/\(id)/followers"
|
||||
case .following(let id, _, _, _, _, _):
|
||||
return "\(apiPath)/\(id)/following"
|
||||
case .statuses(let id, _, _, _, _, _, _):
|
||||
return "\(apiPath)/\(id)/statuses"
|
||||
case .follow(let id):
|
||||
return "\(apiPath)/\(id)/follow"
|
||||
case .unfollow(let id):
|
||||
return "\(apiPath)/\(id)/unfollow"
|
||||
case .block(let id):
|
||||
return "\(apiPath)/\(id)/block"
|
||||
case .unblock(let id):
|
||||
return "\(apiPath)/\(id)/unblock"
|
||||
case .mute(let id):
|
||||
return "\(apiPath)/\(id)/mute"
|
||||
case .unmute(let id):
|
||||
return "\(apiPath)/\(id)/unmute"
|
||||
case .relationships(_):
|
||||
return "\(apiPath)/relationships"
|
||||
case .search(_, _):
|
||||
return "\(apiPath)/search"
|
||||
}
|
||||
}
|
||||
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .follow(_), .unfollow(_), .block(_), .unblock(_), .mute(_), .unmute(_):
|
||||
return .post
|
||||
default:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
public var queryItems: [(String, String)]? {
|
||||
var params: [(String, String)] = []
|
||||
|
||||
var maxId: MaxId? = nil
|
||||
var sinceId: SinceId? = nil
|
||||
var minId: MinId? = nil
|
||||
var limit: Limit? = nil
|
||||
var page: Page? = nil
|
||||
|
||||
switch self {
|
||||
case .statuses(_, let onlyMedia, let excludeReplies, let _maxId, let _sinceId, let _minId, let _limit):
|
||||
params.append(contentsOf: [
|
||||
("only_media", onlyMedia.asString),
|
||||
("exclude_replies", excludeReplies.asString)
|
||||
])
|
||||
maxId = _maxId
|
||||
sinceId = _sinceId
|
||||
minId = _minId
|
||||
limit = _limit
|
||||
case .relationships(let id):
|
||||
return id.map({ id in
|
||||
("id[]", id)
|
||||
})
|
||||
case .search(let query, let limit):
|
||||
return [
|
||||
("q", query),
|
||||
("limit", limit.asString)
|
||||
]
|
||||
case .following(_, let _maxId, let _sinceId, let _minId, let _limit, let _page):
|
||||
maxId = _maxId
|
||||
sinceId = _sinceId
|
||||
minId = _minId
|
||||
limit = _limit
|
||||
page = _page
|
||||
case .followers(_, let _maxId, let _sinceId, let _minId, let _limit, let _page):
|
||||
maxId = _maxId
|
||||
sinceId = _sinceId
|
||||
minId = _minId
|
||||
limit = _limit
|
||||
page = _page
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if let maxId {
|
||||
params.append(("max_id", maxId))
|
||||
}
|
||||
if let sinceId {
|
||||
params.append(("since_id", sinceId))
|
||||
}
|
||||
if let minId {
|
||||
params.append(("min_id", minId))
|
||||
}
|
||||
if let limit {
|
||||
params.append(("limit", "\(limit)"))
|
||||
}
|
||||
if let page {
|
||||
params.append(("page", "\(page)"))
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Apps {
|
||||
case register(clientName: String, redirectUris: String, scopes: String?, website: String?)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Apps: TargetType {
|
||||
struct Request: Encodable {
|
||||
let clientName: String
|
||||
let redirectUris: String
|
||||
let scopes: String?
|
||||
let website: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientName = "client_name"
|
||||
case redirectUris = "redirect_uris"
|
||||
case scopes
|
||||
case website
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container: KeyedEncodingContainer<Mastodon.Apps.Request.CodingKeys> = encoder.container(keyedBy: Mastodon.Apps.Request.CodingKeys.self)
|
||||
try container.encode(self.clientName, forKey: Mastodon.Apps.Request.CodingKeys.clientName)
|
||||
try container.encode(self.redirectUris, forKey: Mastodon.Apps.Request.CodingKeys.redirectUris)
|
||||
try container.encode(self.scopes, forKey: Mastodon.Apps.Request.CodingKeys.scopes)
|
||||
try container.encode(self.website, forKey: Mastodon.Apps.Request.CodingKeys.website)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var apiPath: String { return "/api/v1/apps" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .register(_, _, _, _):
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .register(_, _, _, _):
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .register(let clientName, let redirectUris, let scopes, let website):
|
||||
return try? JSONEncoder().encode(
|
||||
Request(clientName: clientName, redirectUris: redirectUris, scopes: scopes, website: website)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Blocks {
|
||||
case blocks
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Blocks: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/blocks" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .blocks:
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .blocks:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .blocks:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Favourites {
|
||||
case favourites
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Favourites: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/favourites" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .favourites:
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .favourites:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .favourites:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum FollowRequests {
|
||||
case followRequests
|
||||
case authorize(String)
|
||||
case reject(String)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.FollowRequests: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/follow_requests" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .followRequests:
|
||||
return "\(apiPath)"
|
||||
case .authorize(_):
|
||||
return "\(apiPath)/authorize"
|
||||
case .reject(_):
|
||||
return "\(apiPath)/reject"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .followRequests:
|
||||
return .get
|
||||
case .authorize(_), .reject(_):
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .followRequests:
|
||||
return nil
|
||||
case .authorize(let id):
|
||||
return try? JSONEncoder().encode(
|
||||
["id": id]
|
||||
)
|
||||
case .reject:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Follows {
|
||||
case follow(String)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Follows: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/follows" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .follow(_):
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .follow(_):
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .follow(let uri):
|
||||
return try? JSONEncoder().encode(
|
||||
["uri": uri]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Instances {
|
||||
case instance
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Instances: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/instance" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .instance:
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .instance:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .instance:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Markers {
|
||||
public enum Timeline: String, Encodable {
|
||||
case home
|
||||
case notifications
|
||||
}
|
||||
|
||||
case set([Timeline: StatusId])
|
||||
case read(Set<Timeline>)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Markers: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/markers" }
|
||||
|
||||
public var path: String {
|
||||
return apiPath
|
||||
}
|
||||
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .set(_):
|
||||
return .post
|
||||
|
||||
case .read(_):
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String : String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .set(_):
|
||||
return nil
|
||||
|
||||
case .read(let markers):
|
||||
return Array(markers)
|
||||
.map { ("timeline[]", $0.rawValue) }
|
||||
}
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .set(let markers):
|
||||
let dict = Dictionary(uniqueKeysWithValues: markers.map { ($0.rawValue, ["last_read_id": $1]) })
|
||||
let data = try? JSONEncoder().encode(dict)
|
||||
return data
|
||||
|
||||
case .read(_):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import Foundation
|
||||
|
||||
public typealias AccountId = String
|
||||
public typealias SearchQuery = String
|
||||
|
||||
public class Mastodon {
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import Foundation
|
||||
|
||||
fileprivate let multipartBoundary = UUID().uuidString
|
||||
|
||||
extension Mastodon {
|
||||
public enum Media {
|
||||
case upload(Data, String)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Media: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/media" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .upload:
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .upload:
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .upload:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
switch self {
|
||||
case .upload:
|
||||
return ["content-type": "multipart/form-data; boundary=\(multipartBoundary)"]
|
||||
}
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .upload(let data, let mimeType):
|
||||
return data.getMultipartFormDataBuilder(withBoundary: multipartBoundary)?
|
||||
.addDataField(named: "file", data: data, mimeType: mimeType)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Mutes {
|
||||
case mutes
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Mutes: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/mutes" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .mutes:
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .mutes:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .mutes:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Notifications {
|
||||
case notifications
|
||||
case notification(String)
|
||||
case clear
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Notifications: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/notifications" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .notifications:
|
||||
return "\(apiPath)"
|
||||
case .notification(let id):
|
||||
return "\(apiPath)/\(id)"
|
||||
case .clear:
|
||||
return "\(apiPath)/clear"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .notifications, .notification(_):
|
||||
return .get
|
||||
case .clear:
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
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?)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.OAuth: TargetType {
|
||||
struct Request: Encodable {
|
||||
let clientId: String
|
||||
let clientSecret: String
|
||||
let grantType: String
|
||||
let username: String
|
||||
let password: String
|
||||
let scope: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientId = "client_id"
|
||||
case clientSecret = "client_secret"
|
||||
case grantType = "grant_type"
|
||||
case username
|
||||
case password
|
||||
case scope
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container: KeyedEncodingContainer<Mastodon.OAuth.Request.CodingKeys> = encoder.container(keyedBy: Mastodon.OAuth.Request.CodingKeys.self)
|
||||
try container.encode(self.clientId, forKey: Mastodon.OAuth.Request.CodingKeys.clientId)
|
||||
try container.encode(self.clientSecret, forKey: Mastodon.OAuth.Request.CodingKeys.clientSecret)
|
||||
try container.encode(self.grantType, forKey: Mastodon.OAuth.Request.CodingKeys.grantType)
|
||||
try container.encode(self.username, forKey: Mastodon.OAuth.Request.CodingKeys.username)
|
||||
try container.encode(self.password, forKey: Mastodon.OAuth.Request.CodingKeys.password)
|
||||
try container.encode(self.scope, forKey: Mastodon.OAuth.Request.CodingKeys.scope)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var apiPath: String { return "/oauth/token" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case.authenticate(_, _, _, _):
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .authenticate(_, _, _, _):
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .authenticate(let app, let username, let password, let scope):
|
||||
return try? JSONEncoder().encode(
|
||||
Request(
|
||||
clientId: app.clientId,
|
||||
clientSecret: app.clientSecret,
|
||||
grantType: "password",
|
||||
username: username,
|
||||
password: password,
|
||||
scope: scope ?? ""
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Reports {
|
||||
case list
|
||||
case report(String, [String], String)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Reports: TargetType {
|
||||
private struct Request: Encodable {
|
||||
let accountId: String
|
||||
let statusIds: [String]
|
||||
let comment: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case accountId = "account_id"
|
||||
case statusIds = "status_ids"
|
||||
case comment
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container: KeyedEncodingContainer<Mastodon.Reports.Request.CodingKeys> = encoder.container(keyedBy: Mastodon.Reports.Request.CodingKeys.self)
|
||||
try container.encode(self.accountId, forKey: Mastodon.Reports.Request.CodingKeys.accountId)
|
||||
try container.encode(self.statusIds, forKey: Mastodon.Reports.Request.CodingKeys.statusIds)
|
||||
try container.encode(self.comment, forKey: Mastodon.Reports.Request.CodingKeys.comment)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var apiPath: String { return "/api/v1/reports" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .list, .report(_, _, _):
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .list:
|
||||
return .get
|
||||
case .report(_, _, _):
|
||||
return .post
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .list:
|
||||
return nil
|
||||
case .report(let accountId, let statusIds, let comment):
|
||||
return try? JSONEncoder().encode(
|
||||
Request(accountId: accountId, statusIds: statusIds, comment: comment)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Search {
|
||||
case search(SearchQuery, Bool)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Mastodon.Search: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/search" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .search:
|
||||
return "\(apiPath)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .search:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
switch self {
|
||||
case .search(let query, let resolveNonLocal):
|
||||
return [
|
||||
("q", query),
|
||||
("resolve", resolveNonLocal.asString)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
import Foundation
|
||||
|
||||
extension Mastodon {
|
||||
public enum Statuses {
|
||||
public enum Visibility: String, Encodable {
|
||||
case direct = "direct"
|
||||
case priv = "private"
|
||||
case unlisted = "unlisted"
|
||||
case pub = "public"
|
||||
}
|
||||
case status(String)
|
||||
case context(String)
|
||||
case card(String)
|
||||
case rebloggedBy(String)
|
||||
case favouritedBy(String)
|
||||
case new(Components)
|
||||
case delete(String)
|
||||
case reblog(String)
|
||||
case unreblog(String)
|
||||
case favourite(String)
|
||||
case unfavourite(String)
|
||||
case bookmark(String)
|
||||
case unbookmark(String)
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Statuses {
|
||||
public struct Components {
|
||||
public let inReplyToId: StatusId?
|
||||
public let text: String
|
||||
public let spoilerText: String
|
||||
public let mediaIds: [String]
|
||||
public let visibility: Visibility
|
||||
public let sensitive: Bool
|
||||
public let pollOptions: [String]
|
||||
public let pollExpiresIn: Int
|
||||
public let pollMultipleChoice: Bool
|
||||
|
||||
public init(
|
||||
inReplyToId: StatusId? = nil,
|
||||
text: String,
|
||||
spoilerText: String = "",
|
||||
mediaIds: [String] = [],
|
||||
visibility: Visibility = .pub,
|
||||
sensitive: Bool = false,
|
||||
pollOptions: [String] = [],
|
||||
pollExpiresIn: Int = 0,
|
||||
pollMultipleChoice: Bool = false) {
|
||||
self.inReplyToId = inReplyToId
|
||||
self.text = text
|
||||
self.spoilerText = spoilerText
|
||||
self.mediaIds = mediaIds
|
||||
self.visibility = visibility
|
||||
self.sensitive = sensitive
|
||||
self.pollOptions = pollOptions
|
||||
self.pollExpiresIn = pollExpiresIn
|
||||
self.pollMultipleChoice = pollMultipleChoice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Statuses: TargetType {
|
||||
struct Request: Encodable {
|
||||
let status: String
|
||||
let inReplyToId: String?
|
||||
let mediaIds: [String]?
|
||||
let sensitive: Bool
|
||||
let spoilerText: String?
|
||||
let visibility: Visibility
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case status
|
||||
case inReplyToId = "in_reply_to_id"
|
||||
case mediaIds = "media_ids"
|
||||
case sensitive
|
||||
case spoilerText = "spoiler_text"
|
||||
case visibility
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container: KeyedEncodingContainer<Mastodon.Statuses.Request.CodingKeys> = encoder.container(keyedBy: Mastodon.Statuses.Request.CodingKeys.self)
|
||||
try container.encode(self.status, forKey: Mastodon.Statuses.Request.CodingKeys.status)
|
||||
try container.encode(self.inReplyToId, forKey: Mastodon.Statuses.Request.CodingKeys.inReplyToId)
|
||||
try container.encode(self.mediaIds, forKey: Mastodon.Statuses.Request.CodingKeys.mediaIds)
|
||||
try container.encode(self.sensitive, forKey: Mastodon.Statuses.Request.CodingKeys.sensitive)
|
||||
try container.encodeIfPresent(self.spoilerText, forKey: Mastodon.Statuses.Request.CodingKeys.spoilerText)
|
||||
try container.encode(self.visibility, forKey: Mastodon.Statuses.Request.CodingKeys.visibility)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var apiPath: String { return "/api/v1/statuses" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .status(let id):
|
||||
return "\(apiPath)/\(id)"
|
||||
case .context(let id):
|
||||
return "\(apiPath)/\(id)/context"
|
||||
case .card(let id):
|
||||
return "\(apiPath)/\(id)/card"
|
||||
case .rebloggedBy(let id):
|
||||
return "\(apiPath)/\(id)/reblogged_by"
|
||||
case .favouritedBy(let id):
|
||||
return "\(apiPath)/\(id)/favourited_by"
|
||||
case .new(_):
|
||||
return "\(apiPath)"
|
||||
case .delete(let id):
|
||||
return "\(apiPath)/\(id)"
|
||||
case .reblog(let id):
|
||||
return "\(apiPath)/\(id)/reblog"
|
||||
case .unreblog(let id):
|
||||
return "\(apiPath)/\(id)/unreblog"
|
||||
case .favourite(let id):
|
||||
return "\(apiPath)/\(id)/favourite"
|
||||
case .unfavourite(let id):
|
||||
return "\(apiPath)/\(id)/unfavourite"
|
||||
case .bookmark(let id):
|
||||
return "\(apiPath)/\(id)/bookmark"
|
||||
case .unbookmark(let id):
|
||||
return "\(apiPath)/\(id)/unbookmark"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
case .new(_),
|
||||
.reblog(_),
|
||||
.unreblog(_),
|
||||
.favourite(_),
|
||||
.unfavourite(_),
|
||||
.bookmark(_),
|
||||
.unbookmark(_):
|
||||
return .post
|
||||
case .delete(_):
|
||||
return .delete
|
||||
default:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
nil
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .new(let components):
|
||||
return try? JSONEncoder().encode(
|
||||
Request(
|
||||
status: components.text,
|
||||
inReplyToId: components.inReplyToId,
|
||||
mediaIds: components.mediaIds,
|
||||
sensitive: components.sensitive,
|
||||
spoilerText: components.spoilerText,
|
||||
visibility: components.visibility)
|
||||
)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import Foundation
|
||||
|
||||
public typealias SinceId = StatusId
|
||||
public typealias MaxId = StatusId
|
||||
public typealias MinId = StatusId
|
||||
public typealias Limit = Int
|
||||
public typealias Page = Int
|
||||
|
||||
extension Mastodon {
|
||||
public enum Timelines {
|
||||
case home(MaxId?, SinceId?, MinId?, Limit?)
|
||||
case pub(Bool, MaxId?, SinceId?) // Bool = local
|
||||
case tag(String, Bool, MaxId?, SinceId?) // Bool = local
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Timelines: TargetType {
|
||||
fileprivate var apiPath: String { return "/api/v1/timelines" }
|
||||
|
||||
/// The path to be appended to `baseURL` to form the full `URL`.
|
||||
public var path: String {
|
||||
switch self {
|
||||
case .home:
|
||||
return "\(apiPath)/home"
|
||||
case .pub:
|
||||
return "\(apiPath)/public"
|
||||
case .tag(let hashtag, _, _, _):
|
||||
return "\(apiPath)/tag/\(hashtag)"
|
||||
}
|
||||
}
|
||||
|
||||
/// The HTTP method used in the request.
|
||||
public var method: Method {
|
||||
switch self {
|
||||
default:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters to be incoded in the request.
|
||||
public var queryItems: [(String, String)]? {
|
||||
var params: [(String, String)] = []
|
||||
var local: Bool? = nil
|
||||
var maxId: MaxId? = nil
|
||||
var sinceId: SinceId? = nil
|
||||
var minId: MinId? = nil
|
||||
var limit: Limit? = nil
|
||||
|
||||
switch self {
|
||||
case .tag(_, let _local, let _maxId, let _sinceId),
|
||||
.pub(let _local, let _maxId, let _sinceId):
|
||||
local = _local
|
||||
maxId = _maxId
|
||||
sinceId = _sinceId
|
||||
case .home(let _maxId, let _sinceId, let _minId, let _limit):
|
||||
maxId = _maxId
|
||||
sinceId = _sinceId
|
||||
minId = _minId
|
||||
limit = _limit
|
||||
}
|
||||
|
||||
if let maxId {
|
||||
params.append(("max_id", maxId))
|
||||
}
|
||||
if let sinceId {
|
||||
params.append(("since_id", sinceId))
|
||||
}
|
||||
if let minId {
|
||||
params.append(("min_id", minId))
|
||||
}
|
||||
if let limit {
|
||||
params.append(("limit", "\(limit)"))
|
||||
}
|
||||
if let local = local {
|
||||
params.append(("local", local.asString))
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
public var headers: [String: String]? {
|
||||
[:].contentTypeApplicationJson
|
||||
}
|
||||
|
||||
public var httpBody: Data? {
|
||||
nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import XCTest
|
||||
@testable import MastodonKit
|
||||
|
||||
final class MastodonKitTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(MastodonKit().text, "Hello, World!")
|
||||
}
|
||||
}
|
|
@ -34,7 +34,6 @@
|
|||
F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D497A29640C8200751DF7 /* UsernameRow.swift */; };
|
||||
F85D497D29640D5900751DF7 /* InteractionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D497C29640D5900751DF7 /* InteractionRow.swift */; };
|
||||
F85D497F296416C800751DF7 /* CommentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D497E296416C800751DF7 /* CommentsSection.swift */; };
|
||||
F85D4981296417F700751DF7 /* MastodonClientAuthenticated+Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */; };
|
||||
F85D498329642FAC00751DF7 /* AttachmentData+Comperable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */; };
|
||||
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D49842964301800751DF7 /* StatusData+Attachments.swift */; };
|
||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D49862964334100751DF7 /* String+Date.swift */; };
|
||||
|
@ -48,7 +47,6 @@
|
|||
F866F6A729604629002E8F88 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A629604629002E8F88 /* SignInView.swift */; };
|
||||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A929605AFA002E8F88 /* SceneDelegate.swift */; };
|
||||
F866F6AE29606367002E8F88 /* ApplicationViewMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */; };
|
||||
F866F6B729608467002E8F88 /* MastodonSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F866F6B629608467002E8F88 /* MastodonSwift */; };
|
||||
F86B7214296BFDCE00EE59EC /* UserProfileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7213296BFDCE00EE59EC /* UserProfileHeader.swift */; };
|
||||
F86B7216296BFFDA00EE59EC /* UserProfileStatuses.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7215296BFFDA00EE59EC /* UserProfileStatuses.swift */; };
|
||||
F86B7218296C27C100EE59EC /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7217296C27C100EE59EC /* ActionButton.swift */; };
|
||||
|
@ -79,13 +77,11 @@
|
|||
F897978D2968369600B22335 /* HapticService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978C2968369600B22335 /* HapticService.swift */; };
|
||||
F897978F29684BCB00B22335 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897978E29684BCB00B22335 /* LoadingView.swift */; };
|
||||
F8984E4D296B648000A2610F /* UIImage+Blurhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */; };
|
||||
F89992C7296D3DF8005994BF /* MastodonKit in Frameworks */ = {isa = PBXBuildFile; productRef = F89992C6296D3DF8005994BF /* MastodonKit */; };
|
||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
|
||||
F8A93D802965FED4001D8331 /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7F2965FED4001D8331 /* AccountService.swift */; };
|
||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */; };
|
||||
F8C14392296AF0B3001FE31D /* String+Exif.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14391296AF0B3001FE31D /* String+Exif.swift */; };
|
||||
F8C14394296AF21B001FE31D /* Double+Round.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14393296AF21B001FE31D /* Double+Round.swift */; };
|
||||
F8C14398296B208A001FE31D /* HTTPStatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C14397296B208A001FE31D /* HTTPStatusCode.swift */; };
|
||||
F8C1439B296B227C001FE31D /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C1439A296B227C001FE31D /* NetworkError.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -113,7 +109,6 @@
|
|||
F85D497A29640C8200751DF7 /* UsernameRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameRow.swift; sourceTree = "<group>"; };
|
||||
F85D497C29640D5900751DF7 /* InteractionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionRow.swift; sourceTree = "<group>"; };
|
||||
F85D497E296416C800751DF7 /* CommentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsSection.swift; sourceTree = "<group>"; };
|
||||
F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonClientAuthenticated+Context.swift"; sourceTree = "<group>"; };
|
||||
F85D498229642FAC00751DF7 /* AttachmentData+Comperable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttachmentData+Comperable.swift"; sourceTree = "<group>"; };
|
||||
F85D49842964301800751DF7 /* StatusData+Attachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusData+Attachments.swift"; sourceTree = "<group>"; };
|
||||
F85D49862964334100751DF7 /* String+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Date.swift"; sourceTree = "<group>"; };
|
||||
|
@ -128,6 +123,7 @@
|
|||
F866F6A829604FFF002E8F88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
F866F6A929605AFA002E8F88 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationViewMode.swift; sourceTree = "<group>"; };
|
||||
F86728AD296D3CE200475EC9 /* MastodonKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MastodonKit; sourceTree = "<group>"; };
|
||||
F86B7213296BFDCE00EE59EC /* UserProfileHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHeader.swift; sourceTree = "<group>"; };
|
||||
F86B7215296BFFDA00EE59EC /* UserProfileStatuses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileStatuses.swift; sourceTree = "<group>"; };
|
||||
F86B7217296C27C100EE59EC /* ActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = "<group>"; };
|
||||
|
@ -162,11 +158,8 @@
|
|||
F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Blurhash.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>"; };
|
||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonClientAuthenticated+Account.swift"; sourceTree = "<group>"; };
|
||||
F8C14391296AF0B3001FE31D /* String+Exif.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Exif.swift"; sourceTree = "<group>"; };
|
||||
F8C14393296AF21B001FE31D /* Double+Round.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Round.swift"; sourceTree = "<group>"; };
|
||||
F8C14397296B208A001FE31D /* HTTPStatusCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStatusCode.swift; sourceTree = "<group>"; };
|
||||
F8C1439A296B227C001FE31D /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -174,7 +167,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F866F6B729608467002E8F88 /* MastodonSwift in Frameworks */,
|
||||
F89992C7296D3DF8005994BF /* MastodonKit in Frameworks */,
|
||||
F8210DD52966BB7E001D9973 /* Nuke in Frameworks */,
|
||||
F8210DD72966BB7E001D9973 /* NukeExtensions in Frameworks */,
|
||||
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */,
|
||||
|
@ -215,8 +208,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F8341F8F295C636C009C8EE6 /* Data+Exif.swift */,
|
||||
F85D4980296417F700751DF7 /* MastodonClientAuthenticated+Context.swift */,
|
||||
F8A93D812965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift */,
|
||||
F85D49862964334100751DF7 /* String+Date.swift */,
|
||||
F8C14391296AF0B3001FE31D /* String+Exif.swift */,
|
||||
F8210DE22966D256001D9973 /* Status+StatusData.swift */,
|
||||
|
@ -232,10 +223,8 @@
|
|||
F8341F95295C640C009C8EE6 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8C14399296B2150001FE31D /* Errors */,
|
||||
F88FAD2C295F4AD7009B20C9 /* ApplicationState.swift */,
|
||||
F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */,
|
||||
F8C14397296B208A001FE31D /* HTTPStatusCode.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -313,9 +302,11 @@
|
|||
F88C245F295C37B80006098B = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F86728AD296D3CE200475EC9 /* MastodonKit */,
|
||||
F88ABD9529687D4D004EF61E /* README.md */,
|
||||
F88C246A295C37B80006098B /* Vernissage */,
|
||||
F88C2469295C37B80006098B /* Products */,
|
||||
F89992C5296D3DF8005994BF /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -380,12 +371,11 @@
|
|||
path = Haptics;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F8C14399296B2150001FE31D /* Errors */ = {
|
||||
F89992C5296D3DF8005994BF /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8C1439A296B227C001FE31D /* NetworkError.swift */,
|
||||
);
|
||||
path = Errors;
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
@ -405,10 +395,10 @@
|
|||
);
|
||||
name = Vernissage;
|
||||
packageProductDependencies = (
|
||||
F866F6B629608467002E8F88 /* MastodonSwift */,
|
||||
F8210DD42966BB7E001D9973 /* Nuke */,
|
||||
F8210DD62966BB7E001D9973 /* NukeExtensions */,
|
||||
F8210DD82966BB7E001D9973 /* NukeUI */,
|
||||
F89992C6296D3DF8005994BF /* MastodonKit */,
|
||||
);
|
||||
productName = Vernissage;
|
||||
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
|
||||
|
@ -439,7 +429,6 @@
|
|||
);
|
||||
mainGroup = F88C245F295C37B80006098B;
|
||||
packageReferences = (
|
||||
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */,
|
||||
F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||
);
|
||||
productRefGroup = F88C2469295C37B80006098B /* Products */;
|
||||
|
@ -479,7 +468,6 @@
|
|||
F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */,
|
||||
F85DBF93296760790069BF89 /* CacheAvatarService.swift in Sources */,
|
||||
F88FAD23295F3FC4009B20C9 /* LocalFeedView.swift in Sources */,
|
||||
F8C1439B296B227C001FE31D /* NetworkError.swift in Sources */,
|
||||
F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */,
|
||||
F85D4975296407F100751DF7 /* TimelineService.swift in Sources */,
|
||||
F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */,
|
||||
|
@ -488,7 +476,6 @@
|
|||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F8210DE12966D0C4001D9973 /* StatusService.swift in Sources */,
|
||||
F85DBF8F296732E20069BF89 /* FollowersView.swift in Sources */,
|
||||
F8A93D822965FF5D001D8331 /* MastodonClientAuthenticated+Account.swift in Sources */,
|
||||
F85D49872964334100751DF7 /* String+Date.swift in Sources */,
|
||||
F897978829681B9C00B22335 /* UserAvatar.swift in Sources */,
|
||||
F8210DDD2966CF17001D9973 /* StatusData+Status.swift in Sources */,
|
||||
|
@ -507,7 +494,6 @@
|
|||
F897978D2968369600B22335 /* HapticService.swift in Sources */,
|
||||
F8341F90295C636C009C8EE6 /* Data+Exif.swift in Sources */,
|
||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */,
|
||||
F85D4981296417F700751DF7 /* MastodonClientAuthenticated+Context.swift in Sources */,
|
||||
F88C246E295C37B80006098B /* MainView.swift in Sources */,
|
||||
F86B721E296C458700EE59EC /* BlurredImage.swift in Sources */,
|
||||
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
|
||||
|
@ -518,7 +504,6 @@
|
|||
F866F6A529604194002E8F88 /* ApplicationSettingsHandler.swift in Sources */,
|
||||
F88ABD9229686F1C004EF61E /* MemoryCache.swift in Sources */,
|
||||
F8210DE32966D256001D9973 /* Status+StatusData.swift in Sources */,
|
||||
F8C14398296B208A001FE31D /* HTTPStatusCode.swift in Sources */,
|
||||
F85D49852964301800751DF7 /* StatusData+Attachments.swift in Sources */,
|
||||
F8210DE72966E1D1001D9973 /* Color+Assets.swift in Sources */,
|
||||
F88ABD9429687CA4004EF61E /* ComposeView.swift in Sources */,
|
||||
|
@ -757,16 +742,8 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kean/Nuke";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mczachurski/Mastodon.swift";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 11.5.3;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
@ -787,10 +764,9 @@
|
|||
package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
F866F6B629608467002E8F88 /* MastodonSwift */ = {
|
||||
F89992C6296D3DF8005994BF /* MastodonKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F866F6B529608467002E8F88 /* XCRemoteSwiftPackageReference "Mastodon" */;
|
||||
productName = MastodonSwift;
|
||||
productName = MastodonKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
extension AttachmentData {
|
||||
func copyFrom(_ attachment: Attachment) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
extension StatusData {
|
||||
func copyFrom(_ status: Status) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
class StatusDataHandler {
|
||||
public static let shared = StatusDataHandler()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
extension Status {
|
||||
public func getImageWidth() -> Int32? {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
extension Status {
|
||||
func createStatusData() async throws -> StatusData {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
import OAuthSwift
|
||||
|
||||
class SceneDelegate: NSObject, UISceneDelegate {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
public class AccountService {
|
||||
public static let shared = AccountService()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
public class AuthorizationService {
|
||||
public static let shared = AuthorizationService()
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import MastodonKit
|
||||
|
||||
public class RemoteFileService {
|
||||
public static let shared = RemoteFileService()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
public class StatusService {
|
||||
public static let shared = StatusService()
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
public class TimelineService {
|
||||
public static let shared = TimelineService()
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct FollowersView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct FollowingView: View {
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import SwiftUI
|
||||
import UIKit
|
||||
import CoreData
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct MainView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
import AVFoundation
|
||||
|
||||
struct StatusView: View {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct UserProfileView: View {
|
||||
@EnvironmentObject private var applicationState: ApplicationState
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct CommentsSection: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
import NukeUI
|
||||
|
||||
struct ImageRowAsync: View {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct UserProfileHeader: View {
|
||||
@EnvironmentObject private var applicationState: ApplicationState
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import MastodonSwift
|
||||
import MastodonKit
|
||||
|
||||
struct UserProfileStatuses: View {
|
||||
@EnvironmentObject private var applicationState: ApplicationState
|
||||
|
|
Loading…
Reference in New Issue