4 # :title: RFC 2821 Client Protocol module
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)
10 # - The server sends Replies 001 to 004 to a user upon
11 # successful registration.
13 # "Welcome to the Internet Relay Network
14 # <nick>!<user>@<host>"
18 # "Your host is <servername>, running version <ver>"
21 # "This server was created <date>"
24 # "<servername> <version> <available user modes> <available channel modes>"
27 # "005 nick PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
29 # defines the capabilities supported by the server.
31 # Previous RFCs defined message 005 as follows:
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.
37 # # "Try server <server name>, port <port number>"
43 # ":*1<reply> *( " " <reply> )"
45 # - Reply format used by USERHOST to list replies to
46 # the query list. The reply string is composed as
49 # reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
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
58 # ":*1<nick> *( " " <nick> )"
60 # - Reply format used by ISON to list replies to the
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.
72 # "<nick> :<away message>"
75 # ":You are no longer marked as being away"
78 # ":You have been marked as being away"
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.
96 # "<nick> <user> <host> * :<real name>"
99 # "<nick> <server> :<server info>"
102 # "<nick> :is an IRC operator"
103 RPL_WHOISOPERATOR=313
105 # "<nick> <integer> :seconds idle"
108 # "<nick> :End of WHOIS list"
111 # "<nick> :*( ( "@" / "+" ) <channel> " " )"
112 RPL_WHOISCHANNELS=319
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).
121 # "<nick> <user> <host> * :<real name>"
124 # "<nick> :End of WHOWAS"
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.
132 # Obsolete. Not used.
135 # "<channel> <# visible> :<topic>"
141 # "<channel> <nickname>"
144 # "<channel> <mode> <mode params>"
145 RPL_CHANNELMODEIS=324
147 # "<channel> <unixtime>"
153 # "<channel> :No topic is set"
156 # - When sending a TOPIC message to determine the
157 # channel topic, one of two replies is sent. If
158 # the topic is set, RPL_TOPIC is sent back else
161 # "<channel> :<topic>"
164 # <channel> <set by> <unixtime>
169 # - Returned by the server to indicate that the
170 # attempted INVITE message was successful and is
171 # being passed onto the end client.
175 # "<user> :Summoning user to IRC"
177 # - Returned by a server answering a SUMMON message to
178 # indicate that it is summoning that user.
182 # "<channel> <invitemask>"
185 # "<channel> :End of channel invite list"
187 # - When listing the 'invitations masks' for a given channel,
188 # a server is required to send the list back using the
189 # RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A
190 # separate RPL_INVITELIST is sent for each active mask.
191 # After the masks have been listed (or if none present) a
192 # RPL_ENDOFINVITELIST MUST be sent.
194 RPL_ENDOFINVITELIST=347
196 # "<channel> <exceptionmask>"
199 # "<channel> :End of channel exception list"
201 # - When listing the 'exception masks' for a given channel,
202 # a server is required to send the list back using the
203 # RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A
204 # separate RPL_EXCEPTLIST is sent for each active mask.
205 # After the masks have been listed (or if none present)
206 # a RPL_ENDOFEXCEPTLIST MUST be sent.
208 RPL_ENDOFEXCEPTLIST=349
210 # "<version>.<debuglevel> <server> :<comments>"
212 # - Reply by the server showing its version details.
214 # The <version> is the version of the software being
215 # used (including any patchlevel revisions) and the
216 # <debuglevel> is used to indicate if the server is
217 # running in "debug mode".
219 # The "comments" field may contain any comments about
220 # the version or further version details.
224 # - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used
225 # to answer a WHO message. The RPL_WHOREPLY is only
226 # sent if there is an appropriate match to the WHO
227 # query. If there is a list of parameters supplied
228 # with a WHO message, a RPL_ENDOFWHO MUST be sent
229 # after processing each list item with <name> being
232 # "<channel> <user> <host> <server> <nick>
233 # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
234 # :<hopcount> <real name>"
238 # "<name> :End of WHO list"
241 # - To reply to a NAMES message, a reply pair consisting
242 # of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
243 # server back to the client. If there is no channel
244 # found as in the query, then only RPL_ENDOFNAMES is
245 # returned. The exception to this is when a NAMES
246 # message is sent with no parameters and all visible
247 # channels and contents are sent back in a series of
248 # RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
251 # "( "=" / "*" / "@" ) <channel>
252 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
253 # - "@" is used for secret channels, "*" for private
254 # channels, and "=" for others (public channels).
258 # "<channel> :End of NAMES list"
261 # - In replying to the LINKS message, a server MUST send
262 # replies back using the RPL_LINKS numeric and mark the
263 # end of the list using an RPL_ENDOFLINKS reply.
265 # "<mask> <server> :<hopcount> <server info>"
268 # "<mask> :End of LINKS list"
271 # - When listing the active 'bans' for a given channel,
272 # a server is required to send the list back using the
273 # RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate
274 # RPL_BANLIST is sent for each active banmask. After the
275 # banmasks have been listed (or if none present) a
276 # RPL_ENDOFBANLIST MUST be sent.
278 # "<channel> <banmask>"
281 # "<channel> :End of channel ban list"
284 # - A server responding to an INFO message is required to
285 # send all its 'info' in a series of RPL_INFO messages
286 # with a RPL_ENDOFINFO reply to indicate the end of the
292 # ":End of INFO list"
295 # - When responding to the MOTD message and the MOTD file
296 # is found, the file is displayed line by line, with
297 # each line no longer than 80 characters, using
298 # RPL_MOTD format replies. These MUST be surrounded
299 # by a RPL_MOTDSTART (before the RPL_MOTDs) and an
300 # RPL_ENDOFMOTD (after).
302 # ":- <server> Message of the day - "
308 # ":End of MOTD command"
311 # ":You are now an IRC operator"
313 # - RPL_YOUREOPER is sent back to a client which has
314 # just successfully issued an OPER message and gained
319 # "<config file> :Rehashing"
321 # - If the REHASH option is used and an operator sends
322 # a REHASH message, an RPL_REHASHING is sent back to
327 # "You are service <servicename>"
329 # - Sent by the server to a service upon successful
334 # "<server> :<string showing server's local time>"
336 # - When replying to the TIME message, a server MUST send
337 # the reply using the RPL_TIME format above. The string
338 # showing the time need only contain the correct day and
339 # time there. There is no further requirement for the
344 # - If the USERS message is handled by a server, the
345 # replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and
346 # RPL_NOUSERS are used. RPL_USERSSTART MUST be sent
347 # first, following by either a sequence of RPL_USERS
348 # or a single RPL_NOUSER. Following this is
351 # ":UserID Terminal Host"
354 # ":<username> <ttyline> <hostname>"
360 # ":Nobody logged in"
363 # - The RPL_TRACE* are all returned by the server in
364 # response to the TRACE message. How many are
365 # returned is dependent on the TRACE message and
366 # whether it was sent by an operator or not. There
367 # is no predefined order for which occurs first.
368 # Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
369 # RPL_TRACEHANDSHAKE are all used for connections
370 # which have not been fully established and are either
371 # unknown, still attempting to connect or in the
372 # process of completing the 'server handshake'.
373 # RPL_TRACELINK is sent by any server which handles
374 # a TRACE message and has to pass it on to another
375 # server. The list of RPL_TRACELINKs sent in
376 # response to a TRACE command traversing the IRC
377 # network should reflect the actual connectivity of
378 # the servers themselves along that path.
380 # RPL_TRACENEWTYPE is to be used for any connection
381 # which does not fit in the other categories but is
382 # being displayed anyway.
383 # RPL_TRACEEND is sent to indicate the end of the list.
385 # "Link <version & debug level> <destination>
386 # <next server> V<protocol version>
387 # <link uptime in seconds> <backstream sendq>
391 # "Try. <class> <server>"
392 RPL_TRACECONNECTING=201
394 # "H.S. <class> <server>"
395 RPL_TRACEHANDSHAKE=202
397 # "???? <class> [<client IP address in dot form>]"
400 # "Oper <class> <nick>"
401 RPL_TRACEOPERATOR=204
403 # "User <class> <nick>"
406 # "Serv <class> <int>S <int>C <server>
407 # <nick!user|*!*>@<host|server> V<protocol version>"
410 # "Service <class> <name> <type> <active type>"
413 # "<newtype> 0 <client name>"
416 # "Class <class> <count>"
420 RPL_TRACERECONNECT=210
422 # "File <logfile> <debug level>"
425 # "<server name> <version & debug level> :End of TRACE"
428 # ":Current local users: 3 Max: 4"
431 # ":Current global users: 3 Max: 4"
434 # "::Highest connection count: 4 (4 clients) (251 since server was
438 # "<linkname> <sendq> <sent messages>
439 # <sent Kbytes> <received messages>
440 # <received Kbytes> <time open>"
442 # - reports statistics on a connection. <linkname>
443 # identifies the particular connection, <sendq> is
444 # the amount of data that is queued and waiting to be
445 # sent <sent messages> the number of messages sent,
446 # and <sent Kbytes> the amount of data sent, in
447 # Kbytes. <received messages> and <received Kbytes>
448 # are the equivalent of <sent messages> and <sent
449 # Kbytes> for received data, respectively. <time
450 # open> indicates how long ago the connection was
451 # opened, in seconds.
453 RPL_STATSLINKINFO=211
455 # "<command> <count> <byte count> <remote count>"
457 # - reports statistics on commands usage.
459 RPL_STATSCOMMANDS=212
461 # "<stats letter> :End of STATS report"
465 # ":Server Up %d days %d:%02d:%02d"
467 # - reports the server uptime.
471 # "O <hostmask> * <name>"
473 # - reports the allowed hosts from where user may become IRC
478 # "<user mode string>"
480 # - To answer a query about a client's own mode,
481 # RPL_UMODEIS is sent back.
485 # - When listing services in reply to a SERVLIST message,
486 # a server is required to send the list back using the
487 # RPL_SERVLIST and RPL_SERVLISTEND messages. A separate
488 # RPL_SERVLIST is sent for each service. After the
489 # services have been listed (or if none present) a
490 # RPL_SERVLISTEND MUST be sent.
492 # "<name> <server> <mask> <type> <hopcount> <info>"
495 # "<mask> <type> :End of service listing"
498 # - In processing an LUSERS message, the server
499 # sends a set of replies from RPL_LUSERCLIENT,
500 # RPL_LUSEROP, RPL_USERUNKNOWN,
501 # RPL_LUSERCHANNELS and RPL_LUSERME. When
502 # replying, a server MUST send back
503 # RPL_LUSERCLIENT and RPL_LUSERME. The other
504 # replies are only sent back if a non-zero count
507 # ":There are <integer> users and <integer>
508 # services on <integer> servers"
511 # "<integer> :operator(s) online"
514 # "<integer> :unknown connection(s)"
517 # "<integer> :channels formed"
518 RPL_LUSERCHANNELS=254
520 # ":I have <integer> clients and <integer> servers"
523 # - When replying to an ADMIN message, a server
524 # is expected to use replies RPL_ADMINME
525 # through to RPL_ADMINEMAIL and provide a text
526 # message with each. For RPL_ADMINLOC1 a
527 # description of what city, state and country
528 # the server is in is expected, followed by
529 # details of the institution (RPL_ADMINLOC2)
530 # and finally the administrative contact for the
531 # server (an email address here is REQUIRED)
534 # "<server> :Administrative info"
546 # "<command> :Please wait a while and try again."
548 # - When a server drops a command without processing it,
549 # it MUST use the reply RPL_TRYAGAIN to inform the
550 # originating client.
555 # Error replies are found in the range from 400 to 599.
557 # "<nickname> :No such nick/channel"
559 # - Used to indicate the nickname parameter supplied to a
560 # command is currently unused.
564 # "<server name> :No such server"
566 # - Used to indicate the server name given currently
571 # "<channel name> :No such channel"
573 # - Used to indicate the given channel name is invalid.
575 ERR_NOSUCHCHANNEL=403
577 # "<channel name> :Cannot send to channel"
579 # - Sent to a user who is either (a) not on a channel
580 # which is mode +n or (b) not a chanop (or mode +v) on
581 # a channel which has mode +m set or where the user is
582 # banned and is trying to send a PRIVMSG message to
585 ERR_CANNOTSENDTOCHAN=404
587 # "<channel name> :You have joined too many channels"
589 # - Sent to a user when they have joined the maximum
590 # number of allowed channels and they try to join
593 ERR_TOOMANYCHANNELS=405
595 # "<nickname> :There was no such nickname"
597 # - Returned by WHOWAS to indicate there is no history
598 # information for that nickname.
600 ERR_WASNOSUCHNICK=406
602 # "<target> :<error code> recipients. <abort message>"
604 # - Returned to a client which is attempting to send a
605 # PRIVMSG/NOTICE using the user@host destination format
606 # and for a user@host which has several occurrences.
608 # - Returned to a client which trying to send a
609 # PRIVMSG/NOTICE to too many recipients.
611 # - Returned to a client which is attempting to JOIN a safe
612 # channel using the shortname when there are more than one
615 ERR_TOOMANYTARGETS=407
617 # "<service name> :No such service"
619 # - Returned to a client which is attempting to send a SQUERY
620 # to a service which does not exist.
622 ERR_NOSUCHSERVICE=408
624 # ":No origin specified"
626 # - PING or PONG message missing the originator parameter.
630 # ":No recipient given (<command>)"
633 # - 412 - 415 are returned by PRIVMSG to indicate that
634 # the message wasn't delivered for some reason.
635 # ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
636 # are returned when an invalid use of
637 # "PRIVMSG $<server>" or "PRIVMSG #<host>" is attempted.
642 # "<mask> :No toplevel domain specified"
645 # "<mask> :Wildcard in toplevel domain"
648 # "<mask> :Bad Server/host mask"
651 # "<command> :Unknown command"
653 # - Returned to a registered client to indicate that the
654 # command sent is unknown by the server.
656 ERR_UNKNOWNCOMMAND=421
658 # ":MOTD File is missing"
660 # - Server's MOTD file could not be opened by the server.
664 # "<server> :No administrative info available"
666 # - Returned by a server in response to an ADMIN message
667 # when there is an error in finding the appropriate
672 # ":File error doing <file op> on <file>"
674 # - Generic error message used to report a failed file
675 # operation during the processing of a message.
679 # ":No nickname given"
681 # - Returned when a nickname parameter expected for a
682 # command and isn't found.
684 ERR_NONICKNAMEGIVEN=431
686 # "<nick> :Erroneous nickname"
688 # - Returned after receiving a NICK message which contains
689 # characters which do not fall in the defined set. See
690 # section 2.3.1 for details on valid nicknames.
692 ERR_ERRONEUSNICKNAME=432
694 # "<nick> :Nickname is already in use"
696 # - Returned when a NICK message is processed that results
697 # in an attempt to change to a currently existing
700 ERR_NICKNAMEINUSE=433
702 # "<nick> :Nickname collision KILL from <user>@<host>"
704 # - Returned by a server to a client when it detects a
705 # nickname collision (registered of a NICK that
706 # already exists by another server).
708 ERR_NICKCOLLISION=436
710 # "<nick/channel> :Nick/channel is temporarily unavailable"
712 # - Returned by a server to a user trying to join a channel
713 # currently blocked by the channel delay mechanism.
715 # - Returned by a server to a user trying to change nickname
716 # when the desired nickname is blocked by the nick delay
719 ERR_UNAVAILRESOURCE=437
721 # "<nick> <channel> :They aren't on that channel"
723 # - Returned by the server to indicate that the target
724 # user of the command is not on the given channel.
726 ERR_USERNOTINCHANNEL=441
728 # "<channel> :You're not on that channel"
730 # - Returned by the server whenever a client tries to
731 # perform a channel affecting command for which the
732 # client isn't a member.
736 # "<user> <channel> :is already on channel"
738 # - Returned when a client tries to invite a user to a
739 # channel they are already on.
741 ERR_USERONCHANNEL=443
743 # "<user> :User not logged in"
745 # - Returned by the summon after a SUMMON command for a
746 # user was unable to be performed since they were not
751 # ":SUMMON has been disabled"
753 # - Returned as a response to the SUMMON command. MUST be
754 # returned by any server which doesn't implement it.
756 ERR_SUMMONDISABLED=445
758 # ":USERS has been disabled"
760 # - Returned as a response to the USERS command. MUST be
761 # returned by any server which does not implement it.
763 ERR_USERSDISABLED=446
765 # ":You have not registered"
767 # - Returned by the server to indicate that the client
768 # MUST be registered before the server will allow it
769 # to be parsed in detail.
771 ERR_NOTREGISTERED=451
773 # "<command> :Not enough parameters"
775 # - Returned by the server by numerous commands to
776 # indicate to the client that it didn't supply enough
779 ERR_NEEDMOREPARAMS=461
781 # ":Unauthorized command (already registered)"
783 # - Returned by the server to any link which tries to
784 # change part of the registered details (such as
785 # password or user details from second USER message).
787 ERR_ALREADYREGISTRED=462
789 # ":Your host isn't among the privileged"
791 # - Returned to a client which attempts to register with
792 # a server which does not been setup to allow
793 # connections from the host the attempted connection
796 ERR_NOPERMFORHOST=463
798 # ":Password incorrect"
800 # - Returned to indicate a failed attempt at registering
801 # a connection for which a password was required and
802 # was either not given or incorrect.
804 ERR_PASSWDMISMATCH=464
806 # ":You are banned from this server"
808 # - Returned after an attempt to connect and register
809 # yourself with a server which has been setup to
810 # explicitly deny connections to you.
812 ERR_YOUREBANNEDCREEP=465
814 # - Sent by a server to a user to inform that access to the
815 # server will soon be denied.
817 ERR_YOUWILLBEBANNED=466
819 # "<channel> :Channel key already set"
822 # "<channel> :Cannot join channel (+l)"
823 ERR_CHANNELISFULL=471
825 # "<char> :is unknown mode char to me for <channel>"
828 # "<channel> :Cannot join channel (+i)"
829 ERR_INVITEONLYCHAN=473
831 # "<channel> :Cannot join channel (+b)"
832 ERR_BANNEDFROMCHAN=474
834 # "<channel> :Cannot join channel (+k)"
835 ERR_BADCHANNELKEY=475
837 # "<channel> :Bad Channel Mask"
840 # "<channel> :Channel doesn't support modes"
843 # "<channel> <char> :Channel list is full"
847 # ":Permission Denied- You're not an IRC operator"
849 # - Any command requiring operator privileges to operate
850 # MUST return this error to indicate the attempt was
855 # "<channel> :You're not channel operator"
857 # - Any command requiring 'chanop' privileges (such as
858 # MODE messages) MUST return this error if the client
859 # making the attempt is not a chanop on the specified
863 ERR_CHANOPRIVSNEEDED=482
865 # ":You can't kill a server!"
867 # - Any attempts to use the KILL command on a server
868 # are to be refused and this error returned directly
871 ERR_CANTKILLSERVER=483
873 # ":Your connection is restricted!"
875 # - Sent by the server to a user upon connection to indicate
876 # the restricted nature of the connection (user mode "+r").
880 # ":You're not the original channel operator"
882 # - Any MODE requiring "channel creator" privileges MUST
883 # return this error if the client making the attempt is not
884 # a chanop on the specified channel.
886 ERR_UNIQOPPRIVSNEEDED=485
888 # ":No O-lines for your host"
890 # - If a client sends an OPER message and the server has
891 # not been configured to allow connections from the
892 # client's host as an operator, this error MUST be
897 # ":Unknown MODE flag"
899 # - Returned by the server to indicate that a MODE
900 # message was sent with a nickname parameter and that
901 # the a mode flag sent was not recognized.
903 ERR_UMODEUNKNOWNFLAG=501
905 # ":Cannot change mode for other users"
907 # - Error sent to any user trying to view or change the
908 # user mode for a user other than themselves.
910 ERR_USERSDONTMATCH=502
912 # 5.3 Reserved numerics
914 # These numerics are not described above since they fall into one of
915 # the following categories:
917 # 1. no longer in use;
919 # 2. reserved for future planned use;
921 # 3. in current use but are part of a non-generic 'feature' of
922 # the current IRC server.
925 RPL_ENDOFSERVICES=232
946 ERR_NOSERVICEHOST=492
949 # Implements RFC 2812 and prior IRC RFCs.
951 # Clients should register Proc{}s to handle the various server events, and
952 # the Client class will handle dispatch.
955 # the Server we're connected to
957 # the User representing us on that server
960 # Create a new Client instance
962 @server = Server.new # The Server
963 @user = @server.user("*!*@*") # The User representing the client on this Server
967 # This is used by some messages to build lists of users that
968 # will be delegated when the ENDOF... message is received
972 # Clear the server and reset the user
975 @user = @server.user("*!*@*")
978 # key:: server event to handle
979 # value:: proc object called when event occurs
980 # set a handler for a server event
982 # ==server events currently supported:
984 # TODO handle errors ERR_CHANOPRIVSNEEDED, ERR_CANNOTSENDTOCHAN
986 # welcome:: server welcome message on connect
987 # yourhost:: your host details (on connection)
988 # created:: when the server was started
989 # isupport:: information about what this server supports
990 # ping:: server pings you (default handler returns a pong)
991 # nicktaken:: you tried to change nick to one that's in use
992 # badnick:: you tried to change nick to one that's invalid
993 # topic:: someone changed the topic of a channel
994 # topicinfo:: on joining a channel or asking for the topic, tells you
995 # who set it and when
996 # names:: server sends list of channel members when you join
997 # motd:: server message of the day
998 # privmsg:: privmsg, the core of IRC, a message to you from someone
999 # public:: optionally instead of getting privmsg you can hook to only
1000 # the public ones...
1001 # msg:: or only the private ones, or both
1002 # kick:: someone got kicked from a channel
1003 # part:: someone left a channel
1004 # quit:: someone quit IRC
1005 # join:: someone joined a channel
1006 # changetopic:: the topic of a channel changed
1007 # invite:: you are invited to a channel
1008 # nick:: someone changed their nick
1009 # mode:: a mode change
1010 # notice:: someone sends you a notice
1011 # unknown:: any other message not handled by the above
1013 @handlers[key] = value
1017 # remove a handler for a server event
1018 def deletehandler(key)
1019 @handlers.delete(key)
1022 # takes a server string, checks for PING, PRIVMSG, NOTIFY, etc, and parses
1023 # numeric server replies, calling the appropriate handler for each, and
1024 # sending it a hash containing the data from the server
1025 def process(serverstring)
1027 data[:serverstring] = serverstring
1029 unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
1030 raise "Unparseable Server Message!!!: #{serverstring.inspect}"
1033 prefix, command, params = $2, $3, $5
1036 # Most servers will send a full nick!user@host prefix for
1037 # messages from users. Therefore, when the prefix doesn't match this
1038 # syntax it's usually the server hostname.
1040 # This is not always true, though, since some servers do not send a
1041 # full hostmask for user messages.
1043 if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
1044 data[:source] = @server.user(prefix)
1047 if @server.hostname != prefix
1048 # TODO do we want to be able to differentiate messages that are passed on to us from /other/ servers?
1049 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"
1050 data[:source] = @server
1052 data[:source] = @server
1055 @server.instance_variable_set(:@hostname, prefix)
1056 data[:source] = @server
1061 # split parameters in an array
1063 params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
1065 if command =~ /^(\d+)$/ # Numeric replies
1066 data[:target] = argv[0]
1067 # A numeric reply /should/ be directed at the client, except when we're connecting with a used nick, in which case
1068 # it's directed at '*'
1069 not_us = !([@user.nick, '*'].include?(data[:target]))
1071 warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
1077 data[:message] = argv[1]
1078 # "Welcome to the Internet Relay Network
1079 # <nick>!<user>@<host>"
1081 warning "Server thinks client (#{@user.inspect}) has a different nick"
1082 @user.nick = data[:target]
1084 if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
1088 warning "Welcome message nick mismatch (#{nick} vs #{data[:target]})" if nick != data[:target]
1089 @user.user = user if user
1090 @user.host = host if host
1092 handle(:welcome, data)
1094 # "Your host is <servername>, running version <ver>"
1095 data[:message] = argv[1]
1096 handle(:yourhost, data)
1098 # "This server was created <date>"
1099 data[:message] = argv[1]
1100 handle(:created, data)
1102 # "<servername> <version> <available user modes>
1103 # <available channel modes>"
1104 @server.parse_my_info(params.split(' ', 2).last)
1105 data[:servername] = @server.hostname
1106 data[:version] = @server.version
1107 data[:usermodes] = @server.usermodes
1108 data[:chanmodes] = @server.chanmodes
1109 handle(:myinfo, data)
1111 # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
1112 # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
1113 # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
1114 # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
1117 @server.parse_isupport(argv[1..-2].join(' '))
1118 handle(:isupport, data)
1119 when ERR_NICKNAMEINUSE
1120 # "* <nick> :Nickname is already in use"
1121 data[:nick] = argv[1]
1122 data[:message] = argv[2]
1123 handle(:nicktaken, data)
1124 when ERR_ERRONEUSNICKNAME
1125 # "* <nick> :Erroneous nickname"
1126 data[:nick] = argv[1]
1127 data[:message] = argv[2]
1128 handle(:badnick, data)
1130 data[:channel] = @server.channel(argv[1])
1131 data[:topic] = argv[2]
1132 data[:channel].topic.text = data[:topic]
1134 handle(:topic, data)
1136 data[:nick] = @server.user(argv[0])
1137 data[:channel] = @server.channel(argv[1])
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)
1143 data[:time] = Time.at(argv[3].to_i)
1145 data[:channel].topic.set_by = data[:source]
1146 data[:channel].topic.set_on = data[:time]
1148 handle(:topicinfo, data)
1150 # "( "=" / "*" / "@" ) <channel>
1151 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
1152 # - "@" is used for secret channels, "*" for private
1153 # channels, and "=" for others (public channels).
1154 data[:channeltype] = argv[1]
1155 data[:channel] = chan = @server.channel(argv[2])
1158 argv[3].scan(/\S+/).each { |u|
1159 # FIXME beware of servers that allow multiple prefixes
1160 if(u =~ /^([#{@server.supports[:prefix][:prefixes].join}])?(.*)$/)
1163 users << [user, umode]
1168 u = @server.user(ar[0])
1169 chan.add_user(u, :silent => true)
1170 debug "Adding user #{u}"
1172 ms = @server.mode_for_prefix(ar[1].to_sym)
1173 debug "\twith mode #{ar[1]} (#{ms})"
1174 chan.mode[ms].set(u)
1179 data[:channel] = @server.channel(argv[1])
1180 data[:users] = @tmpusers
1181 handle(:names, data)
1182 @tmpusers = Array.new
1183 when RPL_LUSERCLIENT
1184 # ":There are <integer> users and <integer>
1185 # services on <integer> servers"
1186 data[:message] = argv[1]
1187 handle(:luserclient, data)
1189 # "<integer> :operator(s) online"
1190 data[:ops] = argv[1].to_i
1191 handle(:luserop, data)
1192 when RPL_LUSERUNKNOWN
1193 # "<integer> :unknown connection(s)"
1194 data[:unknown] = argv[1].to_i
1195 handle(:luserunknown, data)
1196 when RPL_LUSERCHANNELS
1197 # "<integer> :channels formed"
1198 data[:channels] = argv[1].to_i
1199 handle(:luserchannels, data)
1201 # ":I have <integer> clients and <integer> servers"
1202 data[:message] = argv[1]
1203 handle(:luserme, data)
1205 # ":MOTD File is missing"
1206 data[:message] = argv[1]
1207 handle(:motd_missing, data)
1209 # ":Current local users: 3 Max: 4"
1210 data[:message] = argv[1]
1211 handle(:localusers, data)
1212 when RPL_GLOBALUSERS
1213 # ":Current global users: 3 Max: 4"
1214 data[:message] = argv[1]
1215 handle(:globalusers, data)
1217 # ":Highest connection count: 4 (4 clients) (251 since server was
1219 data[:message] = argv[1]
1220 handle(:statsconn, data)
1222 # "<nick> :- <server> Message of the Day -"
1223 if argv[1] =~ /^-\s+(\S+)\s/
1226 warning "Server doesn't have an RFC compliant MOTD start."
1230 if(argv[1] =~ /^-\s+(.*)$/)
1238 data[:text] = argv[1]
1239 handle(:datastr, data)
1241 data[:nick] = user = @server.user(argv[1])
1242 data[:message] = argv[-1]
1243 user.away = data[:message]
1246 data[:channel] = channel = @server.channel(argv[1])
1247 data[:user] = argv[2]
1248 data[:host] = argv[3]
1249 data[:userserver] = argv[4]
1250 data[:nick] = user = @server.user(argv[5])
1251 if argv[6] =~ /^(H|G)(\*)?(.*)?$/
1252 data[:away] = ($1 == 'G')
1254 data[:modes] = $3.scan(/./).map { |mode|
1255 m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
1256 @server.supports[:prefix][:modes][m]
1259 warning "Strange WHO reply: #{serverstring.inspect}"
1261 data[:hopcount], data[:real_name] = argv[7].split(" ", 2)
1263 user.user = data[:user]
1264 user.host = data[:host]
1265 user.away = data[:away] # FIXME doesn't provide the actual message
1269 user.real_name = data[:real_name]
1271 channel.add_user(user, :silent=>true)
1272 data[:modes].map { |mode|
1273 channel.mode[mode].set(user)
1278 handle(:eowho, data)
1281 @whois[:nick] = argv[1]
1282 @whois[:user] = argv[2]
1283 @whois[:host] = argv[3]
1284 @whois[:real_name] = argv[-1]
1286 user = @server.user(@whois[:nick])
1287 user.user = @whois[:user]
1288 user.host = @whois[:host]
1289 user.real_name = @whois[:real_name]
1290 when RPL_WHOISSERVER
1292 @whois[:nick] = argv[1]
1293 @whois[:server] = argv[2]
1294 @whois[:server_info] = argv[-1]
1295 # TODO update user info
1296 when RPL_WHOISOPERATOR
1298 @whois[:nick] = argv[1]
1299 @whois[:operator] = argv[-1]
1300 # TODO update user info
1303 @whois[:nick] = argv[1]
1304 user = @server.user(@whois[:nick])
1305 @whois[:idle] = argv[2].to_i
1306 user.idle_since = Time.now - @whois[:idle]
1307 if argv[-1] == 'seconds idle, signon time'
1308 @whois[:signon] = Time.at(argv[3].to_i)
1309 user.signon = @whois[:signon]
1313 @whois[:nick] = argv[1]
1314 data[:whois] = @whois.dup
1316 handle(:whois, data)
1317 when RPL_WHOISCHANNELS
1319 @whois[:nick] = argv[1]
1320 @whois[:channels] = []
1321 user = @server.user(@whois[:nick])
1322 argv[-1].split.each do |prechan|
1323 pfx = prechan.scan(/[#{@server.supports[:prefix][:prefixes].join}]/)
1324 modes = pfx.map { |p| @server.mode_for_prefix p }
1325 chan = prechan[pfx.length..prechan.length]
1327 channel = @server.channel(chan)
1328 channel.add_user(user, :silent => true)
1329 modes.map { |mode| channel.mode[mode].set(user) }
1331 @whois[:channels] << [chan, modes]
1333 when RPL_CHANNELMODEIS
1334 parse_mode(serverstring, argv[1..-1], data)
1336 when RPL_CREATIONTIME
1337 data[:channel] = @server.channel(argv[1])
1338 data[:time] = Time.at(argv[2].to_i)
1339 data[:channel].creation_time=data[:time]
1340 handle(:creationtime, data)
1341 when RPL_CHANNEL_URL
1342 data[:channel] = @server.channel(argv[1])
1343 data[:url] = argv[2]
1344 data[:channel].url=data[:url].dup
1345 handle(:channel_url, data)
1347 data[:nick] = argv[1]
1348 if user = @server.get_user(data[:nick])
1349 @server.delete_user(user)
1351 handle(:nosuchnick, data)
1352 when ERR_NOSUCHCHANNEL
1353 data[:channel] = argv[1]
1354 if channel = @server.get_channel(data[:channel])
1355 @server.delete_channel(channel)
1357 handle(:nosuchchannel, data)
1359 warning "Unknown message #{serverstring.inspect}"
1360 handle(:unknown, data)
1362 return # We've processed the numeric reply
1365 # Otherwise, the command should be a single word
1368 data[:pingid] = argv[0]
1371 data[:pingid] = argv[0]
1374 # you can either bind to 'PRIVMSG', to get every one and
1375 # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
1376 # etc and get it all nicely split up for you.
1379 data[:target] = @server.user_or_channel(argv[0])
1381 # The previous may fail e.g. when the target is a server or something
1382 # like that (e.g. $<mask>). In any of these cases, we just use the
1383 # String as a target
1384 # FIXME we probably want to explicitly check for the #<mask> $<mask>
1385 data[:target] = argv[0]
1387 data[:message] = argv[1]
1388 handle(:privmsg, data)
1391 if data[:target].kind_of?(Channel)
1392 handle(:public, data)
1398 data[:target] = @server.user_or_channel(argv[0])
1400 # The previous may fail e.g. when the target is a server or something
1401 # like that (e.g. $<mask>). In any of these cases, we just use the
1402 # String as a target
1403 # FIXME we probably want to explicitly check for the #<mask> $<mask>
1404 data[:target] = argv[0]
1406 data[:message] = argv[1]
1409 handle(:notice, data)
1411 # "server notice" (not from user, noone to reply to)
1412 handle(:snotice, data)
1415 data[:channel] = @server.channel(argv[0])
1416 data[:target] = @server.user(argv[1])
1417 data[:message] = argv[2]
1419 @server.delete_user_from_channel(data[:target], data[:channel])
1420 if data[:target] == @user
1421 @server.delete_channel(data[:channel])
1426 data[:channel] = @server.channel(argv[0])
1427 data[:message] = argv[1]
1429 @server.delete_user_from_channel(data[:source], data[:channel])
1430 if data[:source] == @user
1431 @server.delete_channel(data[:channel])
1436 data[:message] = argv[0]
1437 data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1438 list << ch if ch.has_user?(data[:source])
1442 @server.delete_user(data[:source])
1446 data[:channel] = @server.channel(argv[0])
1447 data[:channel].add_user(data[:source])
1451 data[:channel] = @server.channel(argv[0])
1452 data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
1453 data[:channel].topic.replace(data[:topic])
1455 handle(:changetopic, data)
1457 data[:target] = @server.user(argv[0])
1458 data[:channel] = @server.channel(argv[1])
1460 handle(:invite, data)
1462 data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1463 list << ch if ch.has_user?(data[:source])
1467 data[:newnick] = argv[0]
1468 data[:oldnick] = data[:source].nick.dup
1469 data[:source].nick = data[:newnick]
1471 debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1475 parse_mode(serverstring, argv, data)
1478 data[:message] = argv[1]
1479 handle(:error, data)
1481 warning "Unknown message #{serverstring.inspect}"
1482 handle(:unknown, data)
1488 # key:: server event name
1489 # data:: hash containing data about the event, passed to the proc
1490 # call client's proc for an event, if they set one as a handler
1491 def handle(key, data)
1492 if(@handlers.has_key?(key))
1493 @handlers[key].call(data)
1498 # MODE ([+-]<modes> (<params>)*)*
1499 # When a MODE message is received by a server,
1500 # Type C will have parameters too, so we must
1501 # be able to consume parameters for all
1503 def parse_mode(serverstring, argv, data)
1504 data[:target] = @server.user_or_channel(argv[0])
1505 data[:modestring] = argv[1..-1].join(" ")
1506 # data[:modes] is an array where each element
1507 # is an array with two elements, the first of which
1508 # is either :set or :reset, and the second symbol
1509 # is the mode letter. An optional third element
1510 # is present e.g. for channel modes that need
1515 # User modes aren't currently handled internally,
1516 # but we still parse them and delegate to the client
1517 warning "Unhandled user mode message '#{serverstring}'"
1518 argv[1..-1].each { |arg|
1519 setting = arg[0].chr
1520 if "+-".include?(setting)
1521 setting = setting == "+" ? :set : :reset
1522 arg[1..-1].each_byte { |b|
1524 data[:modes] << [setting, m]
1527 # Although typically User modes don't take an argument,
1528 # this is not true for all modes on all servers. Since
1529 # we have no knowledge of which modes take parameters
1530 # and which don't we just assign it to the last
1531 # mode. This is not going to do strange things often,
1532 # as usually User modes are only set one at a time
1533 warning "Unhandled user mode parameter #{arg} found"
1534 data[:modes].last << arg
1538 # array of indices in data[:modes] where parameters
1540 who_wants_params = []
1542 modes = argv[1..-1].dup
1544 getting_args = false
1545 while arg = modes.shift
1548 # getting args for previously set modes
1549 idx = who_wants_params.shift
1551 warning "Oops, problems parsing #{serverstring.inspect}"
1554 data[:modes][idx] << arg
1555 getting_args = false if who_wants_params.empty?
1557 debug @server.supports[:chanmodes]
1559 arg.each_byte do |c|
1567 data[:modes] << [setting, m]
1569 when *@server.supports[:chanmodes][:typea]
1570 who_wants_params << data[:modes].length - 1
1571 when *@server.supports[:chanmodes][:typeb]
1572 who_wants_params << data[:modes].length - 1
1573 when *@server.supports[:chanmodes][:typec]
1575 who_wants_params << data[:modes].length - 1
1577 when *@server.supports[:chanmodes][:typed]
1579 when *@server.supports[:prefix][:modes]
1580 who_wants_params << data[:modes].length - 1
1582 warning "Ignoring unknown mode #{m} in #{serverstring.inspect}"
1587 getting_args = true unless who_wants_params.empty?
1590 unless who_wants_params.empty?
1591 warning "Unhandled malformed modeline #{data[:modestring]} (unexpected empty arguments)"
1595 data[:modes].each { |mode|
1596 set, key, val = mode
1598 data[:target].mode[key].send(set, val)
1600 data[:target].mode[key].send(set)
1604 warning "Ignoring #{data[:modestring]} for unrecognized target #{argv[0]} (#{data[:target].inspect})"