diff --git a/README.md b/README.md index 957852d5..ce1ac196 100644 --- a/README.md +++ b/README.md @@ -4,92 +4,99 @@ ## Getting Started -###Installation +### Installation 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. -### Swift 3 +### Usage -For those using Xcode 8 and Swift 3, there is a [swift-3 branch](https://github.com/mattdonnelly/Swifter/tree/swift-3) of Swifter available. It will be merged to master once Swift 3 exits beta later this year. - - -###Usage - -Swifter can be used with the 3 different kinds of authentication protocols Twitter allows. You can specify which protocol to use as show below. +Swifter can be used with the 3 different kinds of authentication protocols Twitter allows. You can specify which protocol to use as show below. For more information on each of the authentication protocols, please check [Twitter OAuth Help](https://dev.twitter.com/oauth). Instantiation with ACAccount: ```swift -let swifter = Swifter(account: twitterAccount) -``` -Instantiation with OAuth: +// Instantiation using ACAccount +var swifter = Swifter(account: twitterAccount) -```swift -let swifter = Swifter(consumerKey: "", consumerSecret: "") -``` +// Instantiation using Twitter's OAuth Consumer Key and secret +swifter = Swifter(consumerKey: TWITTER_CONSUMER_KEY, consumerSecret: TWITTER_CONSUMER_SECRET) -Instantiation with App Only Auth: +// Instantiation using App-Only authentication +swifter = Swifter(consumerKey: TWITTER_CONSUMER_KEY, consumerSecret: TWITTER_CONSUMER_SECRET, appOnly: true) -```swift -let swifter = Swifter(consumerKey: "", consumerSecret: "", appOnly: true) ``` -##Example Requests +## Example Requests -####OAuth Authorization: +#### OAuth Authorization: ```swift -swifter.authorizeWithCallbackURL(callbackURL, success: { accessToken, response in - // ... - - }, failure: { error in +swifter.authorize(with: callbackURL, success: { accessToken, response in + // ... +}, failure: { error in + // ... +}) +``` - // ... +#### Get Home Timeline: +```swift +swifter.getHomeTimeline(count: 50, success: { json in + // ... +}, failure: { error in + // ... }) ``` -####Get Home Timeline: +#### Using API that allows for `user_id`/`screenName` or `list_id`/`slug` +Certain Twitter API allows you to use either the `user_id` or `screen_name` to get user related objects (and `list_id`/`slug` for lists). Swifter offers a solution so that the user won't accidentally use the wrong method, and have nothing returned. For more information, check the `SwifterTag.swift` file. ```swift -swifter.getStatusesHomeTimelineWithCount(20, success: { statuses in - // ... - - }, failure: { error in +swifter.getUserFollowersIDs(for: .id(userId), success: { json, prev, next in + // alternatively, you can use .screenName(userName) + // ... +}, failure: { error in // ... - }) -``` -####Streaming API: +``` ```swift -swifter.getStatusesSampleDelimited(progress: { status in - // ... - }, stallWarnings: { code, message, percentFull in +swifter.getListSubscribers(for: .slug(listSlug, owner: .screenName(userName)), success: { json, prev, next in + // alternatively, you can use .id(listId) // ... - - }, failure: { error in +}, failure: { error in // ... - }) + ``` +Additionally, there is also `.screenName(arrayOfUserNames)` and `.id(arrayOfIds)` for methods that take arrays of screen names, or userIDs -####Status Update: +#### Streaming API: ```swift -swifter.postStatusUpdate("Hello, world", success: { status in - // ... +swifter.streamRandomSampleTweets(progress: { status in + // ... +}, stallWarnings: { code, message, percentFull in + // ... +}, failure: { error in + // ... +}) +``` - }, failure: { error in - // ... +#### Status Update: +```swift +swifter.postTweet(status: "Hello, world.", success: { status in + // ... +}, failure: { error in + // ... }) ``` -##JSON Handling +## JSON Handling To make accessing data returned by twitter requests, Swifter provides a class for representing JSON which you interact with similarly to a dictionary. The main advantage of using this instead of a Dictionary is that it works better with Swift's strict typing system and doesn't require you to constantly downcast accessed objects. It also removes the need for lots optional chaining, making your code much cleaner and easier to read. @@ -101,10 +108,10 @@ if let statusText = statuses[0]["text"].string { } ``` -##OAuth Consumer Tokens +## OAuth Consumer Tokens -In Twitter REST API v1.1, each client application must authenticate itself with consumer key and consumer secret tokens. You can request consumer tokens for your app on Twitter's website: https://dev.twitter.com/apps. +In Twitter REST API v1.1, each client application must authenticate itself with consumer key and consumer secret tokens. You can request consumer tokens for your app on [Twitter's dev website](https://dev.twitter.com/apps) -#License +# License Swifter is licensed under the MIT License. See the LICENSE file for more information. diff --git a/Swifter.xcodeproj/project.pbxproj b/Swifter.xcodeproj/project.pbxproj index 0c792d34..da7b5b57 100644 --- a/Swifter.xcodeproj/project.pbxproj +++ b/Swifter.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 8B1F25A0194A663D004BD304 /* SwifterHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B1F259E194A663D004BD304 /* SwifterHelp.swift */; }; 8B300C3E1944AA6600993175 /* SwifteriOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F50EE1944A5AB00894629 /* SwifteriOS.framework */; }; 8B300C3F1944AA6600993175 /* SwifteriOS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F50EE1944A5AB00894629 /* SwifteriOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 8B300C4C1944AECB00993175 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B300C4B1944AECB00993175 /* main.swift */; }; 8B300C4E1944AECB00993175 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B300C4D1944AECB00993175 /* ViewController.swift */; }; 8B300C501944AECB00993175 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B300C4F1944AECB00993175 /* AppDelegate.swift */; }; 8B300C521944AECB00993175 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B300C511944AECB00993175 /* Images.xcassets */; }; @@ -26,8 +25,8 @@ 8B548F5A1945325E00EE2927 /* SwifterAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B548F581945325E00EE2927 /* SwifterAuth.swift */; }; 8B548F5C1945331D00EE2927 /* SwifterTimelines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B548F5B1945331D00EE2927 /* SwifterTimelines.swift */; }; 8B548F5D1945331D00EE2927 /* SwifterTimelines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B548F5B1945331D00EE2927 /* SwifterTimelines.swift */; }; - 8B552D2A194643CF0052EB86 /* NSURL+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B552D29194643CF0052EB86 /* NSURL+Swifter.swift */; }; - 8B552D2B194643CF0052EB86 /* NSURL+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B552D29194643CF0052EB86 /* NSURL+Swifter.swift */; }; + 8B552D2A194643CF0052EB86 /* URL++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B552D29194643CF0052EB86 /* URL++.swift */; }; + 8B552D2B194643CF0052EB86 /* URL++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B552D29194643CF0052EB86 /* URL++.swift */; }; 8B552D2D194695F80052EB86 /* SwifterStreaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B552D2C194695F80052EB86 /* SwifterStreaming.swift */; }; 8B552D2E194695F80052EB86 /* SwifterStreaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B552D2C194695F80052EB86 /* SwifterStreaming.swift */; }; 8B5EFF07195F064400B354F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B5EFF03195F064400B354F9 /* AppDelegate.swift */; }; @@ -36,12 +35,10 @@ 8B5EFF0A195F064400B354F9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B5EFF06195F064400B354F9 /* Main.storyboard */; }; 8B5EFF15195F09F600B354F9 /* Swifter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B5EFF14195F09F600B354F9 /* Swifter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8B5EFF16195F09F600B354F9 /* Swifter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B5EFF14195F09F600B354F9 /* Swifter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8B9F514D1944A8DC00894629 /* Dictionary+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F51471944A8DC00894629 /* Dictionary+Swifter.swift */; }; - 8B9F514E1944A8DC00894629 /* Dictionary+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F51471944A8DC00894629 /* Dictionary+Swifter.swift */; }; - 8B9F51531944A8DC00894629 /* String+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F514A1944A8DC00894629 /* String+Swifter.swift */; }; - 8B9F51541944A8DC00894629 /* String+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F514A1944A8DC00894629 /* String+Swifter.swift */; }; - 8B9F51551944A8DC00894629 /* Swifter-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B9F514B1944A8DC00894629 /* Swifter-Bridging-Header.h */; }; - 8B9F51561944A8DC00894629 /* Swifter-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B9F514B1944A8DC00894629 /* Swifter-Bridging-Header.h */; }; + 8B9F514D1944A8DC00894629 /* Dictionary++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F51471944A8DC00894629 /* Dictionary++.swift */; }; + 8B9F514E1944A8DC00894629 /* Dictionary++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F51471944A8DC00894629 /* Dictionary++.swift */; }; + 8B9F51531944A8DC00894629 /* String++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F514A1944A8DC00894629 /* String++.swift */; }; + 8B9F51541944A8DC00894629 /* String++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F514A1944A8DC00894629 /* String++.swift */; }; 8B9F51571944A8DC00894629 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F514C1944A8DC00894629 /* Swifter.swift */; }; 8B9F51581944A8DC00894629 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B9F514C1944A8DC00894629 /* Swifter.swift */; }; 8BA50EC71953828A00FB1829 /* SwifterAppOnlyClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA50EC61953828A00FB1829 /* SwifterAppOnlyClient.swift */; }; @@ -77,11 +74,26 @@ 8BF1D6861948BCC100E3AA64 /* SwifterAccountsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF1D6841948BCC100E3AA64 /* SwifterAccountsClient.swift */; }; 8BF1D6881948C11E00E3AA64 /* SwifterCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF1D6871948C11E00E3AA64 /* SwifterCredential.swift */; }; 8BF1D6891948C11E00E3AA64 /* SwifterCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF1D6871948C11E00E3AA64 /* SwifterCredential.swift */; }; - A17A694F1C78336200063DFD /* Operator+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17A694D1C78333400063DFD /* Operator+Swifter.swift */; }; - A17A69501C78336300063DFD /* Operator+Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17A694D1C78333400063DFD /* Operator+Swifter.swift */; }; + A17A694F1C78336200063DFD /* Operator++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17A694D1C78333400063DFD /* Operator++.swift */; }; + A17A69501C78336300063DFD /* Operator++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A17A694D1C78333400063DFD /* Operator++.swift */; }; + A1AA8B001D5C514E00CE7F17 /* SwifterError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AA8AFE1D5C50AE00CE7F17 /* SwifterError.swift */; }; + A1AA8B011D5C515000CE7F17 /* SwifterError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AA8AFE1D5C50AE00CE7F17 /* SwifterError.swift */; }; + A1AA8B031D5C59D200CE7F17 /* TweetCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1AA8B021D5C59D200CE7F17 /* TweetCell.swift */; }; + A1BDB38C1D4C864200B0080F /* SwifterTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BDB38A1D4C859200B0080F /* SwifterTag.swift */; }; + A1BDB38D1D4C864200B0080F /* SwifterTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1BDB38A1D4C859200B0080F /* SwifterTag.swift */; }; A1C7F0A51C7845FD00955ADB /* SwifterMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F51131944A61D00894629 /* SwifterMac.framework */; }; A1C7F0A61C7845FD00955ADB /* SwifterMac.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F51131944A61D00894629 /* SwifterMac.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; A1DD65C51B618D7E0070E6FC /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1DD65C41B618D7E0070E6FC /* Launch Screen.storyboard */; }; + A1E207A91D4B85D90093E498 /* HMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207A61D4B85D90093E498 /* HMAC.swift */; }; + A1E207AA1D4B85D90093E498 /* HMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207A61D4B85D90093E498 /* HMAC.swift */; }; + A1E207AB1D4B85D90093E498 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207A71D4B85D90093E498 /* SHA1.swift */; }; + A1E207AC1D4B85D90093E498 /* SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207A71D4B85D90093E498 /* SHA1.swift */; }; + A1E207AD1D4B85D90093E498 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207A81D4B85D90093E498 /* Utils.swift */; }; + A1E207AE1D4B85D90093E498 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207A81D4B85D90093E498 /* Utils.swift */; }; + A1E207B21D4B86170093E498 /* Int++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207B01D4B86170093E498 /* Int++.swift */; }; + A1E207B31D4B86170093E498 /* Int++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207B01D4B86170093E498 /* Int++.swift */; }; + A1E207B41D4B86170093E498 /* Data++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207B11D4B86170093E498 /* Data++.swift */; }; + A1E207B51D4B86170093E498 /* Data++.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E207B11D4B86170093E498 /* Data++.swift */; }; F6B4D5781C7BAC4500E06514 /* SwifterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B4D5771C7BAC4500E06514 /* SwifterTests.swift */; }; F6B4D57A1C7BAC4500E06514 /* SwifteriOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B9F50EE1944A5AB00894629 /* SwifteriOS.framework */; }; F6B4D5811C7BAC5E00E06514 /* String+SwifterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B4D5801C7BAC5E00E06514 /* String+SwifterTests.swift */; }; @@ -143,7 +155,6 @@ 8B300C221944AA1A00993175 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8B300C471944AECB00993175 /* SwifterDemoMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwifterDemoMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8B300C4A1944AECB00993175 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8B300C4B1944AECB00993175 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 8B300C4D1944AECB00993175 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 8B300C4F1944AECB00993175 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8B300C511944AECB00993175 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; @@ -152,7 +163,7 @@ 8B548F5519450BDB00EE2927 /* SwifterOAuthClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterOAuthClient.swift; sourceTree = ""; }; 8B548F581945325E00EE2927 /* SwifterAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterAuth.swift; sourceTree = ""; }; 8B548F5B1945331D00EE2927 /* SwifterTimelines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTimelines.swift; sourceTree = ""; }; - 8B552D29194643CF0052EB86 /* NSURL+Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURL+Swifter.swift"; sourceTree = ""; }; + 8B552D29194643CF0052EB86 /* URL++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL++.swift"; sourceTree = ""; }; 8B552D2C194695F80052EB86 /* SwifterStreaming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SwifterStreaming.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 8B5EFF03195F064400B354F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8B5EFF04195F064400B354F9 /* AuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthViewController.swift; sourceTree = ""; }; @@ -162,9 +173,8 @@ 8B5EFF14195F09F600B354F9 /* Swifter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Swifter.h; sourceTree = ""; }; 8B9F50EE1944A5AB00894629 /* SwifteriOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwifteriOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8B9F51131944A61D00894629 /* SwifterMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwifterMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 8B9F51471944A8DC00894629 /* Dictionary+Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Swifter.swift"; sourceTree = ""; }; - 8B9F514A1944A8DC00894629 /* String+Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Swifter.swift"; sourceTree = ""; }; - 8B9F514B1944A8DC00894629 /* Swifter-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Swifter-Bridging-Header.h"; sourceTree = ""; }; + 8B9F51471944A8DC00894629 /* Dictionary++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary++.swift"; sourceTree = ""; }; + 8B9F514A1944A8DC00894629 /* String++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String++.swift"; sourceTree = ""; }; 8B9F514C1944A8DC00894629 /* Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Swifter.swift; sourceTree = ""; }; 8BA50EC61953828A00FB1829 /* SwifterAppOnlyClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterAppOnlyClient.swift; sourceTree = ""; }; 8BC32D3C1955EEE100FF75A0 /* SwifterTrends.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTrends.swift; sourceTree = ""; }; @@ -183,8 +193,16 @@ 8BF1D67E194889B900E3AA64 /* SwifterClientProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterClientProtocol.swift; sourceTree = ""; }; 8BF1D6841948BCC100E3AA64 /* SwifterAccountsClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterAccountsClient.swift; sourceTree = ""; }; 8BF1D6871948C11E00E3AA64 /* SwifterCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterCredential.swift; sourceTree = ""; }; - A17A694D1C78333400063DFD /* Operator+Swifter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Operator+Swifter.swift"; sourceTree = ""; }; + A17A694D1C78333400063DFD /* Operator++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Operator++.swift"; sourceTree = ""; }; + A1AA8AFE1D5C50AE00CE7F17 /* SwifterError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterError.swift; sourceTree = ""; }; + A1AA8B021D5C59D200CE7F17 /* TweetCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweetCell.swift; sourceTree = ""; }; + A1BDB38A1D4C859200B0080F /* SwifterTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifterTag.swift; sourceTree = ""; }; A1DD65C41B618D7E0070E6FC /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "../Swifter/Launch Screen.storyboard"; sourceTree = ""; }; + A1E207A61D4B85D90093E498 /* HMAC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HMAC.swift; sourceTree = ""; }; + A1E207A71D4B85D90093E498 /* SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SHA1.swift; sourceTree = ""; }; + A1E207A81D4B85D90093E498 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + A1E207B01D4B86170093E498 /* Int++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int++.swift"; sourceTree = ""; }; + A1E207B11D4B86170093E498 /* Data++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data++.swift"; sourceTree = ""; }; F6B4D5751C7BAC4500E06514 /* SwifterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwifterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F6B4D5771C7BAC4500E06514 /* SwifterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwifterTests.swift; sourceTree = ""; }; F6B4D5791C7BAC4500E06514 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -239,9 +257,7 @@ 8B5EFF03195F064400B354F9 /* AppDelegate.swift */, 8B5EFF04195F064400B354F9 /* AuthViewController.swift */, 8B5EFF05195F064400B354F9 /* TweetsViewController.swift */, - 8B5EFF06195F064400B354F9 /* Main.storyboard */, - A1DD65C41B618D7E0070E6FC /* Launch Screen.storyboard */, - 8BCB9328194B2E1200A998EA /* Images.xcassets */, + A1AA8B021D5C59D200CE7F17 /* TweetCell.swift */, 8B300C211944AA1A00993175 /* Supporting Files */, ); path = SwifterDemoiOS; @@ -250,6 +266,9 @@ 8B300C211944AA1A00993175 /* Supporting Files */ = { isa = PBXGroup; children = ( + 8B5EFF06195F064400B354F9 /* Main.storyboard */, + A1DD65C41B618D7E0070E6FC /* Launch Screen.storyboard */, + 8BCB9328194B2E1200A998EA /* Images.xcassets */, 8B300C221944AA1A00993175 /* Info.plist */, ); name = "Supporting Files"; @@ -260,8 +279,6 @@ children = ( 8B300C4F1944AECB00993175 /* AppDelegate.swift */, 8B300C4D1944AECB00993175 /* ViewController.swift */, - 8B300C511944AECB00993175 /* Images.xcassets */, - 8B300C531944AECB00993175 /* Main.storyboard */, 8B300C491944AECB00993175 /* Supporting Files */, ); path = SwifterDemoMac; @@ -270,8 +287,9 @@ 8B300C491944AECB00993175 /* Supporting Files */ = { isa = PBXGroup; children = ( + 8B300C511944AECB00993175 /* Images.xcassets */, + 8B300C531944AECB00993175 /* Main.storyboard */, 8B300C4A1944AECB00993175 /* Info.plist */, - 8B300C4B1944AECB00993175 /* main.swift */, ); name = "Supporting Files"; sourceTree = ""; @@ -311,7 +329,6 @@ isa = PBXGroup; children = ( 8B5EFF14195F09F600B354F9 /* Swifter.h */, - 8B9F514B1944A8DC00894629 /* Swifter-Bridging-Header.h */, 8B9F514C1944A8DC00894629 /* Swifter.swift */, 8BF1D6871948C11E00E3AA64 /* SwifterCredential.swift */, 8B548F581945325E00EE2927 /* SwifterAuth.swift */, @@ -330,6 +347,8 @@ 8BC32D3C1955EEE100FF75A0 /* SwifterTrends.swift */, 8BF1D67B1948882700E3AA64 /* SwifterSpam.swift */, 8B1F259E194A663D004BD304 /* SwifterHelp.swift */, + A1BDB38A1D4C859200B0080F /* SwifterTag.swift */, + A1AA8AFE1D5C50AE00CE7F17 /* SwifterError.swift */, 8BF1D67E194889B900E3AA64 /* SwifterClientProtocol.swift */, 8B548F5519450BDB00EE2927 /* SwifterOAuthClient.swift */, 8BF1D6841948BCC100E3AA64 /* SwifterAccountsClient.swift */, @@ -345,14 +364,27 @@ 8B9F51591944A8E100894629 /* Extensions */ = { isa = PBXGroup; children = ( - 8B9F51471944A8DC00894629 /* Dictionary+Swifter.swift */, - A17A694D1C78333400063DFD /* Operator+Swifter.swift */, - 8B552D29194643CF0052EB86 /* NSURL+Swifter.swift */, - 8B9F514A1944A8DC00894629 /* String+Swifter.swift */, + A1E207AF1D4B85F70093E498 /* CommonCrypto */, + 8B9F51471944A8DC00894629 /* Dictionary++.swift */, + A1E207B01D4B86170093E498 /* Int++.swift */, + A1E207B11D4B86170093E498 /* Data++.swift */, + A17A694D1C78333400063DFD /* Operator++.swift */, + 8B9F514A1944A8DC00894629 /* String++.swift */, + 8B552D29194643CF0052EB86 /* URL++.swift */, ); name = Extensions; sourceTree = ""; }; + A1E207AF1D4B85F70093E498 /* CommonCrypto */ = { + isa = PBXGroup; + children = ( + A1E207A61D4B85D90093E498 /* HMAC.swift */, + A1E207A71D4B85D90093E498 /* SHA1.swift */, + A1E207A81D4B85D90093E498 /* Utils.swift */, + ); + name = CommonCrypto; + sourceTree = ""; + }; F6B4D5761C7BAC4500E06514 /* SwifterTests */ = { isa = PBXGroup; children = ( @@ -370,7 +402,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 8B9F51551944A8DC00894629 /* Swifter-Bridging-Header.h in Headers */, 8B5EFF15195F09F600B354F9 /* Swifter.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -379,7 +410,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 8B9F51561944A8DC00894629 /* Swifter-Bridging-Header.h in Headers */, 8B5EFF16195F09F600B354F9 /* Swifter.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -487,20 +517,24 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Matt Donnelly"; TargetAttributes = { 8B300C1E1944AA1A00993175 = { CreatedOnToolsVersion = 6.0; + LastSwiftMigration = 0800; }; 8B300C461944AECB00993175 = { CreatedOnToolsVersion = 6.0; + LastSwiftMigration = 0800; }; 8B9F50ED1944A5AB00894629 = { CreatedOnToolsVersion = 6.0; + LastSwiftMigration = 0800; }; 8B9F51121944A61D00894629 = { CreatedOnToolsVersion = 6.0; + LastSwiftMigration = 0800; }; F6B4D5741C7BAC4500E06514 = { CreatedOnToolsVersion = 7.2; @@ -580,6 +614,7 @@ 8B5EFF08195F064400B354F9 /* AuthViewController.swift in Sources */, 8B5EFF07195F064400B354F9 /* AppDelegate.swift in Sources */, 8B5EFF09195F064400B354F9 /* TweetsViewController.swift in Sources */, + A1AA8B031D5C59D200CE7F17 /* TweetCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -589,7 +624,6 @@ files = ( 8B300C501944AECB00993175 /* AppDelegate.swift in Sources */, 8B300C4E1944AECB00993175 /* ViewController.swift in Sources */, - 8B300C4C1944AECB00993175 /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -597,30 +631,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A17A694F1C78336200063DFD /* Operator+Swifter.swift in Sources */, + A17A694F1C78336200063DFD /* Operator++.swift in Sources */, 8BCF43291947CC7A00E63301 /* SwifterFavorites.swift in Sources */, 8BCF43261947CABA00E63301 /* SwifterSuggested.swift in Sources */, + A1BDB38C1D4C864200B0080F /* SwifterTag.swift in Sources */, 8B116AF1194601970019A4DC /* SwifterHTTPRequest.swift in Sources */, 8B552D2D194695F80052EB86 /* SwifterStreaming.swift in Sources */, 8B548F5619450BDB00EE2927 /* SwifterOAuthClient.swift in Sources */, + A1E207B21D4B86170093E498 /* Int++.swift in Sources */, 8B9F51571944A8DC00894629 /* Swifter.swift in Sources */, + A1E207B41D4B86170093E498 /* Data++.swift in Sources */, 8BF1D6761948825D00E3AA64 /* SwifterSavedSearches.swift in Sources */, + A1E207A91D4B85D90093E498 /* HMAC.swift in Sources */, 8BCF43171947450A00E63301 /* SwifterTweets.swift in Sources */, 8BF1D6851948BCC100E3AA64 /* SwifterAccountsClient.swift in Sources */, 8B548F591945325E00EE2927 /* SwifterAuth.swift in Sources */, 8BCF431D1947907300E63301 /* SwifterMessages.swift in Sources */, - 8B9F51531944A8DC00894629 /* String+Swifter.swift in Sources */, + 8B9F51531944A8DC00894629 /* String++.swift in Sources */, 8BF1D67C1948882700E3AA64 /* SwifterSpam.swift in Sources */, 8BCF431A19475D3C00E63301 /* SwifterSearch.swift in Sources */, 8B33CA3F19587C6D00739FCB /* JSON.swift in Sources */, 8BCF432C1947CED700E63301 /* SwifterLists.swift in Sources */, 8BA50EC71953828A00FB1829 /* SwifterAppOnlyClient.swift in Sources */, 8BF1D6881948C11E00E3AA64 /* SwifterCredential.swift in Sources */, + A1AA8B001D5C514E00CE7F17 /* SwifterError.swift in Sources */, + A1E207AB1D4B85D90093E498 /* SHA1.swift in Sources */, + A1E207AD1D4B85D90093E498 /* Utils.swift in Sources */, 8BF1D6791948839800E3AA64 /* SwifterPlaces.swift in Sources */, 8BC32D3D1955EEE100FF75A0 /* SwifterTrends.swift in Sources */, 8BCF43231947A6C800E63301 /* SwifterUsers.swift in Sources */, - 8B9F514D1944A8DC00894629 /* Dictionary+Swifter.swift in Sources */, - 8B552D2A194643CF0052EB86 /* NSURL+Swifter.swift in Sources */, + 8B9F514D1944A8DC00894629 /* Dictionary++.swift in Sources */, + 8B552D2A194643CF0052EB86 /* URL++.swift in Sources */, 8BCF4320194798FB00E63301 /* SwifterFollowers.swift in Sources */, 8B548F5C1945331D00EE2927 /* SwifterTimelines.swift in Sources */, 8BF1D67F194889B900E3AA64 /* SwifterClientProtocol.swift in Sources */, @@ -632,30 +673,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A17A69501C78336300063DFD /* Operator+Swifter.swift in Sources */, + A17A69501C78336300063DFD /* Operator++.swift in Sources */, 8BCF432A1947CC7A00E63301 /* SwifterFavorites.swift in Sources */, 8BCF43271947CABA00E63301 /* SwifterSuggested.swift in Sources */, + A1BDB38D1D4C864200B0080F /* SwifterTag.swift in Sources */, 8B116AF2194601970019A4DC /* SwifterHTTPRequest.swift in Sources */, 8B552D2E194695F80052EB86 /* SwifterStreaming.swift in Sources */, 8B548F5719450BDB00EE2927 /* SwifterOAuthClient.swift in Sources */, + A1E207B31D4B86170093E498 /* Int++.swift in Sources */, 8B9F51581944A8DC00894629 /* Swifter.swift in Sources */, + A1E207B51D4B86170093E498 /* Data++.swift in Sources */, 8BF1D6771948825D00E3AA64 /* SwifterSavedSearches.swift in Sources */, + A1E207AA1D4B85D90093E498 /* HMAC.swift in Sources */, 8BCF43181947450A00E63301 /* SwifterTweets.swift in Sources */, 8BF1D6861948BCC100E3AA64 /* SwifterAccountsClient.swift in Sources */, 8B548F5A1945325E00EE2927 /* SwifterAuth.swift in Sources */, 8BCF431E1947907300E63301 /* SwifterMessages.swift in Sources */, - 8B9F51541944A8DC00894629 /* String+Swifter.swift in Sources */, + 8B9F51541944A8DC00894629 /* String++.swift in Sources */, 8BF1D67D1948882700E3AA64 /* SwifterSpam.swift in Sources */, 8BCF431B19475D3C00E63301 /* SwifterSearch.swift in Sources */, 8B33CA4019587C6D00739FCB /* JSON.swift in Sources */, 8BCF432D1947CED700E63301 /* SwifterLists.swift in Sources */, 8BA50EC81953828A00FB1829 /* SwifterAppOnlyClient.swift in Sources */, 8BF1D6891948C11E00E3AA64 /* SwifterCredential.swift in Sources */, + A1AA8B011D5C515000CE7F17 /* SwifterError.swift in Sources */, + A1E207AC1D4B85D90093E498 /* SHA1.swift in Sources */, + A1E207AE1D4B85D90093E498 /* Utils.swift in Sources */, 8BF1D67A1948839800E3AA64 /* SwifterPlaces.swift in Sources */, 8BC32D3E1955EEE100FF75A0 /* SwifterTrends.swift in Sources */, 8BCF43241947A6C800E63301 /* SwifterUsers.swift in Sources */, - 8B9F514E1944A8DC00894629 /* Dictionary+Swifter.swift in Sources */, - 8B552D2B194643CF0052EB86 /* NSURL+Swifter.swift in Sources */, + 8B9F514E1944A8DC00894629 /* Dictionary++.swift in Sources */, + 8B552D2B194643CF0052EB86 /* URL++.swift in Sources */, 8BCF4321194798FB00E63301 /* SwifterFollowers.swift in Sources */, 8B548F5D1945331D00EE2927 /* SwifterTimelines.swift in Sources */, 8BF1D680194889B900E3AA64 /* SwifterClientProtocol.swift in Sources */, @@ -719,6 +767,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mattdonnelly.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -732,16 +781,18 @@ METAL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.mattdonnelly.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; 8B300C631944AECB00993175 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -753,17 +804,18 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mattdonnelly.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Debug; }; 8B300C641944AECB00993175 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = SwifterDemoMac/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -771,6 +823,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mattdonnelly.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -798,6 +852,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -844,6 +899,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -863,6 +919,7 @@ 8B9F51051944A5AB00894629 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -873,13 +930,14 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mattdonnelly.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "Swifter/Swifter-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Debug; }; 8B9F51061944A5AB00894629 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -890,7 +948,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.mattdonnelly.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "Swifter/Swifter-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -916,7 +975,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "Swifter/Swifter-Bridging-Header.h"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -939,7 +998,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "Swifter/Swifter-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -969,6 +1029,7 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.mattdonnelly.SwifteriOS.SwifterTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -1027,6 +1088,7 @@ F6B4D57E1C7BAC4500E06514 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme b/Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme index 44218a6b..dd07ecaa 100644 --- a/Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme +++ b/Swifter.xcodeproj/xcshareddata/xcschemes/SwifterMac.xcscheme @@ -1,6 +1,6 @@ .size + var bytesArray = [UInt8](repeating: 0, count: count) + (self as NSData).getBytes(&bytesArray, length:count * MemoryLayout.size) + return bytesArray + } + + init(bytes: [UInt8]) { + self.init(bytes: UnsafePointer(bytes), count: bytes.count) + } + + mutating func append(_ bytes: [UInt8]) { + self.append(UnsafePointer(bytes), count: bytes.count) + } + +} + diff --git a/Swifter/Dictionary+Swifter.swift b/Swifter/Dictionary++.swift similarity index 72% rename from Swifter/Dictionary+Swifter.swift rename to Swifter/Dictionary++.swift index 92e9b2b8..b51ffe7d 100644 --- a/Swifter/Dictionary+Swifter.swift +++ b/Swifter/Dictionary++.swift @@ -27,51 +27,50 @@ import Foundation extension Dictionary { - func filter(predicate: Element -> Bool) -> Dictionary { + func filter(_ predicate: (Element) -> Bool) -> Dictionary { var filteredDictionary = Dictionary() - for (key, value) in self where predicate(key, value) { - filteredDictionary[key] = value + for element in self where predicate(element) { + filteredDictionary[element.key] = element.value } return filteredDictionary } - func queryStringWithEncoding() -> String { + var queryString: String { var parts = [String]() for (key, value) in self { - let keyString: String = "\(key)" - let valueString: String = "\(value)" - let query: String = "\(keyString)=\(valueString)" + let query: String = "\(key)=\(value)" parts.append(query) } - return parts.joinWithSeparator("&") + return parts.joined(separator: "&") } - func urlEncodedQueryStringWithEncoding(encoding: NSStringEncoding) -> String { + func urlEncodedQueryString(using encoding: String.Encoding) -> String { var parts = [String]() for (key, value) in self { - let keyString: String = "\(key)".urlEncodedStringWithEncoding() - let valueString: String = "\(value)".urlEncodedStringWithEncoding(keyString == "status") + let keyString = "\(key)".urlEncodedString() + let valueString = "\(value)".urlEncodedString(keyString == "status") let query: String = "\(keyString)=\(valueString)" parts.append(query) } - return parts.joinWithSeparator("&") + return parts.joined(separator: "&") } func stringifiedDictionary() -> Dictionary { var dict = [String: String]() for (key, value) in self { - dict[String(key)] = String(value) + dict[String(describing: key)] = String(describing: value) } return dict } } -infix operator +| {} +infix operator +| + func +| (left: Dictionary, right: Dictionary) -> Dictionary { var map = Dictionary() for (k, v) in left { diff --git a/Swifter/HMAC.swift b/Swifter/HMAC.swift new file mode 100755 index 00000000..0fcc4f23 --- /dev/null +++ b/Swifter/HMAC.swift @@ -0,0 +1,45 @@ +// +// HMAC.swift +// OAuthSwift +// +// Created by Dongri Jin on 1/28/15. +// Copyright (c) 2015 Dongri Jin. All rights reserved. +// + + +import Foundation + +public struct HMAC { + + internal static func sha1(key: Data, message: Data) -> Data? { + var key = key.rawBytes + let message = message.rawBytes + + // key + if key.count > 64 { + key = SHA1(message: Data(bytes: key)).calculate().rawBytes + } + + if (key.count < 64) { + key = key + [UInt8](repeating: 0, count: 64 - key.count) + } + + // + var opad = [UInt8](repeating: 0x5c, count: 64) + for (idx, _) in key.enumerated() { + opad[idx] = key[idx] ^ opad[idx] + } + var ipad = [UInt8](repeating: 0x36, count: 64) + for (idx, _) in key.enumerated() { + ipad[idx] = key[idx] ^ ipad[idx] + } + + let ipadAndMessageHash = SHA1(message: Data(bytes: (ipad + message))).calculate().rawBytes + let finalHash = SHA1(message: Data(bytes: opad + ipadAndMessageHash)).calculate().rawBytes + let mac = finalHash + + return Data(bytes: UnsafePointer(mac), count: mac.count) + + } + +} diff --git a/Swifter/Int++.swift b/Swifter/Int++.swift new file mode 100755 index 00000000..f6f28765 --- /dev/null +++ b/Swifter/Int++.swift @@ -0,0 +1,34 @@ +// +// Int+OAuthSwift.swift +// OAuthSwift +// +// Created by Dongri Jin on 1/28/15. +// Copyright (c) 2015 Dongri Jin. All rights reserved. +// + +import Foundation + +extension Int { + + public func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] { + return arrayOfBytes(self, length: totalBytes) + } + +} + +func arrayOfBytes(_ value:T, length: Int? = nil) -> [UInt8] { + let totalBytes = length ?? (MemoryLayout.size * 8) + let valuePointer = UnsafeMutablePointer.allocate(capacity: 1) + valuePointer.pointee = value + + let bytesPointer = valuePointer.withMemoryRebound(to: UInt8.self, capacity: 1) { $0 } + var bytes = [UInt8](repeating: 0, count: totalBytes) + for j in 0...size,totalBytes) { + bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee + } + + valuePointer.deinitialize() + valuePointer.deallocate(capacity: 1) + + return bytes +} diff --git a/Swifter/JSON.swift b/Swifter/JSON.swift index b5880355..7a43c2b1 100755 --- a/Swifter/JSON.swift +++ b/Swifter/JSON.swift @@ -25,326 +25,218 @@ import Foundation -public typealias JSONValue = JSON - -public let JSONTrue = JSONValue(true) -public let JSONFalse = JSONValue(false) - -public let JSONNull = JSONValue.JSONNull - public enum JSON : Equatable, CustomStringConvertible { - case JSONString(String) - case JSONNumber(Double) - case JSONObject(Dictionary) - case JSONArray(Array) - case JSONBool(Bool) - case JSONNull - case JSONInvalid - - init(_ value: Bool?) { - if let bool = value { - self = .JSONBool(bool) - } - else { - self = .JSONInvalid - } - } - - init(_ value: Double?) { - if let number = value { - self = .JSONNumber(number) - } - else { - self = .JSONInvalid - } - } - - init(_ value: Int?) { - if let number = value { - self = .JSONNumber(Double(number)) - } - else { - self = .JSONInvalid - } - } + case string(String) + case number(Double) + case object(Dictionary) + case array(Array) + case bool(Bool) + case null + case invalid - init(_ value: String?) { - if let string = value { - self = .JSONString(string) - } - else { - self = .JSONInvalid - } - } - - init(_ value: Array?) { - if let array = value { - self = .JSONArray(array) - } - else { - self = .JSONInvalid - } - } - - init(_ value: Dictionary?) { - if let dict = value { - self = .JSONObject(dict) - } - else { - self = .JSONInvalid - } - } - - init(_ rawValue: AnyObject?) { - if let value : AnyObject = rawValue { - switch value { - case let data as NSData: - do { - let jsonObject : AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: []) - self = JSON(jsonObject) - } catch { - self = .JSONInvalid - } - - case let array as NSArray: - var newArray : [JSONValue] = [] - for item : AnyObject in array { - newArray.append(JSON(item)) - } - self = .JSONArray(newArray) - - case let dict as NSDictionary: - var newDict : Dictionary = [:] - for (k, v): (AnyObject, AnyObject) in dict { - if let key = k as? String { - newDict[key] = JSON(v) - } - else { - assert(true, "Invalid key type; expected String") - self = .JSONInvalid - return - } - } - self = .JSONObject(newDict) - - case let string as NSString: - self = .JSONString(string as String) - - case let number as NSNumber: - if number.isBool { - self = .JSONBool(number.boolValue) - } - else { - self = .JSONNumber(number.doubleValue) - } - - case _ as NSNull: - self = .JSONNull - - default: - assert(true, "This location should never be reached") - self = .JSONInvalid + public init(_ rawValue: Any) { + switch rawValue { + case let json as JSON: + self = json + + case let array as [JSON]: + self = .array(array) + + case let dict as [String: JSON]: + self = .object(dict) + + case let data as Data: + do { + let object = try JSONSerialization.jsonObject(with: data, options: []) + self = JSON(object) + } catch { + self = .invalid } + + case let array as [Any]: + let newArray = array.map { JSON($0) } + self = .array(newArray) + + case let dict as [String: Any]: + var newDict = [String: JSON]() + for (key, value) in dict { + newDict[key] = JSON(value) + } + self = .object(newDict) + + case let string as String: + self = .string(string) + + case let number as NSNumber: + self = number.isBoolean ? .bool(number.boolValue) : .number(number.doubleValue) + + case _ as Optional: + self = .null + + default: + assert(true, "This location should never be reached") + self = .invalid } - else { - self = .JSONInvalid - } + } - + public var string : String? { - switch self { - case .JSONString(let value): - return value - default: + guard case .string(let value) = self else { return nil } + return value } - + public var integer : Int? { - switch self { - case .JSONNumber(let value): - return Int(value) - - default: + guard case .number(let value) = self else { return nil } + return Int(value) } - + public var double : Double? { - switch self { - case .JSONNumber(let value): - return value - - default: + guard case .number(let value) = self else { return nil } + return value } - - public var object : Dictionary? { - switch self { - case .JSONObject(let value): - return value - - default: + + public var object : [String: JSON]? { + guard case .object(let value) = self else { return nil } + return value } - - public var array : Array? { - switch self { - case .JSONArray(let value): - return value - - default: + + public var array : [JSON]? { + guard case .array(let value) = self else { return nil } + return value } - + public var bool : Bool? { - switch self { - case .JSONBool(let value): - return value - - default: + guard case .bool(let value) = self else { return nil } + return value } - - public subscript(key: String) -> JSONValue { - switch self { - case .JSONObject(let dict): - if let value = dict[key] { - return value - } - else { - return .JSONInvalid - } - - default: - return .JSONInvalid + + public subscript(key: String) -> JSON { + guard case .object(let dict) = self, let value = dict[key] else { + return .invalid } + return value } - - public subscript(index: Int) -> JSONValue { - switch self { - case .JSONArray(let array) where array.count > index: - return array[index] - - default: - return .JSONInvalid + + public subscript(index: Int) -> JSON { + guard case .array(let array) = self, array.count > index else { + return .invalid } + return array[index] } - - static func parseJSONData(jsonData : NSData) throws -> JSON { - let JSONObject = try? NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers) - return (JSONObject == nil) ? nil : JSON(JSONObject) + + static func parse(jsonData: Data) throws -> JSON { + do { + let object = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) + return JSON(object) + } catch { + throw SwifterError(message: "\(error)", kind: .jsonParseError) + } } - - static func parseJSONString(jsonString : String) throws -> JSON { - let error: NSError! = NSError(domain: "Migrator", code: 0, userInfo: nil) - if let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { - return try parseJSONData(data) + + static func parse(string : String) throws -> JSON { + do { + guard let data = string.data(using: .utf8, allowLossyConversion: false) else { + throw SwifterError(message: "Cannot parse invalid string", kind: .jsonParseError) + } + return try parse(jsonData: data) + } catch { + throw SwifterError(message: "\(error)", kind: .jsonParseError) } - throw error } - - func stringify(indent: String = " ") -> String? { - switch self { - case .JSONInvalid: + + func stringify(_ indent: String = " ") -> String? { + guard self != .invalid else { assert(true, "The JSON value is invalid") return nil - - default: - return _prettyPrint(indent, 0) } + return prettyPrint(indent, 0) } - -} - -public func ==(lhs: JSON, rhs: JSON) -> Bool { - switch (lhs, rhs) { - case (.JSONNull, .JSONNull): - return true - - case (.JSONBool(let lhsValue), .JSONBool(let rhsValue)): - return lhsValue == rhsValue - - case (.JSONString(let lhsValue), .JSONString(let rhsValue)): - return lhsValue == rhsValue - - case (.JSONNumber(let lhsValue), .JSONNumber(let rhsValue)): - return lhsValue == rhsValue - - case (.JSONArray(let lhsValue), .JSONArray(let rhsValue)): - return lhsValue == rhsValue - - case (.JSONObject(let lhsValue), .JSONObject(let rhsValue)): - return lhsValue == rhsValue - - default: - return false - } -} - -extension JSON { - + public var description: String { - if let jsonString = stringify() { - return jsonString - } - else { + guard let string = stringify() else { return "" } + return string } - - private func _prettyPrint(indent: String, _ level: Int) -> String { - let currentIndent = (0...level).map({ _ in "" }).joinWithSeparator(indent) + + private func prettyPrint(_ indent: String, _ level: Int) -> String { + let currentIndent = (0...level).map({ _ in "" }).joined(separator: indent) let nextIndent = currentIndent + " " switch self { - case .JSONBool(let bool): + case .bool(let bool): return bool ? "true" : "false" - case .JSONNumber(let number): + case .number(let number): return "\(number)" - case .JSONString(let string): + case .string(let string): return "\"\(string)\"" - case .JSONArray(let array): - return "[\n" + array.map({ "\(nextIndent)\($0._prettyPrint(indent, level + 1))" }).joinWithSeparator(",\n") + "\n\(currentIndent)]" + case .array(let array): + return "[\n" + array.map { "\(nextIndent)\($0.prettyPrint(indent, level + 1))" }.joined(separator: ",\n") + "\n\(currentIndent)]" - case .JSONObject(let dict): - return "{\n" + dict.map({ "\(nextIndent)\"\($0)\" : \($1._prettyPrint(indent, level + 1))"}).joinWithSeparator(",\n") + "\n\(currentIndent)}" + case .object(let dict): + return "{\n" + dict.map { "\(nextIndent)\"\($0)\" : \($1.prettyPrint(indent, level + 1))"}.joined(separator: ",\n") + "\n\(currentIndent)}" - case .JSONNull: + case .null: return "null" - case .JSONInvalid: + case .invalid: assert(true, "This should never be reached") return "" } } - + } -extension JSONValue: BooleanType { - - public var boolValue: Bool { - switch self { - case .JSONBool(let bool): - return bool - case .JSONInvalid: - return false - default: - return true - } +public func ==(lhs: JSON, rhs: JSON) -> Bool { + switch (lhs, rhs) { + case (.null, .null): + return true + + case (.bool(let lhsValue), .bool(let rhsValue)): + return lhsValue == rhsValue + + case (.string(let lhsValue), .string(let rhsValue)): + return lhsValue == rhsValue + + case (.number(let lhsValue), .number(let rhsValue)): + return lhsValue == rhsValue + + case (.array(let lhsValue), .array(let rhsValue)): + return lhsValue == rhsValue + + case (.object(let lhsValue), .object(let rhsValue)): + return lhsValue == rhsValue + + default: + return false } - } -extension JSON: StringLiteralConvertible { + + +extension JSON: ExpressibleByStringLiteral, + ExpressibleByIntegerLiteral, + ExpressibleByBooleanLiteral, + ExpressibleByFloatLiteral, + ExpressibleByArrayLiteral, + ExpressibleByDictionaryLiteral, + ExpressibleByNilLiteral { public init(stringLiteral value: StringLiteralType) { self.init(value) @@ -357,76 +249,45 @@ extension JSON: StringLiteralConvertible { public init(unicodeScalarLiteral value: StringLiteralType) { self.init(value) } - -} - -extension JSON: IntegerLiteralConvertible { public init(integerLiteral value: IntegerLiteralType) { self.init(value) } - -} - -extension JSON: BooleanLiteralConvertible { public init(booleanLiteral value: BooleanLiteralType) { self.init(value) } - -} - -extension JSON: FloatLiteralConvertible { public init(floatLiteral value: FloatLiteralType) { self.init(value) } - -} - -extension JSON: DictionaryLiteralConvertible { - public init(dictionaryLiteral elements: (String, AnyObject)...) { - var dict = [String : AnyObject]() - - for (key, value) in elements { - dict[key] = value - } - - self.init(dict) + public init(dictionaryLiteral elements: (String, Any)...) { + let object = elements.reduce([String: Any]()) { $0 + [$1.0: $1.1] } + self.init(object) } - -} - -extension JSON: ArrayLiteralConvertible { public init(arrayLiteral elements: AnyObject...) { self.init(elements) } -} - -extension JSON: NilLiteralConvertible { public init(nilLiteral: ()) { self.init(NSNull()) } - + } -private let trueNumber = NSNumber(bool: true) -private let falseNumber = NSNumber(bool: false) -private let trueObjCType = String.fromCString(trueNumber.objCType) -private let falseObjCType = String.fromCString(falseNumber.objCType) +private func +(lhs: [String: Any], rhs: [String: Any]) -> [String: Any] { + var lhs = lhs + for element in rhs { + lhs[element.key] = element.value + } + return lhs +} private extension NSNumber { - var isBool:Bool { - get { - let objCType = String.fromCString(self.objCType) - if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){ - return true - } else { - return false - } - } + + var isBoolean: Bool { + return NSNumber(value: true).objCType == self.objCType } } diff --git a/Swifter/Operator+Swifter.swift b/Swifter/Operator++.swift similarity index 65% rename from Swifter/Operator+Swifter.swift rename to Swifter/Operator++.swift index a14840f1..8c15599b 100644 --- a/Swifter/Operator+Swifter.swift +++ b/Swifter/Operator++.swift @@ -9,10 +9,10 @@ import Foundation /// If `rhs` is not `nil`, assign it to `lhs`. -infix operator ??= { associativity right precedence 90 assignment } // matches other assignment operators +infix operator ??= : AssignmentPrecedence // { associativity right precedence 90 assignment } // matches other assignment operators /// If `rhs` is not `nil`, assign it to `lhs`. -func ??=(inout lhs: T?, rhs: T?) { +func ??=(lhs: inout T?, rhs: T?) { guard let rhs = rhs else { return } lhs = rhs } diff --git a/Swifter/SHA1.swift b/Swifter/SHA1.swift new file mode 100755 index 00000000..3fa015e5 --- /dev/null +++ b/Swifter/SHA1.swift @@ -0,0 +1,132 @@ +// +// SHA1.swift +// OAuthSwift +// +// Created by Dongri Jin on 1/28/15. +// Copyright (c) 2015 Dongri Jin. All rights reserved. +// + +import Foundation + +struct SHA1 { + + var message: Data + + /** Common part for hash calculation. Prepare header data. */ + func prepare(_ len:Int = 64) -> Data { + var tmpMessage: Data = self.message + + // Step 1. Append Padding Bits + tmpMessage.append([0x80]) // append one bit (Byte with one bit) to message + + // append "0" bit until message length in bits ≡ 448 (mod 512) + while tmpMessage.count % len != (len - 8) { + tmpMessage.append([0x00]) + } + + return tmpMessage + } + + func calculate() -> Data { + + //var tmpMessage = self.prepare() + let len = 64 + let h: [UInt32] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0] + + var tmpMessage: Data = self.message + + // Step 1. Append Padding Bits + tmpMessage.append([0x80]) // append one bit (Byte with one bit) to message + + // append "0" bit until message length in bits ≡ 448 (mod 512) + while tmpMessage.count % len != (len - 8) { + tmpMessage.append([0x00]) + } + + // hash values + var hh = h + + // append message length, in a 64-bit big-endian integer. So now the message length is a multiple of 512 bits. + tmpMessage.append((self.message.count * 8).bytes(64 / 8)) + + // Process the message in successive 512-bit chunks: + let chunkSizeBytes = 512 / 8 // 64 + var leftMessageBytes = tmpMessage.count + var i = 0; + while i < tmpMessage.count { + + let chunk = tmpMessage.subdata(in: i...size, length: MemoryLayout.size) + (chunk as NSData).getBytes(&le, range: range) + M[x] = le.bigEndian + break + default: + M[x] = rotateLeft(M[x-3] ^ M[x-8] ^ M[x-14] ^ M[x-16], n: 1) + break + } + } + + var A = hh[0], B = hh[1], C = hh[2], D = hh[3], E = hh[4] + + // Main loop + for j in 0...79 { + var f: UInt32 = 0 + var k: UInt32 = 0 + + switch j { + case 0...19: + f = (B & C) | ((~B) & D) + k = 0x5A827999 + break + case 20...39: + f = B ^ C ^ D + k = 0x6ED9EBA1 + break + case 40...59: + f = (B & C) | (B & D) | (C & D) + k = 0x8F1BBCDC + break + case 60...79: + f = B ^ C ^ D + k = 0xCA62C1D6 + break + default: + break + } + + let temp = (rotateLeft(A,n: 5) &+ f &+ E &+ M[j] &+ k) & 0xffffffff + E = D + D = C + C = rotateLeft(B, n: 30) + B = A + A = temp + + } + + hh[0] = (hh[0] &+ A) & 0xffffffff + hh[1] = (hh[1] &+ B) & 0xffffffff + hh[2] = (hh[2] &+ C) & 0xffffffff + hh[3] = (hh[3] &+ D) & 0xffffffff + hh[4] = (hh[4] &+ E) & 0xffffffff + + i = i + chunkSizeBytes + leftMessageBytes -= chunkSizeBytes + } + + // Produce the final hash value (big-endian) as a 160 bit number: + let mutableBuff = NSMutableData() + hh.forEach { + var i = $0.bigEndian + mutableBuff.append(&i, length: MemoryLayout.size) + } + + return mutableBuff as Data + } +} diff --git a/Swifter/String+Swifter.swift b/Swifter/String++.swift similarity index 50% rename from Swifter/String+Swifter.swift rename to Swifter/String++.swift index f014fd52..90fa5029 100644 --- a/Swifter/String+Swifter.swift +++ b/Swifter/String++.swift @@ -27,47 +27,47 @@ import Foundation extension String { - internal func indexOf(sub: String) -> Int? { - guard let range = self.rangeOfString(sub) where !range.isEmpty else { + internal func indexOf(_ sub: String) -> Int? { + guard let range = self.range(of: sub), !range.isEmpty else { return nil } - return self.startIndex.distanceTo(range.startIndex) + return self.characters.distance(from: self.startIndex, to: range.lowerBound) } internal subscript (r: Range) -> String { get { - let startIndex = self.startIndex.advancedBy(r.startIndex) - let endIndex = startIndex.advancedBy(r.endIndex - r.startIndex) + let startIndex = self.characters.index(self.startIndex, offsetBy: r.lowerBound) + let endIndex = self.characters.index(startIndex, offsetBy: r.upperBound - r.lowerBound) return self[startIndex.. String { - let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet - allowedCharacterSet.removeCharactersInString("\n:#/?@!$&'()*+,;=") - if !all { - allowedCharacterSet.addCharactersInString("[]") - } - return self.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet)! + func urlEncodedString(_ encodeAll: Bool = false) -> String { + var allowedCharacterSet: CharacterSet = .urlQueryAllowed + allowedCharacterSet.remove(charactersIn: "\n:#/?@!$&'()*+,;=") + if !encodeAll { + allowedCharacterSet.insert(charactersIn: "[]") + } + return self.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet)! } - func parametersFromQueryString() -> Dictionary { + var queryStringParameters: Dictionary { var parameters = Dictionary() - let scanner = NSScanner(string: self) + let scanner = Scanner(string: self) var key: NSString? var value: NSString? - while !scanner.atEnd { + while !scanner.isAtEnd { key = nil - scanner.scanUpToString("=", intoString: &key) - scanner.scanString("=", intoString: nil) + scanner.scanUpTo("=", into: &key) + scanner.scanString("=", into: nil) value = nil - scanner.scanUpToString("&", intoString: &value) - scanner.scanString("&", intoString: nil) + scanner.scanUpTo("&", into: &value) + scanner.scanString("&", into: nil) if let key = key as? String, let value = value as? String { parameters.updateValue(value, forKey: key) @@ -77,20 +77,5 @@ extension String { return parameters } - func SHA1DigestWithKey(key: String) -> NSData { - let str = self.cStringUsingEncoding(NSUTF8StringEncoding) - let strLen = self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) - - let digestLen = Int(CC_SHA1_DIGEST_LENGTH) - let result = UnsafeMutablePointer.alloc(digestLen) - - let keyStr = key.cStringUsingEncoding(NSUTF8StringEncoding)! - let keyLen = key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) - - CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), keyStr, keyLen, str!, strLen, result) - - return NSData(bytes: result, length: digestLen) - } - } diff --git a/Swifter/Swifter-Bridging-Header.h b/Swifter/Swifter-Bridging-Header.h deleted file mode 100644 index d1f3c96a..00000000 --- a/Swifter/Swifter-Bridging-Header.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Swifter-Bridging-Header.h -// Swifter -// -// Copyright (c) 2014 Matt Donnelly. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -#import diff --git a/Swifter/Swifter.swift b/Swifter/Swifter.swift index d43e71aa..7416ce30 100644 --- a/Swifter/Swifter.swift +++ b/Swifter/Swifter.swift @@ -26,91 +26,107 @@ import Foundation import Accounts -public class Swifter { +extension Notification.Name { + static let SwifterCallbackNotification: Notification.Name = Notification.Name(rawValue: "SwifterCallbackNotificationName") +} + +// MARK: - Twitter URL +public enum TwitterURL { + case api + case upload + case stream + case userStream + case siteStream + case oauth + + var url: URL { + switch self { + case .api: return URL(string: "https://api.twitter.com/1.1/")! + case .upload: return URL(string: "https://upload.twitter.com/1.1/")! + case .stream: return URL(string: "https://stream.twitter.com/1.1/")! + 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/")! + } + } + +} +public class Swifter { + // MARK: - Types - public typealias JSONSuccessHandler = (json: JSON, response: NSHTTPURLResponse) -> Void - public typealias FailureHandler = (error: NSError) -> Void + 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 internal struct CallbackNotification { - static let notificationName = "SwifterCallbackNotificationName" static let optionsURLKey = "SwifterCallbackNotificationOptionsURLKey" } - - internal struct SwifterError { - static let domain = "SwifterErrorDomain" - static let appOnlyAuthenticationErrorCode = 1 - } - + internal struct DataParameters { static let dataKey = "SwifterDataParameterKey" static let fileNameKey = "SwifterDataParameterFilename" } // MARK: - Properties - - let apiURL = NSURL(string: "https://api.twitter.com/1.1/")! - let uploadURL = NSURL(string: "https://upload.twitter.com/1.1/")! - let streamURL = NSURL(string: "https://stream.twitter.com/1.1/")! - let userStreamURL = NSURL(string: "https://userstream.twitter.com/1.1/")! - let siteStreamURL = NSURL(string: "https://sitestream.twitter.com/1.1/")! - + public var client: SwifterClientProtocol // MARK: - Initializers public init(consumerKey: String, consumerSecret: String, appOnly: Bool = false) { self.client = appOnly - ? SwifterAppOnlyClient(consumerKey: consumerKey, consumerSecret: consumerSecret) - : SwifterOAuthClient(consumerKey: consumerKey, consumerSecret: consumerSecret) + ? AppOnlyClient(consumerKey: consumerKey, consumerSecret: consumerSecret) + : OAuthClient(consumerKey: consumerKey, consumerSecret: consumerSecret) } public init(consumerKey: String, consumerSecret: String, oauthToken: String, oauthTokenSecret: String) { - self.client = SwifterOAuthClient(consumerKey: consumerKey, consumerSecret: consumerSecret , accessToken: oauthToken, accessTokenSecret: oauthTokenSecret) + self.client = OAuthClient(consumerKey: consumerKey, consumerSecret: consumerSecret , accessToken: oauthToken, accessTokenSecret: oauthTokenSecret) } public init(account: ACAccount) { - self.client = SwifterAccountsClient(account: account) + self.client = AccountsClient(account: account) } deinit { - NSNotificationCenter.defaultCenter().removeObserver(self) + NotificationCenter.default.removeObserver(self) } // MARK: - JSON Requests - - internal func jsonRequestWithPath(path: String, baseURL: NSURL, method: HTTPMethodType, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler? = nil, downloadProgress: JSONSuccessHandler? = nil, success: JSONSuccessHandler? = nil, failure: SwifterHTTPRequest.FailureHandler? = nil) -> SwifterHTTPRequest { - let jsonDownloadProgressHandler: SwifterHTTPRequest.DownloadProgressHandler = { data, _, _, response in - - guard downloadProgress != nil else { return } - - if let jsonResult = try? JSON.parseJSONData(data) { - downloadProgress?(json: jsonResult, response: response) - } else { - let jsonString = NSString(data: data, encoding: NSUTF8StringEncoding) - let jsonChunks = jsonString!.componentsSeparatedByString("\r\n") as [String] + + @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 { + let jsonDownloadProgressHandler: HTTPRequest.DownloadProgressHandler = { data, _, _, response in + + guard let _ = downloadProgress else { return } + + guard let jsonResult = try? JSON.parse(jsonData: data) else { + let jsonString = String(data: data, encoding: .utf8) + let jsonChunks = jsonString!.components(separatedBy: "\r\n") for chunk in jsonChunks where !chunk.utf16.isEmpty { - if let chunkData = chunk.dataUsingEncoding(NSUTF8StringEncoding), - let jsonResult = try? JSON.parseJSONData(chunkData) { - downloadProgress?(json: jsonResult, response: response) - } + guard let chunkData = chunk.data(using: .utf8), let jsonResult = try? JSON.parse(jsonData: chunkData) else { continue } + downloadProgress?(jsonResult, response) } + return } + + downloadProgress?(jsonResult, response) } - let jsonSuccessHandler: SwifterHTTPRequest.SuccessHandler = { data, response in + let jsonSuccessHandler: HTTPRequest.SuccessHandler = { data, response in - dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) { + DispatchQueue.global(qos: .utility).async { do { - let jsonResult = try JSON.parseJSONData(data) - dispatch_async(dispatch_get_main_queue()) { - success?(json: jsonResult, response: response) + let jsonResult = try JSON.parse(jsonData: data) + DispatchQueue.main.async { + success?(jsonResult, response) } - } catch let error as NSError { - dispatch_async(dispatch_get_main_queue()) { - failure?(error: error) + } catch { + DispatchQueue.main.async { + failure?(error) } } } @@ -118,18 +134,19 @@ public class Swifter { if method == .GET { return self.client.get(path, baseURL: baseURL, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: jsonDownloadProgressHandler, success: jsonSuccessHandler, failure: failure) - } - else { + } else { return self.client.post(path, baseURL: baseURL, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: jsonDownloadProgressHandler, success: jsonSuccessHandler, failure: failure) } } - internal func getJSONWithPath(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler? = nil, downloadProgress: JSONSuccessHandler? = nil, success: JSONSuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - return self.jsonRequestWithPath(path, baseURL: baseURL, method: .GET, parameters: parameters, uploadProgress: uploadProgress, downloadProgress: downloadProgress, success: success, failure: failure) + @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 postJSONWithPath(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler? = nil, downloadProgress: JSONSuccessHandler? = nil, success: JSONSuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - return self.jsonRequestWithPath(path, baseURL: baseURL, method: .POST, 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) } } diff --git a/Swifter/SwifterAccountsClient.swift b/Swifter/SwifterAccountsClient.swift index 24a3aade..ca08f363 100644 --- a/Swifter/SwifterAccountsClient.swift +++ b/Swifter/SwifterAccountsClient.swift @@ -1,5 +1,5 @@ // -// SwifterAccountsClient.swift +// AccountsClient.swift // Swifter // // Copyright (c) 2014 Matt Donnelly. @@ -27,23 +27,23 @@ import Foundation import Accounts import Social -internal class SwifterAccountsClient: SwifterClientProtocol { +internal class AccountsClient: SwifterClientProtocol { - var credential: SwifterCredential? + var credential: Credential? init(account: ACAccount) { - self.credential = SwifterCredential(account: account) + self.credential = Credential(account: account) } - func get(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - let url = NSURL(string: path, relativeToURL: baseURL) + func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, 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() - - let socialRequest = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: SLRequestMethod.GET, URL: url, parameters: stringifiedParameters) + + let socialRequest = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: .GET, url: url, parameters: stringifiedParameters)! socialRequest.account = self.credential!.account! - let request = SwifterHTTPRequest(request: socialRequest.preparedURLRequest()) + let request = HTTPRequest(request: socialRequest.preparedURLRequest()) request.parameters = parameters request.downloadProgressHandler = downloadProgress request.successHandler = success @@ -53,31 +53,31 @@ internal class SwifterAccountsClient: SwifterClientProtocol { return request } - func post(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - let url = NSURL(string: path, relativeToURL: baseURL) + func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url) var params = parameters - var postData: NSData? + var postData: Data? var postDataKey: String? if let keyString = params[Swifter.DataParameters.dataKey] as? String { postDataKey = keyString - postData = params[postDataKey!] as? NSData + postData = params[postDataKey!] as? Data - params.removeValueForKey(Swifter.DataParameters.dataKey) - params.removeValueForKey(postDataKey!) + params.removeValue(forKey: Swifter.DataParameters.dataKey) + params.removeValue(forKey: postDataKey!) } var postDataFileName: String? if let fileName = params[Swifter.DataParameters.fileNameKey] as? String { postDataFileName = fileName - params.removeValueForKey(fileName) + params.removeValue(forKey: fileName) } let stringifiedParameters = params.stringifiedDictionary() - let socialRequest = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: SLRequestMethod.POST, URL: url, parameters: stringifiedParameters) + let socialRequest = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: .POST, url: url, parameters: stringifiedParameters)! socialRequest.account = self.credential!.account! if let data = postData { @@ -85,7 +85,7 @@ internal class SwifterAccountsClient: SwifterClientProtocol { socialRequest.addMultipartData(data, withName: postDataKey!, type: "application/octet-stream", filename: fileName) } - let request = SwifterHTTPRequest(request: socialRequest.preparedURLRequest()) + let request = HTTPRequest(request: socialRequest.preparedURLRequest()) request.parameters = parameters request.downloadProgressHandler = downloadProgress request.successHandler = success diff --git a/Swifter/SwifterAppOnlyClient.swift b/Swifter/SwifterAppOnlyClient.swift index 4ca052c4..a1280fc7 100644 --- a/Swifter/SwifterAppOnlyClient.swift +++ b/Swifter/SwifterAppOnlyClient.swift @@ -1,5 +1,5 @@ // -// SwifterAppOnlyClient.swift +// AppOnlyClient.swift // Swifter // // Copyright (c) 2014 Matt Donnelly. @@ -25,25 +25,24 @@ import Foundation -internal class SwifterAppOnlyClient: SwifterClientProtocol { +internal class AppOnlyClient: SwifterClientProtocol { var consumerKey: String var consumerSecret: String - var credential: SwifterCredential? + var credential: Credential? - var dataEncoding: NSStringEncoding + let dataEncoding: String.Encoding = .utf8 init(consumerKey: String, consumerSecret: String) { self.consumerKey = consumerKey self.consumerSecret = consumerSecret - self.dataEncoding = NSUTF8StringEncoding } - func get(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - let url = NSURL(string: path, relativeToURL: baseURL) + func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url) - let request = SwifterHTTPRequest(URL: url!, method: .GET, parameters: parameters) + let request = HTTPRequest(url: url!, method: .GET, parameters: parameters) request.downloadProgressHandler = downloadProgress request.successHandler = success request.failureHandler = failure @@ -57,10 +56,10 @@ internal class SwifterAppOnlyClient: SwifterClientProtocol { return request } - func post(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - let url = NSURL(string: path, relativeToURL: baseURL) + func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url) - let request = SwifterHTTPRequest(URL: url!, method: .POST, parameters: parameters) + let request = HTTPRequest(url: url!, method: .POST, parameters: parameters) request.downloadProgressHandler = downloadProgress request.successHandler = success request.failureHandler = failure @@ -69,7 +68,7 @@ internal class SwifterAppOnlyClient: SwifterClientProtocol { if let bearerToken = self.credential?.accessToken?.key { request.headers = ["Authorization": "Bearer \(bearerToken)"]; } else { - let basicCredentials = SwifterAppOnlyClient.base64EncodedCredentialsWithKey(self.consumerKey, secret: self.consumerSecret) + let basicCredentials = AppOnlyClient.base64EncodedCredentials(withKey: self.consumerKey, secret: self.consumerSecret) request.headers = ["Authorization": "Basic \(basicCredentials)"]; request.encodeParameters = true } @@ -78,14 +77,14 @@ internal class SwifterAppOnlyClient: SwifterClientProtocol { return request } - class func base64EncodedCredentialsWithKey(key: String, secret: String) -> String { - let encodedKey = key.urlEncodedStringWithEncoding() - let encodedSecret = secret.urlEncodedStringWithEncoding() + class func base64EncodedCredentials(withKey key: String, secret: String) -> String { + let encodedKey = key.urlEncodedString() + let encodedSecret = secret.urlEncodedString() let bearerTokenCredentials = "\(encodedKey):\(encodedSecret)" - if let data = bearerTokenCredentials.dataUsingEncoding(NSUTF8StringEncoding) { - return data.base64EncodedStringWithOptions([]) + guard let data = bearerTokenCredentials.data(using: .utf8) else { + return "" } - return String() + return data.base64EncodedString(options: []) } } diff --git a/Swifter/SwifterAuth.swift b/Swifter/SwifterAuth.swift index 51c1dd2b..e04a1c05 100644 --- a/Swifter/SwifterAuth.swift +++ b/Swifter/SwifterAuth.swift @@ -34,32 +34,32 @@ import Foundation public extension Swifter { - public typealias TokenSuccessHandler = (accessToken: SwifterCredential.OAuthAccessToken?, response: NSURLResponse) -> Void + public typealias TokenSuccessHandler = (Credential.OAuthAccessToken?, URLResponse) -> Void /** Begin Authorization with a Callback URL. - OS X only */ #if os(OSX) - public func authorizeWithCallbackURL(callbackURL: NSURL, success: TokenSuccessHandler?, failure: FailureHandler? = nil) { - self.postOAuthRequestTokenWithCallbackURL(callbackURL, success: { token, response in + public func authorize(with callbackURL: URL, success: TokenSuccessHandler?, failure: FailureHandler? = nil) { + self.postOAuthRequestToken(with: callbackURL, success: { token, response in var requestToken = token! - NSNotificationCenter.defaultCenter().addObserverForName(CallbackNotification.notificationName, object: nil, queue: NSOperationQueue.mainQueue()) { notification in - NSNotificationCenter.defaultCenter().removeObserver(self) - let url = notification.userInfo![CallbackNotification.optionsURLKey] as! NSURL - let parameters = url.query!.parametersFromQueryString() + NotificationCenter.default.addObserver(forName: .SwifterCallbackNotification, object: nil, queue: .main) { notification in + NotificationCenter.default.removeObserver(self) + let url = notification.userInfo![CallbackNotification.optionsURLKey] as! URL + let parameters = url.query!.queryStringParameters requestToken.verifier = parameters["oauth_verifier"] - self.postOAuthAccessTokenWithRequestToken(requestToken, success: { accessToken, response in - self.client.credential = SwifterCredential(accessToken: accessToken!) - success?(accessToken: accessToken!, response: response) + self.postOAuthAccessToken(with: requestToken, success: { accessToken, response in + self.client.credential = Credential(accessToken: accessToken!) + success?(accessToken!, response) }, failure: failure) } - let authorizeURL = NSURL(string: "/oauth/authorize", relativeToURL: self.apiURL) - let queryURL = NSURL(string: authorizeURL!.absoluteString + "?oauth_token=\(token!.key)")! - NSWorkspace.sharedWorkspace().openURL(queryURL) + let authorizeURL = URL(string: "oauth/authorize", relativeTo: TwitterURL.oauth.url) + let queryURL = URL(string: authorizeURL!.absoluteString + "?oauth_token=\(token!.key)")! + NSWorkspace.shared().open(queryURL) }, failure: failure) } #endif @@ -73,135 +73,117 @@ public extension Swifter { */ #if os(iOS) - public func authorizeWithCallbackURL(callbackURL: NSURL, presentFromViewController presentingViewController: UIViewController? , success: TokenSuccessHandler?, failure: FailureHandler? = nil) { - self.postOAuthRequestTokenWithCallbackURL(callbackURL, success: { token, response in + public func authorize(with callbackURL: URL, presentFrom presentingViewController: UIViewController? , success: TokenSuccessHandler?, failure: FailureHandler? = nil) { + self.postOAuthRequestToken(with: callbackURL, success: { token, response in var requestToken = token! - - NSNotificationCenter.defaultCenter().addObserverForName(CallbackNotification.notificationName, object: nil, queue: NSOperationQueue.mainQueue()) { notification in - NSNotificationCenter.defaultCenter().removeObserver(self) - presentingViewController?.presentedViewController?.dismissViewControllerAnimated(true, completion: nil) - let url = notification.userInfo![CallbackNotification.optionsURLKey] as! NSURL + NotificationCenter.default.addObserver(forName: .SwifterCallbackNotification, object: nil, queue: .main) { notification in + NotificationCenter.default.removeObserver(self) + presentingViewController?.presentedViewController?.dismiss(animated: true, completion: nil) + let url = notification.userInfo![CallbackNotification.optionsURLKey] as! URL - let parameters = url.query!.parametersFromQueryString() + let parameters = url.query!.queryStringParameters requestToken.verifier = parameters["oauth_verifier"] - self.postOAuthAccessTokenWithRequestToken(requestToken, success: { accessToken, response in - self.client.credential = SwifterCredential(accessToken: accessToken!) - success?(accessToken: accessToken!, response: response) + self.postOAuthAccessToken(with: requestToken, success: { accessToken, response in + self.client.credential = Credential(accessToken: accessToken!) + success?(accessToken!, response) }, failure: failure) } - let authorizeURL = NSURL(string: "/oauth/authorize", relativeToURL: self.apiURL) - let queryURL = NSURL(string: authorizeURL!.absoluteString + "?oauth_token=\(token!.key)")! + let authorizeURL = URL(string: "oauth/authorize", relativeTo: TwitterURL.oauth.url) + let queryURL = URL(string: authorizeURL!.absoluteString + "?oauth_token=\(token!.key)")! if #available(iOS 9.0, *) , let delegate = presentingViewController as? SFSafariViewControllerDelegate { - let safariView = SFSafariViewController(URL: queryURL) + let safariView = SFSafariViewController(url: queryURL) safariView.delegate = delegate - presentingViewController?.presentViewController(safariView, animated: true, completion: nil) + presentingViewController?.present(safariView, animated: true, completion: nil) } else { - UIApplication.sharedApplication().openURL(queryURL) + UIApplication.shared.openURL(queryURL) } }, failure: failure) } #endif - public class func handleOpenURL(url: NSURL) { - let notification = NSNotification(name: CallbackNotification.notificationName, object: nil, - userInfo: [CallbackNotification.optionsURLKey: url]) - NSNotificationCenter.defaultCenter().postNotification(notification) + public class func handleOpenURL(_ url: URL) { + let notification = Notification(name: .SwifterCallbackNotification, object: nil, userInfo: [CallbackNotification.optionsURLKey: url]) + NotificationCenter.default.post(notification) } - public func authorizeAppOnlyWithSuccess(success: TokenSuccessHandler?, failure: FailureHandler?) { - self.postOAuth2BearerTokenWithSuccess({ json, response in + public func authorizeAppOnly(success: TokenSuccessHandler?, failure: FailureHandler?) { + self.postOAuth2BearerToken(success: { json, response in if let tokenType = json["token_type"].string { if tokenType == "bearer" { let accessToken = json["access_token"].string - let credentialToken = SwifterCredential.OAuthAccessToken(key: accessToken!, secret: "") + let credentialToken = Credential.OAuthAccessToken(key: accessToken!, secret: "") - self.client.credential = SwifterCredential(accessToken: credentialToken) + self.client.credential = Credential(accessToken: credentialToken) - success?(accessToken: credentialToken, response: response) + success?(credentialToken, response) } else { - let error = NSError(domain: "Swifter", code: SwifterError.appOnlyAuthenticationErrorCode, userInfo: [NSLocalizedDescriptionKey: "Cannot find bearer token in server response"]); - failure?(error: error) + let error = SwifterError(message: "Cannot find bearer token in server response", kind: .invalidAppOnlyBearerToken) + failure?(error) } - } else if let errors = json["errors"].object { - let error = NSError(domain: SwifterError.domain, code: errors["code"]!.integer!, userInfo: [NSLocalizedDescriptionKey: errors["message"]!.string!]); - failure?(error: 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 = NSError(domain: SwifterError.domain, code: SwifterError.appOnlyAuthenticationErrorCode, userInfo: [NSLocalizedDescriptionKey: "Cannot find JSON dictionary in response"]); - failure?(error: error) + let error = SwifterError(message: "Cannot find JSON dictionary in response", kind: .invalidJSONResponse) + failure?(error) } }, failure: failure) } - public func postOAuth2BearerTokenWithSuccess(success: JSONSuccessHandler?, failure: FailureHandler?) { - let path = "/oauth2/token" + public func postOAuth2BearerToken(success: JSONSuccessHandler?, failure: FailureHandler?) { + let path = "oauth2/token" var parameters = Dictionary() parameters["grant_type"] = "client_credentials" - self.jsonRequestWithPath(path, baseURL: self.apiURL, method: .POST, parameters: parameters, success: success, failure: failure) + self.jsonRequest(path: path, baseURL: .oauth, method: .POST, parameters: parameters, success: success, failure: failure) } - public func postOAuth2InvalidateBearerTokenWithSuccess(success: TokenSuccessHandler?, failure: FailureHandler?) { - let path = "/oauth2/invalidate_token" + public func invalidateOAuth2BearerToken(success: TokenSuccessHandler?, failure: FailureHandler?) { + let path = "oauth2/invalidate_token" - self.jsonRequestWithPath(path, baseURL: self.apiURL, method: .POST, parameters: [:], success: { json, response in + self.jsonRequest(path: path, baseURL: .oauth, method: .POST, parameters: [:], success: { json, response in if let accessToken = json["access_token"].string { self.client.credential = nil - - let credentialToken = SwifterCredential.OAuthAccessToken(key: accessToken, secret: "") - - success?(accessToken: credentialToken, response: response) - } - else { - success?(accessToken: nil, response: response) + let credentialToken = Credential.OAuthAccessToken(key: accessToken, secret: "") + success?(credentialToken, response) + } else { + success?(nil, response) } - - }, failure: failure) + }, failure: failure) } - public func postOAuthRequestTokenWithCallbackURL(callbackURL: NSURL, success: TokenSuccessHandler, failure: FailureHandler?) { - let path = "/oauth/request_token" + public func postOAuthRequestToken(with callbackURL: URL, success: @escaping TokenSuccessHandler, failure: FailureHandler?) { + let path = "oauth/request_token" + let parameters: [String: Any] = ["oauth_callback": callbackURL.absoluteString] - var parameters = Dictionary() - - parameters["oauth_callback"] = callbackURL.absoluteString - - self.client.post(path, baseURL: self.apiURL, parameters: parameters, uploadProgress: nil, downloadProgress: nil, success: { - data, response in - - let responseString = NSString(data: data, encoding: NSUTF8StringEncoding) - let accessToken = SwifterCredential.OAuthAccessToken(queryString: responseString as String!) - success(accessToken: accessToken, response: response) - - }, failure: failure) + self.client.post(path, baseURL: .oauth, parameters: parameters, uploadProgress: nil, downloadProgress: nil, success: { data, response in + let responseString = String(data: data, encoding: .utf8)! + let accessToken = Credential.OAuthAccessToken(queryString: responseString) + success(accessToken, response) + }, failure: failure) } - public func postOAuthAccessTokenWithRequestToken(requestToken: SwifterCredential.OAuthAccessToken, success: TokenSuccessHandler, failure: FailureHandler?) { + public func postOAuthAccessToken(with requestToken: Credential.OAuthAccessToken, success: @escaping TokenSuccessHandler, failure: FailureHandler?) { if let verifier = requestToken.verifier { - let path = "/oauth/access_token" + let path = "oauth/access_token" + let parameters: [String: Any] = ["oauth_token": requestToken.key, "oauth_verifier": verifier] - var parameters = Dictionary() - parameters["oauth_token"] = requestToken.key - parameters["oauth_verifier"] = verifier - - self.client.post(path, baseURL: self.apiURL, parameters: parameters, uploadProgress: nil, downloadProgress: nil, success: { - data, response in + self.client.post(path, baseURL: .oauth, parameters: parameters, uploadProgress: nil, downloadProgress: nil, success: { data, response in - let responseString = NSString(data: data, encoding: NSUTF8StringEncoding) - let accessToken = SwifterCredential.OAuthAccessToken(queryString: responseString! as String) - success(accessToken: accessToken, response: response) + let responseString = String(data: data, encoding: .utf8)! + let accessToken = Credential.OAuthAccessToken(queryString: responseString) + success(accessToken, response) }, failure: failure) - } - else { - let userInfo = [NSLocalizedFailureReasonErrorKey: "Bad OAuth response received from server"] - let error = NSError(domain: SwifterError.domain, code: NSURLErrorBadServerResponse, userInfo: userInfo) - failure?(error: error) + } else { + let error = SwifterError(message: "Bad OAuth response received from server", kind: .badOAuthResponse) + failure?(error) } } diff --git a/Swifter/SwifterClientProtocol.swift b/Swifter/SwifterClientProtocol.swift index ef4e97ce..56643a30 100644 --- a/Swifter/SwifterClientProtocol.swift +++ b/Swifter/SwifterClientProtocol.swift @@ -29,10 +29,12 @@ import Social public protocol SwifterClientProtocol { - var credential: SwifterCredential? { get set } + var credential: Credential? { get set } - func get(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest + @discardableResult + func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest - func post(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest + @discardableResult + func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest } diff --git a/Swifter/SwifterCredential.swift b/Swifter/SwifterCredential.swift index 79bc242f..fb024701 100644 --- a/Swifter/SwifterCredential.swift +++ b/Swifter/SwifterCredential.swift @@ -1,5 +1,5 @@ // -// SwifterCredential.swift +// Credential.swift // Swifter // // Copyright (c) 2014 Matt Donnelly. @@ -26,7 +26,7 @@ import Foundation import Accounts -public class SwifterCredential { +public class Credential { public struct OAuthAccessToken { @@ -43,7 +43,7 @@ public class SwifterCredential { } public init(queryString: String) { - var attributes = queryString.parametersFromQueryString() + var attributes = queryString.queryStringParameters self.key = attributes["oauth_token"]! self.secret = attributes["oauth_token_secret"]! diff --git a/Swifter/SwifterError.swift b/Swifter/SwifterError.swift new file mode 100644 index 00000000..24510bf9 --- /dev/null +++ b/Swifter/SwifterError.swift @@ -0,0 +1,47 @@ +// +// SwifterError.swift +// Swifter +// +// Created by Andy Liang on 2016-08-11. +// Copyright © 2016 Matt Donnelly. All rights reserved. +// + +import Foundation + +public struct SwifterError: Error { + + public enum ErrorKind: CustomStringConvertible { + case invalidAppOnlyBearerToken + case responseError(code: Int) + case invalidJSONResponse + case badOAuthResponse + case urlResponseError(status: Int, headers: [NSObject: AnyObject], errorCode: Int) + case jsonParseError + + public var description: String { + switch self { + case .invalidAppOnlyBearerToken: + return "invalidAppOnlyBearerToken" + case .invalidJSONResponse: + return "invalidJSONResponse" + case .responseError(let code): + return "responseError(code: \(code))" + case .badOAuthResponse: + return "badOAuthResponse" + case .urlResponseError(let code, let headers, let errorCode): + return "urlResponseError(status: \(code), headers: \(headers), errorCode: \(errorCode)" + case .jsonParseError: + return "jsonParseError" + } + } + + } + + public var message: String + public var kind: ErrorKind + + public var localizedDescription: String { + return "[\(kind.description)] - \(message)" + } + +} diff --git a/Swifter/SwifterFavorites.swift b/Swifter/SwifterFavorites.swift index e710cbdc..46a36804 100644 --- a/Swifter/SwifterFavorites.swift +++ b/Swifter/SwifterFavorites.swift @@ -34,7 +34,7 @@ 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 getFavoritesListWithCount(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getRecentlyFavouritedTweets(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "favorites/list.json" var parameters = Dictionary() @@ -42,37 +42,19 @@ public extension Swifter { parameters["since_id"] ??= sinceID parameters["max_id"] ??= maxID - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } - - public func getFavoritesListWithUserID(userID: String, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "favorites/list.json" - - var parameters = Dictionary() - parameters["user_id"] = userID - parameters["count"] ??= count - parameters["since_id"] ??= sinceID - parameters["max_id"] ??= maxID - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) - } - - public func getFavoritesListWithScreenName(screenName: String, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + + public func getRecentlyFavouritedTweets(for userTag: UserTag, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "favorites/list.json" - + var parameters = Dictionary() - parameters["screen_name"] = screenName + parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["since_id"] ??= sinceID parameters["max_id"] ??= maxID - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) + + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -82,16 +64,14 @@ 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 postDestroyFavoriteWithID(id: String, includeEntities: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func unfavouriteTweet(forID id: String, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "favorites/destroy.json" var parameters = Dictionary() parameters["id"] = id parameters["include_entities"] ??= includeEntities - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -101,16 +81,14 @@ 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 postCreateFavoriteWithID(id: String, includeEntities: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: SwifterHTTPRequest.FailureHandler? = nil) { + public func favouriteTweet(forID id: String, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: HTTPRequest.FailureHandler? = nil) { let path = "favorites/create.json" var parameters = Dictionary() parameters["id"] = id parameters["include_entities"] ??= includeEntities - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterFollowers.swift b/Swifter/SwifterFollowers.swift index 301e25cf..0020610c 100644 --- a/Swifter/SwifterFollowers.swift +++ b/Swifter/SwifterFollowers.swift @@ -26,21 +26,19 @@ import Foundation public extension Swifter { - + /** GET friendships/no_retweets/ids 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 getFriendshipsNoRetweetsIDsWithStringifyIDs(stringifyIDs: Bool = true, success: ((ids: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func listOfNoRetweetsFriends(stringifyIDs: Bool = true, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "friendships/no_retweets/ids.json" var parameters = Dictionary() parameters["stringify_ids"] = stringifyIDs - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -53,31 +51,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 getFriendsIDsWithID(id: String, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friends/ids.json" - - var parameters = Dictionary() - parameters["id"] = id - parameters["cursor"] ??= cursor - parameters["stringify_ids"] ??= stringifyIDs - parameters["count"] ??= count - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - }, failure: failure) - } - - public func getFriendsIDsWithScreenName(screenName: String, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getUserFollowingIDs(for userTag: UserTag, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "friends/ids.json" var parameters = Dictionary() - parameters["screen_name"] = screenName + parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["stringify_ids"] ??= stringifyIDs parameters["count"] ??= count - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -90,31 +74,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 getFollowersIDsWithID(id: String, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getUserFollowersIDs(for userTag: UserTag, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "followers/ids.json" var parameters = Dictionary() - parameters["id"] = id + parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["stringify_ids"] ??= stringifyIDs parameters["count"] ??= count - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - }, failure: failure) - } - - public func getFollowersIDsWithScreenName(screenName: String, cursor: String? = nil, stringifyIDs: Bool? = nil, count: Int? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "followers/ids.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["cursor"] ??= cursor - parameters["stringify_ids"] ??= stringifyIDs - parameters["count"] ??= count - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -123,15 +93,15 @@ public extension Swifter { Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. */ - public func getFriendshipsIncomingWithCursor(cursor: String? = nil, stringifyIDs: String? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getIncomingPendingFollowRequests(cursor: String? = nil, stringifyIDs: String? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "friendships/incoming.json" var parameters = Dictionary() parameters["cursor"] ??= cursor parameters["stringify_ids"] ??= stringifyIDs - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -140,15 +110,15 @@ 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 getFriendshipsOutgoingWithCursor(cursor: String? = nil, stringifyIDs: String? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getOutgoingPendingFollowRequests(cursor: String? = nil, stringifyIDs: String? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "friendships/outgoing.json" var parameters = Dictionary() parameters["cursor"] ??= cursor parameters["stringify_ids"] ??= stringifyIDs - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -161,27 +131,15 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func postCreateFriendshipWithID(id: String, follow: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func followUser(for userTag: UserTag, follow: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "friendships/create.json" var parameters = Dictionary() - parameters["id"] = id + parameters[userTag.key] = userTag.value parameters["follow"] ??= follow - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postCreateFriendshipWithScreenName(screenName: String, follow: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friendships/create.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["follow"] ??= follow - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -194,25 +152,14 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func postDestroyFriendshipWithID(id: String, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func unfollowUser(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "friendships/destroy.json" var parameters = Dictionary() - parameters["id"] = id + parameters[userTag.key] = userTag.value - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postDestroyFriendshipWithScreenName(screenName: String, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friendships/destroy.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -221,29 +168,16 @@ public extension Swifter { Allows one to enable or disable retweets and device notifications from the specified user. */ - public func postUpdateFriendshipWithID(id: String, device: Bool? = nil, retweets: Bool? = nil, success: ((user: Dictionary?) -> Void)? = 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() - parameters["id"] = id + parameters[userTag.key] = userTag.value parameters["device"] ??= device parameters["retweets"] ??= retweets - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postUpdateFriendshipWithScreenName(screenName: String, device: Bool? = nil, retweets: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friendships/update.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["device"] ??= device - parameters["retweets"] ??= retweets - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -252,29 +186,22 @@ public extension Swifter { Returns detailed information about the relationship between two arbitrary users. */ - public func getFriendshipsShowWithSourceID(sourceID: String, targetID: String? = nil, orTargetScreenName targetScreenName: String? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friendships/show.json" - - var parameters = Dictionary() - parameters["source_id"] = sourceID - parameters["target_id"] ??= targetID - parameters["targetScreenName"] ??= targetScreenName - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getFriendshipsShowWithSourceScreenName(sourceScreenName: String, targetID: String? = nil, orTargetScreenName targetScreenName: String? = nil, success: ((user: Dictionary?) -> Void)? = 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() - parameters["source_screen_name"] = sourceScreenName - parameters["target_id"] ??= targetID - parameters["targetScreenName"] ??= targetScreenName + switch sourceTag { + case .id: parameters["source_id"] = sourceTag.value + case .screenName: parameters["source_screen_name"] = sourceTag.value + } + + switch targetTag { + case .id: parameters["target_id"] = targetTag.value + case .screenName: parameters["target_screen_name"] = targetTag.value + } - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -285,34 +212,18 @@ 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 getFriendsListWithID(id: String, cursor: String? = nil, count: Int? = nil, skipStatus: Bool? = nil, includeUserEntities: Bool? = nil, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friends/list.json" - - var parameters = Dictionary() - parameters["id"] = id - parameters["cursor"] ??= cursor - parameters["count"] ??= count - parameters["skip_status"] ??= skipStatus - parameters["include_user_entities"] ??= includeUserEntities - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - }, failure: failure) - } - - public func getFriendsListWithScreenName(screenName: String, cursor: String? = nil, count: Int? = nil, skipStatus: Bool? = nil, includeUserEntities: Bool? = nil, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = 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() - parameters["screen_name"] = screenName + parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["count"] ??= count parameters["skip_status"] ??= skipStatus parameters["include_user_entities"] ??= includeUserEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["users"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -323,33 +234,18 @@ 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 getFollowersListWithID(id: String, cursor: String? = nil, count: Int? = nil, skipStatus: Bool? = nil, includeUserEntities: Bool? = nil, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = 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() - parameters["id"] = id + parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["count"] ??= count parameters["skip_status"] ??= skipStatus parameters["include_user_entities"] ??= includeUserEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - }, failure: failure) - } - - public func getFollowersListWithScreenName(screenName: String, cursor: String? = nil, count: Int? = nil, skipStatus: Bool? = nil, includeUserEntities: Bool? = nil, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "followers/list.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["cursor"] ??= cursor - parameters["count"] ??= count - parameters["skip_status"] ??= skipStatus - parameters["include_user_entities"] ??= includeUserEntities - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["users"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -358,25 +254,14 @@ 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 getFriendshipsLookupWithScreenNames(screenNames: [String], success: ((friendships: [JSONValue]?) -> Void)? = nil, failure: FailureHandler?) { + public func lookupFriendship(with usersTag: UsersTag, success: SuccessHandler? = nil, failure: FailureHandler?) { let path = "friendships/lookup.json" var parameters = Dictionary() - parameters["screen_name"] = screenNames.joinWithSeparator(",") + parameters[usersTag.key] = usersTag.value - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(friendships: json.array) - }, failure: failure) - } - - public func getFriendshipsLookupWithIDs(ids: [String], success: ((friendships: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "friendships/lookup.json" - - var parameters = Dictionary() - parameters["user_id"] = ids.joinWithSeparator(",") - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(friendships: json.array) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } diff --git a/Swifter/SwifterHTTPRequest.swift b/Swifter/SwifterHTTPRequest.swift index ac21ac54..8d28c09c 100644 --- a/Swifter/SwifterHTTPRequest.swift +++ b/Swifter/SwifterHTTPRequest.swift @@ -1,5 +1,5 @@ // -// SwifterHTTPRequest.swift +// HTTPRequest.swift // Swifter // // Copyright (c) 2014 Matt Donnelly. @@ -42,144 +42,130 @@ public enum HTTPMethodType: String { case CONNECT } -public class SwifterHTTPRequest: NSObject, NSURLSessionDataDelegate { +public class HTTPRequest: NSObject, URLSessionDataDelegate { - public typealias UploadProgressHandler = (bytesWritten: Int, totalBytesWritten: Int, totalBytesExpectedToWrite: Int) -> Void - public typealias DownloadProgressHandler = (data: NSData, totalBytesReceived: Int, totalBytesExpectedToReceive: Int, response: NSHTTPURLResponse) -> Void - public typealias SuccessHandler = (data: NSData, response: NSHTTPURLResponse) -> Void - public typealias FailureHandler = (error: NSError) -> Void + public typealias UploadProgressHandler = (_ bytesWritten: Int, _ totalBytesWritten: Int, _ totalBytesExpectedToWrite: Int) -> Void + public typealias DownloadProgressHandler = (Data, _ totalBytesReceived: Int, _ totalBytesExpectedToReceive: Int, HTTPURLResponse) -> Void + public typealias SuccessHandler = (Data, HTTPURLResponse) -> Void + public typealias FailureHandler = (Error) -> Void internal struct DataUpload { - var data: NSData + var data: Data var parameterName: String var mimeType: String? var fileName: String? } - let URL: NSURL + let url: URL let HTTPMethod: HTTPMethodType - var request: NSMutableURLRequest? - var dataTask: NSURLSessionDataTask! + var request: URLRequest? + var dataTask: URLSessionDataTask! - var headers: Dictionary + var headers: Dictionary = [:] var parameters: Dictionary var encodeParameters: Bool - var uploadData: [DataUpload] + var uploadData: [DataUpload] = [] - var dataEncoding: NSStringEncoding + var dataEncoding: String.Encoding = .utf8 - var timeoutInterval: NSTimeInterval + var timeoutInterval: TimeInterval = 60 - var HTTPShouldHandleCookies: Bool + var HTTPShouldHandleCookies: Bool = false - var response: NSHTTPURLResponse! - var responseData: NSMutableData + var response: HTTPURLResponse! + var responseData: Data = Data() var uploadProgressHandler: UploadProgressHandler? var downloadProgressHandler: DownloadProgressHandler? var successHandler: SuccessHandler? var failureHandler: FailureHandler? - public init(URL: NSURL, method: HTTPMethodType = .GET, parameters: Dictionary = [:]) { - self.URL = URL + public init(url: URL, method: HTTPMethodType = .GET, parameters: Dictionary = [:]) { + self.url = url self.HTTPMethod = method - self.headers = [:] self.parameters = parameters self.encodeParameters = false - self.uploadData = [] - self.dataEncoding = NSUTF8StringEncoding - self.timeoutInterval = 60 - self.HTTPShouldHandleCookies = false - self.responseData = NSMutableData() } - public init(request: NSURLRequest) { - self.request = request as? NSMutableURLRequest - self.URL = request.URL! - self.HTTPMethod = HTTPMethodType(rawValue: request.HTTPMethod!)! - self.headers = [:] + public init(request: URLRequest) { + self.request = request + self.url = request.url! + self.HTTPMethod = HTTPMethodType(rawValue: request.httpMethod!)! self.parameters = [:] self.encodeParameters = true - self.uploadData = [] - self.dataEncoding = NSUTF8StringEncoding - self.timeoutInterval = 60 - self.HTTPShouldHandleCookies = false - self.responseData = NSMutableData() } public func start() { + + if request == nil { - self.request = NSMutableURLRequest(URL: self.URL) - self.request!.HTTPMethod = self.HTTPMethod.rawValue + self.request = URLRequest(url: self.url) + self.request!.httpMethod = self.HTTPMethod.rawValue self.request!.timeoutInterval = self.timeoutInterval - self.request!.HTTPShouldHandleCookies = self.HTTPShouldHandleCookies + self.request!.httpShouldHandleCookies = self.HTTPShouldHandleCookies for (key, value) in headers { self.request!.setValue(value, forHTTPHeaderField: key) } - - let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.dataEncoding)) + + let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.dataEncoding.rawValue)) let nonOAuthParameters = self.parameters.filter { key, _ in !key.hasPrefix("oauth_") } - if self.uploadData.count > 0 { - let boundary = "----------SwIfTeRhTtPrEqUeStBoUnDaRy" + if !self.uploadData.isEmpty { + let boundary = "----------HTTPRequestBoUnDaRy" let contentType = "multipart/form-data; boundary=\(boundary)" self.request!.setValue(contentType, forHTTPHeaderField:"Content-Type") - let body = NSMutableData(); + var body = Data() for dataUpload: DataUpload in self.uploadData { - let multipartData = SwifterHTTPRequest.mulipartContentWithBounday(boundary, data: dataUpload.data, fileName: dataUpload.fileName, parameterName: dataUpload.parameterName, mimeType: dataUpload.mimeType) - - body.appendData(multipartData) + let multipartData = HTTPRequest.mulipartContent(with: boundary, data: dataUpload.data, fileName: dataUpload.fileName, parameterName: dataUpload.parameterName, mimeType: dataUpload.mimeType) + body.append(multipartData) } for (key, value): (String, Any) in nonOAuthParameters { - body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) - body.appendData("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) - body.appendData("\(value)".dataUsingEncoding(NSUTF8StringEncoding)!) + body.append("\r\n--\(boundary)\r\n".data(using: .utf8)!) + body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!) + body.append("\(value)".data(using: .utf8)!) } - body.appendData("\r\n--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) + body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!) - self.request!.setValue("\(body.length)", forHTTPHeaderField: "Content-Length") - self.request!.HTTPBody = body - } - else if nonOAuthParameters.count > 0 { + self.request!.setValue("\(body.count)", forHTTPHeaderField: "Content-Length") + self.request!.httpBody = body + } else if !nonOAuthParameters.isEmpty { if self.HTTPMethod == .GET || self.HTTPMethod == .HEAD || self.HTTPMethod == .DELETE { - let queryString = nonOAuthParameters.urlEncodedQueryStringWithEncoding(self.dataEncoding) - self.request!.URL = self.URL.appendQueryString(queryString) + let queryString = nonOAuthParameters.urlEncodedQueryString(using: self.dataEncoding) + self.request!.url = self.url.append(queryString: queryString) self.request!.setValue("application/x-www-form-urlencoded; charset=\(charset)", forHTTPHeaderField: "Content-Type") - } - else { - var queryString = String() + } else { + var queryString = "" if self.encodeParameters { - queryString = nonOAuthParameters.urlEncodedQueryStringWithEncoding(self.dataEncoding) + queryString = nonOAuthParameters.urlEncodedQueryString(using: self.dataEncoding) self.request!.setValue("application/x-www-form-urlencoded; charset=\(charset)", forHTTPHeaderField: "Content-Type") - } - else { - queryString = nonOAuthParameters.queryStringWithEncoding() + } else { + queryString = nonOAuthParameters.queryString } - if let data = queryString.dataUsingEncoding(self.dataEncoding) { - self.request!.setValue(String(data.length), forHTTPHeaderField: "Content-Length") - self.request!.HTTPBody = data + if let data = queryString.data(using: self.dataEncoding) { + self.request!.setValue(String(data.count), forHTTPHeaderField: "Content-Length") + self.request!.httpBody = data } } } } - dispatch_async(dispatch_get_main_queue()) { - let session = NSURLSession(configuration: .defaultSessionConfiguration(), delegate: self, delegateQueue: .mainQueue()) - self.dataTask = session.dataTaskWithRequest(self.request!) + DispatchQueue.main.async { + let session = URLSession(configuration: .default, delegate: self, delegateQueue: .main) + self.dataTask = session.dataTask(with: self.request!) self.dataTask.resume() #if os(iOS) - UIApplication.sharedApplication().networkActivityIndicatorVisible = true + UIApplication.shared.isNetworkActivityIndicatorVisible = true #endif } } @@ -188,101 +174,79 @@ public class SwifterHTTPRequest: NSObject, NSURLSessionDataDelegate { self.dataTask.cancel() } - public func addMultipartData(data: NSData, parameterName: String, mimeType: String?, fileName: String?) -> Void { + public func add(multipartData data: Data, parameterName: String, mimeType: String?, fileName: String?) -> Void { let dataUpload = DataUpload(data: data, parameterName: parameterName, mimeType: mimeType, fileName: fileName) self.uploadData.append(dataUpload) } - private class func mulipartContentWithBounday(boundary: String, data: NSData, fileName: String?, parameterName: String, mimeType mimeTypeOrNil: String?) -> NSData { + private class func mulipartContent(with boundary: String, data: Data, fileName: String?, parameterName: String, mimeType mimeTypeOrNil: String?) -> Data { let mimeType = mimeTypeOrNil ?? "application/octet-stream" - - let tempData = NSMutableData() - - tempData.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) - let fileNameContentDisposition = fileName != nil ? "filename=\"\(fileName)\"" : "" let contentDisposition = "Content-Disposition: form-data; name=\"\(parameterName)\"; \(fileNameContentDisposition)\r\n" - - tempData.appendData(contentDisposition.dataUsingEncoding(NSUTF8StringEncoding)!) - tempData.appendData("Content-Type: \(mimeType)\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) - tempData.appendData(data) - tempData.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) - + + var tempData = Data() + tempData.append("--\(boundary)\r\n".data(using: .utf8)!) + tempData.append(contentDisposition.data(using: .utf8)!) + tempData.append("Content-Type: \(mimeType)\r\n\r\n".data(using: .utf8)!) + tempData.append(data) + tempData.append("\r\n".data(using: .utf8)!) return tempData } - public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) { + // MARK: - URLSessionDataDelegate + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { #if os(iOS) - UIApplication.sharedApplication().networkActivityIndicatorVisible = false + UIApplication.shared.isNetworkActivityIndicatorVisible = false #endif if let error = error { - self.failureHandler?(error: error) + self.failureHandler?(error) return } - if self.response.statusCode >= 400 { - let responseString = NSString(data: self.responseData, encoding: self.dataEncoding) - let responseErrorCode = SwifterHTTPRequest.responseErrorCode(self.responseData) ?? 0 - let localizedDescription = SwifterHTTPRequest.descriptionForHTTPStatus(self.response.statusCode, responseString: responseString! as String) - let userInfo = [ - NSLocalizedDescriptionKey: localizedDescription, - "Response-Headers": self.response.allHeaderFields, - "Response-ErrorCode": responseErrorCode] - let error = NSError(domain: NSURLErrorDomain, code: self.response.statusCode, userInfo: userInfo as [NSObject : AnyObject]) - self.failureHandler?(error: error) + guard self.response.statusCode >= 400 else { + self.successHandler?(self.responseData, self.response) return } + let responseString = String(data: responseData, encoding: dataEncoding)! + let errorCode = HTTPRequest.responseErrorCode(for: responseData) ?? 0 + let localizedDescription = HTTPRequest.description(for: response.statusCode, response: responseString) - self.successHandler?(data: self.responseData, response: self.response) + let error = SwifterError(message: localizedDescription, kind: .urlResponseError(status: response.statusCode, headers: response.allHeaderFields as [NSObject : AnyObject], errorCode: errorCode)) + self.failureHandler?(error) } - public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { - self.responseData.appendData(data) + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + self.responseData.append(data) let expectedContentLength = Int(self.response!.expectedContentLength) - let totalBytesReceived = self.responseData.length + let totalBytesReceived = self.responseData.count - guard data.length > 0 else { return } - self.downloadProgressHandler?(data: data, totalBytesReceived: totalBytesReceived, totalBytesExpectedToReceive: expectedContentLength, response: self.response) + guard !data.isEmpty else { return } + self.downloadProgressHandler?(data, totalBytesReceived, expectedContentLength, self.response) } - public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) { - self.response = response as? NSHTTPURLResponse - self.responseData.length = 0 - completionHandler(.Allow) + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { + self.response = response as? HTTPURLResponse + self.responseData.count = 0 + completionHandler(.allow) } - public func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { - self.uploadProgressHandler?(bytesWritten: Int(bytesSent), totalBytesWritten: Int(totalBytesSent), totalBytesExpectedToWrite: Int(totalBytesExpectedToSend)) - } - - class func stringWithData(data: NSData, encodingName: String?) -> String { - var encoding: UInt = NSUTF8StringEncoding - - if let encodingName = encodingName { - let encodingNameString = encodingName as NSString as CFStringRef - encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(encodingNameString)) - - if encoding == UInt(kCFStringEncodingInvalidId) { - encoding = NSUTF8StringEncoding; // by default - } - } - - return NSString(data: data, encoding: encoding)! as String + public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + self.uploadProgressHandler?(Int(bytesSent), Int(totalBytesSent), Int(totalBytesExpectedToSend)) } + + // MARK: - Error Responses - class func responseErrorCode(data: NSData) -> Int? { - if let json = try? NSJSONSerialization.JSONObjectWithData(data, options: []), - dictionary = json as? NSDictionary, - errors = dictionary["errors"] as? [NSDictionary], - code = errors.first?["code"] as? Int { - return code + class func responseErrorCode(for data: Data) -> Int? { + guard let code = JSON(data)["errors"].array?.first?["code"].integer else { + return nil } - return nil + return code } - class func descriptionForHTTPStatus(status: Int, responseString: String) -> String { + class func description(for status: Int, response string: String) -> String { var s = "HTTP Status \(status)" let description: String @@ -336,7 +300,7 @@ public class SwifterHTTPRequest: NSObject, NSURLSessionDataDelegate { } if !description.isEmpty { - s = s + ": " + description + ", Response: " + responseString + s = s + ": " + description + ", Response: " + string } return s diff --git a/Swifter/SwifterHelp.swift b/Swifter/SwifterHelp.swift index 39bb4d31..502d5d20 100644 --- a/Swifter/SwifterHelp.swift +++ b/Swifter/SwifterHelp.swift @@ -34,12 +34,10 @@ public extension Swifter { It is recommended applications request this endpoint when they are loaded, but no more than once a day. */ - public func getHelpConfigurationWithSuccess(success: ((config: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getHelpConfiguration(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "help/configuration.json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(config: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -47,12 +45,10 @@ 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 getHelpLanguagesWithSuccess(success: ((languages: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getHelpLanguages(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "help/languages.json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(languages: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -60,12 +56,10 @@ public extension Swifter { Returns Twitter's Privacy Policy. */ - public func getHelpPrivacyWithSuccess(success: ((privacy: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getHelpPrivacy(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "help/privacy.json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(privacy: json["privacy"].string) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json["privacy"]) }, failure: failure) } /** @@ -73,12 +67,10 @@ 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 getHelpTermsOfServiceWithSuccess(success: ((tos: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getHelpTermsOfService(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "help/tos.json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(tos: json["tos"].string) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json["tos"]) }, failure: failure) } /** @@ -96,15 +88,13 @@ public extension Swifter { Read more about REST API Rate Limiting in v1.1 and review the limits. */ - public func getRateLimitsForResources(resources: [String], success: ((rateLimitStatus: Dictionary?) -> Void)? = 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.joinWithSeparator(",") + parameters["resources"] = resources.joined(separator: ",") - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(rateLimitStatus: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterLists.swift b/Swifter/SwifterLists.swift index 6b5eb5d2..b63f14f6 100644 --- a/Swifter/SwifterLists.swift +++ b/Swifter/SwifterLists.swift @@ -36,38 +36,26 @@ 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 getListsSubscribedByUserWithReverse(reverse: Bool?, success: ((lists: [JSONValue]?) -> Void)?, failure: FailureHandler?) { + public func getSubscribedLists(reverse: Bool?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/list.json" var parameters = Dictionary() parameters["reverse"] ??= reverse - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json.array) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } - public func getListsSubscribedByUserWithID(userID: String, reverse: Bool?, success: ((lists: [JSONValue]?) -> Void)?, failure: FailureHandler?) { + public func getSubscribedLists(for userTag: UserTag, reverse: Bool?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/list.json" var parameters = Dictionary() - parameters["userID"] = userID + parameters[userTag.key] = userTag.value parameters["reverse"] ??= reverse - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json.array) - }, failure: failure) - } - - public func getListsSubscribedByUserWithScreenName(screenName: String, reverse: Bool?, success: ((lists: [JSONValue]?) -> Void)?, failure: FailureHandler?) { - let path = "lists/list.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["reverse"] ??= reverse - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json.array) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -76,72 +64,21 @@ 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 getListsStatusesWithListID(listID: String, ownerScreenName: String, sinceID: String?, maxID: String?, count: Int?, includeEntities: Bool?, includeRTs: Bool?, success: ((statuses: [JSONValue]?) -> Void)?, failure: FailureHandler?) { + public func listTweets(for listTag: ListTag, sinceID: String?, maxID: String?, count: Int?, includeEntities: Bool?, includeRTs: Bool?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/statuses.json" var parameters = Dictionary() - parameters["list_id"] = listID - parameters["owner_screen_name"] = ownerScreenName + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } parameters["since_id"] ??= sinceID parameters["max_id"] ??= maxID parameters["count"] ??= count parameters["include_entities"] ??= includeEntities parameters["include_rts"] ??= includeRTs - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) - } - - public func getListsStatusesWithListID(listID: String, ownerID: String, sinceID: String?, maxID: String?, count: Int?, includeEntities: Bool?, includeRTs: Bool?, success: ((statuses: [JSONValue]?) -> Void)?, failure: FailureHandler?) { - let path = "lists/statuses.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["owner_id"] = ownerID - parameters["since_id"] ??= sinceID - parameters["max_id"] ??= maxID - parameters["count"] ??= count - parameters["include_entities"] ??= includeEntities - parameters["include_rts"] ??= includeRTs - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) - } - - public func getListsStatusesWithSlug(slug: String, ownerScreenName: String, sinceID: String?, maxID: String?, count: Int?, includeEntities: Bool?, includeRTs: Bool?, success: ((statuses: [JSONValue]?) -> Void)?, failure: FailureHandler?) { - let path = "lists/statuses.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - parameters["since_id"] ??= sinceID - parameters["max_id"] ??= maxID - parameters["count"] ??= count - parameters["include_entities"] ??= includeEntities - parameters["include_rts"] ??= includeRTs - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) - } - - public func getListsStatusesWithSlug(slug: String, ownerID: String, sinceID: String?, maxID: String?, count: Int?, includeEntities: Bool?, includeRTs: Bool?, success: ((statuses: [JSONValue]?) -> Void)?, failure: FailureHandler?) { - let path = "lists/statuses.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["since_id"] ??= sinceID - parameters["max_id"] ??= maxID - parameters["count"] ??= count - parameters["include_entities"] ??= includeEntities - parameters["include_rts"] ??= includeRTs - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -149,80 +86,17 @@ 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 postListsMembersDestroyWithListID(listID: String, userID: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["user_id"] = userID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyWithListID(listID: String, screenName: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { + public func removeMemberFromList(for listTag: ListTag, user userTag: UserTag, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/members/destroy.json" var parameters = Dictionary() - parameters["list_id"] = listID - parameters["screen_name"] = screenName + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } + parameters[userTag.key] = userTag.value - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyWithSlug(slug: String, userID: String, ownerScreenName: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["userID"] = userID - parameters["owner_screen_name"] = ownerScreenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyWithSlug(slug: String, screenName: String, ownerScreenName: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["screen_name"] = screenName - parameters["owner_screen_name"] = ownerScreenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyWithSlug(slug: String, userID: String, ownerID: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["userID"] = userID - parameters["owner_id"] = ownerID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyWithSlug(slug: String, screenName: String, ownerID: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["screen_name"] = screenName - parameters["owner_id"] = ownerID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -230,32 +104,16 @@ 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 getListsMembershipsWithUserID(userID: String, cursor: String?, filterToOwnedLists: Bool?, success: ((lists: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/memberships.json" - - var parameters = Dictionary() - parameters["user_id"] = userID - parameters["cursor"] ??= cursor - parameters["filter_to_owned_lists"] ??= filterToOwnedLists - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json["lists"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - - public func getListsMembershipsWithScreenName(screenName: String, cursor: String?, filterToOwnedLists: Bool?, success: ((lists: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { + public func getListMemberships(for userTag: UserTag, cursor: String?, filterToOwnedLists: Bool?, success: CursorSuccessHandler?, failure: FailureHandler?) { let path = "lists/memberships.json" var parameters = Dictionary() - parameters["screen_name"] = screenName - + parameters[userTag.key] = userTag.value parameters["cursor"] ??= cursor parameters["filter_to_owned_lists"] ??= filterToOwnedLists - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json["lists"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["lists"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -264,67 +122,20 @@ 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 getListsSubscribersWithListID(listID: String, ownerScreenName: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["owner_screen_name"] ??= ownerScreenName - parameters["cursor"] ??= cursor - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - - public func getListsSubscribersWithListID(listID: String, ownerID: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["owner_id"] ??= ownerID - parameters["cursor"] ??= cursor - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - - public func getListsSubscribersWithSlug(slug: String, ownerScreenName: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] ??= ownerScreenName - parameters["cursor"] ??= cursor - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - - public func getListsSubscribersWithSlug(slug: String, ownerID: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { + public func getListSubscribers(for listTag: ListTag, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: CursorSuccessHandler?, failure: FailureHandler?) { let path = "lists/subscribers.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] ??= ownerID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } parameters["cursor"] ??= cursor parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["users"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -333,52 +144,16 @@ public extension Swifter { Subscribes the authenticated user to the specified list. */ - public func postListsSubscribersCreateWithListID(listID: String, ownerScreenName: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" - - var parameters = Dictionary() - parameters["owner_screen_name"] = ownerScreenName - parameters["list_id"] = listID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsSubscribersCreateWithListID(listID: String, ownerID: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func subsribeToList(for listTag: ListTag, owner ownerTag: UserTag, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/subscribers/create.json" var parameters = Dictionary() - parameters["owner_id"] = ownerID - parameters["list_id"] = listID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsSubscribersCreateWithSlug(slug: String, ownerScreenName: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" - - var parameters = Dictionary() - parameters["owner_screen_name"] = ownerScreenName - parameters["slug"] = slug - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsSubscribersCreateWithSlug(slug: String, ownerID: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" - - var parameters = Dictionary() - parameters["owner_id"] = ownerID - parameters["slug"] = slug - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -386,98 +161,19 @@ public extension Swifter { Check if the specified user is a subscriber of the specified list. Returns the user if they are subscriber. */ - public func getListsSubscribersShowWithListID(listID: String, userID: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func checkListSubcription(of userTag: UserTag, for listTag: ListTag, includeEntities: Bool?, skipStatus: Bool?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/subscribers/show.json" var parameters = Dictionary() - parameters["list_id"] = listID - parameters["user_id"] = userID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsSubscribersShowWithListID(listID: String, screenName: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/show.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["screen_name"] = screenName - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsSubscribersShowWithSlug(slug: String, ownerID: String, userID: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["user_id"] = userID - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsSubscribersShowWithSlug(slug: String, ownerID: String, screenName: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["screen_name"] = screenName - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsSubscribersShowWithSlug(slug: String, ownerScreenName: String, userID: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - parameters["user_id"] = userID - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsSubscribersShowWithSlug(slug: String, ownerScreenName: String, screenName: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - parameters["screen_name"] = screenName - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -485,39 +181,16 @@ public extension Swifter { Unsubscribes the authenticated user from the specified list. */ - public func postListsSubscribersDestroyWithListID(listID: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/destroy.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsSubscribersDestroyWithSlug(slug: String, ownerScreenName: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/destroy.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsSubscribersDestroyWithSlug(slug: String, ownerID: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func unsubscribeFromList(for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/subscribers/destroy.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -527,104 +200,20 @@ 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 postListsMembersCreateWithListID(listID: String, userIDs: [String], includeEntities: Bool?, skipStatus: Bool?, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/create_all.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersCreateWithListID(listID: String, screenNames: [String], includeEntities: Bool?, skipStatus: Bool?, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/create_all.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["screen_name"] = screenNames.joinWithSeparator(",") - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersCreateWithSlug(slug: String, ownerID: String, userIDs: [String], includeEntities: Bool?, skipStatus: Bool?, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { + public func subscribeUsersToList(for listTag: ListTag, users usersTag: UsersTag, includeEntities: Bool?, skipStatus: Bool?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/members/create_all.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } + parameters[usersTag.key] = usersTag.value parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersCreateWithSlug(slug: String, ownerID: String, screenNames: [String], includeEntities: Bool?, skipStatus: Bool?, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/create_all.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["screen_name"] = screenNames.joinWithSeparator(",") - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersCreateWithSlug(slug: String, ownerScreenName: String, userIDs: [String], includeEntities: Bool?, skipStatus: Bool?, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/create_all.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersCreateWithSlug(slug: String, ownerScreenName: String, screenNames: [String], includeEntities: Bool?, skipStatus: Bool?, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/create_all.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - parameters["screen_name"] = screenNames.joinWithSeparator(",") - - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -632,77 +221,19 @@ public extension Swifter { Check if the specified user is a member of the specified list. */ - public func getListsMembersShowWithListID(listID: String, userID: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func checkListMembership(of userTag: UserTag, for listTag: ListTag, includeEntities: Bool?, skipStatus: Bool?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/members/show.json" var parameters = Dictionary() - parameters["list_id"] = listID - parameters["user_id"] = userID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } + parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsMembersShowWithListID(listID: String, screenName: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/show.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsMembersShowWithSlug(slug: String, ownerID: String, userID: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["user_id"] = userID - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsMembersShowWithSlug(slug: String, ownerID: String, screenName: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsMembersShowWithSlug(slug: String, ownerScreenName: String, userID: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - parameters["user_id"] = userID - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -710,137 +241,40 @@ 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 getListsMembersShowWithSlug(slug: String, ownerScreenName: String, screenName: String, includeEntities: Bool?, skipStatus: Bool?, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getListsMembersWithListID(listID: String, ownerScreenName: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["owner_screen_name"] ??= ownerScreenName - parameters["cursor"] ??= cursor - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - - public func getListsMembersWithListID(listID: String, ownerID: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["owner_id"] ??= ownerID - parameters["cursor"] ??= cursor - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - }, failure: failure) - } - - public func getListsMembersWithSlug(slug: String, ownerScreenName: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { + public func getListMembers(for listTag: ListTag, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: CursorSuccessHandler?, failure: FailureHandler?) { let path = "lists/members.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] ??= ownerScreenName + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } parameters["cursor"] ??= cursor parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - - public func getListsMembersWithSlug(slug: String, ownerID: String?, cursor: String?, includeEntities: Bool?, skipStatus: Bool?, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] ??= ownerID - parameters["cursor"] ??= cursor - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["users"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } - - + /** POST lists/members/create - Creates a new list for the authenticated user. Note that you can't create more than 20 lists per account. + 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 postListsMembersCreateWithListID(listID: String, ownerScreenName: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" + public func addListMember(_ userTag: UserTag, for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { + let path = "lists/members/create.json" var parameters = Dictionary() - parameters["owner_screen_name"] = ownerScreenName - parameters["list_id"] = listID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } + parameters[userTag.key] = userTag.value - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsMembersCreateWithListID(listID: String, ownerID: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" - - var parameters = Dictionary() - parameters["owner_id"] = ownerID - parameters["list_id"] = listID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsMembersCreateWithSlug(slug: String, ownerScreenName: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" - - var parameters = Dictionary() - parameters["owner_screen_name"] = ownerScreenName - parameters["slug"] = slug - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postListsMembersCreateWithSlug(slug: String, ownerID: String, success: ((user: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscribers/create.json" - - var parameters = Dictionary() - parameters["owner_id"] = ownerID - parameters["slug"] = slug - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -848,39 +282,16 @@ public extension Swifter { Deletes the specified list. The authenticated user must own the list to be able to destroy it. */ - public func postListsDestroyWithListID(listID: String, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/destroy.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) - } - - public func postListsDestroyWithSlug(slug: String, ownerID: String, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func deleteList(for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/destroy.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) - } - - public func postListsDestroyWithSlug(slug: String, ownerScreenName: String, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/destroy.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -888,66 +299,35 @@ public extension Swifter { Updates the specified list. The authenticated user must own the list to be able to update it. */ - public func postListsUpdateWithListID(listID: String, name: String?, publicMode: Bool = true, description: String?, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/update.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["name"] ??= name - parameters["mode"] = publicMode ? "public" : "private" - parameters["description"] ??= description - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) - } - - public func postListsUpdateWithSlug(slug: String, ownerID: String, name: String?, publicMode: Bool = true, description: String?, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/update.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - parameters["name"] ??= name - parameters["mode"] = publicMode ? "public" : "private" - parameters["description"] ??= description - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) - } - - public func postListsUpdateWithSlug(slug: String, ownerScreenName: String, name: String?, publicMode: Bool = true, description: String?, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func updateList(for listTag: ListTag, name: String?, isPublic: Bool = true, description: String?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/update.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } parameters["name"] ??= name - parameters["mode"] = publicMode ? "public" : "private" + parameters["mode"] = isPublic ? "public" : "private" parameters["description"] ??= description - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } - + /** POST lists/create Creates a new list for the authenticated user. Note that you can't create more than 20 lists per account. */ - public func postListsCreateWithName(name: String, publicMode: Bool = true, description: String?, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func createList(named name: String, asPublicList: Bool = true, description: String?, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/create.json" var parameters = Dictionary() parameters["name"] = name - parameters["mode"] = publicMode ? "public" : "private" + parameters["mode"] = asPublicList ? "public" : "private" parameters["description"] ??= description - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -955,38 +335,17 @@ public extension Swifter { Returns the specified list. Private lists will only be shown if the authenticated user owns the specified list. */ - public func getListsShowWithID(listID: String, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/show.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) - } - - public func getListsShowWithSlug(slug: String, ownerID: String, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { - let path = "lists/show.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_id"] = ownerID - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) - }, failure: failure) - } - - public func getListsShowWithSlug(slug: String, ownerScreenName: String, success: ((list: Dictionary?) -> Void)?, failure: FailureHandler?) { + public func showList(for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/show.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["owner_screen_name"] = ownerScreenName + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(list: json.object) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -995,34 +354,19 @@ 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 getListsSubscriptionsWithUserID(userID: String, count: String?, cursor: String?, success: ((lists: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { + public func getSubscribedList(of userTag: UserTag, count: String?, cursor: String?, success: CursorSuccessHandler?, failure: FailureHandler?) { let path = "lists/subscriptions.json" var parameters = Dictionary() - parameters["user_id"] = userID + parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["cursor"] ??= cursor - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json["lists"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["lists"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } - - public func getListsSubscriptionsWithScreenName(screenName: String, count: String?, cursor: String?, success: ((lists: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { - let path = "lists/subscriptions.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["count"] ??= count - parameters["cursor"] ??= cursor - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json["lists"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - - }, failure: failure) - } - + /** POST lists/members/destroy_all @@ -1030,85 +374,17 @@ 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 postListsMembersDestroyAllWithListID(listID: String, userIDs: [String], success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy_all.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyAllWithListID(listID: String, screenNames: [String], success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy_all.json" - - var parameters = Dictionary() - parameters["list_id"] = listID - parameters["screen_name"] = screenNames.joinWithSeparator(",") - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyAllWithSlug(slug: String, userIDs: [String], ownerScreenName: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy_all.json" - - var parameters = Dictionary() - parameters["slug"] = slug - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") - parameters["owner_screen_name"] = ownerScreenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyAllWithSlug(slug: String, screenNames: [String], ownerScreenName: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { + public func removeListMembers(_ usersTag: UsersTag, for listTag: ListTag, success: SuccessHandler?, failure: FailureHandler?) { let path = "lists/members/destroy_all.json" var parameters = Dictionary() - parameters["slug"] = slug - parameters["screen_name"] = screenNames.joinWithSeparator(",") - parameters["owner_screen_name"] = ownerScreenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyAllWithSlug(slug: String, userIDs: [String], ownerID: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy_all.json" - - var parameters = Dictionary() - parameters["slug"] = slug + parameters[listTag.key] = listTag.value + if case .slug(_, let owner) = listTag { + parameters[owner.ownerKey] = owner.value + } + parameters[usersTag.key] = usersTag.value - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") - parameters["owner_id"] = ownerID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) - } - - public func postListsMembersDestroyAllWithSlug(slug: String, screenNames: [String], ownerID: String, success: ((response: JSON?) -> Void)?, failure: FailureHandler?) { - let path = "lists/members/destroy_all.json" - - var parameters = Dictionary() - parameters["slug"] = slug - parameters["screen_name"] = screenNames.joinWithSeparator(",") - parameters["owner_id"] = ownerID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -1116,19 +392,29 @@ 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 getListsOwnershipsWithUserID(userID: String, count: String?, cursor: String?, success: ((lists: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)?, failure: FailureHandler?) { + public func getOwnedLists(for userTag: UserTag, count: String?, cursor: String?, success: CursorSuccessHandler?, failure: FailureHandler?) { let path = "lists/ownerships.json" var parameters = Dictionary() - parameters["user_id"] = userID + parameters[userTag.key] = userTag.value parameters["count"] ??= count parameters["cursor"] ??= cursor - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(lists: json["lists"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) - + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["lists"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } } + +private extension UserTag { + + var ownerKey: String { + switch self { + case .id: return "owner_id" + case .screenName: return "owner_screen_name" + } + } + +} diff --git a/Swifter/SwifterMessages.swift b/Swifter/SwifterMessages.swift index c17fa23d..bd3a5a75 100644 --- a/Swifter/SwifterMessages.swift +++ b/Swifter/SwifterMessages.swift @@ -32,7 +32,7 @@ public extension Swifter { 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 getDirectMessagesSinceID(sinceID: String? = nil, maxID: String? = nil, count: Int? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((messages: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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() @@ -42,9 +42,7 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(messages: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -52,7 +50,7 @@ public extension Swifter { 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 getSentDirectMessagesSinceID(sinceID: String? = nil, maxID: String? = nil, count: Int? = nil, page: Int? = nil, includeEntities: Bool? = nil, success: ((messages: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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() @@ -62,9 +60,7 @@ public extension Swifter { parameters["page"] ??= page parameters["include_entities"] ??= includeEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(messages: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -72,13 +68,11 @@ public extension Swifter { 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 getDirectMessagesShowWithID(id: String, success: ((messages: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(messages: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -86,16 +80,14 @@ public extension Swifter { Destroys the direct message specified in the required ID parameter. The authenticating user must be the recipient of the specified direct message. */ - public func postDestroyDirectMessageWithID(id: String, includeEntities: Bool? = nil, success: ((messages: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func destoryDirectMessage(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.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(messages: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -103,16 +95,14 @@ public extension Swifter { 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 postDirectMessageToUser(userID: String, text: String, success: ((statuses: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func sendDirectMessage(to userID: String, text: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "direct_messages/new.json" var parameters = Dictionary() parameters["user_id"] = userID parameters["text"] = text - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterOAuthClient.swift b/Swifter/SwifterOAuthClient.swift index fffa14a2..bf29961b 100644 --- a/Swifter/SwifterOAuthClient.swift +++ b/Swifter/SwifterOAuthClient.swift @@ -1,5 +1,5 @@ // -// SwifterOAuthClient.swift +// OAuthClient.swift // Swifter // // Copyright (c) 2014 Matt Donnelly. @@ -26,7 +26,7 @@ import Foundation import Accounts -internal class SwifterOAuthClient: SwifterClientProtocol { +internal class OAuthClient: SwifterClientProtocol { struct OAuth { static let version = "1.0" @@ -36,31 +36,28 @@ internal class SwifterOAuthClient: SwifterClientProtocol { var consumerKey: String var consumerSecret: String - var credential: SwifterCredential? + var credential: Credential? - var dataEncoding: NSStringEncoding + let dataEncoding: String.Encoding = .utf8 init(consumerKey: String, consumerSecret: String) { self.consumerKey = consumerKey self.consumerSecret = consumerSecret - self.dataEncoding = NSUTF8StringEncoding } init(consumerKey: String, consumerSecret: String, accessToken: String, accessTokenSecret: String) { self.consumerKey = consumerKey self.consumerSecret = consumerSecret - let credentialAccessToken = SwifterCredential.OAuthAccessToken(key: accessToken, secret: accessTokenSecret) - self.credential = SwifterCredential(accessToken: credentialAccessToken) - - self.dataEncoding = NSUTF8StringEncoding + let credentialAccessToken = Credential.OAuthAccessToken(key: accessToken, secret: accessTokenSecret) + self.credential = Credential(accessToken: credentialAccessToken) } - func get(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - let url = NSURL(string: path, relativeToURL: baseURL)! + func get(_ path: String, baseURL: TwitterURL, parameters: Dictionary, uploadProgress: HTTPRequest.UploadProgressHandler?, downloadProgress: HTTPRequest.DownloadProgressHandler?, success: HTTPRequest.SuccessHandler?, failure: HTTPRequest.FailureHandler?) -> HTTPRequest { + let url = URL(string: path, relativeTo: baseURL.url)! - let request = SwifterHTTPRequest(URL: url, method: .GET, parameters: parameters) - request.headers = ["Authorization": self.authorizationHeaderForMethod(.GET, url: url, parameters: parameters, isMediaUpload: false)] + let request = HTTPRequest(url: url, method: .GET, parameters: parameters) + request.headers = ["Authorization": self.authorizationHeader(for: .GET, url: url, parameters: parameters, isMediaUpload: false)] request.downloadProgressHandler = downloadProgress request.successHandler = success request.failureHandler = failure @@ -70,19 +67,20 @@ internal class SwifterOAuthClient: SwifterClientProtocol { return request } - func post(path: String, baseURL: NSURL, parameters: Dictionary, uploadProgress: SwifterHTTPRequest.UploadProgressHandler?, downloadProgress: SwifterHTTPRequest.DownloadProgressHandler?, success: SwifterHTTPRequest.SuccessHandler?, failure: SwifterHTTPRequest.FailureHandler?) -> SwifterHTTPRequest { - let url = NSURL(string: path, relativeToURL: baseURL)! + func post(_ path: String, baseURL: TwitterURL, parameters: Dictionary, 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: NSData? + var postData: Data? var postDataKey: String? if let key: Any = parameters[Swifter.DataParameters.dataKey] { if let keyString = key as? String { postDataKey = keyString - postData = parameters[postDataKey!] as? NSData + postData = parameters[postDataKey!] as? Data - parameters.removeValueForKey(Swifter.DataParameters.dataKey) - parameters.removeValueForKey(postDataKey!) + parameters.removeValue(forKey: Swifter.DataParameters.dataKey) + parameters.removeValue(forKey: postDataKey!) } } @@ -90,34 +88,34 @@ internal class SwifterOAuthClient: SwifterClientProtocol { if let fileName: Any = parameters[Swifter.DataParameters.fileNameKey] { if let fileNameString = fileName as? String { postDataFileName = fileNameString - parameters.removeValueForKey(fileNameString) + parameters.removeValue(forKey: fileNameString) } } - let request = SwifterHTTPRequest(URL: url, method: .POST, parameters: parameters) - request.headers = ["Authorization": self.authorizationHeaderForMethod(.POST, url: url, parameters: parameters, isMediaUpload: postData != nil)] + let request = HTTPRequest(url: url, method: .POST, parameters: parameters) + request.headers = ["Authorization": self.authorizationHeader(for: .POST, url: url, parameters: parameters, isMediaUpload: postData != nil)] request.downloadProgressHandler = downloadProgress request.successHandler = success request.failureHandler = failure request.dataEncoding = self.dataEncoding request.encodeParameters = postData == nil - if postData != nil { + if let postData = postData { let fileName = postDataFileName ?? "media.jpg" - request.addMultipartData(postData!, parameterName: postDataKey!, mimeType: "application/octet-stream", fileName: fileName) + request.add(multipartData: postData, parameterName: postDataKey!, mimeType: "application/octet-stream", fileName: fileName) } request.start() return request } - func authorizationHeaderForMethod(method: HTTPMethodType, url: NSURL, parameters: Dictionary, isMediaUpload: Bool) -> String { + func authorizationHeader(for method: HTTPMethodType, url: URL, parameters: Dictionary, isMediaUpload: Bool) -> String { var authorizationParameters = Dictionary() authorizationParameters["oauth_version"] = OAuth.version authorizationParameters["oauth_signature_method"] = OAuth.signatureMethod authorizationParameters["oauth_consumer_key"] = self.consumerKey - authorizationParameters["oauth_timestamp"] = String(Int(NSDate().timeIntervalSince1970)) - authorizationParameters["oauth_nonce"] = NSUUID().UUIDString + authorizationParameters["oauth_timestamp"] = String(Int(Date().timeIntervalSince1970)) + authorizationParameters["oauth_nonce"] = UUID().uuidString authorizationParameters["oauth_token"] ??= self.credential?.accessToken?.key @@ -129,42 +127,35 @@ internal class SwifterOAuthClient: SwifterClientProtocol { let finalParameters = isMediaUpload ? authorizationParameters : combinedParameters - authorizationParameters["oauth_signature"] = self.oauthSignatureForMethod(method, url: url, parameters: finalParameters, accessToken: self.credential?.accessToken) + authorizationParameters["oauth_signature"] = self.oauthSignature(for: method, url: url, parameters: finalParameters, accessToken: self.credential?.accessToken) - var authorizationParameterComponents = authorizationParameters.urlEncodedQueryStringWithEncoding(self.dataEncoding).componentsSeparatedByString("&") as [String] - authorizationParameterComponents.sortInPlace { $0 < $1 } + let authorizationParameterComponents = authorizationParameters.urlEncodedQueryString(using: self.dataEncoding).components(separatedBy: "&").sorted() var headerComponents = [String]() for component in authorizationParameterComponents { - let subcomponent = component.componentsSeparatedByString("=") as [String] + let subcomponent = component.components(separatedBy: "=") if subcomponent.count == 2 { headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"") } } - return "OAuth " + headerComponents.joinWithSeparator(", ") + return "OAuth " + headerComponents.joined(separator: ", ") } - func oauthSignatureForMethod(method: HTTPMethodType, url: NSURL, parameters: Dictionary, accessToken token: SwifterCredential.OAuthAccessToken?) -> String { - let tokenSecret: NSString = token?.secret.urlEncodedStringWithEncoding() ?? "" - - let encodedConsumerSecret = self.consumerSecret.urlEncodedStringWithEncoding() - + func oauthSignature(for method: HTTPMethodType, url: URL, parameters: Dictionary, accessToken token: Credential.OAuthAccessToken?) -> String { + let tokenSecret = token?.secret.urlEncodedString() ?? "" + let encodedConsumerSecret = self.consumerSecret.urlEncodedString() let signingKey = "\(encodedConsumerSecret)&\(tokenSecret)" - - var parameterComponents = parameters.urlEncodedQueryStringWithEncoding(self.dataEncoding).componentsSeparatedByString("&") as [String] - parameterComponents.sortInPlace { $0 < $1 } - - let parameterString = parameterComponents.joinWithSeparator("&") - let encodedParameterString = parameterString.urlEncodedStringWithEncoding() - - let encodedURL = url.absoluteString.urlEncodedStringWithEncoding() - + let parameterComponents = parameters.urlEncodedQueryString(using: dataEncoding).components(separatedBy: "&").sorted() + let parameterString = parameterComponents.joined(separator: "&") + let encodedParameterString = parameterString.urlEncodedString() + let encodedURL = url.absoluteString.urlEncodedString() let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)" - - // let signature = signatureBaseString.SHA1DigestWithKey(signingKey) - - return signatureBaseString.SHA1DigestWithKey(signingKey).base64EncodedStringWithOptions([]) + + let key = signingKey.data(using: .utf8)! + let msg = signatureBaseString.data(using: .utf8)! + let sha1 = HMAC.sha1(key: key, message: msg)! + return sha1.base64EncodedString(options: []) } } diff --git a/Swifter/SwifterPlaces.swift b/Swifter/SwifterPlaces.swift index d50e3ace..b1921ef4 100644 --- a/Swifter/SwifterPlaces.swift +++ b/Swifter/SwifterPlaces.swift @@ -32,12 +32,10 @@ public extension Swifter { Returns all the information about a known place. */ - public func getGeoIDWithPlaceID(placeID: String, success: ((place: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getGeoID(for placeID: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "geo/id/\(placeID).json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(place: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -47,21 +45,19 @@ public extension Swifter { This request is an informative call and will deliver generalized results about geography. */ - public func getGeoReverseGeocodeWithLat(lat: Double, long: Double, accuracy: String? = nil, granularity: String? = nil, maxResults: Int? = nil, callback: String? = nil, success: ((place: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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) { let path = "geo/reverse_geocode.json" var parameters = Dictionary() - parameters["lat"] = lat - parameters["long"] = long + parameters["lat"] = coordinate.lat + parameters["long"] = coordinate.long parameters["accuracy"] ??= accuracy parameters["granularity"] ??= granularity parameters["max_results"] ??= maxResults parameters["callback"] ??= callback - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(place: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -73,16 +69,20 @@ 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 getGeoSearchWithLat(lat: Double? = nil, 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: ((places: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - assert(lat != nil || long != nil || query != nil || ipAddress != nil, "At least one of the following parameters must be provided to access this resource: lat, long, 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, 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") let path = "geo/search.json" var parameters = Dictionary() - parameters["lat"] ??= lat - parameters["long"] ??= long - parameters["query"] ??= query - parameters["ipAddress"] ??= ipAddress + 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["accuracy"] ??= accuracy parameters["granularity"] ??= granularity parameters["max_results"] ??= maxResults @@ -90,9 +90,7 @@ public extension Swifter { parameters["attribute:street_address"] ??= attributeStreetAddress parameters["callback"] ??= callback - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(places: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -104,21 +102,19 @@ public extension Swifter { The token contained in the response is the token needed to be able to create a new place. */ - public func getGeoSimilarPlacesWithLat(lat: Double, long: Double, name: String, containedWithin: String? = nil, attributeStreetAddress: String? = nil, callback: String? = nil, success: ((places: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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) { let path = "geo/similar_places.json" var parameters = Dictionary() - parameters["lat"] = lat - parameters["long"] = long + parameters["lat"] = coordinate.lat + parameters["long"] = coordinate.long parameters["name"] = name parameters["contained_within"] ??= containedWithin parameters["attribute:street_address"] ??= attributeStreetAddress parameters["callback"] ??= callback - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(places: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterSavedSearches.swift b/Swifter/SwifterSavedSearches.swift index e2d364a3..422c098a 100644 --- a/Swifter/SwifterSavedSearches.swift +++ b/Swifter/SwifterSavedSearches.swift @@ -32,12 +32,10 @@ public extension Swifter { Returns the authenticated user's saved search queries. */ - public func getSavedSearchesListWithSuccess(success: ((savedSearches: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getSavedSearchesList(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "saved_searches/list.json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(savedSearches: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -45,12 +43,10 @@ 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 getSavedSearchesShowWithID(id: String, success: ((savedSearch: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func showSavedSearch(for id: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "saved_searches/show/\(id).json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(savedSearch: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -58,15 +54,13 @@ public extension Swifter { Create a new saved search for the authenticated user. A user may only have 25 saved searches. */ - public func postSavedSearchesCreateShowWithQuery(query: String, success: ((savedSearch: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func createSavedSearch(for query: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "saved_searches/create.json" var parameters = Dictionary() parameters["query"] = query - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(savedSearch: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -74,12 +68,10 @@ 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 postSavedSearchesDestroyWithID(id: String, success: ((savedSearch: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func deleteSavedSearch(for id: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "saved_searches/destroy/\(id).json" - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(savedSearch: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterSearch.swift b/Swifter/SwifterSearch.swift index 3574ca44..4abba534 100644 --- a/Swifter/SwifterSearch.swift +++ b/Swifter/SwifterSearch.swift @@ -37,11 +37,11 @@ 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 getSearchTweetsWithQuery(q: 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, success: ((statuses: [JSONValue]?, searchMetadata: Dictionary?) -> Void)? = nil, failure: 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, success: ((JSON, _ searchMetadata: JSON) -> Void)? = nil, failure: @escaping FailureHandler) { let path = "search/tweets.json" var parameters = Dictionary() - parameters["q"] = q + parameters["q"] = query parameters["geocode"] ??= geocode parameters["lang"] ??= lang parameters["locale"] ??= locale @@ -53,8 +53,8 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["callback"] ??= callback - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json["statuses"].array, searchMetadata: json["search_metadata"].object) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["statuses"], json["search_metadata"]) }, failure: failure) } diff --git a/Swifter/SwifterSpam.swift b/Swifter/SwifterSpam.swift index 5d09a186..3f764c86 100644 --- a/Swifter/SwifterSpam.swift +++ b/Swifter/SwifterSpam.swift @@ -32,25 +32,12 @@ 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 postUsersReportSpamWithScreenName(screenName: String, success: ((user: Dictionary?) -> Void)? = 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] - var parameters = Dictionary() - parameters["screen_name"] = screenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postUsersReportSpamWithUserID(userID: String, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "users/report_spam.json" - - var parameters = Dictionary() - parameters["user_id"] = userID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } diff --git a/Swifter/SwifterStreaming.swift b/Swifter/SwifterStreaming.swift index bde011b8..e42a8912 100644 --- a/Swifter/SwifterStreaming.swift +++ b/Swifter/SwifterStreaming.swift @@ -26,6 +26,8 @@ import Foundation public extension Swifter { + + typealias StallWarningHandler = (_ code: String?, _ message: String?, _ percentFull: Int?) -> Void /** POST statuses/filter @@ -38,7 +40,8 @@ public extension Swifter { At least one predicate parameter (follow, locations, or track) must be specified. */ - public func postStatusesFilterWithFollow(follow: [String]? = nil, track: [String]? = nil, locations: [String]? = nil, delimited: Bool? = nil, stallWarnings: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: ((status: Dictionary? ) -> Void)? = nil, stallWarningHandler: ((code: String?, message: String?, percentFull: Int?) -> Void)? = nil, failure: FailureHandler? = nil) -> SwifterHTTPRequest { + public func postTweetFilters(follow: [String]? = nil, track: [String]? = nil, locations: [String]? = nil, delimited: Bool? = nil, stallWarnings: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { + assert(follow != nil || track != nil || locations != nil, "At least one predicate parameter (follow, locations, or track) must be specified") let path = "statuses/filter.json" @@ -47,21 +50,19 @@ public extension Swifter { parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filter_level - parameters["language"] ??= language?.joinWithSeparator(",") - parameters["follow"] ??= follow?.joinWithSeparator(",") - parameters["track"] ??= track?.joinWithSeparator(",") - parameters["locations"] ??= locations?.joinWithSeparator(",") + parameters["language"] ??= language?.joined(separator: ",") + parameters["follow"] ??= follow?.joined(separator: ",") + parameters["track"] ??= track?.joined(separator: ",") + parameters["locations"] ??= locations?.joined(separator: ",") - return self.postJSONWithPath(path, baseURL: self.streamURL, parameters: parameters, downloadProgress: { json, _ in + return self.postJSON(path: path, baseURL: .stream, parameters: parameters, downloadProgress: { json, _ in if let stallWarning = json["warning"].object { - stallWarningHandler?(code: stallWarning["code"]?.string, message: stallWarning["message"]?.string, percentFull: stallWarning["percent_full"]?.integer) + stallWarningHandler?(stallWarning["code"]?.string, stallWarning["message"]?.string, stallWarning["percent_full"]?.integer) } else { - progress?(status: json.object) + progress?(json) } - }, success: { json, _ in - progress?(status: json.object) - }, failure: failure) + }, success: { json, _ in progress?(json) }, failure: failure) } /** @@ -69,25 +70,23 @@ public extension Swifter { Returns a small random sample of all public statuses. The Tweets returned by the default access level are the same, so if two different clients connect to this endpoint, they will see the same Tweets. */ - public func getStatusesSampleDelimited(delimited: Bool? = nil, stallWarnings: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: ((status: Dictionary?) -> Void)? = nil, stallWarningHandler: ((code: String?, message: String?, percentFull: Int?) -> Void)? = nil, failure: FailureHandler? = nil) -> SwifterHTTPRequest { + public func streamRandomSampleTweets(delimited: Bool? = nil, stallWarnings: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "statuses/sample.json" var parameters = Dictionary() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filter_level - parameters["language"] ??= language?.joinWithSeparator(",") + parameters["language"] ??= language?.joined(separator: ",") - return self.getJSONWithPath(path, baseURL: self.streamURL, parameters: parameters, downloadProgress: { json, _ in + return self.getJSON(path: path, baseURL: .stream, parameters: parameters, downloadProgress: { json, _ in if let stallWarning = json["warning"].object { - stallWarningHandler?(code: stallWarning["code"]?.string, message: stallWarning["message"]?.string, percentFull: stallWarning["percent_full"]?.integer) + stallWarningHandler?(stallWarning["code"]?.string, stallWarning["message"]?.string, stallWarning["percent_full"]?.integer) } else { - progress?(status: json.object) + progress?(json) } - }, success: { json, _ in - progress?(status: json.object) - }, failure: failure) + }, success: { json, _ in progress?(json) }, failure: failure) } /** @@ -97,7 +96,8 @@ public extension Swifter { Returns all public statuses. Few applications require this level of access. Creative use of a combination of other resources and various access levels can satisfy nearly every application use case. */ - public func getStatusesFirehose(count: Int? = nil, delimited: Bool? = nil, stallWarnings: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: ((status: Dictionary?) -> Void)? = nil, stallWarningHandler: ((code: String?, message: String?, percentFull: Int?) -> Void)? = nil, failure: FailureHandler? = nil) -> SwifterHTTPRequest { + + public func streamFirehoseTweets(count: Int? = nil, delimited: Bool? = nil, stallWarnings: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "statuses/firehose.json" var parameters = Dictionary() @@ -105,18 +105,16 @@ public extension Swifter { parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filter_level - parameters["language"] ??= language?.joinWithSeparator(",") + parameters["language"] ??= language?.joined(separator: ",") - return self.getJSONWithPath(path, baseURL: self.streamURL, parameters: parameters, downloadProgress: { json, _ in + return self.getJSON(path: path, baseURL: .stream, parameters: parameters, downloadProgress: { json, _ in if let stallWarning = json["warning"].object { - stallWarningHandler?(code: stallWarning["code"]?.string, message: stallWarning["message"]?.string, percentFull: stallWarning["percent_full"]?.integer) + stallWarningHandler?(stallWarning["code"]?.string, stallWarning["message"]?.string, stallWarning["percent_full"]?.integer) } else { - progress?(status: json.object) + progress?(json) } - }, success: { json, _ in - progress?(status: json.object) - }, failure: failure) + }, success: { json, _ in progress?(json) }, failure: failure) } /** @@ -124,30 +122,32 @@ public extension Swifter { Streams messages for a single user, as described in User streams https://dev.twitter.com/docs/streaming-apis/streams/user */ - public func getUserStreamDelimited(delimited: Bool? = nil, stallWarnings: Bool? = nil, includeMessagesFromUserOnly: Bool = false, includeReplies: Bool = false, track: [String]? = nil, locations: [String]? = nil, stringifyFriendIDs: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: ((status: Dictionary?) -> Void)? = nil, stallWarningHandler: ((code: String?, message: String?, percentFull: Int?) -> Void)? = nil, failure: FailureHandler? = nil) -> SwifterHTTPRequest { + public func beginUserStream(delimited: Bool? = nil, stallWarnings: Bool? = nil, includeMessagesFromUserOnly: Bool = false, includeReplies: Bool = false, track: [String]? = nil, locations: [String]? = nil, stringifyFriendIDs: Bool? = nil, filter_level: String? = nil, language: [String]? = nil, progress: SuccessHandler? = nil, stallWarningHandler: StallWarningHandler? = nil, failure: FailureHandler? = nil) -> HTTPRequest { let path = "user.json" var parameters = Dictionary() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["filter_level"] ??= filter_level - parameters["language"] ??= language?.joinWithSeparator(",") + parameters["language"] ??= language?.joined(separator: ",") parameters["stringify_friend_ids"] ??= stringifyFriendIDs - parameters["track"] ??= track?.joinWithSeparator(",") - parameters["locations"] ??= locations?.joinWithSeparator(",") - parameters["with"] ??= includeMessagesFromUserOnly ? "user" : nil - parameters["replies"] ??= includeReplies ? "all" : nil - - return self.getJSONWithPath(path, baseURL: self.userStreamURL, parameters: parameters, downloadProgress: { json, _ in + parameters["track"] ??= track?.joined(separator: ",") + parameters["locations"] ??= locations?.joined(separator: ",") + if includeMessagesFromUserOnly { + parameters["with"] = "user" + } + if includeReplies { + parameters["replies"] = "all" + } + + return self.getJSON(path: path, baseURL: .userStream, parameters: parameters, downloadProgress: { json, _ in if let stallWarning = json["warning"].object { - stallWarningHandler?(code: stallWarning["code"]?.string, message: stallWarning["message"]?.string, percentFull: stallWarning["percent_full"]?.integer) + stallWarningHandler?(stallWarning["code"]?.string, stallWarning["message"]?.string, stallWarning["percent_full"]?.integer) } else { - progress?(status: json.object) + progress?(json) } - }, success: { json, _ in - progress?(status: json.object) - }, failure: failure) + }, success: { json, _ in progress?(json) }, failure: failure) } /** @@ -155,21 +155,23 @@ public extension Swifter { Streams messages for a set of users, as described in Site streams https://dev.twitter.com/docs/streaming-apis/streams/site */ - public func getSiteStreamDelimited(delimited: Bool? = nil, stallWarnings: Bool? = nil, restrictToUserMessages: Bool = false, includeReplies: Bool = false, stringifyFriendIDs: Bool? = nil, progress: ((status: Dictionary?) -> Void)? = nil, stallWarningHandler: ((code: String?, message: String?, percentFull: Int?) -> Void)? = nil, failure: FailureHandler? = nil) -> SwifterHTTPRequest { + 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() parameters["delimited"] ??= delimited parameters["stall_warnings"] ??= stallWarnings parameters["stringify_friend_ids"] ??= stringifyFriendIDs - parameters["with"] ??= restrictToUserMessages ? "user" : nil - parameters["replies"] ??= includeReplies ? "all" : nil - - return self.getJSONWithPath(path, baseURL: self.streamURL, parameters: parameters, downloadProgress: { json, _ in - stallWarningHandler?(code: json["warning"]["code"].string, message: json["warning"]["message"].string, percentFull: json["warning"]["percent_full"].integer) - }, success: { json, _ in - progress?(status: json.object) - }, failure: failure) + if restrictToUserMessages { + parameters["with"] = "user" + } + if includeReplies { + parameters["replies"] = "all" + } + + return self.getJSON(path: path, baseURL: .stream, parameters: parameters, downloadProgress: { json, _ in + stallWarningHandler?(json["warning"]["code"].string, json["warning"]["message"].string, json["warning"]["percent_full"].integer) + }, success: { json, _ in progress?(json) }, failure: failure) } } diff --git a/Swifter/SwifterSuggested.swift b/Swifter/SwifterSuggested.swift index d9b6c7da..4986ff38 100644 --- a/Swifter/SwifterSuggested.swift +++ b/Swifter/SwifterSuggested.swift @@ -34,15 +34,13 @@ public extension Swifter { It is recommended that applications cache this data for no more than one hour. */ - public func getUsersSuggestionsWithSlug(slug: String, lang: String? = nil, success: ((users: [JSONValue]?) -> Void)? = 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() parameters["lang"] ??= lang - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -50,15 +48,13 @@ 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 getUsersSuggestionsWithLang(lang: String? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getUsersSuggestions(lang: String? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "users/suggestions.json" var parameters = Dictionary() parameters["lang"] ??= lang - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -66,12 +62,9 @@ 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 getUsersSuggestionsForSlugMembers(slug: String, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getUsersSuggestions(for slug: String, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "users/suggestions/\(slug)/members.json" - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(users: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterTag.swift b/Swifter/SwifterTag.swift new file mode 100644 index 00000000..383bc806 --- /dev/null +++ b/Swifter/SwifterTag.swift @@ -0,0 +1,67 @@ +// +// SwifterTag.swift +// Swifter +// +// Created by Andy on 7/30/16. +// Copyright © 2016 Matt Donnelly. All rights reserved. +// + +import Foundation + +public enum UserTag { + case id(String) + case screenName(String) + + var key: String { + switch self { + case .id: return "user_id" + case .screenName: return "screen_name" + } + } + + var value: String { + switch self { + case .id(let id): return id + case .screenName(let user): return user + } + } +} + +public enum UsersTag { + case id([String]) + case screenName([String]) + + var key: String { + switch self { + case .id: return "user_id" + case .screenName: return "screen_name" + } + } + + var value: String { + switch self { + case .id(let id): return id.joined(separator: ",") + case .screenName(let user): return user.joined(separator: ",") + } + } +} + +public enum ListTag { + case id(String) + case slug(String, owner: UserTag) + + var key: String { + switch self { + case .id: return "list_id" + case .slug: return "slug" + } + } + + var value: String { + switch self { + case .id(let id): return id + case .slug(let slug, _): return slug + } + } + +} diff --git a/Swifter/SwifterTimelines.swift b/Swifter/SwifterTimelines.swift index e49d3931..b658a4aa 100644 --- a/Swifter/SwifterTimelines.swift +++ b/Swifter/SwifterTimelines.swift @@ -28,7 +28,7 @@ import Foundation public extension Swifter { // Convenience method - private func getTimelineAtPath(path: String, parameters: Dictionary, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { var params = parameters params["count"] ??= count params["since_id"] ??= sinceID @@ -37,8 +37,8 @@ public extension Swifter { params["contributor_details"] ??= contributorDetails params["include_entities"] ??= includeEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: params, success: { json, _ in - success?(statuses: json.array) + self.getJSON(path: path, baseURL: .api, parameters: params, success: { json, _ in + success?(json) }, failure: failure) } @@ -52,8 +52,8 @@ public extension Swifter { This method can only return up to 800 tweets. */ - public func getStatusesMentionTimelineWithCount(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler?) { - self.getTimelineAtPath("statuses/mentions_timeline.json", parameters: [:], count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, success: success, failure: failure) + public func getMentionsTimlineTweets(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, 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, success: success, failure: failure) } @@ -69,10 +69,10 @@ 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 getStatusesUserTimelineWithUserID(userID: String, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getTimeline(for userID: String, count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let parameters: Dictionary = ["user_id": userID] - self.getTimelineAtPath("statuses/user_timeline.json", parameters: parameters, count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, success: success, failure: failure) + self.getTimeline(at: "statuses/user_timeline.json", parameters: parameters, count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, success: success, failure: failure) } /** @@ -84,8 +84,8 @@ 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 getStatusesHomeTimelineWithCount(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - self.getTimelineAtPath("statuses/home_timeline.json", parameters: [:], count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, 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, 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, success: success, failure: failure) } /** @@ -93,8 +93,8 @@ 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 getStatusesRetweetsOfMeWithCount(count: Int? = nil, sinceID: String? = nil, maxID: String? = nil, trimUser: Bool? = nil, contributorDetails: Bool? = nil, includeEntities: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - self.getTimelineAtPath("statuses/retweets_of_me.json", parameters: [:], count: count, sinceID: sinceID, maxID: maxID, trimUser: trimUser, contributorDetails: contributorDetails, includeEntities: includeEntities, 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, 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, success: success, failure: failure) } } diff --git a/Swifter/SwifterTrends.swift b/Swifter/SwifterTrends.swift index b7cae49f..3ab92cf6 100644 --- a/Swifter/SwifterTrends.swift +++ b/Swifter/SwifterTrends.swift @@ -19,16 +19,14 @@ 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 getTrendsPlaceWithWOEID(id: String, excludeHashtags: Bool = false, success: ((trends: [JSONValue]?) -> Void)? = 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() - parameters["id"] = id + parameters["id"] = woeid parameters["exclude"] = excludeHashtags ? "hashtags" : nil - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(trends: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -40,12 +38,9 @@ public extension Swifter { A WOEID is a Yahoo! Where On Earth ID. */ - public func getTrendsAvailableWithSuccess(success: ((trends: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getAvailableTrends(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "trends/available.json" - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(trends: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -57,16 +52,13 @@ public extension Swifter { A WOEID is a Yahoo! Where On Earth ID. */ - public func getTrendsClosestWithLat(lat: Double, long: Double, success: ((trends: [JSONValue]?) -> Void)? = 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"] = lat - parameters["long"] = long - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(trends: json.array) - }, failure: failure) + parameters["lat"] = coordinate.lat + parameters["long"] = coordinate.long + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterTweets.swift b/Swifter/SwifterTweets.swift index 84e1b3af..a603eb67 100644 --- a/Swifter/SwifterTweets.swift +++ b/Swifter/SwifterTweets.swift @@ -32,16 +32,13 @@ public extension Swifter { Returns up to 100 of the first retweets of a given tweet. */ - public func getStatusesRetweetsWithID(id: String, count: Int? = nil, trimUser: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getRetweets(forTweetID id: String, count: Int? = nil, trimUser: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/retweets/\(id).json" - var parameters = Dictionary() parameters["count"] ??= count parameters["trim_user"] ??= trimUser - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -63,7 +60,7 @@ 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 getStatusesShowWithID(id: String, count: Int? = nil, trimUser: Bool? = nil, includeMyRetweet: Bool? = nil, includeEntities: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getTweet(forID id: String, count: Int? = nil, trimUser: Bool? = nil, includeMyRetweet: Bool? = nil, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/show.json" var parameters = Dictionary() @@ -73,9 +70,7 @@ public extension Swifter { parameters["include_my_retweet"] ??= includeMyRetweet parameters["include_entities"] ??= includeEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -83,15 +78,13 @@ 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 postStatusesDestroyWithID(id: String, trimUser: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func destroyTweet(forID id: String, trimUser: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/destroy/\(id).json" var parameters = Dictionary() parameters["trim_user"] ??= trimUser - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -116,7 +109,7 @@ public extension Swifter { - https://dev.twitter.com/notifications/multiple-media-entities-in-tweets - https://dev.twitter.com/docs/api/multiple-media-extended-entities */ - public func postStatusUpdate(status: String, inReplyToStatusID: String? = nil, lat: Double? = nil, long: Double? = nil, placeID: Double? = nil, displayCoordinates: Bool? = nil, trimUser: Bool? = nil, media_ids: [String] = [], success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func postTweet(status: String, inReplyToStatusID: String? = nil, coordinate: (lat: Double, long: Double)? = nil, placeID: Double? = nil, displayCoordinates: Bool? = nil, trimUser: Bool? = nil, media_ids: [String] = [], success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path: String = "statuses/update.json" var parameters = Dictionary() @@ -124,25 +117,25 @@ public extension Swifter { parameters["in_reply_to_status_id"] ??= inReplyToStatusID parameters["trim_user"] ??= trimUser - if placeID != nil { - parameters["place_id"] = placeID! + if let placeID = placeID { + parameters["place_id"] = placeID parameters["display_coordinates"] = true - } else if lat != nil && long != nil { - parameters["lat"] = lat! - parameters["long"] = long! + } else if let coordinate = coordinate { + parameters["lat"] = coordinate.lat + parameters["long"] = coordinate.long parameters["display_coordinates"] = true } if !media_ids.isEmpty { - parameters["media_ids"] = media_ids.joinWithSeparator(",") + parameters["media_ids"] = media_ids.joined(separator: ",") } - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } - public func postStatusUpdate(status: String, media: NSData, inReplyToStatusID: String? = nil, lat: Double? = nil, long: Double? = nil, placeID: Double? = nil, displayCoordinates: Bool? = nil, trimUser: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path: String = "statuses/update_with_media.json" var parameters = Dictionary() @@ -155,14 +148,14 @@ public extension Swifter { if placeID != nil { parameters["place_id"] = placeID! parameters["display_coordinates"] = true - } else if lat != nil && long != nil { - parameters["lat"] = lat! - parameters["long"] = long! + } else if let coordinate = coordinate { + parameters["lat"] = coordinate.lat + parameters["long"] = coordinate.long parameters["display_coordinates"] = true } - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -176,42 +169,15 @@ public extension Swifter { - https://dev.twitter.com/rest/public/uploading-media - https://dev.twitter.com/rest/reference/post/media/upload */ - public func postMedia(media: NSData, success: ((status: Dictionary?) -> Void)? = 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() parameters["media"] = media + parameters["additional_owers"] ??= additionalOwners?.value parameters[Swifter.DataParameters.dataKey] = "media" - self.postJSONWithPath(path, baseURL: self.uploadURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .upload, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } - - /** - POST media/upload - - Upload media (images) to Twitter for use in a Tweet or Twitter-hosted Card. For uploading videos or for chunked image uploads (useful for lower bandwidth connections), see our chunked POST media/upload endpoint. - The parameter "additionalOwners" is corresponding to the optional parameter "addtional_owners" in the twitter's document, which is a comma-separated list of user IDs to set as additional owners allowed to use the returned media_id in Tweets or Cards. Up to 100 additional owners may be specified. - - See: - - - https://dev.twitter.com/rest/reference/post/media/upload-init - */ - - public func postMedia(media: NSData, additionalOwners: [String], success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - guard !additionalOwners.isEmpty else { return } - - let path: String = "media/upload.json" - var parameters = Dictionary() - parameters["media"] = media - parameters["additional_owners"] = additionalOwners.joinWithSeparator(",") - print(parameters["additional_owners"]) - parameters[Swifter.DataParameters.dataKey] = "media" - - self.postJSONWithPath(path, baseURL: self.uploadURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) - } /** POST statuses/retweet/:id @@ -224,15 +190,13 @@ public extension Swifter { Returns Tweets (1: the new tweet) */ - public func postStatusRetweetWithID(id: String, trimUser: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func retweetTweet(forID id: String, trimUser: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/retweet/\(id).json" var parameters = Dictionary() parameters["trim_user"] ??= trimUser - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -247,15 +211,13 @@ public extension Swifter { Returns Tweets (1: the original tweet) */ - public func postStatusUnretweetWithID(id: String, trimUser: Bool? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func UnretweetTweet(forID id: String, trimUser: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/unretweet/\(id).json" var parameters = Dictionary() parameters["trim_user"] ??= trimUser - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -265,7 +227,7 @@ 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 getStatusesOEmbedWithID(id: String, maxWidth: Int? = nil, hideMedia: Bool? = nil, hideThread: Bool? = nil, omitScript: Bool? = nil, align: String? = nil, related: String? = nil, lang: String? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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() @@ -278,12 +240,10 @@ public extension Swifter { parameters["related"] ??= related parameters["lang"] ??= lang - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } - public func getStatusesOEmbedWithURL(url: NSURL, maxWidth: Int? = nil, hideMedia: Bool? = nil, hideThread: Bool? = nil, omitScript: Bool? = nil, align: String? = nil, related: String? = nil, lang: String? = nil, success: ((status: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + 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() @@ -296,9 +256,7 @@ public extension Swifter { parameters["related"] ??= related parameters["lang"] ??= lang - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(status: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -308,7 +266,7 @@ 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 getStatusesRetweetersWithID(id: String, cursor: String? = nil, stringifyIDs: Bool? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func tweetRetweeters(forID id: String, cursor: String? = nil, stringifyIDs: Bool? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/retweeters/ids.json" var parameters = Dictionary() @@ -316,8 +274,8 @@ public extension Swifter { parameters["cursor"] ??= cursor parameters["stringify_ids"] ??= stringifyIDs - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -326,17 +284,15 @@ 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 getStatusesLookupTweetIDs(tweetIDs: [String], includeEntities: Bool? = nil, map: Bool? = nil, success: ((statuses: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func lookupTweets(for tweetIDs: [String], includeEntities: Bool? = nil, map: Bool? = nil, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "statuses/lookup.json" var parameters = Dictionary() - parameters["id"] = tweetIDs.joinWithSeparator(",") + parameters["id"] = tweetIDs.joined(separator: ",") parameters["include_entities"] ??= includeEntities parameters["map"] ??= map - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(statuses: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } } diff --git a/Swifter/SwifterUsers.swift b/Swifter/SwifterUsers.swift index 50ec7848..fe502354 100644 --- a/Swifter/SwifterUsers.swift +++ b/Swifter/SwifterUsers.swift @@ -32,12 +32,10 @@ public extension Swifter { Returns settings (including current trend, geo and sleep time information) for the authenticating user. */ - public func getAccountSettingsWithSuccess(success: ((settings: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getAccountSettings(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "account/settings.json" - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(settings: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -45,16 +43,14 @@ 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 getAccountVerifyCredentials(includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((myInfo: Dictionary?) -> Void)? = 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() parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(myInfo: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -62,7 +58,7 @@ public extension Swifter { Updates the authenticating user's settings. */ - public func postAccountSettings(trendLocationWOEID: String? = nil, sleepTimeEnabled: Bool? = nil, startSleepTime: Int? = nil, endSleepTime: Int? = nil, timeZone: String? = nil, lang: String? = nil, success: ((settings: Dictionary?) -> Void)? = 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" @@ -75,26 +71,7 @@ public extension Swifter { parameters["time_zone"] ??= timeZone parameters["lang"] ??= lang - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(settings: json.object) - }, failure: failure) - } - - /** - POST account/update_delivery_device - - Sets which device Twitter delivers updates to for the authenticating user. Sending none as the device parameter will disable SMS updates. - */ - public func postAccountUpdateDeliveryDeviceSMS(device: Bool, includeEntities: Bool? = nil, success: ((deliveryDeviceSettings: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "account/update_delivery_device.json" - - var parameters = Dictionary() - parameters["device"] = device ? "sms" : "none" - parameters["include_entities"] ??= includeEntities - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(deliveryDeviceSettings: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -102,7 +79,7 @@ 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 postAccountUpdateProfileWithName(name: String? = nil, url: String? = nil, location: String? = nil, description: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((profile: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func udpateUserProfile(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) let path = "account/update_profile.json" @@ -115,9 +92,7 @@ public extension Swifter { parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(profile: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -125,20 +100,18 @@ 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 postAccountUpdateProfileBackgroundImage(imageData: NSData, title: String? = nil, includeEntities: Bool? = nil, use: Bool? = nil, success: ((profile: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func updateProfileBacground(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") let path = "account/update_profile_background_image.json" var parameters = Dictionary() - parameters["image"] = imageData.base64EncodedStringWithOptions([]) + parameters["image"] = imageData.base64EncodedString(options: []) parameters["title"] ??= title parameters["include_entities"] ??= includeEntities parameters["use"] ??= use - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(profile: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -146,21 +119,19 @@ 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 postUpdateAccountProfileColors(profileBackgroundColor: String? = nil, profileLinkColor: String? = nil, profileSidebarBorderColor: String? = nil, profileSidebarFillColor: String? = nil, profileTextColor: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((profile: Dictionary?) -> Void)? = nil, failure: 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: @escaping FailureHandler) { let path = "account/update_profile_colors.json" var parameters = Dictionary() - parameters["profile_background_color"] ??= profileBackgroundColor - parameters["profile_link_color"] ??= profileLinkColor - parameters["profile_sidebar_link_color"] ??= profileSidebarBorderColor - parameters["profile_sidebar_fill_color"] ??= profileSidebarFillColor - parameters["profile_text_color"] ??= profileTextColor + parameters["profile_background_color"] ??= backgroundColor + parameters["profile_link_color"] ??= linkColor + parameters["profile_sidebar_link_color"] ??= sidebarBorderColor + parameters["profile_sidebar_fill_color"] ??= sidebarFillColor + parameters["profile_text_color"] ??= textColor parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(profile: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -170,17 +141,15 @@ 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 postAccountUpdateProfileImage(imageData: NSData, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((profile: Dictionary?) -> Void)? = 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() - parameters["image"] = imageData.base64EncodedStringWithOptions([]) + parameters["image"] = imageData.base64EncodedString(options: []) parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(profile: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -188,7 +157,7 @@ public extension Swifter { Returns a collection of user objects that the authenticating user is blocking. */ - public func getBlockListWithIncludeEntities(includeEntities: Bool? = nil, skipStatus: Bool? = nil, cursor: String? = nil, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = 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() @@ -196,8 +165,8 @@ public extension Swifter { parameters["skip_status"] ??= skipStatus parameters["cursor"] ??= cursor - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["users"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -206,15 +175,15 @@ public extension Swifter { Returns an array of numeric user ids the authenticating user is blocking. */ - public func getBlockIDsWithStingifyIDs(stringifyIDs: String? = nil, cursor: String? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler) { + public func getBlockedUsersIDs(stringifyIDs: String? = nil, cursor: String? = nil, success: CursorSuccessHandler? = nil, failure: @escaping FailureHandler) { let path = "blocks/ids.json" var parameters = Dictionary() parameters["stringify_ids"] ??= stringifyIDs parameters["cursor"] ??= cursor - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -223,30 +192,15 @@ 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 postBlocksCreateWithScreenName(screenName: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler) { - let path = "blocks/create.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postBlocksCreateWithUserID(userID: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler) { + public func blockUser(for userTag: UserTag, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { let path = "blocks/create.json" var parameters = Dictionary() - parameters["user_id"] = userID + parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -254,30 +208,15 @@ 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 postDestroyBlocksWithUserID(userID: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler) { - let path = "blocks/destroy.json" - - var parameters = Dictionary() - parameters["user_id"] = userID - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postDestroyBlocksWithScreenName(screenName: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler) { + public func unblockUser(for userTag: UserTag, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { let path = "blocks/destroy.json" var parameters = Dictionary() - parameters["screen_name"] = screenName + parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities parameters["skip_status"] ??= skipStatus - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -297,30 +236,14 @@ 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 getUsersLookupWithScreenNames(screenNames: [String], includeEntities: Bool? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler) { + public func lookupUsers(for usersTag: UsersTag, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { let path = "users/lookup.json" var parameters = Dictionary() - parameters["screen_name"] = screenNames.joinWithSeparator(",") + parameters[usersTag.key] = usersTag.value parameters["include_entities"] ??= includeEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) - } - - public func getUsersLookupWithUserIDs(userIDs: [String], includeEntities: Bool? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler) { - let path = "users/lookup.json" - - var parameters = Dictionary() - - let userIDStrings = userIDs.map { String($0) } - parameters["user_id"] = userIDStrings.joinWithSeparator(",") - parameters["include_entities"] ??= includeEntities - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -330,28 +253,14 @@ 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 getUsersShowWithScreenName(screenName: String, includeEntities: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler) { - let path = "users/show.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func getUsersShowWithUserID(userID: String, includeEntities: Bool? = nil, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler) { + public func showUser(for userTag: UserTag, includeEntities: Bool? = nil, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { let path = "users/show.json" var parameters = Dictionary() - parameters["user_id"] = userID + parameters[userTag.key] = userTag.value parameters["include_entities"] ??= includeEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -361,80 +270,16 @@ public extension Swifter { Only the first 1,000 matching results are available. */ - public func getUsersSearchWithQuery(q: String, page: Int?, count: Int?, includeEntities: Bool?, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler) { + public func searchUsers(using query: String, page: Int?, count: Int?, includeEntities: Bool?, success: SuccessHandler? = nil, failure: @escaping FailureHandler) { let path = "users/search.json" var parameters = Dictionary() - parameters["q"] = q + parameters["q"] = query parameters["page"] ??= page parameters["count"] ??= count parameters["include_entities"] ??= includeEntities - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) - } - - /** - GET users/contributees - - Returns a collection of users that the specified user can "contribute" to. - */ - public func getUsersContributeesWithUserID(id: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "users/contributees.json" - - var parameters = Dictionary() - parameters["id"] = id - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) - } - - public func getUsersContributeesWithScreenName(screenName: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "users/contributees.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) - } - - /** - GET users/contributors - - Returns a collection of users who can contribute to the specified account. - */ - public func getUsersContributorsWithUserID(id: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "users/contributors.json" - - var parameters = Dictionary() - parameters["id"] = id - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) - } - - public func getUsersContributorsWithScreenName(screenName: String, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((users: [JSONValue]?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "users/contributors.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - parameters["include_entities"] ??= includeEntities - parameters["skip_status"] ??= skipStatus - - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json.array) - }, failure: failure) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -442,12 +287,10 @@ public extension Swifter { Removes the uploaded profile banner for the authenticating user. Returns HTTP 200 upon success. */ - public func postAccountRemoveProfileBannerWithSuccess(success: ((response: JSON) -> Void)? = nil, failure: FailureHandler? = nil) { + public func removeProfileBanner(success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "account/remove_profile_banner.json" - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(response: json) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: [:], success: { json, _ in success?(json) }, failure: failure) } /** @@ -464,20 +307,17 @@ 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 postAccountUpdateProfileBannerWithImageData(imageData: NSData, width: Int? = nil, height: Int? = nil, offsetLeft: Int? = nil, offsetTop: Int? = nil, success: ((response: JSON) -> Void)? = 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() - parameters["banner"] = imageData.base64EncodedStringWithOptions([]) + parameters["banner"] = imageData.base64EncodedString parameters["width"] ??= width parameters["height"] ??= height parameters["offset_left"] ??= offsetLeft parameters["offset_top"] ??= offsetTop - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(response: json) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -485,12 +325,11 @@ 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 getUsersProfileBannerWithUserID(userID: String, success: ((response: JSON) -> Void)? = 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.postJSONWithPath(path, baseURL: self.apiURL, parameters: [:], success: { json, _ in - success?(response: json) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -502,26 +341,11 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func postMutesUsersCreateForScreenName(screenName: String, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func muteUser(for userTag: UserTag, success: SuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "mutes/users/create.json" + let parameters: [String: Any] = [userTag.key: userTag.value] - var parameters = Dictionary() - parameters["screen_name"] = screenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postMutesUsersCreateForUserID(userID: String, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "mutes/users/create.json" - - var parameters = Dictionary() - parameters["user_id"] = userID - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in success?(json) }, failure: failure) } /** @@ -533,25 +357,14 @@ public extension Swifter { Actions taken in this method are asynchronous and changes will be eventually consistent. */ - public func postMutesUsersDestroyForScreenName(screenName: String, success: ((user: Dictionary?) -> Void)? = nil, failure: FailureHandler? = nil) { - let path = "mutes/users/destroy.json" - - var parameters = Dictionary() - parameters["screen_name"] = screenName - - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) - }, failure: failure) - } - - public func postMutesUsersDestroyForUserID(userID: String, success: ((user: Dictionary?) -> Void)? = 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["user_id"] = userID + parameters[userTag.key] = userTag.value - self.postJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(user: json.object) + self.postJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json) }, failure: failure) } @@ -560,14 +373,14 @@ public extension Swifter { Returns an array of numeric user ids the authenticating user has muted. */ - public func getMutesUsersIDsWithCursor(cursor: String? = nil, success: ((ids: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = 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() parameters["cursor"] ??= cursor - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(ids: json["ids"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["ids"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } @@ -576,7 +389,7 @@ public extension Swifter { Returns an array of user objects the authenticating user has muted. */ - public func getMutesUsersListWithCursor(cursor: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: ((users: [JSONValue]?, previousCursor: String?, nextCursor: String?) -> Void)? = nil, failure: FailureHandler? = nil) { + public func getMuteUsers(cursor: String? = nil, includeEntities: Bool? = nil, skipStatus: Bool? = nil, success: CursorSuccessHandler? = nil, failure: FailureHandler? = nil) { let path = "mutes/users/list.json" var parameters = Dictionary() @@ -584,8 +397,8 @@ public extension Swifter { parameters["skip_status"] ??= skipStatus parameters["cursor"] ??= cursor - self.getJSONWithPath(path, baseURL: self.apiURL, parameters: parameters, success: { json, _ in - success?(users: json["users"].array, previousCursor: json["previous_cursor_str"].string, nextCursor: json["next_cursor_str"].string) + self.getJSON(path: path, baseURL: .api, parameters: parameters, success: { json, _ in + success?(json["users"], json["previous_cursor_str"].string, json["next_cursor_str"].string) }, failure: failure) } diff --git a/Swifter/NSURL+Swifter.swift b/Swifter/URL++.swift similarity index 80% rename from Swifter/NSURL+Swifter.swift rename to Swifter/URL++.swift index bced0e63..b3100533 100644 --- a/Swifter/NSURL+Swifter.swift +++ b/Swifter/URL++.swift @@ -1,5 +1,5 @@ // -// NSURL+Swifter.swift +// URL+Swifter.swift // Swifter // // Copyright (c) 2014 Matt Donnelly. @@ -25,10 +25,9 @@ import Foundation - -extension NSURL { +extension URL { - func appendQueryString(queryString: String) -> NSURL { + func append(queryString: String) -> URL { guard !queryString.utf16.isEmpty else { return self } @@ -36,11 +35,11 @@ extension NSURL { var absoluteURLString = self.absoluteString if absoluteURLString.hasSuffix("?") { - absoluteURLString = absoluteURLString[0 ..< absoluteURLString.utf16.count] + absoluteURLString = absoluteURLString[0.. UInt16 { + return ((v << n) & 0xFFFF) | (v >> (16 - n)) +} + +func rotateLeft(_ v:UInt32, n:UInt32) -> UInt32 { + return ((v << n) & 0xFFFFFFFF) | (v >> (32 - n)) +} + +func rotateLeft(_ x:UInt64, n:UInt64) -> UInt64 { + return (x << n) | (x >> (64 - n)) +} + +func rotateRight(_ x:UInt16, n:UInt16) -> UInt16 { + return (x >> n) | (x << (16 - n)) +} + +func rotateRight(_ x:UInt32, n:UInt32) -> UInt32 { + return (x >> n) | (x << (32 - n)) +} + +func rotateRight(_ x:UInt64, n:UInt64) -> UInt64 { + return ((x >> n) | (x << (64 - n))) +} + +func reverseBytes(_ value: UInt32) -> UInt32 { + let tmp1 = ((value & 0x000000FF) << 24) | ((value & 0x0000FF00) << 8) + let tmp2 = ((value & 0x00FF0000) >> 8) | ((value & 0xFF000000) >> 24) + return tmp1 | tmp2 +} diff --git a/SwifterDemoMac/AppDelegate.swift b/SwifterDemoMac/AppDelegate.swift index 48943016..fc2bc8f9 100644 --- a/SwifterDemoMac/AppDelegate.swift +++ b/SwifterDemoMac/AppDelegate.swift @@ -26,15 +26,16 @@ import Cocoa import SwifterMac +@NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - - func applicationDidFinishLaunching(aNotification: NSNotification) { - NSAppleEventManager.sharedAppleEventManager().setEventHandler(self, andSelector: Selector("handleEvent:withReplyEvent:"), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) - LSSetDefaultHandlerForURLScheme("swifter", NSBundle.mainBundle().bundleIdentifier! as NSString as CFString) + + func applicationDidFinishLaunching(_ notification: Notification) { + NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.handleEvent(_:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) + LSSetDefaultHandlerForURLScheme("swifter" as CFString, Bundle.main.bundleIdentifier! as CFString) } - func handleEvent(event: NSAppleEventDescriptor!, withReplyEvent: NSAppleEventDescriptor!) { - Swifter.handleOpenURL(NSURL(string: event.paramDescriptorForKeyword(AEKeyword(keyDirectObject))!.stringValue!)!) + func handleEvent(_ event: NSAppleEventDescriptor!, withReplyEvent: NSAppleEventDescriptor!) { + Swifter.handleOpenURL(URL(string: event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))!.stringValue!)!) } } diff --git a/SwifterDemoMac/ViewController.swift b/SwifterDemoMac/ViewController.swift index c80d8013..d56f6aa1 100644 --- a/SwifterDemoMac/ViewController.swift +++ b/SwifterDemoMac/ViewController.swift @@ -35,19 +35,19 @@ class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - let failureHandler: NSError -> Void = { print($0.localizedDescription) } + let failureHandler: (Error) -> Void = { print($0.localizedDescription) } if useACAccount { let accountStore = ACAccountStore() - let accountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierTwitter) + let accountType = accountStore.accountType(withAccountTypeIdentifier: ACAccountTypeIdentifierTwitter) - accountStore.requestAccessToAccountsWithType(accountType, options: nil) { granted, error in + accountStore.requestAccessToAccounts(with: accountType, options: nil) { granted, error in guard granted else { print("There are no Twitter accounts configured. You can add or create a Twitter account in Settings.") return } - guard let twitterAccounts = accountStore.accountsWithAccountType(accountType) where !twitterAccounts.isEmpty else { + guard let twitterAccounts = accountStore.accounts(with: accountType) , !twitterAccounts.isEmpty else { print("There are no Twitter accounts configured. You can add or create a Twitter account in Settings.") return } @@ -55,15 +55,15 @@ class ViewController: NSViewController { let twitterAccount = twitterAccounts[0] as! ACAccount let swifter = Swifter(account: twitterAccount) - swifter.getStatusesHomeTimelineWithCount(20, success: { statuses in + swifter.getHomeTimeline(count: 20, success: { statuses in print(statuses) }, failure: failureHandler) } } else { let swifter = Swifter(consumerKey: "RErEmzj7ijDkJr60ayE2gjSHT", consumerSecret: "SbS0CHk11oJdALARa7NDik0nty4pXvAxdt7aj0R5y1gNzWaNEx") - swifter.authorizeWithCallbackURL(NSURL(string: "swifter://success")!, success: { _ in - swifter.getStatusesHomeTimelineWithCount(100, success: { statuses in - guard let tweets = statuses else { return } + swifter.authorize(with: URL(string: "swifter://success")!, success: { _ in + swifter.getHomeTimeline(count: 100, success: { statuses in + guard let tweets = statuses.array else { return } self.tweets = tweets.map { let tweet = Tweet() tweet.text = $0["text"].string! diff --git a/SwifterDemoMac/main.swift b/SwifterDemoMac/main.swift deleted file mode 100644 index 51b90630..00000000 --- a/SwifterDemoMac/main.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// main.swift -// SwifterDemoMac -// -// Copyright (c) 2014 Matt Donnelly. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Cocoa - -NSApplicationMain(Process.argc, Process.unsafeArgv) diff --git a/SwifterDemoiOS/AppDelegate.swift b/SwifterDemoiOS/AppDelegate.swift index beb06e72..97348142 100755 --- a/SwifterDemoiOS/AppDelegate.swift +++ b/SwifterDemoiOS/AppDelegate.swift @@ -31,11 +31,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool { + func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { Swifter.handleOpenURL(url) - return true } - + } diff --git a/SwifterDemoiOS/AuthViewController.swift b/SwifterDemoiOS/AuthViewController.swift index 600703f0..e0ce5382 100755 --- a/SwifterDemoiOS/AuthViewController.swift +++ b/SwifterDemoiOS/AuthViewController.swift @@ -41,68 +41,68 @@ class AuthViewController: UIViewController, SFSafariViewControllerDelegate { super.init(coder: aDecoder) } - @IBAction func didTouchUpInsideLoginButton(sender: AnyObject) { - let failureHandler: ((NSError) -> Void) = { error in - - self.alertWithTitle("Error", message: error.localizedDescription) + @IBAction func didTouchUpInsideLoginButton(_ sender: AnyObject) { + let failureHandler: (Error) -> Void = { error in + self.alert(title: "Error", message: error.localizedDescription) + } if useACAccount { let accountStore = ACAccountStore() - let accountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierTwitter) + let accountType = accountStore.accountType(withAccountTypeIdentifier: ACAccountTypeIdentifierTwitter) // Prompt the user for permission to their twitter account stored in the phone's settings - accountStore.requestAccessToAccountsWithType(accountType, options: nil) { granted, error in - - if granted { - let twitterAccounts = accountStore.accountsWithAccountType(accountType) - if twitterAccounts?.count == 0 { - self.alertWithTitle("Error", message: "There are no Twitter accounts configured. You can add or create a Twitter account in Settings.") - } else { - let twitterAccount = twitterAccounts[0] as! ACAccount - self.swifter = Swifter(account: twitterAccount) - self.fetchTwitterHomeStream() - } + accountStore.requestAccessToAccounts(with: accountType, options: nil) { granted, error in + guard granted else { + self.alert(title: "Error", message: error!.localizedDescription) + return + } + + let twitterAccounts = accountStore.accounts(with: accountType)! + + if twitterAccounts.isEmpty { + self.alert(title: "Error", message: "There are no Twitter accounts configured. You can add or create a Twitter account in Settings.") } else { - self.alertWithTitle("Error", message: error.localizedDescription) + let twitterAccount = twitterAccounts[0] as! ACAccount + self.swifter = Swifter(account: twitterAccount) + self.fetchTwitterHomeStream() } } } else { - let url = NSURL(string: "swifter://success")! - swifter.authorizeWithCallbackURL(url, presentFromViewController: self, success: { _ in + let url = URL(string: "swifter://success")! + + swifter.authorize(with: url, presentFrom: self, success: { _ in self.fetchTwitterHomeStream() }, failure: failureHandler) } } func fetchTwitterHomeStream() { - let failureHandler: ((NSError) -> Void) = { error in - self.alertWithTitle("Error", message: error.localizedDescription) + let failureHandler: (Error) -> Void = { error in + self.alert(title: "Error", message: error.localizedDescription) } - - self.swifter.getStatusesHomeTimelineWithCount(20, success: { statuses in - + self.swifter.getHomeTimeline(count: 20, success: { json in // Successfully fetched timeline, so lets create and push the table view - let tweetsViewController = self.storyboard!.instantiateViewControllerWithIdentifier("TweetsViewController") as! TweetsViewController - guard let tweets = statuses else { return } - tweetsViewController.tweets = tweets - self.navigationController?.pushViewController(tweetsViewController, animated: true) -// self.presentViewController(tweetsViewController, animated: true, completion: nil) - + + let tweetsViewController = self.storyboard!.instantiateViewController(withIdentifier: "TweetsViewController") as! TweetsViewController + guard let tweets = json.array else { return } + tweetsViewController.tweets = tweets + self.navigationController?.pushViewController(tweetsViewController, animated: true) + }, failure: failureHandler) } - func alertWithTitle(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert) - alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) - self.presentViewController(alert, animated: true, completion: nil) + func alert(title: String, message: String) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) } @available(iOS 9.0, *) - func safariViewControllerDidFinish(controller: SFSafariViewController) { - controller.dismissViewControllerAnimated(true, completion: nil) + func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + controller.dismiss(animated: true, completion: nil) } -} \ No newline at end of file +} diff --git a/SwifterDemoiOS/TweetCell.swift b/SwifterDemoiOS/TweetCell.swift new file mode 100644 index 00000000..4077fcd5 --- /dev/null +++ b/SwifterDemoiOS/TweetCell.swift @@ -0,0 +1,24 @@ +// +// TweetCell.swift +// Swifter +// +// Created by Andy Liang on 2016-08-11. +// Copyright © 2016 Matt Donnelly. All rights reserved. +// + +import UIKit + +class TweetCell: UITableViewCell { + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) + textLabel?.numberOfLines = 0 + textLabel?.font = .systemFont(ofSize: 14) + detailTextLabel?.textColor = .darkGray + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/SwifterDemoiOS/TweetsViewController.swift b/SwifterDemoiOS/TweetsViewController.swift index 0fc4b284..778e65dd 100755 --- a/SwifterDemoiOS/TweetsViewController.swift +++ b/SwifterDemoiOS/TweetsViewController.swift @@ -25,34 +25,44 @@ import UIKit import SwifteriOS +import SafariServices class TweetsViewController: UITableViewController { - var tweets : [JSONValue] = [] - - override func viewWillLayoutSubviews() - { - super.viewWillLayoutSubviews() + var tweets : [JSON] = [] + let reuseIdentifier: String = "reuseIdentifier" + + override func viewDidLoad() { + super.viewDidLoad() self.title = "Timeline" - self.tableView.contentInset = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, 0, 0) - self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, 0, 0) - } - - override func didReceiveMemoryWarning() - { - super.didReceiveMemoryWarning() + + tableView.rowHeight = UITableViewAutomaticDimension + tableView.estimatedRowHeight = 44 + tableView.contentInset = UIEdgeInsets(top: self.topLayoutGuide.length, left: 0, bottom: 0, right: 0) + tableView.scrollIndicatorInsets = UIEdgeInsets(top: self.topLayoutGuide.length, left: 0, bottom: 0, right: 0) + tableView.register(TweetCell.self, forCellReuseIdentifier: reuseIdentifier) } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tweets.count } - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: nil) - + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) cell.textLabel?.text = tweets[indexPath.row]["text"].string - + cell.detailTextLabel?.text = "By \(tweets[indexPath.row]["user"]["name"].string!), @\(tweets[indexPath.row]["user"]["screen_name"].string!)" return cell } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + guard #available(iOS 9.0, *) else { return } + let screenName = tweets[indexPath.row]["user"]["screen_name"].string! + let id = tweets[indexPath.row]["id_str"].string! + let url = URL(string: "https://twitter.com/\(screenName)/status/\(id)")! + let safariView = SFSafariViewController(url: url) + self.present(safariView, animated: true, completion: nil) + } + } +