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.28
Committed: Sun May 13 01:47:44 2001 UTC (23 years ago) by tdb
Branch: MAIN
Changes since 1.27: +13 -2 lines
Log Message:
A small bug had crept in here. The _name attribute was always being returned as
it stood in the skeleton class (when accessed by a method written in the
skeleton class, but called on an extending class). This is solved by adding an
accessor.
This will result in configuration being looked up for the relevant name, as
opposed to always the name of the abstract class.

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: tdb1 $
21 * @version $Id: IRC__Alerter.java,v 1.27 2001/03/24 18:43:38 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.27 $";
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.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.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).startsWith(_nickname+",") || getMsg(line).startsWith(_nickname+":") || getMsg(line).startsWith(_nickname+" ")) {
446 // setup some String's
447 String stopCommand, startCommand, timeSinceLastAlertCommand, lastAlertCommand, joinCommand;
448 String nickChangeCommand, versionCommand, helpCommand, statCommand, uptimeCommand;
449 // get the command set
450 try {
451 stopCommand = cp.getProperty(_name, "Alerter.IRC.stopCommand");
452 startCommand = cp.getProperty(_name, "Alerter.IRC.startCommand");
453 timeSinceLastAlertCommand = cp.getProperty(_name, "Alerter.IRC.timeSinceLastAlertCommand");
454 lastAlertCommand = cp.getProperty(_name, "Alerter.IRC.lastAlertCommand");
455 joinCommand = cp.getProperty(_name, "Alerter.IRC.joinCommand");
456 nickChangeCommand = cp.getProperty(_name, "Alerter.IRC.nickChangeCommand");
457 versionCommand = cp.getProperty(_name, "Alerter.IRC.versionCommand");
458 helpCommand = cp.getProperty(_name, "Alerter.IRC.helpCommand");
459 statCommand = cp.getProperty(_name, "Alerter.IRC.statCommand");
460 uptimeCommand = cp.getProperty(_name, "Alerter.IRC.uptimeCommand");
461 } catch (PropertyNotFoundException e) {
462 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
463 // lets bail from handling this line...
464 // ...it's gonna be hard without a command set!
465 return;
466 }
467
468 // we have a message for us
469 String message = getMsg(line).substring(_nickname.length());
470 if(message.indexOf(stopCommand)!=-1) {
471 _active = false;
472 sendMsg(getMsgSender(line)+", alerts have been stopped");
473 }
474 else if(message.indexOf(startCommand)!=-1) {
475 _active = true;
476 sendMsg(getMsgSender(line)+", alerts have been activated");
477 }
478 // this needs to go here if it contains the same words as the lastAlertCommand
479 else if(message.indexOf(timeSinceLastAlertCommand)!=-1) {
480 if(_lastAlertTime != -1) {
481 long uptime = (System.currentTimeMillis() - _lastAlertTime) / 1000;
482 String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
483 sendMsg(getMsgSender(line)+", I last sent an alert "+uptimeText+ " ago");
484 }
485 else {
486 sendMsg(getMsgSender(line)+", I've never sent an alert!");
487 }
488 }
489 else if(message.indexOf(lastAlertCommand)!=-1) {
490 if(_lastAlertTime != -1) {
491 String date = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.UK).format(new Date(_lastAlertTime));
492 sendMsg(getMsgSender(line)+", last alert was at "+date+"; "+_lastAlert);
493 }
494 else {
495 sendMsg(getMsgSender(line)+", I've never sent an alert!");
496 }
497
498 }
499 else if(message.indexOf(joinCommand)!=-1) {
500 String joinCmd = joinCommand;
501 String newChan = message.substring(message.indexOf(joinCmd) + joinCmd.length() + 1);
502 int endOfChan = newChan.indexOf(" ");
503 if(endOfChan == -1) {
504 endOfChan = newChan.length();
505 }
506 newChan = newChan.substring(0, endOfChan);
507 if(newChan.equals(_channel)) {
508 sendMsg(getMsgSender(line)+", I'm already on "+newChan+"!");
509 } else {
510 sendMsg(getMsgSender(line)+", okay, I'm off to "+newChan);
511 _socketOut.println("PART "+_channel);
512 _socketOut.println("JOIN "+newChan);
513 _channel = newChan;
514 }
515 }
516 else if(message.indexOf(nickChangeCommand)!=-1) {
517 String nickChangeCmd = nickChangeCommand;
518 String newNick = message.substring(message.indexOf(nickChangeCmd) + nickChangeCmd.length() + 1);
519 int endOfNick = newNick.indexOf(" ");
520 if(endOfNick == -1) {
521 endOfNick = newNick.length();
522 }
523 newNick = newNick.substring(0, endOfNick);
524 sendMsg(getMsgSender(line)+", okay, changing my nickname to "+newNick);
525 _socketOut.println("NICK "+newNick);
526 _nickname = newNick;
527 }
528 else if(message.indexOf(versionCommand)!=-1) {
529 sendMsg(getMsgSender(line)+", I am version "+REVISION.substring(11, REVISION.length() -2)+" of the i-scream alerting bot");
530 }
531 else if(message.indexOf(helpCommand)!=-1) {
532 sendPrivMsg(getMsgSender(line), "Hello, I am the i-scream alerting bot version "+REVISION.substring(11, REVISION.length() -2));
533 sendPrivMsg(getMsgSender(line), "I understand the following commands;");
534 sendPrivMsg(getMsgSender(line), stopCommand);
535 sendPrivMsg(getMsgSender(line), startCommand);
536 sendPrivMsg(getMsgSender(line), lastAlertCommand);
537 sendPrivMsg(getMsgSender(line), joinCommand);
538 sendPrivMsg(getMsgSender(line), nickChangeCommand);
539 sendPrivMsg(getMsgSender(line), statCommand);
540 sendPrivMsg(getMsgSender(line), uptimeCommand);
541 sendPrivMsg(getMsgSender(line), timeSinceLastAlertCommand);
542 sendPrivMsg(getMsgSender(line), helpCommand);
543 }
544 else if(message.indexOf(statCommand)!=-1) {
545 sendMsg(getMsgSender(line)+", I have sent a total of "+_alertCount+" alerts, and ignored a total of "+_ignoredCount+"!");
546 }
547 else if(message.indexOf(uptimeCommand)!=-1) {
548 long uptime = (System.currentTimeMillis() - _startTime) / 1000;
549 String uptimeText = DateUtils.formatTime(uptime, "%DAYS% days, %HOURS% hours, %MINS% mins, and %SECS% secs");
550 sendMsg(getMsgSender(line)+", I have been running for "+uptimeText);
551 }
552 else if(message.indexOf("ping")!=-1) {
553 sendMsg("pong");
554 }
555 else if(message.indexOf("do a jibble dance")!=-1) {
556 // little joke :)
557 sendAction("jives to the funky beat shouting \"ii--screeeaaammm\"");
558 }
559 else {
560 String rejectMessage = NOT_CONFIGURED;
561 try {
562 rejectMessage = cp.getProperty(_name, "Alerter.IRC.rejectMessage");
563 } catch(PropertyNotFoundException e) {
564 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
565 }
566 sendMsg(getMsgSender(line)+", "+rejectMessage);
567 }
568 }
569 else if(line.indexOf(_nickname)!=-1 && line.indexOf(_channel)!=-1 && line.indexOf("KICK")!=-1) {
570 sendPrivMsg(getMsgSender(line), "That wasn't a nice thing to do...");
571 try {
572 _channel = cp.getProperty(_name, "Alerter.IRC.channel");
573 } catch(PropertyNotFoundException e) {
574 _logger.write(this.toString(), Logger.ERROR, "Configuration error: "+e);
575 }
576 _socketOut.println("JOIN "+_channel);
577 }
578 }
579
580 /**
581 * Strips the header from a message line
582 *
583 * @param line the line to strip
584 * @return the message from the line
585 */
586 private String getMsg(String line) {
587 String result = "";
588 if(line.indexOf("PRIVMSG")!=-1) {
589 int firstColon = line.indexOf(":");
590 if(firstColon != -1) {
591 int secondColon = line.indexOf(":", firstColon+1);
592 if(secondColon != -1) {
593 result = line.substring(secondColon+1);
594 }
595 }
596 }
597 return result;
598 }
599
600 /**
601 * Finds out the sender of the message
602 *
603 * @param line the line to look for a sender in
604 * @return the sender
605 */
606 private String getMsgSender(String line) {
607 String result = "";
608 int colon = line.indexOf(":");
609 int excl = line.indexOf("!");
610 if(colon!=-1 && excl!=-1) {
611 result = line.substring(colon+1, excl);
612 }
613 return result;
614 }
615
616 /**
617 * The socket connected to the server
618 */
619 private Socket _socket;
620
621 /**
622 * The writer
623 */
624 private PrintWriter _socketOut;
625
626 /**
627 * The reader
628 */
629 private BufferedReader _socketIn;
630
631 /**
632 * Just a reminder to what channel we're on...
633 * this can't be dynamic :)
634 */
635 private String _channel;
636
637 /**
638 * A reminder of our current nickname...
639 */
640 private String _nickname;
641
642 /**
643 * This holds a reference to the
644 * system logger that is being used.
645 */
646 protected Logger _logger = ReferenceManager.getInstance().getLogger();
647
648 }
649
650 }