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