Implement the Retweet API. This means checking home_timeline and sending proper retweet messages. Fixes #12556.
On an annoying note, home_timeline (despite saying "Returns the 20 most recent statuses, including retweets, posted by the authenticating user and that user's friends") does not include outgoing retweets. This will either be fixed by Twitter quickly, or not. I'm tired of Twitter's inconsistent and buggy API. So, as such, there's currently no way to remove a retweet done by yourself.
5 // Created by Matt Gemmell on 10/02/2008.
6 // Copyright 2008 Instinctive Code.
9 #import "MGTwitterEngine.h"
10 #import "MGTwitterHTTPURLConnection.h"
12 #import "NSData+Base64.h"
16 #import "MGTwitterStatusesParser.h"
17 #import "MGTwitterUsersParser.h"
18 #import "MGTwitterMessagesParser.h"
19 #import "MGTwitterMiscParser.h"
21 #define TWITTER_DOMAIN @"twitter.com"
22 #define HTTP_POST_METHOD @"POST"
23 #define HTTP_MULTIPART_METHOD @"MULTIPART" //adium
24 #define MULTIPART_FORM_BOUNDARY @"bf5faadd239c17e35f91e6dafe1d2f96" //adium
25 #define MAX_MESSAGE_LENGTH 140 // Twitter recommends tweets of max 140 chars
26 #define MAX_LOCATION_LENGTH 31
28 #define DEFAULT_CLIENT_NAME @"MGTwitterEngine"
29 #define DEFAULT_CLIENT_VERSION @"1.0"
30 #define DEFAULT_CLIENT_URL @"http://mattgemmell.com/source"
31 #define DEFAULT_CLIENT_TOKEN @"mgtwitterengine"
33 #define URL_REQUEST_TIMEOUT 50.0 // Twitter usually fails quickly if it's going to fail at all.
34 #define DEFAULT_TWEET_COUNT 20
37 @interface MGTwitterEngine (PrivateMethods)
40 - (NSDateFormatter *)_HTTPDateFormatter;
41 - (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed;
42 - (NSDate *)_HTTPToDate:(NSString *)httpDate;
43 - (NSString *)_dateToHTTP:(NSDate *)date;
44 - (NSString *)_encodeString:(NSString *)string;
46 // Connection/Request methods
47 - (NSString *)_sendRequestWithMethod:(NSString *)method
49 queryParameters:(NSDictionary *)params
51 requestType:(MGTwitterRequestType)requestType
52 responseType:(MGTwitterResponseType)responseType;
55 - (void)_parseXMLForConnection:(MGTwitterHTTPURLConnection *)connection;
58 - (BOOL) _isValidDelegateForSelector:(SEL)selector;
63 @implementation MGTwitterEngine
66 #pragma mark Constructors
69 + (MGTwitterEngine *)twitterEngineWithDelegate:(NSObject *)theDelegate
71 return [[[MGTwitterEngine alloc] initWithDelegate:theDelegate] autorelease];
75 - (MGTwitterEngine *)initWithDelegate:(NSObject *)newDelegate
77 if ((self = [super init])) {
78 _delegate = newDelegate; // deliberately weak reference
79 _connections = [[NSMutableDictionary alloc] initWithCapacity:0];
80 _clientName = [DEFAULT_CLIENT_NAME retain];
81 _clientVersion = [DEFAULT_CLIENT_VERSION retain];
82 _clientURL = [DEFAULT_CLIENT_URL retain];
83 _clientSourceToken = [DEFAULT_CLIENT_TOKEN retain];
84 _APIDomain = [TWITTER_DOMAIN retain];
85 _secureConnection = YES;
97 [[_connections allValues] makeObjectsPerformSelector:@selector(cancel)];
98 [_connections release];
100 [_accessToken release];
105 [_clientName release];
106 [_clientVersion release];
107 [_clientURL release];
108 [_clientSourceToken release];
109 [_APIDomain release];
115 #pragma mark Configuration and Accessors
118 + (NSString *)version
120 // 1.0.0 = 22 Feb 2008
121 // 1.0.1 = 26 Feb 2008
122 // 1.0.2 = 04 Mar 2008
123 // 1.0.3 = 04 Mar 2008
124 // 1.0.4 = 11 Apr 2008
125 // 1.0.5 = 06 Jun 2008
126 // 1.0.6 = 05 Aug 2008
127 // 1.0.7 = 28 Sep 2008
128 // 1.0.8 = 01 Oct 2008
133 - (NSString *)username
135 return [[_username retain] autorelease];
139 - (NSString *)password
141 return [[_password retain] autorelease];
145 - (void)setUsername:(NSString *)newUsername password:(NSString *)newPassword
147 // Set new credentials.
149 _username = [newUsername retain];
151 _password = [newPassword retain];
153 if ([self clearsCookies]) {
154 // Remove all cookies for twitter, to ensure next connection uses new credentials.
155 NSString *urlString = [NSString stringWithFormat:@"%@://%@",
156 (_secureConnection) ? @"https" : @"http",
158 NSURL *url = [NSURL URLWithString:urlString];
160 NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
161 NSEnumerator *enumerator = [[cookieStorage cookiesForURL:url] objectEnumerator];
162 NSHTTPCookie *cookie = nil;
163 while ((cookie = [enumerator nextObject])) {
164 [cookieStorage deleteCookie:cookie];
170 - (NSString *)clientName
172 return [[_clientName retain] autorelease];
176 - (NSString *)clientVersion
178 return [[_clientVersion retain] autorelease];
182 - (NSString *)clientURL
184 return [[_clientURL retain] autorelease];
188 - (NSString *)clientSourceToken
190 return [[_clientSourceToken retain] autorelease];
194 - (void)setClientName:(NSString *)name version:(NSString *)version URL:(NSString *)url token:(NSString *)token;
196 [_clientName release];
197 _clientName = [name retain];
198 [_clientVersion release];
199 _clientVersion = [version retain];
200 [_clientURL release];
201 _clientURL = [url retain];
202 [_clientSourceToken release];
203 _clientSourceToken = [token retain];
207 - (NSString *)APIDomain
209 return [[_APIDomain retain] autorelease];
213 - (void)setAPIDomain:(NSString *)domain
215 [_APIDomain release];
216 if (!domain || [domain length] == 0) {
217 _APIDomain = [TWITTER_DOMAIN retain];
219 _APIDomain = [domain retain];
224 - (BOOL)usesSecureConnection
226 return _secureConnection;
230 - (void)setUsesSecureConnection:(BOOL)flag
232 _secureConnection = flag;
236 - (BOOL)clearsCookies
238 return _clearsCookies;
242 - (void)setClearsCookies:(BOOL)flag
244 _clearsCookies = flag;
248 #pragma mark Connection methods
251 - (int)numberOfConnections
253 return [_connections count];
257 - (NSArray *)connectionIdentifiers
259 return [_connections allKeys];
263 - (void)closeConnection:(NSString *)identifier
265 MGTwitterHTTPURLConnection *connection = [_connections objectForKey:identifier];
268 [_connections removeObjectForKey:identifier];
273 - (void)closeAllConnections
275 [[_connections allValues] makeObjectsPerformSelector:@selector(cancel)];
276 [_connections removeAllObjects];
280 #pragma mark Utility methods
283 - (NSDateFormatter *)_HTTPDateFormatter
285 // Returns a formatter for dates in HTTP format (i.e. RFC 822, updated by RFC 1123).
286 // e.g. "Sun, 06 Nov 1994 08:49:37 GMT"
287 NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
288 //[dateFormatter setDateFormat:@"%a, %d %b %Y %H:%M:%S GMT"]; // won't work with -init, which uses new (unicode) format behaviour.
289 [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
290 [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss GMT"];
291 return dateFormatter;
295 - (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed
297 // Append base if specified.
298 NSMutableString *str = [NSMutableString stringWithCapacity:0];
300 [str appendString:base];
303 // Append each name-value pair.
306 NSArray *names = [params allKeys];
307 for (i = 0; i < [names count]; i++) {
308 if (i == 0 && prefixed) {
309 [str appendString:@"?"];
311 [str appendString:@"&"];
313 NSString *name = [names objectAtIndex:i];
314 [str appendString:[NSString stringWithFormat:@"%@=%@",
315 name, [self _encodeString:[params objectForKey:name]]]];
323 - (NSDate *)_HTTPToDate:(NSString *)httpDate
325 NSDateFormatter *dateFormatter = [self _HTTPDateFormatter];
326 return [dateFormatter dateFromString:httpDate];
330 - (NSString *)_dateToHTTP:(NSDate *)date
332 NSDateFormatter *dateFormatter = [self _HTTPDateFormatter];
333 return [dateFormatter stringFromDate:date];
337 - (NSString *)_encodeString:(NSString *)string
339 NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
342 (CFStringRef)@";/?:@&=$+{}<>,",
343 kCFStringEncodingUTF8);
344 return [result autorelease];
348 - (NSString *)getImageAtURL:(NSString *)urlString
350 // This is a method implemented for the convenience of the client,
351 // allowing asynchronous downloading of users' Twitter profile images.
352 NSString *encodedUrlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
353 NSURL *url = [NSURL URLWithString:encodedUrlString];
358 // Construct an NSMutableURLRequest for the URL and set appropriate request method.
359 NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url
360 cachePolicy:NSURLRequestReloadIgnoringCacheData
361 timeoutInterval:URL_REQUEST_TIMEOUT];
363 // Create a connection using this request, with the default timeout and caching policy,
364 // and appropriate Twitter request and response types for parsing and error reporting.
365 MGTwitterHTTPURLConnection *connection;
366 connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest
368 requestType:MGTwitterImageRequest
369 responseType:MGTwitterImage];
374 [_connections setObject:connection forKey:[connection identifier]];
375 [connection release];
378 return [connection identifier];
382 #pragma mark Request sending methods
384 #define SET_AUTHORIZATION_IN_HEADER 1
386 /* See Adium Additions/Changes below—oauth support */
388 - (NSString *)_sendRequestWithMethod:(NSString *)method
389 path:(NSString *)path
390 queryParameters:(NSDictionary *)params
392 requestType:(MGTwitterRequestType)requestType
393 responseType:(MGTwitterResponseType)responseType
395 // Construct appropriate URL string.
396 NSString *fullPath = path;
398 fullPath = [self _queryStringWithBase:fullPath parameters:params prefixed:YES];
401 #if SET_AUTHORIZATION_IN_HEADER
402 NSString *urlString = [NSString stringWithFormat:@"%@://%@/%@",
403 (_secureConnection) ? @"https" : @"http",
404 _APIDomain, fullPath];
406 NSString *urlString = [NSString stringWithFormat:@"%@://%@:%@@%@/%@",
407 (_secureConnection) ? @"https" : @"http",
408 [self _encodeString:_username], [self _encodeString:_password],
409 _APIDomain, fullPath];
412 NSURL *finalURL = [NSURL URLWithString:urlString];
417 // Construct an NSMutableURLRequest for the URL and set appropriate request method.
418 NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:finalURL
419 cachePolicy:NSURLRequestReloadIgnoringCacheData
420 timeoutInterval:URL_REQUEST_TIMEOUT];
421 if(method && [method isEqualToString:HTTP_MULTIPART_METHOD]) {
422 method = HTTP_POST_METHOD;
423 [theRequest setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", MULTIPART_FORM_BOUNDARY] forHTTPHeaderField:@"Content-type"];
427 [theRequest setHTTPMethod:method];
430 [theRequest setHTTPShouldHandleCookies:NO];
432 // Set headers for client information, for tracking purposes at Twitter.
433 [theRequest setValue:_clientName forHTTPHeaderField:@"X-Twitter-Client"];
434 [theRequest setValue:_clientVersion forHTTPHeaderField:@"X-Twitter-Client-Version"];
435 [theRequest setValue:_clientURL forHTTPHeaderField:@"X-Twitter-Client-URL"];
437 #if SET_AUTHORIZATION_IN_HEADER
438 if ([self username] && [self password]) {
439 // Set header for HTTP Basic authentication explicitly, to avoid problems with proxies and other intermediaries
440 NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]];
441 NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
442 NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
443 [theRequest setValue:authValue forHTTPHeaderField:@"Authorization"];
447 // Set the request body if this is a POST request.
448 BOOL isPOST = (method && [method isEqualToString:HTTP_POST_METHOD]);
451 // Set request body, if specified (hopefully so), with 'source' parameter if appropriate.
452 if([body isKindOfClass:[NSString class]]) {
453 NSString *finalBody = @"";
455 finalBody = [finalBody stringByAppendingString:body];
457 if (_clientSourceToken) {
458 finalBody = [finalBody stringByAppendingString:[NSString stringWithFormat:@"%@source=%@",
459 (body) ? @"&" : @"?" ,
460 _clientSourceToken]];
464 [theRequest setHTTPBody:[finalBody dataUsingEncoding:NSUTF8StringEncoding]];
466 } else if ([body isKindOfClass:[NSData class]]) {
467 [theRequest setHTTPBody:body];
472 // Create a connection using this request, with the default timeout and caching policy,
473 // and appropriate Twitter request and response types for parsing and error reporting.
474 MGTwitterHTTPURLConnection *connection;
475 connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest
477 requestType:requestType
478 responseType:responseType];
483 [_connections setObject:connection forKey:[connection identifier]];
484 [connection release];
487 return [connection identifier];
491 #pragma mark Parsing methods
494 - (void)_parseXMLForConnection:(MGTwitterHTTPURLConnection *)connection
496 NSString *identifier = [[[connection identifier] copy] autorelease];
497 NSData *xmlData = [[[connection data] copy] autorelease];
498 MGTwitterRequestType requestType = [connection requestType];
499 MGTwitterResponseType responseType = [connection responseType];
502 NSURL *URL = [connection URL];
504 switch (responseType) {
505 case MGTwitterStatuses:
506 case MGTwitterStatus:
507 [MGTwitterStatusesLibXMLParser parserWithXML:xmlData delegate:self
508 connectionIdentifier:identifier requestType:requestType
509 responseType:responseType URL:URL];
513 [MGTwitterUsersLibXMLParser parserWithXML:xmlData delegate:self
514 connectionIdentifier:identifier requestType:requestType
515 responseType:responseType URL:URL];
517 case MGTwitterDirectMessages:
518 case MGTwitterDirectMessage:
519 [MGTwitterMessagesLibXMLParser parserWithXML:xmlData delegate:self
520 connectionIdentifier:identifier requestType:requestType
521 responseType:responseType URL:URL];
523 case MGTwitterMiscellaneous:
524 [MGTwitterMiscLibXMLParser parserWithXML:xmlData delegate:self
525 connectionIdentifier:identifier requestType:requestType
526 responseType:responseType URL:URL];
532 // Determine which type of parser to use.
533 switch (responseType) {
534 case MGTwitterStatuses:
535 case MGTwitterStatus:
536 [MGTwitterStatusesParser parserWithXML:xmlData delegate:self
537 connectionIdentifier:identifier requestType:requestType
538 responseType:responseType];
542 [MGTwitterUsersParser parserWithXML:xmlData delegate:self
543 connectionIdentifier:identifier requestType:requestType
544 responseType:responseType];
546 case MGTwitterDirectMessages:
547 case MGTwitterDirectMessage:
548 [MGTwitterMessagesParser parserWithXML:xmlData delegate:self
549 connectionIdentifier:identifier requestType:requestType
550 responseType:responseType];
552 case MGTwitterMiscellaneous:
553 [MGTwitterMiscParser parserWithXML:xmlData delegate:self
554 connectionIdentifier:identifier requestType:requestType
555 responseType:responseType];
563 #pragma mark Delegate methods
565 - (BOOL) _isValidDelegateForSelector:(SEL)selector
567 return ((_delegate != nil) && [_delegate respondsToSelector:selector]);
570 #pragma mark MGTwitterParserDelegate methods
573 - (void)parsingSucceededForRequest:(NSString *)identifier
574 ofResponseType:(MGTwitterResponseType)responseType
575 withParsedObjects:(NSArray *)parsedObjects
577 // Forward appropriate message to _delegate, depending on responseType.
578 switch (responseType) {
579 case MGTwitterStatuses:
580 case MGTwitterStatus:
581 if ([self _isValidDelegateForSelector:@selector(statusesReceived:forRequest:)])
582 [_delegate statusesReceived:parsedObjects forRequest:identifier];
586 if ([self _isValidDelegateForSelector:@selector(userInfoReceived:forRequest:)])
587 [_delegate userInfoReceived:parsedObjects forRequest:identifier];
589 case MGTwitterDirectMessages:
590 case MGTwitterDirectMessage:
591 if ([self _isValidDelegateForSelector:@selector(directMessagesReceived:forRequest:)])
592 [_delegate directMessagesReceived:parsedObjects forRequest:identifier];
594 case MGTwitterMiscellaneous:
595 if ([self _isValidDelegateForSelector:@selector(miscInfoReceived:forRequest:)])
596 [_delegate miscInfoReceived:parsedObjects forRequest:identifier];
604 - (void)parsingFailedForRequest:(NSString *)requestIdentifier
605 ofResponseType:(MGTwitterResponseType)responseType
606 withError:(NSError *)error
608 if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)])
609 [_delegate requestFailed:requestIdentifier withError:error];
613 #pragma mark NSURLConnection delegate methods
616 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
618 if ([challenge previousFailureCount] == 0 && ![challenge proposedCredential] && !_useOAuth && _password && _username) {
619 NSURLCredential *credential = [NSURLCredential credentialWithUser:_username password:_password
620 persistence:NSURLCredentialPersistenceForSession];
621 [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
623 [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
628 - (void)connection:(MGTwitterHTTPURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
630 // This method is called when the server has determined that it has enough information to create the NSURLResponse.
631 // it can be called multiple times, for example in the case of a redirect, so each time we reset the data.
632 [connection resetDataLength];
634 // Get response code.
635 NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response;
636 int statusCode = [resp statusCode];
638 if (statusCode >= 400) {
639 // Assume failure, and report to delegate.
640 NSError *error = [NSError errorWithDomain:@"HTTP" code:statusCode userInfo:nil];
641 if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)])
642 [_delegate requestFailed:[connection identifier] withError:error];
644 // Destroy the connection.
646 [_connections removeObjectForKey:[connection identifier]];
648 } else if (statusCode == 304 || [connection responseType] == MGTwitterGeneric) {
649 // Not modified, or generic success.
650 if ([self _isValidDelegateForSelector:@selector(requestSucceeded:)])
651 [_delegate requestSucceeded:[connection identifier]];
652 if (statusCode == 304) {
653 [self parsingSucceededForRequest:[connection identifier]
654 ofResponseType:[connection responseType]
655 withParsedObjects:[NSArray array]];
658 // Destroy the connection.
660 [_connections removeObjectForKey:[connection identifier]];
664 // Display headers for debugging.
665 NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response;
666 NSLog(@"(%d) [%@]:\r%@",
668 [NSHTTPURLResponse localizedStringForStatusCode:[resp statusCode]],
669 [resp allHeaderFields]);
674 - (void)connection:(MGTwitterHTTPURLConnection *)connection didReceiveData:(NSData *)data
676 // Append the new data to the receivedData.
677 [connection appendData:data];
681 - (void)connection:(MGTwitterHTTPURLConnection *)connection didFailWithError:(NSError *)error
684 if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)])
685 [_delegate requestFailed:[connection identifier] withError:error];
687 // Release the connection.
688 [_connections removeObjectForKey:[connection identifier]];
692 - (void)connectionDidFinishLoading:(MGTwitterHTTPURLConnection *)connection
695 if ([self _isValidDelegateForSelector:@selector(requestSucceeded:)])
696 [_delegate requestSucceeded:[connection identifier]];
698 NSData *receivedData = [connection data];
701 // Dump data as string for debugging.
702 NSString *dataString = [NSString stringWithUTF8String:[receivedData bytes]];
703 NSLog(@"Succeeded! Received %d bytes of data:\r\r%@", [receivedData length], dataString);
707 // Dump XML to file for debugging.
708 NSString *dataString = [NSString stringWithUTF8String:[receivedData bytes]];
709 [dataString writeToFile:[@"~/Desktop/twitter_messages.xml" stringByExpandingTildeInPath]
710 atomically:NO encoding:NSUnicodeStringEncoding error:NULL];
713 if ([connection responseType] == MGTwitterImage) {
714 // Create image from data.
716 UIImage *image = [[[UIImage alloc] initWithData:[connection data]] autorelease];
718 NSImage *image = [[[NSImage alloc] initWithData:[connection data]] autorelease];
722 if ([self _isValidDelegateForSelector:@selector(imageReceived:forRequest:)])
723 [_delegate imageReceived:image forRequest:[connection identifier]];
725 // Parse XML appropriately.
726 [self _parseXMLForConnection:connection];
730 // Release the connection.
731 [_connections removeObjectForKey:[connection identifier]];
736 #pragma mark Twitter API methods
740 #pragma mark Account methods
742 - (NSString *)endUserSession
744 NSString *path = @"account/end_session"; // deliberately no format specified
746 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
747 requestType:MGTwitterAccountRequest
748 responseType:MGTwitterGeneric];
752 - (NSString *)enableUpdatesFor:(NSString *)username
758 NSString *path = [NSString stringWithFormat:@"friendships/create/%@.xml", username];
760 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
761 requestType:MGTwitterAccountRequest
762 responseType:MGTwitterUser];
766 - (NSString *)disableUpdatesFor:(NSString *)username
768 // i.e. no longer follow
772 NSString *path = [NSString stringWithFormat:@"friendships/destroy/%@.xml", username];
774 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
775 requestType:MGTwitterAccountRequest
776 responseType:MGTwitterUser];
780 - (NSString *)isUser:(NSString *)username1 receivingUpdatesFor:(NSString *)username2
782 if (!username1 || !username2) {
785 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
786 [params setObject:username1 forKey:@"user_a"];
787 [params setObject:username2 forKey:@"user_b"];
789 NSString *path = @"friendships/exists.xml";
791 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
792 requestType:MGTwitterAccountRequest
793 responseType:MGTwitterMiscellaneous];
797 - (NSString *)enableNotificationsFor:(NSString *)username
802 NSString *path = [NSString stringWithFormat:@"notifications/follow/%@.xml", username];
804 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
805 requestType:MGTwitterAccountRequest
806 responseType:MGTwitterUser];
810 - (NSString *)disableNotificationsFor:(NSString *)username
815 NSString *path = [NSString stringWithFormat:@"notifications/leave/%@.xml", username];
817 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
818 requestType:MGTwitterAccountRequest
819 responseType:MGTwitterUser];
823 - (NSString *)getRateLimitStatus
825 NSString *path = @"account/rate_limit_status.xml";
827 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
828 requestType:MGTwitterAccountRequest
829 responseType:MGTwitterMiscellaneous];
833 - (NSString *)setLocation:(NSString *)location
839 NSString *path = @"account/update_location.xml";
841 NSString *trimmedText = location;
842 if ([trimmedText length] > MAX_LOCATION_LENGTH) {
843 trimmedText = [trimmedText substringToIndex:MAX_LOCATION_LENGTH];
846 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
847 [params setObject:trimmedText forKey:@"location"];
848 NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
850 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path
851 queryParameters:nil body:body
852 requestType:MGTwitterAccountRequest
853 responseType:MGTwitterGeneric];
857 - (NSString *)setNotificationsDeliveryMethod:(NSString *)method
859 NSString *deliveryMethod = method;
860 if (!method || [method length] == 0) {
861 deliveryMethod = @"none";
864 NSString *path = @"account/update_delivery_device.xml";
866 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
867 if (deliveryMethod) {
868 [params setObject:deliveryMethod forKey:@"device"];
871 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
872 requestType:MGTwitterAccountRequest
873 responseType:MGTwitterGeneric];
877 - (NSString *)block:(NSString *)username
883 NSString *path = [NSString stringWithFormat:@"blocks/create/%@.xml", username];
885 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
886 requestType:MGTwitterAccountRequest
887 responseType:MGTwitterUser];
891 - (NSString *)unblock:(NSString *)username
897 NSString *path = [NSString stringWithFormat:@"blocks/destroy/%@.xml", username];
899 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
900 requestType:MGTwitterAccountRequest
901 responseType:MGTwitterUser];
905 - (NSString *)testService
907 NSString *path = @"help/test.xml";
909 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
910 requestType:MGTwitterAccountRequest
911 responseType:MGTwitterGeneric];
915 - (NSString *)getDowntimeSchedule
917 NSString *path = @"help/downtime_schedule.xml";
919 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
920 requestType:MGTwitterAccountRequest
921 responseType:MGTwitterMiscellaneous];
925 #pragma mark Retrieving updates
928 - (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum
930 // Included for backwards-compatibility.
931 return [self getFollowedTimelineFor:username since:date startingAtPage:pageNum count:0]; // zero means default
935 - (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)count
937 NSString *path = @"statuses/home_timeline.xml";
939 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
941 [params setObject:[self _dateToHTTP:date] forKey:@"since"];
944 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
947 path = [NSString stringWithFormat:@"statuses/home_timeline/%@.xml", username];
949 int tweetCount = DEFAULT_TWEET_COUNT;
953 [params setObject:[NSString stringWithFormat:@"%d", tweetCount] forKey:@"count"];
955 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
956 requestType:MGTwitterStatusesRequest
957 responseType:MGTwitterStatuses];
961 - (NSString *)getFollowedTimelineFor:(NSString *)username sinceID:(NSString *)updateID startingAtPage:(int)pageNum count:(int)count
963 NSString *path = @"statuses/home_timeline.xml";
965 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
967 [params setObject:updateID forKey:@"since_id"];
970 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
973 path = [NSString stringWithFormat:@"statuses/home_timeline/%@.xml", username];
975 int tweetCount = DEFAULT_TWEET_COUNT;
979 [params setObject:[NSString stringWithFormat:@"%d", tweetCount] forKey:@"count"];
981 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
982 requestType:MGTwitterStatusesRequest
983 responseType:MGTwitterStatuses];
987 - (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date count:(int)numUpdates
989 // Included for backwards-compatibility.
990 return [self getUserTimelineFor:username since:date startingAtPage:0 count:numUpdates];
994 - (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)numUpdates
996 NSString *path = @"statuses/user_timeline.xml";
998 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1000 [params setObject:[self _dateToHTTP:date] forKey:@"since"];
1003 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1005 if (numUpdates > 0) {
1006 [params setObject:[NSString stringWithFormat:@"%d", numUpdates] forKey:@"count"];
1009 path = [NSString stringWithFormat:@"statuses/user_timeline/%@.xml", username];
1012 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1013 requestType:MGTwitterStatusesRequest
1014 responseType:MGTwitterStatuses];
1018 - (NSString *)getUserTimelineFor:(NSString *)username sinceID:(NSString *)updateID startingAtPage:(int)pageNum count:(int)numUpdates
1020 NSString *path = @"statuses/user_timeline.xml";
1022 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1024 [params setObject:updateID forKey:@"since_id"];
1027 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1029 if (numUpdates > 0) {
1030 [params setObject:[NSString stringWithFormat:@"%d", numUpdates] forKey:@"count"];
1033 path = [NSString stringWithFormat:@"statuses/user_timeline/%@.xml", username];
1036 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1037 requestType:MGTwitterStatusesRequest
1038 responseType:MGTwitterStatuses];
1042 - (NSString *)getUserUpdatesArchiveStartingAtPage:(int)pageNum
1044 NSString *path = @"account/archive.xml";
1046 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1048 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1051 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1052 requestType:MGTwitterStatusesRequest
1053 responseType:MGTwitterStatuses];
1057 - (NSString *)getPublicTimelineSinceID:(NSString *)updateID
1059 NSString *path = @"statuses/public_timeline.xml";
1061 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1063 [params setObject:updateID forKey:@"since_id"];
1066 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1067 requestType:MGTwitterStatusesRequest
1068 responseType:MGTwitterStatuses];
1071 - (NSString *)getRepliesStartingAtPage:(int)pageNum
1073 NSString *path = @"statuses/replies.xml";
1075 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1077 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1080 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1081 requestType:MGTwitterRepliesRequest
1082 responseType:MGTwitterStatuses];
1086 - (NSString *)getFavoriteUpdatesFor:(NSString *)username startingAtPage:(int)pageNum
1088 NSString *path = @"favorites.xml";
1090 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1092 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1095 path = [NSString stringWithFormat:@"favorites/%@.xml", username];
1098 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1099 requestType:MGTwitterStatusesRequest
1100 responseType:MGTwitterStatuses];
1104 - (NSString *)getUpdate:(NSString *)updateID
1106 NSString *path = [NSString stringWithFormat:@"statuses/show/%@.xml", updateID];
1108 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
1109 requestType:MGTwitterStatusesRequest
1110 responseType:MGTwitterStatus];
1114 #pragma mark Retrieving direct messages
1117 - (NSString *)getDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum
1119 NSString *path = @"direct_messages.xml";
1121 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1123 [params setObject:[self _dateToHTTP:date] forKey:@"since"];
1126 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1129 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1130 requestType:MGTwitterDirectMessagesRequest
1131 responseType:MGTwitterDirectMessages];
1135 - (NSString *)getDirectMessagesSinceID:(NSString *)updateID startingAtPage:(int)pageNum
1137 NSString *path = @"direct_messages.xml";
1139 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1141 [params setObject:updateID forKey:@"since_id"];
1144 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1147 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1148 requestType:MGTwitterDirectMessagesRequest
1149 responseType:MGTwitterDirectMessages];
1153 - (NSString *)getSentDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum
1155 NSString *path = @"direct_messages/sent.xml";
1157 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1159 [params setObject:[self _dateToHTTP:date] forKey:@"since"];
1162 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1165 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1166 requestType:MGTwitterDirectMessagesRequest
1167 responseType:MGTwitterDirectMessages];
1171 - (NSString *)getSentDirectMessagesSinceID:(NSString *)updateID startingAtPage:(int)pageNum
1173 NSString *path = @"direct_messages/sent.xml";
1175 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1177 [params setObject:updateID forKey:@"since_id"];
1180 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1183 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1184 requestType:MGTwitterDirectMessagesRequest
1185 responseType:MGTwitterDirectMessages];
1189 #pragma mark Retrieving user information
1192 - (NSString *)getUserInformationFor:(NSString *)username
1197 NSString *path = [NSString stringWithFormat:@"users/show/%@.xml", username];
1199 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
1200 requestType:MGTwitterUserInfoRequest
1201 responseType:MGTwitterUser];
1205 - (NSString *)getUserInformationForEmail:(NSString *)email
1207 NSString *path = @"users/show.xml";
1208 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1210 [params setObject:email forKey:@"email"];
1215 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1216 requestType:MGTwitterUserInfoRequest
1217 responseType:MGTwitterUser];
1221 - (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtPage:(int)pageNum
1223 NSString *path = @"statuses/friends.xml";
1225 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1227 path = [NSString stringWithFormat:@"statuses/friends/%@.xml", username];
1230 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1233 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1234 requestType:MGTwitterUserInfoRequest
1235 responseType:MGTwitterUsers];
1239 - (NSString *)getFollowersIncludingCurrentStatus:(BOOL)flag
1241 NSString *path = @"statuses/followers.xml";
1243 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1245 [params setObject:@"true" forKey:@"lite"]; // slightly bizarre, but correct.
1248 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1249 requestType:MGTwitterUserInfoRequest
1250 responseType:MGTwitterUsers];
1254 - (NSString *)getFeaturedUsers
1256 NSString *path = @"statuses/featured.xml";
1258 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
1259 requestType:MGTwitterUserInfoRequest
1260 responseType:MGTwitterUsers];
1264 #pragma mark Sending and editing updates
1267 - (NSString *)sendUpdate:(NSString *)status
1269 return [self sendUpdate:status inReplyTo:0];
1273 - (NSString *)sendUpdate:(NSString *)status inReplyTo:(NSString *)updateID
1279 NSString *path = @"statuses/update.xml";
1281 NSString *trimmedText = status;
1282 if ([trimmedText length] > MAX_MESSAGE_LENGTH) {
1283 trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH];
1286 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1287 [params setObject:trimmedText forKey:@"status"];
1289 [params setObject:updateID forKey:@"in_reply_to_status_id"];
1291 NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
1293 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path
1294 queryParameters:nil body:body
1295 requestType:MGTwitterStatusSend
1296 responseType:MGTwitterStatus];
1300 - (NSString *)deleteUpdate:(NSString *)updateID
1302 NSString *path = [NSString stringWithFormat:@"statuses/destroy/%@.xml", updateID];
1304 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
1305 requestType:MGTwitterAccountRequest
1306 responseType:MGTwitterGeneric];
1310 - (NSString *)markUpdate:(NSString *)updateID asFavorite:(BOOL)flag
1312 NSString *path = [NSString stringWithFormat:@"favorites/%@/%@.xml",
1313 (flag) ? @"create" : @"destroy" ,
1316 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
1317 requestType:MGTwitterAccountRequest
1318 responseType:MGTwitterStatus];
1321 - (NSString *)retweetUpdate:(NSString *)updateID
1323 NSString *path = [NSString stringWithFormat:@"statuses/retweet/%@.xml", updateID];
1325 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
1326 requestType:MGTwitterAccountRequest
1327 responseType:MGTwitterStatus];
1330 #pragma mark Sending and editing direct messages
1333 - (NSString *)sendDirectMessage:(NSString *)message to:(NSString *)username
1335 if (!message || !username) {
1339 NSString *path = @"direct_messages/new.xml";
1341 NSString *trimmedText = message;
1342 if ([trimmedText length] > MAX_MESSAGE_LENGTH) {
1343 trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH];
1346 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1347 [params setObject:trimmedText forKey:@"text"];
1348 [params setObject:username forKey:@"user"];
1349 NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
1351 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path
1352 queryParameters:nil body:body
1353 requestType:MGTwitterDirectMessageSend
1354 responseType:MGTwitterDirectMessage];
1358 - (NSString *)deleteDirectMessage:(NSString *)updateID
1360 NSString *path = [NSString stringWithFormat:@"direct_messages/destroy/%@.xml", updateID];
1362 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil
1363 requestType:MGTwitterAccountRequest
1364 responseType:MGTwitterGeneric];
1367 #pragma mark Adium Additions/Changes
1369 #define MAX_NAME_LENGTH 20
1370 #define MAX_EMAIL_LENGTH 40
1371 #define MAX_URL_LENGTH 100
1372 #define MAX_DESCRIPTION_LENGTH 160
1374 - (NSString *)checkUserCredentials
1376 NSString *path = @"account/verify_credentials.xml";
1378 return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil
1379 requestType:MGTwitterAccountRequest
1380 responseType:MGTwitterUser];
1384 - (NSString *)getRepliesSinceID:(NSString *)updateID startingAtPage:(int)pageNum
1386 NSString *path = @"statuses/replies.xml";
1388 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1390 [params setObject:updateID forKey:@"since_id"];
1393 [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
1396 return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil
1397 requestType:MGTwitterRepliesRequest
1398 responseType:MGTwitterStatuses];
1401 - (NSString *)updateProfileName:(NSString *)name
1402 email:(NSString *)email
1404 location:(NSString *)location
1405 description:(NSString *)description
1407 if (!name && !email && !url && !location && !description) {
1411 NSString *path = @"account/update_profile.xml";
1413 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1416 if(name.length > MAX_NAME_LENGTH) {
1417 name = [name substringToIndex:MAX_NAME_LENGTH];
1420 [params setObject:name forKey:@"name"];
1424 if(email.length > MAX_EMAIL_LENGTH) {
1425 email = [email substringToIndex:MAX_EMAIL_LENGTH];
1428 [params setObject:email forKey:@"email"];
1432 if(url.length > MAX_URL_LENGTH) {
1433 url = [url substringToIndex:MAX_URL_LENGTH];
1436 [params setObject:url forKey:@"url"];
1440 if(location.length > MAX_LOCATION_LENGTH) {
1441 location = [location substringToIndex:MAX_LOCATION_LENGTH];
1444 [params setObject:location forKey:@"location"];
1448 if(description.length > MAX_DESCRIPTION_LENGTH) {
1449 description = [description substringToIndex:MAX_DESCRIPTION_LENGTH];
1452 [params setObject:description forKey:@"description"];
1455 NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
1457 return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path
1458 queryParameters:nil body:body
1459 requestType:MGTwitterAccountRequest
1460 responseType:MGTwitterUser];
1463 - (NSString *)updateProfileImage:(NSData *)profileImage
1465 if (!profileImage || _useOAuth) {
1469 NSString *path = @"account/update_profile_image.xml";
1471 NSMutableData *body = [NSMutableData data];
1472 NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
1474 NSImage *image = [[[NSImage alloc] initWithData:profileImage] autorelease];
1476 NSBitmapImageRep *bitmapImageRep = nil;
1477 for(NSImageRep *imageRep in image.representations) {
1478 if([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
1479 bitmapImageRep = (NSBitmapImageRep *)imageRep;
1483 if(!bitmapImageRep) {
1487 [body appendData:[[NSString stringWithFormat:@"--%@\r\n", MULTIPART_FORM_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]];
1488 [body appendData:[@"Content-Disposition: form-data; name=\"image\"; filename=\"adium_icon.png\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
1489 [body appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
1490 [body appendData:[bitmapImageRep representationUsingType:NSPNGFileType properties:nil]];
1491 [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", MULTIPART_FORM_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]];
1493 return [self _sendRequestWithMethod:HTTP_MULTIPART_METHOD path:path
1494 queryParameters:params body:body
1495 requestType:MGTwitterAccountRequest
1496 responseType:MGTwitterUser];
1499 #pragma mark Adium OAuth Changes
1501 - (NSString *)_sendRequestWithMethod:(NSString *)method
1502 path:(NSString *)path
1503 queryParameters:(NSDictionary *)params
1505 requestType:(MGTwitterRequestType)requestType
1506 responseType:(MGTwitterResponseType)responseType
1508 // Construct appropriate URL string.
1509 NSString *fullPath = path;
1511 fullPath = [self _queryStringWithBase:fullPath parameters:params prefixed:YES];
1514 NSString *urlString = nil;
1517 #if SET_AUTHORIZATION_IN_HEADER
1518 urlString = [NSString stringWithFormat:@"%@://%@/%@",
1519 (_secureConnection) ? @"https" : @"http",
1520 _APIDomain, fullPath];
1522 urlString = [NSString stringWithFormat:@"%@://%@:%@@%@/%@",
1523 (_secureConnection) ? @"https" : @"http",
1524 [self _encodeString:_username], [self _encodeString:_password],
1525 _APIDomain, fullPath];
1528 urlString = [NSString stringWithFormat:@"%@://%@/%@",
1529 (_secureConnection) ? @"https" : @"http",
1530 _APIDomain, fullPath];
1533 NSURL *finalURL = [NSURL URLWithString:urlString];
1538 NSMutableURLRequest *theRequest = nil;
1541 if (!_consumer || !_accessToken) {
1542 NSLog(@"No consumer or access token, fail.");
1546 theRequest = [[[OAMutableURLRequest alloc] initWithURL:finalURL
1550 signatureProvider:nil] autorelease];
1552 // Construct an NSMutableURLRequest for the URL and set appropriate request method.
1553 theRequest = [NSMutableURLRequest requestWithURL:finalURL
1554 cachePolicy:NSURLRequestReloadIgnoringCacheData
1555 timeoutInterval:URL_REQUEST_TIMEOUT];
1558 if(method && [method isEqualToString:HTTP_MULTIPART_METHOD]) {
1559 method = HTTP_POST_METHOD;
1560 [theRequest setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", MULTIPART_FORM_BOUNDARY] forHTTPHeaderField:@"Content-type"];
1564 [theRequest setHTTPMethod:method];
1567 [theRequest setHTTPShouldHandleCookies:NO];
1569 // Set headers for client information, for tracking purposes at Twitter.
1570 [theRequest setValue:_clientName forHTTPHeaderField:@"X-Twitter-Client"];
1571 [theRequest setValue:_clientVersion forHTTPHeaderField:@"X-Twitter-Client-Version"];
1572 [theRequest setValue:_clientURL forHTTPHeaderField:@"X-Twitter-Client-URL"];
1574 #if SET_AUTHORIZATION_IN_HEADER
1575 if (_useOAuth && [self username] && [self password]) {
1576 // Set header for HTTP Basic authentication explicitly, to avoid problems with proxies and other intermediaries
1577 NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]];
1578 NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
1579 NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
1580 [theRequest setValue:authValue forHTTPHeaderField:@"Authorization"];
1584 // Set the request body if this is a POST request.
1585 BOOL isPOST = (method && [method isEqualToString:HTTP_POST_METHOD]);
1588 // Set request body, if specified (hopefully so), with 'source' parameter if appropriate.
1589 if([body isKindOfClass:[NSString class]]) {
1590 NSString *finalBody = @"";
1592 finalBody = [finalBody stringByAppendingString:body];
1594 if (_clientSourceToken) {
1595 finalBody = [finalBody stringByAppendingString:[NSString stringWithFormat:@"%@source=%@",
1596 (body) ? @"&" : @"?" ,
1597 _clientSourceToken]];
1601 [theRequest setHTTPBody:[finalBody dataUsingEncoding:NSUTF8StringEncoding]];
1603 } else if ([body isKindOfClass:[NSData class]]) {
1604 [theRequest setHTTPBody:body];
1609 [(OAMutableURLRequest *)theRequest prepare];
1612 // Create a connection using this request, with the default timeout and caching policy,
1613 // and appropriate Twitter request and response types for parsing and error reporting.
1614 MGTwitterHTTPURLConnection *connection;
1615 connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest
1617 requestType:requestType
1618 responseType:responseType];
1623 [_connections setObject:connection forKey:[connection identifier]];
1624 [connection release];
1627 return [connection identifier];
1630 @synthesize consumer = _consumer, accessToken = _accessToken, useOAuth = _useOAuth;