From 2324d652cd11b1b559e94b742c7a3031fd20d84d Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 7 May 2020 02:10:54 -0500 Subject: [PATCH] Add Twitter API rate limiting --- .../Twitter/TwitterFeedProvider.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift index 6789d2fac..fbcb8e9eb 100644 --- a/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift +++ b/Frameworks/Account/FeedProvider/Twitter/TwitterFeedProvider.swift @@ -13,11 +13,14 @@ import RSParser import RSWeb public enum TwitterFeedProviderError: LocalizedError { + case rateLimitExceeded case screenNameNotFound case unknown public var localizedDescription: String { switch self { + case .rateLimitExceeded: + return NSLocalizedString("Twitter API rate limit has been exceeded. Please wait a short time and try again.", comment: "Rate Limit") case .screenNameNotFound: return NSLocalizedString("Unable to determine screen name.", comment: "Screen name") case .unknown: @@ -33,7 +36,7 @@ public enum TwitterFeedType: Int { case search = 3 } -public struct TwitterFeedProvider: FeedProvider { +public final class TwitterFeedProvider: FeedProvider { private static let homeURL = "https://www.twitter.com" private static let server = "api.twitter.com" @@ -51,6 +54,9 @@ public struct TwitterFeedProvider: FeedProvider { private var client: OAuthSwiftClient + private var rateLimitRemaining: Int? + private var rateLimitReset: Date? + public init?(tokenSuccess: OAuthSwift.TokenSuccess) { guard let screenName = tokenSuccess.parameters["screen_name"] as? String else { return nil @@ -332,6 +338,11 @@ private extension TwitterFeedProvider { var expandedParameters = parameters expandedParameters["tweet_mode"] = "extended" + if let remaining = rateLimitRemaining, let reset = rateLimitReset, remaining < 1 && reset > Date() { + completion(.failure(TwitterFeedProviderError.rateLimitExceeded)) + return + } + client.get(url, parameters: expandedParameters, headers: Self.userAgentHeaders) { result in switch result { case .success(let response): @@ -340,7 +351,14 @@ private extension TwitterFeedProvider { let dateFormatter = DateFormatter() dateFormatter.dateFormat = Self.dateFormat decoder.dateDecodingStrategy = .formatted(dateFormatter) - + + if let remaining = response.response.value(forHTTPHeaderField: "x-rate-limit-remaining") { + self.rateLimitRemaining = Int(remaining) ?? 0 + } + if let reset = response.response.value(forHTTPHeaderField: "x-rate-limit-reset") { + self.rateLimitReset = Date(timeIntervalSince1970: Double(reset) ?? 0) + } + do { let tweets: [TwitterStatus] if isSearch {