--- a/Adium.xcodeproj/project.pbxproj Sat Mar 23 21:49:08 2013 -0400
+++ b/Adium.xcodeproj/project.pbxproj Sat Mar 23 22:15:45 2013 -0400
@@ -113,7 +113,6 @@
113900B40F85BF880081A418 /* AIURLHandlerWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 113900B30F85BF880081A418 /* AIURLHandlerWindowController.m */; };
1139011C0F85C9450081A418 /* AIURLHandlerPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 1139011B0F85C9450081A418 /* AIURLHandlerPlugin.m */; };
1139FFAA0F85260E0081A418 /* AIIRCChannelLinker.m in Sources */ = {isa = PBXBuildFile; fileRef = 1139FFA90F85260E0081A418 /* AIIRCChannelLinker.m */; };
- 113F26A00F5CC03F00954772 /* AITwitterURLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 113F269F0F5CC03F00954772 /* AITwitterURLParser.m */; };
114849B90F7841C600EA5264 /* AIAuthorizationRequestsWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = 114849B70F7841C600EA5264 /* AIAuthorizationRequestsWindowController.h */; settings = {ATTRIBUTES = (Public, ); }; };
114849BA0F7841C600EA5264 /* AIAuthorizationRequestsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 114849B80F7841C600EA5264 /* AIAuthorizationRequestsWindowController.m */; };
114849BC0F78420300EA5264 /* AIAuthorizationRequestsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 114849BB0F78420300EA5264 /* AIAuthorizationRequestsWindow.xib */; };
@@ -1837,8 +1836,6 @@
1139FFA90F85260E0081A418 /* AIIRCChannelLinker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AIIRCChannelLinker.m; path = Source/AIIRCChannelLinker.m; sourceTree = "<group>"; };
113E06A910D0ABA0005D5B9A /* adiumPurpleMedia.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = adiumPurpleMedia.m; path = "Plugins/Purple Service/adiumPurpleMedia.m"; sourceTree = "<group>"; };
113E06AE10D0ABE3005D5B9A /* adiumPurpleMedia.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = adiumPurpleMedia.h; path = "Plugins/Purple Service/adiumPurpleMedia.h"; sourceTree = "<group>"; };
- 113F269E0F5CC03F00954772 /* AITwitterURLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AITwitterURLParser.h; path = "Plugins/Twitter Plugin/AITwitterURLParser.h"; sourceTree = "<group>"; };
- 113F269F0F5CC03F00954772 /* AITwitterURLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AITwitterURLParser.m; path = "Plugins/Twitter Plugin/AITwitterURLParser.m"; sourceTree = "<group>"; };
1147FCC210D1CB4C004E9E8D /* AIMediaWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AIMediaWindowController.h; path = Source/AIMediaWindowController.h; sourceTree = "<group>"; };
1147FCC310D1CB4C004E9E8D /* AIMediaWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AIMediaWindowController.m; path = Source/AIMediaWindowController.m; sourceTree = "<group>"; };
1147FCC710D1CB83004E9E8D /* AIMediaWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xib; name = AIMediaWindow.xib; path = Resources/AIMediaWindow.xib; sourceTree = "<group>"; };
@@ -5050,8 +5047,6 @@
11BD73D20F5A54BB007D438A /* twitter.png */,
EFB1C3120DDBDA3100B3973D /* AITwitterIMPlugin.h */,
EFB1C3130DDBDA3100B3973D /* AITwitterIMPlugin.m */,
- 113F269E0F5CC03F00954772 /* AITwitterURLParser.h */,
- 113F269F0F5CC03F00954772 /* AITwitterURLParser.m */,
112523170F5F7F86003FC58A /* AITwitterURLHandler.h */,
112523180F5F7F86003FC58A /* AITwitterURLHandler.m */,
11F738FA0F58D19B00B3285B /* AITwitterPlugin.h */,
@@ -10523,7 +10518,6 @@
11F738F90F58D18700B3285B /* AITwitterService.m in Sources */,
11F738FC0F58D19B00B3285B /* AITwitterPlugin.m in Sources */,
11F739020F58D1C400B3285B /* AITwitterAccountViewController.m in Sources */,
- 113F26A00F5CC03F00954772 /* AITwitterURLParser.m in Sources */,
112523190F5F7F86003FC58A /* AITwitterURLHandler.m in Sources */,
1109661A0F61D3E70064CA0E /* AITwitterReplyWindowController.m in Sources */,
1163F0EC0F6C7A8300F12F5D /* AIURLShortenerPlugin.m in Sources */,
--- a/Plugins/Twitter Plugin/AITwitterAccount.h Sat Mar 23 21:49:08 2013 -0400
+++ b/Plugins/Twitter Plugin/AITwitterAccount.h Sat Mar 23 22:15:45 2013 -0400
@@ -193,7 +193,7 @@
- (void)destroyDirectMessage:(NSString *)messageID
forUser:(NSString *)userID;
-- (NSAttributedString *)linkifiedAttributedStringFromString:(NSAttributedString *)inString;
+- (void)linkifyEntities:(NSArray *)entities inString:(NSMutableAttributedString **)inString forLinkType:(AITwitterLinkType)linkType; - (NSString *)addressForLinkType:(AITwitterLinkType)linkType
userID:(NSString *)userID
--- a/Plugins/Twitter Plugin/AITwitterAccount.m Sat Mar 23 21:49:08 2013 -0400
+++ b/Plugins/Twitter Plugin/AITwitterAccount.m Sat Mar 23 22:15:45 2013 -0400
@@ -15,7 +15,6 @@
#import "AITwitterAccount.h"
-#import "AITwitterURLParser.h"
#import "AITwitterReplyWindowController.h"
#import <AIUtilities/AIAttributedStringAdditions.h>
#import <AIUtilities/AIStringAdditions.h>
@@ -43,11 +42,11 @@
- (void)updateTimelineChat:(AIGroupChat *)timelineChat;
-- (NSAttributedString *)parseMessage:(NSString *)inMessage
- tweetID:(NSString *)tweetID
- userID:(NSString *)userID
- inReplyToUser:(NSString *)replyUserID
- inReplyToTweetID:(NSString *)replyTweetID;
+- (NSAttributedString *)parseStatus:(NSDictionary *)inStatus + tweetID:(NSString *)tweetID + userID:(NSString *)userID + inReplyToUser:(NSString *)replyUserID + inReplyToTweetID:(NSString *)replyTweetID; - (NSAttributedString *)parseDirectMessage:(NSString *)inMessage
fromUser:(NSString *)sourceUID;
@@ -623,21 +622,11 @@
AILogWithSignature(@"%@ Updating statuses for profile, user %@", self, inContact);
for (NSDictionary *update in statuses) {
- NSAttributedString *message;
- NSDictionary *retweet = [update valueForKey:TWITTER_STATUS_RETWEET];
- NSString *text = [update objectForKey:TWITTER_STATUS_TEXT];
- if (retweet && [retweet isKindOfClass:[NSDictionary class]]) {
- text = [NSString stringWithFormat:@"RT @%@: %@",
- [[retweet objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_STATUS_UID],
- [retweet objectForKey:TWITTER_STATUS_TEXT]];
- message = [self parseMessage:text
- tweetID:[update objectForKey:TWITTER_STATUS_ID]
- inReplyToUser:[update objectForKey:TWITTER_STATUS_REPLY_UID]
- inReplyToTweetID:[update objectForKey:TWITTER_STATUS_REPLY_ID]];
+ NSAttributedString *message = [self parseStatus:update + tweetID:[update objectForKey:TWITTER_STATUS_ID] + inReplyToUser:[update objectForKey:TWITTER_STATUS_REPLY_UID] + inReplyToTweetID:[update objectForKey:TWITTER_STATUS_REPLY_ID]]; [profileArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:message, KEY_VALUE, nil]];
@@ -1328,7 +1317,7 @@
} else if (linkType == AITwitterLinkUserPage) {
address = [NSString stringWithFormat:@"https://twitter.com/%@", userID];
} else if (linkType == AITwitterLinkSearchHash) {
- address = [NSString stringWithFormat:@"http://search.twitter.com/search?q=%%23%@", context];
+ address = [NSString stringWithFormat:@"http://twitter.com/search?q=%%23%@", context]; } else if (linkType == AITwitterLinkReply) {
address = [NSString stringWithFormat:@"twitterreply://%@@%@?action=reply&status=%@", self.internalObjectID, userID, statusID];
} else if (linkType == AITwitterLinkRetweet) {
@@ -1496,63 +1485,89 @@
* @brief Parse an attributed string into a linkified version.
-- (NSAttributedString *)linkifiedAttributedStringFromString:(NSAttributedString *)inString
- NSAttributedString *attributedString;
- static NSCharacterSet *usernameCharacters = nil;
- static NSCharacterSet *hashCharacters = nil;
- if (!usernameCharacters) {
- usernameCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"] retain];
- NSMutableCharacterSet *disallowedCharacters = [[NSCharacterSet punctuationCharacterSet] mutableCopy];
- [disallowedCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
- [disallowedCharacters removeCharactersInString:@"_"];
+- (void)linkifyEntities:(NSArray *)entities inString:(NSMutableAttributedString **)inString forLinkType:(AITwitterLinkType)linkType { + [entities enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSString *userID = nil; + NSString *context = nil; + if (linkType == AITwitterLinkUserPage) { + userID = [obj objectForKey:@"screen_name"]; + text = [NSString stringWithFormat:@"@%@", userID]; + } else if (linkType == AITwitterLinkSearchHash) { + context = [obj objectForKey:@"text"]; + text = [NSString stringWithFormat:@"#%@", context]; - hashCharacters = [[disallowedCharacters invertedSet] retain];
- [disallowedCharacters release];
- attributedString = [AITwitterURLParser linkifiedStringFromAttributedString:inString
- forPrefixCharacter:@"@"
- forLinkType:AITwitterLinkUserPage
- validCharacterSet:usernameCharacters];
- attributedString = [AITwitterURLParser linkifiedStringFromAttributedString:attributedString
- forPrefixCharacter:@"#"
- forLinkType:AITwitterLinkSearchHash
- validCharacterSet:hashCharacters];
- return attributedString;
+ NSString *linkURL = [self addressForLinkType:linkType + [*inString replaceOccurrencesOfString:text + attributes:@{ NSLinkAttributeName : linkURL } + options:NSCaseInsensitiveSearch + range:NSMakeRange(0, [*inString length])]; * @brief Parses a Twitter message into an attributed string
-- (NSAttributedString *)parseMessage:(NSString *)inMessage
- tweetID:(NSString *)tweetID
- userID:(NSString *)userID
- inReplyToUser:(NSString *)replyUserID
- inReplyToTweetID:(NSString *)replyTweetID
+- (NSAttributedString *)parseStatus:(NSDictionary *)inStatus + tweetID:(NSString *)tweetID + userID:(NSString *)userID + inReplyToUser:(NSString *)replyUserID + inReplyToTweetID:(NSString *)replyTweetID - NSAttributedString *message;
+ NSMutableAttributedString *mutableMessage; + NSDictionary *retweet = [inStatus objectForKey:TWITTER_STATUS_RETWEET]; + if (retweet && [retweet isKindOfClass:[NSDictionary class]]) { + NSString *text = [[retweet objectForKey:TWITTER_STATUS_TEXT] stringByUnescapingFromXMLWithEntities:nil]; + mutableMessage = [[NSMutableAttributedString alloc] initWithString:text]; + [mutableMessage replaceCharactersInRange:NSMakeRange(0, 0) + withString:[NSString stringWithFormat:@"RT @%@: ", + [[retweet objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_STATUS_UID]]]; + NSString *text = [[inStatus objectForKey:TWITTER_STATUS_TEXT] stringByUnescapingFromXMLWithEntities:nil]; + mutableMessage = [[NSMutableAttributedString alloc] initWithString:text]; - message = [NSAttributedString stringWithString:[inMessage stringByUnescapingFromXMLWithEntities:nil]];
+ //Extract hashtags, users, and URLs + NSDictionary *entities = [inStatus objectForKey:@"entities"]; + NSArray *hashtags = [entities objectForKey:@"hashtags"]; + NSArray *urls = [entities objectForKey:@"urls"]; + NSArray *users = [entities objectForKey:@"user_mentions"]; + NSArray *media = [entities objectForKey:@"media"]; + [self linkifyEntities:users inString:&mutableMessage forLinkType:AITwitterLinkUserPage]; + [self linkifyEntities:hashtags inString:&mutableMessage forLinkType:AITwitterLinkSearchHash]; + [urls enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSString *linkURL = [obj objectForKey:@"url"]; + NSString *expandedURL = [obj objectForKey:@"expanded_url"]; + [mutableMessage replaceOccurrencesOfString:linkURL + attributes:@{ NSLinkAttributeName : linkURL } + options:NSLiteralSearch + range:NSMakeRange(0, mutableMessage.length)]; + [media enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + NSString *linkURL = [obj objectForKey:@"url"]; + NSString *displayURL = [obj objectForKey:@"display_url"]; + [mutableMessage replaceOccurrencesOfString:linkURL + attributes:@{ NSLinkAttributeName : linkURL } + options:NSLiteralSearch + range:NSMakeRange(0, mutableMessage.length)]; - message = [self linkifiedAttributedStringFromString:message];
+ NSString *message = [mutableMessage string]; BOOL replyTweet = (replyTweetID.length > 0);
BOOL tweetLink = (tweetID.length && userID.length);
if (replyTweet || tweetLink) {
- NSMutableAttributedString *mutableMessage = [[message mutableCopy] autorelease];
NSUInteger startIndex = message.length;
[mutableMessage appendString:@" (" withAttributes:nil];
@@ -1566,9 +1581,9 @@
- if([inMessage hasPrefix:@"@"] &&
- inMessage.length >= replyUserID.length + 1 &&
- [replyUserID isCaseInsensitivelyEqualToString:[inMessage substringWithRange:NSMakeRange(1, replyUserID.length)]]) {
+ if([message hasPrefix:@"@"] && + message.length >= replyUserID.length + 1 && + [replyUserID isCaseInsensitivelyEqualToString:[message substringWithRange:NSMakeRange(1, replyUserID.length)]]) { // If the message has a "@" prefix, it's a proper in_reply_to_status_id if the usernames match. Set a link appropriately.
[mutableMessage setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:linkAddress, NSLinkAttributeName, nil]
range:NSMakeRange(0, replyUserID.length + 1)];
@@ -1616,7 +1631,7 @@
linkAddress = [self addressForLinkType:AITwitterLinkQuote
- context:[inMessage stringByAddingPercentEscapesForAllCharacters]];
+ context:[message stringByAddingPercentEscapesForAllCharacters]]; #define PILCROW_SIGN @"\u00B6"
@@ -1648,7 +1663,7 @@
linkAddress = [self addressForLinkType:AITwitterLinkDestroyStatus
- context:[inMessage stringByAddingPercentEscapesForAllCharacters]];
+ context:[message stringByAddingPercentEscapesForAllCharacters]]; [mutableMessage appendAttributedString:[self attributedStringWithLinkLabel:@"\u232B"
linkDestination:linkAddress
@@ -1686,9 +1701,9 @@
[NSNumber numberWithBool:YES], AIHiddenMessagePartAttributeName, nil]
range:NSMakeRange(startIndex, mutableMessage.length - startIndex)];
+ return [mutableMessage autorelease];
+ return [[[NSAttributedString alloc] initWithString:message] autorelease]; @@ -1703,9 +1718,9 @@
message = [NSAttributedString stringWithString:[inMessage stringByUnescapingFromXMLWithEntities:nil]];
- message = [self linkifiedAttributedStringFromString:message];
NSMutableAttributedString *mutableMessage = [[message mutableCopy] autorelease];
+ [self linkifyEntities:users inString:&mutableMessage forLinkType:AITwitterLinkUserPage]; + [self linkifyEntities:hashtags inString:&mutableMessage forLinkType:AITwitterLinkSearchHash]; NSUInteger startIndex = message.length;
@@ -1801,26 +1816,22 @@
[[AIContactObserverManager sharedManager] delayListObjectNotifications];
for (NSDictionary *status in sortedQueuedUpdates) {
- NSDictionary *retweet = [status objectForKey:TWITTER_STATUS_RETWEET];
- NSString *text = [status objectForKey:TWITTER_STATUS_TEXT];
- if (retweet && [retweet isKindOfClass:[NSDictionary class]]) {
- text = [NSString stringWithFormat:@"RT @%@: %@",
- [[retweet objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_STATUS_UID],
- [retweet objectForKey:TWITTER_STATUS_TEXT]];
+ NSString *contactUID = [[status objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_STATUS_UID]; + NSAttributedString *message = [self parseStatus:status + tweetID:[status objectForKey:TWITTER_STATUS_ID] + inReplyToUser:[status objectForKey:TWITTER_STATUS_REPLY_UID] + inReplyToTweetID:[status objectForKey:TWITTER_STATUS_REPLY_ID]]; NSDate *date = [status objectForKey:TWITTER_STATUS_CREATED];
- NSString *contactUID = [[status objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_STATUS_UID];
if (![self.UID isCaseInsensitivelyEqualToString:contactUID]) {
AIListContact *listContact = [self contactWithUID:contactUID];
// Update the user's status message
- [listContact setStatusMessage:[NSAttributedString stringWithString:[text stringByUnescapingFromXMLWithEntities:nil]]
+ [listContact setStatusMessage:message [self updateUserIcon:[[status objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_INFO_ICON] forContact:listContact];
@@ -1832,12 +1843,6 @@
- NSAttributedString *message = [self parseMessage:text
- tweetID:[status objectForKey:TWITTER_STATUS_ID]
- inReplyToUser:[status objectForKey:TWITTER_STATUS_REPLY_UID]
- inReplyToTweetID:[status objectForKey:TWITTER_STATUS_REPLY_ID]];
AIContentMessage *contentMessage = [AIContentMessage messageInChat:timelineChat