From 143c102454627a21066d7ac9a37ebb0ae20346cc Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Mon, 4 May 2020 18:13:15 -0500 Subject: [PATCH] Add support for Reddit feed icons --- .../Account/Account.xcodeproj/project.pbxproj | 8 ++-- .../Reddit/RedditFeedProvider.swift | 41 ++++++++++++++++--- .../{RedditUser.swift => RedditMe.swift} | 8 ++-- .../FeedProvider/Reddit/RedditSubreddit.swift | 24 ++++++++++- 4 files changed, 66 insertions(+), 15 deletions(-) rename Frameworks/Account/FeedProvider/Reddit/{RedditUser.swift => RedditMe.swift} (67%) diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 827baf242..a93ad8e0a 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 5133BB47245FD8140001E3D0 /* RedditLinkListing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB46245FD8140001E3D0 /* RedditLinkListing.swift */; }; 5133BB49246078910001E3D0 /* RedditLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB48246078910001E3D0 /* RedditLink.swift */; }; 5133BB4B2460BDF30001E3D0 /* RedditSubreddit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB4A2460BDF30001E3D0 /* RedditSubreddit.swift */; }; + 5133BB4D2460CA0B0001E3D0 /* RedditMe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133BB4C2460CA0B0001E3D0 /* RedditMe.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 */; }; @@ -65,7 +66,6 @@ 516896352448EBEA00185AC5 /* FeedProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516896342448EBEA00185AC5 /* FeedProviderManager.swift */; }; 5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; }; 5193CD54245E3F7A0092735E /* RedditFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD53245E3F7A0092735E /* RedditFeedProvider.swift */; }; - 5193CD81245F295E0092735E /* RedditUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD80245F295E0092735E /* RedditUser.swift */; }; 519E84A62433D49000D238B0 /* OPMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */; }; 519E84A82434C5EF00D238B0 /* CloudKitArticlesZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */; }; 519E84AC2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */; }; @@ -306,6 +306,7 @@ 5133BB46245FD8140001E3D0 /* RedditLinkListing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditLinkListing.swift; sourceTree = ""; }; 5133BB48246078910001E3D0 /* RedditLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditLink.swift; sourceTree = ""; }; 5133BB4A2460BDF30001E3D0 /* RedditSubreddit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditSubreddit.swift; sourceTree = ""; }; + 5133BB4C2460CA0B0001E3D0 /* RedditMe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditMe.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 = ""; }; @@ -324,7 +325,6 @@ 516896342448EBEA00185AC5 /* FeedProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedProviderManager.swift; sourceTree = ""; }; 5170743B232AEDB500A461A3 /* OPMLFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLFile.swift; sourceTree = ""; }; 5193CD53245E3F7A0092735E /* RedditFeedProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditFeedProvider.swift; sourceTree = ""; }; - 5193CD80245F295E0092735E /* RedditUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedditUser.swift; sourceTree = ""; }; 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLNormalizer.swift; sourceTree = ""; }; 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZone.swift; sourceTree = ""; }; 519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZoneDelegate.swift; sourceTree = ""; }; @@ -642,7 +642,7 @@ 5133BB48246078910001E3D0 /* RedditLink.swift */, 5133BB46245FD8140001E3D0 /* RedditLinkListing.swift */, 5133BB4A2460BDF30001E3D0 /* RedditSubreddit.swift */, - 5193CD80245F295E0092735E /* RedditUser.swift */, + 5133BB4C2460CA0B0001E3D0 /* RedditMe.swift */, ); path = Reddit; sourceTree = ""; @@ -1185,6 +1185,7 @@ 9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */, 9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */, 51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */, + 5133BB4D2460CA0B0001E3D0 /* RedditMe.swift in Sources */, 846E77451F6EF9B900A165E2 /* Container.swift in Sources */, 9EA643D3239305680018A28C /* FeedlySearchOperation.swift in Sources */, 5150FFFE243823B800C1A442 /* CloudKitError.swift in Sources */, @@ -1229,7 +1230,6 @@ 9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */, 9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */, 3B826DAB2385C81C00FC1ADB /* FeedWranglerConfig.swift in Sources */, - 5193CD81245F295E0092735E /* RedditUser.swift in Sources */, 515E4EB62324FF8C0057B0E7 /* URLRequest+RSWeb.swift in Sources */, 51B36315244BCCA4000DEF2A /* TwitterSearchResult.swift in Sources */, 9EB1D576238E6A3900A753D7 /* FeedlyAddNewFeedOperation.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift b/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift index 41fbd1808..3456ffa60 100644 --- a/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Reddit/RedditFeedProvider.swift @@ -80,8 +80,25 @@ 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)) + guard urlComponents.path.hasPrefix("/r/"), let secondElement = extractSecondElement(path: urlComponents.path) else { + completion(.failure(RedditFeedProviderError.unknown)) + return + } + + let api = "/r/\(secondElement)/about.json" + + fetch(api: api, parameters: [:], resultType: RedditSubreddit.self) { result in + switch result { + case .success(let subreddit): + if let iconURL = subreddit.data?.iconURL, !iconURL.isEmpty { + completion(.success(iconURL)) + } else { + completion(.failure(RedditFeedProviderError.unknown)) + } + case .failure(let error): + completion(.failure(error)) + } + } } public func assignName(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { @@ -106,7 +123,9 @@ public final class RedditFeedProvider: FeedProvider { completion(.failure(TwitterFeedProviderError.unknown)) return } + let api = "\(urlComponents.path).json" + fetch(api: api, parameters: [:], resultType: RedditLinkListing.self) { result in switch result { case .success(let linkListing): @@ -116,7 +135,6 @@ public final class RedditFeedProvider: FeedProvider { completion(.failure(error)) } } - } public static func create(tokenSuccess: OAuthSwift.TokenSuccess, completion: @escaping (Result) -> Void) { @@ -124,7 +142,7 @@ public final class RedditFeedProvider: FeedProvider { let oauthRefreshToken = tokenSuccess.credential.oauthRefreshToken let redditFeedProvider = RedditFeedProvider(oauthToken: oauthToken, oauthRefreshToken: oauthRefreshToken) - redditFeedProvider.fetch(api: "/api/v1/me", resultType: RedditUser.self) { result in + redditFeedProvider.fetch(api: "/api/v1/me", resultType: RedditMe.self) { result in switch result { case .success(let user): guard let username = user.name else { @@ -146,7 +164,6 @@ public final class RedditFeedProvider: FeedProvider { } } - } // MARK: OAuth1SwiftProvider @@ -264,7 +281,7 @@ private extension RedditFeedProvider { 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)]) + return Set([ParsedAuthor(name: "u/\(username)", url: userURL, avatarURL: nil, emailAddress: nil)]) } func handleFailure(error: OAuthSwiftError, completion: @escaping (Error?) -> Void) { @@ -299,10 +316,22 @@ private extension RedditFeedProvider { } } + func extractSecondElement(path: String) -> String? { + let scanner = Scanner(string: path) + if let _ = scanner.scanString("/"), + let _ = scanner.scanUpToString("/"), + let _ = scanner.scanString("/"), + let secondElement = scanner.scanUpToString("/") { + return secondElement + } + return nil + } + static func storeCredentials(username: String, oauthToken: String, oauthRefreshToken: String) throws { let tokenCredentials = Credentials(type: .oauthAccessToken, username: username, secret: oauthToken) try CredentialsManager.storeCredentials(tokenCredentials, server: Self.server) let tokenSecretCredentials = Credentials(type: .oauthRefreshToken, username: username, secret: oauthRefreshToken) try CredentialsManager.storeCredentials(tokenSecretCredentials, server: Self.server) } + } diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditUser.swift b/Frameworks/Account/FeedProvider/Reddit/RedditMe.swift similarity index 67% rename from Frameworks/Account/FeedProvider/Reddit/RedditUser.swift rename to Frameworks/Account/FeedProvider/Reddit/RedditMe.swift index 5488eb96c..5697d0b02 100644 --- a/Frameworks/Account/FeedProvider/Reddit/RedditUser.swift +++ b/Frameworks/Account/FeedProvider/Reddit/RedditMe.swift @@ -1,17 +1,17 @@ // -// RedditUser.swift +// RedditMe.swift // Account // -// Created by Maurice Parker on 5/3/20. +// Created by Maurice Parker on 5/4/20. // Copyright © 2020 Ranchero Software, LLC. All rights reserved. // import Foundation -struct RedditUser: Codable { +struct RedditMe: Codable { let name: String? - + enum CodingKeys: String, CodingKey { case name = "name" } diff --git a/Frameworks/Account/FeedProvider/Reddit/RedditSubreddit.swift b/Frameworks/Account/FeedProvider/Reddit/RedditSubreddit.swift index d38e845b6..8c9c50e11 100644 --- a/Frameworks/Account/FeedProvider/Reddit/RedditSubreddit.swift +++ b/Frameworks/Account/FeedProvider/Reddit/RedditSubreddit.swift @@ -10,10 +10,32 @@ import Foundation struct RedditSubreddit: Codable { + let kind: String? + let data: RedditSubredditData? + + enum CodingKeys: String, CodingKey { + case kind = "kind" + case data = "data" + } + +} + +struct RedditSubredditData: Codable { + let iconImg: String? + let communityIcon: String? enum CodingKeys: String, CodingKey { - case iconImg = "iconImg" + case iconImg = "icon_img" + case communityIcon = "community_icon" + } + + var iconURL: String? { + if let communityIcon = communityIcon, !communityIcon.isEmpty { + return communityIcon + } else { + return iconImg + } } }