IRC logging refactoring
[rbot] / lib / rbot / rfc2812.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: RFC 2821 Client Protocol module
5 #
6 # This module defines the Irc::Client class, a class that can handle and
7 # dispatch messages based on RFC 2821 (Internet Relay Chat: Client Protocol)
8
9 module Irc
10   # - The server sends Replies 001 to 004 to a user upon
11   #   successful registration.
12
13   # "Welcome to the Internet Relay Network
14   # <nick>!<user>@<host>"
15   #
16   RPL_WELCOME=001
17
18   # "Your host is <servername>, running version <ver>"
19   RPL_YOURHOST=002
20
21   # "This server was created <date>"
22   RPL_CREATED=003
23
24   # "<servername> <version> <available user modes> <available channel modes>"
25   RPL_MYINFO=004
26
27   # "005 nick PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
28   #
29   # defines the capabilities supported by the server.
30   #
31   # Previous RFCs defined message 005 as follows:
32   #
33   # - Sent by the server to a user to suggest an alternative
34   #   server.  This is often used when the connection is
35   #   refused because the server is already full.
36   #
37   # # "Try server <server name>, port <port number>"
38   #
39   # RPL_BOUNCE=005
40   #
41   RPL_ISUPPORT=005
42
43   # ":*1<reply> *( " " <reply> )"
44   #
45   # - Reply format used by USERHOST to list replies to
46   #   the query list.  The reply string is composed as
47   #   follows:
48   #
49   # reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
50   #
51   # The '*' indicates whether the client has registered
52   # as an Operator.  The '-' or '+' characters represent
53   # whether the client has set an AWAY message or not
54   # respectively.
55   #
56   RPL_USERHOST=302
57
58   # ":*1<nick> *( " " <nick> )"
59   #
60   # - Reply format used by ISON to list replies to the
61   #   query list.
62   #
63   RPL_ISON=303
64
65   # - These replies are used with the AWAY command (if
66   #   allowed).  RPL_AWAY is sent to any client sending a
67   #   PRIVMSG to a client which is away.  RPL_AWAY is only
68   #   sent by the server to which the client is connected.
69   #   Replies RPL_UNAWAY and RPL_NOWAWAY are sent when the
70   #   client removes and sets an AWAY message.
71
72   # "<nick> :<away message>"
73   RPL_AWAY=301
74
75   # ":You are no longer marked as being away"
76   RPL_UNAWAY=305
77
78   # ":You have been marked as being away"
79   RPL_NOWAWAY=306
80
81   # - Replies 311 - 313, 317 - 319 are all replies
82   #   generated in response to a WHOIS message.  Given that
83   #   there are enough parameters present, the answering
84   #   server MUST either formulate a reply out of the above
85   #   numerics (if the query nick is found) or return an
86   #   error reply.  The '*' in RPL_WHOISUSER is there as
87   #   the literal character and not as a wild card.  For
88   #   each reply set, only RPL_WHOISCHANNELS may appear
89   #   more than once (for long lists of channel names).
90   #   The '@' and '+' characters next to the channel name
91   #   indicate whether a client is a channel operator or
92   #   has been granted permission to speak on a moderated
93   #   channel.  The RPL_ENDOFWHOIS reply is used to mark
94   #   the end of processing a WHOIS message.
95
96   # "<nick> <user> <host> * :<real name>"
97   RPL_WHOISUSER=311
98
99   # "<nick> <server> :<server info>"
100   RPL_WHOISSERVER=312
101
102   # "<nick> :is an IRC operator"
103   RPL_WHOISOPERATOR=313
104
105   # "<nick> <integer> :seconds idle"
106   RPL_WHOISIDLE=317
107
108   # "<nick> :End of WHOIS list"
109   RPL_ENDOFWHOIS=318
110
111   # "<nick> :*( ( "@" / "+" ) <channel> " " )"
112   RPL_WHOISCHANNELS=319
113
114   # - When replying to a WHOWAS message, a server MUST use
115   #   the replies RPL_WHOWASUSER, RPL_WHOISSERVER or
116   #   ERR_WASNOSUCHNICK for each nickname in the presented
117   #   list.  At the end of all reply batches, there MUST
118   #   be RPL_ENDOFWHOWAS (even if there was only one reply
119   #   and it was an error).
120
121   # "<nick> <user> <host> * :<real name>"
122   RPL_WHOWASUSER=314
123
124   # "<nick> :End of WHOWAS"
125   RPL_ENDOFWHOWAS=369
126
127   # - Replies RPL_LIST, RPL_LISTEND mark the actual replies
128   #   with data and end of the server's response to a LIST
129   #   command.  If there are no channels available to return,
130   #   only the end reply MUST be sent.
131
132   # Obsolete. Not used.
133   RPL_LISTSTART=321
134
135   # "<channel> <# visible> :<topic>"
136   RPL_LIST=322
137
138   # ":End of LIST"
139   RPL_LISTEND=323
140
141   # "<channel> <nickname>"
142   RPL_UNIQOPIS=325
143
144   # "<channel> <mode> <mode params>"
145   RPL_CHANNELMODEIS=324
146
147   # "<channel> :No topic is set"
148   RPL_NOTOPIC=331
149
150   # - When sending a TOPIC message to determine the
151   #   channel topic, one of two replies is sent.  If
152   #   the topic is set, RPL_TOPIC is sent back else
153   #   RPL_NOTOPIC.
154
155   # "<channel> :<topic>"
156   RPL_TOPIC=332
157
158   # <channel> <set by> <unixtime>
159   RPL_TOPIC_INFO=333
160
161   # "<channel> <nick>"
162   #
163   # - Returned by the server to indicate that the
164   #   attempted INVITE message was successful and is
165   #   being passed onto the end client.
166   #
167   RPL_INVITING=341
168
169   # "<user> :Summoning user to IRC"
170   #
171   # - Returned by a server answering a SUMMON message to
172   #   indicate that it is summoning that user.
173   #
174   RPL_SUMMONING=342
175
176   # "<channel> <invitemask>"
177   RPL_INVITELIST=346
178
179   # "<channel> :End of channel invite list"
180   #
181   # - When listing the 'invitations masks' for a given channel,
182   #   a server is required to send the list back using the
183   #   RPL_INVITELIST and RPL_ENDOFINVITELIST messages.  A
184   #   separate RPL_INVITELIST is sent for each active mask.
185   #   After the masks have been listed (or if none present) a
186   #   RPL_ENDOFINVITELIST MUST be sent.
187   #
188   RPL_ENDOFINVITELIST=347
189
190   # "<channel> <exceptionmask>"
191   RPL_EXCEPTLIST=348
192
193   # "<channel> :End of channel exception list"
194   #
195   # - When listing the 'exception masks' for a given channel,
196   #   a server is required to send the list back using the
197   #   RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages.  A
198   #   separate RPL_EXCEPTLIST is sent for each active mask.
199   #   After the masks have been listed (or if none present)
200   #   a RPL_ENDOFEXCEPTLIST MUST be sent.
201   #
202   RPL_ENDOFEXCEPTLIST=349
203
204   # "<version>.<debuglevel> <server> :<comments>"
205   #
206   # - Reply by the server showing its version details.
207   #
208   # The <version> is the version of the software being
209   # used (including any patchlevel revisions) and the
210   # <debuglevel> is used to indicate if the server is
211   # running in "debug mode".
212   #
213   # The "comments" field may contain any comments about
214   # the version or further version details.
215   #
216   RPL_VERSION=351
217
218   # - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used
219   #   to answer a WHO message.  The RPL_WHOREPLY is only
220   #   sent if there is an appropriate match to the WHO
221   #   query.  If there is a list of parameters supplied
222   #   with a WHO message, a RPL_ENDOFWHO MUST be sent
223   #   after processing each list item with <name> being
224   #   the item.
225
226   # "<channel> <user> <host> <server> <nick>
227   # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
228   # :<hopcount> <real name>"
229   #
230   RPL_WHOREPLY=352
231
232   # "<name> :End of WHO list"
233   RPL_ENDOFWHO=315
234
235   # - To reply to a NAMES message, a reply pair consisting
236   #   of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
237   #   server back to the client.  If there is no channel
238   #   found as in the query, then only RPL_ENDOFNAMES is
239   #   returned.  The exception to this is when a NAMES
240   #   message is sent with no parameters and all visible
241   #   channels and contents are sent back in a series of
242   #   RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
243   #   the end.
244
245   # "( "=" / "*" / "@" ) <channel>
246   # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
247   # - "@" is used for secret channels, "*" for private
248   # channels, and "=" for others (public channels).
249   #
250   RPL_NAMREPLY=353
251
252   # "<channel> :End of NAMES list"
253   RPL_ENDOFNAMES=366
254
255   # - In replying to the LINKS message, a server MUST send
256   #   replies back using the RPL_LINKS numeric and mark the
257   #   end of the list using an RPL_ENDOFLINKS reply.
258
259   # "<mask> <server> :<hopcount> <server info>"
260   RPL_LINKS=364
261
262   # "<mask> :End of LINKS list"
263   RPL_ENDOFLINKS=365
264
265   # - When listing the active 'bans' for a given channel,
266   #   a server is required to send the list back using the
267   #   RPL_BANLIST and RPL_ENDOFBANLIST messages.  A separate
268   #   RPL_BANLIST is sent for each active banmask.  After the
269   #   banmasks have been listed (or if none present) a
270   #   RPL_ENDOFBANLIST MUST be sent.
271
272   # "<channel> <banmask>"
273   RPL_BANLIST=367
274
275   # "<channel> :End of channel ban list"
276   RPL_ENDOFBANLIST=368
277
278   # - A server responding to an INFO message is required to
279   #   send all its 'info' in a series of RPL_INFO messages
280   #   with a RPL_ENDOFINFO reply to indicate the end of the
281   #   replies.
282
283   # ":<string>"
284   RPL_INFO=371
285
286   # ":End of INFO list"
287   RPL_ENDOFINFO=374
288
289   # - When responding to the MOTD message and the MOTD file
290   # is found, the file is displayed line by line, with
291   # each line no longer than 80 characters, using
292   # RPL_MOTD format replies.  These MUST be surrounded
293   # by a RPL_MOTDSTART (before the RPL_MOTDs) and an
294   # RPL_ENDOFMOTD (after).
295
296   # ":- <server> Message of the day - "
297   RPL_MOTDSTART=375
298
299   # ":- <text>"
300   RPL_MOTD=372
301
302   # ":End of MOTD command"
303   RPL_ENDOFMOTD=376
304
305   # ":You are now an IRC operator"
306   #
307   # - RPL_YOUREOPER is sent back to a client which has
308   #   just successfully issued an OPER message and gained
309   #   operator status.
310   #
311   RPL_YOUREOPER=381
312
313   # "<config file> :Rehashing"
314   #
315   # - If the REHASH option is used and an operator sends
316   #   a REHASH message, an RPL_REHASHING is sent back to
317   #   the operator.
318   #
319   RPL_REHASHING=382
320
321   # "You are service <servicename>"
322   #
323   # - Sent by the server to a service upon successful
324   #   registration.
325   #
326   RPL_YOURESERVICE=383
327
328   # "<server> :<string showing server's local time>"
329   #
330   # - When replying to the TIME message, a server MUST send
331   #   the reply using the RPL_TIME format above.  The string
332   #   showing the time need only contain the correct day and
333   #   time there.  There is no further requirement for the
334   #   time string.
335   #
336   RPL_TIME=391
337
338   # - If the USERS message is handled by a server, the
339   #   replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and
340   #   RPL_NOUSERS are used.  RPL_USERSSTART MUST be sent
341   #   first, following by either a sequence of RPL_USERS
342   #   or a single RPL_NOUSER.  Following this is
343   #   RPL_ENDOFUSERS.
344
345   # ":UserID   Terminal  Host"
346   RPL_USERSSTART=392
347
348   # ":<username> <ttyline> <hostname>"
349   RPL_USERS=393
350
351   # ":End of users"
352   RPL_ENDOFUSERS=394
353
354   # ":Nobody logged in"
355   RPL_NOUSERS=395
356
357   # - The RPL_TRACE* are all returned by the server in
358   #   response to the TRACE message.  How many are
359   #   returned is dependent on the TRACE message and
360   #   whether it was sent by an operator or not.  There
361   #   is no predefined order for which occurs first.
362   #   Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
363   #   RPL_TRACEHANDSHAKE are all used for connections
364   #   which have not been fully established and are either
365   #   unknown, still attempting to connect or in the
366   #   process of completing the 'server handshake'.
367   #   RPL_TRACELINK is sent by any server which handles
368   #   a TRACE message and has to pass it on to another
369   #   server.  The list of RPL_TRACELINKs sent in
370   #   response to a TRACE command traversing the IRC
371   #   network should reflect the actual connectivity of
372   #   the servers themselves along that path.
373   #
374   # RPL_TRACENEWTYPE is to be used for any connection
375   # which does not fit in the other categories but is
376   # being displayed anyway.
377   # RPL_TRACEEND is sent to indicate the end of the list.
378
379   # "Link <version & debug level> <destination>
380   # <next server> V<protocol version>
381   # <link uptime in seconds> <backstream sendq>
382   # <upstream sendq>"
383   RPL_TRACELINK=200
384
385   # "Try. <class> <server>"
386   RPL_TRACECONNECTING=201
387
388   # "H.S. <class> <server>"
389   RPL_TRACEHANDSHAKE=202
390
391   # "???? <class> [<client IP address in dot form>]"
392   RPL_TRACEUNKNOWN=203
393
394   # "Oper <class> <nick>"
395   RPL_TRACEOPERATOR=204
396
397   # "User <class> <nick>"
398   RPL_TRACEUSER=205
399
400   # "Serv <class> <int>S <int>C <server>
401   # <nick!user|*!*>@<host|server> V<protocol version>"
402   RPL_TRACESERVER=206
403
404   # "Service <class> <name> <type> <active type>"
405   RPL_TRACESERVICE=207
406
407   # "<newtype> 0 <client name>"
408   RPL_TRACENEWTYPE=208
409
410   # "Class <class> <count>"
411   RPL_TRACECLASS=209
412
413   # Unused.
414   RPL_TRACERECONNECT=210
415
416   # "File <logfile> <debug level>"
417   RPL_TRACELOG=261
418
419   # "<server name> <version & debug level> :End of TRACE"
420   RPL_TRACEEND=262
421
422   # ":Current local  users: 3  Max: 4"
423   RPL_LOCALUSERS=265
424
425   # ":Current global users: 3  Max: 4"
426   RPL_GLOBALUSERS=266
427
428   # "::Highest connection count: 4 (4 clients) (251 since server was
429   # (re)started)"
430   RPL_STATSCONN=250
431
432   # "<linkname> <sendq> <sent messages>
433   # <sent Kbytes> <received messages>
434   # <received Kbytes> <time open>"
435   #
436   # - reports statistics on a connection.  <linkname>
437   #   identifies the particular connection, <sendq> is
438   #   the amount of data that is queued and waiting to be
439   #   sent <sent messages> the number of messages sent,
440   #   and <sent Kbytes> the amount of data sent, in
441   #   Kbytes. <received messages> and <received Kbytes>
442   #   are the equivalent of <sent messages> and <sent
443   #   Kbytes> for received data, respectively.  <time
444   #   open> indicates how long ago the connection was
445   #   opened, in seconds.
446   #
447   RPL_STATSLINKINFO=211
448
449   # "<command> <count> <byte count> <remote count>"
450   #
451   # - reports statistics on commands usage.
452   #
453   RPL_STATSCOMMANDS=212
454
455   # "<stats letter> :End of STATS report"
456   #
457   RPL_ENDOFSTATS=219
458
459   # ":Server Up %d days %d:%02d:%02d"
460   #
461   # - reports the server uptime.
462   #
463   RPL_STATSUPTIME=242
464
465   # "O <hostmask> * <name>"
466   #
467   # - reports the allowed hosts from where user may become IRC
468   #   operators.
469   #
470   RPL_STATSOLINE=243
471
472   # "<user mode string>"
473   #
474   # - To answer a query about a client's own mode,
475   #   RPL_UMODEIS is sent back.
476   #
477   RPL_UMODEIS=221
478
479   # - When listing services in reply to a SERVLIST message,
480   #   a server is required to send the list back using the
481   #   RPL_SERVLIST and RPL_SERVLISTEND messages.  A separate
482   #   RPL_SERVLIST is sent for each service.  After the
483   #   services have been listed (or if none present) a
484   #   RPL_SERVLISTEND MUST be sent.
485
486   # "<name> <server> <mask> <type> <hopcount> <info>"
487   RPL_SERVLIST=234
488
489   # "<mask> <type> :End of service listing"
490   RPL_SERVLISTEND=235
491
492   # - In processing an LUSERS message, the server
493   #   sends a set of replies from RPL_LUSERCLIENT,
494   #   RPL_LUSEROP, RPL_USERUNKNOWN,
495   #   RPL_LUSERCHANNELS and RPL_LUSERME.  When
496   #   replying, a server MUST send back
497   #   RPL_LUSERCLIENT and RPL_LUSERME.  The other
498   #   replies are only sent back if a non-zero count
499   #   is found for them.
500
501   # ":There are <integer> users and <integer>
502   # services on <integer> servers"
503   RPL_LUSERCLIENT=251
504
505   # "<integer> :operator(s) online"
506   RPL_LUSEROP=252
507
508   # "<integer> :unknown connection(s)"
509   RPL_LUSERUNKNOWN=253
510
511   # "<integer> :channels formed"
512   RPL_LUSERCHANNELS=254
513
514   # ":I have <integer> clients and <integer> servers"
515   RPL_LUSERME=255
516
517   # - When replying to an ADMIN message, a server
518   # is expected to use replies RPL_ADMINME
519   # through to RPL_ADMINEMAIL and provide a text
520   # message with each.  For RPL_ADMINLOC1 a
521   # description of what city, state and country
522   # the server is in is expected, followed by
523   # details of the institution (RPL_ADMINLOC2)
524   # and finally the administrative contact for the
525   # server (an email address here is REQUIRED)
526   # in RPL_ADMINEMAIL.
527
528   # "<server> :Administrative info"
529   RPL_ADMINME=256
530
531   # ":<admin info>"
532   RPL_ADMINLOC1=257
533
534   # ":<admin info>"
535   RPL_ADMINLOC2=258
536
537   # ":<admin info>"
538   RPL_ADMINEMAIL=259
539
540   # "<command> :Please wait a while and try again."
541   #
542   # - When a server drops a command without processing it,
543   #   it MUST use the reply RPL_TRYAGAIN to inform the
544   #   originating client.
545   RPL_TRYAGAIN=263
546
547   # 5.2 Error Replies
548   #
549   # Error replies are found in the range from 400 to 599.
550
551   # "<nickname> :No such nick/channel"
552   #
553   # - Used to indicate the nickname parameter supplied to a
554   #   command is currently unused.
555   #
556   ERR_NOSUCHNICK=401
557
558   # "<server name> :No such server"
559   #
560   # - Used to indicate the server name given currently
561   #   does not exist.
562   #
563   ERR_NOSUCHSERVER=402
564
565   # "<channel name> :No such channel"
566   #
567   # - Used to indicate the given channel name is invalid.
568   #
569   ERR_NOSUCHCHANNEL=403
570
571   # "<channel name> :Cannot send to channel"
572   #
573   # - Sent to a user who is either (a) not on a channel
574   #   which is mode +n or (b) not a chanop (or mode +v) on
575   #   a channel which has mode +m set or where the user is
576   #   banned and is trying to send a PRIVMSG message to
577   #   that channel.
578   #
579   ERR_CANNOTSENDTOCHAN=404
580
581   # "<channel name> :You have joined too many channels"
582   #
583   # - Sent to a user when they have joined the maximum
584   #   number of allowed channels and they try to join
585   #   another channel.
586   #
587   ERR_TOOMANYCHANNELS=405
588
589   # "<nickname> :There was no such nickname"
590   #
591   # - Returned by WHOWAS to indicate there is no history
592   #   information for that nickname.
593   #
594   ERR_WASNOSUCHNICK=406
595
596   # "<target> :<error code> recipients. <abort message>"
597   #
598   # - Returned to a client which is attempting to send a
599   #   PRIVMSG/NOTICE using the user@host destination format
600   #   and for a user@host which has several occurrences.
601   #
602   # - Returned to a client which trying to send a
603   #   PRIVMSG/NOTICE to too many recipients.
604   #
605   # - Returned to a client which is attempting to JOIN a safe
606   #   channel using the shortname when there are more than one
607   #   such channel.
608   #
609   ERR_TOOMANYTARGETS=407
610
611   # "<service name> :No such service"
612   #
613   # - Returned to a client which is attempting to send a SQUERY
614   #   to a service which does not exist.
615   #
616   ERR_NOSUCHSERVICE=408
617
618   # ":No origin specified"
619   #
620   # - PING or PONG message missing the originator parameter.
621   #
622   ERR_NOORIGIN=409
623
624   # ":No recipient given (<command>)"
625   ERR_NORECIPIENT=411
626
627   # - 412 - 415 are returned by PRIVMSG to indicate that
628   #   the message wasn't delivered for some reason.
629   #   ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
630   #   are returned when an invalid use of
631   #   "PRIVMSG $<server>" or "PRIVMSG #<host>" is attempted.
632
633   # ":No text to send"
634   ERR_NOTEXTTOSEND=412
635
636   # "<mask> :No toplevel domain specified"
637   ERR_NOTOPLEVEL=413
638
639   # "<mask> :Wildcard in toplevel domain"
640   ERR_WILDTOPLEVEL=414
641
642   # "<mask> :Bad Server/host mask"
643   ERR_BADMASK=415
644
645   # "<command> :Unknown command"
646   #
647   # - Returned to a registered client to indicate that the
648   #   command sent is unknown by the server.
649   #
650   ERR_UNKNOWNCOMMAND=421
651
652   # ":MOTD File is missing"
653   #
654   # - Server's MOTD file could not be opened by the server.
655   #
656   ERR_NOMOTD=422
657
658   # "<server> :No administrative info available"
659   #
660   # - Returned by a server in response to an ADMIN message
661   #   when there is an error in finding the appropriate
662   #   information.
663   #
664   ERR_NOADMININFO=423
665
666   # ":File error doing <file op> on <file>"
667   #
668   # - Generic error message used to report a failed file
669   #   operation during the processing of a message.
670   #
671   ERR_FILEERROR=424
672
673   # ":No nickname given"
674   #
675   # - Returned when a nickname parameter expected for a
676   #   command and isn't found.
677   #
678   ERR_NONICKNAMEGIVEN=431
679
680   # "<nick> :Erroneous nickname"
681   #
682   # - Returned after receiving a NICK message which contains
683   #   characters which do not fall in the defined set.  See
684   #   section 2.3.1 for details on valid nicknames.
685   #
686   ERR_ERRONEUSNICKNAME=432
687
688   # "<nick> :Nickname is already in use"
689   #
690   # - Returned when a NICK message is processed that results
691   #   in an attempt to change to a currently existing
692   #   nickname.
693   #
694   ERR_NICKNAMEINUSE=433
695
696   # "<nick> :Nickname collision KILL from <user>@<host>"
697   #
698   # - Returned by a server to a client when it detects a
699   #   nickname collision (registered of a NICK that
700   #   already exists by another server).
701   #
702   ERR_NICKCOLLISION=436
703
704   # "<nick/channel> :Nick/channel is temporarily unavailable"
705   #
706   # - Returned by a server to a user trying to join a channel
707   #   currently blocked by the channel delay mechanism.
708   #
709   # - Returned by a server to a user trying to change nickname
710   #   when the desired nickname is blocked by the nick delay
711   #   mechanism.
712   #
713   ERR_UNAVAILRESOURCE=437
714
715   # "<nick> <channel> :They aren't on that channel"
716   #
717   # - Returned by the server to indicate that the target
718   #   user of the command is not on the given channel.
719   #
720   ERR_USERNOTINCHANNEL=441
721
722   # "<channel> :You're not on that channel"
723   #
724   # - Returned by the server whenever a client tries to
725   #   perform a channel affecting command for which the
726   #   client isn't a member.
727   #
728   ERR_NOTONCHANNEL=442
729
730   # "<user> <channel> :is already on channel"
731   #
732   # - Returned when a client tries to invite a user to a
733   #   channel they are already on.
734   #
735   ERR_USERONCHANNEL=443
736
737   # "<user> :User not logged in"
738   #
739   # - Returned by the summon after a SUMMON command for a
740   #   user was unable to be performed since they were not
741   #   logged in.
742   #
743   ERR_NOLOGIN=444
744
745   # ":SUMMON has been disabled"
746   #
747   # - Returned as a response to the SUMMON command.  MUST be
748   #   returned by any server which doesn't implement it.
749   #
750   ERR_SUMMONDISABLED=445
751
752   # ":USERS has been disabled"
753   #
754   # - Returned as a response to the USERS command.  MUST be
755   #   returned by any server which does not implement it.
756   #
757   ERR_USERSDISABLED=446
758
759   # ":You have not registered"
760   #
761   # - Returned by the server to indicate that the client
762   #   MUST be registered before the server will allow it
763   #   to be parsed in detail.
764   #
765   ERR_NOTREGISTERED=451
766
767   # "<command> :Not enough parameters"
768   #
769   # - Returned by the server by numerous commands to
770   #   indicate to the client that it didn't supply enough
771   #   parameters.
772   #
773   ERR_NEEDMOREPARAMS=461
774
775   # ":Unauthorized command (already registered)"
776   #
777   # - Returned by the server to any link which tries to
778   #   change part of the registered details (such as
779   #   password or user details from second USER message).
780   #
781   ERR_ALREADYREGISTRED=462
782
783   # ":Your host isn't among the privileged"
784   #
785   # - Returned to a client which attempts to register with
786   #   a server which does not been setup to allow
787   #   connections from the host the attempted connection
788   #   is tried.
789   #
790   ERR_NOPERMFORHOST=463
791
792   # ":Password incorrect"
793   #
794   # - Returned to indicate a failed attempt at registering
795   #   a connection for which a password was required and
796   #   was either not given or incorrect.
797   #
798   ERR_PASSWDMISMATCH=464
799
800   # ":You are banned from this server"
801   #
802   # - Returned after an attempt to connect and register
803   #   yourself with a server which has been setup to
804   #   explicitly deny connections to you.
805   #
806   ERR_YOUREBANNEDCREEP=465
807
808   # - Sent by a server to a user to inform that access to the
809   #   server will soon be denied.
810   #
811   ERR_YOUWILLBEBANNED=466
812
813   # "<channel> :Channel key already set"
814   ERR_KEYSET=467
815
816   # "<channel> :Cannot join channel (+l)"
817   ERR_CHANNELISFULL=471
818
819   # "<char> :is unknown mode char to me for <channel>"
820   ERR_UNKNOWNMODE=472
821
822   # "<channel> :Cannot join channel (+i)"
823   ERR_INVITEONLYCHAN=473
824
825   # "<channel> :Cannot join channel (+b)"
826   ERR_BANNEDFROMCHAN=474
827
828   # "<channel> :Cannot join channel (+k)"
829   ERR_BADCHANNELKEY=475
830
831   # "<channel> :Bad Channel Mask"
832   ERR_BADCHANMASK=476
833
834   # "<channel> :Channel doesn't support modes"
835   ERR_NOCHANMODES=477
836
837   # "<channel> <char> :Channel list is full"
838   #
839   ERR_BANLISTFULL=478
840
841   # ":Permission Denied- You're not an IRC operator"
842   #
843   # - Any command requiring operator privileges to operate
844   #   MUST return this error to indicate the attempt was
845   #   unsuccessful.
846   #
847   ERR_NOPRIVILEGES=481
848
849   # "<channel> :You're not channel operator"
850   #
851   # - Any command requiring 'chanop' privileges (such as
852   #   MODE messages) MUST return this error if the client
853   #   making the attempt is not a chanop on the specified
854   #   channel.
855   #
856   #
857   ERR_CHANOPRIVSNEEDED=482
858
859   # ":You can't kill a server!"
860   #
861   # - Any attempts to use the KILL command on a server
862   #   are to be refused and this error returned directly
863   #   to the client.
864   #
865   ERR_CANTKILLSERVER=483
866
867   # ":Your connection is restricted!"
868   #
869   # - Sent by the server to a user upon connection to indicate
870   #   the restricted nature of the connection (user mode "+r").
871   #
872   ERR_RESTRICTED=484
873
874   # ":You're not the original channel operator"
875   #
876   # - Any MODE requiring "channel creator" privileges MUST
877   #   return this error if the client making the attempt is not
878   #   a chanop on the specified channel.
879   #
880   ERR_UNIQOPPRIVSNEEDED=485
881
882   # ":No O-lines for your host"
883   #
884   # - If a client sends an OPER message and the server has
885   #   not been configured to allow connections from the
886   #   client's host as an operator, this error MUST be
887   #   returned.
888   #
889   ERR_NOOPERHOST=491
890
891   # ":Unknown MODE flag"
892   #
893   # - Returned by the server to indicate that a MODE
894   #   message was sent with a nickname parameter and that
895   #   the a mode flag sent was not recognized.
896   #
897   ERR_UMODEUNKNOWNFLAG=501
898
899   # ":Cannot change mode for other users"
900   #
901   # - Error sent to any user trying to view or change the
902   #   user mode for a user other than themselves.
903   #
904   ERR_USERSDONTMATCH=502
905
906   # 5.3 Reserved numerics
907   #
908   # These numerics are not described above since they fall into one of
909   # the following categories:
910   #
911   # 1. no longer in use;
912   #
913   # 2. reserved for future planned use;
914   #
915   # 3. in current use but are part of a non-generic 'feature' of
916   #    the current IRC server.
917   #
918   RPL_SERVICEINFO=231
919   RPL_ENDOFSERVICES=232
920   RPL_SERVICE=233
921   RPL_NONE=300
922   RPL_WHOISCHANOP=316
923   RPL_KILLDONE=361
924   RPL_CLOSING=362
925   RPL_CLOSEEND=363
926   RPL_INFOSTART=373
927   RPL_MYPORTIS=384
928   RPL_STATSCLINE=213
929   RPL_STATSNLINE=214
930   RPL_STATSILINE=215
931   RPL_STATSKLINE=216
932   RPL_STATSQLINE=217
933   RPL_STATSYLINE=218
934   RPL_STATSVLINE=240
935   RPL_STATSLLINE=241
936   RPL_STATSHLINE=244
937   RPL_STATSSLINE=244
938   RPL_STATSPING=246
939   RPL_STATSBLINE=247
940   ERR_NOSERVICEHOST=492
941   RPL_DATASTR=290
942
943   # Implements RFC 2812 and prior IRC RFCs.
944   #
945   # Clients should register Proc{}s to handle the various server events, and
946   # the Client class will handle dispatch.
947   class Client
948
949     # the Server we're connected to
950     attr_reader :server
951     # the User representing us on that server
952     attr_reader :user
953
954     # Create a new Client instance
955     def initialize
956       @server = Server.new         # The Server
957       @user = @server.user("*!*@*")     # The User representing the client on this Server
958
959       @handlers = Hash.new
960
961       # This is used by some messages to build lists of users that
962       # will be delegated when the ENDOF... message is received
963       @tmpusers = []
964     end
965
966     # Clear the server and reset the user
967     def reset
968       @server.clear
969       @user = @server.user("*!*@*")
970     end
971
972     # key::   server event to handle
973     # value:: proc object called when event occurs
974     # set a handler for a server event
975     #
976     # ==server events currently supported:
977     #
978     # TODO handle errors ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL
979     # TODO handle errors ERR_CHANOPRIVSNEEDED, ERR_CANNOTSENDTOCHAN
980     #
981     # welcome::     server welcome message on connect
982     # yourhost::    your host details (on connection)
983     # created::     when the server was started
984     # isupport::    information about what this server supports
985     # ping::        server pings you (default handler returns a pong)
986     # nicktaken::   you tried to change nick to one that's in use
987     # badnick::     you tried to change nick to one that's invalid
988     # topic::       someone changed the topic of a channel
989     # topicinfo::   on joining a channel or asking for the topic, tells you
990     #               who set it and when
991     # names::       server sends list of channel members when you join
992     # motd::        server message of the day
993     # privmsg::     privmsg, the core of IRC, a message to you from someone
994     # public::      optionally instead of getting privmsg you can hook to only
995     #               the public ones...
996     # msg::         or only the private ones, or both
997     # kick::        someone got kicked from a channel
998     # part::        someone left a channel
999     # quit::        someone quit IRC
1000     # join::        someone joined a channel
1001     # changetopic:: the topic of a channel changed
1002     # invite::      you are invited to a channel
1003     # nick::        someone changed their nick
1004     # mode::        a mode change
1005     # notice::      someone sends you a notice
1006     # unknown::     any other message not handled by the above
1007     def []=(key, value)
1008       @handlers[key] = value
1009     end
1010
1011     # key:: event name
1012     # remove a handler for a server event
1013     def deletehandler(key)
1014       @handlers.delete(key)
1015     end
1016
1017     # takes a server string, checks for PING, PRIVMSG, NOTIFY, etc, and parses
1018     # numeric server replies, calling the appropriate handler for each, and
1019     # sending it a hash containing the data from the server
1020     def process(serverstring)
1021       data = Hash.new
1022       data[:serverstring] = serverstring
1023
1024       unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
1025         raise "Unparseable Server Message!!!: #{serverstring.inspect}"
1026       end
1027
1028       prefix, command, params = $2, $3, $5
1029
1030       if prefix != nil
1031         # Most servers will send a full nick!user@host prefix for
1032         # messages from users. Therefore, when the prefix doesn't match this
1033         # syntax it's usually the server hostname.
1034         #
1035         # This is not always true, though, since some servers do not send a
1036         # full hostmask for user messages.
1037         #
1038         if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
1039           data[:source] = @server.user(prefix)
1040         else
1041           if @server.hostname
1042             if @server.hostname != prefix
1043               # TODO do we want to be able to differentiate messages that are passed on to us from /other/ servers?
1044               debug "Origin #{prefix} for message\n\t#{serverstring.inspect}\nis neither a user hostmask nor the server hostname\nI'll pretend that it's from the server anyway"
1045               data[:source] = @server
1046             else
1047               data[:source] = @server
1048             end
1049           else
1050             @server.instance_variable_set(:@hostname, prefix)
1051             data[:source] = @server
1052           end
1053         end
1054       end
1055
1056       # split parameters in an array
1057       argv = []
1058       params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
1059
1060       if command =~ /^(\d+)$/ # Numeric replies
1061         data[:target] = argv[0]
1062         # A numeric reply /should/ be directed at the client, except when we're connecting with a used nick, in which case
1063         # it's directed at '*'
1064         not_us = !([@user.nick, '*'].include?(data[:target]))
1065         if not_us
1066           warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
1067         end
1068
1069         num=command.to_i
1070         case num
1071         when RPL_WELCOME
1072           data[:message] = argv[1]
1073           # "Welcome to the Internet Relay Network
1074           # <nick>!<user>@<host>"
1075           if not_us
1076             warning "Server thinks client (#{@user.inspect}) has a different nick"
1077             @user.nick = data[:target]
1078           end
1079           if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
1080             nick = $1
1081             user = $2
1082             host = $3
1083             warning "Welcome message nick mismatch (#{nick} vs #{data[:target]})" if nick != data[:target]
1084             @user.user = user if user
1085             @user.host = host if host
1086           end
1087           handle(:welcome, data)
1088         when RPL_YOURHOST
1089           # "Your host is <servername>, running version <ver>"
1090           data[:message] = argv[1]
1091           handle(:yourhost, data)
1092         when RPL_CREATED
1093           # "This server was created <date>"
1094           data[:message] = argv[1]
1095           handle(:created, data)
1096         when RPL_MYINFO
1097           # "<servername> <version> <available user modes>
1098           # <available channel modes>"
1099           @server.parse_my_info(params.split(' ', 2).last)
1100           data[:servername] = @server.hostname
1101           data[:version] = @server.version
1102           data[:usermodes] = @server.usermodes
1103           data[:chanmodes] = @server.chanmodes
1104           handle(:myinfo, data)
1105         when RPL_ISUPPORT
1106           # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
1107           # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
1108           # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
1109           # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
1110           # on this server"
1111           #
1112           @server.parse_isupport(argv[1..-2].join(' '))
1113           handle(:isupport, data)
1114         when ERR_NICKNAMEINUSE
1115           # "* <nick> :Nickname is already in use"
1116           data[:nick] = argv[1]
1117           data[:message] = argv[2]
1118           handle(:nicktaken, data)
1119         when ERR_ERRONEUSNICKNAME
1120           # "* <nick> :Erroneous nickname"
1121           data[:nick] = argv[1]
1122           data[:message] = argv[2]
1123           handle(:badnick, data)
1124         when RPL_TOPIC
1125           data[:channel] = @server.get_channel(argv[1])
1126           data[:topic] = argv[2]
1127
1128           if data[:channel]
1129             data[:channel].topic.text = data[:topic]
1130           else
1131             warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1132           end
1133
1134           handle(:topic, data)
1135         when RPL_TOPIC_INFO
1136           data[:nick] = @server.user(argv[0])
1137           data[:channel] = @server.get_channel(argv[1])
1138
1139           # This must not be an IRC::User because it might not be an actual User,
1140           # and we risk overwriting valid User data
1141           data[:source] = argv[2].to_irc_netmask(:server => @server)
1142
1143           data[:time] = Time.at(argv[3].to_i)
1144
1145           if data[:channel]
1146             data[:channel].topic.set_by = data[:source]
1147             data[:channel].topic.set_on = data[:time]
1148           else
1149             warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1150           end
1151
1152           handle(:topicinfo, data)
1153         when RPL_NAMREPLY
1154           # "( "=" / "*" / "@" ) <channel>
1155           # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
1156           # - "@" is used for secret channels, "*" for private
1157           # channels, and "=" for others (public channels).
1158           data[:channeltype] = argv[1]
1159           data[:channel] = argv[2]
1160
1161           chan = @server.get_channel(data[:channel])
1162           unless chan
1163             warning "Received names #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1164             return
1165           end
1166
1167           users = []
1168           argv[3].scan(/\S+/).each { |u|
1169             # FIXME beware of servers that allow multiple prefixes
1170             if(u =~ /^([#{@server.supports[:prefix][:prefixes].join}])?(.*)$/)
1171               umode = $1
1172               user = $2
1173               users << [user, umode]
1174             end
1175           }
1176
1177           users.each { |ar|
1178             u = @server.user(ar[0])
1179             chan.add_user(u, :silent => true)
1180             debug "Adding user #{u}"
1181             if ar[1]
1182               ms = @server.mode_for_prefix(ar[1].to_sym)
1183               debug "\twith mode #{ar[1]} (#{ms})"
1184               chan.mode[ms].set(u)
1185             end
1186           }
1187           @tmpusers += users
1188         when RPL_ENDOFNAMES
1189           data[:channel] = argv[1]
1190           data[:users] = @tmpusers
1191           handle(:names, data)
1192           @tmpusers = Array.new
1193         when RPL_LUSERCLIENT
1194           # ":There are <integer> users and <integer>
1195           # services on <integer> servers"
1196           data[:message] = argv[1]
1197           handle(:luserclient, data)
1198         when RPL_LUSEROP
1199           # "<integer> :operator(s) online"
1200           data[:ops] = argv[1].to_i
1201           handle(:luserop, data)
1202         when RPL_LUSERUNKNOWN
1203           # "<integer> :unknown connection(s)"
1204           data[:unknown] = argv[1].to_i
1205           handle(:luserunknown, data)
1206         when RPL_LUSERCHANNELS
1207           # "<integer> :channels formed"
1208           data[:channels] = argv[1].to_i
1209           handle(:luserchannels, data)
1210         when RPL_LUSERME
1211           # ":I have <integer> clients and <integer> servers"
1212           data[:message] = argv[1]
1213           handle(:luserme, data)
1214         when ERR_NOMOTD
1215           # ":MOTD File is missing"
1216           data[:message] = argv[1]
1217           handle(:motd_missing, data)
1218         when RPL_LOCALUSERS
1219           # ":Current local  users: 3  Max: 4"
1220           data[:message] = argv[1]
1221           handle(:localusers, data)
1222         when RPL_GLOBALUSERS
1223           # ":Current global users: 3  Max: 4"
1224           data[:message] = argv[1]
1225           handle(:globalusers, data)
1226         when RPL_STATSCONN
1227           # ":Highest connection count: 4 (4 clients) (251 since server was
1228           # (re)started)"
1229           data[:message] = argv[1]
1230           handle(:statsconn, data)
1231         when RPL_MOTDSTART
1232           # "<nick> :- <server> Message of the Day -"
1233           if argv[1] =~ /^-\s+(\S+)\s/
1234             server = $1
1235           else
1236             warning "Server doesn't have an RFC compliant MOTD start."
1237           end
1238           @motd = ""
1239         when RPL_MOTD
1240           if(argv[1] =~ /^-\s+(.*)$/)
1241             @motd << $1
1242             @motd << "\n"
1243           end
1244         when RPL_ENDOFMOTD
1245           data[:motd] = @motd
1246           handle(:motd, data)
1247         when RPL_DATASTR
1248           data[:text] = argv[1]
1249           handle(:datastr, data)
1250         when RPL_WHOREPLY
1251           data[:channel] = argv[1]
1252           data[:user] = argv[2]
1253           data[:host] = argv[3]
1254           data[:userserver] = argv[4]
1255           data[:nick] = argv[5]
1256           if argv[6] =~ /^(H|G)(\*)?(.*)?$/
1257             data[:away] = ($1 == 'G')
1258             data[:ircop] = $2
1259             data[:modes] = $3.scan(/./).map { |mode|
1260               m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
1261               @server.supports[:prefix][:modes][m]
1262             } rescue []
1263           else
1264             warning "Strange WHO reply: #{serverstring.inspect}"
1265           end
1266           data[:hopcount], data[:real_name] = argv[7].split(" ", 2)
1267
1268           user = @server.get_user(data[:nick])
1269
1270           user.user = data[:user]
1271           user.host = data[:host]
1272           user.away = data[:away] # FIXME doesn't provide the actual message
1273           # TODO ircop status
1274           # TODO userserver
1275           # TODO hopcount
1276           user.real_name = data[:real_name]
1277
1278           channel = @server.get_channel(data[:channel])
1279
1280           channel.add_user(user, :silent=>true)
1281           data[:modes].map { |mode|
1282             channel.mode[mode].set(user)
1283           }
1284
1285           handle(:who, data)
1286         when RPL_ENDOFWHO
1287           handle(:eowho, data)
1288         else
1289           handle(:unknown, data)
1290         end
1291         return # We've processed the numeric reply
1292       end
1293
1294       # Otherwise, the command should be a single word
1295       case command.to_sym
1296       when :PING
1297         data[:pingid] = argv[0]
1298         handle(:ping, data)
1299       when :PONG
1300         data[:pingid] = argv[0]
1301         handle(:pong, data)
1302       when :PRIVMSG
1303         # you can either bind to 'PRIVMSG', to get every one and
1304         # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
1305         # etc and get it all nicely split up for you.
1306
1307         begin
1308           data[:target] = @server.user_or_channel(argv[0])
1309         rescue
1310           # The previous may fail e.g. when the target is a server or something
1311           # like that (e.g. $<mask>). In any of these cases, we just use the
1312           # String as a target
1313           # FIXME we probably want to explicitly check for the #<mask> $<mask>
1314           data[:target] = argv[0]
1315         end
1316         data[:message] = argv[1]
1317         handle(:privmsg, data)
1318
1319         # Now we split it
1320         if data[:target].kind_of?(Channel)
1321           handle(:public, data)
1322         else
1323           handle(:msg, data)
1324         end
1325       when :NOTICE
1326         begin
1327           data[:target] = @server.user_or_channel(argv[0])
1328         rescue
1329           # The previous may fail e.g. when the target is a server or something
1330           # like that (e.g. $<mask>). In any of these cases, we just use the
1331           # String as a target
1332           # FIXME we probably want to explicitly check for the #<mask> $<mask>
1333           data[:target] = argv[0]
1334         end
1335         data[:message] = argv[1]
1336         case data[:source]
1337         when User
1338           handle(:notice, data)
1339         else
1340           # "server notice" (not from user, noone to reply to)
1341           handle(:snotice, data)
1342         end
1343       when :KICK
1344         data[:channel] = @server.channel(argv[0])
1345         data[:target] = @server.user(argv[1])
1346         data[:message] = argv[2]
1347
1348         @server.delete_user_from_channel(data[:target], data[:channel])
1349         if data[:target] == @user
1350           @server.delete_channel(data[:channel])
1351         end
1352
1353         handle(:kick, data)
1354       when :PART
1355         data[:channel] = @server.channel(argv[0])
1356         data[:message] = argv[1]
1357
1358         @server.delete_user_from_channel(data[:source], data[:channel])
1359         if data[:source] == @user
1360           @server.delete_channel(data[:channel])
1361         end
1362
1363         handle(:part, data)
1364       when :QUIT
1365         data[:message] = argv[0]
1366         data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1367           list << ch if ch.has_user?(data[:source])
1368           list
1369         }
1370
1371         @server.delete_user(data[:source])
1372
1373         handle(:quit, data)
1374       when :JOIN
1375         data[:channel] = @server.channel(argv[0])
1376         data[:channel].add_user(data[:source])
1377
1378         handle(:join, data)
1379       when :TOPIC
1380         data[:channel] = @server.channel(argv[0])
1381         data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
1382         data[:channel].topic.replace(data[:topic])
1383
1384         handle(:changetopic, data)
1385       when :INVITE
1386         data[:target] = @server.user(argv[0])
1387         data[:channel] = @server.channel(argv[1])
1388
1389         handle(:invite, data)
1390       when :NICK
1391         data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1392           list << ch if ch.has_user?(data[:source])
1393           list
1394         }
1395
1396         data[:newnick] = argv[0]
1397         data[:oldnick] = data[:source].nick.dup
1398         data[:source].nick = data[:newnick]
1399
1400         debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1401
1402         handle(:nick, data)
1403       when :MODE
1404         # MODE ([+-]<modes> (<params>)*)*
1405         # When a MODE message is received by a server,
1406         # Type C will have parameters too, so we must
1407         # be able to consume parameters for all
1408         # but Type D modes
1409
1410         data[:target] = @server.user_or_channel(argv[0])
1411         data[:modestring] = argv[1..-1].join(" ")
1412         # data[:modes] is an array where each element
1413         # is an array with two elements, the first of which
1414         # is either :set or :reset, and the second symbol
1415         # is the mode letter. An optional third element
1416         # is present e.g. for channel modes that need
1417         # a parameter
1418         data[:modes] = []
1419         case data[:target]
1420         when User
1421           # User modes aren't currently handled internally,
1422           # but we still parse them and delegate to the client
1423           warning "Unhandled user mode message '#{serverstring}'"
1424           argv[1..-1].each { |arg|
1425             setting = arg[0].chr
1426             if "+-".include?(setting)
1427               setting = setting == "+" ? :set : :reset
1428               arg[1..-1].each_byte { |b|
1429                 m = b.chr.intern
1430                 data[:modes] << [setting, m]
1431               }
1432             else
1433               # Although typically User modes don't take an argument,
1434               # this is not true for all modes on all servers. Since
1435               # we have no knowledge of which modes take parameters
1436               # and which don't we just assign it to the last
1437               # mode. This is not going to do strange things often,
1438               # as usually User modes are only set one at a time
1439               warning "Unhandled user mode parameter #{arg} found"
1440               data[:modes].last << arg
1441             end
1442           }
1443         else
1444           # array of indices in data[:modes] where parameters
1445           # are needed
1446           who_wants_params = []
1447
1448           argv[1..-1].each { |arg|
1449             setting = arg[0].chr
1450             if "+-".include?(setting)
1451               setting = setting == "+" ? :set : :reset
1452               arg[1..-1].each_byte { |b|
1453                 m = b.chr.intern
1454                 data[:modes] << [setting, m]
1455                 case m
1456                 when *@server.supports[:chanmodes][:typea]
1457                   who_wants_params << data[:modes].length - 1
1458                 when *@server.supports[:chanmodes][:typeb]
1459                   who_wants_params << data[:modes].length - 1
1460                 when *@server.supports[:chanmodes][:typec]
1461                   if setting == :set
1462                     who_wants_params << data[:modes].length - 1
1463                   end
1464                 when *@server.supports[:chanmodes][:typed]
1465                   # Nothing to do
1466                 when *@server.supports[:prefix][:modes]
1467                   who_wants_params << data[:modes].length - 1
1468                 else
1469                   warning "Unknown mode #{m} in #{serverstring.inspect}"
1470                 end
1471               }
1472             else
1473               idx = who_wants_params.shift
1474               if idx.nil?
1475                 warning "Oops, problems parsing #{serverstring.inspect}"
1476                 break
1477               end
1478               data[:modes][idx] << arg
1479             end
1480           }
1481
1482           data[:modes].each { |mode|
1483             set, key, val = mode
1484             if val
1485               data[:target].mode[key].send(set, val)
1486             else
1487               data[:target].mode[key].send(set)
1488             end
1489           }
1490         end
1491
1492         handle(:mode, data)
1493       else
1494         warning "Unknown message #{serverstring.inspect}"
1495         handle(:unknown, data)
1496       end
1497     end
1498
1499     private
1500
1501     # key::  server event name
1502     # data:: hash containing data about the event, passed to the proc
1503     # call client's proc for an event, if they set one as a handler
1504     def handle(key, data)
1505       if(@handlers.has_key?(key))
1506         @handlers[key].call(data)
1507       end
1508     end
1509   end
1510 end