ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/cms/source/server/uk/org/iscream/cms/server/client/alerters/IRC__Alerter.java
(Generate patch)

Comparing projects/cms/source/server/uk/org/iscream/cms/server/client/alerters/IRC__Alerter.java (file contents):
Revision 1.24 by ajm, Thu Mar 22 22:07:58 2001 UTC vs.
Revision 1.35 by tdb, Wed Feb 5 16:43:45 2003 UTC

# Line 1 | Line 1
1 + /*
2 + * i-scream central monitoring system
3 + * http://www.i-scream.org.uk
4 + * Copyright (C) 2000-2002 i-scream
5 + *
6 + * This program is free software; you can redistribute it and/or
7 + * modify it under the terms of the GNU General Public License
8 + * as published by the Free Software Foundation; either version 2
9 + * of the License, or (at your option) any later version.
10 + *
11 + * This program is distributed in the hope that it will be useful,
12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 + * GNU General Public License for more details.
15 + *
16 + * You should have received a copy of the GNU General Public License
17 + * along with this program; if not, write to the Free Software
18 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 + */
20 +
21   //---PACKAGE DECLARATION---
22 < package uk.org.iscream.client.alerters;
22 > package uk.org.iscream.cms.server.client.alerters;
23  
24   //---IMPORTS---
25 < import uk.org.iscream.client.*;
26 < import uk.org.iscream.core.*;
27 < import uk.org.iscream.util.*;
28 < import uk.org.iscream.componentmanager.*;
9 <
25 > import uk.org.iscream.cms.server.client.*;
26 > import uk.org.iscream.cms.server.core.*;
27 > import uk.org.iscream.cms.util.*;
28 > import uk.org.iscream.cms.server.componentmanager.*;
29   import java.io.*;
30   import java.net.*;
31   import java.util.*;
32 < import java.text.*;
32 > import java.text.DateFormat;
33 > import org.jibble.pircbot.*;
34  
35   /**
36   * This Alert sends an IRC message.
# Line 21 | Line 41 | import java.text.*;
41   * @author  $Author$
42   * @version $Id$
43   */
44 < public class IRC__Alerter extends Thread implements PluginAlerter {
44 > public class IRC__Alerter extends AlerterSkeleton {
45  
46   //---FINAL ATTRIBUTES---
47  
# Line 35 | Line 55 | public class IRC__Alerter extends Thread implements Pl
55       */
56      public final String DESC = "Sends alerts on an IRC channel";
57      
38    /**
39     * The default reconnect delay in seconds
40     */
41    public final int DEFAULT_RECONNECT_DELAY = 30;
42    
43    public final String DEFAULT_LEVEL = Alert.alertLevels[0];
44    
45    public final String NOT_CONFIGURED = "<NOT CONFIGURED>";
46    
58   //---STATIC METHODS---
59  
60   //---CONSTRUCTORS---
61 <
61 >    
62      public IRC__Alerter() {
63 <                        
64 <        // connect to the IRC server
63 >        super();
64 >        // construct and initialise the bot
65          _ircbot = new IRCBot();
66 +        _ircbot.setVerbose(false);
67 +        Thread ircThread = new Thread(_ircbot);
68          // set it's name and start it
69 <        _ircbot.setName("client.IRC__Alerter$IRCBot");
70 <        _ircbot.start();
69 >        ircThread.setName("client.IRC__Alerter$IRCBot");
70 >        ircThread.start();
71 >        // log our start time
72          _startTime = System.currentTimeMillis();
59        this.start();
73          _logger.write(toString(), Logger.SYSINIT, "IRC Alerter started");
74      }
75  
76   //---PUBLIC METHODS---
77  
78 <    public void run() {
79 <        while(_running) {
80 <            try {
81 <                sendAlert((Alert) getQueue().get(getQueueId()));
82 <            } catch (InvalidQueueException e) {
83 <                _logger.write(this.toString(), Logger.ERROR, "Unable to get queue.");
84 <            }
72 <        }
73 <    }
74 <
78 >    /**
79 >     * Implements the abstract method from the skeleton class.
80 >     * This method will attempt to send an alert
81 >     * message over the IRC channel.
82 >     *
83 >     * @param alert the alert to send
84 >     */
85      public void sendAlert(Alert alert) {
86 +        // sort out the message      
87 +        String alertType = Alert.alertLevels[alert.getLevel()];        
88 +        String message;
89 +        try {
90 +            message = _cp.getProperty(_name, "Alerter.IRC.message");
91 +        } catch (PropertyNotFoundException e) {
92 +            message = NOT_CONFIGURED;
93 +            _logger.write(toString(), Logger.WARNING, "Alerter.IRC.message value unavailable using default of " + message);
94 +        }
95 +        message = processAlertMessage(message, alert);        
96          // only send alerts if we're active
97 <        if(_active) {
98 <            ConfigurationProxy cp = ConfigurationProxy.getInstance();
99 <            
100 <            String levelName;
101 <            try {
102 <                levelName = cp.getProperty(_name, "Alerter.IRC.level");
103 <            } catch (PropertyNotFoundException e) {
104 <                levelName = DEFAULT_LEVEL;
105 <                _logger.write(toString(), Logger.WARNING, "Alerter.IRC.level value unavailable using default of " + levelName);
86 <            }
87 <            int level = StringUtils.getStringPos(levelName, Alert.alertLevels);
88 <            // only send if it's equal (or above) our level
89 <            if(((alert.getLevel() == 0) && (alert.getLastLevel() >= level)) || (alert.getLevel() >= level)) {
90 <                String alertType = Alert.alertLevels[alert.getLevel()];
91 <                String thresholdType = Alert.thresholdLevels[alert.getThreshold()];
92 <                String timeFirstSince = DateUtils.formatTime((System.currentTimeMillis() - alert.getInitialAlertTime())/1000, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
93 <                String timeFirstOccured = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(new Date(alert.getInitialAlertTime()));
94 <                // sort out the message              
95 <                String message;
96 <                try {
97 <                    message = cp.getProperty(_name, "Alerter.IRC.message");
98 <                } catch (PropertyNotFoundException e) {
99 <                    message = NOT_CONFIGURED;
100 <                    _logger.write(toString(), Logger.WARNING, "Alerter.IRC.message value unavailable using default of " + message);
101 <                }
102 <                
103 <                message = StringUtils.replaceText(message, "%level%", alertType);
104 <                message = StringUtils.replaceText(message, "%threshold%", thresholdType);
105 <                message = StringUtils.replaceText(message, "%source%", alert.getSource());
106 <                message = StringUtils.replaceText(message, "%value%", alert.getValue());
107 <                message = StringUtils.replaceText(message, "%thresholdValue%", alert.getThresholdValue());
108 <                message = StringUtils.replaceText(message, "%attributeName%", alert.getAttributeName());
109 <                message = StringUtils.replaceText(message, "%timeTillNextAlert%",  DateUtils.getTimeString(Long.parseLong(alert.getTimeTillNextAlert())));
110 <                message = StringUtils.replaceText(message, "%timeSinceFirstAlert%", timeFirstSince);
111 <                message = StringUtils.replaceText(message, "%timeOfFirstAlert%", timeFirstOccured);
112 <                
113 <                // send the message
114 <                _logger.write(toString(), Logger.DEBUG, "Sending " + _name + " at "+ alertType + " level");
115 <                _ircbot.sendMsg(message);
116 <                _lastAlert = message;
117 <                _lastAlertTime = System.currentTimeMillis();
118 <                _alertCount ++;
119 <            }
97 >        if(_active) {          
98 >            // send the message
99 >            _logger.write(toString(), Logger.DEBUG, "Sending " + _name + " at "+ alertType + " level");
100 >            _ircbot.sendMessage(message);
101 >            // count sent alerts
102 >            _alertCount++;
103 >        } else {
104 >            // don't send, but keep a count that we ignored it
105 >            _ignoredCount++;
106          }
107 <        else {
108 <            _ignoredCount ++;
109 <        }
107 >        // we'll always store the "last alert", regardless
108 >        // of whether we actually display it or not
109 >        _lastAlert = message;
110 >        _lastAlertTime = System.currentTimeMillis();
111      }
112  
113      /**
114       * Overrides the {@link java.lang.Object#toString() Object.toString()}
115       * method to provide clean logging (every class should have this).
116       *
117 <     * This uses the uk.org.iscream.util.NameFormat class
117 >     * This uses the uk.org.iscream.cms.util.NameFormat class
118       * to format the toString()
119       *
120       * @return the name of this class and its CVS revision
# Line 139 | Line 126 | public class IRC__Alerter extends Thread implements Pl
126              REVISION);
127      }
128  
129 <    /**
130 <     * return the String representation of what the filter does
129 >    /**
130 >     * Return the String representation of what the alerter does
131 >     *
132 >     * @return the description
133       */
134      public String getDescription(){
135          return DESC;
# Line 150 | Line 139 | public class IRC__Alerter extends Thread implements Pl
139  
140   //---ACCESSOR/MUTATOR METHODS---
141  
142 <    protected Queue getQueue() {
143 <        return AlerterManager.getInstance().getQueue();
142 >    /**
143 >     * Returns the "friendly" name of this class. This
144 >     * is simply an accessor for _name, required due to
145 >     * inheritance issues with extending AlerterSkeleton.
146 >     *
147 >     * @return the friendly name
148 >     */
149 >    protected String getFName() {
150 >        return _name;
151      }
156    
157    protected int getQueueId() {
158        if (_qID == -1) {
159            _qID = getQueue().getQueue();
160            _logger.write(toString(), Logger.DEBUG, "Assigned Queue - " + _qID);
161        }
162        return _qID;
163    }
152  
153   //---ATTRIBUTES---
154      
# Line 200 | Line 188 | public class IRC__Alerter extends Thread implements Pl
188      private long _startTime;
189      
190      /**
191 <     * The running status of the alerter
191 >     * This holds a reference to the
192 >     * system logger that is being used.
193       */
194 <    private boolean _running = true;
194 >    protected Logger _logger = ReferenceManager.getInstance().getLogger();
195      
196      /**
197       * This is the friendly identifier of the
# Line 213 | Line 202 | public class IRC__Alerter extends Thread implements Pl
202       * can be placed here.  This name could also
203       * be changed to null for utility classes.
204       */
205 <    private String _name = "IRC Alert";
205 >    private String _name = "IRC";
206  
218    /**
219     * This holds a reference to the
220     * system logger that is being used.
221     */
222    private Logger _logger = ReferenceManager.getInstance().getLogger();
223    
224    /**
225     * The queue id for this alerters queue in the alert queue
226     */
227    private int _qID = -1;
228
207   //---STATIC ATTRIBUTES---
208  
209   //---INNER CLASSES---
210  
211 <    /**
234 <     * This class provides some basic IRCBot functionality. It connects
235 <     * to a specified server, and will remain there until told to
236 <     * leave. Whilst connected it can send a message or a notice to
237 <     * the server.
238 <     */
239 <    class IRCBot extends Thread {
211 >    class IRCBot extends PircBot implements Runnable {
212          
213 <        public static final String DEFAULT_STARTUP_NOTICE = "i-scream ircbot starting...";
213 >        /**
214 >         * The default reconnect delay in seconds
215 >         */
216 >        public final int DEFAULT_RECONNECT_DELAY = 30;
217          
218          /**
219 <         * Main thread loop, this part of the class listens for
220 <         * messages from the server, and acts accordingly. At the
221 <         * present moment it only responds to pings.
219 >         * Attempt to kick-start the IRCBot into action. Will
220 >         * keep calling init() until it doesn't throw an
221 >         * an IOException (which will only be thrown if there
222 >         * is an error initialising). After a successful call
223 >         * to init() the method finishes.
224           */
225          public void run() {
226 <            // so we can stop if required
250 <            boolean run = true;
251 <            while(run) {
252 <                // flag so we can stop the loop
253 <                boolean doRead = true;
254 <                // get the startup notice
255 <                String startupNotice;
226 >            while(true) {
227                  try {
228 <                    startupNotice = ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.startupNotice");
229 <                } catch (PropertyNotFoundException e) {
259 <                    startupNotice = DEFAULT_STARTUP_NOTICE;
260 <                    _logger.write(this.toString(), Logger.WARNING, "Configuration error: "+e);
228 >                    init();
229 >                    break;
230                  }
231 <                // connect to the IRC server
232 <                try {
233 <                    connect();
234 <                    sendNotice(startupNotice);
266 <                } catch(IOException e) {
267 <                    doRead=false;
268 <                    _logger.write(this.toString(), Logger.ERROR, "Error connecting: "+e);
231 >                catch (IOException e) {
232 >                    _logger.write(this.toString(), Logger.ERROR, "Error initialising IRCBot: "+e);
233 >                    // wait for a while, defined in the config
234 >                    reconnectSleep();
235                  }
270                while(doRead) {
271                    try {
272                        // read a command
273                        String cmd = _socketIn.readLine();
274                        // if we have a null, we've lost contact
275                        if(cmd == null) {
276                            throw new IOException("End of stream reached");
277                        }
278                        // let another method deal with the input
279                        handleInput(cmd);
280                    } catch (IOException e) {
281                        // comms failure, maybe our link is dead.
282                        _logger.write(this.toString(), Logger.ERROR, "Communication error: "+e);
283                        // stop, and loop round for a reconnect.
284                        doRead = false;
285                    }
286                }
287                // make sure we're disconnected
288                try {
289                    disconnect();
290                } catch (IOException e) {
291                    _logger.write(this.toString(), Logger.ERROR, "Communication error: "+e);
292                }
293                
294                // comms have failed, so wait a while and reconnect
295                int delayTime = 0;
296                try {
297                    delayTime = Integer.parseInt(ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.reconnectDelay"));
298                } catch (NumberFormatException e) {
299                    delayTime = DEFAULT_RECONNECT_DELAY;
300                    _logger.write(this.toString(), Logger.WARNING, "Erronous Alerter.IRC.reconnectDelay value in configuration using default of " + delayTime + " seconds");
301                } catch (PropertyNotFoundException e) {
302                    delayTime = DEFAULT_RECONNECT_DELAY;
303                    _logger.write(this.toString(), Logger.WARNING, "Alerter.IRC.reconnectDelay value unavailable using default of " + delayTime + " seconds");
304                }
305                try {
306                    Thread.sleep(delayTime * 1000);
307                } catch (InterruptedException e) {}
236              }
309            // maybe disconnect here ? - shutdown method not implemented yet
310            //disconnect();
237          }
238          
239          /**
240 <         * Sends a message to the channel.
240 >         * Connects the bot to an irc server and joins a channel,
241 >         * using details supplied from the i-scream configuration
242 >         * system. If this method completes without an exception
243 >         * one can presume the bot is ready for use.
244           *
245 <         * @param msg The message to send
245 >         * @throws IOException if there is any problem initialising
246           */
247 <        public void sendMsg(String msg) {
248 <            _socketOut.println("PRIVMSG "+_channel+" :"+msg);
249 <            // wait a second before returning...
250 <            // this ensures messages can't be sent too fast
322 <            try {Thread.sleep(1000);} catch (InterruptedException e) {}
323 <        }
324 <        
325 <        /**
326 <         * Sends a message to the channel.
327 <         *
328 <         * @param user The user to send to
329 <         * @param msg The message to send
330 <         */
331 <        public void sendPrivMsg(String user, String msg) {
332 <            _socketOut.println("PRIVMSG "+user+" :"+msg);
333 <            // wait a second before returning...
334 <            // this ensures messages can't be sent too fast
335 <            try {Thread.sleep(1000);} catch (InterruptedException e) {}
336 <        }
337 <        
338 <        /**
339 <         * Sends an action to the channel.
340 <         *
341 <         * @param msg the action message
342 <         */
343 <        public void sendAction(String msg) {
344 <            char esc = 001;
345 <            sendMsg(esc+"ACTION "+msg+esc);
346 <            // wait a second before returning...
347 <            // this ensures messages can't be sent too fast
348 <            try {Thread.sleep(1000);} catch (InterruptedException e) {}
349 <        }
350 <        
351 <        /**
352 <         * Sends a notice to the channel.
353 <         *
354 <         * @param msg The notice to send
355 <         */
356 <        public void sendNotice(String msg) {
357 <            _socketOut.println("NOTICE "+_channel+" :"+msg);
358 <            // wait a second before returning...
359 <            // this ensures messages can't be sent too fast
360 <            try {Thread.sleep(1000);} catch (InterruptedException e) {}
361 <        }
362 <        
363 <        /**
364 <         * Connect to the IRC server, log in, and join the channel.
365 <         *
366 <         * @throws IOException if the connection fails
367 <         */
368 <        public void connect() throws IOException {
247 >        private void init() throws IOException {
248 >            _logger.write(this.toString(), Logger.DEBUG, "Initialising IRCBot...");
249 >            
250 >            // get a hook on the configuration system
251              ConfigurationProxy cp = ConfigurationProxy.getInstance();
252 <            // setup the socket, reader and writer
252 >            
253 >            // get hold of the server details
254              String server;
255              int port;
256              try {
# Line 380 | Line 263 | public class IRC__Alerter extends Thread implements Pl
263                  _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
264                  throw new IOException("Can't get irc server details due to malformed configuration");
265              }
266 <            _socket = new Socket(server, port);
267 <            _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
268 <            _socketOut = new PrintWriter(_socket.getOutputStream(), true);
386 <            //_socketOut.println("PASS");
387 <            // send USER details
388 <            String user, comment;
266 >            
267 >            // get hold of the user details and nickname list
268 >            String user, nickList;
269              try {
270                  user = cp.getProperty(_name, "Alerter.IRC.user");
271 <                comment = cp.getProperty(_name, "Alerter.IRC.comment");
271 >                nickList = cp.getProperty(_name, "Alerter.IRC.nickList");
272              } catch (PropertyNotFoundException e) {
273                  _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
274 <                throw new IOException("Can't get user details due to configuration error");
274 >                throw new IOException("Can't get user/nickname details due to configuration error");
275              }
276 <            _socketOut.println("USER "+user+" 8 * :"+comment);
277 <            // attempt to get a nick
278 <            String nickList;
276 >            
277 >            // get hold of comment and finger information
278 >            //  -- we're not too fussed if these aren't set
279 >            String comment = "Alerter.IRC.comment is undefined";
280 >            String finger = "Alerter.IRC.finger is undefined";
281              try {
282 <                nickList = cp.getProperty(_name, "Alerter.IRC.nickList");
282 >                comment = cp.getProperty(_name, "Alerter.IRC.comment");
283 >                finger = cp.getProperty(_name, "Alerter.IRC.finger");
284              } catch (PropertyNotFoundException e) {
285 <                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
403 <                throw new IOException("Can't get nickname due to configuration error");
285 >                _logger.write(this.toString(), Logger.WARNING, "Configuration warning, using default: "+e);
286              }
287 +            
288 +            // put these details into the bot
289 +            this.setLogin(user);
290 +            this.setVersion("[" + getRevision() + "] " + comment);
291 +            this.setFinger(finger);
292 +            
293 +            // attempt to connect, trying each nickname
294 +            // in turn until sucessfully connected
295              StringTokenizer st = new StringTokenizer(nickList, ";");
296              boolean ok = false;
407            // try until we exhaust our list
297              while(!ok && st.hasMoreTokens()) {
298                  String nick = st.nextToken();
299 <                _socketOut.println("NICK "+nick);
300 <                // get a "yes" or "no" response back
301 <                String response = "";
302 <                do {
303 <                    response = _socketIn.readLine();
304 <                    if(response==null) {
416 <                        throw new IOException("Communication error whilst logging in");
417 <                    }
418 <                } while(response.indexOf("001")==-1 && response.indexOf("433")==-1);
419 <                // see if it was a yes
420 <                if(response.indexOf("001")!=-1) {
421 <                    // great, we're logged in !
422 <                    ok = true;
423 <                    // store the name we're using
299 >                try {
300 >                    // try to connect with a nickname
301 >                    _logger.write(this.toString(), Logger.DEBUG, "Trying nick: "+nick);
302 >                    this.setName(nick);
303 >                    this.connect(server, port);
304 >                    // at this point we know the nickname was accepted
305                      _nickname = nick;
306 +                    ok = true;
307                  }
308 <                else {
309 <                    // log we couldn't get the name
310 <                    _logger.write(this.toString(), Logger.WARNING, "Nickname in use: "+nick);
308 >                catch(IOException e) {
309 >                    _logger.write(this.toString(), Logger.ERROR, "IO error when connecting to server: "+e);
310 >                    throw new IOException("IO error when connecting to server");
311                  }
312 +                catch(NickAlreadyInUseException e) {
313 +                    _logger.write(this.toString(), Logger.ERROR, "Nickname "+nick+" is already in use: "+e);
314 +                    // don't do anything, instead just loop round
315 +                    // and try the next nickname in the list
316 +                }
317 +                catch(IrcException e) {
318 +                    _logger.write(this.toString(), Logger.ERROR, "IRC error when connecting to server: "+e);
319 +                    throw new IOException("IRC error when connecting to server");
320 +                }
321              }
322              if(!ok) {
323 <                // oh dear, we couldn't get on.
324 <                throw new IOException("All nicknames in use");
323 >                // must have tried all the nicknames, best bail out
324 >                _logger.write(this.toString(), Logger.ERROR, "All nicknames already in use");
325 >                throw new IOException("All nicknames already in use");
326              }
327 <            // join the channel
327 >            
328 >            // get hold of the channel details, and attempt
329 >            // to join the specified channel
330              try {
331                  _channel = cp.getProperty(_name, "Alerter.IRC.channel");
332 +                this.joinChannel(_channel);
333              } catch (PropertyNotFoundException e) {
334                  _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
335                  throw new IOException("Can't get channel name due to configuration error");
336              }
337 <            _socketOut.println("JOIN "+_channel);
338 <            // allow alerts
337 >            
338 >            // get hold of the startup notice, and send
339 >            // it if it's defined
340 >            String startupNotice;
341 >            try {
342 >                startupNotice = ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.startupNotice");
343 >                sendNotice(_channel, startupNotice);
344 >            } catch (PropertyNotFoundException e) {
345 >                _logger.write(this.toString(), Logger.DEBUG, "Startup notice not defined, so not sending: "+e);
346 >            }
347 >            
348 >            // at this point initialisation is complete, so
349 >            // we can return and set this flag to allow alerts
350              _active = true;
351          }
352          
353          /**
354 <         * Disconnect "nicely" from the IRC server.
354 >         * Send a message to the current channel.
355           *
356 <         * @throws IOException if the disconnection fails
356 >         * @param message The message to send
357           */
358 <        public void disconnect() throws IOException {
359 <            // stop alerts
358 >        private void sendMessage(String message) {
359 >            sendMessage(_channel, message);
360 >        }
361 >        
362 >        /**
363 >         * If we get disconnected this method will be
364 >         * called, so we must take action. We'll simply
365 >         * keep trying to reinitialise, and thus
366 >         * reconnect to the server.
367 >         */
368 >        public void onDisconnect() {
369 >            // stop alerts being sent for now
370              _active = false;
371 <            // send proper QUIT
372 <            _socketOut.println("QUIT : iscreamBot component shutting down...");
373 <            // close the socket
374 <            _socketOut.close();
375 <            _socketIn.close();
376 <            _socket.close();
371 >            while(true) {
372 >                // wait for a while, defined in the config
373 >                reconnectSleep();
374 >                try {
375 >                    init();
376 >                    break;
377 >                }
378 >                catch (IOException e) {
379 >                    _logger.write(this.toString(), Logger.ERROR, "Error initialising IRCBot: "+e);
380 >                }
381 >            }
382          }
383          
384          /**
385 <         * Overrides the {@link java.lang.Object#toString() Object.toString()}
386 <         * method to provide clean logging (every class should have this).
385 >         * If we receive a message this method will
386 >         * be called. We'll check if the message is
387 >         * for us, and call handleInput if it is.
388           *
389 <         * This uses the uk.org.iscream.util.NameFormat class
390 <         * to format the toString()
389 >         * @param channel The channel the message came from
390 >         * @param sender The sender of the message
391 >         * @param login The login of the sender
392 >         * @param hostname The hostname of the sender
393 >         * @param message The message sent
394 >         */
395 >        public void onMessage(String channel, String sender, String login, String hostname, String message) {
396 >            String trimmedMessage = isForMe(message);
397 >            // if trimmedMessage is null, it's not for us
398 >            if(trimmedMessage != null) {
399 >                handleInput(trimmedMessage, channel);
400 >            }
401 >        }
402 >        
403 >        /**
404 >         * If we receive a private message this method
405 >         * will be called. No need to check if it's for
406 >         * us -- it has to be if it's a private message.
407           *
408 <         * @return the name of this class and its CVS revision
408 >         * @param sender The sender of the message
409 >         * @param login The login of the sender
410 >         * @param hostname The hostname of the sender
411 >         * @param message The message sent
412           */
413 <        public String toString() {
414 <            return FormatName.getName(
474 <                _name,
475 <                getClass().getName(),
476 <                REVISION);
413 >        public void onPrivateMessage(String sender, String login, String hostname, String message) {
414 >            handleInput(message, sender);
415          }
416          
417          /**
418 <         * Deals with incoming lines from the server.
418 >         * If we receive a nick change message, this
419 >         * method gets called. We don't care about
420 >         * other users changing their nick, so we only
421 >         * take notice if our nick changes. If it does
422 >         * we need to change our internal nickname
423 >         * state.
424           *
425 <         * @param line the line to deal with
425 >         * @param oldNick the old nickname
426 >         * @param login the login of the nick changer
427 >         * @param hostname the hostname of the nick changer
428 >         * @param newNick the new nickname
429           */
430 <        private void handleInput(String line) {
431 <            ConfigurationProxy cp = ConfigurationProxy.getInstance();
432 <            // if it's a PING...
433 <            if(line.startsWith("PING")) {
434 <                // ...send a PONG
489 <                _socketOut.println("PONG" + line.substring(4));
430 >        public void onNickChange(String oldNick, String login, String hostname, String newNick) {
431 >            if(oldNick.equals(_nickname)) {
432 >                _nickname = newNick;
433 >                // tell the underlying pircbot that our nick has changed too :)
434 >                setName(newNick);
435              }
436 <            // see if it's for us
437 <            else if(getMsg(line).startsWith(_nickname+",") || getMsg(line).startsWith(_nickname+":") || getMsg(line).startsWith(_nickname+" ")) {
438 <                // setup some String's
439 <                String stopCommand, startCommand, timeSinceLastAlertCommand, lastAlertCommand, joinCommand;
440 <                String nickChangeCommand, versionCommand, helpCommand, statCommand, uptimeCommand;
441 <                // get the command set
436 >        }
437 >        
438 >        /**
439 >         * If we receive a kick message this method
440 >         * gets called. We only take notice if it's
441 >         * us getting kicked, and then we'll rejoin
442 >         * the channel after a short wait.
443 >         *
444 >         * @param channel the channel the kick happened on
445 >         * @param kickerNick the person who performed the kick
446 >         * @param kickerLogin the login of the person who performed the kick
447 >         * @param kickerHostname the hostname of the person who performed the kick
448 >         * @param recipientNick the nickname of the person being kicked
449 >         * @param reason the reason for the kick
450 >         */
451 >        public void onKick(String channel, String kickerNick, String kickerLogin, String kickerHostname, String recipientNick, String reason) {
452 >            if(recipientNick.equals(_nickname) && channel.equals(_channel)) {
453 >                // remind the person it's not very nice :)
454 >                sendMessage(kickerNick, "That wasn't a nice thing to do...");
455 >                // get hold of the channel details, in case they've changed
456                  try {
457 <                    stopCommand = cp.getProperty(_name, "Alerter.IRC.stopCommand");
458 <                    startCommand = cp.getProperty(_name, "Alerter.IRC.startCommand");
459 <                    timeSinceLastAlertCommand = cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand");
501 <                    lastAlertCommand = cp.getProperty(_name, "Alerter.IRC.lastAlertCommand");
502 <                    joinCommand = cp.getProperty(_name, "Alerter.IRC.joinCommand");
503 <                    nickChangeCommand = cp.getProperty(_name, "Alerter.IRC.nickChangeCommand");
504 <                    versionCommand = cp.getProperty(_name, "Alerter.IRC.versionCommand");
505 <                    helpCommand = cp.getProperty(_name, "Alerter.IRC.helpCommand");
506 <                    statCommand = cp.getProperty(_name, "Alerter.IRC.statCommand");
507 <                    uptimeCommand = cp.getProperty(_name, "Alerter.IRC.uptimeCommand");
508 <                } catch (PropertyNotFoundException e) {
509 <                    _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
510 <                    // lets bail from handling this line...
511 <                    // ...it's gonna be hard without a command set!
512 <                    return;
457 >                    _channel = ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.channel");
458 >                } catch(PropertyNotFoundException e) {
459 >                    _logger.write(this.toString(), Logger.ERROR, "Can't get channel name due to configuration error: "+e);
460                  }
461 <                
462 <                // we have a message for us
463 <                String message = getMsg(line).substring(_nickname.length());
464 <                if(message.indexOf(stopCommand)!=-1) {
465 <                    _active = false;
466 <                    sendMsg(getMsgSender(line)+", alerts have been stopped");
461 >                // wait for a while, defined in the config
462 >                reconnectSleep();
463 >                // we'll try and rejoin the channel regardless
464 >                // otherwise we might end up doing nothing!
465 >                joinChannel(_channel);
466 >            }
467 >        }
468 >        
469 >        /**
470 >         * This method handles input directed to us, and
471 >         * responds accordingly if required.
472 >         *
473 >         * @param message the message from someone
474 >         * @param source where the message came from, so we know where to send the response
475 >         */
476 >        private void handleInput(String message, String source) {
477 >            // get hold of the configuration system
478 >            ConfigurationProxy cp = ConfigurationProxy.getInstance();
479 >            // setup some String's
480 >            String stopCommand, startCommand, timeSinceLastAlertCommand, lastAlertCommand, joinCommand;
481 >            String nickChangeCommand, versionCommand, helpCommand, statCommand, uptimeCommand;
482 >            // get the command set from the configuration
483 >            try {
484 >                stopCommand = cp.getProperty(_name, "Alerter.IRC.stopCommand");
485 >                startCommand = cp.getProperty(_name, "Alerter.IRC.startCommand");
486 >                timeSinceLastAlertCommand = cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand");
487 >                lastAlertCommand = cp.getProperty(_name, "Alerter.IRC.lastAlertCommand");
488 >                joinCommand = cp.getProperty(_name, "Alerter.IRC.joinCommand");
489 >                nickChangeCommand = cp.getProperty(_name, "Alerter.IRC.nickChangeCommand");
490 >                versionCommand = cp.getProperty(_name, "Alerter.IRC.versionCommand");
491 >                helpCommand = cp.getProperty(_name, "Alerter.IRC.helpCommand");
492 >                statCommand = cp.getProperty(_name, "Alerter.IRC.statCommand");
493 >                uptimeCommand = cp.getProperty(_name, "Alerter.IRC.uptimeCommand");
494 >            } catch (PropertyNotFoundException e) {
495 >                _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
496 >                // lets bail from handling this line...
497 >                // ...it's gonna be hard without a command set!
498 >                return;
499 >            }
500 >            
501 >            if(message.equalsIgnoreCase(stopCommand)) {
502 >                _active = false;
503 >                sendMessage(source, "alerts have been stopped");
504 >            }
505 >            else if(message.equalsIgnoreCase(startCommand)) {
506 >                _active = true;
507 >                sendMessage(source, "alerts have been activated");
508 >            }
509 >            // this needs to go before lastAlertCommand if it contains
510 >            // the same words as the lastAlertCommand.
511 >            else if(message.equalsIgnoreCase(timeSinceLastAlertCommand)) {
512 >                if(_lastAlertTime != -1) {
513 >                    long uptime = (System.currentTimeMillis() - _lastAlertTime) / 1000;
514 >                    String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
515 >                    sendMessage(source, "I last sent an alert "+uptimeText+ " ago");
516                  }
517 <                else if(message.indexOf(startCommand)!=-1) {
518 <                    _active = true;
523 <                    sendMsg(getMsgSender(line)+", alerts have been activated");
517 >                else {
518 >                    sendMessage(source, "I've never sent an alert!");
519                  }
520 <                // this needs to go here if it contains the same words as the lastAlertCommand
521 <                else if(message.indexOf(timeSinceLastAlertCommand)!=-1) {
522 <                    if(_lastAlertTime != -1) {
523 <                        long uptime = (System.currentTimeMillis() - _lastAlertTime) / 1000;
524 <                        String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
530 <                        sendMsg(getMsgSender(line)+", I last sent an alert "+uptimeText+ " ago");
531 <                    }
532 <                    else {
533 <                        sendMsg(getMsgSender(line)+", I've never sent an alert!");
534 <                    }
520 >            }
521 >            else if(message.equalsIgnoreCase(lastAlertCommand)) {
522 >                if(_lastAlertTime != -1) {
523 >                    String date = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(new Date(_lastAlertTime));
524 >                    sendMessage(source, "last alert was at "+date+"; "+_lastAlert);
525                  }
526 <                else if(message.indexOf(lastAlertCommand)!=-1) {
527 <                    if(_lastAlertTime != -1) {
538 <                        String date = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(new Date(_lastAlertTime));
539 <                        sendMsg(getMsgSender(line)+", last alert was at "+date+"; "+_lastAlert);
540 <                    }
541 <                    else {
542 <                        sendMsg(getMsgSender(line)+", I've never sent an alert!");
543 <                    }
544 <                    
526 >                else {
527 >                    sendMessage(source, "I've never sent an alert!");
528                  }
529 <                else if(message.indexOf(joinCommand)!=-1) {
530 <                    String joinCmd = joinCommand;
531 <                    String newChan = message.substring(message.indexOf(joinCmd) + joinCmd.length() + 1);
532 <                    int endOfChan = newChan.indexOf(" ");
533 <                    if(endOfChan == -1) {
534 <                        endOfChan = newChan.length();
535 <                    }
536 <                    newChan = newChan.substring(0, endOfChan);
554 <                    if(newChan.equals(_channel)) {
555 <                        sendMsg(getMsgSender(line)+", I'm already on "+newChan+"!");
556 <                    } else {
557 <                        sendMsg(getMsgSender(line)+", okay, I'm off to "+newChan);
558 <                        _socketOut.println("PART "+_channel);
559 <                        _socketOut.println("JOIN "+newChan);
560 <                        _channel = newChan;
561 <                    }
529 >                
530 >            }
531 >            else if(message.toLowerCase().startsWith(joinCommand.toLowerCase())) {
532 >                String joinCmd = joinCommand;
533 >                String newChan = message.substring(message.indexOf(joinCmd) + joinCmd.length() + 1);
534 >                int endOfChan = newChan.indexOf(" ");
535 >                if(endOfChan == -1) {
536 >                    endOfChan = newChan.length();
537                  }
538 <                else if(message.indexOf(nickChangeCommand)!=-1) {
539 <                    String nickChangeCmd = nickChangeCommand;
540 <                    String newNick = message.substring(message.indexOf(nickChangeCmd) + nickChangeCmd.length() + 1);
541 <                    int endOfNick = newNick.indexOf(" ");
542 <                    if(endOfNick == -1) {
543 <                        endOfNick = newNick.length();
544 <                    }
570 <                    newNick = newNick.substring(0, endOfNick);
571 <                    sendMsg(getMsgSender(line)+", okay, changing my nickname to "+newNick);
572 <                    _socketOut.println("NICK "+newNick);
573 <                    _nickname = newNick;
538 >                newChan = newChan.substring(0, endOfChan);
539 >                if(newChan.equals(_channel)) {
540 >                    sendMessage(source, "I'm already on "+newChan+"!");
541 >                } else {
542 >                    partChannel(_channel);
543 >                    joinChannel(newChan);
544 >                    _channel = newChan;
545                  }
546 <                else if(message.indexOf(versionCommand)!=-1) {
547 <                    sendMsg(getMsgSender(line)+", I am version "+REVISION.substring(11, REVISION.length() -2)+" of the i-scream alerting bot");
546 >            }
547 >            else if(message.toLowerCase().startsWith(nickChangeCommand.toLowerCase())) {
548 >                String nickChangeCmd = nickChangeCommand;
549 >                String newNick = message.substring(message.indexOf(nickChangeCmd) + nickChangeCmd.length() + 1);
550 >                int endOfNick = newNick.indexOf(" ");
551 >                if(endOfNick == -1) {
552 >                    endOfNick = newNick.length();
553                  }
554 <                else if(message.indexOf(helpCommand)!=-1) {
555 <                    sendPrivMsg(getMsgSender(line), "Hello, I am the i-scream alerting bot version "+REVISION.substring(11, REVISION.length() -2));
580 <                    sendPrivMsg(getMsgSender(line), "I understand the following commands;");
581 <                    sendPrivMsg(getMsgSender(line), stopCommand);
582 <                    sendPrivMsg(getMsgSender(line), startCommand);
583 <                    sendPrivMsg(getMsgSender(line), lastAlertCommand);
584 <                    sendPrivMsg(getMsgSender(line), joinCommand);
585 <                    sendPrivMsg(getMsgSender(line), nickChangeCommand);
586 <                    sendPrivMsg(getMsgSender(line), statCommand);
587 <                    sendPrivMsg(getMsgSender(line), uptimeCommand);
588 <                    sendPrivMsg(getMsgSender(line), timeSinceLastAlertCommand);
589 <                    sendPrivMsg(getMsgSender(line), helpCommand);
590 <                }
591 <                else if(message.indexOf(statCommand)!=-1) {
592 <                    sendMsg(getMsgSender(line)+", I have sent a total of "+_alertCount+" alerts, and ignored a total of "+_ignoredCount+"!");
593 <                }
594 <                else if(message.indexOf(uptimeCommand)!=-1) {
595 <                    long uptime = (System.currentTimeMillis() - _startTime) / 1000;
596 <                    String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
597 <                    sendMsg(getMsgSender(line)+", I have been running for "+uptimeText);
598 <                }
599 <                else if(message.indexOf("ping")!=-1) {
600 <                    sendMsg("pong");
601 <                }
602 <                else if(message.indexOf("do a jibble dance")!=-1) {
603 <                    // little joke :)
604 <                    sendAction("jives to the funky beat shouting \"ii--screeeaaammm\"");
605 <                }
606 <                else {
607 <                    String rejectMessage = NOT_CONFIGURED;
608 <                    try {
609 <                        rejectMessage = cp.getProperty(_name, "Alerter.IRC.rejectMessage");
610 <                    } catch(PropertyNotFoundException e) {
611 <                        _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
612 <                    }
613 <                    sendMsg(getMsgSender(line)+", "+rejectMessage);
614 <                }
554 >                newNick = newNick.substring(0, endOfNick);
555 >                changeNick(newNick);
556              }
557 <            else if(line.indexOf(_nickname)!=-1 && line.indexOf(_channel)!=-1 && line.indexOf("KICK")!=-1) {
558 <                sendPrivMsg(getMsgSender(line), "That wasn't a nice thing to do...");
557 >            else if(message.equalsIgnoreCase(versionCommand)) {
558 >                sendMessage(source, "I am version " + getRevision() + " of the i-scream alerting bot");
559 >            }
560 >            else if(message.equalsIgnoreCase(helpCommand)) {
561 >                sendMessage(source, "Hello, I am the i-scream alerting bot version "+REVISION.substring(11, REVISION.length() -2));
562 >                sendMessage(source, "I understand the following commands;");
563 >                sendMessage(source, stopCommand);
564 >                sendMessage(source, startCommand);
565 >                sendMessage(source, lastAlertCommand);
566 >                sendMessage(source, joinCommand);
567 >                sendMessage(source, nickChangeCommand);
568 >                sendMessage(source, statCommand);
569 >                sendMessage(source, uptimeCommand);
570 >                sendMessage(source, timeSinceLastAlertCommand);
571 >                sendMessage(source, helpCommand);
572 >            }
573 >            else if(message.equalsIgnoreCase(statCommand)) {
574 >                sendMessage(source, "I have sent a total of "+_alertCount+" alerts, and ignored a total of "+_ignoredCount+"!");
575 >            }
576 >            else if(message.equalsIgnoreCase(uptimeCommand)) {
577 >                long uptime = (System.currentTimeMillis() - _startTime) / 1000;
578 >                String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
579 >                sendMessage(source, "I have been running for "+uptimeText);
580 >            }
581 >            else if(message.equalsIgnoreCase("ping")) {
582 >                sendMessage(source, "pong");
583 >            }
584 >            else if(message.equalsIgnoreCase("do a jibble dance")) {
585 >                // little joke :)
586 >                sendAction(source, "jives to the funky beat shouting \"ii--screeeaaammm\"");
587 >            }
588 >            else {
589 >                String rejectMessage = NOT_CONFIGURED;
590                  try {
591 <                    _channel = cp.getProperty(_name, "Alerter.IRC.channel");
591 >                    rejectMessage = cp.getProperty(_name, "Alerter.IRC.rejectMessage");
592                  } catch(PropertyNotFoundException e) {
593                      _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
594                  }
595 <                _socketOut.println("JOIN "+_channel);
595 >                sendMessage(source, rejectMessage);
596              }
597          }
598          
599          /**
600 <         * Strips the header from a message line
600 >         * Quick method to check if the message appears
601 >         * to be directed at us. We simply check to see
602 >         * if it starts with our nick in some fashion.
603 >         * This will return null if the message is not
604 >         * for us - thus allowing this method to perform
605 >         * two tasks at once.
606           *
607 <         * @param line the line to strip
608 <         * @return the message from the line
607 >         * @param message the message to check
608 >         * @return the message (with our nick removed), or null if it's not for us.
609           */
610 <        private String getMsg(String line) {
611 <            String result = "";
612 <            if(line.indexOf("PRIVMSG")!=-1) {
613 <                int firstColon = line.indexOf(":");
614 <                if(firstColon != -1) {
615 <                    int secondColon = line.indexOf(":", firstColon+1);
616 <                    if(secondColon != -1) {
617 <                        result = line.substring(secondColon+1);
618 <                    }
619 <                }
610 >        private String isForMe(String message) {
611 >            // change to lower case to remove
612 >            // case ambiguities
613 >            String nick = _nickname.toLowerCase();
614 >            String msg = message.toLowerCase();
615 >            if(msg.startsWith(nick + ", ") ||
616 >               msg.startsWith(nick + ": ") ||
617 >               msg.startsWith(nick + " ")) {
618 >                // NOTE that the 1 here is hardcoded to be the length
619 >                // of the above 'extra' character (eg , or :)
620 >                return message.substring(nick.length()+1).trim();
621              }
622 <            return result;
622 >            else {
623 >                return null;
624 >            }
625          }
626          
627          /**
628 <         * Finds out the sender of the message
629 <         *
630 <         * @param line the line to look for a sender in
651 <         * @return the sender
628 >         * Sleep for a configurable amount of time and
629 >         * then return. This is used when reconnecting
630 >         * to the server or a channel.
631           */
632 <        private String getMsgSender(String line) {
633 <            String result = "";
634 <            int colon = line.indexOf(":");
635 <            int excl = line.indexOf("!");
636 <            if(colon!=-1 && excl!=-1) {
637 <                result = line.substring(colon+1, excl);
632 >        private void reconnectSleep() {
633 >            int delayTime = 0;
634 >            try {
635 >                delayTime = Integer.parseInt(ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.reconnectDelay"));
636 >            } catch (NumberFormatException e) {
637 >                delayTime = DEFAULT_RECONNECT_DELAY;
638 >                _logger.write(this.toString(), Logger.WARNING, "Erronous Alerter.IRC.reconnectDelay value in configuration using default of " + delayTime + " seconds");
639 >            } catch (PropertyNotFoundException e) {
640 >                delayTime = DEFAULT_RECONNECT_DELAY;
641 >                _logger.write(this.toString(), Logger.WARNING, "Alerter.IRC.reconnectDelay value unavailable using default of " + delayTime + " seconds");
642              }
643 <            return result;
643 >            _logger.write(this.toString(), Logger.ERROR, "Waiting "+delayTime+" seconds for reconnect...");
644 >            try {
645 >                Thread.sleep(delayTime * 1000);
646 >            } catch (InterruptedException e) {}
647          }
648          
649          /**
650 <         * The socket connected to the server
650 >         * Returns the revision of the bot.
651 >         *
652 >         * @return the revision of this bot
653           */
654 <        private Socket _socket;
654 >        private String getRevision() {
655 >            return REVISION.substring(11, REVISION.length()-2);
656 >        }
657          
658          /**
659 <         * The writer
659 >         * Overrides the {@link java.lang.Object#toString() Object.toString()}
660 >         * method to provide clean logging (every class should have this).
661 >         *
662 >         * This uses the uk.org.iscream.cms.util.NameFormat class
663 >         * to format the toString()
664 >         *
665 >         * @return the name of this class and its CVS revision
666           */
667 <        private PrintWriter _socketOut;
668 <        
669 <        /**
670 <         * The reader
671 <         */
672 <        private BufferedReader _socketIn;
667 >        public String toString() {
668 >            return FormatName.getName(
669 >                _name,
670 >                getClass().getName(),
671 >                REVISION);
672 >        }
673          
674          /**
675           * Just a reminder to what channel we're on...

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines