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 Foundation
import Web import Web
import Secrets import Secrets
import NewsBlur
public extension URLRequest { public extension URLRequest {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,27 +9,36 @@
import Foundation import Foundation
import Parser import Parser
typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash public typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash
struct NewsBlurStoryHashesResponse: Decodable { public struct NewsBlurStoryHashesResponse: Decodable, Sendable {
typealias StoryHashDictionary = [String: [StoryHash]]
var unread: [StoryHash]? public typealias StoryHashDictionary = [String: [StoryHash]]
var starred: [StoryHash]?
struct StoryHash: Hashable, Codable { public var unread: [StoryHash]?
var hash: String public var starred: [StoryHash]?
var timestamp: Date
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 { extension NewsBlurStoryHashesResponse {
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case unread = "unread_feed_story_hashes" case unread = "unread_feed_story_hashes"
case starred = "starred_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) let container = try decoder.container(keyedBy: CodingKeys.self)
// Parse unread // Parse unread

View File

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

View File

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

View File

@ -11,30 +11,31 @@ import Web
import Secrets import Secrets
@MainActor public final class NewsBlurAPICaller: NSObject { @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/")! let baseURL = URL(string: "https://www.newsblur.com/")!
var transport: Transport! var transport: Transport!
var suspended = false var suspended = false
var credentials: Credentials? public var credentials: Credentials?
init(transport: Transport!) { public init(transport: Transport!) {
super.init() super.init()
self.transport = transport self.transport = transport
} }
/// Cancels all pending requests rejects any that come in later /// Cancels all pending requests rejects any that come in later
func suspend() { public func suspend() {
transport.cancelAll() transport.cancelAll()
suspended = true suspended = true
} }
func resume() { public func resume() {
suspended = false 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 requestData(endpoint: "api/login", resultType: NewsBlurLoginResponse.self) { result in
switch result { switch result {
case .success((let response, let payload)): 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) 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 let url = baseURL
.appendingPathComponent("reader/feeds") .appendingPathComponent("reader/feeds")
.appendingQueryItems([ .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( retrieveStoryHashes(
endpoint: "reader/unread_story_hashes", endpoint: "reader/unread_story_hashes",
completion: completion completion: completion
) )
} }
func retrieveStarredStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) { public func retrieveStarredStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) {
retrieveStoryHashes( retrieveStoryHashes(
endpoint: "reader/starred_story_hashes", endpoint: "reader/starred_story_hashes",
completion: completion 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 let url = baseURL
.appendingPathComponent("reader/feed/\(feedID)") .appendingPathComponent("reader/feed/\(feedID)")
.appendingQueryItems([ .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 let url = baseURL
.appendingPathComponent("reader/river_stories") .appendingPathComponent("reader/river_stories")
.appendingQueryItem(.init(name: "include_hidden", value: "false"))? .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( sendUpdates(
endpoint: "reader/mark_story_hash_as_unread", endpoint: "reader/mark_story_hash_as_unread",
payload: NewsBlurStoryStatusChange(hashes: hashes), 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( sendUpdates(
endpoint: "reader/mark_story_hashes_as_read", endpoint: "reader/mark_story_hashes_as_read",
payload: NewsBlurStoryStatusChange(hashes: hashes), 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( sendUpdates(
endpoint: "reader/mark_story_hash_as_starred", endpoint: "reader/mark_story_hash_as_starred",
payload: NewsBlurStoryStatusChange(hashes: hashes), 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( sendUpdates(
endpoint: "reader/mark_story_hash_as_unstarred", endpoint: "reader/mark_story_hash_as_unstarred",
payload: NewsBlurStoryStatusChange(hashes: hashes), 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( sendUpdates(
endpoint: "reader/add_folder", endpoint: "reader/add_folder",
payload: NewsBlurFolderChange.add(name), 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( sendUpdates(
endpoint: "reader/rename_folder", endpoint: "reader/rename_folder",
payload: NewsBlurFolderChange.rename(folder, name), 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( sendUpdates(
endpoint: "reader/delete_folder", endpoint: "reader/delete_folder",
payload: NewsBlurFolderChange.delete(name, feedIDs), 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( sendUpdates(
endpoint: "reader/add_url", endpoint: "reader/add_url",
payload: NewsBlurFeedChange.add(url, folder), 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( sendUpdates(
endpoint: "reader/rename_feed", endpoint: "reader/rename_feed",
payload: NewsBlurFeedChange.rename(feedID, newName) 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( sendUpdates(
endpoint: "reader/delete_feed", endpoint: "reader/delete_feed",
payload: NewsBlurFeedChange.delete(feedID, folder) 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( sendUpdates(
endpoint: "reader/move_feed_to_folder", endpoint: "reader/move_feed_to_folder",
payload: NewsBlurFeedChange.move(feedID, from, to) payload: NewsBlurFeedChange.move(feedID, from, to)