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.18
Committed: Thu Mar 8 13:43:54 2001 UTC (23 years, 2 months ago) by tdb
Branch: MAIN
Changes since 1.17: +16 -5 lines
Log Message:
Added a count of "ignored" alerts - ie. when in stopped mode.

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