Fix build errors triggered by moving NewsBlur to module.

This commit is contained in:
Brent Simmons 2024-04-07 14:11:11 -07:00
parent 8c2db159d2
commit a51d161e35
11 changed files with 121 additions and 86 deletions

View File

@ -9,6 +9,7 @@
import Foundation
import Web
import Secrets
import NewsBlur
public extension URLRequest {

View File

@ -9,36 +9,42 @@
import Foundation
import Parser
typealias NewsBlurFolder = NewsBlurFeedsResponse.Folder
public typealias NewsBlurFolder = NewsBlurFeedsResponse.Folder
struct NewsBlurFeed: Hashable, Codable {
let name: String
let feedID: Int
let feedURL: String
let homePageURL: String?
let faviconURL: String?
public struct NewsBlurFeed: Hashable, Codable, Sendable {
public let name: String
public let feedID: Int
public let feedURL: String
public let homePageURL: String?
public let faviconURL: String?
}
struct NewsBlurFeedsResponse: Decodable {
let feeds: [NewsBlurFeed]
let folders: [Folder]
public struct NewsBlurFeedsResponse: Decodable, Sendable {
struct Folder: Hashable, Codable {
let name: String
let feedIDs: [Int]
public let feeds: [NewsBlurFeed]
public let folders: [Folder]
public struct Folder: Hashable, Codable, Sendable {
public let name: String
public let feedIDs: [Int]
}
}
struct NewsBlurAddURLResponse: Decodable {
let feed: NewsBlurFeed?
public struct NewsBlurAddURLResponse: Decodable, Sendable {
public let feed: NewsBlurFeed?
}
struct NewsBlurFolderRelationship {
let folderName: String
let feedID: Int
public struct NewsBlurFolderRelationship: Sendable {
public let folderName: String
public let feedID: Int
}
extension NewsBlurFeed {
private enum CodingKeys: String, CodingKey {
case name = "feed_title"
case feedID = "id"
@ -49,13 +55,14 @@ extension NewsBlurFeed {
}
extension NewsBlurFeedsResponse {
private enum CodingKeys: String, CodingKey {
case feeds = "feeds"
case folders = "flat_folders"
// TODO: flat_folders_with_inactive
}
init(from decoder: Decoder) throws {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Tricky part: Some feeds listed in `feeds` don't exist in `folders` for some reason
@ -88,7 +95,8 @@ extension NewsBlurFeedsResponse {
}
extension NewsBlurFeedsResponse.Folder {
var asRelationships: [NewsBlurFolderRelationship] {
public var asRelationships: [NewsBlurFolderRelationship] {
return feedIDs.map { NewsBlurFolderRelationship(folderName: name, feedID: $0) }
}
}

View File

@ -8,7 +8,8 @@
import Foundation
enum NewsBlurFeedChange {
public enum NewsBlurFeedChange: Sendable {
case add(String, String?)
case rename(String, String)
case delete(String, String?)
@ -16,7 +17,8 @@ enum NewsBlurFeedChange {
}
extension NewsBlurFeedChange: NewsBlurDataConvertible {
var asData: Data? {
public var asData: Data? {
var postData = URLComponents()
postData.queryItems = {
switch self {

View File

@ -8,14 +8,16 @@
import Foundation
enum NewsBlurFolderChange {
public enum NewsBlurFolderChange: Sendable {
case add(String)
case rename(String, String)
case delete(String, [String])
}
extension NewsBlurFolderChange: NewsBlurDataConvertible {
var asData: Data? {
public var asData: Data? {
var postData = URLComponents()
postData.queryItems = {
switch self {

View File

@ -8,18 +8,19 @@
import Foundation
struct NewsBlurGenericCodingKeys: CodingKey {
var stringValue: String
public struct NewsBlurGenericCodingKeys: CodingKey {
init?(stringValue: String) {
public var stringValue: String
public init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? {
public var intValue: Int? {
return nil
}
init?(intValue: Int) {
public init?(intValue: Int) {
return nil
}
}

View File

@ -8,17 +8,20 @@
import Foundation
struct NewsBlurLoginResponse: Decodable {
var code: Int
var errors: LoginError?
public struct NewsBlurLoginResponse: Decodable, Sendable {
struct LoginError: Decodable {
var username: [String]?
var others: [String]?
public var code: Int
public var errors: LoginError?
public struct LoginError: Decodable, Sendable {
public var username: [String]?
public var others: [String]?
}
}
extension NewsBlurLoginResponse.LoginError {
private enum CodingKeys: String, CodingKey {
case username = "username"
case others = "__all__"

View File

@ -9,23 +9,25 @@
import Foundation
import Parser
typealias NewsBlurStory = NewsBlurStoriesResponse.Story
public typealias NewsBlurStory = NewsBlurStoriesResponse.Story
struct NewsBlurStoriesResponse: Decodable {
let stories: [Story]
public struct NewsBlurStoriesResponse: Decodable, Sendable {
struct Story: Decodable {
let storyID: String
let feedID: Int
let title: String?
let url: String?
let authorName: String?
let contentHTML: String?
var imageURL: String? {
public let stories: [Story]
public struct Story: Decodable, Sendable {
public let storyID: String
public let feedID: Int
public let title: String?
public let url: String?
public let authorName: String?
public let contentHTML: String?
public var imageURL: String? {
return imageURLs?.first?.value
}
var tags: [String]?
var datePublished: Date? {
public var tags: [String]?
public var datePublished: Date? {
let interval = (publishedTimestamp as NSString).doubleValue
return Date(timeIntervalSince1970: interval)
}
@ -36,12 +38,14 @@ struct NewsBlurStoriesResponse: Decodable {
}
extension NewsBlurStoriesResponse {
private enum CodingKeys: String, CodingKey {
case stories = "stories"
}
}
extension NewsBlurStoriesResponse.Story {
private enum CodingKeys: String, CodingKey {
case storyID = "story_hash"
case feedID = "story_feed_id"

View File

@ -9,27 +9,36 @@
import Foundation
import Parser
typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash
public typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash
struct NewsBlurStoryHashesResponse: Decodable {
typealias StoryHashDictionary = [String: [StoryHash]]
public struct NewsBlurStoryHashesResponse: Decodable, Sendable {
var unread: [StoryHash]?
var starred: [StoryHash]?
public typealias StoryHashDictionary = [String: [StoryHash]]
struct StoryHash: Hashable, Codable {
var hash: String
var timestamp: Date
public var unread: [StoryHash]?
public var starred: [StoryHash]?
public struct StoryHash: Hashable, Codable, Sendable {
public var hash: String
public var timestamp: Date
public init(hash: String, timestamp: Date) {
self.hash = hash
self.timestamp = timestamp
}
}
}
extension NewsBlurStoryHashesResponse {
private enum CodingKeys: String, CodingKey {
case unread = "unread_feed_story_hashes"
case starred = "starred_story_hashes"
}
init(from decoder: Decoder) throws {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Parse unread

View File

@ -8,12 +8,14 @@
import Foundation
struct NewsBlurStoryStatusChange {
let hashes: [String]
public struct NewsBlurStoryStatusChange: Sendable {
public let hashes: [String]
}
extension NewsBlurStoryStatusChange: NewsBlurDataConvertible {
var asData: Data? {
public var asData: Data? {
var postData = URLComponents()
postData.queryItems = hashes.map { URLQueryItem(name: "story_hash", value: $0) }

View File

@ -9,16 +9,18 @@
import Foundation
import Web
protocol NewsBlurDataConvertible {
public protocol NewsBlurDataConvertible {
var asData: Data? { get }
}
enum NewsBlurError: LocalizedError {
public enum NewsBlurError: LocalizedError, Sendable {
case general(message: String)
case invalidParameter
case unknown
var errorDescription: String? {
public var errorDescription: String? {
switch self {
case .general(let message):
return message

View File

@ -11,30 +11,31 @@ import Web
import Secrets
@MainActor public final class NewsBlurAPICaller: NSObject {
static let SessionIdCookie = "newsblur_sessionid"
public static let SessionIdCookie = "newsblur_sessionid"
let baseURL = URL(string: "https://www.newsblur.com/")!
var transport: Transport!
var suspended = false
var credentials: Credentials?
public var credentials: Credentials?
init(transport: Transport!) {
public init(transport: Transport!) {
super.init()
self.transport = transport
}
/// Cancels all pending requests rejects any that come in later
func suspend() {
public func suspend() {
transport.cancelAll()
suspended = true
}
func resume() {
public func resume() {
suspended = false
}
func validateCredentials(completion: @escaping (Result<Credentials?, Error>) -> Void) {
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)):
@ -67,11 +68,11 @@ import Secrets
}
}
func logout(completion: @escaping (Result<Void, Error>) -> Void) {
public func logout(completion: @escaping (Result<Void, Error>) -> Void) {
requestData(endpoint: "api/logout", completion: completion)
}
func retrieveFeeds(completion: @escaping (Result<([NewsBlurFeed]?, [NewsBlurFolder]?), Error>) -> Void) {
public func retrieveFeeds(completion: @escaping (Result<([NewsBlurFeed]?, [NewsBlurFolder]?), Error>) -> Void) {
let url = baseURL
.appendingPathComponent("reader/feeds")
.appendingQueryItems([
@ -111,21 +112,21 @@ import Secrets
}
}
func retrieveUnreadStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
public func retrieveUnreadStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
retrieveStoryHashes(
endpoint: "reader/unread_story_hashes",
completion: completion
)
}
func retrieveStarredStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
public func retrieveStarredStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
retrieveStoryHashes(
endpoint: "reader/starred_story_hashes",
completion: completion
)
}
func retrieveStories(feedID: String, page: Int, completion: @escaping (Result<([NewsBlurStory]?, Date?), Error>) -> Void) {
public func retrieveStories(feedID: String, page: Int, completion: @escaping (Result<([NewsBlurStory]?, Date?), Error>) -> Void) {
let url = baseURL
.appendingPathComponent("reader/feed/\(feedID)")
.appendingQueryItems([
@ -146,7 +147,7 @@ import Secrets
}
}
func retrieveStories(hashes: [NewsBlurStoryHash], completion: @escaping (Result<([NewsBlurStory]?, Date?), Error>) -> Void) {
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"))?
@ -164,7 +165,7 @@ import Secrets
}
}
func markAsUnread(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
public func markAsUnread(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/mark_story_hash_as_unread",
payload: NewsBlurStoryStatusChange(hashes: hashes),
@ -172,7 +173,7 @@ import Secrets
)
}
func markAsRead(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
public func markAsRead(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/mark_story_hashes_as_read",
payload: NewsBlurStoryStatusChange(hashes: hashes),
@ -180,7 +181,7 @@ import Secrets
)
}
func star(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
public func star(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/mark_story_hash_as_starred",
payload: NewsBlurStoryStatusChange(hashes: hashes),
@ -188,7 +189,7 @@ import Secrets
)
}
func unstar(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
public func unstar(hashes: [String], completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/mark_story_hash_as_unstarred",
payload: NewsBlurStoryStatusChange(hashes: hashes),
@ -196,7 +197,7 @@ import Secrets
)
}
func addFolder(named name: String, completion: @escaping (Result<Void, Error>) -> Void) {
public func addFolder(named name: String, completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/add_folder",
payload: NewsBlurFolderChange.add(name),
@ -204,7 +205,7 @@ import Secrets
)
}
func renameFolder(with folder: String, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
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),
@ -212,7 +213,7 @@ import Secrets
)
}
func removeFolder(named name: String, feedIDs: [String], completion: @escaping (Result<Void, Error>) -> Void) {
public func removeFolder(named name: String, feedIDs: [String], completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/delete_folder",
payload: NewsBlurFolderChange.delete(name, feedIDs),
@ -220,7 +221,7 @@ import Secrets
)
}
func addURL(_ url: String, folder: String?, completion: @escaping (Result<NewsBlurFeed?, Error>) -> Void) {
public func addURL(_ url: String, folder: String?, completion: @escaping (Result<NewsBlurFeed?, Error>) -> Void) {
sendUpdates(
endpoint: "reader/add_url",
payload: NewsBlurFeedChange.add(url, folder),
@ -235,7 +236,7 @@ import Secrets
}
}
func renameFeed(feedID: String, newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
public func renameFeed(feedID: String, newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/rename_feed",
payload: NewsBlurFeedChange.rename(feedID, newName)
@ -249,7 +250,7 @@ import Secrets
}
}
func deleteFeed(feedID: String, folder: String? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
public func deleteFeed(feedID: String, folder: String? = nil, completion: @escaping (Result<Void, Error>) -> Void) {
sendUpdates(
endpoint: "reader/delete_feed",
payload: NewsBlurFeedChange.delete(feedID, folder)
@ -263,7 +264,7 @@ import Secrets
}
}
func moveFeed(feedID: String, from: String?, to: String?, completion: @escaping (Result<Void, Error>) -> Void) {
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)