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.25
Committed: Fri Mar 23 01:09:51 2001 UTC (23 years, 1 month ago) by ajm
Branch: MAIN
Changes since 1.24: +39 -94 lines
Log Message:
All skeletorized and javadoc'd now.

File Contents

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