2021-01-26 10:38:30 +01:00
|
|
|
//
|
|
|
|
// Mastodon+API.swift
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Created by xiaojian sun on 2021/1/25.
|
|
|
|
//
|
|
|
|
|
2021-03-03 09:12:48 +01:00
|
|
|
import os.log
|
2021-01-26 10:38:30 +01:00
|
|
|
import Foundation
|
2021-01-27 11:46:14 +01:00
|
|
|
import enum NIOHTTP1.HTTPResponseStatus
|
2021-01-26 10:38:30 +01:00
|
|
|
|
2021-01-27 11:46:14 +01:00
|
|
|
extension Mastodon.API {
|
2021-01-27 09:01:20 +01:00
|
|
|
|
2022-11-18 16:55:25 +01:00
|
|
|
static let timeoutInterval: TimeInterval = 60
|
2021-01-28 07:52:35 +01:00
|
|
|
|
2021-01-27 12:24:31 +01:00
|
|
|
static let httpHeaderDateFormatter: ISO8601DateFormatter = {
|
|
|
|
var formatter = ISO8601DateFormatter()
|
|
|
|
formatter.formatOptions.insert(.withFractionalSeconds)
|
|
|
|
return formatter
|
|
|
|
}()
|
2021-01-28 07:52:35 +01:00
|
|
|
static let fractionalSecondsPreciseISO8601Formatter: ISO8601DateFormatter = {
|
|
|
|
let formatter = ISO8601DateFormatter()
|
|
|
|
formatter.formatOptions.insert(.withFractionalSeconds)
|
|
|
|
return formatter
|
|
|
|
}()
|
|
|
|
static let fullDatePreciseISO8601Formatter: ISO8601DateFormatter = {
|
|
|
|
let formatter = ISO8601DateFormatter()
|
|
|
|
formatter.formatOptions = [.withFullDate, .withDashSeparatorInDate]
|
|
|
|
return formatter
|
|
|
|
}()
|
|
|
|
|
2021-01-27 11:46:14 +01:00
|
|
|
static let encoder: JSONEncoder = {
|
|
|
|
let encoder = JSONEncoder()
|
|
|
|
encoder.dateEncodingStrategy = .iso8601
|
|
|
|
return encoder
|
|
|
|
}()
|
2021-01-26 10:38:30 +01:00
|
|
|
static let decoder: JSONDecoder = {
|
|
|
|
let decoder = JSONDecoder()
|
2021-01-27 11:52:01 +01:00
|
|
|
decoder.dateDecodingStrategy = JSONDecoder.DateDecodingStrategy.custom { decoder throws -> Date in
|
|
|
|
let container = try decoder.singleValueContainer()
|
|
|
|
|
2021-02-02 12:31:10 +01:00
|
|
|
var logInfo = ""
|
|
|
|
do {
|
|
|
|
let string = try container.decode(String.self)
|
|
|
|
logInfo += string
|
|
|
|
|
|
|
|
if let date = fractionalSecondsPreciseISO8601Formatter.date(from: string) {
|
|
|
|
return date
|
|
|
|
}
|
|
|
|
if let date = fullDatePreciseISO8601Formatter.date(from: string) {
|
|
|
|
return date
|
|
|
|
}
|
2021-04-01 05:49:38 +02:00
|
|
|
if let timestamp = TimeInterval(string) {
|
|
|
|
return Date(timeIntervalSince1970: timestamp)
|
|
|
|
}
|
2021-02-02 12:31:10 +01:00
|
|
|
} catch {
|
|
|
|
// do nothing
|
2021-01-28 07:52:35 +01:00
|
|
|
}
|
2021-02-02 12:31:10 +01:00
|
|
|
|
|
|
|
var numberValue = ""
|
|
|
|
do {
|
|
|
|
let number = try container.decode(Double.self)
|
|
|
|
logInfo += "\(number)"
|
|
|
|
|
|
|
|
return Date(timeIntervalSince1970: number)
|
|
|
|
} catch {
|
|
|
|
// do nothing
|
2021-01-27 11:52:01 +01:00
|
|
|
}
|
|
|
|
|
2021-02-02 12:31:10 +01:00
|
|
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "[Decoder] Invalid date: \(logInfo)")
|
2021-01-27 11:52:01 +01:00
|
|
|
}
|
2021-01-26 11:11:44 +01:00
|
|
|
|
2021-01-26 10:38:30 +01:00
|
|
|
return decoder
|
|
|
|
}()
|
2021-01-27 11:46:14 +01:00
|
|
|
|
2021-01-29 12:38:11 +01:00
|
|
|
static func oauthEndpointURL(domain: String) -> URL {
|
2022-03-15 19:02:28 +01:00
|
|
|
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/oauth/")!
|
2021-01-29 12:38:11 +01:00
|
|
|
}
|
2021-01-27 11:46:14 +01:00
|
|
|
static func endpointURL(domain: String) -> URL {
|
2022-03-15 19:02:28 +01:00
|
|
|
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/api/v1/")!
|
2021-01-27 11:46:14 +01:00
|
|
|
}
|
2021-01-29 12:38:11 +01:00
|
|
|
static func endpointV2URL(domain: String) -> URL {
|
2022-03-15 19:02:28 +01:00
|
|
|
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/api/v2/")!
|
2021-01-29 12:38:11 +01:00
|
|
|
}
|
2021-01-26 10:38:30 +01:00
|
|
|
|
2021-02-18 12:36:49 +01:00
|
|
|
static let joinMastodonEndpointURL = URL(string: "https://api.joinmastodon.org/")!
|
|
|
|
|
2021-02-24 13:06:28 +01:00
|
|
|
public static func resendEmailURL(domain: String) -> URL {
|
2022-03-15 19:02:28 +01:00
|
|
|
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/auth/confirmation/new")!
|
2021-02-24 13:06:28 +01:00
|
|
|
}
|
|
|
|
|
2021-03-30 08:31:52 +02:00
|
|
|
public static func serverRulesURL(domain: String) -> URL {
|
2022-03-15 19:02:28 +01:00
|
|
|
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/about/more")!
|
2021-03-30 08:31:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static func privacyURL(domain: String) -> URL {
|
2022-03-15 19:02:28 +01:00
|
|
|
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/terms")!
|
2021-03-30 08:31:52 +02:00
|
|
|
}
|
2021-01-27 09:01:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
extension Mastodon.API {
|
2021-04-20 09:40:10 +02:00
|
|
|
public enum V2 { }
|
2021-02-02 12:31:10 +01:00
|
|
|
public enum Account { }
|
2021-01-27 11:46:14 +01:00
|
|
|
public enum App { }
|
2022-07-29 22:31:38 +02:00
|
|
|
public enum Bookmarks { }
|
2021-03-15 07:40:10 +01:00
|
|
|
public enum CustomEmojis { }
|
|
|
|
public enum Favorites { }
|
2021-02-05 05:10:34 +01:00
|
|
|
public enum Instance { }
|
2021-03-18 12:42:26 +01:00
|
|
|
public enum Media { }
|
2021-01-27 11:46:14 +01:00
|
|
|
public enum OAuth { }
|
2021-02-19 04:56:51 +01:00
|
|
|
public enum Onboarding { }
|
2021-03-03 09:12:48 +01:00
|
|
|
public enum Polls { }
|
2021-03-09 12:39:44 +01:00
|
|
|
public enum Reblog { }
|
2021-03-10 12:12:53 +01:00
|
|
|
public enum Statuses { }
|
2021-03-15 07:40:10 +01:00
|
|
|
public enum Timeline { }
|
2021-03-31 08:48:34 +02:00
|
|
|
public enum Trends { }
|
|
|
|
public enum Suggestions { }
|
2021-04-02 04:21:51 +02:00
|
|
|
public enum Notifications { }
|
2021-04-13 11:37:13 +02:00
|
|
|
public enum Subscriptions { }
|
2021-04-19 14:34:08 +02:00
|
|
|
public enum Reports { }
|
2021-04-29 09:51:52 +02:00
|
|
|
public enum DomainBlock { }
|
2021-01-27 09:01:20 +01:00
|
|
|
}
|
|
|
|
|
2021-04-20 09:40:10 +02:00
|
|
|
extension Mastodon.API.V2 {
|
|
|
|
public enum Search { }
|
|
|
|
public enum Suggestions { }
|
2021-07-15 09:49:30 +02:00
|
|
|
public enum Media { }
|
2021-04-20 09:40:10 +02:00
|
|
|
}
|
|
|
|
|
2021-01-27 09:01:20 +01:00
|
|
|
extension Mastodon.API {
|
|
|
|
|
2021-01-29 12:38:11 +01:00
|
|
|
static func get(
|
2021-01-27 11:46:14 +01:00
|
|
|
url: URL,
|
2021-01-29 12:38:11 +01:00
|
|
|
query: GetQuery?,
|
2021-01-27 11:46:14 +01:00
|
|
|
authorization: OAuth.Authorization?
|
|
|
|
) -> URLRequest {
|
2021-02-19 10:40:05 +01:00
|
|
|
return buildRequest(url: url, method: .GET, query: query, authorization: authorization)
|
2021-01-27 09:01:20 +01:00
|
|
|
}
|
|
|
|
|
2021-01-29 12:38:11 +01:00
|
|
|
static func post(
|
2021-01-27 11:46:14 +01:00
|
|
|
url: URL,
|
2021-01-29 12:38:11 +01:00
|
|
|
query: PostQuery?,
|
2021-01-27 11:46:14 +01:00
|
|
|
authorization: OAuth.Authorization?
|
2021-01-27 09:01:20 +01:00
|
|
|
) -> URLRequest {
|
2021-02-19 10:40:05 +01:00
|
|
|
return buildRequest(url: url, method: .POST, query: query, authorization: authorization)
|
2021-04-29 09:51:52 +02:00
|
|
|
}
|
2021-02-10 07:56:16 +01:00
|
|
|
|
|
|
|
static func patch(
|
|
|
|
url: URL,
|
|
|
|
query: PatchQuery?,
|
|
|
|
authorization: OAuth.Authorization?
|
|
|
|
) -> URLRequest {
|
2021-02-19 10:40:05 +01:00
|
|
|
return buildRequest(url: url, method: .PATCH, query: query, authorization: authorization)
|
2021-02-10 07:56:16 +01:00
|
|
|
}
|
2021-03-19 12:49:48 +01:00
|
|
|
|
|
|
|
static func put(
|
|
|
|
url: URL,
|
|
|
|
query: PutQuery?,
|
|
|
|
authorization: OAuth.Authorization?
|
|
|
|
) -> URLRequest {
|
|
|
|
return buildRequest(url: url, method: .PUT, query: query, authorization: authorization)
|
|
|
|
}
|
2021-04-26 10:57:50 +02:00
|
|
|
|
|
|
|
static func delete(
|
|
|
|
url: URL,
|
|
|
|
query: DeleteQuery?,
|
|
|
|
authorization: OAuth.Authorization?
|
|
|
|
) -> URLRequest {
|
|
|
|
return buildRequest(url: url, method: .DELETE, query: query, authorization: authorization)
|
|
|
|
}
|
2021-02-10 07:56:16 +01:00
|
|
|
|
|
|
|
private static func buildRequest(
|
|
|
|
url: URL,
|
2021-02-19 10:40:05 +01:00
|
|
|
method: RequestMethod,
|
2021-02-10 07:56:16 +01:00
|
|
|
query: RequestQuery?,
|
|
|
|
authorization: OAuth.Authorization?
|
|
|
|
) -> URLRequest {
|
|
|
|
var components = URLComponents(string: url.absoluteString)!
|
2021-02-19 10:40:05 +01:00
|
|
|
components.queryItems = query?.queryItems
|
2021-01-27 11:46:14 +01:00
|
|
|
let requestURL = components.url!
|
|
|
|
var request = URLRequest(
|
|
|
|
url: requestURL,
|
|
|
|
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
|
|
|
|
timeoutInterval: Mastodon.API.timeoutInterval
|
|
|
|
)
|
2021-02-19 10:40:05 +01:00
|
|
|
request.httpMethod = method.rawValue
|
2021-03-08 11:17:15 +01:00
|
|
|
if let contentType = query?.contentType {
|
|
|
|
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
|
|
|
}
|
|
|
|
if let body = query?.body {
|
|
|
|
request.httpBody = body
|
|
|
|
request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
|
|
|
|
}
|
2021-01-27 11:46:14 +01:00
|
|
|
if let authorization = authorization {
|
|
|
|
request.setValue(
|
|
|
|
"Bearer \(authorization.accessToken)",
|
|
|
|
forHTTPHeaderField: Mastodon.API.OAuth.authorizationField
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return request
|
|
|
|
}
|
|
|
|
|
|
|
|
static func decode<T>(type: T.Type, from data: Data, response: URLResponse) throws -> T where T : Decodable {
|
|
|
|
// decode data then decode error if could
|
|
|
|
do {
|
|
|
|
return try Mastodon.API.decoder.decode(type, from: data)
|
|
|
|
} catch let decodeError {
|
|
|
|
#if DEBUG
|
2021-03-03 09:12:48 +01:00
|
|
|
os_log(.info, "%{public}s[%{public}ld], %{public}s: decode fail. content %s", ((#file as NSString).lastPathComponent), #line, #function, String(data: data, encoding: .utf8) ?? "<nil>")
|
2021-01-27 11:46:14 +01:00
|
|
|
debugPrint(decodeError)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
guard let httpURLResponse = response as? HTTPURLResponse else {
|
|
|
|
assertionFailure()
|
|
|
|
throw decodeError
|
|
|
|
}
|
|
|
|
|
|
|
|
let httpResponseStatus = HTTPResponseStatus(statusCode: httpURLResponse.statusCode)
|
2021-01-28 07:52:35 +01:00
|
|
|
if let error = try? Mastodon.API.decoder.decode(Mastodon.Entity.Error.self, from: data) {
|
|
|
|
throw Mastodon.API.Error(httpResponseStatus: httpResponseStatus, error: error)
|
2021-01-27 11:46:14 +01:00
|
|
|
}
|
|
|
|
|
2021-01-28 07:52:35 +01:00
|
|
|
throw Mastodon.API.Error(httpResponseStatus: httpResponseStatus, mastodonError: nil)
|
2021-01-27 11:46:14 +01:00
|
|
|
}
|
2021-01-27 09:01:20 +01:00
|
|
|
}
|
2021-01-26 10:38:30 +01:00
|
|
|
|
|
|
|
}
|