├── Art ├── ios.png ├── osx.png ├── tweet.png └── twitter_app_settings.png ├── Tests ├── STTwitterTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── STTwitterUnitTests-Prefix.pch │ ├── STMiscTests.h │ ├── STOAuthServiceTests.h │ ├── STHTTPRequestUnitTestAdditions │ │ ├── STHTTPRequestTestResponse.m │ │ ├── STHTTPRequestTestResponseQueue.h │ │ ├── STHTTPRequestTestResponse.h │ │ ├── STHTTPRequest+UnitTests.h │ │ ├── STHTTPRequestTestResponseQueue.m │ │ └── STHTTPRequest+UnitTests.m │ ├── STTwitterUnitTests-Info.plist │ └── STMiscTests.m ├── STTwitter │ ├── STTwitter-Prefix.pch │ ├── main.m │ └── STTwitter.1 └── UnitTests.xcodeproj │ └── xcshareddata │ └── xcschemes │ ├── STTwitter.xcscheme │ └── Tests.xcscheme ├── demo_ios └── STTwitterDemoIOS │ ├── en.lproj │ └── InfoPlist.strings │ ├── AppDelegate.h │ ├── WebViewVC.h │ ├── STTwitterDemoIOS-Prefix.pch │ ├── main.m │ ├── Images.xcassets │ ├── LaunchImage.launchimage │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── ViewController.h │ ├── WebViewVC.m │ ├── STTwitterDemoIOS-Info.plist │ └── AppDelegate.m ├── demo_osx └── STTwitterDemoOSX │ ├── _en.lproj │ ├── InfoPlist.strings │ └── Credits.rtf │ ├── en.lproj │ ├── InfoPlist.strings │ └── Credits.rtf │ ├── STTwitterDemoOSX-Prefix.pch │ ├── main.m │ ├── STClientVC.h │ ├── STConsoleVC.h │ ├── AppDelegate.h │ ├── STAuthenticationVC.h │ ├── STTwitterDemoOSX-Info.plist │ ├── AppDelegate.m │ ├── STClientVC.m │ └── STConsoleVC.m ├── .travis.yml ├── STTwitter ├── STTwitter.h ├── STTwitterRequestProtocol.h ├── NSDateFormatter+STTwitter.h ├── NSDateFormatter+STTwitter.m ├── NSString+STTwitter.h ├── STTwitterOS.h ├── STTwitterStreamParser.h ├── STHTTPRequest+STTwitter.h ├── STTwitterAppOnly.h ├── STTwitterHTML.h ├── STTwitterOSRequest.h ├── Vendor │ ├── BAVPlistNode.h │ ├── BAVPlistNode.m │ ├── JSONSyntaxHighlight.h │ ├── STHTTPRequest.h │ └── JSONSyntaxHighlight.m ├── NSString+STTwitter.m ├── STTwitterProtocol.h ├── STTwitterOAuth.h ├── NSError+STTwitter.h ├── STHTTPRequest+STTwitter.m ├── NSError+STTwitter.m ├── STTwitterHTML.m ├── STTwitterStreamParser.m └── STTwitterOSRequest.m ├── demo_cli ├── accounts_creation │ ├── accounts_creation-Prefix.pch │ ├── main.m │ └── accounts_creation.1 ├── TODO.txt ├── streaming │ └── main.m ├── reverse_auth │ └── main.m ├── followers │ └── main.m ├── sttwitter_cli.xcodeproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── t2rss.xcscheme │ │ ├── favorites.xcscheme │ │ ├── followers.xcscheme │ │ ├── streaming.xcscheme │ │ ├── conversation.xcscheme │ │ ├── sttwitter_cli.xcscheme │ │ ├── accounts_creation.xcscheme │ │ ├── streaming_with_bot.xcscheme │ │ └── reverse_auth.xcscheme ├── streaming_with_bot │ └── main.m ├── conversation │ └── main.m ├── t2rss │ └── main.m └── favorites │ └── main.m ├── .gitignore ├── STTwitter.podspec └── LICENCE.txt /Art/ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/STTwitter/master/Art/ios.png -------------------------------------------------------------------------------- /Art/osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/STTwitter/master/Art/osx.png -------------------------------------------------------------------------------- /Art/tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/STTwitter/master/Art/tweet.png -------------------------------------------------------------------------------- /Tests/STTwitterTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Art/twitter_app_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/STTwitter/master/Art/twitter_app_settings.png -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/_en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | script: xctool -project Tests/UnitTests.xcodeproj -scheme STTwitter 4 | -------------------------------------------------------------------------------- /STTwitter/STTwitter.h: -------------------------------------------------------------------------------- 1 | #import "STTwitterAPI.h" 2 | #import "STTwitterHTML.h" 3 | 4 | #import "NSDateFormatter+STTwitter.h" 5 | #import "NSString+STTwitter.h" 6 | #import "NSError+STTwitter.h" 7 | #import "STHTTPRequest+STTwitter.h" 8 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STTwitterDemoOSX-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'STTwitterDemoOSX' target in the 'STTwitterDemoOSX' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Tests/STTwitter/STTwitter-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /demo_cli/accounts_creation/accounts_creation-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STTwitterUnitTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | // #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STMiscTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // STMiscTests.h 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/10/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface STMiscTests : XCTestCase 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STOAuthServiceTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // STOAuthServiceTests.h 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/5/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface STOAuthServiceTests : XCTestCase 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 8/17/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /STTwitter/STTwitterRequestProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterRequestProtocol.h 3 | // STTwitterDemoIOS 4 | // 5 | // Created by Yu Sugawara on 2015/03/22. 6 | // Copyright (c) 2015年 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol STTwitterRequestProtocol 12 | 13 | - (void)cancel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /STTwitter/NSDateFormatter+STTwitter.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDateFormatter+STTwitter.h 3 | // curtter 4 | // 5 | // Created by Nicolas Seriot on 16/11/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSDateFormatter (STTwitter) 12 | 13 | + (NSDateFormatter *)st_TwitterDateFormatter; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // STTwitterDemoiOS 4 | // 5 | // Created by Nicolas Seriot on 10/1/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STClientVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // STClientVC.h 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/22/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | @interface STClientVC : NSViewController 13 | 14 | @property (nonatomic, retain) STTwitterAPI *twitter; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/WebViewVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewVC.h 3 | // STTwitterDemoIOS 4 | // 5 | // Created by Nicolas Seriot on 06/08/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WebViewVC : UIViewController 12 | 13 | @property (nonatomic, strong) IBOutlet UIWebView *webView; 14 | 15 | - (IBAction)cancel:(id)sender; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STConsoleVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // STRequestsVC.h 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/22/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | @interface STConsoleVC : NSViewController 13 | 14 | @property (nonatomic, retain) STTwitterAPI *twitter; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STHTTPRequestUnitTestAdditions/STHTTPRequestTestResponse.m: -------------------------------------------------------------------------------- 1 | #import "STHTTPRequestTestResponse.h" 2 | 3 | @implementation STHTTPRequestTestResponse 4 | 5 | @synthesize block; 6 | 7 | + (STHTTPRequestTestResponse *)testResponseWithBlock:(FillResponseBlock)block { 8 | STHTTPRequestTestResponse *tr = [[STHTTPRequestTestResponse alloc] init]; 9 | tr.block = block; 10 | return tr; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/5/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STAuthenticationVC.h" 11 | 12 | @class STTwitterAPI; 13 | 14 | @interface AppDelegate : NSObject 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/STTwitter/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 14/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | 14 | @autoreleasepool { 15 | 16 | // insert code here... 17 | NSLog(@"Hello, World!"); 18 | 19 | } 20 | return 0; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/STTwitterDemoIOS-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // STTwitterDemoiOS 4 | // 5 | // Created by Nicolas Seriot on 10/1/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STHTTPRequestUnitTestAdditions/STHTTPRequestTestResponseQueue.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class STHTTPRequestTestResponse; 4 | 5 | @interface STHTTPRequestTestResponseQueue : NSObject 6 | 7 | @property (nonatomic, retain) NSMutableArray *responses; 8 | 9 | + (STHTTPRequestTestResponseQueue *)sharedInstance; 10 | 11 | - (void)enqueue:(STHTTPRequestTestResponse *)response; 12 | - (STHTTPRequestTestResponse *)dequeue; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STHTTPRequestUnitTestAdditions/STHTTPRequestTestResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "STHTTPRequest.h" 3 | 4 | // placeholder to simulate server responses 5 | // to be used in a STHTTPRequestTestResponseQueue 6 | 7 | typedef void(^FillResponseBlock)(STHTTPRequest *r); 8 | 9 | @interface STHTTPRequestTestResponse : NSObject 10 | 11 | @property (nonatomic, copy) FillResponseBlock block; 12 | 13 | + (STHTTPRequestTestResponse *)testResponseWithBlock:(FillResponseBlock)block; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STHTTPRequestUnitTestAdditions/STHTTPRequest+UnitTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // STHTTPRequest+UnitTests.h 3 | // 4 | // Created by Nicolas Seriot on 8/8/12. 5 | // 6 | // 7 | 8 | #import "STHTTPRequest.h" 9 | 10 | @interface STHTTPRequest (UnitTests) 11 | 12 | // expose private properties 13 | @property (nonatomic) NSUInteger responseStatus; 14 | @property (nonatomic, retain) NSString *responseString; 15 | @property (nonatomic, retain) NSDictionary *responseHeaders; 16 | @property (nonatomic, retain) NSMutableData *responseData; 17 | @property (nonatomic, retain) NSError *error; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/_en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /demo_cli/TODO.txt: -------------------------------------------------------------------------------- 1 | # requests from 'tweepy' 2 | 3 | 1. List all your friends, in long format, ordered by number of followers 4 | 2. List all your leaders (people you follow who don't follow you back) 5 | 3. Unfollow everyone you follow who doesn't follow you back 6 | 4. Unfollow people who haven't tweeted in the longest time 7 | 5. Who Follow you today 8 | 6. Who Unfollowed you today 9 | 10 | # requests from 'Sultan' 11 | 12 | 7. Top Tweet that got highest Retweet number 13 | 8. Top Retweeters who retweet for me the most 14 | 9. Top Tweet that got highest Favorite number 15 | 10. Top People who favorite my tweets 16 | 11. Top tweets that got highest Reply number 17 | 12. Top People who reply to me the most 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | DerivedData 3 | # http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html 4 | *.pbxuser 5 | # http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 6 | *.mode1v3 7 | *.mode2v3 8 | # http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file 9 | *.perspectivev3 10 | !default.pbxuser 11 | !default.mode1v3 12 | !default.mode2v3 13 | !default.perspectivev3 14 | xcuserdata 15 | # http://stackoverflow.com/questions/1153211/whats-that-classes-1-moved-aside-directory-in-my-classes-directory 16 | *.moved-aside 17 | *.xcuserstate 18 | # http://stackoverflow.com/questions/18340453/should-xccheckout-files-in-xcode5-be-ignored-under-vcs 19 | *.xccheckout 20 | Pods 21 | .DS_Store 22 | *.xcworkspace 23 | !default.xcworkspace -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STAuthenticationVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // STAuthenticationVC.h 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/22/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "STTwitter.h" 12 | 13 | @class STAuthenticationVC; 14 | 15 | @protocol STAuthenticationVCDelegate 16 | - (void)authenticationVC:(STAuthenticationVC *)sender didChangeTwitterObject:(STTwitterAPI *)twitter; 17 | @end 18 | 19 | typedef void (^UsernamePasswordBlock_t)(NSString *username, NSString *password); 20 | 21 | @interface STAuthenticationVC : NSViewController 22 | 23 | @property (nonatomic, assign) id delegate; 24 | 25 | - (void)reloadTokenFile; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Tests/STTwitterTests/STTwitterUnitTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ch.seriot.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /STTwitter/NSDateFormatter+STTwitter.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDateFormatter+STTwitter.m 3 | // curtter 4 | // 5 | // Created by Nicolas Seriot on 16/11/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "NSDateFormatter+STTwitter.h" 10 | 11 | static NSDateFormatter *stTwitterDateFormatter = nil; 12 | 13 | @implementation NSDateFormatter (STTwitter) 14 | 15 | + (NSDateFormatter *)st_TwitterDateFormatter { 16 | 17 | // parses the 'created_at' field, eg. "Sun Jun 28 20:33:01 +0000 2009" 18 | 19 | if(stTwitterDateFormatter == nil) { 20 | stTwitterDateFormatter = [[NSDateFormatter alloc] init]; 21 | [stTwitterDateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; 22 | [stTwitterDateFormatter setDateFormat:@"EEE MMM dd HH:mm:ss Z yyyy"]; 23 | } 24 | 25 | return stTwitterDateFormatter; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /STTwitter.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "STTwitter" 3 | s.version = "0.2.6" 4 | s.summary = "A stable, mature and comprehensive Objective-C library for Twitter REST API 1.1" 5 | s.homepage = "https://github.com/nst/STTwitter" 6 | 7 | s.license = { 8 | :type => 'New BSD', 9 | :file => 'LICENCE.txt' 10 | } 11 | 12 | s.homepage = "https://github.com/nst/STTwitter/" 13 | s.authors = 'Nicolas Seriot', 'Bavarious', 'Chris Ricca', 'Evan Roth', 'Frank Dowsett', 'James Howard', 'Matthew Tomlinson', 'Pawel Niewiadomski', 'Sven Weidauer', 'Thijs Alkemade', 'Victor Ng', 'b123400', 'daisy1754', 'forcha', 'germanSancho', 'ijaycho', 'passwordreset' 14 | s.source = { :git => "https://github.com/nst/STTwitter.git", :tag => '0.2.6' } 15 | 16 | s.ios.deployment_target = '7.0' 17 | s.osx.deployment_target = '10.7' 18 | 19 | s.source_files = 'STTwitter/*.{h,m}', 'STTwitter/Vendor/*.{h,m}' 20 | 21 | s.ios.frameworks = 'CoreGraphics', 'Foundation', 'QuartzCore', 'UIKit', 'Accounts', 'Twitter' 22 | s.ios.weak_frameworks = 'Social' 23 | s.requires_arc = true 24 | end 25 | -------------------------------------------------------------------------------- /STTwitter/NSString+STTwitter.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+STTwitter.h 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 11/2/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSUInteger kSTTwitterDefaultShortURLLength; 12 | extern NSUInteger kSTTwitterDefaultShortURLLengthHTTPS; 13 | 14 | extern NSString *kSTPOSTDataKey; // dummy parameter to tell a key used to post raw media, necessary because media are ignored in OAuth signatures 15 | extern NSString *kSTPOSTMediaFileNameKey; // dummy parameter to tell the name of a file to be uploaded, optional but more correct than none 16 | 17 | @interface NSString (STTwitter) 18 | 19 | - (NSString *)st_firstMatchWithRegex:(NSString *)regex error:(NSError **)e; 20 | 21 | // use values from GET help/configuration 22 | - (NSInteger)st_numberOfCharactersInATweetWithShortURLLength:(NSUInteger)shortURLLength 23 | shortURLLengthHTTPS:(NSUInteger)shortURLLengthHTTS; 24 | 25 | // use default values for URL shortening 26 | - (NSInteger)st_numberOfCharactersInATweet; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /STTwitter/STTwitterOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterOS.h 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 5/1/10. 6 | // Copyright 2010 seriot.ch. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitterProtocol.h" 11 | 12 | extern NS_ENUM(NSUInteger, STTwitterOSErrorCode) { 13 | STTwitterOSSystemCannotAccessTwitter = 0, 14 | STTwitterOSCannotFindTwitterAccount, 15 | STTwitterOSUserDeniedAccessToTheirAccounts, 16 | STTwitterOSNoTwitterAccountIsAvailable, 17 | STTwitterOSTwitterAccountInvalid 18 | }; 19 | 20 | @class ACAccount; 21 | 22 | extern NSString * const STTwitterOSInvalidatedAccount; 23 | 24 | @interface STTwitterOS : NSObject 25 | 26 | @property (nonatomic) NSTimeInterval timeoutInSeconds; 27 | 28 | + (instancetype)twitterAPIOSWithAccount:(ACAccount *)account; 29 | + (instancetype)twitterAPIOSWithFirstAccount; 30 | 31 | - (NSString *)username; 32 | - (NSString *)userID; 33 | 34 | // useful for the so-called 'OAuth Echo' https://dev.twitter.com/twitter-kit/ios/oauth-echo 35 | 36 | - (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // STTwitterDemoiOSSafari 4 | // 5 | // Created by Nicolas Seriot on 10/1/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | @interface ViewController : UIViewController 13 | 14 | @property (nonatomic, strong) NSArray *statuses; 15 | 16 | @property (nonatomic, weak) IBOutlet UITextField *consumerKeyTextField; 17 | @property (nonatomic, weak) IBOutlet UITextField *consumerSecretTextField; 18 | @property (nonatomic, weak) IBOutlet UILabel *loginStatusLabel; 19 | @property (nonatomic, weak) IBOutlet UILabel *getTimelineStatusLabel; 20 | @property (nonatomic, weak) IBOutlet UITableView *tableView; 21 | @property (nonatomic, weak) IBOutlet UISwitch *openSafariSwitch; 22 | 23 | - (IBAction)loginWithiOSAction:(id)sender; 24 | - (IBAction)loginOnTheWebAction:(id)sender; 25 | - (IBAction)reverseAuthAction:(id)sender; 26 | - (IBAction)getTimelineAction:(id)sender; 27 | 28 | - (void)setOAuthToken:(NSString *)token oauthVerifier:(NSString *)verfier; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /STTwitter/STTwitterStreamParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterParser.h 3 | // STTwitterDemoIOS 4 | // 5 | // Created by Yu Sugawara on 2015/03/23. 6 | // Copyright (c) 2015年 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, STTwitterStreamJSONType) { 12 | STTwitterStreamJSONTypeTweet, 13 | STTwitterStreamJSONTypeFriendsLists, 14 | STTwitterStreamJSONTypeDelete, 15 | STTwitterStreamJSONTypeScrubGeo, 16 | STTwitterStreamJSONTypeLimit, 17 | STTwitterStreamJSONTypeDisconnect, 18 | STTwitterStreamJSONTypeWarning, 19 | STTwitterStreamJSONTypeEvent, 20 | STTwitterStreamJSONTypeStatusWithheld, 21 | STTwitterStreamJSONTypeUserWithheld, 22 | STTwitterStreamJSONTypeControl, 23 | STTwitterStreamJSONTypeDirectMessage, 24 | STTwitterStreamJSONTypeUnsupported, 25 | }; 26 | 27 | extern NSString *NSStringFromSTTwitterStreamJSONType(STTwitterStreamJSONType type); 28 | 29 | @interface STTwitterStreamParser : NSObject 30 | 31 | - (void)parseWithStreamData:(NSData *)data 32 | parsedJSONBlock:(void (^)(NSDictionary *json, STTwitterStreamJSONType type))parsedJsonBlock; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STTwitterDemoOSX-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | ch.seriot.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2013 Nicolas Seriot. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STHTTPRequestUnitTestAdditions/STHTTPRequestTestResponseQueue.m: -------------------------------------------------------------------------------- 1 | #import "STHTTPRequestTestResponseQueue.h" 2 | #import "STHTTPRequestTestResponse.h" 3 | 4 | static STHTTPRequestTestResponseQueue *sharedInstance = nil; 5 | 6 | @implementation STHTTPRequestTestResponseQueue 7 | 8 | + (STHTTPRequestTestResponseQueue *)sharedInstance { 9 | if(sharedInstance == nil) { 10 | sharedInstance = [[STHTTPRequestTestResponseQueue alloc] init]; 11 | } 12 | return sharedInstance; 13 | } 14 | 15 | - (id)init { 16 | self = [super init]; 17 | self.responses = [NSMutableArray array]; 18 | return self; 19 | } 20 | 21 | /**/ 22 | 23 | - (void)enqueue:(STHTTPRequestTestResponse *)response { 24 | NSAssert(response != nil, @"can't enqueue nil"); 25 | 26 | [_responses insertObject:response atIndex:0]; 27 | } 28 | 29 | - (STHTTPRequestTestResponse *)dequeue { 30 | 31 | NSAssert([_responses count] > 0, @"can't dequeue because queue is empty, count is %lu", [_responses count]); 32 | 33 | if([_responses count] == 0) { 34 | return nil; 35 | } 36 | 37 | NSUInteger lastIndex = [_responses count] - 1; 38 | 39 | STHTTPRequestTestResponse *response = [_responses objectAtIndex:lastIndex]; 40 | 41 | [_responses removeObjectAtIndex:lastIndex]; 42 | 43 | return response; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/WebViewVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewVC.m 3 | // STTwitterDemoIOS 4 | // 5 | // Created by Nicolas Seriot on 06/08/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "WebViewVC.h" 10 | 11 | @interface WebViewVC () 12 | 13 | @end 14 | 15 | @implementation WebViewVC 16 | 17 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 18 | { 19 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 20 | if (self) { 21 | // Custom initialization 22 | } 23 | return self; 24 | } 25 | 26 | - (void)viewDidLoad 27 | { 28 | [super viewDidLoad]; 29 | // Do any additional setup after loading the view. 30 | } 31 | 32 | - (void)didReceiveMemoryWarning 33 | { 34 | [super didReceiveMemoryWarning]; 35 | // Dispose of any resources that can be recreated. 36 | } 37 | 38 | - (IBAction)cancel:(id)sender { 39 | [self dismissViewControllerAnimated:YES completion:^{ 40 | // 41 | }]; 42 | } 43 | 44 | /* 45 | #pragma mark - Navigation 46 | 47 | // In a storyboard-based application, you will often want to do a little preparation before navigation 48 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 49 | { 50 | // Get the new view controller using [segue destinationViewController]. 51 | // Pass the selected object to the new view controller. 52 | } 53 | */ 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /STTwitter/STHTTPRequest+STTwitter.h: -------------------------------------------------------------------------------- 1 | // 2 | // STHTTPRequest+STTwitter.h 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 8/6/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STHTTPRequest.h" 10 | #import "STTwitterRequestProtocol.h" 11 | 12 | @interface STHTTPRequest (STTwitter) 13 | 14 | + (STHTTPRequest *)twitterRequestWithURLString:(NSString *)urlString 15 | HTTPMethod:(NSString *)HTTPMethod 16 | timeoutInSeconds:(NSTimeInterval)timeoutInSeconds 17 | stTwitterUploadProgressBlock:(void(^)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))uploadProgressBlock 18 | stTwitterDownloadProgressBlock:(void(^)(NSData *data, int64_t totalBytesReceived, int64_t totalBytesExpectedToReceive))stTwitterDownloadProgressBlock 19 | stTwitterSuccessBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock 20 | stTwitterErrorBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; 21 | 22 | + (void)expandedURLStringForShortenedURLString:(NSString *)urlString 23 | successBlock:(void(^)(NSString *expandedURLString))successBlock 24 | errorBlock:(void(^)(NSError *error))errorBlock; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014, Nicolas Seriot 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * 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. 8 | * 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. 9 | 10 | 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. 11 | -------------------------------------------------------------------------------- /STTwitter/STTwitterAppOnly.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterAppOnly.h 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 3/13/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitterProtocol.h" 11 | 12 | #if DEBUG 13 | # define STLog(...) NSLog(__VA_ARGS__) 14 | #else 15 | # define STLog(...) 16 | #endif 17 | 18 | extern NS_ENUM(NSUInteger, STTwitterAppOnlyErrorCode) { 19 | STTwitterAppOnlyCannotFindBearerTokenToBeInvalidated = 0, 20 | STTwitterAppOnlyCannotFindJSONInResponse, 21 | STTwitterAppOnlyCannotFindBearerTokenInResponse 22 | }; 23 | 24 | @interface STTwitterAppOnly : NSObject { 25 | 26 | } 27 | 28 | @property (nonatomic, retain) NSString *consumerName; 29 | @property (nonatomic, retain) NSString *consumerKey; 30 | @property (nonatomic, retain) NSString *consumerSecret; 31 | @property (nonatomic, retain) NSString *bearerToken; 32 | 33 | @property (nonatomic) NSTimeInterval timeoutInSeconds; 34 | 35 | + (instancetype)twitterAppOnlyWithConsumerName:(NSString *)conumerName consumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret; 36 | 37 | + (NSString *)base64EncodedBearerTokenCredentialsWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret; 38 | 39 | - (void)invalidateBearerTokenWithSuccessBlock:(void(^)())successBlock 40 | errorBlock:(void(^)(NSError *error))errorBlock; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /STTwitter/STTwitterHTML.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterWeb.h 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/13/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NS_ENUM(NSUInteger, STTwitterHTMLErrorCode) { 12 | STTwitterHTMLCannotPostWithoutCredentials = 0 13 | }; 14 | 15 | @interface STTwitterHTML : NSObject 16 | 17 | - (void)getLoginForm:(void(^)(NSString *authenticityToken))successBlock 18 | errorBlock:(void(^)(NSError *error))errorBlock; 19 | 20 | - (void)postLoginFormWithUsername:(NSString *)username 21 | password:(NSString *)password 22 | authenticityToken:(NSString *)authenticityToken 23 | successBlock:(void(^)(NSString *body))successBlock 24 | errorBlock:(void(^)(NSError *error))errorBlock; 25 | 26 | 27 | /**/ 28 | 29 | - (void)getAuthorizeFormAtURL:(NSURL *)url 30 | successBlock:(void(^)(NSString *authenticityToken, NSString *oauthToken))successBlock 31 | errorBlock:(void(^)(NSError *error))errorBlock; 32 | 33 | - (void)postAuthorizeFormResultsAtURL:(NSURL *)url 34 | authenticityToken:(NSString *)authenticityToken 35 | oauthToken:(NSString *)oauthToken 36 | successBlock:(void(^)(NSString *PIN))successBlock 37 | errorBlock:(void(^)(NSError *error))errorBlock; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /STTwitter/STTwitterOSRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterOSRequest.h 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 20/02/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitterRequestProtocol.h" 11 | 12 | @class ACAccount; 13 | 14 | @interface STTwitterOSRequest : NSObject 15 | 16 | - (instancetype)initWithAPIResource:(NSString *)resource 17 | baseURLString:(NSString *)baseURLString 18 | httpMethod:(NSInteger)httpMethod 19 | parameters:(NSDictionary *)params 20 | account:(ACAccount *)account 21 | timeoutInSeconds:(NSTimeInterval)timeoutInSeconds 22 | uploadProgressBlock:(void(^)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))uploadProgressBlock 23 | streamBlock:(void(^)(NSObject *request, NSData *data))streamBlock 24 | completionBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))completionBlock 25 | errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; 26 | 27 | - (void)startRequest; 28 | - (NSURLRequest *)preparedURLRequest; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /demo_cli/accounts_creation/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // accounts_creation 4 | // 5 | // Created by Nicolas Seriot on 17/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | int main(int argc, const char * argv[]) 13 | { 14 | 15 | @autoreleasepool { 16 | 17 | STTwitterAPI *t = [STTwitterAPI twitterAPIWithOAuthConsumerKey:@"IHUYavQ7mmPBhNiBBlF9Q" 18 | consumerSecret:@"cIBZ..." 19 | oauthToken:@"179654598-yukdZLZcDfxU5PZBZdJcCpaJF5bKUJTdxXoxUZ9u" 20 | oauthTokenSecret:@"YEhw..."]; 21 | 22 | [t _postAccountGenerateWithADC:@"pad" 23 | discoverableByEmail:YES 24 | email:@"EMAIL" 25 | geoEnabled:NO 26 | language:@"en" 27 | name:@"NAME" 28 | password:@"PASSWORD" 29 | screenName:@"SCREEN_NAME" 30 | sendErrorCode:YES 31 | timeZone:@"CEST" 32 | successBlock:^(id userProfile) { 33 | NSLog(@"-- userProfile: %@", userProfile); 34 | } errorBlock:^(NSError *error) { 35 | NSLog(@"-- error: %@", error); 36 | }]; 37 | 38 | [[NSRunLoop currentRunLoop] run]; 39 | } 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /STTwitter/Vendor/BAVPlistNode.h: -------------------------------------------------------------------------------- 1 | // 2 | // BAVPlistNode.h 3 | // Plistorious 4 | // 5 | // Created by Bavarious on 01/10/2013. 6 | // Copyright (c) 2013 No Organisation. All rights reserved. 7 | // 8 | 9 | /* 10 | The MIT License (MIT) 11 | 12 | Copyright (c) 2013 Bavarious 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | */ 32 | 33 | #import 34 | 35 | @interface BAVPlistNode : NSObject 36 | @property (nonatomic, copy) NSString *key; 37 | @property (nonatomic, copy) NSString *type; 38 | @property (nonatomic, copy) NSObject *value; 39 | @property (nonatomic, copy) NSArray *children; 40 | @property (nonatomic, assign, getter = isCollection) bool collection; 41 | 42 | + (instancetype)plistNodeFromObject:(id)object key:(NSString *)key; 43 | @end 44 | -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/STTwitterDemoIOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | ch.seriot.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | LSApplicationQueriesSchemes 30 | 31 | myapp 32 | 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | CFBundleURLTypes 44 | 45 | 46 | CFBundleTypeRole 47 | Viewer 48 | CFBundleURLName 49 | ch.seriot.STTwitterDemoiOSSafari 50 | CFBundleURLSchemes 51 | 52 | myapp 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /demo_cli/streaming/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // streaming 4 | // 5 | // Created by Nicolas Seriot on 02/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | int main(int argc, const char * argv[]) 13 | { 14 | 15 | @autoreleasepool { 16 | 17 | STTwitterAPI *twitter = [STTwitterAPI twitterAPIOSWithFirstAccount]; 18 | 19 | [twitter verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { 20 | NSLog(@"-- Account: %@", username); 21 | 22 | [twitter postStatusesFilterUserIDs:nil 23 | keywordsToTrack:@[@"Apple"] 24 | locationBoundingBoxes:nil 25 | stallWarnings:nil 26 | progressBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { 27 | 28 | if (type != STTwitterStreamJSONTypeTweet) { 29 | NSLog(@"Invalid tweet (class %@): %@", [json class], json); 30 | exit(1); 31 | return; 32 | } 33 | 34 | printf("-----------------------------------------------------------------\n"); 35 | printf("-- user: @%s\n", [[json valueForKeyPath:@"user.screen_name"] cStringUsingEncoding:NSUTF8StringEncoding]); 36 | printf("-- text: %s\n", [[json objectForKey:@"text"] cStringUsingEncoding:NSUTF8StringEncoding]); 37 | 38 | } errorBlock:^(NSError *error) { 39 | NSLog(@"Stream error: %@", error); 40 | exit(1); 41 | }]; 42 | 43 | } errorBlock:^(NSError *error) { 44 | NSLog(@"-- %@", [error localizedDescription]); 45 | exit(1); 46 | }]; 47 | 48 | /**/ 49 | 50 | [[NSRunLoop currentRunLoop] run]; 51 | 52 | } 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /demo_cli/reverse_auth/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // streaming 4 | // 5 | // Created by Nicolas Seriot on 02/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | int main(int argc, const char * argv[]) 13 | { 14 | 15 | @autoreleasepool { 16 | 17 | NSString * const TWITTER_CONSUMER_KEY = @""; 18 | NSString * const TWITTER_CONSUMER_SECRET_KEY = @""; 19 | 20 | if ([TWITTER_CONSUMER_KEY length] == 0) { 21 | NSLog(@"You need to set a Consumer Key."); 22 | exit(1); 23 | 24 | } 25 | if ([TWITTER_CONSUMER_SECRET_KEY length] == 0) { 26 | NSLog(@"You need to set a Consumer Seacret Key."); 27 | exit(1); 28 | } 29 | 30 | STTwitterAPI *twitter = [STTwitterAPI twitterAPIWithOAuthConsumerName:nil consumerKey:TWITTER_CONSUMER_KEY consumerSecret:TWITTER_CONSUMER_SECRET_KEY]; 31 | [twitter postReverseOAuthTokenRequest:^(NSString *authenticationHeader) { 32 | 33 | STTwitterAPI *twitterAPIOS = [STTwitterAPI twitterAPIOSWithFirstAccount]; 34 | [twitterAPIOS verifyCredentialsWithSuccessBlock:^(NSString *username) { 35 | 36 | [twitterAPIOS postReverseAuthAccessTokenWithAuthenticationHeader:authenticationHeader successBlock:^(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName) { 37 | 38 | NSLog(@"REVERSE AUTH OK"); 39 | exit(0); 40 | 41 | } errorBlock:^(NSError *error) { 42 | 43 | NSLog(@"ERROR, %@", [error localizedDescription]); 44 | exit(1); 45 | 46 | }]; 47 | 48 | } errorBlock:^(NSError *error) { 49 | 50 | NSLog(@"ERROR"); 51 | exit(1); 52 | 53 | }]; 54 | 55 | } errorBlock:^(NSError *error) { 56 | 57 | NSLog(@"ERROR"); 58 | exit(1); 59 | 60 | }]; 61 | 62 | 63 | /**/ 64 | 65 | [[NSRunLoop currentRunLoop] run]; 66 | 67 | } 68 | 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STHTTPRequestUnitTestAdditions/STHTTPRequest+UnitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // STHTTPRequest+UnitTests.m 3 | // 4 | // Created by Nicolas Seriot on 8/8/12. 5 | // 6 | // 7 | 8 | #import "STHTTPRequest+UnitTests.h" 9 | #import "STHTTPRequestTestResponse.h" 10 | #import "STHTTPRequestTestResponseQueue.h" 11 | 12 | #import 13 | #import 14 | 15 | void Swizzle(Class c, SEL orig, SEL new) { 16 | Method origMethod = class_getInstanceMethod(c, orig); 17 | Method newMethod = class_getInstanceMethod(c, new); 18 | if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) 19 | class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 20 | else 21 | method_exchangeImplementations(origMethod, newMethod); 22 | } 23 | 24 | @implementation STHTTPRequest (UnitTests) 25 | 26 | @dynamic responseStatus; 27 | @dynamic responseString; 28 | @dynamic responseHeaders; 29 | @dynamic responseData; 30 | @dynamic error; 31 | 32 | + (void)initialize { 33 | Swizzle([STHTTPRequest class], @selector(startAsynchronous), @selector(unitTests_startAsynchronous)); 34 | Swizzle([STHTTPRequest class], @selector(unitTests_startSynchronousWithError:), @selector(startSynchronousWithError:)); 35 | } 36 | 37 | - (void)unitTests_startAsynchronous { 38 | 39 | NSAssert(self.errorBlock != nil, @"errorBlock needed"); 40 | 41 | STHTTPRequestTestResponse *tr = [[STHTTPRequestTestResponseQueue sharedInstance] dequeue]; 42 | 43 | NSAssert(tr.block != nil, @"block needed"); 44 | 45 | tr.block(self); // simulate network response 46 | 47 | BOOL success = self.responseStatus < 400; 48 | 49 | if(success) { 50 | if(self.completionBlock) { 51 | self.completionBlock(self.responseHeaders, self.responseString); 52 | } else if(self.completionDataBlock) { 53 | self.completionDataBlock(self.responseHeaders, self.responseData); 54 | } 55 | } else { 56 | self.errorBlock(self.error); 57 | } 58 | } 59 | 60 | - (NSString *)unitTests_startSynchronousWithError:(NSError **)error { 61 | 62 | STHTTPRequestTestResponse *tr = [[STHTTPRequestTestResponseQueue sharedInstance] dequeue]; 63 | 64 | NSAssert(tr.block != nil, @"block needed"); 65 | 66 | tr.block(self); 67 | 68 | BOOL success = self.responseStatus < 400; 69 | 70 | if(success) { 71 | return self.responseString; 72 | } else { 73 | *error = self.error; 74 | return nil; 75 | } 76 | 77 | return self.responseString; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/5/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "STTwitter.h" 11 | #import "STConsoleVC.h" 12 | #import "STClientVC.h" 13 | 14 | @interface AppDelegate () 15 | 16 | @property (assign) IBOutlet NSWindow *window; 17 | @property (assign) IBOutlet NSTabView *tabView; 18 | 19 | @property (nonatomic, strong) STAuthenticationVC *authenticationVC; 20 | @property (nonatomic, strong) STConsoleVC *requestsVC; 21 | @property (nonatomic, strong) STClientVC *clientVC; 22 | 23 | @end 24 | 25 | @implementation AppDelegate 26 | 27 | - (void)awakeFromNib { 28 | 29 | // [self.window setContentBorderThickness:24.0 forEdge:NSMinYEdge]; 30 | // [self.window setAutorecalculatesContentBorderThickness:NO forEdge:NSMinYEdge]; 31 | 32 | /**/ 33 | 34 | [_tabView selectFirstTabViewItem:nil]; 35 | 36 | self.authenticationVC = [[STAuthenticationVC alloc] initWithNibName:@"STAuthenticationVC" bundle:nil]; 37 | _authenticationVC.delegate = self; 38 | 39 | NSTabViewItem *tvi1 = [_tabView tabViewItemAtIndex:0]; 40 | [tvi1 setView:_authenticationVC.view]; 41 | 42 | self.clientVC = [[STClientVC alloc] initWithNibName:@"STClientVC" bundle:nil]; 43 | 44 | NSTabViewItem *tvi2 = [_tabView tabViewItemAtIndex:1]; 45 | [tvi2 setView:_clientVC.view]; 46 | 47 | self.requestsVC = [[STConsoleVC alloc] initWithNibName:@"STConsoleVC" bundle:nil]; 48 | 49 | NSTabViewItem *tvi3 = [_tabView tabViewItemAtIndex:2]; 50 | [tvi3 setView:_requestsVC.view]; 51 | } 52 | 53 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 54 | // ... 55 | } 56 | 57 | - (void)applicationDidBecomeActive:(NSNotification *)notification { 58 | [_authenticationVC reloadTokenFile]; 59 | } 60 | 61 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { 62 | return YES; 63 | } 64 | 65 | #pragma mark NSTabViewDelegate 66 | 67 | - (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem { 68 | return YES; 69 | } 70 | 71 | - (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem {} 72 | 73 | - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem { 74 | 75 | } 76 | 77 | - (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView {} 78 | 79 | #pragma mark STAuthenticationVCDelegate 80 | 81 | - (void)authenticationVC:(STAuthenticationVC *)sender didChangeTwitterObject:(STTwitterAPI *)twitter { 82 | _requestsVC.twitter = twitter; 83 | _clientVC.twitter = twitter; 84 | 85 | [self.window setTitle:[twitter prettyDescription]]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /demo_cli/followers/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // streaming 4 | // 5 | // Created by Nicolas Seriot on 03/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | int main(int argc, const char * argv[]) 13 | { 14 | 15 | @autoreleasepool { 16 | 17 | __block NSMutableArray *followersIDs = [NSMutableArray array]; 18 | 19 | STTwitterAPI *t = [STTwitterAPI twitterAPIWithOAuthConsumerKey:@"" 20 | consumerSecret:@"" 21 | username:@"" 22 | password:@""]; 23 | 24 | [t verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { 25 | 26 | [t fetchAndFollowCursorsForResource:@"followers/ids.json" 27 | HTTPMethod:@"GET" 28 | baseURLString:@"https://api.twitter.com/1.1" 29 | parameters:@{@"screen_name":@"0xcharlie"} 30 | uploadProgressBlock:nil 31 | downloadProgressBlock:nil 32 | successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response, BOOL morePagesToCome, BOOL *stop) { 33 | NSLog(@"-- success, more to come: %d, %@", morePagesToCome, response); 34 | 35 | NSArray *ids = [response valueForKey:@"ids"]; 36 | if(ids) { 37 | [followersIDs addObjectsFromArray:ids]; 38 | NSLog(@"-- added %lu IDs, more to come: %d", [ids count], morePagesToCome); 39 | } 40 | 41 | if(morePagesToCome == NO) { 42 | NSLog(@"-- IDs count: %lu", (unsigned long)[followersIDs count]); 43 | NSLog(@"-- IDs: %@", followersIDs); 44 | } 45 | 46 | } pauseBlock:^(NSDate *nextRequestDate) { 47 | NSLog(@"-- rate limit exhausted, nextRequestDate: %@", nextRequestDate); 48 | } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { 49 | NSLog(@"-- %@", error); 50 | }]; 51 | 52 | } errorBlock:^(NSError *error) { 53 | NSLog(@"--1 %@", error); 54 | }]; 55 | 56 | /**/ 57 | 58 | [[NSRunLoop currentRunLoop] run]; 59 | 60 | } 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /STTwitter/Vendor/BAVPlistNode.m: -------------------------------------------------------------------------------- 1 | // 2 | // BAVPlistNode.m 3 | // Plistorious 4 | // 5 | // Created by Bavarious on 01/10/2013. 6 | // Copyright (c) 2013 No Organisation. All rights reserved. 7 | // 8 | 9 | #import "BAVPlistNode.h" 10 | 11 | 12 | static NSString *typeForObject(id object) { 13 | return ([object isKindOfClass:[NSArray class]] ? @"Array" : 14 | [object isKindOfClass:[NSDictionary class]] ? @"Dictionary" : 15 | [object isKindOfClass:[NSString class]] ? @"String" : 16 | [object isKindOfClass:[NSData class]] ? @"Data" : 17 | [object isKindOfClass:[NSDate class]] ? @"Date" : 18 | object == (id)kCFBooleanTrue || object == (id)kCFBooleanFalse ? @"Boolean" : 19 | [object isKindOfClass:[NSNumber class]] ? @"Number" : 20 | [object isKindOfClass:[NSNull class]] ? @"Null" : 21 | @"Unknown"); 22 | } 23 | 24 | static NSString *formatItemCount(NSUInteger count) { 25 | return (count == 1 ? @"1 item" : [NSString stringWithFormat:@"%@ items", @(count)]); 26 | } 27 | 28 | 29 | @implementation BAVPlistNode 30 | 31 | + (instancetype)plistNodeFromObject:(id)object key:(NSString *)key 32 | { 33 | BAVPlistNode *newNode = [BAVPlistNode new]; 34 | newNode.key = key; 35 | newNode.type = typeForObject(object); 36 | 37 | if ([object isKindOfClass:[NSArray class]]) { 38 | NSArray *array = object; 39 | 40 | NSMutableArray *children = [NSMutableArray new]; 41 | NSUInteger elementIndex = 0; 42 | for (id element in array) { 43 | NSString *elementKey = [NSString stringWithFormat:@"Item %@", @(elementIndex)]; 44 | [children addObject:[self plistNodeFromObject:element key:elementKey]]; 45 | elementIndex++; 46 | } 47 | 48 | newNode.value = formatItemCount(array.count); 49 | newNode.children = children; 50 | } 51 | else if ([object isKindOfClass:[NSDictionary class]]) { 52 | NSDictionary *dictionary = object; 53 | NSArray *keys = [dictionary.allKeys sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; 54 | 55 | NSMutableArray *children = [NSMutableArray new]; 56 | for (NSString *elementKey in keys) 57 | [children addObject:[self plistNodeFromObject:dictionary[elementKey] key:elementKey]]; 58 | 59 | newNode.value = formatItemCount(keys.count); 60 | newNode.children = children; 61 | } 62 | else if ([object isKindOfClass:[NSNull class]]) { 63 | newNode.value = @"null"; 64 | } 65 | else if (object == (id)kCFBooleanTrue) { 66 | newNode.value = @"true"; 67 | } 68 | else if (object == (id)kCFBooleanFalse) { 69 | newNode.value = @"false"; 70 | } 71 | else { 72 | newNode.value = [NSString stringWithFormat:@"%@", object]; 73 | } 74 | 75 | return newNode; 76 | } 77 | 78 | - (bool)isCollection 79 | { 80 | return [self.type isEqualToString:@"Array"] || [self.type isEqualToString:@"Dictionary"]; 81 | } 82 | 83 | - (NSString *)description 84 | { 85 | return [NSString stringWithFormat:@"%@ node with key %@", self.type, self.key]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /STTwitter/NSString+STTwitter.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+STTwitter.m 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 11/2/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "NSString+STTwitter.h" 10 | 11 | NSUInteger kSTTwitterDefaultShortURLLength = 22; 12 | NSUInteger kSTTwitterDefaultShortURLLengthHTTPS = 23; 13 | 14 | NSString *kSTPOSTDataKey = @"kSTPOSTDataKey"; 15 | NSString *kSTPOSTMediaFileNameKey = @"kSTPOSTMediaFileNameKey"; 16 | 17 | @implementation NSString (STTwitter) 18 | 19 | - (NSString *)st_firstMatchWithRegex:(NSString *)regex error:(NSError **)e { 20 | NSError *error = nil; 21 | NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:&error]; 22 | 23 | if(re == nil) { 24 | if(e) *e = error; 25 | return nil; 26 | } 27 | 28 | NSArray *matches = [re matchesInString:self options:0 range:NSMakeRange(0, [self length])]; 29 | 30 | if([matches count] == 0) { 31 | NSString *errorDescription = [NSString stringWithFormat:@"Can't find a match for regex: %@", regex]; 32 | if(e) *e = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; 33 | return nil; 34 | } 35 | 36 | NSTextCheckingResult *match = [matches lastObject]; 37 | NSRange matchRange = [match rangeAtIndex:1]; 38 | return [self substringWithRange:matchRange]; 39 | } 40 | 41 | // use values from GET help/configuration 42 | - (NSInteger)st_numberOfCharactersInATweetWithShortURLLength:(NSUInteger)shortURLLength shortURLLengthHTTPS:(NSUInteger)shortURLLengthHTTPS { 43 | 44 | // NFC normalized string https://dev.twitter.com/docs/counting-characters 45 | NSString *s = [self precomposedStringWithCanonicalMapping]; 46 | 47 | __block NSInteger count = 0; 48 | [s enumerateSubstringsInRange:NSMakeRange(0, s.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *subString, NSRange subStringRange, NSRange enclosingRange, BOOL *stop) { 49 | count++; 50 | }]; 51 | 52 | NSError *error = nil; 53 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(https?://[A-Za-z0-9_\\.\\-/#\?=&]+)" 54 | options:0 55 | error:&error]; 56 | 57 | NSArray *matches = [regex matchesInString:s 58 | options:0 59 | range:NSMakeRange(0, [s length])]; 60 | 61 | for (NSTextCheckingResult *match in matches) { 62 | NSRange urlRange = [match rangeAtIndex:1]; 63 | NSString *urlString = [s substringWithRange:urlRange]; 64 | 65 | count -= urlRange.length; 66 | count += [urlString hasPrefix:@"https"] ? shortURLLengthHTTPS : shortURLLength; 67 | } 68 | 69 | return count; 70 | } 71 | 72 | // use default values for URL shortening 73 | - (NSInteger)st_numberOfCharactersInATweet { 74 | return [self st_numberOfCharactersInATweetWithShortURLLength:kSTTwitterDefaultShortURLLength 75 | shortURLLengthHTTPS:kSTTwitterDefaultShortURLLengthHTTPS]; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Tests/STTwitter/STTwitter.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 14/05/14 \" DATE 7 | .Dt STTwitter 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm STTwitter, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /demo_cli/accounts_creation/accounts_creation.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 17/05/14 \" DATE 7 | .Dt accounts_creation 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm accounts_creation, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /demo_ios/STTwitterDemoIOS/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // STTwitterDemoiOS 4 | // 5 | // Created by Nicolas Seriot on 10/1/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | // Override point for customization after application launch. 17 | return YES; 18 | } 19 | 20 | - (void)applicationWillResignActive:(UIApplication *)application 21 | { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | - (void)applicationDidEnterBackground:(UIApplication *)application 27 | { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | - (void)applicationWillEnterForeground:(UIApplication *)application 33 | { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application 38 | { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application 43 | { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | - (NSDictionary *)parametersDictionaryFromQueryString:(NSString *)queryString { 48 | 49 | NSMutableDictionary *md = [NSMutableDictionary dictionary]; 50 | 51 | NSArray *queryComponents = [queryString componentsSeparatedByString:@"&"]; 52 | 53 | for(NSString *s in queryComponents) { 54 | NSArray *pair = [s componentsSeparatedByString:@"="]; 55 | if([pair count] != 2) continue; 56 | 57 | NSString *key = pair[0]; 58 | NSString *value = pair[1]; 59 | 60 | md[key] = value; 61 | } 62 | 63 | return md; 64 | } 65 | 66 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url 67 | sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { 68 | 69 | if ([[url scheme] isEqualToString:@"myapp"] == NO) return NO; 70 | 71 | NSDictionary *d = [self parametersDictionaryFromQueryString:[url query]]; 72 | 73 | NSString *token = d[@"oauth_token"]; 74 | NSString *verifier = d[@"oauth_verifier"]; 75 | 76 | ViewController *vc = (ViewController *)[[self window] rootViewController]; 77 | [vc setOAuthToken:token oauthVerifier:verifier]; 78 | 79 | return YES; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /STTwitter/Vendor/JSONSyntaxHighlight.h: -------------------------------------------------------------------------------- 1 | /** 2 | * JSONSyntaxHighlight.h 3 | * JSONSyntaxHighlight 4 | * 5 | * Syntax highlight JSON 6 | * 7 | * Created by Dave Eddy on 8/3/13. 8 | * Copyright (c) 2013 Dave Eddy. All rights reserved. 9 | * 10 | * The MIT License (MIT) 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | #import 32 | 33 | #if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) 34 | #import 35 | #else 36 | #import 37 | #endif 38 | 39 | @interface JSONSyntaxHighlight : NSObject 40 | 41 | // Create the object by giving a JSON object, nil will be returned 42 | // if the object can't be serialized 43 | - (JSONSyntaxHighlight *)init; 44 | - (JSONSyntaxHighlight *)initWithJSON:(id)JSON; 45 | 46 | // Return an NSAttributedString with the highlighted JSON in a pretty format 47 | - (NSAttributedString *)highlightJSON; 48 | 49 | // Return an NSAttributedString with the highlighted JSON optionally pretty formatted 50 | - (NSAttributedString *)highlightJSONWithPrettyPrint:(BOOL)prettyPrint; 51 | 52 | // Fire a callback for every key item found in the parsed JSON, each callback 53 | // is fired with the NSRange the substring appears in `self.parsedJSON`, as well 54 | // as the NSString at that location. 55 | - (void)enumerateMatchesWithIndentBlock:(void(^)(NSRange, NSString*))indentBlock 56 | keyBlock:(void(^)(NSRange, NSString*))keyBlock 57 | valueBlock:(void(^)(NSRange, NSString*))valueBlock 58 | endBlock:(void(^)(NSRange, NSString*))endBlock; 59 | 60 | // The JSON object, unmodified 61 | @property (readonly, nonatomic, strong) id JSON; 62 | 63 | // The serialized JSON string 64 | @property (readonly, nonatomic, strong) NSString *parsedJSON; 65 | 66 | // The attributes for Attributed Text 67 | @property (nonatomic, strong) NSDictionary *keyAttributes; 68 | @property (nonatomic, strong) NSDictionary *stringAttributes; 69 | @property (nonatomic, strong) NSDictionary *nonStringAttributes; 70 | 71 | // Platform dependent helper functions 72 | #if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) 73 | + (UIColor *)colorWithRGB:(NSInteger)rgbValue; 74 | + (UIColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha; 75 | #else 76 | + (NSColor *)colorWithRGB:(NSInteger)rgbValue; 77 | + (NSColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha; 78 | #endif 79 | 80 | @end -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/t2rss.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Tests/UnitTests.xcodeproj/xcshareddata/xcschemes/STTwitter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/favorites.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/followers.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/streaming.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/conversation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/sttwitter_cli.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/accounts_creation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/streaming_with_bot.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo_cli/sttwitter_cli.xcodeproj/xcshareddata/xcschemes/reverse_auth.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /STTwitter/STTwitterProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // STOAuthProtocol.h 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/18/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitterRequestProtocol.h" 11 | 12 | @class STHTTPRequest; 13 | 14 | @protocol STTwitterProtocol 15 | 16 | @property (nonatomic) NSTimeInterval timeoutInSeconds; 17 | 18 | - (void)verifyCredentialsLocallyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock 19 | errorBlock:(void(^)(NSError *error))errorBlock; 20 | 21 | - (void)verifyCredentialsRemotelyWithSuccessBlock:(void(^)(NSString *username, NSString *userID))successBlock 22 | errorBlock:(void(^)(NSError *error))errorBlock; 23 | 24 | - (NSObject *)fetchResource:(NSString *)resource 25 | HTTPMethod:(NSString *)HTTPMethod 26 | baseURLString:(NSString *)baseURLString 27 | parameters:(NSDictionary *)params 28 | uploadProgressBlock:(void(^)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))uploadProgressBlock 29 | downloadProgressBlock:(void(^)(NSObject *request, NSData *data))progressBlock 30 | successBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))successBlock 31 | errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock; 32 | 33 | - (NSString *)consumerName; 34 | - (NSString *)loginTypeDescription; 35 | 36 | @optional 37 | 38 | - (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock 39 | authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize 40 | forceLogin:(NSNumber *)forceLogin 41 | screenName:(NSString *)screenName 42 | oauthCallback:(NSString *)oauthCallback 43 | errorBlock:(void(^)(NSError *error))errorBlock; 44 | 45 | - (void)postAccessTokenRequestWithPIN:(NSString *)pin 46 | successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock 47 | errorBlock:(void(^)(NSError *error))errorBlock; 48 | 49 | - (void)invalidateBearerTokenWithSuccessBlock:(void(^)(id response))successBlock 50 | errorBlock:(void(^)(NSError *error))errorBlock; 51 | 52 | // access tokens are available only with plain OAuth authentication 53 | - (NSString *)oauthAccessToken; 54 | - (NSString *)oauthAccessTokenSecret; 55 | 56 | - (NSString *)bearerToken; 57 | 58 | // reverse auth phase 1, implemented only in STTwitterOAuth 59 | - (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock 60 | errorBlock:(void(^)(NSError *error))errorBlock; 61 | 62 | // reverse auth phase 2, implemented only in STTwitterOS 63 | - (void)postReverseAuthAccessTokenWithAuthenticationHeader:(NSString *)authenticationHeader 64 | successBlock:(void(^)(NSString *oAuthToken, NSString *oAuthTokenSecret, NSString *userID, NSString *screenName))successBlock 65 | errorBlock:(void(^)(NSError *error))errorBlock; 66 | 67 | // 'OAuth Echo' https://dev.twitter.com/twitter-kit/ios/oauth-echo 68 | 69 | - (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /demo_cli/streaming_with_bot/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // streaming 4 | // 5 | // Created by Nicolas Seriot on 02/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | int main(int argc, const char * argv[]) 13 | { 14 | 15 | @autoreleasepool { 16 | 17 | STTwitterAPI *twitter = [STTwitterAPI twitterAPIOSWithFirstAccount]; 18 | 19 | #warning use your own tokens here 20 | STTwitterAPI *twitter2 = [STTwitterAPI twitterAPIWithOAuthConsumerKey:@"" consumerSecret:@"" username:@"" password:@""]; 21 | 22 | [twitter verifyCredentialsWithUserSuccessBlock:^(NSString *username, NSString *userID) { 23 | NSLog(@"-- Account: %@", username); 24 | 25 | [twitter2 verifyCredentialsWithSuccessBlock:^(NSString *username) { 26 | 27 | [twitter postStatusesFilterUserIDs:nil 28 | keywordsToTrack:@[@"CocoaHeads"] 29 | locationBoundingBoxes:nil 30 | stallWarnings:nil 31 | progressBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { 32 | 33 | if (type != STTwitterStreamJSONTypeTweet) { 34 | NSLog(@"Invalid tweet (class %@): %@", [json class], json); 35 | exit(1); 36 | return; 37 | } 38 | 39 | printf("-----------------------------------------------------------------\n"); 40 | printf("-- user: @%s\n", [[json valueForKeyPath:@"user.screen_name"] cStringUsingEncoding:NSUTF8StringEncoding]); 41 | printf("-- text: %s\n", [[json objectForKey:@"text"] cStringUsingEncoding:NSUTF8StringEncoding]); 42 | 43 | NSString *idStr = [json objectForKey:@"id_str"]; 44 | 45 | NSString *responseText = [NSString stringWithFormat:@"Cocoaheads Yeah! %@", [NSDate date]]; 46 | 47 | [twitter2 postStatusesUpdate:responseText inReplyToStatusID:idStr latitude:nil longitude:nil placeID:nil displayCoordinates:nil trimUser:nil autoPopulateReplyMetadata:nil excludeReplyUserIDsStrings:nil attachmentURLString:nil useExtentedTweetMode:nil successBlock:^(NSDictionary *status) { 48 | NSLog(@"-- status: %@", status); 49 | } errorBlock:^(NSError *error) { 50 | NSLog(@"-- error: %@", error); 51 | }]; 52 | 53 | } errorBlock:^(NSError *error) { 54 | NSLog(@"Stream error: %@", error); 55 | exit(1); 56 | }]; 57 | 58 | 59 | } errorBlock:^(NSError *error) { 60 | NSLog(@"-- %@", [error localizedDescription]); 61 | }]; 62 | 63 | } errorBlock:^(NSError *error) { 64 | NSLog(@"-- %@", [error localizedDescription]); 65 | exit(1); 66 | }]; 67 | 68 | /**/ 69 | 70 | [[NSRunLoop currentRunLoop] run]; 71 | 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /Tests/UnitTests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /demo_cli/conversation/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // streaming 4 | // 5 | // Created by Nicolas Seriot on 03/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | NSString *firstTweetID = nil; 13 | 14 | void postStatus(STTwitterAPI *twitter, 15 | NSMutableArray *statusesAndMediaURLs, 16 | NSString *previousStatusID) { 17 | 18 | NSDictionary *d = [statusesAndMediaURLs firstObject]; 19 | if(d == nil) { 20 | NSLog(@"--------------------"); 21 | NSLog(@"-- posting complete"); 22 | NSLog(@"-- see https://www.twitter.com/statuses/%@/", firstTweetID); 23 | exit(0); 24 | } 25 | [statusesAndMediaURLs removeObjectAtIndex:0]; 26 | 27 | NSString *status = [d objectForKey:@"status"]; 28 | NSString *filePath = [d objectForKey:@"filePath"]; 29 | NSData *mediaData = [NSData dataWithContentsOfFile:filePath]; 30 | 31 | NSLog(@"--------------------"); 32 | NSLog(@"-- text: %@", status); 33 | NSLog(@"-- data: %@", [filePath lastPathComponent]); 34 | 35 | NSMutableDictionary *md = [NSMutableDictionary dictionary]; 36 | md[@"status"] = status; 37 | if(previousStatusID) md[@"in_reply_to_status_id"] = previousStatusID; 38 | md[@"media[]"] = mediaData; 39 | md[kSTPOSTDataKey] = @"media[]"; 40 | 41 | [twitter postResource:@"statuses/update_with_media.json" 42 | baseURLString:kBaseURLStringAPI_1_1 43 | parameters:md 44 | uploadProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { 45 | NSLog(@"-- %.02f%%", 100.0 * totalBytesWritten / totalBytesExpectedToWrite); 46 | 47 | } downloadProgressBlock:^(id json) { 48 | 49 | } successBlock:^(NSDictionary *rateLimits, id response) { 50 | 51 | NSLog(@"-- x-mediaratelimit-remaining: %@", rateLimits[@"x-mediaratelimit-remaining"]); 52 | 53 | NSString *previousStatusID = [response objectForKey:@"id_str"]; 54 | NSLog(@"-- status: %@", previousStatusID); 55 | 56 | if(firstTweetID == nil) firstTweetID = previousStatusID; 57 | 58 | postStatus(twitter, statusesAndMediaURLs, previousStatusID); 59 | 60 | } errorBlock:^(NSError *error) { 61 | NSLog(@"-- %@", error); 62 | exit(1); 63 | }]; 64 | 65 | } 66 | 67 | int main(int argc, const char * argv[]) 68 | { 69 | 70 | @autoreleasepool { 71 | 72 | STTwitterAPI *t = [STTwitterAPI twitterAPIOSWithFirstAccount]; 73 | 74 | [t verifyCredentialsWithSuccessBlock:^(NSString *username) { 75 | 76 | NSString *dirPath = @"/Users/nst/Desktop/sttwitter_cocoaheads/"; 77 | 78 | NSError *error = nil; 79 | NSArray *filenames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; 80 | if(filenames == nil) { 81 | NSLog(@"-- error: %@", error); 82 | exit(1); 83 | } 84 | 85 | NSMutableArray *statusesAndMediaURLs = [NSMutableArray array]; 86 | 87 | for (NSString *filename in filenames) { 88 | if([filename hasPrefix:@"."]) continue; 89 | NSString *filePath = [dirPath stringByAppendingPathComponent:filename]; 90 | [statusesAndMediaURLs addObject:@{@"status":filename, @"filePath":filePath}]; 91 | } 92 | 93 | postStatus(t, statusesAndMediaURLs, nil); 94 | 95 | } errorBlock:^(NSError *error) { 96 | NSLog(@"-- %@", error); 97 | }]; 98 | 99 | /**/ 100 | 101 | [[NSRunLoop currentRunLoop] run]; 102 | 103 | } 104 | 105 | return 0; 106 | } 107 | -------------------------------------------------------------------------------- /STTwitter/STTwitterOAuth.h: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterRequest.h 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/5/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitterProtocol.h" 11 | 12 | /* 13 | Based on the following documentation 14 | http://oauth.net/core/1.0/ 15 | https://dev.twitter.com/docs/auth/authorizing-request 16 | https://dev.twitter.com/docs/auth/implementing-sign-twitter 17 | https://dev.twitter.com/docs/auth/creating-signature 18 | https://dev.twitter.com/docs/api/1/post/oauth/request_token 19 | https://dev.twitter.com/docs/oauth/xauth 20 | ... 21 | */ 22 | 23 | extern NS_ENUM(NSUInteger, STTwitterOAuthErrorCode) { 24 | STTwitterOAuthCannotPostAccessTokenRequestWithoutPIN = 0, 25 | STTwitterOAuthBadCredentialsOrConsumerTokensNotXAuthEnabled 26 | }; 27 | 28 | @interface STTwitterOAuth : NSObject 29 | 30 | @property (nonatomic) NSTimeInterval timeoutInSeconds; 31 | 32 | + (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName 33 | consumerKey:(NSString *)consumerKey 34 | consumerSecret:(NSString *)consumerSecret; 35 | 36 | + (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName 37 | consumerKey:(NSString *)consumerKey 38 | consumerSecret:(NSString *)consumerSecret 39 | oauthToken:(NSString *)oauthToken 40 | oauthTokenSecret:(NSString *)oauthTokenSecret; 41 | 42 | + (instancetype)twitterOAuthWithConsumerName:(NSString *)consumerName 43 | consumerKey:(NSString *)consumerKey 44 | consumerSecret:(NSString *)consumerSecret 45 | username:(NSString *)username 46 | password:(NSString *)password; 47 | 48 | - (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock 49 | authenticateInsteadOfAuthorize:(BOOL)authenticateInsteadOfAuthorize 50 | forceLogin:(NSNumber *)forceLogin // optional, default @(NO) 51 | screenName:(NSString *)screenName // optional, default nil 52 | oauthCallback:(NSString *)oauthCallback 53 | errorBlock:(void(^)(NSError *error))errorBlock; 54 | 55 | - (void)signRequest:(STHTTPRequest *)r isMediaUpload:(BOOL)isMediaUpload oauthCallback:(NSString *)oauthCallback; 56 | 57 | // convenience 58 | - (void)postTokenRequest:(void(^)(NSURL *url, NSString *oauthToken))successBlock 59 | oauthCallback:(NSString *)oauthCallback 60 | errorBlock:(void(^)(NSError *error))errorBlock; 61 | 62 | 63 | - (void)postAccessTokenRequestWithPIN:(NSString *)pin 64 | successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock 65 | errorBlock:(void(^)(NSError *error))errorBlock; 66 | 67 | - (void)postXAuthAccessTokenRequestWithUsername:(NSString *)username 68 | password:(NSString *)password 69 | successBlock:(void(^)(NSString *oauthToken, NSString *oauthTokenSecret, NSString *userID, NSString *screenName))successBlock 70 | errorBlock:(void(^)(NSError *error))errorBlock; 71 | 72 | // reverse auth phase 1 73 | - (void)postReverseOAuthTokenRequest:(void(^)(NSString *authenticationHeader))successBlock 74 | errorBlock:(void(^)(NSError *error))errorBlock; 75 | 76 | // useful for the so-called 'OAuth Echo' https://dev.twitter.com/twitter-kit/ios/oauth-echo 77 | 78 | - (NSDictionary *)OAuthEchoHeadersToVerifyCredentials; 79 | 80 | @end 81 | 82 | @interface NSString (STTwitterOAuth) 83 | + (NSString *)st_random32Characters; 84 | - (NSString *)st_signHmacSHA1WithKey:(NSString *)key; 85 | - (NSDictionary *)st_parametersDictionary; 86 | - (NSString *)st_urlEncodedString; 87 | @end 88 | 89 | @interface NSURL (STTwitterOAuth) 90 | - (NSString *)st_normalizedForOauthSignatureString; 91 | - (NSArray *)st_rawGetParametersDictionaries; 92 | @end 93 | -------------------------------------------------------------------------------- /STTwitter/NSError+STTwitter.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+STTwitter.h 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 19/03/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static NSString *kSTTwitterTwitterErrorDomain = @"STTwitterTwitterErrorDomain"; 12 | static NSString *kSTTwitterRateLimitLimit = @"STTwitterRateLimitLimit"; 13 | static NSString *kSTTwitterRateLimitRemaining = @"STTwitterRateLimitRemaining"; 14 | static NSString *kSTTwitterRateLimitResetDate = @"STTwitterRateLimitResetDate"; 15 | 16 | // https://dev.twitter.com/docs/error-codes-responses 17 | typedef NS_ENUM( NSInteger, STTwitterTwitterErrorCode ) { 18 | STTwitterTwitterErrorCouldNotAuthenticate = 32, // Your call could not be completed as dialed. 19 | STTwitterTwitterErrorPageDoesNotExist = 34, // Corresponds with an HTTP 404 - the specified resource was not found. 20 | STTwitterTwitterErrorInvalidAttachmentURL = 44, // Corresponds with HTTP 400. The URL value provided is not a URL that can be attached to this Tweet. 21 | 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 22 | STTwitterTwitterErrorAPIv1Inactive = 68, // Corresponds to a HTTP request to a retired v1-era URL. 23 | STTwitterTwitterErrorRateLimitExceeded = 88, // The request limit for this resource has been reached for the current rate limit window. 24 | STTwitterTwitterErrorInvalidOrExpiredToken = 89, // The access token used in the request is incorrect or has expired. Used in API v1.1 25 | 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 26 | STTwitterTwitterErrorOverCapacity = 130, // Corresponds with an HTTP 503 - Twitter is temporarily over capacity. 27 | STTwitterTwitterErrorInternalError = 131, // Corresponds with an HTTP 500 - An unknown internal error occurred. 28 | STTwitterTwitterErrorCouldNotAuthenticateYou = 135, // Corresponds with a HTTP 401 - it means that your oauth_timestamp is either ahead or behind our acceptable range 29 | STTwitterTwitterErrorUnableToFollow = 161, // Corresponds with HTTP 403 — thrown when a user cannot follow another user due to some kind of limit 30 | 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. 31 | 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. 32 | STTwitterTwitterErrorDuplicatedStatus = 187, // The status text has been Tweeted already by the authenticated account. 33 | 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. 34 | 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. 35 | STTwitterTwitterErrorRetiredEndpoint = 251, // Corresponds to a HTTP request to a retired URL. 36 | 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. 37 | STTwitterTwitterErrorCannotReplyToDeletedOrInvisibleTweet = 385, // Corresponds with HTTP 403. A reply can only be sent with reference to an existing public Tweet. 38 | STTwitterTwitterErrorTooManyAttachmentTypes = 386 // Corresponds with HTTP 403. A Tweet is limited to a single attachment resource (media, Quote Tweet, etc.) 39 | }; 40 | 41 | @interface NSError (STTwitter) 42 | 43 | + (NSError *)st_twitterErrorFromResponseData:(NSData *)responseData 44 | responseHeaders:(NSDictionary *)responseHeaders 45 | underlyingError:(NSError *)underlyingError; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /STTwitter/STHTTPRequest+STTwitter.m: -------------------------------------------------------------------------------- 1 | // 2 | // STHTTPRequest+STTwitter.m 3 | // STTwitter 4 | // 5 | // Created by Nicolas Seriot on 8/6/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STHTTPRequest+STTwitter.h" 10 | #import "NSString+STTwitter.h" 11 | #import "NSError+STTwitter.h" 12 | 13 | #if DEBUG 14 | # define STLog(...) NSLog(__VA_ARGS__) 15 | #else 16 | # define STLog(...) 17 | #endif 18 | 19 | @implementation STHTTPRequest (STTwitter) 20 | 21 | + (STHTTPRequest *)twitterRequestWithURLString:(NSString *)urlString 22 | HTTPMethod:(NSString *)HTTPMethod 23 | timeoutInSeconds:(NSTimeInterval)timeoutInSeconds 24 | stTwitterUploadProgressBlock:(void(^)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))uploadProgressBlock 25 | stTwitterDownloadProgressBlock:(void(^)(NSData *data, int64_t totalBytesReceived, int64_t totalBytesExpectedToReceive))downloadProgressBlock 26 | stTwitterSuccessBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, id json))successBlock 27 | stTwitterErrorBlock:(void(^)(NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { 28 | 29 | __block STHTTPRequest *r = [self requestWithURLString:urlString]; 30 | __weak STHTTPRequest *wr = r; 31 | 32 | r.sharedContainerIdentifier = [[NSUserDefaults standardUserDefaults] valueForKey:@"STTwitterSharedContainerIdentifier"]; 33 | 34 | r.HTTPMethod = HTTPMethod; 35 | 36 | r.cookieStoragePolicyForInstance = STHTTPRequestCookiesStorageNoStorage; 37 | 38 | r.timeoutSeconds = timeoutInSeconds; 39 | 40 | r.uploadProgressBlock = uploadProgressBlock; 41 | 42 | r.downloadProgressBlock = downloadProgressBlock; 43 | 44 | r.completionDataBlock = ^(NSDictionary *responseHeaders, NSData *responseData) { 45 | 46 | STHTTPRequest *sr = wr; // strong request 47 | 48 | NSError *jsonError = nil; 49 | id json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError]; 50 | 51 | if(json == nil) { 52 | successBlock(sr.requestHeaders, sr.responseHeaders, sr.responseString); // response is not necessarily json 53 | return; 54 | } 55 | 56 | successBlock(sr.requestHeaders, sr.responseHeaders, json); 57 | }; 58 | 59 | r.errorBlock = ^(NSError *error) { 60 | 61 | STHTTPRequest *sr = wr; // strong request 62 | 63 | NSError *e = [NSError st_twitterErrorFromResponseData:sr.responseData responseHeaders:sr.responseHeaders underlyingError:error]; 64 | if(e) { 65 | errorBlock(sr.requestHeaders, sr.responseHeaders, e); 66 | return; 67 | } 68 | 69 | if(error) { 70 | errorBlock(sr.requestHeaders, sr.responseHeaders, error); 71 | return; 72 | } 73 | 74 | e = [NSError errorWithDomain:NSStringFromClass([self class]) code:0 userInfo:@{NSLocalizedDescriptionKey : sr.responseString}]; 75 | 76 | if (sr.responseString) STLog(@"-- body: %@", sr.responseString); 77 | 78 | // BOOL isCancellationError = [[error domain] isEqualToString:@"STHTTPRequest"] && ([error code] == kSTHTTPRequestCancellationError); 79 | // if(isCancellationError) return; 80 | 81 | errorBlock(sr.requestHeaders, sr.responseHeaders, e); 82 | }; 83 | 84 | return r; 85 | } 86 | 87 | + (void)expandedURLStringForShortenedURLString:(NSString *)urlString 88 | successBlock:(void(^)(NSString *expandedURLString))successBlock 89 | errorBlock:(void(^)(NSError *error))errorBlock { 90 | 91 | STHTTPRequest *r = [STHTTPRequest requestWithURLString:urlString]; 92 | 93 | r.sharedContainerIdentifier = [[NSUserDefaults standardUserDefaults] valueForKey:@"STTwitterSharedContainerIdentifier"]; 94 | 95 | r.cookieStoragePolicyForInstance = STHTTPRequestCookiesStorageNoStorage; 96 | 97 | r.preventRedirections = YES; 98 | 99 | r.completionBlock = ^(NSDictionary *responseHeaders, NSString *body) { 100 | 101 | NSString *location = [responseHeaders valueForKey:@"location"]; 102 | if(location == nil) [responseHeaders valueForKey:@"Location"]; 103 | 104 | successBlock(location); 105 | }; 106 | 107 | r.errorBlock = ^(NSError *error) { 108 | errorBlock(error); 109 | }; 110 | 111 | [r startAsynchronous]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /Tests/STTwitterTests/STMiscTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // STMiscTests.m 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/10/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STMiscTests.h" 10 | #import "NSString+STTwitter.h" 11 | #import "STTwitterStreamParser.h" 12 | 13 | @implementation STMiscTests 14 | 15 | 16 | - (void)setUp 17 | { 18 | [super setUp]; 19 | 20 | // Set-up code here. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Tear-down code here. 26 | 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testCharactersCount140 { 31 | 32 | NSString *s = @"0123567890"; 33 | NSMutableString *ms = [NSMutableString string]; 34 | for(int i = 0; i < 14; i++) { 35 | [ms appendString:s]; 36 | } 37 | 38 | int c = (int)[ms st_numberOfCharactersInATweet]; 39 | 40 | XCTAssertEqual(c, 140, @"c: %d", (int)c); 41 | } 42 | 43 | - (void)testCharactersCountUnicode { 44 | 45 | NSString *s = @"\u2070"; 46 | 47 | int c = (int)[s st_numberOfCharactersInATweet]; 48 | 49 | XCTAssertEqual(c, 1, @"c: %d", (int)c); 50 | } 51 | 52 | - (void)testCharactersCountWithDash { 53 | 54 | NSString *s = @"asd http://www.apple.com/#asd dfg"; 55 | 56 | int c = (int)[s st_numberOfCharactersInATweet]; 57 | 58 | int expected = 4 + (int)kSTTwitterDefaultShortURLLength + 4; 59 | 60 | XCTAssertEqual(c, expected, @"c: %d, expected %d", c, expected); 61 | } 62 | 63 | - (void)testCharactersCountAccentedCharacter { 64 | 65 | NSString *s = @"caf\x65\xCC\x81"; 66 | 67 | int c = (int)[s st_numberOfCharactersInATweet]; 68 | 69 | XCTAssertEqual(c, 4, @"c: %d", (int)c); 70 | } 71 | 72 | - (void)testCharactersCountHTTPTwice { 73 | 74 | NSString *s = @"asd http://www.apple.com http://www.google.com sdf"; 75 | 76 | int c = (int)[s st_numberOfCharactersInATweet]; 77 | 78 | int expected = 4 + (int)kSTTwitterDefaultShortURLLength + 1 + (int)kSTTwitterDefaultShortURLLength + 4; 79 | 80 | XCTAssertEqual(c, expected, @"c: %d, expected %d", c, expected); 81 | } 82 | 83 | - (void)testCharactersCountWithTCO { 84 | // https://github.com/nst/STTwitter/issues/87 85 | 86 | NSString *s = @"\"The Game of Thrones season premiere will stream on Xbox and cable after last night\'s HBO Go problems http://t.co/ZTTIOJX3l9\""; 87 | 88 | int c = (int)[s st_numberOfCharactersInATweet]; 89 | 90 | XCTAssertEqual(140 - c, 15, @"c: %d", c); 91 | } 92 | 93 | - (void)testCharactersCountIssue209 { 94 | // https://github.com/nst/STTwitter/issues/209 95 | 96 | NSString *s = @"http://us1.campaign-archive2.com/?u=d0e55f3197099944345708652&id=5c51c9bebb&e=dff350d017"; 97 | 98 | int c = (int)[s st_numberOfCharactersInATweet]; 99 | 100 | XCTAssertEqual(140 - c, 118, @"c: %d", c); 101 | } 102 | 103 | - (void)testURLWithHyphens { 104 | // https://github.com/nst/STTwitter/issues/91 105 | 106 | NSString *s = @"asd http://www.imore.com/auki-iphone-review-ios-7-quick-reply-way-apple-would-have-done-it sdf"; 107 | 108 | int c = (int)[s st_numberOfCharactersInATweet]; 109 | 110 | XCTAssertEqual(140 - c, 110, @"c: %d", c); 111 | } 112 | 113 | - (void)testCharactersCountHTTPS { 114 | 115 | NSString *s = @"https://api.twitter.com/"; 116 | 117 | int c = (int)[s st_numberOfCharactersInATweet]; 118 | 119 | XCTAssertEqual(c, (int)kSTTwitterDefaultShortURLLengthHTTPS, @"c: %d", (int)c); 120 | } 121 | 122 | - (void)testCharactersCountEmojis { 123 | 124 | NSString *s = @"\U0001F601"; 125 | 126 | int c = (int)[s st_numberOfCharactersInATweet]; 127 | 128 | XCTAssertEqual(c, 1, @"c: %d", (int)c); 129 | } 130 | 131 | - (void)testParserOK { 132 | NSString *s1 = @"11\r\n{\"a\":"; 133 | NSString *s2 = @"\"b\"}"; 134 | 135 | NSData *data1 = [s1 dataUsingEncoding:NSUTF8StringEncoding]; 136 | NSData *data2 = [s2 dataUsingEncoding:NSUTF8StringEncoding]; 137 | 138 | NSDictionary *expectedDictionary = @{@"a":@"b"}; 139 | __block NSDictionary *readDictionary = nil; 140 | 141 | STTwitterStreamParser *parser = [[STTwitterStreamParser alloc] init]; 142 | 143 | [parser parseWithStreamData:data1 parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { 144 | XCTAssertFalse(YES, @"shouldn't have finished parsing incomplete JSON"); 145 | }]; 146 | 147 | [parser parseWithStreamData:data2 parsedJSONBlock:^(NSDictionary *json, STTwitterStreamJSONType type) { 148 | XCTAssertEqual(STTwitterStreamJSONTypeUnsupported, type); 149 | readDictionary = json; 150 | }]; 151 | 152 | XCTAssertNotNil(readDictionary); 153 | XCTAssertEqualObjects(expectedDictionary, readDictionary); 154 | } 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /STTwitter/NSError+STTwitter.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+STTwitter.m 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 19/03/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "NSError+STTwitter.h" 10 | 11 | static NSRegularExpression *xmlErrorRegex = nil; 12 | 13 | @implementation NSError (STTwitter) 14 | 15 | + (NSRegularExpression *)st_xmlErrorRegex { 16 | if(xmlErrorRegex == nil) { 17 | xmlErrorRegex = [NSRegularExpression regularExpressionWithPattern:@"(.*)" options:0 error:nil]; 18 | } 19 | return xmlErrorRegex; 20 | } 21 | 22 | + (NSError *)st_twitterErrorFromResponseData:(NSData *)responseData 23 | responseHeaders:(NSDictionary *)responseHeaders 24 | underlyingError:(NSError *)underlyingError { 25 | 26 | NSError *jsonError = nil; 27 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError]; 28 | 29 | NSString *message = nil; 30 | NSInteger code = 0; 31 | 32 | if([json isKindOfClass:[NSDictionary class]]) { 33 | id errors = [json valueForKey:@"errors"]; 34 | if([errors isKindOfClass:[NSArray class]] && [(NSArray *)errors count] > 0) { 35 | // assume format: {"errors":[{"message":"Sorry, that page does not exist","code":34}]} 36 | NSDictionary *errorDictionary = [errors lastObject]; 37 | if([errorDictionary isKindOfClass:[NSDictionary class]]) { 38 | message = errorDictionary[@"message"]; 39 | code = [[errorDictionary valueForKey:@"code"] integerValue]; 40 | } 41 | } else if ([json valueForKey:@"error"] && [json valueForKey:@"error"] != [NSNull null]) { 42 | /* 43 | eg. when requesting timeline from a protected account 44 | { 45 | error = "Not authorized."; 46 | request = "/1.1/statuses/user_timeline.json?count=20&screen_name=premfe"; 47 | } 48 | also, be robust to null errors such as in: 49 | { 50 | error = ""; 51 | state = AwaitingComplete; 52 | } 53 | */ 54 | message = [json valueForKey:@"error"]; 55 | } else if([errors isKindOfClass:[NSString class]]) { 56 | // assume format {errors = "Screen name can't be blank";} 57 | message = errors; 58 | } 59 | } 60 | 61 | if(json == nil) { 62 | // look for XML errors, eg. 63 | /* 64 | 65 | 66 | Client is not permitted to perform this action 67 | 68 | */ 69 | 70 | NSString *s = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; 71 | 72 | NSRegularExpression *xmlErrorRegex = [self st_xmlErrorRegex]; 73 | NSAssert(xmlErrorRegex, @""); 74 | 75 | NSTextCheckingResult *match = [xmlErrorRegex firstMatchInString:s options:0 range:NSMakeRange(0, [s length])]; 76 | 77 | if(match) { 78 | NSRange group1Range = [match rangeAtIndex:1]; 79 | NSRange group2Range = [match rangeAtIndex:2]; 80 | 81 | NSString *codeString = [s substringWithRange:group1Range]; 82 | NSString *errorMessaage = [s substringWithRange:group2Range]; 83 | 84 | return [NSError errorWithDomain:kSTTwitterTwitterErrorDomain code:[codeString integerValue] userInfo:@{NSLocalizedDescriptionKey:errorMessaage}]; 85 | } 86 | } 87 | 88 | if(message) { 89 | NSString *rateLimitLimit = [responseHeaders valueForKey:@"x-rate-limit-limit"]; 90 | NSString *rateLimitRemaining = [responseHeaders valueForKey:@"x-rate-limit-remaining"]; 91 | NSString *rateLimitReset = [responseHeaders valueForKey:@"x-rate-limit-reset"]; 92 | 93 | NSDate *rateLimitResetDate = rateLimitReset ? [NSDate dateWithTimeIntervalSince1970:[rateLimitReset doubleValue]] : nil; 94 | 95 | NSMutableDictionary *md = [NSMutableDictionary dictionary]; 96 | md[NSLocalizedDescriptionKey] = message; 97 | if(underlyingError) md[NSUnderlyingErrorKey] = underlyingError; 98 | if(rateLimitLimit) md[kSTTwitterRateLimitLimit] = rateLimitLimit; 99 | if(rateLimitRemaining) md[kSTTwitterRateLimitRemaining] = rateLimitRemaining; 100 | if(rateLimitResetDate) md[kSTTwitterRateLimitResetDate] = rateLimitResetDate; 101 | 102 | NSDictionary *userInfo = [NSDictionary dictionaryWithDictionary:md]; 103 | 104 | return [NSError errorWithDomain:kSTTwitterTwitterErrorDomain code:code userInfo:userInfo]; 105 | } 106 | 107 | return nil; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /STTwitter/STTwitterHTML.m: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterWeb.m 3 | // STTwitterRequests 4 | // 5 | // Created by Nicolas Seriot on 9/13/12. 6 | // Copyright (c) 2012 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STTwitterHTML.h" 10 | #import "STHTTPRequest.h" 11 | #import "NSString+STTwitter.h" 12 | 13 | @implementation STTwitterHTML 14 | 15 | - (void)getLoginForm:(void(^)(NSString *authenticityToken))successBlock errorBlock:(void(^)(NSError *error))errorBlock { 16 | 17 | __block STHTTPRequest *r = [STHTTPRequest requestWithURLString:@"https://twitter.com/login"]; 18 | 19 | r.completionBlock = ^(NSDictionary *headers, NSString *body) { 20 | 21 | NSError *error = nil; 22 | // NSString *token = [body firstMatchWithRegex:@"" error:&error]; 23 | NSString *token = [body st_firstMatchWithRegex:@"formAuthenticityToken":"(\\S+?)"" error:&error]; 24 | 25 | if(token == nil) { 26 | errorBlock(error); 27 | return; 28 | } 29 | 30 | successBlock(token); 31 | }; 32 | 33 | r.errorBlock = ^(NSError *error) { 34 | errorBlock(error); 35 | }; 36 | 37 | [r startAsynchronous]; 38 | } 39 | 40 | - (void)postLoginFormWithUsername:(NSString *)username 41 | password:(NSString *)password 42 | authenticityToken:(NSString *)authenticityToken 43 | successBlock:(void(^)(NSString *body))successBlock 44 | errorBlock:(void(^)(NSError *error))errorBlock { 45 | 46 | if([username length] == 0 || [password length] == 0) { 47 | NSString *errorDescription = [NSString stringWithFormat:@"Missing credentials"]; 48 | NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) code:STTwitterHTMLCannotPostWithoutCredentials userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; 49 | errorBlock(error); 50 | return; 51 | } 52 | 53 | __block STHTTPRequest *r = [STHTTPRequest requestWithURLString:@"https://twitter.com/sessions"]; 54 | 55 | r.POSTDictionary = @{@"authenticity_token" : authenticityToken, 56 | @"session[username_or_email]" : username, 57 | @"session[password]" : password, 58 | @"remember_me" : @"1", 59 | @"commit" : @"Sign in"}; 60 | 61 | r.completionBlock = ^(NSDictionary *headers, NSString *body) { 62 | successBlock(body); 63 | }; 64 | 65 | r.errorBlock = ^(NSError *error) { 66 | errorBlock(error); 67 | }; 68 | 69 | [r startAsynchronous]; 70 | } 71 | 72 | - (void)getAuthorizeFormAtURL:(NSURL *)url successBlock:(void(^)(NSString *authenticityToken, NSString *oauthToken))successBlock errorBlock:(void(^)(NSError *error))errorBlock { 73 | 74 | STHTTPRequest *r = [STHTTPRequest requestWithURL:url]; 75 | 76 | r.completionBlock = ^(NSDictionary *headers, NSString *body) { 77 | /* 78 |
79 | 80 | 81 | */ 82 | 83 | NSError *error1 = nil; 84 | NSString *authenticityToken = [body st_firstMatchWithRegex:@"" error:&error1]; 85 | 86 | if(authenticityToken == nil) { 87 | errorBlock(error1); 88 | return; 89 | } 90 | 91 | /**/ 92 | 93 | NSError *error2 = nil; 94 | 95 | NSString *oauthToken = [body st_firstMatchWithRegex:@"" error:&error2]; 96 | 97 | if(oauthToken == nil) { 98 | errorBlock(error2); 99 | return; 100 | } 101 | 102 | /**/ 103 | 104 | successBlock(authenticityToken, oauthToken); 105 | }; 106 | 107 | r.errorBlock = ^(NSError *error) { 108 | errorBlock(error); 109 | }; 110 | 111 | [r startAsynchronous]; 112 | } 113 | 114 | - (void)postAuthorizeFormResultsAtURL:(NSURL *)url authenticityToken:(NSString *)authenticityToken oauthToken:(NSString *)oauthToken successBlock:(void(^)(NSString *PIN))successBlock errorBlock:(void(^)(NSError *error))errorBlock { 115 | 116 | STHTTPRequest *r = [STHTTPRequest requestWithURL:url]; 117 | 118 | r.POSTDictionary = @{@"authenticity_token" : authenticityToken, 119 | @"oauth_token" : oauthToken}; 120 | 121 | r.completionBlock = ^(NSDictionary *headers, NSString *body) { 122 | 123 | NSError *error = nil; 124 | NSString *pin = [body st_firstMatchWithRegex:@"(\\d+)" error:&error]; 125 | 126 | if(pin == nil) { 127 | errorBlock(error); 128 | return; 129 | } 130 | 131 | successBlock(pin); 132 | }; 133 | 134 | r.errorBlock = ^(NSError *error) { 135 | errorBlock(error); 136 | }; 137 | 138 | [r startAsynchronous]; 139 | } 140 | 141 | @end 142 | 143 | -------------------------------------------------------------------------------- /demo_cli/t2rss/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // t2rss 4 | // 5 | // Created by Nicolas Seriot on 13/05/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | @interface NSDateFormatter (STTwitter_CLI) 13 | + (NSDateFormatter *)rfc822Formatter; 14 | @end 15 | 16 | @implementation NSDateFormatter (STTwitter_CLI) 17 | + (NSDateFormatter *)rfc822Formatter { 18 | static NSDateFormatter *formatter = nil; 19 | if (formatter == nil) { 20 | formatter = [[NSDateFormatter alloc] init]; 21 | NSLocale *enUS = [NSLocale localeWithLocaleIdentifier:@"en_US"]; 22 | [formatter setLocale:enUS]; 23 | [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss Z"]; 24 | } 25 | return formatter; 26 | } 27 | @end 28 | 29 | int main(int argc, const char * argv[]) 30 | { 31 | 32 | @autoreleasepool { 33 | 34 | STTwitterAPI *t = [STTwitterAPI twitterAPIOSWithFirstAccount]; 35 | 36 | [t verifyCredentialsWithSuccessBlock:^(NSString *username) { 37 | 38 | NSMutableArray *lines = [NSMutableArray array]; 39 | 40 | [lines addObject:@""]; 41 | [lines addObject:@""]; 42 | [lines addObject:@""]; 43 | [lines addObject:@""]; 44 | [lines addObject:@""]; 45 | [lines addObject:[NSString stringWithFormat:@"%@", username]]; 46 | [lines addObject:@"http://localhost/"]; 47 | [lines addObject:@"xxx"]; 48 | 49 | [t getStatusesHomeTimelineWithCount:@"100" 50 | sinceID:nil 51 | maxID:nil 52 | trimUser:nil 53 | excludeReplies:nil 54 | contributorDetails:nil 55 | includeEntities:nil 56 | successBlock:^(NSArray *statuses) { 57 | 58 | for(NSDictionary *d in statuses) { 59 | 60 | NSString *text = [d valueForKey:@"text"]; 61 | NSString *idStr = [d valueForKey:@"id_str"]; 62 | NSString *createdAtDateString = [d valueForKey:@"created_at"]; 63 | NSString *urlString = [NSString stringWithFormat:@"https://www.twitter.com/statuses/%@/", idStr]; 64 | 65 | [lines addObject:@""]; 66 | [lines addObject:@" "]; 67 | [lines addObject:[NSString stringWithFormat:@" @%@", [d valueForKeyPath:@"user.screen_name"]]]; 68 | [lines addObject:[NSString stringWithFormat:@" %@", urlString]]; 69 | [lines addObject:[NSString stringWithFormat:@" %@", text]]; 70 | [lines addObject:[NSString stringWithFormat:@" %@", urlString]]; 71 | [lines addObject:[NSString stringWithFormat:@" %@", text]]; 72 | 73 | NSDate *date = [[NSDateFormatter st_TwitterDateFormatter] dateFromString:createdAtDateString]; 74 | NSString *dateString = [[NSDateFormatter rfc822Formatter] stringFromDate:date]; 75 | 76 | [lines addObject:[NSString stringWithFormat:@" %@", dateString]]; 77 | [lines addObject:@" "]; 78 | } 79 | 80 | [lines addObject:@""]; 81 | [lines addObject:@""]; 82 | [lines addObject:@""]; 83 | [lines addObject:@""]; 84 | 85 | NSString *s = [lines componentsJoinedByString:@"\n"]; 86 | NSError *error = nil; 87 | NSString *path = @"/tmp/t2rss.xml"; 88 | BOOL success = [s writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error]; 89 | if(success == NO) { 90 | NSLog(@"-- %@", error); 91 | exit(1); 92 | } 93 | 94 | NSLog(@"-- RSS contents written in: %@", path); 95 | 96 | exit(0); 97 | } errorBlock:^(NSError *error) { 98 | NSLog(@"-- %@", error); 99 | }]; 100 | 101 | } errorBlock:^(NSError *error) { 102 | NSLog(@"-- %@", error); 103 | }]; 104 | 105 | [[NSRunLoop currentRunLoop] run]; 106 | } 107 | 108 | return 0; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /STTwitter/STTwitterStreamParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterParser.m 3 | // STTwitterDemoIOS 4 | // 5 | // Created by Yu Sugawara on 2015/03/23. 6 | // Copyright (c) 2015年 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STTwitterStreamParser.h" 10 | 11 | NSString *NSStringFromSTTwitterStreamJSONType(STTwitterStreamJSONType type) { 12 | switch (type) { 13 | case STTwitterStreamJSONTypeTweet: 14 | return @"STTwitterStreamJSONTypeTweet"; 15 | case STTwitterStreamJSONTypeFriendsLists: 16 | return @"STTwitterStreamJSONTypeFriendsLists"; 17 | case STTwitterStreamJSONTypeDelete: 18 | return @"STTwitterStreamJSONTypeDelete"; 19 | case STTwitterStreamJSONTypeScrubGeo: 20 | return @"STTwitterStreamJSONTypeScrubGeo"; 21 | case STTwitterStreamJSONTypeLimit: 22 | return @"STTwitterStreamJSONTypeLimit"; 23 | case STTwitterStreamJSONTypeDisconnect: 24 | return @"STTwitterStreamJSONTypeDisconnect"; 25 | case STTwitterStreamJSONTypeWarning: 26 | return @"STTwitterStreamJSONTypeWarning"; 27 | case STTwitterStreamJSONTypeEvent: 28 | return @"STTwitterStreamJSONTypeEvent"; 29 | case STTwitterStreamJSONTypeStatusWithheld: 30 | return @"STTwitterStreamJSONTypeStatusWithheld"; 31 | case STTwitterStreamJSONTypeUserWithheld: 32 | return @"STTwitterStreamJSONTypeUserWithheld"; 33 | case STTwitterStreamJSONTypeControl: 34 | return @"STTwitterStreamJSONTypeControl"; 35 | case STTwitterStreamJSONTypeDirectMessage: 36 | return @"STTwitterStreamJSONTypeDirectMessage"; 37 | default: 38 | case STTwitterStreamJSONTypeUnsupported: 39 | return @"STTwitterStreamJSONTypeUnsupported"; 40 | } 41 | } 42 | 43 | static inline BOOL isDigitsOnlyString(NSString *str) { 44 | static NSCharacterSet *__notDigits; 45 | static dispatch_once_t onceToken; 46 | dispatch_once(&onceToken, ^{ 47 | __notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; 48 | }); 49 | return str.length && [str rangeOfCharacterFromSet:__notDigits].location == NSNotFound; 50 | } 51 | 52 | @interface STTwitterStreamParser () 53 | 54 | @property (nonatomic) NSMutableString *receivedMessage; 55 | @property (nonatomic) int bytesExpected; 56 | 57 | @end 58 | 59 | @implementation STTwitterStreamParser 60 | 61 | - (void)parseWithStreamData:(NSData *)data 62 | parsedJSONBlock:(void (^)(NSDictionary *json, STTwitterStreamJSONType type))parsedJsonBlock { 63 | static NSString * const kDelimiter = @"\r\n"; 64 | NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 65 | 66 | for (NSString* part in [response componentsSeparatedByString:kDelimiter]) { 67 | 68 | if (self.receivedMessage == nil) { 69 | if (isDigitsOnlyString(part)) { 70 | self.receivedMessage = [NSMutableString string]; 71 | self.bytesExpected = [part intValue]; 72 | } 73 | } else if (self.bytesExpected > 0) { 74 | if (self.receivedMessage.length < self.bytesExpected) { 75 | // Append the data 76 | if (part.length > 0) { 77 | [self.receivedMessage appendString:part]; 78 | } else { 79 | [self.receivedMessage appendString:kDelimiter]; 80 | } 81 | if (self.receivedMessage.length + kDelimiter.length == self.bytesExpected) { 82 | [self.receivedMessage appendString:kDelimiter]; 83 | // Success! 84 | NSError *error = nil; 85 | id json = [NSJSONSerialization JSONObjectWithData:[self.receivedMessage dataUsingEncoding:NSUTF8StringEncoding] 86 | options:NSJSONReadingAllowFragments 87 | error:&error]; 88 | if(json == nil) { 89 | NSLog(@"-- error: %@", error); 90 | } 91 | 92 | STTwitterStreamJSONType type = [[self class] streamJSONTypeForJSON:json]; 93 | parsedJsonBlock(json, type); 94 | 95 | // Reset 96 | self.receivedMessage = nil; 97 | self.bytesExpected = 0; 98 | } 99 | } else { 100 | self.receivedMessage = nil; 101 | self.bytesExpected = 0; 102 | } 103 | } else { 104 | self.receivedMessage = nil; 105 | self.bytesExpected = 0; 106 | } 107 | } 108 | } 109 | 110 | + (STTwitterStreamJSONType)streamJSONTypeForJSON:(id)json { 111 | if ([json isKindOfClass:[NSDictionary class]]) { 112 | if ([json objectForKey:@"source"] && [json objectForKey:@"text"]) { 113 | return STTwitterStreamJSONTypeTweet; 114 | } else if ([json objectForKey:@"friends"] || [json objectForKey:@"friends_str"]) { 115 | return STTwitterStreamJSONTypeFriendsLists; 116 | } else if ([json objectForKey:@"delete"]) { 117 | return STTwitterStreamJSONTypeDelete; 118 | } else if ([json objectForKey:@"scrub_geo"]) { 119 | return STTwitterStreamJSONTypeScrubGeo; 120 | } else if ([json objectForKey:@"limit"]) { 121 | return STTwitterStreamJSONTypeLimit; 122 | } else if ([json objectForKey:@"disconnect"]) { 123 | return STTwitterStreamJSONTypeDisconnect; 124 | } else if ([json objectForKey:@"warning"]) { 125 | return STTwitterStreamJSONTypeWarning; 126 | } else if ([json objectForKey:@"event"]) { 127 | return STTwitterStreamJSONTypeEvent; // may be 'Event' or 'User update' 128 | } else if ([json objectForKey:@"status_withheld"]) { 129 | return STTwitterStreamJSONTypeStatusWithheld; 130 | } else if ([json objectForKey:@"user_withheld"]) { 131 | return STTwitterStreamJSONTypeUserWithheld; 132 | } else if ([json objectForKey:@"control"]) { 133 | return STTwitterStreamJSONTypeControl; 134 | } else if ([json objectForKey:@"direct_message"]) { 135 | return STTwitterStreamJSONTypeDirectMessage; 136 | } 137 | } 138 | 139 | return STTwitterStreamJSONTypeUnsupported; 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /demo_cli/favorites/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // clitter 4 | // 5 | // Created by Nicolas Seriot on 10/10/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "STTwitter.h" 11 | 12 | NSString *descriptionForTarget(NSDictionary *target) { 13 | //NSString *timestamp = [target valueForKey:@"created_at"]; 14 | NSString *targetName = [target valueForKeyPath:@"user.screen_name"]; 15 | NSString *text = [target valueForKey:@"text"]; 16 | text = [text stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "]; 17 | NSString *favouritesCount = [target valueForKey:@"favorite_count"]; 18 | NSString *retweetsCount = [target valueForKey:@"retweet_count"]; 19 | NSString *idString = [target valueForKey:@"id_str"]; 20 | 21 | return [NSString stringWithFormat:@"%@\t[F %@] [R %@]\t@%@\t%@", idString, favouritesCount, retweetsCount, targetName, text]; 22 | } 23 | 24 | NSString *descriptionForFavorites(NSArray *favorites) { 25 | 26 | NSMutableString *ms = [NSMutableString string]; 27 | 28 | NSUInteger numberOfFavorites = 0; 29 | 30 | for(NSDictionary *d in favorites) { 31 | [ms appendString:@"----------\n"]; 32 | 33 | NSString *timestamp = [d valueForKey:@"created_at"]; 34 | 35 | for(NSDictionary *source in [d valueForKey:@"sources"]) { 36 | NSString *sourceName = [source valueForKey:@"screen_name"]; 37 | 38 | [ms appendFormat:@"%@ @%@ favorited:\n", timestamp, sourceName]; 39 | } 40 | 41 | NSArray *targets = [d valueForKey:@"targets"]; 42 | 43 | numberOfFavorites += [targets count]; 44 | 45 | for(NSDictionary *target in targets) { 46 | 47 | NSString *targetDescription = descriptionForTarget(target); 48 | 49 | [ms appendFormat:@"%@\n", targetDescription]; 50 | } 51 | } 52 | 53 | return ms; 54 | } 55 | 56 | void setFavoriteStatus(STTwitterAPI *twitter, BOOL isFavorite, NSString *statusID) { 57 | [twitter postFavoriteState:isFavorite forStatusID:statusID successBlock:^(NSDictionary *status) { 58 | NSLog(@"%@", status); 59 | exit(0); 60 | } errorBlock:^(NSError *error) { 61 | printf("%s\n", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]); 62 | exit(1); 63 | }]; 64 | } 65 | 66 | void fetchFavorites(STTwitterAPI *twitter, NSString *sinceID) { 67 | if(sinceID) { 68 | printf("Fetching favorites starting at position: %s\n", [sinceID cStringUsingEncoding:NSUTF8StringEncoding]); 69 | } 70 | 71 | [twitter _getActivityByFriendsSinceID:sinceID 72 | count:@"100" 73 | contributorDetails:@(NO) 74 | includeCards:@(NO) 75 | includeEntities:@(NO) 76 | includeMyRetweets:nil 77 | includeUserEntites:@(NO) 78 | latestResults:@(YES) 79 | sendErrorCodes:nil successBlock:^(NSArray *activities) { 80 | 81 | NSArray *favorites = [activities filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 82 | NSDictionary *d = (NSDictionary *)evaluatedObject; 83 | return [[d valueForKey:@"action"] isEqualToString:@"favorite"]; 84 | }]]; 85 | 86 | if([favorites count] == 0) { 87 | printf("No favorites found.\n"); 88 | exit(0); 89 | } 90 | 91 | NSArray *maxPositions = [favorites valueForKeyPath:@"max_position"]; 92 | NSString *maxPosition = [maxPositions count] ? [maxPositions objectAtIndex:0] : nil; 93 | 94 | if(maxPosition) { 95 | [[NSUserDefaults standardUserDefaults] setValue:maxPosition forKey:@"CurrentPosition"]; 96 | [[NSUserDefaults standardUserDefaults] synchronize]; 97 | } 98 | 99 | printf("Current position: %s\n", [maxPosition cStringUsingEncoding:NSUTF8StringEncoding]); 100 | 101 | NSString *favoritesDescription = descriptionForFavorites(favorites); 102 | printf("%s", [favoritesDescription cStringUsingEncoding:NSUTF8StringEncoding]); 103 | 104 | exit(0); 105 | 106 | } errorBlock:^(NSError *error) { 107 | printf("%s\n", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]); 108 | 109 | exit(1); 110 | }]; 111 | } 112 | 113 | 114 | int main(int argc, const char * argv[]) 115 | { 116 | 117 | @autoreleasepool { 118 | 119 | printf("Clitter displays the latest favorites by your friends, using OS X settings.\n"); 120 | printf("By default, it remembers the latest position and will fetch only new one.\n"); 121 | printf("USAGE: ./clitter [-fav (1|0) -status STATUS_ID] | [-pos POSITION] | [-all YES]\n\n"); 122 | 123 | // 1382776597941 124 | 125 | BOOL fetchAll = [[NSUserDefaults standardUserDefaults] boolForKey:@"all"]; 126 | NSString *setToFavoriteString = [[NSUserDefaults standardUserDefaults] valueForKey:@"fav"]; // 1 or 0 127 | NSString *statusID = [[NSUserDefaults standardUserDefaults] valueForKey:@"status"]; 128 | NSString *sinceIDFromArgument = [[NSUserDefaults standardUserDefaults] valueForKey:@"pos"]; 129 | NSString *sinceIDFromUserDefaults = [[NSUserDefaults standardUserDefaults] valueForKey:@"CurrentPosition"]; 130 | 131 | NSString *sinceID = nil; 132 | 133 | if(fetchAll) { 134 | sinceID = nil; 135 | } else if(sinceIDFromArgument) { 136 | sinceID = sinceIDFromArgument; 137 | } else if (sinceIDFromUserDefaults) { 138 | sinceID = sinceIDFromUserDefaults; 139 | } 140 | 141 | __block STTwitterAPI *twitter = [STTwitterAPI twitterAPIOSWithFirstAccount]; 142 | 143 | [twitter verifyCredentialsWithSuccessBlock:^(NSString *username) { 144 | printf("Account: %s\n", [username cStringUsingEncoding:NSUTF8StringEncoding]); 145 | 146 | if(setToFavoriteString && statusID) { 147 | BOOL setToFavorite = [@([setToFavoriteString integerValue]) boolValue]; 148 | 149 | setFavoriteStatus(twitter, setToFavorite, statusID); 150 | } else { 151 | fetchFavorites(twitter, sinceID); 152 | } 153 | 154 | } errorBlock:^(NSError *error) { 155 | printf("%s\n", [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]); 156 | exit(1); 157 | }]; 158 | 159 | [[NSRunLoop currentRunLoop] run]; 160 | } 161 | return 0; 162 | } 163 | 164 | -------------------------------------------------------------------------------- /STTwitter/Vendor/STHTTPRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012, Nicolas Seriot 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * 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. 9 | * 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. 10 | 11 | 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. 12 | */ 13 | 14 | #import 15 | 16 | extern NSUInteger const kSTHTTPRequestCancellationError; 17 | extern NSUInteger const kSTHTTPRequestDefaultTimeout; 18 | 19 | @class STHTTPRequest; 20 | 21 | typedef void (^sendRequestBlock_t)(STHTTPRequest *request); 22 | typedef void (^uploadProgressBlock_t)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); 23 | typedef void (^downloadProgressBlock_t)(NSData *data, int64_t totalBytesReceived, int64_t totalBytesExpectedToReceive); 24 | typedef void (^completionBlock_t)(NSDictionary *headers, NSString *body); 25 | typedef void (^completionDataBlock_t)(NSDictionary *headers, NSData *body); 26 | typedef void (^errorBlock_t)(NSError *error); 27 | 28 | typedef NS_ENUM(NSUInteger, STHTTPRequestCookiesStorage) { 29 | STHTTPRequestCookiesStorageShared = 0, 30 | STHTTPRequestCookiesStorageLocal = 1, 31 | STHTTPRequestCookiesStorageNoStorage = 2, 32 | STHTTPRequestCookiesStorageUndefined = NSUIntegerMax 33 | }; 34 | 35 | @interface STHTTPRequest : NSObject 36 | 37 | @property (copy) uploadProgressBlock_t uploadProgressBlock; 38 | @property (copy) downloadProgressBlock_t downloadProgressBlock; 39 | @property (copy) completionBlock_t completionBlock; 40 | @property (copy) errorBlock_t errorBlock; 41 | @property (copy) completionDataBlock_t completionDataBlock; 42 | 43 | // request 44 | @property (nonatomic, strong) NSString *HTTPMethod; // default: GET, overridden by POST if POSTDictionary or files to upload 45 | @property (nonatomic, strong) NSMutableDictionary *requestHeaders; 46 | @property (nonatomic, strong) NSDictionary *POSTDictionary; // keys and values are NSString instances 47 | @property (nonatomic, strong) NSDictionary *GETDictionary; // appended to the URL string 48 | @property (nonatomic, strong) NSData *rawPOSTData; // eg. to post JSON contents 49 | @property (nonatomic) NSStringEncoding POSTDataEncoding; 50 | @property (nonatomic) NSTimeInterval timeoutSeconds; // ignored if 0 51 | @property (nonatomic) BOOL addCredentialsToURL; // default NO 52 | @property (nonatomic) BOOL encodePOSTDictionary; // default YES 53 | @property (nonatomic) BOOL encodeGETDictionary; // default YES, set to NO if the parameters are already URL encoded 54 | @property (nonatomic, strong, readonly) NSURL *url; 55 | @property (nonatomic) BOOL preventRedirections; 56 | @property (nonatomic) BOOL useUploadTaskInBackground; 57 | @property (nonatomic) STHTTPRequestCookiesStorage cookieStoragePolicyForInstance; // overrides globalCookiesStoragePolicy 58 | @property (nonatomic) NSString *sharedContainerIdentifier; 59 | 60 | + (void)setBackgroundCompletionHandler:(void(^)())completionHandler forSessionIdentifier:(NSString *)sessionIdentifier; 61 | //+ (void(^)())backgroundCompletionHandlerForSessionIdentifier:(NSString *)sessionIdentifier; 62 | 63 | // response 64 | @property (nonatomic) NSStringEncoding forcedResponseEncoding; 65 | @property (nonatomic, readonly) NSInteger responseStatus; 66 | @property (nonatomic, strong, readonly) NSString *responseStringEncodingName; 67 | @property (nonatomic, strong, readonly) NSDictionary *responseHeaders; 68 | @property (nonatomic, strong) NSString *responseString; 69 | @property (nonatomic, strong, readonly) NSMutableData *responseData; 70 | @property (nonatomic, strong, readonly) NSError *error; 71 | @property (nonatomic) long long responseExpectedContentLength; // set by connection:didReceiveResponse: delegate method; web server must send the Content-Length header for accurate value 72 | 73 | // cache 74 | @property (nonatomic) BOOL ignoreCache; // requests ignore cached responses and responses don't get cached 75 | 76 | + (instancetype)requestWithURL:(NSURL *)url; 77 | + (instancetype)requestWithURLString:(NSString *)urlString; 78 | 79 | + (void)setGlobalIgnoreCache:(BOOL)ignoreCache; // no cache at all when set, overrides the ignoreCache property 80 | 81 | - (NSString *)debugDescription; // logged when launched with -STHTTPRequestShowDebugDescription 1 82 | - (NSString *)curlDescription; // logged when launched with -STHTTPRequestShowCurlDescription 1 83 | 84 | - (NSString *)startSynchronousWithError:(NSError **)error; 85 | - (void)startAsynchronous; 86 | - (void)cancel; 87 | 88 | // Cookies 89 | + (void)addCookieToSharedCookiesStorage:(NSHTTPCookie *)cookie; 90 | + (void)addCookieToSharedCookiesStorageWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url; 91 | - (void)addCookieWithName:(NSString *)name value:(NSString *)value url:(NSURL *)url; 92 | - (void)addCookieWithName:(NSString *)name value:(NSString *)value; 93 | - (NSArray *)requestCookies; 94 | - (NSArray *)sessionCookies; 95 | + (NSArray *)sessionCookiesInSharedCookiesStorage; 96 | + (void)deleteAllCookiesFromSharedCookieStorage; 97 | + (void)deleteAllCookiesFromLocalCookieStorage; 98 | - (void)deleteSessionCookies; // empty the cookie storage that is used 99 | + (void)setGlobalCookiesStoragePolicy:(STHTTPRequestCookiesStorage)cookieStoragePolicy; 100 | 101 | // Credentials 102 | + (NSURLCredential *)sessionAuthenticationCredentialsForURL:(NSURL *)requestURL; 103 | - (void)setUsername:(NSString *)username password:(NSString *)password; 104 | - (NSString *)username; 105 | - (NSString *)password; 106 | + (void)deleteAllCredentials; 107 | 108 | // Headers 109 | - (void)setHeaderWithName:(NSString *)name value:(NSString *)value; 110 | - (void)removeHeaderWithName:(NSString *)name; 111 | - (NSDictionary *)responseHeaders; 112 | 113 | // Upload 114 | - (void)addFileToUpload:(NSString *)path parameterName:(NSString *)param; 115 | - (void)addDataToUpload:(NSData *)data parameterName:(NSString *)param; 116 | - (void)addDataToUpload:(NSData *)data parameterName:(NSString *)param mimeType:(NSString *)mimeType fileName:(NSString *)fileName; 117 | 118 | // Session 119 | + (void)clearSession; // delete all credentials and cookies 120 | 121 | // DEBUG 122 | - (NSURLRequest *)prepareURLRequest; // prepare the request according to the STHTTPRequest instance state 123 | 124 | @end 125 | 126 | @interface NSError (STHTTPRequest) 127 | - (BOOL)st_isAuthenticationError; 128 | - (BOOL)st_isCancellationError; 129 | @end 130 | 131 | @interface NSString (RFC3986) 132 | - (NSString *)st_stringByAddingRFC3986PercentEscapesUsingEncoding:(NSStringEncoding)encoding; 133 | @end 134 | 135 | @interface NSString (STUtilities) 136 | - (NSString *)st_stringByAppendingGETParameters:(NSDictionary *)parameters doApplyURLEncoding:(BOOL)doApplyURLEncoding; 137 | @end 138 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STClientVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // STClientVC.m 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/22/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STClientVC.h" 10 | 11 | @interface STClientVC () 12 | 13 | @property (nonatomic, retain) NSObject *streamingRequest; 14 | 15 | @property (nonatomic, retain) NSString *twitterTimelineUsername; 16 | @property (nonatomic, retain) NSString *twitterStreamingKeywordsString; 17 | @property (nonatomic, retain) NSString *twitterGetTimelineStatus; 18 | @property (nonatomic, retain) NSString *twitterPostTweetText; 19 | @property (nonatomic, retain) NSString *twitterPostTweetStatus; 20 | @property (nonatomic, retain) NSString *twitterStreamingStatus; 21 | 22 | @property (nonatomic, retain) NSURL *twitterPostMediaURL; 23 | @property (nonatomic, retain) NSString *twitterPostLatitude; 24 | @property (nonatomic, retain) NSString *twitterPostLongitude; 25 | 26 | @property (nonatomic, retain) NSArray *timelineStatuses; 27 | 28 | - (IBAction)getTimeline:(id)sender; 29 | - (IBAction)chooseMedia:(id)sender; 30 | - (IBAction)postTweet:(id)sender; 31 | - (IBAction)startStreaming:(id)sender; 32 | - (IBAction)stopStreaming:(id)sender; 33 | 34 | @end 35 | 36 | @implementation STClientVC 37 | 38 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 39 | { 40 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 41 | if (self) { 42 | // Initialization code here. 43 | } 44 | return self; 45 | } 46 | 47 | - (IBAction)postTweet:(id)sender { 48 | 49 | [_streamingRequest cancel]; 50 | self.streamingRequest = nil; 51 | 52 | self.twitterPostTweetStatus = @"-"; 53 | 54 | if(_twitterPostMediaURL) { 55 | 56 | [_twitter deprecated_postStatusesUpdate:_twitterPostTweetText 57 | inReplyToStatusID:nil 58 | mediaURL:_twitterPostMediaURL 59 | placeID:nil 60 | latitude:_twitterPostLatitude 61 | longitude:_twitterPostLongitude 62 | 63 | uploadProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { 64 | NSLog(@"%lld %lld %lld", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); 65 | } successBlock:^(NSDictionary *status) { 66 | 67 | self.twitterPostTweetText = @""; 68 | self.twitterPostTweetStatus = @"OK"; 69 | self.twitterPostLatitude = nil; 70 | self.twitterPostLongitude = nil; 71 | self.twitterPostMediaURL = nil; 72 | } errorBlock:^(NSError *error) { 73 | self.twitterPostTweetStatus = error ? [error localizedDescription] : @"Unknown error"; 74 | }]; 75 | 76 | } else { 77 | 78 | [_twitter postStatusesUpdate:_twitterPostTweetText 79 | inReplyToStatusID:nil 80 | latitude:_twitterPostLatitude 81 | longitude:_twitterPostLongitude 82 | placeID:nil 83 | displayCoordinates:@(YES) 84 | trimUser:nil 85 | autoPopulateReplyMetadata:nil 86 | excludeReplyUserIDsStrings:nil 87 | attachmentURLString:nil 88 | useExtendedTweetMode:@(YES) 89 | successBlock:^(NSDictionary *status) { 90 | 91 | NSLog(@"-- text: %@", [status valueForKey:@"text"]); 92 | NSLog(@"-- full_text: %@", [status valueForKey:@"full_text"]); 93 | 94 | self.twitterPostTweetText = @""; 95 | self.twitterPostTweetStatus = @"OK"; 96 | self.twitterPostLatitude = nil; 97 | self.twitterPostLongitude = nil; 98 | self.twitterPostMediaURL = nil; 99 | } errorBlock:^(NSError *error) { 100 | self.twitterPostTweetStatus = error ? [error localizedDescription] : @"Unknown error"; 101 | }]; 102 | } 103 | } 104 | 105 | - (IBAction)getTimeline:(id)sender { 106 | 107 | [_streamingRequest cancel]; 108 | self.streamingRequest = nil; 109 | 110 | self.twitterGetTimelineStatus = @"-"; 111 | self.timelineStatuses = [NSArray array]; 112 | 113 | if([_twitterTimelineUsername length] > 0) { 114 | [_twitter getUserTimelineWithScreenName:_twitterTimelineUsername successBlock:^(NSArray *statuses) { 115 | self.timelineStatuses = statuses; 116 | self.twitterGetTimelineStatus = @"OK"; 117 | } errorBlock:^(NSError *error) { 118 | self.twitterGetTimelineStatus = error ? [error localizedDescription] : @"Unknown error"; 119 | }]; 120 | } else { 121 | [_twitter getHomeTimelineSinceID:nil count:20 successBlock:^(NSArray *statuses) { 122 | self.timelineStatuses = statuses; 123 | self.twitterGetTimelineStatus = @"OK"; 124 | } errorBlock:^(NSError *error) { 125 | self.twitterGetTimelineStatus = error ? [error localizedDescription] : @"Unknown error"; 126 | }]; 127 | } 128 | } 129 | 130 | - (IBAction)chooseMedia:(id)sender { 131 | self.twitterPostMediaURL = nil; 132 | 133 | NSOpenPanel *panel = [NSOpenPanel openPanel]; 134 | 135 | [panel setCanChooseDirectories:NO]; 136 | [panel setCanChooseFiles:YES]; 137 | [panel setAllowedFileTypes:@[ @"png", @"PNG", @"jpg", @"JPG", @"jpeg", @"JPEG", @"gif", @"GIF"] ]; 138 | 139 | NSWindow *mainWindow = [[NSApplication sharedApplication] mainWindow]; 140 | 141 | [panel beginSheetModalForWindow:mainWindow completionHandler:^(NSInteger result) { 142 | 143 | if (result != NSFileHandlingPanelOKButton) return; 144 | 145 | NSArray *urls = [panel URLs]; 146 | 147 | NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 148 | if([evaluatedObject isKindOfClass:[NSURL class]] == NO) return NO; 149 | 150 | NSURL *url = (NSURL *)evaluatedObject; 151 | 152 | return [url isFileURL]; 153 | }]; 154 | 155 | NSArray *fileURLS = [urls filteredArrayUsingPredicate:p]; 156 | 157 | NSURL *fileURL = [fileURLS lastObject]; 158 | 159 | BOOL isDir = NO; 160 | if ([[NSFileManager defaultManager] fileExistsAtPath: fileURL.path isDirectory: &isDir] == NO) return; 161 | 162 | self.twitterPostMediaURL = fileURL; 163 | }]; 164 | } 165 | 166 | - (IBAction)startStreaming:(id)sender { 167 | 168 | self.timelineStatuses = [NSArray array]; 169 | 170 | if(_twitterStreamingKeywordsString == nil) return; 171 | 172 | self.twitterStreamingStatus = @"Streaming started"; 173 | 174 | self.streamingRequest = [_twitter postStatusesFilterKeyword:_twitterStreamingKeywordsString 175 | tweetBlock:^(NSDictionary *tweet) { 176 | self.timelineStatuses = [_timelineStatuses arrayByAddingObject:tweet]; 177 | } errorBlock:^(NSError *error) { 178 | self.twitterStreamingStatus = [error localizedDescription]; 179 | }]; 180 | } 181 | 182 | - (IBAction)stopStreaming:(id)sender { 183 | 184 | [_streamingRequest cancel]; 185 | self.streamingRequest = nil; 186 | 187 | self.twitterStreamingStatus = @"Streaming stopped"; 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /STTwitter/Vendor/JSONSyntaxHighlight.m: -------------------------------------------------------------------------------- 1 | /** 2 | * JSONSyntaxHighlight.h 3 | * JSONSyntaxHighlight 4 | * 5 | * Syntax highlight JSON 6 | * 7 | * Created by Dave Eddy on 8/3/13. 8 | * Copyright (c) 2013 Dave Eddy. All rights reserved. 9 | * 10 | * The MIT License (MIT) 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | #import "JSONSyntaxHighlight.h" 32 | 33 | @implementation JSONSyntaxHighlight { 34 | NSRegularExpression *regex; 35 | } 36 | 37 | #pragma mark Object Initializer 38 | // Must init with a JSON object 39 | - (JSONSyntaxHighlight *)init 40 | { 41 | return nil; 42 | } 43 | 44 | - (JSONSyntaxHighlight *)initWithJSON:(id)JSON 45 | { 46 | self = [super init]; 47 | if (self) { 48 | // save the origin JSON 49 | _JSON = JSON; 50 | 51 | // create the object local regex 52 | regex = [NSRegularExpression regularExpressionWithPattern:@"^( *)(\".+\" : )?(\"[^\"]*\"|[\\w.+-]*)?([,\\[\\]{}]?,?$)" 53 | options:NSRegularExpressionAnchorsMatchLines 54 | error:nil]; 55 | 56 | // parse the JSON if possible 57 | if ([NSJSONSerialization isValidJSONObject:self.JSON]) { 58 | NSJSONWritingOptions options = NSJSONWritingPrettyPrinted; 59 | NSData *data = [NSJSONSerialization dataWithJSONObject:self.JSON options:options error:nil]; 60 | NSString *o = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 61 | _parsedJSON = o; 62 | } else { 63 | _parsedJSON = [NSString stringWithFormat:@"%@", self.JSON]; 64 | } 65 | 66 | // set the default attributes 67 | self.nonStringAttributes = @{NSForegroundColorAttributeName: [self.class colorWithRGB:0x000080]}; 68 | self.stringAttributes = @{NSForegroundColorAttributeName: [self.class colorWithRGB:0x808000]}; 69 | self.keyAttributes = @{NSForegroundColorAttributeName: [self.class colorWithRGB:0xa52a2a]}; 70 | } 71 | return self; 72 | } 73 | 74 | #pragma mark - 75 | #pragma mark JSON Highlighting 76 | - (NSAttributedString *)highlightJSON 77 | { 78 | return [self highlightJSONWithPrettyPrint:YES]; 79 | } 80 | 81 | - (NSAttributedString *)highlightJSONWithPrettyPrint:(BOOL)prettyPrint 82 | { 83 | NSMutableAttributedString *line = [[NSMutableAttributedString alloc] initWithString:@""]; 84 | [self enumerateMatchesWithIndentBlock: 85 | // The indent 86 | ^(NSRange range, NSString *s) { 87 | NSAttributedString *as = [[NSAttributedString alloc] initWithString:s attributes:@{}]; 88 | if (prettyPrint) [line appendAttributedString:as]; 89 | } 90 | keyBlock: 91 | // The key (with quotes and colon) 92 | ^(NSRange range, NSString *s) { 93 | // I hate this: this changes `"key" : ` to `"key"` 94 | NSString *key = [s substringToIndex:s.length - 3]; 95 | [line appendAttributedString:[[NSAttributedString alloc] initWithString:key attributes:self.keyAttributes]]; 96 | NSString *colon = prettyPrint ? @" : " : @":"; 97 | [line appendAttributedString:[[NSAttributedString alloc] initWithString:colon attributes:@{}]]; 98 | } 99 | valueBlock: 100 | // The value 101 | ^(NSRange range, NSString *s) { 102 | NSAttributedString *as; 103 | if ([s rangeOfString:@"\""].location == NSNotFound) // literal or number 104 | as = [[NSAttributedString alloc] initWithString:s attributes:self.nonStringAttributes]; 105 | else // string 106 | as = [[NSAttributedString alloc] initWithString:s attributes:self.stringAttributes]; 107 | 108 | [line appendAttributedString:as]; 109 | } 110 | endBlock: 111 | // The final comma, or ending character 112 | ^(NSRange range, NSString *s) { 113 | NSAttributedString *as = [[NSAttributedString alloc] initWithString:s attributes:@{}]; 114 | [line appendAttributedString:as]; 115 | if (prettyPrint) [line appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; 116 | }]; 117 | 118 | if ([line isEqualToAttributedString:[[NSAttributedString alloc] initWithString:@""]]) 119 | line = [[NSMutableAttributedString alloc] initWithString:self.parsedJSON]; 120 | return line; 121 | } 122 | 123 | #pragma mark JSON Parser 124 | - (void)enumerateMatchesWithIndentBlock:(void(^)(NSRange, NSString*))indentBlock 125 | keyBlock:(void(^)(NSRange, NSString*))keyBlock 126 | valueBlock:(void(^)(NSRange, NSString*))valueBlock 127 | endBlock:(void(^)(NSRange, NSString*))endBlock 128 | { 129 | [regex enumerateMatchesInString:self.parsedJSON 130 | options:0 131 | range:NSMakeRange(0, self.parsedJSON.length) 132 | usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) { 133 | 134 | NSRange indentRange = [match rangeAtIndex:1]; 135 | NSRange keyRange = [match rangeAtIndex:2]; 136 | NSRange valueRange = [match rangeAtIndex:3]; 137 | NSRange endRange = [match rangeAtIndex:4]; 138 | 139 | if (indentRange.location != NSNotFound) 140 | indentBlock(indentRange, [self.parsedJSON substringWithRange:indentRange]); 141 | if (keyRange.location != NSNotFound) 142 | keyBlock(keyRange, [self.parsedJSON substringWithRange:keyRange]); 143 | if (valueRange.location != NSNotFound) 144 | valueBlock(valueRange, [self.parsedJSON substringWithRange:valueRange]); 145 | if (endRange.location != NSNotFound) 146 | endBlock(endRange, [self.parsedJSON substringWithRange:endRange]); 147 | }]; 148 | } 149 | 150 | #pragma mark - 151 | #pragma mark Color Helper Functions 152 | #if (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) 153 | + (UIColor *)colorWithRGB:(NSInteger)rgbValue 154 | { 155 | return [self.class colorWithRGB:rgbValue alpha:1.0]; 156 | } 157 | 158 | + (UIColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha 159 | { 160 | return [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 161 | green:((float)((rgbValue & 0x00FF00) >> 8 )) / 255.0 162 | blue:((float)((rgbValue & 0x0000FF) >> 0 )) / 255.0 163 | alpha:alpha]; 164 | } 165 | #else 166 | + (NSColor *)colorWithRGB:(NSInteger)rgbValue 167 | { 168 | return [self.class colorWithRGB:rgbValue alpha:1.0]; 169 | } 170 | 171 | + (NSColor *)colorWithRGB:(NSInteger)rgbValue alpha:(CGFloat)alpha 172 | { 173 | return [NSColor colorWithCalibratedRed:((float)((rgbValue & 0xFF0000) >> 16)) / 255.0 174 | green:((float)((rgbValue & 0x00FF00) >> 8 )) / 255.0 175 | blue:((float)((rgbValue & 0x0000FF) >> 0 )) / 255.0 176 | alpha:alpha]; 177 | } 178 | #endif 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /demo_osx/STTwitterDemoOSX/STConsoleVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // STRequestsVC.m 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 9/22/13. 6 | // Copyright (c) 2013 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STConsoleVC.h" 10 | #import "JSONSyntaxHighlight.h" 11 | #import "BAVPlistNode.h" 12 | 13 | @interface STConsoleVC () 14 | 15 | @property (nonatomic, strong) BAVPlistNode *rootNode; 16 | 17 | @property (nonatomic, assign) IBOutlet NSArrayController *requestParametersController; 18 | 19 | @property (nonatomic, assign) IBOutlet NSPopUpButton *genericHTTPMethodPopUpButton; 20 | @property (nonatomic, assign) IBOutlet NSTextView *headersTextView; 21 | @property (nonatomic, assign) IBOutlet NSTextView *bodyTextView; 22 | @property (nonatomic, strong) IBOutlet NSOutlineView *outlineView; 23 | 24 | @property (nonatomic, strong) NSString *genericHTTPMethod; 25 | @property (nonatomic, strong) NSString *genericBaseURLString; 26 | @property (nonatomic, strong) NSString *genericAPIEndpoint; 27 | @property (nonatomic, strong) NSAttributedString *curlTextViewAttributedString; 28 | @property (nonatomic, strong) NSAttributedString *responseHeadersTextViewAttributedString; 29 | @property (nonatomic, strong) NSAttributedString *bodyTextViewAttributedString; 30 | @property (nonatomic, strong) NSMutableArray *genericRequestParameters; 31 | 32 | - (IBAction)changeHTTPMethodAction:(id)sender; 33 | - (IBAction)sendRequestAction:(id)sender; 34 | 35 | @end 36 | 37 | @implementation STConsoleVC 38 | 39 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 40 | { 41 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 42 | if (self) { 43 | // Initialization code here. 44 | } 45 | return self; 46 | } 47 | 48 | - (void)awakeFromNib { 49 | [super awakeFromNib]; 50 | 51 | self.genericBaseURLString = @"https://api.twitter.com/1.1/"; 52 | self.genericAPIEndpoint = @"statuses/home_timeline.json"; 53 | 54 | NSMutableDictionary *md = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"count", @"key", @"10", @"value", nil]; 55 | 56 | self.genericRequestParameters = [ @[md] mutableCopy]; 57 | 58 | [self changeHTTPMethodAction:self]; 59 | } 60 | 61 | - (IBAction)changeHTTPMethodAction:(id)sender { 62 | self.genericHTTPMethod = [_genericHTTPMethodPopUpButton titleOfSelectedItem]; 63 | } 64 | 65 | - (NSString *)curlDescriptionWithMethod:(NSString *)method endpoint:(NSString *)endPoint baseURLString:(NSString *)baseURLString parameters:(NSDictionary *)parameters requestHeaders:(NSDictionary *)requestHeaders { 66 | /* 67 | $ curl -i -H "Authorization: OAuth oauth_consumer_key="7YBPrscvh0RIThrWYVeGg", \ 68 | oauth_nonce="DA5E6B1E-E98D-4AFB-9AAB-18A463F2", \ 69 | oauth_signature_method="HMAC-SHA1", \ 70 | oauth_timestamp="1381908706", \ 71 | oauth_version="1.0", \ 72 | oauth_token="1294332967-UsaIUBcsC4JcHv9tIYxk5EktsVisAtCLNVGKghP", \ 73 | oauth_signature="gnmc02ohamTvTmkTppz%2FbH8OjAs%3D"" \ 74 | "https://api.twitter.com/1.1/statuses/home_timeline.json?count=10" 75 | */ 76 | 77 | if([baseURLString hasSuffix:@"/"]) baseURLString = [baseURLString substringToIndex:[baseURLString length]-1]; 78 | if([endPoint hasPrefix:@"/"]) endPoint = [endPoint substringFromIndex:1]; 79 | 80 | NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@/%@", baseURLString, endPoint]; 81 | 82 | NSMutableArray *parametersArray = [NSMutableArray array]; 83 | 84 | [parameters enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 85 | NSString *s = [NSString stringWithFormat:@"%@=%@", key, obj]; 86 | [parametersArray addObject:s]; 87 | }]; 88 | 89 | NSString *POSTParameters = @""; 90 | 91 | NSMutableArray *ma = [NSMutableArray array]; 92 | NSString *parameterString = [parametersArray componentsJoinedByString:@"&"]; 93 | 94 | if([parameters count]) { 95 | if([method isEqualToString:@"POST"]) { 96 | [ma addObject:[NSString stringWithFormat:@"-d \"%@\"", parameterString]]; 97 | POSTParameters = [ma componentsJoinedByString:@" "]; 98 | } else { 99 | [urlString appendFormat:@"?%@", parameterString]; 100 | } 101 | } 102 | 103 | return [NSString stringWithFormat:@"curl -i -H \"Authorization: %@\" \"%@\" %@", [requestHeaders valueForKey:@"Authorization"], urlString, POSTParameters]; 104 | } 105 | 106 | - (IBAction)sendRequestAction:(id)sender { 107 | NSAssert(_genericAPIEndpoint, @""); 108 | NSAssert(_genericHTTPMethod, @""); 109 | NSAssert(_genericBaseURLString, @""); 110 | 111 | NSDictionary *attributes = @{ NSFontAttributeName : [NSFont fontWithName:@"Menlo" size:12] }; 112 | 113 | NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithCapacity:[_genericRequestParameters count]]; 114 | 115 | for(NSDictionary *d in _genericRequestParameters) { 116 | NSString *k = d[@"key"]; 117 | NSString *v = d[@"value"]; 118 | [parameters setObject:v forKey:k]; 119 | } 120 | 121 | self.curlTextViewAttributedString = [[NSAttributedString alloc] initWithString:@"" attributes:attributes]; 122 | self.responseHeadersTextViewAttributedString = [[NSAttributedString alloc] initWithString:@"" attributes:attributes]; 123 | self.bodyTextViewAttributedString = [[NSAttributedString alloc] initWithString:@"" attributes:attributes]; 124 | self.rootNode = nil; 125 | [_outlineView reloadData]; 126 | 127 | [_twitter fetchResource:_genericAPIEndpoint 128 | HTTPMethod:_genericHTTPMethod 129 | baseURLString:_genericBaseURLString 130 | parameters:parameters 131 | uploadProgressBlock:nil 132 | downloadProgressBlock:nil 133 | successBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response) { 134 | 135 | NSString *curlDescription = [self curlDescriptionWithMethod:_genericHTTPMethod endpoint:_genericAPIEndpoint baseURLString:_genericBaseURLString parameters:parameters requestHeaders:requestHeaders]; 136 | 137 | self.curlTextViewAttributedString = [[NSAttributedString alloc] initWithString:curlDescription attributes:attributes]; 138 | self.responseHeadersTextViewAttributedString = [[NSAttributedString alloc] initWithString:[responseHeaders description] attributes:attributes]; 139 | 140 | JSONSyntaxHighlight *jsh = [[JSONSyntaxHighlight alloc] initWithJSON:response]; 141 | 142 | NSMutableDictionary *keyAttributes = [jsh.keyAttributes mutableCopy]; 143 | [keyAttributes addEntriesFromDictionary:attributes]; 144 | 145 | NSMutableDictionary *stringAttributes = [jsh.stringAttributes mutableCopy]; 146 | [stringAttributes addEntriesFromDictionary:attributes]; 147 | 148 | NSMutableDictionary *nonStringAttributes = [jsh.nonStringAttributes mutableCopy]; 149 | [nonStringAttributes addEntriesFromDictionary:attributes]; 150 | 151 | jsh.keyAttributes = keyAttributes; 152 | jsh.stringAttributes = stringAttributes; 153 | jsh.nonStringAttributes = nonStringAttributes; 154 | 155 | NSMutableAttributedString *mas = [[jsh highlightJSONWithPrettyPrint:YES] mutableCopy]; 156 | 157 | BOOL jsonSyntaxHighlighterCouldNotParseJSON = [jsh.parsedJSON isEqualToString:[NSString stringWithFormat:@"%@", response]]; 158 | if(jsonSyntaxHighlighterCouldNotParseJSON) { 159 | NSDictionary *basicAttributes = @{ NSFontAttributeName : [NSFont fontWithName:@"Menlo" size:12] }; 160 | [mas setAttributes:basicAttributes range:NSMakeRange(0, [mas length])]; 161 | } 162 | 163 | self.bodyTextViewAttributedString = mas; 164 | 165 | self.rootNode = [BAVPlistNode plistNodeFromObject:response key:@"Root"]; 166 | 167 | [_outlineView reloadData]; 168 | 169 | } errorBlock:^(id request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error) { 170 | NSString *s = @"error"; 171 | if(error) { 172 | s = [error localizedDescription]; 173 | } 174 | 175 | NSString *curlDescription = [self curlDescriptionWithMethod:_genericHTTPMethod endpoint:_genericAPIEndpoint baseURLString:_genericBaseURLString parameters:parameters requestHeaders:requestHeaders]; 176 | 177 | //NSString *requestHeadersDescription = requestHeaders ? [requestHeaders description] : @""; 178 | NSString *responseHeadersDescription = responseHeaders ? [responseHeaders description] : @""; 179 | self.curlTextViewAttributedString = [[NSAttributedString alloc] initWithString:curlDescription attributes:attributes]; 180 | self.responseHeadersTextViewAttributedString = [[NSAttributedString alloc] initWithString:responseHeadersDescription attributes:attributes]; 181 | 182 | // TODO: display actualy body here 183 | self.bodyTextViewAttributedString = [[NSAttributedString alloc] initWithString:s attributes:attributes]; 184 | 185 | self.rootNode = nil; 186 | [_outlineView reloadData]; 187 | }]; 188 | } 189 | 190 | #pragma mark - NSOutlineViewDataSource 191 | 192 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(BAVPlistNode *)item 193 | { 194 | if (item == nil) 195 | return self.rootNode; 196 | 197 | return item.children[index]; 198 | } 199 | 200 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(BAVPlistNode *)item 201 | { 202 | if (item == nil) 203 | return 1; 204 | 205 | return item.children.count; 206 | } 207 | 208 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(BAVPlistNode *)item 209 | { 210 | return item.collection; 211 | } 212 | 213 | - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(BAVPlistNode *)item 214 | { 215 | NSString *columnId = tableColumn.identifier; 216 | 217 | if ([columnId isEqualToString:@"key"]) 218 | return item.key; 219 | else if ([columnId isEqualToString:@"type"]) 220 | return item.type; 221 | else if ([columnId isEqualToString:@"value"]) 222 | return item.value; 223 | 224 | return nil; 225 | } 226 | 227 | @end 228 | -------------------------------------------------------------------------------- /STTwitter/STTwitterOSRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // STTwitterOSRequest.m 3 | // STTwitterDemoOSX 4 | // 5 | // Created by Nicolas Seriot on 20/02/14. 6 | // Copyright (c) 2014 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | #import "STTwitterOSRequest.h" 10 | #import 11 | #import 12 | #import "STHTTPRequest.h" 13 | #import "NSString+STTwitter.h" 14 | #import "NSError+STTwitter.h" 15 | 16 | typedef void (^completion_block_t)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response); 17 | typedef void (^error_block_t)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error); 18 | typedef void (^upload_progress_block_t)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); 19 | typedef void (^stream_block_t)(NSObject *request, NSData *data); 20 | 21 | @interface STTwitterOSRequest () 22 | @property (nonatomic, copy) completion_block_t completionBlock; 23 | @property (nonatomic, copy) error_block_t errorBlock; 24 | @property (nonatomic, copy) upload_progress_block_t uploadProgressBlock; 25 | @property (nonatomic, copy) stream_block_t streamBlock; 26 | @property (nonatomic, strong) NSURLSessionDataTask *task; 27 | @property (nonatomic, strong) NSHTTPURLResponse *httpURLResponse; // only used with streaming API 28 | @property (nonatomic, strong) NSMutableData *data; // only used with non-streaming API 29 | @property (nonatomic, strong) ACAccount *account; 30 | @property (nonatomic) NSInteger httpMethod; 31 | @property (nonatomic, strong) NSDictionary *params; 32 | @property (nonatomic, strong) NSString *baseURLString; 33 | @property (nonatomic, strong) NSString *resource; 34 | @property (nonatomic) NSTimeInterval timeoutInSeconds; 35 | @end 36 | 37 | @implementation STTwitterOSRequest 38 | 39 | - (instancetype)initWithAPIResource:(NSString *)resource 40 | baseURLString:(NSString *)baseURLString 41 | httpMethod:(NSInteger)httpMethod 42 | parameters:(NSDictionary *)params 43 | account:(ACAccount *)account 44 | timeoutInSeconds:(NSTimeInterval)timeoutInSeconds 45 | uploadProgressBlock:(void(^)(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))uploadProgressBlock 46 | streamBlock:(void(^)(NSObject *request, NSData *data))streamBlock 47 | completionBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, id response))completionBlock 48 | errorBlock:(void(^)(NSObject *request, NSDictionary *requestHeaders, NSDictionary *responseHeaders, NSError *error))errorBlock { 49 | 50 | NSAssert(completionBlock, @"completionBlock is missing"); 51 | NSAssert(errorBlock, @"errorBlock is missing"); 52 | 53 | self = [super init]; 54 | 55 | self.resource = resource; 56 | self.baseURLString = baseURLString; 57 | self.httpMethod = httpMethod; 58 | self.params = params; 59 | self.account = account; 60 | self.completionBlock = completionBlock; 61 | self.errorBlock = errorBlock; 62 | self.uploadProgressBlock = uploadProgressBlock; 63 | self.streamBlock = streamBlock; 64 | self.timeoutInSeconds = timeoutInSeconds; 65 | 66 | return self; 67 | } 68 | 69 | - (NSURLRequest *)preparedURLRequest { 70 | NSString *postDataKey = [_params valueForKey:kSTPOSTDataKey]; 71 | NSString *postDataFilename = [_params valueForKey:kSTPOSTMediaFileNameKey]; 72 | NSData *mediaData = [_params valueForKey:postDataKey]; 73 | 74 | NSMutableDictionary *paramsWithoutMedia = [_params mutableCopy]; 75 | if(postDataKey) [paramsWithoutMedia removeObjectForKey:postDataKey]; 76 | [paramsWithoutMedia removeObjectForKey:kSTPOSTDataKey]; 77 | [paramsWithoutMedia removeObjectForKey:kSTPOSTMediaFileNameKey]; 78 | 79 | NSString *urlString = [_baseURLString stringByAppendingString:_resource]; 80 | NSURL *url = [NSURL URLWithString:urlString]; 81 | 82 | SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter 83 | requestMethod:_httpMethod 84 | URL:url 85 | parameters:paramsWithoutMedia]; 86 | 87 | [request setAccount:_account]; 88 | 89 | if(mediaData) { 90 | NSString *filename = postDataFilename ? postDataFilename : @"media.jpg"; 91 | [request addMultipartData:mediaData withName:postDataKey type:@"application/octet-stream" filename:filename]; 92 | } 93 | 94 | // we use NSURLSessionDataTask because SLRequest doesn't play well with the streaming API 95 | 96 | return [request preparedURLRequest]; 97 | } 98 | 99 | - (void)startRequest { 100 | 101 | NSURLRequest *preparedURLRequest = [self preparedURLRequest]; 102 | 103 | NSMutableURLRequest *mutablePreparedURLRequest = [preparedURLRequest mutableCopy]; 104 | mutablePreparedURLRequest.timeoutInterval = _timeoutInSeconds; 105 | 106 | if (_task) { 107 | [self cancel]; 108 | } 109 | 110 | NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; 111 | 112 | NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration 113 | delegate:self 114 | delegateQueue:nil]; 115 | 116 | // TODO: use an uploadDataTask when appropriate, need the file URL 117 | self.task = [session dataTaskWithRequest:mutablePreparedURLRequest]; 118 | 119 | [_task resume]; 120 | } 121 | 122 | - (void)cancel { 123 | [_task cancel]; 124 | 125 | NSURLRequest *request = [_task currentRequest]; 126 | 127 | NSString *s = @"Connection was cancelled."; 128 | NSDictionary *userInfo = @{NSLocalizedDescriptionKey: s}; 129 | NSError *error = [NSError errorWithDomain:NSStringFromClass([self class]) 130 | code:kSTHTTPRequestCancellationError 131 | userInfo:userInfo]; 132 | self.errorBlock(self, [self requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], error); 133 | } 134 | 135 | - (NSDictionary *)requestHeadersForRequest:(id)request { 136 | 137 | if([request isKindOfClass:[NSURLRequest class]]) { 138 | return [request allHTTPHeaderFields]; 139 | } 140 | 141 | return [[request preparedURLRequest] allHTTPHeaderFields]; 142 | } 143 | 144 | #pragma mark NSURLSessionDataDelegate 145 | 146 | - (void)URLSession:(NSURLSession *)session 147 | dataTask:(NSURLSessionDataTask *)dataTask 148 | didReceiveResponse:(NSURLResponse *)response 149 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 150 | 151 | __weak typeof(self) weakSelf = self; 152 | 153 | dispatch_async(dispatch_get_main_queue(), ^{ 154 | 155 | __strong typeof(weakSelf) strongSelf = weakSelf; 156 | 157 | if(strongSelf == nil) { 158 | completionHandler(NSURLSessionResponseCancel); 159 | return; 160 | } 161 | 162 | if([response isKindOfClass:[NSHTTPURLResponse class]] == NO) { 163 | // TODO: handle error 164 | completionHandler(NSURLSessionResponseCancel); 165 | return; 166 | } 167 | 168 | strongSelf.httpURLResponse = (NSHTTPURLResponse *)response; 169 | 170 | strongSelf.data = [NSMutableData data]; 171 | 172 | completionHandler(NSURLSessionResponseAllow); 173 | 174 | }); 175 | 176 | } 177 | 178 | - (void)URLSession:(NSURLSession *)session 179 | dataTask:(NSURLSessionDataTask *)dataTask 180 | didReceiveData:(NSData *)data { 181 | 182 | __weak typeof(self) weakSelf = self; 183 | 184 | dispatch_async(dispatch_get_main_queue(), ^{ 185 | 186 | BOOL isStreaming = [[[[dataTask originalRequest] URL] host] rangeOfString:@"stream"].location != NSNotFound; 187 | 188 | __strong typeof(weakSelf) strongSelf = weakSelf; 189 | if(strongSelf == nil) { 190 | return; 191 | } 192 | 193 | if(isStreaming) { 194 | strongSelf.streamBlock(strongSelf, data); 195 | } else { 196 | [strongSelf.data appendData:data]; 197 | } 198 | 199 | }); 200 | } 201 | 202 | #pragma mark NSURLSessionTaskDelegate 203 | 204 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task 205 | didSendBodyData:(int64_t)bytesSent 206 | totalBytesSent:(int64_t)totalBytesSent 207 | totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { 208 | 209 | __weak typeof(self) weakSelf = self; 210 | 211 | dispatch_async(dispatch_get_main_queue(), ^{ 212 | 213 | __strong typeof(weakSelf) strongSelf = weakSelf; 214 | if(strongSelf == nil) { 215 | return; 216 | } 217 | 218 | if(strongSelf.uploadProgressBlock == nil) return; 219 | 220 | // avoid overcommit while posting big images, like 5+ MB 221 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 222 | dispatch_async(queue, ^{ 223 | strongSelf.uploadProgressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); 224 | }); 225 | 226 | }); 227 | } 228 | 229 | - (void)URLSession:(NSURLSession *)session 230 | task:(NSURLSessionTask *)task 231 | didCompleteWithError:(NSError *)error { 232 | 233 | __weak typeof(self) weakSelf = self; 234 | 235 | dispatch_async(dispatch_get_main_queue(), ^{ 236 | 237 | //NSLog(@"-- didCompleteWithError: %@", [error localizedDescription]); 238 | 239 | __strong typeof(weakSelf) strongSelf = weakSelf; 240 | if(strongSelf == nil) return; 241 | 242 | if(error) { 243 | NSURLRequest *request = [strongSelf.task currentRequest]; 244 | NSDictionary *requestHeaders = [request allHTTPHeaderFields]; 245 | NSDictionary *responseHeaders = [strongSelf.httpURLResponse allHeaderFields]; 246 | 247 | strongSelf.errorBlock(strongSelf, requestHeaders, responseHeaders, error); 248 | return; 249 | } 250 | 251 | NSURLRequest *request = [_task currentRequest]; 252 | 253 | if(_data == nil) { 254 | strongSelf.errorBlock(strongSelf, [strongSelf requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], nil); 255 | return; 256 | } 257 | 258 | NSError *error = [NSError st_twitterErrorFromResponseData:_data responseHeaders:[_httpURLResponse allHeaderFields] underlyingError:nil]; 259 | 260 | if(error) { 261 | strongSelf.errorBlock(strongSelf, [strongSelf requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], error); 262 | return; 263 | } 264 | 265 | NSError *jsonError = nil; 266 | id response = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingAllowFragments error:&jsonError]; 267 | 268 | if(response == nil) { 269 | // eg. reverse auth response 270 | // oauth_token=xxx&oauth_token_secret=xxx&user_id=xxx&screen_name=xxx 271 | response = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding]; 272 | } 273 | 274 | if(response) { 275 | strongSelf.completionBlock(strongSelf, [strongSelf requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], response); 276 | } else { 277 | strongSelf.errorBlock(strongSelf, [strongSelf requestHeadersForRequest:request], [_httpURLResponse allHeaderFields], jsonError); 278 | } 279 | 280 | [session finishTasksAndInvalidate]; 281 | }); 282 | } 283 | 284 | @end 285 | --------------------------------------------------------------------------------