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)
9 class ServerMessageParseError < ServerError
13 # - The server sends Replies 001 to 004 to a user upon
14 # successful registration.
16 # "Welcome to the Internet Relay Network
17 # <nick>!<user>@<host>"
21 # "Your host is <servername>, running version <ver>"
24 # "This server was created <date>"
27 # "<servername> <version> <available user modes> <available channel modes>"
30 # "005 nick PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
32 # defines the capabilities supported by the server.
34 # Previous RFCs defined message 005 as follows:
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.
40 # # "Try server <server name>, port <port number>"
46 # ":*1<reply> *( " " <reply> )"
48 # - Reply format used by USERHOST to list replies to
49 # the query list. The reply string is composed as
52 # reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
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
61 # ":*1<nick> *( " " <nick> )"
63 # - Reply format used by ISON to list replies to the
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.
75 # "<nick> :<away message>"
78 # ":You are no longer marked as being away"
81 # ":You have been marked as being away"
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.
99 # "<nick> <user> <host> * :<real name>"
102 # "<nick> <server> :<server info>"
105 # "<nick> :is an IRC operator"
106 RPL_WHOISOPERATOR=313
108 # "<nick> <integer> :seconds idle"
111 # "<nick> :End of WHOIS list"
114 # "<nick> :*( ( "@" / "+" ) <channel> " " )"
115 RPL_WHOISCHANNELS=319
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).
124 # "<nick> <user> <host> * :<real name>"
127 # "<nick> :End of WHOWAS"
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.
135 # Obsolete. Not used.
138 # "<channel> <# visible> :<topic>"
144 # "<channel> <nickname>"
147 # "<channel> <mode> <mode params>"
148 RPL_CHANNELMODEIS=324
150 # "<channel> <unixtime>"
156 # "<channel> :No topic is set"
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
164 # "<channel> :<topic>"
167 # <channel> <set by> <unixtime>
172 # - Returned by the server to indicate that the
173 # attempted INVITE message was successful and is
174 # being passed onto the end client.
178 # "<user> :Summoning user to IRC"
180 # - Returned by a server answering a SUMMON message to
181 # indicate that it is summoning that user.
185 # "<channel> <invitemask>"
188 # "<channel> :End of channel invite list"
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.
197 RPL_ENDOFINVITELIST=347
199 # "<channel> <exceptionmask>"
202 # "<channel> :End of channel exception list"
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.
211 RPL_ENDOFEXCEPTLIST=349
213 # "<version>.<debuglevel> <server> :<comments>"
215 # - Reply by the server showing its version details.
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".
222 # The "comments" field may contain any comments about
223 # the version or further version details.
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
235 # "<channel> <user> <host> <server> <nick>
236 # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
237 # :<hopcount> <real name>"
241 # "<name> :End of WHO list"
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
254 # "( "=" / "*" / "@" ) <channel>
255 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
256 # - "@" is used for secret channels, "*" for private
257 # channels, and "=" for others (public channels).
261 # "<channel> :End of NAMES list"
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.
268 # "<mask> <server> :<hopcount> <server info>"
271 # "<mask> :End of LINKS list"
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.
281 # "<channel> <banmask>"
284 # "<channel> :End of channel ban list"
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
295 # ":End of INFO list"
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).
305 # ":- <server> Message of the day - "
311 # ":End of MOTD command"
314 # ":You are now an IRC operator"
316 # - RPL_YOUREOPER is sent back to a client which has
317 # just successfully issued an OPER message and gained
322 # "<config file> :Rehashing"
324 # - If the REHASH option is used and an operator sends
325 # a REHASH message, an RPL_REHASHING is sent back to
330 # "You are service <servicename>"
332 # - Sent by the server to a service upon successful
337 # "<server> :<string showing server's local time>"
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
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
354 # ":UserID Terminal Host"
357 # ":<username> <ttyline> <hostname>"
363 # ":Nobody logged in"
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.
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.
388 # "Link <version & debug level> <destination>
389 # <next server> V<protocol version>
390 # <link uptime in seconds> <backstream sendq>
394 # "Try. <class> <server>"
395 RPL_TRACECONNECTING=201
397 # "H.S. <class> <server>"
398 RPL_TRACEHANDSHAKE=202
400 # "???? <class> [<client IP address in dot form>]"
403 # "Oper <class> <nick>"
404 RPL_TRACEOPERATOR=204
406 # "User <class> <nick>"
409 # "Serv <class> <int>S <int>C <server>
410 # <nick!user|*!*>@<host|server> V<protocol version>"
413 # "Service <class> <name> <type> <active type>"
416 # "<newtype> 0 <client name>"
419 # "Class <class> <count>"
423 RPL_TRACERECONNECT=210
425 # "File <logfile> <debug level>"
428 # "<server name> <version & debug level> :End of TRACE"
431 # ":Current local users: 3 Max: 4"
434 # ":Current global users: 3 Max: 4"
437 # "::Highest connection count: 4 (4 clients) (251 since server was
441 # "<linkname> <sendq> <sent messages>
442 # <sent Kbytes> <received messages>
443 # <received Kbytes> <time open>"
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.
456 RPL_STATSLINKINFO=211
458 # "<command> <count> <byte count> <remote count>"
460 # - reports statistics on commands usage.
462 RPL_STATSCOMMANDS=212
464 # "<stats letter> :End of STATS report"
468 # ":Server Up %d days %d:%02d:%02d"
470 # - reports the server uptime.
474 # "O <hostmask> * <name>"
476 # - reports the allowed hosts from where user may become IRC
481 # "<user mode string>"
483 # - To answer a query about a client's own mode,
484 # RPL_UMODEIS is sent back.
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.
495 # "<name> <server> <mask> <type> <hopcount> <info>"
498 # "<mask> <type> :End of service listing"
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
510 # ":There are <integer> users and <integer>
511 # services on <integer> servers"
514 # "<integer> :operator(s) online"
517 # "<integer> :unknown connection(s)"
520 # "<integer> :channels formed"
521 RPL_LUSERCHANNELS=254
523 # ":I have <integer> clients and <integer> servers"
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)
537 # "<server> :Administrative info"
549 # "<command> :Please wait a while and try again."
551 # - When a server drops a command without processing it,
552 # it MUST use the reply RPL_TRYAGAIN to inform the
553 # originating client.
558 # Error replies are found in the range from 400 to 599.
560 # "<nickname> :No such nick/channel"
562 # - Used to indicate the nickname parameter supplied to a
563 # command is currently unused.
567 # "<server name> :No such server"
569 # - Used to indicate the server name given currently
574 # "<channel name> :No such channel"
576 # - Used to indicate the given channel name is invalid.
578 ERR_NOSUCHCHANNEL=403
580 # "<channel name> :Cannot send to channel"
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
588 ERR_CANNOTSENDTOCHAN=404
590 # "<channel name> :You have joined too many channels"
592 # - Sent to a user when they have joined the maximum
593 # number of allowed channels and they try to join
596 ERR_TOOMANYCHANNELS=405
598 # "<nickname> :There was no such nickname"
600 # - Returned by WHOWAS to indicate there is no history
601 # information for that nickname.
603 ERR_WASNOSUCHNICK=406
605 # "<target> :<error code> recipients. <abort message>"
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.
611 # - Returned to a client which trying to send a
612 # PRIVMSG/NOTICE to too many recipients.
614 # - Returned to a client which is attempting to JOIN a safe
615 # channel using the shortname when there are more than one
618 ERR_TOOMANYTARGETS=407
620 # "<service name> :No such service"
622 # - Returned to a client which is attempting to send a SQUERY
623 # to a service which does not exist.
625 ERR_NOSUCHSERVICE=408
627 # ":No origin specified"
629 # - PING or PONG message missing the originator parameter.
633 # ":No recipient given (<command>)"
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.
645 # "<mask> :No toplevel domain specified"
648 # "<mask> :Wildcard in toplevel domain"
651 # "<mask> :Bad Server/host mask"
654 # "<command> :Unknown command"
656 # - Returned to a registered client to indicate that the
657 # command sent is unknown by the server.
659 ERR_UNKNOWNCOMMAND=421
661 # ":MOTD File is missing"
663 # - Server's MOTD file could not be opened by the server.
667 # "<server> :No administrative info available"
669 # - Returned by a server in response to an ADMIN message
670 # when there is an error in finding the appropriate
675 # ":File error doing <file op> on <file>"
677 # - Generic error message used to report a failed file
678 # operation during the processing of a message.
682 # ":No nickname given"
684 # - Returned when a nickname parameter expected for a
685 # command and isn't found.
687 ERR_NONICKNAMEGIVEN=431
689 # "<nick> :Erroneous nickname"
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.
695 ERR_ERRONEUSNICKNAME=432
697 # "<nick> :Nickname is already in use"
699 # - Returned when a NICK message is processed that results
700 # in an attempt to change to a currently existing
703 ERR_NICKNAMEINUSE=433
705 # "<nick> :Nickname collision KILL from <user>@<host>"
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).
711 ERR_NICKCOLLISION=436
713 # "<nick/channel> :Nick/channel is temporarily unavailable"
715 # - Returned by a server to a user trying to join a channel
716 # currently blocked by the channel delay mechanism.
718 # - Returned by a server to a user trying to change nickname
719 # when the desired nickname is blocked by the nick delay
722 ERR_UNAVAILRESOURCE=437
724 # "<nick> <channel> :They aren't on that channel"
726 # - Returned by the server to indicate that the target
727 # user of the command is not on the given channel.
729 ERR_USERNOTINCHANNEL=441
731 # "<channel> :You're not on that channel"
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.
739 # "<user> <channel> :is already on channel"
741 # - Returned when a client tries to invite a user to a
742 # channel they are already on.
744 ERR_USERONCHANNEL=443
746 # "<user> :User not logged in"
748 # - Returned by the summon after a SUMMON command for a
749 # user was unable to be performed since they were not
754 # ":SUMMON has been disabled"
756 # - Returned as a response to the SUMMON command. MUST be
757 # returned by any server which doesn't implement it.
759 ERR_SUMMONDISABLED=445
761 # ":USERS has been disabled"
763 # - Returned as a response to the USERS command. MUST be
764 # returned by any server which does not implement it.
766 ERR_USERSDISABLED=446
768 # ":You have not registered"
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.
774 ERR_NOTREGISTERED=451
776 # "<command> :Not enough parameters"
778 # - Returned by the server by numerous commands to
779 # indicate to the client that it didn't supply enough
782 ERR_NEEDMOREPARAMS=461
784 # ":Unauthorized command (already registered)"
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).
790 ERR_ALREADYREGISTRED=462
792 # ":Your host isn't among the privileged"
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
799 ERR_NOPERMFORHOST=463
801 # ":Password incorrect"
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.
807 ERR_PASSWDMISMATCH=464
809 # ":You are banned from this server"
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.
815 ERR_YOUREBANNEDCREEP=465
817 # - Sent by a server to a user to inform that access to the
818 # server will soon be denied.
820 ERR_YOUWILLBEBANNED=466
822 # "<channel> :Channel key already set"
825 # "<channel> :Cannot join channel (+l)"
826 ERR_CHANNELISFULL=471
828 # "<char> :is unknown mode char to me for <channel>"
831 # "<channel> :Cannot join channel (+i)"
832 ERR_INVITEONLYCHAN=473
834 # "<channel> :Cannot join channel (+b)"
835 ERR_BANNEDFROMCHAN=474
837 # "<channel> :Cannot join channel (+k)"
838 ERR_BADCHANNELKEY=475
840 # "<channel> :Bad Channel Mask"
843 # "<channel> :Channel doesn't support modes"
846 # "<channel> <char> :Channel list is full"
850 # ":Permission Denied- You're not an IRC operator"
852 # - Any command requiring operator privileges to operate
853 # MUST return this error to indicate the attempt was
858 # "<channel> :You're not channel operator"
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
866 ERR_CHANOPRIVSNEEDED=482
868 # ":You can't kill a server!"
870 # - Any attempts to use the KILL command on a server
871 # are to be refused and this error returned directly
874 ERR_CANTKILLSERVER=483
876 # ":Your connection is restricted!"
878 # - Sent by the server to a user upon connection to indicate
879 # the restricted nature of the connection (user mode "+r").
883 # ":You're not the original channel operator"
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.
889 ERR_UNIQOPPRIVSNEEDED=485
891 # ":No O-lines for your host"
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
900 # ":Unknown MODE flag"
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.
906 ERR_UMODEUNKNOWNFLAG=501
908 # ":Cannot change mode for other users"
910 # - Error sent to any user trying to view or change the
911 # user mode for a user other than themselves.
913 ERR_USERSDONTMATCH=502
915 # 5.3 Reserved numerics
917 # These numerics are not described above since they fall into one of
918 # the following categories:
920 # 1. no longer in use;
922 # 2. reserved for future planned use;
924 # 3. in current use but are part of a non-generic 'feature' of
925 # the current IRC server.
928 RPL_ENDOFSERVICES=232
949 ERR_NOSERVICEHOST=492
952 # A structure to hold LIST data, in the Irc namespace
953 ListData = Struct.new :channel, :users, :topic
955 # Implements RFC 2812 and prior IRC RFCs.
957 # Clients should register Proc{}s to handle the various server events, and
958 # the Client class will handle dispatch.
961 # the Server we're connected to
963 # the User representing us on that server
966 # Create a new Client instance
968 @server = Server.new # The Server
969 @user = @server.user("*!*@*") # The User representing the client on this Server
973 # This is used by some messages to build lists of users that
974 # will be delegated when the ENDOF... message is received
977 # Same as above, just for bans
981 # Clear the server and reset the user
984 @user = @server.user("*!*@*")
987 # key:: server event to handle
988 # value:: proc object called when event occurs
989 # set a handler for a server event
991 # ==server events currently supported:
993 # TODO handle errors ERR_CHANOPRIVSNEEDED, ERR_CANNOTSENDTOCHAN
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
1022 @handlers[key] = value
1026 # remove a handler for a server event
1027 def deletehandler(key)
1028 @handlers.delete(key)
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)
1036 data[:serverstring] = serverstring
1038 unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
1039 raise ServerMessageParseError, (serverstring.chomp rescue serverstring)
1042 prefix, command, params = $2, $3, $5
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.
1049 # This is not always true, though, since some servers do not send a
1050 # full hostmask for user messages.
1052 if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
1053 data[:source] = @server.user(prefix)
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
1061 data[:source] = @server
1064 @server.instance_variable_set(:@hostname, prefix)
1065 data[:source] = @server
1070 # split parameters in an array
1072 params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
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]))
1080 warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
1086 data[:message] = argv[1]
1087 # "Welcome to the Internet Relay Network
1088 # <nick>!<user>@<host>"
1090 warning "Server thinks client (#{@user.inspect}) has a different nick"
1091 @user.nick = data[:target]
1093 if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
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
1101 handle(:welcome, data)
1103 # "Your host is <servername>, running version <ver>"
1104 data[:message] = argv[1]
1105 handle(:yourhost, data)
1107 # "This server was created <date>"
1108 data[:message] = argv[1]
1109 handle(:created, data)
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)
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
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)
1139 data[:channel] = @server.channel(argv[1])
1140 data[:topic] = argv[2]
1141 data[:channel].topic.text = data[:topic]
1143 handle(:topic, data)
1145 data[:nick] = @server.user(argv[0])
1146 data[:channel] = @server.channel(argv[1])
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)
1152 data[:time] = Time.at(argv[3].to_i)
1154 data[:channel].topic.set_by = data[:source]
1155 data[:channel].topic.set_on = data[:time]
1157 handle(:topicinfo, data)
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])
1167 argv[3].scan(/\S+/).each { |u|
1168 # FIXME beware of servers that allow multiple prefixes
1169 if(u =~ /^([#{@server.supports[:prefix][:prefixes].join}])?(.*)$/)
1172 users << [user, umode]
1177 u = @server.user(ar[0])
1178 chan.add_user(u, :silent => true)
1179 debug "Adding user #{u}"
1181 ms = @server.mode_for_prefix(ar[1].to_sym)
1182 debug "\twith mode #{ar[1]} (#{ms})"
1183 chan.mode[ms].set(u)
1188 data[:channel] = @server.channel(argv[1])
1189 data[:users] = @tmpusers
1190 handle(:names, data)
1191 @tmpusers = Array.new
1193 data[:channel] = @server.channel(argv[1])
1194 data[:mask] = argv[2]
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)
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)
1221 # ":I have <integer> clients and <integer> servers"
1222 data[:message] = argv[1]
1223 handle(:luserme, data)
1225 # ":MOTD File is missing"
1226 data[:message] = argv[1]
1227 handle(:motd_missing, data)
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)
1237 # ":Highest connection count: 4 (4 clients) (251 since server was
1239 data[:message] = argv[1]
1240 handle(:statsconn, data)
1242 # "<nick> :- <server> Message of the Day -"
1243 if argv[1] =~ /^-\s+(\S+)\s/
1246 warning "Server doesn't have an RFC compliant MOTD start."
1250 if(argv[1] =~ /^-\s+(.*)$/)
1258 data[:text] = argv[1]
1259 handle(:datastr, data)
1261 data[:nick] = user = @server.user(argv[1])
1262 data[:message] = argv[-1]
1263 user.away = data[:message]
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')
1274 data[:modes] = $3.scan(/./).map { |mode|
1275 m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
1276 @server.supports[:prefix][:modes][m]
1279 warning "Strange WHO reply: #{serverstring.inspect}"
1281 data[:hopcount], data[:real_name] = argv[7].split(" ", 2)
1283 user.user = data[:user]
1284 user.host = data[:host]
1285 user.away = data[:away] # FIXME doesn't provide the actual message
1289 user.real_name = data[:real_name]
1291 channel.add_user(user, :silent=>true)
1292 data[:modes].map { |mode|
1293 channel.mode[mode].set(user)
1298 handle(:eowho, data)
1301 @whois[:nick] = argv[1]
1302 @whois[:user] = argv[2]
1303 @whois[:host] = argv[3]
1304 @whois[:real_name] = argv[-1]
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
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
1318 @whois[:nick] = argv[1]
1319 @whois[:operator] = argv[-1]
1320 # TODO update user info
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]
1333 @whois[:nick] = argv[1]
1334 data[:whois] = @whois.dup
1336 handle(:whois, data)
1337 when RPL_WHOISCHANNELS
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]
1347 channel = @server.channel(chan)
1348 channel.add_user(user, :silent => true)
1349 modes.map { |mode| channel.mode[mode].set(user) }
1351 @whois[:channels] << [chan, modes]
1360 @list[chan] = ListData.new(chan, users, topic)
1365 when RPL_CHANNELMODEIS
1366 parse_mode(serverstring, argv[1..-1], 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)
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)
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)
1393 warning "Unknown message #{serverstring.inspect}"
1394 handle(:unknown, data)
1396 return # We've processed the numeric reply
1399 # Otherwise, the command should be a single word
1402 data[:pingid] = argv[0]
1405 data[:pingid] = argv[0]
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.
1413 data[:target] = @server.user_or_channel(argv[0])
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]
1421 data[:message] = argv[1]
1422 handle(:privmsg, data)
1425 if data[:target].kind_of?(Channel)
1426 handle(:public, data)
1432 data[:target] = @server.user_or_channel(argv[0])
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]
1440 data[:message] = argv[1]
1443 handle(:notice, data)
1445 # "server notice" (not from user, noone to reply to)
1446 handle(:snotice, data)
1449 data[:channel] = @server.channel(argv[0])
1450 data[:target] = @server.user(argv[1])
1451 data[:message] = argv[2]
1453 @server.delete_user_from_channel(data[:target], data[:channel])
1454 if data[:target] == @user
1455 @server.delete_channel(data[:channel])
1460 data[:channel] = @server.channel(argv[0])
1461 data[:message] = argv[1]
1463 @server.delete_user_from_channel(data[:source], data[:channel])
1464 if data[:source] == @user
1465 @server.delete_channel(data[:channel])
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])
1476 @server.delete_user(data[:source])
1480 data[:channel] = @server.channel(argv[0])
1481 data[:channel].add_user(data[:source])
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])
1489 handle(:changetopic, data)
1491 data[:target] = @server.user(argv[0])
1492 data[:channel] = @server.channel(argv[1])
1494 handle(:invite, data)
1496 data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1497 list << ch if ch.has_user?(data[:source])
1501 data[:newnick] = argv[0]
1502 data[:oldnick] = data[:source].nick.dup
1503 data[:source].nick = data[:newnick]
1505 debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1509 parse_mode(serverstring, argv, data)
1512 data[:message] = argv[1]
1513 handle(:error, data)
1515 warning "Unknown message #{serverstring.inspect}"
1516 handle(:unknown, data)
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)
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
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
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|
1558 data[:modes] << [setting, m]
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
1572 # array of indices in data[:modes] where parameters
1574 who_wants_params = []
1576 modes = argv[1..-1].dup
1578 getting_args = false
1579 while arg = modes.shift
1582 # getting args for previously set modes
1583 idx = who_wants_params.shift
1585 warning "Oops, problems parsing #{serverstring.inspect}"
1588 data[:modes][idx] << arg
1589 getting_args = false if who_wants_params.empty?
1591 debug @server.supports[:chanmodes]
1593 arg.each_byte do |c|
1601 data[:modes] << [setting, 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]
1609 who_wants_params << data[:modes].length - 1
1611 when *@server.supports[:chanmodes][:typed]
1613 when *@server.supports[:prefix][:modes]
1614 who_wants_params << data[:modes].length - 1
1616 warning "Ignoring unknown mode #{m} in #{serverstring.inspect}"
1621 getting_args = true unless who_wants_params.empty?
1624 unless who_wants_params.empty?
1625 warning "Unhandled malformed modeline #{data[:modestring]} (unexpected empty arguments)"
1629 data[:modes].each { |mode|
1630 set, key, val = mode
1632 data[:target].mode[key].send(set, val)
1634 data[:target].mode[key].send(set)
1638 warning "Ignoring #{data[:modestring]} for unrecognized target #{argv[0]} (#{data[:target].inspect})"