|
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 } |