tools/hw2irc/net/ercatec/hw2ircsvr/Connection.java
changeset 15784 823cf18be1fc
equal deleted inserted replaced
15782:6409d756e9da 15784:823cf18be1fc
       
     1 package net.ercatec.hw2ircsvr;
       
     2 
       
     3 import net.ercatec.hw.INetClient;
       
     4 import net.ercatec.hw.ProtocolConnection;
       
     5 
       
     6 import java.io.BufferedReader;
       
     7 import java.io.InputStream;
       
     8 import java.io.InputStreamReader;
       
     9 import java.io.OutputStream;
       
    10 import java.net.InetSocketAddress;
       
    11 import java.net.ServerSocket;
       
    12 import java.net.Socket;
       
    13 import java.util.ArrayList;
       
    14 import java.util.Arrays;
       
    15 import java.util.HashMap;
       
    16 import java.util.Hashtable;
       
    17 import java.util.List;
       
    18 import java.util.Map;
       
    19 import java.util.Queue;
       
    20 import java.util.concurrent.LinkedBlockingQueue;
       
    21 import java.util.Collections;
       
    22 import java.util.Vector;
       
    23 
       
    24 import java.util.regex.Pattern;
       
    25 import java.util.regex.PatternSyntaxException;
       
    26 
       
    27 import java.lang.IllegalArgumentException;
       
    28 
       
    29 // for auth files
       
    30 import java.util.Properties;
       
    31 import java.io.FileInputStream;
       
    32 import java.io.IOException;
       
    33 
       
    34 /* TODO
       
    35  * disconnect clients that are not irc clients
       
    36  * disconnect excess flooders
       
    37  * recognizes stuff after : as single arg
       
    38  * collect pre-irc-join messages and show on join
       
    39  * allow negating regexps
       
    40  * ban
       
    41  * banlist
       
    42  * commandquery // wth did I mean by that?
       
    43  * more room info
       
    44  * filter rooms
       
    45  * warnings
       
    46  * global notice
       
    47  */
       
    48 
       
    49 /**
       
    50  * @author sheepluva
       
    51  * 
       
    52  * based on jircs by Alexander Boyd
       
    53  */
       
    54 public class Connection implements INetClient, Runnable
       
    55 {
       
    56     private static final String DESCRIPTION_SHORT
       
    57         = "connect to hedgewars via irc!";
       
    58 
       
    59     private static final String VERSION = "0.6.7-Alpha_2015-11-07";
       
    60 
       
    61 
       
    62     private static final String MAGIC_BYTES       = "[\1\2\3]";
       
    63     private static final char   MAGIC_BYTE_ACTION = ((char)1); // ^A
       
    64     private static final char   MAGIC_BYTE_BOLD   = ((char)2); // ^B
       
    65     private static final char   MAGIC_BYTE_COLOR  = ((char)3); // ^C
       
    66 
       
    67     private static final String[] DEFAULT_MOTD = {
       
    68         "                                         ",
       
    69         " "+MAGIC_BYTE_COLOR+"06"+MAGIC_BYTE_BOLD+"                            SUCH FLUFFY!",
       
    70         "                                         ",
       
    71         " "+MAGIC_BYTE_COLOR+"04 MUCH BAH     "+MAGIC_BYTE_COLOR+"00__  _                     ",
       
    72         " "+MAGIC_BYTE_COLOR+"00          .-.'  `; `-."+MAGIC_BYTE_COLOR+"00_  __  _          ",
       
    73         " "+MAGIC_BYTE_COLOR+"00         (_,         .-:'  `; `"+MAGIC_BYTE_COLOR+"00-._      ",
       
    74         " "+MAGIC_BYTE_COLOR+"14       ,'"+MAGIC_BYTE_COLOR+"02o "+MAGIC_BYTE_COLOR+"00(        (_,           )     ",
       
    75         " "+MAGIC_BYTE_COLOR+"14      (__"+MAGIC_BYTE_COLOR+"00,-'      "+MAGIC_BYTE_COLOR+"15,'"+MAGIC_BYTE_COLOR+"12o "+MAGIC_BYTE_COLOR+"00(            )>   ",
       
    76         " "+MAGIC_BYTE_COLOR+"00         (       "+MAGIC_BYTE_COLOR+"15(__"+MAGIC_BYTE_COLOR+"00,-'            )    ",
       
    77         " "+MAGIC_BYTE_COLOR+"00          `-'._.--._(             )     ",
       
    78         " "+MAGIC_BYTE_COLOR+"14             |||  |||"+MAGIC_BYTE_COLOR+"00`-'._.--._.-'      ",
       
    79         " "+MAGIC_BYTE_COLOR+"15                        |||  |||        ",
       
    80         " "+MAGIC_BYTE_COLOR+"07"+MAGIC_BYTE_BOLD+"  WOW!                                  ",
       
    81         " "+MAGIC_BYTE_COLOR+"09                   VERY SHEEP           ",
       
    82         "                                         ",
       
    83         "                                         ",
       
    84         "                                         ",
       
    85         " "+MAGIC_BYTE_COLOR+"4 Latest hw2irc crimes/changes:",
       
    86         "     ping: ping of hwserver will only get reply if irc client pingable",
       
    87         "     ping: pings of irc clients will only get reply if hwserver pingable",
       
    88         "     rooms: id-rotation, make channel names at least 2 digits wide",
       
    89         "     auth: support passhash being loaded local auth file and irc pass (sent as cleartext - DO NOT USE!)",
       
    90         "                                         ",
       
    91         "                                         ",
       
    92     };
       
    93 
       
    94 
       
    95     private static final String DEFAULT_QUIT_REASON = "User quit";
       
    96     // NOT final
       
    97     private static char CHAT_COMMAND_CHAR = '\\';
       
    98 
       
    99     private final class Room {
       
   100         public final int id;
       
   101         public final String chan;
       
   102         public String name;
       
   103         private String owner = "";
       
   104         public int nPlayers = 0;
       
   105         public int nTeams   = 0;
       
   106 
       
   107         public Room(final int id, final String name, final String owner) {
       
   108             this.id = id;
       
   109             this.chan = (id<10?"#0":"#") + id;
       
   110             this.name = name;
       
   111             this.setOwner(owner);
       
   112         }
       
   113 
       
   114         public String getOwner() { return this.owner; }
       
   115 
       
   116         public void setOwner(final String owner) {
       
   117             // don't to this for first owner
       
   118             if (!this.owner.isEmpty()) {
       
   119 
       
   120                 // owner didn't change
       
   121                 if (this.owner.equals(owner))
       
   122                     return;
       
   123 
       
   124                 // update old room owner
       
   125                 final Player oldOwner = allPlayers.get(this.owner);
       
   126 
       
   127                 if (oldOwner != null)
       
   128                     oldOwner.isRoomAdm = false;
       
   129 
       
   130             }
       
   131 
       
   132             // update new room owner
       
   133             final Player newOwner = allPlayers.get(owner);
       
   134 
       
   135             if (newOwner != null)
       
   136                 newOwner.isRoomAdm = true;
       
   137 
       
   138             this.owner = owner;
       
   139 
       
   140         }
       
   141     }
       
   142 
       
   143     private final class Player {
       
   144         public final String nick;
       
   145         public final String ircNick;
       
   146         private boolean isAdm;
       
   147         private boolean isCont;
       
   148         private boolean isReg;
       
   149         public boolean inRoom;
       
   150         public boolean isRoomAdm;
       
   151         private String ircId;
       
   152         private String ircHostname;
       
   153         private boolean announced;
       
   154 
       
   155         // server info
       
   156         private String version = "";
       
   157         private String ip = "";
       
   158         private String room = "";
       
   159 
       
   160         public Player(final String nick) {
       
   161             this.nick = nick;
       
   162             this.ircNick = hwToIrcNick(nick);
       
   163             this.announced = false;
       
   164             updateIrcHostname();
       
   165         }
       
   166 
       
   167         public String getIrcHostname() { return ircHostname; }
       
   168         public String getIrcId()       { return ircId; }
       
   169 
       
   170         public String getRoom()        {
       
   171             if (room.isEmpty())
       
   172                 return room;
       
   173 
       
   174             return "[" + ((isAdm?"@":"") + (isRoomAdm?"+":"") + this.room);
       
   175         }
       
   176 
       
   177         public boolean needsAnnounce() {
       
   178             return !announced;
       
   179         }
       
   180 
       
   181         public void setAnnounced() {
       
   182             announced = true;
       
   183         }
       
   184 
       
   185         public void setInfo(final String ip, final String version, final String room) {
       
   186             if (this.version.isEmpty()) {
       
   187                 this.version = version;
       
   188                 this.ip = ip.replaceAll("^\\[|]$", "");
       
   189                 updateIrcHostname();
       
   190             }
       
   191 
       
   192             if (room.isEmpty())
       
   193                 this.room = room;
       
   194             else
       
   195                 this.room = room.replaceAll("^\\[[@+]*", "");
       
   196         }
       
   197 
       
   198         public boolean isServerAdmin()  { return isAdm; }
       
   199         //public boolean isContributor()  { return isCont; }
       
   200         public boolean isRegistered()   { return isReg; }
       
   201 
       
   202         public void setServerAdmin(boolean isAdm) {
       
   203             this.isAdm = isAdm; updateIrcHostname(); }
       
   204         public void setContributor(boolean isCont) {
       
   205             this.isCont = isCont; updateIrcHostname(); }
       
   206         public void setRegistered(boolean isReg) {
       
   207             this.isReg = isReg; updateIrcHostname(); }
       
   208 
       
   209         private void updateIrcHostname() {
       
   210             ircHostname = ip.isEmpty()?"":(ip + '/');
       
   211             ircHostname += "hw/";
       
   212             if (!version.isEmpty())
       
   213                 ircHostname += version;
       
   214             if (isAdm)
       
   215                 ircHostname += "/admin";
       
   216             else if (isCont)
       
   217                 ircHostname += "/contributor";
       
   218             else if (isReg)
       
   219                 ircHostname += "/member";
       
   220             else
       
   221                 ircHostname += "/player";
       
   222 
       
   223             updateIrcId();
       
   224         }
       
   225 
       
   226         private void updateIrcId() {
       
   227             ircId = ircNick + "!~" + ircNick + "@" + ircHostname;
       
   228         }
       
   229     }
       
   230 
       
   231     public String hw404NickToIrcId(String nick) {
       
   232         nick = hwToIrcNick(nick);
       
   233         return nick + "!~" + nick + "@hw/404";
       
   234     }
       
   235 
       
   236     // hash tables are thread-safe
       
   237     private final Map<String,  Player>  allPlayers = new Hashtable<String,  Player>();
       
   238     private final Map<String,  Player> roomPlayers = new Hashtable<String,  Player>();
       
   239     private final Map<Integer, Room>   roomsById   = new Hashtable<Integer, Room>();
       
   240     private final Map<String,  Room>   roomsByName = new Hashtable<String,  Room>();
       
   241     private final List<Room> roomsSorted = new Vector<Room>();
       
   242 
       
   243     private final List<String> ircPingQueue = new Vector<String>();
       
   244 
       
   245     private static final String DEFAULT_SERVER_HOST = "netserver.hedgewars.org";
       
   246     private static String SERVER_HOST = DEFAULT_SERVER_HOST;
       
   247     private static int IRC_PORT = 46667;
       
   248     
       
   249     private String hostname;
       
   250 
       
   251     private static final String LOBBY_CHANNEL_NAME = "#lobby";
       
   252     private static final String  ROOM_CHANNEL_NAME = "#room";
       
   253 
       
   254     // hack
       
   255     // TODO: ,
       
   256     private static final char MAGIC_SPACE   = ' ';
       
   257     private static final char MAGIC_ATSIGN  = '៙';
       
   258     private static final char MAGIC_PERCENT = '%';
       
   259     private static final char MAGIC_PLUS    = '+';
       
   260     private static final char MAGIC_EXCLAM  = '❢';
       
   261     private static final char MAGIC_COMMA   = ',';
       
   262     private static final char MAGIC_COLON   = ':';
       
   263 
       
   264     private static String hwToIrcNick(final String nick) {
       
   265         return nick
       
   266             .replace(' ', MAGIC_SPACE)
       
   267             .replace('@', MAGIC_ATSIGN)
       
   268             .replace('%', MAGIC_PERCENT)
       
   269             .replace('+', MAGIC_PLUS)
       
   270             .replace('!', MAGIC_EXCLAM)
       
   271             .replace(',', MAGIC_COMMA)
       
   272             .replace(':', MAGIC_COLON)
       
   273             ;
       
   274     }
       
   275     private static String ircToHwNick(final String nick) {
       
   276         return nick
       
   277             .replace(MAGIC_COLON,   ':')
       
   278             .replace(MAGIC_COMMA,   ',')
       
   279             .replace(MAGIC_EXCLAM,  '!')
       
   280             .replace(MAGIC_PLUS,    '+')
       
   281             .replace(MAGIC_PERCENT, '%')
       
   282             .replace(MAGIC_ATSIGN,  '@')
       
   283             .replace(MAGIC_SPACE,   ' ')
       
   284             ;
       
   285     }
       
   286 
       
   287     private ProtocolConnection hwcon;
       
   288     private boolean joined = false;
       
   289     private boolean ircJoined = false;
       
   290 
       
   291     private void collectFurtherInfo() {
       
   292         hwcon.sendPing();
       
   293         hwcon.processNextClientFlagsMessages();
       
   294     }
       
   295 
       
   296     public void onPing() {
       
   297         send("PING :" + globalServerName);
       
   298     }
       
   299 
       
   300     public void onPong() {
       
   301         if (!ircPingQueue.isEmpty())
       
   302                 send(":" + globalServerName + " PONG " + globalServerName
       
   303                         + " :" + ircPingQueue.remove(0));
       
   304             
       
   305     }
       
   306 
       
   307     public void onConnectionLoss() {
       
   308         quit("Connection Loss");
       
   309     }
       
   310 
       
   311     public void onDisconnect(final String reason) {
       
   312         quit(reason);
       
   313     }
       
   314 
       
   315     public String onPasswordHashNeededForAuth() {
       
   316         return passwordHash;
       
   317     }
       
   318 
       
   319     public void onMalformedMessage(String contents)
       
   320     {
       
   321         this.logError("MALFORMED MESSAGE: " + contents);
       
   322     }
       
   323 
       
   324     public void onChat(final String user, final String message) {
       
   325         String ircId;
       
   326         Player player = allPlayers.get(user);
       
   327         if (player == null) {
       
   328             // fake user - so probably a notice
       
   329             sendChannelNotice(message, hwToIrcNick(user));
       
   330             //logWarning("onChat(): Couldn't find player with specified nick! nick: " + user);
       
   331             //send(":" + hw404NickToIrcId(user) + " PRIVMSG "
       
   332                      //+ LOBBY_CHANNEL_NAME + " :" + hwActionToIrc(message));
       
   333         }
       
   334         else
       
   335             send(":" + player.getIrcId() + " PRIVMSG "
       
   336                      + LOBBY_CHANNEL_NAME + " :" + hwActionToIrc(message));
       
   337     }
       
   338 
       
   339     public void onWelcomeMessage(final String message) {
       
   340     }
       
   341 
       
   342     public void onNotice(int number) {
       
   343     }
       
   344 
       
   345     public void onBanListEntry(BanType type, String target, String duration, String reason) {
       
   346         // TODO
       
   347     }
       
   348     public void onBanListEnd() {
       
   349         // TODO
       
   350     }
       
   351 
       
   352     public String onNickCollision(final String nick) {
       
   353         return nick + "_";
       
   354     }
       
   355 
       
   356     public void onNickSet(final String nick) {
       
   357         final String newNick = hwToIrcNick(nick);
       
   358         // tell irc client
       
   359         send(":" + ownIrcNick + "!~" + username + "@"
       
   360                             + hostname + " NICK :" + nick);
       
   361         ownIrcNick = newNick;
       
   362         updateLogPrefix();
       
   363         logInfo("Nickname set to " + nick);
       
   364     }
       
   365 
       
   366     private void flagAsInLobby(final Player player) {
       
   367         if (!ircJoined)
       
   368             return;
       
   369         final String ircNick = player.ircNick;
       
   370         if (player.isServerAdmin())
       
   371             send(":room-part!~@~ MODE " + LOBBY_CHANNEL_NAME + " -h+o " + ircNick + " " + ircNick);
       
   372         //else
       
   373             //send(":room-part!~@~ MODE " + LOBBY_CHANNEL_NAME + " +v " + ircNick);
       
   374     }
       
   375 
       
   376     private void flagAsInRoom(final Player player) {
       
   377         if (!ircJoined)
       
   378             return;
       
   379         final String ircNick = player.ircNick;
       
   380         if (player.isServerAdmin())
       
   381             send(":room-join!~@~ MODE " + LOBBY_CHANNEL_NAME + " -o+h " + ircNick + " " + ircNick);
       
   382         //else
       
   383             //send(":room-join!~@~ MODE " + LOBBY_CHANNEL_NAME + " -v " + ircNick);
       
   384     }
       
   385 
       
   386 // TODO somewhere: escape char for magic chars!
       
   387 
       
   388 // TODO /join with playername => FOLLOW :D
       
   389 
       
   390     public void sendPlayerMode(final Player player) {
       
   391         char c;
       
   392         if (player.isServerAdmin())
       
   393             c = player.inRoom?'h':'o';
       
   394         else if (player.isRegistered())
       
   395             c = 'v';
       
   396         else
       
   397             // no mode
       
   398             return;
       
   399 
       
   400         send(":server-join!~@~ MODE " + LOBBY_CHANNEL_NAME + " +" + c + " " + player.ircNick);
       
   401     }
       
   402 
       
   403     private Player ownPlayer = null;
       
   404 
       
   405     public void onLobbyJoin(final String[] users) {
       
   406 
       
   407         final List<Player> newPlayers = new ArrayList<Player>(users.length);
       
   408 
       
   409         // process joins
       
   410         for (final String user : users) {
       
   411             final Player player = new Player(user);
       
   412             if (ownPlayer == null)
       
   413                 ownPlayer = player;
       
   414             newPlayers.add(player);
       
   415             allPlayers.put(user, player);
       
   416         }
       
   417 
       
   418         // make sure we get the client flags before we announce anything
       
   419         collectFurtherInfo();
       
   420 
       
   421         // get player info
       
   422         // NOTE: if player is in room, then info was already retrieved
       
   423         for (final Player player : newPlayers) {
       
   424             if (!player.inRoom)
       
   425                 hwcon.requestInfo(player.nick);
       
   426         }
       
   427 
       
   428         /* DISABLED - we'll announce later - when receiving info
       
   429         // announce joins
       
   430         if (ircJoined) {
       
   431             for (final Player player : newPlayers) {
       
   432                 final String ircId = player.getIrcId();
       
   433                 send(":" + ircId
       
   434                          + " JOIN "+ lobbyChannel.name);
       
   435                 sendPlayerMode(player);
       
   436             }
       
   437         }
       
   438         */
       
   439         if (!ircJoined) {
       
   440             // don't announced players that were there before join already
       
   441             for (final Player player : newPlayers) {
       
   442                 player.setAnnounced();
       
   443             }
       
   444         }
       
   445 
       
   446         if (!joined) {
       
   447             joined = true;
       
   448             // forget password hash, we don't need it anymore.
       
   449             passwordHash = "";
       
   450             logInfo("Hedgewars server/lobby joined.");
       
   451             sendSelfNotice("Hedgewars server was joined successfully");
       
   452             // do this after join so that rooms can be assigned to their owners
       
   453             hwcon.requestRoomsList();
       
   454         }
       
   455     }
       
   456 
       
   457     private void makeIrcJoinLobby() {
       
   458             sendGlobal("INVITE " + ownIrcNick + " " + LOBBY_CHANNEL_NAME);
       
   459             try{Thread.sleep(3000);}catch(Exception e){}
       
   460             join(lobbyChannel.name);
       
   461             sendSelfNotice("Joining lobby-channel: " + lobbyChannel.name);
       
   462     }
       
   463 
       
   464     private void announcePlayerJoinLobby(final Player player) {
       
   465             player.setAnnounced();
       
   466             send(":" + player.getIrcId()
       
   467                      + " JOIN "+ lobbyChannel.name);
       
   468             sendPlayerMode(player);
       
   469     }
       
   470 
       
   471     public void onLobbyLeave(final String user, final String reason) {
       
   472         final Player player = allPlayers.get(user);
       
   473         if (player == null) {
       
   474             logWarning("onLobbyLeave(): Couldn't find player with specified nick! nick: " + user);
       
   475             sendIfJoined(":" + hw404NickToIrcId(user)
       
   476                  + " PART " + lobbyChannel.name + " " + reason);
       
   477         }
       
   478         else {
       
   479             if (ircJoined && player.needsAnnounce())
       
   480                 announcePlayerJoinLobby(player);
       
   481             sendIfJoined(":" + player.getIrcId()
       
   482                  + " PART " + lobbyChannel.name + " " + reason);
       
   483             allPlayers.remove(user);
       
   484         }
       
   485     }
       
   486 
       
   487     private int lastRoomId = 0;
       
   488 
       
   489     public void onRoomInfo(final String name, final String flags,
       
   490                            final String newName, final int nUsers,
       
   491                            final int nTeams, final String owner,
       
   492                            final String map, final String style,
       
   493                            final String scheme, final String weapons) {
       
   494 
       
   495         Room room = roomsByName.get(name);
       
   496 
       
   497         if (room == null) {
       
   498             // try to reuse old ids
       
   499             if (lastRoomId >= 90)
       
   500                 lastRoomId = 9;
       
   501 
       
   502             // search for first free
       
   503             while(roomsById.containsKey(++lastRoomId)) { }
       
   504 
       
   505             room = new Room(lastRoomId, newName, owner);
       
   506             roomsById.put(lastRoomId, room);
       
   507             roomsByName.put(newName, room);
       
   508             roomsSorted.add(room);
       
   509         }
       
   510         else if (!room.name.equals(newName)) {
       
   511             room.name = newName;
       
   512             roomsByName.put(newName, roomsByName.remove(name));
       
   513         }
       
   514 
       
   515         // update data
       
   516         room.setOwner(owner);
       
   517         room.nPlayers = nUsers;
       
   518         room.nTeams = nTeams;
       
   519     }
       
   520 
       
   521     public void onRoomDel(final String name) {
       
   522         final Room room = roomsByName.remove(name);
       
   523 
       
   524         if (room != null) {
       
   525             roomsById.remove(room.id);
       
   526             roomsSorted.remove(room);
       
   527         }
       
   528     }
       
   529 
       
   530     public void onRoomJoin(final String[] users) {
       
   531     }
       
   532 
       
   533     public void onRoomLeave(final String[] users) {
       
   534     }
       
   535 
       
   536     // TODO vector that remembers who's info was requested for manually
       
   537     List<String> requestedInfos =  new Vector<String>();
       
   538 
       
   539     public void onUserInfo(final String user, final String ip, final String version, final String room) {
       
   540         Player player = allPlayers.get(user);
       
   541         if (player != null) {
       
   542             player.setInfo(ip, version, room);
       
   543             if (ircJoined) {
       
   544                 if (player.needsAnnounce())
       
   545                     announcePlayerJoinLobby(player);
       
   546             }
       
   547             else {
       
   548                 if (player == ownPlayer) {
       
   549                     
       
   550                     makeIrcJoinLobby();
       
   551                 }
       
   552             }
       
   553         }
       
   554 
       
   555         // if MANUAL send notice
       
   556         if (requestedInfos.remove(user)) {
       
   557             final String nick = hwToIrcNick(user);
       
   558             sendServerNotice(nick + " - " + buildInfoString(ip, version, room));
       
   559         }
       
   560     }
       
   561 
       
   562     public void onUserFlagChange(final String user, final UserFlagType flag, final boolean newValue) {
       
   563         final Player player = allPlayers.get(user);
       
   564         if (player == null) {
       
   565             logError("onUserFlagChange(): Couldn't find player with specified nick! nick: " + user);
       
   566             return;
       
   567         }
       
   568         switch (flag) {
       
   569             case ADMIN:
       
   570                 player.setServerAdmin(newValue);
       
   571                 if (newValue) {
       
   572                     logDebug(user + " is server admin");
       
   573                     //sendIfJoined(":server!~@~ MODE " + LOBBY_CHANNEL_NAME + " -v+o " + player.ircNick + " " + player.ircNick);
       
   574                 }
       
   575                 break;
       
   576             case INROOM:
       
   577                 player.inRoom = newValue;
       
   578                 if (newValue) {
       
   579                     flagAsInRoom(player);
       
   580                     logDebug(user + " entered a room");
       
   581                     // get new room info
       
   582                     hwcon.requestInfo(player.nick);
       
   583                 }
       
   584                 else {
       
   585                     flagAsInLobby(player);
       
   586                     logDebug(user + " returned to lobby");
       
   587                     player.inRoom = false;
       
   588                 }
       
   589                 break;
       
   590             case REGISTERED:
       
   591                 player.setRegistered(newValue);
       
   592                 break;
       
   593             default: break;
       
   594         }
       
   595     }
       
   596 
       
   597     public class Channel
       
   598     {
       
   599         private String topic;
       
   600         private final String name;
       
   601         private final Map<String, Player> players;
       
   602 
       
   603         public Channel(final String name, final String topic, final Map<String, Player> players) {
       
   604             this.name = name;
       
   605             this.topic = topic;
       
   606             this.players = players;
       
   607         }
       
   608     }
       
   609 
       
   610     public void logInfo(final String message) {
       
   611         System.out.println(this.logPrefix + ": " + message);
       
   612     }
       
   613 
       
   614     public void logDebug(final String message) {
       
   615         System.out.println(this.logPrefix + "| " + message);
       
   616     }
       
   617 
       
   618     public void logWarning(final String message) {
       
   619         System.err.println(this.logPrefix + "? " + message);
       
   620     }
       
   621 
       
   622     public void logError(final String message) {
       
   623         System.err.println(this.logPrefix + "! " + message);
       
   624     }
       
   625 
       
   626 
       
   627     //private static final Object mutex = new Object();
       
   628     private boolean joinSent = false;
       
   629     private Socket socket;
       
   630     private String username;
       
   631     private String ownIrcNick;
       
   632     private String description;
       
   633     private static Map<String, Connection> connectionMap = new HashMap<String, Connection>();
       
   634     // TODO those MUST NOT be static!
       
   635     //private Map<String, Channel> channelMap = new HashMap<String, Channel>();
       
   636     private final Channel lobbyChannel;
       
   637     private static String globalServerName;
       
   638     private String logPrefix;
       
   639     private final String clientId;
       
   640     private String passwordHash = "";
       
   641 
       
   642     private final Connection thisConnection;
       
   643 
       
   644     public Connection(Socket socket, final String clientId) throws Exception
       
   645     {
       
   646         this.ownIrcNick = "NONAME";
       
   647         this.socket = socket;
       
   648         this.hostname = ((InetSocketAddress)socket.getRemoteSocketAddress())
       
   649                  .getAddress().getHostAddress();
       
   650         this.clientId = clientId;
       
   651         updateLogPrefix();
       
   652         thisConnection = this;
       
   653         logInfo("New Connection");
       
   654 
       
   655         this.hwcon = null;
       
   656 
       
   657         try {
       
   658             this.hwcon = new ProtocolConnection(this, SERVER_HOST);
       
   659             logInfo("Connection to " + SERVER_HOST + " established.");
       
   660         }
       
   661         catch(Exception ex) {
       
   662             final String errmsg = "Could not connect to " + SERVER_HOST + ": "
       
   663                 + ex.getMessage();
       
   664             logError(errmsg);
       
   665             sendQuit(errmsg);
       
   666         }
       
   667 
       
   668         final String lobbyTopic = " # " + SERVER_HOST + " - HEDGEWARS SERVER LOBBY # ";
       
   669         this.lobbyChannel = new Channel(LOBBY_CHANNEL_NAME, lobbyTopic, allPlayers);
       
   670 
       
   671         // start in new thread
       
   672         if (hwcon != null) {
       
   673             (this.hwcon.processMessages(true)).start();
       
   674         }
       
   675     }
       
   676     
       
   677     private void updateLogPrefix() {
       
   678         if (ownIrcNick == null)
       
   679             this.logPrefix = clientId + " ";
       
   680         else
       
   681             this.logPrefix = clientId + " [" + ownIrcNick + "] ";
       
   682     }
       
   683 
       
   684     private void setNick(final String nick) {
       
   685         if (passwordHash.isEmpty()) {
       
   686             try {
       
   687               final Properties authProps = new Properties();
       
   688               final String authFile = this.hostname + ".auth";
       
   689               logInfo("Attempting to load auth info from " + authFile);
       
   690               authProps.load(new FileInputStream(authFile));
       
   691               passwordHash = authProps.getProperty(nick, "");
       
   692               if (passwordHash.isEmpty())
       
   693                 logInfo("Auth info file didn't contain any password hash for: " + nick);
       
   694             } catch (IOException e) {
       
   695                 logInfo("Auth info file couldn't be loaded.");
       
   696             }
       
   697         }
       
   698 
       
   699         // append _ just in case
       
   700         if (!passwordHash.isEmpty() || nick.endsWith("_")) {
       
   701             ownIrcNick = nick;
       
   702             hwcon.setNick(ircToHwNick(nick));
       
   703         }
       
   704         else {
       
   705             final String nick_ = nick + "_";
       
   706             ownIrcNick = nick_;
       
   707             hwcon.setNick(ircToHwNick(nick_));
       
   708         }
       
   709     }
       
   710 
       
   711     public String getRepresentation()
       
   712     {
       
   713         return ownIrcNick + "!~" + username + "@" + hostname;
       
   714     }
       
   715 
       
   716     private static int lastClientId = 0;
       
   717 
       
   718     /**
       
   719      * @param args
       
   720      */
       
   721     public static void main(String[] args) throws Throwable
       
   722     {
       
   723         if (args.length > 0)
       
   724         {
       
   725             SERVER_HOST = args[0];
       
   726         }
       
   727         if (args.length > 1)
       
   728         {
       
   729             IRC_PORT = Integer.parseInt(args[1]);
       
   730         }
       
   731 
       
   732         globalServerName = "hw2irc";
       
   733 
       
   734         if (!SERVER_HOST.equals(DEFAULT_SERVER_HOST))
       
   735             globalServerName += "~" + SERVER_HOST;
       
   736 
       
   737         final int port = IRC_PORT;
       
   738         ServerSocket ss = new ServerSocket(port);
       
   739         System.out.println("Listening on port " + port);
       
   740         while (true)
       
   741         {
       
   742             Socket s = ss.accept();
       
   743             final String clientId = "client" + (++lastClientId) + '-'
       
   744                  + ((InetSocketAddress)s.getRemoteSocketAddress())
       
   745                  .getAddress().getHostAddress();
       
   746             try {
       
   747                 Connection clientCon = new Connection(s, clientId);
       
   748                 //clientCon.run();
       
   749                 Thread clientThread = new Thread(clientCon, clientId);
       
   750                 clientThread.start();
       
   751             }
       
   752             catch (Exception ex) {
       
   753                 System.err.println("FATAL: Server connection thread " + clientId + " crashed on startup! " + ex.getMessage());
       
   754                 ex.printStackTrace();
       
   755             }
       
   756 
       
   757             System.out.println("Note: Not accepting new clients for the next " + SLEEP_BETWEEN_LOGIN_DURATION + "s, trying to avoid reconnecting too quickly.");
       
   758             Thread.sleep(SLEEP_BETWEEN_LOGIN_DURATION * 1000);
       
   759             System.out.println("Note: Accepting clients again!");
       
   760         }
       
   761     }
       
   762 
       
   763     private static final int SLEEP_BETWEEN_LOGIN_DURATION = 122;
       
   764 
       
   765     private boolean hasQuit = false;
       
   766 
       
   767     public synchronized void quit(final String reason) {
       
   768         if (hasQuit)
       
   769             return;
       
   770 
       
   771         hasQuit = true;
       
   772         // disconnect from hedgewars server
       
   773         if (hwcon != null)
       
   774             hwcon.disconnect(reason);
       
   775         // disconnect irc client
       
   776         sendQuit("Quit: " + reason);
       
   777         // wait some time so that last data can be pushed
       
   778         try {
       
   779             Thread.sleep(200);
       
   780         }
       
   781         catch (Exception e) { }
       
   782         // terminate
       
   783         terminateConnection = true;
       
   784     }
       
   785 
       
   786 
       
   787     private static String hwActionToIrc(final String chatMsg) {
       
   788         if (!chatMsg.startsWith("/me ") || (chatMsg.length() <= 4))
       
   789             return chatMsg;
       
   790 
       
   791         return MAGIC_BYTE_ACTION + "ACTION " + chatMsg.substring(4) + MAGIC_BYTE_ACTION;
       
   792     }
       
   793 
       
   794     private static String ircActionToHw(final String chatMsg) {
       
   795         if (!chatMsg.startsWith(MAGIC_BYTE_ACTION + "ACTION ") || (chatMsg.length() <= 9))
       
   796             return chatMsg;
       
   797 
       
   798         return "/me " + chatMsg.substring(8, chatMsg.length() - 1);
       
   799     }
       
   800 
       
   801 // TODO: why is still still being called when joining bogus channel name?
       
   802     public void join(String channelName)
       
   803     {
       
   804         if (ownPlayer == null) {
       
   805             sendSelfNotice("Trying to join while ownPlayer == null. Aborting!");
       
   806             quit("Something went horribly wrong.");
       
   807             return;
       
   808         }
       
   809 
       
   810 
       
   811         final Channel channel = getChannel(channelName);
       
   812 
       
   813         // TODO reserve special char for creating a new ROOM
       
   814         // it will be named after the player name by default
       
   815         // can be changed with /topic after
       
   816 
       
   817         // not a valid channel
       
   818         if (channel == null) {
       
   819             sendSelfNotice("You cannot manually create channels here.");
       
   820             sendGlobal(ERR_NOSUCHCHANNEL + ownIrcNick + " " + channel.name
       
   821                     + " :No such channel");
       
   822             return;
       
   823         }
       
   824 
       
   825         // TODO if inRoom "Can't join rooms while still in room"
       
   826 
       
   827         // TODO set this based on room host/admin mode maybe
       
   828 
       
   829 /* :testuser2131!~r@asdasdasdasd.at JOIN #asdkjasda
       
   830 :weber.freenode.net MODE #asdkjasda +ns
       
   831 :weber.freenode.net 353 testuser2131 @ #asdkjasda :@testuser2131
       
   832 :weber.freenode.net 366 testuser2131 #asdkjasda :End of /NAMES list.
       
   833 :weber.freenode.net NOTICE #asdkjasda :[freenode-info] why register and identify? your IRC nick is how people know you. http://freenode.net/faq.shtml#nicksetup
       
   834 
       
   835 */ 
       
   836         send(":" + ownPlayer.getIrcId() + " JOIN "
       
   837          + channelName);
       
   838 
       
   839         //send(":sheeppidgin!~r@localhost JOIN " + channelName);
       
   840 
       
   841         ircJoined = true;
       
   842 
       
   843         sendGlobal(":hw2irc MODE #lobby +nt");
       
   844 
       
   845         sendTopic(channel);
       
   846 
       
   847         sendNames(channel);
       
   848 
       
   849     }
       
   850 
       
   851     private void sendTopic(final Channel channel) {
       
   852         if (channel.topic != null)
       
   853             sendGlobal(RPL_TOPIC + ownIrcNick + " " + channel.name
       
   854                     + " :" + channel.topic);
       
   855         else
       
   856             sendGlobal(RPL_NOTOPIC + ownIrcNick + " " + channel.name
       
   857                     + " :No topic is set");
       
   858     }
       
   859 
       
   860     private void sendNames(final Channel channel) {
       
   861         // There is no error reply for bad channel names.
       
   862 
       
   863         if (channel != null) {
       
   864             // send player list
       
   865             for (final Player player : channel.players.values()) {
       
   866 
       
   867                 final String prefix;
       
   868 
       
   869                 if (player.isServerAdmin())
       
   870                     prefix = (player.isServerAdmin())?"@":"%";
       
   871                 else
       
   872                     prefix = (player.isRegistered())?"+":"";
       
   873 
       
   874                 sendGlobal(RPL_NAMREPLY + ownIrcNick + " = " + channel.name
       
   875                         + " :" + prefix + player.ircNick);
       
   876             }
       
   877         }
       
   878 
       
   879         sendGlobal(RPL_ENDOFNAMES + ownIrcNick + " " + channel.name
       
   880                 + " :End of /NAMES list");
       
   881     }
       
   882 
       
   883     private void sendList(final String filter) {
       
   884         // id column size
       
   885         //int idl = 1 + String.valueOf(lastRoomId).length();
       
   886 
       
   887         //if (idl < 3)
       
   888             //idl = 3;
       
   889 
       
   890         // send rooms list
       
   891         sendGlobal(RPL_LISTSTART + ownIrcNick 
       
   892             //+ String.format(" %1$" + idl + "s  #P  #T  Name", "ID"));
       
   893             + String.format(" %1$s #P #T Name", "ID"));
       
   894 
       
   895         if (filter.isEmpty() || filter.equals(".")) {
       
   896             // lobby
       
   897             if (filter.isEmpty())
       
   898                 sendGlobal(RPL_LIST + ownIrcNick + " " + LOBBY_CHANNEL_NAME
       
   899                     + " " + allPlayers.size() + " :" + lobbyChannel.topic);
       
   900 
       
   901             // room list could be changed by server while we reply client
       
   902             synchronized (roomsSorted) {
       
   903                 for (final Room room : roomsSorted) {
       
   904                     sendGlobal(RPL_LIST + ownIrcNick
       
   905                         //+ String.format(" %1$" + idl + "s  %2$2d  :%3$2d  %4$s",
       
   906                         + String.format(" %1$s %2$d :%3$d  %4$s",
       
   907                             room.chan, room.nPlayers, room.nTeams, room.name));
       
   908                 }
       
   909             }
       
   910         }
       
   911         // TODO filter
       
   912 
       
   913         sendGlobal(RPL_LISTEND + ownIrcNick + " " + " :End of /LIST");
       
   914     }
       
   915 
       
   916     private List<Player> findPlayers(final String expr) {
       
   917         List<Player> matches = new ArrayList<Player>(allPlayers.size());
       
   918 
       
   919         try {
       
   920             final int flags = Pattern.CASE_INSENSITIVE + Pattern.UNICODE_CASE;
       
   921             final Pattern regx = Pattern.compile(expr, flags);
       
   922 
       
   923             for (final Player p : allPlayers.values()) {
       
   924                 if ((regx.matcher(p.nick).find())
       
   925                     || (regx.matcher(p.ircId).find())
       
   926                     //|| (regx.matcher(p.version).find())
       
   927                     //|| ((p.ip.length() > 2) && regx.matcher(p.ip).find())
       
   928                     || (!p.getRoom().isEmpty() && regx.matcher(p.getRoom()).find())
       
   929                 ) matches.add(p);
       
   930             }
       
   931         }
       
   932         catch(PatternSyntaxException ex) {
       
   933             sendSelfNotice("Pattern not understood: " + ex.getMessage());
       
   934         }
       
   935 
       
   936         return matches;
       
   937     }
       
   938 
       
   939     private String buildInfoString(final String ip, final String version, final String room) {
       
   940         return (ip.equals("[]")?"":ip + " ") + version + (room.isEmpty()?"":" " + room);
       
   941     }
       
   942 
       
   943     private void sendWhoForPlayer(final Player player) {
       
   944         sendWhoForPlayer(LOBBY_CHANNEL_NAME, player.ircNick, (player.inRoom?player.getRoom():""), player.getIrcHostname());
       
   945     }
       
   946 
       
   947     private void sendWhoForPlayer(final Player player, final String info) {
       
   948         sendWhoForPlayer(LOBBY_CHANNEL_NAME, player.ircNick, info, player.getIrcHostname());
       
   949     }
       
   950 
       
   951     private void sendWhoForPlayer(final String nick, final String info) {
       
   952         sendWhoForPlayer(LOBBY_CHANNEL_NAME, nick, info);
       
   953     }
       
   954 
       
   955     private void sendWhoForPlayer(final String channel, final String nick, final String info) {
       
   956         final Player player = allPlayers.get(nick);
       
   957 
       
   958         if (player == null)
       
   959             sendWhoForPlayer("OFFLINE", hwToIrcNick(nick), info, "hw/offline");
       
   960         else
       
   961             sendWhoForPlayer(channel,   player.ircNick,    info, player.getIrcHostname());
       
   962     }
       
   963 
       
   964     private void sendWhoForPlayer(final String channel, final String ircNick, final String info, final String hostname) {
       
   965         sendGlobal(RPL_WHOREPLY + channel + " " + channel
       
   966                             + " ~" + ircNick + " " + hostname
       
   967                             + " " + globalServerName + " " + ircNick
       
   968                             + " H :0 " + info);
       
   969     }
       
   970 
       
   971     private void sendWhoEnd(final String ofWho) {
       
   972         send(RPL_ENDOFWHO + ownIrcNick + " " + ofWho
       
   973                         + " :End of /WHO list.");
       
   974     }
       
   975 
       
   976     private void sendMotd() {
       
   977         sendGlobal(RPL_MOTDSTART + ownIrcNick + " :- Message of the Day -");
       
   978         final String mline = RPL_MOTD + ownIrcNick + " :";
       
   979         for(final String line : DEFAULT_MOTD) {
       
   980             sendGlobal(mline + line);
       
   981         }
       
   982         sendGlobal(RPL_ENDOFMOTD + ownIrcNick + " :End of /MOTD command.");
       
   983     }
       
   984 
       
   985     private Channel getChannel(final String name) {
       
   986         if (name.equals(LOBBY_CHANNEL_NAME)) {
       
   987             return lobbyChannel;
       
   988         }
       
   989 
       
   990         return null;
       
   991     }
       
   992 
       
   993     private enum Command
       
   994     {
       
   995         PASS(1, 1)
       
   996         {
       
   997             @Override
       
   998             public void run(final Connection con, final String prefix, final String[] args)
       
   999                     throws Exception
       
  1000             {
       
  1001                 con.passwordHash = args[0];
       
  1002             }
       
  1003         },
       
  1004         NICK(1, 1)
       
  1005         {
       
  1006             @Override
       
  1007             public void run(final Connection con, final String prefix, final String[] args)
       
  1008                     throws Exception
       
  1009             {
       
  1010                 con.setNick(args[0]);
       
  1011             }
       
  1012         },
       
  1013         USER(1, 4)
       
  1014         {
       
  1015             @Override
       
  1016             public void run(final Connection con, final String prefix, final String[] args)
       
  1017                     throws Exception
       
  1018             {
       
  1019                 if (con.username != null)
       
  1020                 {
       
  1021                     con.send("NOTICE AUTH :You can't change your user "
       
  1022                             + "information after you've logged in right now.");
       
  1023                     return;
       
  1024                 }
       
  1025                 con.username = args[0];
       
  1026                 String forDescription = args.length > 3 ? args[3]
       
  1027                         : "(no description)";
       
  1028                 con.description = forDescription;
       
  1029                 /*
       
  1030                  * Now we'll send the user their initial information.
       
  1031                  */
       
  1032                 con.sendGlobal(RPL_WELCOME + con.ownIrcNick + " :Welcome to "
       
  1033                         + globalServerName + " - " + DESCRIPTION_SHORT);
       
  1034                 con.sendGlobal("004 " + con.ownIrcNick + " " + globalServerName
       
  1035                         + " " + VERSION);
       
  1036 
       
  1037                 con.sendMotd();
       
  1038 
       
  1039             }
       
  1040         },
       
  1041         MOTD(0, 0)
       
  1042         {
       
  1043             @Override
       
  1044             public void run(final Connection con, final String prefix, final String[] args)
       
  1045                     throws Exception
       
  1046             {
       
  1047                 con.sendMotd();
       
  1048             }
       
  1049         },
       
  1050         PING(1, 1)
       
  1051         {
       
  1052             @Override
       
  1053             public void run(final Connection con, final String prefix, final String[] args)
       
  1054                     throws Exception
       
  1055             {
       
  1056                 con.ircPingQueue.add(args[0]);
       
  1057                 con.hwcon.sendPing();
       
  1058             }
       
  1059         },
       
  1060         PONG(1, 2)
       
  1061         {
       
  1062             @Override
       
  1063             public void run(final Connection con, final String prefix, final String[] args)
       
  1064                     throws Exception
       
  1065             {
       
  1066                 con.hwcon.sendPong();
       
  1067             }
       
  1068         },
       
  1069         NAMES(1, 1)
       
  1070         {
       
  1071             @Override
       
  1072             public void run(final Connection con, final String prefix, final String[] args)
       
  1073                     throws Exception
       
  1074             {
       
  1075                 final Channel channel = con.getChannel(args[0]);
       
  1076                 con.sendNames(channel);
       
  1077             }
       
  1078         },
       
  1079         LIST(0, 2)
       
  1080         {
       
  1081             @Override
       
  1082             public void run(final Connection con, final String prefix, final String[] args)
       
  1083                     throws Exception
       
  1084             {
       
  1085                 // TODO filter by args[1] (comma sep list of chans), make # optional
       
  1086                 // ignore args[1] (server), TODO: maybe check and send RPL_NOSUCHSERVER if wrong
       
  1087                 con.sendList(args.length > 0?args[0]:"");
       
  1088             }
       
  1089         },
       
  1090         JOIN(1, 2)
       
  1091         {
       
  1092             @Override
       
  1093             public void run(final Connection con, final String prefix, final String[] args)
       
  1094                     throws Exception
       
  1095             {
       
  1096                 if (args.length < 1)  {
       
  1097                     con.sendSelfNotice("You didn't specify what you want to join!");
       
  1098                     return;
       
  1099                 }
       
  1100 
       
  1101                 if (con.ownPlayer == null) {
       
  1102                     con.sendSelfNotice("Lobby is not ready to be joined yet - hold on a second!");
       
  1103                     return;
       
  1104                 }
       
  1105 
       
  1106                 if (args[0].equals(LOBBY_CHANNEL_NAME)) {
       
  1107                     //con.sendSelfNotice("Lobby can't be joined manually!");
       
  1108                     con.join(LOBBY_CHANNEL_NAME);
       
  1109                     return;
       
  1110                 }
       
  1111                 con.sendSelfNotice("Joining rooms is not supported yet, sorry!");
       
  1112             }
       
  1113         },
       
  1114         WHO(0, 2)
       
  1115         {
       
  1116             @Override
       
  1117             public void run(final Connection con, final String prefix, final String[] args)
       
  1118                     throws Exception
       
  1119             {
       
  1120                 if (args.length < 1)
       
  1121                     return;
       
  1122 
       
  1123                 final String target = args[0];
       
  1124 
       
  1125                 Map<String, Player> players = null;
       
  1126 
       
  1127                 if (target.equals(LOBBY_CHANNEL_NAME)) {
       
  1128                     players = con.allPlayers;
       
  1129                 }
       
  1130                 // on channel join WHO is called on channel
       
  1131                 else if (target.equals(ROOM_CHANNEL_NAME)) {
       
  1132                     players = con.roomPlayers;
       
  1133                 }
       
  1134 
       
  1135                 if (players != null) {
       
  1136                     for (final Player player : players.values()) {
       
  1137                         con.sendWhoForPlayer(player);
       
  1138                     }
       
  1139                 }
       
  1140                 // not a known channel. assume arg is player name
       
  1141                 // TODO support search expressions!
       
  1142                 else {
       
  1143                     final String nick = ircToHwNick(target);
       
  1144                     final Player player = con.allPlayers.get(nick);
       
  1145                     if (player != null)
       
  1146                         con.sendWhoForPlayer(player);
       
  1147                     else {
       
  1148                         con.sendSelfNotice("WHO: No player named " + nick + ", interpreting term as pattern.");
       
  1149                         List<Player> matches = con.findPlayers(target);
       
  1150                         if (matches.isEmpty())
       
  1151                             con.sendSelfNotice("No Match.");
       
  1152                         else {
       
  1153                             for (final Player match : matches) {
       
  1154                                 con.sendWhoForPlayer(match);
       
  1155                             }
       
  1156                         }
       
  1157                     }
       
  1158                 }
       
  1159 
       
  1160                 con.sendWhoEnd(target);
       
  1161             }
       
  1162         },
       
  1163         WHOIS(1, 2)
       
  1164         {
       
  1165             @Override
       
  1166             public void run(final Connection con, final String prefix, final String[] args)
       
  1167                     throws Exception
       
  1168             {
       
  1169                 // there's an optional param in the beginning that we don't care about
       
  1170                 final String targets = args[args.length-1];
       
  1171                 for (final String target : targets.split(",")) {
       
  1172                     if (target.isEmpty())
       
  1173                         continue;
       
  1174                     final String nick = ircToHwNick(target);
       
  1175                     // flag this nick as manually requested, so that response is shown
       
  1176                     if (con.ircJoined) {
       
  1177                         con.requestedInfos.add(nick);
       
  1178                         con.hwcon.requestInfo(nick);
       
  1179                     }
       
  1180 
       
  1181                     final Player player = con.allPlayers.get(nick);
       
  1182                     if (player != null) {
       
  1183                         con.send(RPL_WHOISUSER + con.ownIrcNick + " " + target + " ~"
       
  1184                                 + target + " " + player.getIrcHostname() + " * : "
       
  1185                                 + player.ircNick);
       
  1186                         // TODO send e.g. channels: @#lobby   or   @#123
       
  1187                         con.send(RPL_ENDOFWHOIS + con.ownIrcNick + " " + target 
       
  1188                                 + " :End of /WHOIS list.");
       
  1189                     }
       
  1190                 }
       
  1191             }
       
  1192         },
       
  1193         USERHOST(1, 5)
       
  1194         {
       
  1195             @Override
       
  1196             public void run(final Connection con, final String prefix, final String[] args)
       
  1197                     throws Exception
       
  1198             {
       
  1199                 /*
       
  1200                 // TODO set server host
       
  1201                 con.hostname = "hw/" + SERVER_HOST;
       
  1202                 
       
  1203                 ArrayList<String> replies = new ArrayList<String>();
       
  1204                 for (String s : arguments)
       
  1205                 {
       
  1206                     Connection user = connectionMap.get(s);
       
  1207                     if (user != null)
       
  1208                         replies.add(user.nick + "=+" + con.ownIrc + "@"
       
  1209                                 + con.hostname);
       
  1210                 }
       
  1211                 con.sendGlobal(RPL_USERHOST + con.ownIrcNick + " :"
       
  1212                         + delimited(replies.toArray(new String[0]), " "));
       
  1213                 */
       
  1214             }
       
  1215         },
       
  1216         MODE(0, 2)
       
  1217         {
       
  1218             @Override
       
  1219             public void run(final Connection con, final String prefix, final String[] args)
       
  1220                     throws Exception
       
  1221             {
       
  1222                 final boolean forChan = args[0].startsWith("#");
       
  1223                 
       
  1224                 if (args.length == 1)
       
  1225                 {
       
  1226                     if (forChan) {
       
  1227                         //con.sendGlobal(ERR_NOCHANMODES + args[0]
       
  1228                         //                + " :Channel doesn't support modes");
       
  1229                         con.sendGlobal(RPL_CHANNELMODEIS + con.ownIrcNick + " " + args[0]
       
  1230                                 + " +nt");
       
  1231                     }
       
  1232                     else
       
  1233                     {
       
  1234                         // TODO
       
  1235                         con.sendSelfNotice("User mode querying not supported yet.");
       
  1236                     }
       
  1237                 }
       
  1238                 else if (args.length == 2) {
       
  1239 
       
  1240                     if (forChan) {
       
  1241 
       
  1242                         final int l = args[1].length();
       
  1243 
       
  1244                         for (int i = 0; i < l; i++) {
       
  1245 
       
  1246                             final char c = args[1].charAt(i);  
       
  1247 
       
  1248                             switch (c) {
       
  1249                                 case '+':
       
  1250                                     // skip
       
  1251                                     break;
       
  1252                                 case '-':
       
  1253                                     // skip
       
  1254                                     break;
       
  1255                                 case 'b':
       
  1256                                     con.sendGlobal(RPL_ENDOFBANLIST
       
  1257                                         + con.ownIrcNick + " " + args[0]
       
  1258                                         + " :End of channel ban list");
       
  1259                                     break;
       
  1260                                 case 'e':
       
  1261                                     con.sendGlobal(RPL_ENDOFEXCEPTLIST
       
  1262                                         + con.ownIrcNick + " " + args[0]
       
  1263                                         + " :End of channel exception list");
       
  1264                                     break;
       
  1265                                 default:
       
  1266                                     con.sendGlobal(ERR_UNKNOWNMODE + c
       
  1267                                         + " :Unknown MODE flag " + c);
       
  1268                                     break;
       
  1269                                     
       
  1270                             }
       
  1271                         }
       
  1272                     }
       
  1273                     // user mode
       
  1274                     else {
       
  1275                         con.sendGlobal(ERR_UMODEUNKNOWNFLAG + args[0]
       
  1276                                         + " :Unknown MODE flag");
       
  1277                     }
       
  1278                 }
       
  1279                 else
       
  1280                 {
       
  1281                     con.sendSelfNotice("Specific modes not supported yet.");
       
  1282                 }
       
  1283             }
       
  1284         },
       
  1285         PART(1, 2)
       
  1286         {
       
  1287             @Override
       
  1288             public void run(final Connection con, final String prefix, final String[] args)
       
  1289                     throws Exception
       
  1290             {
       
  1291                 String[] channels = args[0].split(",");
       
  1292                 boolean doQuit = false;
       
  1293 
       
  1294                 for (String channelName : channels)
       
  1295                 {
       
  1296                     if (channelName.equals(LOBBY_CHANNEL_NAME)) {
       
  1297                         doQuit = true;
       
  1298                     }
       
  1299                     // TODO: part from room
       
  1300                     /*
       
  1301                     synchronized (mutex)
       
  1302                     {
       
  1303                         Channel channel = channelMap.get(channelName);
       
  1304                         if (channelName == null)
       
  1305                             con
       
  1306                                     .sendSelfNotice("You're not a member of the channel "
       
  1307                                             + channelName
       
  1308                                             + ", so you can't part it.");
       
  1309                         else
       
  1310                         {
       
  1311                             channel.send(":" + con.getRepresentation()
       
  1312                                     + " PART " + channelName);
       
  1313                             channel.channelMembers.remove(con);
       
  1314                             if (channel.channelMembers.size() == 0)
       
  1315                                 channelMap.remove(channelName);
       
  1316                         }
       
  1317                     }
       
  1318                     */
       
  1319                 }
       
  1320 
       
  1321                 final String reason;
       
  1322 
       
  1323                 if (args.length > 1)
       
  1324                     reason = args[1];
       
  1325                 else
       
  1326                     reason = DEFAULT_QUIT_REASON;
       
  1327 
       
  1328                 // quit after parting
       
  1329                 if (doQuit)
       
  1330                     con.quit(reason);
       
  1331             }
       
  1332         },
       
  1333         QUIT(0, 1)
       
  1334         {
       
  1335             @Override
       
  1336             public void run(final Connection con, final String prefix, final String[] args)
       
  1337                     throws Exception
       
  1338             {
       
  1339                 final String reason;
       
  1340 
       
  1341                 if (args.length == 0)
       
  1342                     reason = DEFAULT_QUIT_REASON;
       
  1343                 else
       
  1344                     reason = args[0];
       
  1345 
       
  1346                 con.quit(reason);
       
  1347             }
       
  1348         },
       
  1349         PRIVMSG(2, 2)
       
  1350         {
       
  1351             @Override
       
  1352             public void run(final Connection con, final String prefix, final String[] args)
       
  1353                     throws Exception
       
  1354             {
       
  1355                 String message = ircActionToHw(args[1]);
       
  1356                 if (message.charAt(0) == CHAT_COMMAND_CHAR) {
       
  1357                     if (message.length() < 1 )
       
  1358                         return;
       
  1359                     message = message.substring(1);
       
  1360                     // TODO maybe \rebind CUSTOMCMDCHAR command
       
  1361                     con.hwcon.sendCommand(con.ircToHwNick(message));
       
  1362                 }
       
  1363                 else
       
  1364                     con.hwcon.sendChat(con.ircToHwNick(message));
       
  1365             }
       
  1366         },
       
  1367         TOPIC(1, 2)
       
  1368         {
       
  1369             @Override
       
  1370             public void run(final Connection con, final String prefix, final String[] args)
       
  1371                     throws Exception
       
  1372             {
       
  1373                 final String chan = args[0];
       
  1374 
       
  1375                 final Channel channel = con.getChannel(chan);
       
  1376 
       
  1377                 if (channel == null) {
       
  1378                     con.sendSelfNotice("No such channel for topic viewing: "
       
  1379                             + chan);
       
  1380                     return;
       
  1381                 }
       
  1382 
       
  1383                 // The user wants to see the channel topic.
       
  1384                 if (args.length == 1)
       
  1385                     con.sendTopic(channel);
       
  1386                 // The user wants to set the channel topic.
       
  1387                 else
       
  1388                     channel.topic = args[1];
       
  1389             }
       
  1390         },
       
  1391         KICK(3, 3)
       
  1392         {
       
  1393             @Override
       
  1394             public void run(final Connection con, final String prefix, final String[] args)
       
  1395                     throws Exception
       
  1396             {
       
  1397                 final String victim = args[1];
       
  1398                 con.logInfo("Issuing kick for " + victim);
       
  1399                 // "KICK #channel nick :kick reason (not relevant)"
       
  1400                 con.hwcon.kick(ircToHwNick(victim));
       
  1401             }
       
  1402         }
       
  1403         ;
       
  1404         public final int minArgumentCount;
       
  1405         public final int maxArgumentCount;
       
  1406         
       
  1407         private Command(int min, int max)
       
  1408         {
       
  1409             minArgumentCount = min;
       
  1410             maxArgumentCount = max;
       
  1411         }
       
  1412 
       
  1413         public abstract void run(Connection con, String prefix,
       
  1414                 String[] arguments) throws Exception;
       
  1415     }
       
  1416     
       
  1417     public static String delimited(String[] items, String delimiter)
       
  1418     {
       
  1419         StringBuffer response = new StringBuffer();
       
  1420         boolean first = true;
       
  1421         for (String s : items)
       
  1422         {
       
  1423             if (first)
       
  1424                 first = false;
       
  1425             else
       
  1426                 response.append(delimiter);
       
  1427             response.append(s);
       
  1428         }
       
  1429         return response.toString();
       
  1430     }
       
  1431     
       
  1432     protected void sendQuit(String quitMessage)
       
  1433     {
       
  1434         send(":" + getRepresentation() + " QUIT :" + quitMessage);
       
  1435     }
       
  1436     
       
  1437     @Override
       
  1438     public void run()
       
  1439     {
       
  1440         try
       
  1441         {
       
  1442             doServer();
       
  1443         }
       
  1444         catch (Exception e) {
       
  1445             e.printStackTrace();
       
  1446         }
       
  1447         finally
       
  1448         {
       
  1449             // TODO sense?
       
  1450             if (ownIrcNick != null && connectionMap.get(ownIrcNick) == this) {
       
  1451                 quit("Client disconnected.");
       
  1452             }
       
  1453 
       
  1454             try {
       
  1455                 socket.close();
       
  1456             }
       
  1457             catch (Exception e) { }
       
  1458 
       
  1459             quit("Connection terminated.");
       
  1460         }
       
  1461     }
       
  1462     
       
  1463     protected void sendGlobal(String string)
       
  1464     {
       
  1465         send(":" + globalServerName + " " + string);
       
  1466     }
       
  1467     
       
  1468     private LinkedBlockingQueue<String> outQueue = new LinkedBlockingQueue<String>(
       
  1469             1000);
       
  1470     
       
  1471     private Thread outThread = new Thread()
       
  1472     {
       
  1473         public void run()
       
  1474         {
       
  1475             try
       
  1476             {
       
  1477                 OutputStream out = socket.getOutputStream();
       
  1478                 while (!terminateConnection)
       
  1479                 {
       
  1480                     String s = outQueue.take();
       
  1481                     s = s.replace("\n", "").replace("\r", "");
       
  1482                     s = s + "\r\n";
       
  1483                     out.write(s.getBytes());
       
  1484                     out.flush();
       
  1485                 }
       
  1486             }
       
  1487             catch (Exception ex)
       
  1488             {
       
  1489                 thisConnection.logError("Outqueue died");
       
  1490                 //ex.printStackTrace();
       
  1491             }
       
  1492             finally {
       
  1493                 outQueue.clear();
       
  1494                 outQueue = null;
       
  1495                 try
       
  1496                 {
       
  1497                     socket.close();
       
  1498                 }
       
  1499                 catch (Exception e2)
       
  1500                 {
       
  1501                     e2.printStackTrace();
       
  1502                 }
       
  1503             }
       
  1504         }
       
  1505     };
       
  1506 
       
  1507     private boolean terminateConnection = false;
       
  1508 
       
  1509     private void doServer() throws Exception
       
  1510     {
       
  1511         outThread.start();
       
  1512         InputStream socketIn = socket.getInputStream();
       
  1513         BufferedReader clientReader = new BufferedReader(new InputStreamReader(
       
  1514                 socketIn));
       
  1515         String line;
       
  1516         while (!terminateConnection && ((line = clientReader.readLine()) != null))
       
  1517         {
       
  1518             processLine(line);
       
  1519         }
       
  1520     }
       
  1521 
       
  1522     public void sanitizeInputs(final String[] inputs) {
       
  1523 
       
  1524         // no for-each loop, because we need write access to the elements
       
  1525 
       
  1526         final int l = inputs.length;
       
  1527 
       
  1528         for (int i = 0; i < l; i++) {
       
  1529             inputs[i] = inputs[i].replaceAll(MAGIC_BYTES, " ");
       
  1530         }
       
  1531     }
       
  1532 
       
  1533     private void processLine(final String line) throws Exception
       
  1534     {
       
  1535         String l = line;
       
  1536 
       
  1537         // log things
       
  1538         if (l.startsWith("PASS") || l.startsWith("pass"))
       
  1539             this.logInfo("IRC-Client provided PASS");
       
  1540         else
       
  1541             this.logDebug("IRC-Client: " + l);
       
  1542 
       
  1543         String prefix = "";
       
  1544         if (l.startsWith(":"))
       
  1545         {
       
  1546             String[] tokens = l.split(" ", 2);
       
  1547             prefix = tokens[0];
       
  1548             l = (tokens.length > 1 ? tokens[1] : "");
       
  1549         }
       
  1550         String[] tokens1 = l.split(" ", 2);
       
  1551         String command = tokens1[0];
       
  1552         l = tokens1.length > 1 ? tokens1[1] : "";
       
  1553         String[] tokens2 = l.split("(^| )\\:", 2);
       
  1554         String trailing = null;
       
  1555         l = tokens2[0];
       
  1556         if (tokens2.length > 1)
       
  1557             trailing = tokens2[1];
       
  1558         ArrayList<String> argumentList = new ArrayList<String>();
       
  1559         if (!l.equals(""))
       
  1560             argumentList.addAll(Arrays.asList(l.split(" ")));
       
  1561         if (trailing != null)
       
  1562             argumentList.add(trailing);
       
  1563         final String[] args = argumentList.toArray(new String[0]);
       
  1564 
       
  1565         // process command
       
  1566 
       
  1567         // numeric commands
       
  1568         if (command.matches("[0-9][0-9][0-9]"))
       
  1569             command = "N" + command;
       
  1570 
       
  1571         final Command commandObject;
       
  1572 
       
  1573         try {
       
  1574             commandObject = Command.valueOf(command.toUpperCase());
       
  1575         }
       
  1576         catch (Exception ex) {
       
  1577             // forward raw unknown command to hw server
       
  1578             hwcon.sendCommand(ircToHwNick(line));
       
  1579             return;
       
  1580         }
       
  1581 
       
  1582         if (args.length < commandObject.minArgumentCount
       
  1583                 || args.length > commandObject.maxArgumentCount)
       
  1584         {
       
  1585             sendSelfNotice("Invalid number of arguments for this"
       
  1586                     + " command, expected not more than "
       
  1587                     + commandObject.maxArgumentCount + " and not less than "
       
  1588                     + commandObject.minArgumentCount + " but got " + args.length
       
  1589                     + " arguments");
       
  1590             return;
       
  1591         }
       
  1592         commandObject.run(this, prefix, args);
       
  1593     }
       
  1594 
       
  1595     /**
       
  1596      * Sends a notice from the server to the user represented by this
       
  1597      * connection.
       
  1598      * 
       
  1599      * @param string
       
  1600      *            The text to send as a notice
       
  1601      */
       
  1602 
       
  1603     private void sendSelfNotice(final String string)
       
  1604     {
       
  1605         send(":" + globalServerName + " NOTICE " + ownIrcNick + " :" + string);
       
  1606     }
       
  1607 
       
  1608     private void sendChannelNotice(final String string) {
       
  1609         sendChannelNotice(string, globalServerName);
       
  1610     }
       
  1611 
       
  1612     private void sendChannelNotice(final String string, final String from) {
       
  1613         // TODO send to room if user is in room
       
  1614         send(":" + from + " NOTICE " + LOBBY_CHANNEL_NAME + " :" + string);
       
  1615     }
       
  1616 
       
  1617     private void sendServerNotice(final String string)
       
  1618     {
       
  1619         if (ircJoined)
       
  1620             sendChannelNotice(string, "[INFO]");
       
  1621 
       
  1622         sendSelfNotice(string);
       
  1623     }
       
  1624 
       
  1625     private String[] padSplit(final String line, final String regex, int max)
       
  1626     {
       
  1627         String[] split = line.split(regex);
       
  1628         String[] output = new String[max];
       
  1629         for (int i = 0; i < output.length; i++)
       
  1630         {
       
  1631             output[i] = "";
       
  1632         }
       
  1633         for (int i = 0; i < split.length; i++)
       
  1634         {
       
  1635             output[i] = split[i];
       
  1636         }
       
  1637         return output;
       
  1638     }
       
  1639 
       
  1640     public void sendIfJoined(final String s) {
       
  1641         if (joined)
       
  1642             send(s);
       
  1643     }
       
  1644 
       
  1645     public void send(final String s)
       
  1646     {
       
  1647         final Queue<String> testQueue = outQueue;
       
  1648         if (testQueue != null)
       
  1649         {
       
  1650             this.logDebug("IRC-Server: " + s);
       
  1651             testQueue.add(s);
       
  1652         }
       
  1653     }
       
  1654 
       
  1655 final static String RPL_WELCOME = "001 ";
       
  1656 final static String RPL_YOURHOST = "002 ";
       
  1657 final static String RPL_CREATED = "003 ";
       
  1658 final static String RPL_MYINFO = "004 ";
       
  1659 final static String RPL_BOUNCE = "005 ";
       
  1660 final static String RPL_TRACELINK = "200 ";
       
  1661 final static String RPL_TRACECONNECTING = "201 ";
       
  1662 final static String RPL_TRACEHANDSHAKE = "202 ";
       
  1663 final static String RPL_TRACEUNKNOWN = "203 ";
       
  1664 final static String RPL_TRACEOPERATOR = "204 ";
       
  1665 final static String RPL_TRACEUSER = "205 ";
       
  1666 final static String RPL_TRACESERVER = "206 ";
       
  1667 final static String RPL_TRACESERVICE = "207 ";
       
  1668 final static String RPL_TRACENEWTYPE = "208 ";
       
  1669 final static String RPL_TRACECLASS = "209 ";
       
  1670 final static String RPL_TRACERECONNECT = "210 ";
       
  1671 final static String RPL_STATSLINKINFO = "211 ";
       
  1672 final static String RPL_STATSCOMMANDS = "212 ";
       
  1673 final static String RPL_STATSCLINE = "213 ";
       
  1674 final static String RPL_STATSNLINE = "214 ";
       
  1675 final static String RPL_STATSILINE = "215 ";
       
  1676 final static String RPL_STATSKLINE = "216 ";
       
  1677 final static String RPL_STATSQLINE = "217 ";
       
  1678 final static String RPL_STATSYLINE = "218 ";
       
  1679 final static String RPL_ENDOFSTATS = "219 ";
       
  1680 final static String RPL_UMODEIS = "221 ";
       
  1681 final static String RPL_SERVICEINFO = "231 ";
       
  1682 final static String RPL_ENDOFSERVICES = "232 ";
       
  1683 final static String RPL_SERVICE = "233 ";
       
  1684 final static String RPL_SERVLIST = "234 ";
       
  1685 final static String RPL_SERVLISTEND = "235 ";
       
  1686 final static String RPL_STATSVLINE = "240 ";
       
  1687 final static String RPL_STATSLLINE = "241 ";
       
  1688 final static String RPL_STATSUPTIME = "242 ";
       
  1689 final static String RPL_STATSOLINE = "243 ";
       
  1690 final static String RPL_STATSHLINE = "244 ";
       
  1691 final static String RPL_STATSPING = "246 ";
       
  1692 final static String RPL_STATSBLINE = "247 ";
       
  1693 final static String RPL_STATSDLINE = "250 ";
       
  1694 final static String RPL_LUSERCLIENT = "251 ";
       
  1695 final static String RPL_LUSEROP = "252 ";
       
  1696 final static String RPL_LUSERUNKNOWN = "253 ";
       
  1697 final static String RPL_LUSERCHANNELS = "254 ";
       
  1698 final static String RPL_LUSERME = "255 ";
       
  1699 final static String RPL_ADMINME = "256 ";
       
  1700 final static String RPL_ADMINEMAIL = "259 ";
       
  1701 final static String RPL_TRACELOG = "261 ";
       
  1702 final static String RPL_TRACEEND = "262 ";
       
  1703 final static String RPL_TRYAGAIN = "263 ";
       
  1704 final static String RPL_NONE = "300 ";
       
  1705 final static String RPL_AWAY = "301 ";
       
  1706 final static String RPL_USERHOST = "302 ";
       
  1707 final static String RPL_ISON = "303 ";
       
  1708 final static String RPL_UNAWAY = "305 ";
       
  1709 final static String RPL_NOWAWAY = "306 ";
       
  1710 final static String RPL_WHOISUSER = "311 ";
       
  1711 final static String RPL_WHOISSERVER = "312 ";
       
  1712 final static String RPL_WHOISOPERATOR = "313 ";
       
  1713 final static String RPL_WHOWASUSER = "314 ";
       
  1714 final static String RPL_ENDOFWHO = "315 ";
       
  1715 final static String RPL_WHOISCHANOP = "316 ";
       
  1716 final static String RPL_WHOISIDLE = "317 ";
       
  1717 final static String RPL_ENDOFWHOIS = "318 ";
       
  1718 final static String RPL_WHOISCHANNELS = "319 ";
       
  1719 final static String RPL_LISTSTART = "321 ";
       
  1720 final static String RPL_LIST = "322 ";
       
  1721 final static String RPL_LISTEND = "323 ";
       
  1722 final static String RPL_CHANNELMODEIS = "324 ";
       
  1723 final static String RPL_UNIQOPIS = "325 ";
       
  1724 final static String RPL_NOTOPIC = "331 ";
       
  1725 final static String RPL_TOPIC = "332 ";
       
  1726 final static String RPL_INVITING = "341 ";
       
  1727 final static String RPL_SUMMONING = "342 ";
       
  1728 final static String RPL_INVITELIST = "346 ";
       
  1729 final static String RPL_ENDOFINVITELIST = "347 ";
       
  1730 final static String RPL_EXCEPTLIST = "348 ";
       
  1731 final static String RPL_ENDOFEXCEPTLIST = "349 ";
       
  1732 final static String RPL_VERSION = "351 ";
       
  1733 final static String RPL_WHOREPLY = "352 ";
       
  1734 final static String RPL_NAMREPLY = "353 ";
       
  1735 final static String RPL_KILLDONE = "361 ";
       
  1736 final static String RPL_CLOSING = "362 ";
       
  1737 final static String RPL_CLOSEEND = "363 ";
       
  1738 final static String RPL_LINKS = "364 ";
       
  1739 final static String RPL_ENDOFLINKS = "365 ";
       
  1740 final static String RPL_ENDOFNAMES = "366 ";
       
  1741 final static String RPL_BANLIST = "367 ";
       
  1742 final static String RPL_ENDOFBANLIST = "368 ";
       
  1743 final static String RPL_ENDOFWHOWAS = "369 ";
       
  1744 final static String RPL_INFO = "371 ";
       
  1745 final static String RPL_MOTD = "372 ";
       
  1746 final static String RPL_INFOSTART = "373 ";
       
  1747 final static String RPL_ENDOFINFO = "374 ";
       
  1748 final static String RPL_MOTDSTART = "375 ";
       
  1749 final static String RPL_ENDOFMOTD = "376 ";
       
  1750 final static String RPL_YOUREOPER = "381 ";
       
  1751 final static String RPL_REHASHING = "382 ";
       
  1752 final static String RPL_YOURESERVICE = "383 ";
       
  1753 final static String RPL_MYPORTIS = "384 ";
       
  1754 final static String RPL_TIME = "391 ";
       
  1755 final static String RPL_USERSSTART = "392 ";
       
  1756 final static String RPL_USERS = "393 ";
       
  1757 final static String RPL_ENDOFUSERS = "394 ";
       
  1758 final static String RPL_NOUSERS = "395 ";
       
  1759 final static String ERR_NOSUCHNICK = "401 ";
       
  1760 final static String ERR_NOSUCHSERVER = "402 ";
       
  1761 final static String ERR_NOSUCHCHANNEL = "403 ";
       
  1762 final static String ERR_CANNOTSENDTOCHAN = "404 ";
       
  1763 final static String ERR_TOOMANYCHANNELS = "405 ";
       
  1764 final static String ERR_WASNOSUCHNICK = "406 ";
       
  1765 final static String ERR_TOOMANYTARGETS = "407 ";
       
  1766 final static String ERR_NOSUCHSERVICE = "408 ";
       
  1767 final static String ERR_NOORIGIN = "409 ";
       
  1768 final static String ERR_NORECIPIENT = "411 ";
       
  1769 final static String ERR_NOTEXTTOSEND = "412 ";
       
  1770 final static String ERR_NOTOPLEVEL = "413 ";
       
  1771 final static String ERR_WILDTOPLEVEL = "414 ";
       
  1772 final static String ERR_BADMASK = "415 ";
       
  1773 final static String ERR_UNKNOWNCOMMAND = "421 ";
       
  1774 final static String ERR_NOMOTD = "422 ";
       
  1775 final static String ERR_NOADMININFO = "423 ";
       
  1776 final static String ERR_FILEERROR = "424 ";
       
  1777 final static String ERR_NONICKNAMEGIVEN = "431 ";
       
  1778 final static String ERR_ERRONEUSNICKNAME = "432 ";
       
  1779 final static String ERR_NICKNAMEINUSE = "433 ";
       
  1780 final static String ERR_NICKCOLLISION = "436 ";
       
  1781 final static String ERR_UNAVAILRESOURCE = "437 ";
       
  1782 final static String ERR_USERNOTINCHANNEL = "441 ";
       
  1783 final static String ERR_NOTONCHANNEL = "442 ";
       
  1784 final static String ERR_USERONCHANNEL = "443 ";
       
  1785 final static String ERR_NOLOGIN = "444 ";
       
  1786 final static String ERR_SUMMONDISABLED = "445 ";
       
  1787 final static String ERR_USERSDISABLED = "446 ";
       
  1788 final static String ERR_NOTREGISTERED = "451 ";
       
  1789 final static String ERR_NEEDMOREPARAMS = "461 ";
       
  1790 final static String ERR_ALREADYREGISTRED = "462 ";
       
  1791 final static String ERR_NOPERMFORHOST = "463 ";
       
  1792 final static String ERR_PASSWDMISMATCH = "464 ";
       
  1793 final static String ERR_YOUREBANNEDCREEP = "465 ";
       
  1794 final static String ERR_YOUWILLBEBANNED = "466 ";
       
  1795 final static String ERR_KEYSET = "467 ";
       
  1796 final static String ERR_CHANNELISFULL = "471 ";
       
  1797 final static String ERR_UNKNOWNMODE = "472 ";
       
  1798 final static String ERR_INVITEONLYCHAN = "473 ";
       
  1799 final static String ERR_BANNEDFROMCHAN = "474 ";
       
  1800 final static String ERR_BADCHANNELKEY = "475 ";
       
  1801 final static String ERR_BADCHANMASK = "476 ";
       
  1802 final static String ERR_NOCHANMODES = "477 ";
       
  1803 final static String ERR_BANLISTFULL = "478 ";
       
  1804 final static String ERR_NOPRIVILEGES = "481 ";
       
  1805 final static String ERR_CHANOPRIVSNEEDED = "482 ";
       
  1806 final static String ERR_CANTKILLSERVER = "483 ";
       
  1807 final static String ERR_RESTRICTED = "484 ";
       
  1808 final static String ERR_UNIQOPPRIVSNEEDED = "485 ";
       
  1809 final static String ERR_NOOPERHOST = "491 ";
       
  1810 final static String ERR_NOSERVICEHOST = "492 ";
       
  1811 final static String ERR_UMODEUNKNOWNFLAG = "501 ";
       
  1812 final static String ERR_USERSDONTMATCH = "502 ";
       
  1813 
       
  1814 }