2020-08-30 23:33:11 +00:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Combine
|
2020-09-05 02:31:43 +00:00
|
|
|
import Foundation
|
2020-08-31 01:40:58 +00:00
|
|
|
import HTTP
|
2020-09-04 01:55:46 +00:00
|
|
|
import Mastodon
|
2020-08-30 23:33:11 +00:00
|
|
|
|
2020-09-07 04:56:18 +00:00
|
|
|
public final class MastodonAPIClient: HTTPClient {
|
2020-09-09 01:02:55 +00:00
|
|
|
public var instanceURL: URL
|
2020-08-30 23:33:11 +00:00
|
|
|
public var accessToken: String?
|
|
|
|
|
2020-09-23 07:04:37 +00:00
|
|
|
public required init(session: URLSession, instanceURL: URL) {
|
2020-09-09 01:02:55 +00:00
|
|
|
self.instanceURL = instanceURL
|
2020-09-04 01:55:46 +00:00
|
|
|
super.init(session: session, decoder: MastodonDecoder())
|
2020-08-30 23:33:11 +00:00
|
|
|
}
|
|
|
|
|
2020-09-24 01:33:13 +00:00
|
|
|
public override func dataTaskPublisher<T: DecodableTarget>(
|
2020-12-17 06:48:06 +00:00
|
|
|
_ target: T, progress: Progress? = nil) -> AnyPublisher<(data: Data, response: HTTPURLResponse), Error> {
|
|
|
|
super.dataTaskPublisher(target, progress: progress)
|
2020-09-24 01:33:13 +00:00
|
|
|
.mapError { [weak self] error -> Error in
|
|
|
|
if case let HTTPError.invalidStatusCode(data, _) = error,
|
|
|
|
let apiError = try? self?.decoder.decode(APIError.self, from: data) {
|
|
|
|
return apiError
|
|
|
|
}
|
|
|
|
|
|
|
|
return error
|
|
|
|
}
|
|
|
|
.eraseToAnyPublisher()
|
2020-08-30 23:33:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 01:55:46 +00:00
|
|
|
extension MastodonAPIClient {
|
2020-12-17 06:48:06 +00:00
|
|
|
public func request<E: Endpoint>(_ endpoint: E, progress: Progress? = nil) -> AnyPublisher<E.ResultType, Error> {
|
|
|
|
dataTaskPublisher(target(endpoint: endpoint), progress: progress)
|
2020-09-24 01:33:13 +00:00
|
|
|
.map(\.data)
|
|
|
|
.decode(type: E.ResultType.self, decoder: decoder)
|
|
|
|
.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
public func pagedRequest<E: Endpoint>(
|
|
|
|
_ endpoint: E,
|
2020-10-05 22:50:05 +00:00
|
|
|
maxId: String? = nil,
|
|
|
|
minId: String? = nil,
|
|
|
|
sinceId: String? = nil,
|
2020-12-17 06:48:06 +00:00
|
|
|
limit: Int? = nil,
|
|
|
|
progress: Progress? = nil) -> AnyPublisher<PagedResult<E.ResultType>, Error> {
|
2020-10-05 22:50:05 +00:00
|
|
|
let pagedTarget = target(endpoint: Paged(endpoint, maxId: maxId, minId: minId, sinceId: sinceId, limit: limit))
|
2020-12-17 06:48:06 +00:00
|
|
|
let dataTask = dataTaskPublisher(pagedTarget, progress: progress).share()
|
2020-09-24 01:33:13 +00:00
|
|
|
let decoded = dataTask.map(\.data).decode(type: E.ResultType.self, decoder: decoder)
|
|
|
|
let info = dataTask.map { _, response -> PagedResult<E.ResultType>.Info in
|
2020-10-05 22:50:05 +00:00
|
|
|
var maxId: String?
|
|
|
|
var minId: String?
|
|
|
|
var sinceId: String?
|
2020-09-24 01:33:13 +00:00
|
|
|
|
|
|
|
if let links = response.value(forHTTPHeaderField: "Link") {
|
|
|
|
let queryItems = Self.linkDataDetector.matches(
|
|
|
|
in: links,
|
|
|
|
range: .init(links.startIndex..<links.endIndex, in: links))
|
|
|
|
.compactMap { match -> [URLQueryItem]? in
|
|
|
|
guard let url = match.url else { return nil }
|
|
|
|
|
|
|
|
return URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems
|
|
|
|
}
|
|
|
|
.reduce([], +)
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
maxId = queryItems.first { $0.name == "max_id" }?.value
|
|
|
|
minId = queryItems.first { $0.name == "min_id" }?.value
|
|
|
|
sinceId = queryItems.first { $0.name == "since_id" }?.value
|
2020-09-24 01:33:13 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 22:50:05 +00:00
|
|
|
return PagedResult.Info(maxId: maxId, minId: minId, sinceId: sinceId)
|
2020-09-24 01:33:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return decoded.zip(info).map(PagedResult.init(result:info:)).eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension MastodonAPIClient {
|
|
|
|
// swiftlint:disable force_try
|
|
|
|
static let linkDataDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
|
|
|
// swiftlint:enable force_try
|
|
|
|
|
|
|
|
func target<E: Endpoint>(endpoint: E) -> MastodonAPITarget<E> {
|
|
|
|
MastodonAPITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken)
|
2020-08-30 23:33:11 +00:00
|
|
|
}
|
|
|
|
}
|