Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.m
author Zachary West <zacw@adium.im>
Thu Nov 19 21:12:23 2009 -0500 (2009-11-19)
changeset 2768 85857106a45e
parent 2501 6a7ff9587023
child 2774 6bcb9802d1cf
permissions -rw-r--r--
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.
zacw@776
     1
//
zacw@776
     2
//  MGTwitterEngine.m
zacw@776
     3
//  MGTwitterEngine
zacw@776
     4
//
zacw@776
     5
//  Created by Matt Gemmell on 10/02/2008.
zacw@776
     6
//  Copyright 2008 Instinctive Code.
zacw@776
     7
//
zacw@776
     8
zacw@776
     9
#import "MGTwitterEngine.h"
zacw@776
    10
#import "MGTwitterHTTPURLConnection.h"
zacw@776
    11
zacw@776
    12
#import "NSData+Base64.h"
zacw@776
    13
zacw@776
    14
#define USE_LIBXML 0
zacw@776
    15
zacw@949
    16
#import "MGTwitterStatusesParser.h"
zacw@949
    17
#import "MGTwitterUsersParser.h"
zacw@949
    18
#import "MGTwitterMessagesParser.h"
zacw@949
    19
#import "MGTwitterMiscParser.h"
zacw@776
    20
zacw@776
    21
#define TWITTER_DOMAIN          @"twitter.com"
zacw@776
    22
#define HTTP_POST_METHOD        @"POST"
zacw@962
    23
#define HTTP_MULTIPART_METHOD	@"MULTIPART" //adium
zacw@962
    24
#define MULTIPART_FORM_BOUNDARY	@"bf5faadd239c17e35f91e6dafe1d2f96" //adium
zacw@776
    25
#define MAX_MESSAGE_LENGTH      140 // Twitter recommends tweets of max 140 chars
zacw@776
    26
#define MAX_LOCATION_LENGTH		31
zacw@776
    27
zacw@776
    28
#define DEFAULT_CLIENT_NAME     @"MGTwitterEngine"
zacw@776
    29
#define DEFAULT_CLIENT_VERSION  @"1.0"
zacw@776
    30
#define DEFAULT_CLIENT_URL      @"http://mattgemmell.com/source"
zacw@776
    31
#define DEFAULT_CLIENT_TOKEN	@"mgtwitterengine"
zacw@776
    32
zacw@2152
    33
#define URL_REQUEST_TIMEOUT     50.0 // Twitter usually fails quickly if it's going to fail at all.
zacw@776
    34
#define DEFAULT_TWEET_COUNT		20
zacw@776
    35
zacw@776
    36
zacw@776
    37
@interface MGTwitterEngine (PrivateMethods)
zacw@776
    38
zacw@776
    39
// Utility methods
zacw@776
    40
- (NSDateFormatter *)_HTTPDateFormatter;
zacw@776
    41
- (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed;
zacw@776
    42
- (NSDate *)_HTTPToDate:(NSString *)httpDate;
zacw@776
    43
- (NSString *)_dateToHTTP:(NSDate *)date;
zacw@776
    44
- (NSString *)_encodeString:(NSString *)string;
zacw@776
    45
zacw@776
    46
// Connection/Request methods
zacw@776
    47
- (NSString *)_sendRequestWithMethod:(NSString *)method 
zacw@776
    48
                                path:(NSString *)path 
zacw@776
    49
                     queryParameters:(NSDictionary *)params
zacw@962
    50
                                body:(id)body 
zacw@776
    51
                         requestType:(MGTwitterRequestType)requestType 
zacw@776
    52
                        responseType:(MGTwitterResponseType)responseType;
zacw@776
    53
zacw@776
    54
// Parsing methods
zacw@776
    55
- (void)_parseXMLForConnection:(MGTwitterHTTPURLConnection *)connection;
zacw@776
    56
zacw@776
    57
// Delegate methods
zacw@776
    58
- (BOOL) _isValidDelegateForSelector:(SEL)selector;
zacw@776
    59
zacw@776
    60
@end
zacw@776
    61
zacw@776
    62
zacw@776
    63
@implementation MGTwitterEngine
zacw@776
    64
zacw@776
    65
zacw@776
    66
#pragma mark Constructors
zacw@776
    67
zacw@776
    68
zacw@776
    69
+ (MGTwitterEngine *)twitterEngineWithDelegate:(NSObject *)theDelegate
zacw@776
    70
{
zacw@776
    71
    return [[[MGTwitterEngine alloc] initWithDelegate:theDelegate] autorelease];
zacw@776
    72
}
zacw@776
    73
zacw@776
    74
zacw@776
    75
- (MGTwitterEngine *)initWithDelegate:(NSObject *)newDelegate
zacw@776
    76
{
zacw@776
    77
    if ((self = [super init])) {
zacw@776
    78
        _delegate = newDelegate; // deliberately weak reference
zacw@776
    79
        _connections = [[NSMutableDictionary alloc] initWithCapacity:0];
zacw@776
    80
        _clientName = [DEFAULT_CLIENT_NAME retain];
zacw@776
    81
        _clientVersion = [DEFAULT_CLIENT_VERSION retain];
zacw@776
    82
        _clientURL = [DEFAULT_CLIENT_URL retain];
zacw@776
    83
		_clientSourceToken = [DEFAULT_CLIENT_TOKEN retain];
zacw@776
    84
		_APIDomain = [TWITTER_DOMAIN retain];
zacw@776
    85
        _secureConnection = YES;
zacw@776
    86
		_clearsCookies = NO;
zacw@776
    87
    }
zacw@776
    88
    
zacw@776
    89
    return self;
zacw@776
    90
}
zacw@776
    91
zacw@776
    92
zacw@776
    93
- (void)dealloc
zacw@776
    94
{
zacw@776
    95
    _delegate = nil;
zacw@776
    96
    
zacw@776
    97
    [[_connections allValues] makeObjectsPerformSelector:@selector(cancel)];
zacw@776
    98
    [_connections release];
zacw@776
    99
    
zacw@1132
   100
	[_accessToken release];
zacw@1132
   101
	[_consumer release];
zacw@1132
   102
	
zacw@776
   103
    [_username release];
zacw@776
   104
    [_password release];
zacw@776
   105
    [_clientName release];
zacw@776
   106
    [_clientVersion release];
zacw@776
   107
    [_clientURL release];
zacw@776
   108
    [_clientSourceToken release];
zacw@776
   109
	[_APIDomain release];
zacw@776
   110
    
zacw@776
   111
    [super dealloc];
zacw@776
   112
}
zacw@776
   113
zacw@776
   114
zacw@776
   115
#pragma mark Configuration and Accessors
zacw@776
   116
zacw@776
   117
zacw@776
   118
+ (NSString *)version
zacw@776
   119
{
zacw@776
   120
    // 1.0.0 = 22 Feb 2008
zacw@776
   121
    // 1.0.1 = 26 Feb 2008
zacw@776
   122
    // 1.0.2 = 04 Mar 2008
zacw@776
   123
    // 1.0.3 = 04 Mar 2008
zacw@776
   124
	// 1.0.4 = 11 Apr 2008
zacw@776
   125
	// 1.0.5 = 06 Jun 2008
zacw@776
   126
	// 1.0.6 = 05 Aug 2008
zacw@776
   127
	// 1.0.7 = 28 Sep 2008
zacw@776
   128
	// 1.0.8 = 01 Oct 2008
zacw@776
   129
    return @"1.0.8";
zacw@776
   130
}
zacw@776
   131
zacw@776
   132
zacw@776
   133
- (NSString *)username
zacw@776
   134
{
zacw@776
   135
    return [[_username retain] autorelease];
zacw@776
   136
}
zacw@776
   137
zacw@776
   138
zacw@776
   139
- (NSString *)password
zacw@776
   140
{
zacw@776
   141
    return [[_password retain] autorelease];
zacw@776
   142
}
zacw@776
   143
zacw@776
   144
zacw@776
   145
- (void)setUsername:(NSString *)newUsername password:(NSString *)newPassword
zacw@776
   146
{
zacw@776
   147
    // Set new credentials.
zacw@776
   148
    [_username release];
zacw@776
   149
    _username = [newUsername retain];
zacw@776
   150
    [_password release];
zacw@776
   151
    _password = [newPassword retain];
zacw@776
   152
    
zacw@776
   153
	if ([self clearsCookies]) {
zacw@776
   154
		// Remove all cookies for twitter, to ensure next connection uses new credentials.
zacw@776
   155
		NSString *urlString = [NSString stringWithFormat:@"%@://%@", 
zacw@776
   156
							   (_secureConnection) ? @"https" : @"http", 
zacw@776
   157
							   _APIDomain];
zacw@776
   158
		NSURL *url = [NSURL URLWithString:urlString];
zacw@776
   159
		
zacw@776
   160
		NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
zacw@776
   161
		NSEnumerator *enumerator = [[cookieStorage cookiesForURL:url] objectEnumerator];
zacw@776
   162
		NSHTTPCookie *cookie = nil;
zacw@776
   163
		while ((cookie = [enumerator nextObject])) {
zacw@776
   164
			[cookieStorage deleteCookie:cookie];
zacw@776
   165
		}
zacw@776
   166
	}
zacw@776
   167
}
zacw@776
   168
zacw@776
   169
zacw@776
   170
- (NSString *)clientName
zacw@776
   171
{
zacw@776
   172
    return [[_clientName retain] autorelease];
zacw@776
   173
}
zacw@776
   174
zacw@776
   175
zacw@776
   176
- (NSString *)clientVersion
zacw@776
   177
{
zacw@776
   178
    return [[_clientVersion retain] autorelease];
zacw@776
   179
}
zacw@776
   180
zacw@776
   181
zacw@776
   182
- (NSString *)clientURL
zacw@776
   183
{
zacw@776
   184
    return [[_clientURL retain] autorelease];
zacw@776
   185
}
zacw@776
   186
zacw@776
   187
zacw@776
   188
- (NSString *)clientSourceToken
zacw@776
   189
{
zacw@776
   190
    return [[_clientSourceToken retain] autorelease];
zacw@776
   191
}
zacw@776
   192
zacw@776
   193
zacw@776
   194
- (void)setClientName:(NSString *)name version:(NSString *)version URL:(NSString *)url token:(NSString *)token;
zacw@776
   195
{
zacw@776
   196
    [_clientName release];
zacw@776
   197
    _clientName = [name retain];
zacw@776
   198
    [_clientVersion release];
zacw@776
   199
    _clientVersion = [version retain];
zacw@776
   200
    [_clientURL release];
zacw@776
   201
    _clientURL = [url retain];
zacw@776
   202
    [_clientSourceToken release];
zacw@776
   203
    _clientSourceToken = [token retain];
zacw@776
   204
}
zacw@776
   205
zacw@776
   206
zacw@776
   207
- (NSString *)APIDomain
zacw@776
   208
{
zacw@776
   209
	return [[_APIDomain retain] autorelease];
zacw@776
   210
}
zacw@776
   211
zacw@776
   212
zacw@776
   213
- (void)setAPIDomain:(NSString *)domain
zacw@776
   214
{
zacw@776
   215
	[_APIDomain release];
zacw@776
   216
	if (!domain || [domain length] == 0) {
zacw@776
   217
		_APIDomain = [TWITTER_DOMAIN retain];
zacw@776
   218
	} else {
zacw@776
   219
		_APIDomain = [domain retain];
zacw@776
   220
	}
zacw@776
   221
}
zacw@776
   222
zacw@776
   223
zacw@776
   224
- (BOOL)usesSecureConnection
zacw@776
   225
{
zacw@776
   226
    return _secureConnection;
zacw@776
   227
}
zacw@776
   228
zacw@776
   229
zacw@776
   230
- (void)setUsesSecureConnection:(BOOL)flag
zacw@776
   231
{
zacw@776
   232
    _secureConnection = flag;
zacw@776
   233
}
zacw@776
   234
zacw@776
   235
zacw@776
   236
- (BOOL)clearsCookies
zacw@776
   237
{
zacw@776
   238
	return _clearsCookies;
zacw@776
   239
}
zacw@776
   240
zacw@776
   241
zacw@776
   242
- (void)setClearsCookies:(BOOL)flag
zacw@776
   243
{
zacw@776
   244
	_clearsCookies = flag;
zacw@776
   245
}
zacw@776
   246
zacw@776
   247
zacw@776
   248
#pragma mark Connection methods
zacw@776
   249
zacw@776
   250
zacw@776
   251
- (int)numberOfConnections
zacw@776
   252
{
zacw@776
   253
    return [_connections count];
zacw@776
   254
}
zacw@776
   255
zacw@776
   256
zacw@776
   257
- (NSArray *)connectionIdentifiers
zacw@776
   258
{
zacw@776
   259
    return [_connections allKeys];
zacw@776
   260
}
zacw@776
   261
zacw@776
   262
zacw@776
   263
- (void)closeConnection:(NSString *)identifier
zacw@776
   264
{
zacw@776
   265
    MGTwitterHTTPURLConnection *connection = [_connections objectForKey:identifier];
zacw@776
   266
    if (connection) {
zacw@776
   267
        [connection cancel];
zacw@776
   268
        [_connections removeObjectForKey:identifier];
zacw@776
   269
    }
zacw@776
   270
}
zacw@776
   271
zacw@776
   272
zacw@776
   273
- (void)closeAllConnections
zacw@776
   274
{
zacw@776
   275
    [[_connections allValues] makeObjectsPerformSelector:@selector(cancel)];
zacw@776
   276
    [_connections removeAllObjects];
zacw@776
   277
}
zacw@776
   278
zacw@776
   279
zacw@776
   280
#pragma mark Utility methods
zacw@776
   281
zacw@776
   282
zacw@776
   283
- (NSDateFormatter *)_HTTPDateFormatter
zacw@776
   284
{
zacw@776
   285
    // Returns a formatter for dates in HTTP format (i.e. RFC 822, updated by RFC 1123).
zacw@776
   286
    // e.g. "Sun, 06 Nov 1994 08:49:37 GMT"
zacw@776
   287
	NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
zacw@776
   288
	//[dateFormatter setDateFormat:@"%a, %d %b %Y %H:%M:%S GMT"]; // won't work with -init, which uses new (unicode) format behaviour.
zacw@776
   289
	[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
zacw@776
   290
	[dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss GMT"];
zacw@776
   291
	return dateFormatter;
zacw@776
   292
}
zacw@776
   293
zacw@776
   294
zacw@776
   295
- (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed
zacw@776
   296
{
zacw@776
   297
    // Append base if specified.
zacw@776
   298
    NSMutableString *str = [NSMutableString stringWithCapacity:0];
zacw@776
   299
    if (base) {
zacw@776
   300
        [str appendString:base];
zacw@776
   301
    }
zacw@776
   302
    
zacw@776
   303
    // Append each name-value pair.
zacw@776
   304
    if (params) {
zacw@776
   305
        int i;
zacw@776
   306
        NSArray *names = [params allKeys];
zacw@776
   307
        for (i = 0; i < [names count]; i++) {
zacw@776
   308
            if (i == 0 && prefixed) {
zacw@776
   309
                [str appendString:@"?"];
zacw@776
   310
            } else if (i > 0) {
zacw@776
   311
                [str appendString:@"&"];
zacw@776
   312
            }
zacw@776
   313
            NSString *name = [names objectAtIndex:i];
zacw@776
   314
            [str appendString:[NSString stringWithFormat:@"%@=%@", 
zacw@776
   315
             name, [self _encodeString:[params objectForKey:name]]]];
zacw@776
   316
        }
zacw@776
   317
    }
zacw@776
   318
    
zacw@776
   319
    return str;
zacw@776
   320
}
zacw@776
   321
zacw@776
   322
zacw@776
   323
- (NSDate *)_HTTPToDate:(NSString *)httpDate
zacw@776
   324
{
zacw@776
   325
    NSDateFormatter *dateFormatter = [self _HTTPDateFormatter];
zacw@776
   326
    return [dateFormatter dateFromString:httpDate];
zacw@776
   327
}
zacw@776
   328
zacw@776
   329
zacw@776
   330
- (NSString *)_dateToHTTP:(NSDate *)date
zacw@776
   331
{
zacw@776
   332
    NSDateFormatter *dateFormatter = [self _HTTPDateFormatter];
zacw@776
   333
    return [dateFormatter stringFromDate:date];
zacw@776
   334
}
zacw@776
   335
zacw@776
   336
zacw@776
   337
- (NSString *)_encodeString:(NSString *)string
zacw@776
   338
{
zacw@776
   339
    NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, 
zacw@776
   340
                                                                 (CFStringRef)string, 
zacw@776
   341
                                                                 NULL, 
zacw@776
   342
                                                                 (CFStringRef)@";/?:@&=$+{}<>,",
zacw@776
   343
                                                                 kCFStringEncodingUTF8);
zacw@776
   344
    return [result autorelease];
zacw@776
   345
}
zacw@776
   346
zacw@776
   347
zacw@776
   348
- (NSString *)getImageAtURL:(NSString *)urlString
zacw@776
   349
{
zacw@776
   350
    // This is a method implemented for the convenience of the client, 
zacw@776
   351
    // allowing asynchronous downloading of users' Twitter profile images.
zacw@776
   352
	NSString *encodedUrlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
zacw@776
   353
    NSURL *url = [NSURL URLWithString:encodedUrlString];
zacw@776
   354
    if (!url) {
zacw@776
   355
        return nil;
zacw@776
   356
    }
zacw@776
   357
    
zacw@776
   358
    // Construct an NSMutableURLRequest for the URL and set appropriate request method.
zacw@776
   359
    NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url 
zacw@776
   360
                                                              cachePolicy:NSURLRequestReloadIgnoringCacheData 
zacw@776
   361
                                                          timeoutInterval:URL_REQUEST_TIMEOUT];
zacw@776
   362
    
zacw@776
   363
    // Create a connection using this request, with the default timeout and caching policy, 
zacw@776
   364
    // and appropriate Twitter request and response types for parsing and error reporting.
zacw@776
   365
    MGTwitterHTTPURLConnection *connection;
zacw@776
   366
    connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest 
zacw@776
   367
                                                            delegate:self 
zacw@776
   368
                                                         requestType:MGTwitterImageRequest 
zacw@776
   369
                                                        responseType:MGTwitterImage];
zacw@776
   370
    
zacw@776
   371
    if (!connection) {
zacw@776
   372
        return nil;
zacw@776
   373
    } else {
zacw@776
   374
        [_connections setObject:connection forKey:[connection identifier]];
zacw@776
   375
        [connection release];
zacw@776
   376
    }
zacw@776
   377
    
zacw@776
   378
    return [connection identifier];
zacw@776
   379
}
zacw@776
   380
zacw@776
   381
zacw@776
   382
#pragma mark Request sending methods
zacw@776
   383
zacw@776
   384
#define SET_AUTHORIZATION_IN_HEADER 1
zacw@776
   385
zacw@1132
   386
/* See Adium Additions/Changes below—oauth support */
zacw@1132
   387
#if 0
zacw@776
   388
- (NSString *)_sendRequestWithMethod:(NSString *)method 
zacw@776
   389
                                path:(NSString *)path 
zacw@776
   390
                     queryParameters:(NSDictionary *)params 
zacw@962
   391
                                body:(id)body 
zacw@776
   392
                         requestType:(MGTwitterRequestType)requestType 
zacw@776
   393
                        responseType:(MGTwitterResponseType)responseType
zacw@776
   394
{
zacw@776
   395
    // Construct appropriate URL string.
zacw@776
   396
    NSString *fullPath = path;
zacw@776
   397
    if (params) {
zacw@776
   398
        fullPath = [self _queryStringWithBase:fullPath parameters:params prefixed:YES];
zacw@776
   399
    }
zacw@962
   400
	
zacw@776
   401
#if SET_AUTHORIZATION_IN_HEADER
zacw@776
   402
    NSString *urlString = [NSString stringWithFormat:@"%@://%@/%@", 
zacw@776
   403
                           (_secureConnection) ? @"https" : @"http",
zacw@776
   404
                           _APIDomain, fullPath];
zacw@776
   405
#else    
zacw@776
   406
    NSString *urlString = [NSString stringWithFormat:@"%@://%@:%@@%@/%@", 
zacw@776
   407
                           (_secureConnection) ? @"https" : @"http", 
zacw@776
   408
                           [self _encodeString:_username], [self _encodeString:_password], 
zacw@776
   409
                           _APIDomain, fullPath];
zacw@776
   410
#endif
zacw@776
   411
    
zacw@776
   412
    NSURL *finalURL = [NSURL URLWithString:urlString];
zacw@776
   413
    if (!finalURL) {
zacw@776
   414
        return nil;
zacw@776
   415
    }
zacw@776
   416
    
zacw@776
   417
    // Construct an NSMutableURLRequest for the URL and set appropriate request method.
zacw@776
   418
    NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:finalURL 
zacw@776
   419
                                                              cachePolicy:NSURLRequestReloadIgnoringCacheData 
zacw@776
   420
                                                          timeoutInterval:URL_REQUEST_TIMEOUT];
zacw@962
   421
	if(method && [method isEqualToString:HTTP_MULTIPART_METHOD]) {
zacw@962
   422
		method = HTTP_POST_METHOD;
zacw@962
   423
		[theRequest setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", MULTIPART_FORM_BOUNDARY] forHTTPHeaderField:@"Content-type"];
zacw@962
   424
	}
zacw@962
   425
	
zacw@776
   426
    if (method) {
zacw@776
   427
        [theRequest setHTTPMethod:method];
zacw@776
   428
    }
zacw@962
   429
zacw@776
   430
    [theRequest setHTTPShouldHandleCookies:NO];
zacw@962
   431
	
zacw@776
   432
    // Set headers for client information, for tracking purposes at Twitter.
zacw@776
   433
    [theRequest setValue:_clientName    forHTTPHeaderField:@"X-Twitter-Client"];
zacw@776
   434
    [theRequest setValue:_clientVersion forHTTPHeaderField:@"X-Twitter-Client-Version"];
zacw@776
   435
    [theRequest setValue:_clientURL     forHTTPHeaderField:@"X-Twitter-Client-URL"];
zacw@776
   436
    
zacw@776
   437
#if SET_AUTHORIZATION_IN_HEADER
zacw@776
   438
	if ([self username] && [self password]) {
zacw@776
   439
		// Set header for HTTP Basic authentication explicitly, to avoid problems with proxies and other intermediaries
zacw@776
   440
		NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]];
zacw@776
   441
		NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
zacw@776
   442
		NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
zacw@776
   443
		[theRequest setValue:authValue forHTTPHeaderField:@"Authorization"];
zacw@776
   444
	}
zacw@776
   445
#endif
zacw@776
   446
zacw@776
   447
    // Set the request body if this is a POST request.
zacw@776
   448
    BOOL isPOST = (method && [method isEqualToString:HTTP_POST_METHOD]);
zacw@962
   449
	
zacw@776
   450
    if (isPOST) {
zacw@776
   451
        // Set request body, if specified (hopefully so), with 'source' parameter if appropriate.
zacw@962
   452
		if([body isKindOfClass:[NSString class]]) {
zacw@962
   453
			NSString *finalBody = @"";
zacw@962
   454
			if (body) {
zacw@962
   455
				finalBody = [finalBody stringByAppendingString:body];
zacw@962
   456
			}
zacw@962
   457
			if (_clientSourceToken) {
zacw@962
   458
				finalBody = [finalBody stringByAppendingString:[NSString stringWithFormat:@"%@source=%@", 
zacw@962
   459
																(body) ? @"&" : @"?" , 
zacw@962
   460
																_clientSourceToken]];
zacw@962
   461
			}
zacw@962
   462
			
zacw@962
   463
			if (finalBody) {
zacw@962
   464
				[theRequest setHTTPBody:[finalBody dataUsingEncoding:NSUTF8StringEncoding]];
zacw@962
   465
			}
zacw@962
   466
		} else if ([body isKindOfClass:[NSData class]]) {
zacw@962
   467
			[theRequest setHTTPBody:body];
zacw@776
   468
		}
zacw@776
   469
    }
zacw@776
   470
    
zacw@776
   471
    
zacw@776
   472
    // Create a connection using this request, with the default timeout and caching policy, 
zacw@776
   473
    // and appropriate Twitter request and response types for parsing and error reporting.
zacw@776
   474
    MGTwitterHTTPURLConnection *connection;
zacw@776
   475
    connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest 
zacw@776
   476
                                                            delegate:self 
zacw@776
   477
                                                         requestType:requestType 
zacw@776
   478
                                                        responseType:responseType];
zacw@776
   479
    
zacw@776
   480
    if (!connection) {
zacw@776
   481
        return nil;
zacw@776
   482
    } else {
zacw@776
   483
        [_connections setObject:connection forKey:[connection identifier]];
zacw@776
   484
        [connection release];
zacw@776
   485
    }
zacw@776
   486
    
zacw@776
   487
    return [connection identifier];
zacw@776
   488
}
zacw@1132
   489
#endif
zacw@776
   490
zacw@776
   491
#pragma mark Parsing methods
zacw@776
   492
zacw@776
   493
zacw@776
   494
- (void)_parseXMLForConnection:(MGTwitterHTTPURLConnection *)connection
zacw@776
   495
{
zacw@776
   496
    NSString *identifier = [[[connection identifier] copy] autorelease];
zacw@776
   497
    NSData *xmlData = [[[connection data] copy] autorelease];
zacw@776
   498
    MGTwitterRequestType requestType = [connection requestType];
zacw@776
   499
    MGTwitterResponseType responseType = [connection responseType];
zacw@776
   500
    
zacw@776
   501
#if USE_LIBXML
zacw@776
   502
	NSURL *URL = [connection URL];
zacw@776
   503
zacw@776
   504
    switch (responseType) {
zacw@776
   505
        case MGTwitterStatuses:
zacw@776
   506
        case MGTwitterStatus:
zacw@776
   507
            [MGTwitterStatusesLibXMLParser parserWithXML:xmlData delegate:self 
zacw@776
   508
                              connectionIdentifier:identifier requestType:requestType 
zacw@776
   509
                                      responseType:responseType URL:URL];
zacw@776
   510
            break;
zacw@776
   511
        case MGTwitterUsers:
zacw@776
   512
        case MGTwitterUser:
zacw@776
   513
            [MGTwitterUsersLibXMLParser parserWithXML:xmlData delegate:self 
zacw@776
   514
                           connectionIdentifier:identifier requestType:requestType 
zacw@776
   515
                                   responseType:responseType URL:URL];
zacw@776
   516
            break;
zacw@776
   517
        case MGTwitterDirectMessages:
zacw@776
   518
        case MGTwitterDirectMessage:
zacw@776
   519
            [MGTwitterMessagesLibXMLParser parserWithXML:xmlData delegate:self 
zacw@776
   520
                              connectionIdentifier:identifier requestType:requestType 
zacw@776
   521
                                      responseType:responseType URL:URL];
zacw@776
   522
            break;
zacw@776
   523
		case MGTwitterMiscellaneous:
zacw@776
   524
			[MGTwitterMiscLibXMLParser parserWithXML:xmlData delegate:self 
zacw@776
   525
						  connectionIdentifier:identifier requestType:requestType 
zacw@776
   526
								  responseType:responseType URL:URL];
zacw@776
   527
			break;
zacw@776
   528
        default:
zacw@776
   529
            break;
zacw@776
   530
    }
zacw@776
   531
#else
zacw@776
   532
    // Determine which type of parser to use.
zacw@776
   533
    switch (responseType) {
zacw@776
   534
        case MGTwitterStatuses:
zacw@776
   535
        case MGTwitterStatus:
zacw@776
   536
            [MGTwitterStatusesParser parserWithXML:xmlData delegate:self 
zacw@776
   537
                              connectionIdentifier:identifier requestType:requestType 
zacw@776
   538
                                      responseType:responseType];
zacw@776
   539
            break;
zacw@776
   540
        case MGTwitterUsers:
zacw@776
   541
        case MGTwitterUser:
zacw@776
   542
            [MGTwitterUsersParser parserWithXML:xmlData delegate:self 
zacw@776
   543
                           connectionIdentifier:identifier requestType:requestType 
zacw@776
   544
                                   responseType:responseType];
zacw@776
   545
            break;
zacw@776
   546
        case MGTwitterDirectMessages:
zacw@776
   547
        case MGTwitterDirectMessage:
zacw@776
   548
            [MGTwitterMessagesParser parserWithXML:xmlData delegate:self 
zacw@776
   549
                              connectionIdentifier:identifier requestType:requestType 
zacw@776
   550
                                      responseType:responseType];
zacw@776
   551
            break;
zacw@776
   552
		case MGTwitterMiscellaneous:
zacw@776
   553
			[MGTwitterMiscParser parserWithXML:xmlData delegate:self 
zacw@776
   554
						  connectionIdentifier:identifier requestType:requestType 
zacw@776
   555
								  responseType:responseType];
zacw@776
   556
			break;
zacw@776
   557
        default:
zacw@776
   558
            break;
zacw@776
   559
    }
zacw@776
   560
#endif
zacw@776
   561
}
zacw@776
   562
zacw@776
   563
#pragma mark Delegate methods
zacw@776
   564
zacw@776
   565
- (BOOL) _isValidDelegateForSelector:(SEL)selector
zacw@776
   566
{
zacw@776
   567
	return ((_delegate != nil) && [_delegate respondsToSelector:selector]);
zacw@776
   568
}
zacw@776
   569
zacw@776
   570
#pragma mark MGTwitterParserDelegate methods
zacw@776
   571
zacw@776
   572
zacw@776
   573
- (void)parsingSucceededForRequest:(NSString *)identifier 
zacw@776
   574
                    ofResponseType:(MGTwitterResponseType)responseType 
zacw@776
   575
                 withParsedObjects:(NSArray *)parsedObjects
zacw@776
   576
{
zacw@776
   577
    // Forward appropriate message to _delegate, depending on responseType.
zacw@776
   578
    switch (responseType) {
zacw@776
   579
        case MGTwitterStatuses:
zacw@776
   580
        case MGTwitterStatus:
zacw@776
   581
			if ([self _isValidDelegateForSelector:@selector(statusesReceived:forRequest:)])
zacw@776
   582
				[_delegate statusesReceived:parsedObjects forRequest:identifier];
zacw@776
   583
            break;
zacw@776
   584
        case MGTwitterUsers:
zacw@776
   585
        case MGTwitterUser:
zacw@776
   586
			if ([self _isValidDelegateForSelector:@selector(userInfoReceived:forRequest:)])
zacw@776
   587
				[_delegate userInfoReceived:parsedObjects forRequest:identifier];
zacw@776
   588
            break;
zacw@776
   589
        case MGTwitterDirectMessages:
zacw@776
   590
        case MGTwitterDirectMessage:
zacw@776
   591
			if ([self _isValidDelegateForSelector:@selector(directMessagesReceived:forRequest:)])
zacw@776
   592
				[_delegate directMessagesReceived:parsedObjects forRequest:identifier];
zacw@776
   593
            break;
zacw@776
   594
		case MGTwitterMiscellaneous:
zacw@776
   595
			if ([self _isValidDelegateForSelector:@selector(miscInfoReceived:forRequest:)])
zacw@776
   596
				[_delegate miscInfoReceived:parsedObjects forRequest:identifier];
zacw@776
   597
			break;
zacw@776
   598
        default:
zacw@776
   599
            break;
zacw@776
   600
    }
zacw@776
   601
}
zacw@776
   602
zacw@776
   603
zacw@776
   604
- (void)parsingFailedForRequest:(NSString *)requestIdentifier 
zacw@776
   605
                 ofResponseType:(MGTwitterResponseType)responseType 
zacw@776
   606
                      withError:(NSError *)error
zacw@776
   607
{
zacw@776
   608
	if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)])
zacw@776
   609
		[_delegate requestFailed:requestIdentifier withError:error];
zacw@776
   610
}
zacw@776
   611
zacw@776
   612
zacw@776
   613
#pragma mark NSURLConnection delegate methods
zacw@776
   614
zacw@776
   615
zacw@776
   616
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
zacw@776
   617
{
zacw@1187
   618
	if ([challenge previousFailureCount] == 0 && ![challenge proposedCredential] && !_useOAuth && _password && _username) {
zacw@776
   619
		NSURLCredential *credential = [NSURLCredential credentialWithUser:_username password:_password 
zacw@776
   620
															  persistence:NSURLCredentialPersistenceForSession];
zacw@776
   621
		[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
zacw@776
   622
	} else {
zacw@776
   623
		[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
zacw@776
   624
	}
zacw@776
   625
}
zacw@776
   626
zacw@776
   627
zacw@776
   628
- (void)connection:(MGTwitterHTTPURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
zacw@776
   629
{
zacw@776
   630
    // This method is called when the server has determined that it has enough information to create the NSURLResponse.
zacw@776
   631
    // it can be called multiple times, for example in the case of a redirect, so each time we reset the data.
zacw@776
   632
    [connection resetDataLength];
zacw@776
   633
    
zacw@776
   634
    // Get response code.
zacw@776
   635
    NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response;
zacw@776
   636
    int statusCode = [resp statusCode];
zacw@776
   637
    
zacw@776
   638
    if (statusCode >= 400) {
zacw@776
   639
        // Assume failure, and report to delegate.
zacw@776
   640
        NSError *error = [NSError errorWithDomain:@"HTTP" code:statusCode userInfo:nil];
zacw@776
   641
		if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)])
zacw@776
   642
			[_delegate requestFailed:[connection identifier] withError:error];
zacw@776
   643
        
zacw@776
   644
        // Destroy the connection.
zacw@776
   645
        [connection cancel];
zacw@776
   646
        [_connections removeObjectForKey:[connection identifier]];
zacw@776
   647
        
zacw@776
   648
    } else if (statusCode == 304 || [connection responseType] == MGTwitterGeneric) {
zacw@776
   649
        // Not modified, or generic success.
zacw@776
   650
		if ([self _isValidDelegateForSelector:@selector(requestSucceeded:)])
zacw@776
   651
			[_delegate requestSucceeded:[connection identifier]];
zacw@776
   652
        if (statusCode == 304) {
zacw@776
   653
            [self parsingSucceededForRequest:[connection identifier] 
zacw@776
   654
                              ofResponseType:[connection responseType] 
zacw@776
   655
                           withParsedObjects:[NSArray array]];
zacw@776
   656
        }
zacw@776
   657
        
zacw@776
   658
        // Destroy the connection.
zacw@776
   659
        [connection cancel];
zacw@776
   660
        [_connections removeObjectForKey:[connection identifier]];
zacw@776
   661
    }
zacw@776
   662
    
zacw@776
   663
    if (NO) {
zacw@776
   664
        // Display headers for debugging.
zacw@776
   665
        NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response;
zacw@776
   666
        NSLog(@"(%d) [%@]:\r%@", 
zacw@776
   667
              [resp statusCode], 
zacw@776
   668
              [NSHTTPURLResponse localizedStringForStatusCode:[resp statusCode]], 
zacw@776
   669
              [resp allHeaderFields]);
zacw@776
   670
    }
zacw@776
   671
}
zacw@776
   672
zacw@776
   673
zacw@776
   674
- (void)connection:(MGTwitterHTTPURLConnection *)connection didReceiveData:(NSData *)data
zacw@776
   675
{
zacw@776
   676
    // Append the new data to the receivedData.
zacw@776
   677
    [connection appendData:data];
zacw@776
   678
}
zacw@776
   679
zacw@776
   680
zacw@776
   681
- (void)connection:(MGTwitterHTTPURLConnection *)connection didFailWithError:(NSError *)error
zacw@776
   682
{
zacw@776
   683
    // Inform delegate.
zacw@776
   684
	if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)])
zacw@776
   685
		[_delegate requestFailed:[connection identifier] withError:error];
zacw@776
   686
    
zacw@776
   687
    // Release the connection.
zacw@776
   688
    [_connections removeObjectForKey:[connection identifier]];
zacw@776
   689
}
zacw@776
   690
zacw@776
   691
zacw@776
   692
- (void)connectionDidFinishLoading:(MGTwitterHTTPURLConnection *)connection
zacw@776
   693
{
zacw@776
   694
    // Inform delegate.
zacw@776
   695
	if ([self _isValidDelegateForSelector:@selector(requestSucceeded:)])
zacw@776
   696
		[_delegate requestSucceeded:[connection identifier]];
zacw@776
   697
    
zacw@776
   698
    NSData *receivedData = [connection data];
zacw@776
   699
    if (receivedData) {
zacw@776
   700
        if (NO) {
zacw@776
   701
            // Dump data as string for debugging.
zacw@776
   702
            NSString *dataString = [NSString stringWithUTF8String:[receivedData bytes]];
zacw@776
   703
            NSLog(@"Succeeded! Received %d bytes of data:\r\r%@", [receivedData length], dataString);
zacw@776
   704
        }
zacw@776
   705
        
zacw@776
   706
        if (NO) {
zacw@776
   707
            // Dump XML to file for debugging.
zacw@776
   708
            NSString *dataString = [NSString stringWithUTF8String:[receivedData bytes]];
zacw@776
   709
            [dataString writeToFile:[@"~/Desktop/twitter_messages.xml" stringByExpandingTildeInPath] 
zacw@776
   710
                         atomically:NO encoding:NSUnicodeStringEncoding error:NULL];
zacw@776
   711
        }
zacw@776
   712
        
zacw@776
   713
        if ([connection responseType] == MGTwitterImage) {
zacw@776
   714
			// Create image from data.
zacw@776
   715
#if TARGET_OS_IPHONE
zacw@776
   716
            UIImage *image = [[[UIImage alloc] initWithData:[connection data]] autorelease];
zacw@776
   717
#else
zacw@776
   718
            NSImage *image = [[[NSImage alloc] initWithData:[connection data]] autorelease];
zacw@776
   719
#endif
zacw@776
   720
            
zacw@776
   721
            // Inform delegate.
zacw@776
   722
			if ([self _isValidDelegateForSelector:@selector(imageReceived:forRequest:)])
zacw@776
   723
				[_delegate imageReceived:image forRequest:[connection identifier]];
zacw@776
   724
        } else {
zacw@776
   725
            // Parse XML appropriately.
zacw@776
   726
            [self _parseXMLForConnection:connection];
zacw@776
   727
        }
zacw@776
   728
    }
zacw@776
   729
    
zacw@776
   730
    // Release the connection.
zacw@776
   731
    [_connections removeObjectForKey:[connection identifier]];
zacw@776
   732
}
zacw@776
   733
zacw@776
   734
zacw@776
   735
#pragma mark -
zacw@776
   736
#pragma mark Twitter API methods
zacw@776
   737
#pragma mark -
zacw@776
   738
zacw@776
   739
zacw@776
   740
#pragma mark Account methods
zacw@776
   741
zacw@776
   742
- (NSString *)endUserSession
zacw@776
   743
{
zacw@776
   744
    NSString *path = @"account/end_session"; // deliberately no format specified
zacw@776
   745
    
zacw@776
   746
    return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
   747
                            requestType:MGTwitterAccountRequest 
zacw@776
   748
                           responseType:MGTwitterGeneric];
zacw@776
   749
}
zacw@776
   750
zacw@776
   751
zacw@776
   752
- (NSString *)enableUpdatesFor:(NSString *)username
zacw@776
   753
{
zacw@776
   754
    // i.e. follow
zacw@776
   755
    if (!username) {
zacw@776
   756
        return nil;
zacw@776
   757
    }
zacw@776
   758
    NSString *path = [NSString stringWithFormat:@"friendships/create/%@.xml", username];
zacw@776
   759
    
zacw@776
   760
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   761
                            requestType:MGTwitterAccountRequest 
zacw@776
   762
                           responseType:MGTwitterUser];
zacw@776
   763
}
zacw@776
   764
zacw@776
   765
zacw@776
   766
- (NSString *)disableUpdatesFor:(NSString *)username
zacw@776
   767
{
zacw@776
   768
    // i.e. no longer follow
zacw@776
   769
    if (!username) {
zacw@776
   770
        return nil;
zacw@776
   771
    }
zacw@776
   772
    NSString *path = [NSString stringWithFormat:@"friendships/destroy/%@.xml", username];
zacw@776
   773
    
zacw@776
   774
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   775
                            requestType:MGTwitterAccountRequest 
zacw@776
   776
                           responseType:MGTwitterUser];
zacw@776
   777
}
zacw@776
   778
zacw@776
   779
zacw@776
   780
- (NSString *)isUser:(NSString *)username1 receivingUpdatesFor:(NSString *)username2
zacw@776
   781
{
zacw@776
   782
	if (!username1 || !username2) {
zacw@776
   783
        return nil;
zacw@776
   784
    }
zacw@776
   785
	NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
   786
    [params setObject:username1 forKey:@"user_a"];
zacw@776
   787
	[params setObject:username2 forKey:@"user_b"];
zacw@776
   788
	
zacw@776
   789
    NSString *path = @"friendships/exists.xml";
zacw@776
   790
    
zacw@776
   791
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
   792
                            requestType:MGTwitterAccountRequest 
zacw@776
   793
                           responseType:MGTwitterMiscellaneous];
zacw@776
   794
}
zacw@776
   795
zacw@776
   796
zacw@776
   797
- (NSString *)enableNotificationsFor:(NSString *)username
zacw@776
   798
{
zacw@776
   799
    if (!username) {
zacw@776
   800
        return nil;
zacw@776
   801
    }
zacw@776
   802
    NSString *path = [NSString stringWithFormat:@"notifications/follow/%@.xml", username];
zacw@776
   803
    
zacw@776
   804
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   805
                            requestType:MGTwitterAccountRequest 
zacw@776
   806
                           responseType:MGTwitterUser];
zacw@776
   807
}
zacw@776
   808
zacw@776
   809
zacw@776
   810
- (NSString *)disableNotificationsFor:(NSString *)username
zacw@776
   811
{
zacw@776
   812
    if (!username) {
zacw@776
   813
        return nil;
zacw@776
   814
    }
zacw@776
   815
    NSString *path = [NSString stringWithFormat:@"notifications/leave/%@.xml", username];
zacw@776
   816
    
zacw@776
   817
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   818
                            requestType:MGTwitterAccountRequest 
zacw@776
   819
                           responseType:MGTwitterUser];
zacw@776
   820
}
zacw@776
   821
zacw@776
   822
zacw@776
   823
- (NSString *)getRateLimitStatus
zacw@776
   824
{
zacw@776
   825
	NSString *path = @"account/rate_limit_status.xml";
zacw@776
   826
	
zacw@776
   827
	return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
   828
                            requestType:MGTwitterAccountRequest 
zacw@776
   829
                           responseType:MGTwitterMiscellaneous];
zacw@776
   830
}
zacw@776
   831
zacw@776
   832
zacw@776
   833
- (NSString *)setLocation:(NSString *)location
zacw@776
   834
{
zacw@776
   835
	if (!location) {
zacw@776
   836
        return nil;
zacw@776
   837
    }
zacw@776
   838
    
zacw@776
   839
    NSString *path = @"account/update_location.xml";
zacw@776
   840
    
zacw@776
   841
    NSString *trimmedText = location;
zacw@776
   842
    if ([trimmedText length] > MAX_LOCATION_LENGTH) {
zacw@776
   843
        trimmedText = [trimmedText substringToIndex:MAX_LOCATION_LENGTH];
zacw@776
   844
    }
zacw@776
   845
    
zacw@776
   846
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
   847
    [params setObject:trimmedText forKey:@"location"];
zacw@776
   848
    NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
zacw@776
   849
    
zacw@776
   850
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 
zacw@1681
   851
                        queryParameters:nil body:body 
zacw@776
   852
                            requestType:MGTwitterAccountRequest 
zacw@776
   853
                           responseType:MGTwitterGeneric];
zacw@776
   854
}
zacw@776
   855
zacw@776
   856
zacw@776
   857
- (NSString *)setNotificationsDeliveryMethod:(NSString *)method
zacw@776
   858
{
zacw@776
   859
	NSString *deliveryMethod = method;
zacw@776
   860
	if (!method || [method length] == 0) {
zacw@776
   861
		deliveryMethod = @"none";
zacw@776
   862
	}
zacw@776
   863
	
zacw@776
   864
	NSString *path = @"account/update_delivery_device.xml";
zacw@776
   865
    
zacw@776
   866
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
   867
    if (deliveryMethod) {
zacw@776
   868
        [params setObject:deliveryMethod forKey:@"device"];
zacw@776
   869
    }
zacw@776
   870
    
zacw@1681
   871
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   872
                            requestType:MGTwitterAccountRequest
zacw@776
   873
                           responseType:MGTwitterGeneric];
zacw@776
   874
}
zacw@776
   875
zacw@776
   876
zacw@776
   877
- (NSString *)block:(NSString *)username
zacw@776
   878
{
zacw@776
   879
	if (!username) {
zacw@776
   880
		return nil;
zacw@776
   881
	}
zacw@776
   882
	
zacw@776
   883
	NSString *path = [NSString stringWithFormat:@"blocks/create/%@.xml", username];
zacw@776
   884
    
zacw@776
   885
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   886
                            requestType:MGTwitterAccountRequest
zacw@776
   887
                           responseType:MGTwitterUser];
zacw@776
   888
}
zacw@776
   889
zacw@776
   890
zacw@776
   891
- (NSString *)unblock:(NSString *)username
zacw@776
   892
{
zacw@776
   893
	if (!username) {
zacw@776
   894
		return nil;
zacw@776
   895
	}
zacw@776
   896
	
zacw@776
   897
	NSString *path = [NSString stringWithFormat:@"blocks/destroy/%@.xml", username];
zacw@776
   898
    
zacw@776
   899
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
   900
                            requestType:MGTwitterAccountRequest
zacw@776
   901
                           responseType:MGTwitterUser];
zacw@776
   902
}
zacw@776
   903
zacw@776
   904
zacw@776
   905
- (NSString *)testService
zacw@776
   906
{
zacw@776
   907
	NSString *path = @"help/test.xml";
zacw@776
   908
	
zacw@776
   909
	return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
   910
                            requestType:MGTwitterAccountRequest
zacw@776
   911
                           responseType:MGTwitterGeneric];
zacw@776
   912
}
zacw@776
   913
zacw@776
   914
zacw@776
   915
- (NSString *)getDowntimeSchedule
zacw@776
   916
{
zacw@776
   917
	NSString *path = @"help/downtime_schedule.xml";
zacw@776
   918
	
zacw@776
   919
	return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
   920
                            requestType:MGTwitterAccountRequest
zacw@776
   921
                           responseType:MGTwitterMiscellaneous];
zacw@776
   922
}
zacw@776
   923
zacw@776
   924
zacw@776
   925
#pragma mark Retrieving updates
zacw@776
   926
zacw@776
   927
zacw@776
   928
- (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum
zacw@776
   929
{
zacw@776
   930
	// Included for backwards-compatibility.
zacw@776
   931
    return [self getFollowedTimelineFor:username since:date startingAtPage:pageNum count:0]; // zero means default
zacw@776
   932
}
zacw@776
   933
zacw@776
   934
zacw@776
   935
- (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)count
zacw@776
   936
{
zacw@2768
   937
	NSString *path = @"statuses/home_timeline.xml";
zacw@776
   938
    
zacw@776
   939
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
   940
    if (date) {
zacw@776
   941
        [params setObject:[self _dateToHTTP:date] forKey:@"since"];
zacw@776
   942
    }
zacw@776
   943
    if (pageNum > 0) {
zacw@776
   944
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
   945
    }
zacw@776
   946
    if (username) {
zacw@2768
   947
        path = [NSString stringWithFormat:@"statuses/home_timeline/%@.xml", username];
zacw@776
   948
    }
zacw@776
   949
	int tweetCount = DEFAULT_TWEET_COUNT;
zacw@776
   950
	if (count > 0) {
zacw@776
   951
		tweetCount = count;
zacw@776
   952
	}
zacw@776
   953
	[params setObject:[NSString stringWithFormat:@"%d", tweetCount] forKey:@"count"];
zacw@776
   954
    
zacw@776
   955
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
   956
                            requestType:MGTwitterStatusesRequest 
zacw@776
   957
                           responseType:MGTwitterStatuses];
zacw@776
   958
}
zacw@776
   959
zacw@776
   960
zacw@2499
   961
- (NSString *)getFollowedTimelineFor:(NSString *)username sinceID:(NSString *)updateID startingAtPage:(int)pageNum count:(int)count
zacw@776
   962
{
zacw@2768
   963
	NSString *path = @"statuses/home_timeline.xml";
zacw@776
   964
    
zacw@776
   965
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
   966
    if (updateID > 0) {
zacw@2501
   967
        [params setObject:updateID forKey:@"since_id"];
zacw@776
   968
    }
zacw@776
   969
    if (pageNum > 0) {
zacw@776
   970
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
   971
    }
zacw@776
   972
    if (username) {
zacw@2768
   973
        path = [NSString stringWithFormat:@"statuses/home_timeline/%@.xml", username];
zacw@776
   974
    }
zacw@776
   975
	int tweetCount = DEFAULT_TWEET_COUNT;
zacw@776
   976
	if (count > 0) {
zacw@776
   977
		tweetCount = count;
zacw@776
   978
	}
zacw@776
   979
	[params setObject:[NSString stringWithFormat:@"%d", tweetCount] forKey:@"count"];
zacw@2768
   980
	
zacw@776
   981
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
   982
                            requestType:MGTwitterStatusesRequest 
zacw@776
   983
                           responseType:MGTwitterStatuses];
zacw@776
   984
}
zacw@776
   985
zacw@776
   986
zacw@776
   987
- (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date count:(int)numUpdates
zacw@776
   988
{
zacw@776
   989
	// Included for backwards-compatibility.
zacw@776
   990
    return [self getUserTimelineFor:username since:date startingAtPage:0 count:numUpdates];
zacw@776
   991
}
zacw@776
   992
zacw@776
   993
zacw@776
   994
- (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)numUpdates
zacw@776
   995
{
zacw@776
   996
	NSString *path = @"statuses/user_timeline.xml";
zacw@776
   997
    
zacw@776
   998
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
   999
    if (date) {
zacw@776
  1000
        [params setObject:[self _dateToHTTP:date] forKey:@"since"];
zacw@776
  1001
    }
zacw@776
  1002
	if (pageNum > 0) {
zacw@776
  1003
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1004
    }
zacw@776
  1005
    if (numUpdates > 0) {
zacw@776
  1006
        [params setObject:[NSString stringWithFormat:@"%d", numUpdates] forKey:@"count"];
zacw@776
  1007
    }
zacw@776
  1008
    if (username) {
zacw@776
  1009
        path = [NSString stringWithFormat:@"statuses/user_timeline/%@.xml", username];
zacw@776
  1010
    }
zacw@776
  1011
    
zacw@776
  1012
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1013
                            requestType:MGTwitterStatusesRequest 
zacw@776
  1014
                           responseType:MGTwitterStatuses];
zacw@776
  1015
}
zacw@776
  1016
zacw@776
  1017
zacw@2499
  1018
- (NSString *)getUserTimelineFor:(NSString *)username sinceID:(NSString *)updateID startingAtPage:(int)pageNum count:(int)numUpdates
zacw@776
  1019
{
zacw@776
  1020
	NSString *path = @"statuses/user_timeline.xml";
zacw@776
  1021
    
zacw@776
  1022
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1023
    if (updateID > 0) {
zacw@2500
  1024
        [params setObject:updateID forKey:@"since_id"];
zacw@776
  1025
    }
zacw@776
  1026
	if (pageNum > 0) {
zacw@776
  1027
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1028
    }
zacw@776
  1029
    if (numUpdates > 0) {
zacw@776
  1030
        [params setObject:[NSString stringWithFormat:@"%d", numUpdates] forKey:@"count"];
zacw@776
  1031
    }
zacw@776
  1032
    if (username) {
zacw@776
  1033
        path = [NSString stringWithFormat:@"statuses/user_timeline/%@.xml", username];
zacw@776
  1034
    }
zacw@776
  1035
    
zacw@776
  1036
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1037
                            requestType:MGTwitterStatusesRequest 
zacw@776
  1038
                           responseType:MGTwitterStatuses];
zacw@776
  1039
}
zacw@776
  1040
zacw@776
  1041
zacw@776
  1042
- (NSString *)getUserUpdatesArchiveStartingAtPage:(int)pageNum
zacw@776
  1043
{
zacw@776
  1044
    NSString *path = @"account/archive.xml";
zacw@776
  1045
    
zacw@776
  1046
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1047
    if (pageNum > 0) {
zacw@776
  1048
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1049
    }
zacw@776
  1050
    
zacw@776
  1051
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1052
                            requestType:MGTwitterStatusesRequest 
zacw@776
  1053
                           responseType:MGTwitterStatuses];
zacw@776
  1054
}
zacw@776
  1055
zacw@776
  1056
zacw@2499
  1057
- (NSString *)getPublicTimelineSinceID:(NSString *)updateID
zacw@776
  1058
{
zacw@776
  1059
    NSString *path = @"statuses/public_timeline.xml";
zacw@776
  1060
    
zacw@776
  1061
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1062
    if (updateID > 0) {
zacw@2500
  1063
        [params setObject:updateID forKey:@"since_id"];
zacw@776
  1064
    }
zacw@776
  1065
    
zacw@776
  1066
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1067
                            requestType:MGTwitterStatusesRequest 
zacw@776
  1068
                           responseType:MGTwitterStatuses];
zacw@776
  1069
}
zacw@776
  1070
zacw@776
  1071
- (NSString *)getRepliesStartingAtPage:(int)pageNum
zacw@776
  1072
{
zacw@776
  1073
    NSString *path = @"statuses/replies.xml";
zacw@776
  1074
    
zacw@776
  1075
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1076
    if (pageNum > 0) {
zacw@776
  1077
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1078
    }
zacw@776
  1079
    
zacw@776
  1080
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1081
                            requestType:MGTwitterRepliesRequest 
zacw@776
  1082
                           responseType:MGTwitterStatuses];
zacw@776
  1083
}
zacw@776
  1084
zacw@776
  1085
zacw@776
  1086
- (NSString *)getFavoriteUpdatesFor:(NSString *)username startingAtPage:(int)pageNum
zacw@776
  1087
{
zacw@776
  1088
    NSString *path = @"favorites.xml";
zacw@776
  1089
    
zacw@776
  1090
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1091
    if (pageNum > 0) {
zacw@776
  1092
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1093
    }
zacw@776
  1094
    if (username) {
zacw@776
  1095
        path = [NSString stringWithFormat:@"favorites/%@.xml", username];
zacw@776
  1096
    }
zacw@776
  1097
    
zacw@776
  1098
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1099
                            requestType:MGTwitterStatusesRequest 
zacw@776
  1100
                           responseType:MGTwitterStatuses];
zacw@776
  1101
}
zacw@776
  1102
zacw@776
  1103
zacw@2499
  1104
- (NSString *)getUpdate:(NSString *)updateID
zacw@776
  1105
{
zacw@2499
  1106
    NSString *path = [NSString stringWithFormat:@"statuses/show/%@.xml", updateID];
zacw@776
  1107
    
zacw@776
  1108
    return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
  1109
                            requestType:MGTwitterStatusesRequest 
zacw@776
  1110
                           responseType:MGTwitterStatus];
zacw@776
  1111
}
zacw@776
  1112
zacw@776
  1113
zacw@776
  1114
#pragma mark Retrieving direct messages
zacw@776
  1115
zacw@776
  1116
zacw@776
  1117
- (NSString *)getDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum
zacw@776
  1118
{
zacw@776
  1119
    NSString *path = @"direct_messages.xml";
zacw@776
  1120
    
zacw@776
  1121
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1122
    if (date) {
zacw@776
  1123
        [params setObject:[self _dateToHTTP:date] forKey:@"since"];
zacw@776
  1124
    }
zacw@776
  1125
    if (pageNum > 0) {
zacw@776
  1126
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1127
    }
zacw@776
  1128
    
zacw@776
  1129
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1130
                            requestType:MGTwitterDirectMessagesRequest 
zacw@776
  1131
                           responseType:MGTwitterDirectMessages];
zacw@776
  1132
}
zacw@776
  1133
zacw@776
  1134
zacw@2499
  1135
- (NSString *)getDirectMessagesSinceID:(NSString *)updateID startingAtPage:(int)pageNum
zacw@776
  1136
{
zacw@776
  1137
    NSString *path = @"direct_messages.xml";
zacw@776
  1138
    
zacw@776
  1139
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1140
    if (updateID > 0) {
zacw@2500
  1141
        [params setObject:updateID forKey:@"since_id"];
zacw@776
  1142
    }
zacw@776
  1143
    if (pageNum > 0) {
zacw@776
  1144
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1145
    }
zacw@776
  1146
    
zacw@776
  1147
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1148
                            requestType:MGTwitterDirectMessagesRequest 
zacw@776
  1149
                           responseType:MGTwitterDirectMessages];
zacw@776
  1150
}
zacw@776
  1151
zacw@776
  1152
zacw@776
  1153
- (NSString *)getSentDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum
zacw@776
  1154
{
zacw@776
  1155
    NSString *path = @"direct_messages/sent.xml";
zacw@776
  1156
    
zacw@776
  1157
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1158
    if (date) {
zacw@776
  1159
        [params setObject:[self _dateToHTTP:date] forKey:@"since"];
zacw@776
  1160
    }
zacw@776
  1161
    if (pageNum > 0) {
zacw@776
  1162
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1163
    }
zacw@776
  1164
    
zacw@776
  1165
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1166
                            requestType:MGTwitterDirectMessagesRequest 
zacw@776
  1167
                           responseType:MGTwitterDirectMessages];
zacw@776
  1168
}
zacw@776
  1169
zacw@776
  1170
zacw@2499
  1171
- (NSString *)getSentDirectMessagesSinceID:(NSString *)updateID startingAtPage:(int)pageNum
zacw@776
  1172
{
zacw@776
  1173
    NSString *path = @"direct_messages/sent.xml";
zacw@776
  1174
    
zacw@776
  1175
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1176
    if (updateID > 0) {
zacw@2500
  1177
        [params setObject:updateID forKey:@"since_id"];
zacw@776
  1178
    }
zacw@776
  1179
    if (pageNum > 0) {
zacw@776
  1180
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1181
    }
zacw@776
  1182
    
zacw@776
  1183
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1184
                            requestType:MGTwitterDirectMessagesRequest 
zacw@776
  1185
                           responseType:MGTwitterDirectMessages];
zacw@776
  1186
}
zacw@776
  1187
zacw@776
  1188
zacw@776
  1189
#pragma mark Retrieving user information
zacw@776
  1190
zacw@776
  1191
zacw@776
  1192
- (NSString *)getUserInformationFor:(NSString *)username
zacw@776
  1193
{
zacw@776
  1194
    if (!username) {
zacw@776
  1195
        return nil;
zacw@776
  1196
    }
zacw@776
  1197
    NSString *path = [NSString stringWithFormat:@"users/show/%@.xml", username];
zacw@776
  1198
    
zacw@776
  1199
    return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
  1200
                            requestType:MGTwitterUserInfoRequest 
zacw@776
  1201
                           responseType:MGTwitterUser];
zacw@776
  1202
}
zacw@776
  1203
zacw@776
  1204
zacw@776
  1205
- (NSString *)getUserInformationForEmail:(NSString *)email
zacw@776
  1206
{
zacw@776
  1207
    NSString *path = @"users/show.xml";
zacw@776
  1208
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1209
    if (email) {
zacw@776
  1210
        [params setObject:email forKey:@"email"];
zacw@776
  1211
    } else {
zacw@776
  1212
        return nil;
zacw@776
  1213
    }
zacw@776
  1214
    
zacw@776
  1215
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1216
                            requestType:MGTwitterUserInfoRequest 
zacw@776
  1217
                           responseType:MGTwitterUser];
zacw@776
  1218
}
zacw@776
  1219
zacw@776
  1220
zacw@776
  1221
- (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtPage:(int)pageNum
zacw@776
  1222
{
zacw@776
  1223
    NSString *path = @"statuses/friends.xml";
zacw@776
  1224
    
zacw@776
  1225
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1226
    if (username) {
zacw@776
  1227
        path = [NSString stringWithFormat:@"statuses/friends/%@.xml", username];
zacw@776
  1228
    }
zacw@776
  1229
    if (pageNum > 0) {
zacw@776
  1230
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@776
  1231
    }
zacw@776
  1232
    
zacw@776
  1233
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1234
                            requestType:MGTwitterUserInfoRequest 
zacw@776
  1235
                           responseType:MGTwitterUsers];
zacw@776
  1236
}
zacw@776
  1237
zacw@776
  1238
zacw@776
  1239
- (NSString *)getFollowersIncludingCurrentStatus:(BOOL)flag
zacw@776
  1240
{
zacw@776
  1241
    NSString *path = @"statuses/followers.xml";
zacw@776
  1242
    
zacw@776
  1243
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1244
    if (!flag) {
zacw@776
  1245
        [params setObject:@"true" forKey:@"lite"]; // slightly bizarre, but correct.
zacw@776
  1246
    }
zacw@776
  1247
    
zacw@776
  1248
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@776
  1249
                            requestType:MGTwitterUserInfoRequest 
zacw@776
  1250
                           responseType:MGTwitterUsers];
zacw@776
  1251
}
zacw@776
  1252
zacw@776
  1253
zacw@776
  1254
- (NSString *)getFeaturedUsers
zacw@776
  1255
{
zacw@776
  1256
    NSString *path = @"statuses/featured.xml";
zacw@776
  1257
    
zacw@776
  1258
    return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@776
  1259
                            requestType:MGTwitterUserInfoRequest 
zacw@776
  1260
                           responseType:MGTwitterUsers];
zacw@776
  1261
}
zacw@776
  1262
zacw@776
  1263
zacw@776
  1264
#pragma mark Sending and editing updates
zacw@776
  1265
zacw@776
  1266
zacw@776
  1267
- (NSString *)sendUpdate:(NSString *)status
zacw@776
  1268
{
zacw@776
  1269
    return [self sendUpdate:status inReplyTo:0];
zacw@776
  1270
}
zacw@776
  1271
zacw@776
  1272
zacw@2499
  1273
- (NSString *)sendUpdate:(NSString *)status inReplyTo:(NSString *)updateID
zacw@776
  1274
{
zacw@776
  1275
    if (!status) {
zacw@776
  1276
        return nil;
zacw@776
  1277
    }
zacw@776
  1278
    
zacw@776
  1279
    NSString *path = @"statuses/update.xml";
zacw@776
  1280
    
zacw@776
  1281
    NSString *trimmedText = status;
zacw@776
  1282
    if ([trimmedText length] > MAX_MESSAGE_LENGTH) {
zacw@776
  1283
        trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH];
zacw@776
  1284
    }
zacw@776
  1285
    
zacw@776
  1286
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1287
    [params setObject:trimmedText forKey:@"status"];
zacw@776
  1288
    if (updateID > 0) {
zacw@2500
  1289
        [params setObject:updateID forKey:@"in_reply_to_status_id"];
zacw@776
  1290
    }
zacw@776
  1291
    NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
zacw@776
  1292
    
zacw@776
  1293
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 
zacw@1681
  1294
                        queryParameters:nil body:body 
zacw@776
  1295
                            requestType:MGTwitterStatusSend 
zacw@776
  1296
                           responseType:MGTwitterStatus];
zacw@776
  1297
}
zacw@776
  1298
zacw@776
  1299
zacw@2499
  1300
- (NSString *)deleteUpdate:(NSString *)updateID
zacw@776
  1301
{
zacw@2499
  1302
    NSString *path = [NSString stringWithFormat:@"statuses/destroy/%@.xml", updateID];
zacw@776
  1303
    
zacw@776
  1304
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
  1305
                            requestType:MGTwitterAccountRequest 
zacw@776
  1306
                           responseType:MGTwitterGeneric];
zacw@776
  1307
}
zacw@776
  1308
zacw@776
  1309
zacw@2499
  1310
- (NSString *)markUpdate:(NSString *)updateID asFavorite:(BOOL)flag
zacw@776
  1311
{
zacw@2499
  1312
    NSString *path = [NSString stringWithFormat:@"favorites/%@/%@.xml", 
zacw@776
  1313
                      (flag) ? @"create" : @"destroy" ,
zacw@776
  1314
                      updateID];
zacw@776
  1315
    
zacw@776
  1316
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
  1317
                            requestType:MGTwitterAccountRequest 
zacw@776
  1318
                           responseType:MGTwitterStatus];
zacw@776
  1319
}
zacw@776
  1320
zacw@2768
  1321
- (NSString *)retweetUpdate:(NSString *)updateID
zacw@2768
  1322
{
zacw@2768
  1323
    NSString *path = [NSString stringWithFormat:@"statuses/retweet/%@.xml", updateID];
zacw@2768
  1324
    
zacw@2768
  1325
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@2768
  1326
                            requestType:MGTwitterAccountRequest 
zacw@2768
  1327
                           responseType:MGTwitterStatus];	
zacw@2768
  1328
}
zacw@776
  1329
zacw@776
  1330
#pragma mark Sending and editing direct messages
zacw@776
  1331
zacw@776
  1332
zacw@776
  1333
- (NSString *)sendDirectMessage:(NSString *)message to:(NSString *)username
zacw@776
  1334
{
zacw@776
  1335
    if (!message || !username) {
zacw@776
  1336
        return nil;
zacw@776
  1337
    }
zacw@776
  1338
    
zacw@776
  1339
    NSString *path = @"direct_messages/new.xml";
zacw@776
  1340
    
zacw@776
  1341
    NSString *trimmedText = message;
zacw@776
  1342
    if ([trimmedText length] > MAX_MESSAGE_LENGTH) {
zacw@776
  1343
        trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH];
zacw@776
  1344
    }
zacw@776
  1345
    
zacw@776
  1346
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@776
  1347
    [params setObject:trimmedText forKey:@"text"];
zacw@776
  1348
    [params setObject:username forKey:@"user"];
zacw@776
  1349
    NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
zacw@776
  1350
    
zacw@776
  1351
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 
zacw@1681
  1352
                        queryParameters:nil body:body 
zacw@776
  1353
                            requestType:MGTwitterDirectMessageSend 
zacw@776
  1354
                           responseType:MGTwitterDirectMessage];
zacw@776
  1355
}
zacw@776
  1356
zacw@776
  1357
zacw@2499
  1358
- (NSString *)deleteDirectMessage:(NSString *)updateID
zacw@776
  1359
{
zacw@2499
  1360
    NSString *path = [NSString stringWithFormat:@"direct_messages/destroy/%@.xml", updateID];
zacw@776
  1361
    
zacw@776
  1362
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 
zacw@776
  1363
                            requestType:MGTwitterAccountRequest 
zacw@776
  1364
                           responseType:MGTwitterGeneric];
zacw@776
  1365
}
zacw@776
  1366
zacw@948
  1367
#pragma mark Adium Additions/Changes
zacw@930
  1368
zacw@930
  1369
#define MAX_NAME_LENGTH			20
zacw@930
  1370
#define MAX_EMAIL_LENGTH		40
zacw@930
  1371
#define MAX_URL_LENGTH			100
zacw@930
  1372
#define MAX_DESCRIPTION_LENGTH	160
zacw@930
  1373
zacw@948
  1374
- (NSString *)checkUserCredentials
zacw@948
  1375
{
zacw@948
  1376
    NSString *path = @"account/verify_credentials.xml";
zacw@948
  1377
    
zacw@948
  1378
    return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 
zacw@948
  1379
                            requestType:MGTwitterAccountRequest 
zacw@948
  1380
                           responseType:MGTwitterUser];
zacw@948
  1381
}
zacw@948
  1382
zacw@948
  1383
zacw@2499
  1384
- (NSString *)getRepliesSinceID:(NSString *)updateID startingAtPage:(int)pageNum
zacw@930
  1385
{
zacw@930
  1386
	NSString *path = @"statuses/replies.xml";
zacw@930
  1387
    
zacw@930
  1388
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@930
  1389
    if (updateID > 0) {
zacw@2500
  1390
        [params setObject:updateID forKey:@"since_id"];
zacw@930
  1391
    }
zacw@930
  1392
    if (pageNum > 0) {
zacw@930
  1393
        [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"];
zacw@930
  1394
    }
zacw@930
  1395
    
zacw@930
  1396
    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
zacw@930
  1397
                            requestType:MGTwitterRepliesRequest 
zacw@930
  1398
                           responseType:MGTwitterStatuses];
zacw@930
  1399
}
zacw@930
  1400
zacw@930
  1401
- (NSString *)updateProfileName:(NSString *)name
zacw@930
  1402
						  email:(NSString *)email
zacw@930
  1403
							url:(NSString *)url
zacw@930
  1404
					   location:(NSString *)location
zacw@930
  1405
					description:(NSString *)description
zacw@930
  1406
{
zacw@930
  1407
	if (!name && !email && !url && !location && !description) {
zacw@930
  1408
        return nil;
zacw@930
  1409
    }
zacw@930
  1410
    
zacw@930
  1411
    NSString *path = @"account/update_profile.xml";
zacw@930
  1412
    
zacw@930
  1413
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@930
  1414
	
zacw@930
  1415
	if (name) {
zacw@930
  1416
		if(name.length > MAX_NAME_LENGTH) {
zacw@930
  1417
			name = [name substringToIndex:MAX_NAME_LENGTH];
zacw@930
  1418
		}
zacw@930
  1419
		
zacw@930
  1420
		[params setObject:name forKey:@"name"];
zacw@930
  1421
	}
zacw@930
  1422
zacw@930
  1423
	if (email) {
zacw@930
  1424
		if(email.length > MAX_EMAIL_LENGTH) {
zacw@930
  1425
			email = [email substringToIndex:MAX_EMAIL_LENGTH];
zacw@930
  1426
		}
zacw@930
  1427
		
zacw@930
  1428
		[params setObject:email forKey:@"email"];
zacw@930
  1429
	}
zacw@930
  1430
	
zacw@930
  1431
	if (url) {
zacw@930
  1432
		if(url.length > MAX_URL_LENGTH) {
zacw@930
  1433
			url = [url substringToIndex:MAX_URL_LENGTH];
zacw@930
  1434
		}
zacw@930
  1435
		
zacw@930
  1436
		[params setObject:url forKey:@"url"];
zacw@930
  1437
	}
zacw@930
  1438
	
zacw@930
  1439
	if (location) {
zacw@930
  1440
		if(location.length > MAX_LOCATION_LENGTH) {
zacw@930
  1441
			location = [location substringToIndex:MAX_LOCATION_LENGTH];
zacw@930
  1442
		}
zacw@930
  1443
		
zacw@930
  1444
		[params setObject:location forKey:@"location"];
zacw@930
  1445
	}
zacw@930
  1446
	
zacw@930
  1447
	if (description) {
zacw@930
  1448
		if(description.length > MAX_DESCRIPTION_LENGTH) {
zacw@930
  1449
			description = [description substringToIndex:MAX_DESCRIPTION_LENGTH];
zacw@930
  1450
		}
zacw@930
  1451
		
zacw@930
  1452
		[params setObject:description forKey:@"description"];
zacw@930
  1453
	}
zacw@930
  1454
zacw@930
  1455
    NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO];
zacw@930
  1456
    
zacw@930
  1457
    return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 
zacw@1681
  1458
                        queryParameters:nil body:body 
zacw@930
  1459
                            requestType:MGTwitterAccountRequest 
zacw@930
  1460
                           responseType:MGTwitterUser];
zacw@930
  1461
}
zacw@776
  1462
zacw@962
  1463
- (NSString *)updateProfileImage:(NSData *)profileImage
zacw@962
  1464
{
zacw@1146
  1465
	if (!profileImage || _useOAuth) {
zacw@962
  1466
        return nil;
zacw@962
  1467
    }
zacw@962
  1468
    
zacw@962
  1469
    NSString *path = @"account/update_profile_image.xml";
zacw@962
  1470
	
zacw@962
  1471
	NSMutableData *body = [NSMutableData data];
zacw@962
  1472
	NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
zacw@962
  1473
	
zacw@962
  1474
	NSImage *image = [[[NSImage alloc] initWithData:profileImage] autorelease];
zacw@962
  1475
	
zacw@962
  1476
	NSBitmapImageRep *bitmapImageRep = nil;
zacw@962
  1477
	for(NSImageRep *imageRep in image.representations) {
zacw@962
  1478
		if([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
zacw@962
  1479
			bitmapImageRep = (NSBitmapImageRep *)imageRep;
zacw@962
  1480
		}
zacw@962
  1481
	}
zacw@962
  1482
	
zacw@962
  1483
	if(!bitmapImageRep) {
zacw@962
  1484
		return nil;
zacw@962
  1485
	}
zacw@962
  1486
	
zacw@962
  1487
	[body appendData:[[NSString stringWithFormat:@"--%@\r\n", MULTIPART_FORM_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]];
zacw@962
  1488
	[body appendData:[@"Content-Disposition: form-data; name=\"image\"; filename=\"adium_icon.png\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
zacw@962
  1489
	[body appendData:[@"Content-Type: image/png\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
zacw@962
  1490
	[body appendData:[bitmapImageRep representationUsingType:NSPNGFileType properties:nil]];
zacw@962
  1491
	[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", MULTIPART_FORM_BOUNDARY] dataUsingEncoding:NSUTF8StringEncoding]];
zacw@962
  1492
zacw@962
  1493
    return [self _sendRequestWithMethod:HTTP_MULTIPART_METHOD path:path 
zacw@962
  1494
                        queryParameters:params body:body 
zacw@962
  1495
                            requestType:MGTwitterAccountRequest 
zacw@962
  1496
                           responseType:MGTwitterUser];
zacw@962
  1497
}
zacw@962
  1498
zacw@1132
  1499
#pragma mark Adium OAuth Changes
zacw@1132
  1500
zacw@1132
  1501
- (NSString *)_sendRequestWithMethod:(NSString *)method 
zacw@1132
  1502
                                path:(NSString *)path 
zacw@1132
  1503
                     queryParameters:(NSDictionary *)params 
zacw@1132
  1504
                                body:(id)body 
zacw@1132
  1505
                         requestType:(MGTwitterRequestType)requestType 
zacw@1132
  1506
                        responseType:(MGTwitterResponseType)responseType
zacw@1132
  1507
{
zacw@1132
  1508
    // Construct appropriate URL string.
zacw@1132
  1509
    NSString *fullPath = path;
zacw@1132
  1510
    if (params) {
zacw@1132
  1511
        fullPath = [self _queryStringWithBase:fullPath parameters:params prefixed:YES];
zacw@1132
  1512
    }
zacw@1132
  1513
zacw@1132
  1514
	NSString *urlString = nil;
zacw@1132
  1515
	
zacw@1132
  1516
	if (!_useOAuth) {
zacw@1132
  1517
		#if SET_AUTHORIZATION_IN_HEADER
zacw@1132
  1518
			urlString = [NSString stringWithFormat:@"%@://%@/%@", 
zacw@1132
  1519
						 (_secureConnection) ? @"https" : @"http",
zacw@1132
  1520
						 _APIDomain, fullPath];
zacw@1132
  1521
		#else 
zacw@1132
  1522
			urlString = [NSString stringWithFormat:@"%@://%@:%@@%@/%@", 
zacw@1132
  1523
						 (_secureConnection) ? @"https" : @"http", 
zacw@1132
  1524
						 [self _encodeString:_username], [self _encodeString:_password], 
zacw@1132
  1525
						 _APIDomain, fullPath];
zacw@1132
  1526
		#endif
zacw@1132
  1527
	} else {
zacw@1132
  1528
		urlString = [NSString stringWithFormat:@"%@://%@/%@", 
zacw@1132
  1529
					 (_secureConnection) ? @"https" : @"http",
zacw@1132
  1530
					 _APIDomain, fullPath];		
zacw@1132
  1531
	}
zacw@1132
  1532
    
zacw@1132
  1533
    NSURL *finalURL = [NSURL URLWithString:urlString];
zacw@1132
  1534
    if (!finalURL) {
zacw@1132
  1535
        return nil;
zacw@1132
  1536
    }
zacw@1132
  1537
	
zacw@1132
  1538
	NSMutableURLRequest *theRequest = nil;
zacw@1132
  1539
	
zacw@1132
  1540
	if (_useOAuth) {
zacw@1132
  1541
		if (!_consumer || !_accessToken) {
zacw@1132
  1542
			NSLog(@"No consumer or access token, fail.");
zacw@1132
  1543
			return nil;
zacw@1132
  1544
		}
zacw@1132
  1545
		
zacw@1132
  1546
		theRequest = [[[OAMutableURLRequest alloc] initWithURL:finalURL
zacw@1132
  1547
													  consumer:_consumer
zacw@1132
  1548
														 token:_accessToken
zacw@1132
  1549
														 realm:nil
zacw@1132
  1550
											 signatureProvider:nil] autorelease];
zacw@1132
  1551
	} else {
zacw@1132
  1552
		// Construct an NSMutableURLRequest for the URL and set appropriate request method.
zacw@1132
  1553
		theRequest = [NSMutableURLRequest requestWithURL:finalURL 
zacw@1132
  1554
											 cachePolicy:NSURLRequestReloadIgnoringCacheData 
zacw@1132
  1555
										 timeoutInterval:URL_REQUEST_TIMEOUT];
zacw@1132
  1556
	}
zacw@1132
  1557
		
zacw@1132
  1558
	if(method && [method isEqualToString:HTTP_MULTIPART_METHOD]) {
zacw@1132
  1559
		method = HTTP_POST_METHOD;
zacw@1132
  1560
		[theRequest setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", MULTIPART_FORM_BOUNDARY] forHTTPHeaderField:@"Content-type"];
zacw@1132
  1561
	}
zacw@1132
  1562
	
zacw@1132
  1563
    if (method) {
zacw@1132
  1564
        [theRequest setHTTPMethod:method];
zacw@1132
  1565
    }
zacw@1132
  1566
	
zacw@1132
  1567
    [theRequest setHTTPShouldHandleCookies:NO];
zacw@1132
  1568
	
zacw@1132
  1569
    // Set headers for client information, for tracking purposes at Twitter.
zacw@1132
  1570
    [theRequest setValue:_clientName    forHTTPHeaderField:@"X-Twitter-Client"];
zacw@1132
  1571
    [theRequest setValue:_clientVersion forHTTPHeaderField:@"X-Twitter-Client-Version"];
zacw@1132
  1572
    [theRequest setValue:_clientURL     forHTTPHeaderField:@"X-Twitter-Client-URL"];
zacw@1132
  1573
    
zacw@1132
  1574
#if SET_AUTHORIZATION_IN_HEADER
zacw@1132
  1575
	if (_useOAuth && [self username] && [self password]) {
zacw@1132
  1576
		// Set header for HTTP Basic authentication explicitly, to avoid problems with proxies and other intermediaries
zacw@1132
  1577
		NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]];
zacw@1132
  1578
		NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
zacw@1132
  1579
		NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
zacw@1132
  1580
		[theRequest setValue:authValue forHTTPHeaderField:@"Authorization"];
zacw@1132
  1581
	}
zacw@1132
  1582
#endif
zacw@1132
  1583
	
zacw@1132
  1584
    // Set the request body if this is a POST request.
zacw@1132
  1585
    BOOL isPOST = (method && [method isEqualToString:HTTP_POST_METHOD]);
zacw@1132
  1586
	
zacw@1132
  1587
    if (isPOST) {
zacw@1132
  1588
        // Set request body, if specified (hopefully so), with 'source' parameter if appropriate.
zacw@1132
  1589
		if([body isKindOfClass:[NSString class]]) {
zacw@1132
  1590
			NSString *finalBody = @"";
zacw@1132
  1591
			if (body) {
zacw@1132
  1592
				finalBody = [finalBody stringByAppendingString:body];
zacw@1132
  1593
			}
zacw@1132
  1594
			if (_clientSourceToken) {
zacw@1132
  1595
				finalBody = [finalBody stringByAppendingString:[NSString stringWithFormat:@"%@source=%@", 
zacw@1132
  1596
																(body) ? @"&" : @"?" , 
zacw@1132
  1597
																_clientSourceToken]];
zacw@1132
  1598
			}
zacw@1132
  1599
			
zacw@1132
  1600
			if (finalBody) {
zacw@1132
  1601
				[theRequest setHTTPBody:[finalBody dataUsingEncoding:NSUTF8StringEncoding]];
zacw@1132
  1602
			}
zacw@1132
  1603
		} else if ([body isKindOfClass:[NSData class]]) {
zacw@1132
  1604
			[theRequest setHTTPBody:body];
zacw@1132
  1605
		}
zacw@1132
  1606
    }
zacw@1132
  1607
	
zacw@1132
  1608
	if (_useOAuth) {
zacw@1132
  1609
		[(OAMutableURLRequest *)theRequest prepare];
zacw@1132
  1610
    }
zacw@1132
  1611
    
zacw@1132
  1612
    // Create a connection using this request, with the default timeout and caching policy, 
zacw@1132
  1613
    // and appropriate Twitter request and response types for parsing and error reporting.
zacw@1132
  1614
    MGTwitterHTTPURLConnection *connection;
zacw@1132
  1615
    connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest 
zacw@1132
  1616
                                                            delegate:self 
zacw@1132
  1617
                                                         requestType:requestType 
zacw@1132
  1618
                                                        responseType:responseType];
zacw@1132
  1619
    
zacw@1132
  1620
    if (!connection) {
zacw@1132
  1621
        return nil;
zacw@1132
  1622
    } else {
zacw@1132
  1623
        [_connections setObject:connection forKey:[connection identifier]];
zacw@1132
  1624
        [connection release];
zacw@1132
  1625
    }
zacw@1132
  1626
    
zacw@1132
  1627
    return [connection identifier];
zacw@1132
  1628
}
zacw@1132
  1629
zacw@1132
  1630
@synthesize consumer = _consumer, accessToken = _accessToken, useOAuth = _useOAuth;
zacw@1132
  1631
zacw@776
  1632
@end