diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 7eea1c7c3..450707e1c 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 5132AAC42448BAD90077840A /* FeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC12448BAD90077840A /* FeedProvider.swift */; }; 5132AAC52448BAD90077840A /* TwitterFeedProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */; }; 5132DE812449159100806ADE /* TwitterUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE802449159100806ADE /* TwitterUser.swift */; }; + 5132DE832449306F00806ADE /* Tweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5132DE822449306F00806ADE /* Tweet.swift */; }; 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 */; }; @@ -278,6 +279,7 @@ 5132AAC12448BAD90077840A /* FeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedProvider.swift; sourceTree = ""; }; 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwitterFeedProvider.swift; sourceTree = ""; }; 5132DE802449159100806ADE /* TwitterUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterUser.swift; sourceTree = ""; }; + 5132DE822449306F00806ADE /* Tweet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tweet.swift; sourceTree = ""; }; 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 = ""; }; @@ -566,6 +568,7 @@ children = ( 5132AAC32448BAD90077840A /* TwitterFeedProvider.swift */, 5132DE802449159100806ADE /* TwitterUser.swift */, + 5132DE822449306F00806ADE /* Tweet.swift */, ); path = Twitter; sourceTree = ""; @@ -1127,6 +1130,7 @@ 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, 9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */, + 5132DE832449306F00806ADE /* Tweet.swift in Sources */, 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, diff --git a/Frameworks/Account/FeedProvider/Twitter/Tweet.swift b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift new file mode 100644 index 000000000..eb9bd50d5 --- /dev/null +++ b/Frameworks/Account/FeedProvider/Twitter/Tweet.swift @@ -0,0 +1,35 @@ +// +// Tweet.swift +// Account +// +// Created by Maurice Parker on 4/16/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct Tweet: Codable { + + let createdAt: Date? + let idStr: String? + let text: String? + let user: TwitterUser + let truncated: Bool + let extendedTweet: ExtendedTweet? + + enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case idStr = "id_str" + case text = "text" + case user = "user" + case truncated = "truncated" + case extendedTweet = "extended_tweet" + } + +} + +struct ExtendedTweet: Codable { + + let full_text: String? + +} diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index a5d326ceb..0e7538334 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -87,7 +87,7 @@ public struct TwitterFeedProvider: FeedProvider { public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result) -> Void) { if let screenName = deriveScreenName(urlComponents) { - fetchUser(screenName: screenName) { result in + retrieveUser(screenName: screenName) { result in switch result { case .success(let user): if let avatarURL = user.avatarURL { @@ -127,7 +127,7 @@ public struct TwitterFeedProvider: FeedProvider { default: if let screenName = deriveScreenName(urlComponents) { - fetchUser(screenName: screenName) { result in + retrieveUser(screenName: screenName) { result in switch result { case .success(let user): if let userName = user.name { @@ -149,7 +149,41 @@ public struct TwitterFeedProvider: FeedProvider { } public func refresh(_ webFeed: WebFeed, completion: @escaping (Result, Error>) -> Void) { - // TODO: Finish implementation + let api = "statuses/user_timeline.json" + + retrieveTweets(api: api) { result in + switch result { + case .success(let tweets): + + var parsedItems = Set() + for tweet in tweets { + guard let idStr = tweet.idStr, let userScreenName = tweet.user.screenName else { continue } + let parsedItem = ParsedItem(syncServiceID: idStr, + uniqueID: idStr, + feedURL: webFeed.url, + url: "https://twitter.com/\(userScreenName)/status/\(idStr)", + externalURL: nil, + title: nil, + language: nil, + contentHTML: tweet.text, + contentText: tweet.text, + summary: tweet.text, + imageURL: nil, + bannerImageURL: nil, + datePublished: tweet.createdAt, + dateModified: nil, + authors: nil, + tags: nil, + attachments: nil) + parsedItems.insert(parsedItem) + } + + completion(.success(parsedItems)) + + case .failure(let error): + completion(.failure(error)) + } + } } } @@ -185,7 +219,7 @@ private extension TwitterFeedProvider { } } - func fetchUser(screenName: String, completion: @escaping (Result) -> Void) { + func retrieveUser(screenName: String, completion: @escaping (Result) -> Void) { let url = "\(Self.apiBase)users/show.json" let parameters = ["screen_name": screenName] @@ -205,4 +239,27 @@ private extension TwitterFeedProvider { } } + func retrieveTweets(api: String, completion: @escaping (Result<[Tweet], Error>) -> Void) { + let url = "\(Self.apiBase)\(api)" + let parameters = [String: Any]() + + client.get(url, parameters: parameters) { result in + switch result { + case .success(let response): + let decoder = JSONDecoder() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" + decoder.dateDecodingStrategy = .formatted(dateFormatter) + do { + let tweets = try decoder.decode([Tweet].self, from: response.data) + completion(.success(tweets)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + } diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift index 2b456f4cb..5b2aa24c8 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterUser.swift @@ -11,10 +11,12 @@ import Foundation struct TwitterUser: Codable { let name: String? + let screenName: String? let avatarURL: String? enum CodingKeys: String, CodingKey { case name = "name" + case screenName = "screen_name" case avatarURL = "profile_image_url_https" } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index cc59efa67..9b29c32c4 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -141,6 +141,7 @@ final class LocalAccountDelegate: AccountDelegate { case .success(let parsedItems): account.update(urlString, with: parsedItems) { _ in container.addWebFeed(feed) + completion(.success(feed)) } case .failure: completion(.failure(AccountError.createErrorNotFound))