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.30
Committed: Thu Jan 17 17:58:12 2002 UTC (22 years, 4 months ago) by tdb
Branch: MAIN
Branch point for: SERVER_PIRCBOT
Changes since 1.29: +5 -3 lines
Log Message:
Will now respond to it's name regardless of it's case.

File Contents

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