adium/adium

No more t.co links! (Well, almost none).
adium-1.6
2013-03-23, Frank Dowsett
7d45cfb6ae92
Parents 51084cf32f39
Children ca83a87cff60
No more t.co links! (Well, almost none).

Use the entities that Twitter sends to parse and link URLs, users, and hashtags. Fixes #13947
--- 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
withID:(NSString *)dmID
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]
- userID:inContact.UID
- 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]
+ userID:inContact.UID
+ 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];
- }
-
- if (!hashCharacters) {
- 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 *text = @"";
+ 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
- forAccount:self
- validCharacterSet:usernameCharacters];
-
- attributedString = [AITwitterURLParser linkifiedStringFromAttributedString:attributedString
- forPrefixCharacter:@"#"
- forLinkType:AITwitterLinkSearchHash
- forAccount:self
- validCharacterSet:hashCharacters];
-
- return attributedString;
+ NSString *linkURL = [self addressForLinkType:linkType
+ userID:userID
+ statusID:nil
+ context:context];
+
+ [*inString replaceOccurrencesOfString:text
+ withString: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]]];
+ } else {
+ 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
+ withString:expandedURL
+ 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
+ withString:displayURL
+ 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 @@
statusID:replyTweetID
context:nil];
- 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
userID:userID
statusID:tweetID
- context:[inMessage stringByAddingPercentEscapesForAllCharacters]];
+ context:[message stringByAddingPercentEscapesForAllCharacters]];
#define PILCROW_SIGN @"\u00B6"
@@ -1648,7 +1663,7 @@
linkAddress = [self addressForLinkType:AITwitterLinkDestroyStatus
userID:userID
statusID:tweetID
- 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;
+ return [mutableMessage autorelease];
} else {
- return message;
+ 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]
+ userID:contactUID
+ 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];
-
id fromObject = nil;
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
notify:NotifyNow];
[self updateUserIcon:[[status objectForKey:TWITTER_STATUS_USER] objectForKey:TWITTER_INFO_ICON] forContact:listContact];
@@ -1832,12 +1843,6 @@
fromObject = (id)self;
}
- NSAttributedString *message = [self parseMessage:text
- tweetID:[status objectForKey:TWITTER_STATUS_ID]
- userID:contactUID
- inReplyToUser:[status objectForKey:TWITTER_STATUS_REPLY_UID]
- inReplyToTweetID:[status objectForKey:TWITTER_STATUS_REPLY_ID]];
-
AIContentMessage *contentMessage = [AIContentMessage messageInChat:timelineChat
withSource:fromObject
destination:self
--- a/Plugins/Twitter Plugin/AITwitterURLParser.h Sat Mar 23 21:49:08 2013 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-/*
- * Adium is the legal property of its developers, whose names are listed in the copyright file included
- * with this source distribution.
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software Foundation; either version 2 of the License,
- * or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
- * Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program; if not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#import "AITwitterAccount.h"
-
-@interface AITwitterURLParser : NSObject {
-
-}
-
-+(NSAttributedString *)linkifiedStringFromAttributedString:(NSAttributedString *)inString
- forPrefixCharacter:(NSString *)prefixCharacter
- forLinkType:(AITwitterLinkType)linkType
- forAccount:(AITwitterAccount *)account
- validCharacterSet:(NSCharacterSet *)validValues;
-
-@end
--- a/Plugins/Twitter Plugin/AITwitterURLParser.m Sat Mar 23 21:49:08 2013 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Adium is the legal property of its developers, whose names are listed in the copyright file included
- * with this source distribution.
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
- * General Public License as published by the Free Software Foundation; either version 2 of the License,
- * or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
- * Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program; if not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#import "AITwitterURLParser.h"
-#import "AITwitterAccount.h"
-#import <AIUtilities/AIAttributedStringAdditions.h>
-#import <AIUtilities/AIStringAdditions.h>
-
-@implementation AITwitterURLParser
-
-+(NSAttributedString *)linkifiedStringFromAttributedString:(NSAttributedString *)inString
- forPrefixCharacter:(NSString *)prefixCharacter
- forLinkType:(AITwitterLinkType)linkType
- forAccount:(AITwitterAccount *)account
- validCharacterSet:(NSCharacterSet *)validValues
-{
- NSMutableAttributedString *newString = [inString mutableCopy];
-
- NSScanner *scanner = [NSScanner scannerWithString:[inString string]];
-
- [scanner setCharactersToBeSkipped:nil];
-
- [newString beginEditing];
-
- while(!scanner.isAtEnd) {
- [scanner scanUpToString:prefixCharacter intoString:NULL];
-
- if(scanner.isAtEnd) {
- break;
- }
-
- NSUInteger startLocation = scanner.scanLocation;
- NSString *linkText = nil;
-
- // Advance to the start of the string we want.
- // Check to make sure we aren't exceeding the string bounds.
- if(startLocation + 1 < scanner.string.length) {
- scanner.scanLocation++;
- } else {
- break;
- }
-
- // Grab any valid characters we can.
- BOOL scannedCharacters = [scanner scanCharactersFromSet:validValues intoString:&linkText];
-
- if(scannedCharacters) {
- if((scanner.scanLocation - linkText.length) == prefixCharacter.length ||
- [[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[scanner.string characterAtIndex:(scanner.scanLocation - linkText.length - prefixCharacter.length - 1)]]) {
-
- NSString *linkURL = nil;
- if(linkType == AITwitterLinkUserPage) {
- linkURL = [account addressForLinkType:linkType userID:[linkText stringByEncodingURLEscapes] statusID:nil context:nil];
- } else if (linkType == AITwitterLinkSearchHash) {
- linkURL = [account addressForLinkType:linkType userID:nil statusID:nil context:[linkText stringByEncodingURLEscapes]];
- } else if (linkType == AITwitterLinkGroup) {
- linkURL = [account addressForLinkType:linkType userID:nil statusID:nil context:[linkText stringByEncodingURLEscapes]];
- }
-
- if(linkURL) {
- [newString addAttribute:NSLinkAttributeName
- value:linkURL
- range:NSMakeRange(startLocation + 1, linkText.length)];
- }
- }
- } else {
- scanner.scanLocation++;
- }
- }
-
- [newString endEditing];
-
- return [newString autorelease];
-}
-
-@end