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> :No topic is set"
150 # - When sending a TOPIC message to determine the
151 # channel topic, one of two replies is sent. If
152 # the topic is set, RPL_TOPIC is sent back else
155 # "<channel> :<topic>"
158 # <channel> <set by> <unixtime>
163 # - Returned by the server to indicate that the
164 # attempted INVITE message was successful and is
165 # being passed onto the end client.
169 # "<user> :Summoning user to IRC"
171 # - Returned by a server answering a SUMMON message to
172 # indicate that it is summoning that user.
176 # "<channel> <invitemask>"
179 # "<channel> :End of channel invite list"
181 # - When listing the 'invitations masks' for a given channel,
182 # a server is required to send the list back using the
183 # RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A
184 # separate RPL_INVITELIST is sent for each active mask.
185 # After the masks have been listed (or if none present) a
186 # RPL_ENDOFINVITELIST MUST be sent.
188 RPL_ENDOFINVITELIST=347
190 # "<channel> <exceptionmask>"
193 # "<channel> :End of channel exception list"
195 # - When listing the 'exception masks' for a given channel,
196 # a server is required to send the list back using the
197 # RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A
198 # separate RPL_EXCEPTLIST is sent for each active mask.
199 # After the masks have been listed (or if none present)
200 # a RPL_ENDOFEXCEPTLIST MUST be sent.
202 RPL_ENDOFEXCEPTLIST=349
204 # "<version>.<debuglevel> <server> :<comments>"
206 # - Reply by the server showing its version details.
208 # The <version> is the version of the software being
209 # used (including any patchlevel revisions) and the
210 # <debuglevel> is used to indicate if the server is
211 # running in "debug mode".
213 # The "comments" field may contain any comments about
214 # the version or further version details.
218 # - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used
219 # to answer a WHO message. The RPL_WHOREPLY is only
220 # sent if there is an appropriate match to the WHO
221 # query. If there is a list of parameters supplied
222 # with a WHO message, a RPL_ENDOFWHO MUST be sent
223 # after processing each list item with <name> being
226 # "<channel> <user> <host> <server> <nick>
227 # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
228 # :<hopcount> <real name>"
232 # "<name> :End of WHO list"
235 # - To reply to a NAMES message, a reply pair consisting
236 # of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
237 # server back to the client. If there is no channel
238 # found as in the query, then only RPL_ENDOFNAMES is
239 # returned. The exception to this is when a NAMES
240 # message is sent with no parameters and all visible
241 # channels and contents are sent back in a series of
242 # RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
245 # "( "=" / "*" / "@" ) <channel>
246 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
247 # - "@" is used for secret channels, "*" for private
248 # channels, and "=" for others (public channels).
252 # "<channel> :End of NAMES list"
255 # - In replying to the LINKS message, a server MUST send
256 # replies back using the RPL_LINKS numeric and mark the
257 # end of the list using an RPL_ENDOFLINKS reply.
259 # "<mask> <server> :<hopcount> <server info>"
262 # "<mask> :End of LINKS list"
265 # - When listing the active 'bans' for a given channel,
266 # a server is required to send the list back using the
267 # RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate
268 # RPL_BANLIST is sent for each active banmask. After the
269 # banmasks have been listed (or if none present) a
270 # RPL_ENDOFBANLIST MUST be sent.
272 # "<channel> <banmask>"
275 # "<channel> :End of channel ban list"
278 # - A server responding to an INFO message is required to
279 # send all its 'info' in a series of RPL_INFO messages
280 # with a RPL_ENDOFINFO reply to indicate the end of the
286 # ":End of INFO list"
289 # - When responding to the MOTD message and the MOTD file
290 # is found, the file is displayed line by line, with
291 # each line no longer than 80 characters, using
292 # RPL_MOTD format replies. These MUST be surrounded
293 # by a RPL_MOTDSTART (before the RPL_MOTDs) and an
294 # RPL_ENDOFMOTD (after).
296 # ":- <server> Message of the day - "
302 # ":End of MOTD command"
305 # ":You are now an IRC operator"
307 # - RPL_YOUREOPER is sent back to a client which has
308 # just successfully issued an OPER message and gained
313 # "<config file> :Rehashing"
315 # - If the REHASH option is used and an operator sends
316 # a REHASH message, an RPL_REHASHING is sent back to
321 # "You are service <servicename>"
323 # - Sent by the server to a service upon successful
328 # "<server> :<string showing server's local time>"
330 # - When replying to the TIME message, a server MUST send
331 # the reply using the RPL_TIME format above. The string
332 # showing the time need only contain the correct day and
333 # time there. There is no further requirement for the
338 # - If the USERS message is handled by a server, the
339 # replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and
340 # RPL_NOUSERS are used. RPL_USERSSTART MUST be sent
341 # first, following by either a sequence of RPL_USERS
342 # or a single RPL_NOUSER. Following this is
345 # ":UserID Terminal Host"
348 # ":<username> <ttyline> <hostname>"
354 # ":Nobody logged in"
357 # - The RPL_TRACE* are all returned by the server in
358 # response to the TRACE message. How many are
359 # returned is dependent on the TRACE message and
360 # whether it was sent by an operator or not. There
361 # is no predefined order for which occurs first.
362 # Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
363 # RPL_TRACEHANDSHAKE are all used for connections
364 # which have not been fully established and are either
365 # unknown, still attempting to connect or in the
366 # process of completing the 'server handshake'.
367 # RPL_TRACELINK is sent by any server which handles
368 # a TRACE message and has to pass it on to another
369 # server. The list of RPL_TRACELINKs sent in
370 # response to a TRACE command traversing the IRC
371 # network should reflect the actual connectivity of
372 # the servers themselves along that path.
374 # RPL_TRACENEWTYPE is to be used for any connection
375 # which does not fit in the other categories but is
376 # being displayed anyway.
377 # RPL_TRACEEND is sent to indicate the end of the list.
379 # "Link <version & debug level> <destination>
380 # <next server> V<protocol version>
381 # <link uptime in seconds> <backstream sendq>
385 # "Try. <class> <server>"
386 RPL_TRACECONNECTING=201
388 # "H.S. <class> <server>"
389 RPL_TRACEHANDSHAKE=202
391 # "???? <class> [<client IP address in dot form>]"
394 # "Oper <class> <nick>"
395 RPL_TRACEOPERATOR=204
397 # "User <class> <nick>"
400 # "Serv <class> <int>S <int>C <server>
401 # <nick!user|*!*>@<host|server> V<protocol version>"
404 # "Service <class> <name> <type> <active type>"
407 # "<newtype> 0 <client name>"
410 # "Class <class> <count>"
414 RPL_TRACERECONNECT=210
416 # "File <logfile> <debug level>"
419 # "<server name> <version & debug level> :End of TRACE"
422 # ":Current local users: 3 Max: 4"
425 # ":Current global users: 3 Max: 4"
428 # "::Highest connection count: 4 (4 clients) (251 since server was
432 # "<linkname> <sendq> <sent messages>
433 # <sent Kbytes> <received messages>
434 # <received Kbytes> <time open>"
436 # - reports statistics on a connection. <linkname>
437 # identifies the particular connection, <sendq> is
438 # the amount of data that is queued and waiting to be
439 # sent <sent messages> the number of messages sent,
440 # and <sent Kbytes> the amount of data sent, in
441 # Kbytes. <received messages> and <received Kbytes>
442 # are the equivalent of <sent messages> and <sent
443 # Kbytes> for received data, respectively. <time
444 # open> indicates how long ago the connection was
445 # opened, in seconds.
447 RPL_STATSLINKINFO=211
449 # "<command> <count> <byte count> <remote count>"
451 # - reports statistics on commands usage.
453 RPL_STATSCOMMANDS=212
455 # "<stats letter> :End of STATS report"
459 # ":Server Up %d days %d:%02d:%02d"
461 # - reports the server uptime.
465 # "O <hostmask> * <name>"
467 # - reports the allowed hosts from where user may become IRC
472 # "<user mode string>"
474 # - To answer a query about a client's own mode,
475 # RPL_UMODEIS is sent back.
479 # - When listing services in reply to a SERVLIST message,
480 # a server is required to send the list back using the
481 # RPL_SERVLIST and RPL_SERVLISTEND messages. A separate
482 # RPL_SERVLIST is sent for each service. After the
483 # services have been listed (or if none present) a
484 # RPL_SERVLISTEND MUST be sent.
486 # "<name> <server> <mask> <type> <hopcount> <info>"
489 # "<mask> <type> :End of service listing"
492 # - In processing an LUSERS message, the server
493 # sends a set of replies from RPL_LUSERCLIENT,
494 # RPL_LUSEROP, RPL_USERUNKNOWN,
495 # RPL_LUSERCHANNELS and RPL_LUSERME. When
496 # replying, a server MUST send back
497 # RPL_LUSERCLIENT and RPL_LUSERME. The other
498 # replies are only sent back if a non-zero count
501 # ":There are <integer> users and <integer>
502 # services on <integer> servers"
505 # "<integer> :operator(s) online"
508 # "<integer> :unknown connection(s)"
511 # "<integer> :channels formed"
512 RPL_LUSERCHANNELS=254
514 # ":I have <integer> clients and <integer> servers"
517 # - When replying to an ADMIN message, a server
518 # is expected to use replies RPL_ADMINME
519 # through to RPL_ADMINEMAIL and provide a text
520 # message with each. For RPL_ADMINLOC1 a
521 # description of what city, state and country
522 # the server is in is expected, followed by
523 # details of the institution (RPL_ADMINLOC2)
524 # and finally the administrative contact for the
525 # server (an email address here is REQUIRED)
528 # "<server> :Administrative info"
540 # "<command> :Please wait a while and try again."
542 # - When a server drops a command without processing it,
543 # it MUST use the reply RPL_TRYAGAIN to inform the
544 # originating client.
549 # Error replies are found in the range from 400 to 599.
551 # "<nickname> :No such nick/channel"
553 # - Used to indicate the nickname parameter supplied to a
554 # command is currently unused.
558 # "<server name> :No such server"
560 # - Used to indicate the server name given currently
565 # "<channel name> :No such channel"
567 # - Used to indicate the given channel name is invalid.
569 ERR_NOSUCHCHANNEL=403
571 # "<channel name> :Cannot send to channel"
573 # - Sent to a user who is either (a) not on a channel
574 # which is mode +n or (b) not a chanop (or mode +v) on
575 # a channel which has mode +m set or where the user is
576 # banned and is trying to send a PRIVMSG message to
579 ERR_CANNOTSENDTOCHAN=404
581 # "<channel name> :You have joined too many channels"
583 # - Sent to a user when they have joined the maximum
584 # number of allowed channels and they try to join
587 ERR_TOOMANYCHANNELS=405
589 # "<nickname> :There was no such nickname"
591 # - Returned by WHOWAS to indicate there is no history
592 # information for that nickname.
594 ERR_WASNOSUCHNICK=406
596 # "<target> :<error code> recipients. <abort message>"
598 # - Returned to a client which is attempting to send a
599 # PRIVMSG/NOTICE using the user@host destination format
600 # and for a user@host which has several occurrences.
602 # - Returned to a client which trying to send a
603 # PRIVMSG/NOTICE to too many recipients.
605 # - Returned to a client which is attempting to JOIN a safe
606 # channel using the shortname when there are more than one
609 ERR_TOOMANYTARGETS=407
611 # "<service name> :No such service"
613 # - Returned to a client which is attempting to send a SQUERY
614 # to a service which does not exist.
616 ERR_NOSUCHSERVICE=408
618 # ":No origin specified"
620 # - PING or PONG message missing the originator parameter.
624 # ":No recipient given (<command>)"
627 # - 412 - 415 are returned by PRIVMSG to indicate that
628 # the message wasn't delivered for some reason.
629 # ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
630 # are returned when an invalid use of
631 # "PRIVMSG $<server>" or "PRIVMSG #<host>" is attempted.
636 # "<mask> :No toplevel domain specified"
639 # "<mask> :Wildcard in toplevel domain"
642 # "<mask> :Bad Server/host mask"
645 # "<command> :Unknown command"
647 # - Returned to a registered client to indicate that the
648 # command sent is unknown by the server.
650 ERR_UNKNOWNCOMMAND=421
652 # ":MOTD File is missing"
654 # - Server's MOTD file could not be opened by the server.
658 # "<server> :No administrative info available"
660 # - Returned by a server in response to an ADMIN message
661 # when there is an error in finding the appropriate
666 # ":File error doing <file op> on <file>"
668 # - Generic error message used to report a failed file
669 # operation during the processing of a message.
673 # ":No nickname given"
675 # - Returned when a nickname parameter expected for a
676 # command and isn't found.
678 ERR_NONICKNAMEGIVEN=431
680 # "<nick> :Erroneous nickname"
682 # - Returned after receiving a NICK message which contains
683 # characters which do not fall in the defined set. See
684 # section 2.3.1 for details on valid nicknames.
686 ERR_ERRONEUSNICKNAME=432
688 # "<nick> :Nickname is already in use"
690 # - Returned when a NICK message is processed that results
691 # in an attempt to change to a currently existing
694 ERR_NICKNAMEINUSE=433
696 # "<nick> :Nickname collision KILL from <user>@<host>"
698 # - Returned by a server to a client when it detects a
699 # nickname collision (registered of a NICK that
700 # already exists by another server).
702 ERR_NICKCOLLISION=436
704 # "<nick/channel> :Nick/channel is temporarily unavailable"
706 # - Returned by a server to a user trying to join a channel
707 # currently blocked by the channel delay mechanism.
709 # - Returned by a server to a user trying to change nickname
710 # when the desired nickname is blocked by the nick delay
713 ERR_UNAVAILRESOURCE=437
715 # "<nick> <channel> :They aren't on that channel"
717 # - Returned by the server to indicate that the target
718 # user of the command is not on the given channel.
720 ERR_USERNOTINCHANNEL=441
722 # "<channel> :You're not on that channel"
724 # - Returned by the server whenever a client tries to
725 # perform a channel affecting command for which the
726 # client isn't a member.
730 # "<user> <channel> :is already on channel"
732 # - Returned when a client tries to invite a user to a
733 # channel they are already on.
735 ERR_USERONCHANNEL=443
737 # "<user> :User not logged in"
739 # - Returned by the summon after a SUMMON command for a
740 # user was unable to be performed since they were not
745 # ":SUMMON has been disabled"
747 # - Returned as a response to the SUMMON command. MUST be
748 # returned by any server which doesn't implement it.
750 ERR_SUMMONDISABLED=445
752 # ":USERS has been disabled"
754 # - Returned as a response to the USERS command. MUST be
755 # returned by any server which does not implement it.
757 ERR_USERSDISABLED=446
759 # ":You have not registered"
761 # - Returned by the server to indicate that the client
762 # MUST be registered before the server will allow it
763 # to be parsed in detail.
765 ERR_NOTREGISTERED=451
767 # "<command> :Not enough parameters"
769 # - Returned by the server by numerous commands to
770 # indicate to the client that it didn't supply enough
773 ERR_NEEDMOREPARAMS=461
775 # ":Unauthorized command (already registered)"
777 # - Returned by the server to any link which tries to
778 # change part of the registered details (such as
779 # password or user details from second USER message).
781 ERR_ALREADYREGISTRED=462
783 # ":Your host isn't among the privileged"
785 # - Returned to a client which attempts to register with
786 # a server which does not been setup to allow
787 # connections from the host the attempted connection
790 ERR_NOPERMFORHOST=463
792 # ":Password incorrect"
794 # - Returned to indicate a failed attempt at registering
795 # a connection for which a password was required and
796 # was either not given or incorrect.
798 ERR_PASSWDMISMATCH=464
800 # ":You are banned from this server"
802 # - Returned after an attempt to connect and register
803 # yourself with a server which has been setup to
804 # explicitly deny connections to you.
806 ERR_YOUREBANNEDCREEP=465
808 # - Sent by a server to a user to inform that access to the
809 # server will soon be denied.
811 ERR_YOUWILLBEBANNED=466
813 # "<channel> :Channel key already set"
816 # "<channel> :Cannot join channel (+l)"
817 ERR_CHANNELISFULL=471
819 # "<char> :is unknown mode char to me for <channel>"
822 # "<channel> :Cannot join channel (+i)"
823 ERR_INVITEONLYCHAN=473
825 # "<channel> :Cannot join channel (+b)"
826 ERR_BANNEDFROMCHAN=474
828 # "<channel> :Cannot join channel (+k)"
829 ERR_BADCHANNELKEY=475
831 # "<channel> :Bad Channel Mask"
834 # "<channel> :Channel doesn't support modes"
837 # "<channel> <char> :Channel list is full"
841 # ":Permission Denied- You're not an IRC operator"
843 # - Any command requiring operator privileges to operate
844 # MUST return this error to indicate the attempt was
849 # "<channel> :You're not channel operator"
851 # - Any command requiring 'chanop' privileges (such as
852 # MODE messages) MUST return this error if the client
853 # making the attempt is not a chanop on the specified
857 ERR_CHANOPRIVSNEEDED=482
859 # ":You can't kill a server!"
861 # - Any attempts to use the KILL command on a server
862 # are to be refused and this error returned directly
865 ERR_CANTKILLSERVER=483
867 # ":Your connection is restricted!"
869 # - Sent by the server to a user upon connection to indicate
870 # the restricted nature of the connection (user mode "+r").
874 # ":You're not the original channel operator"
876 # - Any MODE requiring "channel creator" privileges MUST
877 # return this error if the client making the attempt is not
878 # a chanop on the specified channel.
880 ERR_UNIQOPPRIVSNEEDED=485
882 # ":No O-lines for your host"
884 # - If a client sends an OPER message and the server has
885 # not been configured to allow connections from the
886 # client's host as an operator, this error MUST be
891 # ":Unknown MODE flag"
893 # - Returned by the server to indicate that a MODE
894 # message was sent with a nickname parameter and that
895 # the a mode flag sent was not recognized.
897 ERR_UMODEUNKNOWNFLAG=501
899 # ":Cannot change mode for other users"
901 # - Error sent to any user trying to view or change the
902 # user mode for a user other than themselves.
904 ERR_USERSDONTMATCH=502
906 # 5.3 Reserved numerics
908 # These numerics are not described above since they fall into one of
909 # the following categories:
911 # 1. no longer in use;
913 # 2. reserved for future planned use;
915 # 3. in current use but are part of a non-generic 'feature' of
916 # the current IRC server.
919 RPL_ENDOFSERVICES=232
940 ERR_NOSERVICEHOST=492
943 # Implements RFC 2812 and prior IRC RFCs.
945 # Clients should register Proc{}s to handle the various server events, and
946 # the Client class will handle dispatch.
949 # the Server we're connected to
951 # the User representing us on that server
954 # Create a new Client instance
956 @server = Server.new # The Server
957 @user = @server.user("*!*@*") # The User representing the client on this Server
961 # This is used by some messages to build lists of users that
962 # will be delegated when the ENDOF... message is received
966 # Clear the server and reset the user
969 @user = @server.user("*!*@*")
972 # key:: server event to handle
973 # value:: proc object called when event occurs
974 # set a handler for a server event
976 # ==server events currently supported:
978 # TODO handle errors ERR_NOSUCHNICK, ERR_NOSUCHCHANNEL
979 # TODO handle errors ERR_CHANOPRIVSNEEDED, ERR_CANNOTSENDTOCHAN
981 # welcome:: server welcome message on connect
982 # yourhost:: your host details (on connection)
983 # created:: when the server was started
984 # isupport:: information about what this server supports
985 # ping:: server pings you (default handler returns a pong)
986 # nicktaken:: you tried to change nick to one that's in use
987 # badnick:: you tried to change nick to one that's invalid
988 # topic:: someone changed the topic of a channel
989 # topicinfo:: on joining a channel or asking for the topic, tells you
990 # who set it and when
991 # names:: server sends list of channel members when you join
992 # motd:: server message of the day
993 # privmsg:: privmsg, the core of IRC, a message to you from someone
994 # public:: optionally instead of getting privmsg you can hook to only
996 # msg:: or only the private ones, or both
997 # kick:: someone got kicked from a channel
998 # part:: someone left a channel
999 # quit:: someone quit IRC
1000 # join:: someone joined a channel
1001 # changetopic:: the topic of a channel changed
1002 # invite:: you are invited to a channel
1003 # nick:: someone changed their nick
1004 # mode:: a mode change
1005 # notice:: someone sends you a notice
1006 # unknown:: any other message not handled by the above
1008 @handlers[key] = value
1012 # remove a handler for a server event
1013 def deletehandler(key)
1014 @handlers.delete(key)
1017 # takes a server string, checks for PING, PRIVMSG, NOTIFY, etc, and parses
1018 # numeric server replies, calling the appropriate handler for each, and
1019 # sending it a hash containing the data from the server
1020 def process(serverstring)
1022 data[:serverstring] = serverstring
1024 unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
1025 raise "Unparseable Server Message!!!: #{serverstring.inspect}"
1028 prefix, command, params = $2, $3, $5
1031 # Most servers will send a full nick!user@host prefix for
1032 # messages from users. Therefore, when the prefix doesn't match this
1033 # syntax it's usually the server hostname.
1035 # This is not always true, though, since some servers do not send a
1036 # full hostmask for user messages.
1038 if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
1039 data[:source] = @server.user(prefix)
1042 if @server.hostname != prefix
1043 # TODO do we want to be able to differentiate messages that are passed on to us from /other/ servers?
1044 debug "Origin #{prefix} for message\n\t#{serverstring.inspect}\nis neither a user hostmask nor the server hostname\nI'll pretend that it's from the server anyway"
1045 data[:source] = @server
1047 data[:source] = @server
1050 @server.instance_variable_set(:@hostname, prefix)
1051 data[:source] = @server
1056 # split parameters in an array
1058 params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
1060 if command =~ /^(\d+)$/ # Numeric replies
1061 data[:target] = argv[0]
1062 # A numeric reply /should/ be directed at the client, except when we're connecting with a used nick, in which case
1063 # it's directed at '*'
1064 not_us = !([@user.nick, '*'].include?(data[:target]))
1066 warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
1072 data[:message] = argv[1]
1073 # "Welcome to the Internet Relay Network
1074 # <nick>!<user>@<host>"
1076 warning "Server thinks client (#{@user.inspect}) has a different nick"
1077 @user.nick = data[:target]
1079 if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
1083 warning "Welcome message nick mismatch (#{nick} vs #{data[:target]})" if nick != data[:target]
1084 @user.user = user if user
1085 @user.host = host if host
1087 handle(:welcome, data)
1089 # "Your host is <servername>, running version <ver>"
1090 data[:message] = argv[1]
1091 handle(:yourhost, data)
1093 # "This server was created <date>"
1094 data[:message] = argv[1]
1095 handle(:created, data)
1097 # "<servername> <version> <available user modes>
1098 # <available channel modes>"
1099 @server.parse_my_info(params.split(' ', 2).last)
1100 data[:servername] = @server.hostname
1101 data[:version] = @server.version
1102 data[:usermodes] = @server.usermodes
1103 data[:chanmodes] = @server.chanmodes
1104 handle(:myinfo, data)
1106 # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
1107 # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
1108 # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
1109 # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
1112 @server.parse_isupport(argv[1..-2].join(' '))
1113 handle(:isupport, data)
1114 when ERR_NICKNAMEINUSE
1115 # "* <nick> :Nickname is already in use"
1116 data[:nick] = argv[1]
1117 data[:message] = argv[2]
1118 handle(:nicktaken, data)
1119 when ERR_ERRONEUSNICKNAME
1120 # "* <nick> :Erroneous nickname"
1121 data[:nick] = argv[1]
1122 data[:message] = argv[2]
1123 handle(:badnick, data)
1125 data[:channel] = @server.get_channel(argv[1])
1126 data[:topic] = argv[2]
1129 data[:channel].topic.text = data[:topic]
1131 warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1134 handle(:topic, data)
1136 data[:nick] = @server.user(argv[0])
1137 data[:channel] = @server.get_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)
1146 data[:channel].topic.set_by = data[:source]
1147 data[:channel].topic.set_on = data[:time]
1149 warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1152 handle(:topicinfo, data)
1154 # "( "=" / "*" / "@" ) <channel>
1155 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
1156 # - "@" is used for secret channels, "*" for private
1157 # channels, and "=" for others (public channels).
1158 data[:channeltype] = argv[1]
1159 data[:channel] = argv[2]
1161 chan = @server.get_channel(data[:channel])
1163 warning "Received names #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1168 argv[3].scan(/\S+/).each { |u|
1169 # FIXME beware of servers that allow multiple prefixes
1170 if(u =~ /^([#{@server.supports[:prefix][:prefixes].join}])?(.*)$/)
1173 users << [user, umode]
1178 u = @server.user(ar[0])
1179 chan.add_user(u, :silent => true)
1180 debug "Adding user #{u}"
1182 ms = @server.mode_for_prefix(ar[1].to_sym)
1183 debug "\twith mode #{ar[1]} (#{ms})"
1184 chan.mode[ms].set(u)
1189 data[:channel] = argv[1]
1190 data[:users] = @tmpusers
1191 handle(:names, data)
1192 @tmpusers = Array.new
1193 when RPL_LUSERCLIENT
1194 # ":There are <integer> users and <integer>
1195 # services on <integer> servers"
1196 data[:message] = argv[1]
1197 handle(:luserclient, data)
1199 # "<integer> :operator(s) online"
1200 data[:ops] = argv[1].to_i
1201 handle(:luserop, data)
1202 when RPL_LUSERUNKNOWN
1203 # "<integer> :unknown connection(s)"
1204 data[:unknown] = argv[1].to_i
1205 handle(:luserunknown, data)
1206 when RPL_LUSERCHANNELS
1207 # "<integer> :channels formed"
1208 data[:channels] = argv[1].to_i
1209 handle(:luserchannels, data)
1211 # ":I have <integer> clients and <integer> servers"
1212 data[:message] = argv[1]
1213 handle(:luserme, data)
1215 # ":MOTD File is missing"
1216 data[:message] = argv[1]
1217 handle(:motd_missing, data)
1219 # ":Current local users: 3 Max: 4"
1220 data[:message] = argv[1]
1221 handle(:localusers, data)
1222 when RPL_GLOBALUSERS
1223 # ":Current global users: 3 Max: 4"
1224 data[:message] = argv[1]
1225 handle(:globalusers, data)
1227 # ":Highest connection count: 4 (4 clients) (251 since server was
1229 data[:message] = argv[1]
1230 handle(:statsconn, data)
1232 # "<nick> :- <server> Message of the Day -"
1233 if argv[1] =~ /^-\s+(\S+)\s/
1236 warning "Server doesn't have an RFC compliant MOTD start."
1240 if(argv[1] =~ /^-\s+(.*)$/)
1248 data[:text] = argv[1]
1249 handle(:datastr, data)
1251 data[:channel] = argv[1]
1252 data[:user] = argv[2]
1253 data[:host] = argv[3]
1254 data[:userserver] = argv[4]
1255 data[:nick] = argv[5]
1256 if argv[6] =~ /^(H|G)(\*)?(.*)?$/
1257 data[:away] = ($1 == 'G')
1259 data[:modes] = $3.scan(/./).map { |mode|
1260 m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
1261 @server.supports[:prefix][:modes][m]
1264 warning "Strange WHO reply: #{serverstring.inspect}"
1266 data[:hopcount], data[:real_name] = argv[7].split(" ", 2)
1268 user = @server.get_user(data[:nick])
1270 user.user = data[:user]
1271 user.host = data[:host]
1272 user.away = data[:away] # FIXME doesn't provide the actual message
1276 user.real_name = data[:real_name]
1278 channel = @server.get_channel(data[:channel])
1280 channel.add_user(user, :silent=>true)
1281 data[:modes].map { |mode|
1282 channel.mode[mode].set(user)
1287 handle(:eowho, data)
1289 handle(:unknown, data)
1291 return # We've processed the numeric reply
1294 # Otherwise, the command should be a single word
1297 data[:pingid] = argv[0]
1300 data[:pingid] = argv[0]
1303 # you can either bind to 'PRIVMSG', to get every one and
1304 # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
1305 # etc and get it all nicely split up for you.
1308 data[:target] = @server.user_or_channel(argv[0])
1310 # The previous may fail e.g. when the target is a server or something
1311 # like that (e.g. $<mask>). In any of these cases, we just use the
1312 # String as a target
1313 # FIXME we probably want to explicitly check for the #<mask> $<mask>
1314 data[:target] = argv[0]
1316 data[:message] = argv[1]
1317 handle(:privmsg, data)
1320 if data[:target].kind_of?(Channel)
1321 handle(:public, data)
1327 data[:target] = @server.user_or_channel(argv[0])
1329 # The previous may fail e.g. when the target is a server or something
1330 # like that (e.g. $<mask>). In any of these cases, we just use the
1331 # String as a target
1332 # FIXME we probably want to explicitly check for the #<mask> $<mask>
1333 data[:target] = argv[0]
1335 data[:message] = argv[1]
1338 handle(:notice, data)
1340 # "server notice" (not from user, noone to reply to)
1341 handle(:snotice, data)
1344 data[:channel] = @server.channel(argv[0])
1345 data[:target] = @server.user(argv[1])
1346 data[:message] = argv[2]
1348 @server.delete_user_from_channel(data[:target], data[:channel])
1349 if data[:target] == @user
1350 @server.delete_channel(data[:channel])
1355 data[:channel] = @server.channel(argv[0])
1356 data[:message] = argv[1]
1358 @server.delete_user_from_channel(data[:source], data[:channel])
1359 if data[:source] == @user
1360 @server.delete_channel(data[:channel])
1365 data[:message] = argv[0]
1366 data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1367 list << ch if ch.has_user?(data[:source])
1371 @server.delete_user(data[:source])
1375 data[:channel] = @server.channel(argv[0])
1376 data[:channel].add_user(data[:source])
1380 data[:channel] = @server.channel(argv[0])
1381 data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
1382 data[:channel].topic.replace(data[:topic])
1384 handle(:changetopic, data)
1386 data[:target] = @server.user(argv[0])
1387 data[:channel] = @server.channel(argv[1])
1389 handle(:invite, data)
1391 data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1392 list << ch if ch.has_user?(data[:source])
1396 data[:newnick] = argv[0]
1397 data[:oldnick] = data[:source].nick.dup
1398 data[:source].nick = data[:newnick]
1400 debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1404 # MODE ([+-]<modes> (<params>)*)*
1405 # When a MODE message is received by a server,
1406 # Type C will have parameters too, so we must
1407 # be able to consume parameters for all
1410 data[:target] = @server.user_or_channel(argv[0])
1411 data[:modestring] = argv[1..-1].join(" ")
1412 # data[:modes] is an array where each element
1413 # is an array with two elements, the first of which
1414 # is either :set or :reset, and the second symbol
1415 # is the mode letter. An optional third element
1416 # is present e.g. for channel modes that need
1421 # User modes aren't currently handled internally,
1422 # but we still parse them and delegate to the client
1423 warning "Unhandled user mode message '#{serverstring}'"
1424 argv[1..-1].each { |arg|
1425 setting = arg[0].chr
1426 if "+-".include?(setting)
1427 setting = setting == "+" ? :set : :reset
1428 arg[1..-1].each_byte { |b|
1430 data[:modes] << [setting, m]
1433 # Although typically User modes don't take an argument,
1434 # this is not true for all modes on all servers. Since
1435 # we have no knowledge of which modes take parameters
1436 # and which don't we just assign it to the last
1437 # mode. This is not going to do strange things often,
1438 # as usually User modes are only set one at a time
1439 warning "Unhandled user mode parameter #{arg} found"
1440 data[:modes].last << arg
1444 # array of indices in data[:modes] where parameters
1446 who_wants_params = []
1448 argv[1..-1].each { |arg|
1449 setting = arg[0].chr
1450 if "+-".include?(setting)
1451 setting = setting == "+" ? :set : :reset
1452 arg[1..-1].each_byte { |b|
1454 data[:modes] << [setting, m]
1456 when *@server.supports[:chanmodes][:typea]
1457 who_wants_params << data[:modes].length - 1
1458 when *@server.supports[:chanmodes][:typeb]
1459 who_wants_params << data[:modes].length - 1
1460 when *@server.supports[:chanmodes][:typec]
1462 who_wants_params << data[:modes].length - 1
1464 when *@server.supports[:chanmodes][:typed]
1466 when *@server.supports[:prefix][:modes]
1467 who_wants_params << data[:modes].length - 1
1469 warning "Unknown mode #{m} in #{serverstring.inspect}"
1473 idx = who_wants_params.shift
1475 warning "Oops, problems parsing #{serverstring.inspect}"
1478 data[:modes][idx] << arg
1482 data[:modes].each { |mode|
1483 set, key, val = mode
1485 data[:target].mode[key].send(set, val)
1487 data[:target].mode[key].send(set)
1494 warning "Unknown message #{serverstring.inspect}"
1495 handle(:unknown, data)
1501 # key:: server event name
1502 # data:: hash containing data about the event, passed to the proc
1503 # call client's proc for an event, if they set one as a handler
1504 def handle(key, data)
1505 if(@handlers.has_key?(key))
1506 @handlers[key].call(data)