diff --git a/Feedly/Sources/Feedly/FeedlyModel.swift b/Feedly/Sources/Feedly/FeedlyModel.swift new file mode 100644 index 000000000..17162508a --- /dev/null +++ b/Feedly/Sources/Feedly/FeedlyModel.swift @@ -0,0 +1,457 @@ +// +// FeedlyModel.swift +// +// +// Created by Brent Simmons on 4/27/24. +// Includes text of a bunch of files created by Kiel Gillard 2019-2020 +// + +import Foundation +import Articles +import Parser + +public struct FeedlyCategory: Decodable, Sendable, Equatable { + + public let label: String + public let id: String + + public static func ==(lhs: FeedlyCategory, rhs: FeedlyCategory) -> Bool { + lhs.label == rhs.label && lhs.id == rhs.id + } +} + +public struct FeedlyCollection: Codable, Sendable { + + public let feeds: [FeedlyFeed] + public let label: String + public let id: String +} + +public struct FeedlyCollectionParser: Sendable { + + public let collection: FeedlyCollection + + private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() + + public var folderName: String { + return rightToLeftTextSantizer.sanitize(collection.label) ?? "" + } + + public var externalID: String { + return collection.id + } + + public init(collection: FeedlyCollection) { + self.collection = collection + } +} + +public struct FeedlyEntry: Decodable, Sendable, Hashable { + + /// the unique, immutable ID for this particular article. + public let id: String + + /// the article’s title. This string does not contain any HTML markup. + public let title: String? + + public struct Content: Decodable, Sendable, Equatable { + + public enum Direction: String, Decodable, Sendable { + case leftToRight = "ltr" + case rightToLeft = "rtl" + } + + public let content: String? + public let direction: Direction? + + public static func ==(lhs: Content, rhs: Content) -> Bool { + lhs.content == rhs.content && lhs.direction == rhs.direction + } + } + + /// This object typically has two values: “content” for the content itself, and “direction” (“ltr” for left-to-right, “rtl” for right-to-left). The content itself contains sanitized HTML markup. + public let content: Content? + + /// content object the article summary. See the content object above. + public let summary: Content? + + /// the author’s name + public let author: String? + + /// the immutable timestamp, in ms, when this article was processed by the feedly Cloud servers. + public let crawled: Date + + /// the timestamp, in ms, when this article was re-processed and updated by the feedly Cloud servers. + public let recrawled: Date? + + /// the feed from which this article was crawled. If present, “streamId” will contain the feed id, “title” will contain the feed title, and “htmlUrl” will contain the feed’s website. + public let origin: FeedlyOrigin? + + /// Used to help find the URL to visit an article on a web site. + /// See https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + public let canonical: [FeedlyLink]? + + /// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page. + public let alternate: [FeedlyLink]? + + /// Was this entry read by the user? If an Authorization header is not provided, this will always return false. If an Authorization header is provided, it will reflect if the user has read this entry or not. + public let unread: Bool + + /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present. + public let tags: [FeedlyTag]? + + /// a list of category objects (“id” and “label”) that the user associated with the feed of this entry. This value is only returned if an Authorization header is provided. + public let categories: [FeedlyCategory]? + + /// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links. + public let enclosure: [FeedlyLink]? + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + + public static func ==(lhs: FeedlyEntry, rhs: FeedlyEntry) -> Bool { + + lhs.id == rhs.id && lhs.title == rhs.title && lhs.content == rhs.content && lhs.summary == rhs.summary && lhs.author == rhs.author && lhs.crawled == rhs.crawled && lhs.recrawled == rhs.recrawled && lhs.origin == rhs.origin && lhs.canonical == rhs.canonical && lhs.alternate == rhs.alternate && lhs.unread == rhs.unread && lhs.tags == rhs.tags && lhs.categories == rhs.categories && lhs.enclosure == rhs.enclosure + } +} + +public protocol FeedlyEntryIdentifierProviding: AnyObject { + @MainActor var entryIDs: Set { get } +} + +public final class FeedlyEntryIdentifierProvider: FeedlyEntryIdentifierProviding { + + private (set) public var entryIDs: Set + + public init(entryIDs: Set = Set()) { + self.entryIDs = entryIDs + } + + @MainActor public func addEntryIDs(from provider: FeedlyEntryIdentifierProviding) { + entryIDs.formUnion(provider.entryIDs) + } + + @MainActor public func addEntryIDs(in articleIDs: [String]) { + entryIDs.formUnion(articleIDs) + } +} + +public struct FeedlyEntryParser: Sendable { + + public let entry: FeedlyEntry + + private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() + + public var id: String { + return entry.id + } + + /// When ingesting articles, the feedURL must match a feed's `feedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key. + public var feedUrl: String? { + guard let id = entry.origin?.streamID else { + // At this point, check Feedly's API isn't glitching or the response has not changed structure. + assertionFailure("Entries need to be traceable to a feed or this entry will be dropped.") + return nil + } + return id + } + + /// Convoluted external URL logic "documented" here: + /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + public var externalUrl: String? { + let multidimensionalArrayOfLinks = [entry.canonical, entry.alternate] + let withExistingValues = multidimensionalArrayOfLinks.compactMap { $0 } + let flattened = withExistingValues.flatMap { $0 } + let webPageLinks = flattened.filter { $0.type == nil || $0.type == "text/html" } + return webPageLinks.first?.href + } + + public var title: String? { + return rightToLeftTextSantizer.sanitize(entry.title) + } + + public var contentHMTL: String? { + return entry.content?.content ?? entry.summary?.content + } + + public var contentText: String? { + // We could strip HTML from contentHTML? + return nil + } + + public var summary: String? { + return rightToLeftTextSantizer.sanitize(entry.summary?.content) + } + + public var datePublished: Date { + return entry.crawled + } + + public var dateModified: Date? { + return entry.recrawled + } + + public var authors: Set? { + guard let name = entry.author else { + return nil + } + return Set([ParsedAuthor(name: name, url: nil, avatarURL: nil, emailAddress: nil)]) + } + + /// While there is not yet a tagging interface, articles can still be searched for by tags. + public var tags: Set? { + guard let labels = entry.tags?.compactMap({ $0.label }), !labels.isEmpty else { + return nil + } + return Set(labels) + } + + public var attachments: Set? { + guard let enclosure = entry.enclosure, !enclosure.isEmpty else { + return nil + } + let attachments = enclosure.compactMap { ParsedAttachment(url: $0.href, mimeType: $0.type, title: nil, sizeInBytes: nil, durationInSeconds: nil) } + return attachments.isEmpty ? nil : Set(attachments) + } + + public var parsedItemRepresentation: ParsedItem? { + guard let feedUrl = feedUrl else { + return nil + } + + return ParsedItem(syncServiceID: id, + uniqueID: id, // This value seems to get ignored or replaced. + feedURL: feedUrl, + url: nil, + externalURL: externalUrl, + title: title, + language: nil, + contentHTML: contentHMTL, + contentText: contentText, + summary: summary, + imageURL: nil, + bannerImageURL: nil, + datePublished: datePublished, + dateModified: dateModified, + authors: authors, + tags: tags, + attachments: attachments) + } +} + +public struct FeedlyFeed: Codable, Sendable { + + public let id: String + public let title: String? + public let updated: Date? + public let website: String? +} + +public struct FeedlyFeedParser: Sendable { + + public let feed: FeedlyFeed + + private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() + + public var title: String? { + return rightToLeftTextSantizer.sanitize(feed.title) ?? "" + } + + public var feedID: String { + return feed.id + } + + public var url: String { + let resource = FeedlyFeedResourceID(id: feed.id) + return resource.url + } + + public var homePageURL: String? { + return feed.website + } + + public init(feed: FeedlyFeed) { + + self.feed = feed + } +} + +public struct FeedlyFeedsSearchResponse: Decodable, Sendable { + + public struct Feed: Decodable, Sendable { + + public let title: String + public let feedId: String + } + + public let results: [Feed] +} + +public struct FeedlyLink: Decodable, Sendable, Equatable { + + public let href: String + + /// The mime type of the resource located by `href`. + /// When `nil`, it's probably a web page? + /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ + public let type: String? + + public static func ==(lhs: FeedlyLink, rhs: FeedlyLink) -> Bool { + lhs.href == rhs.href && lhs.type == rhs.type + } +} + +public struct FeedlyOrigin: Decodable, Sendable, Equatable { + + public let title: String? + public let streamID: String? + public let htmlURL: String? + + public static func ==(lhs: FeedlyOrigin, rhs: FeedlyOrigin) -> Bool { + + lhs.title == rhs.title && lhs.streamID == rhs.streamID && lhs.htmlURL == rhs.htmlURL + } +} + +/// The kinds of Resource IDs is documented here: https://developer.feedly.com/cloud/ +public protocol FeedlyResourceID { + + /// The resource ID from Feedly. + @MainActor var id: String { get } +} + +/// The Feed Resource is documented here: https://developer.feedly.com/cloud/ +public struct FeedlyFeedResourceID: FeedlyResourceID, Sendable { + + public let id: String + + /// The location of the kind of resource a concrete type represents. + /// If the concrete type cannot strip the resource type from the ID, it should just return the ID + /// since the ID is a legitimate URL. + /// This is basically assuming Feedly prefixes source feed URLs with `feed/`. + /// It is not documented as such and could potentially change. + /// Feedly does not include the source feed URL as a separate field. + /// See https://developer.feedly.com/v3/feeds/#get-the-metadata-about-a-specific-feed + public var url: String { + if let range = id.range(of: "feed/"), range.lowerBound == id.startIndex { + var mutant = id + mutant.removeSubrange(range) + return mutant + } + + // It seems values like "something/https://my.blog/posts.xml" is a legit URL. + return id + } + + public init(id: String) { + self.id = id + } +} + +extension FeedlyFeedResourceID { + + init(url: String) { + self.id = "feed/\(url)" + } +} + +public struct FeedlyCategoryResourceID: FeedlyResourceID, Sendable { + + public let id: String + + public enum Global { + + public static func uncategorized(for userID: String) -> FeedlyCategoryResourceID { + // https://developer.feedly.com/cloud/#global-resource-ids + let id = "user/\(userID)/category/global.uncategorized" + return FeedlyCategoryResourceID(id: id) + } + + /// All articles from all the feeds the user subscribes to. + public static func all(for userID: String) -> FeedlyCategoryResourceID { + // https://developer.feedly.com/cloud/#global-resource-ids + let id = "user/\(userID)/category/global.all" + return FeedlyCategoryResourceID(id: id) + } + + /// All articles from all the feeds the user loves most. + public static func mustRead(for userID: String) -> FeedlyCategoryResourceID { + // https://developer.feedly.com/cloud/#global-resource-ids + let id = "user/\(userID)/category/global.must" + return FeedlyCategoryResourceID(id: id) + } + } +} + +public struct FeedlyTagResourceID: FeedlyResourceID, Sendable { + + public let id: String + + public enum Global { + + public static func saved(for userID: String) -> FeedlyTagResourceID { + // https://developer.feedly.com/cloud/#global-resource-ids + let id = "user/\(userID)/tag/global.saved" + return FeedlyTagResourceID(id: id) + } + } +} + +public struct FeedlyRTLTextSanitizer: Sendable { + + private let rightToLeftPrefix = "
" + private let rightToLeftSuffix = "
" + + public func sanitize(_ sourceText: String?) -> String? { + guard let source = sourceText, !source.isEmpty else { + return sourceText + } + + guard source.hasPrefix(rightToLeftPrefix) && source.hasSuffix(rightToLeftSuffix) else { + return source + } + + let start = source.index(source.startIndex, offsetBy: rightToLeftPrefix.indices.count) + let end = source.index(source.endIndex, offsetBy: -rightToLeftSuffix.indices.count) + return String(source[start.. Bool { + lhs.id == rhs.id && lhs.label == rhs.label + } +} diff --git a/Feedly/Sources/Feedly/Models/FeedlyCategory.swift b/Feedly/Sources/Feedly/Models/FeedlyCategory.swift deleted file mode 100644 index 72abb7d62..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyCategory.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// FeedlyCategory.swift -// Account -// -// Created by Kiel Gillard on 19/9/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyCategory: Decodable, Sendable, Equatable { - - public let label: String - public let id: String - - public static func ==(lhs: FeedlyCategory, rhs: FeedlyCategory) -> Bool { - lhs.label == rhs.label && lhs.id == rhs.id - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyCollection.swift b/Feedly/Sources/Feedly/Models/FeedlyCollection.swift deleted file mode 100644 index 145a5f936..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyCollection.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// FeedlyCollection.swift -// Account -// -// Created by Kiel Gillard on 19/9/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyCollection: Codable, Sendable { - - public let feeds: [FeedlyFeed] - public let label: String - public let id: String -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyCollectionParser.swift b/Feedly/Sources/Feedly/Models/FeedlyCollectionParser.swift deleted file mode 100644 index 693de807a..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyCollectionParser.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// FeedlyCollectionParser.swift -// Account -// -// Created by Kiel Gillard on 28/1/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyCollectionParser: Sendable { - - public let collection: FeedlyCollection - - private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() - - public var folderName: String { - return rightToLeftTextSantizer.sanitize(collection.label) ?? "" - } - - public var externalID: String { - return collection.id - } - - public init(collection: FeedlyCollection) { - self.collection = collection - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyEntry.swift b/Feedly/Sources/Feedly/Models/FeedlyEntry.swift deleted file mode 100644 index 933886992..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyEntry.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// FeedlyEntry.swift -// Account -// -// Created by Kiel Gillard on 19/9/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyEntry: Decodable, Sendable, Hashable { - - /// the unique, immutable ID for this particular article. - public let id: String - - /// the article’s title. This string does not contain any HTML markup. - public let title: String? - - public struct Content: Decodable, Sendable, Equatable { - - public enum Direction: String, Decodable, Sendable { - case leftToRight = "ltr" - case rightToLeft = "rtl" - } - - public let content: String? - public let direction: Direction? - - public static func ==(lhs: Content, rhs: Content) -> Bool { - lhs.content == rhs.content && lhs.direction == rhs.direction - } - } - - /// This object typically has two values: “content” for the content itself, and “direction” (“ltr” for left-to-right, “rtl” for right-to-left). The content itself contains sanitized HTML markup. - public let content: Content? - - /// content object the article summary. See the content object above. - public let summary: Content? - - /// the author’s name - public let author: String? - - /// the immutable timestamp, in ms, when this article was processed by the feedly Cloud servers. - public let crawled: Date - - /// the timestamp, in ms, when this article was re-processed and updated by the feedly Cloud servers. - public let recrawled: Date? - - /// the feed from which this article was crawled. If present, “streamId” will contain the feed id, “title” will contain the feed title, and “htmlUrl” will contain the feed’s website. - public let origin: FeedlyOrigin? - - /// Used to help find the URL to visit an article on a web site. - /// See https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ - public let canonical: [FeedlyLink]? - - /// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page. - public let alternate: [FeedlyLink]? - - /// Was this entry read by the user? If an Authorization header is not provided, this will always return false. If an Authorization header is provided, it will reflect if the user has read this entry or not. - public let unread: Bool - - /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present. - public let tags: [FeedlyTag]? - - /// a list of category objects (“id” and “label”) that the user associated with the feed of this entry. This value is only returned if an Authorization header is provided. - public let categories: [FeedlyCategory]? - - /// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links. - public let enclosure: [FeedlyLink]? - - public func hash(into hasher: inout Hasher) { - hasher.combine(id) - } - - public static func ==(lhs: FeedlyEntry, rhs: FeedlyEntry) -> Bool { - - lhs.id == rhs.id && lhs.title == rhs.title && lhs.content == rhs.content && lhs.summary == rhs.summary && lhs.author == rhs.author && lhs.crawled == rhs.crawled && lhs.recrawled == rhs.recrawled && lhs.origin == rhs.origin && lhs.canonical == rhs.canonical && lhs.alternate == rhs.alternate && lhs.unread == rhs.unread && lhs.tags == rhs.tags && lhs.categories == rhs.categories && lhs.enclosure == rhs.enclosure - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyEntryIdentifierProviding.swift b/Feedly/Sources/Feedly/Models/FeedlyEntryIdentifierProviding.swift deleted file mode 100644 index 06a87a2d0..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyEntryIdentifierProviding.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// FeedlyEntryIdentifierProviding.swift -// Account -// -// Created by Kiel Gillard on 9/1/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public protocol FeedlyEntryIdentifierProviding: AnyObject { - @MainActor var entryIDs: Set { get } -} - -public final class FeedlyEntryIdentifierProvider: FeedlyEntryIdentifierProviding { - - private (set) public var entryIDs: Set - - public init(entryIDs: Set = Set()) { - self.entryIDs = entryIDs - } - - @MainActor public func addEntryIDs(from provider: FeedlyEntryIdentifierProviding) { - entryIDs.formUnion(provider.entryIDs) - } - - @MainActor public func addEntryIDs(in articleIDs: [String]) { - entryIDs.formUnion(articleIDs) - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyEntryParser.swift b/Feedly/Sources/Feedly/Models/FeedlyEntryParser.swift deleted file mode 100644 index f7621b9e3..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyEntryParser.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// FeedlyEntryParser.swift -// Account -// -// Created by Kiel Gillard on 3/10/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -import Articles -import Parser - -public struct FeedlyEntryParser: Sendable { - - public let entry: FeedlyEntry - - private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() - - public var id: String { - return entry.id - } - - /// When ingesting articles, the feedURL must match a feed's `feedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key. - public var feedUrl: String? { - guard let id = entry.origin?.streamID else { - // At this point, check Feedly's API isn't glitching or the response has not changed structure. - assertionFailure("Entries need to be traceable to a feed or this entry will be dropped.") - return nil - } - return id - } - - /// Convoluted external URL logic "documented" here: - /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ - public var externalUrl: String? { - let multidimensionalArrayOfLinks = [entry.canonical, entry.alternate] - let withExistingValues = multidimensionalArrayOfLinks.compactMap { $0 } - let flattened = withExistingValues.flatMap { $0 } - let webPageLinks = flattened.filter { $0.type == nil || $0.type == "text/html" } - return webPageLinks.first?.href - } - - public var title: String? { - return rightToLeftTextSantizer.sanitize(entry.title) - } - - public var contentHMTL: String? { - return entry.content?.content ?? entry.summary?.content - } - - public var contentText: String? { - // We could strip HTML from contentHTML? - return nil - } - - public var summary: String? { - return rightToLeftTextSantizer.sanitize(entry.summary?.content) - } - - public var datePublished: Date { - return entry.crawled - } - - public var dateModified: Date? { - return entry.recrawled - } - - public var authors: Set? { - guard let name = entry.author else { - return nil - } - return Set([ParsedAuthor(name: name, url: nil, avatarURL: nil, emailAddress: nil)]) - } - - /// While there is not yet a tagging interface, articles can still be searched for by tags. - public var tags: Set? { - guard let labels = entry.tags?.compactMap({ $0.label }), !labels.isEmpty else { - return nil - } - return Set(labels) - } - - public var attachments: Set? { - guard let enclosure = entry.enclosure, !enclosure.isEmpty else { - return nil - } - let attachments = enclosure.compactMap { ParsedAttachment(url: $0.href, mimeType: $0.type, title: nil, sizeInBytes: nil, durationInSeconds: nil) } - return attachments.isEmpty ? nil : Set(attachments) - } - - public var parsedItemRepresentation: ParsedItem? { - guard let feedUrl = feedUrl else { - return nil - } - - return ParsedItem(syncServiceID: id, - uniqueID: id, // This value seems to get ignored or replaced. - feedURL: feedUrl, - url: nil, - externalURL: externalUrl, - title: title, - language: nil, - contentHTML: contentHMTL, - contentText: contentText, - summary: summary, - imageURL: nil, - bannerImageURL: nil, - datePublished: datePublished, - dateModified: dateModified, - authors: authors, - tags: tags, - attachments: attachments) - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyFeed.swift b/Feedly/Sources/Feedly/Models/FeedlyFeed.swift deleted file mode 100644 index 97fbda4d0..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyFeed.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// FeedlyFeed.swift -// Account -// -// Created by Kiel Gillard on 19/9/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyFeed: Codable, Sendable { - - public let id: String - public let title: String? - public let updated: Date? - public let website: String? -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyFeedParser.swift b/Feedly/Sources/Feedly/Models/FeedlyFeedParser.swift deleted file mode 100644 index 9f10640aa..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyFeedParser.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// FeedlyFeedParser.swift -// Account -// -// Created by Kiel Gillard on 29/1/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyFeedParser: Sendable { - - public let feed: FeedlyFeed - - private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() - - public var title: String? { - return rightToLeftTextSantizer.sanitize(feed.title) ?? "" - } - - public var feedID: String { - return feed.id - } - - public var url: String { - let resource = FeedlyFeedResourceID(id: feed.id) - return resource.url - } - - public var homePageURL: String? { - return feed.website - } - - public init(feed: FeedlyFeed) { - - self.feed = feed - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyFeedsSearchResponse.swift b/Feedly/Sources/Feedly/Models/FeedlyFeedsSearchResponse.swift deleted file mode 100644 index baa9f916c..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyFeedsSearchResponse.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// FeedlyFeedsSearchResponse.swift -// Account -// -// Created by Kiel Gillard on 1/12/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyFeedsSearchResponse: Decodable, Sendable { - - public struct Feed: Decodable, Sendable { - - public let title: String - public let feedId: String - } - - public let results: [Feed] -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyLink.swift b/Feedly/Sources/Feedly/Models/FeedlyLink.swift deleted file mode 100644 index 49337ca53..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyLink.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// FeedlyLink.swift -// Account -// -// Created by Kiel Gillard on 3/10/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyLink: Decodable, Sendable, Equatable { - - public let href: String - - /// The mime type of the resource located by `href`. - /// When `nil`, it's probably a web page? - /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ - public let type: String? - - public static func ==(lhs: FeedlyLink, rhs: FeedlyLink) -> Bool { - lhs.href == rhs.href && lhs.type == rhs.type - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyOrigin.swift b/Feedly/Sources/Feedly/Models/FeedlyOrigin.swift deleted file mode 100644 index fe02e5e11..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyOrigin.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// FeedlyOrigin.swift -// Account -// -// Created by Kiel Gillard on 19/9/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyOrigin: Decodable, Sendable, Equatable { - - public let title: String? - public let streamID: String? - public let htmlURL: String? - - public static func ==(lhs: FeedlyOrigin, rhs: FeedlyOrigin) -> Bool { - - lhs.title == rhs.title && lhs.streamID == rhs.streamID && lhs.htmlURL == rhs.htmlURL - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyRTLTextSanitizer.swift b/Feedly/Sources/Feedly/Models/FeedlyRTLTextSanitizer.swift deleted file mode 100644 index 87a44d012..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyRTLTextSanitizer.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// FeedlyRTLTextSanitizer.swift -// Account -// -// Created by Kiel Gillard on 28/1/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyRTLTextSanitizer: Sendable { - - private let rightToLeftPrefix = "
" - private let rightToLeftSuffix = "
" - - public func sanitize(_ sourceText: String?) -> String? { - guard let source = sourceText, !source.isEmpty else { - return sourceText - } - - guard source.hasPrefix(rightToLeftPrefix) && source.hasSuffix(rightToLeftSuffix) else { - return source - } - - let start = source.index(source.startIndex, offsetBy: rightToLeftPrefix.indices.count) - let end = source.index(source.endIndex, offsetBy: -rightToLeftSuffix.indices.count) - return String(source[start.. FeedlyCategoryResourceID { - // https://developer.feedly.com/cloud/#global-resource-ids - let id = "user/\(userID)/category/global.uncategorized" - return FeedlyCategoryResourceID(id: id) - } - - /// All articles from all the feeds the user subscribes to. - public static func all(for userID: String) -> FeedlyCategoryResourceID { - // https://developer.feedly.com/cloud/#global-resource-ids - let id = "user/\(userID)/category/global.all" - return FeedlyCategoryResourceID(id: id) - } - - /// All articles from all the feeds the user loves most. - public static func mustRead(for userID: String) -> FeedlyCategoryResourceID { - // https://developer.feedly.com/cloud/#global-resource-ids - let id = "user/\(userID)/category/global.must" - return FeedlyCategoryResourceID(id: id) - } - } -} - -public struct FeedlyTagResourceID: FeedlyResourceID, Sendable { - - public let id: String - - public enum Global { - - public static func saved(for userID: String) -> FeedlyTagResourceID { - // https://developer.feedly.com/cloud/#global-resource-ids - let id = "user/\(userID)/tag/global.saved" - return FeedlyTagResourceID(id: id) - } - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyStream.swift b/Feedly/Sources/Feedly/Models/FeedlyStream.swift deleted file mode 100644 index d447c5959..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyStream.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// FeedlyStream.swift -// Account -// -// Created by Kiel Gillard on 19/9/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyStream: Decodable, Sendable { - - public let id: String - - /// Of the most recent entry for this stream (regardless of continuation, newerThan, etc). - public let updated: Date? - - /// the continuation id to pass to the next stream call, for pagination. - /// This id guarantees that no entry will be duplicated in a stream (meaning, there is no need to de-duplicate entries returned by this call). - /// If this value is not returned, it means the end of the stream has been reached. - public let continuation: String? - public let items: [FeedlyEntry] - - public var isStreamEnd: Bool { - return continuation == nil - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyStreamIDs.swift b/Feedly/Sources/Feedly/Models/FeedlyStreamIDs.swift deleted file mode 100644 index 7f28b2805..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyStreamIDs.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// FeedlyStreamIDs.swift -// Account -// -// Created by Kiel Gillard on 18/10/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyStreamIDs: Decodable, Sendable { - - public let continuation: String? - public let ids: [String] - - public var isStreamEnd: Bool { - return continuation == nil - } -} diff --git a/Feedly/Sources/Feedly/Models/FeedlyTag.swift b/Feedly/Sources/Feedly/Models/FeedlyTag.swift deleted file mode 100644 index 20cb3463b..000000000 --- a/Feedly/Sources/Feedly/Models/FeedlyTag.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// FeedlyTag.swift -// Account -// -// Created by Kiel Gillard on 3/10/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public struct FeedlyTag: Decodable, Sendable, Equatable { - - public let id: String - public let label: String? - - public static func ==(lhs: FeedlyTag, rhs: FeedlyTag) -> Bool { - lhs.id == rhs.id && lhs.label == rhs.label - } -}