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.23
Committed: Fri Mar 16 17:13:49 2001 UTC (23 years, 2 months ago) by tdb
Branch: MAIN
Changes since 1.22: +3 -15 lines
Log Message:
Moved the getTimeString() method into the util.DateUtils package.

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