Implement the Retweet API. This means checking home_timeline and sending proper retweet messages. Fixes #12556.
On an annoying note, home_timeline (despite saying "Returns the 20 most recent statuses, including retweets, posted by the authenticating user and that user's friends") does not include outgoing retweets. This will either be fixed by Twitter quickly, or not. I'm tired of Twitter's inconsistent and buggy API. So, as such, there's currently no way to remove a retweet done by yourself.
2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "AIMessageViewController.h"
18 #import "AIMessageTabViewItem.h"
19 #import "AITwitterAccount.h"
21 #import "AITwitterURLHandler.h"
22 #import "AIURLHandlerPlugin.h"
23 #import <AIUtilities/AIURLAdditions.h>
24 #import <AIUtilities/AIAttributedStringAdditions.h>
25 #import <AIUtilities/AIStringAdditions.h>
26 #import <Adium/AIMessageEntryTextView.h>
27 #import <Adium/AIAccount.h>
28 #import <Adium/AIChat.h>
29 #import <Adium/AIService.h>
30 #import <Adium/AIAccountControllerProtocol.h>
31 #import <Adium/AIChatControllerProtocol.h>
32 #import <Adium/AIInterfaceControllerProtocol.h>
33 #import <Adium/AIContentControllerProtocol.h>
35 @implementation AITwitterURLHandler
38 * @brief Install the plugin
40 * This plugin handles links in the format "twitterreply://account@username?status=(sid)" where the account as a provided user is optional.
44 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(urlRequest:) name:AIURLHandleNotification object:nil];
48 * @brief Uninstall plugin
50 - (void)uninstallPlugin
52 [[NSNotificationCenter defaultCenter] removeObserver:self];
56 * @brief A reply link was licked
58 * Parse the reply link and set up as appropriate.
60 - (void)urlRequest:(NSNotification *)notification
62 NSString *urlString = notification.object;
63 NSURL *url = [NSURL URLWithString:urlString];
65 if (![url.scheme isEqualToString:@"twitterreply"]) {
69 NSString *inUser = [url host];
70 NSString *inAction = [url queryArgumentForKey:@"action" withDelimiter:@"&"] ?: @"reply";
71 NSString *inTweet = [url queryArgumentForKey:@"status" withDelimiter:@"&"];
72 NSString *inDM = [url queryArgumentForKey:@"dm" withDelimiter:@"&"];
73 NSString *inMessage = [url queryArgumentForKey:@"message" withDelimiter:@"&"];
74 NSString *inAccount = [url user];
76 AILogWithSignature(@"Twitter Reply requested: %@", url);
78 NSArray *accountArray = adium.accountController.accounts;
80 AITwitterAccount *account = nil;
81 BOOL exactMatchForInternalID = NO;
83 // Look for an account with the given internalObjectID
84 for(AIAccount *tempAccount in accountArray) {
85 if (![tempAccount isKindOfClass:[AITwitterAccount class]]) {
89 account = (AITwitterAccount *)tempAccount;
91 if([tempAccount.internalObjectID isEqualToString:inAccount]) {
92 exactMatchForInternalID = YES;
98 // No exact match. Fail.
102 if ([inAction isEqualToString:@"retweet"]) {
103 [account retweetTweet:inTweet];
104 } else if ([inAction isEqualToString:@"reply"]) {
105 AIChat *timelineChat = [adium.chatController existingChatWithName:account.timelineChatName
109 // Timeline chat isn't already open. Open it.
110 timelineChat = [adium.chatController chatWithName:account.timelineChatName
113 chatCreationInfo:nil];
116 if (!timelineChat.isOpen) {
117 [adium.interfaceController openChat:timelineChat];
120 [adium.interfaceController setActiveChat:timelineChat];
122 AIMessageEntryTextView *textView = ((AIMessageTabViewItem *)[timelineChat valueForProperty:@"MessageTabViewItem"]).messageViewController.textEntryView;
124 // Insert the @reply text
125 NSString *prefix = [NSString stringWithFormat:@"@%@ ", inUser];
127 if (![textView.string hasPrefix:prefix]) {
128 NSMutableAttributedString *newString;
129 if (textView.attributedString.length > 0){
130 newString = [[[textView.attributedString attributedSubstringFromRange:NSMakeRange(0, 1)] mutableCopy] autorelease];
131 [newString replaceCharactersInRange:NSMakeRange(0, 1) withString:prefix];
134 newString = [[[NSMutableAttributedString alloc] initWithString:prefix attributes:[adium.contentController defaultFormattingAttributes]] autorelease];
136 [newString appendAttributedString:textView.attributedString];
137 [textView setAttributedString:newString];
139 // Shift the selected range over by the length of our prefix string
140 NSRange selectedRange = textView.selectedRange;
141 [textView setSelectedRange:NSMakeRange(selectedRange.location + prefix.length, selectedRange.length)];
144 // Make the text view have focus
145 [[adium.interfaceController windowForChat:timelineChat] makeFirstResponder:textView];
147 [timelineChat setValue:inTweet forProperty:@"TweetInReplyToStatusID" notify:NotifyNow];
148 [timelineChat setValue:inUser forProperty:@"TweetInReplyToUserID" notify:NotifyNow];
149 [timelineChat setValue:@"@" forProperty:@"Character Counter Prefix" notify:NotifyNow];
151 AILogWithSignature(@"Flagging chat %@ to in_reply_to_status_id = %@", timelineChat, inTweet);
153 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextDidChangeNotification object:textView];
155 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:NSTextDidChangeNotification object:textView];
156 } else if ([inAction isEqualToString:@"favorite"]) {
157 [account toggleFavoriteTweet:inTweet];
158 } else if ([inAction isEqualToString:@"destroy"] && exactMatchForInternalID) {
159 if (inTweet && inMessage) {
160 // Confirm if the user wants to delete this tweet.
161 if (NSRunAlertPanel(AILocalizedString(@"Delete Tweet?", nil),
162 AILocalizedString(@"Are you sure you want to delete the tweet:\n\n\"%@\"\n\nThis action cannot be undone.", nil),
163 AILocalizedString(@"Delete", nil), AILocalizedString(@"Cancel", nil), nil,
164 [inMessage stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]) == NSAlertDefaultReturn) {
165 [account destroyTweet:inTweet];
167 } else if (inDM && inMessage) {
168 // Confirm if the user wants to delete this DM.
169 if (NSRunAlertPanel(AILocalizedString(@"Delete Direct Message?", nil),
170 AILocalizedString(@"Are you sure you want to delete the direct message:\n\n\"%@\"\n\nThis action cannot be undone.", nil),
171 AILocalizedString(@"Delete", nil), AILocalizedString(@"Cancel", nil), nil,
172 [inMessage stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]) == NSAlertDefaultReturn) {
173 [account destroyDirectMessage:inDM forUser:inUser];
179 - (void)textDidChange:(NSNotification *)notification
181 AIMessageEntryTextView *textView = [notification object];
183 AIChat *chat = textView.chat;
185 if(![chat valueForProperty:@"TweetInReplyToStatusID"] || ![chat valueForProperty:@"TweetInReplyToUserID"]) {
186 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextDidChangeNotification object:textView];
190 NSString *contents = [textView string];
191 BOOL keepTweetValues = YES;
193 NSRange usernameRange = [contents rangeOfString:[NSString stringWithFormat:@"@%@", [chat valueForProperty:@"TweetInReplyToUserID"]]];
195 if (usernameRange.location == NSNotFound) {
196 keepTweetValues = NO;
199 if (!keepTweetValues) {
200 AILogWithSignature(@"Removing in_reply_to_status_id from chat %@", chat);
202 [chat setValue:nil forProperty:@"TweetInReplyToStatusID" notify:NotifyNow];
203 [chat setValue:nil forProperty:@"TweetInReplyToUserID" notify:NotifyNow];
204 [chat setValue:nil forProperty:@"Character Counter Prefix" notify:NotifyNow];
206 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTextDidChangeNotification object:textView];