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, 3 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

# User Rev Content
1 tdb 1.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 tdb 1.9 import java.util.*;
13 tdb 1.1
14     /**
15     * This Alert sends an IRC message.
16     *
17 tdb 1.9 * Clean shutdown could be achieved by stopping the run() method in the
18     * IRCBot inner class.
19     *
20 tdb 1.2 * @author $Author: tdb1 $
21 ajm 1.8 * @version $Id: IRC__Alerter.java,v 1.7 2001/03/03 01:09:53 tdb1 Exp $
22 tdb 1.1 */
23     public class IRC__Alerter implements PluginAlerter {
24    
25     //---FINAL ATTRIBUTES---
26    
27     /**
28     * The current CVS revision of this class
29     */
30 tdb 1.9 public final String REVISION = "$Revision: 1.8 $";
31 tdb 1.1
32 tdb 1.9 /**
33     * A description of this alerter
34     */
35 tdb 1.1 public final String DESC = "Sends alerts on an IRC channel";
36    
37 tdb 1.9 /**
38     * The default reconnect delay in seconds
39     */
40     public final int DEFAULT_RECONNECT_DELAY = 30;
41    
42 tdb 1.1 //---STATIC METHODS---
43    
44     //---CONSTRUCTORS---
45    
46     public IRC__Alerter() {
47 tdb 1.7
48 tdb 1.1 // connect to the IRC server
49 tdb 1.9 _ircbot = new IRCBot();
50     _ircbot.start();
51 tdb 1.4
52 tdb 1.5 _logger.write(toString(), Logger.SYSINIT, "IRC Alerter started");
53 tdb 1.1 }
54    
55     //---PUBLIC METHODS---
56    
57     public void sendAlert(Alert alert) {
58 tdb 1.9 // 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 tdb 1.1 }
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 ajm 1.8 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 tdb 1.1 //---ACCESSOR/MUTATOR METHODS---
123    
124     //---ATTRIBUTES---
125    
126 tdb 1.7 /**
127     * A reference to the IRCBot
128     */
129 tdb 1.1 private IRCBot _ircbot;
130    
131     /**
132 tdb 1.9 * 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 tdb 1.1 * 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 ajm 1.8 private String _name = "IRC Alert";
151 tdb 1.1
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 tdb 1.9 // so we can stop if required
177 tdb 1.1 boolean run = true;
178     while(run) {
179 tdb 1.9 // flag so we can stop the loop
180     boolean doRead = true;
181     // connect to the IRC server
182 tdb 1.1 try {
183 tdb 1.9 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 tdb 1.1 }
205 tdb 1.9 }
206     // make sure we're disconnected
207     try {
208     disconnect();
209 tdb 1.1 } catch (IOException e) {
210 tdb 1.9 _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 tdb 1.1 }
224 tdb 1.9 try {
225     Thread.sleep(delayTime * 1000);
226     } catch (InterruptedException e) {}
227 tdb 1.1 }
228 tdb 1.9 // maybe disconnect here ? - shutdown method not implemented yet
229     //disconnect();
230 tdb 1.1 }
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 tdb 1.9 * 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 tdb 1.1 * 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 tdb 1.7 ConfigurationProxy cp = ConfigurationProxy.getInstance();
277 tdb 1.1 // setup the socket, reader and writer
278 tdb 1.7 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 tdb 1.1 _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
282     _socketOut = new PrintWriter(_socket.getOutputStream(), true);
283 tdb 1.9 //_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 tdb 1.1 // join the channel
321 tdb 1.7 _channel = cp.getProperty(_name, "Alerter.IRC.channel");
322 tdb 1.1 _socketOut.println("JOIN "+_channel);
323 tdb 1.9 // allow alerts
324     _active = true;
325 tdb 1.1 }
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 tdb 1.9 // stop alerts
334     _active = false;
335 tdb 1.6 // send proper QUIT
336 tdb 1.1 _socketOut.println("QUIT : iscreamBot component shutting down...");
337     // close the socket
338     _socketOut.close();
339     _socketIn.close();
340     _socket.close();
341     }
342 tdb 1.7
343 tdb 1.1 /**
344 tdb 1.9 * 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 tdb 1.1 * 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 tdb 1.7 /**
473     * Just a reminder to what channel we're on...
474     * this can't be dynamic :)
475     */
476     private String _channel;
477 tdb 1.9
478     /**
479     * A reminder of our current nickname...
480     */
481     private String _nickname;
482    
483 tdb 1.1 }
484    
485     }