From 099a21069fe000aec9d05bb24b9dc72de5d6a54f Mon Sep 17 00:00:00 2001 From: Zoufishan Mehdi Date: Fri, 25 Sep 2015 21:19:09 -0400 Subject: [PATCH 1/2] homework --- .../TalkinToTheNet.xcodeproj/project.pbxproj | 112 ++++++- TalkinToTheNet/TalkinToTheNet/APIManager.h | 16 + TalkinToTheNet/TalkinToTheNet/APIManager.m | 30 ++ .../TalkinToTheNet/Base.lproj/Main.storyboard | 282 +++++++++++++++++- .../TalkinToTheNet/DetailViewController.h | 18 ++ .../TalkinToTheNet/DetailViewController.m | 153 ++++++++++ .../TalkinToTheNet/LocationSearchResult.h | 19 ++ .../TalkinToTheNet/LocationSearchResult.m | 14 + .../TalkinToTheNet/LocationViewController.h | 13 + .../TalkinToTheNet/LocationViewController.m | 154 ++++++++++ .../TalkinToTheNet/ViewController.h | 15 - .../TalkinToTheNet/ViewController.m | 27 -- 12 files changed, 795 insertions(+), 58 deletions(-) create mode 100644 TalkinToTheNet/TalkinToTheNet/APIManager.h create mode 100644 TalkinToTheNet/TalkinToTheNet/APIManager.m create mode 100644 TalkinToTheNet/TalkinToTheNet/DetailViewController.h create mode 100644 TalkinToTheNet/TalkinToTheNet/DetailViewController.m create mode 100644 TalkinToTheNet/TalkinToTheNet/LocationSearchResult.h create mode 100644 TalkinToTheNet/TalkinToTheNet/LocationSearchResult.m create mode 100644 TalkinToTheNet/TalkinToTheNet/LocationViewController.h create mode 100644 TalkinToTheNet/TalkinToTheNet/LocationViewController.m delete mode 100644 TalkinToTheNet/TalkinToTheNet/ViewController.h delete mode 100644 TalkinToTheNet/TalkinToTheNet/ViewController.m diff --git a/TalkinToTheNet/TalkinToTheNet.xcodeproj/project.pbxproj b/TalkinToTheNet/TalkinToTheNet.xcodeproj/project.pbxproj index ee35a70..b8252f0 100644 --- a/TalkinToTheNet/TalkinToTheNet.xcodeproj/project.pbxproj +++ b/TalkinToTheNet/TalkinToTheNet.xcodeproj/project.pbxproj @@ -7,25 +7,40 @@ objects = { /* Begin PBXBuildFile section */ + 5FB9C22D1BB47BCB00AF003D /* APIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FB9C22C1BB47BCB00AF003D /* APIManager.m */; }; + 5FB9C2301BB47BFD00AF003D /* LocationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FB9C22F1BB47BFD00AF003D /* LocationViewController.m */; }; + 5FB9C2341BB47D0A00AF003D /* LocationSearchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FB9C2331BB47D0A00AF003D /* LocationSearchResult.m */; }; + 5FB9C2C21BB5ED8D00AF003D /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FB9C2C11BB5ED8D00AF003D /* DetailViewController.m */; }; + 5FB9C2C41BB6125900AF003D /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB9C2C31BB6125900AF003D /* CoreLocation.framework */; }; 8D7DCD4B1BAF859400A92AD2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D7DCD4A1BAF859400A92AD2 /* main.m */; }; 8D7DCD4E1BAF859400A92AD2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D7DCD4D1BAF859400A92AD2 /* AppDelegate.m */; }; - 8D7DCD511BAF859400A92AD2 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8D7DCD501BAF859400A92AD2 /* ViewController.m */; }; 8D7DCD541BAF859400A92AD2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D7DCD521BAF859400A92AD2 /* Main.storyboard */; }; 8D7DCD561BAF859400A92AD2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8D7DCD551BAF859400A92AD2 /* Assets.xcassets */; }; 8D7DCD591BAF859400A92AD2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D7DCD571BAF859400A92AD2 /* LaunchScreen.storyboard */; }; + 91DEF8253B0F3A4E0787CBE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CC222C9DAD89A9FB0C7A93EE /* libPods.a */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 5FB9C22B1BB47BCB00AF003D /* APIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIManager.h; sourceTree = ""; }; + 5FB9C22C1BB47BCB00AF003D /* APIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APIManager.m; sourceTree = ""; }; + 5FB9C22E1BB47BFD00AF003D /* LocationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocationViewController.h; sourceTree = ""; }; + 5FB9C22F1BB47BFD00AF003D /* LocationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationViewController.m; sourceTree = ""; }; + 5FB9C2321BB47D0A00AF003D /* LocationSearchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocationSearchResult.h; sourceTree = ""; }; + 5FB9C2331BB47D0A00AF003D /* LocationSearchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationSearchResult.m; sourceTree = ""; }; + 5FB9C2C01BB5ED8D00AF003D /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; + 5FB9C2C11BB5ED8D00AF003D /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 5FB9C2C31BB6125900AF003D /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; + 80E9BC16CFE15DD0EF9D6A4D /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 8D7DCD461BAF859400A92AD2 /* TalkinToTheNet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TalkinToTheNet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8D7DCD4A1BAF859400A92AD2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 8D7DCD4C1BAF859400A92AD2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 8D7DCD4D1BAF859400A92AD2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 8D7DCD4F1BAF859400A92AD2 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 8D7DCD501BAF859400A92AD2 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 8D7DCD531BAF859400A92AD2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 8D7DCD551BAF859400A92AD2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8D7DCD581BAF859400A92AD2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 8D7DCD5A1BAF859400A92AD2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CC222C9DAD89A9FB0C7A93EE /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FD4E88563752442FF1D5C651 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -33,17 +48,46 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5FB9C2C41BB6125900AF003D /* CoreLocation.framework in Frameworks */, + 91DEF8253B0F3A4E0787CBE6 /* libPods.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5FB9C2311BB47CCE00AF003D /* Location */ = { + isa = PBXGroup; + children = ( + ); + name = Location; + sourceTree = ""; + }; + 5FB9C2BF1BB5ED6600AF003D /* Detail */ = { + isa = PBXGroup; + children = ( + 5FB9C2C01BB5ED8D00AF003D /* DetailViewController.h */, + 5FB9C2C11BB5ED8D00AF003D /* DetailViewController.m */, + ); + name = Detail; + sourceTree = ""; + }; + 63C694589E2F21170409A809 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5FB9C2C31BB6125900AF003D /* CoreLocation.framework */, + CC222C9DAD89A9FB0C7A93EE /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; 8D7DCD3D1BAF859400A92AD2 = { isa = PBXGroup; children = ( 8D7DCD481BAF859400A92AD2 /* TalkinToTheNet */, 8D7DCD471BAF859400A92AD2 /* Products */, + CC1DED86F9B88FF02492A4D3 /* Pods */, + 63C694589E2F21170409A809 /* Frameworks */, ); sourceTree = ""; }; @@ -60,8 +104,14 @@ children = ( 8D7DCD4C1BAF859400A92AD2 /* AppDelegate.h */, 8D7DCD4D1BAF859400A92AD2 /* AppDelegate.m */, - 8D7DCD4F1BAF859400A92AD2 /* ViewController.h */, - 8D7DCD501BAF859400A92AD2 /* ViewController.m */, + 5FB9C22B1BB47BCB00AF003D /* APIManager.h */, + 5FB9C22C1BB47BCB00AF003D /* APIManager.m */, + 5FB9C2311BB47CCE00AF003D /* Location */, + 5FB9C22E1BB47BFD00AF003D /* LocationViewController.h */, + 5FB9C22F1BB47BFD00AF003D /* LocationViewController.m */, + 5FB9C2321BB47D0A00AF003D /* LocationSearchResult.h */, + 5FB9C2331BB47D0A00AF003D /* LocationSearchResult.m */, + 5FB9C2BF1BB5ED6600AF003D /* Detail */, 8D7DCD521BAF859400A92AD2 /* Main.storyboard */, 8D7DCD551BAF859400A92AD2 /* Assets.xcassets */, 8D7DCD571BAF859400A92AD2 /* LaunchScreen.storyboard */, @@ -79,6 +129,15 @@ name = "Supporting Files"; sourceTree = ""; }; + CC1DED86F9B88FF02492A4D3 /* Pods */ = { + isa = PBXGroup; + children = ( + FD4E88563752442FF1D5C651 /* Pods.debug.xcconfig */, + 80E9BC16CFE15DD0EF9D6A4D /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -86,9 +145,11 @@ isa = PBXNativeTarget; buildConfigurationList = 8D7DCD5D1BAF859400A92AD2 /* Build configuration list for PBXNativeTarget "TalkinToTheNet" */; buildPhases = ( + 0CB6641708D4DA025C03C734 /* Check Pods Manifest.lock */, 8D7DCD421BAF859400A92AD2 /* Sources */, 8D7DCD431BAF859400A92AD2 /* Frameworks */, 8D7DCD441BAF859400A92AD2 /* Resources */, + 48C83267E687C428AA4AED56 /* Copy Pods Resources */, ); buildRules = ( ); @@ -144,14 +205,50 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 0CB6641708D4DA025C03C734 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 48C83267E687C428AA4AED56 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 8D7DCD421BAF859400A92AD2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 8D7DCD511BAF859400A92AD2 /* ViewController.m in Sources */, + 5FB9C22D1BB47BCB00AF003D /* APIManager.m in Sources */, + 5FB9C2341BB47D0A00AF003D /* LocationSearchResult.m in Sources */, 8D7DCD4E1BAF859400A92AD2 /* AppDelegate.m in Sources */, 8D7DCD4B1BAF859400A92AD2 /* main.m in Sources */, + 5FB9C2301BB47BFD00AF003D /* LocationViewController.m in Sources */, + 5FB9C2C21BB5ED8D00AF003D /* DetailViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -259,6 +356,7 @@ }; 8D7DCD5E1BAF859400A92AD2 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FD4E88563752442FF1D5C651 /* Pods.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = TalkinToTheNet/Info.plist; @@ -271,6 +369,7 @@ }; 8D7DCD5F1BAF859400A92AD2 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 80E9BC16CFE15DD0EF9D6A4D /* Pods.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = TalkinToTheNet/Info.plist; @@ -300,6 +399,7 @@ 8D7DCD5F1BAF859400A92AD2 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/TalkinToTheNet/TalkinToTheNet/APIManager.h b/TalkinToTheNet/TalkinToTheNet/APIManager.h new file mode 100644 index 0000000..832b4ac --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/APIManager.h @@ -0,0 +1,16 @@ +// +// APIManager.h +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/24/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import + +@interface APIManager : NSObject + ++ (void)GETRequestWithURL:(NSURL *)URL + completionHandler:(void(^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/APIManager.m b/TalkinToTheNet/TalkinToTheNet/APIManager.m new file mode 100644 index 0000000..6ef6cbc --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/APIManager.m @@ -0,0 +1,30 @@ +// +// APIManager.m +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/24/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import "APIManager.h" + +@implementation APIManager + ++ (void)GETRequestWithURL:(NSURL *)URL + completionHandler:(void(^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler { + + NSURLSession *session = [NSURLSession sharedSession]; + + NSURLSessionDataTask *task = [session dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(data, response, error); + }); + }]; + + + [task resume]; + +} + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/Base.lproj/Main.storyboard b/TalkinToTheNet/TalkinToTheNet/Base.lproj/Main.storyboard index f56d2f3..e4bce80 100644 --- a/TalkinToTheNet/TalkinToTheNet/Base.lproj/Main.storyboard +++ b/TalkinToTheNet/TalkinToTheNet/Base.lproj/Main.storyboard @@ -1,25 +1,287 @@ - + - + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + diff --git a/TalkinToTheNet/TalkinToTheNet/DetailViewController.h b/TalkinToTheNet/TalkinToTheNet/DetailViewController.h new file mode 100644 index 0000000..3cc6107 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/DetailViewController.h @@ -0,0 +1,18 @@ +// +// DetailViewController.h +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/25/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import +#import "LocationSearchResult.h" + +@interface DetailViewController : UIViewController + +@property (nonatomic) NSDictionary *foursquareData; +@property (nonatomic) LocationSearchResult *dataPassed; + + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/DetailViewController.m b/TalkinToTheNet/TalkinToTheNet/DetailViewController.m new file mode 100644 index 0000000..697e991 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/DetailViewController.m @@ -0,0 +1,153 @@ +// +// DetailViewController.m +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/25/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import "APIManager.h" +#import "DetailViewController.h" +#import "STTwitter.h" +#import "LocationViewController.h" +#import "LocationSearchResult.h" + + +@interface DetailViewController () + +@property (weak, nonatomic) IBOutlet UILabel *locationLabel; +@property (weak, nonatomic) IBOutlet UILabel *categoryLabel; +@property (weak, nonatomic) IBOutlet UILabel *distanceLabel; +@property (weak, nonatomic) IBOutlet UIButton *twitterButton; +@property (weak, nonatomic) IBOutlet UITableView *tweetsTableView; + +@property (nonatomic) NSString *twitterHandle; +@property (nonatomic) NSMutableArray *twitterNewsfeed; + +@end + +@implementation DetailViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.categoryLabel.text = self.dataPassed.restaurantName; + self.locationLabel.text = self.dataPassed.restaurantAddress; + self.distanceLabel.text = self.dataPassed.restaurantDistance; + + [self initialAndTwitterSetup]; + +} + + +- (void)initialAndTwitterSetup { + self.tweetsTableView.dataSource = self; + self.tweetsTableView.delegate = self; + + + +// NSDictionary *location = [self.foursquareData objectForKey:@"location"]; +// NSArray *formattedAddress = [location objectForKey:@"formattedAddress"]; +// +// NSString *address = [formattedAddress firstObject]; +// self.locationLabel.text = address; +// +// +// +// NSArray *categoryArray = [self.foursquareData objectForKey:@"categories"]; +// NSDictionary *categoryObject = [categoryArray firstObject]; +// NSString *category = [categoryObject objectForKey:@"name"]; +// self.categoryLabel.text = category; + + + + NSDictionary *contacts = [self.foursquareData objectForKey:@"contact"]; + NSArray *allKeys = [contacts allKeys]; + if([allKeys containsObject:@"twitter"]){ + self.twitterHandle = [contacts objectForKey:@"twitter"]; + } + else{ + self.twitterHandle = @"Doesn't have twitter account"; + } + +} + + +#pragma mark TableView Data source and Delegate + +-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ + return self.twitterNewsfeed.count; +} + +-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifierDV" forIndexPath:indexPath]; + + if(cell == nil){ + cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CellIdentifierDV"]; + } + + + NSDictionary *tweets = self.twitterNewsfeed[indexPath.row]; + cell.textLabel.text = tweets[@"text"]; + cell.imageView.image = tweets[@"image"]; + + return cell; + +} + +#pragma mark Alert +-(void) noTweetsAlert{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"No Twitter Account Found" message:@"Sorry" preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + NSLog(@"Ok Action"); + }]; + [alert addAction:okAction]; + [self presentViewController:alert animated:YES completion:nil]; +} + +- (IBAction)twitterButtonTapped:(id)sender { + + STTwitterAPI *twitter = [STTwitterAPI twitterAPIAppOnlyWithConsumerKey:@"JNSvKZtVsPN6UEgFZSuZsbFJn" consumerSecret:@"sFk4RbyK83Mo7S7aRwCfENS2KHa1zi5CkQ0LNHmJ5vBlpqfjaJ"]; + [twitter verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { + [twitter getUserTimelineWithScreenName:self.twitterHandle successBlock:^(NSArray *statuses) { + + self.twitterNewsfeed = [NSMutableArray arrayWithArray:statuses]; + [self.tweetsTableView reloadData]; + + + } errorBlock:^(NSError *error) { + if(self.twitterNewsfeed.count == 0){ + [self noTweetsAlert]; + } + NSLog(@"%@",error.debugDescription); + + }]; + + } errorBlock:^(NSError *error) { + if(self.twitterNewsfeed.count == 0){ + [self noTweetsAlert]; + } + + NSLog(@"%@",error.debugDescription); + }]; +} + + + + + /* + #pragma mark - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + // Get the new view controller using [segue destinationViewController]. + // Pass the selected object to the new view controller. + } + */ + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/LocationSearchResult.h b/TalkinToTheNet/TalkinToTheNet/LocationSearchResult.h new file mode 100644 index 0000000..7999ec9 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/LocationSearchResult.h @@ -0,0 +1,19 @@ +// +// LocationSearchResult.h +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/24/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import + +@interface LocationSearchResult : NSObject + +@property (nonatomic) NSString *restaurantName; +@property (nonatomic) NSString *restaurantAddress; +@property (nonatomic) NSString *restaurantDistance; +@property (nonatomic) NSString *restaurantSearchEntry; + + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/LocationSearchResult.m b/TalkinToTheNet/TalkinToTheNet/LocationSearchResult.m new file mode 100644 index 0000000..6a9b3f4 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/LocationSearchResult.m @@ -0,0 +1,14 @@ +// +// LocationSearchResult.m +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/24/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import "LocationSearchResult.h" + +@implementation LocationSearchResult + + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/LocationViewController.h b/TalkinToTheNet/TalkinToTheNet/LocationViewController.h new file mode 100644 index 0000000..e0ed834 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/LocationViewController.h @@ -0,0 +1,13 @@ +// +// LocationViewController.h +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/24/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import + +@interface LocationViewController : UIViewController + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/LocationViewController.m b/TalkinToTheNet/TalkinToTheNet/LocationViewController.m new file mode 100644 index 0000000..04e9de9 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet/LocationViewController.m @@ -0,0 +1,154 @@ +// +// LocationViewController.m +// TalkinToTheNet +// +// Created by Zoufishan Mehdi on 9/24/15. +// Copyright © 2015 Mike Kavouras. All rights reserved. +// + +#import "LocationViewController.h" +#import "APIManager.h" +#import "LocationSearchResult.h" +#import "DetailViewController.h" + +@interface LocationViewController () + +@property (weak, nonatomic) IBOutlet UITextField *textField; +@property (weak, nonatomic) IBOutlet UITableView *tableView; +@property (nonatomic) NSMutableArray *searchResults; +@property (nonatomic) NSString *locationSearchEntry; + + +@end + +@implementation LocationViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.textField.delegate = self; + +} + + + +#pragma mark - FS API Request +- (void)makeNewFourSquareAPIRequestWithSearchTerm: (NSString *)searchTerm // pass four square search term + callbackBlock:(void(^)())block { // call block + + // search terms via url + NSString *urlString = [NSString stringWithFormat:@"https://api.foursquare.com/v2/venues/search?client_id=M1KDUWRS5OBWUNQCXHHF23TAUEG2YOB0RXGBSP0LBVRCX2XL&client_secret=FWTAPZOJ4UBPUXX2R5Q1D5F3X0HXMCSMERWL4DJFW3UA33YX&v=20150919&ll=40.7,-74&query=%@", searchTerm]; + + self.locationSearchEntry = searchTerm; + + + NSString *encodedString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + + NSLog(@"%@", encodedString); + + + NSURL *url = [NSURL URLWithString:encodedString]; + +[APIManager GETRequestWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + if (data != nil) { + + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:nil]; + + + NSArray *venues = [[json objectForKey:@"response"] objectForKey:@"venues"]; + + // NSLog(@"%@", venues); + + self.searchResults = [[NSMutableArray alloc]init]; + + for (NSDictionary *venue in venues) { + + NSString *venueName = [venue objectForKey:@"name"]; + NSString *venueLocation = [venue objectForKey:@"location"]; + + NSString *address = [venueLocation valueForKey:@"address"]; + NSString *city = [venueLocation valueForKey:@"city"]; + + NSString *distance = [venueLocation valueForKey:@"distance"]; + NSString *state = [venueLocation valueForKey:@"state"]; + + double distanceConvertedToDouble = [distance doubleValue]; + double metersInAMile = 1609.34; + double distanceInMiles = distanceConvertedToDouble / metersInAMile; + + + NSString *stringInMiles = [NSString stringWithFormat:@"%.2f", distanceInMiles]; + + if (address == nil){ + address = @""; + } + if (city == nil){ + city = @""; + } + + LocationSearchResult *resultsObject = [[LocationSearchResult alloc]init]; + + resultsObject.restaurantName = venueName; + resultsObject.restaurantAddress = [NSString stringWithFormat:@"%@, %@, %@", address, city, state]; + resultsObject.restaurantDistance = [NSString stringWithFormat:@"%@ miles", stringInMiles]; + resultsObject.restaurantSearchEntry = self.locationSearchEntry; + + [self.searchResults addObject:resultsObject]; + } + block(); + } +}]; + +} + +#pragma mark - textField +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + + [self.view endEditing:YES]; + + [self makeNewFourSquareAPIRequestWithSearchTerm:textField.text callbackBlock:^{ + + [self.tableView reloadData]; + }]; + return YES; +} + + +#pragma mark - tableView + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.searchResults.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SearchID" forIndexPath:indexPath]; + + LocationSearchResult *currentResult = self.searchResults[indexPath.row]; + + cell.textLabel.text = currentResult.restaurantName; + cell.detailTextLabel.text = currentResult.restaurantAddress; + + return cell; +} + + +#pragma mark - prepareForSegue + +- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + + NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; + LocationSearchResult *dataToPass = self.searchResults[indexPath.row]; + DetailViewController *vc = segue.destinationViewController; + vc.dataPassed = dataToPass; + +} + +@end diff --git a/TalkinToTheNet/TalkinToTheNet/ViewController.h b/TalkinToTheNet/TalkinToTheNet/ViewController.h deleted file mode 100644 index 8113a85..0000000 --- a/TalkinToTheNet/TalkinToTheNet/ViewController.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ViewController.h -// TalkinToTheNet -// -// Created by Michael Kavouras on 9/20/15. -// Copyright © 2015 Mike Kavouras. All rights reserved. -// - -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/TalkinToTheNet/TalkinToTheNet/ViewController.m b/TalkinToTheNet/TalkinToTheNet/ViewController.m deleted file mode 100644 index cbefa29..0000000 --- a/TalkinToTheNet/TalkinToTheNet/ViewController.m +++ /dev/null @@ -1,27 +0,0 @@ -// -// ViewController.m -// TalkinToTheNet -// -// Created by Michael Kavouras on 9/20/15. -// Copyright © 2015 Mike Kavouras. All rights reserved. -// - -#import "ViewController.h" - -@interface ViewController () - -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do any additional setup after loading the view, typically from a nib. -} - -- (void)didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -@end From a84981fb4429eec3b64e49a026b52ddc1a275bb6 Mon Sep 17 00:00:00 2001 From: Zoufishan Mehdi Date: Fri, 25 Sep 2015 21:26:44 -0400 Subject: [PATCH 2/2] pods added --- TalkinToTheNet/Podfile | 3 + TalkinToTheNet/Podfile.lock | 10 + .../Headers/Private/STTwitter/BAVPlistNode.h | 1 + .../Private/STTwitter/JSONSyntaxHighlight.h | 1 + .../STTwitter/NSDateFormatter+STTwitter.h | 1 + .../Private/STTwitter/NSError+STTwitter.h | 1 + .../Private/STTwitter/NSString+STTwitter.h | 1 + .../STTwitter/STHTTPRequest+STTwitter.h | 1 + .../Headers/Private/STTwitter/STHTTPRequest.h | 1 + .../Headers/Private/STTwitter/STTwitter.h | 1 + .../Headers/Private/STTwitter/STTwitterAPI.h | 1 + .../Private/STTwitter/STTwitterAppOnly.h | 1 + .../Headers/Private/STTwitter/STTwitterHTML.h | 1 + .../Private/STTwitter/STTwitterOAuth.h | 1 + .../Headers/Private/STTwitter/STTwitterOS.h | 1 + .../Private/STTwitter/STTwitterOSRequest.h | 1 + .../Private/STTwitter/STTwitterProtocol.h | 1 + .../STTwitter/STTwitterRequestProtocol.h | 1 + .../Private/STTwitter/STTwitterStreamParser.h | 1 + .../Headers/Public/STTwitter/BAVPlistNode.h | 1 + .../Public/STTwitter/JSONSyntaxHighlight.h | 1 + .../STTwitter/NSDateFormatter+STTwitter.h | 1 + .../Public/STTwitter/NSError+STTwitter.h | 1 + .../Public/STTwitter/NSString+STTwitter.h | 1 + .../STTwitter/STHTTPRequest+STTwitter.h | 1 + .../Headers/Public/STTwitter/STHTTPRequest.h | 1 + .../Pods/Headers/Public/STTwitter/STTwitter.h | 1 + .../Headers/Public/STTwitter/STTwitterAPI.h | 1 + .../Public/STTwitter/STTwitterAppOnly.h | 1 + .../Headers/Public/STTwitter/STTwitterHTML.h | 1 + .../Headers/Public/STTwitter/STTwitterOAuth.h | 1 + .../Headers/Public/STTwitter/STTwitterOS.h | 1 + .../Public/STTwitter/STTwitterOSRequest.h | 1 + .../Public/STTwitter/STTwitterProtocol.h | 1 + .../STTwitter/STTwitterRequestProtocol.h | 1 + .../Public/STTwitter/STTwitterStreamParser.h | 1 + TalkinToTheNet/Pods/Manifest.lock | 10 + .../Pods/Pods.xcodeproj/project.pbxproj | 566 ++ TalkinToTheNet/Pods/STTwitter/LICENCE.txt | 10 + TalkinToTheNet/Pods/STTwitter/README.md | 534 ++ .../STTwitter/NSDateFormatter+STTwitter.h | 15 + .../STTwitter/NSDateFormatter+STTwitter.m | 28 + .../STTwitter/STTwitter/NSError+STTwitter.h | 44 + .../STTwitter/STTwitter/NSError+STTwitter.m | 110 + .../STTwitter/STTwitter/NSString+STTwitter.h | 28 + .../STTwitter/STTwitter/NSString+STTwitter.m | 78 + .../STTwitter/STHTTPRequest+STTwitter.h | 26 + .../STTwitter/STHTTPRequest+STTwitter.m | 110 + .../Pods/STTwitter/STTwitter/STTwitter.h | 7 + .../Pods/STTwitter/STTwitter/STTwitterAPI.h | 2255 ++++++++ .../Pods/STTwitter/STTwitter/STTwitterAPI.m | 5117 +++++++++++++++++ .../STTwitter/STTwitter/STTwitterAppOnly.h | 42 + .../STTwitter/STTwitter/STTwitterAppOnly.m | 315 + .../Pods/STTwitter/STTwitter/STTwitterHTML.h | 39 + .../Pods/STTwitter/STTwitter/STTwitterHTML.m | 142 + .../Pods/STTwitter/STTwitter/STTwitterOAuth.h | 92 + .../Pods/STTwitter/STTwitter/STTwitterOAuth.m | 768 +++ .../Pods/STTwitter/STTwitter/STTwitterOS.h | 35 + .../Pods/STTwitter/STTwitter/STTwitterOS.m | 397 ++ .../STTwitter/STTwitter/STTwitterOSRequest.h | 30 + .../STTwitter/STTwitter/STTwitterOSRequest.m | 245 + .../STTwitter/STTwitter/STTwitterProtocol.h | 71 + .../STTwitter/STTwitterRequestProtocol.h | 15 + .../STTwitter/STTwitterStreamParser.h | 33 + .../STTwitter/STTwitterStreamParser.m | 138 + .../STTwitter/STTwitter/Vendor/BAVPlistNode.h | 43 + .../STTwitter/STTwitter/Vendor/BAVPlistNode.m | 88 + .../STTwitter/Vendor/JSONSyntaxHighlight.h | 80 + .../STTwitter/Vendor/JSONSyntaxHighlight.m | 180 + .../STTwitter/Vendor/STHTTPRequest.h | 132 + .../STTwitter/Vendor/STHTTPRequest.m | 1079 ++++ .../Pods/Pods-acknowledgements.markdown | 17 + .../Pods/Pods-acknowledgements.plist | 47 + .../Target Support Files/Pods/Pods-dummy.m | 5 + .../Pods/Pods-resources.sh | 95 + .../Pods/Pods.debug.xcconfig | 5 + .../Pods/Pods.release.xcconfig | 5 + .../STTwitter/STTwitter-Private.xcconfig | 6 + .../STTwitter/STTwitter-dummy.m | 5 + .../STTwitter/STTwitter-prefix.pch | 4 + .../STTwitter/STTwitter.xcconfig | 1 + .../contents.xcworkspacedata | 10 + 82 files changed, 13149 insertions(+) create mode 100644 TalkinToTheNet/Podfile create mode 100644 TalkinToTheNet/Podfile.lock create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/BAVPlistNode.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/JSONSyntaxHighlight.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/NSDateFormatter+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/NSError+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/NSString+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAPI.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAppOnly.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterHTML.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOAuth.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOS.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOSRequest.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterProtocol.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterRequestProtocol.h create mode 120000 TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterStreamParser.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/BAVPlistNode.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/JSONSyntaxHighlight.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/NSDateFormatter+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/NSError+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/NSString+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest+STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitter.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAPI.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAppOnly.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterHTML.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOAuth.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOS.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOSRequest.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterProtocol.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterRequestProtocol.h create mode 120000 TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterStreamParser.h create mode 100644 TalkinToTheNet/Pods/Manifest.lock create mode 100644 TalkinToTheNet/Pods/Pods.xcodeproj/project.pbxproj create mode 100644 TalkinToTheNet/Pods/STTwitter/LICENCE.txt create mode 100644 TalkinToTheNet/Pods/STTwitter/README.md create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitter.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterProtocol.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterRequestProtocol.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.m create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.h create mode 100644 TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.m create mode 100644 TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown create mode 100644 TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.plist create mode 100644 TalkinToTheNet/Pods/Target Support Files/Pods/Pods-dummy.m create mode 100755 TalkinToTheNet/Pods/Target Support Files/Pods/Pods-resources.sh create mode 100644 TalkinToTheNet/Pods/Target Support Files/Pods/Pods.debug.xcconfig create mode 100644 TalkinToTheNet/Pods/Target Support Files/Pods/Pods.release.xcconfig create mode 100644 TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-Private.xcconfig create mode 100644 TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-dummy.m create mode 100644 TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-prefix.pch create mode 100644 TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter.xcconfig create mode 100644 TalkinToTheNet/TalkinToTheNet.xcworkspace/contents.xcworkspacedata diff --git a/TalkinToTheNet/Podfile b/TalkinToTheNet/Podfile new file mode 100644 index 0000000..077e36b --- /dev/null +++ b/TalkinToTheNet/Podfile @@ -0,0 +1,3 @@ +platform:ios, '7.0' +pod 'STTwitter' + diff --git a/TalkinToTheNet/Podfile.lock b/TalkinToTheNet/Podfile.lock new file mode 100644 index 0000000..1153368 --- /dev/null +++ b/TalkinToTheNet/Podfile.lock @@ -0,0 +1,10 @@ +PODS: + - STTwitter (0.2.2) + +DEPENDENCIES: + - STTwitter + +SPEC CHECKSUMS: + STTwitter: eb85d8770ddb0f8989f341cf41ea875e90ac6e7f + +COCOAPODS: 0.38.2 diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/BAVPlistNode.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/BAVPlistNode.h new file mode 120000 index 0000000..e56c041 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/BAVPlistNode.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/Vendor/BAVPlistNode.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/JSONSyntaxHighlight.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/JSONSyntaxHighlight.h new file mode 120000 index 0000000..e66a12d --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/JSONSyntaxHighlight.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSDateFormatter+STTwitter.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSDateFormatter+STTwitter.h new file mode 120000 index 0000000..900548d --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSDateFormatter+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/NSDateFormatter+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSError+STTwitter.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSError+STTwitter.h new file mode 120000 index 0000000..635ec95 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSError+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/NSError+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSString+STTwitter.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSString+STTwitter.h new file mode 120000 index 0000000..e9aa08f --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/NSString+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/NSString+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest+STTwitter.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest+STTwitter.h new file mode 120000 index 0000000..e93710a --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STHTTPRequest+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest.h new file mode 120000 index 0000000..773ebc4 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STHTTPRequest.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/Vendor/STHTTPRequest.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitter.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitter.h new file mode 120000 index 0000000..c51ac46 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAPI.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAPI.h new file mode 120000 index 0000000..feab44a --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAPI.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterAPI.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAppOnly.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAppOnly.h new file mode 120000 index 0000000..5cf3a54 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterAppOnly.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterAppOnly.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterHTML.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterHTML.h new file mode 120000 index 0000000..216f277 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterHTML.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterHTML.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOAuth.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOAuth.h new file mode 120000 index 0000000..1d5d8de --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOAuth.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterOAuth.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOS.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOS.h new file mode 120000 index 0000000..e309b8c --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOS.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterOS.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOSRequest.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOSRequest.h new file mode 120000 index 0000000..c0cd18b --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterOSRequest.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterOSRequest.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterProtocol.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterProtocol.h new file mode 120000 index 0000000..68bdf36 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterProtocol.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterProtocol.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterRequestProtocol.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterRequestProtocol.h new file mode 120000 index 0000000..79168d1 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterRequestProtocol.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterRequestProtocol.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterStreamParser.h b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterStreamParser.h new file mode 120000 index 0000000..763334e --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Private/STTwitter/STTwitterStreamParser.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterStreamParser.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/BAVPlistNode.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/BAVPlistNode.h new file mode 120000 index 0000000..e56c041 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/BAVPlistNode.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/Vendor/BAVPlistNode.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/JSONSyntaxHighlight.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/JSONSyntaxHighlight.h new file mode 120000 index 0000000..e66a12d --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/JSONSyntaxHighlight.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSDateFormatter+STTwitter.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSDateFormatter+STTwitter.h new file mode 120000 index 0000000..900548d --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSDateFormatter+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/NSDateFormatter+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSError+STTwitter.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSError+STTwitter.h new file mode 120000 index 0000000..635ec95 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSError+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/NSError+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSString+STTwitter.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSString+STTwitter.h new file mode 120000 index 0000000..e9aa08f --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/NSString+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/NSString+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest+STTwitter.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest+STTwitter.h new file mode 120000 index 0000000..e93710a --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest+STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STHTTPRequest+STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest.h new file mode 120000 index 0000000..773ebc4 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STHTTPRequest.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/Vendor/STHTTPRequest.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitter.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitter.h new file mode 120000 index 0000000..c51ac46 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitter.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitter.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAPI.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAPI.h new file mode 120000 index 0000000..feab44a --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAPI.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterAPI.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAppOnly.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAppOnly.h new file mode 120000 index 0000000..5cf3a54 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterAppOnly.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterAppOnly.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterHTML.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterHTML.h new file mode 120000 index 0000000..216f277 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterHTML.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterHTML.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOAuth.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOAuth.h new file mode 120000 index 0000000..1d5d8de --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOAuth.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterOAuth.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOS.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOS.h new file mode 120000 index 0000000..e309b8c --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOS.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterOS.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOSRequest.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOSRequest.h new file mode 120000 index 0000000..c0cd18b --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterOSRequest.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterOSRequest.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterProtocol.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterProtocol.h new file mode 120000 index 0000000..68bdf36 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterProtocol.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterProtocol.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterRequestProtocol.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterRequestProtocol.h new file mode 120000 index 0000000..79168d1 --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterRequestProtocol.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterRequestProtocol.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterStreamParser.h b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterStreamParser.h new file mode 120000 index 0000000..763334e --- /dev/null +++ b/TalkinToTheNet/Pods/Headers/Public/STTwitter/STTwitterStreamParser.h @@ -0,0 +1 @@ +../../../STTwitter/STTwitter/STTwitterStreamParser.h \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Manifest.lock b/TalkinToTheNet/Pods/Manifest.lock new file mode 100644 index 0000000..1153368 --- /dev/null +++ b/TalkinToTheNet/Pods/Manifest.lock @@ -0,0 +1,10 @@ +PODS: + - STTwitter (0.2.2) + +DEPENDENCIES: + - STTwitter + +SPEC CHECKSUMS: + STTwitter: eb85d8770ddb0f8989f341cf41ea875e90ac6e7f + +COCOAPODS: 0.38.2 diff --git a/TalkinToTheNet/Pods/Pods.xcodeproj/project.pbxproj b/TalkinToTheNet/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..135ada6 --- /dev/null +++ b/TalkinToTheNet/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,566 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 00FE8F843F6C7C88D38EE89D932B4A5D /* STTwitterHTML.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EDF83245624C2794D558A8C6D68B090 /* STTwitterHTML.h */; }; + 01626EDA84CAEB5C30C3638798AA6C0D /* STTwitterAppOnly.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F3B7FC6DE513D4CD3206DA5AC68E8BD /* STTwitterAppOnly.h */; }; + 1A1E064E227A25DE371CB1DB49DA9EFC /* STTwitterProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C9B674580C63637D9A1F614EE1EAD006 /* STTwitterProtocol.h */; }; + 1F31CAC1A51DF8299D7A7B4D627ABE34 /* STHTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = A12F60DB2097C19D41957B96DB0901C1 /* STHTTPRequest.h */; }; + 23EA9C40766F3A2D71DD8501C803C01B /* JSONSyntaxHighlight.m in Sources */ = {isa = PBXBuildFile; fileRef = 693C49A08D3DE559718099011EE13B92 /* JSONSyntaxHighlight.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 28A314CB61CCF86BCEBDA9AEEE9EE2BE /* Pods-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = A847BA68449E10300F46ADEA24A06F1D /* Pods-dummy.m */; }; + 29257D361EF0A8146EE4AA450B328E8E /* STTwitterOAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CEA540C68C89CEE37DF3EA34A9A1888 /* STTwitterOAuth.h */; }; + 2C6D8793D26BA8C39CA07E3200B5A3ED /* STTwitter-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BBB9747401A09530B2A11E93BB5BA4C /* STTwitter-dummy.m */; }; + 3024A981FE404B6FA8EFC41C61B51313 /* NSDateFormatter+STTwitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 433B477AB930B316F2F78CF8D2ACE84C /* NSDateFormatter+STTwitter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 3119C5971B31FF3D8DA9F4DF51937F7A /* STHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 451CF09EA99F9C5AB5FCFF4EA6569A39 /* STHTTPRequest.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 38C77975DCB01D4195914D4D775C1D2B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C7B34F62C2C1F4BCEF5354B018F32F7 /* QuartzCore.framework */; }; + 3CD175097334418E202A072C0AA22DB2 /* STTwitterHTML.m in Sources */ = {isa = PBXBuildFile; fileRef = 90B709DF14D6AF9C0224F9CEFD01B519 /* STTwitterHTML.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 44B8D7C456305C4229AFB477B451116D /* STTwitterOSRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 02A83AA8779FF2196F26B229AF4846D8 /* STTwitterOSRequest.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 483FE9C84609E4A07FCB534D13604C93 /* STTwitterStreamParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 3877F1CBE67DAB4C27831F97A7FBEB4B /* STTwitterStreamParser.h */; }; + 4A3F78C7FE6BD6CDB0A6DA3154F99498 /* STTwitterAppOnly.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF4F7F151FE76FE31A8D4E1C3683311 /* STTwitterAppOnly.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 4A7C0B580BDD791700BB625EB71F2A2D /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CD1AB572B2663A5DA8B53C4151D78D6 /* Accounts.framework */; }; + 4C6ABBD22A4E067142D98F77F8152223 /* NSError+STTwitter.h in Headers */ = {isa = PBXBuildFile; fileRef = A2316D0006BD348C9081423E4B5B781C /* NSError+STTwitter.h */; }; + 4D6849BF2429C2F47441DAB0E6D434CA /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08C72A4657886A27F0982D5392E96A33 /* Twitter.framework */; }; + 4D99650C149E08FCAB02F9981C9A655E /* STTwitterOSRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 30F5F9378E7EFDBD99BB3EA28B10C28B /* STTwitterOSRequest.h */; }; + 6C4115738431F1E9A89B6FC4C585F2E9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25EEDA123B6590B319BF23B51BF65026 /* UIKit.framework */; }; + 6DF1252AED3DF03701906F0601921C92 /* BAVPlistNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BCE4542105BEA7218273DC2DA0884A2 /* BAVPlistNode.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 71C69EB4FC82E291DA18F4A45012159A /* STTwitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BCF59ED8B0F809CADA28615E2456EEA /* STTwitter.h */; }; + 77ACEE5EB1EB49E411A18BB42E771972 /* STTwitterStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 318E852D89E7911F38469D27AA672B54 /* STTwitterStreamParser.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 7F28D1C6B09EE1A352155A9120F8D03C /* STTwitterOAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = D75A2D0352B4BC8B24831DB608BCCD69 /* STTwitterOAuth.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 8DDA424329CC5798DD23DADDFBCC5489 /* STTwitterOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 90231108075102494D5D2E843A74D061 /* STTwitterOS.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + 9FD2364DA8BC0F97FF6CF960E0D392D6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F66AC0BCCBDBFD85E480DFE677D90187 /* CoreGraphics.framework */; }; + A750036903496E500D73B09E3ED4A672 /* NSString+STTwitter.h in Headers */ = {isa = PBXBuildFile; fileRef = D5C006976F2E5B5E04198F7FF51BCD89 /* NSString+STTwitter.h */; }; + A8737131CC1239B7EB359A8FA952029D /* JSONSyntaxHighlight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E76A03D4541169539CEE9E4732C7AA8 /* JSONSyntaxHighlight.h */; }; + ACC44E859A0CAEBD660AA5A7715D742D /* NSDateFormatter+STTwitter.h in Headers */ = {isa = PBXBuildFile; fileRef = BBD908F6F5AF59E73D6FB7E33A9DFD17 /* NSDateFormatter+STTwitter.h */; }; + AED20F2FE65D97D9BC1344AC7B7358D9 /* STTwitterOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 6700BEFFB1AEA8ABA59ECB32FC15FE80 /* STTwitterOS.h */; }; + C14FE79549DF9E7EE46F956DB2B9E4DD /* STTwitterAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 60FC5CF3C2590874175B0F36C4B6B979 /* STTwitterAPI.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + CB5EF37DED1432346B2A4B95D988FAAD /* NSError+STTwitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 396FE6FFC4C5E78504A30DFA397B5400 /* NSError+STTwitter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + E3841DB8EEE9420DD080F7F6CD27695E /* NSString+STTwitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 69830A88AE84966F381C33E394E91AF1 /* NSString+STTwitter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + EC04CC14A454D25911389A70683EDEA1 /* BAVPlistNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 20A54F53FBDA0C6F322BE4860332668D /* BAVPlistNode.h */; }; + EC4AEDACC255BAEB99E5BD666701CCF6 /* STHTTPRequest+STTwitter.h in Headers */ = {isa = PBXBuildFile; fileRef = B170276CEFD41205635857FCA7134573 /* STHTTPRequest+STTwitter.h */; }; + F209D0F64626E80DB1A9AE284F28E510 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6815CBBC026C34F7D83EDB4365718D9 /* Foundation.framework */; }; + F7731C03102D44F49F8F6E877EDF1B95 /* STTwitterRequestProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3009BBAE9AB3015EFA514636A63FA0E4 /* STTwitterRequestProtocol.h */; }; + F7A15DD1E6FFD026D8D55DFB45AFA954 /* STHTTPRequest+STTwitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A814EC9551E4CDA013C889175947E /* STHTTPRequest+STTwitter.m */; settings = {COMPILER_FLAGS = "-DOS_OBJECT_USE_OBJC=0"; }; }; + F8EC1548C5A2F74BF8C63612A817D21C /* STTwitterAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = D730344CF94D8A80B35BFD7A68D6DB75 /* STTwitterAPI.h */; }; + FEBB945F445508C0D0837D396B24629F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6815CBBC026C34F7D83EDB4365718D9 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + AB8F6BA3D9B2B8F658CEF10B4FA46CF5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13E35ABA8C3D6AE483EAEA6CA19E0266; + remoteInfo = STTwitter; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 02A83AA8779FF2196F26B229AF4846D8 /* STTwitterOSRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterOSRequest.m; path = STTwitter/STTwitterOSRequest.m; sourceTree = ""; }; + 08C72A4657886A27F0982D5392E96A33 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/Twitter.framework; sourceTree = DEVELOPER_DIR; }; + 15A529C27057E4A57D259CBC6E6CE49C /* Pods-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-acknowledgements.markdown"; sourceTree = ""; }; + 1DB72CB09EBF7857893F54C01DCE3026 /* STTwitter.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = STTwitter.xcconfig; sourceTree = ""; }; + 1E76A03D4541169539CEE9E4732C7AA8 /* JSONSyntaxHighlight.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = JSONSyntaxHighlight.h; path = STTwitter/Vendor/JSONSyntaxHighlight.h; sourceTree = ""; }; + 20A54F53FBDA0C6F322BE4860332668D /* BAVPlistNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = BAVPlistNode.h; path = STTwitter/Vendor/BAVPlistNode.h; sourceTree = ""; }; + 25EEDA123B6590B319BF23B51BF65026 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 270A814EC9551E4CDA013C889175947E /* STHTTPRequest+STTwitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "STHTTPRequest+STTwitter.m"; path = "STTwitter/STHTTPRequest+STTwitter.m"; sourceTree = ""; }; + 3009BBAE9AB3015EFA514636A63FA0E4 /* STTwitterRequestProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterRequestProtocol.h; path = STTwitter/STTwitterRequestProtocol.h; sourceTree = ""; }; + 30F5F9378E7EFDBD99BB3EA28B10C28B /* STTwitterOSRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterOSRequest.h; path = STTwitter/STTwitterOSRequest.h; sourceTree = ""; }; + 318E852D89E7911F38469D27AA672B54 /* STTwitterStreamParser.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterStreamParser.m; path = STTwitter/STTwitterStreamParser.m; sourceTree = ""; }; + 36076BD38500A250656334D71BB533B0 /* STTwitter-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "STTwitter-prefix.pch"; sourceTree = ""; }; + 3877F1CBE67DAB4C27831F97A7FBEB4B /* STTwitterStreamParser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterStreamParser.h; path = STTwitter/STTwitterStreamParser.h; sourceTree = ""; }; + 396FE6FFC4C5E78504A30DFA397B5400 /* NSError+STTwitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSError+STTwitter.m"; path = "STTwitter/NSError+STTwitter.m"; sourceTree = ""; }; + 3BCE4542105BEA7218273DC2DA0884A2 /* BAVPlistNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = BAVPlistNode.m; path = STTwitter/Vendor/BAVPlistNode.m; sourceTree = ""; }; + 433B477AB930B316F2F78CF8D2ACE84C /* NSDateFormatter+STTwitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSDateFormatter+STTwitter.m"; path = "STTwitter/NSDateFormatter+STTwitter.m"; sourceTree = ""; }; + 451CF09EA99F9C5AB5FCFF4EA6569A39 /* STHTTPRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STHTTPRequest.m; path = STTwitter/Vendor/STHTTPRequest.m; sourceTree = ""; }; + 5AF4F7F151FE76FE31A8D4E1C3683311 /* STTwitterAppOnly.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterAppOnly.m; path = STTwitter/STTwitterAppOnly.m; sourceTree = ""; }; + 5C7B34F62C2C1F4BCEF5354B018F32F7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + 60FC5CF3C2590874175B0F36C4B6B979 /* STTwitterAPI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterAPI.m; path = STTwitter/STTwitterAPI.m; sourceTree = ""; }; + 641AE05DD55E5E6AC1590CD7B4A18F97 /* Pods-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-resources.sh"; sourceTree = ""; }; + 64501735F5A4AE09F48C98C772F89CFF /* libSTTwitter.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSTTwitter.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 6700BEFFB1AEA8ABA59ECB32FC15FE80 /* STTwitterOS.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterOS.h; path = STTwitter/STTwitterOS.h; sourceTree = ""; }; + 693C49A08D3DE559718099011EE13B92 /* JSONSyntaxHighlight.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = JSONSyntaxHighlight.m; path = STTwitter/Vendor/JSONSyntaxHighlight.m; sourceTree = ""; }; + 69830A88AE84966F381C33E394E91AF1 /* NSString+STTwitter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+STTwitter.m"; path = "STTwitter/NSString+STTwitter.m"; sourceTree = ""; }; + 6BCF59ED8B0F809CADA28615E2456EEA /* STTwitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitter.h; path = STTwitter/STTwitter.h; sourceTree = ""; }; + 6CD1AB572B2663A5DA8B53C4151D78D6 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/Accounts.framework; sourceTree = DEVELOPER_DIR; }; + 700D84B2A74CAF3CB707CAD689C10119 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Pods.debug.xcconfig; sourceTree = ""; }; + 77739D87F5CA093EA4C3F97FB8D874EE /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7BBB9747401A09530B2A11E93BB5BA4C /* STTwitter-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "STTwitter-dummy.m"; sourceTree = ""; }; + 7EDF83245624C2794D558A8C6D68B090 /* STTwitterHTML.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterHTML.h; path = STTwitter/STTwitterHTML.h; sourceTree = ""; }; + 7F3B7FC6DE513D4CD3206DA5AC68E8BD /* STTwitterAppOnly.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterAppOnly.h; path = STTwitter/STTwitterAppOnly.h; sourceTree = ""; }; + 80539119C364D1C78D9F2D5C67EF6CC5 /* STTwitter-Private.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "STTwitter-Private.xcconfig"; sourceTree = ""; }; + 90231108075102494D5D2E843A74D061 /* STTwitterOS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterOS.m; path = STTwitter/STTwitterOS.m; sourceTree = ""; }; + 90B709DF14D6AF9C0224F9CEFD01B519 /* STTwitterHTML.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterHTML.m; path = STTwitter/STTwitterHTML.m; sourceTree = ""; }; + 9CEA540C68C89CEE37DF3EA34A9A1888 /* STTwitterOAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterOAuth.h; path = STTwitter/STTwitterOAuth.h; sourceTree = ""; }; + A12F60DB2097C19D41957B96DB0901C1 /* STHTTPRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STHTTPRequest.h; path = STTwitter/Vendor/STHTTPRequest.h; sourceTree = ""; }; + A2316D0006BD348C9081423E4B5B781C /* NSError+STTwitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSError+STTwitter.h"; path = "STTwitter/NSError+STTwitter.h"; sourceTree = ""; }; + A6815CBBC026C34F7D83EDB4365718D9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + A847BA68449E10300F46ADEA24A06F1D /* Pods-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-dummy.m"; sourceTree = ""; }; + B170276CEFD41205635857FCA7134573 /* STHTTPRequest+STTwitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "STHTTPRequest+STTwitter.h"; path = "STTwitter/STHTTPRequest+STTwitter.h"; sourceTree = ""; }; + BA6428E9F66FD5A23C0A2E06ED26CD2F /* Podfile */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + BBD908F6F5AF59E73D6FB7E33A9DFD17 /* NSDateFormatter+STTwitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDateFormatter+STTwitter.h"; path = "STTwitter/NSDateFormatter+STTwitter.h"; sourceTree = ""; }; + BF59BC15D23E1E1912C8F334E7236813 /* Pods-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-acknowledgements.plist"; sourceTree = ""; }; + C9B674580C63637D9A1F614EE1EAD006 /* STTwitterProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterProtocol.h; path = STTwitter/STTwitterProtocol.h; sourceTree = ""; }; + D25658059F5FD70758AED8664CAEBC6C /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Pods.release.xcconfig; sourceTree = ""; }; + D5C006976F2E5B5E04198F7FF51BCD89 /* NSString+STTwitter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+STTwitter.h"; path = "STTwitter/NSString+STTwitter.h"; sourceTree = ""; }; + D730344CF94D8A80B35BFD7A68D6DB75 /* STTwitterAPI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = STTwitterAPI.h; path = STTwitter/STTwitterAPI.h; sourceTree = ""; }; + D75A2D0352B4BC8B24831DB608BCCD69 /* STTwitterOAuth.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = STTwitterOAuth.m; path = STTwitter/STTwitterOAuth.m; sourceTree = ""; }; + F66AC0BCCBDBFD85E480DFE677D90187 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B42F78FA5B475D6D2862F0F77F7F6D9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4A7C0B580BDD791700BB625EB71F2A2D /* Accounts.framework in Frameworks */, + 9FD2364DA8BC0F97FF6CF960E0D392D6 /* CoreGraphics.framework in Frameworks */, + FEBB945F445508C0D0837D396B24629F /* Foundation.framework in Frameworks */, + 38C77975DCB01D4195914D4D775C1D2B /* QuartzCore.framework in Frameworks */, + 4D6849BF2429C2F47441DAB0E6D434CA /* Twitter.framework in Frameworks */, + 6C4115738431F1E9A89B6FC4C585F2E9 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BB15B706F2BB12D9DE1E98FB6B51C78C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F209D0F64626E80DB1A9AE284F28E510 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 03926547405742868DC7BC4067A4F11B /* Support Files */ = { + isa = PBXGroup; + children = ( + 1DB72CB09EBF7857893F54C01DCE3026 /* STTwitter.xcconfig */, + 80539119C364D1C78D9F2D5C67EF6CC5 /* STTwitter-Private.xcconfig */, + 7BBB9747401A09530B2A11E93BB5BA4C /* STTwitter-dummy.m */, + 36076BD38500A250656334D71BB533B0 /* STTwitter-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/STTwitter"; + sourceTree = ""; + }; + 0F75DF6C7C5F002280EC53F48E80B587 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2AE6C1D44A91870276108430D74D3220 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + 2AE6C1D44A91870276108430D74D3220 /* iOS */ = { + isa = PBXGroup; + children = ( + 6CD1AB572B2663A5DA8B53C4151D78D6 /* Accounts.framework */, + F66AC0BCCBDBFD85E480DFE677D90187 /* CoreGraphics.framework */, + A6815CBBC026C34F7D83EDB4365718D9 /* Foundation.framework */, + 5C7B34F62C2C1F4BCEF5354B018F32F7 /* QuartzCore.framework */, + 08C72A4657886A27F0982D5392E96A33 /* Twitter.framework */, + 25EEDA123B6590B319BF23B51BF65026 /* UIKit.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 5F5AC14A4695395CC7B245ACE63672F6 /* STTwitter */ = { + isa = PBXGroup; + children = ( + 20A54F53FBDA0C6F322BE4860332668D /* BAVPlistNode.h */, + 3BCE4542105BEA7218273DC2DA0884A2 /* BAVPlistNode.m */, + 1E76A03D4541169539CEE9E4732C7AA8 /* JSONSyntaxHighlight.h */, + 693C49A08D3DE559718099011EE13B92 /* JSONSyntaxHighlight.m */, + BBD908F6F5AF59E73D6FB7E33A9DFD17 /* NSDateFormatter+STTwitter.h */, + 433B477AB930B316F2F78CF8D2ACE84C /* NSDateFormatter+STTwitter.m */, + A2316D0006BD348C9081423E4B5B781C /* NSError+STTwitter.h */, + 396FE6FFC4C5E78504A30DFA397B5400 /* NSError+STTwitter.m */, + D5C006976F2E5B5E04198F7FF51BCD89 /* NSString+STTwitter.h */, + 69830A88AE84966F381C33E394E91AF1 /* NSString+STTwitter.m */, + A12F60DB2097C19D41957B96DB0901C1 /* STHTTPRequest.h */, + 451CF09EA99F9C5AB5FCFF4EA6569A39 /* STHTTPRequest.m */, + B170276CEFD41205635857FCA7134573 /* STHTTPRequest+STTwitter.h */, + 270A814EC9551E4CDA013C889175947E /* STHTTPRequest+STTwitter.m */, + 6BCF59ED8B0F809CADA28615E2456EEA /* STTwitter.h */, + D730344CF94D8A80B35BFD7A68D6DB75 /* STTwitterAPI.h */, + 60FC5CF3C2590874175B0F36C4B6B979 /* STTwitterAPI.m */, + 7F3B7FC6DE513D4CD3206DA5AC68E8BD /* STTwitterAppOnly.h */, + 5AF4F7F151FE76FE31A8D4E1C3683311 /* STTwitterAppOnly.m */, + 7EDF83245624C2794D558A8C6D68B090 /* STTwitterHTML.h */, + 90B709DF14D6AF9C0224F9CEFD01B519 /* STTwitterHTML.m */, + 9CEA540C68C89CEE37DF3EA34A9A1888 /* STTwitterOAuth.h */, + D75A2D0352B4BC8B24831DB608BCCD69 /* STTwitterOAuth.m */, + 6700BEFFB1AEA8ABA59ECB32FC15FE80 /* STTwitterOS.h */, + 90231108075102494D5D2E843A74D061 /* STTwitterOS.m */, + 30F5F9378E7EFDBD99BB3EA28B10C28B /* STTwitterOSRequest.h */, + 02A83AA8779FF2196F26B229AF4846D8 /* STTwitterOSRequest.m */, + C9B674580C63637D9A1F614EE1EAD006 /* STTwitterProtocol.h */, + 3009BBAE9AB3015EFA514636A63FA0E4 /* STTwitterRequestProtocol.h */, + 3877F1CBE67DAB4C27831F97A7FBEB4B /* STTwitterStreamParser.h */, + 318E852D89E7911F38469D27AA672B54 /* STTwitterStreamParser.m */, + 03926547405742868DC7BC4067A4F11B /* Support Files */, + ); + path = STTwitter; + sourceTree = ""; + }; + 7DB346D0F39D3F0E887471402A8071AB = { + isa = PBXGroup; + children = ( + BA6428E9F66FD5A23C0A2E06ED26CD2F /* Podfile */, + 0F75DF6C7C5F002280EC53F48E80B587 /* Frameworks */, + E9F2194FF21AAD6BDA6C0485E6E30190 /* Pods */, + CCA510CFBEA2D207524CDA0D73C3B561 /* Products */, + D2411A5FE7F7A004607BED49990C37F4 /* Targets Support Files */, + ); + sourceTree = ""; + }; + 952EEBFAF8F7E620423C9F156F25A506 /* Pods */ = { + isa = PBXGroup; + children = ( + 15A529C27057E4A57D259CBC6E6CE49C /* Pods-acknowledgements.markdown */, + BF59BC15D23E1E1912C8F334E7236813 /* Pods-acknowledgements.plist */, + A847BA68449E10300F46ADEA24A06F1D /* Pods-dummy.m */, + 641AE05DD55E5E6AC1590CD7B4A18F97 /* Pods-resources.sh */, + 700D84B2A74CAF3CB707CAD689C10119 /* Pods.debug.xcconfig */, + D25658059F5FD70758AED8664CAEBC6C /* Pods.release.xcconfig */, + ); + name = Pods; + path = "Target Support Files/Pods"; + sourceTree = ""; + }; + CCA510CFBEA2D207524CDA0D73C3B561 /* Products */ = { + isa = PBXGroup; + children = ( + 77739D87F5CA093EA4C3F97FB8D874EE /* libPods.a */, + 64501735F5A4AE09F48C98C772F89CFF /* libSTTwitter.a */, + ); + name = Products; + sourceTree = ""; + }; + D2411A5FE7F7A004607BED49990C37F4 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 952EEBFAF8F7E620423C9F156F25A506 /* Pods */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + E9F2194FF21AAD6BDA6C0485E6E30190 /* Pods */ = { + isa = PBXGroup; + children = ( + 5F5AC14A4695395CC7B245ACE63672F6 /* STTwitter */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 7724E2138676AE40CD1DC4BACCDF5186 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + EC04CC14A454D25911389A70683EDEA1 /* BAVPlistNode.h in Headers */, + A8737131CC1239B7EB359A8FA952029D /* JSONSyntaxHighlight.h in Headers */, + ACC44E859A0CAEBD660AA5A7715D742D /* NSDateFormatter+STTwitter.h in Headers */, + 4C6ABBD22A4E067142D98F77F8152223 /* NSError+STTwitter.h in Headers */, + A750036903496E500D73B09E3ED4A672 /* NSString+STTwitter.h in Headers */, + EC4AEDACC255BAEB99E5BD666701CCF6 /* STHTTPRequest+STTwitter.h in Headers */, + 1F31CAC1A51DF8299D7A7B4D627ABE34 /* STHTTPRequest.h in Headers */, + 71C69EB4FC82E291DA18F4A45012159A /* STTwitter.h in Headers */, + F8EC1548C5A2F74BF8C63612A817D21C /* STTwitterAPI.h in Headers */, + 01626EDA84CAEB5C30C3638798AA6C0D /* STTwitterAppOnly.h in Headers */, + 00FE8F843F6C7C88D38EE89D932B4A5D /* STTwitterHTML.h in Headers */, + 29257D361EF0A8146EE4AA450B328E8E /* STTwitterOAuth.h in Headers */, + AED20F2FE65D97D9BC1344AC7B7358D9 /* STTwitterOS.h in Headers */, + 4D99650C149E08FCAB02F9981C9A655E /* STTwitterOSRequest.h in Headers */, + 1A1E064E227A25DE371CB1DB49DA9EFC /* STTwitterProtocol.h in Headers */, + F7731C03102D44F49F8F6E877EDF1B95 /* STTwitterRequestProtocol.h in Headers */, + 483FE9C84609E4A07FCB534D13604C93 /* STTwitterStreamParser.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 1392CB665ED083A1CA3DB69C79C0874F /* Pods */ = { + isa = PBXNativeTarget; + buildConfigurationList = AD189C74F87AA7BF36679DD9D455CCD0 /* Build configuration list for PBXNativeTarget "Pods" */; + buildPhases = ( + AE365690A237172FC87C419E853A0D7E /* Sources */, + BB15B706F2BB12D9DE1E98FB6B51C78C /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 676499BBDC75C8A4D1A6E07518992B0E /* PBXTargetDependency */, + ); + name = Pods; + productName = Pods; + productReference = 77739D87F5CA093EA4C3F97FB8D874EE /* libPods.a */; + productType = "com.apple.product-type.library.static"; + }; + 13E35ABA8C3D6AE483EAEA6CA19E0266 /* STTwitter */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6A9BC50477902D12D19D96F28C150C38 /* Build configuration list for PBXNativeTarget "STTwitter" */; + buildPhases = ( + E69841BFCA70F49CC579D12084444E8C /* Sources */, + B42F78FA5B475D6D2862F0F77F7F6D9E /* Frameworks */, + 7724E2138676AE40CD1DC4BACCDF5186 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = STTwitter; + productName = STTwitter; + productReference = 64501735F5A4AE09F48C98C772F89CFF /* libSTTwitter.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; + }; + buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 7DB346D0F39D3F0E887471402A8071AB; + productRefGroup = CCA510CFBEA2D207524CDA0D73C3B561 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1392CB665ED083A1CA3DB69C79C0874F /* Pods */, + 13E35ABA8C3D6AE483EAEA6CA19E0266 /* STTwitter */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + AE365690A237172FC87C419E853A0D7E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 28A314CB61CCF86BCEBDA9AEEE9EE2BE /* Pods-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E69841BFCA70F49CC579D12084444E8C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6DF1252AED3DF03701906F0601921C92 /* BAVPlistNode.m in Sources */, + 23EA9C40766F3A2D71DD8501C803C01B /* JSONSyntaxHighlight.m in Sources */, + 3024A981FE404B6FA8EFC41C61B51313 /* NSDateFormatter+STTwitter.m in Sources */, + CB5EF37DED1432346B2A4B95D988FAAD /* NSError+STTwitter.m in Sources */, + E3841DB8EEE9420DD080F7F6CD27695E /* NSString+STTwitter.m in Sources */, + F7A15DD1E6FFD026D8D55DFB45AFA954 /* STHTTPRequest+STTwitter.m in Sources */, + 3119C5971B31FF3D8DA9F4DF51937F7A /* STHTTPRequest.m in Sources */, + 2C6D8793D26BA8C39CA07E3200B5A3ED /* STTwitter-dummy.m in Sources */, + C14FE79549DF9E7EE46F956DB2B9E4DD /* STTwitterAPI.m in Sources */, + 4A3F78C7FE6BD6CDB0A6DA3154F99498 /* STTwitterAppOnly.m in Sources */, + 3CD175097334418E202A072C0AA22DB2 /* STTwitterHTML.m in Sources */, + 7F28D1C6B09EE1A352155A9120F8D03C /* STTwitterOAuth.m in Sources */, + 8DDA424329CC5798DD23DADDFBCC5489 /* STTwitterOS.m in Sources */, + 44B8D7C456305C4229AFB477B451116D /* STTwitterOSRequest.m in Sources */, + 77ACEE5EB1EB49E411A18BB42E771972 /* STTwitterStreamParser.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 676499BBDC75C8A4D1A6E07518992B0E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = STTwitter; + target = 13E35ABA8C3D6AE483EAEA6CA19E0266 /* STTwitter */; + targetProxy = AB8F6BA3D9B2B8F658CEF10B4FA46CF5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 052A17875CB827423D627183396CEB60 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_PREPROCESSOR_DEFINITIONS = "RELEASE=1"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 21552A42CB2A0A170EF049B0574FAFDF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 80539119C364D1C78D9F2D5C67EF6CC5 /* STTwitter-Private.xcconfig */; + buildSettings = { + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREFIX_HEADER = "Target Support Files/STTwitter/STTwitter-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + B31843965670DF73A0E3704048ECF349 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 80539119C364D1C78D9F2D5C67EF6CC5 /* STTwitter-Private.xcconfig */; + buildSettings = { + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_PREFIX_HEADER = "Target Support Files/STTwitter/STTwitter-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Release; + }; + B37F0F91F85060E28F1DAAB522DC7EC1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + ONLY_ACTIVE_ARCH = YES; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + C5B1E53B07FFE09C7480D7FD551EA9A3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D25658059F5FD70758AED8664CAEBC6C /* Pods.release.xcconfig */; + buildSettings = { + ENABLE_STRICT_OBJC_MSGSEND = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Release; + }; + E1F5543350B8A8A441F5E186730DA40A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 700D84B2A74CAF3CB707CAD689C10119 /* Pods.debug.xcconfig */; + buildSettings = { + ENABLE_STRICT_OBJC_MSGSEND = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B37F0F91F85060E28F1DAAB522DC7EC1 /* Debug */, + 052A17875CB827423D627183396CEB60 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6A9BC50477902D12D19D96F28C150C38 /* Build configuration list for PBXNativeTarget "STTwitter" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 21552A42CB2A0A170EF049B0574FAFDF /* Debug */, + B31843965670DF73A0E3704048ECF349 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AD189C74F87AA7BF36679DD9D455CCD0 /* Build configuration list for PBXNativeTarget "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1F5543350B8A8A441F5E186730DA40A /* Debug */, + C5B1E53B07FFE09C7480D7FD551EA9A3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; +} diff --git a/TalkinToTheNet/Pods/STTwitter/LICENCE.txt b/TalkinToTheNet/Pods/STTwitter/LICENCE.txt new file mode 100644 index 0000000..fcef86c --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/LICENCE.txt @@ -0,0 +1,10 @@ +Copyright (c) 2012-2014, Nicolas Seriot +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of the Nicolas Seriot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/TalkinToTheNet/Pods/STTwitter/README.md b/TalkinToTheNet/Pods/STTwitter/README.md new file mode 100644 index 0000000..9f2dbf0 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/README.md @@ -0,0 +1,534 @@ +## STTwitter + +_A stable, mature and comprehensive Objective-C library for Twitter REST API 1.1_ + +_Like a FOSS version of Twitter Fabric TwitterKit, without the UI parts but with much more flexibility_ + +_Also includes a powerful Twitter dev console for OS X_ + +**[2015-03-28]** Signed build of OS X demo app: [STTwitterDemoOSX.app.zip](http://www.seriot.ch/temp/STTwitterDemoOSX.app.zip) +**[2014-06-18]** [Swifter](https://github.com/mattdonnelly/Swifter), A Twitter framework for iOS & OS X written in Swift, by [@MatthewDonnelly](htps://www.twitter.com/MatthewDonnelly/) +**[2014-05-31]** Follow STTwitter on Twitter: [@STTLibrary](https://www.twitter.com/STTLibrary/) +**[2014-05-22]** STTwitter was presented at [CocoaHeads Lausanne](https://www.facebook.com/events/732041160150290/) ([slides](http://seriot.ch/resources/abusing_twitter_api/sttwitter_cocoaheads.pdf)) +**[2013-10-24]** STTwitter was presented at [SoftShake 2013](http://soft-shake.ch) ([slides](http://seriot.ch/resources/abusing_twitter_api/ios_twitter_integration_sos13.pdf)). + +[![Build Status](https://travis-ci.org/nst/STTwitter.svg?branch=master)](https://travis-ci.org/nst/STTwitter) + +1. [Testimonials](#testimonials) +2. [Installation](#installation) +3. [Code Snippets](#code-snippets) +4. [Various Kinds of OAuth Connections](#various-kinds-of-oauth-connections) +5. [Twitter Digits](#twitter-digits) +6. [OAuth Consumer Tokens](#oauth-consumer-tokens) +7. [Demo / Test Project](#demo--test-project) +8. [Integration Tips](#integration-tips) +9. [Troubleshooting](#troubleshooting) +10. [Developers](#developers) +11. [BSD 3-Clause License](#bsd-3-clause-license) + +### Testimonials + +> "We are now using STTwitter" +[Adium developers](https://adium.im/blog/2013/07/adium-1-5-7-released/) + +> "An awesome Objective-C wrapper for Twitter’s HTTP API? Yes please!" +[@nilsou](https://twitter.com/nilsou/status/392364862472736768) + +> "Your Library is really great, I stopped the development of my client because I was hating twitter APIs for some reasons, this Library make me want to continue, seriously thank you!" +[MP0w](https://github.com/nst/STTwitter/pull/49#issuecomment-28746249) + +> "Powered by his own backend wrapper for HTTP calls, STTwitter writes most of the code for you for oAuth based authentication and API resource access like statuses, mentions, users, searches, friends & followers, favorites, lists, places, trends. The documentation is also excellent." +[STTwitter - Delightful Twitter Library for iOS / buddingdevelopers.com](http://buddingdevelopers.com/sttwitter-delightful-twitter-library-for-ios/) + +> Starting using STTwitter on a project. It is absolutely amazing. So easy to use. Thanks @nst021 +[@iOSDevZone](https://twitter.com/iOSDevZone/status/578975327264747520) + +> "I'm using this library for a WatchKit app and it works fantastically." [inb4ohnoes](https://github.com/nst/STTwitter/issues/177) + +> "I love STTwitter - it made things a breeze when building [@entourageio](http://bit.ly/entourageio)" [@_jeffreyjackson](https://twitter.com/_jeffreyjackson/status/573961997747773441) + +### Installation + +Drag and drop STTwitter directory into your project. + +Link your project with the following frameworks: + +- Accounts.framework +- Social.framework +- Twitter.framework (iOS only) +- Security.framework (OS X only) + +If you want to use CocoaPods, add the following two lines to your Podfile: + + pod 'STTwitter' + +Then, run the following command to install the STTwitter pod: + + pod install + +STTwitter does not depend on AppKit or UIKit and hence can be used in a command-line Twitter client. + +STTwitter requires iOS 5+ or OS X 10.7+. + +Vea Software has a great live-demo [tutorial](http://www.veasoftware.com/tutorials/2014/6/17/xcode-5-tutorial-ios-7-app-only-authentication-twitter-api-version-11) about creating a simple iOS app using STTwitter's app only mode. + +### Code Snippets + +Here are several ways to use STTwitter. + +You'll find complete, minimal, command-line projects in the `demo_cli` directory: + +- [streaming](https://github.com/nst/STTwitter/blob/master/demo_cli/streaming/main.m) use the streaming API to filter all tweets with some word +- [streaming_with_bot](https://github.com/nst/STTwitter/blob/master/demo_cli/streaming_with_bot/main.m) same as above, but reply instantly from another account +- [reverse_auth](https://github.com/nst/STTwitter/blob/master/demo_cli/reverse_auth/main.m) perform reverse authentication +- [followers](https://github.com/nst/STTwitter/blob/master/demo_cli/followers/main.m) display your followers by following Twitter's API cursors, and waiting for a while before reaching rate limits +- [favorites](https://github.com/nst/STTwitter/blob/master/demo_cli/favorites/main.m) display tweets favorited by people you follow, set a tweet's favorite status +- [conversation](https://github.com/nst/STTwitter/blob/master/demo_cli/conversation/main.m) post several tweets in the same conversation +- [t2rss](https://github.com/nst/STTwitter/blob/master/demo_cli/t2rss/main.m) convert your timeline into an RSS feed + +##### Instantiate STTwitterAPI + +```Objective-C +STTwitterAPI *twitter = [STTwitterAPI twitterAPIWithOAuthConsumerKey:@"" + consumerSecret:@"" + username:@"" + password:@""]; +``` + +##### Verify the credentials + +```Objective-C +[twitter verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { + // ... +} errorBlock:^(NSError *error) { + // ... +}]; +``` + +##### Get the timeline statuses + +```Objective-C +[twitter getHomeTimelineSinceID:nil + count:100 + successBlock:^(NSArray *statuses) { + // ... +} errorBlock:^(NSError *error) { + // ... +}]; +``` + +##### Streaming API + +```Objective-C +NSObject *request = [twitter getStatusesSampleDelimited:nil + stallWarnings:nil + progressBlock:^(id response) { + // ... +} stallWarningBlock:nil + errorBlock:^(NSError *error) { + // ... +}]; + +// ... + +[request cancel]; // when you're done with it +``` + +##### App Only Authentication + +```Objective-C +STTwitterAPI *twitter = [STTwitterAPI twitterAPIAppOnlyWithConsumerKey:@"" + consumerSecret:@""]; + +[twitter verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { + + [twitter getUserTimelineWithScreenName:@"barackobama" + successBlock:^(NSArray *statuses) { + // ... + } errorBlock:^(NSError *error) { + // ... + }]; + +} errorBlock:^(NSError *error) { + // ... +}]; +``` + +##### Enumerate results with cursors, pause according to rate limits + +```Objective-C +[_twitter fetchAndFollowCursorsForResource:@"followers/ids.json" + HTTPMethod:@"GET" + baseURLString:@"https://api.twitter.com/1.1" + parameters:@{@"screen_name":@"0xcharlie"} + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response, BOOL morePagesToCome, BOOL *stop) { + NSLog(@"-- success, more to come: %d, %@", morePagesToCome, response); +} pauseBlock:^(NSDate *nextRequestDate) { + NSLog(@"-- rate limit exhausted, nextRequestDate: %@", nextRequestDate); +} errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + NSLog(@"-- %@", error); +}]; +``` + +### Various Kinds of OAuth Connections + +You can instantiate `STTwitterAPI` in three ways: + +- use the Twitter account set in OS X Preferences or iOS Settings +- use a custom `consumer key` and `consumer secret` (four flavors) + - get an URL, fetch a PIN, enter it in your app, get oauth access tokens + - set `username` and `password`, get oauth access tokens with XAuth, if the app is entitled to + - set `oauth token` and `oauth token secret` directly + - open Safari (or a `UIWebView` instance if you prefer), authenticate on Twitter and receive access tokens in your app through a custom URL scheme +- use the [Application Only](https://dev.twitter.com/docs/auth/application-only-auth) authentication and get / use a "bearer token" + +So there are five cases altogether, hence these five methods: + +```Objective-C ++ (STTwitterAPI *)twitterAPIOSWithFirstAccount; + ++ (STTwitterAPI *)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; + ++ (STTwitterAPI *)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password; + ++ (STTwitterAPI *)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken + oauthTokenSecret:(NSString *)oauthTokenSecret; + ++ (STTwitterAPI *)twitterAPIAppOnlyWithConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; +``` + +##### Callbacks URLs + +After authenticating in Safari or in a web view, Twitter redirects to the callback URL with some additional parameters. ([Your Twitter app' settings](https://apps.twitter.com/) MUST allow the usage of callbacks by specifying a dummy URL, such as `http://www.cnn.com`. +This URL is then overriden by the `oauthCallback ` parameter in: + + - (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock + authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize + forceLogin:(NSNumber *)forceLogin + screenName:(NSString *)screenName + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock; + +STTwitter Twitter App Settings + +##### Reverse Authentication + +Reference: [https://dev.twitter.com/docs/ios/using-reverse-auth](https://dev.twitter.com/docs/ios/using-reverse-auth) + +The most common use case of reverse authentication is letting users register/login to a remote service with their OS X or iOS Twitter account. + + iOS/OSX Twitter Server + --------------> reverse auth. + < - - - - - - - access tokens + + -----------------------------> access tokens + + <-------------- access Twitter on user's behalf + - - - - - - -> + +Here is how to use reverse authentication with STTwitter: + +```Objective-C +STTwitterAPI *twitter = [STTwitterAPI twitterAPIWithOAuthConsumerName:nil + consumerKey:@"CONSUMER_KEY" + consumerSecret:@"CONSUMER_SECRET"]; + +[twitter postReverseOAuthTokenRequest:^(NSString *authenticationHeader) { + + STTwitterAPI *twitterAPIOS = [STTwitterAPI twitterAPIOSWithFirstAccount]; + + [twitterAPIOS verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { + + [twitterAPIOS postReverseAuthAccessTokenWithAuthenticationHeader:authenticationHeader + successBlock:^(NSString *oAuthToken, + NSString *oAuthTokenSecret, + NSString *userID, + NSString *screenName) { + + // use the tokens... + + } errorBlock:^(NSError *error) { + // ... + }]; + + } errorBlock:^(NSError *error) { + // ... + }]; + +} errorBlock:^(NSError *error) { + // ... +}]; +``` + +Contrary to what can be read here and there, you can perfectly [access direct messages from iOS Twitter accounts](http://stackoverflow.com/questions/17990484/accessing-twitter-direct-messages-using-slrequest-ios/18760445#18760445). + +### Twitter Digits + +[https://dev.twitter.com/twitter-kit/ios/digits](https://dev.twitter.com/twitter-kit/ios/digits) + +In this flow, you start with consumer tokens and app only mode, and end up with access tokens, after verifying a phone number with a PIN sent by SMS. + +It goes like this: + + 1. start with consumer tokens + 2. get a bearer token (ie. app only mode) + 2. get a guest token, (ie. temporary user id) + 3. post a phone number, using the guest token + 4. post the received PIN code for the phone number, using the guest token + 5. receive access tokens in return + +See a working example in [STAuthenticationVC.m](https://github.com/nst/STTwitter/blob/master/demo_osx/STTwitterDemoOSX/STAuthenticationVC.m#L368-L407). + +### 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](https://dev.twitter.com/apps). + +STTwitter demo project comes with `TwitterClients.plist` where you can enter your own consumer tokens. + +### Demo / Test Project + +There is a demo project for OS X in `demo_osx`, which lets you choose how to get the OAuth tokens (see below). + +An archive generated on 2013-10-20 10:35 is available at [http://seriot.ch/temp/STTwitterDemoOSX.app.zip](http://seriot.ch/temp/STTwitterDemoOSX.app.zip). + +Once you got the OAuth tokens, you can get your timeline and post a new status. + +There is also a simple iOS demo project in `demo_ios`. + +STTwitter Demo iOS +STTwitter Demo iOS +sample tweet + +### Integration Tips + +##### Concurrency + +STTwitter is supposed to be used from the main thread. The HTTP requests are performed anychronously and the callbacks are guaranteed to be called on main thread. + +##### Credentials verification + +There's no need to verify the credentials before each request. + +Doing so when the application starts and when the application enters foreground sounds reasonable, though. + +##### Timeout + +Unless told otherwise, STTwitter will use the underling classes default timeouts. + +You can also set the timeout by yourself: + + [twitter setTimeoutInSeconds:5.0]; + +##### Remove Asserts in Release Mode + +There are several asserts in the code. They are very useful in debug mode but you should not include them in release. + +New projects created with Xcode 5 already remove NSAssert logic by default in release. + +In older projects, you can set the compilation flag `-DNS_BLOCK_ASSERTIONS=1`. + +##### Number of Characters in a Tweet + +Use the method `-[NSString st_numberOfCharactersInATweet]` to let the user know how many characters she can enter before the end of the Tweet. The method may also return a negative value if the string exceeds a tweet's maximum length. The method considers the shortened URL lengths. + +##### Date Formatter + +In order to convert the string in the `created_at` field from Twitter's JSON into an NSDate instance, you can use the `+[NSDateFormatter st_TwitterDateFormatter]`. + +```Objective-C +NSDateFormatter *df = [NSDateFormatter st_TwitterDateFormatter]; +NSString *dateString = [d valueForKey:@"created_at"]; // "Sun Jun 28 20:33:01 +0000 2009" +NSDate *date = [df dateFromString:dateString]; +``` + +##### URLs Shorteners + +In order to expand shortened URLs such as Twitter's `t.co` service, use: + +```Objective-C +[STHTTPRequest expandedURLStringForShortenedURLString:@"http://t.co/tmoxbSfDWc" successBlock:^(NSString *expandedURLString) { + // +} errorBlock:^(NSError *error) { + // +}]; +``` + +##### API Responses Text Processing + +You may want to use Twitter's own Objective-C library for text processing: [https://github.com/twitter/twitter-text-objc/](https://github.com/twitter/twitter-text-objc/). + +`twitter-text-objc` provides you with methods such as: + +```Objective-C ++ (NSArray*)entitiesInText:(NSString*)text; ++ (NSArray*)URLsInText:(NSString*)text; ++ (NSArray*)hashtagsInText:(NSString*)text checkingURLOverlap:(BOOL)checkingURLOverlap; ++ (NSArray*)symbolsInText:(NSString*)text checkingURLOverlap:(BOOL)checkingURLOverlap; ++ (NSArray*)mentionedScreenNamesInText:(NSString*)text; ++ (NSArray*)mentionsOrListsInText:(NSString*)text; ++ (TwitterTextEntity*)repliedScreenNameInText:(NSString*)text; +``` + +##### Logout + +The correct approach to logout a user is setting the `STTwitterAPI` instance to nil. + +You'll create a new one at the next login. + +##### Boolean Parameters + +There are a lot of optional parameters in Twitter API. In STTwitter, you can ignore such parameters by passing `nil`. Regarding boolean parameters, STTwitter can't just use Objective-C `YES` and `NO` because `NO` has the same value as `nil` (zero). So boolean parameters are wrapped into `NSNumber` objects, which are pretty easy to use with boolean values thanks to Objective-C literals. So, with STTwitter, you will assign an optional parameter of Twitter API either as `@(YES)`, `@(NO)` or `nil`. + +##### Long Methods + +STTwitter provides a full, "one-to-one" Objective-C front-end to Twitter REST API. It often results in long methd names with many parameters. In your application, you may want to add your own, simplified methods on top of STTwitterAPI. A good idea is to create an Objective-C category for your application, such as in the following code. + +`STTwitterAPI+MyApp.h` + +```Objective-C +#import "STTwitterAPI.h" + +@interface STTwitterAPI (MyApp) + +- (void)getStatusesShowID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +@end +``` + +`STTwitterAPI+MyApp.m` + +```Objective-C +#import "STTwitterAPI+MyApp.h" + +@implementation STTwitterAPI (MyApp) + +- (void)getStatusesShowID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + [self getStatusesShowID:statusID + trimUser:@(YES) + includeMyRetweet:nil + includeEntities:@(NO) + successBlock:^(NSDictionary *status) { + + successBlock(status); + + } errorBlock:^(NSError *error) { + + errorBlock(error); + + }]; +} + +@end +``` + +##### Stream Request and Connection Losses + +Streaming requests may be lost when your iOS application comes back to foreground after a while in background. In order to handle this case properly, you can detect the connection loss in the error block and restart the stream request from there. + +```Objective-C +// ... +} errorBlock:^(NSError *error) { + + if([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorNetworkConnectionLost) { + [self startStreamRequest]; + } + +}]; +``` + +### Troubleshooting + +##### xAuth + +Twitter restricts the xAuth authentication process to xAuth-enabled consumer tokens only. So, if you get an error like `The consumer tokens are probably not xAuth enabled.` while accessing `https://api.twitter.com/oauth/access_token`, see Twitter's website [https://dev.twitter.com/docs/oauth/xauth](https://dev.twitter.com/docs/oauth/xauth) and ask Twitter to enable the xAuth authentication process for your consumer tokens. + +##### Anything Else + +Please [fill an issue](https://github.com/nst/STTwitter/issues) on GitHub or use the [STTwitter tag](http://stackoverflow.com/questions/tagged/sttwitter) on StackOverflow. + +### Developers + +The application only interacts with `STTwitterAPI`. + +`STTwitterAPI` maps Objective-C methods with all Twitter API endpoints. + +You can create your own convenience methods with fewer parameters. You can also use this generic method directly: + +```Objective-C + - (id)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock +downloadProgressBlock:(void(^)(NSObject *request, id response))downloadProgressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; +``` + +##### Layer Model + + +------------------------------------------------------------------------+ + | Your Application | + +--------------------------------------------------------+---------------+ + | STTwitterAPI | STTwitterHTML | + +--------------------------------------------------------+ | + + - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ | + | STTwitterOAuthProtocol | | + + - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ | + +--------------------+----------------+------------------+ | + | STTwitterOS | STTwitterOAuth | STTwitterAppOnly | | + +--------------------+----------------+------------------+---------------+ + | STTwitterOSRequest | STHTTPRequest | + +--------------------+---------------------------------------------------+ + | + + Accounts.framework + + Social.framework + +##### Summary + + * STTwitterAPI + - can be instantiated with the authentication mode you want + - provides methods to interact with each Twitter API endpoint + + * STTwitterHTML + - a hackish class to login on Twitter by parsing the HTML code and get a PIN + - it can break at anytime, your app should not rely on it in production + + * STTwitterOAuthProtocol + - provides generic methods to POST and GET resources on Twitter's hosts + + * STTwitterOS + - uses Twitter accounts defined in OS X Preferences or iOS Settings + - uses OS X / iOS frameworks to interact with Twitter API + + * STTwitterOSRequest + - block-based wrapper around SLRequest's underlying NSURLRequest + + * STTwitterOAuth + - implements OAuth and xAuth authentication + + * STTwitterAppOnly + - implements the 'app only' authentication + - https://dev.twitter.com/docs/auth/application-only-auth + + * STHTTPRequest + - block-based wrapper around NSURLConnection + - https://github.com/nst/STHTTPRequest + +### BSD 3-Clause License + +See [LICENCE.txt](LICENCE.txt). diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.h new file mode 100644 index 0000000..66e3e38 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.h @@ -0,0 +1,15 @@ +// +// NSDateFormatter+STTwitter.h +// curtter +// +// Created by Nicolas Seriot on 16/11/13. +// Copyright (c) 2013 Nicolas Seriot. All rights reserved. +// + +#import + +@interface NSDateFormatter (STTwitter) + ++ (NSDateFormatter *)st_TwitterDateFormatter; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.m new file mode 100644 index 0000000..7800c0f --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSDateFormatter+STTwitter.m @@ -0,0 +1,28 @@ +// +// NSDateFormatter+STTwitter.m +// curtter +// +// Created by Nicolas Seriot on 16/11/13. +// Copyright (c) 2013 Nicolas Seriot. All rights reserved. +// + +#import "NSDateFormatter+STTwitter.h" + +static NSDateFormatter *stTwitterDateFormatter = nil; + +@implementation NSDateFormatter (STTwitter) + ++ (NSDateFormatter *)st_TwitterDateFormatter { + + // parses the 'created_at' field, eg. "Sun Jun 28 20:33:01 +0000 2009" + + if(stTwitterDateFormatter == nil) { + stTwitterDateFormatter = [[NSDateFormatter alloc] init]; + [stTwitterDateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; + [stTwitterDateFormatter setDateFormat:@"EEE MMM dd HH:mm:ss Z yyyy"]; + } + + return stTwitterDateFormatter; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.h new file mode 100644 index 0000000..c1dbaf8 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.h @@ -0,0 +1,44 @@ +// +// NSError+STTwitter.h +// STTwitterDemoOSX +// +// Created by Nicolas Seriot on 19/03/14. +// Copyright (c) 2014 Nicolas Seriot. All rights reserved. +// + +#import + +static NSString *kSTTwitterTwitterErrorDomain = @"STTwitterTwitterErrorDomain"; +static NSString *kSTTwitterRateLimitLimit = @"STTwitterRateLimitLimit"; +static NSString *kSTTwitterRateLimitRemaining = @"STTwitterRateLimitRemaining"; +static NSString *kSTTwitterRateLimitResetDate = @"STTwitterRateLimitResetDate"; + +// https://dev.twitter.com/docs/error-codes-responses +typedef NS_ENUM( NSInteger, STTwitterTwitterErrorCode ) { + STTwitterTwitterErrorCouldNotAuthenticate = 32, // Your call could not be completed as dialed. + STTwitterTwitterErrorPageDoesNotExist = 34, // Corresponds with an HTTP 404 - the specified resource was not found. + STTwitterTwitterErrorAccountSuspended = 64, // Corresponds with an HTTP 403 — the access token being used belongs to a suspended user and they can't complete the action you're trying to take + STTwitterTwitterErrorAPIv1Inactive = 68, // Corresponds to a HTTP request to a retired v1-era URL. + STTwitterTwitterErrorRateLimitExceeded = 88, // The request limit for this resource has been reached for the current rate limit window. + STTwitterTwitterErrorInvalidOrExpiredToken = 89, // The access token used in the request is incorrect or has expired. Used in API v1.1 + STTwitterTwitterErrorSSLRequired = 92, // Only SSL connections are allowed in the API, you should update your request to a secure connection. See how to connect using SSL + STTwitterTwitterErrorOverCapacity = 130, // Corresponds with an HTTP 503 - Twitter is temporarily over capacity. + STTwitterTwitterErrorInternalError = 131, // Corresponds with an HTTP 500 - An unknown internal error occurred. + STTwitterTwitterErrorCouldNotAuthenticateYou = 135, // Corresponds with a HTTP 401 - it means that your oauth_timestamp is either ahead or behind our acceptable range + STTwitterTwitterErrorUnableToFollow = 161, // Corresponds with HTTP 403 — thrown when a user cannot follow another user due to some kind of limit + STTwitterTwitterErrorNotAuthorizedToSeeStatus = 179, // Corresponds with HTTP 403 — thrown when a Tweet cannot be viewed by the authenticating user, usually due to the tweet's author having protected their tweets. + STTwitterTwitterErrorDailyStatuUpdateLimitExceeded = 185, // Corresponds with HTTP 403 — thrown when a tweet cannot be posted due to the user having no allowance remaining to post. Despite the text in the error message indicating that this error is only thrown when a daily limit is reached, this error will be thrown whenever a posting limitation has been reached. Posting allowances have roaming windows of time of unspecified duration. + STTwitterTwitterErrorDuplicatedStatus = 187, // The status text has been Tweeted already by the authenticated account. + STTwitterTwitterErrorBadAuthenticationData = 215, // Typically sent with 1.1 responses with HTTP code 400. The method requires authentication but it was not presented or was wholly invalid. + STTwitterTwitterErrorUserMustVerifyLogin = 231, // Returned as a challenge in xAuth when the user has login verification enabled on their account and needs to be directed to twitter.com to generate a temporary password. + STTwitterTwitterErrorRetiredEndpoint = 251, // Corresponds to a HTTP request to a retired URL. + STTwitterTwitterErrorApplicationCannotWrite = 261 // Corresponds with HTTP 403 — thrown when the application is restricted from POST, PUT, or DELETE actions. See How to appeal application suspension and other disciplinary actions. +}; + +@interface NSError (STTwitter) + ++ (NSError *)st_twitterErrorFromResponseData:(NSData *)responseData + responseHeaders:(NSDictionary *)responseHeaders + underlyingError:(NSError *)underlyingError; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.m new file mode 100644 index 0000000..f86d31f --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSError+STTwitter.m @@ -0,0 +1,110 @@ +// +// NSError+STTwitter.m +// STTwitterDemoOSX +// +// Created by Nicolas Seriot on 19/03/14. +// Copyright (c) 2014 Nicolas Seriot. All rights reserved. +// + +#import "NSError+STTwitter.h" + +static NSRegularExpression *xmlErrorRegex = nil; + +@implementation NSError (STTwitter) + ++ (NSRegularExpression *)st_xmlErrorRegex { + if(xmlErrorRegex == nil) { + xmlErrorRegex = [NSRegularExpression regularExpressionWithPattern:@"(.*)" options:0 error:nil]; + } + return xmlErrorRegex; +} + ++ (NSError *)st_twitterErrorFromResponseData:(NSData *)responseData + responseHeaders:(NSDictionary *)responseHeaders + underlyingError:(NSError *)underlyingError { + + NSError *jsonError = nil; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError]; + + NSString *message = nil; + NSInteger code = 0; + + if([json isKindOfClass:[NSDictionary class]]) { + id errors = [json valueForKey:@"errors"]; + if([errors isKindOfClass:[NSArray class]] && [(NSArray *)errors count] > 0) { + // assume format: {"errors":[{"message":"Sorry, that page does not exist","code":34}]} + NSDictionary *errorDictionary = [errors lastObject]; + if([errorDictionary isKindOfClass:[NSDictionary class]]) { + message = errorDictionary[@"message"]; + code = [[errorDictionary valueForKey:@"code"] integerValue]; + } + } else if ([json valueForKey:@"error"] && [json valueForKey:@"error"] != [NSNull null]) { + /* + eg. when requesting timeline from a protected account + { + error = "Not authorized."; + request = "/1.1/statuses/user_timeline.json?count=20&screen_name=premfe"; + } + also, be robust to null errors such as in: + { + error = ""; + state = AwaitingComplete; + } + */ + message = [json valueForKey:@"error"]; + } else if([errors isKindOfClass:[NSString class]]) { + // assume format {errors = "Screen name can't be blank";} + message = errors; + } + } + + if(json == nil) { + // look for XML errors, eg. + /* + + + Client is not permitted to perform this action + + */ + + NSString *s = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; + + NSRegularExpression *xmlErrorRegex = [self st_xmlErrorRegex]; + NSAssert(xmlErrorRegex, @""); + + NSTextCheckingResult *match = [xmlErrorRegex firstMatchInString:s options:0 range:NSMakeRange(0, [s length])]; + + if(match) { + NSRange group1Range = [match rangeAtIndex:1]; + NSRange group2Range = [match rangeAtIndex:2]; + + NSString *codeString = [s substringWithRange:group1Range]; + NSString *errorMessaage = [s substringWithRange:group2Range]; + + return [NSError errorWithDomain:kSTTwitterTwitterErrorDomain code:[codeString integerValue] userInfo:@{NSLocalizedDescriptionKey:errorMessaage}]; + } + } + + if(message) { + NSString *rateLimitLimit = [responseHeaders valueForKey:@"x-rate-limit-limit"]; + NSString *rateLimitRemaining = [responseHeaders valueForKey:@"x-rate-limit-remaining"]; + NSString *rateLimitReset = [responseHeaders valueForKey:@"x-rate-limit-reset"]; + + NSDate *rateLimitResetDate = rateLimitReset ? [NSDate dateWithTimeIntervalSince1970:[rateLimitReset doubleValue]] : nil; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[NSLocalizedDescriptionKey] = message; + if(underlyingError) md[NSUnderlyingErrorKey] = underlyingError; + if(rateLimitLimit) md[kSTTwitterRateLimitLimit] = rateLimitLimit; + if(rateLimitRemaining) md[kSTTwitterRateLimitRemaining] = rateLimitRemaining; + if(rateLimitResetDate) md[kSTTwitterRateLimitResetDate] = rateLimitResetDate; + + NSDictionary *userInfo = [NSDictionary dictionaryWithDictionary:md]; + + return [NSError errorWithDomain:kSTTwitterTwitterErrorDomain code:code userInfo:userInfo]; + } + + return nil; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.h new file mode 100644 index 0000000..37f783b --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.h @@ -0,0 +1,28 @@ +// +// NSString+STTwitter.h +// STTwitter +// +// Created by Nicolas Seriot on 11/2/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import + +extern NSUInteger kSTTwitterDefaultShortURLLength; +extern NSUInteger kSTTwitterDefaultShortURLLengthHTTPS; + +extern NSString *kSTPOSTDataKey; // dummy parameter to tell a key used to post raw media, necessary because media are ignored in OAuth signatures +extern NSString *kSTPOSTMediaFileNameKey; // dummy parameter to tell the name of a file to be uploaded, optional but more correct than none + +@interface NSString (STTwitter) + +- (NSString *)st_firstMatchWithRegex:(NSString *)regex error:(NSError **)e; + +// use values from GET help/configuration +- (NSInteger)st_numberOfCharactersInATweetWithShortURLLength:(NSUInteger)shortURLLength + shortURLLengthHTTPS:(NSUInteger)shortURLLengthHTTS; + +// use default values for URL shortening +- (NSInteger)st_numberOfCharactersInATweet; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.m new file mode 100644 index 0000000..9e299c0 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/NSString+STTwitter.m @@ -0,0 +1,78 @@ +// +// NSString+STTwitter.m +// STTwitter +// +// Created by Nicolas Seriot on 11/2/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import "NSString+STTwitter.h" + +NSUInteger kSTTwitterDefaultShortURLLength = 22; +NSUInteger kSTTwitterDefaultShortURLLengthHTTPS = 23; + +NSString *kSTPOSTDataKey = @"kSTPOSTDataKey"; +NSString *kSTPOSTMediaFileNameKey = @"kSTPOSTMediaFileNameKey"; + +@implementation NSString (STTwitter) + +- (NSString *)st_firstMatchWithRegex:(NSString *)regex error:(NSError **)e { + NSError *error = nil; + NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:&error]; + + if(re == nil) { + if(e) *e = error; + return nil; + } + + NSArray *matches = [re matchesInString:self options:0 range:NSMakeRange(0, [self length])]; + + if([matches count] == 0) { + NSString *errorDescription = [NSString stringWithFormat:@"Can't find a match for regex: %@", regex]; + if(e) *e = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; + return nil; + } + + NSTextCheckingResult *match = [matches lastObject]; + NSRange matchRange = [match rangeAtIndex:1]; + return [self substringWithRange:matchRange]; +} + +// use values from GET help/configuration +- (NSInteger)st_numberOfCharactersInATweetWithShortURLLength:(NSUInteger)shortURLLength shortURLLengthHTTPS:(NSUInteger)shortURLLengthHTTPS { + + // NFC normalized string https://dev.twitter.com/docs/counting-characters + NSString *s = [self precomposedStringWithCanonicalMapping]; + + __block NSInteger count = 0; + [s enumerateSubstringsInRange:NSMakeRange(0, s.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *subString, NSRange subStringRange, NSRange enclosingRange, BOOL *stop) { + count++; + }]; + + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(https?://[A-Za-z0-9_\\.\\-/#\?=&]+)" + options:0 + error:&error]; + + NSArray *matches = [regex matchesInString:s + options:0 + range:NSMakeRange(0, [s length])]; + + for (NSTextCheckingResult *match in matches) { + NSRange urlRange = [match rangeAtIndex:1]; + NSString *urlString = [s substringWithRange:urlRange]; + + count -= urlRange.length; + count += [urlString hasPrefix:@"https"] ? shortURLLengthHTTPS : shortURLLength; + } + + return count; +} + +// use default values for URL shortening +- (NSInteger)st_numberOfCharactersInATweet { + return [self st_numberOfCharactersInATweetWithShortURLLength:kSTTwitterDefaultShortURLLength + shortURLLengthHTTPS:kSTTwitterDefaultShortURLLengthHTTPS]; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.h new file mode 100644 index 0000000..043576e --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.h @@ -0,0 +1,26 @@ +// +// STHTTPRequest+STTwitter.h +// STTwitter +// +// Created by Nicolas Seriot on 8/6/13. +// Copyright (c) 2013 Nicolas Seriot. All rights reserved. +// + +#import "STHTTPRequest.h" +#import "STTwitterRequestProtocol.h" + +@interface STHTTPRequest (STTwitter) + ++ (STHTTPRequest *)twitterRequestWithURLString:(NSString *)urlString + HTTPMethod:(NSString *)HTTPMethod + timeoutInSeconds:(NSTimeInterval)timeoutInSeconds + stTwitterUploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + stTwitterDownloadProgressBlock:(void(^)(NSData *data, NSUInteger totalBytesReceived, long long totalBytesExpectedToReceive))stTwitterDownloadProgressBlock + stTwitterSuccessBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock + stTwitterErrorBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; + ++ (void)expandedURLStringForShortenedURLString:(NSString *)urlString + successBlock:(void(^)(NSString *expandedURLString))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.m new file mode 100644 index 0000000..5a9ea9e --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STHTTPRequest+STTwitter.m @@ -0,0 +1,110 @@ +// +// STHTTPRequest+STTwitter.m +// STTwitter +// +// Created by Nicolas Seriot on 8/6/13. +// Copyright (c) 2013 Nicolas Seriot. All rights reserved. +// + +#import "STHTTPRequest+STTwitter.h" +#import "NSString+STTwitter.h" +#import "NSError+STTwitter.h" + +#if DEBUG +# define STLog(...) NSLog(__VA_ARGS__) +#else +# define STLog(...) +#endif + +@implementation STHTTPRequest (STTwitter) + ++ (STHTTPRequest *)twitterRequestWithURLString:(NSString *)urlString + HTTPMethod:(NSString *)HTTPMethod + timeoutInSeconds:(NSTimeInterval)timeoutInSeconds + stTwitterUploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + stTwitterDownloadProgressBlock:(void(^)(NSData *data, NSUInteger totalBytesReceived, long long totalBytesExpectedToReceive))downloadProgressBlock + stTwitterSuccessBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock + stTwitterErrorBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + __block STHTTPRequest *r = [self requestWithURLString:urlString]; + __weak STHTTPRequest *wr = r; + + r.HTTPMethod = HTTPMethod; + + r.cookieStoragePolicyForInstance = STHTTPRequestCookiesStorageNoStorage; + + r.timeoutSeconds = timeoutInSeconds; + + r.uploadProgressBlock = uploadProgressBlock; + + r.downloadProgressBlock = downloadProgressBlock; + + r.completionDataBlock = ^(NSDictionary *responseHeaders, NSData *responseData) { + + STHTTPRequest *sr = wr; // strong request + + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError]; + + if(json == nil) { + successBlock(sr.requestHeaders, sr.responseHeaders, sr.responseString); // response is not necessarily json + return; + } + + successBlock(sr.requestHeaders, sr.responseHeaders, json); + }; + + r.errorBlock = ^(NSError *error) { + + STHTTPRequest *sr = wr; // strong request + + NSError *e = [NSError st_twitterErrorFromResponseData:sr.responseData responseHeaders:sr.responseHeaders underlyingError:error]; + if(e) { + errorBlock(sr.requestHeaders, sr.responseHeaders, e); + return; + } + + if(error) { + errorBlock(sr.requestHeaders, sr.responseHeaders, error); + return; + } + + e = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey : sr.responseString}]; + + if (sr.responseString) STLog(@"-- body: %@", sr.responseString); + + // BOOL isCancellationError = [[error domain] isEqualToString:@"STHTTPRequest"] && ([error code] == kSTHTTPRequestCancellationError); + // if(isCancellationError) return; + + errorBlock(sr.requestHeaders, sr.responseHeaders, e); + }; + + return r; +} + ++ (void)expandedURLStringForShortenedURLString:(NSString *)urlString + successBlock:(void(^)(NSString *expandedURLString))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + STHTTPRequest *r = [STHTTPRequest requestWithURLString:urlString]; + + r.cookieStoragePolicyForInstance = STHTTPRequestCookiesStorageNoStorage; + + r.preventRedirections = YES; + + r.completionBlock = ^(NSDictionary *responseHeaders, NSString *body) { + + NSString *location = [responseHeaders valueForKey:@"location"]; + if(location == nil) [responseHeaders valueForKey:@"Location"]; + + successBlock(location); + }; + + r.errorBlock = ^(NSError *error) { + errorBlock(error); + }; + + [r startAsynchronous]; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitter.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitter.h new file mode 100644 index 0000000..e679425 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitter.h @@ -0,0 +1,7 @@ +#import "STTwitterAPI.h" +#import "STTwitterHTML.h" + +#import "NSDateFormatter+STTwitter.h" +#import "NSString+STTwitter.h" +#import "NSError+STTwitter.h" +#import "STHTTPRequest+STTwitter.h" diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.h new file mode 100644 index 0000000..3a749a7 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.h @@ -0,0 +1,2255 @@ +/* + Copyright (c) 2012, Nicolas Seriot + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of the Nicolas Seriot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// STTwitterAPI.h +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/18/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import +#import "STTwitterStreamParser.h" +#import "STTwitterRequestProtocol.h" + +extern NS_ENUM(NSUInteger, STTwitterAPIErrorCode) { + STTwitterAPICannotPostEmptyStatus = 0, + STTwitterAPIMediaDataIsEmpty, + STTwitterAPIEmptyStream +}; + +extern NSString *kBaseURLStringAPI_1_1; +extern NSString *kBaseURLStringStream_1_1; +extern NSString *kBaseURLStringUserStream_1_1; +extern NSString *kBaseURLStringSiteStream_1_1; + +/* + Tweet fields contents + https://dev.twitter.com/docs/platform-objects/tweets + https://dev.twitter.com/blog/new-withheld-content-fields-api-responses + */ + +@class ACAccount; + +@interface STTwitterAPI : NSObject + ++ (NSString *)versionString; + ++ (instancetype)twitterAPIOSWithAccount:(ACAccount *)account; ++ (instancetype)twitterAPIOSWithFirstAccount; + ++ (instancetype)twitterAPIWithOAuthConsumerName:(NSString *)consumerName // purely informational, can be anything + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; + +// convenience ++ (instancetype)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; + ++ (instancetype)twitterAPIWithOAuthConsumerName:(NSString *)consumerName // purely informational, can be anything + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password; + +// convenience ++ (instancetype)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password; + ++ (instancetype)twitterAPIWithOAuthConsumerName:(NSString *)consumerName // purely informational, can be anything + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken // aka accessToken + oauthTokenSecret:(NSString *)oauthTokenSecret; // aka accessTokenSecret + +// convenience ++ (instancetype)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken // aka accessToken + oauthTokenSecret:(NSString *)oauthTokenSecret; // aka accessTokenSecret + +// https://dev.twitter.com/docs/auth/application-only-auth ++ (instancetype)twitterAPIAppOnlyWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; + +// convenience ++ (instancetype)twitterAPIAppOnlyWithConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; + +/* + authenticateInsteadOfAuthorize == NO will return an URL to oauth/authorize + authenticateInsteadOfAuthorize == YES will return an URL to oauth/authenticate + + GET oauth/authenticate differs from GET oauth/authorize in that if the user has already granted the application permission, the redirect will occur without the user having to re-approve the application. To realize this behavior, you must enable the Use Sign in with Twitter setting on your application record. + */ + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock +authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize // use NO if you're not sure + forceLogin:(NSNumber *)forceLogin + screenName:(NSString *)screenName + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)postAccessTokenRequestWithPIN:(NSString *)pin + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// https://dev.twitter.com/docs/ios/using-reverse-auth + +// reverse auth step 1 +- (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// reverse auth step 2 +// WARNING: if the Twitter account was set in iOS settings, the tokens may be nil after a system update. +// In this case, you can call -[ACAccountStore renewCredentialsForAccount:completion:] to let the user enter her Twitter password again. +- (void)postReverseAuthAccessTokenWithAuthenticationHeader:(NSString *)authenticationHeader + successBlock:(void(^)(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// ensure that the Twitter account is usable by performing local access checks and then an API call +// this method should typically be called at each launch of a Twitter client +- (void)verifyCredentialsWithUserSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// deprecated, use verifyCredentialsWithUserSuccessBlock:errorBlock: +- (void)verifyCredentialsWithSuccessBlock:(void(^)(NSString *username))successBlock + errorBlock:(void(^)(NSError *error))errorBlock __deprecated_msg("use verifyCredentialsWithUserSuccessBlock:errorBlock: instead"); + +- (void)invalidateBearerTokenWithSuccessBlock:(void(^)())successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSString *)prettyDescription; + +- (void)setTimeoutInSeconds:(NSTimeInterval)timeoutInSeconds; // optional + +@property (nonatomic, retain) NSString *userName; // set after successful connection for STTwitterOAuth +@property (nonatomic, retain) NSString *userID; // set after successful connection for STTwitterOAuth + +@property (nonatomic, readonly) NSString *oauthAccessToken; +@property (nonatomic, readonly) NSString *oauthAccessTokenSecret; +@property (nonatomic, readonly) NSString *bearerToken; + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; + +#pragma mark Generic methods to GET and POST + +- (NSObject *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void (^)(NSObject *request, NSData *data))downloadProgressBlock + successBlock:(void (^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void (^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; + +- (NSObject *)fetchAndFollowCursorsForResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *request, NSData *data))downloadProgressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response, BOOL morePagesToCome, BOOL *stop))successBlock + pauseBlock:(void(^)(NSDate *nextRequestDate))pauseBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; + +- (NSObject *)getResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)parameters + downloadProgressBlock:(void(^)(NSData *data))progredownloadProgressBlockssBlock + successBlock:(void(^)(NSDictionary *rateLimits, id json))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)parameters + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSData *data))downloadProgressBlock + successBlock:(void(^)(NSDictionary *rateLimits, id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Timelines + +/* + GET statuses/mentions_timeline + Returns Tweets (*: mentions for the user) + + Returns the 20 most recent mentions (tweets containing a users's @screen_name) for the authenticating user. + + The timeline returned is the equivalent of the one seen when you view your mentions on twitter.com. + + This method can only return up to 800 tweets. + */ + +- (NSObject *)getStatusesMentionTimelineWithCount:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + contributorDetails:(NSNumber *)contributorDetails + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getMentionsTimelineSinceID:(NSString *)sinceID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/user_timeline + Returns Tweets (*: tweets for the user) + + Returns a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters. + + User timelines belonging to protected users may only be requested when the authenticated user either "owns" the timeline or is an approved follower of the owner. + + The timeline returned is the equivalent of the one seen when you view a user's profile on twitter.com. + + 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. + */ + +- (NSObject *)getStatusesUserTimelineForUserID:(NSString *)userID + screenName:(NSString *)screenName + sinceID:(NSString *)sinceID + count:(NSString *)count + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + excludeReplies:(NSNumber *)excludeReplies + contributorDetails:(NSNumber *)contributorDetails + includeRetweets:(NSNumber *)includeRetweets + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getUserTimelineWithScreenName:(NSString *)screenName + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getUserTimelineWithScreenName:(NSString *)screenName + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getUserTimelineWithScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/home_timeline + + Returns Tweets (*: tweets from people the user follows) + + Returns a collection of the most recent Tweets and retweets posted by the authenticating user and the users they follow. The home timeline is central to how most users interact with the Twitter service. + + 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. + */ + +- (NSObject *)getStatusesHomeTimelineWithCount:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + excludeReplies:(NSNumber *)excludeReplies + contributorDetails:(NSNumber *)contributorDetails + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getHomeTimelineSinceID:(NSString *)sinceID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/retweets_of_me + + 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. + */ + +- (NSObject *)getStatusesRetweetsOfMeWithCount:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + includeEntities:(NSNumber *)includeEntities + includeUserEntities:(NSNumber *)includeUserEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getStatusesRetweetsOfMeWithSuccessBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Tweets + +/* + GET statuses/retweets/:id + + Returns up to 100 of the first retweets of a given tweet. + */ +- (NSObject *)getStatusesRetweetsForID:(NSString *)statusID + count:(NSString *)count + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/show/:id + + Returns a single Tweet, specified by the id parameter. The Tweet's author will also be embedded within the tweet. + + See Embeddable Timelines, Embeddable Tweets, and GET statuses/oembed for tools to render Tweets according to Display Requirements. + + # About Geo + + If there is no geotag for a status, then there will be an empty or "geo" : {}. This can only be populated if the user has used the Geotagging API to send a statuses/update. + + The JSON response mostly uses conventions laid out in GeoJSON. Unfortunately, the coordinates that Twitter renders are reversed from the GeoJSON specification (GeoJSON specifies a longitude then a latitude, whereas we are currently representing it as a latitude then a longitude). Our JSON renders as: "geo": { "type":"Point", "coordinates":[37.78029, -122.39697] } + + # Contributors + + If there are no contributors for a Tweet, then there will be an empty or "contributors" : {}. This field will only be populated if the user has contributors enabled on his or her account -- this is a beta feature that is not yet generally available to all. + + 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]. + */ + +- (NSObject *)getStatusesShowID:(NSString *)statusID + trimUser:(NSNumber *)trimUser + includeMyRetweet:(NSNumber *)includeMyRetweet + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST statuses/destroy/:id + + 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. + */ + +- (NSObject *)postStatusesDestroy:(NSString *)statusID + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST statuses/update + + Updates the authenticating user's current status, also known as tweeting. To upload an image to accompany the tweet, use POST statuses/update_with_media. + + For each update attempt, the update text is compared with the authenticating user's recent tweets. Any attempt that would result in duplication will be blocked, resulting in a 403 error. Therefore, a user cannot submit the same status twice in a row. + + While not rate limited by the API a user is limited in the number of tweets they can create at a time. If the number of updates posted by the user reaches the current allowed limit this method will return an HTTP 403 error. + + - Any geo-tagging parameters in the update will be ignored if geo_enabled for the user is false (this is the default setting for all users unless the user has enabled geolocation in their settings) + - The number of digits passed the decimal separator passed to lat, up to 8, will be tracked so that the lat is returned in a status object it will have the same number of digits after the decimal separator. + - Please make sure to use to use a decimal point as the separator (and not the decimal comma) for the latitude and the longitude - usage of the decimal comma will cause the geo-tagged portion of the status update to be dropped. + - For JSON, the response mostly uses conventions described in GeoJSON. Unfortunately, the geo object has coordinates that Twitter renderers are reversed from the GeoJSON specification (GeoJSON specifies a longitude then a latitude, whereas we are currently representing it as a latitude then a longitude. Our JSON renders as: "geo": { "type":"Point", "coordinates":[37.78217, -122.40062] } + - The coordinates object is replacing the geo object (no deprecation date has been set for the geo object yet) -- the difference is that the coordinates object, in JSON, is now rendered correctly in GeoJSON. + - If a place_id is passed into the status update, then that place will be attached to the status. If no place_id was explicitly provided, but latitude and longitude are, we attempt to implicitly provide a place by calling geo/reverse_geocode. + - Users will have the ability, from their settings page, to remove all the geotags from all their tweets en masse. Currently we are not doing any automatic scrubbing nor providing a method to remove geotags from individual tweets. + */ + +- (NSObject *)postStatusUpdate:(NSString *)status + inReplyToStatusID:(NSString *)existingStatusID + latitude:(NSString *)latitude + longitude:(NSString *)longitude + placeID:(NSString *)placeID + displayCoordinates:(NSNumber *)displayCoordinates + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// starting May 28th, 2014 +// https://dev.twitter.com/notifications/multiple-media-entities-in-tweets +// https://dev.twitter.com/docs/api/multiple-media-extended-entities +- (NSObject *)postStatusUpdate:(NSString *)status + inReplyToStatusID:(NSString *)existingStatusID + mediaIDs:(NSArray *)mediaIDs + latitude:(NSString *)latitude + longitude:(NSString *)longitude + placeID:(NSString *)placeID + displayCoordinates:(NSNumber *)displayCoordinates + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST statuses/retweet/:id + + Retweets a tweet. Returns the original tweet with retweet details embedded. + + - This method is subject to update limits. A HTTP 403 will be returned if this limit as been hit. + - Twitter will ignore attempts to perform duplicate retweets. + - The retweet_count will be current as of when the payload is generated and may not reflect the exact count. It is intended as an approximation. + + Returns Tweets (1: the new tweet) + */ + +- (NSObject *)postStatusRetweetWithID:(NSString *)statusID + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postStatusRetweetWithID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST statuses/update_with_media + + Updates the authenticating user's current status and attaches media for upload. In other words, it creates a Tweet with a picture attached. + + Unlike POST statuses/update, this method expects raw multipart data. Your POST request's Content-Type should be set to multipart/form-data with the media[] parameter + + The Tweet text will be rewritten to include the media URL(s), which will reduce the number of characters allowed in the Tweet text. If the URL(s) cannot be appended without text truncation, the tweet will be rejected and this method will return an HTTP 403 error. + */ + +- (NSObject *)postStatusUpdate:(NSString *)status + mediaDataArray:(NSArray *)mediaDataArray // only one media is currently supported, help/configuration.json returns "max_media_per_upload" = 1 + possiblySensitive:(NSNumber *)possiblySensitive + inReplyToStatusID:(NSString *)inReplyToStatusID + latitude:(NSString *)latitude + longitude:(NSString *)longitude + placeID:(NSString *)placeID + displayCoordinates:(NSNumber *)displayCoordinates + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postStatusUpdate:(NSString *)status + inReplyToStatusID:(NSString *)existingStatusID + mediaURL:(NSURL *)mediaURL + placeID:(NSString *)placeID + latitude:(NSString *)latitude + longitude:(NSString *)longitude + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/oembed + + Returns information allowing the creation of an embedded representation of a Tweet on third party sites. See the oEmbed specification for information about the response format. + + 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. + */ + +- (NSObject *)getStatusesOEmbedForStatusID:(NSString *)statusID + urlString:(NSString *)urlString + maxWidth:(NSString *)maxWidth + hideMedia:(NSNumber *)hideMedia + hideThread:(NSNumber *)hideThread + omitScript:(NSNumber *)omitScript + align:(NSString *)align // 'left', 'right', 'center' or 'none' (default) + related:(NSString *)related // eg. twitterapi,twittermedia,twitter + lang:(NSString *)lang + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/retweeters/ids + + Returns a collection of up to 100 user IDs belonging to users who have retweeted the tweet specified by the id parameter. + + This method offers similar data to GET statuses/retweets/:id and replaces API v1's GET statuses/:id/retweeted_by/ids method. + */ + +- (NSObject *)getStatusesRetweetersIDsForStatusID:(NSString *)statusID + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *ids, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Search + +// GET search/tweets +- (NSObject *)getSearchTweetsWithQuery:(NSString *)q + geocode:(NSString *)geoCode // eg. "37.781157,-122.398720,1mi" + lang:(NSString *)lang // eg. "eu" + locale:(NSString *)locale // eg. "ja" + resultType:(NSString *)resultType // eg. "mixed, recent, popular" + count:(NSString *)count // eg. "100" + until:(NSString *)until // eg. "2012-09-01" + sinceID:(NSString *)sinceID // eg. "12345" + maxID:(NSString *)maxID // eg. "54321" + includeEntities:(NSNumber *)includeEntities + callback:(NSString *)callback // eg. "processTweets" + successBlock:(void(^)(NSDictionary *searchMetadata, NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience method +- (NSObject *)getSearchTweetsWithQuery:(NSString *)q + successBlock:(void(^)(NSDictionary *searchMetadata, NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Streaming + +/* + POST statuses/filter + + Returns public statuses that match one or more filter predicates. Multiple parameters may be specified which allows most clients to use a single connection to the Streaming API. Both GET and POST requests are supported, but GET requests with too many parameters may cause the request to be rejected for excessive URL length. Use a POST request to avoid long URLs. + + The track, follow, and locations fields should be considered to be combined with an OR operator. track=foo&follow=1234 returns Tweets matching "foo" OR created by user 1234. + + The default access level allows up to 400 track keywords, 5,000 follow userids and 25 0.1-360 degree location boxes. If you need elevated access to the Streaming API, you should explore our partner providers of Twitter data here: https://dev.twitter.com/programs/twitter-certified-products/products#Certified-Data-Products + + At least one predicate parameter (follow, locations, or track) must be specified. + */ + +- (NSObject *)postStatusesFilterUserIDs:(NSArray *)userIDs + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + stallWarnings:(NSNumber *)stallWarnings + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postStatusesFilterKeyword:(NSString *)keyword + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + stallWarningBlock:(void(^)(NSString *code, NSString *message, NSUInteger percentFull))stallWarningBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postStatusesFilterKeyword:(NSString *)keyword + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/sample + + 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. + */ + +- (NSObject *)getStatusesSampleStallWarnings:(NSNumber *)stallWarnings + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getStatusesSampleTweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + stallWarningBlock:(void(^)(NSString *code, NSString *message, NSUInteger percentFull))stallWarningBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getStatusesSampleTweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET statuses/firehose + + This endpoint requires special permission to access. + + 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. + */ + +- (NSObject *)getStatusesFirehoseWithCount:(NSString *)count + stallWarnings:(NSNumber *)stallWarnings + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET user + + Streams messages for a single user, as described in User streams https://dev.twitter.com/docs/streaming-apis/streams/user + */ + +- (NSObject *)getUserStreamStallWarnings:(NSNumber *)stallWarnings + includeMessagesFromFollowedAccounts:(NSNumber *)includeMessagesFromFollowedAccounts + includeReplies:(NSNumber *)includeReplies + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getUserStreamIncludeMessagesFromFollowedAccounts:(NSNumber *)includeMessagesFromFollowedAccounts + includeReplies:(NSNumber *)includeReplies + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + stallWarningBlock:(void(^)(NSString *code, NSString *message, NSUInteger percentFull))stallWarningBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getUserStreamIncludeMessagesFromFollowedAccounts:(NSNumber *)includeMessagesFromFollowedAccounts + includeReplies:(NSNumber *)includeReplies + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET site + + Streams messages for a set of users, as described in Site streams https://dev.twitter.com/docs/streaming-apis/streams/site + */ + +- (NSObject *)getSiteStreamForUserIDs:(NSArray *)userIDs + delimited:(NSNumber *)delimited + stallWarnings:(NSNumber *)stallWarnings + restrictToUserMessages:(NSNumber *)restrictToUserMessages + includeReplies:(NSNumber *)includeReplies + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Direct Messages + +/* + GET direct_messages + + Returns the 20 most recent direct messages sent to the authenticating user. Includes detailed information about the sender and recipient user. You can request up to 200 direct messages per call, up to a maximum of 800 incoming DMs. + */ + +- (NSObject *)getDirectMessagesSinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + fullText:(NSNumber *)fullText + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; +// convenience +- (NSObject *)getDirectMessagesSinceID:(NSString *)sinceID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET direct_messages/sent + + Returns the 20 most recent direct messages sent by the authenticating user. Includes detailed information about the sender and recipient user. You can request up to 200 direct messages per call, up to a maximum of 800 outgoing DMs. + */ + +- (NSObject *)getDirectMessagesSinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + fullText:(NSNumber *)fullText + page:(NSString *)page + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET direct_messages/show + + Returns a single direct message, specified by an id parameter. Like the /1.1/direct_messages.format request, this method will include the user objects of the sender and recipient. + */ + +- (NSObject *)getDirectMessagesShowWithID:(NSString *)messageID + fullText:(NSNumber *)fullText + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST direct_messages/destroy + + Destroys the direct message specified in the required ID parameter. The authenticating user must be the recipient of the specified direct message. + */ + +- (NSObject *)postDestroyDirectMessageWithID:(NSString *)messageID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *message))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST direct_messages/new + + Sends a new direct message to the specified user from the authenticating user. Requires both the user and text parameters and must be a POST. Returns the sent message in the requested format if successful. + */ + +- (NSObject *)postDirectMessage:(NSString *)status + forScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *message))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Friends & Followers + +/* + 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. + */ + +- (NSObject *)getFriendshipNoRetweetsIDsWithSuccessBlock:(void(^)(NSArray *ids))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET friends/ids + Returns Users (*: user IDs for followees) + + Returns a cursored collection of user IDs for every user the specified user is following (otherwise known as their "friends"). + + 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 5,000 user IDs 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. + + 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. + */ + +- (NSObject *)getFriendsIDsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + count:(NSString *)count + successBlock:(void(^)(NSArray *ids, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getFriendsIDsForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *friends))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET followers/ids + + Returns a cursored collection of user IDs for every user following the specified user. + + 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 5,000 user IDs 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. + + 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. + */ + +- (NSObject *)getFollowersIDsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + count:(NSString *)count + successBlock:(void(^)(NSArray *followersIDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getFollowersIDsForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *followers))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET friendships/lookup + + 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. + */ + +- (NSObject *)getFriendshipsLookupForScreenNames:(NSArray *)screenNames + orUserIDs:(NSArray *)userIDs + successBlock:(void(^)(NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET friendships/incoming + + Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. + */ + +- (NSObject *)getFriendshipIncomingWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *IDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET friendships/outgoing + + Returns a collection of numeric IDs for every protected user for whom the authenticating user has a pending follow request. + */ + +- (NSObject *)getFriendshipOutgoingWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *IDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST friendships/create + + Allows the authenticating users to follow the user specified in the ID parameter. + + Returns the befriended user in the requested format when successful. Returns a string describing the failure condition when unsuccessful. If you are already friends with the user a HTTP 403 may be returned, though for performance reasons you may get a 200 OK message even if the friendship already exists. + + Actions taken in this method are asynchronous and changes will be eventually consistent. + */ + +- (NSObject *)postFriendshipsCreateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *befriendedUser))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postFollow:(NSString *)screenName + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST friendships/destroy + + Allows the authenticating user to unfollow the user specified in the ID parameter. + + Returns the unfollowed user in the requested format when successful. Returns a string describing the failure condition when unsuccessful. + + Actions taken in this method are asynchronous and changes will be eventually consistent. + */ + +- (NSObject *)postFriendshipsDestroyScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *unfollowedUser))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postUnfollow:(NSString *)screenName + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST friendships/update + + Allows one to enable or disable retweets and device notifications from the specified user. + */ + +- (NSObject *)postFriendshipsUpdateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + enableDeviceNotifications:(NSNumber *)enableDeviceNotifications + enableRetweets:(NSNumber *)enableRetweets + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postFriendshipsUpdateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + enableDeviceNotifications:(BOOL)enableDeviceNotifications + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postFriendshipsUpdateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + enableRetweets:(BOOL)enableRetweets + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET friendships/show + + Returns detailed information about the relationship between two arbitrary users. + */ + +- (NSObject *)getFriendshipShowForSourceID:(NSString *)sourceID + orSourceScreenName:(NSString *)sourceScreenName + targetID:(NSString *)targetID + orTargetScreenName:(NSString *)targetScreenName + successBlock:(void(^)(id relationship))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET friends/list + + Returns a cursored collection of user objects for every user the specified user is following (otherwise known as their "friends"). + + 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. + */ + +- (NSObject *)getFriendsListForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + count:(NSString *)count + skipStatus:(NSNumber *)skipStatus + includeUserEntities:(NSNumber *)includeUserEntities + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getFriendsForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *friends))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET followers/list + + Returns a cursored collection of user objects for users following the specified user. + + 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. + */ + +- (NSObject *)getFollowersListForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + cursor:(NSString *)cursor + skipStatus:(NSNumber *)skipStatus + includeUserEntities:(NSNumber *)includeUserEntities + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getFollowersForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *followers))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Users + +/* + GET account/settings + + Returns settings (including current trend, geo and sleep time information) for the authenticating user. + */ + +- (NSObject *)getAccountSettingsWithSuccessBlock:(void(^)(NSDictionary *settings))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET account/verify_credentials + + 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. + */ + +- (NSObject *)getAccountVerifyCredentialsWithIncludeEntites:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + includeEmail:(NSNumber *)includeEmail + successBlock:(void(^)(NSDictionary *account))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getAccountVerifyCredentialsWithSuccessBlock:(void(^)(NSDictionary *account))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST account/settings + + Updates the authenticating user's settings. + */ + +- (NSObject *)postAccountSettingsWithTrendLocationWOEID:(NSString *)trendLocationWOEID // eg. "1" + sleepTimeEnabled:(NSNumber *)sleepTimeEnabled // eg. @(YES) + startSleepTime:(NSString *)startSleepTime // eg. "13" + endSleepTime:(NSString *)endSleepTime // eg. "13" + timezone:(NSString *)timezone // eg. "Europe/Copenhagen", "Pacific/Tongatapu" + language:(NSString *)language // eg. "it", "en", "es" + successBlock:(void(^)(NSDictionary *settings))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + 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. + */ + +- (NSObject *)postAccountUpdateDeliveryDeviceSMS:(BOOL)deliveryDeviceSMS + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST account/update_profile + + Sets values that users are able to set under the "Account" tab of their settings page. Only the parameters specified will be updated. + */ + +- (NSObject *)postAccountUpdateProfileWithName:(NSString *)name + URLString:(NSString *)URLString + location:(NSString *)location + description:(NSString *)description + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postUpdateProfile:(NSDictionary *)profileData + successBlock:(void(^)(NSDictionary *myInfo))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST account/update_profile_background_image + + 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. + */ + +- (NSObject *)postAccountUpdateProfileBackgroundImageWithImage:(NSString *)base64EncodedImage + title:(NSString *)title + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + use:(NSNumber *)use + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST account/update_profile_colors + + 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). + */ + +// https://twittercommunity.com/t/deprecation-of-account-update-profile-colors/28692 + +- (NSObject *)postAccountUpdateProfileColorsWithBackgroundColor:(NSString *)backgroundColor + linkColor:(NSString *)linkColor + sidebarBorderColor:(NSString *)sidebarBorderColor + sidebarFillColor:(NSString *)sidebarFillColor + profileTextColor:(NSString *)profileTextColor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock __attribute__((deprecated)); + +/* + POST account/update_profile_image + + Updates the authenticating user's profile image. Note that this method expects raw multipart data, not a URL to an image. + + 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. + */ + +- (NSObject *)postAccountUpdateProfileImage:(NSString *)base64EncodedImage + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET blocks/list + + Returns a collection of user objects that the authenticating user is blocking. + */ + +- (NSObject *)getBlocksListWithincludeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET blocks/ids + + Returns an array of numeric user ids the authenticating user is blocking. + */ + +- (NSObject *)getBlocksIDsWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *ids, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST blocks/create + + 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. + */ + +- (NSObject *)postBlocksCreateWithScreenName:(NSString *)screenName + orUserID:(NSString *)userID + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST blocks/destroy + + 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. + */ + +- (NSObject *)postBlocksDestroyWithScreenName:(NSString *)screenName + orUserID:(NSString *)userID + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/lookup + + Returns fully-hydrated user objects for up to 100 users per request, as specified by comma-separated values passed to the user_id and/or screen_name parameters. + + This method is especially useful when used in conjunction with collections of user IDs returned from GET friends/ids and GET followers/ids. + + GET users/show is used to retrieve a single user object. + + There are a few things to note when using this method. + + - You must be following a protected user to be able to see their most recent status update. If you don't follow a protected user their status will be removed. + - The order of user IDs or screen names may not match the order of users in the returned array. + - If a requested user is unknown, suspended, or deleted, then that user will not be returned in the results list. + - 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. + */ + +- (NSObject *)getUsersLookupForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/show + + Returns a variety of information about the user specified by the required user_id or screen_name parameter. The author's most recent Tweet will be returned inline when possible. GET users/lookup is used to retrieve a bulk collection of user objects. + + 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. + */ + +- (NSObject *)getUsersShowForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getUserInformationFor:(NSString *)screenName + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)profileImageFor:(NSString *)screenName + successBlock:(void(^)(id image))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/search + + Provides a simple, relevance-based search interface to public user accounts on Twitter. Try querying by topical interest, full name, company name, location, or other criteria. Exact match searches are not supported. + + Only the first 1,000 matching results are available. + */ + +- (NSObject *)getUsersSearchQuery:(NSString *)query + page:(NSString *)page + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/contributees + + Returns a collection of users that the specified user can "contribute" to. + */ + +- (NSObject *)getUsersContributeesWithUserID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *contributees))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/contributors + + Returns a collection of users who can contribute to the specified account. + */ + +- (NSObject *)getUsersContributorsWithUserID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *contributors))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST account/remove_profile_banner + + Removes the uploaded profile banner for the authenticating user. Returns HTTP 200 upon success. + */ + +- (NSObject *)postAccountRemoveProfileBannerWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST account/update_profile_banner + + Uploads a profile banner on behalf of the authenticating user. For best results, upload an <5MB image that is exactly 1252px by 626px. Images will be resized for a number of display options. Users with an uploaded profile banner will have a profile_banner_url node in their Users objects. More information about sizing variations can be found in User Profile Images and Banners and GET users/profile_banner. + + Profile banner images are processed asynchronously. The profile_banner_url and its variant sizes will not necessary be available directly after upload. + + If providing any one of the height, width, offset_left, or offset_top parameters, you must provide all of the sizing parameters. + + HTTP Response Codes + 200, 201, 202 Profile banner image succesfully uploaded + 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. + */ + +- (NSObject *)postAccountUpdateProfileBannerWithImage:(NSString *)base64encodedImage + width:(NSString *)width + height:(NSString *)height + offsetLeft:(NSString *)offsetLeft + offsetTop:(NSString *)offsetTop + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/profile_banner + + 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. + */ + +- (NSObject *)getUsersProfileBannerForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + successBlock:(void(^)(NSDictionary *banner))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST mutes/users/create + + Mutes the user specified in the ID parameter for the authenticating user. + + Returns the muted user in the requested format when successful. Returns a string describing the failure condition when unsuccessful. + + Actions taken in this method are asynchronous and changes will be eventually consistent. + */ +- (NSObject *)postMutesUsersCreateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST mutes/users/destroy + + Un-mutes the user specified in the ID parameter for the authenticating user. + + Returns the unmuted user in the requested format when successful. Returns a string describing the failure condition when unsuccessful. + + Actions taken in this method are asynchronous and changes will be eventually consistent. + */ +- (NSObject *)postMutesUsersDestroyForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET mutes/users/ids + + Returns an array of numeric user ids the authenticating user has muted. + */ +- (NSObject *)getMutesUsersIDsWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *userIDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET mutes/users/list + + Returns an array of user objects the authenticating user has muted. + */ +- (NSObject *)getMutesUsersListWithCursor:(NSString *)cursor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Suggested Users + +/* + GET users/suggestions/:slug + + Access the users in a given category of the Twitter suggested user list. + + It is recommended that applications cache this data for no more than one hour. + */ + +- (NSObject *)getUsersSuggestionsForSlug:(NSString *)slug // short name of list or a category, eg. "twitter" + lang:(NSString *)lang + successBlock:(void(^)(NSString *name, NSString *slug, NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/suggestions + + 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. + */ + +- (NSObject *)getUsersSuggestionsWithISO6391LanguageCode:(NSString *)ISO6391LanguageCode + successBlock:(void(^)(NSArray *suggestions))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET users/suggestions/:slug/members + + 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. + */ + +- (NSObject *)getUsersSuggestionsForSlugMembers:(NSString *)slug // short name of list or a category, eg. "twitter" + successBlock:(void(^)(NSArray *members))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Favorites + +/* + GET favorites/list + + Returns the 20 most recent Tweets favorited by the authenticating or specified user. + + 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. + */ + +- (NSObject *)getFavoritesListWithUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getFavoritesListWithSuccessBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST favorites/destroy + + Un-favorites the status specified in the ID parameter as the authenticating user. Returns the un-favorited status in the requested format when successful. + + 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. + */ + +- (NSObject *)postFavoriteDestroyWithStatusID:(NSString *)statusID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST favorites/create + + Favorites the status specified in the ID parameter as the authenticating user. Returns the favorite status when successful. + + 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. + */ + +- (NSObject *)postFavoriteCreateWithStatusID:(NSString *)statusID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)postFavoriteState:(BOOL)favoriteState + forStatusID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Lists + +/* + GET lists/list + + Returns all lists the authenticating or specified user subscribes to, including their own. The user is specified using the user_id or screen_name parameters. If no user is given, the authenticating user is used. + + This method used to be GET lists in version 1.0 of the API and has been renamed for consistency with other call. + + 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. + */ + +- (NSObject *)getListsSubscribedByUsername:(NSString *)username + orUserID:(NSString *)userID + reverse:(NSNumber *)reverse + successBlock:(void(^)(NSArray *lists))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/statuses + + 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. + */ + +- (NSObject *)getListsStatusesForListID:(NSString *)listID + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + includeRetweets:(NSNumber *)includeRetweets + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getListsStatusesForSlug:(NSString *)slug + screenName:(NSString *)ownerScreenName + ownerID:(NSString *)ownerID + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + includeRetweets:(NSNumber *)includeRetweets + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/members/destroy + + Removes the specified member from the list. The authenticated user must be the list's owner to remove members from the list. + */ + +- (NSObject *)postListsMembersDestroyForListID:(NSString *)listID + slug:(NSString *)slug + userID:(NSString *)userID + screenName:(NSString *)screenName + ownerScreenName:(NSString *)ownerScreenName + ownerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListsMembersDestroyForSlug:(NSString *)slug + userID:(NSString *)userID + screenName:(NSString *)screenName + ownerScreenName:(NSString *)ownerScreenName + ownerID:(NSString *)ownerID + successBlock:(void(^)())successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/memberships + + 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. + */ + +- (NSObject *)getListsMembershipsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + filterToOwnedLists:(NSNumber *)filterToOwnedLists // When set to true, t or 1, will return just lists the authenticating user owns, and the user represented by user_id or screen_name is a member of. + successBlock:(void(^)(NSArray *lists, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/subscribers + + Returns the subscribers of the specified list. Private list subscribers will only be shown if the authenticated user owns the specified list. + */ + +- (NSObject *)getListsSubscribersForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + cursor:(NSString *)cursor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getListsSubscribersForListID:(NSString *)listID + cursor:(NSString *)cursor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/subscribers/create + + Subscribes the authenticated user to the specified list. + */ + +- (NSObject *)postListSubscribersCreateForListID:(NSString *)listID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListSubscribersCreateForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/subscribers/show + + Check if the specified user is a subscriber of the specified list. Returns the user if they are subscriber. + */ + +- (NSObject *)getListsSubscribersShowForListID:(NSString *)listID + userID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getListsSubscribersShowForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/subscribers/destroy + + Unsubscribes the authenticated user from the specified list. + */ + +- (NSObject *)postListSubscribersDestroyForListID:(NSString *)listID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListSubscribersDestroyForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/members/create_all + + Adds multiple members to a list, by specifying a comma-separated list of member ids or screen names. The authenticated user must own the list to be able to add members to it. Note that lists can't have more than 5,000 members, and you are limited to adding up to 100 members to a list at a time with this method. + + 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. + */ + +- (NSObject *)postListsMembersCreateAllForListID:(NSString *)listID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListsMembersCreateAllForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/members/show + + Check if the specified user is a member of the specified list. + */ + +- (NSObject *)getListsMembersShowForListID:(NSString *)listID + userID:(NSString *)userID + screenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getListsMembersShowForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userID:(NSString *)userID + screenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/members + + Returns the members of the specified list. Private list members will only be shown if the authenticated user owns the specified list. + */ + +- (NSObject *)getListsMembersForListID:(NSString *)listID + cursor:(NSString *)cursor + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getListsMembersForSlug:(NSString *)slug + ownerScreenName:(NSString *)screenName + orOwnerID:(NSString *)ownerID + cursor:(NSString *)cursor + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/members/create + + Creates a new list for the authenticated user. Note that you can't create more than 20 lists per account. + */ + +- (NSObject *)postListMemberCreateForListID:(NSString *)listID + userID:(NSString *)userID + screenName:(NSString *)screenName + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListMemberCreateForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userID:(NSString *)userID + screenName:(NSString *)screenName + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/destroy + + Deletes the specified list. The authenticated user must own the list to be able to destroy it. + */ + +- (NSObject *)postListsDestroyForListID:(NSString *)listID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListsDestroyForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/update + + Updates the specified list. The authenticated user must own the list to be able to update it. + */ + +- (NSObject *)postListsUpdateForListID:(NSString *)listID + name:(NSString *)name + isPrivate:(BOOL)isPrivate + description:(NSString *)description + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListsUpdateForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + name:(NSString *)name + isPrivate:(BOOL)isPrivate + description:(NSString *)description + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/create + + Creates a new list for the authenticated user. Note that you can't create more than 20 lists per account. + */ + +- (NSObject *)postListsCreateWithName:(NSString *)name + isPrivate:(BOOL)isPrivate + description:(NSString *)description + successBlock:(void(^)(NSDictionary *list))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/show + + Returns the specified list. Private lists will only be shown if the authenticated user owns the specified list. + */ + +- (NSObject *)getListsShowListID:(NSString *)listID + successBlock:(void(^)(NSDictionary *list))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)getListsShowListSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(NSDictionary *list))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/subscriptions + + 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. + */ + +- (NSObject *)getListsSubscriptionsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *lists, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST lists/members/destroy_all + + Removes multiple members from a list, by specifying a comma-separated list of member ids or screen names. The authenticated user must own the list to be able to remove members from it. Note that lists can't have more than 500 members, and you are limited to removing up to 100 members to a list at a time with this method. + + 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. + */ + +- (NSObject *)postListsMembersDestroyAllForListID:(NSString *)listID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postListsMembersDestroyAllForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET lists/ownerships + + 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. + */ + +- (NSObject *)getListsOwnershipsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *lists, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Saved Searches + +/* + GET saved_searches/list + + Returns the authenticated user's saved search queries. + */ + +- (NSObject *)getSavedSearchesListWithSuccessBlock:(void(^)(NSArray *savedSearches))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET saved_searches/show/:id + + 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. + */ + +- (NSObject *)getSavedSearchesShow:(NSString *)savedSearchID + successBlock:(void(^)(NSDictionary *savedSearch))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST saved_searches/create + + Create a new saved search for the authenticated user. A user may only have 25 saved searches. + */ + +- (NSObject *)postSavedSearchesCreateWithQuery:(NSString *)query + successBlock:(void(^)(NSDictionary *createdSearch))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST saved_searches/destroy/:id + + Destroys a saved search for the authenticating user. The authenticating user must be the owner of saved search id being destroyed. + */ + +- (NSObject *)postSavedSearchesDestroy:(NSString *)savedSearchID + successBlock:(void(^)(NSDictionary *destroyedSearch))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Places & Geo + +/* + GET geo/id/:place_id + + Returns all the information about a known place. + */ + +- (NSObject *)getGeoIDForPlaceID:(NSString *)placeID // A place in the world. These IDs can be retrieved from geo/reverse_geocode. + successBlock:(void(^)(NSDictionary *place))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET geo/reverse_geocode + + Given a latitude and a longitude, searches for up to 20 places that can be used as a place_id when updating a status. + + This request is an informative call and will deliver generalized results about geography. + */ + +- (NSObject *)getGeoReverseGeocodeWithLatitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + accuracy:(NSString *)accuracy // eg. "5ft" + granularity:(NSString *)granularity // eg. "city" + maxResults:(NSString *)maxResults // eg. "3" + callback:(NSString *)callback + successBlock:(void(^)(NSDictionary *query, NSDictionary *result))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getGeoReverseGeocodeWithLatitude:(NSString *)latitude + longitude:(NSString *)longitude + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET geo/search + + Search for places that can be attached to a statuses/update. Given a latitude and a longitude pair, an IP address, or a name, this request will return a list of all the valid places that can be used as the place_id when updating a status. + + Conceptually, a query can be made from the user's location, retrieve a list of places, have the user validate the location he or she is at, and then send the ID of this location with a call to POST statuses/update. + + 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. + */ + +- (NSObject *)getGeoSearchWithLatitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + query:(NSString *)query // eg. "Twitter HQ" + ipAddress:(NSString *)ipAddress // eg. 74.125.19.104 + granularity:(NSString *)granularity // eg. "city" + accuracy:(NSString *)accuracy // eg. "5ft" + maxResults:(NSString *)maxResults // eg. "3" + placeIDContaintedWithin:(NSString *)placeIDContaintedWithin // eg. "247f43d441defc03" + attributeStreetAddress:(NSString *)attributeStreetAddress // eg. "795 Folsom St" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *query, NSDictionary *result))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getGeoSearchWithLatitude:(NSString *)latitude + longitude:(NSString *)longitude + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getGeoSearchWithIPAddress:(NSString *)ipAddress + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience +- (NSObject *)getGeoSearchWithQuery:(NSString *)query + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET geo/similar_places + + Locates places near the given coordinates which are similar in name. + + Conceptually you would use this method to get a list of known places to choose from first. Then, if the desired place doesn't exist, make a request to POST geo/place to create a new one. + + The token contained in the response is the token needed to be able to create a new place. + */ + +- (NSObject *)getGeoSimilarPlacesToLatitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + name:(NSString *)name // eg. "Twitter HQ" + placeIDContaintedWithin:(NSString *)placeIDContaintedWithin // eg. "247f43d441defc03" + attributeStreetAddress:(NSString *)attributeStreetAddress // eg. "795 Folsom St" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *query, NSArray *resultPlaces, NSString *resultToken))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + POST geo/place + + Creates a new place object at the given latitude and longitude. + + Before creating a place you need to query GET geo/similar_places with the latitude, longitude and name of the place you wish to create. The query will return an array of places which are similar to the one you wish to create, and a token. If the place you wish to create isn't in the returned array you can use the token with this method to create a new one. + + Learn more about Finding Tweets about Places. + + WARNING: deprecated since December 2nd, 2013 https://dev.twitter.com/discussions/22452 + */ + +- (NSObject *)postGeoPlaceWithName:(NSString *)name // eg. "Twitter HQ" + placeIDContaintedWithin:(NSString *)placeIDContaintedWithin // eg. "247f43d441defc03" + similarPlaceToken:(NSString *)similarPlaceToken // eg. "36179c9bf78835898ebf521c1defd4be" + latitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + attributeStreetAddress:(NSString *)attributeStreetAddress // eg. "795 Folsom St" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *place))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Trends + +/* + GET trends/place + + Returns the top 10 trending topics for a specific WOEID, if trending information is available for it. + + The response is an array of "trend" objects that encode the name of the trending topic, the query parameter that can be used to search for the topic on Twitter Search, and the Twitter Search URL. + + 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. + */ + +- (NSObject *)getTrendsForWOEID:(NSString *)WOEID // 'Yahoo! Where On Earth ID', Paris is "615702" + excludeHashtags:(NSNumber *)excludeHashtags + successBlock:(void(^)(NSDate *asOf, NSDate *createdAt, NSArray *locations, NSArray *trends))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET trends/available + + Returns the locations that Twitter has trending topic information for. + + The response is an array of "locations" that encode the location's WOEID and some other human-readable information such as a canonical name and country the location belongs in. + + A WOEID is a Yahoo! Where On Earth ID. + */ + +- (NSObject *)getTrendsAvailableWithSuccessBlock:(void(^)(NSArray *locations))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET trends/closest + + Returns the locations that Twitter has trending topic information for, closest to a specified location. + + The response is an array of "locations" that encode the location's WOEID and some other human-readable information such as a canonical name and country the location belongs in. + + A WOEID is a Yahoo! Where On Earth ID. + */ + +- (NSObject *)getTrendsClosestToLatitude:(NSString *)latitude + longitude:(NSString *)longitude + successBlock:(void(^)(NSArray *locations))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Spam Reporting + +/* + POST users/report_spam + + Report the specified user as a spam account to Twitter. Additionally performs the equivalent of POST blocks/create on behalf of the authenticated user. + */ + +- (NSObject *)postUsersReportSpamForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(id userProfile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark OAuth + +#pragma mark Help + +/* + GET help/configuration + + Returns the current configuration used by Twitter including twitter.com slugs which are not usernames, maximum photo resolutions, and t.co URL lengths. + + It is recommended applications request this endpoint when they are loaded, but no more than once a day. + */ + +- (NSObject *)getHelpConfigurationWithSuccessBlock:(void(^)(NSDictionary *currentConfiguration))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET help/languages + + 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. + */ + +- (NSObject *)getHelpLanguagesWithSuccessBlock:(void(^)(NSArray *languages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET help/privacy + + Returns Twitter's Privacy Policy. + */ + +- (NSObject *)getHelpPrivacyWithSuccessBlock:(void(^)(NSString *privacy))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET help/tos + + Returns the Twitter Terms of Service in the requested format. These are not the same as the Developer Rules of the Road. + */ + +- (NSObject *)getHelpTermsOfServiceWithSuccessBlock:(void(^)(NSString *tos))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + GET application/rate_limit_status + + Returns the current rate limits for methods belonging to the specified resource families. + + Each 1.1 API resource belongs to a "resource family" which is indicated in its method documentation. You can typically determine a method's resource family from the first component of the path after the resource version. + + This method responds with a map of methods belonging to the families specified by the resources parameter, the current remaining uses for each of those resources within the current rate limiting window, and its expiration time in epoch time. It also includes a rate_limit_context field that indicates the current access token or application-only authentication context. + + You may also issue requests to this method without any parameters to receive a map of all rate limited GET methods. If your application only uses a few of methods, please explicitly provide a resources parameter with the specified resource families you work with. + + When using app-only auth, this method's response indicates the app-only auth rate limiting context. + + Read more about REST API Rate Limiting in v1.1 and review the limits. + */ + +- (NSObject *)getRateLimitsForResources:(NSArray *)resources // eg. statuses,friends,trends,help + successBlock:(void(^)(NSDictionary *rateLimits))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Tweets + +/* + GET statuses/lookup + + 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. + */ + +- (NSObject *)getStatusesLookupTweetIDs:(NSArray *)tweetIDs + includeEntities:(NSNumber *)includeEntities + trimUser:(NSNumber *)trimUser + map:(NSNumber *)map + successBlock:(void(^)(NSArray *tweets))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark Media + +/* + POST media/upload.json + + https://dev.twitter.com/docs/api/multiple-media-extended-entities + */ + +- (NSObject *)postMediaUpload:(NSURL *)mediaURL + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *imageDictionary, NSString *mediaID, NSString *size))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postMediaUploadData:(NSData *)data + fileName:(NSString *)fileName + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *imageDictionary, NSString *mediaID, NSString *size))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +/* + The maximum file size is 15MB and is checked during the upload process + The maximum length is 30 seconds and is checked when Tweeting with a video media_id + One video (or animated GIF) media_id can be added to a Tweet. Photos are the only media type that can be added up to four times. + */ + +- (NSObject *)postMediaUploadINITWithVideoURL:(NSURL *)videoMediaURL + successBlock:(void(^)(NSString *mediaID, NSString *expiresAfterSecs))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)postMediaUploadAPPENDWithVideoURL:(NSURL *)videoMediaURL + mediaID:(NSString *)mediaID + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)postMediaUploadFINALIZEWithMediaID:(NSString *)mediaID + successBlock:(void(^)(NSString *mediaID, NSString *size, NSString *expiresAfter, NSString *videoType))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// convenience + +// NSURL *videoURL = [NSURL fileURLWithPath:@"/Users/nst/Desktop/x.mov"]; +// +// [_twitter postMediaUploadThreeStepsWithVideoURL:videoURL +// uploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) { +// NSLog(@"-- %ld", (long)bytesWritten); +// } successBlock:^(NSString *mediaID, NSString *size, NSString *expiresAfter, NSString *videoType) { +// NSLog(@"-- %@", mediaID); +// +// [_twitter postStatusUpdate:@"coucou" +// inReplyToStatusID:nil +// mediaIDs:@[mediaID] +// latitude:nil +// longitude:nil +// placeID:nil +// displayCoordinates:nil +// trimUser:nil +// successBlock:^(NSDictionary *status) { +// NSLog(@"-- %@", status); +// } errorBlock:^(NSError *error) { +// NSLog(@"-- %@", error); +// }]; +// +// } errorBlock:^(NSError *error) { +// NSLog(@"-- %@", error); +// }]; + +- (void)postMediaUploadThreeStepsWithVideoURL:(NSURL *)videoURL // local URL + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSString *mediaID, NSString *size, NSString *expiresAfter, NSString *videoType))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark - +#pragma mark UNDOCUMENTED APIs + +// GET activity/about_me.json +- (NSObject *)_getActivityAboutMeSinceID:(NSString *)sinceID + count:(NSString *)count + includeCards:(NSNumber *)includeCards + modelVersion:(NSNumber *)modelVersion + sendErrorCodes:(NSNumber *)sendErrorCodes + contributorDetails:(NSNumber *)contributorDetails + includeEntities:(NSNumber *)includeEntities + includeMyRetweet:(NSNumber *)includeMyRetweet + successBlock:(void(^)(NSArray *activities))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET activity/by_friends.json +- (NSObject *)_getActivityByFriendsSinceID:(NSString *)sinceID + count:(NSString *)count + contributorDetails:(NSNumber *)contributorDetails + includeCards:(NSNumber *)includeCards + includeEntities:(NSNumber *)includeEntities + includeMyRetweets:(NSNumber *)includeMyRetweets + includeUserEntites:(NSNumber *)includeUserEntites + latestResults:(NSNumber *)latestResults + sendErrorCodes:(NSNumber *)sendErrorCodes + successBlock:(void(^)(NSArray *activities))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET statuses/:id/activity/summary.json +- (NSObject *)_getStatusesActivitySummaryForStatusID:(NSString *)statusID + successBlock:(void(^)(NSArray *favoriters, NSArray *repliers, NSArray *retweeters, NSString *favoritersCount, NSString *repliersCount, NSString *retweetersCount))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET conversation/show.json +- (NSObject *)_getConversationShowForStatusID:(NSString *)statusID + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET discover/highlight.json +- (NSObject *)_getDiscoverHighlightWithSuccessBlock:(void(^)(NSDictionary *metadata, NSArray *modules))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET discover/universal.json +- (NSObject *)_getDiscoverUniversalWithSuccessBlock:(void(^)(NSDictionary *metadata, NSArray *modules))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET statuses/media_timeline.json +- (NSObject *)_getMediaTimelineWithSuccessBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET users/recommendations.json +- (NSObject *)_getUsersRecommendationsWithSuccessBlock:(void(^)(NSArray *recommendations))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET timeline/home.json +- (NSObject *)_getTimelineHomeWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET statuses/mentions_timeline.json +- (NSObject *)_getStatusesMentionsTimelineWithCount:(NSString *)count + contributorsDetails:(NSNumber *)contributorsDetails + includeEntities:(NSNumber *)includeEntities + includeMyRetweet:(NSNumber *)includeMyRetweet + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET trends/available.json +- (NSObject *)_getTrendsAvailableWithSuccessBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST users/report_spam +- (NSObject *)_postUsersReportSpamForTweetID:(NSString *)tweetID + reportAs:(NSString *)reportAs // spam, abused, compromised + blockUser:(NSNumber *)blockUser + successBlock:(void(^)(id userProfile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST account/generate.json +- (NSObject *)_postAccountGenerateWithADC:(NSString *)adc + discoverableByEmail:(BOOL)discoverableByEmail + email:(NSString *)email + geoEnabled:(BOOL)geoEnabled + language:(NSString *)language + name:(NSString *)name + password:(NSString *)password + screenName:(NSString *)screenName + sendErrorCode:(BOOL)sendErrorCode + timeZone:(NSString *)timeZone + successBlock:(void(^)(id userProfile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET search/typeahead.json +- (NSObject *)_getSearchTypeaheadQuery:(NSString *)query + resultType:(NSString *)resultType // "all" + sendErrorCodes:(NSNumber *)sendErrorCodes + successBlock:(void(^)(id results))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST direct_messages/new.json +// only the media_id is part of the private API +- (NSObject *)_postDirectMessage:(NSString *)status + forScreenName:(NSString *)screenName + orUserID:(NSString *)userID + mediaID:(NSString *)mediaID // returned by POST media/upload.json + successBlock:(void(^)(NSDictionary *message))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET conversation/show/:id.json +- (NSObject *)_getConversationShowWithTweetID:(NSString *)tweetID + successBlock:(void(^)(id results))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark UNDOCUMENTED APIS SCHEDULED TWEETS - VALID ONLY FOR TWEETDECK + +// GET schedule/status/list.json +- (NSObject *)_getScheduleStatusesWithCount:(NSString *)count + includeEntities:(NSNumber *)includeEntities + includeUserEntities:(NSNumber *)includeUserEntities + includeCards:(NSNumber *)includeCards + successBlock:(void(^)(NSArray *scheduledTweets))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST schedule/status/tweet.json +- (NSObject *)_postScheduleStatus:(NSString *)status + executeAt:(NSString *)executeAtUnixTimestamp + mediaIDs:(NSArray *)mediaIDs + successBlock:(void(^)(NSDictionary *scheduledTweet))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// DELETE schedule/status/:id.json +// delete a scheduled tweet +- (NSObject *)_deleteScheduleStatusWithID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *deletedTweet))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// PUT schedule/status/:id.json +// edit a scheduled tweet +- (NSObject *)_putScheduleStatusWithID:(NSString *)statusID + status:(NSString *)status + executeAt:(NSString *)executeAtUnixTimestamp + mediaIDs:(NSArray *)mediaIDs + successBlock:(void(^)(NSDictionary *scheduledTweet))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark UNDOCUMENTED APIS FOR DIGITS AUTH + +// POST guest/activate.json +- (NSObject *)_postGuestActivateWithSuccessBlock:(void(^)(NSString *guestToken))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST device/register.json +- (NSObject *)_postDeviceRegisterPhoneNumber:(NSString *)phoneNumber // eg. @"+41764948273" + guestToken:(NSString *)guestToken + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST sdk/account.json +- (NSObject *)_postSDKAccountNumericPIN:(NSString *)numericPIN + forPhoneNumber:(NSString *)phoneNumber + guestToken:(NSString *)guestToken + successBlock:(void(^)(id response, NSString *accessToken, NSString *accessTokenSecret))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark UNDOCUMENTED APIS FOR CONTACTS + +// POST contacts/upload.json +/* + { + "vcards": [ + "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Apple Inc.//iOS 8.1.2//EN\r\nN:Obama;Barack Obama;;;\r\nFN:Barack Obama\r\nTEL;type=CELL;type=VOICE;type=pref:00 33 6 11 22 33 44\r\nEND:VCARD\r\n", + "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Apple Inc.//iOS 8.1.2//EN\r\nN:Bush ;Georges Bush;;;\r\nFN:Georges Bush\r\nTEL;type=CELL;type=VOICE;type=pref:00 33 6 22 33 44 55\r\nEND:VCARD\r\n" + ] + } + */ +- (NSObject *)_postContactsUpload:(NSArray *)vcards + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// GET contacts/users_and_uploaded_by.json?count=50 +- (NSObject *)_getContactsUsersAndUploadedByWithCount:(NSString *)count + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// POST contacts/destroy/all.json +- (NSObject *)_getContactsDestroyAllWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +#pragma mark UNDOCUMENTED APIS FOR TWITTER ANALYTICS + +// GET https://analytics.twitter.com/user/:screenname/tweet/:tweetid/mobile/poll.json +- (NSObject *)_getAnalyticsWithScreenName:(NSString *)screenName + tweetID:(NSString *)tweetID + successBlock:(void(^)(id rawResponse, NSDictionary *responseDictionary))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.m new file mode 100644 index 0000000..0a20edc --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAPI.m @@ -0,0 +1,5117 @@ +// +// STTwitterAPI.m +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/18/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import "STTwitterAPI.h" +#import "STTwitterOS.h" +#import "STTwitterOAuth.h" +#import "NSString+STTwitter.h" +#import "STTwitterAppOnly.h" +#import +#import "STHTTPRequest.h" +#import "STHTTPRequest+STTwitter.h" + +NSString *kBaseURLStringAPI_1_1 = @"https://api.twitter.com/1.1"; +NSString *kBaseURLStringUpload_1_1 = @"https://upload.twitter.com/1.1"; +NSString *kBaseURLStringStream_1_1 = @"https://stream.twitter.com/1.1"; +NSString *kBaseURLStringUserStream_1_1 = @"https://userstream.twitter.com/1.1"; +NSString *kBaseURLStringSiteStream_1_1 = @"https://sitestream.twitter.com/1.1"; + +static NSDateFormatter *dateFormatter = nil; + +@interface STTwitterAPI () +@property (nonatomic, retain) NSObject *oauth; +@property (nonatomic, retain) STTwitterStreamParser *streamParser; +@end + +@implementation STTwitterAPI + +- (instancetype)init { + self = [super init]; + + STTwitterAPI * __weak weakSelf = self; + + [[NSNotificationCenter defaultCenter] addObserverForName:ACAccountStoreDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + // account must be considered invalid + + if(weakSelf == nil) return; + + typeof(self) strongSelf = weakSelf; + + if([strongSelf.oauth isKindOfClass:[STTwitterOS class]]) { + strongSelf.oauth = nil; + } + }]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:ACAccountStoreDidChangeNotification object:nil]; +} + ++ (NSString *)versionString { + return @"0.2.2"; +} + ++ (instancetype)twitterAPIOSWithAccount:(ACAccount *)account { + STTwitterAPI *twitter = [[STTwitterAPI alloc] init]; + twitter.oauth = [STTwitterOS twitterAPIOSWithAccount:account]; + return twitter; +} + ++ (instancetype)twitterAPIOSWithFirstAccount { + STTwitterAPI *twitter = [[STTwitterAPI alloc] init]; + twitter.oauth = [STTwitterOS twitterAPIOSWithAccount:nil]; + return twitter; +} + ++ (instancetype)twitterAPIWithOAuthConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password { + + STTwitterAPI *twitter = [[STTwitterAPI alloc] init]; + + twitter.oauth = [STTwitterOAuth twitterOAuthWithConsumerName:consumerName + consumerKey:consumerKey + consumerSecret:consumerSecret + username:username + password:password]; + + return twitter; +} + ++ (instancetype)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password { + + return [self twitterAPIWithOAuthConsumerName:nil + consumerKey:consumerKey + consumerSecret:consumerSecret + username:username + password:password]; +} + ++ (instancetype)twitterAPIWithOAuthConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken + oauthTokenSecret:(NSString *)oauthTokenSecret { + + STTwitterAPI *twitter = [[STTwitterAPI alloc] init]; + + twitter.oauth = [STTwitterOAuth twitterOAuthWithConsumerName:consumerName + consumerKey:consumerKey + consumerSecret:consumerSecret + oauthToken:oauthToken + oauthTokenSecret:oauthTokenSecret]; + + return twitter; +} + ++ (instancetype)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken + oauthTokenSecret:(NSString *)oauthTokenSecret { + + return [self twitterAPIWithOAuthConsumerName:nil + consumerKey:consumerKey + consumerSecret:consumerSecret + oauthToken:oauthToken + oauthTokenSecret:oauthTokenSecret]; +} + ++ (instancetype)twitterAPIWithOAuthConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret { + + return [self twitterAPIWithOAuthConsumerName:consumerName + consumerKey:consumerKey + consumerSecret:consumerSecret + username:nil + password:nil]; +} + ++ (instancetype)twitterAPIWithOAuthConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret { + + return [self twitterAPIWithOAuthConsumerName:nil + consumerKey:consumerKey + consumerSecret:consumerSecret]; +} + ++ (instancetype)twitterAPIAppOnlyWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret { + + STTwitterAPI *twitter = [[STTwitterAPI alloc] init]; + + STTwitterAppOnly *appOnly = [STTwitterAppOnly twitterAppOnlyWithConsumerName:consumerName consumerKey:consumerKey consumerSecret:consumerSecret]; + + twitter.oauth = appOnly; + + return twitter; +} + ++ (instancetype)twitterAPIAppOnlyWithConsumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret { + return [self twitterAPIAppOnlyWithConsumerName:nil consumerKey:consumerKey consumerSecret:consumerSecret]; +} + +- (void)setTimeoutInSeconds:(NSTimeInterval)timeoutInSeconds { + _oauth.timeoutInSeconds = timeoutInSeconds; +} + +- (NSString *)prettyDescription { + NSMutableString *ms = [[_oauth loginTypeDescription] mutableCopy]; + + if([_oauth consumerName]) { + [ms appendFormat:@" (%@)", [_oauth consumerName]]; + } + + if([self userName]) { + [ms appendFormat:@" - %@", [self userName]]; + } + + return ms; +} + +- (NSDateFormatter *)dateFormatter { + if(dateFormatter == nil) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:SS'Z'"]; + } + return dateFormatter; +} + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock +authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize + forceLogin:(NSNumber *)forceLogin screenName:(NSString *)screenName + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock { + + [_oauth postTokenRequest:successBlock +authenticateInsteadOfAuthorize:authenticateInsteadOfAuthorize + forceLogin:forceLogin + screenName:screenName + oauthCallback:oauthCallback + errorBlock:errorBlock]; +} + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock { + [_oauth postTokenRequest:successBlock authenticateInsteadOfAuthorize:NO forceLogin:nil screenName:nil oauthCallback:oauthCallback errorBlock:errorBlock]; +} + +- (void)postAccessTokenRequestWithPIN:(NSString *)pin + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + [_oauth postAccessTokenRequestWithPIN:pin + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (void)verifyCredentialsWithUserSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + + __weak typeof(self) weakSelf = self; + + [_oauth verifyCredentialsLocallyWithSuccessBlock:^(NSString *username, NSString *userID) { + + __strong typeof(self) strongSelf = weakSelf; + if(strongSelf == nil) { + errorBlock(nil); + return; + } + + if(username) [strongSelf setUserName:username]; + if(userID) [strongSelf setUserID:userID]; + + [_oauth verifyCredentialsRemotelyWithSuccessBlock:^(NSString *username, NSString *userID) { + + if(strongSelf == nil) { + errorBlock(nil); + return; + } + + [strongSelf setUserName:username]; + [strongSelf setUserID:userID]; + + successBlock(username, userID); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; + + } errorBlock:^(NSError *error) { + errorBlock(error); // early, local detection of account issues, eg. incomplete OS account + }]; +} + +// deprecated, use verifyCredentialsWithUserSuccessBlock:errorBlock: +- (void)verifyCredentialsWithSuccessBlock:(void(^)(NSString *username))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + [self verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { + successBlock(username); + } errorBlock:errorBlock]; +} + +- (void)invalidateBearerTokenWithSuccessBlock:(void(^)())successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if([self.oauth respondsToSelector:@selector(invalidateBearerTokenWithSuccessBlock:errorBlock:)]) { + [self.oauth invalidateBearerTokenWithSuccessBlock:successBlock errorBlock:errorBlock]; + } else { + STLog(@"-- self.oauth does not support tokens invalidation"); + } +} + +- (NSString *)oauthAccessTokenSecret { + if([_oauth respondsToSelector:@selector(oauthAccessTokenSecret)]) { + return [_oauth oauthAccessTokenSecret]; + } + return nil; +} + +- (NSString *)oauthAccessToken { + if([_oauth respondsToSelector:@selector(oauthAccessToken)]) { + return [_oauth oauthAccessToken]; + } + return nil; +} + +- (NSString *)bearerToken { + if([_oauth respondsToSelector:@selector(bearerToken)]) { + return [_oauth bearerToken]; + } + + return nil; +} + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials { + if([_oauth respondsToSelector:@selector(OAuthEchoHeadersToVerifyCredentials)]) { + return [_oauth OAuthEchoHeadersToVerifyCredentials]; + } + + return nil; +} + +- (NSString *)userName { + + if([_oauth isKindOfClass:[STTwitterOS class]]) { + STTwitterOS *twitterOS = (STTwitterOS *)_oauth; + return twitterOS.username; + } + + return _userName; +} + +- (NSString *)userID { + + if([_oauth isKindOfClass:[STTwitterOS class]]) { + STTwitterOS *twitterOS = (STTwitterOS *)_oauth; + return twitterOS.userID; + } + + return _userID; +} + +/**/ + +#pragma mark Generic methods to GET and POST + +- (NSObject *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *request, NSData *data))downloadProgressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + return [_oauth fetchResource:resource + HTTPMethod:HTTPMethod + baseURLString:baseURLString + parameters:params + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:downloadProgressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSObject *)fetchAndFollowCursorsForResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *request, NSData *data))downloadProgressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response, BOOL morePagesToCome, BOOL *stop))successBlock + pauseBlock:(void(^)(NSDate *nextRequestDate))pauseBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + __block BOOL shouldStop = NO; + + return [_oauth fetchResource:resource + HTTPMethod:HTTPMethod + baseURLString:baseURLString + parameters:params + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:downloadProgressBlock + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + + // https://dev.twitter.com/overview/api/cursoring + + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + + BOOL morePagesToCome = NO; + + NSMutableDictionary *paramsWithCursor = [params mutableCopy]; + if([nextCursor integerValue] > 0) { + paramsWithCursor[@"cursor"] = nextCursor; + morePagesToCome = YES; + } + + successBlock(request, requestHeaders, responseHeaders, response, morePagesToCome, &shouldStop); + + if(shouldStop || morePagesToCome == NO) return; + + // now consider rate limits + + NSString *remainingString = [responseHeaders objectForKey:@"x-rate-limit-remaining"]; + NSString *resetString = [responseHeaders objectForKey:@"x-rate-limit-reset"]; + + NSInteger remainingInteger = [remainingString integerValue]; + NSInteger resetInteger = [resetString integerValue]; + NSTimeInterval timeInterval = 0; + + if(remainingInteger == 0) { + NSDate *resetDate = [[NSDate alloc] initWithTimeIntervalSince1970:resetInteger]; + timeInterval = [resetDate timeIntervalSinceDate:[NSDate date]] + 5; + pauseBlock([NSDate dateWithTimeIntervalSinceNow:timeInterval]); + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + + [self fetchAndFollowCursorsForResource:resource + HTTPMethod:HTTPMethod + baseURLString:baseURLString + parameters:paramsWithCursor + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:downloadProgressBlock + successBlock:successBlock + pauseBlock:pauseBlock + errorBlock:errorBlock]; + + }); + + } errorBlock:errorBlock]; +} + +- (NSObject *)getResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)parameters +//uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSData *data))downloadProgressBlock + successBlock:(void(^)(NSDictionary *rateLimits, id json))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [_oauth fetchResource:resource + HTTPMethod:@"GET" + baseURLString:baseURLString + parameters:parameters + uploadProgressBlock:nil + downloadProgressBlock:^(id request, NSData *data) { + if(downloadProgressBlock) downloadProgressBlock(data); + } successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + if(successBlock) successBlock(responseHeaders, response); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + if(errorBlock) errorBlock(error); + }]; +} + +- (NSObject *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)parameters + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSData *data))downloadProgressBlock + successBlock:(void(^)(NSDictionary *rateLimits, id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [_oauth fetchResource:resource + HTTPMethod:@"POST" + baseURLString:baseURLString + parameters:parameters + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:^(id request, NSData *data) { + if(downloadProgressBlock) downloadProgressBlock(data); + } successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + if(successBlock) successBlock(responseHeaders, response); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + if(errorBlock) errorBlock(error); + }]; +} + +- (NSObject *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)parameters + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSData *data))downloadProgressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [_oauth fetchResource:resource + HTTPMethod:@"POST" + baseURLString:baseURLString + parameters:parameters + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:^(id request, NSData *data) { + if(downloadProgressBlock) downloadProgressBlock(data); + } successBlock:nil + errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)parameters + downloadProgressBlock:(void(^)(NSData *data))downloadProgressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [_oauth fetchResource:resource + HTTPMethod:@"GET" + baseURLString:baseURLString + parameters:parameters + uploadProgressBlock:nil + downloadProgressBlock:^(id request, NSData *data) { + if(downloadProgressBlock) downloadProgressBlock(data); + } successBlock:nil + errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getAPIResource:(NSString *)resource + parameters:(NSDictionary *)parameters + progressBlock:(void(^)(NSData *data))progressBlock + successBlock:(void(^)(NSDictionary *rateLimits, id json))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getResource:resource + baseURLString:kBaseURLStringAPI_1_1 + parameters:parameters + downloadProgressBlock:progressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +// convenience +- (NSObject *)getAPIResource:(NSString *)resource + parameters:(NSDictionary *)parameters + successBlock:(void(^)(NSDictionary *rateLimits, id json))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getResource:resource + baseURLString:kBaseURLStringAPI_1_1 + parameters:parameters + downloadProgressBlock:nil + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSObject *)postAPIResource:(NSString *)resource + parameters:(NSDictionary *)parameters + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + progressBlock:(void(^)(NSData *data))progressBlock + successBlock:(void(^)(NSDictionary *rateLimits, id json))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postResource:resource + baseURLString:kBaseURLStringAPI_1_1 + parameters:parameters + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:progressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +// convenience +- (NSObject *)postAPIResource:(NSString *)resource + parameters:(NSDictionary *)parameters + successBlock:(void(^)(NSDictionary *rateLimits, id json))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postResource:resource + baseURLString:kBaseURLStringAPI_1_1 + parameters:parameters + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:successBlock + errorBlock:errorBlock]; +} + +/**/ + +// reverse auth step 1 + +- (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + [_oauth postReverseOAuthTokenRequest:^(NSString *authenticationHeader) { + successBlock(authenticationHeader); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// reverse auth step 2 + +- (void)postReverseAuthAccessTokenWithAuthenticationHeader:(NSString *)authenticationHeader + successBlock:(void(^)(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + [_oauth postReverseAuthAccessTokenWithAuthenticationHeader:authenticationHeader successBlock:^(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName) { + successBlock(oAuthToken, oAuthTokenSecret, userID, screenName); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +/**/ + +- (NSObject *)profileImageFor:(NSString *)screenName + + successBlock:(void(^)(id image))successBlock + + errorBlock:(void(^)(NSError *error))errorBlock { + + __weak typeof(self) weakSelf = self; + + return [self getUserInformationFor:screenName + successBlock:^(NSDictionary *response) { + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + NSString *imageURLString = [response objectForKey:@"profile_image_url"]; + + STHTTPRequest *r = [STHTTPRequest requestWithURLString:imageURLString]; + __weak STHTTPRequest *wr = r; + + r.timeoutSeconds = strongSelf.oauth.timeoutInSeconds; + + r.completionBlock = ^(NSDictionary *headers, NSString *body) { + + STHTTPRequest *sr = wr; // strong request + + NSData *imageData = sr.responseData; + +#if TARGET_OS_IPHONE + Class STImageClass = NSClassFromString(@"UIImage"); +#else + Class STImageClass = NSClassFromString(@"NSImage"); +#endif + successBlock([[STImageClass alloc] initWithData:imageData]); + }; + + r.errorBlock = ^(NSError *error) { + errorBlock(error); + }; + + [r startAsynchronous]; + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Timelines + +- (NSObject *)getStatusesMentionTimelineWithCount:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + contributorDetails:(NSNumber *)contributorDetails + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"include_rts"] = @"1"; // "It is recommended you always send include_rts=1 when using this API method" https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline + if(count) md[@"count"] = count; + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + if(contributorDetails) md[@"contributor_details"] = [contributorDetails boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"statuses/mentions_timeline.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getMentionsTimelineSinceID:(NSString *)sinceID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getStatusesMentionTimelineWithCount:[@(count) description] + sinceID:sinceID + maxID:nil + trimUser:nil + contributorDetails:nil + includeEntities:nil + successBlock:^(NSArray *statuses) { + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +/**/ + +- (NSObject *)getStatusesUserTimelineForUserID:(NSString *)userID + screenName:(NSString *)screenName + sinceID:(NSString *)sinceID + count:(NSString *)count + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + excludeReplies:(NSNumber *)excludeReplies + contributorDetails:(NSNumber *)contributorDetails + includeRetweets:(NSNumber *)includeRetweets + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(sinceID) md[@"since_id"] = sinceID; + if(count) md[@"count"] = count; + if(maxID) md[@"max_id"] = maxID; + + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + if(excludeReplies) md[@"exclude_replies"] = [excludeReplies boolValue] ? @"1" : @"0"; + if(contributorDetails) md[@"contributor_details"] = [contributorDetails boolValue] ? @"1" : @"0"; + if(includeRetweets) md[@"include_rts"] = [includeRetweets boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"statuses/user_timeline.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getStatusesHomeTimelineWithCount:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + excludeReplies:(NSNumber *)excludeReplies + contributorDetails:(NSNumber *)contributorDetails + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(count) md[@"count"] = count; + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + if(excludeReplies) md[@"exclude_replies"] = [excludeReplies boolValue] ? @"1" : @"0"; + if(contributorDetails) md[@"contributor_details"] = [contributorDetails boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"statuses/home_timeline.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getUserTimelineWithScreenName:(NSString *)screenName + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getStatusesUserTimelineForUserID:nil + screenName:screenName + sinceID:sinceID + count:(count == NSNotFound ? nil : [@(count) description]) + maxID:maxID + trimUser:nil + excludeReplies:nil + contributorDetails:nil + includeRetweets:nil + successBlock:^(NSArray *statuses) { + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getUserTimelineWithScreenName:(NSString *)screenName + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getUserTimelineWithScreenName:screenName + sinceID:nil + maxID:nil + count:count + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSObject *)getUserTimelineWithScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getUserTimelineWithScreenName:screenName + count:20 + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSObject *)getHomeTimelineSinceID:(NSString *)sinceID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *countString = count > 0 ? [@(count) description] : nil; + + return [self getStatusesHomeTimelineWithCount:countString + sinceID:sinceID + maxID:nil + trimUser:nil + excludeReplies:nil + contributorDetails:nil + includeEntities:nil + successBlock:^(NSArray *statuses) { + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getStatusesRetweetsOfMeWithCount:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + trimUser:(NSNumber *)trimUser + includeEntities:(NSNumber *)includeEntities + includeUserEntities:(NSNumber *)includeUserEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(count) md[@"count"] = count; + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(includeUserEntities) md[@"include_user_entities"] = [includeUserEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"statuses/retweets_of_me.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience method, shorter +- (NSObject *)getStatusesRetweetsOfMeWithSuccessBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getStatusesRetweetsOfMeWithCount:nil + sinceID:nil + maxID:nil + trimUser:nil + includeEntities:nil + includeUserEntities:nil + successBlock:^(NSArray *statuses) { + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Tweets + +- (NSObject *)getStatusesRetweetsForID:(NSString *)statusID + count:(NSString *)count + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSString *resource = [NSString stringWithFormat:@"statuses/retweets/%@.json", statusID]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(count) md[@"count"] = count; + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + + return [self getAPIResource:resource parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getStatusesShowID:(NSString *)statusID + trimUser:(NSNumber *)trimUser + includeMyRetweet:(NSNumber *)includeMyRetweet + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + md[@"id"] = statusID; + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + if(includeMyRetweet) md[@"include_my_retweet"] = [includeMyRetweet boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"statuses/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postStatusesDestroy:(NSString *)statusID + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSString *resource = [NSString stringWithFormat:@"statuses/destroy/%@.json", statusID]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"id"] = statusID; + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + + return [self postAPIResource:resource parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postStatusUpdate:(NSString *)status + inReplyToStatusID:(NSString *)existingStatusID + mediaIDs:(NSArray *)mediaIDs + latitude:(NSString *)latitude + longitude:(NSString *)longitude + placeID:(NSString *)placeID // wins over lat/lon + displayCoordinates:(NSNumber *)displayCoordinates + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if([mediaIDs count] == 0 && status == nil) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterAPICannotPostEmptyStatus userInfo:@{NSLocalizedDescriptionKey : @"cannot post empty status"}]; + errorBlock(error); + return nil; + } + + NSMutableDictionary *md = [NSMutableDictionary dictionaryWithObject:status forKey:@"status"]; + + if([mediaIDs count] > 0) { + NSString *mediaIDsString = [mediaIDs componentsJoinedByString:@","]; + md[@"media_ids"] = mediaIDsString; + } + + if(existingStatusID) { + md[@"in_reply_to_status_id"] = existingStatusID; + } + + if(placeID) { + md[@"place_id"] = placeID; + md[@"display_coordinates"] = @"true"; + } else if(latitude && longitude) { + md[@"lat"] = latitude; + md[@"lon"] = longitude; + md[@"display_coordinates"] = @"true"; + } + + return [self postAPIResource:@"statuses/update.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postStatusUpdate:(NSString *)status + inReplyToStatusID:(NSString *)existingStatusID + latitude:(NSString *)latitude + longitude:(NSString *)longitude + placeID:(NSString *)placeID // wins over lat/lon + displayCoordinates:(NSNumber *)displayCoordinates + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postStatusUpdate:status + inReplyToStatusID:existingStatusID + mediaIDs:nil + latitude:latitude + longitude:longitude + placeID:placeID + displayCoordinates:displayCoordinates + trimUser:trimUser + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSObject *)postStatusUpdate:(NSString *)status + mediaDataArray:(NSArray *)mediaDataArray // only one media is currently supported, help/configuration.json returns "max_media_per_upload" = 1 + possiblySensitive:(NSNumber *)possiblySensitive + inReplyToStatusID:(NSString *)inReplyToStatusID + latitude:(NSString *)latitude + longitude:(NSString *)longitude + placeID:(NSString *)placeID + displayCoordinates:(NSNumber *)displayCoordinates + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(status); + NSAssert([mediaDataArray count] > 0, @"media data array must not be empty"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"status"] = status; + if(possiblySensitive) md[@"possibly_sensitive"] = [possiblySensitive boolValue] ? @"1" : @"0"; + if(displayCoordinates) md[@"display_coordinates"] = [displayCoordinates boolValue] ? @"1" : @"0"; + if(inReplyToStatusID) md[@"in_reply_to_status_id"] = inReplyToStatusID; + if(latitude) md[@"lat"] = latitude; + if(longitude) md[@"long"] = longitude; + if(placeID) md[@"place_id"] = placeID; + md[@"media[]"] = [mediaDataArray objectAtIndex:0]; + md[kSTPOSTDataKey] = @"media[]"; + + return [self postResource:@"statuses/update_with_media.json" + baseURLString:kBaseURLStringAPI_1_1 + parameters:md + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:nil + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:errorBlock]; +} + +- (NSObject *)postStatusUpdate:(NSString *)status + inReplyToStatusID:(NSString *)existingStatusID + mediaURL:(NSURL *)mediaURL + placeID:(NSString *)placeID + latitude:(NSString *)latitude + longitude:(NSString *)longitude + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSData *data = [NSData dataWithContentsOfURL:mediaURL]; + + if(data == nil) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterAPIMediaDataIsEmpty userInfo:@{NSLocalizedDescriptionKey : @"data is nil"}]; + errorBlock(error); + return nil; + } + + return [self postStatusUpdate:status + mediaDataArray:@[data] + possiblySensitive:nil + inReplyToStatusID:existingStatusID + latitude:latitude + longitude:longitude + placeID:placeID + displayCoordinates:@(YES) + uploadProgressBlock:uploadProgressBlock + successBlock:^(NSDictionary *status) { + successBlock(status); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET statuses/oembed + +- (NSObject *)getStatusesOEmbedForStatusID:(NSString *)statusID + urlString:(NSString *)urlString + maxWidth:(NSString *)maxWidth + hideMedia:(NSNumber *)hideMedia + hideThread:(NSNumber *)hideThread + omitScript:(NSNumber *)omitScript + align:(NSString *)align // 'left', 'right', 'center' or 'none' (default) + related:(NSString *)related // eg. twitterapi,twittermedia,twitter + lang:(NSString *)lang + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + NSParameterAssert(urlString); + +#if DEBUG + if(align) { + NSArray *validValues = @[@"left", @"right", @"center", @"none"]; + NSAssert([validValues containsObject: align], @""); + } +#endif + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"id"] = statusID; + md[@"url"] = urlString; + + if(maxWidth) md[@"maxwidth"] = maxWidth; + + if(hideMedia) md[@"hide_media"] = [hideMedia boolValue] ? @"1" : @"0"; + if(hideThread) md[@"hide_thread"] = [hideThread boolValue] ? @"1" : @"0"; + if(omitScript) md[@"omit_script"] = [omitScript boolValue] ? @"1" : @"0"; + + if(align) md[@"align"] = align; + if(related) md[@"related"] = related; + if(lang) md[@"lang"] = lang; + + return [self getAPIResource:@"statuses/oembed.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST statuses/retweet/:id +- (NSObject *)postStatusRetweetWithID:(NSString *)statusID + trimUser:(NSNumber *)trimUser + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + + NSString *resource = [NSString stringWithFormat:@"statuses/retweet/%@.json", statusID]; + + return [self postAPIResource:resource parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postStatusRetweetWithID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postStatusRetweetWithID:statusID + trimUser:nil + successBlock:^(NSDictionary *status) { + successBlock(status); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getStatusesRetweetersIDsForStatusID:(NSString *)statusID + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *ids, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + md[@"id"] = statusID; + if(cursor) md[@"cursor"] = cursor; + md[@"stringify_ids"] = @"1"; + + return [self getAPIResource:@"statuses/retweeters/ids.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSString *previousCursor = nil; + NSString *nextCursor = nil; + NSArray *ids = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + previousCursor = response[@"previous_cursor_str"]; + nextCursor = response[@"next_cursor_str"]; + ids = response[@"ids"]; + } + + successBlock(ids, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; + +} + +- (NSObject *)getListsSubscriptionsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *lists, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"missing userID or screenName"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(userID) { + md[@"user_id"] = userID; + } else if (screenName) { + md[@"screen_name"] = screenName; + } + + if(count) md[@"count"] = count; + if(cursor) md[@"cursor"] = cursor; + + return [self getAPIResource:@"lists/subscriptions.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *lists = [response valueForKey:@"lists"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + successBlock(lists, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/ownerships + +- (NSObject *)getListsOwnershipsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *lists, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"missing userID or screenName"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(userID) { + md[@"user_id"] = userID; + } else if (screenName) { + md[@"screen_name"] = screenName; + } + + if(count) md[@"count"] = count; + if(cursor) md[@"cursor"] = cursor; + + return [self getAPIResource:@"lists/ownerships.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *lists = [response valueForKey:@"lists"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + successBlock(lists, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Search + +- (NSObject *)getSearchTweetsWithQuery:(NSString *)q + geocode:(NSString *)geoCode // eg. "37.781157,-122.398720,1mi" + lang:(NSString *)lang // eg. "eu" + locale:(NSString *)locale // eg. "ja" + resultType:(NSString *)resultType // eg. "mixed, recent, popular" + count:(NSString *)count // eg. "100" + until:(NSString *)until // eg. "2012-09-01" + sinceID:(NSString *)sinceID // eg. "12345" + maxID:(NSString *)maxID // eg. "54321" + includeEntities:(NSNumber *)includeEntities + callback:(NSString *)callback // eg. "processTweets" + successBlock:(void(^)(NSDictionary *searchMetadata, NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + NSParameterAssert(q); + + if(geoCode) md[@"geocode"] = geoCode; + if(lang) md[@"lang"] = lang; + if(locale) md[@"locale"] = locale; + if(resultType) md[@"result_type"] = resultType; + if(count) md[@"count"] = count; + if(until) md[@"until"] = until; + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(callback) md[@"callback"] = callback; + + // eg. "(from:nst021 OR to:nst021)" -> "%28from%3Anst021%20OR%20to%3Anst021%29" + // md[@"q"] = @"(from:nst021 OR to:nst021)"; + md[@"q"] = q; + + return [self getAPIResource:@"search/tweets.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *searchMetadata = [response valueForKey:@"search_metadata"]; + NSArray *statuses = [response valueForKey:@"statuses"]; + + successBlock(searchMetadata, statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getSearchTweetsWithQuery:(NSString *)q + successBlock:(void(^)(NSDictionary *searchMetadata, NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getSearchTweetsWithQuery:q + geocode:nil + lang:nil + locale:nil + resultType:nil + count:nil + until:nil + sinceID:nil + maxID:nil + includeEntities:@(YES) + callback:nil + successBlock:^(NSDictionary *searchMetadata, NSArray *statuses) { + successBlock(searchMetadata, statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Streaming + ++ (NSDictionary *)stallWarningDictionaryFromJSON:(NSString *)json { + if([json isKindOfClass:[NSDictionary class]]) return nil; + return [json valueForKey:@"warning"]; +} + +// POST statuses/filter + +- (NSObject *)postStatusesFilterUserIDs:(NSArray *)userIDs + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + stallWarnings:(NSNumber *)stallWarnings + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *follow = [userIDs componentsJoinedByString:@","]; + NSString *keywords = [keywordsToTrack componentsJoinedByString:@","]; + NSString *locations = [locationBoundingBoxes componentsJoinedByString:@","]; + + NSAssert(([follow length] || [keywords length] || [locations length]), @"At least one predicate parameter (follow, locations, or track) must be specified."); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"delimited"] = @"length"; + + if(stallWarnings) md[@"stall_warnings"] = [stallWarnings boolValue] ? @"1" : @"0"; + + if([follow length]) md[@"follow"] = follow; + if([keywords length]) md[@"track"] = keywords; + if([locations length]) md[@"locations"] = locations; + + self.streamParser = [[STTwitterStreamParser alloc] init]; + __weak STTwitterStreamParser *streamParser = self.streamParser; + + return [self postResource:@"statuses/filter.json" + baseURLString:kBaseURLStringStream_1_1 + parameters:md + uploadProgressBlock:nil + downloadProgressBlock:^(NSData *data) { + + if (streamParser) { + [streamParser parseWithStreamData:data parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + progressBlock(json, type); + }]; + } + + } successBlock:^(NSDictionary *rateLimits, id response) { + if([response isKindOfClass:[NSString class]] && [response length] == 0) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterAPIEmptyStream userInfo:@{NSLocalizedDescriptionKey : @"stream is empty"}]; + errorBlock(error); + return; + }; + + // reaching successBlock for a stream request is an error + errorBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience +- (NSObject *)postStatusesFilterKeyword:(NSString *)keyword + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + stallWarningBlock:(void(^)(NSString *code, NSString *message, NSUInteger percentFull))stallWarningBlock + errorBlock:(void(^)(NSError *error))errorBlock +{ + NSParameterAssert(keyword); + + return [self postStatusesFilterUserIDs:nil + keywordsToTrack:@[keyword] + locationBoundingBoxes:nil + stallWarnings:stallWarningBlock ? @YES : @NO + progressBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + + switch (type) { + case STTwitterStreamJSONTypeTweet: + tweetBlock(json); + break; + case STTwitterStreamJSONTypeWarning: + if (stallWarningBlock) { + stallWarningBlock([json valueForKey:@"code"], + [json valueForKey:@"message"], + [[json valueForKey:@"percent_full"] integerValue]); + } + break; + default: + break; + } + + } errorBlock:errorBlock]; +} + +- (NSObject *)postStatusesFilterKeyword:(NSString *)keyword + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + errorBlock:(void(^)(NSError *error))errorBlock +{ + return [self postStatusesFilterKeyword:keyword + tweetBlock:tweetBlock + stallWarningBlock:nil + errorBlock:errorBlock]; +} + +// GET statuses/sample +- (NSObject *)getStatusesSampleStallWarnings:(NSNumber *)stallWarnings + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"delimited"] = @"length"; + + if(stallWarnings) md[@"stall_warnings"] = [stallWarnings boolValue] ? @"1" : @"0"; + + self.streamParser = [[STTwitterStreamParser alloc] init]; + __weak STTwitterStreamParser *streamParser = self.streamParser; + + return [self getResource:@"statuses/sample.json" + baseURLString:kBaseURLStringStream_1_1 + parameters:md + downloadProgressBlock:^(id response) { + + if (streamParser) { + [streamParser parseWithStreamData:response parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + progressBlock(json, type); + }]; + } + + } successBlock:^(NSDictionary *rateLimits, id json) { + // reaching successBlock for a stream request is an error + errorBlock(json); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience +- (NSObject *)getStatusesSampleTweetBlock:(void (^)(NSDictionary *))tweetBlock + stallWarningBlock:(void (^)(NSString *, NSString *, NSUInteger))stallWarningBlock + errorBlock:(void (^)(NSError *))errorBlock +{ + return [self getStatusesSampleStallWarnings:stallWarningBlock ? @YES : @NO + progressBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + + switch (type) { + case STTwitterStreamJSONTypeTweet: + tweetBlock(json); + break; + case STTwitterStreamJSONTypeWarning: + if (stallWarningBlock) { + stallWarningBlock([json valueForKey:@"code"], + [json valueForKey:@"message"], + [[json valueForKey:@"percent_full"] integerValue]); + } + break; + default: + break; + } + + } errorBlock:errorBlock]; +} + +- (NSObject *)getStatusesSampleTweetBlock:(void (^)(NSDictionary *))tweetBlock + errorBlock:(void (^)(NSError *))errorBlock +{ + return [self getStatusesSampleTweetBlock:tweetBlock + stallWarningBlock:nil + errorBlock:errorBlock]; +} + +// GET statuses/firehose +- (NSObject *)getStatusesFirehoseWithCount:(NSString *)count + stallWarnings:(NSNumber *)stallWarnings + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"delimited"] = @"length"; + + if(count) md[@"count"] = count; + if(stallWarnings) md[@"stall_warnings"] = [stallWarnings boolValue] ? @"1" : @"0"; + + self.streamParser = [[STTwitterStreamParser alloc] init]; + __weak STTwitterStreamParser *streamParser = self.streamParser; + + return [self getResource:@"statuses/firehose.json" + baseURLString:kBaseURLStringStream_1_1 + parameters:md + downloadProgressBlock:^(id response) { + + if (streamParser) { + [streamParser parseWithStreamData:response parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + progressBlock(json, type); + }]; + } + + } successBlock:^(NSDictionary *rateLimits, id json) { + // reaching successBlock for a stream request is an error + errorBlock(json); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET user +- (NSObject *)getUserStreamStallWarnings:(NSNumber *)stallWarnings + includeMessagesFromFollowedAccounts:(NSNumber *)includeMessagesFromFollowedAccounts // default: @(NO) + includeReplies:(NSNumber *)includeReplies + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + progressBlock:(void(^)(NSDictionary *json, STTwitterStreamJSONType type))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"stringify_friend_ids"] = @"1"; + md[@"delimited"] = @"length"; + + if(stallWarnings) md[@"stall_warnings"] = [stallWarnings boolValue] ? @"1" : @"0"; + if(includeMessagesFromFollowedAccounts) md[@"with"] = @"followings"; + if(includeReplies && [includeReplies boolValue]) md[@"replies"] = @"all"; + + NSString *keywords = [keywordsToTrack componentsJoinedByString:@","]; + NSString *locations = [locationBoundingBoxes componentsJoinedByString:@","]; + + if([keywords length]) md[@"track"] = keywords; + if([locations length]) md[@"locations"] = locations; + + self.streamParser = [[STTwitterStreamParser alloc] init]; + __weak STTwitterStreamParser *streamParser = self.streamParser; + + return [self getResource:@"user.json" + baseURLString:kBaseURLStringUserStream_1_1 + parameters:md + downloadProgressBlock:^(id response) { + + if (streamParser) { + [streamParser parseWithStreamData:response parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + progressBlock(json, type); + }]; + } + + } successBlock:^(NSDictionary *rateLimits, id json) { + // reaching successBlock for a stream request is an error + errorBlock(json); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience +- (NSObject *)getUserStreamIncludeMessagesFromFollowedAccounts:(NSNumber *)includeMessagesFromFollowedAccounts + includeReplies:(NSNumber *)includeReplies + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + stallWarningBlock:(void(^)(NSString *code, NSString *message, NSUInteger percentFull))stallWarningBlock + errorBlock:(void(^)(NSError *error))errorBlock; +{ + return [self getUserStreamStallWarnings:stallWarningBlock ? @YES : @NO + includeMessagesFromFollowedAccounts:includeMessagesFromFollowedAccounts + includeReplies:includeReplies + keywordsToTrack:keywordsToTrack + locationBoundingBoxes:locationBoundingBoxes + progressBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + + switch (type) { + case STTwitterStreamJSONTypeTweet: + tweetBlock(json); + break; + case STTwitterStreamJSONTypeWarning: + if (stallWarningBlock) { + stallWarningBlock([json valueForKey:@"code"], + [json valueForKey:@"message"], + [[json valueForKey:@"percent_full"] integerValue]); + } + break; + default: + break; + } + + } errorBlock:errorBlock]; +} + +- (NSObject *)getUserStreamIncludeMessagesFromFollowedAccounts:(NSNumber *)includeMessagesFromFollowedAccounts + includeReplies:(NSNumber *)includeReplies + keywordsToTrack:(NSArray *)keywordsToTrack + locationBoundingBoxes:(NSArray *)locationBoundingBoxes + tweetBlock:(void(^)(NSDictionary *tweet))tweetBlock + errorBlock:(void(^)(NSError *error))errorBlock +{ + return [self getUserStreamIncludeMessagesFromFollowedAccounts:includeMessagesFromFollowedAccounts + includeReplies:includeReplies + keywordsToTrack:keywordsToTrack + locationBoundingBoxes:locationBoundingBoxes + tweetBlock:tweetBlock + stallWarningBlock:nil + errorBlock:errorBlock]; +} + +// GET site +- (NSObject *)getSiteStreamForUserIDs:(NSArray *)userIDs + delimited:(NSNumber *)delimited + stallWarnings:(NSNumber *)stallWarnings + restrictToUserMessages:(NSNumber *)restrictToUserMessages + includeReplies:(NSNumber *)includeReplies + progressBlock:(void (^)(NSDictionary *, STTwitterStreamJSONType))progressBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"stringify_friend_ids"] = @"1"; + if(delimited) md[@"delimited"] = [delimited boolValue] ? @"1" : @"0"; + if(stallWarnings) md[@"stall_warnings"] = [stallWarnings boolValue] ? @"1" : @"0"; + if(restrictToUserMessages) md[@"with"] = @"user"; // default is 'followings' + if(includeReplies && [includeReplies boolValue]) md[@"replies"] = @"all"; + + NSString *follow = [userIDs componentsJoinedByString:@","]; + if([follow length]) md[@"follow"] = follow; + + self.streamParser = [[STTwitterStreamParser alloc] init]; + __weak STTwitterStreamParser *streamParser = self.streamParser; + + return [self getResource:@"site.json" + baseURLString:kBaseURLStringSiteStream_1_1 + parameters:md + downloadProgressBlock:^(NSData *data) { + + if (streamParser) { + [streamParser parseWithStreamData:data parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { + progressBlock(json, type); + }]; + } + + } successBlock:^(NSDictionary *rateLimits, id json) { + // reaching successBlock for a stream request is an error + errorBlock(json); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Direct Messages + +- (NSObject *)getDirectMessagesSinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + fullText:(NSNumber *)fullText + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(sinceID) [md setObject:sinceID forKey:@"since_id"]; + if(maxID) [md setObject:maxID forKey:@"max_id"]; + if(count) [md setObject:count forKey:@"count"]; + if(fullText) md[@"full_text"] = [fullText boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"direct_messages.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience +- (NSObject *)getDirectMessagesSinceID:(NSString *)sinceID + count:(NSUInteger)count + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *countString = count > 0 ? [@(count) description] : nil; + + return [self getDirectMessagesSinceID:sinceID + maxID:nil + count:countString + fullText:@(1) + includeEntities:nil + skipStatus:nil + successBlock:^(NSArray *statuses) { + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getDirectMessagesSinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + fullText:(NSNumber *)fullText + page:(NSString *)page + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(sinceID) [md setObject:sinceID forKey:@"since_id"]; + if(maxID) [md setObject:maxID forKey:@"max_id"]; + if(count) [md setObject:count forKey:@"count"]; + if(fullText) md[@"full_text"] = [fullText boolValue] ? @"1" : @"0"; + if(page) [md setObject:page forKey:@"page"]; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"direct_messages/sent.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getDirectMessagesShowWithID:(NSString *)messageID + fullText:(NSNumber *)fullText + successBlock:(void(^)(NSArray *messages))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"id"] = messageID; + if(fullText) md[@"full_text"] = [fullText boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"direct_messages/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; + +} + +- (NSObject *)postDestroyDirectMessageWithID:(NSString *)messageID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *message))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"id"] = messageID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"direct_messages/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postDirectMessage:(NSString *)status + forScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *message))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSMutableDictionary *md = [NSMutableDictionary dictionaryWithObject:status forKey:@"text"]; + + NSAssert(screenName != nil || userID != nil, @"screenName OR userID is required"); + + if(screenName) { + md[@"screen_name"] = screenName; + } else { + md[@"user_id"] = userID; + } + + return [self postAPIResource:@"direct_messages/new.json" + parameters:md + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)_postDirectMessage:(NSString *)status + forScreenName:(NSString *)screenName + orUserID:(NSString *)userID + mediaID:(NSString *)mediaID + successBlock:(void(^)(NSDictionary *message))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionaryWithObject:status forKey:@"text"]; + + NSAssert(screenName != nil || userID != nil, @"screenName OR userID is required"); + + if(screenName) { + md[@"screen_name"] = screenName; + } else { + md[@"user_id"] = userID; + } + + if(mediaID) md[@"media_id"] = mediaID; + + return [self postAPIResource:@"direct_messages/new.json" + parameters:md + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Friends & Followers + +- (NSObject *)getFriendshipNoRetweetsIDsWithSuccessBlock:(void(^)(NSArray *ids))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"stringify_ids"] = @"1"; + + return [self getAPIResource:@"friendships/no_retweets/ids.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendsIDsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + count:(NSString *)count + successBlock:(void(^)(NSArray *ids, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(cursor) md[@"cursor"] = cursor; + md[@"stringify_ids"] = @"1"; + + if(count) md[@"count"] = count; + + return [self getAPIResource:@"friends/ids.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *ids = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + ids = [response valueForKey:@"ids"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(ids, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendsIDsForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *friends))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getFriendsIDsForUserID:nil + orScreenName:screenName + cursor:nil + count:nil + successBlock:^(NSArray *ids, NSString *previousCursor, NSString *nextCursor) { + successBlock(ids); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFollowersIDsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + count:(NSString *)count + successBlock:(void(^)(NSArray *followersIDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(cursor) md[@"cursor"] = cursor; + md[@"stringify_ids"] = @"1"; + if(count) md[@"count"] = count; + + return [self getAPIResource:@"followers/ids.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *followersIDs = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + followersIDs = [response valueForKey:@"ids"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(followersIDs, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFollowersIDsForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *followers))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getFollowersIDsForUserID:nil + orScreenName:screenName + cursor:nil + count:nil + successBlock:^(NSArray *ids, NSString *previousCursor, NSString *nextCursor) { + successBlock(ids); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendshipsLookupForScreenNames:(NSArray *)screenNames + orUserIDs:(NSArray *)userIDs + successBlock:(void(^)(NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenNames || userIDs), @"missing screen names or user IDs"); + + NSString *commaSeparatedScreenNames = [screenNames componentsJoinedByString:@","]; + NSString *commaSeparatedUserIDs = [userIDs componentsJoinedByString:@","]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(commaSeparatedScreenNames) md[@"screen_name"] = commaSeparatedScreenNames; + if(commaSeparatedUserIDs) md[@"user_id"] = commaSeparatedUserIDs; + + return [self getAPIResource:@"friendships/lookup.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendshipIncomingWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *IDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(cursor) md[@"cursor"] = cursor; + md[@"stringify_ids"] = @"1"; + + return [self getAPIResource:@"friendships/incoming.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *ids = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + ids = [response valueForKey:@"ids"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(ids, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendshipOutgoingWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *IDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(cursor) md[@"cursor"] = cursor; + md[@"stringify_ids"] = @"1"; + + return [self getAPIResource:@"friendships/outgoing.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *ids = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + ids = [response valueForKey:@"ids"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(ids, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFriendshipsCreateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *befriendedUser))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSAssert((screenName || userID), @"screenName or userID is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + + return [self postAPIResource:@"friendships/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFollow:(NSString *)screenName + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postFriendshipsCreateForScreenName:screenName orUserID:nil successBlock:^(NSDictionary *user) { + successBlock(user); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFriendshipsDestroyScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *unfollowedUser))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"screenName or userID is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + + return [self postAPIResource:@"friendships/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postUnfollow:(NSString *)screenName + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postFriendshipsDestroyScreenName:screenName orUserID:nil successBlock:^(NSDictionary *user) { + successBlock(user); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFriendshipsUpdateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + enableDeviceNotifications:(NSNumber *)enableDeviceNotifications + enableRetweets:(NSNumber *)enableRetweets + successBlock:(void (^)(NSDictionary *))successBlock errorBlock:(void (^)(NSError *))errorBlock { + NSAssert((screenName || userID), @"screenName or userID is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + if(enableDeviceNotifications) md[@"device"] = [enableDeviceNotifications boolValue] ? @"1" : @"0"; + if(enableRetweets) md[@"retweets"] = [enableRetweets boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"friendships/update.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFriendshipsUpdateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + enableDeviceNotifications:(BOOL)enableDeviceNotifications + successBlock:(void (^)(NSDictionary *))successBlock errorBlock:(void (^)(NSError *))errorBlock { + NSAssert((screenName || userID), @"screenName or userID is missing"); + + return [self postFriendshipsUpdateForScreenName:screenName + orUserID:userID + enableDeviceNotifications:@(enableDeviceNotifications) + enableRetweets:nil + successBlock:^(NSDictionary *user) { + successBlock(user); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFriendshipsUpdateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + enableRetweets:(BOOL)enableRetweets + successBlock:(void (^)(NSDictionary *))successBlock errorBlock:(void (^)(NSError *))errorBlock { + NSAssert((screenName || userID), @"screenName or userID is missing"); + + return [self postFriendshipsUpdateForScreenName:screenName + orUserID:userID + enableDeviceNotifications:nil + enableRetweets:@(enableRetweets) + successBlock:^(NSDictionary *user) { + successBlock(user); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendshipShowForSourceID:(NSString *)sourceID + orSourceScreenName:(NSString *)sourceScreenName + targetID:(NSString *)targetID + orTargetScreenName:(NSString *)targetScreenName + successBlock:(void(^)(id relationship))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSAssert((sourceID || sourceScreenName), @"sourceID or sourceScreenName is missing"); + NSAssert((targetID || targetScreenName), @"targetID or targetScreenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(sourceID) md[@"source_id"] = sourceID; + if(sourceScreenName) md[@"source_screen_name"] = sourceScreenName; + if(targetID) md[@"target_id"] = targetID; + if(targetScreenName) md[@"target_screen_name"] = targetScreenName; + + return [self getAPIResource:@"friendships/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendsListForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + count:(NSString *)count + skipStatus:(NSNumber *)skipStatus + includeUserEntities:(NSNumber *)includeUserEntities + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(cursor) md[@"cursor"] = cursor; + if(count) md[@"count"] = count; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + if(includeUserEntities) md[@"include_user_entities"] = [includeUserEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"friends/list.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *users = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + users = [response valueForKey:@"users"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFriendsForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *friends))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getFriendsListForUserID:nil + orScreenName:screenName + cursor:nil + count:nil + skipStatus:@(NO) + includeUserEntities:@(YES) + successBlock:^(NSArray *users, NSString *previousCursor, NSString *nextCursor) { + successBlock(users); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFollowersListForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + cursor:(NSString *)cursor + skipStatus:(NSNumber *)skipStatus + includeUserEntities:(NSNumber *)includeUserEntities + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(count) md[@"count"] = count; + if(cursor) md[@"cursor"] = cursor; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + if(includeUserEntities) md[@"include_user_entities"] = [includeUserEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"followers/list.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *users = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + users = [response valueForKey:@"users"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience +- (NSObject *)getFollowersForScreenName:(NSString *)screenName + successBlock:(void(^)(NSArray *followers))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getFollowersListForUserID:nil + orScreenName:screenName + count:nil + cursor:nil + skipStatus:nil + includeUserEntities:nil + successBlock:^(NSArray *users, NSString *previousCursor, NSString *nextCursor) { + successBlock(users); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Users + +// GET account/settings +- (NSObject *)getAccountSettingsWithSuccessBlock:(void(^)(NSDictionary *settings))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getAPIResource:@"account/settings.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET account/verify_credentials +- (NSObject *)getAccountVerifyCredentialsWithIncludeEntites:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + includeEmail:(NSNumber *)includeEmail + successBlock:(void(^)(NSDictionary *myInfo))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + if(includeEmail) md[@"include_email"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"account/verify_credentials.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getAccountVerifyCredentialsWithSuccessBlock:(void(^)(NSDictionary *account))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getAccountVerifyCredentialsWithIncludeEntites:nil skipStatus:nil includeEmail:nil successBlock:^(NSDictionary *account) { + successBlock(account); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/settings +- (NSObject *)postAccountSettingsWithTrendLocationWOEID:(NSString *)trendLocationWOEID // eg. "1" + sleepTimeEnabled:(NSNumber *)sleepTimeEnabled // eg. @(YES) + startSleepTime:(NSString *)startSleepTime // eg. "13" + endSleepTime:(NSString *)endSleepTime // eg. "13" + timezone:(NSString *)timezone // eg. "Europe/Copenhagen", "Pacific/Tongatapu" + language:(NSString *)language // eg. "it", "en", "es" + successBlock:(void(^)(NSDictionary *settings))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSAssert((trendLocationWOEID || sleepTimeEnabled || startSleepTime || endSleepTime || timezone || language), @"at least one parameter is needed"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(trendLocationWOEID) md[@"trend_location_woeid"] = trendLocationWOEID; + if(sleepTimeEnabled) md[@"sleep_time_enabled"] = [sleepTimeEnabled boolValue] ? @"1" : @"0"; + if(startSleepTime) md[@"start_sleep_time"] = startSleepTime; + if(endSleepTime) md[@"end_sleep_time"] = endSleepTime; + if(timezone) md[@"time_zone"] = timezone; + if(language) md[@"lang"] = language; + + return [self postAPIResource:@"account/settings.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/update_delivery_device +- (NSObject *)postAccountUpdateDeliveryDeviceSMS:(BOOL)deliveryDeviceSMS + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"device"] = deliveryDeviceSMS ? @"sms" : @"none"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"account/update_delivery_device.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/update_profile +- (NSObject *)postAccountUpdateProfileWithName:(NSString *)name + URLString:(NSString *)URLString + location:(NSString *)location + description:(NSString *)description + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((name || URLString || location || description || includeEntities || skipStatus), @"at least one parameter is needed"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(name) md[@"name"] = name; + if(URLString) md[@"url"] = URLString; + if(location) md[@"location"] = location; + if(description) md[@"description"] = description; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0";; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"account/update_profile.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postUpdateProfile:(NSDictionary *)profileData + successBlock:(void(^)(NSDictionary *myInfo))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self postAPIResource:@"account/update_profile.json" parameters:profileData successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/update_profile_background_image +- (NSObject *)postAccountUpdateProfileBackgroundImageWithImage:(NSString *)base64EncodedImage + title:(NSString *)title + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + use:(NSNumber *)use + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSAssert((base64EncodedImage || title || includeEntities || skipStatus || use), @"at least one parameter is needed"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(base64EncodedImage) md[@"image"] = base64EncodedImage; + if(title) md[@"title"] = title; + + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0";; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + if(use) md[@"use"] = [use boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"account/update_profile_background_image.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/update_profile_colors +- (NSObject *)postAccountUpdateProfileColorsWithBackgroundColor:(NSString *)backgroundColor + linkColor:(NSString *)linkColor + sidebarBorderColor:(NSString *)sidebarBorderColor + sidebarFillColor:(NSString *)sidebarFillColor + profileTextColor:(NSString *)profileTextColor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(backgroundColor) md[@"profile_background_color"] = backgroundColor; + if(linkColor) md[@"profile_link_color"] = linkColor; + if(sidebarBorderColor) md[@"profile_sidebar_border_color"] = sidebarBorderColor; + if(sidebarFillColor) md[@"profile_sidebar_fill_color"] = sidebarFillColor; + if(profileTextColor) md[@"profile_text_color"] = profileTextColor; + + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"account/update_profile_colors.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/update_profile_image +- (NSObject *)postAccountUpdateProfileImage:(NSString *)base64EncodedImage + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *profile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(base64EncodedImage); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"image"] = base64EncodedImage; + + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"account/update_profile_image.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET blocks/list +- (NSObject *)getBlocksListWithincludeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + cursor:(NSString *)cursor + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + if(cursor) md[@"cursor"] = cursor; + + return [self getAPIResource:@"blocks/list.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *users = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + users = [response valueForKey:@"users"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET blocks/ids +- (NSObject *)getBlocksIDsWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *ids, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"stringify_ids"] = @"1"; + if(cursor) md[@"cursor"] = cursor; + + return [self getAPIResource:@"blocks/ids.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *ids = nil; + NSString *previousCursor = nil; + NSString *nextCursor = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + ids = [response valueForKey:@"ids"]; + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + } + + successBlock(ids, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST blocks/create +- (NSObject *)postBlocksCreateWithScreenName:(NSString *)screenName + orUserID:(NSString *)userID + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"blocks/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST blocks/destroy +- (NSObject *)postBlocksDestroyWithScreenName:(NSString *)screenName + orUserID:(NSString *)userID + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"blocks/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/lookup +- (NSObject *)getUsersLookupForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"users/lookup.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/show +- (NSObject *)getUsersShowForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"users/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getUserInformationFor:(NSString *)screenName + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getUsersShowForUserID:nil orScreenName:screenName includeEntities:nil successBlock:^(NSDictionary *user) { + successBlock(user); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/search +- (NSObject *)getUsersSearchQuery:(NSString *)query + page:(NSString *)page + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(query); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + md[@"q"] = query; + if(page) md[@"page"] = page; + if(count) md[@"count"] = count; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"users/search.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); // NSArray of users dictionaries + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/contributees +- (NSObject *)getUsersContributeesWithUserID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *contributees))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"users/contributees.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/contributors +- (NSObject *)getUsersContributorsWithUserID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *contributors))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"users/contributors.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/remove_profile_banner +- (NSObject *)postAccountRemoveProfileBannerWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self postAPIResource:@"account/remove_profile_banner.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/update_profile_banner +- (NSObject *)postAccountUpdateProfileBannerWithImage:(NSString *)base64encodedImage + width:(NSString *)width + height:(NSString *)height + offsetLeft:(NSString *)offsetLeft + offsetTop:(NSString *)offsetTop + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if(width || height || offsetLeft || offsetTop) { + NSParameterAssert(width); + NSParameterAssert(height); + NSParameterAssert(offsetLeft); + NSParameterAssert(offsetTop); + } + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"banner"] = base64encodedImage; + if(width) md[@"width"] = width; + if(height) md[@"height"] = height; + if(offsetLeft) md[@"offset_left"] = offsetLeft; + if(offsetTop) md[@"offset_top"] = offsetTop; + + return [self postAPIResource:@"account/update_profile_banner.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/profile_banner +- (NSObject *)getUsersProfileBannerForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + successBlock:(void(^)(NSDictionary *banner))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((screenName || userID), @"missing screenName or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + + return [self getAPIResource:@"users/profile_banner.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/suggestions +- (NSObject *)getUsersSuggestionsWithISO6391LanguageCode:(NSString *)ISO6391LanguageCode + successBlock:(void(^)(NSArray *suggestions))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(ISO6391LanguageCode) md[@"lang"] = ISO6391LanguageCode; + + return [self getAPIResource:@"users/suggestions.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/suggestions/:slug/members +- (NSObject *)getUsersSuggestionsForSlugMembers:(NSString *)slug // short name of list or a category, eg. "twitter" + successBlock:(void(^)(NSArray *members))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert(slug, @"missing slug"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + md[@"slug"] = slug; + + NSString *resource = [NSString stringWithFormat:@"users/suggestions/%@/members.json", slug]; + + return [self getAPIResource:resource parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST mutes/users/create +- (NSObject *)postMutesUsersCreateForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + + return [self postAPIResource:@"mutes/users/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST mutes/users/destroy +- (NSObject *)postMutesUsersDestroyForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + + return [self postAPIResource:@"mutes/users/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET mutes/users/ids +- (NSObject *)getMutesUsersIDsWithCursor:(NSString *)cursor + successBlock:(void(^)(NSArray *userIDs, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(cursor) md[@"cursor"] = cursor; + + return [self getAPIResource:@"mutes/users/ids.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *userIDs = [response valueForKey:@"ids"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + + successBlock(userIDs, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET mutes/users/list +- (NSObject *)getMutesUsersListWithCursor:(NSString *)cursor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(cursor) md[@"cursor"] = cursor; + if(includeEntities) md[@"include_entities"] = includeEntities; + if(skipStatus) md[@"skip_status"] = skipStatus; + + return [self getAPIResource:@"mutes/users/list.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *users = [response valueForKey:@"users"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Suggested Users + +// GET users/suggestions/:slug +- (NSObject *)getUsersSuggestionsForSlug:(NSString *)slug // short name of list or a category, eg. "twitter" + lang:(NSString *)lang + successBlock:(void(^)(NSString *name, NSString *slug, NSArray *users))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert(slug, @"slug is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(lang) md[@"lang"] = lang; + + return [self getAPIResource:@"users/suggestions/twitter.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSString *name = nil; + NSString *slug = nil; + NSArray *users = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + name = [response valueForKey:@"name"]; + slug = [response valueForKey:@"slug"]; + users = [response valueForKey:@"users"]; + } + + successBlock(name, slug, users); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Favorites + +// GET favorites/list +- (NSObject *)getFavoritesListWithUserID:(NSString *)userID + orScreenName:(NSString *)screenName + count:(NSString *)count + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(count) md[@"count"] = count; + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"favorites/list.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getFavoritesListWithSuccessBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getFavoritesListWithUserID:nil + orScreenName:nil + count:nil + sinceID:nil + maxID:nil + includeEntities:nil + successBlock:^(NSArray *statuses) { + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST favorites/destroy +- (NSObject *)postFavoriteDestroyWithStatusID:(NSString *)statusID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(statusID) md[@"id"] = statusID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"favorites/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST favorites/create +- (NSObject *)postFavoriteCreateWithStatusID:(NSString *)statusID + includeEntities:(NSNumber *)includeEntities + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(statusID) md[@"id"] = statusID; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + + return [self postAPIResource:@"favorites/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postFavoriteState:(BOOL)favoriteState + forStatusID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *status))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *action = favoriteState ? @"create" : @"destroy"; + + NSString *resource = [NSString stringWithFormat:@"favorites/%@.json", action]; + + NSDictionary *d = @{@"id" : statusID}; + + return [self postAPIResource:resource parameters:d successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Lists + +// GET lists/list + +- (NSObject *)getListsSubscribedByUsername:(NSString *)username + orUserID:(NSString *)userID + reverse:(NSNumber *)reverse + successBlock:(void(^)(NSArray *lists))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((username || userID), @"missing username or userID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(username) { + md[@"screen_name"] = username; + } else if (userID) { + md[@"user_id"] = userID; + } + + if(reverse) md[@"reverse"] = [reverse boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/list.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSAssert([response isKindOfClass:[NSArray class]], @"bad response type"); + + NSArray *lists = (NSArray *)response; + + successBlock(lists); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/statuses + +- (NSObject *)getListsStatusesForListID:(NSString *)listID + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + includeRetweets:(NSNumber *)includeRetweets + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + if(count) md[@"count"] = count; + + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(includeRetweets) md[@"include_rts"] = includeRetweets ? @"1" : @"0"; + + return [self getAPIResource:@"lists/statuses.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSAssert([response isKindOfClass:[NSArray class]], @"bad response type"); + + NSArray *statuses = (NSArray *)response; + + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getListsStatusesForSlug:(NSString *)slug + screenName:(NSString *)ownerScreenName + ownerID:(NSString *)ownerID + sinceID:(NSString *)sinceID + maxID:(NSString *)maxID + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + includeRetweets:(NSNumber *)includeRetweets + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + if(sinceID) md[@"since_id"] = sinceID; + if(maxID) md[@"max_id"] = maxID; + if(count) md[@"count"] = count; + + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(includeRetweets) md[@"include_rts"] = [includeRetweets boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/statuses.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSAssert([response isKindOfClass:[NSArray class]], @"bad response type"); + + NSArray *statuses = (NSArray *)response; + + successBlock(statuses); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/members/destroy + +- (NSObject *)postListsMembersDestroyForListID:(NSString *)listID + slug:(NSString *)slug + userID:(NSString *)userID + screenName:(NSString *)screenName + ownerScreenName:(NSString *)ownerScreenName + ownerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((listID || slug), @"missing listID or slug"); + + if(slug) NSAssert((ownerScreenName || ownerID), @"slug requires either ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + if(listID) md[@"list_id"] = listID; + if(slug) md[@"slug"] = slug; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + + return [self postAPIResource:@"lists/members/destroy" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListsMembersDestroyForSlug:(NSString *)slug + userID:(NSString *)userID + screenName:(NSString *)screenName + ownerScreenName:(NSString *)ownerScreenName + ownerID:(NSString *)ownerID + successBlock:(void(^)())successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + md[@"slug"] = slug; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerScreenName) md[@"owner_id"] = ownerID; + + return [self postAPIResource:@"lists/members/destroy" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/memberships + +- (NSObject *)getListsMembershipsForUserID:(NSString *)userID + orScreenName:(NSString *)screenName + cursor:(NSString *)cursor + filterToOwnedLists:(NSNumber *)filterToOwnedLists + successBlock:(void(^)(NSArray *lists, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert((userID || screenName), @"userID or screenName is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(cursor) md[@"cursor"] = cursor; + if(filterToOwnedLists) md[@"filter_to_owned_lists"] = [filterToOwnedLists boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/memberships.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSString *previousCursor = nil; + NSString *nextCursor = nil; + NSArray *lists = nil; + + if([response isKindOfClass:[NSDictionary class]]) { + previousCursor = [response valueForKey:@"previous_cursor_str"]; + nextCursor = [response valueForKey:@"next_cursor_str"]; + lists = [response valueForKey:@"lists"]; + } + + successBlock(lists, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/subscribers + +- (NSObject *)getListsSubscribersForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + cursor:(NSString *)cursor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or onwerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) { + md[@"owner_screen_name"] = ownerScreenName; + } else if (ownerID) { + md[@"owner_id"] = ownerID; + } + if(cursor) md[@"cursor"] = cursor; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/subscribers.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *users = [response valueForKey:@"users"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getListsSubscribersForListID:(NSString *)listID + cursor:(NSString *)cursor + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + if(cursor) md[@"cursor"] = cursor; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/subscribers.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *users = [response valueForKey:@"users"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/subscribers/create + +- (NSObject *)postListSubscribersCreateForListID:(NSString *)listID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + return [self postAPIResource:@"lists/subscribers/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListSubscribersCreateForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + + return [self postAPIResource:@"lists/subscribers/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/subscribers/show + +- (NSObject *)getListsSubscribersShowForListID:(NSString *)listID + userID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSParameterAssert(listID); + NSAssert((userID || screenName), @"missing userID or screenName"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/subscribers/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getListsSubscribersShowForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userID:(NSString *)userID + orScreenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + NSAssert((userID || screenName), @"missing userID or screenName"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/subscribers/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/subscribers/destroy + +- (NSObject *)postListSubscribersDestroyForListID:(NSString *)listID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + return [self postAPIResource:@"lists/subscribers/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListSubscribersDestroyForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + + return [self postAPIResource:@"lists/subscribers/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/members/create_all + +- (NSObject *)postListsMembersCreateAllForListID:(NSString *)listID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSParameterAssert(listID); + NSAssert((userIDs || screenNames), @"missing usersIDs or screenNames"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + if(userIDs) { + md[@"user_id"] = [userIDs componentsJoinedByString:@","]; + } else if (screenNames) { + md[@"screen_name"] = [screenNames componentsJoinedByString:@","]; + } + + return [self postAPIResource:@"lists/members/create_all.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListsMembersCreateAllForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + NSAssert((userIDs || screenNames), @"missing usersIDs or screenNames"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + + if(ownerScreenName) { + md[@"owner_screen_name"] = ownerScreenName; + } else if (ownerID) { + md[@"owner_id"] = ownerID; + } + + if(userIDs) { + md[@"user_id"] = [userIDs componentsJoinedByString:@","]; + } else if (screenNames) { + md[@"screen_name"] = [screenNames componentsJoinedByString:@","]; + } + + return [self postAPIResource:@"lists/members/create_all.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/members/show + +- (NSObject *)getListsMembersShowForListID:(NSString *)listID + userID:(NSString *)userID + screenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert(listID, @"listID is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/members/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getListsMembersShowForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userID:(NSString *)userID + screenName:(NSString *)screenName + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSDictionary *user))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/members/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/members + +- (NSObject *)getListsMembersForListID:(NSString *)listID + cursor:(NSString *)cursor + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert(listID, @"listID is missing"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + if(cursor) md[@"cursor"] = cursor; + if(count) md[@"count"] = count; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/members.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *users = [response valueForKey:@"users"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getListsMembersForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + cursor:(NSString *)cursor + count:(NSString *)count + includeEntities:(NSNumber *)includeEntities + skipStatus:(NSNumber *)skipStatus + successBlock:(void(^)(NSArray *users, NSString *previousCursor, NSString *nextCursor))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + if(cursor) md[@"cursor"] = cursor; + if(count) md[@"count"] = count; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"1" : @"0"; + if(skipStatus) md[@"skip_status"] = [skipStatus boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"lists/members.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + NSArray *users = [response valueForKey:@"users"]; + NSString *previousCursor = [response valueForKey:@"previous_cursor_str"]; + NSString *nextCursor = [response valueForKey:@"next_cursor_str"]; + successBlock(users, previousCursor, nextCursor); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/members/create + +- (NSObject *)postListMemberCreateForListID:(NSString *)listID + userID:(NSString *)userID + screenName:(NSString *)screenName + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + NSAssert((userID || screenName), @"missing userID or screenName"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + if(userID) md[@"user_id"] = userID; + if(screenName) md[@"screen_name"] = screenName; + + return [self postAPIResource:@"lists/members/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListMemberCreateForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userID:(NSString *)userID + screenName:(NSString *)screenName + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + md[@"user_id"] = userID; + md[@"screen_name"] = screenName; + + return [self postAPIResource:@"lists/members/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/destroy + +- (NSObject *)postListsDestroyForListID:(NSString *)listID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + return [self postAPIResource:@"lists/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListsDestroyForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + + return [self postAPIResource:@"lists/destroy.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/update + +- (NSObject *)postListsUpdateForListID:(NSString *)listID + name:(NSString *)name + isPrivate:(BOOL)isPrivate + description:(NSString *)description + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + if(name) md[@"name"] = name; + md[@"mode"] = isPrivate ? @"private" : @"public"; + if(description) md[@"description"] = description; + + return [self postAPIResource:@"lists/update.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListsUpdateForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + name:(NSString *)name + isPrivate:(BOOL)isPrivate + description:(NSString *)description + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + if(name) md[@"name"] = name; + md[@"mode"] = isPrivate ? @"private" : @"public"; + if(description) md[@"description"] = description; + + return [self postAPIResource:@"lists/update.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/create + +- (NSObject *)postListsCreateWithName:(NSString *)name + isPrivate:(BOOL)isPrivate + description:(NSString *)description + successBlock:(void(^)(NSDictionary *list))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(name); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"name"] = name; + md[@"mode"] = isPrivate ? @"private" : @"public"; + if(description) md[@"description"] = description; + + return [self postAPIResource:@"lists/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET lists/show + +- (NSObject *)getListsShowListID:(NSString *)listID + successBlock:(void(^)(NSDictionary *list))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + return [self getAPIResource:@"lists/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getListsShowListSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + successBlock:(void(^)(NSDictionary *list))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + if(ownerScreenName) md[@"owner_screen_name"] = ownerScreenName; + if(ownerID) md[@"owner_id"] = ownerID; + + return [self getAPIResource:@"lists/show.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST lists/members/destroy_all + +- (NSObject *)postListsMembersDestroyAllForListID:(NSString *)listID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(listID); + NSAssert((userIDs || screenNames), @"missing usersIDs or screenNames"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"list_id"] = listID; + + if(userIDs) { + md[@"user_id"] = [userIDs componentsJoinedByString:@","]; + } else if (screenNames) { + md[@"screen_name"] = [screenNames componentsJoinedByString:@","]; + } + + return [self postAPIResource:@"lists/members/destroy_all.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postListsMembersDestroyAllForSlug:(NSString *)slug + ownerScreenName:(NSString *)ownerScreenName + orOwnerID:(NSString *)ownerID + userIDs:(NSArray *)userIDs // array of strings + orScreenNames:(NSArray *)screenNames // array of strings + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(slug); + NSAssert((ownerScreenName || ownerID), @"missing ownerScreenName or ownerID"); + NSAssert((userIDs || screenNames), @"missing usersIDs or screenNames"); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"slug"] = slug; + + if(ownerScreenName) { + md[@"owner_screen_name"] = ownerScreenName; + } else if (ownerID) { + md[@"owner_id"] = ownerID; + } + + if(userIDs) { + md[@"user_id"] = [userIDs componentsJoinedByString:@","]; + } else if (screenNames) { + md[@"screen_name"] = [screenNames componentsJoinedByString:@","]; + } + + return [self postAPIResource:@"lists/members/destroy_all.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Saved Searches + +// GET saved_searches/list +- (NSObject *)getSavedSearchesListWithSuccessBlock:(void(^)(NSArray *savedSearches))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"saved_searches/list.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET saved_searches/show/:id +- (NSObject *)getSavedSearchesShow:(NSString *)savedSearchID + successBlock:(void(^)(NSDictionary *savedSearch))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(savedSearchID); + + NSString *resource = [NSString stringWithFormat:@"saved_searches/show/%@.json", savedSearchID]; + + return [self getAPIResource:resource parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST saved_searches/create +- (NSObject *)postSavedSearchesCreateWithQuery:(NSString *)query + successBlock:(void(^)(NSDictionary *createdSearch))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(query); + + NSDictionary *d = @{ @"query" : query }; + + return [self postAPIResource:@"saved_searches/create.json" parameters:d successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST saved_searches/destroy/:id +- (NSObject *)postSavedSearchesDestroy:(NSString *)savedSearchID + successBlock:(void(^)(NSDictionary *destroyedSearch))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(savedSearchID); + + NSString *resource = [NSString stringWithFormat:@"saved_searches/destroy/%@.json", savedSearchID]; + + return [self postAPIResource:resource parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Places & Geo + +// GET geo/id/:place_id +- (NSObject *)getGeoIDForPlaceID:(NSString *)placeID // A place in the world. These IDs can be retrieved from geo/reverse_geocode. + successBlock:(void(^)(NSDictionary *place))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *resource = [NSString stringWithFormat:@"geo/id/%@.json", placeID]; + + return [self getAPIResource:resource parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET geo/reverse_geocode +- (NSObject *)getGeoReverseGeocodeWithLatitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + accuracy:(NSString *)accuracy // eg. "5ft" + granularity:(NSString *)granularity // eg. "city" + maxResults:(NSString *)maxResults // eg. "3" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *query, NSDictionary *result))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(latitude); + NSParameterAssert(longitude); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"lat"] = latitude; + md[@"long"] = longitude; + if(accuracy) md[@"accuracy"] = accuracy; + if(granularity) md[@"granularity"] = granularity; + if(maxResults) md[@"max_results"] = maxResults; + if(callback) md[@"callback"] = callback; + + return [self getAPIResource:@"geo/reverse_geocode.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *query = [response valueForKeyPath:@"query"]; + NSDictionary *result = [response valueForKeyPath:@"result"]; + + successBlock(query, result); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getGeoReverseGeocodeWithLatitude:(NSString *)latitude + longitude:(NSString *)longitude + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getGeoReverseGeocodeWithLatitude:latitude + longitude:longitude + accuracy:nil + granularity:nil + maxResults:nil + callback:nil + successBlock:^(NSDictionary *query, NSDictionary *result) { + successBlock([result valueForKey:@"places"]); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET geo/search + +- (NSObject *)getGeoSearchWithLatitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + query:(NSString *)query // eg. "Twitter HQ" + ipAddress:(NSString *)ipAddress // eg. 74.125.19.104 + granularity:(NSString *)granularity // eg. "city" + accuracy:(NSString *)accuracy // eg. "5ft" + maxResults:(NSString *)maxResults // eg. "3" + placeIDContaintedWithin:(NSString *)placeIDContaintedWithin // eg. "247f43d441defc03" + attributeStreetAddress:(NSString *)attributeStreetAddress // eg. "795 Folsom St" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *query, NSDictionary *result))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(latitude) md[@"lat"] = latitude; + if(longitude) md[@"long"] = longitude; + if(query) md[@"query"] = query; + if(ipAddress) md[@"ip"] = ipAddress; + if(granularity) md[@"granularity"] = granularity; + if(accuracy) md[@"accuracy"] = accuracy; + if(maxResults) md[@"max_results"] = maxResults; + if(placeIDContaintedWithin) md[@"contained_within"] = placeIDContaintedWithin; + if(attributeStreetAddress) md[@"attribute:street_address"] = attributeStreetAddress; + if(callback) md[@"callback"] = callback; + + return [self getAPIResource:@"geo/reverse_geocode.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *query = [response valueForKeyPath:@"query"]; + NSDictionary *result = [response valueForKeyPath:@"result"]; + + successBlock(query, result); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getGeoSearchWithLatitude:(NSString *)latitude + longitude:(NSString *)longitude + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(latitude); + NSParameterAssert(longitude); + + return [self getGeoSearchWithLatitude:latitude + longitude:longitude + query:nil + ipAddress:nil + granularity:nil + accuracy:nil + maxResults:nil + placeIDContaintedWithin:nil + attributeStreetAddress:nil + callback:nil + successBlock:^(NSDictionary *query, NSDictionary *result) { + successBlock([result valueForKey:@"places"]); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getGeoSearchWithIPAddress:(NSString *)ipAddress + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(ipAddress); + + return [self getGeoSearchWithLatitude:nil + longitude:nil + query:nil + ipAddress:ipAddress + granularity:nil + accuracy:nil + maxResults:nil + placeIDContaintedWithin:nil + attributeStreetAddress:nil + callback:nil + successBlock:^(NSDictionary *query, NSDictionary *result) { + successBlock([result valueForKey:@"places"]); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)getGeoSearchWithQuery:(NSString *)query + successBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(query); + + return [self getGeoSearchWithLatitude:nil + longitude:nil + query:query + ipAddress:nil + granularity:nil + accuracy:nil + maxResults:nil + placeIDContaintedWithin:nil + attributeStreetAddress:nil + callback:nil + successBlock:^(NSDictionary *query, NSDictionary *result) { + successBlock([result valueForKey:@"places"]); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET geo/similar_places + +- (NSObject *)getGeoSimilarPlacesToLatitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + name:(NSString *)name // eg. "Twitter HQ" + placeIDContaintedWithin:(NSString *)placeIDContaintedWithin // eg. "247f43d441defc03" + attributeStreetAddress:(NSString *)attributeStreetAddress // eg. "795 Folsom St" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *query, NSArray *resultPlaces, NSString *resultToken))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(latitude); + NSParameterAssert(longitude); + NSParameterAssert(name); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"lat"] = latitude; + md[@"long"] = longitude; + md[@"name"] = name; + if(placeIDContaintedWithin) md[@"contained_within"] = placeIDContaintedWithin; + if(attributeStreetAddress) md[@"attribute:street_address"] = attributeStreetAddress; + if(callback) md[@"callback"] = callback; + + return [self getAPIResource:@"geo/reverse_geocode.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *query = [response valueForKey:@"query"]; + NSDictionary *result = [response valueForKey:@"result"]; + NSArray *places = [result valueForKey:@"places"]; + NSString *token = [result valueForKey:@"token"]; + + successBlock(query, places, token); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST get/place + +// WARNING: deprecated since December 2nd, 2013 https://dev.twitter.com/discussions/22452 + +- (NSObject *)postGeoPlaceWithName:(NSString *)name // eg. "Twitter HQ" + placeIDContaintedWithin:(NSString *)placeIDContaintedWithin // eg. "247f43d441defc03" + similarPlaceToken:(NSString *)similarPlaceToken // eg. "36179c9bf78835898ebf521c1defd4be" + latitude:(NSString *)latitude // eg. "37.7821120598956" + longitude:(NSString *)longitude // eg. "-122.400612831116" + attributeStreetAddress:(NSString *)attributeStreetAddress // eg. "795 Folsom St" + callback:(NSString *)callback // If supplied, the response will use the JSONP format with a callback of the given name. + successBlock:(void(^)(NSDictionary *place))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"name"] = name; + md[@"contained_within"] = placeIDContaintedWithin; + md[@"token"] = similarPlaceToken; + md[@"lat"] = latitude; + md[@"long"] = longitude; + if(attributeStreetAddress) md[@"attribute:street_address"] = attributeStreetAddress; + if(callback) md[@"callback"] = callback; + + return [self postAPIResource:@"get/create.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Trends + +// GET trends/place +- (NSObject *)getTrendsForWOEID:(NSString *)WOEID // 'Yahoo! Where On Earth ID', Paris is "615702" + excludeHashtags:(NSNumber *)excludeHashtags + successBlock:(void(^)(NSDate *asOf, NSDate *createdAt, NSArray *locations, NSArray *trends))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(WOEID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"id"] = WOEID; + if(excludeHashtags) md[@"exclude"] = [excludeHashtags boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"trends/place.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *d = [response lastObject]; + + NSDate *asOf = nil; + NSDate *createdAt = nil; + NSArray *locations = nil; + NSArray *trends = nil; + + if([d isKindOfClass:[NSDictionary class]]) { + NSString *asOfString = [d valueForKey:@"as_of"]; + NSString *createdAtString = [d valueForKey:@"created_at"]; + + asOf = [[self dateFormatter] dateFromString:asOfString]; + createdAt = [[self dateFormatter] dateFromString:createdAtString]; + + locations = [d valueForKey:@"locations"]; + trends = [d valueForKey:@"trends"]; + } + + successBlock(asOf, createdAt, locations, trends); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET trends/available +- (NSObject *)getTrendsAvailableWithSuccessBlock:(void(^)(NSArray *locations))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getAPIResource:@"trends/available.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET trends/closest +- (NSObject *)getTrendsClosestToLatitude:(NSString *)latitude + longitude:(NSString *)longitude + successBlock:(void(^)(NSArray *locations))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(latitude); + NSParameterAssert(longitude); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"lat"] = latitude; + md[@"long"] = longitude; + + return [self getAPIResource:@"trends/closest.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Spam Reporting + +// POST users/report_spam +- (NSObject *)postUsersReportSpamForScreenName:(NSString *)screenName + orUserID:(NSString *)userID + successBlock:(void(^)(id userProfile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(screenName || userID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(screenName) md[@"screen_name"] = screenName; + if(userID) md[@"user_id"] = userID; + + return [self postAPIResource:@"users/report_spam.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark OAuth + +// GET oauth/authenticate +// GET oauth/authorize +// POST oauth/access_token +// POST oauth/request_token +// POST oauth2/token +// POST oauth2/invalidate_token + +#pragma mark Help + +// GET help/configuration +- (NSObject *)getHelpConfigurationWithSuccessBlock:(void(^)(NSDictionary *currentConfiguration))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getAPIResource:@"help/configuration.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET help/languages +- (NSObject *)getHelpLanguagesWithSuccessBlock:(void (^)(NSArray *languages))successBlock + errorBlock:(void (^)(NSError *))errorBlock { + return [self getAPIResource:@"help/languages.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET help/privacy +- (NSObject *)getHelpPrivacyWithSuccessBlock:(void(^)(NSString *tos))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getAPIResource:@"help/privacy.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock([response valueForKey:@"privacy"]); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET help/tos +- (NSObject *)getHelpTermsOfServiceWithSuccessBlock:(void(^)(NSString *tos))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + return [self getAPIResource:@"help/tos.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock([response valueForKey:@"tos"]); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET application/rate_limit_status +- (NSObject *)getRateLimitsForResources:(NSArray *)resources // eg. statuses,friends,trends,help + successBlock:(void(^)(NSDictionary *rateLimits))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + NSDictionary *d = nil; + if (resources) + d = @{ @"resources" : [resources componentsJoinedByString:@","] }; + return [self getAPIResource:@"application/rate_limit_status.json" parameters:d successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Tweets + +/* + GET statuses/lookup + + 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. + */ + +- (NSObject *)getStatusesLookupTweetIDs:(NSArray *)tweetIDs + includeEntities:(NSNumber *)includeEntities + trimUser:(NSNumber *)trimUser + map:(NSNumber *)map + successBlock:(void(^)(NSArray *tweets))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + NSParameterAssert(tweetIDs); + NSAssert(([tweetIDs isKindOfClass:[NSArray class]]), @"tweetIDs must be an array"); + + md[@"id"] = [tweetIDs componentsJoinedByString:@","]; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"true" : @"false"; + if(trimUser) md[@"trim_user"] = [trimUser boolValue] ? @"1" : @"0"; + if(map) md[@"map"] = [map boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"statuses/lookup.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark Media + +- (NSObject *)postMediaUpload:(NSURL *)mediaURL + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *imageDictionary, NSString *mediaID, NSString *size))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSData *data = [NSData dataWithContentsOfURL:mediaURL]; + + NSString *fileName = [mediaURL isFileURL] ? [[mediaURL path] lastPathComponent] : @"media.jpg"; + + return [self postMediaUploadData:data + fileName:fileName + uploadProgressBlock:uploadProgressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSObject *)postMediaUploadData:(NSData *)data + fileName:(NSString *)fileName + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSDictionary *imageDictionary, NSString *mediaID, NSString *size))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + // https://dev.twitter.com/docs/api/multiple-media-extended-entities + + if(data == nil) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterAPIMediaDataIsEmpty userInfo:@{NSLocalizedDescriptionKey : @"data is nil"}]; + errorBlock(error); + return nil; + } + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"media"] = data; + md[kSTPOSTDataKey] = @"media"; + md[kSTPOSTMediaFileNameKey] = fileName; + + return [self postResource:@"media/upload.json" + baseURLString:kBaseURLStringUpload_1_1 + parameters:md + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:nil + successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *imageDictionary = [response valueForKey:@"image"]; + NSString *mediaID = [response valueForKey:@"media_id_string"]; + NSString *size = [response valueForKey:@"size"]; + + successBlock(imageDictionary, mediaID, size); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (NSObject *)postMediaUploadINITWithVideoURL:(NSURL *)videoMediaURL + successBlock:(void(^)(NSString *mediaID, NSString *expiresAfterSecs))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + // https://dev.twitter.com/rest/public/uploading-media + + NSData *data = [NSData dataWithContentsOfURL:videoMediaURL]; + + if(data == nil) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) + code:STTwitterAPIMediaDataIsEmpty + userInfo:@{NSLocalizedDescriptionKey : @"data is nil"}]; + errorBlock(error); + return nil; + } + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"command"] = @"INIT"; + md[@"media_type"] = @"video/mp4"; + md[@"total_bytes"] = [NSString stringWithFormat:@"%@", @([data length])]; + + return [self postResource:@"media/upload.json" + baseURLString:kBaseURLStringUpload_1_1 + parameters:md + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(NSDictionary *rateLimits, id response) { + + /* + { + "expires_after_secs" = 3599; + "media_id" = 605333580483575808; + "media_id_string" = 605333580483575808; + } + */ + + NSString *mediaID = [response valueForKey:@"media_id_string"]; + NSString *expiresAfterSecs = [response valueForKey:@"expires_after_secs"]; + + successBlock(mediaID, expiresAfterSecs); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +- (void)postMediaUploadAPPENDWithVideoURL:(NSURL *)videoMediaURL + mediaID:(NSString *)mediaID + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + // https://dev.twitter.com/rest/public/uploading-media + // https://dev.twitter.com/rest/reference/post/media/upload-chunked + + NSData *data = [NSData dataWithContentsOfURL:videoMediaURL]; + + NSInteger dataLength = [data length]; + + if(dataLength == 0) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterAPIMediaDataIsEmpty userInfo:@{NSLocalizedDescriptionKey : @"cannot upload empty data"}]; + errorBlock(error); + return; + } + + NSString *fileName = [videoMediaURL isFileURL] ? [[videoMediaURL path] lastPathComponent] : @"media.jpg"; + + NSUInteger fiveMegaBytes = 5 * (int) pow((double) 2,20); + + NSUInteger segmentIndex = 0; + + __block id lastResponseReceived = nil; + __block NSError *lastErrorReceived = nil; + __block NSUInteger accumulatedBytesWritten = 0; + + dispatch_group_t group = dispatch_group_create(); + + while((segmentIndex * fiveMegaBytes) < dataLength) { + + NSUInteger subDataLength = MIN(dataLength - segmentIndex * fiveMegaBytes, fiveMegaBytes); + NSRange subDataRange = NSMakeRange(segmentIndex * fiveMegaBytes, subDataLength); + NSData *subData = [data subdataWithRange:subDataRange]; + + //NSLog(@"-- SEGMENT INDEX %lu, SUBDATA %@", segmentIndex, NSStringFromRange(subDataRange)); + + __weak typeof(self) weakSelf = self; + + dispatch_group_enter(group); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + + __strong typeof(weakSelf) strongSelf = weakSelf; + if(strongSelf == nil) { + lastErrorReceived = [NSError errorWithDomain:@"STTwitter" code:9999 userInfo:nil]; // TODO: improve + return; + } + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"command"] = @"APPEND"; + md[@"media_id"] = mediaID; + md[@"segment_index"] = [NSString stringWithFormat:@"%lu", (unsigned long)segmentIndex]; + md[@"media"] = subData; + md[kSTPOSTDataKey] = @"media"; + md[kSTPOSTMediaFileNameKey] = fileName; + + //NSLog(@"-- POST %@", [md valueForKey:@"segment_index"]); + + [strongSelf postResource:@"media/upload.json" + baseURLString:kBaseURLStringUpload_1_1 + parameters:md + uploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) { + accumulatedBytesWritten += bytesWritten; + uploadProgressBlock(bytesWritten, accumulatedBytesWritten, dataLength); + } downloadProgressBlock:nil + successBlock:^(NSDictionary *rateLimits, id response) { + //NSLog(@"-- POST OK %@", [md valueForKey:@"segment_index"]); + lastResponseReceived = response; + dispatch_group_leave(group); + } errorBlock:^(NSError *error) { + //NSLog(@"-- POST KO %@", [md valueForKey:@"segment_index"]); + errorBlock(error); + dispatch_group_leave(group); + }]; + }); + + segmentIndex += 1; + } + + dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + NSLog(@"finished"); + if(lastErrorReceived) { + errorBlock(lastErrorReceived); + } else { + successBlock(lastResponseReceived); + } + }); +} + +- (NSObject *)postMediaUploadFINALIZEWithMediaID:(NSString *)mediaID + successBlock:(void(^)(NSString *mediaID, NSString *size, NSString *expiresAfter, NSString *videoType))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + // https://dev.twitter.com/rest/public/uploading-media + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"command"] = @"FINALIZE"; + md[@"media_id"] = mediaID; + + return [self postResource:@"media/upload.json" + baseURLString:kBaseURLStringUpload_1_1 + parameters:md + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(NSDictionary *rateLimits, id response) { + + //NSLog(@"-- %@", response); + + NSString *mediaID = [response valueForKey:@"media_id"]; + NSString *expiresAfterSecs = [response valueForKey:@"expires_after_secs"]; + NSString *size = [response valueForKey:@"size"]; + NSString *videoType = [response valueForKeyPath:@"video.video_type"]; + + /* + { + "expires_after_secs" = 3600; + "media_id" = 607552320679706624; + "media_id_string" = 607552320679706624; + size = 992496; + video = { + "video_type" = "video/mp4"; + }; + } + */ + + successBlock(mediaID, size, expiresAfterSecs, videoType); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// convenience + +- (void)postMediaUploadThreeStepsWithVideoURL:(NSURL *)videoURL // local URL + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + successBlock:(void(^)(NSString *mediaID, NSString *size, NSString *expiresAfter, NSString *videoType))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + __weak typeof(self) weakSelf = self; + + [self postMediaUploadINITWithVideoURL:videoURL + successBlock:^(NSString *mediaID, NSString *expiresAfterSecs) { + + __strong typeof(self) strongSelf = weakSelf; + if(strongSelf == nil) { + errorBlock(nil); + return; + } + + [strongSelf postMediaUploadAPPENDWithVideoURL:videoURL + mediaID:mediaID + uploadProgressBlock:uploadProgressBlock + successBlock:^(id response) { + + __strong typeof(self) strongSelf2 = weakSelf; + if(strongSelf2 == nil) { + errorBlock(nil); + return; + } + + [strongSelf2 postMediaUploadFINALIZEWithMediaID:mediaID + successBlock:successBlock + errorBlock:errorBlock]; + } errorBlock:errorBlock]; + } errorBlock:errorBlock]; +} + +#pragma mark - +#pragma mark UNDOCUMENTED APIs + +// GET activity/about_me.json +- (NSObject *)_getActivityAboutMeSinceID:(NSString *)sinceID + count:(NSString *)count // + includeCards:(NSNumber *)includeCards + modelVersion:(NSNumber *)modelVersion + sendErrorCodes:(NSNumber *)sendErrorCodes + contributorDetails:(NSNumber *)contributorDetails + includeEntities:(NSNumber *)includeEntities + includeMyRetweet:(NSNumber *)includeMyRetweet + successBlock:(void(^)(NSArray *activities))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(sinceID) md[@"since_id"] = sinceID; + if(count) md[@"count"] = count; + if(contributorDetails) md[@"contributor_details"] = [contributorDetails boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"true" : @"false"; + if(includeMyRetweet) md[@"include_my_retweet"] = [includeMyRetweet boolValue] ? @"1" : @"0"; + if(includeCards) md[@"include_cards"] = [includeCards boolValue] ? @"1" : @"0"; + if(modelVersion) md[@"model_version"] = [modelVersion boolValue] ? @"true" : @"false"; + if(sendErrorCodes) md[@"send_error_codes"] = [sendErrorCodes boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"activity/about_me.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET activity/by_friends.json +- (NSObject *)_getActivityByFriendsSinceID:(NSString *)sinceID + count:(NSString *)count + contributorDetails:(NSNumber *)contributorDetails + includeCards:(NSNumber *)includeCards + includeEntities:(NSNumber *)includeEntities + includeMyRetweets:(NSNumber *)includeMyRetweets + includeUserEntites:(NSNumber *)includeUserEntites + latestResults:(NSNumber *)latestResults + sendErrorCodes:(NSNumber *)sendErrorCodes + successBlock:(void(^)(NSArray *activities))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(sinceID) md[@"since_id"] = sinceID; + if(count) md[@"count"] = count; + if(includeCards) md[@"include_cards"] = [includeCards boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"true" : @"false"; + if(includeMyRetweets) md[@"include_my_retweet"] = [includeMyRetweets boolValue] ? @"true" : @"false"; + if(includeUserEntites) md[@"include_user_entities"] = [includeUserEntites boolValue] ? @"1" : @"0"; + if(latestResults) md[@"latest_results"] = [latestResults boolValue] ? @"true" : @"false"; + if(sendErrorCodes) md[@"send_error_codes"] = [sendErrorCodes boolValue] ? @"1" : @"0"; + + return [self getAPIResource:@"activity/by_friends.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET statuses/:id/activity/summary.json +- (NSObject *)_getStatusesActivitySummaryForStatusID:(NSString *)statusID + successBlock:(void(^)(NSArray *favoriters, NSArray *repliers, NSArray *retweeters, NSString *favoritersCount, NSString *repliersCount, NSString *retweetersCount))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *resource = [NSString stringWithFormat:@"statuses/%@/activity/summary.json", statusID]; + + return [self getAPIResource:resource parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + + NSArray *favoriters = [response valueForKey:@"favoriters"]; + NSArray *repliers = [response valueForKey:@"repliers"]; + NSArray *retweeters = [response valueForKey:@"retweeters"]; + NSString *favoritersCount = [response valueForKey:@"favoriters_count"]; + NSString *repliersCount = [response valueForKey:@"repliers_count"]; + NSString *retweetersCount = [response valueForKey:@"retweeters_count"]; + + successBlock(favoriters, repliers, retweeters, favoritersCount, repliersCount, retweetersCount); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET conversation/show.json +- (NSObject *)_getConversationShowForStatusID:(NSString *)statusID + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSDictionary *d = @{@"id":statusID}; + + return [self getAPIResource:@"conversation/show.json" parameters:d successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET discover/highlight.json +- (NSObject *)_getDiscoverHighlightWithSuccessBlock:(void(^)(NSDictionary *metadata, NSArray *modules))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"discover/highlight.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *metadata = [response valueForKey:@"metadata"]; + NSArray *modules = [response valueForKey:@"modules"]; + + successBlock(metadata, modules); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET discover/universal.json +- (NSObject *)_getDiscoverUniversalWithSuccessBlock:(void(^)(NSDictionary *metadata, NSArray *modules))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"discover/universal.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + + NSDictionary *metadata = [response valueForKey:@"metadata"]; + NSArray *modules = [response valueForKey:@"modules"]; + + successBlock(metadata, modules); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET statuses/media_timeline.json +- (NSObject *)_getMediaTimelineWithSuccessBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"statuses/media_timeline.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET users/recommendations.json +- (NSObject *)_getUsersRecommendationsWithSuccessBlock:(void(^)(NSArray *recommendations))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"users/recommendations.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET timeline/home.json +- (NSObject *)_getTimelineHomeWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"timeline/home.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET statuses/mentions_timeline.json +- (NSObject *)_getStatusesMentionsTimelineWithCount:(NSString *)count + contributorsDetails:(NSNumber *)contributorsDetails + includeEntities:(NSNumber *)includeEntities + includeMyRetweet:(NSNumber *)includeMyRetweet + successBlock:(void(^)(NSArray *statuses))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(count) md[@"count"] = count; + if(contributorsDetails) md[@"contributor_details"] = [contributorsDetails boolValue] ? @"1" : @"0"; + if(includeEntities) md[@"include_entities"] = [includeEntities boolValue] ? @"true" : @"false"; + if(includeMyRetweet) md[@"include_my_retweet"] = [includeMyRetweet boolValue] ? @"true" : @"false"; + + return [self getAPIResource:@"statuses/mentions_timeline.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET trends/available.json +- (NSObject *)_getTrendsAvailableWithSuccessBlock:(void(^)(NSArray *places))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self getAPIResource:@"trends/available.json" parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST users/report_spam +- (NSObject *)_postUsersReportSpamForTweetID:(NSString *)tweetID + reportAs:(NSString *)reportAs // spam, abused, compromised + blockUser:(NSNumber *)blockUser + successBlock:(void(^)(id userProfile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(tweetID); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"tweet_id"] = tweetID; + if(reportAs) md[@"report_as"] = reportAs; + if(blockUser) md[@"block_user"] = [blockUser boolValue] ? @"true" : @"false"; + + return [self postAPIResource:@"users/report_spam.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST account/generate.json +- (NSObject *)_postAccountGenerateWithADC:(NSString *)adc + discoverableByEmail:(BOOL)discoverableByEmail + email:(NSString *)email + geoEnabled:(BOOL)geoEnabled + language:(NSString *)language + name:(NSString *)name + password:(NSString *)password + screenName:(NSString *)screenName + sendErrorCode:(BOOL)sendErrorCode + timeZone:(NSString *)timeZone + successBlock:(void(^)(id userProfile))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"adc"] = adc; + md[@"discoverable_by_email"] = discoverableByEmail ? @"1" : @"0"; + md[@"email"] = email; + md[@"geo_enabled"] = geoEnabled ? @"1" : @"0"; + md[@"lang"] = language; + md[@"name"] = name; + md[@"password"] = password; + md[@"screen_name"] = screenName; + md[@"send_error_codes"] = sendErrorCode ? @"1": @"0"; + md[@"time_zone"] = timeZone; + + return [self postResource:@"account/generate.json" + baseURLString:@"https://api.twitter.com/1" + parameters:md + uploadProgressBlock:nil + downloadProgressBlock:^(NSData *data) { + // + } successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET search/typeahead.json +- (NSObject *)_getSearchTypeaheadQuery:(NSString *)query + resultType:(NSString *)resultType // "all" + sendErrorCodes:(NSNumber *)sendErrorCodes + successBlock:(void(^)(id results))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"q"] = query; + if(resultType) md[@"result_type"] = resultType; + if(sendErrorCodes) md[@"send_error_codes"] = @([sendErrorCodes boolValue]); + + return [self getAPIResource:@"search/typeahead.json" parameters:md successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// GET conversation/show/:id.json +- (NSObject *)_getConversationShowWithTweetID:(NSString *)tweetID + successBlock:(void(^)(id results))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(tweetID); + + NSString *ressource = [NSString stringWithFormat:@"conversation/show/%@.json", tweetID]; + + return [self getAPIResource:ressource parameters:nil successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark UNDOCUMENTED APIS SCHEDULED TWEETS - VALID ONLY FOR TWEETDECK + +// GET schedule/status/list.json +- (NSObject *)_getScheduleStatusesWithCount:(NSString *)count + includeEntities:(NSNumber *)includeEntities + includeUserEntities:(NSNumber *)includeUserEntities + includeCards:(NSNumber *)includeCards + successBlock:(void(^)(NSArray *scheduledTweets))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(count) md[@"count"] = count; + if(includeEntities) md[@"include_entities"] = @([includeEntities boolValue]); + if(includeUserEntities) md[@"include_user_entities"] = @([includeUserEntities boolValue]); + if(includeCards) md[@"include_cards"] = @([includeCards boolValue]); + + return [self getAPIResource:@"schedule/status/list.json" + parameters:md + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST schedule/status/tweet.json +- (NSObject *)_postScheduleStatus:(NSString *)status + executeAt:(NSString *)executeAtUnixTimestamp + mediaIDs:(NSArray *)mediaIDs + successBlock:(void(^)(NSDictionary *scheduledTweet))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(status); + NSParameterAssert(executeAtUnixTimestamp); + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + md[@"status"] = status; + md[@"execute_at"] = executeAtUnixTimestamp; + if(mediaIDs) md[@"media_ids"] = [mediaIDs componentsJoinedByString:@","]; + + return [self postAPIResource:@"schedule/status/tweet.json" + parameters:md + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// DELETE schedule/status/:id.json +// delete a scheduled tweet +- (NSObject *)_deleteScheduleStatusWithID:(NSString *)statusID + successBlock:(void(^)(NSDictionary *deletedTweet))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSString *resource = [NSString stringWithFormat:@"schedule/status/%@.json", statusID]; + + return [self fetchResource:resource + HTTPMethod:@"DELETE" + baseURLString:kBaseURLStringAPI_1_1 + parameters:nil + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + successBlock(response); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +// PUT schedule/status/:id.json +// edit a scheduled tweet +- (NSObject *)_putScheduleStatusWithID:(NSString *)statusID + status:(NSString *)status + executeAt:(NSString *)executeAtUnixTimestamp + mediaIDs:(NSArray *)mediaIDs + successBlock:(void(^)(NSDictionary *scheduledTweet))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(statusID); + + NSString *resource = [NSString stringWithFormat:@"schedule/status/%@.json", statusID]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(status) md[@"status"] = status; + if(executeAtUnixTimestamp) md[@"execute_at"] = executeAtUnixTimestamp; + if(mediaIDs) md[@"media_ids"] = [mediaIDs componentsJoinedByString:@","]; + + return [self fetchResource:resource + HTTPMethod:@"PUT" + baseURLString:kBaseURLStringAPI_1_1 + parameters:md + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + successBlock(response); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark UNDOCUMENTED APIS FOR DIGITS AUTH + +// POST guest/activate.json +- (NSObject *)_postGuestActivateWithSuccessBlock:(void(^)(NSString *guestToken))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postAPIResource:@"guest/activate.json" + parameters:nil + successBlock:^(NSDictionary *rateLimits, id response) { + NSString *guestToken = [response valueForKey:@"guest_token"]; + successBlock(guestToken); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST device/register.json +- (NSObject *)_postDeviceRegisterPhoneNumber:(NSString *)phoneNumber // eg. @"+41764948273" + guestToken:(NSString *)guestToken + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(phoneNumber); + + NSDictionary *parameters = @{@"raw_phone_number":phoneNumber, + @"text_key":@"third_party_confirmation_code", + @"send_numeric_pin":@"true", + @"[STTWITTER_HEADER_APPONLY_POST]x-guest-token":guestToken}; + + return [self postAPIResource:@"device/register.json" + parameters:parameters + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST sdk/account.json +- (NSObject *)_postSDKAccountNumericPIN:(NSString *)numericPIN + forPhoneNumber:(NSString *)phoneNumber + guestToken:(NSString *)guestToken + successBlock:(void(^)(id response, NSString *accessToken, NSString *accessTokenSecret))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(numericPIN); + NSParameterAssert(phoneNumber); + + NSDictionary *parameters = @{@"numeric_pin":numericPIN, + @"phone_number":phoneNumber, + @"[STTWITTER_HEADER_APPONLY_POST]x-guest-token":guestToken}; + + return [self fetchResource:@"sdk/account.json" + HTTPMethod:@"POST" + baseURLString:kBaseURLStringAPI_1_1 + parameters:parameters + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + NSString *accessToken = [responseHeaders valueForKey:@"x-twitter-new-account-oauth-access-token"]; + NSString *accessTokenSecret = [responseHeaders valueForKey:@"x-twitter-new-account-oauth-secret"]; + successBlock(response, accessToken, accessTokenSecret); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark UNDOCUMENTED APIS FOR CONTACTS + +// POST contacts/upload.json +- (NSObject *)_postContactsUpload:(NSArray *)vCards + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSDictionary *d = @{@"vcards":vCards}; + + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:d options:0 error:&error]; + if(data == nil) { + errorBlock(error); + return nil; + } + + NSString *urlString = [NSString stringWithFormat:@"%@/%@", kBaseURLStringAPI_1_1, @"contacts/upload.json"]; + + STHTTPRequest *r = [STHTTPRequest twitterRequestWithURLString:urlString + HTTPMethod:@"POST" + timeoutInSeconds:10 + stTwitterUploadProgressBlock:nil + stTwitterDownloadProgressBlock:nil + stTwitterSuccessBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json) { + successBlock(json); + } stTwitterErrorBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; + + STTwitterOAuth *oAuth = (STTwitterOAuth *)self.oauth; + [oAuth signRequest:r isMediaUpload:NO oauthCallback:nil]; + + [r setRawPOSTData:data]; + [r setHeaderWithName:@"Content-Type" value:@"application/json"]; + + [r startAsynchronous]; + + return r; +} + +// GET contacts/users_and_uploaded_by.json +- (NSObject *)_getContactsUsersAndUploadedByWithCount:(NSString *)count + successBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + if(count) md[@"count"] = count; + + return [self getAPIResource:@"contacts/users_and_uploaded_by.json" + parameters:md + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +// POST contacts/destroy/all.json +- (NSObject *)_getContactsDestroyAllWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + return [self postAPIResource:@"contacts/destroy/all.json" + parameters:nil + successBlock:^(NSDictionary *rateLimits, id response) { + successBlock(response); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; +} + +#pragma mark UNDOCUMENTED APIS FOR TWITTER ANALYTICS + +// GET https://analytics.twitter.com/user/:screenname/tweet/:tweetid/mobile/poll.json +- (NSObject *)_getAnalyticsWithScreenName:(NSString *)screenName + tweetID:(NSString *)tweetID + successBlock:(void(^)(id rawResponse, NSDictionary *responseDictionary))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSParameterAssert(successBlock); + NSParameterAssert(errorBlock); + + NSParameterAssert(screenName); + NSParameterAssert(tweetID); + + NSString *resource = [NSString stringWithFormat:@"user/%@/tweet/%@/mobile/poll.json", screenName, tweetID]; + + return [_oauth fetchResource:resource + HTTPMethod:@"GET" + baseURLString:@"https://analytics.twitter.com" + parameters:nil + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + + NSString *prefix = @"/**/retrieveNewMetrics("; + //NSString *suffix = @")"; + + NSDictionary *json = nil; + + if([response hasPrefix:prefix] && [response length] >= [prefix length] + 2) { + // transform jsonp into NSDictionary + NSMutableString *ms = [response mutableCopy]; + [ms deleteCharactersInRange:NSMakeRange(0, [prefix length])]; + [ms deleteCharactersInRange:NSMakeRange([ms length]-2, 2)]; + NSLog(@"-- %@", ms); + NSData *data = [ms dataUsingEncoding:NSUTF8StringEncoding]; + NSError *jsonError = nil; + json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if(json == nil) { + NSLog(@"-- %@", [jsonError localizedDescription]); + } + NSLog(@"-- %@", json); + } + + successBlock(response, json); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +@end + +@implementation NSString (STTwitterAPI) + +- (NSString *)htmlLinkName { + NSString *ahref = [self st_firstMatchWithRegex:@"(.*)" error:nil]; + + return ahref ? ahref : self; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.h new file mode 100644 index 0000000..92815ec --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.h @@ -0,0 +1,42 @@ +// +// STTwitterAppOnly.h +// STTwitter +// +// Created by Nicolas Seriot on 3/13/13. +// Copyright (c) 2013 Nicolas Seriot. All rights reserved. +// + +#import +#import "STTwitterProtocol.h" + +#if DEBUG +# define STLog(...) NSLog(__VA_ARGS__) +#else +# define STLog(...) +#endif + +extern NS_ENUM(NSUInteger, STTwitterAppOnlyErrorCode) { + STTwitterAppOnlyCannotFindBearerTokenToBeInvalidated = 0, + STTwitterAppOnlyCannotFindJSONInResponse, + STTwitterAppOnlyCannotFindBearerTokenInResponse +}; + +@interface STTwitterAppOnly : NSObject { + +} + +@property (nonatomic, retain) NSString *consumerName; +@property (nonatomic, retain) NSString *consumerKey; +@property (nonatomic, retain) NSString *consumerSecret; +@property (nonatomic, retain) NSString *bearerToken; + +@property (nonatomic) NSTimeInterval timeoutInSeconds; + ++ (instancetype)twitterAppOnlyWithConsumerName:(NSString *)conumerName consumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret; + ++ (NSString *)base64EncodedBearerTokenCredentialsWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret; + +- (void)invalidateBearerTokenWithSuccessBlock:(void(^)())successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.m new file mode 100644 index 0000000..c2cc980 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterAppOnly.m @@ -0,0 +1,315 @@ +// +// STTwitterAppOnly.m +// STTwitter +// +// Created by Nicolas Seriot on 3/13/13. +// Copyright (c) 2013 Nicolas Seriot. All rights reserved. +// + +#import "STTwitterAppOnly.h" +#import "NSString+STTwitter.h" +#import "STHTTPRequest+STTwitter.h" + +@interface NSData (Base64) +- (NSString *)base64Encoding; // private API +@end + +@implementation STTwitterAppOnly + +- (id)init { + self = [super init]; + + // TODO: remove cookies from Twitter if needed + + return self; +} + ++ (instancetype)twitterAppOnlyWithConsumerName:(NSString *)consumerName consumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret { + STTwitterAppOnly *twitterAppOnly = [[[self class] alloc] init]; + twitterAppOnly.consumerName = consumerName; + twitterAppOnly.consumerKey = consumerKey; + twitterAppOnly.consumerSecret = consumerSecret; + return twitterAppOnly; +} + +#pragma mark STTwitterOAuthProtocol + +- (void)verifyCredentialsLocallyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + successBlock(nil, nil); // local check is not possible +} + +- (NSString *)oauthAccessToken { + return nil; +} + +- (NSString *)oauthAccessTokenSecret { + return nil; +} + +- (void)invalidateBearerTokenWithSuccessBlock:(void(^)())successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if(_bearerToken == nil) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterAppOnlyCannotFindBearerTokenToBeInvalidated userInfo:@{NSLocalizedDescriptionKey : @"Cannot invalidate missing bearer token"}]; + errorBlock(error); + return; + } + + [self postResource:@"oauth2/invalidate_token" + baseURLString:@"https://api.twitter.com" + parameters:@{ @"access_token" : _bearerToken } + useBasicAuth:YES + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json) { + + if([json isKindOfClass:[NSDictionary class]] == NO) { + successBlock(json); + return; + } + + self.bearerToken = [json valueForKey:@"access_token"]; + + NSString *oldToken = self.bearerToken; + + self.bearerToken = nil; + + successBlock(oldToken); + + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; + + // POST /oauth2/invalidate_token HTTP/1.1 + // Authorization: Basic eHZ6MWV2RlM0d0VFUFRHRUZQSEJvZzpMOHFxOVBaeVJn + // NmllS0dFS2hab2xHQzB2SldMdzhpRUo4OERSZHlPZw== + // User-Agent: My Twitter App v1.0.23 + // Host: api.twitter.com + // Accept: */* + // + // Content-Length: 119 + // Content-Type: application/x-www-form-urlencoded + // + // access_token=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + // HTTP/1.1 200 OK + // Content-Type: application/json; charset=utf-8 + // Content-Length: 127 + // ... + // + // {"access_token":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"} +} + ++ (NSString *)base64EncodedBearerTokenCredentialsWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret { + NSString *encodedConsumerToken = [consumerKey st_stringByAddingRFC3986PercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *encodedConsumerSecret = [consumerSecret st_stringByAddingRFC3986PercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *bearerTokenCredentials = [NSString stringWithFormat:@"%@:%@", encodedConsumerToken, encodedConsumerSecret]; + NSData *data = [bearerTokenCredentials dataUsingEncoding:NSUTF8StringEncoding]; + return [data base64Encoding]; +} + +- (void)verifyCredentialsRemotelyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + __weak typeof(self) weakSelf = self; + + [self postResource:@"oauth2/token" + baseURLString:@"https://api.twitter.com" + parameters:@{ @"grant_type" : @"client_credentials" } + useBasicAuth:YES + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json) { + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + if([json isKindOfClass:[NSDictionary class]] == NO) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([strongSelf class]) code:STTwitterAppOnlyCannotFindJSONInResponse userInfo:@{NSLocalizedDescriptionKey : @"Cannot find JSON dictionary in response"}]; + errorBlock(error); + return; + } + + NSString *tokenType = [json valueForKey:@"token_type"]; + if([tokenType isEqualToString:@"bearer"] == NO) { + NSError *error = [NSError errorWithDomain:NSStringFromClass([strongSelf class]) code:STTwitterAppOnlyCannotFindBearerTokenInResponse userInfo:@{NSLocalizedDescriptionKey : @"Cannot find bearer token in server response"}]; + errorBlock(error); + return; + } + + strongSelf.bearerToken = [json valueForKey:@"access_token"]; + + successBlock(strongSelf.bearerToken, nil); + + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +- (STHTTPRequest *)getResource:(NSString *)resource + baseURLString:(NSString *)baseURLString // no trailing slash + parameters:(NSDictionary *)params + progressBlock:(void(^)(STHTTPRequest *r, NSData *data))progressBlock + successBlock:(void (^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock + errorBlock:(void (^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + /* + GET /1.1/statuses/user_timeline.json?count=100&screen_name=twitterapi HTTP/1.1 + Host: api.twitter.com + User-Agent: My Twitter App v1.0.23 + Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAAAA + AAAAAAAA%3DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + Accept-Encoding: gzip + */ + + NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@/%@", baseURLString, resource]; + + // NSString *requestID = [[NSUUID UUID] UUIDString]; + + __block __weak STHTTPRequest *wr = nil; + __block STHTTPRequest *r = [STHTTPRequest twitterRequestWithURLString:urlString + HTTPMethod:@"GET" + timeoutInSeconds:_timeoutInSeconds + stTwitterUploadProgressBlock:nil + stTwitterDownloadProgressBlock:^(NSData *data, NSUInteger totalBytesReceived, long long totalBytesExpectedToReceive) { + if(progressBlock) progressBlock(wr, data); + } stTwitterSuccessBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json) { + successBlock(wr, requestHeaders, responseHeaders, json); + } stTwitterErrorBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(wr, requestHeaders, responseHeaders, error); + }]; + wr = r; + + if(_bearerToken) { + [r setHeaderWithName:@"Authorization" value:[NSString stringWithFormat:@"Bearer %@", _bearerToken]]; + } + + r.GETDictionary = params; + + [r startAsynchronous]; + + return r; +} + +- (NSObject *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *r, NSData *data))downloadProgressBlock + successBlock:(void(^)(NSObject *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock + errorBlock:(void(^)(NSObject *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + if([baseURLString hasSuffix:@"/"]) { + baseURLString = [baseURLString substringToIndex:[baseURLString length]-1]; + } + + if([HTTPMethod isEqualToString:@"GET"]) { + + return [self getResource:resource + baseURLString:baseURLString + parameters:params + progressBlock:downloadProgressBlock + successBlock:successBlock + errorBlock:errorBlock]; + + } else if ([HTTPMethod isEqualToString:@"POST"]) { + + return [self postResource:resource + baseURLString:baseURLString + parameters:params + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:downloadProgressBlock + successBlock:successBlock + errorBlock:errorBlock]; + + } else { + NSAssert(NO, @"unsupported HTTP method"); + return nil; + } +} + +- (STHTTPRequest *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString // no trailing slash + parameters:(NSDictionary *)params + useBasicAuth:(BOOL)useBasicAuth + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *request, NSData *data))downloadProgressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, resource]; + + __block __weak STHTTPRequest *wr = nil; + __block STHTTPRequest *r = [STHTTPRequest twitterRequestWithURLString:urlString + HTTPMethod:@"POST" + timeoutInSeconds:_timeoutInSeconds + stTwitterUploadProgressBlock:nil + stTwitterDownloadProgressBlock:^(NSData *data, NSUInteger totalBytesReceived, long long totalBytesExpectedToReceive) { + if(downloadProgressBlock) downloadProgressBlock(wr, data); + } stTwitterSuccessBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json) { + successBlock(wr, requestHeaders, responseHeaders, json); + } stTwitterErrorBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(wr, requestHeaders, responseHeaders, error); + }]; + + NSMutableDictionary *paramsToBeSent = [NSMutableDictionary dictionaryWithCapacity:[params count]]; + + NSString *stTwitterHeaderPrefix = @"[STTWITTER_HEADER_APPONLY_POST]"; + [params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { + if([key hasPrefix:stTwitterHeaderPrefix]) { + NSString *headerName = [key substringFromIndex:[stTwitterHeaderPrefix length]]; + [r setHeaderWithName:headerName value:obj]; + } else { + paramsToBeSent[key] = obj; + } + }]; + + wr = r; + + r.POSTDictionary = paramsToBeSent; + + NSMutableDictionary *mutableParams = [paramsToBeSent mutableCopy]; + + r.encodePOSTDictionary = NO; + + r.POSTDictionary = mutableParams ? mutableParams : @{}; + + if(useBasicAuth) { + NSString *base64EncodedTokens = [[self class] base64EncodedBearerTokenCredentialsWithConsumerKey:_consumerKey consumerSecret:_consumerSecret]; + + [r setHeaderWithName:@"Authorization" value:[NSString stringWithFormat:@"Basic %@", base64EncodedTokens]]; + } else if(_bearerToken) { + [r setHeaderWithName:@"Authorization" value:[NSString stringWithFormat:@"Bearer %@", _bearerToken]]; + r.encodePOSTDictionary = YES; + } + + [r startAsynchronous]; + + return r; +} + +- (STHTTPRequest *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *r, NSData *data))downloadProgressBlock + successBlock:(void(^)(NSObject *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock + errorBlock:(void(^)(NSObject *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + return [self postResource:resource + baseURLString:baseURLString + parameters:params + useBasicAuth:NO + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:downloadProgressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (NSString *)loginTypeDescription { + return @"App Only"; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.h new file mode 100644 index 0000000..ed91887 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.h @@ -0,0 +1,39 @@ +// +// STTwitterWeb.h +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/13/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import + +extern NS_ENUM(NSUInteger, STTwitterHTMLErrorCode) { + STTwitterHTMLCannotPostWithoutCredentials = 0 +}; + +@interface STTwitterHTML : NSObject + +- (void)getLoginForm:(void(^)(NSString *authenticityToken))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)postLoginFormWithUsername:(NSString *)username + password:(NSString *)password + authenticityToken:(NSString *)authenticityToken + successBlock:(void(^)(NSString *body))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + + +/**/ + +- (void)getAuthorizeFormAtURL:(NSURL *)url + successBlock:(void(^)(NSString *authenticityToken, NSString *oauthToken))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)postAuthorizeFormResultsAtURL:(NSURL *)url + authenticityToken:(NSString *)authenticityToken + oauthToken:(NSString *)oauthToken + successBlock:(void(^)(NSString *PIN))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.m new file mode 100644 index 0000000..31012b7 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterHTML.m @@ -0,0 +1,142 @@ +// +// STTwitterWeb.m +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/13/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import "STTwitterHTML.h" +#import "STHTTPRequest.h" +#import "NSString+STTwitter.h" + +@implementation STTwitterHTML + +- (void)getLoginForm:(void(^)(NSString *authenticityToken))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + + __block STHTTPRequest *r = [STHTTPRequest requestWithURLString:@"https://twitter.com/login"]; + + r.completionBlock = ^(NSDictionary *headers, NSString *body) { + + NSError *error = nil; + // NSString *token = [body firstMatchWithRegex:@"" error:&error]; + NSString *token = [body st_firstMatchWithRegex:@"formAuthenticityToken":"(\\S+?)"" error:&error]; + + if(token == nil) { + errorBlock(error); + return; + } + + successBlock(token); + }; + + r.errorBlock = ^(NSError *error) { + errorBlock(error); + }; + + [r startAsynchronous]; +} + +- (void)postLoginFormWithUsername:(NSString *)username + password:(NSString *)password + authenticityToken:(NSString *)authenticityToken + successBlock:(void(^)(NSString *body))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if([username length] == 0 || [password length] == 0) { + NSString *errorDescription = [NSString stringWithFormat:@"Missing credentials"]; + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterHTMLCannotPostWithoutCredentials userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; + errorBlock(error); + return; + } + + __block STHTTPRequest *r = [STHTTPRequest requestWithURLString:@"https://twitter.com/sessions"]; + + r.POSTDictionary = @{@"authenticity_token" : authenticityToken, + @"session[username_or_email]" : username, + @"session[password]" : password, + @"remember_me" : @"1", + @"commit" : @"Sign in"}; + + r.completionBlock = ^(NSDictionary *headers, NSString *body) { + successBlock(body); + }; + + r.errorBlock = ^(NSError *error) { + errorBlock(error); + }; + + [r startAsynchronous]; +} + +- (void)getAuthorizeFormAtURL:(NSURL *)url successBlock:(void(^)(NSString *authenticityToken, NSString *oauthToken))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + + STHTTPRequest *r = [STHTTPRequest requestWithURL:url]; + + r.completionBlock = ^(NSDictionary *headers, NSString *body) { + /* +
+ + + */ + + NSError *error1 = nil; + NSString *authenticityToken = [body st_firstMatchWithRegex:@"" error:&error1]; + + if(authenticityToken == nil) { + errorBlock(error1); + return; + } + + /**/ + + NSError *error2 = nil; + + NSString *oauthToken = [body st_firstMatchWithRegex:@"" error:&error2]; + + if(oauthToken == nil) { + errorBlock(error2); + return; + } + + /**/ + + successBlock(authenticityToken, oauthToken); + }; + + r.errorBlock = ^(NSError *error) { + errorBlock(error); + }; + + [r startAsynchronous]; +} + +- (void)postAuthorizeFormResultsAtURL:(NSURL *)url authenticityToken:(NSString *)authenticityToken oauthToken:(NSString *)oauthToken successBlock:(void(^)(NSString *PIN))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + + STHTTPRequest *r = [STHTTPRequest requestWithURL:url]; + + r.POSTDictionary = @{@"authenticity_token" : authenticityToken, + @"oauth_token" : oauthToken}; + + r.completionBlock = ^(NSDictionary *headers, NSString *body) { + + NSError *error = nil; + NSString *pin = [body st_firstMatchWithRegex:@"(\\d+)" error:&error]; + + if(pin == nil) { + errorBlock(error); + return; + } + + successBlock(pin); + }; + + r.errorBlock = ^(NSError *error) { + errorBlock(error); + }; + + [r startAsynchronous]; +} + +@end + diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.h new file mode 100644 index 0000000..fbbd6ad --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.h @@ -0,0 +1,92 @@ +// +// STTwitterRequest.h +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/5/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import +#import "STTwitterProtocol.h" + +/* + Based on the following documentation + http://oauth.net/core/1.0/ + https://dev.twitter.com/docs/auth/authorizing-request + https://dev.twitter.com/docs/auth/implementing-sign-twitter + https://dev.twitter.com/docs/auth/creating-signature + https://dev.twitter.com/docs/api/1/post/oauth/request_token + https://dev.twitter.com/docs/oauth/xauth + ... + */ + +extern NS_ENUM(NSUInteger, STTwitterOAuthErrorCode) { + STTwitterOAuthCannotPostAccessTokenRequestWithoutPIN = 0, + STTwitterOAuthBadCredentialsOrConsumerTokensNotXAuthEnabled +}; + +@interface STTwitterOAuth : NSObject + +@property (nonatomic) NSTimeInterval timeoutInSeconds; + ++ (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret; + ++ (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken + oauthTokenSecret:(NSString *)oauthTokenSecret; + ++ (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password; + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock +authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize + forceLogin:(NSNumber *)forceLogin // optional, default @(NO) + screenName:(NSString *)screenName // optional, default nil + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)signRequest:(STHTTPRequest *)r isMediaUpload:(BOOL)isMediaUpload oauthCallback:(NSString *)oauthCallback; + +// convenience +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock; + + +- (void)postAccessTokenRequestWithPIN:(NSString *)pin + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)postXAuthAccessTokenRequestWithUsername:(NSString *)username + password:(NSString *)password + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// reverse auth phase 1 +- (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// useful for the so-called 'OAuth Echo' https://dev.twitter.com/twitter-kit/ios/oauth-echo + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; + +@end + +@interface NSString (STTwitterOAuth) ++ (NSString *)st_random32Characters; +- (NSString *)st_signHmacSHA1WithKey:(NSString *)key; +- (NSDictionary *)st_parametersDictionary; +- (NSString *)st_urlEncodedString; +@end + +@interface NSURL (STTwitterOAuth) +- (NSString *)st_normalizedForOauthSignatureString; +- (NSArray *)st_rawGetParametersDictionaries; +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.m new file mode 100644 index 0000000..6d2d6ee --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOAuth.m @@ -0,0 +1,768 @@ +// +// STTwitterRequest.m +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/5/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import "STTwitterOAuth.h" +#import "STHTTPRequest.h" +#import "NSString+STTwitter.h" +#import "STHTTPRequest+STTwitter.h" + +#include + +#if DEBUG +# define STLog(...) NSLog(__VA_ARGS__) +#else +# define STLog(...) +#endif + +@interface NSData (Base64) +- (NSString *)base64Encoding; // private API +@end + +@interface STTwitterOAuth () + +@property (nonatomic, retain) NSString *username; +@property (nonatomic, retain) NSString *password; + +@property (nonatomic, retain) NSString *oauthConsumerName; +@property (nonatomic, retain) NSString *oauthConsumerKey; +@property (nonatomic, retain) NSString *oauthConsumerSecret; + +@property (nonatomic, retain) NSString *oauthRequestToken; +@property (nonatomic, retain) NSString *oauthRequestTokenSecret; + +@property (nonatomic, retain) NSString *oauthAccessToken; +@property (nonatomic, retain) NSString *oauthAccessTokenSecret; + +@property (nonatomic, retain) NSString *testOauthNonce; +@property (nonatomic, retain) NSString *testOauthTimestamp; + +@end + +@implementation STTwitterOAuth + ++ (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret { + + STTwitterOAuth *to = [[STTwitterOAuth alloc] init]; + + to.oauthConsumerName = consumerName; + to.oauthConsumerKey = consumerKey; + to.oauthConsumerSecret = consumerSecret; + + return to; +} + ++ (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + oauthToken:(NSString *)oauthToken + oauthTokenSecret:(NSString *)oauthTokenSecret { + + STTwitterOAuth *to = [self twitterOAuthWithConsumerName:consumerName consumerKey:consumerKey consumerSecret:consumerSecret]; + + to.oauthAccessToken = oauthToken; + to.oauthAccessTokenSecret = oauthTokenSecret; + + return to; +} + ++ (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName + consumerKey:(NSString *)consumerKey + consumerSecret:(NSString *)consumerSecret + username:(NSString *)username + password:(NSString *)password { + + STTwitterOAuth *to = [self twitterOAuthWithConsumerName:consumerName consumerKey:consumerKey consumerSecret:consumerSecret]; + + to.username = username; + to.password = password; + + return to; +} + ++ (NSArray *)encodedParametersDictionaries:(NSArray *)parameters { + + NSMutableArray *encodedParameters = [NSMutableArray array]; + + for(NSDictionary *d in parameters) { + + NSString *key = [[d allKeys] lastObject]; + NSString *value = [[d allValues] lastObject]; + + NSString *encodedKey = [key st_urlEncodedString]; + NSString *encodedValue = [value st_urlEncodedString]; + + [encodedParameters addObject:@{encodedKey : encodedValue}]; + } + + return encodedParameters; +} + ++ (NSString *)stringFromParametersDictionaries:(NSArray *)parametersDictionaries { + + NSMutableArray *parameters = [NSMutableArray array]; + + for(NSDictionary *d in parametersDictionaries) { + + NSString *encodedKey = [[d allKeys] lastObject]; + NSString *encodedValue = [[d allValues] lastObject]; + + NSString *s = [NSString stringWithFormat:@"%@=\"%@\"", encodedKey, encodedValue]; + + [parameters addObject:s]; + } + + return [parameters componentsJoinedByString:@", "]; +} + ++ (NSString *)oauthHeaderValueWithParameters:(NSArray *)parametersDictionaries { + + NSArray *encodedParametersDictionaries = [self encodedParametersDictionaries:parametersDictionaries]; + + NSString *encodedParametersString = [self stringFromParametersDictionaries:encodedParametersDictionaries]; + + NSString *headerValue = [NSString stringWithFormat:@"OAuth %@", encodedParametersString]; + + return headerValue; +} + ++ (NSArray *)parametersDictionariesSortedByKey:(NSArray *)parametersDictionaries { + + return [parametersDictionaries sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSDictionary *d1 = (NSDictionary *)obj1; + NSDictionary *d2 = (NSDictionary *)obj2; + + NSString *key1 = [[d1 allKeys] lastObject]; + NSString *key2 = [[d2 allKeys] lastObject]; + + return [key1 compare:key2]; + }]; + +} + +- (NSString *)consumerName { + return _oauthConsumerName; +} + +- (NSString *)loginTypeDescription { + return @"OAuth"; +} + +- (NSString *)oauthNonce { + if(_testOauthNonce) return _testOauthNonce; + + return [NSString st_random32Characters]; +} + ++ (NSString *)signatureBaseStringWithHTTPMethod:(NSString *)httpMethod url:(NSURL *)url allParametersUnsorted:(NSArray *)parameters { + NSMutableArray *allParameters = [NSMutableArray arrayWithArray:parameters]; + + NSArray *encodedParametersDictionaries = [self encodedParametersDictionaries:allParameters]; + + NSArray *sortedEncodedParametersDictionaries = [self parametersDictionariesSortedByKey:encodedParametersDictionaries]; + + /**/ + + NSMutableArray *encodedParameters = [NSMutableArray array]; + + for(NSDictionary *d in sortedEncodedParametersDictionaries) { + NSString *encodedKey = [[d allKeys] lastObject]; + NSString *encodedValue = [[d allValues] lastObject]; + + NSString *s = [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue]; + + [encodedParameters addObject:s]; + } + + NSString *encodedParametersString = [encodedParameters componentsJoinedByString:@"&"]; + + NSString *signatureBaseString = [NSString stringWithFormat:@"%@&%@&%@", + [httpMethod uppercaseString], + [[url st_normalizedForOauthSignatureString] st_urlEncodedString], + [encodedParametersString st_urlEncodedString]]; + + return signatureBaseString; +} + ++ (NSString *)oauthSignatureWithHTTPMethod:(NSString *)httpMethod url:(NSURL *)url parameters:(NSArray *)parameters consumerSecret:(NSString *)consumerSecret tokenSecret:(NSString *)tokenSecret { + /* + The oauth_signature parameter contains a value which is generated by running all of the other request parameters and two secret values through a signing algorithm. The purpose of the signature is so that Twitter can verify that the request has not been modified in transit, verify the application sending the request, and verify that the application has authorization to interact with the user's account. + https://dev.twitter.com/docs/auth/creating-signature + */ + + NSString *signatureBaseString = [[self class] signatureBaseStringWithHTTPMethod:httpMethod url:url allParametersUnsorted:parameters]; + + /* + Note that there are some flows, such as when obtaining a request token, where the token secret is not yet known. In this case, the signing key should consist of the percent encoded consumer secret followed by an ampersand character '&'. + */ + + NSString *encodedConsumerSecret = [consumerSecret st_urlEncodedString]; + NSString *encodedTokenSecret = [tokenSecret st_urlEncodedString]; + + NSString *signingKey = [NSString stringWithFormat:@"%@&", encodedConsumerSecret]; + + if(encodedTokenSecret) { + signingKey = [signingKey stringByAppendingString:encodedTokenSecret]; + } + + NSString *oauthSignature = [signatureBaseString st_signHmacSHA1WithKey:signingKey]; + + return oauthSignature; +} + +- (void)verifyCredentialsLocallyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + successBlock(nil, nil); // no local check +} + +- (void)verifyCredentialsRemotelyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if(_username && _password) { + + [self postXAuthAccessTokenRequestWithUsername:_username password:_password successBlock:^(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName) { + successBlock(screenName, userID); + } errorBlock:^(NSError *error) { + errorBlock(error); + }]; + + } else { + + [self fetchResource:@"account/verify_credentials.json" + HTTPMethod:@"GET" + baseURLString:@"https://api.twitter.com/1.1" + parameters:nil + oauthCallback:nil + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + + if([response isKindOfClass:[NSDictionary class]] == NO) { + NSString *errorDescription = [NSString stringWithFormat:@"Expected dictionary, found %@", response]; + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; + errorBlock(error); + return; + } + + NSDictionary *dict = response; + successBlock(dict[@"screen_name"], dict[@"id_str"]); + + } errorBlock:^(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; + } +} + +- (NSString *)oauthSignatureMethod { + return @"HMAC-SHA1"; +} + +- (NSString *)oauthTimestamp { + /* + The oauth_timestamp parameter indicates when the request was created. This value should be the number of seconds since the Unix epoch at the point the request is generated, and should be easily generated in most programming languages. Twitter will reject requests which were created too far in the past, so it is important to keep the clock of the computer generating requests in sync with NTP. + */ + + if(_testOauthTimestamp) return _testOauthTimestamp; + + NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]; + + return [NSString stringWithFormat:@"%d", (int)timeInterval]; +} + +- (NSString *)oauthVersion { + return @"1.0"; +} + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize forceLogin:(NSNumber *)forceLogin screenName:(NSString *)screenName oauthCallback:(NSString *)oauthCallback errorBlock:(void(^)(NSError *error))errorBlock { + + NSString *theOAuthCallback = [oauthCallback length] ? oauthCallback : @"oob"; // out of band, ie PIN instead of redirect + + __weak typeof(self) weakSelf = self; + + [self fetchResource:@"oauth/request_token" + HTTPMethod:@"POST" + baseURLString:@"https://api.twitter.com" + parameters:@{} + oauthCallback:theOAuthCallback + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id body) { + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + NSMutableDictionary *md = [[body st_parametersDictionary] mutableCopy]; + + if([forceLogin boolValue]) md[@"force_login"] = @"1"; + if(screenName) md[@"screen_name"] = screenName; + + // + + NSMutableArray *parameters = [NSMutableArray array]; + + [md enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSString *s = [NSString stringWithFormat:@"%@=%@", key, obj]; + [parameters addObject:s]; + }]; + + NSString *parameterString = [parameters componentsJoinedByString:@"&"]; + + NSString *authenticateOrAuthorizeString = authenticateInsteadOfAuthorize ? @"authenticate" : @"authorize"; + + NSString *urlString = [NSString stringWithFormat:@"https://api.twitter.com/oauth/%@?%@", authenticateOrAuthorizeString, parameterString]; + + // + + NSURL *url = [NSURL URLWithString:urlString]; + + strongSelf.oauthRequestToken = md[@"oauth_token"]; + strongSelf.oauthRequestTokenSecret = md[@"oauth_token_secret"]; // unused + + successBlock(url, strongSelf.oauthRequestToken); + + } errorBlock:^(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock oauthCallback:(NSString *)oauthCallback errorBlock:(void(^)(NSError *error))errorBlock { + [self postTokenRequest:successBlock authenticateInsteadOfAuthorize:NO forceLogin:nil screenName:nil oauthCallback:oauthCallback errorBlock:errorBlock]; +} + +- (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + + [self fetchResource:@"oauth/request_token" + HTTPMethod:@"POST" + baseURLString:@"https://api.twitter.com" + parameters:@{@"x_auth_mode" : @"reverse_auth"} + oauthCallback:nil + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id body) { + + successBlock(body); + + } errorBlock:^(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +- (void)postXAuthAccessTokenRequestWithUsername:(NSString *)username + password:(NSString *)password + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSDictionary *d = @{@"x_auth_username" : username, + @"x_auth_password" : password, + @"x_auth_mode" : @"client_auth"}; + + + __weak typeof(self) weakSelf = self; + + [self postResource:@"oauth/access_token" + baseURLString:@"https://api.twitter.com" + parameters:d + successBlock:^(STHTTPRequest *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSString *body) { + NSDictionary *dict = [body st_parametersDictionary]; + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + // https://api.twitter.com/oauth/authorize?oauth_token=OAUTH_TOKEN&oauth_token_secret=OAUTH_TOKEN_SECRET&user_id=USER_ID&screen_name=SCREEN_NAME + + self.oauthAccessToken = dict[@"oauth_token"]; + self.oauthAccessTokenSecret = dict[@"oauth_token_secret"]; + + successBlock(strongSelf.oauthAccessToken, strongSelf.oauthAccessTokenSecret, dict[@"user_id"], dict[@"screen_name"]); + } errorBlock:^(STHTTPRequest *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + if([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorUserCancelledAuthentication) { + NSError *xAuthNotEnabledError = [NSError errorWithDomain:NSStringFromClass([strongSelf class]) + code:STTwitterOAuthBadCredentialsOrConsumerTokensNotXAuthEnabled + userInfo:@{NSLocalizedDescriptionKey : @"Bad credentials, or tokens not xAuth enabled."}]; + errorBlock(xAuthNotEnabledError); + return; + } + + errorBlock(error); + }]; +} + +- (void)postAccessTokenRequestWithPIN:(NSString *)pin + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + if([pin length] == 0) { + errorBlock([NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterOAuthCannotPostAccessTokenRequestWithoutPIN userInfo:@{NSLocalizedDescriptionKey : @"PIN needed"}]); + return; + } + + //NSParameterAssert(pin); + + NSDictionary *d = @{@"oauth_verifier" : pin}; + + __weak typeof(self) weakSelf = self; + + [self postResource:@"oauth/access_token" + baseURLString:@"https://api.twitter.com" + parameters:d + successBlock:^(STHTTPRequest *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSString *body) { + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + NSDictionary *dict = [body st_parametersDictionary]; + + // https://api.twitter.com/oauth/authorize?oauth_token=OAUTH_TOKEN&oauth_token_secret=OAUTH_TOKEN_SECRET&user_id=USER_ID&screen_name=SCREEN_NAME + + strongSelf.oauthAccessToken = dict[@"oauth_token"]; + strongSelf.oauthAccessTokenSecret = dict[@"oauth_token_secret"]; + + successBlock(strongSelf.oauthAccessToken, strongSelf.oauthAccessTokenSecret, dict[@"user_id"], dict[@"screen_name"]); + + } errorBlock:^(STHTTPRequest *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); + }]; +} + +- (void)signRequest:(STHTTPRequest *)r isMediaUpload:(BOOL)isMediaUpload oauthCallback:(NSString *)oauthCallback { + NSParameterAssert(_oauthConsumerKey); + NSParameterAssert(_oauthConsumerSecret); + + NSMutableArray *oauthParameters = [NSMutableArray arrayWithObjects: + @{@"oauth_consumer_key" : [self oauthConsumerKey]}, + @{@"oauth_nonce" : [self oauthNonce]}, + @{@"oauth_signature_method" : [self oauthSignatureMethod]}, + @{@"oauth_timestamp" : [self oauthTimestamp]}, + @{@"oauth_version" : [self oauthVersion]}, nil]; + + if([oauthCallback length]) [oauthParameters addObject:@{@"oauth_callback" : oauthCallback}]; + + if(_oauthAccessToken) { // missing while authenticating with XAuth + [oauthParameters addObject:@{@"oauth_token" : [self oauthAccessToken]}]; + } else if(_oauthRequestToken) { + [oauthParameters addObject:@{@"oauth_token" : [self oauthRequestToken]}]; + } + + NSMutableArray *oauthAndPOSTParameters = [oauthParameters mutableCopy]; + + if(r.POSTDictionary) { + [r.POSTDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [oauthAndPOSTParameters addObject:@{ key : obj }]; + }]; + } + + // "In the HTTP request the parameters are URL encoded, but you should collect the raw values." + // https://dev.twitter.com/docs/auth/creating-signature + + NSMutableArray *oauthAndPOSTandGETParameters = [[r.url st_rawGetParametersDictionaries] mutableCopy]; + [oauthAndPOSTandGETParameters addObjectsFromArray:oauthAndPOSTParameters]; + + [r.GETDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSDictionary *d = @{key:obj}; + [oauthAndPOSTandGETParameters addObject:d]; + }]; + + NSString *signature = [[self class] oauthSignatureWithHTTPMethod:r.HTTPMethod + url:r.url + parameters:isMediaUpload ? oauthParameters : oauthAndPOSTandGETParameters + consumerSecret:_oauthConsumerSecret + tokenSecret:_oauthAccessTokenSecret]; + + [oauthParameters addObject:@{@"oauth_signature" : signature}]; + + NSString *s = [[self class] oauthHeaderValueWithParameters:oauthParameters]; + + [r setHeaderWithName:@"Authorization" value:s]; +} + +- (void)signRequest:(STHTTPRequest *)r isMediaUpload:(BOOL)isMediaUpload { + [self signRequest:r isMediaUpload:isMediaUpload oauthCallback:nil]; +} + +- (void)signRequest:(STHTTPRequest *)r { + [self signRequest:r isMediaUpload:NO]; +} + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials { + NSString *verifyCredentialsURLString = @"https://api.twitter.com/1.1/account/verify_credentials.json"; + + STHTTPRequest *r = [STHTTPRequest requestWithURLString:verifyCredentialsURLString]; + [self signRequest:r]; + NSString *authorization = [r.requestHeaders valueForKey:@"Authorization"]; + + if(authorization == nil) return nil; + + return @{@"X-Auth-Service-Provider" : verifyCredentialsURLString, + @"X-Verify-Credentials-Authorization" : authorization}; +} + +- (NSObject *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *request, NSData *data))progressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + return [self fetchResource:resource + HTTPMethod:HTTPMethod + baseURLString:baseURLString + parameters:params + oauthCallback:nil + uploadProgressBlock:uploadProgressBlock + downloadProgressBlock:progressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +- (STHTTPRequest *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + oauthCallback:(NSString *)oauthCallback + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(STHTTPRequest *r, NSData *data))downloadProgressBlock + successBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + if([baseURLString hasSuffix:@"/"]) { + baseURLString = [baseURLString substringToIndex:[baseURLString length]-1]; + } + + NSString *urlString = [NSString stringWithFormat:@"%@/%@", baseURLString, resource]; + + __block __weak STHTTPRequest *wr = nil; + STHTTPRequest *r = [STHTTPRequest twitterRequestWithURLString:urlString + HTTPMethod:HTTPMethod + timeoutInSeconds:_timeoutInSeconds + stTwitterUploadProgressBlock:uploadProgressBlock + stTwitterDownloadProgressBlock:^(NSData *data, NSUInteger totalBytesReceived, long long totalBytesExpectedToReceive) { + if(downloadProgressBlock) downloadProgressBlock(wr, data); + } stTwitterSuccessBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json) { + successBlock(wr, requestHeaders, responseHeaders, json); + } stTwitterErrorBlock:^(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(wr, requestHeaders, responseHeaders, error); + }]; + wr = r; + + r.HTTPMethod = HTTPMethod; + + NSString *postKey = [params valueForKey:kSTPOSTDataKey]; + NSData *postData = [params valueForKey:postKey];; + + if([HTTPMethod isEqualToString:@"GET"]) { + r.GETDictionary = params; + [self signRequest:r]; + } else { + // https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media + + r.POSTDictionary = params; + + NSString *postMediaFileName = [params valueForKey:kSTPOSTMediaFileNameKey]; + + NSMutableDictionary *mutableParams = [params mutableCopy]; + [mutableParams removeObjectForKey:kSTPOSTDataKey]; + [mutableParams removeObjectForKey:kSTPOSTMediaFileNameKey]; + if(postData) { + [mutableParams removeObjectForKey:postKey]; + + NSString *filename = postMediaFileName ? postMediaFileName : @"media.jpg"; + + [r addDataToUpload:postData parameterName:postKey mimeType:@"application/octet-stream" fileName:filename]; + } + + [self signRequest:r isMediaUpload:(postData != nil) oauthCallback:oauthCallback]; + + // POST parameters must not be encoded while posting media, or spaces will appear as %20 in the status + r.encodePOSTDictionary = (postData == nil); + + r.POSTDictionary = mutableParams ? mutableParams : @{}; + } + + [r startAsynchronous]; + + return r; +} + +// convenience +- (STHTTPRequest *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString // no trailing slash + parameters:(NSDictionary *)params + progressBlock:(void(^)(STHTTPRequest *r, NSData *data))progressBlock + successBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + return [self fetchResource:resource + HTTPMethod:@"POST" + baseURLString:baseURLString + parameters:params + oauthCallback:nil + uploadProgressBlock:nil + downloadProgressBlock:progressBlock + successBlock:successBlock + errorBlock:errorBlock]; +} + +// convenience +- (STHTTPRequest *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString // no trailing slash + parameters:(NSDictionary *)params + oauthCallback:(NSString *)oauthCallback + successBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + return [self fetchResource:resource + HTTPMethod:@"POST" + baseURLString:baseURLString + parameters:params + oauthCallback:oauthCallback + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:successBlock + errorBlock:errorBlock]; +} + +// convenience +- (STHTTPRequest *)postResource:(NSString *)resource + baseURLString:(NSString *)baseURLString // no trailing slash + parameters:(NSDictionary *)params + successBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(STHTTPRequest *r, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + return [self fetchResource:resource + HTTPMethod:@"POST" + baseURLString:baseURLString + parameters:params + oauthCallback:nil + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:successBlock + errorBlock:errorBlock]; +} + +@end + +@implementation NSURL (STTwitterOAuth) + +- (NSArray *)st_rawGetParametersDictionaries { + + NSString *q = [self query]; + + NSArray *getParameters = [q componentsSeparatedByString:@"&"]; + + NSMutableArray *ma = [NSMutableArray array]; + + for(NSString *s in getParameters) { + NSArray *kv = [s componentsSeparatedByString:@"="]; + NSAssert([kv count] == 2, @"-- bad length"); + if([kv count] != 2) continue; + NSString *value = [kv[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; // use raw parameters for signing + [ma addObject:@{kv[0] : value}]; + } + + return ma; +} + +- (NSString *)st_normalizedForOauthSignatureString { + return [NSString stringWithFormat:@"%@://%@%@", [self scheme], [self host], [self path]]; +} + +@end + +@implementation NSString (STTwitterOAuth) + ++ (NSString *)st_randomString { + CFUUIDRef cfuuid = CFUUIDCreate (kCFAllocatorDefault); + NSString *uuid = (__bridge_transfer NSString *)(CFUUIDCreateString (kCFAllocatorDefault, cfuuid)); + CFRelease (cfuuid); + return uuid; +} + ++ (NSString *)st_random32Characters { + NSString *randomString = [self st_randomString]; + + NSAssert([randomString length] >= 32, @""); + + return [randomString substringToIndex:32]; +} + +- (NSString *)st_signHmacSHA1WithKey:(NSString *)key { + + unsigned char buf[CC_SHA1_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA1, [key UTF8String], [key length], [self UTF8String], [self length], buf); + NSData *data = [NSData dataWithBytes:buf length:CC_SHA1_DIGEST_LENGTH]; + return [data base64Encoding]; +} + +- (NSDictionary *)st_parametersDictionary { + + NSArray *parameters = [self componentsSeparatedByString:@"&"]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + for(NSString *parameter in parameters) { + NSArray *keyValue = [parameter componentsSeparatedByString:@"="]; + if([keyValue count] != 2) { + continue; + } + + [md setObject:keyValue[1] forKey:keyValue[0]]; + } + + return md; +} + +- (NSString *)st_urlEncodedString { + // https://dev.twitter.com/docs/auth/percent-encoding-parameters + // http://tools.ietf.org/html/rfc3986#section-2.1 + + return [self st_stringByAddingRFC3986PercentEscapesUsingEncoding:NSUTF8StringEncoding]; +} + +@end + +@implementation NSData (STTwitterOAuth) + +- (NSString *)base64EncodedString { + +#if TARGET_OS_IPHONE + return [self base64Encoding]; // private API +#else + + CFDataRef retval = NULL; + SecTransformRef encodeTrans = SecEncodeTransformCreate(kSecBase64Encoding, NULL); + if (encodeTrans == NULL) return nil; + + if (SecTransformSetAttribute(encodeTrans, kSecTransformInputAttributeName, (__bridge CFTypeRef)self, NULL)) { + retval = SecTransformExecute(encodeTrans, NULL); + } + CFRelease(encodeTrans); + + NSString *s = [[NSString alloc] initWithData:(__bridge NSData *)retval encoding:NSUTF8StringEncoding]; + + if(retval) { + CFRelease(retval); + } + + return s; + +#endif + +} +@end + diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.h new file mode 100644 index 0000000..fb302d6 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.h @@ -0,0 +1,35 @@ +// +// STTwitterOS.h +// STTwitter +// +// Created by Nicolas Seriot on 5/1/10. +// Copyright 2010 seriot.ch. All rights reserved. +// + +#import +#import "STTwitterProtocol.h" + +extern NS_ENUM(NSUInteger, STTwitterOSErrorCode) { + STTwitterOSSystemCannotAccessTwitter = 0, + STTwitterOSCannotFindTwitterAccount, + STTwitterOSUserDeniedAccessToTheirAccounts, + STTwitterOSNoTwitterAccountIsAvailable +}; + +@class ACAccount; + +@interface STTwitterOS : NSObject + +@property (nonatomic) NSTimeInterval timeoutInSeconds; + ++ (instancetype)twitterAPIOSWithAccount:(ACAccount *)account; ++ (instancetype)twitterAPIOSWithFirstAccount; + +- (NSString *)username; +- (NSString *)userID; + +// useful for the so-called 'OAuth Echo' https://dev.twitter.com/twitter-kit/ios/oauth-echo + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.m new file mode 100644 index 0000000..27c8945 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOS.m @@ -0,0 +1,397 @@ +// +// STTwitterOS.m +// STTwitter +// +// Created by Nicolas Seriot on 5/1/10. +// Copyright 2010 seriot.ch. All rights reserved. +// + +#import "STTwitterOS.h" +#import "NSString+STTwitter.h" +#import "STTwitterOSRequest.h" +#import +#import +#import "NSError+STTwitter.h" +#if TARGET_OS_IPHONE +#import // iOS 5 +#endif + +@interface ACAccount (STTwitterOS) +- (NSString*)st_userID; // private API +@end + +@interface STTwitterOS () +@property (nonatomic, retain) ACAccountStore *accountStore; // the ACAccountStore must be kept alive for as long as we need an ACAccount instance, see WWDC 2011 Session 124 for more info +@property (nonatomic, retain) ACAccount *account; // if nil, will be set to first account available +@end + +@implementation STTwitterOS + +- (instancetype)init { + self = [super init]; + + self.accountStore = [[ACAccountStore alloc] init]; + + return self; +} + +- (instancetype)initWithAccount:(ACAccount *) account { + self = [super init]; + self.accountStore = [[ACAccountStore alloc] init]; + self.account = account; + return self; +} + ++ (instancetype)twitterAPIOSWithAccount:(ACAccount *)account { + return [[self alloc] initWithAccount:account]; +} + ++ (instancetype)twitterAPIOSWithFirstAccount { + return [self twitterAPIOSWithAccount:nil]; +} + +- (NSString *)username { + return self.account.username; +} + +- (NSString *)userID { + return [self.account st_userID]; +} + +- (NSString *)consumerName { +#if TARGET_OS_IPHONE + return @"iOS"; +#else + return @"OS X"; +#endif +} + +- (NSString *)loginTypeDescription { + return @"System"; +} + +- (void)verifyCredentialsRemotelyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + __weak typeof(self) weakSelf = self; + + [self fetchResource:@"account/verify_credentials.json" + HTTPMethod:@"GET" + baseURLString:@"https://api.twitter.com/1.1" + parameters:nil + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + + if([response isKindOfClass:[NSDictionary class]] == NO) { + NSString *errorDescription = [NSString stringWithFormat:@"Expected dictionary, found %@", response]; + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; + errorBlock(error); + return; + } + + typeof(self) strongSelf = weakSelf; + if(strongSelf == nil) return; + + NSDictionary *dict = response; + successBlock(dict[@"screen_name"], dict[@"id_str"]); + } errorBlock:^(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + + // add recovery suggestion if we can + if([[error domain] isEqualToString:kSTTwitterTwitterErrorDomain] && ([error code] == 220)) { + NSMutableDictionary *extendedUserInfo = [[error userInfo] mutableCopy]; + extendedUserInfo[NSLocalizedRecoverySuggestionErrorKey] = @"Consider entering the Twitter credentials again in OS Settings."; + NSError *extendedError = [NSError errorWithDomain:[error domain] code:[error code] userInfo:extendedUserInfo]; + errorBlock(extendedError); + return; + } + + errorBlock(error); + }]; +} + + +- (BOOL)hasAccessToTwitter { + +#if !TARGET_OS_IPHONE + return YES; +#else + +#if (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0) { + return [TWTweetComposeViewController canSendTweet]; // iOS 5 + } else { + return [SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]; + } +#else + return [SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]; +#endif + +#endif +} + +- (void)verifyCredentialsLocallyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock errorBlock:(void(^)(NSError *error))errorBlock { + if([self hasAccessToTwitter] == NO) { + NSString *message = @"This system cannot access Twitter."; + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterOSSystemCannotAccessTwitter userInfo:@{NSLocalizedDescriptionKey : message}]; + errorBlock(error); + return; + } + + ACAccountType *accountType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]; + + if(accountType == nil) { + NSString *message = @"Cannot find Twitter account."; + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterOSCannotFindTwitterAccount userInfo:@{NSLocalizedDescriptionKey : message}]; + errorBlock(error); + return; + } + + __weak typeof(self) weakSelf = self; + + ACAccountStoreRequestAccessCompletionHandler accountStoreRequestCompletionHandler = ^(BOOL granted, NSError *error) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + + typeof(self) strongSelf = weakSelf; + + if(strongSelf == nil) return; + + if(granted == NO) { + + if(error) { + errorBlock(error); + return; + } + + NSString *message = @"User denied access to their account(s)."; + NSError *grantError = [NSError errorWithDomain:NSStringFromClass([strongSelf class]) code:STTwitterOSUserDeniedAccessToTheirAccounts userInfo:@{NSLocalizedDescriptionKey : message}]; + errorBlock(grantError); + return; + } + + if(strongSelf.account == nil) { + NSArray *accounts = [strongSelf.accountStore accountsWithAccountType:accountType]; + + // ignore accounts that have no indentifier + // possible workaround for accounts with no password stored + // see https://twittercommunity.com/t/ios-6-twitter-accounts-with-no-password-stored/6183 + NSMutableArray *accountsWithIdentifiers = [NSMutableArray array]; + [accounts enumerateObjectsUsingBlock:^(ACAccount *account, NSUInteger idx, BOOL *stop) { + + NSString *accountID = [account identifier]; + + if([accountID length] > 0) { + [accountsWithIdentifiers addObject:account]; + } else { + NSLog(@"-- ignore account %@ because identifier is empty", account); + } + }]; + + if([accountsWithIdentifiers count] == 0) { + NSString *message = @"No Twitter account available."; + NSError *error = [NSError errorWithDomain:NSStringFromClass([strongSelf class]) code:STTwitterOSNoTwitterAccountIsAvailable userInfo:@{NSLocalizedDescriptionKey : message}]; + errorBlock(error); + return; + } + + strongSelf.account = [accountsWithIdentifiers firstObject]; + } + + successBlock(strongSelf.account.username, [strongSelf.account st_userID]); + }]; + }; + +#if TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0) { + [self.accountStore requestAccessToAccountsWithType:accountType + withCompletionHandler:accountStoreRequestCompletionHandler]; + } else { + [self.accountStore requestAccessToAccountsWithType:accountType + options:NULL + completion:accountStoreRequestCompletionHandler]; + } +#else + [self.accountStore requestAccessToAccountsWithType:accountType + options:NULL + completion:accountStoreRequestCompletionHandler]; +#endif +} + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials { + + // https://api.twitter.com/1.1/account/verify_credentials.json + + STTwitterOSRequest *r = [[STTwitterOSRequest alloc] initWithAPIResource:@"/account/verify_credentials.json" + baseURLString:@"https://api.twitter.com/1.1" + httpMethod:SLRequestMethodGET + parameters:nil + account:self.account + timeoutInSeconds:0 + uploadProgressBlock:nil + streamBlock:nil + completionBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + // + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + // + }]; + + NSURLRequest *preparedURLRequest = [r preparedURLRequest]; + + NSDictionary *headers = [preparedURLRequest allHTTPHeaderFields]; + + NSString *authorization = [headers valueForKey:@"Authorization"]; + + if(authorization == nil) return nil; + + /* + Please note that one should use the URL provided to them by X-Auth-Service-Provider to perform the look up, + not a hard coded value on your servers. Apple iOS5, for example, adds an additional application_id parameter + to all OAuth requests, and its existence should be maintained at each stage of OAuth Echo. + https://dev.twitter.com/oauth/echo + */ + NSString *verifyCredentialsURLString = [[preparedURLRequest URL] description];//@"https://api.twitter.com/1.1/account/verify_credentials.json"; + + return @{@"X-Auth-Service-Provider" : verifyCredentialsURLString, + @"X-Verify-Credentials-Authorization" : authorization}; + + return headers; +} + ++ (SLRequestMethod)slRequestMethodForString:(NSString *)HTTPMethod { + if([HTTPMethod isEqualToString:@"POST"]) return SLRequestMethodPOST; + if([HTTPMethod isEqualToString:@"PUT"]) return SLRequestMethodPUT; + if([HTTPMethod isEqualToString:@"DELETE"]) return SLRequestMethodDELETE; + if([HTTPMethod isEqualToString:@"GET"] == NO) { + NSAssert(NO, @"Unsupported HTTP method"); + } + return SLRequestMethodGET; +} + +- (NSObject *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void (^)(NSObject *request, NSData *data))progressBlock // FIXME: how to handle progressBlock? + successBlock:(void (^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void (^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + NSInteger slRequestMethod = [[self class] slRequestMethodForString:HTTPMethod]; + + NSDictionary *d = params; + + if([HTTPMethod isEqualToString:@"GET"] == NO) { + if (d == nil) d = @{}; + } + + NSString *baseURLStringWithTrailingSlash = baseURLString; + if([baseURLString hasSuffix:@"/"] == NO) { + baseURLStringWithTrailingSlash = [baseURLString stringByAppendingString:@"/"]; + } + + STTwitterOSRequest *r = [[STTwitterOSRequest alloc] initWithAPIResource:resource + baseURLString:baseURLStringWithTrailingSlash + httpMethod:slRequestMethod + parameters:d + account:self.account + timeoutInSeconds:_timeoutInSeconds + uploadProgressBlock:uploadProgressBlock + streamBlock:progressBlock + completionBlock:successBlock + errorBlock:errorBlock]; + [r startRequest]; + + return r; +} + ++ (NSDictionary *)parametersDictionaryFromCommaSeparatedParametersString:(NSString *)s { + + NSArray *parameters = [s componentsSeparatedByString:@", "]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + for(NSString *parameter in parameters) { + // transform k="v" into {'k':'v'} + + NSArray *keyValue = [parameter componentsSeparatedByString:@"="]; + if([keyValue count] != 2) { + continue; + } + + NSString *value = [keyValue[1] stringByReplacingOccurrencesOfString:@"\"" withString:@""]; + + [md setObject:value forKey:keyValue[0]]; + } + + return md; +} + +// TODO: this code is duplicated from STTwitterOAuth ++ (NSDictionary *)parametersDictionaryFromAmpersandSeparatedParameterString:(NSString *)s { + + NSArray *parameters = [s componentsSeparatedByString:@"&"]; + + NSMutableDictionary *md = [NSMutableDictionary dictionary]; + + for(NSString *parameter in parameters) { + NSArray *keyValue = [parameter componentsSeparatedByString:@"="]; + if([keyValue count] != 2) { + continue; + } + + [md setObject:keyValue[1] forKey:keyValue[0]]; + } + + return md; +} + +// reverse auth phase 2 +- (void)postReverseAuthAccessTokenWithAuthenticationHeader:(NSString *)authenticationHeader + successBlock:(void(^)(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock { + + NSAssert(self.account, @"no account is set, try to call -verifyCredentialsWithUserSuccessBlock:errorBlock: first"); + + NSParameterAssert(authenticationHeader); + + NSString *shortHeader = [authenticationHeader stringByReplacingOccurrencesOfString:@"OAuth " withString:@""]; + + NSDictionary *authenticationHeaderDictionary = [[self class] parametersDictionaryFromCommaSeparatedParametersString:shortHeader]; + + NSString *consumerKey = [authenticationHeaderDictionary valueForKey:@"oauth_consumer_key"]; + + NSAssert((consumerKey != nil), @"cannot find out consumerKey"); + + NSDictionary *d = @{@"x_reverse_auth_target" : consumerKey, + @"x_reverse_auth_parameters" : authenticationHeader}; + + [self fetchResource:@"oauth/access_token" + HTTPMethod:@"POST" + baseURLString:@"https://api.twitter.com" + parameters:d + uploadProgressBlock:nil + downloadProgressBlock:nil + successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { + + NSDictionary *d = [[self class] parametersDictionaryFromAmpersandSeparatedParameterString:response]; + + NSString *oAuthToken = [d valueForKey:@"oauth_token"]; + NSString *oAuthTokenSecret = [d valueForKey:@"oauth_token_secret"]; + NSString *userID = [d valueForKey:@"user_id"]; + NSString *screenName = [d valueForKey:@"screen_name"]; + + successBlock(oAuthToken, oAuthTokenSecret, userID, screenName); + } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { + errorBlock(error); // in case of -1012 error, the Twitter account may lack the 'oauth_token' property and need to be setup again in iOS Settings, see http://stackoverflow.com/questions/30307062/access-users-ios-default-twitter-account-ios/30461959#30461959 + }]; +} + +@end + +@implementation ACAccount (STTwitterOS) + +- (NSString*)st_userID +{ + return [self valueForKeyPath:@"properties.user_id"]; +} + +@end \ No newline at end of file diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.h new file mode 100644 index 0000000..4fbd3bd --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.h @@ -0,0 +1,30 @@ +// +// STTwitterOSRequest.h +// STTwitterDemoOSX +// +// Created by Nicolas Seriot on 20/02/14. +// Copyright (c) 2014 Nicolas Seriot. All rights reserved. +// + +#import +#import "STTwitterRequestProtocol.h" + +@class ACAccount; + +@interface STTwitterOSRequest : NSObject + +- (instancetype)initWithAPIResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + httpMethod:(NSInteger)httpMethod + parameters:(NSDictionary *)params + account:(ACAccount *)account + timeoutInSeconds:(NSTimeInterval)timeoutInSeconds + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + streamBlock:(void(^)(NSObject *request, NSData *data))streamBlock + completionBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))completionBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock NS_DESIGNATED_INITIALIZER; + +- (void)startRequest; +- (NSURLRequest *)preparedURLRequest; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.m new file mode 100644 index 0000000..bbf90a4 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterOSRequest.m @@ -0,0 +1,245 @@ +// +// STTwitterOSRequest.m +// STTwitterDemoOSX +// +// Created by Nicolas Seriot on 20/02/14. +// Copyright (c) 2014 Nicolas Seriot. All rights reserved. +// + +#import "STTwitterOSRequest.h" +#import +#import +#if TARGET_OS_IPHONE +#import // iOS 5 +#endif +#import "STHTTPRequest.h" +#import "NSString+STTwitter.h" +#import "NSError+STTwitter.h" + +typedef void (^completion_block_t)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response); +typedef void (^error_block_t)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error); +typedef void (^upload_progress_block_t)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite); +typedef void (^stream_block_t)(NSObject *request, NSData *data); + +@interface STTwitterOSRequest () +@property (nonatomic, copy) completion_block_t completionBlock; +@property (nonatomic, copy) error_block_t errorBlock; +@property (nonatomic, copy) upload_progress_block_t uploadProgressBlock; +@property (nonatomic, copy) stream_block_t streamBlock; +@property (nonatomic, retain) NSURLConnection *connection; +@property (nonatomic, retain) NSHTTPURLResponse *httpURLResponse; // only used with streaming API +@property (nonatomic, retain) NSMutableData *data; // only used with non-streaming API +@property (nonatomic, retain) ACAccount *account; +@property (nonatomic) NSInteger httpMethod; +@property (nonatomic, retain) NSDictionary *params; +@property (nonatomic, retain) NSString *baseURLString; +@property (nonatomic, retain) NSString *resource; +@property (nonatomic) NSTimeInterval timeoutInSeconds; +@end + + +@implementation STTwitterOSRequest + +- (instancetype)initWithAPIResource:(NSString *)resource + baseURLString:(NSString *)baseURLString + httpMethod:(NSInteger)httpMethod + parameters:(NSDictionary *)params + account:(ACAccount *)account + timeoutInSeconds:(NSTimeInterval)timeoutInSeconds + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + streamBlock:(void(^)(NSObject *request, NSData *data))streamBlock + completionBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))completionBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { + + NSAssert(completionBlock, @"completionBlock is missing"); + NSAssert(errorBlock, @"errorBlock is missing"); + + self = [super init]; + + self.resource = resource; + self.baseURLString = baseURLString; + self.httpMethod = httpMethod; + self.params = params; + self.account = account; + self.completionBlock = completionBlock; + self.errorBlock = errorBlock; + self.uploadProgressBlock = uploadProgressBlock; + self.streamBlock = streamBlock; + self.timeoutInSeconds = timeoutInSeconds; + + return self; +} + +- (NSURLRequest *)preparedURLRequest { + NSString *postDataKey = [_params valueForKey:kSTPOSTDataKey]; + NSString *postDataFilename = [_params valueForKey:kSTPOSTMediaFileNameKey]; + NSData *mediaData = [_params valueForKey:postDataKey]; + + NSMutableDictionary *paramsWithoutMedia = [_params mutableCopy]; + if(postDataKey) [paramsWithoutMedia removeObjectForKey:postDataKey]; + [paramsWithoutMedia removeObjectForKey:kSTPOSTDataKey]; + [paramsWithoutMedia removeObjectForKey:kSTPOSTMediaFileNameKey]; + + NSString *urlString = [_baseURLString stringByAppendingString:_resource]; + NSURL *url = [NSURL URLWithString:urlString]; + + id request = nil; + +#if TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) + + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0) { + TWRequestMethod method = (_httpMethod == 0) ? TWRequestMethodGET : TWRequestMethodPOST; + request = [[TWRequest alloc] initWithURL:url parameters:paramsWithoutMedia requestMethod:method]; + } else { + request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:_httpMethod URL:url parameters:paramsWithoutMedia]; + } + +#else + request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:_httpMethod URL:url parameters:paramsWithoutMedia]; +#endif + + [request setAccount:_account]; + + if(mediaData) { + NSString *filename = postDataFilename ? postDataFilename : @"media.jpg"; + [request addMultipartData:mediaData withName:postDataKey type:@"application/octet-stream" filename:filename]; + } + + // we use NSURLConnection because SLRequest doesn't play well with the streaming API + + NSURLRequest *preparedURLRequest = nil; +#if TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0) { + preparedURLRequest = [request signedURLRequest]; + } else { + preparedURLRequest = [request preparedURLRequest]; + } +#else + preparedURLRequest = [request preparedURLRequest]; +#endif + + return preparedURLRequest; +} + +- (void)startRequest { + + NSURLRequest *preparedURLRequest = [self preparedURLRequest]; + + NSMutableURLRequest *mutablePreparedURLRequest = [preparedURLRequest mutableCopy]; + mutablePreparedURLRequest.timeoutInterval = _timeoutInSeconds; + + if (_connection) { + [self cancel]; + } + _connection = [NSURLConnection connectionWithRequest:mutablePreparedURLRequest delegate:self]; + + [_connection start]; +} + +- (void)cancel { + [_connection cancel]; + + NSURLRequest *request = [_connection currentRequest]; + + NSString *s = @"Connection was cancelled."; + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: s}; + NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) + code:kSTHTTPRequestCancellationError + userInfo:userInfo]; + self.errorBlock(self, [self requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], error); +} + +- (NSDictionary *)requestHeadersForRequest:(id)request { + + if([request isKindOfClass:[NSURLRequest class]]) { + return [request allHTTPHeaderFields]; + } + +#if TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0) { + return [[request signedURLRequest] allHTTPHeaderFields]; + } else { + return [[request preparedURLRequest] allHTTPHeaderFields]; + } +#else + return [[request preparedURLRequest] allHTTPHeaderFields]; +#endif +} + +#pragma mark NSURLConnectionDataDelegate + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + + if([response isKindOfClass:[NSHTTPURLResponse class]] == NO) return; + + self.httpURLResponse = (NSHTTPURLResponse *)response; + + self.data = [NSMutableData data]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + + BOOL isStreaming = [[[[connection originalRequest] URL] host] rangeOfString:@"stream"].location != NSNotFound; + + if(isStreaming) { + self.streamBlock(self, data); + } else { + [self.data appendData:data]; + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + + NSURLRequest *request = [connection currentRequest]; + NSDictionary *requestHeaders = [request allHTTPHeaderFields]; + NSDictionary *responseHeaders = [_httpURLResponse allHeaderFields]; + + self.errorBlock(self, requestHeaders, responseHeaders, error); +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + + NSURLRequest *request = [connection currentRequest]; + + if(_data == nil) { + self.errorBlock(self, [self requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], nil); + return; + } + + NSError *error = [NSError st_twitterErrorFromResponseData:_data responseHeaders:[_httpURLResponse allHeaderFields] underlyingError:nil]; + + if(error) { + self.errorBlock(self, [self requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], error); + return; + } + + NSError *jsonError = nil; + id response = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingAllowFragments error:&jsonError]; + + if(response == nil) { + // eg. reverse auth response + // oauth_token=xxx&oauth_token_secret=xxx&user_id=xxx&screen_name=xxx + response = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding]; + } + + if(response) { + self.completionBlock(self, [self requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], response); + } else { + self.errorBlock(self, [self requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], jsonError); + } +} + +- (void)connection:(NSURLConnection *)connection + didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten +totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + if(self.uploadProgressBlock == nil) return; + + // avoid overcommit while posting big images, like 5+ MB + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(queue, ^{ + self.uploadProgressBlock(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + }); +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterProtocol.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterProtocol.h new file mode 100644 index 0000000..1f08b49 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterProtocol.h @@ -0,0 +1,71 @@ +// +// STOAuthProtocol.h +// STTwitterRequests +// +// Created by Nicolas Seriot on 9/18/12. +// Copyright (c) 2012 Nicolas Seriot. All rights reserved. +// + +#import +#import "STTwitterRequestProtocol.h" + +@class STHTTPRequest; + +@protocol STTwitterProtocol + +@property (nonatomic) NSTimeInterval timeoutInSeconds; + +- (void)verifyCredentialsLocallyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)verifyCredentialsRemotelyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (NSObject *)fetchResource:(NSString *)resource + HTTPMethod:(NSString *)HTTPMethod + baseURLString:(NSString *)baseURLString + parameters:(NSDictionary *)params + uploadProgressBlock:(void(^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))uploadProgressBlock + downloadProgressBlock:(void(^)(NSObject *request, NSData *data))progressBlock + successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock + errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; + +- (NSString *)consumerName; +- (NSString *)loginTypeDescription; + +@optional + +- (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock +authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize + forceLogin:(NSNumber *)forceLogin + screenName:(NSString *)screenName + oauthCallback:(NSString *)oauthCallback + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)postAccessTokenRequestWithPIN:(NSString *)pin + successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +- (void)invalidateBearerTokenWithSuccessBlock:(void(^)(id response))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// access tokens are available only with plain OAuth authentication +- (NSString *)oauthAccessToken; +- (NSString *)oauthAccessTokenSecret; + +- (NSString *)bearerToken; + +// reverse auth phase 1, implemented only in STTwitterOAuth +- (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// reverse auth phase 2, implemented only in STTwitterOS +- (void)postReverseAuthAccessTokenWithAuthenticationHeader:(NSString *)authenticationHeader + successBlock:(void(^)(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName))successBlock + errorBlock:(void(^)(NSError *error))errorBlock; + +// 'OAuth Echo' https://dev.twitter.com/twitter-kit/ios/oauth-echo + +- (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterRequestProtocol.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterRequestProtocol.h new file mode 100644 index 0000000..2afc197 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterRequestProtocol.h @@ -0,0 +1,15 @@ +// +// STTwitterRequestProtocol.h +// STTwitterDemoIOS +// +// Created by Yu Sugawara on 2015/03/22. +// Copyright (c) 2015年 Nicolas Seriot. All rights reserved. +// + +#import + +@protocol STTwitterRequestProtocol + +- (void)cancel; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.h new file mode 100644 index 0000000..69881a4 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.h @@ -0,0 +1,33 @@ +// +// STTwitterParser.h +// STTwitterDemoIOS +// +// Created by Yu Sugawara on 2015/03/23. +// Copyright (c) 2015年 Nicolas Seriot. All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, STTwitterStreamJSONType) { + STTwitterStreamJSONTypeTweet, + STTwitterStreamJSONTypeFriendsLists, + STTwitterStreamJSONTypeDelete, + STTwitterStreamJSONTypeScrubGeo, + STTwitterStreamJSONTypeLimit, + STTwitterStreamJSONTypeDisconnect, + STTwitterStreamJSONTypeWarning, + STTwitterStreamJSONTypeEvent, + STTwitterStreamJSONTypeStatusWithheld, + STTwitterStreamJSONTypeUserWithheld, + STTwitterStreamJSONTypeControl, + STTwitterStreamJSONTypeUnsupported, +}; + +extern NSString *NSStringFromSTTwitterStreamJSONType(STTwitterStreamJSONType type); + +@interface STTwitterStreamParser : NSObject + +- (void)parseWithStreamData:(NSData *)data + parsedJSONBlock:(void (^)(NSDictionary *json, STTwitterStreamJSONType type))parsedJsonBlock; + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.m new file mode 100644 index 0000000..e6e8fc0 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/STTwitterStreamParser.m @@ -0,0 +1,138 @@ +// +// STTwitterParser.m +// STTwitterDemoIOS +// +// Created by Yu Sugawara on 2015/03/23. +// Copyright (c) 2015年 Nicolas Seriot. All rights reserved. +// + +#import "STTwitterStreamParser.h" + +NSString *NSStringFromSTTwitterStreamJSONType(STTwitterStreamJSONType type) { + switch (type) { + case STTwitterStreamJSONTypeTweet: + return @"STTwitterStreamJSONTypeTweet"; + case STTwitterStreamJSONTypeFriendsLists: + return @"STTwitterStreamJSONTypeFriendsLists"; + case STTwitterStreamJSONTypeDelete: + return @"STTwitterStreamJSONTypeDelete"; + case STTwitterStreamJSONTypeScrubGeo: + return @"STTwitterStreamJSONTypeScrubGeo"; + case STTwitterStreamJSONTypeLimit: + return @"STTwitterStreamJSONTypeLimit"; + case STTwitterStreamJSONTypeDisconnect: + return @"STTwitterStreamJSONTypeDisconnect"; + case STTwitterStreamJSONTypeWarning: + return @"STTwitterStreamJSONTypeWarning"; + case STTwitterStreamJSONTypeEvent: + return @"STTwitterStreamJSONTypeEvent"; + case STTwitterStreamJSONTypeStatusWithheld: + return @"STTwitterStreamJSONTypeStatusWithheld"; + case STTwitterStreamJSONTypeUserWithheld: + return @"STTwitterStreamJSONTypeUserWithheld"; + case STTwitterStreamJSONTypeControl: + return @"STTwitterStreamJSONTypeControl"; + default: + case STTwitterStreamJSONTypeUnsupported: + return @"STTwitterStreamJSONTypeUnsupported"; + } +} + +static inline BOOL isDigitsOnlyString(NSString *str) { + static NSCharacterSet *__notDigits; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; + }); + return str.length && [str rangeOfCharacterFromSet:__notDigits].location == NSNotFound; +} + +@interface STTwitterStreamParser () + +@property (nonatomic) NSMutableString *receivedMessage; +@property (nonatomic) int bytesExpected; + +@end + +@implementation STTwitterStreamParser + +- (void)parseWithStreamData:(NSData *)data + parsedJSONBlock:(void (^)(NSDictionary *json, STTwitterStreamJSONType type))parsedJsonBlock { + static NSString * const kDelimiter = @"\r\n"; + NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + for (NSString* part in [response componentsSeparatedByString:kDelimiter]) { + + if (self.receivedMessage == nil) { + if (isDigitsOnlyString(part)) { + self.receivedMessage = [NSMutableString string]; + self.bytesExpected = [part intValue]; + } + } else if (self.bytesExpected > 0) { + if (self.receivedMessage.length < self.bytesExpected) { + // Append the data + if (part.length > 0) { + [self.receivedMessage appendString:part]; + } else { + [self.receivedMessage appendString:kDelimiter]; + } + if (self.receivedMessage.length + kDelimiter.length == self.bytesExpected) { + [self.receivedMessage appendString:kDelimiter]; + // Success! + NSError *error = nil; + id json = [NSJSONSerialization JSONObjectWithData:[self.receivedMessage dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingAllowFragments + error:&error]; + if(json == nil) { + NSLog(@"-- error: %@", error); + } + + STTwitterStreamJSONType type = [[self class] streamJSONTypeForJSON:json]; + parsedJsonBlock(json, type); + + // Reset + self.receivedMessage = nil; + self.bytesExpected = 0; + } + } else { + self.receivedMessage = nil; + self.bytesExpected = 0; + } + } else { + self.receivedMessage = nil; + self.bytesExpected = 0; + } + } +} + ++ (STTwitterStreamJSONType)streamJSONTypeForJSON:(id)json { + if ([json isKindOfClass:[NSDictionary class]]) { + if ([json objectForKey:@"source"] && [json objectForKey:@"text"]) { + return STTwitterStreamJSONTypeTweet; + } else if ([json objectForKey:@"friends"] || [json objectForKey:@"friends_str"]) { + return STTwitterStreamJSONTypeFriendsLists; + } else if ([json objectForKey:@"delete"]) { + return STTwitterStreamJSONTypeDelete; + } else if ([json objectForKey:@"scrub_geo"]) { + return STTwitterStreamJSONTypeScrubGeo; + } else if ([json objectForKey:@"limit"]) { + return STTwitterStreamJSONTypeLimit; + } else if ([json objectForKey:@"disconnect"]) { + return STTwitterStreamJSONTypeDisconnect; + } else if ([json objectForKey:@"warning"]) { + return STTwitterStreamJSONTypeWarning; + } else if ([json objectForKey:@"event"]) { + return STTwitterStreamJSONTypeEvent; // may be 'Event' or 'User update' + } else if ([json objectForKey:@"status_withheld"]) { + return STTwitterStreamJSONTypeStatusWithheld; + } else if ([json objectForKey:@"user_withheld"]) { + return STTwitterStreamJSONTypeUserWithheld; + } else if ([json objectForKey:@"control"]) { + return STTwitterStreamJSONTypeControl; + } + } + + return STTwitterStreamJSONTypeUnsupported; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.h new file mode 100644 index 0000000..583a301 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.h @@ -0,0 +1,43 @@ +// +// BAVPlistNode.h +// Plistorious +// +// Created by Bavarious on 01/10/2013. +// Copyright (c) 2013 No Organisation. All rights reserved. +// + +/* +The MIT License (MIT) + +Copyright (c) 2013 Bavarious + +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 + +@interface BAVPlistNode : NSObject +@property (nonatomic, copy) NSString *key; +@property (nonatomic, copy) NSString *type; +@property (nonatomic, copy) NSObject *value; +@property (nonatomic, copy) NSArray *children; +@property (nonatomic, assign, getter = isCollection) bool collection; + ++ (instancetype)plistNodeFromObject:(id)object key:(NSString *)key; +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.m new file mode 100644 index 0000000..db23b8c --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/BAVPlistNode.m @@ -0,0 +1,88 @@ +// +// BAVPlistNode.m +// Plistorious +// +// Created by Bavarious on 01/10/2013. +// Copyright (c) 2013 No Organisation. All rights reserved. +// + +#import "BAVPlistNode.h" + + +static NSString *typeForObject(id object) { + return ([object isKindOfClass:[NSArray class]] ? @"Array" : + [object isKindOfClass:[NSDictionary class]] ? @"Dictionary" : + [object isKindOfClass:[NSString class]] ? @"String" : + [object isKindOfClass:[NSData class]] ? @"Data" : + [object isKindOfClass:[NSDate class]] ? @"Date" : + object == (id)kCFBooleanTrue || object == (id)kCFBooleanFalse ? @"Boolean" : + [object isKindOfClass:[NSNumber class]] ? @"Number" : + [object isKindOfClass:[NSNull class]] ? @"Null" : + @"Unknown"); +} + +static NSString *formatItemCount(NSUInteger count) { + return (count == 1 ? @"1 item" : [NSString stringWithFormat:@"%@ items", @(count)]); +} + + +@implementation BAVPlistNode + ++ (instancetype)plistNodeFromObject:(id)object key:(NSString *)key +{ + BAVPlistNode *newNode = [BAVPlistNode new]; + newNode.key = key; + newNode.type = typeForObject(object); + + if ([object isKindOfClass:[NSArray class]]) { + NSArray *array = object; + + NSMutableArray *children = [NSMutableArray new]; + NSUInteger elementIndex = 0; + for (id element in array) { + NSString *elementKey = [NSString stringWithFormat:@"Item %@", @(elementIndex)]; + [children addObject:[self plistNodeFromObject:element key:elementKey]]; + elementIndex++; + } + + newNode.value = formatItemCount(array.count); + newNode.children = children; + } + else if ([object isKindOfClass:[NSDictionary class]]) { + NSDictionary *dictionary = object; + NSArray *keys = [dictionary.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + + NSMutableArray *children = [NSMutableArray new]; + for (NSString *elementKey in keys) + [children addObject:[self plistNodeFromObject:dictionary[elementKey] key:elementKey]]; + + newNode.value = formatItemCount(keys.count); + newNode.children = children; + } + else if ([object isKindOfClass:[NSNull class]]) { + newNode.value = @"null"; + } + else if (object == (id)kCFBooleanTrue) { + newNode.value = @"true"; + } + else if (object == (id)kCFBooleanFalse) { + newNode.value = @"false"; + } + else { + newNode.value = [NSString stringWithFormat:@"%@", object]; + } + + return newNode; +} + +- (bool)isCollection +{ + return [self.type isEqualToString:@"Array"] || [self.type isEqualToString:@"Dictionary"]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ node with key %@", self.type, self.key]; +} + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.h new file mode 100644 index 0000000..20bb11f --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.h @@ -0,0 +1,80 @@ +/** + * JSONSyntaxHighlight.h + * JSONSyntaxHighlight + * + * Syntax highlight JSON + * + * Created by Dave Eddy on 8/3/13. + * Copyright (c) 2013 Dave Eddy. All rights reserved. + * + * The MIT License (MIT) + * + * 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 + +#if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) +#import +#else +#import +#endif + +@interface JSONSyntaxHighlight : NSObject + +// Create the object by giving a JSON object, nil will be returned +// if the object can't be serialized +- (JSONSyntaxHighlight *)init; +- (JSONSyntaxHighlight *)initWithJSON:(id)JSON; + +// Return an NSAttributedString with the highlighted JSON in a pretty format +- (NSAttributedString *)highlightJSON; + +// Return an NSAttributedString with the highlighted JSON optionally pretty formatted +- (NSAttributedString *)highlightJSONWithPrettyPrint:(BOOL)prettyPrint; + +// Fire a callback for every key item found in the parsed JSON, each callback +// is fired with the NSRange the substring appears in `self.parsedJSON`, as well +// as the NSString at that location. +- (void)enumerateMatchesWithIndentBlock:(void(^)(NSRange, NSString*))indentBlock + keyBlock:(void(^)(NSRange, NSString*))keyBlock + valueBlock:(void(^)(NSRange, NSString*))valueBlock + endBlock:(void(^)(NSRange, NSString*))endBlock; + +// The JSON object, unmodified +@property (readonly, nonatomic, strong) id JSON; + +// The serialized JSON string +@property (readonly, nonatomic, strong) NSString *parsedJSON; + +// The attributes for Attributed Text +@property (nonatomic, strong) NSDictionary *keyAttributes; +@property (nonatomic, strong) NSDictionary *stringAttributes; +@property (nonatomic, strong) NSDictionary *nonStringAttributes; + +// Platform dependent helper functions +#if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) ++ (UIColor *)colorWithRGB:(NSInteger)rgbValue; ++ (UIColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha; +#else ++ (NSColor *)colorWithRGB:(NSInteger)rgbValue; ++ (NSColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha; +#endif + +@end \ No newline at end of file diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.m new file mode 100644 index 0000000..ed7aca7 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/JSONSyntaxHighlight.m @@ -0,0 +1,180 @@ +/** + * JSONSyntaxHighlight.h + * JSONSyntaxHighlight + * + * Syntax highlight JSON + * + * Created by Dave Eddy on 8/3/13. + * Copyright (c) 2013 Dave Eddy. All rights reserved. + * + * The MIT License (MIT) + * + * 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 "JSONSyntaxHighlight.h" + +@implementation JSONSyntaxHighlight { + NSRegularExpression *regex; +} + +#pragma mark Object Initializer +// Must init with a JSON object +- (JSONSyntaxHighlight *)init +{ + return nil; +} + +- (JSONSyntaxHighlight *)initWithJSON:(id)JSON +{ + self = [super init]; + if (self) { + // save the origin JSON + _JSON = JSON; + + // create the object local regex + regex = [NSRegularExpression regularExpressionWithPattern:@"^( *)(\".+\" : )?(\"[^\"]*\"|[\\w.+-]*)?([,\\[\\]{}]?,?$)" + options:NSRegularExpressionAnchorsMatchLines + error:nil]; + + // parse the JSON if possible + if ([NSJSONSerialization isValidJSONObject:self.JSON]) { + NSJSONWritingOptions options = NSJSONWritingPrettyPrinted; + NSData *data = [NSJSONSerialization dataWithJSONObject:self.JSON options:options error:nil]; + NSString *o = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + _parsedJSON = o; + } else { + _parsedJSON = [NSString stringWithFormat:@"%@", self.JSON]; + } + + // set the default attributes + self.nonStringAttributes = @{NSForegroundColorAttributeName: [self.class colorWithRGB:0x000080]}; + self.stringAttributes = @{NSForegroundColorAttributeName: [self.class colorWithRGB:0x808000]}; + self.keyAttributes = @{NSForegroundColorAttributeName: [self.class colorWithRGB:0xa52a2a]}; + } + return self; +} + +#pragma mark - +#pragma mark JSON Highlighting +- (NSAttributedString *)highlightJSON +{ + return [self highlightJSONWithPrettyPrint:YES]; +} + +- (NSAttributedString *)highlightJSONWithPrettyPrint:(BOOL)prettyPrint +{ + NSMutableAttributedString *line = [[NSMutableAttributedString alloc] initWithString:@""]; + [self enumerateMatchesWithIndentBlock: + // The indent + ^(NSRange range, NSString *s) { + NSAttributedString *as = [[NSAttributedString alloc] initWithString:s attributes:@{}]; + if (prettyPrint) [line appendAttributedString:as]; + } + keyBlock: + // The key (with quotes and colon) + ^(NSRange range, NSString *s) { + // I hate this: this changes `"key" : ` to `"key"` + NSString *key = [s substringToIndex:s.length - 3]; + [line appendAttributedString:[[NSAttributedString alloc] initWithString:key attributes:self.keyAttributes]]; + NSString *colon = prettyPrint ? @" : " : @":"; + [line appendAttributedString:[[NSAttributedString alloc] initWithString:colon attributes:@{}]]; + } + valueBlock: + // The value + ^(NSRange range, NSString *s) { + NSAttributedString *as; + if ([s rangeOfString:@"\""].location == NSNotFound) // literal or number + as = [[NSAttributedString alloc] initWithString:s attributes:self.nonStringAttributes]; + else // string + as = [[NSAttributedString alloc] initWithString:s attributes:self.stringAttributes]; + + [line appendAttributedString:as]; + } + endBlock: + // The final comma, or ending character + ^(NSRange range, NSString *s) { + NSAttributedString *as = [[NSAttributedString alloc] initWithString:s attributes:@{}]; + [line appendAttributedString:as]; + if (prettyPrint) [line appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + }]; + + if ([line isEqualToAttributedString:[[NSAttributedString alloc] initWithString:@""]]) + line = [[NSMutableAttributedString alloc] initWithString:self.parsedJSON]; + return line; +} + +#pragma mark JSON Parser +- (void)enumerateMatchesWithIndentBlock:(void(^)(NSRange, NSString*))indentBlock + keyBlock:(void(^)(NSRange, NSString*))keyBlock + valueBlock:(void(^)(NSRange, NSString*))valueBlock + endBlock:(void(^)(NSRange, NSString*))endBlock +{ + [regex enumerateMatchesInString:self.parsedJSON + options:0 + range:NSMakeRange(0, self.parsedJSON.length) + usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { + + NSRange indentRange = [match rangeAtIndex:1]; + NSRange keyRange = [match rangeAtIndex:2]; + NSRange valueRange = [match rangeAtIndex:3]; + NSRange endRange = [match rangeAtIndex:4]; + + if (indentRange.location != NSNotFound) + indentBlock(indentRange, [self.parsedJSON substringWithRange:indentRange]); + if (keyRange.location != NSNotFound) + keyBlock(keyRange, [self.parsedJSON substringWithRange:keyRange]); + if (valueRange.location != NSNotFound) + valueBlock(valueRange, [self.parsedJSON substringWithRange:valueRange]); + if (endRange.location != NSNotFound) + endBlock(endRange, [self.parsedJSON substringWithRange:endRange]); + }]; +} + +#pragma mark - +#pragma mark Color Helper Functions +#if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) ++ (UIColor *)colorWithRGB:(NSInteger)rgbValue +{ + return [self.class colorWithRGB:rgbValue alpha:1.0]; +} + ++ (UIColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha +{ + return [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 + green:((float)((rgbValue & 0x00FF00) >> 8 )) / 255.0 + blue:((float)((rgbValue & 0x0000FF) >> 0 )) / 255.0 + alpha:alpha]; +} +#else ++ (NSColor *)colorWithRGB:(NSInteger)rgbValue +{ + return [self.class colorWithRGB:rgbValue alpha:1.0]; +} + ++ (NSColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha +{ + return [NSColor colorWithCalibratedRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 + green:((float)((rgbValue & 0x00FF00) >> 8 )) / 255.0 + blue:((float)((rgbValue & 0x0000FF) >> 0 )) / 255.0 + alpha:alpha]; +} +#endif + +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.h b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.h new file mode 100644 index 0000000..387da5e --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.h @@ -0,0 +1,132 @@ +/* + Copyright (c) 2012, Nicolas Seriot + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of the Nicolas Seriot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +extern NSUInteger const kSTHTTPRequestCancellationError; +extern NSUInteger const kSTHTTPRequestDefaultTimeout; + +@class STHTTPRequest; + +typedef void (^sendRequestBlock_t)(STHTTPRequest *request); +typedef void (^uploadProgressBlock_t)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite); +typedef void (^downloadProgressBlock_t)(NSData *data, NSUInteger totalBytesReceived, long long totalBytesExpectedToReceive); +typedef void (^completionBlock_t)(NSDictionary *headers, NSString *body); +typedef void (^completionDataBlock_t)(NSDictionary *headers, NSData *body); +typedef void (^errorBlock_t)(NSError *error); + +typedef NS_ENUM(NSUInteger, STHTTPRequestCookiesStorage) { + STHTTPRequestCookiesStorageShared = 0, + STHTTPRequestCookiesStorageLocal = 1, + STHTTPRequestCookiesStorageNoStorage = 2, + STHTTPRequestCookiesStorageUndefined = NSUIntegerMax +}; + +@interface STHTTPRequest : NSObject + +@property (copy) uploadProgressBlock_t uploadProgressBlock; +@property (copy) downloadProgressBlock_t downloadProgressBlock; +@property (copy) completionBlock_t completionBlock; +@property (copy) errorBlock_t errorBlock; +@property (copy) completionDataBlock_t completionDataBlock; + +// request +@property (nonatomic, strong) NSString *HTTPMethod; // default: GET, overridden by POST if POSTDictionary or files to upload +@property (nonatomic, strong) NSMutableDictionary *requestHeaders; +@property (nonatomic, strong) NSDictionary *POSTDictionary; // keys and values are NSString instances +@property (nonatomic, strong) NSDictionary *GETDictionary; // appended to the URL string +@property (nonatomic, strong) NSData *rawPOSTData; // eg. to post JSON contents +@property (nonatomic) NSStringEncoding POSTDataEncoding; +@property (nonatomic) NSTimeInterval timeoutSeconds; // ignored if 0 +@property (nonatomic) BOOL addCredentialsToURL; // default NO +@property (nonatomic) BOOL encodePOSTDictionary; // default YES +@property (nonatomic) BOOL encodeGETDictionary; // default YES, set to NO if the parameters are already URL encoded +@property (nonatomic, strong, readonly) NSURL *url; +@property (nonatomic) BOOL preventRedirections; +@property (nonatomic) STHTTPRequestCookiesStorage cookieStoragePolicyForInstance; // overrides globalCookiesStoragePolicy + +// response +@property (nonatomic) NSStringEncoding forcedResponseEncoding; +@property (nonatomic, readonly) NSInteger responseStatus; +@property (nonatomic, strong, readonly) NSString *responseStringEncodingName; +@property (nonatomic, strong, readonly) NSDictionary *responseHeaders; +@property (nonatomic, strong) NSString *responseString; +@property (nonatomic, strong, readonly) NSMutableData *responseData; +@property (nonatomic, strong, readonly) NSError *error; +@property (nonatomic) long long responseExpectedContentLength; // set by connection:didReceiveResponse: delegate method; web server must send the Content-Length header for accurate value + +// cache +@property (nonatomic) BOOL ignoreCache; // requests ignore cached responses and responses don't get cached + ++ (STHTTPRequest *)requestWithURL:(NSURL *)url; ++ (STHTTPRequest *)requestWithURLString:(NSString *)urlString; + ++ (void)setGlobalIgnoreCache:(BOOL)ignoreCache; // no cache at all when set, overrides the ignoreCache property + +- (NSString *)debugDescription; // logged when launched with -STHTTPRequestShowDebugDescription 1 +- (NSString *)curlDescription; // logged when launched with -STHTTPRequestShowCurlDescription 1 + +- (NSString *)startSynchronousWithError:(NSError **)error; +- (void)startAsynchronous; +- (void)cancel; + +// Cookies ++ (void)addCookieToSharedCookiesStorage:(NSHTTPCookie *)cookie; ++ (void)addCookieToSharedCookiesStorageWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url; +- (void)addCookieWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url; +- (void)addCookieWithName:(NSString *)name value:(NSString *)value; +- (NSArray *)requestCookies; +- (NSArray *)sessionCookies; ++ (NSArray *)sessionCookiesInSharedCookiesStorage; ++ (void)deleteAllCookiesFromSharedCookieStorage; ++ (void)deleteAllCookiesFromLocalCookieStorage; +- (void)deleteSessionCookies; // empty the cookie storage that is used ++ (void)setGlobalCookiesStoragePolicy:(STHTTPRequestCookiesStorage)cookieStoragePolicy; + +// Credentials ++ (NSURLCredential *)sessionAuthenticationCredentialsForURL:(NSURL *)requestURL; +- (void)setUsername:(NSString *)username password:(NSString *)password; +- (NSString *)username; +- (NSString *)password; ++ (void)deleteAllCredentials; + +// Headers +- (void)setHeaderWithName:(NSString *)name value:(NSString *)value; +- (void)removeHeaderWithName:(NSString *)name; +- (NSDictionary *)responseHeaders; + +// Upload +- (void)addFileToUpload:(NSString *)path parameterName:(NSString *)param; +- (void)addDataToUpload:(NSData *)data parameterName:(NSString *)param; +- (void)addDataToUpload:(NSData *)data parameterName:(NSString *)param mimeType:(NSString *)mimeType fileName:(NSString *)fileName; + +// Session ++ (void)clearSession; // delete all credentials and cookies + +// DEBUG +- (NSURLRequest *)prepareURLRequest; // prepare the request according to the STHTTPRequest instance state + +@end + +@interface NSError (STHTTPRequest) +- (BOOL)st_isAuthenticationError; +- (BOOL)st_isCancellationError; +@end + +@interface NSString (RFC3986) +- (NSString *)st_stringByAddingRFC3986PercentEscapesUsingEncoding:(NSStringEncoding)encoding; +@end + +@interface NSString (STUtilities) +- (NSString *)st_stringByAppendingGETParameters:(NSDictionary *)parameters doApplyURLEncoding:(BOOL)doApplyURLEncoding; +@end diff --git a/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.m b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.m new file mode 100644 index 0000000..29372d1 --- /dev/null +++ b/TalkinToTheNet/Pods/STTwitter/STTwitter/Vendor/STHTTPRequest.m @@ -0,0 +1,1079 @@ +// +// STHTTPRequest.m +// STHTTPRequest +// +// Created by Nicolas Seriot on 07.11.11. +// Copyright (c) 2011 __MyCompanyName__. All rights reserved. +// + +#if __has_feature(objc_arc) +#else +// see http://www.codeography.com/2011/10/10/making-arc-and-non-arc-play-nice.html +#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag +#endif + +#import "STHTTPRequest.h" + +NSUInteger const kSTHTTPRequestCancellationError = 1; +NSUInteger const kSTHTTPRequestDefaultTimeout = 30; + +static NSMutableDictionary *localCredentialsStorage = nil; +static NSMutableArray *localCookiesStorage = nil; + +static BOOL globalIgnoreCache = NO; +static STHTTPRequestCookiesStorage globalCookiesStoragePolicy = STHTTPRequestCookiesStorageShared; + +/**/ + +@interface STHTTPRequestFileUpload : NSObject +@property (nonatomic, retain) NSString *path; +@property (nonatomic, retain) NSString *parameterName; +@property (nonatomic, retain) NSString *mimeType; + ++ (instancetype)fileUploadWithPath:(NSString *)path parameterName:(NSString *)parameterName mimeType:(NSString *)mimeType; ++ (instancetype)fileUploadWithPath:(NSString *)path parameterName:(NSString *)parameterName; +@end + +@interface STHTTPRequestDataUpload : NSObject +@property (nonatomic, retain) NSData *data; +@property (nonatomic, retain) NSString *parameterName; +@property (nonatomic, retain) NSString *mimeType; // can be nil +@property (nonatomic, retain) NSString *fileName; // can be nil ++ (instancetype)dataUploadWithData:(NSData *)data parameterName:(NSString *)parameterName mimeType:(NSString *)mimeType fileName:(NSString *)fileName; +@end + +/**/ + +@interface STHTTPRequest () + +@property (nonatomic) NSInteger responseStatus; +@property (nonatomic, strong) NSURLConnection *connection; +@property (nonatomic, strong) NSMutableData *responseData; +@property (nonatomic, strong) NSString *responseStringEncodingName; +@property (nonatomic, strong) NSDictionary *responseHeaders; +@property (nonatomic, strong) NSURL *url; +@property (nonatomic, strong) NSError *error; +@property (nonatomic, strong) NSMutableArray *filesToUpload; // STHTTPRequestFileUpload instances +@property (nonatomic, strong) NSMutableArray *dataToUpload; // STHTTPRequestDataUpload instances +@property (nonatomic, strong) NSURLRequest *request; +@end + +@interface NSData (Base64) +- (NSString *)base64Encoding; // private API +@end + +@implementation STHTTPRequest + +#pragma mark Initializers + ++ (STHTTPRequest *)requestWithURL:(NSURL *)url { + if(url == nil) return nil; + return [(STHTTPRequest *)[self alloc] initWithURL:url]; +} + ++ (STHTTPRequest *)requestWithURLString:(NSString *)urlString { + NSURL *url = [NSURL URLWithString:urlString]; + return [self requestWithURL:url]; +} + ++ (void)setGlobalIgnoreCache:(BOOL)ignoreCache { + globalIgnoreCache = ignoreCache; +} + ++ (void)setGlobalCookiesStoragePolicy:(STHTTPRequestCookiesStorage)cookieStoragePolicy { + globalCookiesStoragePolicy = cookieStoragePolicy; +} + +- (STHTTPRequest *)initWithURL:(NSURL *)theURL { + + if (self = [super init]) { + self.url = theURL; + self.responseData = [[NSMutableData alloc] init]; + self.requestHeaders = [NSMutableDictionary dictionary]; + self.POSTDataEncoding = NSUTF8StringEncoding; + self.encodePOSTDictionary = YES; + self.encodeGETDictionary = YES; + self.addCredentialsToURL = NO; + self.timeoutSeconds = kSTHTTPRequestDefaultTimeout; + self.filesToUpload = [NSMutableArray array]; + self.dataToUpload = [NSMutableArray array]; + self.HTTPMethod = @"GET"; // default + self.cookieStoragePolicyForInstance = STHTTPRequestCookiesStorageUndefined; // globalCookiesStoragePolicy will be used + } + + return self; +} + ++ (void)clearSession { + [[self class] deleteAllCookiesFromSharedCookieStorage]; + [[self class] deleteAllCookiesFromLocalCookieStorage]; + [[self class] deleteAllCredentials]; +} + +#pragma mark Credentials + ++ (NSMutableDictionary *)sharedCredentialsStorage { + if(localCredentialsStorage == nil) { + localCredentialsStorage = [NSMutableDictionary dictionary]; + } + return localCredentialsStorage; +} + ++ (NSURLCredential *)sessionAuthenticationCredentialsForURL:(NSURL *)requestURL { + return [[[self class] sharedCredentialsStorage] valueForKey:[requestURL host]]; +} + ++ (void)deleteAllCredentials { + localCredentialsStorage = [NSMutableDictionary dictionary]; +} + +- (void)setCredentialForCurrentHost:(NSURLCredential *)c { +#if DEBUG + NSAssert(_url, @"missing url to set credential"); +#endif + [[[self class] sharedCredentialsStorage] setObject:c forKey:[_url host]]; +} + +- (NSURLCredential *)credentialForCurrentHost { + return [[[self class] sharedCredentialsStorage] valueForKey:[_url host]]; +} + +- (void)setUsername:(NSString *)username password:(NSString *)password { + NSURLCredential *c = [NSURLCredential credentialWithUser:username + password:password + persistence:NSURLCredentialPersistenceNone]; + + [self setCredentialForCurrentHost:c]; +} + +- (NSString *)username { + return [[self credentialForCurrentHost] user]; +} + +- (NSString *)password { + return [[self credentialForCurrentHost] password]; +} + +#pragma mark Cookies + +- (STHTTPRequestCookiesStorage)cookieStoragePolicy { + if(_cookieStoragePolicyForInstance != STHTTPRequestCookiesStorageUndefined) { + return _cookieStoragePolicyForInstance; + } + + return globalCookiesStoragePolicy; +} + ++ (NSMutableArray *)localCookiesStorage { + if(localCookiesStorage == nil) { + localCookiesStorage = [NSMutableArray array]; + } + return localCookiesStorage; +} + ++ (NSArray *)sessionCookiesInSharedCookiesStorage { + NSArray *allCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; + + NSArray *sessionCookies = [allCookies filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + NSHTTPCookie *cookie = (NSHTTPCookie *)evaluatedObject; + return [cookie isSessionOnly]; + }]]; + + return sessionCookies; +} + +- (NSArray *)sessionCookies { + + NSArray *allCookies = nil; + + if([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared) { + allCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; + } else if ([self cookieStoragePolicy] == STHTTPRequestCookiesStorageLocal) { + allCookies = [[self class] localCookiesStorage]; + } + + NSArray *sessionCookies = [allCookies filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + NSHTTPCookie *cookie = (NSHTTPCookie *)evaluatedObject; + return [cookie isSessionOnly]; + }]]; + + return sessionCookies; +} + +- (void)deleteSessionCookies { + + for(NSHTTPCookie *cookie in [self sessionCookies]) { + if([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared) { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; + } else if ([self cookieStoragePolicy] == STHTTPRequestCookiesStorageLocal) { + [[[self class] localCookiesStorage] removeObject:cookie]; + } + } +} + ++ (void)deleteAllCookiesFromSharedCookieStorage { + NSHTTPCookieStorage *sharedCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + NSArray *cookies = [sharedCookieStorage cookies]; + for (NSHTTPCookie *cookie in cookies) { + [sharedCookieStorage deleteCookie:cookie]; + } +} + ++ (void)deleteAllCookiesFromLocalCookieStorage { + localCookiesStorage = nil; +} + +- (void)deleteAllCookies { + if([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared) { + [[self class] deleteAllCookiesFromSharedCookieStorage]; + } else if ([self cookieStoragePolicy] == STHTTPRequestCookiesStorageLocal) { + [[[self class] localCookiesStorage] removeAllObjects]; + } +} + ++ (void)addCookieToSharedCookiesStorage:(NSHTTPCookie *)cookie { + [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; + +#if DEBUG + NSHTTPCookie *readCookie = [[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] lastObject]; + NSAssert(readCookie, @"cannot read any cookie after adding one"); +#endif +} + +- (void)addCookie:(NSHTTPCookie *)cookie { + + NSParameterAssert(cookie); + if(cookie == nil) return; + + if([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared) { + [[self class] addCookieToSharedCookiesStorage:cookie]; + } else if ([self cookieStoragePolicy] == STHTTPRequestCookiesStorageLocal) { + [[[self class] localCookiesStorage] addObject:cookie]; + } // else don't store anything +} + ++ (void)addCookieToSharedCookiesStorageWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url { + NSHTTPCookie *cookie = [[self class] createCookieWithName:name value:value url:url]; + + [self addCookieToSharedCookiesStorage:cookie]; +} + ++ (NSHTTPCookie *)createCookieWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url { + NSParameterAssert(url); + if(url == nil) return nil; + + NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionaryWithObjectsAndKeys: + name, NSHTTPCookieName, + value, NSHTTPCookieValue, + url, NSHTTPCookieOriginURL, + @"FALSE", NSHTTPCookieDiscard, + @"/", NSHTTPCookiePath, + @"0", NSHTTPCookieVersion, + [[NSDate date] dateByAddingTimeInterval:3600 * 24 * 30], NSHTTPCookieExpires, + nil]; + + NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties]; + + return cookie; +} + +- (void)addCookieWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url { + NSHTTPCookie *cookie = [[self class] createCookieWithName:name value:value url:url]; + + [self addCookie:cookie]; +} + +- (NSArray *)requestCookies { + + if([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared) { + return [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[_url absoluteURL]]; + } else if ([self cookieStoragePolicy] == STHTTPRequestCookiesStorageLocal) { + NSArray *filteredCookies = [[[self class] localCookiesStorage] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { + NSHTTPCookie *cookie = (NSHTTPCookie *)evaluatedObject; + return [[cookie domain] isEqualToString:[self.url host]]; + }]]; + return filteredCookies; + } + + return nil; +} + +- (void)addCookieWithName:(NSString *)name value:(NSString *)value { + [self addCookieWithName:name value:value url:_url]; +} + +#pragma mark Headers + +- (void)setHeaderWithName:(NSString *)name value:(NSString *)value { + if(name == nil || value == nil) return; + [[self requestHeaders] setObject:value forKey:name]; +} + +- (void)removeHeaderWithName:(NSString *)name { + if(name == nil) return; + [[self requestHeaders] removeObjectForKey:name]; +} + ++ (NSURL *)urlByAddingCredentials:(NSURLCredential *)credentials toURL:(NSURL *)url { + + if(credentials == nil) return nil; // no credentials to add + + NSString *scheme = [url scheme]; + NSString *host = [url host]; + + BOOL hostAlreadyContainsCredentials = [host rangeOfString:@"@"].location != NSNotFound; + if(hostAlreadyContainsCredentials) return url; + + NSMutableString *resourceSpecifier = [[url resourceSpecifier] mutableCopy]; + + if([resourceSpecifier hasPrefix:@"//"] == NO) return nil; + + NSString *userPassword = [NSString stringWithFormat:@"%@:%@@", credentials.user, credentials.password]; + + [resourceSpecifier insertString:userPassword atIndex:2]; + + NSString *urlString = [NSString stringWithFormat:@"%@:%@", scheme, resourceSpecifier]; + + return [NSURL URLWithString:urlString]; +} + +// {k2:v2, k1:v1} -> [{k1:v1}, {k2:v2}] ++ (NSArray *)dictionariesSortedByKey:(NSDictionary *)dictionary { + + NSArray *keys = [dictionary allKeys]; + NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSComparisonResult result = [obj1 compare:obj2]; + return result; + }]; + + NSMutableArray *sortedDictionaries = [NSMutableArray arrayWithCapacity:[dictionary count]]; + + for(NSString *key in sortedKeys) { + NSDictionary *d = @{ key : dictionary[key] }; + [sortedDictionaries addObject:d]; + } + return sortedDictionaries; +} + ++ (NSData *)multipartContentWithBoundary:(NSString *)boundary data:(NSData *)someData fileName:(NSString *)fileName parameterName:(NSString *)parameterName mimeType:(NSString *)aMimeType { + + NSString *mimeType = aMimeType ? aMimeType : @"application/octet-stream"; + + NSMutableData *data = [[NSMutableData alloc] init]; + + [data appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + NSString *fileNameContentDisposition = fileName ? [NSString stringWithFormat:@"filename=\"%@\"", fileName] : @""; + NSString *contentDisposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; %@\r\n", parameterName, fileNameContentDisposition]; + + [data appendData:[contentDisposition dataUsingEncoding:NSUTF8StringEncoding]]; + [data appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimeType] dataUsingEncoding:NSUTF8StringEncoding]]; + [data appendData:someData]; + + return data; +} + ++ (NSURL *)appendURL:(NSURL *)url withGETParameters:(NSDictionary *)parameters doApplyURLEncoding:(BOOL)doApplyURLEncoding { + NSMutableString *urlString = [[NSMutableString alloc] initWithString:[url absoluteString]]; + + NSString *s = [urlString st_stringByAppendingGETParameters:parameters doApplyURLEncoding:doApplyURLEncoding]; + + return [NSURL URLWithString:s]; +} + +- (NSURLRequest *)prepareURLRequest { + + NSURL *theURL = nil; + + if(_addCredentialsToURL) { + NSURLCredential *credential = [self credentialForCurrentHost]; + if(credential == nil) return nil; + theURL = [[self class] urlByAddingCredentials:credential toURL:_url]; + if(theURL == nil) return nil; + } else { + theURL = _url; + } + + theURL = [[self class] appendURL:theURL withGETParameters:_GETDictionary doApplyURLEncoding:_encodeGETDictionary]; + + if([_HTTPMethod isEqualToString:@"GET"]) { + if(_POSTDictionary || _rawPOSTData || [self.filesToUpload count] > 0 || [self.dataToUpload count] > 0) { + self.HTTPMethod = @"POST"; + } + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:theURL]; + [request setHTTPMethod:_HTTPMethod]; + + if(globalIgnoreCache || _ignoreCache) { + request.cachePolicy = NSURLRequestReloadIgnoringCacheData; + } + + if(self.timeoutSeconds != 0.0) { + request.timeoutInterval = self.timeoutSeconds; + } + + if([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared || [self cookieStoragePolicy] == STHTTPRequestCookiesStorageLocal) { + NSArray *cookies = [self sessionCookies]; + NSDictionary *d = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + [request setAllHTTPHeaderFields:d]; + } + + // escape POST dictionary keys and values if needed + if(_encodePOSTDictionary) { + NSMutableDictionary *escapedPOSTDictionary = _POSTDictionary ? [NSMutableDictionary dictionary] : nil; + [_POSTDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSString *k = [key st_stringByAddingRFC3986PercentEscapesUsingEncoding:self.POSTDataEncoding]; + NSString *v = [[obj description] st_stringByAddingRFC3986PercentEscapesUsingEncoding:self.POSTDataEncoding]; + [escapedPOSTDictionary setValue:v forKey:k]; + }]; + self.POSTDictionary = escapedPOSTDictionary; + } + + // sort POST parameters in order to get deterministic, unit testable requests + NSArray *sortedPOSTDictionaries = [[self class] dictionariesSortedByKey:_POSTDictionary]; + + if([self.filesToUpload count] > 0 || [self.dataToUpload count] > 0) { + + NSString *boundary = @"----------kStHtTpReQuEsTbOuNdArY"; + + NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; + [request addValue:contentType forHTTPHeaderField:@"Content-Type"]; + + NSMutableData *body = [NSMutableData data]; + + /**/ + + for(STHTTPRequestFileUpload *fileToUpload in self.filesToUpload) { + + NSData *data = [NSData dataWithContentsOfFile:fileToUpload.path]; + if(data == nil) continue; + NSString *fileName = [fileToUpload.path lastPathComponent]; + + NSData *multipartData = [[self class] multipartContentWithBoundary:boundary + data:data + fileName:fileName + parameterName:fileToUpload.parameterName + mimeType:fileToUpload.mimeType]; + [body appendData:multipartData]; + } + + /**/ + + for(STHTTPRequestDataUpload *dataToUpload in self.dataToUpload) { + NSData *multipartData = [[self class] multipartContentWithBoundary:boundary + data:dataToUpload.data + fileName:dataToUpload.fileName + parameterName:dataToUpload.parameterName + mimeType:dataToUpload.mimeType]; + + [body appendData:multipartData]; + } + + /**/ + + [sortedPOSTDictionaries enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSDictionary *d = (NSDictionary *)obj; + NSString *key = [[d allKeys] lastObject]; + NSObject *value = [[d allValues] lastObject]; + + [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]]; + [body appendData:[[value description] dataUsingEncoding:NSUTF8StringEncoding]]; + }]; + + /**/ + + [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + [request setValue:[NSString stringWithFormat:@"%u", (unsigned int)[body length]] forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:body]; + + } else if (_rawPOSTData) { + + [request setValue:[NSString stringWithFormat:@"%u", (unsigned int)[_rawPOSTData length]] forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:_rawPOSTData]; + + } else if (_POSTDictionary != nil) { // may be empty (POST request without body) + + if(_encodePOSTDictionary) { + + CFStringEncoding cfStringEncoding = CFStringConvertNSStringEncodingToEncoding(_POSTDataEncoding); + NSString *encodingName = (NSString *)CFStringConvertEncodingToIANACharSetName(cfStringEncoding); + + if(encodingName) { + NSString *contentTypeValue = [NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", encodingName]; + [self setHeaderWithName:@"Content-Type" value:contentTypeValue]; + } + } + + NSMutableArray *ma = [NSMutableArray arrayWithCapacity:[_POSTDictionary count]]; + + [sortedPOSTDictionaries enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSDictionary *d = (NSDictionary *)obj; + NSString *key = [[d allKeys] lastObject]; + NSObject *value = [[d allValues] lastObject]; + + NSString *kv = [NSString stringWithFormat:@"%@=%@", key, value]; + [ma addObject:kv]; + }]; + + NSString *s = [ma componentsJoinedByString:@"&"]; + + NSData *data = [s dataUsingEncoding:_POSTDataEncoding allowLossyConversion:YES]; + + [request setValue:[NSString stringWithFormat:@"%u", (unsigned int)[data length]] forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody:data]; + } + + [_requestHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [request addValue:obj forHTTPHeaderField:key]; + }]; + + NSURLCredential *credentialForHost = [self credentialForCurrentHost]; + + if(credentialForHost) { + NSString *authString = [NSString stringWithFormat:@"%@:%@", credentialForHost.user, credentialForHost.password]; + NSData *authData = [authString dataUsingEncoding:NSASCIIStringEncoding]; + NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64Encoding]]; + [request addValue:authValue forHTTPHeaderField:@"Authorization"]; + } + + request.HTTPShouldHandleCookies = ([self cookieStoragePolicy] == STHTTPRequestCookiesStorageShared); + + return request; +} + +#pragma mark Upload + +- (void)addFileToUpload:(NSString *)path parameterName:(NSString *)parameterName { + + STHTTPRequestFileUpload *fu = [STHTTPRequestFileUpload fileUploadWithPath:path parameterName:parameterName]; + [self.filesToUpload addObject:fu]; +} + +- (void)addDataToUpload:(NSData *)data parameterName:(NSString *)param { + STHTTPRequestDataUpload *du = [STHTTPRequestDataUpload dataUploadWithData:data parameterName:param mimeType:nil fileName:nil]; + [self.dataToUpload addObject:du]; +} + +- (void)addDataToUpload:(NSData *)data parameterName:(NSString *)param mimeType:(NSString *)mimeType fileName:(NSString *)fileName { + STHTTPRequestDataUpload *du = [STHTTPRequestDataUpload dataUploadWithData:data parameterName:param mimeType:mimeType fileName:fileName]; + [self.dataToUpload addObject:du]; +} + +#pragma mark Response + +- (NSString *)responseString { + if(_responseString == nil) { + self.responseString = [self stringWithData:_responseData encodingName:_responseStringEncodingName]; + } + return _responseString; +} + +- (NSString *)stringWithData:(NSData *)data encodingName:(NSString *)encodingName { + if(data == nil) return nil; + + if(_forcedResponseEncoding > 0) { + return [[NSString alloc] initWithData:data encoding:_forcedResponseEncoding]; + } + + NSStringEncoding encoding = NSUTF8StringEncoding; + + /* try to use encoding declared in HTTP response headers */ + + if(encodingName != nil) { + + encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName)); + + if(encoding == kCFStringEncodingInvalidId) { + encoding = NSUTF8StringEncoding; // by default + } + } + + return [[NSString alloc] initWithData:data encoding:encoding]; +} + +#pragma mark HTTP Error Codes + ++ (NSString *)descriptionForHTTPStatus:(NSUInteger)status { + NSString *s = [NSString stringWithFormat:@"HTTP Status %@", @(status)]; + + NSString *description = nil; + // http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + if(status == 400) description = @"Bad Request"; + if(status == 401) description = @"Unauthorized"; + if(status == 402) description = @"Payment Required"; + if(status == 403) description = @"Forbidden"; + if(status == 404) description = @"Not Found"; + if(status == 405) description = @"Method Not Allowed"; + if(status == 406) description = @"Not Acceptable"; + if(status == 407) description = @"Proxy Authentication Required"; + if(status == 408) description = @"Request Timeout"; + if(status == 409) description = @"Conflict"; + if(status == 410) description = @"Gone"; + if(status == 411) description = @"Length Required"; + if(status == 412) description = @"Precondition Failed"; + if(status == 413) description = @"Payload Too Large"; + if(status == 414) description = @"URI Too Long"; + if(status == 415) description = @"Unsupported Media Type"; + if(status == 416) description = @"Requested Range Not Satisfiable"; + if(status == 417) description = @"Expectation Failed"; + if(status == 422) description = @"Unprocessable Entity"; + if(status == 423) description = @"Locked"; + if(status == 424) description = @"Failed Dependency"; + if(status == 425) description = @"Unassigned"; + if(status == 426) description = @"Upgrade Required"; + if(status == 427) description = @"Unassigned"; + if(status == 428) description = @"Precondition Required"; + if(status == 429) description = @"Too Many Requests"; + if(status == 430) description = @"Unassigned"; + if(status == 431) description = @"Request Header Fields Too Large"; + if(status == 432) description = @"Unassigned"; + if(status == 500) description = @"Internal Server Error"; + if(status == 501) description = @"Not Implemented"; + if(status == 502) description = @"Bad Gateway"; + if(status == 503) description = @"Service Unavailable"; + if(status == 504) description = @"Gateway Timeout"; + if(status == 505) description = @"HTTP Version Not Supported"; + if(status == 506) description = @"Variant Also Negotiates"; + if(status == 507) description = @"Insufficient Storage"; + if(status == 508) description = @"Loop Detected"; + if(status == 509) description = @"Unassigned"; + if(status == 510) description = @"Not Extended"; + if(status == 511) description = @"Network Authentication Required"; + + if(description) { + s = [s stringByAppendingFormat:@": %@", description]; + } + + return s; +} + ++ (NSDictionary *)userInfoWithErrorDescriptionForHTTPStatus:(NSUInteger)status { + NSString *s = [self descriptionForHTTPStatus:status]; + if(s == nil) return nil; + return @{ NSLocalizedDescriptionKey : s }; +} + +#pragma mark Descriptions + +- (NSString *)curlDescription { + + //[self prepareURLRequest]; + + NSMutableArray *ma = [NSMutableArray array]; + [ma addObject:@"\U0001F300 curl -i"]; + + if([_HTTPMethod isEqualToString:@"GET"] == NO) { // GET is optional in curl + NSString *s = [NSString stringWithFormat:@"-X %@", _HTTPMethod]; + [ma addObject:s]; + } + + // -u username:password + + NSURLCredential *credential = [[self class] sessionAuthenticationCredentialsForURL:[self url]]; + if(credential) { + NSString *s = [NSString stringWithFormat:@"-u \"%@:%@\"", credential.user, credential.password]; + [ma addObject:s]; + } + + // -d "k1=v1&k2=v2" // POST, url encoded params + + if(_POSTDictionary) { + NSMutableArray *postParameters = [NSMutableArray array]; + [_POSTDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSString *s = [NSString stringWithFormat:@"%@=%@", key, obj]; + [postParameters addObject:s]; + }]; + NSString *ss = [postParameters componentsJoinedByString:@"&"]; + [ma addObject:[NSString stringWithFormat:@"-d \"%@\"", ss]]; + } + + if(_rawPOSTData) { + // try JSON + id jsonObject = [NSJSONSerialization JSONObjectWithData:_rawPOSTData options:NSJSONReadingMutableContainers error:nil]; + if(jsonObject) { + NSString *jsonString = [[NSString alloc] initWithData:_rawPOSTData encoding:NSUTF8StringEncoding]; + // [ma addObject:@"-X POST"]; + [ma addObject:[NSString stringWithFormat:@"-d \'%@\'", jsonString]]; + } + } + + // -F "coolfiles=@fil1.gif;type=image/gif,fil2.txt,fil3.html" // file upload + + for(STHTTPRequestFileUpload *f in _filesToUpload) { + NSString *s = [NSString stringWithFormat:@"%@=@%@", f.parameterName, f.path]; + [ma addObject:[NSString stringWithFormat:@"-F \"%@\"", s]]; + } + + // -H "X-you-and-me: yes" // extra headers + + NSMutableDictionary *headers = [[_request allHTTPHeaderFields] mutableCopy]; +// [headers removeObjectForKey:@"Cookie"]; + + NSMutableArray *headersStrings = [NSMutableArray array]; + [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSString *s = [NSString stringWithFormat:@"-H \"%@: %@\"", key, obj]; + [headersStrings addObject:s]; + }]; + + if([headersStrings count] > 0) { + [ma addObject:[headersStrings componentsJoinedByString:@" \\\n"]]; + } + + // url + + NSURL *url = [_request URL] ? [_request URL] : _url; + [ma addObject:[NSString stringWithFormat:@"\"%@\"", url]]; + + return [ma componentsJoinedByString:@" \\\n"]; +} + +- (NSString *)debugDescription { + + NSMutableString *ms = [NSMutableString string]; + + NSString *method = (self.POSTDictionary || [self.filesToUpload count] || [self.dataToUpload count]) ? @"POST" : @"GET"; + + [ms appendFormat:@"%@ %@\n", method, [_request URL]]; + + NSMutableDictionary *headers = [[_request allHTTPHeaderFields] mutableCopy]; + + if([headers count]) [ms appendString:@"HEADERS\n"]; + + [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [ms appendFormat:@"\t %@ = %@\n", key, obj]; + }]; + + NSArray *kvDictionaries = [[self class] dictionariesSortedByKey:_POSTDictionary]; + + if([kvDictionaries count]) [ms appendString:@"POST DATA\n"]; + + for(NSDictionary *kv in kvDictionaries) { + NSString *k = [[kv allKeys] lastObject]; + NSString *v = [[kv allValues] lastObject]; + [ms appendFormat:@"\t %@ = %@\n", k, v]; + } + + for(STHTTPRequestFileUpload *f in self.filesToUpload) { + [ms appendString:@"UPLOAD FILE\n"]; + [ms appendFormat:@"\t %@ = %@\n", f.parameterName, f.path]; + } + + for(STHTTPRequestDataUpload *d in self.dataToUpload) { + [ms appendString:@"UPLOAD DATA\n"]; + [ms appendFormat:@"\t %@ = [%u bytes]\n", d.parameterName, (unsigned int)[d.data length]]; + } + + return ms; +} + +#pragma mark Start Request + +- (void)startAsynchronous { + + NSAssert((self.completionBlock || self.completionDataBlock), @"a completion block is mandatory"); + NSAssert(self.errorBlock, @"the error block is mandatory"); + + NSURLRequest *request = [self prepareURLRequest]; + + self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; + [_connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + [_connection start]; + + self.request = [_connection currentRequest]; + + self.requestHeaders = [[_request allHTTPHeaderFields] mutableCopy]; + + /**/ + + BOOL showDebugDescription = [[NSUserDefaults standardUserDefaults] boolForKey:@"STHTTPRequestShowDebugDescription"]; + BOOL showCurlDescription = [[NSUserDefaults standardUserDefaults] boolForKey:@"STHTTPRequestShowCurlDescription"]; + + NSMutableString *logString = nil; + + if(showDebugDescription || showCurlDescription) { + logString = [NSMutableString stringWithString:@"\n----------\n"]; + } + + if(showDebugDescription) { + [logString appendString:[self debugDescription]]; + } + + if(showDebugDescription && showCurlDescription) { + [logString appendString:@"\n"]; + } + + if(showCurlDescription) { + [logString appendString:[self curlDescription]]; + } + + if(showDebugDescription || showCurlDescription) { + [logString appendString:@"\n----------\n"]; + } + + if(logString) NSLog(@"%@", logString); + + /**/ + + if(_connection == nil) { + NSString *s = @"can't create connection"; + self.error = [NSError errorWithDomain:NSStringFromClass([self class]) + code:0 + userInfo:@{NSLocalizedDescriptionKey: s}]; + + self.errorBlock(self.error); + } +} + +- (NSString *)startSynchronousWithError:(NSError **)e { + + self.responseHeaders = nil; + self.responseStatus = 0; + + NSURLRequest *request = [self prepareURLRequest]; + + NSURLResponse *urlResponse = nil; + + NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:e]; + + self.responseData = [NSMutableData dataWithData:data]; + + if([urlResponse isKindOfClass:[NSHTTPURLResponse class]]) { + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)urlResponse; + + self.responseHeaders = [httpResponse allHeaderFields]; + self.responseStatus = [httpResponse statusCode]; + self.responseStringEncodingName = [httpResponse textEncodingName]; + } + + self.responseString = [self stringWithData:_responseData encodingName:_responseStringEncodingName]; + + if(_responseStatus >= 400) { + NSDictionary *userInfo = [[self class] userInfoWithErrorDescriptionForHTTPStatus:_responseStatus]; + if(e) *e = [NSError errorWithDomain:NSStringFromClass([self class]) code:_responseStatus userInfo:userInfo]; + } + + return _responseString; +} + +- (void)cancel { + [_connection cancel]; + + NSString *s = @"Connection was cancelled."; + self.error = [NSError errorWithDomain:NSStringFromClass([self class]) + code:kSTHTTPRequestCancellationError + userInfo:@{NSLocalizedDescriptionKey: s}]; + + self.errorBlock(self.error); +} + +#pragma mark NSURLConnectionDataDelegate + +- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { + if(globalIgnoreCache || _ignoreCache) return nil; + + return cachedResponse; +} + +#pragma mark NSURLConnectionDelegate + +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { + + if(_preventRedirections && redirectResponse) { + return nil; + } + + return request; +} + +- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + + NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; + NSString *authenticationMethod = [protectionSpace authenticationMethod]; + + if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest] || + [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic]) { + + if([challenge previousFailureCount] == 0) { + NSURLCredential *credential = [self credentialForCurrentHost]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } else { + [[[self class] sharedCredentialsStorage] removeObjectForKey:[_url host]]; + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } else { + [[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge]; + } +} + +- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + if (_uploadProgressBlock) { + _uploadProgressBlock(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + + if([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *r = (NSHTTPURLResponse *)response; + self.responseHeaders = [r allHeaderFields]; + self.responseStatus = [r statusCode]; + self.responseStringEncodingName = [r textEncodingName]; + self.responseExpectedContentLength = [r expectedContentLength]; + + NSArray *responseCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:_responseHeaders forURL:connection.currentRequest.URL]; + for(NSHTTPCookie *cookie in responseCookies) { + [self addCookie:cookie]; // won't store anything when STHTTPRequestCookiesStorageNoStorage + } + } + + [_responseData setLength:0]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData { + + [_responseData appendData:theData]; + + if (_downloadProgressBlock) { + _downloadProgressBlock(theData, [_responseData length], self.responseExpectedContentLength); + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + + if(_responseStatus >= 400) { + NSDictionary *userInfo = [[self class] userInfoWithErrorDescriptionForHTTPStatus:_responseStatus]; + self.error = [NSError errorWithDomain:NSStringFromClass([self class]) code:_responseStatus userInfo:userInfo]; + _errorBlock(_error); + return; + } + + if(_completionDataBlock) { + _completionDataBlock(_responseHeaders,_responseData); + } + + if(_completionBlock) { + NSString *responseString = [self stringWithData:_responseData encodingName:_responseStringEncodingName]; + _completionBlock(_responseHeaders, responseString); + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)e { + self.error = e; + _errorBlock(_error); +} + +@end + +/**/ + +@implementation NSError (STHTTPRequest) + +- (BOOL)st_isAuthenticationError { + + if ([[self domain] isEqualToString:@"STHTTPRequest"] && ([self code] == 401)) return YES; + + if ([[self domain] isEqualToString:NSURLErrorDomain] && ([self code] == kCFURLErrorUserCancelledAuthentication || [self code] == kCFURLErrorUserAuthenticationRequired)) return YES; + + return NO; +} + +- (BOOL)st_isCancellationError { + return ([[self domain] isEqualToString:@"STHTTPRequest"] && [self code] == kSTHTTPRequestCancellationError); +} + +@end + +@implementation NSString (RFC3986) +- (NSString *)st_stringByAddingRFC3986PercentEscapesUsingEncoding:(NSStringEncoding)encoding { + + NSString *s = (__bridge_transfer NSString *)(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)self, + NULL, + CFSTR("!*'();:@&=+$,/?%#[]"), + kCFStringEncodingUTF8)); + return s; +} +@end + +/**/ + +@implementation NSString (STUtilities) + +- (NSString *)st_stringByAppendingGETParameters:(NSDictionary *)parameters doApplyURLEncoding:(BOOL)doApplyURLEncoding { + + NSMutableString *ms = [self mutableCopy]; + + __block BOOL questionMarkFound = NO; + + NSArray *sortedParameters = [STHTTPRequest dictionariesSortedByKey:parameters]; + + [sortedParameters enumerateObjectsUsingBlock:^(NSDictionary *d, NSUInteger idx, BOOL *stop) { + + NSString *key = [[d allKeys] lastObject]; + NSString *value = [[[d allValues] lastObject] description]; + + if(questionMarkFound == NO) { + questionMarkFound = [ms rangeOfString:@"?"].location != NSNotFound; + } + + [ms appendString: (questionMarkFound ? @"&" : @"?") ]; + + if(doApplyURLEncoding) { + key = [key st_stringByAddingRFC3986PercentEscapesUsingEncoding:NSUTF8StringEncoding]; + value = [value st_stringByAddingRFC3986PercentEscapesUsingEncoding:NSUTF8StringEncoding]; + } + + [ms appendFormat:@"%@=%@", key, value]; + }]; + + return ms; +} + +@end + +/**/ + +#if DEBUG +@implementation NSURLRequest (IgnoreSSLValidation) + ++ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host { + return NO; +} + +@end +#endif + +/**/ + +@implementation STHTTPRequestFileUpload + ++ (instancetype)fileUploadWithPath:(NSString *)path parameterName:(NSString *)parameterName mimeType:(NSString *)mimeType { + STHTTPRequestFileUpload *fu = [[self alloc] init]; + fu.path = path; + fu.parameterName = parameterName; + fu.mimeType = mimeType; + return fu; +} + ++ (instancetype)fileUploadWithPath:(NSString *)path parameterName:(NSString *)fileName { + return [self fileUploadWithPath:path parameterName:fileName mimeType:@"application/octet-stream"]; +} + +@end + +@implementation STHTTPRequestDataUpload + ++ (instancetype)dataUploadWithData:(NSData *)data parameterName:(NSString *)parameterName mimeType:(NSString *)mimeType fileName:(NSString *)fileName { + STHTTPRequestDataUpload *du = [[self alloc] init]; + du.data = data; + du.parameterName = parameterName; + du.mimeType = mimeType; + du.fileName = fileName; + return du; +} + +@end diff --git a/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown new file mode 100644 index 0000000..8a2e7cb --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown @@ -0,0 +1,17 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## STTwitter + +Copyright (c) 2012-2014, Nicolas Seriot +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of the Nicolas Seriot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Generated by CocoaPods - http://cocoapods.org diff --git a/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.plist b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.plist new file mode 100644 index 0000000..d05280f --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-acknowledgements.plist @@ -0,0 +1,47 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2012-2014, Nicolas Seriot +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of the Nicolas Seriot nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Title + STTwitter + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - http://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-dummy.m b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-dummy.m new file mode 100644 index 0000000..ade64bd --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods : NSObject +@end +@implementation PodsDummy_Pods +@end diff --git a/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-resources.sh b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-resources.sh new file mode 100755 index 0000000..ea685a2 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods-resources.sh @@ -0,0 +1,95 @@ +#!/bin/sh +set -e + +mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +realpath() { + DIRECTORY="$(cd "${1%/*}" && pwd)" + FILENAME="${1##*/}" + echo "$DIRECTORY/$FILENAME" +} + +install_resource() +{ + case $1 in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" + ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" + ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" + ;; + *.framework) + echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" + xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" + xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" + xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + /*) + echo "$1" + echo "$1" >> "$RESOURCES_TO_COPY" + ;; + *) + echo "${PODS_ROOT}/$1" + echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" + ;; + esac +} + +mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] +then + case "${TARGETED_DEVICE_FAMILY}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; + esac + + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "`realpath $PODS_ROOT`*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi diff --git a/TalkinToTheNet/Pods/Target Support Files/Pods/Pods.debug.xcconfig b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods.debug.xcconfig new file mode 100644 index 0000000..4f5d9b5 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods.debug.xcconfig @@ -0,0 +1,5 @@ +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/STTwitter" +OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/STTwitter" +OTHER_LDFLAGS = $(inherited) -ObjC -l"STTwitter" -framework "Accounts" -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "Twitter" -framework "UIKit" -weak_framework "Social" +PODS_ROOT = ${SRCROOT}/Pods \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Target Support Files/Pods/Pods.release.xcconfig b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods.release.xcconfig new file mode 100644 index 0000000..4f5d9b5 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/Pods/Pods.release.xcconfig @@ -0,0 +1,5 @@ +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/STTwitter" +OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/STTwitter" +OTHER_LDFLAGS = $(inherited) -ObjC -l"STTwitter" -framework "Accounts" -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "Twitter" -framework "UIKit" -weak_framework "Social" +PODS_ROOT = ${SRCROOT}/Pods \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-Private.xcconfig b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-Private.xcconfig new file mode 100644 index 0000000..ba0b869 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-Private.xcconfig @@ -0,0 +1,6 @@ +#include "STTwitter.xcconfig" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/STTwitter" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/STTwitter" +OTHER_LDFLAGS = ${STTWITTER_OTHER_LDFLAGS} +PODS_ROOT = ${SRCROOT} +SKIP_INSTALL = YES \ No newline at end of file diff --git a/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-dummy.m b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-dummy.m new file mode 100644 index 0000000..84b9a13 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_STTwitter : NSObject +@end +@implementation PodsDummy_STTwitter +@end diff --git a/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-prefix.pch b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-prefix.pch new file mode 100644 index 0000000..aa992a4 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter-prefix.pch @@ -0,0 +1,4 @@ +#ifdef __OBJC__ +#import +#endif + diff --git a/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter.xcconfig b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter.xcconfig new file mode 100644 index 0000000..56daa20 --- /dev/null +++ b/TalkinToTheNet/Pods/Target Support Files/STTwitter/STTwitter.xcconfig @@ -0,0 +1 @@ +STTWITTER_OTHER_LDFLAGS = -framework "Accounts" -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "Twitter" -framework "UIKit" -weak_framework "Social" \ No newline at end of file diff --git a/TalkinToTheNet/TalkinToTheNet.xcworkspace/contents.xcworkspacedata b/TalkinToTheNet/TalkinToTheNet.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1ecaee8 --- /dev/null +++ b/TalkinToTheNet/TalkinToTheNet.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + +