Create Feedbin module.
This commit is contained in:
parent
a51d161e35
commit
826ec7d413
@ -23,6 +23,7 @@ let package = Package(
|
||||
.package(path: "../ReaderAPI"),
|
||||
.package(path: "../CloudKitSync"),
|
||||
.package(path: "../NewsBlur"),
|
||||
.package(path: "../Feedbin"),
|
||||
.package(path: "../CommonErrors")
|
||||
],
|
||||
targets: [
|
||||
@ -41,6 +42,7 @@ let package = Package(
|
||||
"ReaderAPI",
|
||||
"NewsBlur",
|
||||
"CloudKitSync",
|
||||
"Feedbin",
|
||||
"CommonErrors"
|
||||
],
|
||||
swiftSettings: [
|
||||
|
@ -13,6 +13,7 @@
|
||||
import Foundation
|
||||
import Web
|
||||
import Secrets
|
||||
import Feedbin
|
||||
|
||||
enum CreateSubscriptionResult {
|
||||
case created(FeedbinSubscription)
|
||||
|
@ -14,6 +14,7 @@ import SyncDatabase
|
||||
import os.log
|
||||
import Secrets
|
||||
import Core
|
||||
import Feedbin
|
||||
|
||||
public enum FeedbinAccountDelegateError: String, Error {
|
||||
case invalidParameter = "There was an invalid parameter passed."
|
||||
|
@ -1,73 +0,0 @@
|
||||
//
|
||||
// FeedbinFeed.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Brent Simmons on 12/10/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Parser
|
||||
|
||||
struct FeedbinSubscription: Hashable, Codable {
|
||||
|
||||
let subscriptionID: Int
|
||||
let feedID: Int
|
||||
let name: String?
|
||||
let url: String
|
||||
let homePageURL: String?
|
||||
let jsonFeed: FeedbinSubscriptionJSONFeed?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case subscriptionID = "id"
|
||||
case feedID = "feed_id"
|
||||
case name = "title"
|
||||
case url = "feed_url"
|
||||
case homePageURL = "site_url"
|
||||
case jsonFeed = "json_feed"
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(subscriptionID)
|
||||
}
|
||||
|
||||
static func == (lhs: FeedbinSubscription, rhs: FeedbinSubscription) -> Bool {
|
||||
return lhs.subscriptionID == rhs.subscriptionID
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct FeedbinSubscriptionJSONFeed: Codable {
|
||||
let favicon: String?
|
||||
let icon: String?
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case favicon = "favicon"
|
||||
case icon = "icon"
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbinCreateSubscription: Codable {
|
||||
let feedURL: String
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case feedURL = "feed_url"
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbinUpdateSubscription: Codable {
|
||||
let title: String
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case title
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbinSubscriptionChoice: Codable {
|
||||
|
||||
let name: String?
|
||||
let url: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "title"
|
||||
case url = "feed_url"
|
||||
}
|
||||
|
||||
}
|
@ -37,7 +37,7 @@ public extension URLRequest {
|
||||
]
|
||||
httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8)
|
||||
case .newsBlurSessionId:
|
||||
setValue("\(NewsBlurAPICaller.SessionIdCookie)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
|
||||
setValue("\(NewsBlurAPICaller.sessionIDCookieKey)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
|
||||
httpShouldHandleCookies = true
|
||||
case .readerBasic:
|
||||
setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||
|
8
Feedbin/.gitignore
vendored
Normal file
8
Feedbin/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/configuration/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
31
Feedbin/Package.swift
Normal file
31
Feedbin/Package.swift
Normal file
@ -0,0 +1,31 @@
|
||||
// swift-tools-version: 5.10
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Feedbin",
|
||||
platforms: [.macOS(.v14), .iOS(.v17)],
|
||||
products: [
|
||||
.library(
|
||||
name: "Feedbin",
|
||||
targets: ["Feedbin"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../Parser"),
|
||||
],
|
||||
|
||||
targets: [
|
||||
.target(
|
||||
name: "Feedbin",
|
||||
dependencies: [
|
||||
"Parser"
|
||||
],
|
||||
swiftSettings: [
|
||||
.enableExperimentalFeature("StrictConcurrency")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "FeedbinTests",
|
||||
dependencies: ["Feedbin"]),
|
||||
]
|
||||
)
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedbinDate {
|
||||
public struct FeedbinDate {
|
||||
|
||||
public static let formatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
@ -17,5 +17,4 @@ struct FeedbinDate {
|
||||
formatter.timeZone = TimeZone(abbreviation: "GMT")
|
||||
return formatter
|
||||
}()
|
||||
|
||||
}
|
@ -9,24 +9,24 @@
|
||||
import Foundation
|
||||
import Parser
|
||||
|
||||
final class FeedbinEntry: Decodable {
|
||||
public final class FeedbinEntry: Decodable, @unchecked Sendable {
|
||||
|
||||
let articleID: Int
|
||||
let feedID: Int
|
||||
let title: String?
|
||||
let url: String?
|
||||
let authorName: String?
|
||||
let contentHTML: String?
|
||||
let summary: String?
|
||||
let datePublished: String?
|
||||
let dateArrived: String?
|
||||
let jsonFeed: FeedbinEntryJSONFeed?
|
||||
public let articleID: Int
|
||||
public let feedID: Int
|
||||
public let title: String?
|
||||
public let url: String?
|
||||
public let authorName: String?
|
||||
public let contentHTML: String?
|
||||
public let summary: String?
|
||||
public let datePublished: String?
|
||||
public let dateArrived: String?
|
||||
public let jsonFeed: FeedbinEntryJSONFeed?
|
||||
|
||||
// Feedbin dates can't be decoded by the JSONDecoding 8601 decoding strategy. Feedbin
|
||||
// requires a very specific date formatter to work and even then it fails occasionally.
|
||||
// Rather than loose all the entries we only lose the one date by decoding as a string
|
||||
// and letting the one date fail when parsed.
|
||||
lazy var parsedDatePublished: Date? = {
|
||||
public lazy var parsedDatePublished: Date? = {
|
||||
if let datePublished = datePublished {
|
||||
return RSDateWithString(datePublished)
|
||||
}
|
||||
@ -49,9 +49,10 @@ final class FeedbinEntry: Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbinEntryJSONFeed: Decodable {
|
||||
let jsonFeedAuthor: FeedbinEntryJSONFeedAuthor?
|
||||
let jsonFeedExternalURL: String?
|
||||
public struct FeedbinEntryJSONFeed: Decodable, Sendable {
|
||||
|
||||
public let jsonFeedAuthor: FeedbinEntryJSONFeedAuthor?
|
||||
public let jsonFeedExternalURL: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case jsonFeedAuthor = "author"
|
||||
@ -74,9 +75,11 @@ struct FeedbinEntryJSONFeed: Decodable {
|
||||
|
||||
}
|
||||
|
||||
struct FeedbinEntryJSONFeedAuthor: Decodable {
|
||||
let url: String?
|
||||
let avatarURL: String?
|
||||
public struct FeedbinEntryJSONFeedAuthor: Decodable, Sendable {
|
||||
|
||||
public let url: String?
|
||||
public let avatarURL: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "url"
|
||||
case avatarURL = "avatar"
|
@ -8,10 +8,10 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedbinImportResult: Codable {
|
||||
|
||||
let importResultID: Int
|
||||
let complete: Bool
|
||||
public struct FeedbinImportResult: Codable, Sendable {
|
||||
|
||||
public let importResultID: Int
|
||||
public let complete: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case importResultID = "id"
|
@ -8,12 +8,16 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedbinStarredEntry: Codable {
|
||||
|
||||
let starredEntries: [Int]
|
||||
public struct FeedbinStarredEntry: Codable, Sendable {
|
||||
|
||||
public let starredEntries: [Int]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case starredEntries = "starred_entries"
|
||||
}
|
||||
|
||||
public init(starredEntries: [Int]) {
|
||||
|
||||
self.starredEntries = starredEntries
|
||||
}
|
||||
}
|
89
Feedbin/Sources/Feedbin/FeedbinSubscription.swift
Normal file
89
Feedbin/Sources/Feedbin/FeedbinSubscription.swift
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// FeedbinFeed.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Brent Simmons on 12/10/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Parser
|
||||
|
||||
public struct FeedbinSubscription: Hashable, Codable, Sendable {
|
||||
|
||||
public let subscriptionID: Int
|
||||
public let feedID: Int
|
||||
public let name: String?
|
||||
public let url: String
|
||||
public let homePageURL: String?
|
||||
public let jsonFeed: FeedbinSubscriptionJSONFeed?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case subscriptionID = "id"
|
||||
case feedID = "feed_id"
|
||||
case name = "title"
|
||||
case url = "feed_url"
|
||||
case homePageURL = "site_url"
|
||||
case jsonFeed = "json_feed"
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(subscriptionID)
|
||||
}
|
||||
|
||||
public static func == (lhs: FeedbinSubscription, rhs: FeedbinSubscription) -> Bool {
|
||||
return lhs.subscriptionID == rhs.subscriptionID
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct FeedbinSubscriptionJSONFeed: Codable, Sendable {
|
||||
|
||||
public let favicon: String?
|
||||
public let icon: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case favicon = "favicon"
|
||||
case icon = "icon"
|
||||
}
|
||||
}
|
||||
|
||||
public struct FeedbinCreateSubscription: Codable, Sendable {
|
||||
|
||||
public let feedURL: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case feedURL = "feed_url"
|
||||
}
|
||||
|
||||
public init(feedURL: String) {
|
||||
|
||||
self.feedURL = feedURL
|
||||
}
|
||||
}
|
||||
|
||||
public struct FeedbinUpdateSubscription: Codable, Sendable {
|
||||
|
||||
public let title: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case title
|
||||
}
|
||||
|
||||
public init(title: String) {
|
||||
|
||||
self.title = title
|
||||
}
|
||||
}
|
||||
|
||||
public struct FeedbinSubscriptionChoice: Codable, Sendable {
|
||||
|
||||
public let name: String?
|
||||
public let url: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "title"
|
||||
case url = "feed_url"
|
||||
}
|
||||
|
||||
}
|
@ -8,36 +8,39 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedbinTag: Codable {
|
||||
|
||||
let tagID: Int
|
||||
let name: String
|
||||
|
||||
public struct FeedbinTag: Codable, Sendable {
|
||||
|
||||
public let tagID: Int
|
||||
public let name: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case tagID = "id"
|
||||
case name = "name"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct FeedbinRenameTag: Codable {
|
||||
|
||||
let oldName: String
|
||||
let newName: String
|
||||
|
||||
public struct FeedbinRenameTag: Codable, Sendable {
|
||||
|
||||
public let oldName: String
|
||||
public let newName: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case oldName = "old_name"
|
||||
case newName = "new_name"
|
||||
}
|
||||
|
||||
|
||||
public init(oldName: String, newName: String) {
|
||||
|
||||
self.oldName = oldName
|
||||
self.newName = newName
|
||||
}
|
||||
}
|
||||
|
||||
struct FeedbinDeleteTag: Codable {
|
||||
|
||||
let name: String
|
||||
public struct FeedbinDeleteTag: Codable, Sendable {
|
||||
|
||||
public let name: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
}
|
||||
|
||||
}
|
@ -8,28 +8,32 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedbinTagging: Codable {
|
||||
public struct FeedbinTagging: Codable, Sendable {
|
||||
|
||||
let taggingID: Int
|
||||
let feedID: Int
|
||||
let name: String
|
||||
public let taggingID: Int
|
||||
public let feedID: Int
|
||||
public let name: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case taggingID = "id"
|
||||
case feedID = "feed_id"
|
||||
case name = "name"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct FeedbinCreateTagging: Codable {
|
||||
|
||||
let feedID: Int
|
||||
let name: String
|
||||
public struct FeedbinCreateTagging: Codable, Sendable {
|
||||
|
||||
public let feedID: Int
|
||||
public let name: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case feedID = "feed_id"
|
||||
case name = "name"
|
||||
}
|
||||
|
||||
|
||||
public init(feedID: Int, name: String) {
|
||||
|
||||
self.feedID = feedID
|
||||
self.name = name
|
||||
}
|
||||
}
|
@ -8,12 +8,16 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeedbinUnreadEntry: Codable {
|
||||
|
||||
let unreadEntries: [Int]
|
||||
public struct FeedbinUnreadEntry: Codable, Sendable {
|
||||
|
||||
public let unreadEntries: [Int]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case unreadEntries = "unread_entries"
|
||||
}
|
||||
|
||||
|
||||
public init(unreadEntries: [Int]) {
|
||||
|
||||
self.unreadEntries = unreadEntries
|
||||
}
|
||||
}
|
12
Feedbin/Tests/FeedbinTests/FeedbinTests.swift
Normal file
12
Feedbin/Tests/FeedbinTests/FeedbinTests.swift
Normal file
@ -0,0 +1,12 @@
|
||||
import XCTest
|
||||
@testable import Feedbin
|
||||
|
||||
final class FeedbinTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// XCTest Documentation
|
||||
// https://developer.apple.com/documentation/xctest
|
||||
|
||||
// Defining Test Cases and Test Methods
|
||||
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
|
||||
}
|
||||
}
|
@ -1466,6 +1466,7 @@
|
||||
84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = "<group>"; };
|
||||
84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84FB9FAC2BC33AFE00B7AFC3 /* NewsBlur */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NewsBlur; sourceTree = "<group>"; };
|
||||
84FB9FAD2BC344F800B7AFC3 /* Feedbin */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Feedbin; sourceTree = "<group>"; };
|
||||
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = "<group>"; };
|
||||
B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+NetNewsWire.swift"; sourceTree = "<group>"; };
|
||||
B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@ -2358,6 +2359,7 @@
|
||||
849C64611ED37A5D003D8FC0 /* Products */,
|
||||
51C452B22265141B00C03939 /* Frameworks */,
|
||||
51CD32C624D2DEF9009ABAEF /* Account */,
|
||||
84FB9FAD2BC344F800B7AFC3 /* Feedbin */,
|
||||
84FB9FAC2BC33AFE00B7AFC3 /* NewsBlur */,
|
||||
84CC98D92BC1DD25006A05C9 /* ReaderAPI */,
|
||||
845F3D2B2BC268FE00AEBB68 /* CloudKitSync */,
|
||||
|
@ -1,5 +1,4 @@
|
||||
// swift-tools-version: 5.10
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
@ -7,7 +6,6 @@ let package = Package(
|
||||
name: "NewsBlur",
|
||||
platforms: [.macOS(.v14), .iOS(.v17)],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||
.library(
|
||||
name: "NewsBlur",
|
||||
targets: ["NewsBlur"]),
|
||||
@ -18,8 +16,6 @@ let package = Package(
|
||||
.package(path: "../Parser"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||
// Targets can depend on other targets in this package and products from dependencies.
|
||||
.target(
|
||||
name: "NewsBlur",
|
||||
dependencies: [
|
||||
|
@ -12,7 +12,7 @@ import Secrets
|
||||
|
||||
@MainActor public final class NewsBlurAPICaller: NSObject {
|
||||
|
||||
public static let SessionIdCookie = "newsblur_sessionid"
|
||||
public static let sessionIDCookieKey = "newsblur_sessionid"
|
||||
|
||||
let baseURL = URL(string: "https://www.newsblur.com/")!
|
||||
var transport: Transport!
|
||||
@ -55,7 +55,7 @@ import Secrets
|
||||
}
|
||||
|
||||
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
|
||||
for cookie in cookies where cookie.name == Self.SessionIdCookie {
|
||||
for cookie in cookies where cookie.name == Self.sessionIDCookieKey {
|
||||
let credentials = Credentials(type: .newsBlurSessionId, username: username, secret: cookie.value)
|
||||
completion(.success(credentials))
|
||||
return
|
||||
|
@ -36,7 +36,7 @@ public extension URLRequest {
|
||||
|
||||
} else if credentialsType == .newsBlurSessionId {
|
||||
|
||||
setValue("\(NewsBlurAPICaller.SessionIdCookie)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
|
||||
setValue("\(NewsBlurAPICaller.sessionIDCookieKey)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
|
||||
httpShouldHandleCookies = true
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
public struct FeedParserError: Error, Sendable {
|
||||
|
||||
public enum FeedParserErrorType {
|
||||
public enum FeedParserErrorType: Sendable {
|
||||
|
||||
case rssChannelNotFound
|
||||
case rssItemsNotFound
|
||||
|
Loading…
x
Reference in New Issue
Block a user