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