diff --git a/README.md b/README.md index d67d5321..9820c948 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ If you're using Xcode 6 and above, Swifter can be installed by simply dragging the Swifter Xcode project into your own project and adding either the SwifteriOS or SwifterMac framework as an embedded framework. -### Swifter 2.0 - -With the introduction of Swift 3, the naming convention of the language was changed. As a result, Swifter was updated to match the updated naming conventions of Swift. Additionally, we've slimmed down the framework and simplified a lot of the processes with the introduction of the `ListTag`, `UserTag`, and `UsersTag` enums for methods that allow for either id/slug for Lists, or id/screenName for Users. - ### Usage Swifter can be used with the 3 different kinds of authentication protocols Twitter allows. You can specify which protocol to use as shown below. For more information on each of the authentication protocols, please check [Twitter OAuth Help](https://dev.twitter.com/oauth). @@ -81,6 +77,8 @@ Additionally, there is also `.screenName(arrayOfUserNames)` and `.id(arrayOfIds) #### Streaming API: +**Important Note**: Twitter has deprecated the Streaming API in favour of their new [Accounts Activity API](https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/api-reference/aaa-enterprise). You can find out more about migrating to the new API [here](https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/migration/us-ss-migration-guide). Twitter plans to remove the old streaming API on August 16, 2018, Swifter will remove the endpoints for it shortly after that. + ```swift swifter.streamRandomSampleTweets(progress: { status in // ... diff --git a/Sources/Swifter.swift b/Sources/Swifter.swift index 68abcb96..9c730129 100644 --- a/Sources/Swifter.swift +++ b/Sources/Swifter.swift @@ -31,14 +31,16 @@ import Accounts #endif extension Notification.Name { - static let SwifterCallbackNotification: Notification.Name = Notification.Name(rawValue: "SwifterCallbackNotificationName") + static let swifterCallback = Notification.Name(rawValue: "Swifter.CallbackNotificationName") } // MARK: - Twitter URL public enum TwitterURL { + case api case upload case stream + case publish case userStream case siteStream case oauth @@ -51,6 +53,7 @@ public enum TwitterURL { case .userStream: return URL(string: "https://userstream.twitter.com/1.1/")! case .siteStream: return URL(string: "https://sitestream.twitter.com/1.1/")! case .oauth: return URL(string: "https://api.twitter.com/")! + case .publish: return URL(string: "https://publish.twitter.com/")! } } @@ -58,6 +61,7 @@ public enum TwitterURL { // MARK: - Tweet Mode public enum TweetMode { + case `default` case extended case compat @@ -84,7 +88,9 @@ public class Swifter { public typealias SuccessHandler = (JSON) -> Void public typealias CursorSuccessHandler = (JSON, _ previousCursor: String?, _ nextCursor: String?) -> Void public typealias JSONSuccessHandler = (JSON, _ response: HTTPURLResponse) -> Void - public typealias FailureHandler = (_ error: Error) -> Void + public typealias SearchResultHandler = (JSON, _ searchMetadata: JSON) -> Void + public typealias FailureHandler = (_ error: Error) -> Void + internal struct CallbackNotification { static let optionsURLKey = "SwifterCallbackNotificationOptionsURLKey" @@ -93,6 +99,7 @@ public class Swifter { internal struct DataParameters { static let dataKey = "SwifterDataParameterKey" static let fileNameKey = "SwifterDataParameterFilename" + static let jsonDataKey = "SwifterDataJSONDataParameterKey" } // MARK: - Properties @@ -109,7 +116,8 @@ public class Swifter { } public init(consumerKey: String, consumerSecret: String, oauthToken: String, oauthTokenSecret: String) { - self.client = OAuthClient(consumerKey: consumerKey, consumerSecret: consumerSecret , accessToken: oauthToken, accessTokenSecret: oauthTokenSecret) + self.client = OAuthClient(consumerKey: consumerKey, consumerSecret: consumerSecret, + accessToken: oauthToken, accessTokenSecret: oauthTokenSecret) } #if os(macOS) || os(iOS) @@ -125,15 +133,22 @@ public class Swifter { // MARK: - JSON Requests @discardableResult - internal func jsonRequest(path: String, baseURL: TwitterURL, method: HTTPMethodType, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler? = nil, downloadProgress: JSONSuccessHandler? = nil, success: JSONSuccessHandler? = nil, failure: HTTPRequest.FailureHandler? = nil) -> HTTPRequest { + internal func jsonRequest(path: String, + baseURL: TwitterURL, + method: HTTPMethodType, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler? = nil, + downloadProgress: JSONSuccessHandler? = nil, + success: JSONSuccessHandler? = nil, + failure: HTTPRequest.FailureHandler? = nil) -> HTTPRequest { + let jsonDownloadProgressHandler: HTTPRequest.DownloadProgressHandler = { [weak self] data, _, _, response in - guard let `self` = self else { return } - guard let _ = downloadProgress else { return } - self.handleStreamProgress(data: data, response: response, handler: downloadProgress) + if let progress = downloadProgress { + self?.handleStreamProgress(data: data, response: response, handler: progress) + } } - + let jsonSuccessHandler: HTTPRequest.SuccessHandler = { data, response in - DispatchQueue.global(qos: .utility).async { do { let jsonResult = try JSON.parse(jsonData: data) @@ -142,7 +157,7 @@ public class Swifter { } } catch { DispatchQueue.main.async { - if case 200 ... 299 = response.statusCode, data.count == 0 { + if case 200...299 = response.statusCode, data.isEmpty { success?(JSON("{}"), response) } else { failure?(error) @@ -151,12 +166,22 @@ public class Swifter { } } } - - if method == .GET { - return self.client.get(path, baseURL: baseURL, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: jsonDownloadProgressHandler, success: jsonSuccessHandler, failure: failure) - } else { - return self.client.post(path, baseURL: baseURL, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: jsonDownloadProgressHandler, success: jsonSuccessHandler, failure: failure) - } + + switch method { + case .GET: + return self.client.get(path, baseURL: baseURL, parameters: parameters, + uploadProgress: uploadProgress, downloadProgress: jsonDownloadProgressHandler, + success: jsonSuccessHandler, failure: failure) + case .POST: + return self.client.post(path, baseURL: baseURL, parameters: parameters, + uploadProgress: uploadProgress, downloadProgress: jsonDownloadProgressHandler, + success: jsonSuccessHandler, failure: failure) + case .DELETE: + return self.client.delete(path, baseURL: baseURL, parameters: parameters, + success: jsonSuccessHandler, failure: failure) + default: + fatalError("This HTTP Method is not supported") + } } private func handleStreamProgress(data: Data, response: HTTPURLResponse, handler: JSONSuccessHandler? = nil) { @@ -180,13 +205,39 @@ public class Swifter { } @discardableResult - internal func getJSON(path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler? = nil, downloadProgress: JSONSuccessHandler? = nil, success: JSONSuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { - return self.jsonRequest(path: path, baseURL: baseURL, method: .GET, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: downloadProgress, success: success, failure: failure) + internal func getJSON(path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler? = nil, + downloadProgress: JSONSuccessHandler? = nil, + success: JSONSuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + return self.jsonRequest(path: path, baseURL: baseURL, method: .GET, parameters: parameters, + uploadProgress: uploadProgress, downloadProgress: downloadProgress, + success: success, failure: failure) } @discardableResult - internal func postJSON(path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler? = nil, downloadProgress: JSONSuccessHandler? = nil, success: JSONSuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { - return self.jsonRequest(path: path, baseURL: baseURL, method: .POST, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: downloadProgress, success: success, failure: failure) + internal func postJSON(path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler? = nil, + downloadProgress: JSONSuccessHandler? = nil, + success: JSONSuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + return self.jsonRequest(path: path, baseURL: baseURL, method: .POST, parameters: parameters, + uploadProgress: uploadProgress, downloadProgress: downloadProgress, + success: success, failure: failure) } + + @discardableResult + internal func deleteJSON(path: String, + baseURL: TwitterURL, + parameters: [String: Any], + success: JSONSuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + return self.jsonRequest(path: path, baseURL: baseURL, method: .DELETE, parameters: parameters, + success: success, failure: failure) + } } diff --git a/Sources/SwifterAccountsClient.swift b/Sources/SwifterAccountsClient.swift index 8146a0bb..a467317f 100644 --- a/Sources/SwifterAccountsClient.swift +++ b/Sources/SwifterAccountsClient.swift @@ -37,7 +37,7 @@ internal class AccountsClient: SwifterClientProtocol { self.credential = Credential(account: account) } - func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + func get(_ path: String, baseURL: TwitterURL, parameters: [String: Any], uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { let url = URL(string: path, relativeTo: baseURL.url) let stringifiedParameters = parameters.stringifiedDictionary() @@ -55,7 +55,7 @@ internal class AccountsClient: SwifterClientProtocol { return request } - func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + func post(_ path: String, baseURL: TwitterURL, parameters: [String: Any], uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { let url = URL(string: path, relativeTo: baseURL.url) var params = parameters @@ -96,6 +96,27 @@ internal class AccountsClient: SwifterClientProtocol { request.start() return request } + + func delete(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url) + + let stringifiedParameters = parameters.stringifiedDictionary() + + let socialRequest = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: .DELETE, url: url, parameters: stringifiedParameters)! + socialRequest.account = self.credential!.account! + + let request = HTTPRequest(request: socialRequest.preparedURLRequest()) + request.parameters = parameters + request.successHandler = success + request.failureHandler = failure + + request.start() + return request + } } #endif diff --git a/Sources/SwifterAppOnlyClient.swift b/Sources/SwifterAppOnlyClient.swift index a1280fc7..c67954c6 100644 --- a/Sources/SwifterAppOnlyClient.swift +++ b/Sources/SwifterAppOnlyClient.swift @@ -39,7 +39,13 @@ internal class AppOnlyClient: SwifterClientProtocol { self.consumerSecret = consumerSecret } - func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + func get(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler?, + downloadProgress: HTTPRequest.DownloadProgressHandler?, + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { let url = URL(string: path, relativeTo: baseURL.url) let request = HTTPRequest(url: url!, method: .GET, parameters: parameters) @@ -56,7 +62,13 @@ internal class AppOnlyClient: SwifterClientProtocol { return request } - func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + func post(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler?, + downloadProgress: HTTPRequest.DownloadProgressHandler?, + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { let url = URL(string: path, relativeTo: baseURL.url) let request = HTTPRequest(url: url!, method: .POST, parameters: parameters) @@ -76,6 +88,26 @@ internal class AppOnlyClient: SwifterClientProtocol { request.start() return request } + + func delete(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url) + + let request = HTTPRequest(url: url!, method: .DELETE, parameters: parameters) + request.successHandler = success + request.failureHandler = failure + request.dataEncoding = self.dataEncoding + + if let bearerToken = self.credential?.accessToken?.key { + request.headers = ["Authorization": "Bearer \(bearerToken)"]; + } + + request.start() + return request + } class func base64EncodedCredentials(withKey key: String, secret: String) -> String { let encodedKey = key.urlEncodedString() diff --git a/Sources/SwifterAuth.swift b/Sources/SwifterAuth.swift index fd8fbcaa..de664e48 100644 --- a/Sources/SwifterAuth.swift +++ b/Sources/SwifterAuth.swift @@ -73,12 +73,15 @@ public extension Swifter { */ #if os(iOS) - public func authorize(with callbackURL: URL, presentFrom presentingViewController: UIViewController? , success: TokenSuccessHandler?, failure: FailureHandler? = nil) { + public func authorize(withCallback callbackURL: URL, + presentingFrom presenting: UIViewController?, + success: TokenSuccessHandler?, + failure: FailureHandler? = nil) { self.postOAuthRequestToken(with: callbackURL, success: { token, response in var requestToken = token! - NotificationCenter.default.addObserver(forName: .SwifterCallbackNotification, object: nil, queue: .main) { notification in + NotificationCenter.default.addObserver(forName: .swifterCallback, object: nil, queue: .main) { notification in NotificationCenter.default.removeObserver(self) - presentingViewController?.presentedViewController?.dismiss(animated: true, completion: nil) + presenting?.presentedViewController?.dismiss(animated: true, completion: nil) let url = notification.userInfo![CallbackNotification.optionsURLKey] as! URL let parameters = url.query!.queryStringParameters @@ -90,22 +93,21 @@ public extension Swifter { }, failure: failure) } - let authorizeURL = URL(string: "oauth/authorize", relativeTo: TwitterURL.oauth.url) - let queryURL = URL(string: authorizeURL!.absoluteString + "?oauth_token=\(token!.key)")! + let queryUrl = URL(string: "oauth/authorize?oauth_token=\(token!.key)", relativeTo: TwitterURL.oauth.url)! - if #available(iOS 9.0, *) , let delegate = presentingViewController as? SFSafariViewControllerDelegate { - let safariView = SFSafariViewController(url: queryURL) + if #available(iOS 9.0, *) , let delegate = presenting as? SFSafariViewControllerDelegate { + let safariView = SFSafariViewController(url: queryUrl) safariView.delegate = delegate - presentingViewController?.present(safariView, animated: true, completion: nil) + presenting?.present(safariView, animated: true, completion: nil) } else { - UIApplication.shared.openURL(queryURL) + UIApplication.shared.openURL(queryUrl) } }, failure: failure) } #endif public class func handleOpenURL(_ url: URL) { - let notification = Notification(name: .SwifterCallbackNotification, object: nil, userInfo: [CallbackNotification.optionsURLKey: url]) + let notification = Notification(name: .swifterCallback, object: nil, userInfo: [CallbackNotification.optionsURLKey: url]) NotificationCenter.default.post(notification) } @@ -121,14 +123,16 @@ public extension Swifter { success?(credentialToken, response) } else { - let error = SwifterError(message: "Cannot find bearer token in server response", kind: .invalidAppOnlyBearerToken) + let error = SwifterError(message: "Cannot find bearer token in server response", + kind: .invalidAppOnlyBearerToken) failure?(error) } } else if case .object = json["errors"] { let error = SwifterError(message: json["errors"]["message"].string!, kind: .responseError(code: json["errors"]["code"].integer!)) failure?(error) } else { - let error = SwifterError(message: "Cannot find JSON dictionary in response", kind: .invalidJSONResponse) + let error = SwifterError(message: "Cannot find JSON dictionary in response", + kind: .invalidJSONResponse) failure?(error) } @@ -137,10 +141,7 @@ public extension Swifter { public func postOAuth2BearerToken(success: JSONSuccessHandler?, failure: FailureHandler?) { let path = "oauth2/token" - - var parameters = Dictionary() - parameters["grant_type"] = "client_credentials" - + let parameters = ["grant_type": "client_credentials"] self.jsonRequest(path: path, baseURL: .oauth, method: .POST, parameters: parameters, success: success, failure: failure) } @@ -160,7 +161,7 @@ public extension Swifter { public func postOAuthRequestToken(with callbackURL: URL, success: @escaping TokenSuccessHandler, failure: FailureHandler?) { let path = "oauth/request_token" - let parameters: [String: Any] = ["oauth_callback": callbackURL.absoluteString] + let parameters = ["oauth_callback": callbackURL.absoluteString] self.client.post(path, baseURL: .oauth, parameters: parameters, uploadProgress: nil, downloadProgress: nil, success: { data, response in let responseString = String(data: data, encoding: .utf8)! @@ -172,7 +173,7 @@ public extension Swifter { public func postOAuthAccessToken(with requestToken: Credential.OAuthAccessToken, success: @escaping TokenSuccessHandler, failure: FailureHandler?) { if let verifier = requestToken.verifier { let path = "oauth/access_token" - let parameters: [String: Any] = ["oauth_token": requestToken.key, "oauth_verifier": verifier] + let parameters = ["oauth_token": requestToken.key, "oauth_verifier": verifier] self.client.post(path, baseURL: .oauth, parameters: parameters, uploadProgress: nil, downloadProgress: nil, success: { data, response in @@ -182,7 +183,8 @@ public extension Swifter { }, failure: failure) } else { - let error = SwifterError(message: "Bad OAuth response received from server", kind: .badOAuthResponse) + let error = SwifterError(message: "Bad OAuth response received from server", + kind: .badOAuthResponse) failure?(error) } } diff --git a/Sources/SwifterClientProtocol.swift b/Sources/SwifterClientProtocol.swift index c9285e13..fa7c6110 100644 --- a/Sources/SwifterClientProtocol.swift +++ b/Sources/SwifterClientProtocol.swift @@ -30,9 +30,28 @@ public protocol SwifterClientProtocol { var credential: Credential? { get set } @discardableResult - func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest + func get(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler?, + downloadProgress: HTTPRequest.DownloadProgressHandler?, + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest @discardableResult - func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest + func post(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler?, + downloadProgress: HTTPRequest.DownloadProgressHandler?, + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest + + @discardableResult + func delete(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest } diff --git a/Sources/SwifterError.swift b/Sources/SwifterError.swift index f8994514..0b31b2cb 100644 --- a/Sources/SwifterError.swift +++ b/Sources/SwifterError.swift @@ -17,6 +17,8 @@ public struct SwifterError: Error { case badOAuthResponse case urlResponseError(status: Int, headers: [AnyHashable: Any], errorCode: Int) case jsonParseError + case invalidGifData + case invalidGifResponse public var description: String { switch self { @@ -32,6 +34,10 @@ public struct SwifterError: Error { return "urlResponseError(status: \(code), headers: \(headers), errorCode: \(errorCode)" case .jsonParseError: return "jsonParseError" + case .invalidGifData: + return "invalidGifData" + case .invalidGifResponse: + return "invalidGifResponse" } } diff --git a/Sources/SwifterFavorites.swift b/Sources/SwifterFavorites.swift index fdae8b42..ae6ed92d 100644 --- a/Sources/SwifterFavorites.swift +++ b/Sources/SwifterFavorites.swift @@ -35,32 +35,46 @@ public extension Swifter { If you do not provide either a user_id or screen_name to this method, it will assume you are requesting on behalf of the authenticating user. Specify one or the other for best results. */ - public func getRecentlyFavoritedTweets(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - + public func getRecentlyFavoritedTweets(count: Int? = nil, + sinceID: String? = nil, + maxID: String? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "favorites/list.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["count"] ??= count parameters["since_id"] ??= sinceID parameters["max_id"] ??= maxID parameters["tweet_mode"] ??= tweetMode.stringValue - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } - public func getRecentlyFavoritedTweets(for userTag: UserTag, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getRecentlyFavoritedTweets(for userTag: UserTag, + count: Int? = nil, + sinceID: String? = nil, + maxID: String? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "favorites/list.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["since_id"] ??= sinceID parameters["max_id"] ??= maxID parameters["tweet_mode"] ??= tweetMode.stringValue - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -70,15 +84,21 @@ public extension Swifter { This process invoked by this method is asynchronous. The immediately returned status may not indicate the resultant favorited status of the tweet. A 200 OK response from this method will indicate whether the intended action was successful or not. */ - public func unfavoriteTweet(forID id: String, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func unfavoriteTweet(forID id: String, + includeEntities: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "favorites/destroy.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["id"] = id parameters["include_entities"] ??= includeEntities parameters["tweet_mode"] ??= tweetMode.stringValue - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -88,15 +108,21 @@ public extension Swifter { This process invoked by this method is asynchronous. The immediately returned status may not indicate the resultant favorited status of the tweet. A 200 OK response from this method will indicate whether the intended action was successful or not. */ - public func favoriteTweet(forID id: String, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: HTTPRequest.FailureHandler? = nil) { + public func favoriteTweet(forID id: String, + includeEntities: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "favorites/create.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["id"] = id parameters["include_entities"] ??= includeEntities parameters["tweet_mode"] ??= tweetMode.stringValue - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterFollowers.swift b/Sources/SwifterFollowers.swift index 0020610c..b6c9fbf7 100644 --- a/Sources/SwifterFollowers.swift +++ b/Sources/SwifterFollowers.swift @@ -32,11 +32,10 @@ public extension Swifter { Returns a collection of user_ids that the currently authenticated user does not want to receive retweets from. Use POST friendships/update to set the "no retweets" status for a given user account on behalf of the current user. */ - public func listOfNoRetweetsFriends(stringifyIDs: Bool = true, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func listOfNoRetweetsFriends(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/no_retweets/ids.json" - - var parameters = Dictionary() - parameters["stringify_ids"] = stringifyIDs + let parameters = ["stringigy_ids": true] self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } @@ -51,13 +50,17 @@ public extension Swifter { This method is especially powerful when used in conjunction with GET users/lookup, a method that allows you to convert user IDs into full user objects in bulk. */ - public func getUserFollowingIDs(for userTag: UserTag, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUserFollowingIDs(for userTag: UserTag, + cursor: String? = nil, + count: Int? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friends/ids.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor - parameters["stringify_ids"] ??= stringifyIDs + parameters["stringify_ids"] = true parameters["count"] ??= count self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in @@ -74,13 +77,17 @@ public extension Swifter { This method is especially powerful when used in conjunction with GET users/lookup, a method that allows you to convert user IDs into full user objects in bulk. */ - public func getUserFollowersIDs(for userTag: UserTag, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUserFollowersIDs(for userTag: UserTag, + cursor: String? = nil, + count: Int? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "followers/ids.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor - parameters["stringify_ids"] ??= stringifyIDs + parameters["stringify_ids"] = true parameters["count"] ??= count self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in @@ -93,12 +100,14 @@ public extension Swifter { Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. */ - public func getIncomingPendingFollowRequests(cursor: String? = nil, stringifyIDs: String? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getIncomingPendingFollowRequests(cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/incoming.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["cursor"] ??= cursor - parameters["stringify_ids"] ??= stringifyIDs + parameters["stringify_ids"] = true self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) @@ -110,12 +119,14 @@ public extension Swifter { Returns a collection of numeric IDs for every protected user for whom the authenticating user has a pending follow request. */ - public func getOutgoingPendingFollowRequests(cursor: String? = nil, stringifyIDs: String? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getOutgoingPendingFollowRequests(cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/outgoing.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["cursor"] ??= cursor - parameters["stringify_ids"] ??= stringifyIDs + parameters["stringify_ids"] = true self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) @@ -131,10 +142,13 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func followUser(for userTag: UserTag, follow: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func followUser(_ userTag: UserTag, + follow: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/create.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["follow"] ??= follow @@ -152,10 +166,12 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func unfollowUser(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func unfollowUser(_ userTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/destroy.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in @@ -168,10 +184,14 @@ public extension Swifter { Allows one to enable or disable retweets and device notifications from the specified user. */ - public func updateFriendship(with userTag: UserTag, device: Bool? = nil, retweets: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func updateFriendship(with userTag: UserTag, + device: Bool? = nil, + retweets: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/update.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["device"] ??= device parameters["retweets"] ??= retweets @@ -186,10 +206,13 @@ public extension Swifter { Returns detailed information about the relationship between two arbitrary users. */ - public func showFriendship(between sourceTag: UserTag, and targetTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func showFriendship(between sourceTag: UserTag, + and targetTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/show.json" - var parameters = Dictionary() + var parameters = [String: Any]() switch sourceTag { case .id: parameters["source_id"] = sourceTag.value case .screenName: parameters["source_screen_name"] = sourceTag.value @@ -212,10 +235,16 @@ public extension Swifter { At this time, results are ordered with the most recent following first — however, this ordering is subject to unannounced change and eventual consistency issues. Results are given in groups of 20 users and multiple "pages" of results can be navigated through using the next_cursor value in subsequent requests. See Using cursors to navigate collections for more information. */ - public func getUserFollowing(for userTag: UserTag, cursor: String? = nil, count: Int? = nil, skipStatus: Bool? = nil, includeUserEntities: Bool? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUserFollowing(for userTag: UserTag, + cursor: String? = nil, + count: Int? = nil, + skipStatus: Bool? = nil, + includeUserEntities: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friends/list.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["count"] ??= count @@ -234,10 +263,16 @@ public extension Swifter { At this time, results are ordered with the most recent following first — however, this ordering is subject to unannounced change and eventual consistency issues. Results are given in groups of 20 users and multiple "pages" of results can be navigated through using the next_cursor value in subsequent requests. See Using cursors to navigate collections for more information. */ - public func getUserFollowers(for userTag: UserTag, cursor: String? = nil, count: Int? = nil, skipStatus: Bool? = nil, includeUserEntities: Bool? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUserFollowers(for userTag: UserTag, + cursor: String? = nil, + count: Int? = nil, + skipStatus: Bool? = nil, + includeUserEntities: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "followers/list.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["count"] ??= count @@ -254,10 +289,12 @@ public extension Swifter { Returns the relationships of the authenticating user to the comma-separated list of up to 100 screen_names or user_ids provided. Values for connections can be: following, following_requested, followed_by, none. */ - public func lookupFriendship(with usersTag: UsersTag, success: SuccessHandler? = nil, failure: FailureHandler?) { + public func lookupFriendship(with usersTag: UsersTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "friendships/lookup.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[usersTag.key] = usersTag.value self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in diff --git a/Sources/SwifterGIF.swift b/Sources/SwifterGIF.swift index 1c80fb54..a0969956 100644 --- a/Sources/SwifterGIF.swift +++ b/Sources/SwifterGIF.swift @@ -8,44 +8,22 @@ import Foundation - -public enum GIFterError: Error { - case invalidData, invalidResponse -} - - public extension Swifter { - func tweetGIF(_ text: String, attachment: URL, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - guard let data = try? Data(contentsOf: attachment) else { failure?(GIFterError.invalidData); return } - prepareUpload(data: data, success: { json, response in - if let media_id = json["media_id_string"].string { - self.uploadGIF(media_id, data: data, name: attachment.lastPathComponent, success: { json, response in - self.finalizeUpload(mediaId: media_id, success: { json, resoponse in - self.postTweet(status: text, mediaIDs: [media_id], success: success, failure: failure) - }, failure: failure) - }, failure: failure) - } - else { - failure?(GIFterError.invalidResponse) - } - }, failure: failure) - } - - private func prepareUpload(data: Data, success: JSONSuccessHandler? = nil, failure: FailureHandler? = nil) { + internal func prepareUpload(data: Data, success: JSONSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "media/upload.json" - let parameters : [String:Any] = ["command": "INIT", "total_bytes": data.count, - "media_type": "image/gif", "media_category": "tweet_gif"] + let parameters: [String : Any] = [ "command": "INIT", "total_bytes": data.count, + "media_type": "image/gif", "media_category": "tweet_gif"] self.postJSON(path: path, baseURL: .upload, parameters: parameters, success: success, failure: failure) } - private func finalizeUpload(mediaId: String, success: JSONSuccessHandler? = nil, failure: FailureHandler? = nil) { + internal func finalizeUpload(mediaId: String, success: JSONSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "media/upload.json" let parameters = ["command": "FINALIZE", "media_id" : mediaId] self.postJSON(path: path, baseURL: .upload, parameters: parameters, success: success, failure: failure) } - private func uploadGIF(_ mediaId: String, data: Data, name: String? = nil, success: JSONSuccessHandler? = nil, failure: FailureHandler? = nil) { + internal func uploadGIF(_ mediaId: String, data: Data, name: String? = nil, success: JSONSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "media/upload.json" let parameters : [String:Any] = ["command": "APPEND", "media_id": mediaId, "segment_index": "0", Swifter.DataParameters.dataKey : "media", diff --git a/Sources/SwifterHTTPRequest.swift b/Sources/SwifterHTTPRequest.swift index db92fe65..5a998d57 100644 --- a/Sources/SwifterHTTPRequest.swift +++ b/Sources/SwifterHTTPRequest.swift @@ -65,10 +65,12 @@ public class HTTPRequest: NSObject, URLSessionDataDelegate { var dataTask: URLSessionDataTask? var headers: Dictionary = [:] - var parameters: Dictionary + var parameters: [String: Any] var encodeParameters: Bool var uploadData: [DataUpload] = [] + + var jsonBody: Data? var dataEncoding: String.Encoding = .utf8 @@ -84,7 +86,7 @@ public class HTTPRequest: NSObject, URLSessionDataDelegate { var successHandler: SuccessHandler? var failureHandler: FailureHandler? - public init(url: URL, method: HTTPMethodType = .GET, parameters: Dictionary = [:]) { + public init(url: URL, method: HTTPMethodType = .GET, parameters: [String: Any] = [:]) { self.url = url self.HTTPMethod = method self.parameters = parameters @@ -114,15 +116,18 @@ public class HTTPRequest: NSObject, URLSessionDataDelegate { let nonOAuthParameters = self.parameters.filter { key, _ in !key.hasPrefix("oauth_") } - if !self.uploadData.isEmpty { + if let body = self.jsonBody { + self.request!.setValue("application/json", forHTTPHeaderField: "Content-Type") + self.request!.setValue("\(body.count)", forHTTPHeaderField: "Content-Length") + self.request!.httpBody = body + } else if !self.uploadData.isEmpty { let boundary = "--" + UUID().uuidString let contentType = "multipart/form-data; boundary=\(boundary)" self.request!.setValue(contentType, forHTTPHeaderField:"Content-Type") var body = Data() - - for dataUpload: DataUpload in self.uploadData { + for dataUpload in self.uploadData { let multipartData = HTTPRequest.mulipartContent(with: boundary, data: dataUpload.data, fileName: dataUpload.fileName, parameterName: dataUpload.parameterName, mimeType: dataUpload.mimeType) body.append(multipartData) } @@ -179,6 +184,12 @@ public class HTTPRequest: NSObject, URLSessionDataDelegate { let dataUpload = DataUpload(data: data, parameterName: parameterName, mimeType: mimeType, fileName: fileName) self.uploadData.append(dataUpload) } + + public func add(body: [String: Any]) { + if let data = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted) { + self.jsonBody = data + } + } private class func mulipartContent(with boundary: String, data: Data, fileName: String?, parameterName: String, mimeType mimeTypeOrNil: String?) -> Data { let mimeType = mimeTypeOrNil ?? "application/octet-stream" diff --git a/Sources/SwifterHelp.swift b/Sources/SwifterHelp.swift index 502d5d20..c4b9e5ff 100644 --- a/Sources/SwifterHelp.swift +++ b/Sources/SwifterHelp.swift @@ -34,10 +34,12 @@ public extension Swifter { It is recommended applications request this endpoint when they are loaded, but no more than once a day. */ - public func getHelpConfiguration(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getHelpConfiguration(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "help/configuration.json" - - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -45,10 +47,12 @@ public extension Swifter { Returns the list of languages supported by Twitter along with their ISO 639-1 code. The ISO 639-1 code is the two letter value to use if you include lang with any of your requests. */ - public func getHelpLanguages(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getHelpLanguages(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "help/languages.json" - - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + },failure: failure) } /** @@ -56,10 +60,12 @@ public extension Swifter { Returns Twitter's Privacy Policy. */ - public func getHelpPrivacy(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getHelpPrivacy(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "help/privacy.json" - - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json["privacy"]) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json["privacy"]) + }, failure: failure) } /** @@ -67,10 +73,12 @@ public extension Swifter { Returns the Twitter Terms of Service in the requested format. These are not the same as the Developer Rules of the Road. */ - public func getHelpTermsOfService(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getHelpTermsOfService(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "help/tos.json" - - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json["tos"]) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json["tos"]) + }, failure: failure) } /** @@ -88,13 +96,14 @@ public extension Swifter { Read more about REST API Rate Limiting in v1.1 and review the limits. */ - public func getRateLimits(for resources: [String], success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getRateLimits(for resources: [String], + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "application/rate_limit_status.json" - - var parameters = Dictionary() - parameters["resources"] = resources.joined(separator: ",") - - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + let parameters = ["resources": resources.joined(separator: ",")] + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterLists.swift b/Sources/SwifterLists.swift index 5b71cc3f..851c9079 100644 --- a/Sources/SwifterLists.swift +++ b/Sources/SwifterLists.swift @@ -36,24 +36,18 @@ public extension Swifter { A maximum of 100 results will be returned by this call. Subscribed lists are returned first, followed by owned lists. This means that if a user subscribes to 90 lists and owns 20 lists, this method returns 90 subscriptions and 10 owned lists. The reverse method returns owned lists first, so with reverse=true, 20 owned lists and 80 subscriptions would be returned. If your goal is to obtain every list a user owns or subscribes to, use GET lists/ownerships and/or GET lists/subscriptions instead. */ - public func getSubscribedLists(reverse: Bool?, success: SuccessHandler?, failure: FailureHandler?) { + public func getSubscribedLists(for userTag: UserTag? = nil, + reverse: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/list.json" - - var parameters = Dictionary() - parameters["reverse"] ??= reverse - - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in - success?(json) - }, failure: failure) - } - - public func getSubscribedLists(for userTag: UserTag, reverse: Bool?, success: SuccessHandler?, failure: FailureHandler?) { - let path = "lists/list.json" - - var parameters = Dictionary() - parameters[userTag.key] = userTag.value - parameters["reverse"] ??= reverse - + + var parameters = [String: Any]() + parameters["reverse"] ??= reverse + if let userTag = userTag { + parameters[userTag.key] = userTag.value + } + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) @@ -64,10 +58,18 @@ public extension Swifter { Returns a timeline of tweets authored by members of the specified list. Retweets are included by default. Use the include_rts=false parameter to omit retweets. Embedded Timelines is a great way to embed list timelines on your website. */ - public func listTweets(for listTag: ListTag, sinceID: String?, maxID: String?, count: Int?, includeEntities: Bool?, includeRTs: Bool?, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler?, failure: FailureHandler?) { + public func listTweets(for listTag: ListTag, + sinceID: String? = nil, + maxID: String? = nil, + count: Int? = nil, + includeEntities: Bool? = nil, + includeRTs: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/statuses.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -79,7 +81,9 @@ public extension Swifter { parameters["include_rts"] ??= includeRTs parameters["tweet_mode"] ??= tweetMode.stringValue - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -87,17 +91,22 @@ public extension Swifter { Removes the specified member from the list. The authenticated user must be the list's owner to remove members from the list. */ - public func removeMemberFromList(for listTag: ListTag, user userTag: UserTag, success: SuccessHandler?, failure: FailureHandler?) { + public func removeMemberFromList(for listTag: ListTag, + user userTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/members/destroy.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value } parameters[userTag.key] = userTag.value - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -105,10 +114,15 @@ public extension Swifter { Returns the lists the specified user has been added to. If user_id or screen_name are not provided the memberships for the authenticating user are returned. */ - public func getListMemberships(for userTag: UserTag, count: Int? = nil, cursor: String?, filterToOwnedLists: Bool?, success: CursorSuccessHandler?, failure: FailureHandler?) { + public func getListMemberships(for userTag: UserTag, + count: Int? = nil, + cursor: String? = nil, + filterToOwnedLists: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/memberships.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["cursor"] ??= cursor @@ -124,10 +138,15 @@ public extension Swifter { Returns the subscribers of the specified list. Private list subscribers will only be shown if the authenticated user owns the specified list. */ - public func getListSubscribers(for listTag: ListTag, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: CursorSuccessHandler?, failure: FailureHandler?) { + public func getListSubscribers(for listTag: ListTag, + cursor: String? = nil, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/subscribers.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -146,16 +165,21 @@ public extension Swifter { Subscribes the authenticated user to the specified list. */ - public func subscribeToList(for listTag: ListTag, owner ownerTag: UserTag, success: SuccessHandler?, failure: FailureHandler?) { + public func subscribeToList(for listTag: ListTag, + owner ownerTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/subscribers/create.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value } - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -163,10 +187,15 @@ public extension Swifter { Check if the specified user is a subscriber of the specified list. Returns the user if they are subscriber. */ - public func checkListSubcription(of userTag: UserTag, for listTag: ListTag, includeEntities: Bool?, skipStatus: Bool?, success: SuccessHandler?, failure: FailureHandler?) { + public func checkListSubcription(of userTag: UserTag, + for listTag: ListTag, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/subscribers/show.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -183,16 +212,20 @@ public extension Swifter { Unsubscribes the authenticated user from the specified list. */ - public func unsubscribeFromList(for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { + public func unsubscribeFromList(for listTag: ListTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/subscribers/destroy.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value } - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -202,10 +235,15 @@ public extension Swifter { Please note that there can be issues with lists that rapidly remove and add memberships. Take care when using these methods such that you are not too rapidly switching between removals and adds on the same list. */ - public func subscribeUsersToList(for listTag: ListTag, users usersTag: UsersTag, includeEntities: Bool?, skipStatus: Bool?, success: SuccessHandler?, failure: FailureHandler?) { + public func subscribeUsersToList(for listTag: ListTag, + users usersTag: UsersTag, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/members/create_all.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -215,7 +253,9 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -223,10 +263,15 @@ public extension Swifter { Check if the specified user is a member of the specified list. */ - public func checkListMembership(of userTag: UserTag, for listTag: ListTag, includeEntities: Bool?, skipStatus: Bool?, success: SuccessHandler?, failure: FailureHandler?) { + public func checkListMembership(of userTag: UserTag, + for listTag: ListTag, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/members/show.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -235,7 +280,9 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -244,10 +291,15 @@ public extension Swifter { Returns the members of the specified list. Private list members will only be shown if the authenticated user owns the specified list. */ - public func getListMembers(for listTag: ListTag, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: CursorSuccessHandler?, failure: FailureHandler?) { + public func getListMembers(for listTag: ListTag, + cursor: String? = nil, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/members.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -266,10 +318,13 @@ public extension Swifter { Add a member to a list. The authenticated user must own the list to be able to add members to it. Note that lists cannot have more than 5,000 members. */ - public func addListMember(_ userTag: UserTag, for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { + public func addListMember(_ userTag: UserTag, + to listTag: ListTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/members/create.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -284,16 +339,20 @@ public extension Swifter { Deletes the specified list. The authenticated user must own the list to be able to destroy it. */ - public func deleteList(for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { + public func deleteList(for listTag: ListTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/destroy.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value } - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -301,10 +360,15 @@ public extension Swifter { Updates the specified list. The authenticated user must own the list to be able to update it. */ - public func updateList(for listTag: ListTag, name: String?, isPublic: Bool = true, description: String?, success: SuccessHandler?, failure: FailureHandler?) { + public func updateList(for listTag: ListTag, + name: String? = nil, + isPublic: Bool = true, + description: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/update.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -321,15 +385,21 @@ public extension Swifter { Creates a new list for the authenticated user. Note that you can't create more than 20 lists per account. */ - public func createList(named name: String, asPublicList: Bool = true, description: String?, success: SuccessHandler?, failure: FailureHandler?) { + public func createList(named name: String, + asPublicList: Bool = true, + description: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/create.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["name"] = name parameters["mode"] = asPublicList ? "public" : "private" parameters["description"] ??= description - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -337,10 +407,12 @@ public extension Swifter { Returns the specified list. Private lists will only be shown if the authenticated user owns the specified list. */ - public func showList(for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { + public func showList(for listTag: ListTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/show.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value @@ -356,10 +428,14 @@ public extension Swifter { Obtain a collection of the lists the specified user is subscribed to, 20 lists per page by default. Does not include the user's own lists. */ - public func getSubscribedList(of userTag: UserTag, count: String?, cursor: String?, success: CursorSuccessHandler?, failure: FailureHandler?) { + public func getSubscribedList(of userTag: UserTag, + count: String? = nil, + cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/subscriptions.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["cursor"] ??= cursor @@ -376,17 +452,22 @@ public extension Swifter { Please note that there can be issues with lists that rapidly remove and add memberships. Take care when using these methods such that you are not too rapidly switching between removals and adds on the same list. */ - public func removeListMembers(_ usersTag: UsersTag, for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { + public func removeListMembers(_ usersTag: UsersTag, + from listTag: ListTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/members/destroy_all.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[listTag.key] = listTag.value if case .slug(_, let owner) = listTag { parameters[owner.ownerKey] = owner.value } parameters[usersTag.key] = usersTag.value - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -394,10 +475,14 @@ public extension Swifter { Returns the lists owned by the specified Twitter user. Private lists will only be shown if the authenticated user is also the owner of the lists. */ - public func getOwnedLists(for userTag: UserTag, count: String?, cursor: String?, success: CursorSuccessHandler?, failure: FailureHandler?) { + public func getOwnedLists(for userTag: UserTag, + count: String? = nil, + cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "lists/ownerships.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["cursor"] ??= cursor diff --git a/Sources/SwifterMessages.swift b/Sources/SwifterMessages.swift index 348a7ec8..b8167fce 100644 --- a/Sources/SwifterMessages.swift +++ b/Sources/SwifterMessages.swift @@ -26,83 +26,87 @@ import Foundation public extension Swifter { + + + /** + GET direct_messages/events/show + + Returns a single Direct Message event by the given id. + */ + public func getDirectMessage(for messageId: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + let path = "direct_messages/events/show.json" + let parameters = ["id": messageId] + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) + } + + /** + GET direct_messages/events/list + + Returns all Direct Message events (both sent and received) within the last 30 days. Sorted in reverse-chronological order. + */ + public func getDirectMessages(count: Int? = nil, + cursor: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + let path = "direct_messages/events/list.json" + var parameters: [String: Any] = [:] + parameters["count"] ??= count + parameters["cursor"] ??= cursor + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) + } + + /** + DELETE direct_messages/events/destroy + + Returns all Direct Message events (both sent and received) within the last 30 days. Sorted in reverse-chronological order. + */ + public func destroyDirectMessage(for messageId: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + let path = "direct_messages/events/destroy.json" + let parameters = ["id": messageId] + self.deleteJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) + } /** - GET direct_messages - - Returns the 20 most recent direct messages sent to the authenticating user. Includes detailed information about the sender and recipient user. You can request up to 200 direct messages per call, up to a maximum of 800 incoming DMs. - */ - public func getDirectMessages(since sinceID: String? = nil, maxID: String? = nil, count: Int? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "direct_messages.json" - - var parameters = Dictionary() - parameters["since_id"] ??= sinceID - parameters["max_id"] ??= maxID - parameters["count"] ??= count - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) - } - - /** - GET direct_messages/sent - - Returns the 20 most recent direct messages sent by the authenticating user. Includes detailed information about the sender and recipient user. You can request up to 200 direct messages per call, up to a maximum of 800 outgoing DMs. - */ - public func getSentDirectMessages(since sinceID: String? = nil, maxID: String? = nil, count: Int? = nil, page: Int? = nil, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "direct_messages/sent.json" - - var parameters = Dictionary() - parameters["since_id"] ??= sinceID - parameters["max_id"] ??= maxID - parameters["count"] ??= count - parameters["page"] ??= page - parameters["include_entities"] ??= includeEntities - - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) - } - - /** - GET direct_messages/show - - Returns a single direct message, specified by an id parameter. Like the /1.1/direct_messages.format request, this method will include the user objects of the sender and recipient. - */ - public func showDirectMessage(forID id: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "direct_messages/show.json" - let parameters: [String: Any] = ["id" : id] - - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) - } - - /** - POST direct_messages/destroy - - Destroys the direct message specified in the required ID parameter. The authenticating user must be the recipient of the specified direct message. - */ - public func destroyDirectMessage(forID id: String, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "direct_messages/destroy.json" - - var parameters = Dictionary() - parameters["id"] = id - parameters["include_entities"] ??= includeEntities - - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) - } - - /** - POST direct_messages/new - - Sends a new direct message to the specified user from the authenticating user. Requires both the user and text parameters and must be a POST. Returns the sent message in the requested format if successful. - */ - public func sendDirectMessage(to userTag: UserTag, text: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "direct_messages/new.json" - - var parameters = Dictionary() - parameters[userTag.key] = userTag.value - parameters["text"] = text - - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) - } + POST direct_messages/events/new + + Publishes a new message_create event resulting in a Direct Message sent to a specified user from the authenticating user. Returns an event if successful. Supports publishing Direct Messages with optional Quick Reply and media attachment. Replaces behavior currently provided by POST direct_messages/new. + Requires a JSON POST body and Content-Type header to be set to application/json. Setting Content-Length may also be required if it is not automatically. + + # Direct Message Rate Limiting + When a message is received from a user you may send up to 5 messages in response within a 24 hour window. Each message received resets the 24 hour window and the 5 allotted messages. Sending a 6th message within a 24 hour window or sending a message outside of a 24 hour window will count towards rate-limiting. This behavior only applies when using the POST direct_messages/events/new endpoint. + + */ + public func postDirectMessage(to recipientUserId: String, + message: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + let path = "direct_messages/events/new.json" + + var parameters: [String: Any] = [:] + parameters[Swifter.DataParameters.jsonDataKey] = "json-body" + parameters["json-body"] = [ + "event": [ + "type": "message_create", + "message_create": [ + "target": [ "recipient_id": recipientUserId ], + "message_data": ["text": message ] + ] + ] + ] + + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) + } } diff --git a/Sources/SwifterOAuthClient.swift b/Sources/SwifterOAuthClient.swift index bd926494..9cdefd65 100644 --- a/Sources/SwifterOAuthClient.swift +++ b/Sources/SwifterOAuthClient.swift @@ -52,7 +52,13 @@ internal class OAuthClient: SwifterClientProtocol { self.credential = Credential(accessToken: credentialAccessToken) } - func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + func get(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler?, + downloadProgress: HTTPRequest.DownloadProgressHandler?, + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { let url = URL(string: path, relativeTo: baseURL.url)! let request = HTTPRequest(url: url, method: .GET, parameters: parameters) @@ -66,29 +72,38 @@ internal class OAuthClient: SwifterClientProtocol { return request } - func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + func post(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + uploadProgress: HTTPRequest.UploadProgressHandler?, + downloadProgress: HTTPRequest.DownloadProgressHandler?, + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { let url = URL(string: path, relativeTo: baseURL.url)! var parameters = parameters var postData: Data? var postDataKey: String? - - if let key: Any = parameters[Swifter.DataParameters.dataKey] { - if let keyString = key as? String { - postDataKey = keyString - postData = parameters[keyString] as? Data - - parameters.removeValue(forKey: Swifter.DataParameters.dataKey) - parameters.removeValue(forKey: keyString) - } + var jsonData: [String: Any]? + + if let jsonDataKey = parameters[Swifter.DataParameters.jsonDataKey] as? String { + jsonData = parameters[jsonDataKey] as? [String: Any] + parameters.removeValue(forKey: Swifter.DataParameters.jsonDataKey) + parameters.removeValue(forKey: jsonDataKey) + } + + if let keyString = parameters[Swifter.DataParameters.dataKey] as? String { + postDataKey = keyString + postData = parameters[keyString] as? Data + + parameters.removeValue(forKey: Swifter.DataParameters.dataKey) + parameters.removeValue(forKey: keyString) } var postDataFileName: String? - if let fileName: Any = parameters[Swifter.DataParameters.fileNameKey] { - if let fileNameString = fileName as? String { - postDataFileName = fileNameString - parameters.removeValue(forKey: Swifter.DataParameters.fileNameKey) - } + if let fileName = parameters[Swifter.DataParameters.fileNameKey] as? String { + postDataFileName = fileName + parameters.removeValue(forKey: Swifter.DataParameters.fileNameKey) } let request = HTTPRequest(url: url, method: .POST, parameters: parameters) @@ -103,13 +118,34 @@ internal class OAuthClient: SwifterClientProtocol { let fileName = postDataFileName ?? "media.jpg" request.add(multipartData: postData, parameterName: postDataKey!, mimeType: "application/octet-stream", fileName: fileName) } + + if let jsonData = jsonData { + request.add(body: jsonData) + } request.start() return request } - - func authorizationHeader(for method: HTTPMethodType, url: URL, parameters: Dictionary, isMediaUpload: Bool) -> String { - var authorizationParameters = Dictionary() + + func delete(_ path: String, + baseURL: TwitterURL, + parameters: [String: Any], + success: HTTPRequest.SuccessHandler?, + failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url)! + + let request = HTTPRequest(url: url, method: .DELETE, parameters: parameters) + let authorizationHeader = self.authorizationHeader(for: .DELETE, url: url, parameters: parameters, isMediaUpload: false) + request.headers = ["Authorization": authorizationHeader] + request.successHandler = success + request.failureHandler = failure + request.dataEncoding = self.dataEncoding + request.start() + return request + } + + func authorizationHeader(for method: HTTPMethodType, url: URL, parameters: [String: Any], isMediaUpload: Bool) -> String { + var authorizationParameters = [String: Any]() authorizationParameters["oauth_version"] = OAuth.version authorizationParameters["oauth_signature_method"] = OAuth.signatureMethod authorizationParameters["oauth_consumer_key"] = self.consumerKey @@ -141,7 +177,7 @@ internal class OAuthClient: SwifterClientProtocol { return "OAuth " + headerComponents.joined(separator: ", ") } - func oauthSignature(for method: HTTPMethodType, url: URL, parameters: Dictionary, accessToken token: Credential.OAuthAccessToken?) -> String { + func oauthSignature(for method: HTTPMethodType, url: URL, parameters: [String: Any], accessToken token: Credential.OAuthAccessToken?) -> String { let tokenSecret = token?.secret.urlEncodedString() ?? "" let encodedConsumerSecret = self.consumerSecret.urlEncodedString() let signingKey = "\(encodedConsumerSecret)&\(tokenSecret)" diff --git a/Sources/SwifterPlaces.swift b/Sources/SwifterPlaces.swift index b1921ef4..b559be46 100644 --- a/Sources/SwifterPlaces.swift +++ b/Sources/SwifterPlaces.swift @@ -32,10 +32,13 @@ public extension Swifter { Returns all the information about a known place. */ - public func getGeoID(for placeID: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getGeoID(for placeID: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "geo/id/\(placeID).json" - - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -45,19 +48,24 @@ public extension Swifter { This request is an informative call and will deliver generalized results about geography. */ - public func getReverseGeocode(for coordinate: (lat: Double, long: Double), accuracy: String? = nil, granularity: String? = nil, maxResults: Int? = nil, callback: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getReverseGeocode(for coordinate: (lat: Double, long: Double), + accuracy: String? = nil, + granularity: String? = nil, + maxResults: Int? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "geo/reverse_geocode.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["lat"] = coordinate.lat parameters["long"] = coordinate.long - parameters["accuracy"] ??= accuracy parameters["granularity"] ??= granularity parameters["max_results"] ??= maxResults - parameters["callback"] ??= callback - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -69,26 +77,35 @@ public extension Swifter { This is the recommended method to use find places that can be attached to statuses/update. Unlike GET geo/reverse_geocode which provides raw data access, this endpoint can potentially re-order places with regards to the user who is authenticated. This approach is also preferred for interactive place matching with the user. */ - public func searchGeo(coordinate: (lat: Double, long: Double)? = nil, query: String? = nil, ipAddress: String? = nil, accuracy: String? = nil, granularity: String? = nil, maxResults: Int? = nil, containedWithin: String? = nil, attributeStreetAddress: String? = nil, callback: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - assert(coordinate != nil || query != nil || ipAddress != nil, "At least one of the following parameters must be provided to access this resource: coordinate, ipAddress, or query") + public func searchGeo(coordinate: (lat: Double, long: Double)? = nil, + query: String? = nil, + ipAddress: String? = nil, + accuracy: String? = nil, + granularity: String? = nil, + maxResults: Int? = nil, + containedWithin: String? = nil, + attributeStreetAddress: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + assert(coordinate != nil || query != nil || ipAddress != nil, + "At least one of the following parameters must be provided to access this resource: coordinate, ipAddress, or query") let path = "geo/search.json" - var parameters = Dictionary() + var parameters = [String: Any]() if let coordinate = coordinate { parameters["lat"] = coordinate.lat parameters["long"] = coordinate.long } else if let query = query { parameters["query"] = query } else if let ip = ipAddress { - parameters["ipAddress"] = ip + parameters["ip"] = ip } parameters["accuracy"] ??= accuracy parameters["granularity"] ??= granularity parameters["max_results"] ??= maxResults parameters["contained_within"] ??= containedWithin parameters["attribute:street_address"] ??= attributeStreetAddress - parameters["callback"] ??= callback self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } @@ -102,19 +119,24 @@ public extension Swifter { The token contained in the response is the token needed to be able to create a new place. */ - public func getSimilarPlaces(for coordinate: (lat: Double, long: Double), name: String, containedWithin: String? = nil, attributeStreetAddress: String? = nil, callback: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getSimilarPlaces(for coordinate: (lat: Double, long: Double), + name: String, + containedWithin: String? = nil, + attributeStreetAddress: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "geo/similar_places.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["lat"] = coordinate.lat parameters["long"] = coordinate.long parameters["name"] = name - parameters["contained_within"] ??= containedWithin parameters["attribute:street_address"] ??= attributeStreetAddress - parameters["callback"] ??= callback - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterSavedSearches.swift b/Sources/SwifterSavedSearches.swift index 422c098a..8a826cb2 100644 --- a/Sources/SwifterSavedSearches.swift +++ b/Sources/SwifterSavedSearches.swift @@ -32,10 +32,12 @@ public extension Swifter { Returns the authenticated user's saved search queries. */ - public func getSavedSearchesList(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getSavedSearchesList(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "saved_searches/list.json" - - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -43,10 +45,14 @@ public extension Swifter { Retrieve the information for the saved search represented by the given id. The authenticating user must be the owner of saved search ID being requested. */ - public func showSavedSearch(for id: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func showSavedSearch(for id: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "saved_searches/show/\(id).json" - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -54,13 +60,15 @@ public extension Swifter { Create a new saved search for the authenticated user. A user may only have 25 saved searches. */ - public func createSavedSearch(for query: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func createSavedSearch(for query: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "saved_searches/create.json" + let parameters = ["query": query] - var parameters = Dictionary() - parameters["query"] = query - - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -68,10 +76,13 @@ public extension Swifter { Destroys a saved search for the authenticating user. The authenticating user must be the owner of saved search id being destroyed. */ - public func deleteSavedSearch(for id: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func deleteSavedSearch(for id: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "saved_searches/destroy/\(id).json" - - self.postJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterSearch.swift b/Sources/SwifterSearch.swift index 2efb9ff4..5877806c 100644 --- a/Sources/SwifterSearch.swift +++ b/Sources/SwifterSearch.swift @@ -37,10 +37,23 @@ public extension Swifter { In API v1.1, the response format of the Search API has been improved to return Tweet objects more similar to the objects you’ll find across the REST API and platform. However, perspectival attributes (fields that pertain to the perspective of the authenticating user) are not currently supported on this endpoint. */ - public func searchTweet(using query: String, geocode: String? = nil, lang: String? = nil, locale: String? = nil, resultType: String? = nil, count: Int? = nil, until: String? = nil, sinceID: String? = nil, maxID: String? = nil, includeEntities: Bool? = nil, callback: String? = nil, tweetMode: TweetMode = TweetMode.default, success: ((JSON, _ searchMetadata: JSON) -> Void)? = nil, failure: @escaping FailureHandler) { + public func searchTweet(using query: String, + geocode: String? = nil, + lang: String? = nil, + locale: String? = nil, + resultType: String? = nil, + count: Int? = nil, + until: String? = nil, + sinceID: String? = nil, + maxID: String? = nil, + includeEntities: Bool? = nil, + callback: String? = nil, + tweetMode: TweetMode = TweetMode.default, + success: SearchResultHandler? = nil, + failure: @escaping FailureHandler) { let path = "search/tweets.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["q"] = query parameters["geocode"] ??= geocode parameters["lang"] ??= lang diff --git a/Sources/SwifterSpam.swift b/Sources/SwifterSpam.swift index 3f764c86..f9be7cc5 100644 --- a/Sources/SwifterSpam.swift +++ b/Sources/SwifterSpam.swift @@ -32,10 +32,11 @@ public extension Swifter { Report the specified user as a spam account to Twitter. Additionally performs the equivalent of POST blocks/create on behalf of the authenticated user. */ - public func reportSpam(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func reportSpam(for userTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/report_spam.json" let parameters: [String: Any] = [userTag.key: userTag.value] - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) diff --git a/Sources/SwifterStreaming.swift b/Sources/SwifterStreaming.swift index 3140c67d..662b0e61 100644 --- a/Sources/SwifterStreaming.swift +++ b/Sources/SwifterStreaming.swift @@ -46,7 +46,7 @@ public extension Swifter { let path = "statuses/filter.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filterLevel @@ -73,7 +73,7 @@ public extension Swifter { public func streamRandomSampleTweets(delimited: Bool? = nil, stallWarnings: Bool? = nil, filterLevel: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "statuses/sample.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filterLevel @@ -100,7 +100,7 @@ public extension Swifter { public func streamFirehoseTweets(count: Int? = nil, delimited: Bool? = nil, stallWarnings: Bool? = nil, filterLevel: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "statuses/firehose.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["count"] ??= count parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings @@ -125,7 +125,7 @@ public extension Swifter { public func beginUserStream(delimited: Bool? = nil, stallWarnings: Bool? = nil, includeMessagesFromUserOnly: Bool = false, includeReplies: Bool = false, track: [String]? = nil, locations: [String]? = nil, stringifyFriendIDs: Bool? = nil, filterLevel: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "user.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filterLevel @@ -158,7 +158,7 @@ public extension Swifter { public func beginSiteStream(delimited: Bool? = nil, stallWarnings: Bool? = nil, restrictToUserMessages: Bool = false, includeReplies: Bool = false, stringifyFriendIDs: Bool? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "site.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["stringify_friend_ids"] ??= stringifyFriendIDs diff --git a/Sources/SwifterSuggested.swift b/Sources/SwifterSuggested.swift index 4986ff38..1f2f2085 100644 --- a/Sources/SwifterSuggested.swift +++ b/Sources/SwifterSuggested.swift @@ -34,13 +34,18 @@ public extension Swifter { It is recommended that applications cache this data for no more than one hour. */ - public func getUserSuggestions(slug: String, lang: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUserSuggestions(slug: String, + lang: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/suggestions/\(slug).json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["lang"] ??= lang - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -48,13 +53,17 @@ public extension Swifter { Access to Twitter's suggested user list. This returns the list of suggested user categories. The category can be used in GET users/suggestions/:slug to get the users in that category. */ - public func getUsersSuggestions(lang: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUsersSuggestions(lang: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/suggestions.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["lang"] ??= lang - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -62,9 +71,13 @@ public extension Swifter { Access the users in a given category of the Twitter suggested user list and return their most recent status if they are not a protected user. */ - public func getUsersSuggestions(for slug: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getUsersSuggestions(for slug: String, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/suggestions/\(slug)/members.json" - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterTimelines.swift b/Sources/SwifterTimelines.swift index 26b78af5..f1235e97 100644 --- a/Sources/SwifterTimelines.swift +++ b/Sources/SwifterTimelines.swift @@ -27,8 +27,19 @@ import Foundation public extension Swifter { - // Convenience method - private func getTimeline(at path: String, parameters: Dictionary, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + // Convenience method to help us get a timeline + // per path and its parameters + private func getTimeline(at path: String, + parameters: [String: Any], + count: Int? = nil, + sinceID: String? = nil, + maxID: String? = nil, + trimUser: Bool? = nil, + contributorDetails: Bool? = nil, + includeEntities: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { var params = parameters params["count"] ??= count params["since_id"] ??= sinceID @@ -38,21 +49,9 @@ public extension Swifter { params["include_entities"] ??= includeEntities params["tweet_mode"] ??= tweetMode.stringValue - self.getJSON(path: path, baseURL: .api, parameters: params, success: { json, _ in - success?(json) - }, failure: failure) - } - - /** - GET statuses/user_timeline - Returns Tweets (*: tweets for the user) - - Returns a collection of the most recent Tweets posted by the user based on custom parameters. - */ - public func getTimeline(withCustomParameters params:Dictionary, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - self.getJSON(path: "statuses/user_timeline.json", baseURL: .api, parameters: params, success: { json, _ in - success?(json) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: params, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -66,7 +65,17 @@ public extension Swifter { This method can only return up to 800 tweets. */ public func getMentionsTimelineTweets(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler?) { - self.getTimeline(at: "statuses/mentions_timeline.json", parameters: [:], count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, tweetMode: tweetMode, success: success, failure: failure) + self.getTimeline(at: "statuses/mentions_timeline.json", + parameters: [:], + count: count, + sinceID: sinceID, + maxID: maxID, + trimUser: trimUser, + contributorDetails: contributorDetails, + includeEntities: includeEntities, + tweetMode: tweetMode, + success: success, + failure: failure) } @@ -82,10 +91,30 @@ public extension Swifter { This method can only return up to 3,200 of a user's most recent Tweets. Native retweets of other statuses by the user is included in this total, regardless of whether include_rts is set to false when requesting this resource. */ - public func getTimeline(for userID: String, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let parameters: Dictionary = ["user_id": userID] - - self.getTimeline(at: "statuses/user_timeline.json", parameters: parameters, count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, tweetMode: tweetMode, success: success, failure: failure) + public func getTimeline(for userID: String, + customParam: [String: Any] = [:], + count: Int? = nil, + sinceID: String? = nil, + maxID: String? = nil, + trimUser: Bool? = nil, + contributorDetails: Bool? = nil, + includeEntities: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + var parameters: [String: Any] = customParam + parameters["user_id"] = userID + self.getTimeline(at: "statuses/user_timeline.json", + parameters: parameters, + count: count, + sinceID: sinceID, + maxID: maxID, + trimUser: trimUser, + contributorDetails: contributorDetails, + includeEntities: includeEntities, + tweetMode: tweetMode, + success: success, + failure: failure) } /** @@ -97,8 +126,26 @@ public extension Swifter { Up to 800 Tweets are obtainable on the home timeline. It is more volatile for users that follow many users or follow users who tweet frequently. */ - public func getHomeTimeline(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - self.getTimeline(at: "statuses/home_timeline.json", parameters: [:], count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, tweetMode: tweetMode, success: success, failure: failure) + public func getHomeTimeline(count: Int? = nil, + sinceID: String? = nil, + maxID: String? = nil, + trimUser: Bool? = nil, + contributorDetails: Bool? = nil, + includeEntities: Bool? = nil, + tweetMode: TweetMode = TweetMode.default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + self.getTimeline(at: "statuses/home_timeline.json", + parameters: [:], + count: count, + sinceID: sinceID, + maxID: maxID, + trimUser: trimUser, + contributorDetails: contributorDetails, + includeEntities: includeEntities, + tweetMode: tweetMode, + success: success, + failure: failure) } /** @@ -106,8 +153,26 @@ public extension Swifter { Returns the most recent tweets authored by the authenticating user that have been retweeted by others. This timeline is a subset of the user's GET statuses/user_timeline. See Working with Timelines for instructions on traversing timelines. */ - public func getRetweetsOfMe(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - self.getTimeline(at: "statuses/retweets_of_me.json", parameters: [:], count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, tweetMode: tweetMode, success: success, failure: failure) + public func getRetweetsOfMe(count: Int? = nil, + sinceID: String? = nil, + maxID: String? = nil, + trimUser: Bool? = nil, + contributorDetails: Bool? = nil, + includeEntities: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + self.getTimeline(at: "statuses/retweets_of_me.json", + parameters: [:], + count: count, + sinceID: sinceID, + maxID: maxID, + trimUser: trimUser, + contributorDetails: contributorDetails, + includeEntities: includeEntities, + tweetMode: tweetMode, + success: success, + failure: failure) } } diff --git a/Sources/SwifterTrends.swift b/Sources/SwifterTrends.swift index 3ab92cf6..ae3c1be2 100644 --- a/Sources/SwifterTrends.swift +++ b/Sources/SwifterTrends.swift @@ -19,14 +19,18 @@ public extension Swifter { This information is cached for 5 minutes. Requesting more frequently than that will not return any more data, and will count against your rate limit usage. */ - public func getTrendsPlace(with woeid: String, excludeHashtags: Bool = false, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getTrendsPlace(with woeid: String, + excludeHashtags: Bool = false, + success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "trends/place.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["id"] = woeid parameters["exclude"] = excludeHashtags ? "hashtags" : nil - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -38,9 +42,12 @@ public extension Swifter { A WOEID is a Yahoo! Where On Earth ID. */ - public func getAvailableTrends(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getAvailableTrends(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "trends/available.json" - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -52,13 +59,14 @@ public extension Swifter { A WOEID is a Yahoo! Where On Earth ID. */ - public func getClosestTrends(for coordinate: (lat: Double, long: Double), success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getClosestTrends(for coordinate: (lat: Double, long: Double), + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "trends/closest.json" - - var parameters = Dictionary() - parameters["lat"] = coordinate.lat - parameters["long"] = coordinate.long - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + let parameters = ["lat": coordinate.lat, "long": coordinate.long] + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterTweets.swift b/Sources/SwifterTweets.swift index d7406bf3..40a857de 100644 --- a/Sources/SwifterTweets.swift +++ b/Sources/SwifterTweets.swift @@ -32,14 +32,21 @@ public extension Swifter { Returns up to 100 of the first retweets of a given tweet. */ - public func getRetweets(forTweetID id: String, count: Int? = nil, trimUser: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getRetweets(forTweetID id: String, + count: Int? = nil, + trimUser: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/retweets/\(id).json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["count"] ??= count parameters["trim_user"] ??= trimUser parameters["tweet_mode"] ??= tweetMode.stringValue - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -61,15 +68,22 @@ public extension Swifter { This object contains an array of user IDs for users who have contributed to this status (an example of a status that has been contributed to is this one). In practice, there is usually only one ID in this array. The JSON renders as such "contributors":[8285392]. */ - public func getTweet(forID id: String, count: Int? = nil, trimUser: Bool? = nil, includeMyRetweet: Bool? = nil, includeEntities: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getTweet(for id: String, + trimUser: Bool? = nil, + includeMyRetweet: Bool? = nil, + includeEntities: Bool? = nil, + includeExtAltText: Bool? = nil, + tweetMode: TweetMode = TweetMode.default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/show.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["id"] = id - parameters["count"] ??= count parameters["trim_user"] ??= trimUser parameters["include_my_retweet"] ??= includeMyRetweet parameters["include_entities"] ??= includeEntities + parameters["include_ext_alt_text"] ??= includeExtAltText parameters["tweet_mode"] ??= tweetMode.stringValue self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) @@ -80,14 +94,20 @@ public extension Swifter { Destroys the status specified by the required ID parameter. The authenticating user must be the author of the specified status. Returns the destroyed status if successful. */ - public func destroyTweet(forID id: String, trimUser: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func destroyTweet(forID id: String, + trimUser: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/destroy/\(id).json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["trim_user"] ??= trimUser parameters["tweet_mode"] ??= tweetMode.stringValue - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -113,13 +133,27 @@ public extension Swifter { - https://dev.twitter.com/docs/api/multiple-media-extended-entities */ - public func postTweet(status: String, inReplyToStatusID: String? = nil, coordinate: (lat: Double, long: Double)? = nil, placeID: Double? = nil, displayCoordinates: Bool? = nil, trimUser: Bool? = nil, mediaIDs: [String] = [], attachmentURL: URL? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func postTweet(status: String, + inReplyToStatusID: String? = nil, + coordinate: (lat: Double, long: Double)? = nil, + autoPopulateReplyMetadata: Bool? = nil, + excludeReplyUserIds: Bool? = nil, + placeID: Double? = nil, + displayCoordinates: Bool? = nil, + trimUser: Bool? = nil, + mediaIDs: [String] = [], + attachmentURL: URL? = nil, + tweetMode: TweetMode = TweetMode.default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path: String = "statuses/update.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["status"] = status parameters["in_reply_to_status_id"] ??= inReplyToStatusID + parameters["auto_populate_reply_metadata"] ??= autoPopulateReplyMetadata + parameters["exclude_reply_user_ids"] ??= excludeReplyUserIds parameters["trim_user"] ??= trimUser parameters["attachment_url"] ??= attachmentURL parameters["tweet_mode"] ??= tweetMode.stringValue @@ -142,11 +176,24 @@ public extension Swifter { }, failure: failure) } - public func postTweet(status: String, media: Data, inReplyToStatusID: String? = nil, coordinate: (lat: Double, long: Double)? = nil, placeID: Double? = nil, displayCoordinates: Bool? = nil, trimUser: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func postTweet(status: String, + media: Data, + inReplyToStatusID: String? = nil, + autoPopulateReplyMetadata: Bool? = nil, + excludeReplyUserIds: Bool? = nil, + coordinate: (lat: Double, long: Double)? = nil, + placeID: Double? = nil, + displayCoordinates: Bool? = nil, + trimUser: Bool? = nil, + tweetMode: TweetMode = TweetMode.default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path: String = "statuses/update_with_media.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["status"] = status + parameters["auto_populate_reply_metadata"] ??= autoPopulateReplyMetadata + parameters["exclude_reply_user_ids"] ??= excludeReplyUserIds parameters["media[]"] = media parameters[Swifter.DataParameters.dataKey] = "media[]" parameters["in_reply_to_status_id"] ??= inReplyToStatusID @@ -166,6 +213,28 @@ public extension Swifter { success?(json) }, failure: failure) } + + public func postTweetWithGif(attachmentUrl: URL, text: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + guard let data = try? Data(contentsOf: attachmentUrl) else { + let error = SwifterError(message: "Found invalid GIF Data", kind: .invalidGifData) + failure?(error) + return + } + + self.prepareUpload(data: data, success: { json, response in + if let media_id = json["media_id_string"].string { + self.uploadGIF(media_id, data: data, name: attachmentUrl.lastPathComponent, success: { json, response in + self.finalizeUpload(mediaId: media_id, success: { json, resoponse in + self.postTweet(status: text, mediaIDs: [media_id], success: success, failure: failure) + }, failure: failure) + }, failure: failure) + } + else { + let error = SwifterError(message: "Bad Response for GIF Upload", kind: .invalidGifResponse) + failure?(error) + } + }, failure: failure) + } /** POST media/upload @@ -177,14 +246,19 @@ public extension Swifter { - https://dev.twitter.com/rest/public/uploading-media - https://dev.twitter.com/rest/reference/post/media/upload */ - public func postMedia(_ media: Data, additionalOwners: UsersTag? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func postMedia(_ media: Data, + additionalOwners: UsersTag? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path: String = "media/upload.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["media"] = media parameters["additional_owers"] ??= additionalOwners?.value parameters[Swifter.DataParameters.dataKey] = "media" - self.postJSON(path: path, baseURL: .upload, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .upload, parameters: parameters, success: { + json, _ in success?(json) + }, failure: failure) } /** @@ -198,10 +272,14 @@ public extension Swifter { Returns Tweets (1: the new tweet) */ - public func retweetTweet(forID id: String, trimUser: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func retweetTweet(forID id: String, + trimUser: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/retweet/\(id).json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["trim_user"] ??= trimUser parameters["tweet_mode"] ??= tweetMode.stringValue @@ -220,14 +298,20 @@ public extension Swifter { Returns Tweets (1: the original tweet) */ - public func unretweetTweet(forID id: String, trimUser: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func unretweetTweet(forID id: String, + trimUser: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/unretweet/\(id).json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["trim_user"] ??= trimUser parameters["tweet_mode"] ??= tweetMode.stringValue - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -237,26 +321,19 @@ public extension Swifter { While this endpoint allows a bit of customization for the final appearance of the embedded Tweet, be aware that the appearance of the rendered Tweet may change over time to be consistent with Twitter's Display Requirements. Do not rely on any class or id parameters to stay constant in the returned markup. */ - public func oembedInfo(forID id: String, maxWidth: Int? = nil, hideMedia: Bool? = nil, hideThread: Bool? = nil, omitScript: Bool? = nil, align: String? = nil, related: String? = nil, lang: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "statuses/oembed.json" - - var parameters = Dictionary() - parameters["id"] = id - parameters["max_width"] ??= maxWidth - parameters["hide_media"] ??= hideMedia - parameters["hide_thread"] ??= hideThread - parameters["omit_scipt"] ??= omitScript - parameters["align"] ??= align - parameters["related"] ??= related - parameters["lang"] ??= lang - - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) - } - - public func oembedInfo(forUrl url: URL, maxWidth: Int? = nil, hideMedia: Bool? = nil, hideThread: Bool? = nil, omitScript: Bool? = nil, align: String? = nil, related: String? = nil, lang: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - let path = "statuses/oembed.json" - - var parameters = Dictionary() + public func oembedInfo(for url: URL, + maxWidth: Int? = nil, + hideMedia: Bool? = nil, + hideThread: Bool? = nil, + omitScript: Bool? = nil, + align: String? = nil, + related: String? = nil, + lang: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + let path = "oembed" + + var parameters = [String: Any]() parameters["url"] = url.absoluteString parameters["max_width"] ??= maxWidth parameters["hide_media"] ??= hideMedia @@ -266,7 +343,9 @@ public extension Swifter { parameters["related"] ??= related parameters["lang"] ??= lang - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .publish, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -276,11 +355,17 @@ public extension Swifter { This method offers similar data to GET statuses/retweets/:id and replaces API v1's GET statuses/:id/retweeted_by/ids method. */ - public func tweetRetweeters(forID id: String, cursor: String? = nil, stringifyIDs: Bool? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func tweetRetweeters(for id: String, + count: Int? = nil, + cursor: String? = nil, + stringifyIDs: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/retweeters/ids.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["id"] = id + parameters["count"] = count parameters["cursor"] ??= cursor parameters["stringify_ids"] ??= stringifyIDs @@ -294,16 +379,27 @@ public extension Swifter { Returns fully-hydrated tweet objects for up to 100 tweets per request, as specified by comma-separated values passed to the id parameter. This method is especially useful to get the details (hydrate) a collection of Tweet IDs. GET statuses/show/:id is used to retrieve a single tweet object. */ - public func lookupTweets(for tweetIDs: [String], includeEntities: Bool? = nil, map: Bool? = nil, tweetMode: TweetMode = TweetMode.default, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func lookupTweets(for tweetIDs: [String], + includeEntities: Bool? = nil, + trimUser: Bool? = nil, + map: Bool? = nil, + includeExtAltText: Bool? = nil, + tweetMode: TweetMode = .default, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "statuses/lookup.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["id"] = tweetIDs.joined(separator: ",") + parameters["trim_user"] ??= trimUser parameters["include_entities"] ??= includeEntities + parameters["include_ext_alt_text"] ??= includeExtAltText parameters["map"] ??= map parameters["tweet_mode"] ??= tweetMode.stringValue - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } } diff --git a/Sources/SwifterUsers.swift b/Sources/SwifterUsers.swift index 9c6dcc07..9b21b77e 100644 --- a/Sources/SwifterUsers.swift +++ b/Sources/SwifterUsers.swift @@ -32,10 +32,13 @@ public extension Swifter { Returns settings (including current trend, geo and sleep time information) for the authenticating user. */ - public func getAccountSettings(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getAccountSettings(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "account/settings.json" - self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -43,14 +46,19 @@ public extension Swifter { Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful; returns a 401 status code and an error message if not. Use this method to test if supplied user credentials are valid. */ - public func verifyAccountCredentials(includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func verifyAccountCredentials(includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "account/verify_credentials.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -58,12 +66,19 @@ public extension Swifter { Updates the authenticating user's settings. */ - public func updateAccountSettings(trendLocationWOEID: String? = nil, sleepTimeEnabled: Bool? = nil, startSleepTime: Int? = nil, endSleepTime: Int? = nil, timeZone: String? = nil, lang: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func updateAccountSettings(trendLocationWOEID: String? = nil, + sleepTimeEnabled: Bool? = nil, + startSleepTime: Int? = nil, + endSleepTime: Int? = nil, + timeZone: String? = nil, + lang: String? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { assert(trendLocationWOEID != nil || sleepTimeEnabled != nil || startSleepTime != nil || endSleepTime != nil || timeZone != nil || lang != nil, "At least one or more should be provided when executing this request") let path = "account/settings.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["trend_location_woeid"] ??= trendLocationWOEID parameters["sleep_time_enabled"] ??= sleepTimeEnabled parameters["start_sleep_time"] ??= startSleepTime @@ -79,12 +94,20 @@ public extension Swifter { Sets values that users are able to set under the "Account" tab of their settings page. Only the parameters specified will be updated. */ - public func updateUserProfile(name: String? = nil, url: String? = nil, location: String? = nil, description: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - assert(name != nil || url != nil || location != nil || description != nil || includeEntities != nil || skipStatus != nil) + public func updateUserProfile(name: String? = nil, + url: String? = nil, + location: String? = nil, + description: String? = nil, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + assert(name != nil || url != nil || location != nil || description != nil, + "At least one of name, url, location, description should be non-nil") let path = "account/update_profile.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["name"] ??= name parameters["url"] ??= url parameters["location"] ??= location @@ -92,7 +115,9 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -100,18 +125,25 @@ public extension Swifter { Updates the authenticating user's profile background image. This method can also be used to enable or disable the profile background image. Although each parameter is marked as optional, at least one of image, tile or use must be provided when making this request. */ - public func updateProfileBackground(using imageData: Data, title: String? = nil, includeEntities: Bool? = nil, use: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { - assert(title != nil || use != nil, "At least one of image, tile or use must be provided when making this request") + public func updateProfileBackground(using imageData: Data, + title: String? = nil, + includeEntities: Bool? = nil, + use: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { + assert(title != nil || use != nil, "At least one of image, title or use must be provided when making this request") let path = "account/update_profile_background_image.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["image"] = imageData.base64EncodedString(options: []) parameters["title"] ??= title parameters["include_entities"] ??= includeEntities parameters["use"] ??= use - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -119,10 +151,18 @@ public extension Swifter { Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. Each parameter's value must be a valid hexidecimal value, and may be either three or six characters (ex: #fff or #ffffff). */ - public func updateProfileColors(backgroundColor: String? = nil, linkColor: String? = nil, sidebarBorderColor: String? = nil, sidebarFillColor: String? = nil, textColor: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { + public func updateProfileColors(backgroundColor: String? = nil, + linkColor: String? = nil, + sidebarBorderColor: String? = nil, + sidebarFillColor: String? = nil, + textColor: String? = nil, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "account/update_profile_colors.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["profile_background_color"] ??= backgroundColor parameters["profile_link_color"] ??= linkColor parameters["profile_sidebar_link_color"] ??= sidebarBorderColor @@ -131,7 +171,9 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -141,15 +183,21 @@ public extension Swifter { This method asynchronously processes the uploaded file before updating the user's profile image URL. You can either update your local cache the next time you request the user's information, or, at least 5 seconds after uploading the image, ask for the updated URL using GET users/show. */ - public func updateProfileImage(using imageData: Data, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func updateProfileImage(using imageData: Data, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "account/update_profile_image.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["image"] = imageData.base64EncodedString(options: []) parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -157,10 +205,14 @@ public extension Swifter { Returns a collection of user objects that the authenticating user is blocking. */ - public func getBlockedUsers(includeEntities: Bool? = nil, skipStatus: Bool? = nil, cursor: String? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getBlockedUsers(includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "blocks/list.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus parameters["cursor"] ??= cursor @@ -175,10 +227,13 @@ public extension Swifter { Returns an array of numeric user ids the authenticating user is blocking. */ - public func getBlockedUsersIDs(stringifyIDs: String? = nil, cursor: String? = nil, success: CursorSuccessHandler? = nil, failure: @escaping FailureHandler) { + public func getBlockedUsersIDs(stringifyIDs: String? = nil, + cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "blocks/ids.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["stringify_ids"] ??= stringifyIDs parameters["cursor"] ??= cursor @@ -192,15 +247,21 @@ public extension Swifter { Blocks the specified user from following the authenticating user. In addition the blocked user will not show in the authenticating users mentions or timeline (unless retweeted by another user). If a follow or friend relationship exists it is destroyed. */ - public func blockUser(for userTag: UserTag, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { + public func blockUser(_ userTag: UserTag, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "blocks/create.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -208,15 +269,21 @@ public extension Swifter { Un-blocks the user specified in the ID parameter for the authenticating user. Returns the un-blocked user in the requested format when successful. If relationships existed before the block was instated, they will not be restored. */ - public func unblockUser(for userTag: UserTag, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { + public func unblockUser(for userTag: UserTag, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "blocks/destroy.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -236,14 +303,19 @@ public extension Swifter { - If none of your lookup criteria can be satisfied by returning a user object, a HTTP 404 will be thrown. - You are strongly encouraged to use a POST for larger requests. */ - public func lookupUsers(for usersTag: UsersTag, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { + public func lookupUsers(for usersTag: UsersTag, + includeEntities: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/lookup.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[usersTag.key] = usersTag.value parameters["include_entities"] ??= includeEntities - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -253,14 +325,19 @@ public extension Swifter { You must be following a protected user to be able to see their most recent Tweet. If you don't follow a protected user, the users Tweet will be removed. A Tweet will not always be returned in the current_status field. */ - public func showUser(for userTag: UserTag, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { + public func showUser(_ userTag: UserTag, + includeEntities: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/show.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -270,16 +347,23 @@ public extension Swifter { Only the first 1,000 matching results are available. */ - public func searchUsers(using query: String, page: Int?, count: Int?, includeEntities: Bool?, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { + public func searchUsers(using query: String, + page: Int? = nil, + count: Int? = nil, + includeEntities: Bool? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/search.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["q"] = query parameters["page"] ??= page parameters["count"] ??= count parameters["include_entities"] ??= includeEntities - self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -287,10 +371,13 @@ public extension Swifter { Removes the uploaded profile banner for the authenticating user. Returns HTTP 200 upon success. */ - public func removeProfileBanner(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func removeProfileBanner(success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "account/remove_profile_banner.json" - self.postJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -307,17 +394,25 @@ public extension Swifter { 400 Either an image was not provided or the image data could not be processed 422 The image could not be resized or is too large. */ - public func updateProfileBanner(using imageData: Data, width: Int? = nil, height: Int? = nil, offsetLeft: Int? = nil, offsetTop: Int? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func updateProfileBanner(using imageData: Data, + width: Int? = nil, + height: Int? = nil, + offsetLeft: Int? = nil, + offsetTop: Int? = nil, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "account/update_profile_banner.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["banner"] = imageData.base64EncodedString parameters["width"] ??= width parameters["height"] ??= height parameters["offset_left"] ??= offsetLeft parameters["offset_top"] ??= offsetTop - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -325,11 +420,15 @@ public extension Swifter { Returns a map of the available size variations of the specified user's profile banner. If the user has not uploaded a profile banner, a HTTP 404 will be served instead. This method can be used instead of string manipulation on the profile_banner_url returned in user objects as described in User Profile Images and Banners. */ - public func getProfileBanner(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getProfileBanner(for userTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "users/profile_banner.json" let parameters: [String: Any] = [userTag.key: userTag.value] - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -341,11 +440,15 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func muteUser(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func muteUser(_ userTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "mutes/users/create.json" let parameters: [String: Any] = [userTag.key: userTag.value] - self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) + }, failure: failure) } /** @@ -357,11 +460,11 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func unmuteUser(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { + public func unmuteUser(for userTag: UserTag, + success: SuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "mutes/users/destroy.json" - - var parameters = Dictionary() - parameters[userTag.key] = userTag.value + let parameters = [userTag.key: userTag.value] self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) @@ -373,10 +476,12 @@ public extension Swifter { Returns an array of numeric user ids the authenticating user has muted. */ - public func getMuteUsersIDs(cursor: String? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getMuteUsersIDs(cursor: String? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "mutes/users/ids.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["cursor"] ??= cursor self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in @@ -389,10 +494,14 @@ public extension Swifter { Returns an array of user objects the authenticating user has muted. */ - public func getMuteUsers(cursor: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { + public func getMutedUsers(cursor: String? = nil, + includeEntities: Bool? = nil, + skipStatus: Bool? = nil, + success: CursorSuccessHandler? = nil, + failure: FailureHandler? = nil) { let path = "mutes/users/list.json" - var parameters = Dictionary() + var parameters = [String: Any]() parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus parameters["cursor"] ??= cursor diff --git a/Swifter.podspec b/Swifter.podspec index eacde090..16319893 100644 --- a/Swifter.podspec +++ b/Swifter.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "Swifter" - s.version = "2.1.0" + s.version = "2.2.0" s.summary = ":bird: A Twitter framework for iOS & macOS written in Swift" s.description = <<-DESC Twitter framework for iOS & macOS written in Swift, with support of three different types of authentication protocol, and most, if not all, of the REST API. diff --git a/Swifter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Swifter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Swifter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SwifterDemoMac/ViewController.swift b/SwifterDemoMac/ViewController.swift index 81477661..8ed0045c 100644 --- a/SwifterDemoMac/ViewController.swift +++ b/SwifterDemoMac/ViewController.swift @@ -60,7 +60,8 @@ class ViewController: NSViewController { }, failure: failureHandler) } } else { - let swifter = Swifter(consumerKey: "RErEmzj7ijDkJr60ayE2gjSHT", consumerSecret: "SbS0CHk11oJdALARa7NDik0nty4pXvAxdt7aj0R5y1gNzWaNEx") + let swifter = Swifter(consumerKey: "nLl1mNYc25avPPF4oIzMyQzft", + consumerSecret: "Qm3e5JTXDhbbLl44cq6WdK00tSUwa17tWlO8Bf70douE4dcJe2") swifter.authorize(with: URL(string: "swifter://success")!, success: { _, _ in swifter.getHomeTimeline(count: 100, success: { statuses in guard let tweets = statuses.array else { return } diff --git a/SwifterDemoiOS/AuthViewController.swift b/SwifterDemoiOS/AuthViewController.swift index 98c2168c..8b704355 100755 --- a/SwifterDemoiOS/AuthViewController.swift +++ b/SwifterDemoiOS/AuthViewController.swift @@ -37,7 +37,8 @@ class AuthViewController: UIViewController, SFSafariViewControllerDelegate { let useACAccount = false required init?(coder aDecoder: NSCoder) { - self.swifter = Swifter(consumerKey: "RErEmzj7ijDkJr60ayE2gjSHT", consumerSecret: "SbS0CHk11oJdALARa7NDik0nty4pXvAxdt7aj0R5y1gNzWaNEx") + self.swifter = Swifter(consumerKey: "nLl1mNYc25avPPF4oIzMyQzft", + consumerSecret: "Qm3e5JTXDhbbLl44cq6WdK00tSUwa17tWlO8Bf70douE4dcJe2") super.init(coder: aDecoder) } @@ -70,7 +71,7 @@ class AuthViewController: UIViewController, SFSafariViewControllerDelegate { } } else { let url = URL(string: "swifter://success")! - swifter.authorize(with: url, presentFrom: self, success: { _, _ in + swifter.authorize(withCallback: url, presentingFrom: self, success: { _, _ in self.fetchTwitterHomeStream() }, failure: failureHandler) }