|
zacw@847
|
1 |
/* |
|
zacw@847
|
2 |
* Adium is the legal property of its developers, whose names are listed in the copyright file included |
|
zacw@847
|
3 |
* with this source distribution. |
|
zacw@847
|
4 |
* |
|
zacw@847
|
5 |
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU |
|
zacw@847
|
6 |
* General Public License as published by the Free Software Foundation; either version 2 of the License, |
|
zacw@847
|
7 |
* or (at your option) any later version. |
|
zacw@847
|
8 |
* |
|
zacw@847
|
9 |
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
zacw@847
|
10 |
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
zacw@847
|
11 |
* Public License for more details. |
|
zacw@847
|
12 |
* |
|
zacw@847
|
13 |
* You should have received a copy of the GNU General Public License along with this program; if not, |
|
zacw@847
|
14 |
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
zacw@847
|
15 |
*/ |
|
zacw@847
|
16 |
|
|
zacw@847
|
17 |
#import "AIMessageViewController.h" |
|
zacw@847
|
18 |
#import "AIMessageTabViewItem.h" |
|
zacw@847
|
19 |
#import "AITwitterAccount.h" |
|
zacw@847
|
20 |
|
|
zacw@847
|
21 |
#import "AITwitterURLHandler.h" |
|
zacw@1718
|
22 |
#import "AIURLHandlerPlugin.h" |
|
zacw@847
|
23 |
#import <AIUtilities/AIURLAdditions.h> |
|
zacw@847
|
24 |
#import <AIUtilities/AIAttributedStringAdditions.h> |
|
zacw@1154
|
25 |
#import <AIUtilities/AIStringAdditions.h> |
|
zacw@847
|
26 |
#import <Adium/AIMessageEntryTextView.h> |
|
zacw@847
|
27 |
#import <Adium/AIAccount.h> |
|
zacw@847
|
28 |
#import <Adium/AIChat.h> |
|
zacw@847
|
29 |
#import <Adium/AIService.h> |
|
zacw@847
|
30 |
#import <Adium/AIAccountControllerProtocol.h> |
|
zacw@847
|
31 |
#import <Adium/AIChatControllerProtocol.h> |
|
zacw@847
|
32 |
#import <Adium/AIInterfaceControllerProtocol.h> |
|
zacw@2060
|
33 |
#import <Adium/AIContentControllerProtocol.h> |
|
zacw@847
|
34 |
|
|
zacw@847
|
35 |
@implementation AITwitterURLHandler |
|
zacw@847
|
36 |
|
|
zacw@847
|
37 |
/*! |
|
zacw@847
|
38 |
* @brief Install the plugin |
|
zacw@847
|
39 |
* |
|
zacw@847
|
40 |
* This plugin handles links in the format "twitterreply://account@username?status=(sid)" where the account as a provided user is optional. |
|
zacw@847
|
41 |
*/ |
|
zacw@847
|
42 |
- (void)installPlugin |
|
zacw@847
|
43 |
{ |
|
zacw@1718
|
44 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(urlRequest:) name:AIURLHandleNotification object:nil]; |
|
zacw@847
|
45 |
} |
|
zacw@847
|
46 |
|
|
zacw@847
|
47 |
/*! |
|
zacw@847
|
48 |
* @brief Uninstall plugin |
|
zacw@847
|
49 |
*/ |
|
zacw@847
|
50 |
- (void)uninstallPlugin |
|
zacw@847
|
51 |
{ |
|
David@1109
|
52 |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
zacw@847
|
53 |
} |
|
zacw@847
|
54 |
|
|
zacw@847
|
55 |
/*! |
|
zacw@847
|
56 |
* @brief A reply link was licked |
|
zacw@847
|
57 |
* |
|
zacw@847
|
58 |
* Parse the reply link and set up as appropriate. |
|
zacw@847
|
59 |
*/ |
|
zacw@847
|
60 |
- (void)urlRequest:(NSNotification *)notification |
|
zacw@847
|
61 |
{ |
|
zacw@1718
|
62 |
NSString *urlString = notification.object; |
|
zacw@1718
|
63 |
NSURL *url = [NSURL URLWithString:urlString]; |
|
zacw@1718
|
64 |
|
|
zacw@1718
|
65 |
if (![url.scheme isEqualToString:@"twitterreply"]) { |
|
zacw@1718
|
66 |
return; |
|
zacw@1718
|
67 |
} |
|
zacw@1718
|
68 |
|
|
zacw@847
|
69 |
NSString *inUser = [url host]; |
|
zacw@1242
|
70 |
NSString *inAction = [url queryArgumentForKey:@"action" withDelimiter:@"&"] ?: @"reply"; |
|
zacw@1242
|
71 |
NSString *inTweet = [url queryArgumentForKey:@"status" withDelimiter:@"&"]; |
|
zacw@1251
|
72 |
NSString *inDM = [url queryArgumentForKey:@"dm" withDelimiter:@"&"]; |
|
zacw@1242
|
73 |
NSString *inMessage = [url queryArgumentForKey:@"message" withDelimiter:@"&"]; |
|
zacw@847
|
74 |
NSString *inAccount = [url user]; |
|
zacw@847
|
75 |
|
|
zacw@847
|
76 |
AILogWithSignature(@"Twitter Reply requested: %@", url); |
|
zacw@847
|
77 |
|
|
catfish@1823
|
78 |
NSArray *accountArray = adium.accountController.accounts; |
|
zacw@847
|
79 |
|
|
zacw@847
|
80 |
AITwitterAccount *account = nil; |
|
zacw@1197
|
81 |
BOOL exactMatchForInternalID = NO; |
|
zacw@847
|
82 |
|
|
zacw@1035
|
83 |
// Look for an account with the given internalObjectID |
|
zacw@847
|
84 |
for(AIAccount *tempAccount in accountArray) { |
|
zacw@847
|
85 |
if (![tempAccount isKindOfClass:[AITwitterAccount class]]) { |
|
zacw@1035
|
86 |
continue; |
|
zacw@847
|
87 |
} |
|
zacw@847
|
88 |
|
|
zacw@1197
|
89 |
account = (AITwitterAccount *)tempAccount; |
|
zacw@1197
|
90 |
|
|
zacw@1035
|
91 |
if([tempAccount.internalObjectID isEqualToString:inAccount]) { |
|
zacw@1197
|
92 |
exactMatchForInternalID = YES; |
|
zacw@847
|
93 |
break; |
|
zacw@847
|
94 |
} |
|
zacw@847
|
95 |
} |
|
zacw@1197
|
96 |
|
|
zacw@847
|
97 |
if(!account) { |
|
zacw@1197
|
98 |
// No exact match. Fail. |
|
zacw@847
|
99 |
return; |
|
zacw@847
|
100 |
} |
|
zacw@2768
|
101 |
|
|
zacw@2768
|
102 |
if ([inAction isEqualToString:@"retweet"]) { |
|
zacw@2768
|
103 |
[account retweetTweet:inTweet]; |
|
zacw@2768
|
104 |
} else if ([inAction isEqualToString:@"reply"]) { |
|
zacw@1184
|
105 |
AIChat *timelineChat = [adium.chatController existingChatWithName:account.timelineChatName |
|
zacw@1184
|
106 |
onAccount:account]; |
|
zacw@1184
|
107 |
|
|
zacw@1184
|
108 |
if (!timelineChat) { |
|
zacw@1184
|
109 |
// Timeline chat isn't already open. Open it. |
|
zacw@1184
|
110 |
timelineChat = [adium.chatController chatWithName:account.timelineChatName |
|
zacw@1184
|
111 |
identifier:nil |
|
zacw@1184
|
112 |
onAccount:account |
|
zacw@1184
|
113 |
chatCreationInfo:nil]; |
|
zacw@1184
|
114 |
} |
|
zacw@1184
|
115 |
|
|
zacw@1184
|
116 |
if (!timelineChat.isOpen) { |
|
zacw@1184
|
117 |
[adium.interfaceController openChat:timelineChat]; |
|
zacw@1184
|
118 |
} |
|
zacw@1184
|
119 |
|
|
zacw@1184
|
120 |
[adium.interfaceController setActiveChat:timelineChat]; |
|
zacw@1184
|
121 |
|
|
zacw@1184
|
122 |
AIMessageEntryTextView *textView = ((AIMessageTabViewItem *)[timelineChat valueForProperty:@"MessageTabViewItem"]).messageViewController.textEntryView; |
|
zacw@926
|
123 |
|
|
zacw@1184
|
124 |
// Insert the @reply text |
|
zacw@2768
|
125 |
NSString *prefix = [NSString stringWithFormat:@"@%@ ", inUser]; |
|
zacw@1154
|
126 |
|
|
zacw@1499
|
127 |
if (![textView.string hasPrefix:prefix]) { |
|
zacw@2060
|
128 |
NSMutableAttributedString *newString; |
|
zacw@2060
|
129 |
if (textView.attributedString.length > 0){ |
|
zacw@2060
|
130 |
newString = [[[textView.attributedString attributedSubstringFromRange:NSMakeRange(0, 1)] mutableCopy] autorelease]; |
|
zacw@2060
|
131 |
[newString replaceCharactersInRange:NSMakeRange(0, 1) withString:prefix]; |
|
zacw@2060
|
132 |
} |
|
zacw@2060
|
133 |
else |
|
zacw@2060
|
134 |
newString = [[[NSMutableAttributedString alloc] initWithString:prefix attributes:[adium.contentController defaultFormattingAttributes]] autorelease]; |
|
zacw@2060
|
135 |
|
|
zacw@1499
|
136 |
[newString appendAttributedString:textView.attributedString]; |
|
zacw@1499
|
137 |
[textView setAttributedString:newString]; |
|
zacw@1499
|
138 |
|
|
zacw@1499
|
139 |
// Shift the selected range over by the length of our prefix string |
|
zacw@1499
|
140 |
NSRange selectedRange = textView.selectedRange; |
|
zacw@1499
|
141 |
[textView setSelectedRange:NSMakeRange(selectedRange.location + prefix.length, selectedRange.length)]; |
|
zacw@1499
|
142 |
} |
|
zacw@1499
|
143 |
|
|
zacw@1184
|
144 |
// Make the text view have focus |
|
zacw@1184
|
145 |
[[adium.interfaceController windowForChat:timelineChat] makeFirstResponder:textView]; |
|
zacw@1184
|
146 |
|
|
zacw@2051
|
147 |
[timelineChat setValue:inTweet forProperty:@"TweetInReplyToStatusID" notify:NotifyNow]; |
|
zacw@2051
|
148 |
[timelineChat setValue:inUser forProperty:@"TweetInReplyToUserID" notify:NotifyNow]; |
|
zacw@2051
|
149 |
[timelineChat setValue:@"@" forProperty:@"Character Counter Prefix" notify:NotifyNow]; |
|
zacw@2051
|
150 |
|
|
zacw@2051
|
151 |
AILogWithSignature(@"Flagging chat %@ to in_reply_to_status_id = %@", timelineChat, inTweet); |
|
zacw@2051
|
152 |
|
|
zacw@2051
|
153 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextDidChangeNotification object:textView]; |
|
zacw@2051
|
154 |
|
|
zacw@2051
|
155 |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextDidChangeNotification object:textView]; |
|
zacw@1184
|
156 |
} else if ([inAction isEqualToString:@"favorite"]) { |
|
zacw@1184
|
157 |
[account toggleFavoriteTweet:inTweet]; |
|
zacw@1197
|
158 |
} else if ([inAction isEqualToString:@"destroy"] && exactMatchForInternalID) { |
|
zacw@1242
|
159 |
if (inTweet && inMessage) { |
|
zacw@1197
|
160 |
// Confirm if the user wants to delete this tweet. |
|
zacw@1197
|
161 |
if (NSRunAlertPanel(AILocalizedString(@"Delete Tweet?", nil), |
|
zacw@1197
|
162 |
AILocalizedString(@"Are you sure you want to delete the tweet:\n\n\"%@\"\n\nThis action cannot be undone.", nil), |
|
zacw@1197
|
163 |
AILocalizedString(@"Delete", nil), AILocalizedString(@"Cancel", nil), nil, |
|
zacw@1242
|
164 |
[inMessage stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]) == NSAlertDefaultReturn) { |
|
zacw@1197
|
165 |
[account destroyTweet:inTweet]; |
|
zacw@1197
|
166 |
} |
|
zacw@1251
|
167 |
} else if (inDM && inMessage) { |
|
zacw@1251
|
168 |
// Confirm if the user wants to delete this DM. |
|
zacw@1251
|
169 |
if (NSRunAlertPanel(AILocalizedString(@"Delete Direct Message?", nil), |
|
zacw@1251
|
170 |
AILocalizedString(@"Are you sure you want to delete the direct message:\n\n\"%@\"\n\nThis action cannot be undone.", nil), |
|
zacw@1251
|
171 |
AILocalizedString(@"Delete", nil), AILocalizedString(@"Cancel", nil), nil, |
|
zacw@1251
|
172 |
[inMessage stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]) == NSAlertDefaultReturn) { |
|
zacw@1251
|
173 |
[account destroyDirectMessage:inDM forUser:inUser]; |
|
zacw@1251
|
174 |
} |
|
zacw@1197
|
175 |
} |
|
zacw@1154
|
176 |
} |
|
zacw@847
|
177 |
} |
|
zacw@847
|
178 |
|
|
zacw@847
|
179 |
- (void)textDidChange:(NSNotification *)notification |
|
zacw@847
|
180 |
{ |
|
zacw@847
|
181 |
AIMessageEntryTextView *textView = [notification object]; |
|
zacw@847
|
182 |
|
|
zacw@847
|
183 |
AIChat *chat = textView.chat; |
|
zacw@847
|
184 |
|
|
zacw@847
|
185 |
if(![chat valueForProperty:@"TweetInReplyToStatusID"] || ![chat valueForProperty:@"TweetInReplyToUserID"]) { |
|
zacw@847
|
186 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextDidChangeNotification object:textView]; |
|
zacw@847
|
187 |
return; |
|
zacw@847
|
188 |
} |
|
zacw@847
|
189 |
|
|
zacw@1184
|
190 |
NSString *contents = [textView string]; |
|
zacw@847
|
191 |
BOOL keepTweetValues = YES; |
|
zacw@847
|
192 |
|
|
zacw@2051
|
193 |
NSRange usernameRange = [contents rangeOfString:[NSString stringWithFormat:@"@%@", [chat valueForProperty:@"TweetInReplyToUserID"]]]; |
|
zacw@2051
|
194 |
|
|
zacw@2051
|
195 |
if (usernameRange.location == NSNotFound) { |
|
zacw@847
|
196 |
keepTweetValues = NO; |
|
zacw@847
|
197 |
} |
|
zacw@847
|
198 |
|
|
zacw@847
|
199 |
if (!keepTweetValues) { |
|
zacw@847
|
200 |
AILogWithSignature(@"Removing in_reply_to_status_id from chat %@", chat); |
|
zacw@847
|
201 |
|
|
zacw@847
|
202 |
[chat setValue:nil forProperty:@"TweetInReplyToStatusID" notify:NotifyNow]; |
|
zacw@847
|
203 |
[chat setValue:nil forProperty:@"TweetInReplyToUserID" notify:NotifyNow]; |
|
zacw@847
|
204 |
[chat setValue:nil forProperty:@"Character Counter Prefix" notify:NotifyNow]; |
|
zacw@847
|
205 |
|
|
zacw@847
|
206 |
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextDidChangeNotification object:textView]; |
|
zacw@847
|
207 |
} |
|
zacw@847
|
208 |
} |
|
zacw@847
|
209 |
|
|
zacw@847
|
210 |
@end |