William Bowling is sharing code with you
Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.
Don't show this again1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | /*
* 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 "AIURLShortenerPlugin.h"
#import <AIUtilities/AIMenuAdditions.h>
#import <AIUtilities/AIWindowAdditions.h>
#import <AIUtilities/AIStringAdditions.h>
#import <AIUtilities/AIAttributedStringAdditions.h>
#import <AutoHyperlinks/AHHyperlinkScanner.h>
#import <Adium/AIMenuControllerProtocol.h>
#import <Adium/AIContentControllerProtocol.h>
#define SHORTEN_LINK_TITLE AILocalizedString(@"Replace with Shortened URL", nil)
@interface AIURLShortenerPlugin()
- (void)shortenLink;
- (void)shortenAddress:(NSString *)address
withService:(AIShortenLinkService)service
inTextView:(NSTextView *)textView;
- (void)insertResultFromURL:(NSURL *)inURL intoTextView:(NSTextView *)textView;
- (NSString *)resultFromURL:(NSURL *)inURL;
@end
@implementation AIURLShortenerPlugin
- (void)installPlugin
{
NSMenuItem *menuItem;
NSMenu *shortenerSubMenu = [[NSMenu alloc] init];
[shortenerSubMenu setDelegate:self];
// Edit menu
menuItem = [[[NSMenuItem alloc] initWithTitle:SHORTEN_LINK_TITLE
target:self
action:@selector(shortenLink)
keyEquivalent:@"K"
keyMask:NSCommandKeyMask] autorelease];
[menuItem setSubmenu:shortenerSubMenu];
[adium.menuController addMenuItem:menuItem toLocation:LOC_Edit_Links];
// Context menu
menuItem = [[[NSMenuItem alloc] initWithTitle:SHORTEN_LINK_TITLE
target:self
action:@selector(shortenLink)
keyEquivalent:@""] autorelease];
[menuItem setSubmenu:[[shortenerSubMenu copy] autorelease]];
[adium.menuController addContextualMenuItem:menuItem toLocation:Context_TextView_Edit];
[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_FORMATTING];
}
- (void)uninstallPlugin
{
[adium.preferenceController unregisterPreferenceObserver:self];
}
- (void)dealloc
{
[super dealloc];
}
#pragma mark Preferences
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
{
if(object)
return;
if(firstTime || [key isEqualToString:KEY_SHORTENER_PREFERENCE]) {
shortener = [[prefDict objectForKey:KEY_SHORTENER_PREFERENCE] integerValue];
}
}
#pragma mark Menu Item
/*!
* @brief Update our shortener list
*
* @param menu The NSMenu which needs to be recomputed
*
* We're dealing with two separate menus with the same contents and a changing value.
* Dynamically generate each time, since it's a short and simple operation.
*/
- (void)menuNeedsUpdate:(NSMenu *)menu
{
NSDictionary *shorteners = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:AITinyURL], @"tinyurl.com",
[NSNumber numberWithInteger:AIisgd], @"is.gd",
[NSNumber numberWithInteger:AIMetamark], @"xrl.us",
[NSNumber numberWithInteger:AITrim], @"tr.im",
[NSNumber numberWithInteger:AIKlam], @"kl.am",
nil];
[menu removeAllItems];
for(NSString *service in shorteners.allKeys) {
NSInteger shortenerTag = [[shorteners objectForKey:service] integerValue];
NSMenuItem *newItem = [menu addItemWithTitle:service
target:self
action:@selector(setShortener:)
keyEquivalent:@""
tag:shortenerTag];
[newItem setState:(shortener == shortenerTag)];
}
}
/*!
* @brief Shortens the URL to the chosen service
*
* @param menuItem An NSMenuItem whose tag is a valid AIShortenLinkService
*/
- (void)setShortener:(NSMenuItem *)menuItem
{
NSInteger shortenerTag = menuItem.tag;
[adium.preferenceController setPreference:[NSNumber numberWithInteger:shortenerTag]
forKey:KEY_SHORTENER_PREFERENCE
group:PREF_GROUP_FORMATTING];
[self shortenLink];
}
/*!
* @brief Our menu item is valid if we have a text view to replace in, the text view has some selected text in it, and the selected text is a valid URL.
*/
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
NSTextView *textView = (NSTextView *)[[NSApp keyWindow] earliestResponderOfClass:[NSTextView class]];
if (textView) {
NSAttributedString *text = textView.textStorage;
NSRange selectedRange = textView.selectedRange;
//If we have some text and the start of the selection is not at the end of the string...
if ((text.length > 0) && (selectedRange.location < text.length)) {
if ([text attribute:NSLinkAttributeName atIndex:selectedRange.location effectiveRange:NULL])
return YES;
if (selectedRange.length > 0) {
//If the selected text is a URL (more or less), good enough for us.
return [AHHyperlinkScanner isStringValidURI:[text.string substringWithRange:selectedRange] usingStrict:NO fromIndex:NULL withStatus:NULL];
}
}
}
return NO;
}
/*!
* @brief Shorten a URL
*
* In the current window, take the currently-selected URL, or the URL of the attributed range the
* cursor is on, and shorten it using the service the user has set.
*/
- (void)shortenLink
{
NSWindow *keyWindow = NSApplication.sharedApplication.keyWindow;
NSTextView *textView = (NSTextView *)[keyWindow earliestResponderOfClass:[NSTextView class]];
// Don't try and do anything on an empty input line or if we're at the end
if(!textView.textStorage.length || textView.selectedRange.location == textView.textStorage.length) {
NSBeep();
return;
}
NSRange selectedRange = textView.selectedRange;
NSRange rangeOfLinkAttribute;
NSString *linkURL = nil;
id unknownLinkURL = [textView.textStorage attribute:NSLinkAttributeName
atIndex:selectedRange.location
effectiveRange:&rangeOfLinkAttribute];
if (unknownLinkURL) {
//If a link exists at our selection, expand the selection to encompass that entire link
[textView setSelectedRange:rangeOfLinkAttribute];
selectedRange = rangeOfLinkAttribute;
if([unknownLinkURL isKindOfClass:[NSURL class]]) {
linkURL = [(NSURL *)unknownLinkURL absoluteString];
} else {
linkURL = unknownLinkURL;
}
} else {
linkURL = [[textView attributedSubstringFromRange:selectedRange] string];
}
if(linkURL.length) {
// Make sure the HTTP prefix is set.
if(![linkURL hasPrefix:@"http"]) {
linkURL = [@"http://" stringByAppendingString:linkURL];
}
// Convert to a shortened URL using the user's preference.
[self shortenAddress:linkURL
withService:shortener
inTextView:textView];
} else {
NSBeep();
}
}
#pragma mark Shorten a URL
/*!
* @brief Shorten the requested address
*
* @param address An NSString with the absolute address to shorten
* @param service An AIShortenLinkService value corresponding to the service used for shortening
* @param textView An NSTextView whose selected range will be replaced with the shortened value
*/
- (void)shortenAddress:(NSString *)address
withService:(AIShortenLinkService)service
inTextView:(NSTextView *)textView
{
NSString *request = nil;
switch(service) {
case AITinyURL:
request = [NSString stringWithFormat:@"http://tinyurl.com/api-create.php?url=%@", [address stringByAddingPercentEscapesForAllCharacters]];
break;
case AIisgd:
request = [NSString stringWithFormat:@"http://is.gd/api.php?longurl=%@", [address stringByAddingPercentEscapesForAllCharacters]];
break;
case AIMetamark:
request = [NSString stringWithFormat:@"http://metamark.net/api/rest/simple?long_url=%@", [address stringByAddingPercentEscapesForAllCharacters]];
break;
case AITrim:
request = [NSString stringWithFormat:@"http://api.tr.im/api/trim_simple?api_key=zghQN6sv5y0FkLPNlQAopm7qDQz6ItO33ENU21OBsy3dL1Kl&url=%@", [address stringByAddingPercentEscapesForAllCharacters]];
break;
case AIKlam:
request = [NSString stringWithFormat:@"http://kl.am/api/shorten?url=%@&format=text", [address stringByAddingPercentEscapesForAllCharacters]];
break;
default:
break;
}
if (request) {
[self insertResultFromURL:[NSURL URLWithString:request] intoTextView:textView];
}
}
#pragma mark Simple shorteners
/*!
* @brief Request a URL, insert into text view
*
* @param inURL The NSURL to request
* @param textView the NSTextView to insert the shortened URL itno
*
* Replaces the selected text in textView with the result of requesting
* the page at inURL if successful. Otherwise, beep.
*/
- (void)insertResultFromURL:(NSURL *)inURL intoTextView:(NSTextView *)textView
{
NSString *shortenedURL = [self resultFromURL:inURL];
if(shortenedURL) {
NSRange selectedRange = textView.selectedRange;
// Replace the current selection with the new URL
NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:[textView.attributedString attributesAtIndex:selectedRange.location effectiveRange:nil]];
[attrs setObject:shortenedURL forKey:NSLinkAttributeName];
[textView.textStorage replaceCharactersInRange:selectedRange
withAttributedString:[[[NSAttributedString alloc] initWithString:shortenedURL attributes:attrs] autorelease]];
// Select the inserted URL
textView.selectedRange = NSMakeRange(selectedRange.location, shortenedURL.length);
// Post a notification that we've changed the text
[[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidChangeNotification
object:textView];
} else {
// Be as obscure as possible: roadrunner.
NSBeep();
}
}
/*!
* @brief Requests a URL, returns the contents
*
* @param inURL The NSURL to request
* @return An NSString of the page requested or nil
*
* Synchronously requests the given URL. If the request is successful, i.e. the
* HTTP status code is 200 and there's no error, the contents of the page are returned.
*/
- (NSString *)resultFromURL:(NSURL *)inURL
{
NSString *resultString = nil;
NSURLResponse *response = nil;
NSError *errorResponse = nil;
// We send a synchronous request so the user can't change selection on us.
// If the target site is slow, this may seem unpleasant.
NSData *shortenedData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:inURL]
returningResponse:&response
error:&errorResponse];
AILogWithSignature(@"Requesting %@", inURL);
// If the request was successful, replace the selected text with the shortened URL. Otherwise fail silently.
if(shortenedData && !errorResponse && ((NSHTTPURLResponse *)response).statusCode == 200) {
resultString = [[NSString stringWithData:shortenedData encoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
AILogWithSignature(@"Shortened to %@", resultString);
} else {
AILogWithSignature(@"Unable to shorten: %@", errorResponse);
}
return resultString;
}
@end
|