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
Revision: 1.16
Committed: Mon Mar 5 23:12:43 2001 UTC (23 years, 2 months ago) by tdb
Branch: MAIN
Changes since 1.15: +124 -39 lines
Log Message:
A lot of error catching with the configuration has been implemented.

File Contents

# Content
1 //---PACKAGE DECLARATION---
2 package uk.ac.ukc.iscream.client.alerters;
3
4 //---IMPORTS---
5 import uk.ac.ukc.iscream.client.*;
6 import uk.ac.ukc.iscream.core.*;
7 import uk.ac.ukc.iscream.util.*;
8 import uk.ac.ukc.iscream.componentmanager.*;
9
10 import java.io.*;
11 import java.net.*;
12 import java.util.*;
13 import java.text.*;
14
15 /**
16 * This Alert sends an IRC message.
17 *
18 * Clean shutdown could be achieved by stopping the run() method in the
19 * IRCBot inner class.
20 *
21 * @author $Author: ajm4 $
22 * @version $Id: IRC__Alerter.java,v 1.15 2001/03/05 12:45:51 ajm4 Exp $
23 */
24 public class IRC__Alerter implements PluginAlerter {
25
26 //---FINAL ATTRIBUTES---
27
28 /**
29 * The current CVS revision of this class
30 */
31 public final String REVISION = "$Revision: 1.15 $";
32
33 /**
34 * A description of this alerter
35 */
36 public final String DESC = "Sends alerts on an IRC channel";
37
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
47 //---STATIC METHODS---
48
49 //---CONSTRUCTORS---
50
51 public IRC__Alerter() {
52
53 // connect to the IRC server
54 _ircbot = new IRCBot();
55 _ircbot.start();
56 _startTime = System.currentTimeMillis();
57
58 _logger.write(toString(), Logger.SYSINIT, "IRC Alerter started");
59 }
60
61 //---PUBLIC METHODS---
62
63 public void sendAlert(Alert alert) {
64 // only send alerts if we're active
65 if(_active) {
66 ConfigurationProxy cp = ConfigurationProxy.getInstance();
67
68 String levelName;
69 try {
70 levelName = cp.getProperty(_name, "Alerter.IRC.level");
71 } catch (PropertyNotFoundException e) {
72 levelName = DEFAULT_LEVEL;
73 _logger.write(toString(), Logger.WARNING, "Alerter.IRC.level value unavailable using default of " + levelName);
74 }
75 int level = StringUtils.getStringPos(levelName, Alert.alertLevels);
76 // only send if it's equal (or above) our level
77 if(((alert.getLevel() == 0) && (alert.getLastLevel() >= level)) || (alert.getLevel() >= level)) {
78 String alertType = Alert.alertLevels[alert.getLevel()];
79 String thresholdType = Alert.thresholdLevels[alert.getThreshold()];
80 // sort out the message
81 String message;
82 try {
83 message = cp.getProperty(_name, "Alerter.IRC.message");
84 } catch (PropertyNotFoundException e) {
85 message = NOT_CONFIGURED;
86 _logger.write(toString(), Logger.WARNING, "Alerter.IRC.message value unavailable using default of " + message);
87 }
88
89 message = StringUtils.replaceText(message, "%level%", alertType);
90 message = StringUtils.replaceText(message, "%threshold%", thresholdType);
91 message = StringUtils.replaceText(message, "%source%", alert.getSource());
92 message = StringUtils.replaceText(message, "%value%", alert.getValue());
93 message = StringUtils.replaceText(message, "%thresholdValue%", alert.getThresholdValue());
94 message = StringUtils.replaceText(message, "%attributeName%", alert.getAttributeName());
95 message = StringUtils.replaceText(message, "%timeTillNextAlert%", getTimeString(Long.parseLong(alert.getTimeTillNextAlert())));
96
97 // send the message
98 _logger.write(toString(), Logger.DEBUG, "Sending " + _name + " at "+ alertType + " level");
99 _ircbot.sendMsg(message);
100 _lastAlert = message;
101 _lastAlertTime = System.currentTimeMillis();
102 _alertCount ++;
103 }
104 }
105 }
106
107 /**
108 * Overrides the {@link java.lang.Object#toString() Object.toString()}
109 * method to provide clean logging (every class should have this).
110 *
111 * This uses the uk.ac.ukc.iscream.util.NameFormat class
112 * to format the toString()
113 *
114 * @return the name of this class and its CVS revision
115 */
116 public String toString() {
117 return FormatName.getName(
118 _name,
119 getClass().getName(),
120 REVISION);
121 }
122
123 /**
124 * return the String representation of what the filter does
125 */
126 public String getDescription(){
127 return DESC;
128 }
129
130 //---PRIVATE METHODS---
131
132 private String getTimeString(long time) {
133 String timeString = null;
134 if (time >= 60) {
135 timeString = (time / 60) + " minute(s)";
136 } else if (time >= 3600) {
137 timeString = ((time/60) / 60) + " hour(s)";
138 } else {
139 timeString = time + " second(s)";
140 }
141 return timeString;
142 }
143
144 //---ACCESSOR/MUTATOR METHODS---
145
146 //---ATTRIBUTES---
147
148 /**
149 * A reference to the IRCBot
150 */
151 private IRCBot _ircbot;
152
153 /**
154 * Are we "active"
155 */
156 private boolean _active = false;
157
158 /**
159 * The last alert that was sent
160 */
161 private String _lastAlert = "no alerts have been sent";
162
163 /**
164 * The time of the last alert
165 */
166 private long _lastAlertTime = -1;
167
168 /**
169 * Number of alerts sent
170 */
171 private long _alertCount = 0;
172
173 /**
174 * Time of IRCBot startup
175 */
176 private long _startTime;
177
178 /**
179 * This is the friendly identifier of the
180 * component this class is running in.
181 * eg, a Filter may be called "filter1",
182 * If this class does not have an owning
183 * component, a name from the configuration
184 * can be placed here. This name could also
185 * be changed to null for utility classes.
186 */
187 private String _name = "IRC Alert";
188
189 /**
190 * This holds a reference to the
191 * system logger that is being used.
192 */
193 private Logger _logger = ReferenceManager.getInstance().getLogger();
194
195 //---STATIC ATTRIBUTES---
196
197 //---INNER CLASSES---
198
199 /**
200 * This class provides some basic IRCBot functionality. It connects
201 * to a specified server, and will remain there until told to
202 * leave. Whilst connected it can send a message or a notice to
203 * the server.
204 */
205 class IRCBot extends Thread {
206
207 public static final String DEFAULT_STARTUP_NOTICE = "i-scream ircbot starting...";
208
209 /**
210 * Main thread loop, this part of the class listens for
211 * messages from the server, and acts accordingly. At the
212 * present moment it only responds to pings.
213 */
214 public void run() {
215 // so we can stop if required
216 boolean run = true;
217 while(run) {
218 // flag so we can stop the loop
219 boolean doRead = true;
220 // get the startup notice
221 String startupNotice;
222 try {
223 startupNotice = ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.startupNotice");
224 } catch (PropertyNotFoundException e) {
225 startupNotice = DEFAULT_STARTUP_NOTICE;
226 _logger.write(this.toString(), Logger.WARNING, "Configuration error: "+e);
227 }
228 // connect to the IRC server
229 try {
230 connect();
231 sendNotice(startupNotice);
232 } catch(IOException e) {
233 doRead=false;
234 _logger.write(this.toString(), Logger.ERROR, "Error connecting: "+e);
235 }
236 while(doRead) {
237 try {
238 // read a command
239 String cmd = _socketIn.readLine();
240 // if we have a null, we've lost contact
241 if(cmd == null) {
242 throw new IOException("End of stream reached");
243 }
244 // let another method deal with the input
245 handleInput(cmd);
246 } catch (IOException e) {
247 // comms failure, maybe our link is dead.
248 _logger.write(this.toString(), Logger.ERROR, "Communication error: "+e);
249 // stop, and loop round for a reconnect.
250 doRead = false;
251 }
252 }
253 // make sure we're disconnected
254 try {
255 disconnect();
256 } catch (IOException e) {
257 _logger.write(this.toString(), Logger.ERROR, "Communication error: "+e);
258 }
259
260 // comms have failed, so wait a while and reconnect
261 int delayTime = 0;
262 try {
263 delayTime = Integer.parseInt(ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.reconnectDelay"));
264 } catch (NumberFormatException e) {
265 delayTime = DEFAULT_RECONNECT_DELAY;
266 _logger.write(this.toString(), Logger.WARNING, "Erronous Alerter.IRC.reconnectDelay value in configuration using default of " + delayTime + " seconds");
267 } catch (PropertyNotFoundException e) {
268 delayTime = DEFAULT_RECONNECT_DELAY;
269 _logger.write(this.toString(), Logger.WARNING, "Alerter.IRC.reconnectDelay value unavailable using default of " + delayTime + " seconds");
270 }
271 try {
272 Thread.sleep(delayTime * 1000);
273 } catch (InterruptedException e) {}
274 }
275 // maybe disconnect here ? - shutdown method not implemented yet
276 //disconnect();
277 }
278
279 /**
280 * Sends a message to the channel.
281 *
282 * @param msg The message to send
283 */
284 public void sendMsg(String msg) {
285 _socketOut.println("PRIVMSG "+_channel+" :"+msg);
286 // wait a second before returning...
287 // this ensures messages can't be sent too fast
288 try {Thread.sleep(1000);} catch (InterruptedException e) {}
289 }
290
291 /**
292 * Sends a message to the channel.
293 *
294 * @param user The user to send to
295 * @param msg The message to send
296 */
297 public void sendPrivMsg(String user, String msg) {
298 _socketOut.println("PRIVMSG "+user+" :"+msg);
299 // wait a second before returning...
300 // this ensures messages can't be sent too fast
301 try {Thread.sleep(1000);} catch (InterruptedException e) {}
302 }
303
304 /**
305 * Sends an action to the channel.
306 *
307 * @param msg the action message
308 */
309 public void sendAction(String msg) {
310 char esc = 001;
311 sendMsg(esc+"ACTION "+msg+esc);
312 // wait a second before returning...
313 // this ensures messages can't be sent too fast
314 try {Thread.sleep(1000);} catch (InterruptedException e) {}
315 }
316
317 /**
318 * Sends a notice to the channel.
319 *
320 * @param msg The notice to send
321 */
322 public void sendNotice(String msg) {
323 _socketOut.println("NOTICE "+_channel+" :"+msg);
324 // wait a second before returning...
325 // this ensures messages can't be sent too fast
326 try {Thread.sleep(1000);} catch (InterruptedException e) {}
327 }
328
329 /**
330 * Connect to the IRC server, log in, and join the channel.
331 *
332 * @throws IOException if the connection fails
333 */
334 public void connect() throws IOException {
335 ConfigurationProxy cp = ConfigurationProxy.getInstance();
336 // setup the socket, reader and writer
337 String server;
338 int port;
339 try {
340 server = cp.getProperty(_name, "Alerter.IRC.IRCServer");
341 port = Integer.parseInt(cp.getProperty(_name, "Alerter.IRC.IRCPort"));
342 } catch (PropertyNotFoundException e) {
343 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
344 throw new IOException("Can't get irc server details due to configuration error");
345 } catch (NumberFormatException e) {
346 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
347 throw new IOException("Can't get irc server details due to malformed configuration");
348 }
349 _socket = new Socket(server, port);
350 _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
351 _socketOut = new PrintWriter(_socket.getOutputStream(), true);
352 //_socketOut.println("PASS");
353 // send USER details
354 String user, comment;
355 try {
356 user = cp.getProperty(_name, "Alerter.IRC.user");
357 comment = cp.getProperty(_name, "Alerter.IRC.comment");
358 } catch (PropertyNotFoundException e) {
359 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
360 throw new IOException("Can't get user details due to configuration error");
361 }
362 _socketOut.println("USER "+user+" 8 * :"+comment);
363 // attempt to get a nick
364 String nickList;
365 try {
366 nickList = cp.getProperty(_name, "Alerter.IRC.nickList");
367 } catch (PropertyNotFoundException e) {
368 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
369 throw new IOException("Can't get nickname due to configuration error");
370 }
371 StringTokenizer st = new StringTokenizer(nickList, ";");
372 boolean ok = false;
373 // try until we exhaust our list
374 while(!ok && st.hasMoreTokens()) {
375 String nick = st.nextToken();
376 _socketOut.println("NICK "+nick);
377 // get a "yes" or "no" response back
378 String response = "";
379 do {
380 response = _socketIn.readLine();
381 if(response==null) {
382 throw new IOException("Communication error whilst logging in");
383 }
384 } while(response.indexOf("001")==-1 && response.indexOf("433")==-1);
385 // see if it was a yes
386 if(response.indexOf("001")!=-1) {
387 // great, we're logged in !
388 ok = true;
389 // store the name we're using
390 _nickname = nick;
391 }
392 else {
393 // log we couldn't get the name
394 _logger.write(this.toString(), Logger.WARNING, "Nickname in use: "+nick);
395 }
396 }
397 if(!ok) {
398 // oh dear, we couldn't get on.
399 throw new IOException("All nicknames in use");
400 }
401 // join the channel
402 try {
403 _channel = cp.getProperty(_name, "Alerter.IRC.channel");
404 } catch (PropertyNotFoundException e) {
405 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
406 throw new IOException("Can't get channel name due to configuration error");
407 }
408 _socketOut.println("JOIN "+_channel);
409 // allow alerts
410 _active = true;
411 }
412
413 /**
414 * Disconnect "nicely" from the IRC server.
415 *
416 * @throws IOException if the disconnection fails
417 */
418 public void disconnect() throws IOException {
419 // stop alerts
420 _active = false;
421 // send proper QUIT
422 _socketOut.println("QUIT : iscreamBot component shutting down...");
423 // close the socket
424 _socketOut.close();
425 _socketIn.close();
426 _socket.close();
427 }
428
429 /**
430 * Overrides the {@link java.lang.Object#toString() Object.toString()}
431 * method to provide clean logging (every class should have this).
432 *
433 * This uses the uk.ac.ukc.iscream.util.NameFormat class
434 * to format the toString()
435 *
436 * @return the name of this class and its CVS revision
437 */
438 public String toString() {
439 return FormatName.getName(
440 _name,
441 getClass().getName(),
442 REVISION);
443 }
444
445 /**
446 * Deals with incoming lines from the server.
447 *
448 * @param line the line to deal with
449 */
450 private void handleInput(String line) {
451 ConfigurationProxy cp = ConfigurationProxy.getInstance();
452 // if it's a PING...
453 if(line.startsWith("PING")) {
454 // ...send a PONG
455 _socketOut.println("PONG" + line.substring(4));
456 }
457 // see if it's for us
458 else if(getMsg(line).startsWith(_nickname+",") || getMsg(line).startsWith(_nickname+":") || getMsg(line).startsWith(_nickname+" ")) {
459 // setup some String's
460 String stopCommand, startCommand, timeSinceLastAlertCommand, lastAlertCommand, joinCommand;
461 String nickChangeCommand, versionCommand, helpCommand, statCommand, uptimeCommand;
462 // get the command set
463 try {
464 stopCommand = cp.getProperty(_name, "Alerter.IRC.stopCommand");
465 startCommand = cp.getProperty(_name, "Alerter.IRC.startCommand");
466 timeSinceLastAlertCommand = cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand");
467 lastAlertCommand = cp.getProperty(_name, "Alerter.IRC.lastAlertCommand");
468 joinCommand = cp.getProperty(_name, "Alerter.IRC.joinCommand");
469 nickChangeCommand = cp.getProperty(_name, "Alerter.IRC.nickChangeCommand");
470 versionCommand = cp.getProperty(_name, "Alerter.IRC.versionCommand");
471 helpCommand = cp.getProperty(_name, "Alerter.IRC.helpCommand");
472 statCommand = cp.getProperty(_name, "Alerter.IRC.statCommand");
473 uptimeCommand = cp.getProperty(_name, "Alerter.IRC.uptimeCommand");
474 } catch (PropertyNotFoundException e) {
475 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
476 // lets bail from handling this line...
477 // ...it's gonna be hard without a command set!
478 return;
479 }
480
481 // we have a message for us
482 String message = getMsg(line).substring(_nickname.length());
483 if(message.indexOf(stopCommand)!=-1) {
484 _active = false;
485 sendMsg(getMsgSender(line)+", alerts have been stopped");
486 }
487 else if(message.indexOf(startCommand)!=-1) {
488 _active = true;
489 sendMsg(getMsgSender(line)+", alerts have been activated");
490 }
491 // this needs to go here if it contains the same words as the lastAlertCommand
492 else if(message.indexOf(timeSinceLastAlertCommand)!=-1) {
493 if(_lastAlertTime != -1) {
494 long uptime = (System.currentTimeMillis() - _lastAlertTime) / 1000;
495 String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
496 sendMsg(getMsgSender(line)+", I last sent an alert "+uptimeText+ " ago");
497 }
498 else {
499 sendMsg(getMsgSender(line)+", I've never sent an alert!");
500 }
501 }
502 else if(message.indexOf(lastAlertCommand)!=-1) {
503 if(_lastAlertTime != -1) {
504 String date = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(new Date(_lastAlertTime));
505 sendMsg(getMsgSender(line)+", last alert was at "+date+"; "+_lastAlert);
506 }
507 else {
508 sendMsg(getMsgSender(line)+", I've never sent an alert!");
509 }
510
511 }
512 else if(message.indexOf(joinCommand)!=-1) {
513 String joinCmd = joinCommand;
514 String newChan = message.substring(message.indexOf(joinCmd) + joinCmd.length() + 1);
515 int endOfChan = newChan.indexOf(" ");
516 if(endOfChan == -1) {
517 endOfChan = newChan.length();
518 }
519 newChan = newChan.substring(0, endOfChan);
520 sendMsg(getMsgSender(line)+", okay, I'm off to "+newChan);
521 _socketOut.println("PART "+_channel);
522 _socketOut.println("JOIN "+newChan);
523 _channel = newChan;
524 }
525 else if(message.indexOf(nickChangeCommand)!=-1) {
526 String nickChangeCmd = nickChangeCommand;
527 String newNick = message.substring(message.indexOf(nickChangeCmd) + nickChangeCmd.length() + 1);
528 int endOfNick = newNick.indexOf(" ");
529 if(endOfNick == -1) {
530 endOfNick = newNick.length();
531 }
532 newNick = newNick.substring(0, endOfNick);
533 sendMsg(getMsgSender(line)+", okay, changing my nickname to "+newNick);
534 _socketOut.println("NICK "+newNick);
535 _nickname = newNick;
536 }
537 else if(message.indexOf(versionCommand)!=-1) {
538 sendMsg(getMsgSender(line)+", I am version "+REVISION.substring(11, REVISION.length() -2)+" of the i-scream alerting bot");
539 }
540 else if(message.indexOf(helpCommand)!=-1) {
541 sendPrivMsg(getMsgSender(line), "Hello, I am the i-scream alerting bot version "+REVISION.substring(11, REVISION.length() -2));
542 sendPrivMsg(getMsgSender(line), "I understand the following commands;");
543 sendPrivMsg(getMsgSender(line), stopCommand);
544 sendPrivMsg(getMsgSender(line), startCommand);
545 sendPrivMsg(getMsgSender(line), lastAlertCommand);
546 sendPrivMsg(getMsgSender(line), joinCommand);
547 sendPrivMsg(getMsgSender(line), nickChangeCommand);
548 sendPrivMsg(getMsgSender(line), statCommand);
549 sendPrivMsg(getMsgSender(line), uptimeCommand);
550 sendPrivMsg(getMsgSender(line), timeSinceLastAlertCommand);
551 sendPrivMsg(getMsgSender(line), helpCommand);
552 }
553 else if(message.indexOf(statCommand)!=-1) {
554 sendMsg(getMsgSender(line)+", I have sent a total of "+_alertCount+" alerts!");
555 }
556 else if(message.indexOf(uptimeCommand)!=-1) {
557 long uptime = (System.currentTimeMillis() - _startTime) / 1000;
558 String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
559 sendMsg(getMsgSender(line)+", I have been running for "+uptimeText);
560 }
561 else if(message.indexOf("do a jibble dance")!=-1) {
562 // little joke :)
563 sendAction("jives to the funky beat shouting \"ii--screeeaaammm\"");
564 }
565 else {
566 String rejectMessage = NOT_CONFIGURED;
567 try {
568 rejectMessage = cp.getProperty(_name, "Alerter.IRC.rejectMessage");
569 } catch(PropertyNotFoundException e) {
570 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
571 }
572 sendMsg(getMsgSender(line)+", "+rejectMessage);
573 }
574 }
575 else if(line.indexOf(_nickname)!=-1 && line.indexOf(_channel)!=-1 && line.indexOf("KICK")!=-1) {
576 sendPrivMsg(getMsgSender(line), "That wasn't a nice thing to do...");
577 try {
578 _channel = cp.getProperty(_name, "Alerter.IRC.channel");
579 } catch(PropertyNotFoundException e) {
580 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
581 }
582 _socketOut.println("JOIN "+_channel);
583 }
584 }
585
586 /**
587 * Strips the header from a message line
588 *
589 * @param line the line to strip
590 * @return the message from the line
591 */
592 private String getMsg(String line) {
593 String result = "";
594 if(line.indexOf("PRIVMSG")!=-1) {
595 int firstColon = line.indexOf(":");
596 if(firstColon != -1) {
597 int secondColon = line.indexOf(":", firstColon+1);
598 if(secondColon != -1) {
599 result = line.substring(secondColon+1);
600 }
601 }
602 }
603 return result;
604 }
605
606 /**
607 * Finds out the sender of the message
608 *
609 * @param line the line to look for a sender in
610 * @return the sender
611 */
612 private String getMsgSender(String line) {
613 String result = "";
614 int colon = line.indexOf(":");
615 int excl = line.indexOf("!");
616 if(colon!=-1 && excl!=-1) {
617 result = line.substring(colon+1, excl);
618 }
619 return result;
620 }
621
622 /**
623 * The socket connected to the server
624 */
625 private Socket _socket;
626
627 /**
628 * The writer
629 */
630 private PrintWriter _socketOut;
631
632 /**
633 * The reader
634 */
635 private BufferedReader _socketIn;
636
637 /**
638 * Just a reminder to what channel we're on...
639 * this can't be dynamic :)
640 */
641 private String _channel;
642
643 /**
644 * A reminder of our current nickname...
645 */
646 private String _nickname;
647
648 }
649
650 }