mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2024-12-22 15:50:17 +01:00
Create ReaderAPI module.
This commit is contained in:
parent
552753abd2
commit
5555ae5adc
@ -19,7 +19,8 @@ let package = Package(
|
||||
.package(path: "../Database"),
|
||||
.package(path: "../SyncDatabase"),
|
||||
.package(path: "../Core"),
|
||||
.package(path: "../CloudKitExtras")
|
||||
.package(path: "../CloudKitExtras"),
|
||||
.package(path: "../ReaderAPI")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@ -33,7 +34,8 @@ let package = Package(
|
||||
"SyncDatabase",
|
||||
"Database",
|
||||
"Core",
|
||||
"CloudKitExtras"
|
||||
"CloudKitExtras",
|
||||
"ReaderAPI"
|
||||
],
|
||||
swiftSettings: [
|
||||
.enableExperimentalFeature("StrictConcurrency")
|
||||
|
@ -14,6 +14,7 @@ import os.log
|
||||
import Secrets
|
||||
import Database
|
||||
import Core
|
||||
import ReaderAPI
|
||||
|
||||
public enum ReaderAPIAccountDelegateError: LocalizedError {
|
||||
case unknown
|
||||
@ -815,22 +816,23 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
func mapEntriesToParsedItems(account: Account, entries: [ReaderAPIEntry]?) -> Set<ParsedItem> {
|
||||
|
||||
guard let entries = entries else {
|
||||
guard let entries else {
|
||||
return Set<ParsedItem>()
|
||||
}
|
||||
|
||||
let parsedItems: [ParsedItem] = entries.compactMap { entry in
|
||||
guard let streamID = entry.origin.streamId else {
|
||||
return nil
|
||||
}
|
||||
let entriesWithOriginStreamIDs = entries.filter { $0.origin.streamId != nil }
|
||||
|
||||
var authors: Set<ParsedAuthor>? {
|
||||
let parsedItems: [ParsedItem] = entries.map { entry in
|
||||
|
||||
let streamID = entry.origin.streamId!
|
||||
|
||||
let authors: Set<ParsedAuthor>? = {
|
||||
guard let name = entry.author else {
|
||||
return nil
|
||||
}
|
||||
return Set([ParsedAuthor(name: name, url: nil, avatarURL: nil, emailAddress: nil)])
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return ParsedItem(syncServiceID: entry.uniqueID(variant: variant),
|
||||
uniqueID: entry.uniqueID(variant: variant),
|
||||
feedURL: streamID,
|
||||
@ -851,7 +853,6 @@ private extension ReaderAPIAccountDelegate {
|
||||
}
|
||||
|
||||
return Set(parsedItems)
|
||||
|
||||
}
|
||||
|
||||
func syncArticleReadState(account: Account, articleIDs: [String]?) async throws {
|
||||
|
@ -9,6 +9,7 @@
|
||||
import Foundation
|
||||
import Web
|
||||
import Secrets
|
||||
import ReaderAPI
|
||||
|
||||
enum CreateReaderAPISubscriptionResult {
|
||||
case created(ReaderAPISubscription)
|
||||
|
@ -10,6 +10,7 @@ import AppKit
|
||||
import Account
|
||||
import Web
|
||||
import Secrets
|
||||
import ReaderAPI
|
||||
|
||||
class AccountsReaderAPIWindowController: NSWindowController {
|
||||
|
||||
|
@ -579,6 +579,8 @@
|
||||
840958632201629A002C1579 /* Subscribe to Feed.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; };
|
||||
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D617E2029031C009BC708 /* AppDelegate.swift */; };
|
||||
8410C4A32BC1E27A00D4F799 /* ReaderAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 8410C4A22BC1E27A00D4F799 /* ReaderAPI */; };
|
||||
8410C4A52BC1E28200D4F799 /* ReaderAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 8410C4A42BC1E28200D4F799 /* ReaderAPI */; };
|
||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
|
||||
841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */; };
|
||||
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; };
|
||||
@ -1423,6 +1425,7 @@
|
||||
84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchRequestOperation.swift; sourceTree = "<group>"; };
|
||||
84CBDDAE1FD3674C005A61AA /* Technotes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Technotes; sourceTree = "<group>"; };
|
||||
84CC88171FE59CBF00644329 /* SmartFeedsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedsController.swift; sourceTree = "<group>"; };
|
||||
84CC98D92BC1DD25006A05C9 /* ReaderAPI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = ReaderAPI; sourceTree = "<group>"; };
|
||||
84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
|
||||
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailStatusBarView.swift; sourceTree = "<group>"; };
|
||||
84DCA50D2BAB643700792720 /* FoundationExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FoundationExtras; sourceTree = "<group>"; };
|
||||
@ -1587,6 +1590,7 @@
|
||||
84DCA5202BABB7A200792720 /* UIKitExtras in Frameworks */,
|
||||
179D280B26F6F93D003B2E0A /* Zip in Frameworks */,
|
||||
84DCA51E2BABB79900792720 /* FoundationExtras in Frameworks */,
|
||||
8410C4A52BC1E28200D4F799 /* ReaderAPI in Frameworks */,
|
||||
84C1A8582BBBA5BD006E3E96 /* Web in Frameworks */,
|
||||
516B695F24D2F33B00B5702F /* Account in Frameworks */,
|
||||
845611742BBD145D00507B73 /* ParserObjC in Frameworks */,
|
||||
@ -1613,6 +1617,7 @@
|
||||
5132775E2590FC640064F1E7 /* Articles in Frameworks */,
|
||||
84DCA5252BABBB5A00792720 /* Core in Frameworks */,
|
||||
8479ABE32B9E906E00F84C4D /* Database in Frameworks */,
|
||||
8410C4A32BC1E27A00D4F799 /* ReaderAPI in Frameworks */,
|
||||
84DCA5122BABB75600792720 /* FoundationExtras in Frameworks */,
|
||||
513277612590FC640064F1E7 /* ArticlesDatabase in Frameworks */,
|
||||
51C4CFF624D37DD500AF9874 /* Secrets in Frameworks */,
|
||||
@ -2350,6 +2355,7 @@
|
||||
849C64611ED37A5D003D8FC0 /* Products */,
|
||||
51C452B22265141B00C03939 /* Frameworks */,
|
||||
51CD32C624D2DEF9009ABAEF /* Account */,
|
||||
84CC98D92BC1DD25006A05C9 /* ReaderAPI */,
|
||||
51CD32C424D2CF1D009ABAEF /* Articles */,
|
||||
51CD32C324D2CD57009ABAEF /* ArticlesDatabase */,
|
||||
51CD32C724D2E06C009ABAEF /* Secrets */,
|
||||
@ -2968,6 +2974,7 @@
|
||||
84C1A8572BBBA5BD006E3E96 /* Web */,
|
||||
845611702BBD145D00507B73 /* Parser */,
|
||||
845611732BBD145D00507B73 /* ParserObjC */,
|
||||
8410C4A42BC1E28200D4F799 /* ReaderAPI */,
|
||||
);
|
||||
productName = "NetNewsWire-iOS";
|
||||
productReference = 840D617C2029031C009BC708 /* NetNewsWire.app */;
|
||||
@ -3014,6 +3021,7 @@
|
||||
841CECD72BAD04B20001EE72 /* Tree */,
|
||||
8456116A2BBD145200507B73 /* Parser */,
|
||||
8456116D2BBD145200507B73 /* ParserObjC */,
|
||||
8410C4A22BC1E27A00D4F799 /* ReaderAPI */,
|
||||
);
|
||||
productName = NetNewsWire;
|
||||
productReference = 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */;
|
||||
@ -4863,6 +4871,14 @@
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Account;
|
||||
};
|
||||
8410C4A22BC1E27A00D4F799 /* ReaderAPI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ReaderAPI;
|
||||
};
|
||||
8410C4A42BC1E28200D4F799 /* ReaderAPI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ReaderAPI;
|
||||
};
|
||||
841CECD72BAD04B20001EE72 /* Tree */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Tree;
|
||||
|
8
ReaderAPI/.gitignore
vendored
Normal file
8
ReaderAPI/.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
|
28
ReaderAPI/Package.swift
Normal file
28
ReaderAPI/Package.swift
Normal file
@ -0,0 +1,28 @@
|
||||
// swift-tools-version: 5.10
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ReaderAPI",
|
||||
platforms: [.macOS(.v14), .iOS(.v17)],
|
||||
products: [
|
||||
.library(
|
||||
name: "ReaderAPI",
|
||||
targets: ["ReaderAPI"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../FoundationExtras")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "ReaderAPI",
|
||||
dependencies: ["FoundationExtras"],
|
||||
swiftSettings: [
|
||||
.enableExperimentalFeature("StrictConcurrency")
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "ReaderAPITests",
|
||||
dependencies: ["ReaderAPI"]),
|
||||
]
|
||||
)
|
@ -7,14 +7,13 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Parser
|
||||
|
||||
struct ReaderAPIEntryWrapper: Codable {
|
||||
let id: String
|
||||
let updated: Int
|
||||
let entries: [ReaderAPIEntry]
|
||||
|
||||
|
||||
public struct ReaderAPIEntryWrapper: Codable, Sendable {
|
||||
|
||||
public let id: String
|
||||
public let updated: Int
|
||||
public let entries: [ReaderAPIEntry]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "id"
|
||||
case updated = "updated"
|
||||
@ -46,20 +45,21 @@ struct ReaderAPIEntryWrapper: Codable {
|
||||
}
|
||||
}
|
||||
*/
|
||||
struct ReaderAPIEntry: Codable {
|
||||
|
||||
let articleID: String
|
||||
let title: String?
|
||||
let author: String?
|
||||
public struct ReaderAPIEntry: Codable, Sendable {
|
||||
|
||||
let publishedTimestamp: Double?
|
||||
let crawledTimestamp: String?
|
||||
let timestampUsec: String?
|
||||
|
||||
let summary: ReaderAPIArticleSummary
|
||||
let alternates: [ReaderAPIAlternateLocation]?
|
||||
let categories: [String]
|
||||
let origin: ReaderAPIEntryOrigin
|
||||
public let articleID: String
|
||||
public let title: String?
|
||||
public let author: String?
|
||||
|
||||
public let publishedTimestamp: Double?
|
||||
public let crawledTimestamp: String?
|
||||
public let timestampUsec: String?
|
||||
|
||||
public let summary: ReaderAPIArticleSummary
|
||||
public let alternates: [ReaderAPIAlternateLocation]?
|
||||
public let categories: [String]
|
||||
public let origin: ReaderAPIEntryOrigin
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case articleID = "id"
|
||||
@ -74,14 +74,14 @@ struct ReaderAPIEntry: Codable {
|
||||
case timestampUsec = "timestampUsec"
|
||||
}
|
||||
|
||||
func parseDatePublished() -> Date? {
|
||||
public func parseDatePublished() -> Date? {
|
||||
guard let unixTime = publishedTimestamp else {
|
||||
return nil
|
||||
}
|
||||
return Date(timeIntervalSince1970: unixTime)
|
||||
}
|
||||
|
||||
func uniqueID(variant: ReaderAPIVariant) -> String {
|
||||
public func uniqueID(variant: ReaderAPIVariant) -> String {
|
||||
// Should look something like "tag:google.com,2005:reader/item/00058b10ce338909"
|
||||
// REGEX feels heavy, I should be able to just split on / and take the last element
|
||||
|
||||
@ -103,25 +103,28 @@ struct ReaderAPIEntry: Codable {
|
||||
|
||||
}
|
||||
|
||||
struct ReaderAPIArticleSummary: Codable {
|
||||
let content: String?
|
||||
public struct ReaderAPIArticleSummary: Codable, Sendable {
|
||||
|
||||
public let content: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case content = "content"
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderAPIAlternateLocation: Codable {
|
||||
let url: String?
|
||||
|
||||
public struct ReaderAPIAlternateLocation: Codable, Sendable {
|
||||
|
||||
public let url: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "href"
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderAPIEntryOrigin: Codable {
|
||||
let streamId: String?
|
||||
let title: String?
|
||||
public struct ReaderAPIEntryOrigin: Codable, Sendable {
|
||||
|
||||
public let streamId: String?
|
||||
public let title: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case streamId = "streamId"
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Parser
|
||||
import FoundationExtras
|
||||
|
||||
/*
|
||||
|
||||
@ -18,10 +18,11 @@ import Parser
|
||||
|
||||
*/
|
||||
|
||||
struct ReaderAPIQuickAddResult: Codable {
|
||||
let numResults: Int
|
||||
let error: String?
|
||||
let streamId: String?
|
||||
public struct ReaderAPIQuickAddResult: Codable {
|
||||
|
||||
public let numResults: Int
|
||||
public let error: String?
|
||||
public let streamId: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case numResults = "numResults"
|
||||
@ -30,9 +31,10 @@ struct ReaderAPIQuickAddResult: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderAPISubscriptionContainer: Codable {
|
||||
let subscriptions: [ReaderAPISubscription]
|
||||
public struct ReaderAPISubscriptionContainer: Codable, Sendable {
|
||||
|
||||
public let subscriptions: [ReaderAPISubscription]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case subscriptions = "subscriptions"
|
||||
}
|
||||
@ -54,13 +56,14 @@ struct ReaderAPISubscriptionContainer: Codable {
|
||||
}
|
||||
|
||||
*/
|
||||
struct ReaderAPISubscription: Codable {
|
||||
let feedID: String
|
||||
let name: String?
|
||||
let categories: [ReaderAPICategory]
|
||||
let feedURL: String?
|
||||
let homePageURL: String?
|
||||
let iconURL: String?
|
||||
public struct ReaderAPISubscription: Codable, Sendable {
|
||||
|
||||
public let feedID: String
|
||||
public let name: String?
|
||||
public let categories: [ReaderAPICategory]
|
||||
public let feedURL: String?
|
||||
public let homePageURL: String?
|
||||
public let iconURL: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case feedID = "id"
|
||||
@ -71,7 +74,7 @@ struct ReaderAPISubscription: Codable {
|
||||
case iconURL = "iconUrl"
|
||||
}
|
||||
|
||||
var url: String {
|
||||
public var url: String {
|
||||
if let feedURL = feedURL {
|
||||
return feedURL
|
||||
} else {
|
||||
@ -80,9 +83,10 @@ struct ReaderAPISubscription: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderAPICategory: Codable {
|
||||
let categoryId: String
|
||||
let categoryLabel: String
|
||||
public struct ReaderAPICategory: Codable, Sendable {
|
||||
|
||||
public let categoryId: String
|
||||
public let categoryLabel: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case categoryId = "id"
|
@ -8,25 +8,26 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ReaderAPITagContainer: Codable {
|
||||
let tags: [ReaderAPITag]
|
||||
|
||||
public struct ReaderAPITagContainer: Codable, Sendable {
|
||||
|
||||
public let tags: [ReaderAPITag]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case tags = "tags"
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderAPITag: Codable {
|
||||
|
||||
let tagID: String
|
||||
let type: String?
|
||||
public struct ReaderAPITag: Codable, Sendable {
|
||||
|
||||
public let tagID: String
|
||||
public let type: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case tagID = "id"
|
||||
case type = "type"
|
||||
}
|
||||
|
||||
var folderName: String? {
|
||||
public var folderName: String? {
|
||||
guard let range = tagID.range(of: "/label/") else {
|
||||
return nil
|
||||
}
|
@ -8,18 +8,20 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ReaderAPIReferenceWrapper: Codable {
|
||||
let itemRefs: [ReaderAPIReference]?
|
||||
let continuation: String?
|
||||
|
||||
public struct ReaderAPIReferenceWrapper: Codable, Sendable {
|
||||
|
||||
public let itemRefs: [ReaderAPIReference]?
|
||||
public let continuation: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case itemRefs = "itemRefs"
|
||||
case continuation = "continuation"
|
||||
}
|
||||
}
|
||||
|
||||
struct ReaderAPIReference: Codable {
|
||||
let itemId: String?
|
||||
public struct ReaderAPIReference: Codable, Sendable {
|
||||
|
||||
public let itemId: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case itemId = "id"
|
12
ReaderAPI/Tests/ReaderAPITests/ReaderAPITests.swift
Normal file
12
ReaderAPI/Tests/ReaderAPITests/ReaderAPITests.swift
Normal file
@ -0,0 +1,12 @@
|
||||
import XCTest
|
||||
@testable import ReaderAPI
|
||||
|
||||
final class ReaderAPITests: 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
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import Account
|
||||
import Secrets
|
||||
import Web
|
||||
import SafariServices
|
||||
import ReaderAPI
|
||||
|
||||
class ReaderAPIAccountViewController: UITableViewController {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user