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 "AIAutoReplyPlugin.h"
18 #import <Adium/AIContactControllerProtocol.h>
19 #import <Adium/AIContentControllerProtocol.h>
20 #import <Adium/AIPreferenceControllerProtocol.h>
21 #import "AIStatusController.h"
22 #import <AIUtilities/AIArrayAdditions.h>
23 #import <Adium/AIAccount.h>
24 #import <Adium/AIChat.h>
25 #import <Adium/AIContentMessage.h>
26 #import <Adium/AIContentObject.h>
29 * @class AIAutoReplyPlugin
30 * @brief Provides AutoReply functionality for the state system
32 * This class implements the state system behavior for auto-reply. If auto-reply status is active on an account,
33 * initial messages recieved on that account will be replied to automatically. Subsequent messages will not receive
34 * a reply unless the chat window is closed.
36 * This is the expected behavior on certain protocols such as AIM, and considered a convenience on other protocols.
38 @implementation AIAutoReplyPlugin
41 * @brief Initialize the auto-reply system
43 * Initialize the auto-reply system to monitor account status. When an account auto-reply flag is set we begin to
44 * monitor chat messaging and auto-reply as necessary.
49 receivedAutoReply = [[NSMutableSet alloc] init];
52 [[adium notificationCenter] addObserver:self
53 selector:@selector(didReceiveContent:)
54 name:CONTENT_MESSAGE_RECEIVED object:nil];
55 [[adium notificationCenter] addObserver:self
56 selector:@selector(didSendContent:)
57 name:CONTENT_MESSAGE_SENT object:nil];
58 [[adium notificationCenter] addObserver:self
59 selector:@selector(chatWillClose:)
60 name:Chat_WillClose object:nil];
62 [[adium contactController] registerListObjectObserver:self];
70 [[adium contactController] unregisterListObjectObserver:self];
71 [receivedAutoReply release];
77 * @brief Account status changed.
79 * Update our chat monitoring in response to account status changes.
81 * TODO: If there are no accounts with an auto-reply flag set we SHOULD stop monitoring messages for optimal performance.
83 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
85 if ([inObject isKindOfClass:[AIAccount class]] &&
86 [inModifiedKeys containsObject:@"StatusState"]) {
88 //Reset our list of contacts who have already received an auto-reply
89 [receivedAutoReply release]; receivedAutoReply = [[NSMutableSet alloc] init];
91 //Don't want to remove from the new set any chats which previously closed and are pending removal
92 [NSObject cancelPreviousPerformRequestsWithTarget:self];
99 * @brief Respond to a received message
101 * Respond to a received message by sending out the current auto-reply. We only send the auto-reply once to each
102 * contact, and then their name is added to a list and additional received messages are ignored.
104 - (void)didReceiveContent:(NSNotification *)notification
106 AIContentObject *contentObject = [[notification userInfo] objectForKey:@"AIContentObject"];
107 AIChat *chat = [contentObject chat];
109 /* We will not respond to the received message if it is an auto-reply, over a chat where we have already responded,
110 * or over a group chat.
111 * Additionally, it's not sensible to send autoreplies if the arriving message
112 * was an offline message we're getting as we connect and it's older than 5 minutes
113 * (or the person sending it is no longer online) -RAF
114 * For AIM accounts, we know it is an offline message if it starts with "[Offline IM sent". Of course, a user could send a message like that...
115 * but said user deserves not to receive an auto-reply.
117 if ([[contentObject type] isEqualToString:CONTENT_MESSAGE_TYPE] &&
118 ![(AIContentMessage *)contentObject isAutoreply] &&
119 ![receivedAutoReply containsObject:[chat uniqueChatID]] &&
120 ![chat isGroupChat] &&
121 (abs([[contentObject date] timeIntervalSinceNow]) < 300) &&
122 !([[[contentObject source] serviceClass] isEqualToString:@"AIM-compatible"] && [[[contentObject message] string] hasPrefix:@"[Offline IM sent"])) {
123 //300 is 5 minutes in seconds
125 [self sendAutoReplyFromAccount:[contentObject destination]
126 toContact:[contentObject source]
129 [receivedAutoReply addObject:[chat uniqueChatID]];
134 * @brief Send an auto-reply
136 * Sends our current auto-reply to the specified contact.
137 * @param source Account sending the object
138 * @param destination Contact receiving the object
139 * @param chat Chat the communication is occuring over
141 - (void)sendAutoReplyFromAccount:(id)source toContact:(id)destination onChat:(AIChat *)chat
143 AIContentMessage *responseContent = nil;
144 NSAttributedString *autoReply = [[[chat account] statusState] autoReply];
145 BOOL supportsAutoreply = [source supportsAutoReplies];
148 if (!supportsAutoreply) {
149 //Tthe service isn't natively expecting an autoresponse, so make it a bit clearer what's going on
150 NSMutableAttributedString *mutableAutoReply = [[autoReply mutableCopy] autorelease];
151 [mutableAutoReply replaceCharactersInRange:NSMakeRange(0, 0)
152 withString:AILocalizedString(@"(Autoreply) ",
153 "Prefix to place before autoreplies on services which do not natively support them")];
154 autoReply = mutableAutoReply;
157 responseContent = [AIContentMessage messageInChat:chat
159 destination:destination
162 autoreply:supportsAutoreply];
164 [[adium contentController] sendContentObject:responseContent];
169 * @brief Respond to our user sending messages
171 * For convenience, when our user messages a contact while away we exclude that contact from receiving our auto-away
172 * on future messages.
174 - (void)didSendContent:(NSNotification *)notification
176 AIContentObject *contentObject = [[notification userInfo] objectForKey:@"AIContentObject"];
177 AIChat *chat = [contentObject chat];
179 if ([[contentObject type] isEqualToString:CONTENT_MESSAGE_TYPE]) {
180 [receivedAutoReply addObject:[chat uniqueChatID]];
184 - (void)removeChatIDFromReceivedAutoReply:(id)uniqueChatID
186 [receivedAutoReply removeObject:uniqueChatID];
190 * @brief Respond to a chat closing
192 * Once a chat is closed we forget about whether it has received an auto-response. If the chat is re-opened, it will
193 * receive our auto-response again. This behavior is not necessarily desired, but is a side effect of basing our
194 * already-received list on chats and not contacts. However, many users have come to expect this behavior and it's
195 * presence is neither strongly negative or positive.
197 - (void)chatWillClose:(NSNotification *)notification
199 /* Don't remove the chat until 30 seconds from now to prevent the classic situation in which you close the window,
200 * the contact messages you one last message, and a needless autoreply is sent
202 [NSObject cancelPreviousPerformRequestsWithTarget:self
203 selector:@selector(removeChatIDFromReceivedAutoReply:)
204 object:[[notification object] uniqueChatID]];
205 [self performSelector:@selector(removeChatIDFromReceivedAutoReply:)
206 withObject:[[notification object] uniqueChatID]