Now, groups can retrieve a list of the contacts in that group. I probably should...
[adiumx.git] / Source / AIAutoReplyPlugin.m
blob3a590bf475bdd08b6852cd1cda9ae2e2ea7a2830
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
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>
28 /*!
29  * @class AIAutoReplyPlugin
30  * @brief Provides AutoReply functionality for the state system
31  *
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.
35  *
36  * This is the expected behavior on certain protocols such as AIM, and considered a convenience on other protocols.
37  */
38 @implementation AIAutoReplyPlugin
40 /*!
41  * @brief Initialize the auto-reply system
42  *
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.
45  */
46 - (void)installPlugin
48         //Init
49         receivedAutoReply = [[NSMutableSet alloc] init];
50         
51         //Add observers
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];
61         
62         [[adium contactController] registerListObjectObserver:self];
65 /*!
66  * Deallocate
67  */
68 - (void)dealloc
70         [[adium contactController] unregisterListObjectObserver:self];
71         [receivedAutoReply release];
72         
73         [super dealloc];
76 /*!
77  * @brief Account status changed.
78  *
79  * Update our chat monitoring in response to account status changes.  
80  *
81  * TODO: If there are no accounts with an auto-reply flag set we SHOULD stop monitoring messages for optimal performance.
82  */
83 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
85         if ([inObject isKindOfClass:[AIAccount class]] &&
86            [inModifiedKeys containsObject:@"StatusState"]) {
87                         
88                 //Reset our list of contacts who have already received an auto-reply
89                 [receivedAutoReply release]; receivedAutoReply = [[NSMutableSet alloc] init];
90                 
91                 //Don't want to remove from the new set any chats which previously closed and are pending removal
92                 [NSObject cancelPreviousPerformRequestsWithTarget:self];
93         }
94     
95     return nil;
98 /*!
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.
103  */
104 - (void)didReceiveContent:(NSNotification *)notification
106     AIContentObject     *contentObject = [[notification userInfo] objectForKey:@"AIContentObject"];
107     AIChat                              *chat = [contentObject chat];
108         
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.
116          */
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
124                 
125                 [self sendAutoReplyFromAccount:[contentObject destination]
126                                                          toContact:[contentObject source]
127                                                                 onChat:chat];
129                 [receivedAutoReply addObject:[chat uniqueChatID]];
130         }
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
140  */
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];
146                 
147         if (autoReply) {
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;
155                 }
157                 responseContent = [AIContentMessage messageInChat:chat
158                                                                                            withSource:source
159                                                                                           destination:destination
160                                                                                                          date:nil
161                                                                                                   message:autoReply
162                                                                                                 autoreply:supportsAutoreply];
163                 
164                 [[adium contentController] sendContentObject:responseContent];
165         }
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.
173  */
174 - (void)didSendContent:(NSNotification *)notification
176     AIContentObject     *contentObject = [[notification userInfo] objectForKey:@"AIContentObject"];
177         AIChat                  *chat = [contentObject chat];
178    
179     if ([[contentObject type] isEqualToString:CONTENT_MESSAGE_TYPE]) {
180                 [receivedAutoReply addObject:[chat uniqueChatID]];
181     }
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.
196  */
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
201          */
202         [NSObject cancelPreviousPerformRequestsWithTarget:self
203                                                                                          selector:@selector(removeChatIDFromReceivedAutoReply:)
204                                                                                            object:[[notification object] uniqueChatID]];
205         [self performSelector:@selector(removeChatIDFromReceivedAutoReply:)
206                            withObject:[[notification object] uniqueChatID]
207                            afterDelay:30.0];
210 @end