├── GrowlTwitterPrefs.nib ├── keyedobjects.nib ├── info.nib └── classes.nib ├── MGTwitterEngine ├── MGTwitterEngine_Prefix.pch ├── NSString+UUID.h ├── MGTwitterStatusesParser.h ├── MGTwitterUsersParser.h ├── MGTwitterMessagesParser.h ├── MGTwitterMiscLibXMLParser.h ├── MGTwitterUsersLibXMLParser.h ├── MGTwitterMiscParser.h ├── MGTwitterMessagesLibXMLParser.h ├── MGTwitterStatusesLibXMLParser.h ├── MGTwitterEngineGlobalHeader.h ├── NSString+UUID.m ├── MGTwitterParserDelegate.h ├── MGTwitterEngineDelegate.h ├── MGTwitterUsersLibXMLParser.m ├── MGTwitterStatusesLibXMLParser.m ├── MGTwitterXMLParser.h ├── MGTwitterHTTPURLConnection.h ├── MGTwitterMiscLibXMLParser.m ├── MGTwitterMiscParser.m ├── NSData+Base64.h ├── MGTwitterLibXMLParser.h ├── MGTwitterUsersParser.m ├── MGTwitterRequestTypes.h ├── MGTwitterMessagesParser.m ├── MGTwitterStatusesParser.m ├── MGTwitterHTTPURLConnection.m ├── MGTwitterMessagesLibXMLParser.m ├── NSData+Base64.m ├── MGTwitterXMLParser.m ├── MGTwitterEngine.h ├── Source Code License.rtf ├── README.txt ├── MGTwitterLibXMLParser.m └── MGTwitterEngine.m ├── GrowlTwitterDisplay.h ├── GrowlTwitterPrefs.h ├── Info.plist ├── GrowlTwitterPrefs.m └── GrowlTwitterDisplay.m /GrowlTwitterPrefs.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerhall/growl-twitter-plugin/master/GrowlTwitterPrefs.nib/keyedobjects.nib -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterEngine_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'MGTwitterEngine' target in the 'MGTwitterEngine' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /MGTwitterEngine/NSString+UUID.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+UUID.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 16/09/2007. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | @interface NSString (UUID) 12 | 13 | + (NSString*)stringWithNewUUID; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterStatusesParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterStatusesParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterXMLParser.h" 12 | 13 | @interface MGTwitterStatusesParser : MGTwitterXMLParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterUsersParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterUsersParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 19/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterStatusesParser.h" 12 | 13 | @interface MGTwitterUsersParser : MGTwitterStatusesParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMessagesParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMessagesParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 19/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterStatusesParser.h" 12 | 13 | @interface MGTwitterMessagesParser : MGTwitterStatusesParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMiscLibXMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMiscLibXMLParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterLibXMLParser.h" 12 | 13 | @interface MGTwitterMiscLibXMLParser : MGTwitterLibXMLParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterUsersLibXMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterUsersLibXMLParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterLibXMLParser.h" 12 | 13 | @interface MGTwitterUsersLibXMLParser : MGTwitterLibXMLParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMiscParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMiscParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 06/06/2008. 6 | // Copyright 2008 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterStatusesParser.h" 12 | 13 | @interface MGTwitterMiscParser : MGTwitterStatusesParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMessagesLibXMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMessagesLibXMLParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterLibXMLParser.h" 12 | 13 | @interface MGTwitterMessagesLibXMLParser : MGTwitterLibXMLParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterStatusesLibXMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterStatusesLibXMLParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterLibXMLParser.h" 12 | 13 | @interface MGTwitterStatusesLibXMLParser : MGTwitterLibXMLParser { 14 | 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterEngineGlobalHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterEngineGlobalHeader.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 09/08/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | /* 10 | This file conditionally includes the correct headers for either Mac OS X or iPhone deployment. 11 | */ 12 | 13 | #if TARGET_OS_IPHONE 14 | #import 15 | #import 16 | #else 17 | #import 18 | #endif 19 | -------------------------------------------------------------------------------- /GrowlTwitterDisplay.h: -------------------------------------------------------------------------------- 1 | // 2 | // GrowlTwitterDisplay.h 3 | // Growl Display Plugins 4 | // 5 | // Created by Tyler Hall. 6 | // Code based on GrowlSMSDisplay by by Diggory Laycock. 7 | // Copyright 2005-2009 The Growl Project All rights reserved. 8 | // 9 | 10 | #import 11 | #import "GrowlDisplayPlugin.h" 12 | #import "MGTwitterEngine.h" 13 | 14 | @interface GrowlTwitterDisplay: GrowlDisplayPlugin { 15 | MGTwitterEngine *twitterEngine; 16 | } 17 | 18 | - (void) displayNotification:(GrowlApplicationNotification *)notification; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /MGTwitterEngine/NSString+UUID.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+UUID.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 16/09/2007. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "NSString+UUID.h" 10 | 11 | 12 | @implementation NSString (UUID) 13 | 14 | 15 | + (NSString*)stringWithNewUUID 16 | { 17 | // Create a new UUID 18 | CFUUIDRef uuidObj = CFUUIDCreate(nil); 19 | 20 | // Get the string representation of the UUID 21 | NSString *newUUID = (NSString*)CFUUIDCreateString(nil, uuidObj); 22 | CFRelease(uuidObj); 23 | return [newUUID autorelease]; 24 | } 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /GrowlTwitterPrefs.h: -------------------------------------------------------------------------------- 1 | // 2 | // GrowlTwitterPrefs.h 3 | // Display Plugins 4 | // 5 | // Created by Tyler Hall. 6 | // Code based on GrowlSMSDisplay by by Diggory Laycock. 7 | // Copyright 2005-2009 The Growl Project All rights reserved. 8 | // 9 | 10 | #import 11 | 12 | @interface GrowlTwitterPrefs: NSPreferencePane { 13 | } 14 | 15 | - (NSString *) getUsername; 16 | - (void) setUsername:(NSString *)value; 17 | 18 | - (NSString *) getPassword; 19 | - (void) setPassword:(NSString *)value; 20 | 21 | - (NSString *) getTweetPrefix; 22 | - (void) setTweetPrefix:(NSString *)value; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /GrowlTwitterPrefs.nib/info.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IBFramework Version 6 | 672 7 | IBLastKnownRelativeProjectPath 8 | ../../../../Growl.xcodeproj 9 | IBOldestOS 10 | 3 11 | IBOpenObjects 12 | 13 | 6 14 | 15 | IBSystem Version 16 | 9G55 17 | targetFramework 18 | IBCocoaFramework 19 | 20 | 21 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterParserDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterParserDelegate.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 18/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterRequestTypes.h" 12 | 13 | @protocol MGTwitterParserDelegate 14 | 15 | - (void)parsingSucceededForRequest:(NSString *)identifier 16 | ofResponseType:(MGTwitterResponseType)responseType 17 | withParsedObjects:(NSArray *)parsedObjects; 18 | 19 | - (void)parsingFailedForRequest:(NSString *)requestIdentifier 20 | ofResponseType:(MGTwitterResponseType)responseType 21 | withError:(NSError *)error; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterEngineDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterEngineDelegate.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 16/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | @protocol MGTwitterEngineDelegate 12 | 13 | - (void)requestSucceeded:(NSString *)requestIdentifier; 14 | - (void)requestFailed:(NSString *)requestIdentifier withError:(NSError *)error; 15 | 16 | - (void)statusesReceived:(NSArray *)statuses forRequest:(NSString *)identifier; 17 | - (void)directMessagesReceived:(NSArray *)messages forRequest:(NSString *)identifier; 18 | - (void)userInfoReceived:(NSArray *)userInfo forRequest:(NSString *)identifier; 19 | - (void)miscInfoReceived:(NSArray *)miscInfo forRequest:(NSString *)identifier; 20 | 21 | #if TARGET_OS_IPHONE 22 | - (void)imageReceived:(UIImage *)image forRequest:(NSString *)identifier; 23 | #else 24 | - (void)imageReceived:(NSImage *)image forRequest:(NSString *)identifier; 25 | #endif 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | Twitter 9 | CFBundleIdentifier 10 | com.Growl.Twitter 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | DISP 15 | CFBundleSignature 16 | GRRR 17 | CFBundleVersion 18 | 1.0 19 | CSResourcesFileMapped 20 | yes 21 | NSPrincipalClass 22 | GrowlTwitterDisplay 23 | CFBundleName 24 | Twitter 25 | GrowlPluginAuthor 26 | Tyler Hall 27 | GrowlPluginDescription 28 | Send notifications via Twitter 29 | 30 | 31 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterUsersLibXMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterUsersLibXMLParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterUsersLibXMLParser.h" 10 | 11 | 12 | @implementation MGTwitterUsersLibXMLParser 13 | 14 | - (void)parse 15 | { 16 | int readerResult = xmlTextReaderRead(_reader); 17 | if (readerResult != 1) 18 | return; 19 | int nodeType = xmlTextReaderNodeType(_reader); 20 | const xmlChar *name = xmlTextReaderConstName(_reader); 21 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(BAD_CAST "users", name))) 22 | { 23 | if (nodeType == XML_READER_TYPE_ELEMENT) 24 | { 25 | if (xmlStrEqual(name, BAD_CAST "user")) 26 | { 27 | [parsedObjects addObject:[self _userDictionaryForNodeWithName:name]]; 28 | } 29 | } 30 | 31 | // advance reader 32 | int readerResult = xmlTextReaderRead(_reader); 33 | if (readerResult != 1) 34 | { 35 | break; 36 | } 37 | nodeType = xmlTextReaderNodeType(_reader); 38 | name = xmlTextReaderConstName(_reader); 39 | } 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterStatusesLibXMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterStatusesLibXMLParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterStatusesLibXMLParser.h" 10 | 11 | 12 | @implementation MGTwitterStatusesLibXMLParser 13 | 14 | 15 | - (void)parse 16 | { 17 | int readerResult = xmlTextReaderRead(_reader); 18 | if (readerResult != 1) 19 | return; 20 | int nodeType = xmlTextReaderNodeType(_reader); 21 | const xmlChar *name = xmlTextReaderConstName(_reader); 22 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(BAD_CAST "statuses", name))) 23 | { 24 | if (nodeType == XML_READER_TYPE_ELEMENT) 25 | { 26 | if (xmlStrEqual(name, BAD_CAST "status")) 27 | { 28 | [parsedObjects addObject:[self _statusDictionaryForNodeWithName:name]]; 29 | } 30 | } 31 | 32 | // advance reader 33 | int readerResult = xmlTextReaderRead(_reader); 34 | if (readerResult != 1) 35 | { 36 | break; 37 | } 38 | nodeType = xmlTextReaderNodeType(_reader); 39 | name = xmlTextReaderConstName(_reader); 40 | } 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterXMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterXMLParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 18/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterParserDelegate.h" 12 | 13 | @interface MGTwitterXMLParser : NSObject { 14 | __weak NSObject *delegate; // weak ref 15 | NSString *identifier; 16 | MGTwitterRequestType requestType; 17 | MGTwitterResponseType responseType; 18 | NSData *xml; 19 | NSMutableArray *parsedObjects; 20 | NSXMLParser *parser; 21 | __weak NSMutableDictionary *currentNode; 22 | NSString *lastOpenedElement; 23 | } 24 | 25 | + (id)parserWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 26 | connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType 27 | responseType:(MGTwitterResponseType)respType; 28 | - (id)initWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 29 | connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType 30 | responseType:(MGTwitterResponseType)respType; 31 | 32 | - (NSString *)lastOpenedElement; 33 | - (void)setLastOpenedElement:(NSString *)value; 34 | 35 | - (void)addSource; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterHTTPURLConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterHTTPURLConnection.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 16/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterRequestTypes.h" 12 | 13 | @interface MGTwitterHTTPURLConnection : NSURLConnection { 14 | NSMutableData *_data; // accumulated data received on this connection 15 | MGTwitterRequestType _requestType; // general type of this request, mostly for error handling 16 | MGTwitterResponseType _responseType; // type of response data expected (if successful) 17 | NSString *_identifier; 18 | NSURL *_URL; // the URL used for the connection (needed as a base URL when parsing with libxml) 19 | } 20 | 21 | // Initializer 22 | - (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate 23 | requestType:(MGTwitterRequestType)requestType responseType:(MGTwitterResponseType)responseType; 24 | 25 | // Data helper methods 26 | - (void)resetDataLength; 27 | - (void)appendData:(NSData *)data; 28 | 29 | // Accessors 30 | - (NSString *)identifier; 31 | - (NSData *)data; 32 | - (NSURL *)URL; 33 | - (MGTwitterRequestType)requestType; 34 | - (MGTwitterResponseType)responseType; 35 | - (NSString *)description; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMiscLibXMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMiscLibXMLParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterMiscLibXMLParser.h" 10 | 11 | 12 | @implementation MGTwitterMiscLibXMLParser 13 | 14 | - (void)parse 15 | { 16 | int readerResult = xmlTextReaderRead(_reader); 17 | if (readerResult != 1) 18 | return; 19 | 20 | int nodeType = xmlTextReaderNodeType(_reader); 21 | const xmlChar *name = xmlTextReaderConstName(_reader); 22 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT)) 23 | { 24 | if (nodeType == XML_READER_TYPE_ELEMENT) 25 | { 26 | if (xmlStrEqual(name, BAD_CAST "hash")) 27 | { 28 | [parsedObjects addObject:[self _hashDictionaryForNodeWithName:name]]; 29 | } 30 | else 31 | { 32 | // process element as a string -- API calls like friendships/exists.xml just return false or true 33 | NSString *string = [self _nodeValueAsString]; 34 | if (string) 35 | { 36 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 37 | [dictionary setObject:string forKey:[NSString stringWithUTF8String:(const char *)name]]; 38 | [parsedObjects addObject:dictionary]; 39 | } 40 | } 41 | } 42 | 43 | // advance reader 44 | int readerResult = xmlTextReaderRead(_reader); 45 | if (readerResult != 1) 46 | { 47 | break; 48 | } 49 | nodeType = xmlTextReaderNodeType(_reader); 50 | name = xmlTextReaderConstName(_reader); 51 | } 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMiscParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMiscParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 06/06/2008. 6 | // Copyright 2008 Instinctive Code. All rights reserved. 7 | // 8 | 9 | #import "MGTwitterMiscParser.h" 10 | 11 | 12 | @implementation MGTwitterMiscParser 13 | 14 | 15 | #pragma mark NSXMLParser delegate methods 16 | 17 | 18 | - (void)parser:(NSXMLParser *)theParser didStartElement:(NSString *)elementName 19 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 20 | attributes:(NSDictionary *)attributeDict 21 | { 22 | //NSLog(@"Started element: %@ (%@)", elementName, attributeDict); 23 | [self setLastOpenedElement:elementName]; 24 | 25 | if (!currentNode) { 26 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 27 | [parsedObjects addObject:newNode]; 28 | currentNode = newNode; 29 | } 30 | 31 | // Create relevant name-value pair. 32 | [currentNode setObject:[NSMutableString string] forKey:elementName]; 33 | } 34 | 35 | 36 | - (void)parser:(NSXMLParser *)theParser didEndElement:(NSString *)elementName 37 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 38 | { 39 | [super parser:theParser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName]; 40 | 41 | if ([elementName isEqualToString:@"remaining_hits"]) { 42 | NSNumber *hits = [NSNumber numberWithInt:[[currentNode objectForKey:elementName] intValue]]; 43 | [currentNode setObject:hits forKey:elementName]; 44 | } 45 | } 46 | 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /MGTwitterEngine/NSData+Base64.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Base64.m 3 | // 4 | // Derived from http://colloquy.info/project/browser/trunk/NSDataAdditions.h?rev=1576 5 | // Created by khammond on Mon Oct 29 2001. 6 | // Formatted by Timothy Hatcher on Sun Jul 4 2004. 7 | // Copyright (c) 2001 Kyle Hammond. All rights reserved. 8 | // Original development by Dave Winer. 9 | // 10 | 11 | #import "MGTwitterEngineGlobalHeader.h" 12 | 13 | @interface NSData (Base64) 14 | 15 | /*! @function +dataWithBase64EncodedString: 16 | @discussion This method returns an autoreleased NSData object. The NSData object is initialized with the 17 | contents of the Base 64 encoded string. This is a convenience method. 18 | @param inBase64String An NSString object that contains only Base 64 encoded data. 19 | @result The NSData object. */ 20 | + (NSData *) dataWithBase64EncodedString:(NSString *) string; 21 | 22 | /*! @function -initWithBase64EncodedString: 23 | @discussion The NSData object is initialized with the contents of the Base 64 encoded string. 24 | This method returns self as a convenience. 25 | @param inBase64String An NSString object that contains only Base 64 encoded data. 26 | @result This method returns self. */ 27 | - (id) initWithBase64EncodedString:(NSString *) string; 28 | 29 | /*! @function -base64EncodingWithLineLength: 30 | @discussion This method returns a Base 64 encoded string representation of the data object. 31 | @param inLineLength A value of zero means no line breaks. This is crunched to a multiple of 4 (the next 32 | one greater than inLineLength). 33 | @result The base 64 encoded data. */ 34 | - (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength; 35 | 36 | @end -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterLibXMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterLibXMLParser.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 18/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | #include 11 | 12 | #import "MGTwitterParserDelegate.h" 13 | 14 | @interface MGTwitterLibXMLParser : NSObject { 15 | __weak NSObject *delegate; // weak ref 16 | NSString *identifier; 17 | MGTwitterRequestType requestType; 18 | MGTwitterResponseType responseType; 19 | NSURL *URL; 20 | NSData *xml; 21 | NSMutableArray *parsedObjects; 22 | 23 | xmlTextReaderPtr _reader; 24 | } 25 | 26 | + (id)parserWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 27 | connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType 28 | responseType:(MGTwitterResponseType)respType URL:(NSURL *)URL; 29 | - (id)initWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 30 | connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType 31 | responseType:(MGTwitterResponseType)respType URL:(NSURL *)URL; 32 | 33 | - (void)parse; 34 | 35 | // subclass utilities 36 | - (xmlChar *)_nodeValue; 37 | - (NSString *)_nodeValueAsString; 38 | - (NSDate *)_nodeValueAsDate; 39 | - (NSNumber *)_nodeValueAsInt; 40 | - (NSNumber *)_nodeValueAsBool; 41 | - (NSDictionary *)_statusDictionaryForNodeWithName:(const xmlChar *)parentNodeName; 42 | - (NSDictionary *)_userDictionaryForNodeWithName:(const xmlChar *)parentNodeName; 43 | - (NSDictionary *)_hashDictionaryForNodeWithName:(const xmlChar *)parentNodeName; 44 | 45 | // delegate callbacks 46 | - (void)_parsingDidEnd; 47 | - (void)_parsingErrorOccurred:(NSError *)parseError; 48 | 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /GrowlTwitterPrefs.nib/classes.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IBClasses 6 | 7 | 8 | CLASS 9 | NSObject 10 | LANGUAGE 11 | ObjC 12 | 13 | 14 | CLASS 15 | NSWindow 16 | LANGUAGE 17 | ObjC 18 | SUPERCLASS 19 | NSResponder 20 | 21 | 22 | CLASS 23 | NSView 24 | LANGUAGE 25 | ObjC 26 | SUPERCLASS 27 | NSResponder 28 | 29 | 30 | CLASS 31 | FirstResponder 32 | LANGUAGE 33 | ObjC 34 | SUPERCLASS 35 | NSObject 36 | 37 | 38 | CLASS 39 | NSPreferencePane 40 | LANGUAGE 41 | ObjC 42 | OUTLETS 43 | 44 | _firstKeyView 45 | NSView 46 | _initialKeyView 47 | NSView 48 | _lastKeyView 49 | NSView 50 | _window 51 | NSWindow 52 | 53 | SUPERCLASS 54 | NSObject 55 | 56 | 57 | CLASS 58 | GrowlSMSPrefs 59 | LANGUAGE 60 | ObjC 61 | SUPERCLASS 62 | NSPreferencePane 63 | 64 | 65 | IBVersion 66 | 1 67 | 68 | 69 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterUsersParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterUsersParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 19/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterUsersParser.h" 10 | 11 | 12 | @implementation MGTwitterUsersParser 13 | 14 | 15 | #pragma mark NSXMLParser delegate methods 16 | 17 | 18 | - (void)parser:(NSXMLParser *)theParser didStartElement:(NSString *)elementName 19 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 20 | attributes:(NSDictionary *)attributeDict 21 | { 22 | //NSLog(@"Started element: %@ (%@)", elementName, attributeDict); 23 | [self setLastOpenedElement:elementName]; 24 | 25 | if ([elementName isEqualToString:@"user"]) { 26 | // Make new entry in parsedObjects. 27 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 28 | [parsedObjects addObject:newNode]; 29 | currentNode = newNode; 30 | } else if ([elementName isEqualToString:@"status"]) { 31 | // Add an appropriate dictionary to current node. 32 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 33 | [currentNode setObject:newNode forKey:elementName]; 34 | currentNode = newNode; 35 | } else if (currentNode) { 36 | // Create relevant name-value pair. 37 | [currentNode setObject:[NSMutableString string] forKey:elementName]; 38 | } 39 | } 40 | 41 | 42 | - (void)parser:(NSXMLParser *)theParser didEndElement:(NSString *)elementName 43 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 44 | { 45 | [super parser:theParser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName]; 46 | 47 | if ([elementName isEqualToString:@"status"]) { 48 | currentNode = [parsedObjects lastObject]; 49 | } else if ([elementName isEqualToString:@"user"]) { 50 | [self addSource]; 51 | currentNode = nil; 52 | } 53 | } 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterRequestTypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterEngineDelegate.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 17/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | typedef enum _MGTwitterRequestType { 12 | MGTwitterStatusesRequest = 0, // all status requests, excluding replies and direct messages 13 | MGTwitterRepliesRequest = 1, // status requests which are specifically for replies 14 | MGTwitterDirectMessagesRequest = 2, // all direct message requests, including sent messages 15 | MGTwitterAccountRequest = 3, // credentials, session, follow/leave, notifications, favorites, deletions 16 | MGTwitterUserInfoRequest = 4, // requests for one or more users' info, including featured users 17 | MGTwitterStatusSend = 5, // sending a new status 18 | MGTwitterDirectMessageSend = 6, // sending a new direct message 19 | MGTwitterImageRequest = 7, // requesting an image 20 | } MGTwitterRequestType; 21 | 22 | typedef enum _MGTwitterResponseType { 23 | MGTwitterStatuses = 0, // one or more statuses 24 | MGTwitterStatus = 1, // exactly one status 25 | MGTwitterUsers = 2, // one or more user's information 26 | MGTwitterUser = 3, // info for exactly one user 27 | MGTwitterDirectMessages = 4, // one or more direct messages 28 | MGTwitterDirectMessage = 5, // exactly one direct message 29 | MGTwitterGeneric = 6, // a generic response not requiring parsing 30 | MGTwitterMiscellaneous = 8, // a miscellaneous response of key-value pairs 31 | MGTwitterImage = 7, // an image 32 | } MGTwitterResponseType; 33 | 34 | // This key is added to each tweet or direct message returned, 35 | // with an NSNumber value containing an MGTwitterRequestType. 36 | // This is designed to help client applications aggregate updates. 37 | #define TWITTER_SOURCE_REQUEST_TYPE @"source_api_request_type" 38 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMessagesParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMessagesParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 19/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterMessagesParser.h" 10 | 11 | 12 | @implementation MGTwitterMessagesParser 13 | 14 | 15 | #pragma mark NSXMLParser delegate methods 16 | 17 | 18 | - (void)parser:(NSXMLParser *)theParser didStartElement:(NSString *)elementName 19 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 20 | attributes:(NSDictionary *)attributeDict 21 | { 22 | //NSLog(@"Started element: %@ (%@)", elementName, attributeDict); 23 | [self setLastOpenedElement:elementName]; 24 | 25 | if ([elementName isEqualToString:@"direct_message"]) { 26 | // Make new entry in parsedObjects. 27 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 28 | [parsedObjects addObject:newNode]; 29 | currentNode = newNode; 30 | } else if ([elementName isEqualToString:@"sender"] || [elementName isEqualToString:@"recipient"]) { 31 | // Add an appropriate dictionary to current node. 32 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 33 | [currentNode setObject:newNode forKey:elementName]; 34 | currentNode = newNode; 35 | } else if (currentNode) { 36 | // Create relevant name-value pair. 37 | [currentNode setObject:[NSMutableString string] forKey:elementName]; 38 | } 39 | } 40 | 41 | 42 | - (void)parser:(NSXMLParser *)theParser didEndElement:(NSString *)elementName 43 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 44 | { 45 | [super parser:theParser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName]; 46 | 47 | if ([elementName isEqualToString:@"sender"] || [elementName isEqualToString:@"recipient"]) { 48 | currentNode = [parsedObjects lastObject]; 49 | } else if ([elementName isEqualToString:@"direct_message"]) { 50 | [self addSource]; 51 | currentNode = nil; 52 | } 53 | } 54 | 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterStatusesParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterStatusesParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterStatusesParser.h" 10 | 11 | 12 | @implementation MGTwitterStatusesParser 13 | 14 | 15 | #pragma mark NSXMLParser delegate methods 16 | 17 | 18 | - (void)parser:(NSXMLParser *)theParser didStartElement:(NSString *)elementName 19 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 20 | attributes:(NSDictionary *)attributeDict 21 | { 22 | //NSLog(@"Started element: %@ (%@)", elementName, attributeDict); 23 | [self setLastOpenedElement:elementName]; 24 | 25 | if ([elementName isEqualToString:@"status"]) { 26 | // Make new entry in parsedObjects. 27 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 28 | [parsedObjects addObject:newNode]; 29 | currentNode = newNode; 30 | } else if ([elementName isEqualToString:@"user"]) { 31 | // Add a 'user' dictionary to current node. 32 | NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0]; 33 | [currentNode setObject:newNode forKey:elementName]; 34 | currentNode = newNode; 35 | } else if (currentNode) { 36 | // Create relevant name-value pair. 37 | [currentNode setObject:[NSMutableString string] forKey:elementName]; 38 | } 39 | } 40 | 41 | 42 | - (void)parser:(NSXMLParser *)theParser foundCharacters:(NSString *)characters 43 | { 44 | //NSLog(@"Found characters: %@", characters); 45 | // Append found characters to value of lastOpenedElement in currentNode. 46 | if (lastOpenedElement && currentNode) { 47 | [[currentNode objectForKey:lastOpenedElement] appendString:characters]; 48 | } 49 | } 50 | 51 | 52 | - (void)parser:(NSXMLParser *)theParser didEndElement:(NSString *)elementName 53 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 54 | { 55 | [super parser:theParser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName]; 56 | 57 | if ([elementName isEqualToString:@"user"]) { 58 | currentNode = [parsedObjects lastObject]; 59 | } else if ([elementName isEqualToString:@"status"]) { 60 | [self addSource]; 61 | currentNode = nil; 62 | } 63 | } 64 | 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterHTTPURLConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterHTTPURLConnection.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 16/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterHTTPURLConnection.h" 10 | #import "NSString+UUID.h" 11 | 12 | 13 | @implementation MGTwitterHTTPURLConnection 14 | 15 | 16 | #pragma mark Initializer 17 | 18 | 19 | - (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate 20 | requestType:(MGTwitterRequestType)requestType responseType:(MGTwitterResponseType)responseType 21 | { 22 | if (self = [super initWithRequest:request delegate:delegate]) { 23 | _data = [[NSMutableData alloc] initWithCapacity:0]; 24 | _identifier = [[NSString stringWithNewUUID] retain]; 25 | _requestType = requestType; 26 | _responseType = responseType; 27 | _URL = [[request URL] retain]; 28 | } 29 | 30 | return self; 31 | } 32 | 33 | 34 | - (void)dealloc 35 | { 36 | [_data release]; 37 | [_identifier release]; 38 | [_URL release]; 39 | [super dealloc]; 40 | } 41 | 42 | 43 | #pragma mark Data helper methods 44 | 45 | 46 | - (void)resetDataLength 47 | { 48 | [_data setLength:0]; 49 | } 50 | 51 | 52 | - (void)appendData:(NSData *)data 53 | { 54 | [_data appendData:data]; 55 | } 56 | 57 | 58 | #pragma mark Accessors 59 | 60 | 61 | - (NSString *)identifier 62 | { 63 | return [[_identifier retain] autorelease]; 64 | } 65 | 66 | 67 | - (NSData *)data 68 | { 69 | return [[_data retain] autorelease]; 70 | } 71 | 72 | 73 | - (NSURL *)URL 74 | { 75 | return [[_URL retain] autorelease]; 76 | } 77 | 78 | 79 | - (MGTwitterRequestType)requestType 80 | { 81 | return _requestType; 82 | } 83 | 84 | 85 | - (MGTwitterResponseType)responseType 86 | { 87 | return _responseType; 88 | } 89 | 90 | 91 | - (NSString *)description 92 | { 93 | NSString *description = [super description]; 94 | 95 | switch (_requestType) { 96 | case MGTwitterStatusesRequest: 97 | description = @"Twitter statuses timeline request"; 98 | break; 99 | case MGTwitterDirectMessagesRequest: 100 | description = @"Twitter direct messages timeline request"; 101 | break; 102 | case MGTwitterAccountRequest: 103 | description = @"Twitter account action request"; 104 | break; 105 | case MGTwitterUserInfoRequest: 106 | description = @"Twitter user information request"; 107 | break; 108 | case MGTwitterStatusSend: 109 | description = @"Twitter status send"; 110 | break; 111 | case MGTwitterDirectMessageSend: 112 | description = @"Twitter direct message send"; 113 | break; 114 | default: 115 | description = @"Twitter other action"; 116 | break; 117 | } 118 | 119 | return [description stringByAppendingFormat:@" (%@)", _identifier]; 120 | } 121 | 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterMessagesLibXMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterMessagesLibXMLParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 11/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterMessagesLibXMLParser.h" 10 | 11 | 12 | @implementation MGTwitterMessagesLibXMLParser 13 | 14 | - (NSDictionary *)_directMessageDictionaryForNodeWithName:(const xmlChar *)parentNodeName { 15 | if (xmlTextReaderIsEmptyElement(_reader)) 16 | return nil; 17 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 18 | 19 | int readerResult = xmlTextReaderRead(_reader); 20 | if (readerResult != 1) 21 | return nil; 22 | int nodeType = xmlTextReaderNodeType(_reader); 23 | const xmlChar *name = xmlTextReaderConstName(_reader); 24 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(parentNodeName, name))) 25 | { 26 | if (nodeType == XML_READER_TYPE_ELEMENT) 27 | { 28 | if (xmlStrEqual(name, BAD_CAST "sender") || xmlStrEqual(name, BAD_CAST "recipient")) 29 | { 30 | // "user" is the name of a sub-dictionary in each item 31 | [dictionary setObject:[self _userDictionaryForNodeWithName:name] forKey:[NSString stringWithUTF8String:(const char *)name]]; 32 | } 33 | else if (xmlStrEqual(name, BAD_CAST "id") || xmlStrEqual(name, BAD_CAST "sender_id") || xmlStrEqual(name, BAD_CAST "recipient_id")) 34 | { 35 | // process element as an integer 36 | NSNumber *number = [self _nodeValueAsInt]; 37 | if (number) 38 | { 39 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 40 | } 41 | } 42 | else if (xmlStrEqual(name, BAD_CAST "created_at")) 43 | { 44 | // process element as a date 45 | NSDate *date = [self _nodeValueAsDate]; 46 | if (date) 47 | { 48 | [dictionary setObject:date forKey:[NSString stringWithUTF8String:(const char *)name]]; 49 | } 50 | } 51 | else if (xmlStrEqual(name, BAD_CAST "protected")) 52 | { 53 | // process element as a boolean 54 | NSNumber *number = [self _nodeValueAsBool]; 55 | if (number) 56 | { 57 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 58 | } 59 | } 60 | else 61 | { 62 | // process element as a string 63 | NSString *string = [self _nodeValueAsString]; 64 | if (string) 65 | { 66 | [dictionary setObject:string forKey:[NSString stringWithUTF8String:(const char *)name]]; 67 | } 68 | } 69 | } 70 | 71 | // advance reader 72 | int readerResult = xmlTextReaderRead(_reader); 73 | if (readerResult != 1) 74 | break; 75 | nodeType = xmlTextReaderNodeType(_reader); 76 | name = xmlTextReaderConstName(_reader); 77 | } 78 | 79 | // save the request type in the tweet 80 | [dictionary setObject:[NSNumber numberWithInt:requestType] forKey:TWITTER_SOURCE_REQUEST_TYPE]; 81 | 82 | return dictionary; 83 | } 84 | 85 | 86 | - (void)parse 87 | { 88 | int readerResult = xmlTextReaderRead(_reader); 89 | if (readerResult != 1) 90 | return; 91 | int nodeType = xmlTextReaderNodeType(_reader); 92 | const xmlChar *name = xmlTextReaderConstName(_reader); 93 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(BAD_CAST "direct-messages", name))) 94 | { 95 | if (nodeType == XML_READER_TYPE_ELEMENT) 96 | { 97 | if (xmlStrEqual(name, BAD_CAST "direct_message")) 98 | { 99 | [parsedObjects addObject:[self _directMessageDictionaryForNodeWithName:BAD_CAST "direct_message"]]; 100 | } 101 | } 102 | 103 | // advance reader 104 | int readerResult = xmlTextReaderRead(_reader); 105 | if (readerResult != 1) 106 | { 107 | break; 108 | } 109 | nodeType = xmlTextReaderNodeType(_reader); 110 | name = xmlTextReaderConstName(_reader); 111 | } 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /GrowlTwitterPrefs.m: -------------------------------------------------------------------------------- 1 | // 2 | // GrowlTwitterPrefs.m 3 | // Display Plugins 4 | // 5 | // Created by Tyler Hall. 6 | // Code based on GrowlSMSDisplay by by Diggory Laycock. 7 | // Copyright 2005-2009 The Growl Project All rights reserved. 8 | // 9 | 10 | #import "GrowlTwitterPrefs.h" 11 | #import "GrowlDefinesInternal.h" 12 | #import "NSStringAdditions.h" 13 | #import 14 | #import 15 | 16 | #define GrowlTwitterPrefDomain @"com.Growl.Twitter" 17 | #define usernameKey @"Twitter - Username" 18 | #define tweetPrefixKey @"Twitter - Tweet Prefix" 19 | 20 | #define keychainServiceName "GrowlTwitter" 21 | #define keychainAccountName "TwitterWebServicePassword" 22 | 23 | 24 | @implementation GrowlTwitterPrefs 25 | 26 | - (NSString *) mainNibName { 27 | return @"GrowlTwitterPrefs"; 28 | } 29 | 30 | - (void) didSelect { 31 | SYNCHRONIZE_GROWL_PREFS(); 32 | } 33 | 34 | #pragma mark - 35 | 36 | - (NSString *) getUsername { 37 | NSString *value = nil; 38 | READ_GROWL_PREF_VALUE(usernameKey, GrowlTwitterPrefDomain, NSString *, &value); 39 | return [value autorelease]; 40 | } 41 | 42 | - (void) setUsername:(NSString *)value { 43 | if (!value) 44 | value = @""; 45 | WRITE_GROWL_PREF_VALUE(usernameKey, value, GrowlTwitterPrefDomain); 46 | UPDATE_GROWL_PREFS(); 47 | } 48 | 49 | - (NSString *) getPassword { 50 | unsigned char *password; 51 | UInt32 passwordLength; 52 | OSStatus status; 53 | status = SecKeychainFindGenericPassword( NULL, 54 | strlen(keychainServiceName), keychainServiceName, 55 | strlen(keychainAccountName), keychainAccountName, 56 | &passwordLength, (void **)&password, NULL ); 57 | 58 | NSString *passwordString; 59 | if (status == noErr) { 60 | passwordString = (NSString *)CFStringCreateWithBytes(kCFAllocatorDefault, password, passwordLength, kCFStringEncodingUTF8, false); 61 | [passwordString autorelease]; 62 | SecKeychainItemFreeContent(NULL, password); 63 | } else { 64 | if (status != errSecItemNotFound) 65 | NSLog(@"Failed to retrieve Twitter Account password from keychain. Error: %d", status); 66 | passwordString = @""; 67 | } 68 | 69 | return passwordString; 70 | } 71 | 72 | - (void) setPassword:(NSString *)value { 73 | const char *password = value ? [value UTF8String] : ""; 74 | unsigned length = strlen(password); 75 | OSStatus status; 76 | SecKeychainItemRef itemRef = nil; 77 | status = SecKeychainFindGenericPassword( NULL, 78 | strlen(keychainServiceName), keychainServiceName, 79 | strlen(keychainAccountName), keychainAccountName, 80 | NULL, NULL, &itemRef ); 81 | if (status == errSecItemNotFound) { 82 | // add new item 83 | status = SecKeychainAddGenericPassword( NULL, 84 | strlen(keychainServiceName), keychainServiceName, 85 | strlen(keychainAccountName), keychainAccountName, 86 | length, password, NULL ); 87 | if (status) 88 | NSLog(@"Failed to add Twitter password to keychain."); 89 | } else { 90 | // change existing password 91 | SecKeychainAttribute attrs[] = { 92 | { kSecAccountItemAttr, strlen(keychainAccountName), (char *)keychainAccountName }, 93 | { kSecServiceItemAttr, strlen(keychainServiceName), (char *)keychainServiceName } 94 | }; 95 | const SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]), attrs }; 96 | status = SecKeychainItemModifyAttributesAndData( itemRef, // the item reference 97 | &attributes, // no change to attributes 98 | length, // length of password 99 | password // pointer to password data 100 | ); 101 | if (itemRef) 102 | CFRelease(itemRef); 103 | if (status) 104 | NSLog(@"Failed to change Twitter password in keychain."); 105 | } 106 | } 107 | 108 | - (NSString *) getTweetPrefix { 109 | NSString *value = nil; 110 | READ_GROWL_PREF_VALUE(tweetPrefixKey, GrowlTwitterPrefDomain, NSString *, &value); 111 | return [value autorelease]; 112 | } 113 | 114 | - (void) setTweetPrefix:(NSString *)value { 115 | if (!value) 116 | value = @""; 117 | WRITE_GROWL_PREF_VALUE(tweetPrefixKey, value, GrowlTwitterPrefDomain); 118 | UPDATE_GROWL_PREFS(); 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /MGTwitterEngine/NSData+Base64.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Base64.m 3 | // 4 | // Derived from http://colloquy.info/project/browser/trunk/NSDataAdditions.h?rev=1576 5 | // Created by khammond on Mon Oct 29 2001. 6 | // Formatted by Timothy Hatcher on Sun Jul 4 2004. 7 | // Copyright (c) 2001 Kyle Hammond. All rights reserved. 8 | // Original development by Dave Winer. 9 | // 10 | 11 | #import "NSData+Base64.h" 12 | 13 | #import 14 | 15 | static char encodingTable[64] = { 16 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 17 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 18 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 19 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; 20 | 21 | @implementation NSData (Base64) 22 | 23 | + (NSData *) dataWithBase64EncodedString:(NSString *) string { 24 | NSData *result = [[NSData alloc] initWithBase64EncodedString:string]; 25 | return [result autorelease]; 26 | } 27 | 28 | - (id) initWithBase64EncodedString:(NSString *) string { 29 | NSMutableData *mutableData = nil; 30 | 31 | if( string ) { 32 | unsigned long ixtext = 0; 33 | unsigned long lentext = 0; 34 | unsigned char ch = 0; 35 | unsigned char inbuf[3], outbuf[4]; 36 | short i = 0, ixinbuf = 0; 37 | BOOL flignore = NO; 38 | BOOL flendtext = NO; 39 | NSData *base64Data = nil; 40 | const unsigned char *base64Bytes = nil; 41 | 42 | // Convert the string to ASCII data. 43 | base64Data = [string dataUsingEncoding:NSASCIIStringEncoding]; 44 | base64Bytes = [base64Data bytes]; 45 | mutableData = [NSMutableData dataWithCapacity:[base64Data length]]; 46 | lentext = [base64Data length]; 47 | 48 | while( YES ) { 49 | if( ixtext >= lentext ) break; 50 | ch = base64Bytes[ixtext++]; 51 | flignore = NO; 52 | 53 | if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; 54 | else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; 55 | else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; 56 | else if( ch == '+' ) ch = 62; 57 | else if( ch == '=' ) flendtext = YES; 58 | else if( ch == '/' ) ch = 63; 59 | else flignore = YES; 60 | 61 | if( ! flignore ) { 62 | short ctcharsinbuf = 3; 63 | BOOL flbreak = NO; 64 | 65 | if( flendtext ) { 66 | if( ! ixinbuf ) break; 67 | if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; 68 | else ctcharsinbuf = 2; 69 | ixinbuf = 3; 70 | flbreak = YES; 71 | } 72 | 73 | inbuf [ixinbuf++] = ch; 74 | 75 | if( ixinbuf == 4 ) { 76 | ixinbuf = 0; 77 | outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); 78 | outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); 79 | outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); 80 | 81 | for( i = 0; i < ctcharsinbuf; i++ ) 82 | [mutableData appendBytes:&outbuf[i] length:1]; 83 | } 84 | 85 | if( flbreak ) break; 86 | } 87 | } 88 | } 89 | 90 | self = [self initWithData:mutableData]; 91 | return self; 92 | } 93 | 94 | - (NSString *) base64EncodingWithLineLength:(unsigned int) lineLength { 95 | const unsigned char *bytes = [self bytes]; 96 | NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; 97 | unsigned long ixtext = 0; 98 | unsigned long lentext = [self length]; 99 | long ctremaining = 0; 100 | unsigned char inbuf[3], outbuf[4]; 101 | short i = 0; 102 | short charsonline = 0, ctcopy = 0; 103 | unsigned long ix = 0; 104 | 105 | while( YES ) { 106 | ctremaining = lentext - ixtext; 107 | if( ctremaining <= 0 ) break; 108 | 109 | for( i = 0; i < 3; i++ ) { 110 | ix = ixtext + i; 111 | if( ix < lentext ) inbuf[i] = bytes[ix]; 112 | else inbuf [i] = 0; 113 | } 114 | 115 | outbuf [0] = (inbuf [0] & 0xFC) >> 2; 116 | outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); 117 | outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); 118 | outbuf [3] = inbuf [2] & 0x3F; 119 | ctcopy = 4; 120 | 121 | switch( ctremaining ) { 122 | case 1: 123 | ctcopy = 2; 124 | break; 125 | case 2: 126 | ctcopy = 3; 127 | break; 128 | } 129 | 130 | for( i = 0; i < ctcopy; i++ ) 131 | [result appendFormat:@"%c", encodingTable[outbuf[i]]]; 132 | 133 | for( i = ctcopy; i < 4; i++ ) 134 | [result appendFormat:@"%c",'=']; 135 | 136 | ixtext += 3; 137 | charsonline += 4; 138 | 139 | if( lineLength > 0 ) { 140 | if (charsonline >= lineLength) { 141 | charsonline = 0; 142 | [result appendString:@"\n"]; 143 | } 144 | } 145 | } 146 | 147 | return result; 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterXMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterXMLParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 18/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterXMLParser.h" 10 | 11 | 12 | @implementation MGTwitterXMLParser 13 | 14 | 15 | #pragma mark Creation and Destruction 16 | 17 | 18 | + (id)parserWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 19 | connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType 20 | responseType:(MGTwitterResponseType)respType 21 | { 22 | id parser = [[self alloc] initWithXML:theXML 23 | delegate:theDelegate 24 | connectionIdentifier:identifier 25 | requestType:reqType 26 | responseType:respType]; 27 | return [parser autorelease]; 28 | } 29 | 30 | 31 | - (id)initWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 32 | connectionIdentifier:(NSString *)theIdentifier requestType:(MGTwitterRequestType)reqType 33 | responseType:(MGTwitterResponseType)respType 34 | { 35 | if (self = [super init]) { 36 | xml = [theXML retain]; 37 | identifier = [theIdentifier retain]; 38 | requestType = reqType; 39 | responseType = respType; 40 | delegate = theDelegate; 41 | parsedObjects = [[NSMutableArray alloc] initWithCapacity:0]; 42 | 43 | // Set up the parser object. 44 | parser = [[NSXMLParser alloc] initWithData:xml]; 45 | [parser setDelegate:self]; 46 | [parser setShouldReportNamespacePrefixes:NO]; 47 | [parser setShouldProcessNamespaces:NO]; 48 | [parser setShouldResolveExternalEntities:NO]; 49 | 50 | // Begin parsing. 51 | [parser parse]; 52 | } 53 | 54 | return self; 55 | } 56 | 57 | 58 | - (void)dealloc 59 | { 60 | [parser release]; 61 | [parsedObjects release]; 62 | [xml release]; 63 | [identifier release]; 64 | delegate = nil; 65 | [super dealloc]; 66 | } 67 | 68 | 69 | #pragma mark NSXMLParser delegate methods 70 | 71 | 72 | - (void)parserDidStartDocument:(NSXMLParser *)theParser 73 | { 74 | //NSLog(@"Parsing begun"); 75 | } 76 | 77 | 78 | - (void)parserDidEndDocument:(NSXMLParser *)theParser 79 | { 80 | //NSLog(@"Parsing complete: %@", parsedObjects); 81 | [delegate parsingSucceededForRequest:identifier ofResponseType:responseType 82 | withParsedObjects:parsedObjects]; 83 | } 84 | 85 | 86 | - (void)parser:(NSXMLParser *)theParser didStartElement:(NSString *)elementName 87 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName 88 | attributes:(NSDictionary *)attributeDict 89 | { 90 | //NSLog(@"Started element: %@ (%@)", elementName, attributeDict); 91 | } 92 | 93 | 94 | - (void)parser:(NSXMLParser *)theParser foundCharacters:(NSString *)characters 95 | { 96 | //NSLog(@"Found characters: %@", characters); 97 | } 98 | 99 | 100 | - (void)parser:(NSXMLParser *)theParser didEndElement:(NSString *)elementName 101 | namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 102 | { 103 | //NSLog(@"Ended element: %@", elementName); 104 | [self setLastOpenedElement:nil]; 105 | 106 | if ([elementName isEqualToString:@"protected"] 107 | || [elementName isEqualToString:@"truncated"] 108 | || [elementName isEqualToString:@"following"]) { 109 | // Change "true"/"false" into an NSNumber with a BOOL value. 110 | NSNumber *boolNumber = [NSNumber numberWithBool:[[currentNode objectForKey:elementName] isEqualToString:@"true"]]; 111 | [currentNode setObject:boolNumber forKey:elementName]; 112 | } else if ([elementName isEqualToString:@"created_at"]) { 113 | // Change date-string into an NSDate. 114 | NSDate *creationDate = [NSDate dateWithNaturalLanguageString:[currentNode objectForKey:elementName]]; 115 | if (creationDate) { 116 | [currentNode setObject:creationDate forKey:elementName]; 117 | } 118 | } 119 | } 120 | 121 | 122 | - (void)parser:(NSXMLParser *)theParser foundAttributeDeclarationWithName:(NSString *)attributeName 123 | forElement:(NSString *)elementName type:(NSString *)type defaultValue:(NSString *)defaultValue 124 | { 125 | //NSLog(@"Found attribute: %@ (%@) [%@] {%@}", attributeName, elementName, type, defaultValue); 126 | } 127 | 128 | 129 | - (void)parser:(NSXMLParser *)theParser foundIgnorableWhitespace:(NSString *)whitespaceString 130 | { 131 | //NSLog(@"Found ignorable whitespace: %@", whitespaceString); 132 | } 133 | 134 | 135 | - (void)parser:(NSXMLParser *)theParser parseErrorOccurred:(NSError *)parseError 136 | { 137 | //NSLog(@"Parsing error occurred: %@", parseError); 138 | [delegate parsingFailedForRequest:identifier ofResponseType:responseType 139 | withError:parseError]; 140 | } 141 | 142 | 143 | #pragma mark Accessors 144 | 145 | 146 | - (NSString *)lastOpenedElement { 147 | return [[lastOpenedElement retain] autorelease]; 148 | } 149 | 150 | 151 | - (void)setLastOpenedElement:(NSString *)value { 152 | if (lastOpenedElement != value) { 153 | [lastOpenedElement release]; 154 | lastOpenedElement = [value copy]; 155 | } 156 | } 157 | 158 | 159 | #pragma mark Utility methods 160 | 161 | 162 | - (void)addSource 163 | { 164 | if (![currentNode objectForKey:TWITTER_SOURCE_REQUEST_TYPE]) { 165 | [currentNode setObject:[NSNumber numberWithInt:requestType] 166 | forKey:TWITTER_SOURCE_REQUEST_TYPE]; 167 | } 168 | } 169 | 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /GrowlTwitterDisplay.m: -------------------------------------------------------------------------------- 1 | // 2 | // GrowlTwitterDisplay.m 3 | // Growl Display Plugins 4 | // 5 | // Created by Tyler Hall. 6 | // Code based on GrowlSMSDisplay by by Diggory Laycock. 7 | // Copyright 2005-2009 The Growl Project All rights reserved. 8 | // 9 | 10 | #import "GrowlTwitterDisplay.h" 11 | #import "GrowlTwitterPrefs.h" 12 | #import "NSStringAdditions.h" 13 | #import "GrowlDefinesInternal.h" 14 | #import "GrowlApplicationNotification.h" 15 | #include 16 | #include 17 | 18 | #define keychainServiceName "GrowlTwitter" 19 | #define keychainAccountName "TwitterWebServicePassword" 20 | 21 | #define GrowlTwitterPrefDomain @"com.Growl.Twitter" 22 | #define usernameKey @"Twitter - Username" 23 | #define tweetPrefixKey @"Twitter - Tweet Prefix" 24 | 25 | @implementation GrowlTwitterDisplay 26 | 27 | - (id) init { 28 | NSLog(@"GrowlTwitter init"); 29 | if ((self = [super init])) { 30 | twitterEngine = [[MGTwitterEngine alloc] initWithDelegate:self]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void) dealloc { 36 | [preferencePane release]; 37 | [super dealloc]; 38 | } 39 | 40 | - (NSPreferencePane *) preferencePane { 41 | if (!preferencePane) 42 | preferencePane = [[GrowlTwitterPrefs alloc] initWithBundle:[NSBundle bundleWithIdentifier:@"com.Growl.Twitter"]]; 43 | return preferencePane; 44 | } 45 | 46 | - (void) displayNotification:(GrowlApplicationNotification *)notification { 47 | NSString *usernameValue = nil; 48 | NSString *tweetPrefixValue = nil; 49 | 50 | NSLog(@"GrowlTwitter notification"); 51 | 52 | READ_GROWL_PREF_VALUE(usernameKey, GrowlTwitterPrefDomain, NSString *, &usernameValue); 53 | [usernameValue autorelease]; 54 | 55 | if (![usernameValue length]) { 56 | NSLog(@"Twitter display: Cannot send tweet - username required."); 57 | return; 58 | } 59 | 60 | READ_GROWL_PREF_VALUE(tweetPrefixKey, GrowlTwitterPrefDomain, NSString *, &tweetPrefixValue); 61 | [tweetPrefixValue autorelease]; 62 | if (![tweetPrefixValue length]) { 63 | tweetPrefixValue = @""; 64 | } 65 | 66 | NSDictionary *noteDict = [notification dictionaryRepresentation]; 67 | NSString *title = [noteDict objectForKey:GROWL_NOTIFICATION_TITLE]; 68 | NSString *desc = [noteDict objectForKey:GROWL_NOTIFICATION_DESCRIPTION]; 69 | 70 | // Fetch the Twitter password from the keychain 71 | unsigned char *password; 72 | UInt32 passwordLength; 73 | OSStatus status; 74 | status = SecKeychainFindGenericPassword(NULL, 75 | strlen(keychainServiceName), keychainServiceName, 76 | strlen(keychainAccountName), keychainAccountName, 77 | &passwordLength, (void **)&password, NULL); 78 | 79 | CFStringRef passwordString; 80 | if (status == noErr) { 81 | passwordString = CFStringCreateWithBytes(kCFAllocatorDefault, password, passwordLength, kCFStringEncodingUTF8, false); 82 | SecKeychainItemFreeContent(NULL, password); 83 | } else { 84 | if (status != errSecItemNotFound) 85 | NSLog(@"Twitter display: Failed to retrieve Twitter password from keychain. Error: %d", status); 86 | passwordString = CFSTR(""); 87 | } 88 | 89 | // Send tweet 90 | NSString *tweet = [[NSString alloc] initWithFormat: 91 | @"%@%@ %@", 92 | tweetPrefixValue, 93 | title, 94 | desc]; 95 | 96 | NSLog(@"GrowlTwitter: %@", tweet); 97 | [twitterEngine setUsername:usernameValue password:(NSString *)passwordString]; 98 | [twitterEngine sendUpdate:tweet]; 99 | 100 | CFRelease(passwordString); 101 | 102 | id clickContext = [noteDict objectForKey:GROWL_NOTIFICATION_CLICK_CONTEXT]; 103 | if (clickContext) { 104 | NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys: 105 | [noteDict objectForKey:@"ClickHandlerEnabled"], @"ClickHandlerEnabled", 106 | clickContext, GROWL_KEY_CLICKED_CONTEXT, 107 | [noteDict objectForKey:GROWL_APP_PID], GROWL_APP_PID, 108 | nil]; 109 | [[NSNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION_TIMED_OUT 110 | object:[notification applicationName] 111 | userInfo:userInfo]; 112 | [userInfo release]; 113 | } 114 | 115 | } 116 | 117 | - (BOOL) requiresPositioning { 118 | return NO; 119 | } 120 | 121 | #pragma mark MGTwitterEngineDelegate methods 122 | 123 | - (void)requestSucceeded:(NSString *)requestIdentifier 124 | { 125 | // NSLog(@"Request succeeded (%@)", requestIdentifier); 126 | } 127 | 128 | 129 | - (void)requestFailed:(NSString *)requestIdentifier withError:(NSError *)error 130 | { 131 | // NSLog(@"Twitter request failed! (%@) Error: %@ (%@)", 132 | // requestIdentifier, 133 | // [error localizedDescription], 134 | // [[error userInfo] objectForKey:NSErrorFailingURLStringKey]); 135 | } 136 | 137 | 138 | - (void)statusesReceived:(NSArray *)statuses forRequest:(NSString *)identifier 139 | { 140 | // NSLog(@"Got statuses:\r%@", statuses); 141 | } 142 | 143 | 144 | - (void)directMessagesReceived:(NSArray *)messages forRequest:(NSString *)identifier 145 | { 146 | // NSLog(@"Got direct messages:\r%@", messages); 147 | } 148 | 149 | 150 | - (void)userInfoReceived:(NSArray *)userInfo forRequest:(NSString *)identifier 151 | { 152 | // NSLog(@"Got user info:\r%@", userInfo); 153 | } 154 | 155 | 156 | - (void)miscInfoReceived:(NSArray *)miscInfo forRequest:(NSString *)identifier 157 | { 158 | // NSLog(@"Got misc info:\r%@", miscInfo); 159 | } 160 | 161 | 162 | - (void)imageReceived:(NSImage *)image forRequest:(NSString *)identifier 163 | { 164 | // NSLog(@"Got an image: %@", image); 165 | // 166 | // // Save image to the Desktop. 167 | // NSString *path = [[NSString stringWithFormat:@"~/Desktop/%@.tiff", identifier] 168 | // stringByExpandingTildeInPath]; 169 | // [[image TIFFRepresentation] writeToFile:path atomically:NO]; 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterEngine.h 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 10/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngineGlobalHeader.h" 10 | 11 | #import "MGTwitterEngineDelegate.h" 12 | #import "MGTwitterParserDelegate.h" 13 | 14 | @interface MGTwitterEngine : NSObject { 15 | __weak NSObject *_delegate; 16 | NSString *_username; 17 | NSString *_password; 18 | NSMutableDictionary *_connections; // MGTwitterHTTPURLConnection objects 19 | NSString *_clientName; 20 | NSString *_clientVersion; 21 | NSString *_clientURL; 22 | NSString *_clientSourceToken; 23 | NSString *_APIDomain; 24 | BOOL _secureConnection; 25 | BOOL _clearsCookies; 26 | } 27 | 28 | // Constructors 29 | + (MGTwitterEngine *)twitterEngineWithDelegate:(NSObject *)delegate; 30 | - (MGTwitterEngine *)initWithDelegate:(NSObject *)delegate; 31 | 32 | // Configuration and Accessors 33 | + (NSString *)version; // returns the version of MGTwitterEngine 34 | - (NSString *)username; 35 | - (NSString *)password; 36 | - (void)setUsername:(NSString *)username password:(NSString *)password; 37 | - (NSString *)clientName; // see README.txt for info on clientName/Version/URL/SourceToken 38 | - (NSString *)clientVersion; 39 | - (NSString *)clientURL; 40 | - (NSString *)clientSourceToken; 41 | - (void)setClientName:(NSString *)name version:(NSString *)version URL:(NSString *)url token:(NSString *)token; 42 | - (NSString *)APIDomain; 43 | - (void)setAPIDomain:(NSString *)domain; 44 | - (BOOL)usesSecureConnection; // YES = uses HTTPS, default is YES 45 | - (void)setUsesSecureConnection:(BOOL)flag; 46 | - (BOOL)clearsCookies; // YES = deletes twitter.com cookies when setting username/password, default is NO (see README.txt) 47 | - (void)setClearsCookies:(BOOL)flag; 48 | 49 | // Connection methods 50 | - (int)numberOfConnections; 51 | - (NSArray *)connectionIdentifiers; 52 | - (void)closeConnection:(NSString *)identifier; 53 | - (void)closeAllConnections; 54 | 55 | // Utility methods 56 | /// Note: the -getImageAtURL: method works for any image URL, not just Twitter images. 57 | // It does not require authentication, and is provided here for convenience. 58 | // As with the Twitter API methods below, it returns a unique connection identifier. 59 | // Retrieved images are sent to the delegate via the -imageReceived:forRequest: method. 60 | - (NSString *)getImageAtURL:(NSString *)urlString; 61 | 62 | // ====================================================================================================== 63 | // Twitter API methods 64 | // See Twitter API docs at: http://apiwiki.twitter.com/REST+API+Documentation 65 | // All methods below return a unique connection identifier. 66 | // ====================================================================================================== 67 | 68 | // Account methods 69 | - (NSString *)checkUserCredentials; 70 | - (NSString *)endUserSession; 71 | - (NSString *)enableUpdatesFor:(NSString *)username; // i.e. follow 72 | - (NSString *)disableUpdatesFor:(NSString *)username; // i.e. no longer follow 73 | - (NSString *)isUser:(NSString *)username1 receivingUpdatesFor:(NSString *)username2; // i.e. test if username1 follows username2 (not the reverse) 74 | - (NSString *)enableNotificationsFor:(NSString *)username; 75 | - (NSString *)disableNotificationsFor:(NSString *)username; 76 | - (NSString *)getRateLimitStatus; 77 | - (NSString *)setLocation:(NSString *)location; 78 | - (NSString *)setNotificationsDeliveryMethod:(NSString *)method; 79 | - (NSString *)block:(NSString *)username; 80 | - (NSString *)unblock:(NSString *)username; 81 | - (NSString *)testService; 82 | - (NSString *)getDowntimeSchedule; 83 | 84 | // Retrieving updates 85 | - (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum; 86 | - (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)count; // max 200 87 | - (NSString *)getFollowedTimelineFor:(NSString *)username sinceID:(int)updateID startingAtPage:(int)pageNum count:(int)count; // max 200 88 | - (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date count:(int)numUpdates; // max 200 89 | - (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)numUpdates; // max 200 90 | - (NSString *)getUserTimelineFor:(NSString *)username sinceID:(int)updateID startingAtPage:(int)pageNum count:(int)numUpdates; // max 200 91 | - (NSString *)getUserUpdatesArchiveStartingAtPage:(int)pageNum; // 80 per page 92 | - (NSString *)getPublicTimelineSinceID:(int)updateID; 93 | - (NSString *)getRepliesStartingAtPage:(int)pageNum; // sent TO this user 94 | - (NSString *)getFavoriteUpdatesFor:(NSString *)username startingAtPage:(int)pageNum; 95 | - (NSString *)getUpdate:(int)updateID; 96 | 97 | // Retrieving direct messages 98 | - (NSString *)getDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum; // sent TO this user 99 | - (NSString *)getDirectMessagesSinceID:(int)updateID startingAtPage:(int)pageNum; // sent TO this user 100 | - (NSString *)getSentDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum; // sent BY this user 101 | - (NSString *)getSentDirectMessagesSinceID:(int)updateID startingAtPage:(int)pageNum; // sent BY this user 102 | 103 | // Retrieving user information 104 | - (NSString *)getUserInformationFor:(NSString *)username; 105 | - (NSString *)getUserInformationForEmail:(NSString *)email; 106 | - (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtPage:(int)pageNum; 107 | - (NSString *)getFollowersIncludingCurrentStatus:(BOOL)flag; 108 | - (NSString *)getFeaturedUsers; 109 | 110 | // Sending and editing updates 111 | - (NSString *)sendUpdate:(NSString *)status; 112 | - (NSString *)sendUpdate:(NSString *)status inReplyTo:(int)updateID; 113 | - (NSString *)deleteUpdate:(int)updateID; // this user must be the AUTHOR 114 | - (NSString *)markUpdate:(int)updateID asFavorite:(BOOL)flag; 115 | 116 | // Sending and editing direct messages 117 | - (NSString *)sendDirectMessage:(NSString *)message to:(NSString *)username; 118 | - (NSString *)deleteDirectMessage:(int)updateID; // this user must be the RECIPIENT 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /MGTwitterEngine/Source Code License.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf270 2 | {\fonttbl\f0\fnil\fcharset0 LucidaGrande;} 3 | {\colortbl;\red255\green255\blue255;\red51\green51\blue51;\red0\green180\blue128;\red255\green0\blue0; 4 | \red31\green105\blue199;\red119\green119\blue119;} 5 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid0\'02\'05.;}{\levelnumbers\'01;}}{\listname ;}\listid1}} 6 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} 7 | \deftab720 8 | \pard\pardeftab720\ql\qnatural 9 | 10 | \f0\b\fs24 \cf2 Matt Gemmell / Instinctive Code Source Code License\ 11 | 12 | \b0\fs22 Last updated: 19th May 2008 13 | \fs24 \ 14 | \ 15 | \ 16 | Thanks for downloading some of our source code!\ 17 | \ 18 | This is the license agreement for the source code which this document accompanies (don\'92t worry: you\'92re allowed to use it in your own products, commercial or otherwise).\ 19 | \ 20 | The full license text is further down this page, and you should only use the source code if you agree to the terms in that text. For convenience, though, we\'92ve put together a human-readable 21 | \b non-authoritative 22 | \b0 interpretation of the license which will hopefully answer any questions you have.\ 23 | \ 24 | \ 25 | 26 | \b \cf3 Green 27 | \b0 \cf2 text shows 28 | \b \cf3 what you can do with the code 29 | \b0 \cf2 .\ 30 | 31 | \b \cf4 Red 32 | \b0 \cf2 text means 33 | \b \cf4 restrictions you must abide by 34 | \b0 \cf2 .\ 35 | \ 36 | Basically, the license says that:\ 37 | \ 38 | \pard\tx220\tx720\pardeftab720\li720\fi-720\ql\qnatural 39 | \ls1\ilvl0\cf2 {\listtext 1. }You can 40 | \b \cf3 use the code in your own products, including commercial and/or closed-source products 41 | \b0 \cf2 .\ 42 | {\listtext 2. }You can 43 | \b \cf3 modify the code 44 | \b0 \cf0 as you wish\cf2 , and 45 | \b \cf3 use the modified code in your products 46 | \b0 \cf2 .\ 47 | {\listtext 3. }You can 48 | \b \cf3 redistribute the original, unmodified code 49 | \b0 \cf2 , but you 50 | \b \cf4 have to include the full license text below 51 | \b0 \cf2 .\ 52 | {\listtext 4. }You can 53 | \b \cf3 redistribute the modified code 54 | \b0 \cf2 as you wish ( 55 | \b \cf4 without the full license text below 56 | \b0 \cf2 ).\ 57 | {\listtext 5. }In all cases, you 58 | \b \cf4 must include a credit mentioning Matt Gemmell 59 | \b0 \cf2 as the original author of the source.\ 60 | {\listtext 6. }Matt Gemmell is \cf0 not liable for anything you do with the code\cf2 , no matter what. So be sensible.\ 61 | {\listtext 7. }You 62 | \b \cf4 can\'92t use the name Matt Gemmell, the name Instinctive Code, the Instinctive Code logo or any other related marks to promote your products 63 | \b0 \cf2 based on the code.\ 64 | {\listtext 8. }If you agree to all of that, go ahead and use the source. Otherwise, don\'92t!\ 65 | \pard\pardeftab720\ql\qnatural 66 | \cf2 \ 67 | 68 | \b \ 69 | \ 70 | Suggested Attribution Format\ 71 | 72 | \b0 \ 73 | The license requires that you give credit to Matt Gemmell, as the original author of any of our source that you use. The placement and format of the credit is up to you, but we prefer the credit to be in the software\'92s \'93About\'94 window. Alternatively, you could put the credit in a list of acknowledgements within the software, in the software\'92s documentation, or on the web page for the software. The suggested format for the attribution is:\ 74 | \ 75 | \pard\pardeftab720\ql\qnatural 76 | 77 | \b \cf0 Includes code by {\field{\*\fldinst{HYPERLINK "http://mattgemmell.com/"}}{\fldrslt \cf5 Matt Gemmell}}\cf6 . 78 | \b0 \ 79 | \pard\pardeftab720\ql\qnatural 80 | \cf2 \ 81 | where would be replaced by the name of the specific source-code package you made use of. Where possible, please link the text \'93Matt Gemmell\'94 to the following URL, or include the URL as plain text: {\field{\*\fldinst{HYPERLINK "http://mattgemmell.com/"}}{\fldrslt \cf5 http://mattgemmell.com/}}\ 82 | \ 83 | \ 84 | 85 | \b Full Source Code License Text\ 86 | \ 87 | 88 | \b0 Below you can find the actual text of the license agreement. 89 | \b \ 90 | \ 91 | \pard\pardeftab720\ql\qnatural 92 | \cf6 \ 93 | License Agreement for Source Code provided by Matt Gemmell 94 | \b0 \ 95 | \ 96 | This software is supplied to you by Matt Gemmell in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this software.\ 97 | \ 98 | In consideration of your agreement to abide by the following terms, and subject to these terms, Matt Gemmell grants you a personal, non-exclusive license, to use, reproduce, modify and redistribute the software, with or without modifications, in source and/or binary forms; provided that if you redistribute the software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the software, and that in all cases attribution of Matt Gemmell as the original author of the source code shall be included in all such resulting software products or distributions.\uc0\u8232 \ 99 | Neither the name, trademarks, service marks or logos of Matt Gemmell or Instinctive Code may be used to endorse or promote products derived from the software without specific prior written permission from Matt Gemmell. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Matt Gemmell herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the software may be incorporated.\ 100 | \ 101 | The software is provided by Matt Gemmell on an "AS IS" basis. MATT GEMMELL AND INSTINCTIVE CODE MAKE NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.\ 102 | \ 103 | IN NO EVENT SHALL MATT GEMMELL OR INSTINCTIVE CODE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF MATT GEMMELL OR INSTINCTIVE CODE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ 104 | } -------------------------------------------------------------------------------- /MGTwitterEngine/README.txt: -------------------------------------------------------------------------------- 1 | MGTwitterEngine 2 | by Matt Legend Gemmell - http://mattgemmell.com 3 | 4 | 5 | 6 | How to use MGTwitterEngine 7 | ========================== 8 | 9 | MGTwitterEngine is an Objective-C/Cocoa class which makes it easy to add Twitter integration to your own Cocoa apps. It communicates with Twitter via the public Twitter API, which you can read about here: 10 | http://apiwiki.twitter.com/REST+API+Documentation 11 | 12 | Using MGTwitterEngine is easy. The basic steps are: 13 | 14 | 15 | 1. Copy all the relevant source files into your own project. You need everything that starts with "MGTwitter", and also the NSString+UUID and NSData+Base64 category files. 16 | 17 | 18 | 2. In whatever class you're going to use MGTwitterEngine from, obviously make sure you #import the MGTwitterEngine.h header file. You should also declare that your class implements the MGTwitterEngineDelegate protocol. The AppController.h header file in the demo project is an example you can use. 19 | 20 | 21 | 3. Implement the MGTwitterEngineDelegate methods, just as the AppController in the demo project does. These are the methods you'll need to implement: 22 | 23 | - (void)requestSucceeded:(NSString *)requestIdentifier; 24 | - (void)requestFailed:(NSString *)requestIdentifier withError:(NSError *)error; 25 | - (void)statusesReceived:(NSArray *)statuses forRequest:(NSString *)identifier; 26 | - (void)directMessagesReceived:(NSArray *)messages forRequest:(NSString *)identifier; 27 | - (void)userInfoReceived:(NSArray *)userInfo forRequest:(NSString *)identifier; 28 | 29 | 30 | 4. Go ahead and use MGTwitterEngine! Just instantiate the object and set the relevant username and password (as AppController does in the demo project), and then go ahead and call some of the Twitter API methods - you can see a full list of them in the MGTwitterEngine.h header file, which also includes a link to the Twitter API documentation online. 31 | 32 | 33 | A note about XML parsing 34 | ======================== 35 | 36 | You may wish to use the LibXML parser rather than the NSXMLParser, since LibXML can be faster and has a smaller memory footprint. 37 | 38 | In this case, you make need to make the following changes to your project: 39 | 40 | 1. Set USE_LIBXML to 1, near the top of the MGTwitterEngine.m file. 41 | 42 | 2. Add libxml2.dylib in Other Frameworks. You'll find the library in: 43 | 44 | /usr/lib/libxml2.dylib 45 | 46 | 3. Add "/usr/include/libxml2" as a Header Search Path in your Project Settings. 47 | 48 | 49 | 50 | A note about using MGTwitterEngine on the iPhone 51 | ================================================ 52 | 53 | MGTwitterEngine can also be used on the iPhone (with the official iPhone SDK). Simply add it to your iPhone application project as usual. 54 | 55 | It's recommended that you use the LibXML parser rather than the NSXMLParser on the iPhone. The native parser is faster and has a smaller memory footprint, and every little bit counts on the device. If you configure USE_LIBXML to 1 in MGTwitterEngine.m, you'll need to make a couple of additions to your project. 56 | 57 | 1. Add libxml2.dylib in Other Frameworks. You'll find the library in: 58 | 59 | /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk/usr/lib/libxml2.dylib 60 | 61 | 2. Add "$SDKROOT/usr/include/libxml2" as a Header Search Path in your Project Settings. 62 | 63 | 64 | 65 | A note about the data returned from Twitter 66 | =========================================== 67 | 68 | Each Twitter API method returns an NSString which is a unique identifier for that connection. Those identifiers are passed to all the delegate methods, so you can keep track of what's happening. 69 | 70 | Whenever a request is successful, you will receive a call to your implementation of requestSucceeded: so you'll know that everything went OK. For most of the API methods, you will then receive a call to the appropriate method for the type of data you requested (statusesReceived:... or directMessagesReceived:... or userInfoReceived:...). The values sent to these methods are all NSArrays containing an NSDictionary for each status or user or direct message, with sub-dictionaries if necessary (for example, the timeline methods usually return statuses, each of which has a sub-dictionary giving information about the user who posted that status). 71 | 72 | Just try calling some of the methods and use NSLog() to see what data you get back; you should find the format very easy to integrate into your applications. 73 | 74 | Sometimes, of course, requests will fail - that's just how life is. In the unlikely event that the initial connection for a request can't be made, you will simply get nil back instead of a connection identifier, and then receive no further calls relating to that request. If you get nil back instead of an NSString, the connection has failed entirely. That's a good time to check that the computer is connected to the internet, and so on. 75 | 76 | It's far more common however that the connection itself will go ahead just fine, but there will be an error on Twitter's side, either due to technical difficulties, or because there was something wrong with your request (e.g. you entered the wrong username and password, or you tried to get info on a user that doesn't exist, or some such thing). The specific error conditions are mostly documented in the Twitter API documentation online. 77 | 78 | In these cases you'll receive a call to requestFailed:withError: which will include an NSError object detailing the error. Twitter usually returns meaningful HTTP error codes (like 404 for 'user not found', etc), and in that case the -domain of the NSError will be "HTTP" and the -code will be the relevant HTTP status code. This makes it really, really easy to know what's happening with your connections. 79 | 80 | 81 | 82 | About twitter.com cookies 83 | ========================= 84 | 85 | Like most web sites/services, twitter.com sets cookies on your computer when you authenticate with their server. These cookies (stored in NSHTTPCookieStorage) are shared amongst all applications which use NSURLConnection (including Safari and many more). 86 | 87 | MGTwitterEngine does not use those cookies, since it does its own direct authentication in the URLs of the requests it makes to the twitter servers. For this reason, as of version 1.0.4 (11th April 2008), it does not attempt to clear any saved cookies for twitter.com when you set a username and password for MGTwitterEngine to use. However, previous versions of MGTwitterEngine did indeed clear twitter's cookies whenever you called the -setUsername:password: method, in order to avoid an old and now fixed possibility of using the wrong credentials for the next request. There are two outcomes from this: 88 | 89 | 1. MGTwitterEngine no longer clears your twitter.com cookies, so for example you will now no longer have to re-login to Twitter in Safari after using an app which includes MGTwitterEngine. You would usually only have had to re-login with Safari once, but it was still an annoyance if you regularly used Twitter both on the web and with an MGTwitterEngine-using client. This should be fixed now. 90 | 91 | 2. In the unlikely event that you have any authentication problems when your MGTwitterEngine-using app switches from one Twitter account to another (for example, after switching accounts you still get data back from the old account, at least for the very first new request), you can easily re-enable the old cookie-clearing behaviour. Simply call the method -setClearsCookies: passing YES as the argument, and then call -setUsername:password: again, and all should be well. 92 | 93 | 94 | 95 | About supplying a custom name and other information for your Twitter client 96 | =========================================================================== 97 | 98 | The client name, url and version information supplied to -setClientName:version:URL:token: is used only for tracking purposes at Twitter; it is not displayed on the website. In order to have a custom name shown for your client when it sends updates to Twitter (e.g. "from MyCoolApp"), you must first contact Twitter and agree on a special identifier which you will send whenever you post an update - this is the 'token' parameter to the previously mentioned method. 99 | 100 | You can request such a token using this form at twitter.com: 101 | 102 | http://twitter.com/help/request_source 103 | 104 | When you receive your token, you can then set that token value using the aforementioned method, and MGTwitterEngine will do the right thing. 105 | 106 | 107 | 108 | That's about it. If you have trouble with the code, or want to make a feature request or report a bug (or even contribute some improvements), you can get in touch with me using the info below. I hope you enjoy using MGTwitterEngine! 109 | 110 | 111 | Cheers, 112 | -Matt Legend Gemmell 113 | 114 | 115 | Web: http://mattgemmell.com 116 | AIM: MadMcProgrammer 117 | MSN: mulderuk@hotmail.com 118 | Twitter: mattgemmell 119 | 120 | P.S. If you'd like to hire me for your own Mac OS X (Cocoa) or iPhone / iPod Touch development project, take a look at my consulting site at http://instinctivecode.com :) 121 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterLibXMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterLibXMLParser.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 18/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | // Major portions derived from BSTweetParser by Brent Simmons 9 | // 10 | 11 | #import "MGTwitterLibXMLParser.h" 12 | 13 | 14 | @implementation MGTwitterLibXMLParser 15 | 16 | 17 | #pragma mark Creation and Destruction 18 | 19 | 20 | + (id)parserWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 21 | connectionIdentifier:(NSString *)identifier requestType:(MGTwitterRequestType)reqType 22 | responseType:(MGTwitterResponseType)respType URL:(NSURL *)URL 23 | { 24 | id parser = [[self alloc] initWithXML:theXML 25 | delegate:theDelegate 26 | connectionIdentifier:identifier 27 | requestType:reqType 28 | responseType:respType 29 | URL:URL]; 30 | 31 | return [parser autorelease]; 32 | } 33 | 34 | 35 | - (id)initWithXML:(NSData *)theXML delegate:(NSObject *)theDelegate 36 | connectionIdentifier:(NSString *)theIdentifier requestType:(MGTwitterRequestType)reqType 37 | responseType:(MGTwitterResponseType)respType URL:(NSURL *)theURL 38 | { 39 | if (self = [super init]) 40 | { 41 | xml = [theXML retain]; 42 | identifier = [theIdentifier retain]; 43 | requestType = reqType; 44 | responseType = respType; 45 | URL = [theURL retain]; 46 | delegate = theDelegate; 47 | parsedObjects = [[NSMutableArray alloc] initWithCapacity:0]; 48 | 49 | // setup the xml reader 50 | _reader = xmlReaderForMemory([xml bytes], [xml length], [[URL absoluteString] UTF8String], nil, XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NOERROR | XML_PARSE_NOWARNING); 51 | if (! _reader) 52 | { 53 | return nil; 54 | } 55 | 56 | // run the parser and create parsedObjects 57 | [self parse]; 58 | 59 | // free the xml reader used for parsing 60 | xmlFree(_reader); 61 | 62 | // notify the delegate that parsing completed 63 | [self _parsingDidEnd]; 64 | } 65 | 66 | return self; 67 | } 68 | 69 | 70 | - (void)dealloc 71 | { 72 | [parsedObjects release]; 73 | [xml release]; 74 | [identifier release]; 75 | [URL release]; 76 | 77 | delegate = nil; 78 | [super dealloc]; 79 | } 80 | 81 | - (void)parse 82 | { 83 | // empty implementation -- override in subclasses 84 | } 85 | 86 | #pragma mark Subclass utilities 87 | 88 | // get the value from the current node 89 | - (xmlChar *)_nodeValue 90 | { 91 | if (xmlTextReaderIsEmptyElement(_reader)) 92 | { 93 | return nil; 94 | } 95 | 96 | xmlChar *result = nil; 97 | int nodeType = xmlTextReaderNodeType(_reader); 98 | while (nodeType != XML_READER_TYPE_END_ELEMENT) 99 | { 100 | if (nodeType == XML_READER_TYPE_TEXT) 101 | { 102 | result = xmlTextReaderValue(_reader); 103 | } 104 | 105 | // advance reader 106 | int readerResult = xmlTextReaderRead(_reader); 107 | if (readerResult != 1) 108 | { 109 | break; 110 | } 111 | nodeType = xmlTextReaderNodeType(_reader); 112 | } 113 | 114 | //NSLog(@"node: %25s = %s", xmlTextReaderConstName(_reader), result); 115 | 116 | return result; 117 | } 118 | 119 | - (NSString *)_nodeValueAsString 120 | { 121 | xmlChar *nodeValue = [self _nodeValue]; 122 | if (! nodeValue) 123 | { 124 | return nil; 125 | } 126 | 127 | NSMutableString *value = [NSMutableString stringWithUTF8String:(const char *)nodeValue]; 128 | xmlFree(nodeValue); 129 | 130 | // convert HTML entities back into UTF-8 131 | [value replaceOccurrencesOfString:@">" withString:@">" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [value length])]; 132 | [value replaceOccurrencesOfString:@"<" withString:@"<" options:NSCaseInsensitiveSearch range:NSMakeRange(0, [value length])]; 133 | 134 | NSString *result = [NSString stringWithString:value]; 135 | return result; 136 | } 137 | 138 | - (NSDate *)_nodeValueAsDate 139 | { 140 | xmlChar *nodeValue = [self _nodeValue]; 141 | if (! nodeValue) 142 | { 143 | return nil; 144 | } 145 | 146 | struct tm theTime; 147 | strptime((char *)nodeValue, "%a %b %d %H:%M:%S +0000 %Y", &theTime); 148 | xmlFree(nodeValue); 149 | time_t epochTime = timegm(&theTime); 150 | return [NSDate dateWithTimeIntervalSince1970:epochTime]; 151 | } 152 | 153 | - (NSNumber *)_nodeValueAsInt 154 | { 155 | xmlChar *nodeValue = [self _nodeValue]; 156 | if (! nodeValue) 157 | { 158 | return nil; 159 | } 160 | 161 | NSString *intString = [NSString stringWithUTF8String:(const char *)nodeValue]; 162 | xmlFree(nodeValue); 163 | return [NSNumber numberWithInt:[intString intValue]]; 164 | } 165 | 166 | - (NSNumber *)_nodeValueAsBool 167 | { 168 | xmlChar *nodeValue = [self _nodeValue]; 169 | if (! nodeValue) 170 | { 171 | return nil; 172 | } 173 | 174 | NSString *boolString = [NSString stringWithUTF8String:(const char *)nodeValue]; 175 | xmlFree(nodeValue); 176 | return [NSNumber numberWithBool:[boolString isEqualToString:@"true"]]; 177 | } 178 | 179 | - (NSDictionary *)_statusDictionaryForNodeWithName:(const xmlChar *)parentNodeName 180 | { 181 | if (xmlTextReaderIsEmptyElement(_reader)) 182 | return nil; 183 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 184 | 185 | int readerResult = xmlTextReaderRead(_reader); 186 | if (readerResult != 1) 187 | return nil; 188 | int nodeType = xmlTextReaderNodeType(_reader); 189 | const xmlChar *name = xmlTextReaderConstName(_reader); 190 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(parentNodeName, name))) 191 | { 192 | if (nodeType == XML_READER_TYPE_ELEMENT) 193 | { 194 | if (xmlStrEqual(name, BAD_CAST "user")) 195 | { 196 | // "user" is the name of a sub-dictionary in each item 197 | [dictionary setObject:[self _userDictionaryForNodeWithName:name] forKey:@"user"]; 198 | } 199 | else if (xmlStrEqual(name, BAD_CAST "id") || xmlStrEqual(name, BAD_CAST "in_reply_to_user_id") || xmlStrEqual(name, BAD_CAST "in_reply_to_status_id")) 200 | { 201 | // process element as an integer 202 | NSNumber *number = [self _nodeValueAsInt]; 203 | if (number) 204 | { 205 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 206 | } 207 | } 208 | else if (xmlStrEqual(name, BAD_CAST "created_at")) 209 | { 210 | // process element as a date 211 | NSDate *date = [self _nodeValueAsDate]; 212 | if (date) 213 | { 214 | [dictionary setObject:date forKey:[NSString stringWithUTF8String:(const char *)name]]; 215 | } 216 | } 217 | else if (xmlStrEqual(name, BAD_CAST "truncated") || xmlStrEqual(name, BAD_CAST "favorited")) 218 | { 219 | // process element as a boolean 220 | NSNumber *number = [self _nodeValueAsBool]; 221 | if (number) 222 | { 223 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 224 | } 225 | } 226 | else 227 | { 228 | // process element as a string 229 | NSString *string = [self _nodeValueAsString]; 230 | if (string) 231 | { 232 | [dictionary setObject:string forKey:[NSString stringWithUTF8String:(const char *)name]]; 233 | } 234 | } 235 | } 236 | 237 | // advance reader 238 | int readerResult = xmlTextReaderRead(_reader); 239 | if (readerResult != 1) 240 | break; 241 | nodeType = xmlTextReaderNodeType(_reader); 242 | name = xmlTextReaderConstName(_reader); 243 | } 244 | 245 | // save the request type in the tweet 246 | [dictionary setObject:[NSNumber numberWithInt:requestType] forKey:TWITTER_SOURCE_REQUEST_TYPE]; 247 | 248 | return dictionary; 249 | } 250 | 251 | - (NSDictionary *)_userDictionaryForNodeWithName:(const xmlChar *)parentNodeName 252 | { 253 | if (xmlTextReaderIsEmptyElement(_reader)) 254 | return nil; 255 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 256 | 257 | int readerResult = xmlTextReaderRead(_reader); 258 | if (readerResult != 1) 259 | return nil; 260 | int nodeType = xmlTextReaderNodeType(_reader); 261 | const xmlChar *name = xmlTextReaderConstName(_reader); 262 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(parentNodeName, name))) 263 | { 264 | if (nodeType == XML_READER_TYPE_ELEMENT) 265 | { 266 | if (xmlStrEqual(name, BAD_CAST "id") || xmlStrEqual(name, BAD_CAST "followers_count") 267 | || xmlStrEqual(name, BAD_CAST "friends_count") || xmlStrEqual(name, BAD_CAST "favourites_count") 268 | || xmlStrEqual(name, BAD_CAST "statuses_count")) 269 | { 270 | // process element as an integer 271 | NSNumber *number = [self _nodeValueAsInt]; 272 | if (number) 273 | { 274 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 275 | } 276 | } 277 | else if (xmlStrEqual(name, BAD_CAST "protected")) 278 | { 279 | // process element as a boolean 280 | NSNumber *number = [self _nodeValueAsBool]; 281 | if (number) 282 | { 283 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 284 | } 285 | } 286 | else 287 | { 288 | // process element as a string 289 | NSString *s = [self _nodeValueAsString]; 290 | if (s) 291 | { 292 | [dictionary setObject:s forKey:[NSString stringWithUTF8String:(const char *)name]]; 293 | } 294 | } 295 | } 296 | 297 | // advance reader 298 | int readerResult = xmlTextReaderRead(_reader); 299 | if (readerResult != 1) 300 | break; 301 | nodeType = xmlTextReaderNodeType(_reader); 302 | name = xmlTextReaderConstName(_reader); 303 | } 304 | 305 | return dictionary; 306 | } 307 | 308 | - (NSDictionary *)_hashDictionaryForNodeWithName:(const xmlChar *)parentNodeName 309 | { 310 | if (xmlTextReaderIsEmptyElement(_reader)) 311 | return nil; 312 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; 313 | 314 | int readerResult = xmlTextReaderRead(_reader); 315 | if (readerResult != 1) 316 | return nil; 317 | int nodeType = xmlTextReaderNodeType(_reader); 318 | const xmlChar *name = xmlTextReaderConstName(_reader); 319 | while (! (nodeType == XML_READER_TYPE_END_ELEMENT && xmlStrEqual(parentNodeName, name))) 320 | { 321 | if (nodeType == XML_READER_TYPE_ELEMENT) 322 | { 323 | if (xmlStrEqual(name, BAD_CAST "hourly-limit") || xmlStrEqual(name, BAD_CAST "remaining-hits") 324 | || xmlStrEqual(name, BAD_CAST "reset-time-in-seconds")) 325 | { 326 | // process element as an integer 327 | NSNumber *number = [self _nodeValueAsInt]; 328 | if (number) 329 | { 330 | [dictionary setObject:number forKey:[NSString stringWithUTF8String:(const char *)name]]; 331 | } 332 | } 333 | else 334 | { 335 | // process element as a string 336 | NSString *s = [self _nodeValueAsString]; 337 | if (s) 338 | { 339 | [dictionary setObject:s forKey:[NSString stringWithUTF8String:(const char *)name]]; 340 | } 341 | } 342 | } 343 | 344 | // advance reader 345 | int readerResult = xmlTextReaderRead(_reader); 346 | if (readerResult != 1) 347 | break; 348 | nodeType = xmlTextReaderNodeType(_reader); 349 | name = xmlTextReaderConstName(_reader); 350 | } 351 | 352 | return dictionary; 353 | } 354 | 355 | 356 | #pragma mark Delegate callbacks 357 | 358 | - (void)_parsingDidEnd 359 | { 360 | //NSLog(@"Parsing complete: %@", parsedObjects); 361 | [delegate parsingSucceededForRequest:identifier ofResponseType:responseType withParsedObjects:parsedObjects]; 362 | } 363 | 364 | - (void)_parsingErrorOccurred:(NSError *)parseError 365 | { 366 | //NSLog(@"Parsing error occurred: %@", parseError); 367 | [delegate parsingFailedForRequest:identifier ofResponseType:responseType withError:parseError]; 368 | } 369 | 370 | @end 371 | -------------------------------------------------------------------------------- /MGTwitterEngine/MGTwitterEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // MGTwitterEngine.m 3 | // MGTwitterEngine 4 | // 5 | // Created by Matt Gemmell on 10/02/2008. 6 | // Copyright 2008 Instinctive Code. 7 | // 8 | 9 | #import "MGTwitterEngine.h" 10 | #import "MGTwitterHTTPURLConnection.h" 11 | 12 | #import "NSData+Base64.h" 13 | 14 | #define USE_LIBXML 1 15 | 16 | #if USE_LIBXML 17 | #import "MGTwitterStatusesLibXMLParser.h" 18 | #import "MGTwitterMessagesLibXMLParser.h" 19 | #import "MGTwitterUsersLibXMLParser.h" 20 | #import "MGTwitterMiscLibXMLParser.h" 21 | #else 22 | #import "MGTwitterStatusesParser.h" 23 | #import "MGTwitterUsersParser.h" 24 | #import "MGTwitterMessagesParser.h" 25 | #import "MGTwitterMiscParser.h" 26 | #endif 27 | 28 | #define TWITTER_DOMAIN @"twitter.com" 29 | #define HTTP_POST_METHOD @"POST" 30 | #define MAX_MESSAGE_LENGTH 140 // Twitter recommends tweets of max 140 chars 31 | #define MAX_LOCATION_LENGTH 31 32 | 33 | #define DEFAULT_CLIENT_NAME @"GrowlTwitter" 34 | #define DEFAULT_CLIENT_VERSION @"1.0" 35 | #define DEFAULT_CLIENT_URL @"http://clickontyler.com/growl-twitter/" 36 | #define DEFAULT_CLIENT_TOKEN @"growltwitter" 37 | 38 | #define URL_REQUEST_TIMEOUT 25.0 // Twitter usually fails quickly if it's going to fail at all. 39 | #define DEFAULT_TWEET_COUNT 20 40 | 41 | 42 | @interface MGTwitterEngine (PrivateMethods) 43 | 44 | // Utility methods 45 | - (NSDateFormatter *)_HTTPDateFormatter; 46 | - (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed; 47 | - (NSDate *)_HTTPToDate:(NSString *)httpDate; 48 | - (NSString *)_dateToHTTP:(NSDate *)date; 49 | - (NSString *)_encodeString:(NSString *)string; 50 | 51 | // Connection/Request methods 52 | - (NSString *)_sendRequestWithMethod:(NSString *)method 53 | path:(NSString *)path 54 | queryParameters:(NSDictionary *)params 55 | body:(NSString *)body 56 | requestType:(MGTwitterRequestType)requestType 57 | responseType:(MGTwitterResponseType)responseType; 58 | 59 | // Parsing methods 60 | - (void)_parseXMLForConnection:(MGTwitterHTTPURLConnection *)connection; 61 | 62 | // Delegate methods 63 | - (BOOL) _isValidDelegateForSelector:(SEL)selector; 64 | 65 | @end 66 | 67 | 68 | @implementation MGTwitterEngine 69 | 70 | 71 | #pragma mark Constructors 72 | 73 | 74 | + (MGTwitterEngine *)twitterEngineWithDelegate:(NSObject *)theDelegate 75 | { 76 | return [[[MGTwitterEngine alloc] initWithDelegate:theDelegate] autorelease]; 77 | } 78 | 79 | 80 | - (MGTwitterEngine *)initWithDelegate:(NSObject *)newDelegate 81 | { 82 | if (self = [super init]) { 83 | _delegate = newDelegate; // deliberately weak reference 84 | _connections = [[NSMutableDictionary alloc] initWithCapacity:0]; 85 | _clientName = [DEFAULT_CLIENT_NAME retain]; 86 | _clientVersion = [DEFAULT_CLIENT_VERSION retain]; 87 | _clientURL = [DEFAULT_CLIENT_URL retain]; 88 | _clientSourceToken = [DEFAULT_CLIENT_TOKEN retain]; 89 | _APIDomain = [TWITTER_DOMAIN retain]; 90 | _secureConnection = YES; 91 | _clearsCookies = NO; 92 | } 93 | 94 | return self; 95 | } 96 | 97 | 98 | - (void)dealloc 99 | { 100 | _delegate = nil; 101 | 102 | [[_connections allValues] makeObjectsPerformSelector:@selector(cancel)]; 103 | [_connections release]; 104 | 105 | [_username release]; 106 | [_password release]; 107 | [_clientName release]; 108 | [_clientVersion release]; 109 | [_clientURL release]; 110 | [_clientSourceToken release]; 111 | [_APIDomain release]; 112 | 113 | [super dealloc]; 114 | } 115 | 116 | 117 | #pragma mark Configuration and Accessors 118 | 119 | 120 | + (NSString *)version 121 | { 122 | // 1.0.0 = 22 Feb 2008 123 | // 1.0.1 = 26 Feb 2008 124 | // 1.0.2 = 04 Mar 2008 125 | // 1.0.3 = 04 Mar 2008 126 | // 1.0.4 = 11 Apr 2008 127 | // 1.0.5 = 06 Jun 2008 128 | // 1.0.6 = 05 Aug 2008 129 | // 1.0.7 = 28 Sep 2008 130 | // 1.0.8 = 01 Oct 2008 131 | return @"1.0.8"; 132 | } 133 | 134 | 135 | - (NSString *)username 136 | { 137 | return [[_username retain] autorelease]; 138 | } 139 | 140 | 141 | - (NSString *)password 142 | { 143 | return [[_password retain] autorelease]; 144 | } 145 | 146 | 147 | - (void)setUsername:(NSString *)newUsername password:(NSString *)newPassword 148 | { 149 | // Set new credentials. 150 | [_username release]; 151 | _username = [newUsername retain]; 152 | [_password release]; 153 | _password = [newPassword retain]; 154 | 155 | if ([self clearsCookies]) { 156 | // Remove all cookies for twitter, to ensure next connection uses new credentials. 157 | NSString *urlString = [NSString stringWithFormat:@"%@://%@", 158 | (_secureConnection) ? @"https" : @"http", 159 | _APIDomain]; 160 | NSURL *url = [NSURL URLWithString:urlString]; 161 | 162 | NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 163 | NSEnumerator *enumerator = [[cookieStorage cookiesForURL:url] objectEnumerator]; 164 | NSHTTPCookie *cookie = nil; 165 | while (cookie = [enumerator nextObject]) { 166 | [cookieStorage deleteCookie:cookie]; 167 | } 168 | } 169 | } 170 | 171 | 172 | - (NSString *)clientName 173 | { 174 | return [[_clientName retain] autorelease]; 175 | } 176 | 177 | 178 | - (NSString *)clientVersion 179 | { 180 | return [[_clientVersion retain] autorelease]; 181 | } 182 | 183 | 184 | - (NSString *)clientURL 185 | { 186 | return [[_clientURL retain] autorelease]; 187 | } 188 | 189 | 190 | - (NSString *)clientSourceToken 191 | { 192 | return [[_clientSourceToken retain] autorelease]; 193 | } 194 | 195 | 196 | - (void)setClientName:(NSString *)name version:(NSString *)version URL:(NSString *)url token:(NSString *)token; 197 | { 198 | [_clientName release]; 199 | _clientName = [name retain]; 200 | [_clientVersion release]; 201 | _clientVersion = [version retain]; 202 | [_clientURL release]; 203 | _clientURL = [url retain]; 204 | [_clientSourceToken release]; 205 | _clientSourceToken = [token retain]; 206 | } 207 | 208 | 209 | - (NSString *)APIDomain 210 | { 211 | return [[_APIDomain retain] autorelease]; 212 | } 213 | 214 | 215 | - (void)setAPIDomain:(NSString *)domain 216 | { 217 | [_APIDomain release]; 218 | if (!domain || [domain length] == 0) { 219 | _APIDomain = [TWITTER_DOMAIN retain]; 220 | } else { 221 | _APIDomain = [domain retain]; 222 | } 223 | } 224 | 225 | 226 | - (BOOL)usesSecureConnection 227 | { 228 | return _secureConnection; 229 | } 230 | 231 | 232 | - (void)setUsesSecureConnection:(BOOL)flag 233 | { 234 | _secureConnection = flag; 235 | } 236 | 237 | 238 | - (BOOL)clearsCookies 239 | { 240 | return _clearsCookies; 241 | } 242 | 243 | 244 | - (void)setClearsCookies:(BOOL)flag 245 | { 246 | _clearsCookies = flag; 247 | } 248 | 249 | 250 | #pragma mark Connection methods 251 | 252 | 253 | - (int)numberOfConnections 254 | { 255 | return [_connections count]; 256 | } 257 | 258 | 259 | - (NSArray *)connectionIdentifiers 260 | { 261 | return [_connections allKeys]; 262 | } 263 | 264 | 265 | - (void)closeConnection:(NSString *)identifier 266 | { 267 | MGTwitterHTTPURLConnection *connection = [_connections objectForKey:identifier]; 268 | if (connection) { 269 | [connection cancel]; 270 | [_connections removeObjectForKey:identifier]; 271 | } 272 | } 273 | 274 | 275 | - (void)closeAllConnections 276 | { 277 | [[_connections allValues] makeObjectsPerformSelector:@selector(cancel)]; 278 | [_connections removeAllObjects]; 279 | } 280 | 281 | 282 | #pragma mark Utility methods 283 | 284 | 285 | - (NSDateFormatter *)_HTTPDateFormatter 286 | { 287 | // Returns a formatter for dates in HTTP format (i.e. RFC 822, updated by RFC 1123). 288 | // e.g. "Sun, 06 Nov 1994 08:49:37 GMT" 289 | NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; 290 | //[dateFormatter setDateFormat:@"%a, %d %b %Y %H:%M:%S GMT"]; // won't work with -init, which uses new (unicode) format behaviour. 291 | [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; 292 | [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss GMT"]; 293 | return dateFormatter; 294 | } 295 | 296 | 297 | - (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed 298 | { 299 | // Append base if specified. 300 | NSMutableString *str = [NSMutableString stringWithCapacity:0]; 301 | if (base) { 302 | [str appendString:base]; 303 | } 304 | 305 | // Append each name-value pair. 306 | if (params) { 307 | int i; 308 | NSArray *names = [params allKeys]; 309 | for (i = 0; i < [names count]; i++) { 310 | if (i == 0 && prefixed) { 311 | [str appendString:@"?"]; 312 | } else if (i > 0) { 313 | [str appendString:@"&"]; 314 | } 315 | NSString *name = [names objectAtIndex:i]; 316 | [str appendString:[NSString stringWithFormat:@"%@=%@", 317 | name, [self _encodeString:[params objectForKey:name]]]]; 318 | } 319 | } 320 | 321 | return str; 322 | } 323 | 324 | 325 | - (NSDate *)_HTTPToDate:(NSString *)httpDate 326 | { 327 | NSDateFormatter *dateFormatter = [self _HTTPDateFormatter]; 328 | return [dateFormatter dateFromString:httpDate]; 329 | } 330 | 331 | 332 | - (NSString *)_dateToHTTP:(NSDate *)date 333 | { 334 | NSDateFormatter *dateFormatter = [self _HTTPDateFormatter]; 335 | return [dateFormatter stringFromDate:date]; 336 | } 337 | 338 | 339 | - (NSString *)_encodeString:(NSString *)string 340 | { 341 | NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, 342 | (CFStringRef)string, 343 | NULL, 344 | (CFStringRef)@";/?:@&=$+{}<>,", 345 | kCFStringEncodingUTF8); 346 | return [result autorelease]; 347 | } 348 | 349 | 350 | - (NSString *)getImageAtURL:(NSString *)urlString 351 | { 352 | // This is a method implemented for the convenience of the client, 353 | // allowing asynchronous downloading of users' Twitter profile images. 354 | NSString *encodedUrlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 355 | NSURL *url = [NSURL URLWithString:encodedUrlString]; 356 | if (!url) { 357 | return nil; 358 | } 359 | 360 | // Construct an NSMutableURLRequest for the URL and set appropriate request method. 361 | NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url 362 | cachePolicy:NSURLRequestReloadIgnoringCacheData 363 | timeoutInterval:URL_REQUEST_TIMEOUT]; 364 | 365 | // Create a connection using this request, with the default timeout and caching policy, 366 | // and appropriate Twitter request and response types for parsing and error reporting. 367 | MGTwitterHTTPURLConnection *connection; 368 | connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest 369 | delegate:self 370 | requestType:MGTwitterImageRequest 371 | responseType:MGTwitterImage]; 372 | 373 | if (!connection) { 374 | return nil; 375 | } else { 376 | [_connections setObject:connection forKey:[connection identifier]]; 377 | [connection release]; 378 | } 379 | 380 | return [connection identifier]; 381 | } 382 | 383 | 384 | #pragma mark Request sending methods 385 | 386 | #define SET_AUTHORIZATION_IN_HEADER 1 387 | 388 | - (NSString *)_sendRequestWithMethod:(NSString *)method 389 | path:(NSString *)path 390 | queryParameters:(NSDictionary *)params 391 | body:(NSString *)body 392 | requestType:(MGTwitterRequestType)requestType 393 | responseType:(MGTwitterResponseType)responseType 394 | { 395 | // Construct appropriate URL string. 396 | NSString *fullPath = path; 397 | if (params) { 398 | fullPath = [self _queryStringWithBase:fullPath parameters:params prefixed:YES]; 399 | } 400 | 401 | #if SET_AUTHORIZATION_IN_HEADER 402 | NSString *urlString = [NSString stringWithFormat:@"%@://%@/%@", 403 | (_secureConnection) ? @"https" : @"http", 404 | _APIDomain, fullPath]; 405 | #else 406 | NSString *urlString = [NSString stringWithFormat:@"%@://%@:%@@%@/%@", 407 | (_secureConnection) ? @"https" : @"http", 408 | [self _encodeString:_username], [self _encodeString:_password], 409 | _APIDomain, fullPath]; 410 | #endif 411 | 412 | NSURL *finalURL = [NSURL URLWithString:urlString]; 413 | if (!finalURL) { 414 | return nil; 415 | } 416 | 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) { 422 | [theRequest setHTTPMethod:method]; 423 | } 424 | [theRequest setHTTPShouldHandleCookies:NO]; 425 | 426 | // Set headers for client information, for tracking purposes at Twitter. 427 | [theRequest setValue:_clientName forHTTPHeaderField:@"X-Twitter-Client"]; 428 | [theRequest setValue:_clientVersion forHTTPHeaderField:@"X-Twitter-Client-Version"]; 429 | [theRequest setValue:_clientURL forHTTPHeaderField:@"X-Twitter-Client-URL"]; 430 | 431 | #if SET_AUTHORIZATION_IN_HEADER 432 | if ([self username] && [self password]) { 433 | // Set header for HTTP Basic authentication explicitly, to avoid problems with proxies and other intermediaries 434 | NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]]; 435 | NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; 436 | NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]]; 437 | [theRequest setValue:authValue forHTTPHeaderField:@"Authorization"]; 438 | } 439 | #endif 440 | 441 | // Set the request body if this is a POST request. 442 | BOOL isPOST = (method && [method isEqualToString:HTTP_POST_METHOD]); 443 | if (isPOST) { 444 | // Set request body, if specified (hopefully so), with 'source' parameter if appropriate. 445 | NSString *finalBody = @""; 446 | if (body) { 447 | finalBody = [finalBody stringByAppendingString:body]; 448 | } 449 | if (_clientSourceToken) { 450 | finalBody = [finalBody stringByAppendingString:[NSString stringWithFormat:@"%@source=%@", 451 | (body) ? @"&" : @"?" , 452 | _clientSourceToken]]; 453 | } 454 | 455 | if (finalBody) { 456 | [theRequest setHTTPBody:[finalBody dataUsingEncoding:NSUTF8StringEncoding]]; 457 | } 458 | } 459 | 460 | 461 | // Create a connection using this request, with the default timeout and caching policy, 462 | // and appropriate Twitter request and response types for parsing and error reporting. 463 | MGTwitterHTTPURLConnection *connection; 464 | connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest 465 | delegate:self 466 | requestType:requestType 467 | responseType:responseType]; 468 | 469 | if (!connection) { 470 | return nil; 471 | } else { 472 | [_connections setObject:connection forKey:[connection identifier]]; 473 | [connection release]; 474 | } 475 | 476 | return [connection identifier]; 477 | } 478 | 479 | 480 | #pragma mark Parsing methods 481 | 482 | 483 | - (void)_parseXMLForConnection:(MGTwitterHTTPURLConnection *)connection 484 | { 485 | NSString *identifier = [[[connection identifier] copy] autorelease]; 486 | NSData *xmlData = [[[connection data] copy] autorelease]; 487 | MGTwitterRequestType requestType = [connection requestType]; 488 | MGTwitterResponseType responseType = [connection responseType]; 489 | 490 | #if USE_LIBXML 491 | NSURL *URL = [connection URL]; 492 | 493 | switch (responseType) { 494 | case MGTwitterStatuses: 495 | case MGTwitterStatus: 496 | [MGTwitterStatusesLibXMLParser parserWithXML:xmlData delegate:self 497 | connectionIdentifier:identifier requestType:requestType 498 | responseType:responseType URL:URL]; 499 | break; 500 | case MGTwitterUsers: 501 | case MGTwitterUser: 502 | [MGTwitterUsersLibXMLParser parserWithXML:xmlData delegate:self 503 | connectionIdentifier:identifier requestType:requestType 504 | responseType:responseType URL:URL]; 505 | break; 506 | case MGTwitterDirectMessages: 507 | case MGTwitterDirectMessage: 508 | [MGTwitterMessagesLibXMLParser parserWithXML:xmlData delegate:self 509 | connectionIdentifier:identifier requestType:requestType 510 | responseType:responseType URL:URL]; 511 | break; 512 | case MGTwitterMiscellaneous: 513 | [MGTwitterMiscLibXMLParser parserWithXML:xmlData delegate:self 514 | connectionIdentifier:identifier requestType:requestType 515 | responseType:responseType URL:URL]; 516 | break; 517 | default: 518 | break; 519 | } 520 | #else 521 | // Determine which type of parser to use. 522 | switch (responseType) { 523 | case MGTwitterStatuses: 524 | case MGTwitterStatus: 525 | [MGTwitterStatusesParser parserWithXML:xmlData delegate:self 526 | connectionIdentifier:identifier requestType:requestType 527 | responseType:responseType]; 528 | break; 529 | case MGTwitterUsers: 530 | case MGTwitterUser: 531 | [MGTwitterUsersParser parserWithXML:xmlData delegate:self 532 | connectionIdentifier:identifier requestType:requestType 533 | responseType:responseType]; 534 | break; 535 | case MGTwitterDirectMessages: 536 | case MGTwitterDirectMessage: 537 | [MGTwitterMessagesParser parserWithXML:xmlData delegate:self 538 | connectionIdentifier:identifier requestType:requestType 539 | responseType:responseType]; 540 | break; 541 | case MGTwitterMiscellaneous: 542 | [MGTwitterMiscParser parserWithXML:xmlData delegate:self 543 | connectionIdentifier:identifier requestType:requestType 544 | responseType:responseType]; 545 | break; 546 | default: 547 | break; 548 | } 549 | #endif 550 | } 551 | 552 | #pragma mark Delegate methods 553 | 554 | - (BOOL) _isValidDelegateForSelector:(SEL)selector 555 | { 556 | return ((_delegate != nil) && [_delegate respondsToSelector:selector]); 557 | } 558 | 559 | #pragma mark MGTwitterParserDelegate methods 560 | 561 | 562 | - (void)parsingSucceededForRequest:(NSString *)identifier 563 | ofResponseType:(MGTwitterResponseType)responseType 564 | withParsedObjects:(NSArray *)parsedObjects 565 | { 566 | // Forward appropriate message to _delegate, depending on responseType. 567 | switch (responseType) { 568 | case MGTwitterStatuses: 569 | case MGTwitterStatus: 570 | if ([self _isValidDelegateForSelector:@selector(statusesReceived:forRequest:)]) 571 | [_delegate statusesReceived:parsedObjects forRequest:identifier]; 572 | break; 573 | case MGTwitterUsers: 574 | case MGTwitterUser: 575 | if ([self _isValidDelegateForSelector:@selector(userInfoReceived:forRequest:)]) 576 | [_delegate userInfoReceived:parsedObjects forRequest:identifier]; 577 | break; 578 | case MGTwitterDirectMessages: 579 | case MGTwitterDirectMessage: 580 | if ([self _isValidDelegateForSelector:@selector(directMessagesReceived:forRequest:)]) 581 | [_delegate directMessagesReceived:parsedObjects forRequest:identifier]; 582 | break; 583 | case MGTwitterMiscellaneous: 584 | if ([self _isValidDelegateForSelector:@selector(miscInfoReceived:forRequest:)]) 585 | [_delegate miscInfoReceived:parsedObjects forRequest:identifier]; 586 | break; 587 | default: 588 | break; 589 | } 590 | } 591 | 592 | 593 | - (void)parsingFailedForRequest:(NSString *)requestIdentifier 594 | ofResponseType:(MGTwitterResponseType)responseType 595 | withError:(NSError *)error 596 | { 597 | if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)]) 598 | [_delegate requestFailed:requestIdentifier withError:error]; 599 | } 600 | 601 | 602 | #pragma mark NSURLConnection delegate methods 603 | 604 | 605 | - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 606 | { 607 | if ([challenge previousFailureCount] == 0 && ![challenge proposedCredential]) { 608 | NSURLCredential *credential = [NSURLCredential credentialWithUser:_username password:_password 609 | persistence:NSURLCredentialPersistenceForSession]; 610 | [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; 611 | } else { 612 | [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; 613 | } 614 | } 615 | 616 | 617 | - (void)connection:(MGTwitterHTTPURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 618 | { 619 | // This method is called when the server has determined that it has enough information to create the NSURLResponse. 620 | // it can be called multiple times, for example in the case of a redirect, so each time we reset the data. 621 | [connection resetDataLength]; 622 | 623 | // Get response code. 624 | NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response; 625 | int statusCode = [resp statusCode]; 626 | 627 | if (statusCode >= 400) { 628 | // Assume failure, and report to delegate. 629 | NSError *error = [NSError errorWithDomain:@"HTTP" code:statusCode userInfo:nil]; 630 | if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)]) 631 | [_delegate requestFailed:[connection identifier] withError:error]; 632 | 633 | // Destroy the connection. 634 | [connection cancel]; 635 | [_connections removeObjectForKey:[connection identifier]]; 636 | 637 | } else if (statusCode == 304 || [connection responseType] == MGTwitterGeneric) { 638 | // Not modified, or generic success. 639 | if ([self _isValidDelegateForSelector:@selector(requestSucceeded:)]) 640 | [_delegate requestSucceeded:[connection identifier]]; 641 | if (statusCode == 304) { 642 | [self parsingSucceededForRequest:[connection identifier] 643 | ofResponseType:[connection responseType] 644 | withParsedObjects:[NSArray array]]; 645 | } 646 | 647 | // Destroy the connection. 648 | [connection cancel]; 649 | [_connections removeObjectForKey:[connection identifier]]; 650 | } 651 | 652 | if (NO) { 653 | // Display headers for debugging. 654 | NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response; 655 | NSLog(@"(%d) [%@]:\r%@", 656 | [resp statusCode], 657 | [NSHTTPURLResponse localizedStringForStatusCode:[resp statusCode]], 658 | [resp allHeaderFields]); 659 | } 660 | } 661 | 662 | 663 | - (void)connection:(MGTwitterHTTPURLConnection *)connection didReceiveData:(NSData *)data 664 | { 665 | // Append the new data to the receivedData. 666 | [connection appendData:data]; 667 | } 668 | 669 | 670 | - (void)connection:(MGTwitterHTTPURLConnection *)connection didFailWithError:(NSError *)error 671 | { 672 | // Inform delegate. 673 | if ([self _isValidDelegateForSelector:@selector(requestFailed:withError:)]) 674 | [_delegate requestFailed:[connection identifier] withError:error]; 675 | 676 | // Release the connection. 677 | [_connections removeObjectForKey:[connection identifier]]; 678 | } 679 | 680 | 681 | - (void)connectionDidFinishLoading:(MGTwitterHTTPURLConnection *)connection 682 | { 683 | // Inform delegate. 684 | if ([self _isValidDelegateForSelector:@selector(requestSucceeded:)]) 685 | [_delegate requestSucceeded:[connection identifier]]; 686 | 687 | NSData *receivedData = [connection data]; 688 | if (receivedData) { 689 | if (NO) { 690 | // Dump data as string for debugging. 691 | NSString *dataString = [NSString stringWithUTF8String:[receivedData bytes]]; 692 | NSLog(@"Succeeded! Received %d bytes of data:\r\r%@", [receivedData length], dataString); 693 | } 694 | 695 | if (NO) { 696 | // Dump XML to file for debugging. 697 | NSString *dataString = [NSString stringWithUTF8String:[receivedData bytes]]; 698 | [dataString writeToFile:[@"~/Desktop/twitter_messages.xml" stringByExpandingTildeInPath] 699 | atomically:NO encoding:NSUnicodeStringEncoding error:NULL]; 700 | } 701 | 702 | if ([connection responseType] == MGTwitterImage) { 703 | // Create image from data. 704 | #if TARGET_OS_IPHONE 705 | UIImage *image = [[[UIImage alloc] initWithData:[connection data]] autorelease]; 706 | #else 707 | NSImage *image = [[[NSImage alloc] initWithData:[connection data]] autorelease]; 708 | #endif 709 | 710 | // Inform delegate. 711 | if ([self _isValidDelegateForSelector:@selector(imageReceived:forRequest:)]) 712 | [_delegate imageReceived:image forRequest:[connection identifier]]; 713 | } else { 714 | // Parse XML appropriately. 715 | [self _parseXMLForConnection:connection]; 716 | } 717 | } 718 | 719 | // Release the connection. 720 | [_connections removeObjectForKey:[connection identifier]]; 721 | } 722 | 723 | 724 | #pragma mark - 725 | #pragma mark Twitter API methods 726 | #pragma mark - 727 | 728 | 729 | #pragma mark Account methods 730 | 731 | 732 | - (NSString *)checkUserCredentials 733 | { 734 | NSString *path = @"account/verify_credentials.xml"; 735 | 736 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 737 | requestType:MGTwitterAccountRequest 738 | responseType:MGTwitterGeneric]; 739 | } 740 | 741 | 742 | - (NSString *)endUserSession 743 | { 744 | NSString *path = @"account/end_session"; // deliberately no format specified 745 | 746 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 747 | requestType:MGTwitterAccountRequest 748 | responseType:MGTwitterGeneric]; 749 | } 750 | 751 | 752 | - (NSString *)enableUpdatesFor:(NSString *)username 753 | { 754 | // i.e. follow 755 | if (!username) { 756 | return nil; 757 | } 758 | NSString *path = [NSString stringWithFormat:@"friendships/create/%@.xml", username]; 759 | 760 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 761 | requestType:MGTwitterAccountRequest 762 | responseType:MGTwitterUser]; 763 | } 764 | 765 | 766 | - (NSString *)disableUpdatesFor:(NSString *)username 767 | { 768 | // i.e. no longer follow 769 | if (!username) { 770 | return nil; 771 | } 772 | NSString *path = [NSString stringWithFormat:@"friendships/destroy/%@.xml", username]; 773 | 774 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 775 | requestType:MGTwitterAccountRequest 776 | responseType:MGTwitterUser]; 777 | } 778 | 779 | 780 | - (NSString *)isUser:(NSString *)username1 receivingUpdatesFor:(NSString *)username2 781 | { 782 | if (!username1 || !username2) { 783 | return nil; 784 | } 785 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 786 | [params setObject:username1 forKey:@"user_a"]; 787 | [params setObject:username2 forKey:@"user_b"]; 788 | 789 | NSString *path = @"friendships/exists.xml"; 790 | 791 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 792 | requestType:MGTwitterAccountRequest 793 | responseType:MGTwitterMiscellaneous]; 794 | } 795 | 796 | 797 | - (NSString *)enableNotificationsFor:(NSString *)username 798 | { 799 | if (!username) { 800 | return nil; 801 | } 802 | NSString *path = [NSString stringWithFormat:@"notifications/follow/%@.xml", username]; 803 | 804 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 805 | requestType:MGTwitterAccountRequest 806 | responseType:MGTwitterUser]; 807 | } 808 | 809 | 810 | - (NSString *)disableNotificationsFor:(NSString *)username 811 | { 812 | if (!username) { 813 | return nil; 814 | } 815 | NSString *path = [NSString stringWithFormat:@"notifications/leave/%@.xml", username]; 816 | 817 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 818 | requestType:MGTwitterAccountRequest 819 | responseType:MGTwitterUser]; 820 | } 821 | 822 | 823 | - (NSString *)getRateLimitStatus 824 | { 825 | NSString *path = @"account/rate_limit_status.xml"; 826 | 827 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 828 | requestType:MGTwitterAccountRequest 829 | responseType:MGTwitterMiscellaneous]; 830 | } 831 | 832 | 833 | - (NSString *)setLocation:(NSString *)location 834 | { 835 | if (!location) { 836 | return nil; 837 | } 838 | 839 | NSString *path = @"account/update_location.xml"; 840 | 841 | NSString *trimmedText = location; 842 | if ([trimmedText length] > MAX_LOCATION_LENGTH) { 843 | trimmedText = [trimmedText substringToIndex:MAX_LOCATION_LENGTH]; 844 | } 845 | 846 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 847 | [params setObject:trimmedText forKey:@"location"]; 848 | NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO]; 849 | 850 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 851 | queryParameters:params body:body 852 | requestType:MGTwitterAccountRequest 853 | responseType:MGTwitterGeneric]; 854 | } 855 | 856 | 857 | - (NSString *)setNotificationsDeliveryMethod:(NSString *)method 858 | { 859 | NSString *deliveryMethod = method; 860 | if (!method || [method length] == 0) { 861 | deliveryMethod = @"none"; 862 | } 863 | 864 | NSString *path = @"account/update_delivery_device.xml"; 865 | 866 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 867 | if (deliveryMethod) { 868 | [params setObject:deliveryMethod forKey:@"device"]; 869 | } 870 | 871 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:params body:nil 872 | requestType:MGTwitterAccountRequest 873 | responseType:MGTwitterGeneric]; 874 | } 875 | 876 | 877 | - (NSString *)block:(NSString *)username 878 | { 879 | if (!username) { 880 | return nil; 881 | } 882 | 883 | NSString *path = [NSString stringWithFormat:@"blocks/create/%@.xml", username]; 884 | 885 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 886 | requestType:MGTwitterAccountRequest 887 | responseType:MGTwitterUser]; 888 | } 889 | 890 | 891 | - (NSString *)unblock:(NSString *)username 892 | { 893 | if (!username) { 894 | return nil; 895 | } 896 | 897 | NSString *path = [NSString stringWithFormat:@"blocks/destroy/%@.xml", username]; 898 | 899 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 900 | requestType:MGTwitterAccountRequest 901 | responseType:MGTwitterUser]; 902 | } 903 | 904 | 905 | - (NSString *)testService 906 | { 907 | NSString *path = @"help/test.xml"; 908 | 909 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 910 | requestType:MGTwitterAccountRequest 911 | responseType:MGTwitterGeneric]; 912 | } 913 | 914 | 915 | - (NSString *)getDowntimeSchedule 916 | { 917 | NSString *path = @"help/downtime_schedule.xml"; 918 | 919 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 920 | requestType:MGTwitterAccountRequest 921 | responseType:MGTwitterMiscellaneous]; 922 | } 923 | 924 | 925 | #pragma mark Retrieving updates 926 | 927 | 928 | - (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum 929 | { 930 | // Included for backwards-compatibility. 931 | return [self getFollowedTimelineFor:username since:date startingAtPage:pageNum count:0]; // zero means default 932 | } 933 | 934 | 935 | - (NSString *)getFollowedTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)count 936 | { 937 | NSString *path = @"statuses/friends_timeline.xml"; 938 | 939 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 940 | if (date) { 941 | [params setObject:[self _dateToHTTP:date] forKey:@"since"]; 942 | } 943 | if (pageNum > 0) { 944 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 945 | } 946 | if (username) { 947 | path = [NSString stringWithFormat:@"statuses/friends_timeline/%@.xml", username]; 948 | } 949 | int tweetCount = DEFAULT_TWEET_COUNT; 950 | if (count > 0) { 951 | tweetCount = count; 952 | } 953 | [params setObject:[NSString stringWithFormat:@"%d", tweetCount] forKey:@"count"]; 954 | 955 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 956 | requestType:MGTwitterStatusesRequest 957 | responseType:MGTwitterStatuses]; 958 | } 959 | 960 | 961 | - (NSString *)getFollowedTimelineFor:(NSString *)username sinceID:(int)updateID startingAtPage:(int)pageNum count:(int)count 962 | { 963 | NSString *path = @"statuses/friends_timeline.xml"; 964 | 965 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 966 | if (updateID > 0) { 967 | [params setObject:[NSString stringWithFormat:@"%d", updateID] forKey:@"since_id"]; 968 | } 969 | if (pageNum > 0) { 970 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 971 | } 972 | if (username) { 973 | path = [NSString stringWithFormat:@"statuses/friends_timeline/%@.xml", username]; 974 | } 975 | int tweetCount = DEFAULT_TWEET_COUNT; 976 | if (count > 0) { 977 | tweetCount = count; 978 | } 979 | [params setObject:[NSString stringWithFormat:@"%d", tweetCount] forKey:@"count"]; 980 | 981 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 982 | requestType:MGTwitterStatusesRequest 983 | responseType:MGTwitterStatuses]; 984 | } 985 | 986 | 987 | - (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date count:(int)numUpdates 988 | { 989 | // Included for backwards-compatibility. 990 | return [self getUserTimelineFor:username since:date startingAtPage:0 count:numUpdates]; 991 | } 992 | 993 | 994 | - (NSString *)getUserTimelineFor:(NSString *)username since:(NSDate *)date startingAtPage:(int)pageNum count:(int)numUpdates 995 | { 996 | NSString *path = @"statuses/user_timeline.xml"; 997 | 998 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 999 | if (date) { 1000 | [params setObject:[self _dateToHTTP:date] forKey:@"since"]; 1001 | } 1002 | if (pageNum > 0) { 1003 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1004 | } 1005 | if (numUpdates > 0) { 1006 | [params setObject:[NSString stringWithFormat:@"%d", numUpdates] forKey:@"count"]; 1007 | } 1008 | if (username) { 1009 | path = [NSString stringWithFormat:@"statuses/user_timeline/%@.xml", username]; 1010 | } 1011 | 1012 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1013 | requestType:MGTwitterStatusesRequest 1014 | responseType:MGTwitterStatuses]; 1015 | } 1016 | 1017 | 1018 | - (NSString *)getUserTimelineFor:(NSString *)username sinceID:(int)updateID startingAtPage:(int)pageNum count:(int)numUpdates 1019 | { 1020 | NSString *path = @"statuses/user_timeline.xml"; 1021 | 1022 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1023 | if (updateID > 0) { 1024 | [params setObject:[NSString stringWithFormat:@"%d", updateID] forKey:@"since_id"]; 1025 | } 1026 | if (pageNum > 0) { 1027 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1028 | } 1029 | if (numUpdates > 0) { 1030 | [params setObject:[NSString stringWithFormat:@"%d", numUpdates] forKey:@"count"]; 1031 | } 1032 | if (username) { 1033 | path = [NSString stringWithFormat:@"statuses/user_timeline/%@.xml", username]; 1034 | } 1035 | 1036 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1037 | requestType:MGTwitterStatusesRequest 1038 | responseType:MGTwitterStatuses]; 1039 | } 1040 | 1041 | 1042 | - (NSString *)getUserUpdatesArchiveStartingAtPage:(int)pageNum 1043 | { 1044 | NSString *path = @"account/archive.xml"; 1045 | 1046 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1047 | if (pageNum > 0) { 1048 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1049 | } 1050 | 1051 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1052 | requestType:MGTwitterStatusesRequest 1053 | responseType:MGTwitterStatuses]; 1054 | } 1055 | 1056 | 1057 | - (NSString *)getPublicTimelineSinceID:(int)updateID 1058 | { 1059 | NSString *path = @"statuses/public_timeline.xml"; 1060 | 1061 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1062 | if (updateID > 0) { 1063 | [params setObject:[NSString stringWithFormat:@"%d", updateID] forKey:@"since_id"]; 1064 | } 1065 | 1066 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1067 | requestType:MGTwitterStatusesRequest 1068 | responseType:MGTwitterStatuses]; 1069 | } 1070 | 1071 | 1072 | - (NSString *)getRepliesStartingAtPage:(int)pageNum 1073 | { 1074 | NSString *path = @"statuses/replies.xml"; 1075 | 1076 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1077 | if (pageNum > 0) { 1078 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1079 | } 1080 | 1081 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1082 | requestType:MGTwitterRepliesRequest 1083 | responseType:MGTwitterStatuses]; 1084 | } 1085 | 1086 | 1087 | - (NSString *)getFavoriteUpdatesFor:(NSString *)username startingAtPage:(int)pageNum 1088 | { 1089 | NSString *path = @"favorites.xml"; 1090 | 1091 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1092 | if (pageNum > 0) { 1093 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1094 | } 1095 | if (username) { 1096 | path = [NSString stringWithFormat:@"favorites/%@.xml", username]; 1097 | } 1098 | 1099 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1100 | requestType:MGTwitterStatusesRequest 1101 | responseType:MGTwitterStatuses]; 1102 | } 1103 | 1104 | 1105 | - (NSString *)getUpdate:(int)updateID 1106 | { 1107 | NSString *path = [NSString stringWithFormat:@"statuses/show/%d.xml", updateID]; 1108 | 1109 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 1110 | requestType:MGTwitterStatusesRequest 1111 | responseType:MGTwitterStatus]; 1112 | } 1113 | 1114 | 1115 | #pragma mark Retrieving direct messages 1116 | 1117 | 1118 | - (NSString *)getDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum 1119 | { 1120 | NSString *path = @"direct_messages.xml"; 1121 | 1122 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1123 | if (date) { 1124 | [params setObject:[self _dateToHTTP:date] forKey:@"since"]; 1125 | } 1126 | if (pageNum > 0) { 1127 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1128 | } 1129 | 1130 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1131 | requestType:MGTwitterDirectMessagesRequest 1132 | responseType:MGTwitterDirectMessages]; 1133 | } 1134 | 1135 | 1136 | - (NSString *)getDirectMessagesSinceID:(int)updateID startingAtPage:(int)pageNum 1137 | { 1138 | NSString *path = @"direct_messages.xml"; 1139 | 1140 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1141 | if (updateID > 0) { 1142 | [params setObject:[NSString stringWithFormat:@"%d", updateID] forKey:@"since_id"]; 1143 | } 1144 | if (pageNum > 0) { 1145 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1146 | } 1147 | 1148 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1149 | requestType:MGTwitterDirectMessagesRequest 1150 | responseType:MGTwitterDirectMessages]; 1151 | } 1152 | 1153 | 1154 | - (NSString *)getSentDirectMessagesSince:(NSDate *)date startingAtPage:(int)pageNum 1155 | { 1156 | NSString *path = @"direct_messages/sent.xml"; 1157 | 1158 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1159 | if (date) { 1160 | [params setObject:[self _dateToHTTP:date] forKey:@"since"]; 1161 | } 1162 | if (pageNum > 0) { 1163 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1164 | } 1165 | 1166 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1167 | requestType:MGTwitterDirectMessagesRequest 1168 | responseType:MGTwitterDirectMessages]; 1169 | } 1170 | 1171 | 1172 | - (NSString *)getSentDirectMessagesSinceID:(int)updateID startingAtPage:(int)pageNum 1173 | { 1174 | NSString *path = @"direct_messages/sent.xml"; 1175 | 1176 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1177 | if (updateID > 0) { 1178 | [params setObject:[NSString stringWithFormat:@"%d", updateID] forKey:@"since_id"]; 1179 | } 1180 | if (pageNum > 0) { 1181 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1182 | } 1183 | 1184 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1185 | requestType:MGTwitterDirectMessagesRequest 1186 | responseType:MGTwitterDirectMessages]; 1187 | } 1188 | 1189 | 1190 | #pragma mark Retrieving user information 1191 | 1192 | 1193 | - (NSString *)getUserInformationFor:(NSString *)username 1194 | { 1195 | if (!username) { 1196 | return nil; 1197 | } 1198 | NSString *path = [NSString stringWithFormat:@"users/show/%@.xml", username]; 1199 | 1200 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 1201 | requestType:MGTwitterUserInfoRequest 1202 | responseType:MGTwitterUser]; 1203 | } 1204 | 1205 | 1206 | - (NSString *)getUserInformationForEmail:(NSString *)email 1207 | { 1208 | NSString *path = @"users/show.xml"; 1209 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1210 | if (email) { 1211 | [params setObject:email forKey:@"email"]; 1212 | } else { 1213 | return nil; 1214 | } 1215 | 1216 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1217 | requestType:MGTwitterUserInfoRequest 1218 | responseType:MGTwitterUser]; 1219 | } 1220 | 1221 | 1222 | - (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtPage:(int)pageNum 1223 | { 1224 | NSString *path = @"statuses/friends.xml"; 1225 | 1226 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1227 | if (username) { 1228 | path = [NSString stringWithFormat:@"statuses/friends/%@.xml", username]; 1229 | } 1230 | if (pageNum > 0) { 1231 | [params setObject:[NSString stringWithFormat:@"%d", pageNum] forKey:@"page"]; 1232 | } 1233 | 1234 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1235 | requestType:MGTwitterUserInfoRequest 1236 | responseType:MGTwitterUsers]; 1237 | } 1238 | 1239 | 1240 | - (NSString *)getFollowersIncludingCurrentStatus:(BOOL)flag 1241 | { 1242 | NSString *path = @"statuses/followers.xml"; 1243 | 1244 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1245 | if (!flag) { 1246 | [params setObject:@"true" forKey:@"lite"]; // slightly bizarre, but correct. 1247 | } 1248 | 1249 | return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 1250 | requestType:MGTwitterUserInfoRequest 1251 | responseType:MGTwitterUsers]; 1252 | } 1253 | 1254 | 1255 | - (NSString *)getFeaturedUsers 1256 | { 1257 | NSString *path = @"statuses/featured.xml"; 1258 | 1259 | return [self _sendRequestWithMethod:nil path:path queryParameters:nil body:nil 1260 | requestType:MGTwitterUserInfoRequest 1261 | responseType:MGTwitterUsers]; 1262 | } 1263 | 1264 | 1265 | #pragma mark Sending and editing updates 1266 | 1267 | 1268 | - (NSString *)sendUpdate:(NSString *)status 1269 | { 1270 | return [self sendUpdate:status inReplyTo:0]; 1271 | } 1272 | 1273 | 1274 | - (NSString *)sendUpdate:(NSString *)status inReplyTo:(int)updateID 1275 | { 1276 | // NSLog(@"send"); 1277 | if (!status) { 1278 | return nil; 1279 | } 1280 | // NSLog(@"send2"); 1281 | 1282 | NSString *path = @"statuses/update.xml"; 1283 | 1284 | NSString *trimmedText = status; 1285 | if ([trimmedText length] > MAX_MESSAGE_LENGTH) { 1286 | trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH]; 1287 | } 1288 | 1289 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1290 | [params setObject:trimmedText forKey:@"status"]; 1291 | if (updateID > 0) { 1292 | [params setObject:[NSString stringWithFormat:@"%d", updateID] forKey:@"in_reply_to_status_id"]; 1293 | } 1294 | NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO]; 1295 | 1296 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 1297 | queryParameters:params body:body 1298 | requestType:MGTwitterStatusSend 1299 | responseType:MGTwitterStatus]; 1300 | } 1301 | 1302 | 1303 | - (NSString *)deleteUpdate:(int)updateID 1304 | { 1305 | NSString *path = [NSString stringWithFormat:@"statuses/destroy/%d.xml", updateID]; 1306 | 1307 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 1308 | requestType:MGTwitterAccountRequest 1309 | responseType:MGTwitterGeneric]; 1310 | } 1311 | 1312 | 1313 | - (NSString *)markUpdate:(int)updateID asFavorite:(BOOL)flag 1314 | { 1315 | NSString *path = [NSString stringWithFormat:@"favorites/%@/%d.xml", 1316 | (flag) ? @"create" : @"destroy" , 1317 | updateID]; 1318 | 1319 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 1320 | requestType:MGTwitterAccountRequest 1321 | responseType:MGTwitterStatus]; 1322 | } 1323 | 1324 | 1325 | #pragma mark Sending and editing direct messages 1326 | 1327 | 1328 | - (NSString *)sendDirectMessage:(NSString *)message to:(NSString *)username 1329 | { 1330 | if (!message || !username) { 1331 | return nil; 1332 | } 1333 | 1334 | NSString *path = @"direct_messages/new.xml"; 1335 | 1336 | NSString *trimmedText = message; 1337 | if ([trimmedText length] > MAX_MESSAGE_LENGTH) { 1338 | trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH]; 1339 | } 1340 | 1341 | NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0]; 1342 | [params setObject:trimmedText forKey:@"text"]; 1343 | [params setObject:username forKey:@"user"]; 1344 | NSString *body = [self _queryStringWithBase:nil parameters:params prefixed:NO]; 1345 | 1346 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path 1347 | queryParameters:params body:body 1348 | requestType:MGTwitterDirectMessageSend 1349 | responseType:MGTwitterDirectMessage]; 1350 | } 1351 | 1352 | 1353 | - (NSString *)deleteDirectMessage:(int)updateID 1354 | { 1355 | NSString *path = [NSString stringWithFormat:@"direct_messages/destroy/%d.xml", updateID]; 1356 | 1357 | return [self _sendRequestWithMethod:HTTP_POST_METHOD path:path queryParameters:nil body:nil 1358 | requestType:MGTwitterAccountRequest 1359 | responseType:MGTwitterGeneric]; 1360 | } 1361 | 1362 | 1363 | @end 1364 | --------------------------------------------------------------------------------