adium 3188:5871e3c08231: Patch from astrange and kbotc which fix...

commits at adium.im commits at adium.im
Wed May 12 01:38:41 UTC 2010


details:	http://hg.adium.im/adium/rev/5871e3c08231
revision:	3188:5871e3c08231
author:		Evan Schoenberg
date:		Tue May 11 20:37:20 2010 -0500

Patch from astrange and kbotc which fixes cursor support for long Twitter follow lists. Fixes #13590.

diffs (879 lines):

diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/AILaconicaAccount.m
--- a/Plugins/Twitter Plugin/AILaconicaAccount.m	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/AILaconicaAccount.m	Tue May 11 20:37:20 2010 -0500
@@ -16,6 +16,14 @@
 
 #import "AILaconicaAccount.h"
 #import "AITwitterURLParser.h"
+#import <Adium/AIContactObserverManager.h>
+
+ at interface AITwitterAccount()
+
+- (BOOL)checkForCursorSupport;
+
+ at end
+
 
 @implementation AILaconicaAccount
 
@@ -26,6 +34,7 @@
 												  [NSNumber numberWithBool:YES], LACONICA_PREFERENCE_SSL, nil]
 										forGroup:LACONICA_PREF_GROUP
 										  object:self];
+	supportsCursors = [self checkForCursorSupport];
 }
 
 - (void)connect
@@ -184,4 +193,16 @@
 	return NO;
 }
 
+/*!
+ * @brief Check if the server supports cursor based userlists.
+ *
+ * @returns YES if the support cursor lists, NO if the account doesn't support it.
+ *
+ * XXX This should probably do some actual checking so we don't have to touch this when it goes live.
+ */
+- (BOOL)checkForCursorSupport
+{
+	return NO;
+}
+
 @end
diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/AITwitterAccount.h
--- a/Plugins/Twitter Plugin/AITwitterAccount.h	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/AITwitterAccount.h	Tue May 11 20:37:20 2010 -0500
@@ -134,6 +134,8 @@
 #define TWITTER_INFO_DISPLAY_NAME			@"name"
 #define TWITTER_INFO_UID					@"screen_name"
 #define TWITTER_INFO_ICON					@"profile_image_url"
+#define TWITTER_INFO_PREVIOUS_CURSOR		@"previous_cursor"
+#define TWITTER_INFO_NEXT_CURSOR			@"next_cursor"
 
 // Rate Limit
 #define TWITTER_RATE_LIMIT_HOURLY_LIMIT		@"hourly-limit"
@@ -152,6 +154,7 @@
 	
 	BOOL				followedTimelineCompleted;
 	BOOL				repliesCompleted;
+	BOOL				supportsCursors;
 	NSMutableArray		*queuedUpdates;
 	NSMutableArray		*queuedDM;
 	NSMutableArray		*queuedOutgoingDM;
@@ -169,6 +172,7 @@
 
 @property (readonly, nonatomic) BOOL useSSL;
 @property (readonly, nonatomic) BOOL useOAuth;
+ at property (readonly, nonatomic) BOOL supportsCursors;
 @property (readonly, nonatomic) NSString *consumerKey;
 @property (readonly, nonatomic) NSString *secretKey;
 @property (readonly, nonatomic) NSString *tokenRequestURL;
diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/AITwitterAccount.m
--- a/Plugins/Twitter Plugin/AITwitterAccount.m	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/AITwitterAccount.m	Tue May 11 20:37:20 2010 -0500
@@ -74,6 +74,9 @@
 @end
 
 @implementation AITwitterAccount
+
+ at synthesize supportsCursors;
+
 - (void)initAccount
 {
 	[super initAccount];
@@ -82,11 +85,12 @@
 	queuedUpdates = [[NSMutableArray alloc] init];
 	queuedDM = [[NSMutableArray alloc] init];
 	queuedOutgoingDM = [[NSMutableArray alloc] init];
-
+	supportsCursors = YES;
+	
 	[[NSNotificationCenter defaultCenter] addObserver:self
-							     selector:@selector(chatDidOpen:) 
-									 name:Chat_DidOpen
-								   object:nil];
+											 selector:@selector(chatDidOpen:) 
+												 name:Chat_DidOpen
+											   object:nil];
 	
 	[adium.preferenceController registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
 												  [NSNumber numberWithInt:TWITTER_UPDATE_INTERVAL_MINUTES], TWITTER_PREFERENCE_UPDATE_INTERVAL,
@@ -94,12 +98,12 @@
 												  [NSNumber numberWithBool:YES], TWITTER_PREFERENCE_LOAD_CONTACTS, nil]
 										forGroup:TWITTER_PREFERENCE_GROUP_UPDATES
 										  object:self];
-
+	
 	// If we don't have a server set, set our default (if we have one)
 	if (!self.host && self.defaultServer) {
 		[self setPreference:self.defaultServer forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
 	}
-
+	
 	[adium.preferenceController registerPreferenceObserver:self forGroup:TWITTER_PREFERENCE_GROUP_UPDATES];
 	[adium.preferenceController informObserversOfChangedKey:nil inGroup:TWITTER_PREFERENCE_GROUP_UPDATES object:self];
 }
@@ -217,8 +221,8 @@
 		[newTimelineChat setDisplayName:self.timelineChatName];
 		
 		timelineBookmark = [adium.contactController bookmarkForChat:newTimelineChat inGroup:[adium.contactController groupWithUID:TWITTER_REMOTE_GROUP_NAME]];
-
-
+		
+		
 		if(!timelineBookmark) {
 			AILog(@"%@ Timeline bookmark is nil! Tried checking for existing bookmark for chat name %@, and creating a bookmark for chat %@ in group %@", self.timelineChatName, newTimelineChat, [adium.contactController groupWithUID:TWITTER_REMOTE_GROUP_NAME]);
 		}
@@ -428,7 +432,7 @@
 - (void)setSocialNetworkingStatusMessage:(NSAttributedString *)statusMessage
 {
 	NSString *requestID = [twitterEngine sendUpdate:[statusMessage string]];
-
+	
 	if(requestID) {
 		[self setRequestType:AITwitterSendUpdate
 				forRequestID:requestID
@@ -466,7 +470,7 @@
 			
 			AILogWithSignature(@"%@ Sending update [in reply to %@]: %@", self, [inContentMessage.chat valueForProperty:@"TweetInReplyToStatusID"], inContentMessage.encodedMessage);
 		}
-
+		
 	} else {		
 		requestID = [twitterEngine sendDirectMessage:inContentMessage.encodedMessage
 												  to:inContentMessage.destination.UID];
@@ -627,7 +631,7 @@
 	[menuItem setImage:serviceIcon];
 	[menuItem setRepresentedObject:inContact];
 	[menuItemArray addObject:menuItem];	
-
+	
 	menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[NSString stringWithFormat:AILocalizedString(@"Enable device notifications for %@", "Enable sending Twitter notifications to your phone (device)"), inContact.UID]
 																	 target:self
 																	 action:@selector(enableOrDisableNotifications:)
@@ -636,7 +640,7 @@
 	[menuItem setImage:serviceIcon];
 	[menuItem setRepresentedObject:inContact];
 	[menuItemArray addObject:menuItem];
-
+	
 	menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[NSString stringWithFormat:AILocalizedString(@"Disable device notifications for %@", "Disable sending Twitter notifications to your phone"), inContact.UID]
 																	 target:self
 																	 action:@selector(enableOrDisableNotifications:)
@@ -670,7 +674,7 @@
 	if(![menuItem.representedObject isKindOfClass:[AIListContact class]]) {
 		return;
 	}
-
+	
 	BOOL enableNotification = menuItem.tag;
 	AIListContact *contact = menuItem.representedObject;
 	
@@ -680,7 +684,7 @@
 	
 	if (enableNotification) {
 		requestID = [twitterEngine enableNotificationsFor:contact.UID];
-
+		
 		if (requestID) {
 			[self setRequestType:AITwitterNotificationEnable
 					forRequestID:requestID
@@ -688,7 +692,7 @@
 		} else {
 			initialFailure = YES;
 		}
-	
+		
 	} else {
 		requestID = [twitterEngine disableNotificationsFor:contact.UID];
 		
@@ -703,8 +707,8 @@
 	
 	if (initialFailure) {
 		[adium.interfaceController handleErrorMessage:(enableNotification ?
-														AILocalizedString(@"Unable to Enable Notifications", nil) :
-														AILocalizedString(@"Unable to Disable Notifications", nil))
+													   AILocalizedString(@"Unable to Enable Notifications", nil) :
+													   AILocalizedString(@"Unable to Disable Notifications", nil))
 									  withDescription:AILocalizedString(@"Unable to connect to the Twitter server.", nil)];
 	}
 }
@@ -758,17 +762,17 @@
 	NSMenuItem *menuItem;
 	
 	menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Update Tweets",nil)
-																	target:self
-																	action:@selector(periodicUpdate)
-															 keyEquivalent:@""] autorelease];
+																	 target:self
+																	 action:@selector(periodicUpdate)
+															  keyEquivalent:@""] autorelease];
 	[menuItemArray addObject:menuItem];
-
+	
 	menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Reply to a Tweet",nil)
 																	 target:self
 																	 action:@selector(replyToTweet)
 															  keyEquivalent:@""] autorelease];
 	[menuItemArray addObject:menuItem];
-
+	
 	menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Get Rate Limit Amount",nil)
 																	 target:self
 																	 action:@selector(getRateLimitAmount)
@@ -820,15 +824,15 @@
 - (AIChat *)timelineChat
 {
 	AIChat *timelineChat = [adium.chatController existingChatWithName:self.timelineChatName
-							onAccount:self];
+															onAccount:self];
 	
 	if (!timelineChat) {
 		timelineChat = [adium.chatController chatWithName:self.timelineChatName
-						identifier:nil
-						onAccount:self
-						chatCreationInfo:nil];
+											   identifier:nil
+												onAccount:self
+										 chatCreationInfo:nil];
 	}
-
+	
 	return timelineChat;	
 }
 
@@ -994,7 +998,7 @@
 			// Avoid pushing an icon update which we just downloaded.
 			if(![self boolValueForProperty:TWITTER_PROPERTY_REQUESTED_USER_ICON]) {
 				NSString *requestID = [twitterEngine updateProfileImage:[prefDict objectForKey:KEY_USER_ICON]];
-			
+				
 				if(requestID) {
 					AILogWithSignature(@"%@ Pushing self icon update", self);
 					
@@ -1034,12 +1038,20 @@
 				// Delay updates when loading our contacts list.
 				[self silenceAllContactUpdatesForInterval:18.0];
 				// Grab our user list.
-				NSString	*requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtPage:1];
+				NSString	*requestID;
+				NSString	*friendRequestType;
+				if (self.supportsCursors) {
+					requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtCursor:-1];
+					friendRequestType = @"Cursor";
+				} else {
+					requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtPage:1];
+					friendRequestType = @"Page";
+				}
 				
 				if (requestID) {
 					[self setRequestType:AITwitterInitialUserInfo
 							forRequestID:requestID
-						  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1] forKey:@"Page"]];
+						  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1] forKey:friendRequestType]];
 				}
 			} else {
 				[self removeAllContacts];
@@ -1090,11 +1102,11 @@
 	} else {
 		--pendingUpdateCount;
 	}
-
+	
 	// Pull followed timeline
 	lastID = [self preferenceForKey:TWITTER_PREFERENCE_TIMELINE_LAST_ID
 							  group:TWITTER_PREFERENCE_GROUP_UPDATES];
-
+	
 	requestID = [twitterEngine getFollowedTimelineFor:nil
 											  sinceID:lastID
 									   startingAtPage:1
@@ -1384,7 +1396,7 @@
 		NSUInteger startIndex = message.length;
 		
 		[mutableMessage appendString:@"  (" withAttributes:nil];
-	
+		
 		BOOL commaNeeded = NO;
 		
 		// Append a link to the tweet this is in reply to
@@ -1393,7 +1405,7 @@
 													  userID:replyUserID
 													statusID:replyTweetID
 													 context:nil];
-
+			
 			if([inMessage hasPrefix:@"@"] &&
 			   inMessage.length >= replyUserID.length + 1 &&
 			   [replyUserID isCaseInsensitivelyEqualToString:[inMessage substringWithRange:NSMakeRange(1, replyUserID.length)]]) {
@@ -1435,7 +1447,7 @@
 						
 						[mutableMessage appendString:@", " withAttributes:nil];
 					}
-									
+					
 					linkAddress = [self addressForLinkType:AITwitterLinkQuote
 													userID:userID
 												  statusID:tweetID
@@ -1479,16 +1491,16 @@
 			}
 			
 			[mutableMessage appendString:@", " withAttributes:nil];
-
+			
 			linkAddress = [self addressForLinkType:AITwitterLinkFavorite
 											userID:userID
 										  statusID:tweetID
 										   context:nil];
-
+			
 			[mutableMessage appendAttributedString:[self attributedStringWithLinkLabel:@"\u2606"
 																	   linkDestination:linkAddress
 																			 linkClass:AITwitterFavoriteClassName]];
-
+			
 			[mutableMessage appendString:@", " withAttributes:nil];
 			
 			linkAddress = [self addressForLinkType:AITwitterLinkStatus
@@ -1499,16 +1511,16 @@
 			[mutableMessage appendAttributedString:[self attributedStringWithLinkLabel:@"#"
 																	   linkDestination:linkAddress
 																			 linkClass:AITwitterStatusLinkClassName]];
-
+			
 		}
-	
+		
 		[mutableMessage appendString:@")" withAttributes:nil];
 		
 		[mutableMessage addAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
 									   [NSNumber numberWithBool:YES], AITwitterActionLinksAttributeName,
 									   [NSNumber numberWithBool:YES], AIHiddenMessagePartAttributeName, nil]
 								range:NSMakeRange(startIndex, mutableMessage.length - startIndex)];
-	
+		
 		return mutableMessage;
 	} else {
 		return message;
@@ -1646,7 +1658,7 @@
 			} else {
 				fromObject = (id)self;
 			}
-
+			
 			NSAttributedString *message = [self parseMessage:text
 													 tweetID:[status objectForKey:TWITTER_STATUS_ID]
 													  userID:contactUID
@@ -1738,8 +1750,8 @@
 		AIChat *timelineChat = self.timelineChat;
 		
 		[adium.contentController displayEvent:AILocalizedString(@"Your tweet has been successfully deleted.", nil)
-									  ofType:@"delete"
-									  inChat:timelineChat];
+									   ofType:@"delete"
+									   inChat:timelineChat];
 	} else if ([self requestTypeForRequestID:identifier] == AITwitterDestroyDM) {
 		AIListContact *contact = [[self dictionaryForRequestID:identifier] objectForKey:@"ListContact"];
 		AIChat *chat = [adium.chatController chatWithContact:contact];
@@ -1871,7 +1883,7 @@
 		case AITwitterFavoriteNo:
 		{
 			AIChat *timelineChat = self.timelineChat;
-
+			
 			if (error.code == 403) {
 				// We've attempted to add or remove when we already have it marked as such. Try the opposite.
 				BOOL addAsFavorite = ([self requestTypeForRequestID:identifier] == AITwitterFavoriteNo);
@@ -1923,12 +1935,12 @@
 			
 		case AITwitterDestroyDM:
 		{
-				AIListContact *contact = [[self dictionaryForRequestID:identifier] objectForKey:@"ListContact"];
-				AIChat *chat = [adium.chatController chatWithContact:contact];
-				
-				[adium.contentController displayEvent:[NSString stringWithFormat:AILocalizedString(@"The direct message failed to delete. %@", nil), [self errorMessageForError:error]]
-											   ofType:@"delete"
-											   inChat:chat];	
+			AIListContact *contact = [[self dictionaryForRequestID:identifier] objectForKey:@"ListContact"];
+			AIChat *chat = [adium.chatController chatWithContact:contact];
+			
+			[adium.contentController displayEvent:[NSString stringWithFormat:AILocalizedString(@"The direct message failed to delete. %@", nil), [self errorMessageForError:error]]
+										   ofType:@"delete"
+										   inChat:chat];	
 			break;
 		}
 			
@@ -1941,7 +1953,7 @@
 			// While we don't handle the errors, it's a good idea to not have a "default" just to prevent accidentally letting something
 			// we should really handle slip through.
 			break;
-
+			
 	}
 	
 	AILogWithSignature(@"%@ Request failed (%@ - %u) - %@", self, identifier, [self requestTypeForRequestID:identifier], error);
@@ -2067,7 +2079,7 @@
 					
 					[self displayQueuedUpdatesForRequestType:[self requestTypeForRequestID:identifier]];
 				}
-
+				
 				if (![self preferenceForKey:TWITTER_PREFERENCE_EVER_LOADED_TIMELINE group:TWITTER_PREFERENCE_GROUP_UPDATES]) {
 					[self setPreference:[NSNumber numberWithBool:YES]
 								 forKey:TWITTER_PREFERENCE_EVER_LOADED_TIMELINE
@@ -2077,7 +2089,7 @@
 		}
 	} else if ([self requestTypeForRequestID:identifier] == AITwitterProfileStatusUpdates) {
 		AIListContact *listContact = [[self dictionaryForRequestID:identifier] objectForKey:@"ListContact"];
-
+		
 		NSMutableArray *profileArray = [[[listContact profileArray] mutableCopy] autorelease];
 		
 		AILogWithSignature(@"%@ Updating statuses for profile, user %@", self, listContact);
@@ -2101,9 +2113,9 @@
 		if (statuses.count) {
 			[adium.contentController displayEvent:AILocalizedString(@"Tweet successfully sent.", nil)
 										   ofType:@"tweet"
-										  inChat:self.timelineChat];
+										   inChat:self.timelineChat];
 		}
-				
+		
 		for(NSDictionary *update in statuses) {
 			[[NSNotificationCenter defaultCenter] postNotificationName:AITwitterNotificationPostedStatus
 																object:update
@@ -2122,7 +2134,7 @@
 	} else if ([self requestTypeForRequestID:identifier] == AITwitterFavoriteYes ||
 			   [self requestTypeForRequestID:identifier] == AITwitterFavoriteNo) {
 		AIChat *timelineChat = self.timelineChat;
-
+		
 		for (NSDictionary *status in statuses) {
 			NSString *message;
 			
@@ -2133,7 +2145,7 @@
 			} else {
 				message = AILocalizedString(@"The <a href=\"%@\">requested tweet</a> by <a href=\"%@\">%@</a> is no longer a favorite.", nil);
 			}
-
+			
 			NSString *userID = [[status objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_STATUS_UID];
 			
 			
@@ -2159,7 +2171,7 @@
 			
 			content.postProcessContent = NO;
 			content.coalescingKey = @"favorite";
-
+			
 			[adium.contentController receiveContentObject:content];
 		}
 	}
@@ -2197,7 +2209,7 @@
 			int	nextPage = [[[self dictionaryForRequestID:identifier] objectForKey:@"Page"] intValue] + 1;
 			
 			NSString	*requestID = [twitterEngine getDirectMessagesSinceID:lastID
-														      startingAtPage:nextPage];
+														   startingAtPage:nextPage];
 			
 			AILogWithSignature(@"%@ Pulling additional DM page %d", self, nextPage);
 			
@@ -2222,7 +2234,7 @@
 							 forKey:TWITTER_PREFERENCE_DM_LAST_ID
 							  group:TWITTER_PREFERENCE_GROUP_UPDATES];
 			}
-		
+			
 			// On first load, don't display any direct messages. Just ge the largest ID.
 			if (queuedDM.count && lastID) {
 				[self displayQueuedUpdatesForRequestType:[self requestTypeForRequestID:identifier]];
@@ -2248,62 +2260,98 @@
 		[[self preferenceForKey:TWITTER_PREFERENCE_LOAD_CONTACTS group:TWITTER_PREFERENCE_GROUP_UPDATES] boolValue]) {
 		[[AIContactObserverManager sharedManager] delayListObjectNotifications];
 		
-		// The current amount of friends per page is 100. Use >= just in case this changes.
-		BOOL nextPageNecessary = (userInfo.count >= 100);
-		
-		AILogWithSignature(@"%@ User info pull, Next page necessary: %d Count: %d", self, nextPageNecessary, userInfo.count);
+		BOOL nextPageNecessary = NO;
+		long long nextCursor = 0;
 		
 		for (NSDictionary *info in userInfo) {
-			AIListContact *listContact = [self contactWithUID:[info objectForKey:TWITTER_INFO_UID]];
+			// Iterate users and next_cursor (previous_cursor is not used yet)
 			
-			// If the user isn't in a group, set them in the Twitter group.
-			if(listContact.countOfRemoteGroupNames == 0) {
-				[listContact addRemoteGroupName:TWITTER_REMOTE_GROUP_NAME];
+			NSArray *users = [info objectForKey:@"users"];
+			
+			if (users.count > 0) {
+				
+				AILogWithSignature(@"%@ User info pull, Users count: %d", self, users.count);
+				
+				for (NSDictionary *user in users) {
+					
+					NSString *twitterInfoUID = [user objectForKey:TWITTER_INFO_UID];
+					
+					if (twitterInfoUID) {
+						
+						AIListContact *listContact = [self contactWithUID:twitterInfoUID];
+						
+						// If the user isn't in a group, set them in the Twitter group.
+						if (listContact.countOfRemoteGroupNames == 0) {
+							[listContact addRemoteGroupName:TWITTER_REMOTE_GROUP_NAME];
+						}
+						
+						// Grab the Twitter display name and set it as the remote alias.
+						if (![[listContact valueForProperty:@"Server Display Name"] isEqualToString:[user objectForKey:TWITTER_INFO_DISPLAY_NAME]]) {
+							[listContact setServersideAlias:[user objectForKey:TWITTER_INFO_DISPLAY_NAME]
+												   silently:silentAndDelayed];
+						}
+						
+						// Grab the user icon and set it as their serverside icon.
+						[self updateUserIcon:[user objectForKey:TWITTER_INFO_ICON] forContact:listContact];
+						
+						// Set the user as available.
+						[listContact setStatusWithName:nil
+											statusType:AIAvailableStatusType
+												notify:NotifyLater];
+						
+						// Set the user's status message to their current twitter status text
+						NSString *statusText = [[user objectForKey:TWITTER_INFO_STATUS] objectForKey:TWITTER_INFO_STATUS_TEXT];
+						if (!statusText) //nil if they've never tweeted
+							statusText = @"";
+						[listContact setStatusMessage:[NSAttributedString stringWithString:[statusText stringByUnescapingFromXMLWithEntities:nil]] notify:NotifyLater];
+						
+						// Set the user as online.
+						[listContact setOnline:YES notify:NotifyLater silently:silentAndDelayed];
+						
+						[listContact notifyOfChangedPropertiesSilently:silentAndDelayed];
+						AILogWithSignature(@"%@ User info pull, Next page necessary: %d", self, nextPageNecessary);
+					}
+				}	
+			}			
+			
+			NSString *nextCursorString = [info objectForKey:TWITTER_INFO_NEXT_CURSOR];
+			
+			if (([nextCursorString length] > 0) && self.supportsCursors) {
+				nextCursor = [nextCursorString longLongValue];
+				nextPageNecessary = (nextCursor > 0) ? YES:NO;
+			} else if (!self.supportsCursors) {
+				nextPageNecessary = (userInfo.count >= 100);
 			}
-		
-			// Grab the Twitter display name and set it as the remote alias.
-			if (![[listContact valueForProperty:@"Server Display Name"] isEqualToString:[info objectForKey:TWITTER_INFO_DISPLAY_NAME]]) {
-				[listContact setServersideAlias:[info objectForKey:TWITTER_INFO_DISPLAY_NAME]
-									   silently:silentAndDelayed];
-			}
-			
-			// Grab the user icon and set it as their serverside icon.
-			[self updateUserIcon:[info objectForKey:TWITTER_INFO_ICON] forContact:listContact];
-			
-			// Set the user as available.
-			[listContact setStatusWithName:nil
-								statusType:AIAvailableStatusType
-									notify:NotifyLater];
-			
-			// Set the user's status message to their current twitter status text
-			NSString *statusText = [[info objectForKey:TWITTER_INFO_STATUS] objectForKey:TWITTER_INFO_STATUS_TEXT];
-			if (!statusText) //nil if they've never tweeted
-				statusText = @"";
-			[listContact setStatusMessage:[NSAttributedString stringWithString:[statusText stringByUnescapingFromXMLWithEntities:nil]] notify:NotifyLater];
-			
-			// Set the user as online.
-			[listContact setOnline:YES notify:NotifyLater silently:silentAndDelayed];
-			
-			[listContact notifyOfChangedPropertiesSilently:silentAndDelayed];
 		}
 		
 		[[AIContactObserverManager sharedManager] endListObjectNotificationsDelay];
 		
 		if (nextPageNecessary) {
-			int	nextPage = [[[self dictionaryForRequestID:identifier] objectForKey:@"Page"] intValue] + 1;
-			NSString	*requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtPage:nextPage];
-			
-			AILogWithSignature(@"%@ Pulling additional user info page %d", self, nextPage);
-			
-			if(requestID) {
-				[self setRequestType:AITwitterInitialUserInfo
-						forRequestID:requestID
-					  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:nextPage]
-																 forKey:@"Page"]];
-			} else { 
-				[self setLastDisconnectionError:AILocalizedString(@"Unable to retrieve user list [additional fail]", "Message when a (vital) twitter request to retrieve the follow list fails")];
-				[self didDisconnect];
+			if (self.supportsCursors) {
+				NSString	*requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtCursor:nextCursor];
+				if (requestID) {
+					[self setRequestType:AITwitterInitialUserInfo
+							forRequestID:requestID
+						  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLongLong:nextCursor]
+																	 forKey:@"Cursor"]];
+				} else { 
+					[self setLastDisconnectionError:AILocalizedString(@"Unable to retrieve user list [additional fail]", "Message when a (vital) twitter request to retrieve the follow list fails")];
+					[self didDisconnect];
+				}
+			} else {
+				int			nextPage = [[[self dictionaryForRequestID:identifier] objectForKey:@"Page"] intValue] + 1;
+				NSString	*requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtPage:nextPage];
+				if (requestID) {
+					[self setRequestType:AITwitterInitialUserInfo
+							forRequestID:requestID
+						  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:nextPage]
+																	 forKey:@"Page"]];
+				} else { 
+					[self setLastDisconnectionError:AILocalizedString(@"Unable to retrieve user list [additional fail]", "Message when a (vital) twitter request to retrieve the follow list fails")];
+					[self didDisconnect];
+				}
 			}
+			AILogWithSignature(@"%@ Pulling additional user info page", self);
 			
 		} else if ([self valueForProperty:@"Connecting"]) {			
 			// Trigger our normal update routine.
@@ -2342,7 +2390,7 @@
 					} else {
 						value = [NSAttributedString stringWithString:unattributedValue];
 					}
-						
+					
 					[profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:readableName, KEY_KEY, value, KEY_VALUE, nil]];
 				}
 			}
@@ -2370,13 +2418,13 @@
 						forRequestID:requestID
 					  withDictionary:nil];
 			}
-
+			
 			[self filterAndSetUID:[info objectForKey:TWITTER_INFO_UID]];
 			
 			if ([info objectForKey:@"name"]) {
 				[self setPreference:[[NSAttributedString stringWithString:[info objectForKey:@"name"]] dataRepresentation]
-								forKey:KEY_ACCOUNT_DISPLAY_NAME
-								 group:GROUP_ACCOUNT_STATUS];		
+							 forKey:KEY_ACCOUNT_DISPLAY_NAME
+							  group:GROUP_ACCOUNT_STATUS];		
 			}
 			
 			[self setValue:[info objectForKey:@"name"] forProperty:@"Profile Name" notify:NotifyLater];
@@ -2396,12 +2444,12 @@
 				// Delay updates on initial login.
 				[self silenceAllContactUpdatesForInterval:18.0];
 				// Grab our user list.
-				NSString	*requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtPage:1];
+				NSString	*requestID = [twitterEngine getRecentlyUpdatedFriendsFor:self.UID startingAtCursor:-1];
 				
 				if (requestID) {
 					[self setRequestType:AITwitterInitialUserInfo
 							forRequestID:requestID
-						  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:1] forKey:@"Page"]];
+						  withDictionary:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:-1] forKey:@"Cursor"]];
 				} else {
 					[self setLastDisconnectionError:AILocalizedString(@"Unable to retrieve user list", nil)];
 					[self didDisconnect];
@@ -2444,12 +2492,12 @@
 		
 		[adium.interfaceController handleMessage:AILocalizedString(@"Current Twitter rate limit", "Message in the rate limit status window")
 								 withDescription:[NSString stringWithFormat:AILocalizedString(@"You have %d/%d more requests for %@.", "The first %d is the number of requests, the second is the total number of requests per hour. The %@ is the duration of time until the count resets."),
-													[[rateLimit objectForKey:TWITTER_RATE_LIMIT_REMAINING] intValue],
-													[[rateLimit objectForKey:TWITTER_RATE_LIMIT_HOURLY_LIMIT] intValue],
-													[NSDateFormatter stringForTimeInterval:[resetDate timeIntervalSinceNow]
-																			showingSeconds:YES
-																			   abbreviated:YES
-																			  approximated:NO]]
+												  [[rateLimit objectForKey:TWITTER_RATE_LIMIT_REMAINING] intValue],
+												  [[rateLimit objectForKey:TWITTER_RATE_LIMIT_HOURLY_LIMIT] intValue],
+												  [NSDateFormatter stringForTimeInterval:[resetDate timeIntervalSinceNow]
+																		  showingSeconds:YES
+																			 abbreviated:YES
+																			approximated:NO]]
 								 withWindowTitle:AILocalizedString(@"Rate Limit Status", nil)];
 	}
 	
@@ -2472,7 +2520,7 @@
 		[listContact setValue:nil forProperty:TWITTER_PROPERTY_REQUESTED_USER_ICON notify:NotifyNever];
 	} else if([self requestTypeForRequestID:identifier] == AITwitterSelfUserIconPull) {
 		AILogWithSignature(@"Updated self icon for %@", self);
-
+		
 		// Set a property so we don't re-send thie image we're just now downloading.
 		[self setValue:[NSNumber numberWithBool:YES] forProperty:TWITTER_PROPERTY_REQUESTED_USER_ICON notify:NotifyNever];
 		
diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.h
--- a/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.h	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.h	Tue May 11 20:37:20 2010 -0500
@@ -113,6 +113,7 @@
 - (NSString *)getUserInformationFor:(NSString *)username;
 - (NSString *)getUserInformationForEmail:(NSString *)email;
 - (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtPage:(int)pageNum;
+- (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtCursor:(long long)cursorNum;
 - (NSString *)getFollowersIncludingCurrentStatus:(BOOL)flag;
 - (NSString *)getFeaturedUsers;
 
diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.m
--- a/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.m	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterEngine.m	Tue May 11 20:37:20 2010 -0500
@@ -18,7 +18,7 @@
 #import "MGTwitterMessagesParser.h"
 #import "MGTwitterMiscParser.h"
 
-#define TWITTER_DOMAIN          @"twitter.com"
+#define TWITTER_DOMAIN          @"api.twitter.com/1"
 #define HTTP_POST_METHOD        @"POST"
 #define HTTP_MULTIPART_METHOD	@"MULTIPART" //adium
 #define MULTIPART_FORM_BOUNDARY	@"bf5faadd239c17e35f91e6dafe1d2f96" //adium
@@ -1232,6 +1232,23 @@
                            responseType:MGTwitterUsers];
 }
 
+- (NSString *)getRecentlyUpdatedFriendsFor:(NSString *)username startingAtCursor:(long long)cursorNum
+{
+    NSString *path = @"statuses/friends.xml";
+    
+    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:0];
+    if (username) {
+        path = [NSString stringWithFormat:@"statuses/friends/%@.xml", username];
+    }
+    if (cursorNum >= -1) {
+        [params setObject:[NSString stringWithFormat:@"%lld", cursorNum] forKey:@"cursor"];
+    }
+    
+    return [self _sendRequestWithMethod:nil path:path queryParameters:params body:nil 
+                            requestType:MGTwitterUserInfoRequest 
+                           responseType:MGTwitterUsers];
+}
+
 
 - (NSString *)getFollowersIncludingCurrentStatus:(BOOL)flag
 {
diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterUsersParser.h
--- a/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterUsersParser.h	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterUsersParser.h	Tue May 11 20:37:20 2010 -0500
@@ -11,7 +11,7 @@
 #import "MGTwitterStatusesParser.h"
 
 @interface MGTwitterUsersParser : MGTwitterStatusesParser {
-
+	BOOL	supportsCursorPaging;
 }
 
 @end
diff -r f24619866752 -r 5871e3c08231 Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterUsersParser.m
--- a/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterUsersParser.m	Tue May 11 20:08:06 2010 -0500
+++ b/Plugins/Twitter Plugin/MGTwitterEngine/MGTwitterUsersParser.m	Tue May 11 20:37:20 2010 -0500
@@ -22,34 +22,90 @@
     //NSLog(@"Started element: %@ (%@)", elementName, attributeDict);
     [self setLastOpenedElement:elementName];
     
-    if ([elementName isEqualToString:@"user"]) {
-        // Make new entry in parsedObjects.
-        NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
-        [parsedObjects addObject:newNode];
-        currentNode = newNode;
-    } else if ([elementName isEqualToString:@"status"]) {
-        // Add an appropriate dictionary to current node.
-        NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
-        [currentNode setObject:newNode forKey:elementName];
-        currentNode = newNode;
-    } else if (currentNode) {
-        // Create relevant name-value pair.
-        [currentNode setObject:[NSMutableString string] forKey:elementName];
-    }
+	if ([elementName isEqualToString:@"users_list"]) {
+		supportsCursorPaging = YES;
+		NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
+		[parsedObjects addObject:newNode];
+		currentNode = newNode;
+	} else if ([elementName isEqualToString:@"users"]) {
+		if (!supportsCursorPaging) {
+			NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
+			[parsedObjects addObject:newNode];
+			currentNode = newNode;
+		}
+		// Create the users mutable array
+		[currentNode setObject:[NSMutableArray arrayWithCapacity:0] forKey:elementName];
+	} else if ([elementName isEqualToString:@"user"]) {
+		// Add the user object into the users array
+		NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
+		
+		// Accomodate different type of xml response
+		NSMutableArray *users = [currentNode objectForKey:@"users"];
+		
+		if (users) {
+			[users addObject:newNode];
+		} else {
+			[parsedObjects addObject:newNode];
+		}
+
+		currentNode = newNode;
+	} else if ([elementName isEqualToString:@"status"]) {
+		// Add an appropriate dictionary to current node.
+		NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
+		[currentNode setObject:newNode forKey:elementName];
+		currentNode = newNode;
+	} else if ([elementName isEqualToString:@"next_cursor"]) {
+		// Add a new entry for next cursor object
+		NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
+		[parsedObjects addObject:newNode];
+		currentNode = newNode;
+		[currentNode setObject:[NSMutableString string] forKey:elementName];
+	} else if ([elementName isEqualToString:@"previous_cursor"]) {
+		// Add a new entry for previous cursor object
+		NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithCapacity:0];
+		[parsedObjects addObject:newNode];
+		currentNode = newNode;
+		[currentNode setObject:[NSMutableString string] forKey:elementName];
+	} else if (currentNode) {
+		// Create relevant name-value pair.
+		[currentNode setObject:[NSMutableString string] forKey:elementName];
+	}
 }
 
+- (void)parser:(NSXMLParser *)theParser foundCharacters:(NSString *)characters
+{
+	if (![lastOpenedElement isEqualToString:@"users"] && currentNode) {
+		[[currentNode objectForKey:lastOpenedElement] appendString:characters];
+	}
+}
 
 - (void)parser:(NSXMLParser *)theParser didEndElement:(NSString *)elementName 
   namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
 {
-    [super parser:theParser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName];
-    
-    if ([elementName isEqualToString:@"status"]) {
-        currentNode = [parsedObjects lastObject];
-    } else if ([elementName isEqualToString:@"user"]) {
-        [self addSource];
-        currentNode = nil;
-    }
+	[super parser:theParser didEndElement:elementName namespaceURI:namespaceURI qualifiedName:qName];
+	
+	if ([elementName isEqualToString:@"status"]) {
+		NSMutableArray *users = [[parsedObjects lastObject] objectForKey:@"users"];
+		
+		if (users) {
+			currentNode = [users lastObject];
+		} else {
+			currentNode = [parsedObjects lastObject];
+		}
+
+		currentNode = [parsedObjects lastObject];
+	} else if ([elementName isEqualToString:@"user"]) {
+		[self addSource];
+		
+		NSMutableArray *users = [[parsedObjects lastObject] objectForKey:@"users"];
+		
+		if (users) {
+			currentNode = [parsedObjects lastObject];
+		} else {
+			currentNode = nil;
+		}
+
+	}
 }
 
 




More information about the commits mailing list