From e8a2b541845ffd042c33a99d41cd31a31142f090 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 4 May 2020 14:42:34 -0500 Subject: [PATCH] Add support for basic Reddit articles --- .../Account/Account.xcodeproj/project.pbxproj | 12 +- .../Reddit/RedditFeedProvider.swift | 108 +++++++++--------- .../FeedProvider/Reddit/RedditLink.swift | 71 ++++++++++++ .../Reddit/RedditLinkListing.swift | 31 +++++ .../FeedProvider/Reddit/RedditListing.swift | 19 --- 5 files changed, 166 insertions(+), 75 deletions(-) create mode 100644 Frameworks/Account/FeedProvider/Reddit/RedditLink.swift create mode 100644 Frameworks/Account/FeedProvider/Reddit/RedditLinkListing.swift delete mode 100644 Frameworks/Account/FeedProvider/Reddit/RedditListing.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 808d28db7..34f422214 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -44,7 +44,8 @@ 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; - 5133BB47245FD8140001E3D0 /* RedditListing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB46245FD8140001E3D0 /* RedditListing.swift */; }; + 5133BB47245FD8140001E3D0 /* RedditLinkListing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB46245FD8140001E3D0 /* RedditLinkListing.swift */; }; + 5133BB49246078910001E3D0 /* RedditLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB48246078910001E3D0 /* RedditLink.swift */; }; 5139A6382459822D004D960C /* CloudKitArticleStatusUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5139A6372459822D004D960C /* CloudKitArticleStatusUpdate.swift */; }; 5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; }; 5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; }; @@ -301,7 +302,8 @@ 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; - 5133BB46245FD8140001E3D0 /* RedditListing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditListing.swift; sourceTree = ""; }; + 5133BB46245FD8140001E3D0 /* RedditLinkListing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditLinkListing.swift; sourceTree = ""; }; + 5133BB48246078910001E3D0 /* RedditLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditLink.swift; sourceTree = ""; }; 5139A6372459822D004D960C /* CloudKitArticleStatusUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitArticleStatusUpdate.swift; sourceTree = ""; }; 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = ""; }; 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = ""; }; @@ -635,7 +637,8 @@ isa = PBXGroup; children = ( 5193CD53245E3F7A0092735E /* RedditFeedProvider.swift */, - 5133BB46245FD8140001E3D0 /* RedditListing.swift */, + 5133BB48246078910001E3D0 /* RedditLink.swift */, + 5133BB46245FD8140001E3D0 /* RedditLinkListing.swift */, 5193CD80245F295E0092735E /* RedditUser.swift */, ); path = Reddit; @@ -1207,7 +1210,7 @@ 51F6C593245DBA8E001E41CA /* CloudKitRemoteNotificationOperation.swift in Sources */, 846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */, 51E490362288C37100C791F0 /* FeedbinDate.swift in Sources */, - 5133BB47245FD8140001E3D0 /* RedditListing.swift in Sources */, + 5133BB47245FD8140001E3D0 /* RedditLinkListing.swift in Sources */, 9EEAE06E235D002D00E3FEE4 /* FeedlyGetCollectionsService.swift in Sources */, 5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */, 9E85C8ED2367020700D0F1F7 /* FeedlyGetEntriesService.swift in Sources */, @@ -1277,6 +1280,7 @@ 9E964EB823754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift in Sources */, 9EF1B10923590E93000A486A /* FeedlyStreamIds.swift in Sources */, 84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */, + 5133BB49246078910001E3D0 /* RedditLink.swift in Sources */, 84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */, 5165D72A22835F7D00D9D53D /* HTMLFeedFinder.swift in Sources */, 841974011F6DD1EC006346C4 /* Folder.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift b/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift index 5e6f9d09c..4b423db60 100644 --- a/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift @@ -80,6 +80,7 @@ public final class RedditFeedProvider: FeedProvider { } public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { + // TODO: call https://www.reddit.com/user//about.json to get the key "icon_img" completion(.failure(RedditFeedProviderError.unknown)) } @@ -107,7 +108,13 @@ public final class RedditFeedProvider: FeedProvider { } let api = urlComponents.path retrieveListing(api: api, parameters: [:]) { result in - completion(.success(Set())) + switch result { + case .success(let linkListing): + let parsedItems = self.makeParsedItems(webFeed.url, linkListing) + completion(.success(parsedItems)) + case .failure(let error): + completion(.failure(error)) + } } } @@ -194,7 +201,7 @@ private extension RedditFeedProvider { } } - func retrieveListing(api: String, parameters: [String: Any], completion: @escaping (Result) -> Void) { + func retrieveListing(api: String, parameters: [String: Any], completion: @escaping (Result) -> Void) { guard let client = client else { completion(.failure(RedditFeedProviderError.unknown)) return @@ -214,21 +221,15 @@ private extension RedditFeedProvider { print("******** writing to: \(url.path)") try? jsonString?.write(toFile: url.path, atomically: true, encoding: .utf8) -// let decoder = JSONDecoder() -// let dateFormatter = DateFormatter() -// dateFormatter.dateFormat = Self.dateFormat -// decoder.dateDecodingStrategy = .formatted(dateFormatter) + let decoder = JSONDecoder() -// do { -// let listing = try decoder.decode(RedditListing.self, from: response.data) -// completion(.success(listing)) -// } catch { -// completion(.failure(error)) -// } + do { + let listing = try decoder.decode(RedditLinkListing.self, from: response.data) + completion(.success(listing)) + } catch { + completion(.failure(error)) + } - let listing = RedditListing(name: "") - completion(.success(listing)) - case .failure(let oathError): self.handleFailure(error: oathError) { error in if let error = error { @@ -241,43 +242,46 @@ private extension RedditFeedProvider { } } -// func makeParsedItems(_ webFeedURL: String, _ statuses: [TwitterStatus]) -> Set { -// var parsedItems = Set() -// -// for status in statuses { -// guard let idStr = status.idStr, let statusURL = status.url else { continue } -// -// let parsedItem = ParsedItem(syncServiceID: nil, -// uniqueID: idStr, -// feedURL: webFeedURL, -// url: statusURL, -// externalURL: nil, -// title: nil, -// language: nil, -// contentHTML: status.renderAsHTML(), -// contentText: status.renderAsText(), -// summary: nil, -// imageURL: nil, -// bannerImageURL: nil, -// datePublished: status.createdAt, -// dateModified: nil, -// authors: makeParsedAuthors(status.user), -// tags: nil, -// attachments: nil) -// parsedItems.insert(parsedItem) -// } -// -// return parsedItems -// } -// -// func makeUserURL(_ screenName: String) -> String { -// return "https://twitter.com/\(screenName)" -// } -// -// func makeParsedAuthors(_ user: TwitterUser?) -> Set? { -// guard let user = user else { return nil } -// return Set([ParsedAuthor(name: user.name, url: user.url, avatarURL: user.avatarURL, emailAddress: nil)]) -// } + func makeParsedItems(_ webFeedURL: String, _ linkListing: RedditLinkListing) -> Set { + var parsedItems = Set() + + guard let linkDatas = linkListing.data?.children?.compactMap({ $0.data }), !linkDatas.isEmpty else { + return parsedItems + } + + for linkData in linkDatas { + guard let permalink = linkData.permalink else { continue } + + let parsedItem = ParsedItem(syncServiceID: nil, + uniqueID: permalink, + feedURL: webFeedURL, + url: "https://www.reddit.com\(permalink)", + externalURL: linkData.url, + title: linkData.title, + language: nil, + contentHTML: linkData.renderAsHTML(), + contentText: linkData.selfText, + summary: nil, + imageURL: nil, + bannerImageURL: nil, + datePublished: linkData.createdDate, + dateModified: nil, + authors: makeParsedAuthors(linkData.author), + tags: nil, + attachments: nil) + parsedItems.insert(parsedItem) + } + + return parsedItems + } + + func makeParsedAuthors(_ username: String?) -> Set? { + guard let username = username else { return nil } + var urlComponents = URLComponents(string: "https://www.reddit.com") + urlComponents?.path = "/u/\(username)" + let userURL = urlComponents?.url?.absoluteString + return Set([ParsedAuthor(name: "u/\(username)", url: userURL, avatarURL: userURL, emailAddress: nil)]) + } func handleFailure(error: OAuthSwiftError, completion: @escaping (Error?) -> Void) { if case .tokenExpired = error { diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditLink.swift b/Frameworks/Account/FeedProvider/Reddit/RedditLink.swift new file mode 100644 index 000000000..b469a5648 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Reddit/RedditLink.swift @@ -0,0 +1,71 @@ +// +// RedditLink.swift +// Account +// +// Created by Maurice Parker on 5/4/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct RedditLink: Codable { + + let kind: String? + let data: RedditLinkData? + + enum CodingKeys: String, CodingKey { + case kind = "kind" + case data = "data" + } + +} + +struct RedditLinkData: Codable { + + let title: String? + let permalink: String? + let url: String? + let id: String? + let selfHTML: String? + let selfText: String? + let author: String? + let created: Double? + + enum CodingKeys: String, CodingKey { + case title = "title" + case permalink = "permalink" + case url = "url" + case id = "id" + case selfHTML = "selftext_html" + case selfText = "selftext" + case author = "author" + case created = "created_utc" + } + + var createdDate: Date? { + guard let created = created else { return nil } + return Date(timeIntervalSince1970: created) + } + + func renderAsHTML() -> String? { + var html = String() + if let selfHTML = selfHTML { + html.append(selfHTML) + } + if let urlHTML = renderURLAsHTML() { + html.append(urlHTML) + } + return html + } + + func renderURLAsHTML() -> String? { + guard let url = url else { return nil } + + guard url.hasSuffix(".jpg") || url.hasSuffix(".jpeg") || url.hasSuffix(".png") || url.hasSuffix(".gif") else { + return nil + } + + return "
" + } + +} diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditLinkListing.swift b/Frameworks/Account/FeedProvider/Reddit/RedditLinkListing.swift new file mode 100644 index 000000000..4acdbcf85 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Reddit/RedditLinkListing.swift @@ -0,0 +1,31 @@ +// +// RedditLinkListing.swift +// Account +// +// Created by Maurice Parker on 5/3/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct RedditLinkListing: Codable { + + let kind: String? + let data: RedditLinkListingData? + + enum CodingKeys: String, CodingKey { + case kind + case data + } + +} + +struct RedditLinkListingData: Codable { + + let children: [RedditLink]? + + enum CodingKeys: String, CodingKey { + case children = "children" + } + +} diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditListing.swift b/Frameworks/Account/FeedProvider/Reddit/RedditListing.swift deleted file mode 100644 index e7861d754..000000000 --- a/Frameworks/Account/FeedProvider/Reddit/RedditListing.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// RedditListing.swift -// Account -// -// Created by Maurice Parker on 5/3/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -struct RedditListing: Codable { - - let name: String? - - enum CodingKeys: String, CodingKey { - case name = "name" - } - -}