Convert NewsBlurAPICaller to async await.
This commit is contained in:
parent
a3151181eb
commit
be4564716f
@ -35,194 +35,85 @@ public enum NewsBlurError: LocalizedError, Sendable {
|
|||||||
// MARK: - Interact with endpoints
|
// MARK: - Interact with endpoints
|
||||||
|
|
||||||
extension NewsBlurAPICaller {
|
extension NewsBlurAPICaller {
|
||||||
// GET endpoint, discard response
|
|
||||||
func requestData(
|
|
||||||
endpoint: String,
|
|
||||||
completion: @escaping (Result<Void, Error>) -> Void
|
|
||||||
) {
|
|
||||||
let callURL = baseURL.appendingPathComponent(endpoint)
|
|
||||||
|
|
||||||
requestData(callURL: callURL, completion: completion)
|
/// GET endpoint, discard response
|
||||||
|
func requestData(endpoint: String) async throws {
|
||||||
|
|
||||||
|
let callURL = baseURL.appendingPathComponent(endpoint)
|
||||||
|
try await requestData(callURL: callURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET endpoint
|
/// GET endpoint
|
||||||
func requestData<R: Decodable & Sendable>(
|
func requestData<R: Decodable & Sendable>(endpoint: String, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) async throws -> (HTTPURLResponse, R?) {
|
||||||
endpoint: String,
|
|
||||||
resultType: R.Type,
|
|
||||||
dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601,
|
|
||||||
keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
|
|
||||||
completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void
|
|
||||||
) {
|
|
||||||
let callURL = baseURL.appendingPathComponent(endpoint)
|
|
||||||
|
|
||||||
requestData(
|
let callURL = baseURL.appendingPathComponent(endpoint)
|
||||||
callURL: callURL,
|
return try await requestData(callURL: callURL, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding)
|
||||||
resultType: resultType,
|
|
||||||
dateDecoding: dateDecoding,
|
|
||||||
keyDecoding: keyDecoding,
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST to endpoint, discard response
|
/// POST to endpoint, discard response
|
||||||
func sendUpdates(
|
func sendUpdates(endpoint: String, payload: NewsBlurDataConvertible) async throws {
|
||||||
endpoint: String,
|
|
||||||
payload: NewsBlurDataConvertible,
|
|
||||||
completion: @escaping (Result<Void, Error>) -> Void
|
|
||||||
) {
|
|
||||||
let callURL = baseURL.appendingPathComponent(endpoint)
|
|
||||||
|
|
||||||
sendUpdates(callURL: callURL, payload: payload, completion: completion)
|
let callURL = baseURL.appendingPathComponent(endpoint)
|
||||||
|
try await sendUpdates(callURL: callURL, payload: payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST to endpoint
|
/// POST to endpoint
|
||||||
func sendUpdates<R: Decodable & Sendable>(
|
func sendUpdates<R: Decodable & Sendable>(endpoint: String, payload: NewsBlurDataConvertible, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) async throws -> (HTTPURLResponse, R?) {
|
||||||
endpoint: String,
|
|
||||||
payload: NewsBlurDataConvertible,
|
|
||||||
resultType: R.Type,
|
|
||||||
dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601,
|
|
||||||
keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
|
|
||||||
completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void
|
|
||||||
) {
|
|
||||||
let callURL = baseURL.appendingPathComponent(endpoint)
|
|
||||||
|
|
||||||
sendUpdates(
|
let callURL = baseURL.appendingPathComponent(endpoint)
|
||||||
callURL: callURL,
|
return try await sendUpdates(callURL: callURL, payload: payload, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding)
|
||||||
payload: payload,
|
|
||||||
resultType: resultType,
|
|
||||||
dateDecoding: dateDecoding,
|
|
||||||
keyDecoding: keyDecoding,
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Interact with URLs
|
// MARK: - Interact with URLs
|
||||||
|
|
||||||
extension NewsBlurAPICaller {
|
extension NewsBlurAPICaller {
|
||||||
// GET URL with params, discard response
|
|
||||||
func requestData(
|
/// GET URL with params, discard response
|
||||||
callURL: URL?,
|
func requestData(callURL: URL) async throws {
|
||||||
completion: @escaping (Result<Void, Error>) -> Void
|
|
||||||
) {
|
guard !isSuspended else { throw TransportError.suspended }
|
||||||
guard let callURL = callURL else {
|
|
||||||
completion(.failure(TransportError.noURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
let request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
|
|
||||||
do {
|
|
||||||
try await transport.send(request: request)
|
try await transport.send(request: request)
|
||||||
completion(.success(()))
|
|
||||||
} catch {
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET URL with params
|
/// GET URL with params
|
||||||
func requestData<R: Decodable & Sendable>(
|
@discardableResult
|
||||||
callURL: URL?,
|
func requestData<R: Decodable & Sendable>(callURL: URL, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) async throws -> (HTTPURLResponse, R?) {
|
||||||
resultType: R.Type,
|
|
||||||
dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601,
|
guard !isSuspended else { throw TransportError.suspended }
|
||||||
keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
|
|
||||||
completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void
|
|
||||||
) {
|
|
||||||
guard let callURL = callURL else {
|
|
||||||
completion(.failure(TransportError.noURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
let request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
|
|
||||||
do {
|
|
||||||
let response = try await transport.send(request: request, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding)
|
let response = try await transport.send(request: request, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding)
|
||||||
|
return response
|
||||||
if self.suspended {
|
|
||||||
completion(.failure(TransportError.suspended))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
completion(.success(response))
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST to URL with params, discard response
|
/// POST to URL with params, discard response
|
||||||
func sendUpdates(
|
func sendUpdates(callURL: URL, payload: NewsBlurDataConvertible) async throws {
|
||||||
callURL: URL?,
|
|
||||||
payload: NewsBlurDataConvertible,
|
guard !isSuspended else { throw TransportError.suspended }
|
||||||
completion: @escaping (Result<Void, Error>) -> Void
|
|
||||||
) {
|
|
||||||
guard let callURL = callURL else {
|
|
||||||
completion(.failure(TransportError.noURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
var request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
||||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
request.setValue(MimeType.formURLEncoded, forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||||
request.httpBody = payload.asData
|
request.httpBody = payload.asData
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
|
|
||||||
do {
|
|
||||||
try await transport.send(request: request, method: HTTPMethod.post)
|
try await transport.send(request: request, method: HTTPMethod.post)
|
||||||
if self.suspended {
|
|
||||||
completion(.failure(TransportError.suspended))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
completion(.success(()))
|
|
||||||
} catch {
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST to URL with params
|
/// POST to URL with params
|
||||||
func sendUpdates<R: Decodable & Sendable>(
|
func sendUpdates<R: Decodable & Sendable>(callURL: URL, payload: NewsBlurDataConvertible, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) async throws -> (HTTPURLResponse, R?) {
|
||||||
callURL: URL?,
|
|
||||||
payload: NewsBlurDataConvertible,
|
guard !isSuspended else { throw TransportError.suspended }
|
||||||
resultType: R.Type,
|
|
||||||
dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601,
|
|
||||||
keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
|
|
||||||
completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void
|
|
||||||
) {
|
|
||||||
guard let callURL = callURL else {
|
|
||||||
completion(.failure(TransportError.noURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = payload.asData else {
|
guard let data = payload.asData else {
|
||||||
completion(.failure(NewsBlurError.invalidParameter))
|
throw NewsBlurError.invalidParameter
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
var request = URLRequest(url: callURL, newsBlurCredentials: credentials)
|
||||||
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: HTTPRequestHeader.contentType)
|
request.setValue(MimeType.formURLEncoded, forHTTPHeaderField: HTTPRequestHeader.contentType)
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
|
|
||||||
do {
|
|
||||||
|
|
||||||
let response = try await transport.send(request: request, method: HTTPMethod.post, data: data, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding)
|
let response = try await transport.send(request: request, method: HTTPMethod.post, data: data, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding)
|
||||||
|
return response
|
||||||
if self.suspended {
|
|
||||||
completion(.failure(TransportError.suspended))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(.success(response))
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import Secrets
|
|||||||
|
|
||||||
let baseURL = URL(string: "https://www.newsblur.com/")!
|
let baseURL = URL(string: "https://www.newsblur.com/")!
|
||||||
var transport: Transport!
|
var transport: Transport!
|
||||||
var suspended = false
|
var isSuspended = false
|
||||||
|
|
||||||
public var credentials: Credentials?
|
public var credentials: Credentials?
|
||||||
|
|
||||||
@ -28,106 +28,83 @@ import Secrets
|
|||||||
/// Cancels all pending requests rejects any that come in later
|
/// Cancels all pending requests rejects any that come in later
|
||||||
public func suspend() {
|
public func suspend() {
|
||||||
transport.cancelAll()
|
transport.cancelAll()
|
||||||
suspended = true
|
isSuspended = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func resume() {
|
public func resume() {
|
||||||
suspended = false
|
isSuspended = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func validateCredentials(completion: @escaping (Result<Credentials?, Error>) -> Void) {
|
public func validateCredentials() async throws -> Credentials? {
|
||||||
requestData(endpoint: "api/login", resultType: NewsBlurLoginResponse.self) { result in
|
|
||||||
switch result {
|
let (response, payload) = try await requestData(endpoint: "api/login", resultType: NewsBlurLoginResponse.self)
|
||||||
case .success((let response, let payload)):
|
|
||||||
guard let url = response.url, let headerFields = response.allHeaderFields as? [String: String], payload?.code != -1 else {
|
guard let url = response.url, let headerFields = response.allHeaderFields as? [String: String], payload?.code != -1 else {
|
||||||
let error = payload?.errors?.username ?? payload?.errors?.others
|
let error = payload?.errors?.username ?? payload?.errors?.others
|
||||||
if let message = error?.first {
|
if let message = error?.first {
|
||||||
completion(.failure(NewsBlurError.general(message: message)))
|
throw NewsBlurError.general(message: message)
|
||||||
} else {
|
|
||||||
completion(.failure(NewsBlurError.unknown))
|
|
||||||
}
|
}
|
||||||
return
|
throw NewsBlurError.unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let username = self.credentials?.username else {
|
guard let username = self.credentials?.username else {
|
||||||
completion(.failure(NewsBlurError.unknown))
|
throw NewsBlurError.unknown
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
|
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
|
||||||
for cookie in cookies where cookie.name == Self.sessionIDCookieKey {
|
for cookie in cookies where cookie.name == Self.sessionIDCookieKey {
|
||||||
let credentials = Credentials(type: .newsBlurSessionID, username: username, secret: cookie.value)
|
let credentials = Credentials(type: .newsBlurSessionID, username: username, secret: cookie.value)
|
||||||
completion(.success(credentials))
|
return credentials
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
completion(.failure(NewsBlurError.general(message: "Failed to retrieve session")))
|
throw NewsBlurError.general(message: "Failed to retrieve session")
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func logout(completion: @escaping (Result<Void, Error>) -> Void) {
|
public func logout() async throws {
|
||||||
requestData(endpoint: "api/logout", completion: completion)
|
|
||||||
|
try await requestData(endpoint: "api/logout")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func retrieveFeeds(completion: @escaping (Result<([NewsBlurFeed]?, [NewsBlurFolder]?), Error>) -> Void) {
|
public func retrieveFeeds() async throws -> ([NewsBlurFeed]?, [NewsBlurFolder]?) {
|
||||||
let url = baseURL
|
|
||||||
|
let url: URL! = baseURL
|
||||||
.appendingPathComponent("reader/feeds")
|
.appendingPathComponent("reader/feeds")
|
||||||
.appendingQueryItems([
|
.appendingQueryItems([
|
||||||
URLQueryItem(name: "flat", value: "true"),
|
URLQueryItem(name: "flat", value: "true"),
|
||||||
URLQueryItem(name: "update_counts", value: "false"),
|
URLQueryItem(name: "update_counts", value: "false"),
|
||||||
])
|
])
|
||||||
|
|
||||||
requestData(callURL: url, resultType: NewsBlurFeedsResponse.self) { result in
|
let (_, payload) = try await requestData(callURL: url, resultType: NewsBlurFeedsResponse.self)
|
||||||
switch result {
|
return (payload?.feeds, payload?.folders)
|
||||||
case .success((_, let payload)):
|
|
||||||
completion(.success((payload?.feeds, payload?.folders)))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func retrieveStoryHashes(endpoint: String, completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
|
func retrieveStoryHashes(endpoint: String) async throws -> [NewsBlurStoryHash]? {
|
||||||
let url = baseURL
|
|
||||||
|
let url: URL! = baseURL
|
||||||
.appendingPathComponent(endpoint)
|
.appendingPathComponent(endpoint)
|
||||||
.appendingQueryItems([
|
.appendingQueryItems([
|
||||||
URLQueryItem(name: "include_timestamps", value: "true"),
|
URLQueryItem(name: "include_timestamps", value: "true"),
|
||||||
])
|
])
|
||||||
|
|
||||||
requestData(
|
let (_, payload) = try await requestData(callURL: url, resultType: NewsBlurStoryHashesResponse.self, dateDecoding: .secondsSince1970)
|
||||||
callURL: url,
|
|
||||||
resultType: NewsBlurStoryHashesResponse.self,
|
|
||||||
dateDecoding: .secondsSince1970
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .success((_, let payload)):
|
|
||||||
let hashes = payload?.unread ?? payload?.starred
|
let hashes = payload?.unread ?? payload?.starred
|
||||||
completion(.success(hashes))
|
return hashes
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func retrieveUnreadStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
|
public func retrieveUnreadStoryHashes() async throws -> [NewsBlurStoryHash]? {
|
||||||
retrieveStoryHashes(
|
|
||||||
endpoint: "reader/unread_story_hashes",
|
return try await retrieveStoryHashes(endpoint: "reader/unread_story_hashes")
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func retrieveStarredStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
|
public func retrieveStarredStoryHashes() async throws -> [NewsBlurStoryHash]? {
|
||||||
retrieveStoryHashes(
|
|
||||||
endpoint: "reader/starred_story_hashes",
|
return try await retrieveStoryHashes(endpoint: "reader/starred_story_hashes")
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func retrieveStories(feedID: String, page: Int, completion: @escaping (Result<([NewsBlurStory]?, Date?), Error>) -> Void) {
|
public func retrieveStories(feedID: String, page: Int) async throws -> ([NewsBlurStory]?, Date?) {
|
||||||
let url = baseURL
|
|
||||||
|
let url: URL! = baseURL
|
||||||
.appendingPathComponent("reader/feed/\(feedID)")
|
.appendingPathComponent("reader/feed/\(feedID)")
|
||||||
.appendingQueryItems([
|
.appendingQueryItems([
|
||||||
URLQueryItem(name: "page", value: String(page)),
|
URLQueryItem(name: "page", value: String(page)),
|
||||||
@ -137,144 +114,76 @@ import Secrets
|
|||||||
URLQueryItem(name: "include_story_content", value: "true"),
|
URLQueryItem(name: "include_story_content", value: "true"),
|
||||||
])
|
])
|
||||||
|
|
||||||
requestData(callURL: url, resultType: NewsBlurStoriesResponse.self) { result in
|
let (response, payload) = try await requestData(callURL: url, resultType: NewsBlurStoriesResponse.self)
|
||||||
switch result {
|
return (payload?.stories, HTTPDateInfo(urlResponse: response)?.date)
|
||||||
case .success(let (response, payload)):
|
|
||||||
completion(.success((payload?.stories, HTTPDateInfo(urlResponse: response)?.date)))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func retrieveStories(hashes: [NewsBlurStoryHash], completion: @escaping (Result<([NewsBlurStory]?, Date?), Error>) -> Void) {
|
public func retrieveStories(hashes: [NewsBlurStoryHash]) async throws -> ([NewsBlurStory]?, Date?) {
|
||||||
let url = baseURL
|
|
||||||
|
let url: URL! = baseURL
|
||||||
.appendingPathComponent("reader/river_stories")
|
.appendingPathComponent("reader/river_stories")
|
||||||
.appendingQueryItem(.init(name: "include_hidden", value: "false"))?
|
.appendingQueryItem(.init(name: "include_hidden", value: "false"))?
|
||||||
.appendingQueryItems(hashes.map {
|
.appendingQueryItems(hashes.map {
|
||||||
URLQueryItem(name: "h", value: $0.hash)
|
URLQueryItem(name: "h", value: $0.hash)
|
||||||
})
|
})
|
||||||
|
|
||||||
requestData(callURL: url, resultType: NewsBlurStoriesResponse.self) { result in
|
let (response, payload) = try await requestData(callURL: url, resultType: NewsBlurStoriesResponse.self)
|
||||||
switch result {
|
return (payload?.stories, HTTPDateInfo(urlResponse: response)?.date)
|
||||||
case .success(let (response, payload)):
|
|
||||||
completion(.success((payload?.stories, HTTPDateInfo(urlResponse: response)?.date)))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func markAsUnread(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
public func markAsUnread(hashes: [String]) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/mark_story_hash_as_unread",
|
try await sendUpdates(endpoint: "reader/mark_story_hash_as_unread", payload: NewsBlurStoryStatusChange(hashes: hashes))
|
||||||
payload: NewsBlurStoryStatusChange(hashes: hashes),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func markAsRead(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
public func markAsRead(hashes: [String]) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/mark_story_hashes_as_read",
|
try await sendUpdates(endpoint: "reader/mark_story_hashes_as_read", payload: NewsBlurStoryStatusChange(hashes: hashes))
|
||||||
payload: NewsBlurStoryStatusChange(hashes: hashes),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func star(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
public func star(hashes: [String]) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/mark_story_hash_as_starred",
|
try await sendUpdates(endpoint: "reader/mark_story_hash_as_starred", payload: NewsBlurStoryStatusChange(hashes: hashes))
|
||||||
payload: NewsBlurStoryStatusChange(hashes: hashes),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func unstar(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
public func unstar(hashes: [String]) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/mark_story_hash_as_unstarred",
|
try await sendUpdates(endpoint: "reader/mark_story_hash_as_unstarred", payload: NewsBlurStoryStatusChange(hashes: hashes))
|
||||||
payload: NewsBlurStoryStatusChange(hashes: hashes),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addFolder(named name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
public func addFolder(named name: String) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/add_folder",
|
try await sendUpdates(endpoint: "reader/add_folder", payload: NewsBlurFolderChange.add(name))
|
||||||
payload: NewsBlurFolderChange.add(name),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func renameFolder(with folder: String, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
public func renameFolder(with folder: String, to name: String) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/rename_folder",
|
try await sendUpdates(endpoint: "reader/rename_folder", payload: NewsBlurFolderChange.rename(folder, name))
|
||||||
payload: NewsBlurFolderChange.rename(folder, name),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeFolder(named name: String, feedIDs: [String], completion: @escaping (Result<Void, Error>) -> Void) {
|
public func removeFolder(named name: String, feedIDs: [String]) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/delete_folder",
|
try await sendUpdates(endpoint: "reader/delete_folder", payload: NewsBlurFolderChange.delete(name, feedIDs))
|
||||||
payload: NewsBlurFolderChange.delete(name, feedIDs),
|
|
||||||
completion: completion
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func addURL(_ url: String, folder: String?, completion: @escaping (Result<NewsBlurFeed?, Error>) -> Void) {
|
public func addURL(_ url: String, folder: String?) async throws -> NewsBlurFeed? {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/add_url",
|
let (_, payload) = try await sendUpdates(endpoint: "reader/add_url", payload: NewsBlurFeedChange.add(url, folder), resultType: NewsBlurAddURLResponse.self)
|
||||||
payload: NewsBlurFeedChange.add(url, folder),
|
return payload?.feed
|
||||||
resultType: NewsBlurAddURLResponse.self
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .success((_, let payload)):
|
|
||||||
completion(.success(payload?.feed))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func renameFeed(feedID: String, newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
public func renameFeed(feedID: String, newName: String) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/rename_feed",
|
try await sendUpdates(endpoint: "reader/rename_feed", payload: NewsBlurFeedChange.rename(feedID, newName))
|
||||||
payload: NewsBlurFeedChange.rename(feedID, newName)
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
completion(.success(()))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteFeed(feedID: String, folder: String? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
|
public func deleteFeed(feedID: String, folder: String? = nil) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/delete_feed",
|
try await sendUpdates(endpoint: "reader/delete_feed", payload: NewsBlurFeedChange.delete(feedID, folder))
|
||||||
payload: NewsBlurFeedChange.delete(feedID, folder)
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
completion(.success(()))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func moveFeed(feedID: String, from: String?, to: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
public func moveFeed(feedID: String, from: String?, to: String?) async throws {
|
||||||
sendUpdates(
|
|
||||||
endpoint: "reader/move_feed_to_folder",
|
try await sendUpdates(endpoint: "reader/move_feed_to_folder", payload: NewsBlurFeedChange.move(feedID, from, to))
|
||||||
payload: NewsBlurFeedChange.move(feedID, from, to)
|
|
||||||
) { result in
|
|
||||||
switch result {
|
|
||||||
case .success:
|
|
||||||
completion(.success(()))
|
|
||||||
case .failure(let error):
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user