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.9
Committed: Sun Mar 4 03:34:50 2001 UTC (23 years, 2 months ago) by tdb
Branch: MAIN
Changes since 1.8: +277 -45 lines
Log Message:
Added a lot of features merged with the stuff AJ has just done. The IRC bot now
is a lot more robust - it will make more effort to connect, and reconnect. It
will respond to user commands, such as "stop alerts". It can be moved between
channels, and even supports different names if one is in use.

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
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.7 2001/03/03 01:09:53 tdb1 Exp $
22 */
23 public class IRC__Alerter implements PluginAlerter {
24
25 //---FINAL ATTRIBUTES---
26
27 /**
28 * The current CVS revision of this class
29 */
30 public final String REVISION = "$Revision: 1.8 $";
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
48 // connect to the IRC server
49 _ircbot = new IRCBot();
50 _ircbot.start();
51
52 _logger.write(toString(), Logger.SYSINIT, "IRC Alerter started");
53 }
54
55 //---PUBLIC METHODS---
56
57 public void sendAlert(Alert alert) {
58 // only send alerts if we're active
59 if(_active) {
60 ConfigurationProxy cp = ConfigurationProxy.getInstance();
61 String levelName = cp.getProperty(_name, "Alerter.IRC.level");
62 int level = StringUtils.getStringPos(levelName, Alert.alertLevels);
63 // only send if it's equal (or above) our level
64 if(alert.getLevel() >= level) {
65 String alertType = Alert.alertLevels[alert.getLevel()];
66 String thresholdType = Alert.thresholdLevels[alert.getThreshold()];
67 // sort out the message
68 String message = cp.getProperty(_name, "Alerter.IRC.message");
69 message = StringUtils.replaceText(message, "%level%", alertType);
70 message = StringUtils.replaceText(message, "%threshold%", thresholdType);
71 message = StringUtils.replaceText(message, "%source%", alert.getSource());
72 message = StringUtils.replaceText(message, "%value%", alert.getValue());
73 message = StringUtils.replaceText(message, "%thresholdValue%", alert.getThresholdValue());
74 message = StringUtils.replaceText(message, "%attributeName%", alert.getAttributeName());
75 message = StringUtils.replaceText(message, "%timeTillNextAlert%", getTimeString(Long.parseLong(alert.getTimeTillNextAlert())));
76
77 // send the message
78 _logger.write(toString(), Logger.DEBUG, "Sending " + _name + " at "+ levelName + " level");
79 _ircbot.sendMsg(message);
80 _lastAlert = message;
81 }
82 }
83 }
84
85 /**
86 * Overrides the {@link java.lang.Object#toString() Object.toString()}
87 * method to provide clean logging (every class should have this).
88 *
89 * This uses the uk.ac.ukc.iscream.util.NameFormat class
90 * to format the toString()
91 *
92 * @return the name of this class and its CVS revision
93 */
94 public String toString() {
95 return FormatName.getName(
96 _name,
97 getClass().getName(),
98 REVISION);
99 }
100
101 /**
102 * return the String representation of what the filter does
103 */
104 public String getDescription(){
105 return DESC;
106 }
107
108 //---PRIVATE METHODS---
109
110 private String getTimeString(long time) {
111 String timeString = null;
112 if (time >= 60) {
113 timeString = (time / 60) + " minute(s)";
114 } else if (time >= 3600) {
115 timeString = ((time/60) / 60) + " hour(s)";
116 } else {
117 timeString = time + " second(s)";
118 }
119 return timeString;
120 }
121
122 //---ACCESSOR/MUTATOR METHODS---
123
124 //---ATTRIBUTES---
125
126 /**
127 * A reference to the IRCBot
128 */
129 private IRCBot _ircbot;
130
131 /**
132 * Are we "active"
133 */
134 private boolean _active = false;
135
136 /**
137 * The last alert that was sent
138 */
139 private String _lastAlert = "no alerts have been sent";
140
141 /**
142 * This is the friendly identifier of the
143 * component this class is running in.
144 * eg, a Filter may be called "filter1",
145 * If this class does not have an owning
146 * component, a name from the configuration
147 * can be placed here. This name could also
148 * be changed to null for utility classes.
149 */
150 private String _name = "IRC Alert";
151
152 /**
153 * This holds a reference to the
154 * system logger that is being used.
155 */
156 private Logger _logger = ReferenceManager.getInstance().getLogger();
157
158 //---STATIC ATTRIBUTES---
159
160 //---INNER CLASSES---
161
162 /**
163 * This class provides some basic IRCBot functionality. It connects
164 * to a specified server, and will remain there until told to
165 * leave. Whilst connected it can send a message or a notice to
166 * the server.
167 */
168 class IRCBot extends Thread {
169
170 /**
171 * Main thread loop, this part of the class listens for
172 * messages from the server, and acts accordingly. At the
173 * present moment it only responds to pings.
174 */
175 public void run() {
176 // so we can stop if required
177 boolean run = true;
178 while(run) {
179 // flag so we can stop the loop
180 boolean doRead = true;
181 // connect to the IRC server
182 try {
183 connect();
184 sendNotice(ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.startupNotice"));
185 } catch(IOException e) {
186 doRead=false;
187 _logger.write(this.toString(), Logger.ERROR, "Error connecting: "+e);
188 }
189 while(doRead) {
190 try {
191 // read a command
192 String cmd = _socketIn.readLine();
193 // if we have a null, we've lost contact
194 if(cmd == null) {
195 throw new IOException("End of stream reached");
196 }
197 // let another method deal with the input
198 handleInput(cmd);
199 } catch (IOException e) {
200 // comms failure, maybe our link is dead.
201 _logger.write(this.toString(), Logger.ERROR, "Communication error: "+e);
202 // stop, and loop round for a reconnect.
203 doRead = false;
204 }
205 }
206 // make sure we're disconnected
207 try {
208 disconnect();
209 } catch (IOException e) {
210 _logger.write(this.toString(), Logger.ERROR, "Communication error: "+e);
211 }
212
213 // comms have failed, so wait a while and reconnect
214 int delayTime = 0;
215 try {
216 delayTime = Integer.parseInt(ConfigurationProxy.getInstance().getProperty(_name, "Alerter.IRC.reconnectDelay"));
217 } catch (NumberFormatException e) {
218 delayTime = DEFAULT_RECONNECT_DELAY;
219 _logger.write(this.toString(), Logger.WARNING, "Erronous Alerter.IRC.reconnectDelay value in configuration using default of " + delayTime + " seconds");
220 } catch (org.omg.CORBA.MARSHAL e2) {
221 delayTime = DEFAULT_RECONNECT_DELAY;
222 _logger.write(this.toString(), Logger.WARNING, "Alerter.IRC.reconnectDelay value unavailable using default of " + delayTime + " seconds");
223 }
224 try {
225 Thread.sleep(delayTime * 1000);
226 } catch (InterruptedException e) {}
227 }
228 // maybe disconnect here ? - shutdown method not implemented yet
229 //disconnect();
230 }
231
232 /**
233 * Sends a message to the channel.
234 *
235 * @param msg The message to send
236 */
237 public void sendMsg(String msg) {
238 _socketOut.println("PRIVMSG "+_channel+" :"+msg);
239 }
240
241 /**
242 * Sends a message to the channel.
243 *
244 * @param user The user to send to
245 * @param msg The message to send
246 */
247 public void sendPrivMsg(String user, String msg) {
248 _socketOut.println("PRIVMSG "+user+" :"+msg);
249 }
250
251 /**
252 * Sends an action to the channel.
253 *
254 * @param msg the action message
255 */
256 public void sendAction(String msg) {
257 char esc = 001;
258 sendMsg(esc+"ACTION "+msg+esc);
259 }
260
261 /**
262 * Sends a notice to the channel.
263 *
264 * @param msg The notice to send
265 */
266 public void sendNotice(String msg) {
267 _socketOut.println("NOTICE "+_channel+" :"+msg);
268 }
269
270 /**
271 * Connect to the IRC server, log in, and join the channel.
272 *
273 * @throws IOException if the connection fails
274 */
275 public void connect() throws IOException {
276 ConfigurationProxy cp = ConfigurationProxy.getInstance();
277 // setup the socket, reader and writer
278 String server = cp.getProperty(_name, "Alerter.IRC.IRCServer");
279 int port = Integer.parseInt(cp.getProperty(_name, "Alerter.IRC.IRCPort"));
280 _socket = new Socket(server, port);
281 _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
282 _socketOut = new PrintWriter(_socket.getOutputStream(), true);
283 //_socketOut.println("PASS");
284 // send USER details
285 String user = cp.getProperty(_name, "Alerter.IRC.user");
286 String comment = cp.getProperty(_name, "Alerter.IRC.comment");
287 _socketOut.println("USER "+user+" 8 * :"+comment);
288 // attempt to get a nick
289 String nickList = cp.getProperty(_name, "Alerter.IRC.nickList");
290 StringTokenizer st = new StringTokenizer(nickList, ";");
291 boolean ok = false;
292 // try until we exhaust our list
293 while(!ok && st.hasMoreTokens()) {
294 String nick = st.nextToken();
295 _socketOut.println("NICK "+nick);
296 // get a "yes" or "no" response back
297 String response = "";
298 do {
299 response = _socketIn.readLine();
300 if(response==null) {
301 throw new IOException("Communication error whilst logging in");
302 }
303 } while(response.indexOf("001")==-1 && response.indexOf("433")==-1);
304 // see if it was a yes
305 if(response.indexOf("001")!=-1) {
306 // great, we're logged in !
307 ok = true;
308 // store the name we're using
309 _nickname = nick;
310 }
311 else {
312 // log we couldn't get the name
313 _logger.write(this.toString(), Logger.WARNING, "Nickname in use: "+nick);
314 }
315 }
316 if(!ok) {
317 // oh dear, we couldn't get on.
318 throw new IOException("All nicknames in use");
319 }
320 // join the channel
321 _channel = cp.getProperty(_name, "Alerter.IRC.channel");
322 _socketOut.println("JOIN "+_channel);
323 // allow alerts
324 _active = true;
325 }
326
327 /**
328 * Disconnect "nicely" from the IRC server.
329 *
330 * @throws IOException if the disconnection fails
331 */
332 public void disconnect() throws IOException {
333 // stop alerts
334 _active = false;
335 // send proper QUIT
336 _socketOut.println("QUIT : iscreamBot component shutting down...");
337 // close the socket
338 _socketOut.close();
339 _socketIn.close();
340 _socket.close();
341 }
342
343 /**
344 * Overrides the {@link java.lang.Object#toString() Object.toString()}
345 * method to provide clean logging (every class should have this).
346 *
347 * This uses the uk.ac.ukc.iscream.util.NameFormat class
348 * to format the toString()
349 *
350 * @return the name of this class and its CVS revision
351 */
352 public String toString() {
353 return FormatName.getName(
354 _name,
355 getClass().getName(),
356 REVISION);
357 }
358
359 /**
360 * Deals with incoming lines from the server.
361 *
362 * @param line the line to deal with
363 */
364 private void handleInput(String line) {
365 ConfigurationProxy cp = ConfigurationProxy.getInstance();
366 // if it's a PING...
367 if(line.startsWith("PING")) {
368 // ...send a PONG
369 _socketOut.println("PONG" + line.substring(4));
370 }
371 // see if it's for us
372 else if(getMsg(line).startsWith(_nickname)) {
373 // we have a message for us
374 String message = getMsg(line).substring(_nickname.length());
375 if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.stopCommand"))!=-1) {
376 _active = false;
377 sendMsg(getMsgSender(line)+", alerts have been stopped");
378 }
379 else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.startCommand"))!=-1) {
380 _active = true;
381 sendMsg(getMsgSender(line)+", alerts have been activated");
382 }
383 else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.lastAlertCommand"))!=-1) {
384 sendMsg(getMsgSender(line)+", last alert was: "+_lastAlert);
385 }
386 else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.joinCommand"))!=-1) {
387 String joinCmd = cp.getProperty(_name, "Alerter.IRC.joinCommand");
388 String newChan = message.substring(message.indexOf(joinCmd) + joinCmd.length() + 1);
389 int endOfChan = newChan.indexOf(" ");
390 if(endOfChan == -1) {
391 endOfChan = newChan.length();
392 }
393 newChan = newChan.substring(0, endOfChan);
394 sendMsg(getMsgSender(line)+", okay, I'm off to "+newChan);
395 _socketOut.println("PART "+_channel);
396 _socketOut.println("JOIN "+newChan);
397 _channel = newChan;
398 }
399 else if(message.indexOf(cp.getProperty(_name, "Alerter.IRC.helpCommand"))!=-1) {
400 sendPrivMsg(getMsgSender(line), "I am the i-scream alerting bot revision "+REVISION.substring(11, REVISION.length() -2));
401 sendPrivMsg(getMsgSender(line), "I understand the following commands;");
402 sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.stopCommand"));
403 sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.startCommand"));
404 sendPrivMsg(getMsgSender(line), cp.getProperty(_name, "Alerter.IRC.lastAlertCommand"));
405 }
406 else if(message.indexOf("do a jibble dance")!=-1) {
407 // little joke :)
408 sendAction("jives to the funky beat shouting \"ii--screeeaaammm\"");
409 }
410 else {
411 sendMsg(getMsgSender(line)+", "+cp.getProperty(_name, "Alerter.IRC.rejectMessage"));
412 }
413 }
414 else if(line.indexOf(_nickname)!=-1 && line.indexOf(_channel)!=-1 && line.indexOf("KICK")!=-1) {
415 sendPrivMsg(getMsgSender(line), "That wasn't a nice thing to do...");
416 _channel = cp.getProperty(_name, "Alerter.IRC.channel");
417 _socketOut.println("JOIN "+_channel);
418 }
419 }
420
421 /**
422 * Strips the header from a message line
423 *
424 * @param line the line to strip
425 * @return the message from the line
426 */
427 private String getMsg(String line) {
428 String result = "";
429 if(line.indexOf("PRIVMSG")!=-1) {
430 int firstColon = line.indexOf(":");
431 if(firstColon != -1) {
432 int secondColon = line.indexOf(":", firstColon+1);
433 if(secondColon != -1) {
434 result = line.substring(secondColon+1);
435 }
436 }
437 }
438 return result;
439 }
440
441 /**
442 * Finds out the sender of the message
443 *
444 * @param line the line to look for a sender in
445 * @return the sender
446 */
447 private String getMsgSender(String line) {
448 String result = "";
449 int colon = line.indexOf(":");
450 int excl = line.indexOf("!");
451 if(colon!=-1 && excl!=-1) {
452 result = line.substring(colon+1, excl);
453 }
454 return result;
455 }
456
457 /**
458 * The socket connected to the server
459 */
460 private Socket _socket;
461
462 /**
463 * The writer
464 */
465 private PrintWriter _socketOut;
466
467 /**
468 * The reader
469 */
470 private BufferedReader _socketIn;
471
472 /**
473 * Just a reminder to what channel we're on...
474 * this can't be dynamic :)
475 */
476 private String _channel;
477
478 /**
479 * A reminder of our current nickname...
480 */
481 private String _nickname;
482
483 }
484
485 }