2 # Calculate the penalty which will be assigned to this message
5 # According to eggrdop, the initial penalty is
6 penalty = 1 + self.length/100
7 # on everything but UnderNET where it's
8 # penalty = 2 + self.length/120
10 cmd, pars = self.split($;,2)
11 debug "cmd: #{cmd}, pars: #{pars.inspect}"
14 chan, nick, msg = pars.split
15 chan = chan.split(',')
16 nick = nick.split(',')
17 penalty += nick.length
18 penalty *= chan.length
20 chan, modes, argument = pars.split
25 extra += modes.split(/\+|-/).length
27 extra += 3 * modes.split(/\+|-/).length
31 extra += 2 * argument.split.length
33 penalty += extra * chan.split.length
36 penalty += 2 unless pars.split.length < 2
37 when :PRIVMSG, :NOTICE
38 dests = pars.split($;,2).first
39 penalty += dests.split(',').length
41 # I'm too lazy to implement this one correctly
43 when :AWAY, :JOIN, :VERSION, :TIME, :TRACE, :WHOIS, :DNS
49 else # Unknown messages
53 debug "Wow, more than 99 secs of penalty!"
57 debug "Wow, less than 2 secs of penalty!"
60 debug "penalty: #{penalty}"
66 # RFC 2812 Internet Relay Chat: Client Protocol
69 # "Welcome to the Internet Relay Network
70 # <nick>!<user>@<host>"
72 # "Your host is <servername>, running version <ver>"
74 # "This server was created <date>"
76 # "<servername> <version> <available user modes>
77 # <available channel modes>"
79 # - The server sends Replies 001 to 004 to a user upon
80 # successful registration.
83 # # "Try server <server name>, port <port number>"
85 # "005 nick PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
87 # - Sent by the server to a user to suggest an alternative
88 # server. This is often used when the connection is
89 # refused because the server is already full.
92 # ":*1<reply> *( " " <reply> )"
94 # - Reply format used by USERHOST to list replies to
95 # the query list. The reply string is composed as
98 # reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
100 # The '*' indicates whether the client has registered
101 # as an Operator. The '-' or '+' characters represent
102 # whether the client has set an AWAY message or not
106 # ":*1<nick> *( " " <nick> )"
108 # - Reply format used by ISON to list replies to the
112 # "<nick> :<away message>"
114 # ":You are no longer marked as being away"
116 # ":You have been marked as being away"
118 # - These replies are used with the AWAY command (if
119 # allowed). RPL_AWAY is sent to any client sending a
120 # PRIVMSG to a client which is away. RPL_AWAY is only
121 # sent by the server to which the client is connected.
122 # Replies RPL_UNAWAY and RPL_NOWAWAY are sent when the
123 # client removes and sets an AWAY message.
126 # "<nick> <user> <host> * :<real name>"
128 # "<nick> <server> :<server info>"
129 RPL_WHOISOPERATOR=313
130 # "<nick> :is an IRC operator"
132 # "<nick> <integer> :seconds idle"
134 # "<nick> :End of WHOIS list"
135 RPL_WHOISCHANNELS=319
136 # "<nick> :*( ( "@" / "+" ) <channel> " " )"
138 # - Replies 311 - 313, 317 - 319 are all replies
139 # generated in response to a WHOIS message. Given that
140 # there are enough parameters present, the answering
141 # server MUST either formulate a reply out of the above
142 # numerics (if the query nick is found) or return an
143 # error reply. The '*' in RPL_WHOISUSER is there as
144 # the literal character and not as a wild card. For
145 # each reply set, only RPL_WHOISCHANNELS may appear
146 # more than once (for long lists of channel names).
147 # The '@' and '+' characters next to the channel name
148 # indicate whether a client is a channel operator or
149 # has been granted permission to speak on a moderated
150 # channel. The RPL_ENDOFWHOIS reply is used to mark
151 # the end of processing a WHOIS message.
154 # "<nick> <user> <host> * :<real name>"
156 # "<nick> :End of WHOWAS"
158 # - When replying to a WHOWAS message, a server MUST use
159 # the replies RPL_WHOWASUSER, RPL_WHOISSERVER or
160 # ERR_WASNOSUCHNICK for each nickname in the presented
161 # list. At the end of all reply batches, there MUST
162 # be RPL_ENDOFWHOWAS (even if there was only one reply
163 # and it was an error).
166 # Obsolete. Not used.
169 # "<channel> <# visible> :<topic>"
173 # - Replies RPL_LIST, RPL_LISTEND mark the actual replies
174 # with data and end of the server's response to a LIST
175 # command. If there are no channels available to return,
176 # only the end reply MUST be sent.
179 # "<channel> <nickname>"
181 RPL_CHANNELMODEIS=324
182 # "<channel> <mode> <mode params>"
185 # "<channel> :No topic is set"
187 # "<channel> :<topic>"
189 # - When sending a TOPIC message to determine the
190 # channel topic, one of two replies is sent. If
191 # the topic is set, RPL_TOPIC is sent back else
195 # <channel> <set by> <unixtime>
199 # - Returned by the server to indicate that the
200 # attempted INVITE message was successful and is
201 # being passed onto the end client.
204 # "<user> :Summoning user to IRC"
206 # - Returned by a server answering a SUMMON message to
207 # indicate that it is summoning that user.
210 # "<channel> <invitemask>"
211 RPL_ENDOFINVITELIST=347
212 # "<channel> :End of channel invite list"
214 # - When listing the 'invitations masks' for a given channel,
215 # a server is required to send the list back using the
216 # RPL_INVITELIST and RPL_ENDOFINVITELIST messages. A
217 # separate RPL_INVITELIST is sent for each active mask.
218 # After the masks have been listed (or if none present) a
219 # RPL_ENDOFINVITELIST MUST be sent.
222 # "<channel> <exceptionmask>"
223 RPL_ENDOFEXCEPTLIST=349
224 # "<channel> :End of channel exception list"
226 # - When listing the 'exception masks' for a given channel,
227 # a server is required to send the list back using the
228 # RPL_EXCEPTLIST and RPL_ENDOFEXCEPTLIST messages. A
229 # separate RPL_EXCEPTLIST is sent for each active mask.
230 # After the masks have been listed (or if none present)
231 # a RPL_ENDOFEXCEPTLIST MUST be sent.
234 # "<version>.<debuglevel> <server> :<comments>"
236 # - Reply by the server showing its version details.
237 # The <version> is the version of the software being
238 # used (including any patchlevel revisions) and the
239 # <debuglevel> is used to indicate if the server is
240 # running in "debug mode".
242 # The "comments" field may contain any comments about
243 # the version or further version details.
246 # "<channel> <user> <host> <server> <nick>
247 # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
248 # :<hopcount> <real name>"
251 # "<name> :End of WHO list"
253 # - The RPL_WHOREPLY and RPL_ENDOFWHO pair are used
254 # to answer a WHO message. The RPL_WHOREPLY is only
255 # sent if there is an appropriate match to the WHO
256 # query. If there is a list of parameters supplied
257 # with a WHO message, a RPL_ENDOFWHO MUST be sent
258 # after processing each list item with <name> being
262 # "( "=" / "*" / "@" ) <channel>
263 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
264 # - "@" is used for secret channels, "*" for private
265 # channels, and "=" for others (public channels).
268 # "<channel> :End of NAMES list"
270 # - To reply to a NAMES message, a reply pair consisting
271 # of RPL_NAMREPLY and RPL_ENDOFNAMES is sent by the
272 # server back to the client. If there is no channel
273 # found as in the query, then only RPL_ENDOFNAMES is
274 # returned. The exception to this is when a NAMES
275 # message is sent with no parameters and all visible
276 # channels and contents are sent back in a series of
277 # RPL_NAMEREPLY messages with a RPL_ENDOFNAMES to mark
281 # "<mask> <server> :<hopcount> <server info>"
283 # "<mask> :End of LINKS list"
285 # - In replying to the LINKS message, a server MUST send
286 # replies back using the RPL_LINKS numeric and mark the
287 # end of the list using an RPL_ENDOFLINKS reply.
290 # "<channel> <banmask>"
292 # "<channel> :End of channel ban list"
294 # - When listing the active 'bans' for a given channel,
295 # a server is required to send the list back using the
296 # RPL_BANLIST and RPL_ENDOFBANLIST messages. A separate
297 # RPL_BANLIST is sent for each active banmask. After the
298 # banmasks have been listed (or if none present) a
299 # RPL_ENDOFBANLIST MUST be sent.
304 # ":End of INFO list"
306 # - A server responding to an INFO message is required to
307 # send all its 'info' in a series of RPL_INFO messages
308 # with a RPL_ENDOFINFO reply to indicate the end of the
312 # ":- <server> Message of the day - "
316 # ":End of MOTD command"
318 # - When responding to the MOTD message and the MOTD file
319 # is found, the file is displayed line by line, with
320 # each line no longer than 80 characters, using
321 # RPL_MOTD format replies. These MUST be surrounded
322 # by a RPL_MOTDSTART (before the RPL_MOTDs) and an
323 # RPL_ENDOFMOTD (after).
326 # ":You are now an IRC operator"
328 # - RPL_YOUREOPER is sent back to a client which has
329 # just successfully issued an OPER message and gained
333 # "<config file> :Rehashing"
335 # - If the REHASH option is used and an operator sends
336 # a REHASH message, an RPL_REHASHING is sent back to
340 # "You are service <servicename>"
342 # - Sent by the server to a service upon successful
346 # "<server> :<string showing server's local time>"
348 # - When replying to the TIME message, a server MUST send
349 # the reply using the RPL_TIME format above. The string
350 # showing the time need only contain the correct day and
351 # time there. There is no further requirement for the
355 # ":UserID Terminal Host"
357 # ":<username> <ttyline> <hostname>"
361 # ":Nobody logged in"
363 # - If the USERS message is handled by a server, the
364 # replies RPL_USERSTART, RPL_USERS, RPL_ENDOFUSERS and
365 # RPL_NOUSERS are used. RPL_USERSSTART MUST be sent
366 # first, following by either a sequence of RPL_USERS
367 # or a single RPL_NOUSER. Following this is
371 # "Link <version & debug level> <destination>
372 # <next server> V<protocol version>
373 # <link uptime in seconds> <backstream sendq>
375 RPL_TRACECONNECTING=201
376 # "Try. <class> <server>"
377 RPL_TRACEHANDSHAKE=202
378 # "H.S. <class> <server>"
380 # "???? <class> [<client IP address in dot form>]"
381 RPL_TRACEOPERATOR=204
382 # "Oper <class> <nick>"
384 # "User <class> <nick>"
386 # "Serv <class> <int>S <int>C <server>
387 # <nick!user|*!*>@<host|server> V<protocol version>"
389 # "Service <class> <name> <type> <active type>"
391 # "<newtype> 0 <client name>"
393 # "Class <class> <count>"
394 RPL_TRACERECONNECT=210
397 # "File <logfile> <debug level>"
399 # "<server name> <version & debug level> :End of TRACE"
401 # - The RPL_TRACE* are all returned by the server in
402 # response to the TRACE message. How many are
403 # returned is dependent on the TRACE message and
404 # whether it was sent by an operator or not. There
405 # is no predefined order for which occurs first.
406 # Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
407 # RPL_TRACEHANDSHAKE are all used for connections
408 # which have not been fully established and are either
409 # unknown, still attempting to connect or in the
410 # process of completing the 'server handshake'.
411 # RPL_TRACELINK is sent by any server which handles
412 # a TRACE message and has to pass it on to another
413 # server. The list of RPL_TRACELINKs sent in
414 # response to a TRACE command traversing the IRC
415 # network should reflect the actual connectivity of
416 # the servers themselves along that path.
418 # RPL_TRACENEWTYPE is to be used for any connection
419 # which does not fit in the other categories but is
420 # being displayed anyway.
421 # RPL_TRACEEND is sent to indicate the end of the list.
424 # ":Current local users: 3 Max: 4"
426 # ":Current global users: 3 Max: 4"
428 # "::Highest connection count: 4 (4 clients) (251 since server was
430 RPL_STATSLINKINFO=211
431 # "<linkname> <sendq> <sent messages>
432 # <sent Kbytes> <received messages>
433 # <received Kbytes> <time open>"
435 # - reports statistics on a connection. <linkname>
436 # identifies the particular connection, <sendq> is
437 # the amount of data that is queued and waiting to be
438 # sent <sent messages> the number of messages sent,
439 # and <sent Kbytes> the amount of data sent, in
440 # Kbytes. <received messages> and <received Kbytes>
441 # are the equivalent of <sent messages> and <sent
442 # Kbytes> for received data, respectively. <time
443 # open> indicates how long ago the connection was
444 # opened, in seconds.
446 RPL_STATSCOMMANDS=212
447 # "<command> <count> <byte count> <remote count>"
449 # - reports statistics on commands usage.
452 # "<stats letter> :End of STATS report"
455 # ":Server Up %d days %d:%02d:%02d"
457 # - reports the server uptime.
460 # "O <hostmask> * <name>"
462 # - reports the allowed hosts from where user may become IRC
466 # "<user mode string>"
468 # - To answer a query about a client's own mode,
469 # RPL_UMODEIS is sent back.
472 # "<name> <server> <mask> <type> <hopcount> <info>"
475 # "<mask> <type> :End of service listing"
477 # - When listing services in reply to a SERVLIST message,
478 # a server is required to send the list back using the
479 # RPL_SERVLIST and RPL_SERVLISTEND messages. A separate
480 # RPL_SERVLIST is sent for each service. After the
481 # services have been listed (or if none present) a
482 # RPL_SERVLISTEND MUST be sent.
485 # ":There are <integer> users and <integer>
486 # services on <integer> servers"
488 # "<integer> :operator(s) online"
490 # "<integer> :unknown connection(s)"
491 RPL_LUSERCHANNELS=254
492 # "<integer> :channels formed"
494 # ":I have <integer> clients and <integer>
497 # - In processing an LUSERS message, the server
498 # sends a set of replies from RPL_LUSERCLIENT,
499 # RPL_LUSEROP, RPL_USERUNKNOWN,
500 # RPL_LUSERCHANNELS and RPL_LUSERME. When
501 # replying, a server MUST send back
502 # RPL_LUSERCLIENT and RPL_LUSERME. The other
503 # replies are only sent back if a non-zero count
507 # "<server> :Administrative info"
515 # - When replying to an ADMIN message, a server
516 # is expected to use replies RPL_ADMINME
517 # through to RPL_ADMINEMAIL and provide a text
518 # message with each. For RPL_ADMINLOC1 a
519 # description of what city, state and country
520 # the server is in is expected, followed by
521 # details of the institution (RPL_ADMINLOC2)
522 # and finally the administrative contact for the
523 # server (an email address here is REQUIRED)
527 # "<command> :Please wait a while and try again."
529 # - When a server drops a command without processing it,
530 # it MUST use the reply RPL_TRYAGAIN to inform the
531 # originating client.
535 # Error replies are found in the range from 400 to 599.
538 # "<nickname> :No such nick/channel"
540 # - Used to indicate the nickname parameter supplied to a
541 # command is currently unused.
544 # "<server name> :No such server"
546 # - Used to indicate the server name given currently
549 ERR_NOSUCHCHANNEL=403
550 # "<channel name> :No such channel"
552 # - Used to indicate the given channel name is invalid.
554 ERR_CANNOTSENDTOCHAN=404
555 # "<channel name> :Cannot send to channel"
557 # - Sent to a user who is either (a) not on a channel
558 # which is mode +n or (b) not a chanop (or mode +v) on
559 # a channel which has mode +m set or where the user is
560 # banned and is trying to send a PRIVMSG message to
563 ERR_TOOMANYCHANNELS=405
564 # "<channel name> :You have joined too many channels"
566 # - Sent to a user when they have joined the maximum
567 # number of allowed channels and they try to join
570 ERR_WASNOSUCHNICK=406
571 # "<nickname> :There was no such nickname"
573 # - Returned by WHOWAS to indicate there is no history
574 # information for that nickname.
576 ERR_TOOMANYTARGETS=407
577 # "<target> :<error code> recipients. <abort message>"
579 # - Returned to a client which is attempting to send a
580 # PRIVMSG/NOTICE using the user@host destination format
581 # and for a user@host which has several occurrences.
583 # - Returned to a client which trying to send a
584 # PRIVMSG/NOTICE to too many recipients.
586 # - Returned to a client which is attempting to JOIN a safe
587 # channel using the shortname when there are more than one
590 ERR_NOSUCHSERVICE=408
591 # "<service name> :No such service"
593 # - Returned to a client which is attempting to send a SQUERY
594 # to a service which does not exist.
597 # ":No origin specified"
599 # - PING or PONG message missing the originator parameter.
602 # ":No recipient given (<command>)"
606 # "<mask> :No toplevel domain specified"
608 # "<mask> :Wildcard in toplevel domain"
610 # "<mask> :Bad Server/host mask"
612 # - 412 - 415 are returned by PRIVMSG to indicate that
613 # the message wasn't delivered for some reason.
614 # ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
615 # are returned when an invalid use of
616 # "PRIVMSG $<server>" or "PRIVMSG #<host>" is attempted.
618 ERR_UNKNOWNCOMMAND=421
619 # "<command> :Unknown command"
621 # - Returned to a registered client to indicate that the
622 # command sent is unknown by the server.
625 # ":MOTD File is missing"
627 # - Server's MOTD file could not be opened by the server.
630 # "<server> :No administrative info available"
632 # - Returned by a server in response to an ADMIN message
633 # when there is an error in finding the appropriate
637 # ":File error doing <file op> on <file>"
639 # - Generic error message used to report a failed file
640 # operation during the processing of a message.
642 ERR_NONICKNAMEGIVEN=431
643 # ":No nickname given"
645 # - Returned when a nickname parameter expected for a
646 # command and isn't found.
648 ERR_ERRONEUSNICKNAME=432
649 # "<nick> :Erroneous nickname"
651 # - Returned after receiving a NICK message which contains
652 # characters which do not fall in the defined set. See
653 # section 2.3.1 for details on valid nicknames.
655 ERR_NICKNAMEINUSE=433
656 # "<nick> :Nickname is already in use"
658 # - Returned when a NICK message is processed that results
659 # in an attempt to change to a currently existing
662 ERR_NICKCOLLISION=436
663 # "<nick> :Nickname collision KILL from <user>@<host>"
665 # - Returned by a server to a client when it detects a
666 # nickname collision (registered of a NICK that
667 # already exists by another server).
669 ERR_UNAVAILRESOURCE=437
670 # "<nick/channel> :Nick/channel is temporarily unavailable"
672 # - Returned by a server to a user trying to join a channel
673 # currently blocked by the channel delay mechanism.
675 # - Returned by a server to a user trying to change nickname
676 # when the desired nickname is blocked by the nick delay
679 ERR_USERNOTINCHANNEL=441
680 # "<nick> <channel> :They aren't on that channel"
682 # - Returned by the server to indicate that the target
683 # user of the command is not on the given channel.
686 # "<channel> :You're not on that channel"
688 # - Returned by the server whenever a client tries to
689 # perform a channel affecting command for which the
690 # client isn't a member.
692 ERR_USERONCHANNEL=443
693 # "<user> <channel> :is already on channel"
695 # - Returned when a client tries to invite a user to a
696 # channel they are already on.
699 # "<user> :User not logged in"
701 # - Returned by the summon after a SUMMON command for a
702 # user was unable to be performed since they were not
706 ERR_SUMMONDISABLED=445
707 # ":SUMMON has been disabled"
709 # - Returned as a response to the SUMMON command. MUST be
710 # returned by any server which doesn't implement it.
712 ERR_USERSDISABLED=446
713 # ":USERS has been disabled"
715 # - Returned as a response to the USERS command. MUST be
716 # returned by any server which does not implement it.
718 ERR_NOTREGISTERED=451
719 # ":You have not registered"
721 # - Returned by the server to indicate that the client
722 # MUST be registered before the server will allow it
723 # to be parsed in detail.
725 ERR_NEEDMOREPARAMS=461
726 # "<command> :Not enough parameters"
728 # - Returned by the server by numerous commands to
729 # indicate to the client that it didn't supply enough
732 ERR_ALREADYREGISTRED=462
733 # ":Unauthorized command (already registered)"
735 # - Returned by the server to any link which tries to
736 # change part of the registered details (such as
737 # password or user details from second USER message).
739 ERR_NOPERMFORHOST=463
740 # ":Your host isn't among the privileged"
742 # - Returned to a client which attempts to register with
743 # a server which does not been setup to allow
744 # connections from the host the attempted connection
747 ERR_PASSWDMISMATCH=464
748 # ":Password incorrect"
750 # - Returned to indicate a failed attempt at registering
751 # a connection for which a password was required and
752 # was either not given or incorrect.
754 ERR_YOUREBANNEDCREEP=465
755 # ":You are banned from this server"
757 # - Returned after an attempt to connect and register
758 # yourself with a server which has been setup to
759 # explicitly deny connections to you.
761 ERR_YOUWILLBEBANNED=466
763 # - Sent by a server to a user to inform that access to the
764 # server will soon be denied.
767 # "<channel> :Channel key already set"
768 ERR_CHANNELISFULL=471
769 # "<channel> :Cannot join channel (+l)"
771 # "<char> :is unknown mode char to me for <channel>"
772 ERR_INVITEONLYCHAN=473
773 # "<channel> :Cannot join channel (+i)"
774 ERR_BANNEDFROMCHAN=474
775 # "<channel> :Cannot join channel (+b)"
776 ERR_BADCHANNELKEY=475
777 # "<channel> :Cannot join channel (+k)"
779 # "<channel> :Bad Channel Mask"
781 # "<channel> :Channel doesn't support modes"
783 # "<channel> <char> :Channel list is full"
786 # ":Permission Denied- You're not an IRC operator"
788 # - Any command requiring operator privileges to operate
789 # MUST return this error to indicate the attempt was
792 ERR_CHANOPRIVSNEEDED=482
793 # "<channel> :You're not channel operator"
795 # - Any command requiring 'chanop' privileges (such as
796 # MODE messages) MUST return this error if the client
797 # making the attempt is not a chanop on the specified
801 ERR_CANTKILLSERVER=483
802 # ":You can't kill a server!"
804 # - Any attempts to use the KILL command on a server
805 # are to be refused and this error returned directly
809 # ":Your connection is restricted!"
811 # - Sent by the server to a user upon connection to indicate
812 # the restricted nature of the connection (user mode "+r").
814 ERR_UNIQOPPRIVSNEEDED=485
815 # ":You're not the original channel operator"
817 # - Any MODE requiring "channel creator" privileges MUST
818 # return this error if the client making the attempt is not
819 # a chanop on the specified channel.
822 # ":No O-lines for your host"
824 # - If a client sends an OPER message and the server has
825 # not been configured to allow connections from the
826 # client's host as an operator, this error MUST be
829 ERR_UMODEUNKNOWNFLAG=501
830 # ":Unknown MODE flag"
832 # - Returned by the server to indicate that a MODE
833 # message was sent with a nickname parameter and that
834 # the a mode flag sent was not recognized.
836 ERR_USERSDONTMATCH=502
837 # ":Cannot change mode for other users"
839 # - Error sent to any user trying to view or change the
840 # user mode for a user other than themselves.
842 # 5.3 Reserved numerics
844 # These numerics are not described above since they fall into one of
845 # the following categories:
847 # 1. no longer in use;
849 # 2. reserved for future planned use;
851 # 3. in current use but are part of a non-generic 'feature' of
852 # the current IRC server.
854 RPL_ENDOFSERVICES=232
875 ERR_NOSERVICEHOST=492
878 # implements RFC 2812 and prior IRC RFCs.
879 # clients register handler proc{}s for different server events and IrcClient
883 attr_reader :server, :client
885 # create a new IrcClient instance
887 @server = Server.new # The Server
888 @client = @server.user("") # The User representing the client on this Server
892 # This is used by some messages to build lists of users that
893 # will be delegated when the ENDOF... message is received
897 # key:: server event to handle
898 # value:: proc object called when event occurs
899 # set a handler for a server event
901 # ==server events currently supported:
903 # welcome:: server welcome message on connect
904 # yourhost:: your host details (on connection)
905 # created:: when the server was started
906 # isupport:: information about what this server supports
907 # ping:: server pings you (default handler returns a pong)
908 # nicktaken:: you tried to change nick to one that's in use
909 # badnick:: you tried to change nick to one that's invalid
910 # topic:: someone changed the topic of a channel
911 # topicinfo:: on joining a channel or asking for the topic, tells you
912 # who set it and when
913 # names:: server sends list of channel members when you join
914 # motd:: server message of the day
915 # privmsg:: privmsg, the core of IRC, a message to you from someone
916 # public:: optionally instead of getting privmsg you can hook to only
918 # msg:: or only the private ones, or both
919 # kick:: someone got kicked from a channel
920 # part:: someone left a channel
921 # quit:: someone quit IRC
922 # join:: someone joined a channel
923 # changetopic:: the topic of a channel changed
924 # invite:: you are invited to a channel
925 # nick:: someone changed their nick
926 # mode:: a mode change
927 # notice:: someone sends you a notice
928 # unknown:: any other message not handled by the above
930 @handlers[key] = value
934 # remove a handler for a server event
935 def deletehandler(key)
936 @handlers.delete(key)
939 # takes a server string, checks for PING, PRIVMSG, NOTIFY, etc, and parses
940 # numeric server replies, calling the appropriate handler for each, and
941 # sending it a hash containing the data from the server
942 def process(serverstring)
944 data[:serverstring] = serverstring
946 unless serverstring =~ /^(:(\S+)\s)?(\S+)(\s(.*))?/
947 raise "Unparseable Server Message!!!: #{serverstring}"
950 prefix, command, params = $2, $3, $5
953 data[:source] = prefix
954 if prefix =~ /^(\S+)!(\S+)$/
955 data[:source] = @server.user(prefix)
957 if @server.hostname && @server.hostname != data[:source]
958 warning "Unknown origin #{data[:source]} for message\n#{serverstring.inspect}"
960 @server.instance_variable_set(:@hostname, data[:source])
962 data[:source] = @server
966 # split parameters in an array
968 params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
972 data[:pingid] = argv[0]
975 data[:pingid] = argv[0]
977 when /^(\d+)$/ # numerical server message
981 # "Welcome to the Internet Relay Network
982 # <nick>!<user>@<host>"
988 @client = @server.user(data[:netmask])
990 when /Welcome to the Internet Relay Network\s(\S+)/
992 when /Welcome.*\s+(\S+)$/
997 @client = @server.user(data[:nick]) unless set
998 handle(:welcome, data)
1000 # "Your host is <servername>, running version <ver>"
1001 handle(:yourhost, data)
1003 # "This server was created <date>"
1004 data[:message] = argv[1]
1005 handle(:created, data)
1007 # "<servername> <version> <available user modes>
1008 # <available channel modes>"
1009 @server.parse_my_info(params.split(' ', 2).last)
1010 data[:servername] = @server.hostname
1011 data[:version] = @server.version
1012 data[:usermodes] = @server.usermodes
1013 data[:chanmodes] = @server.chanmodes
1014 handle(:myinfo, data)
1016 # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
1017 # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
1018 # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
1019 # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
1022 @server.parse_isupport(argv[1..-2].join(' '))
1023 handle(:isupport, data)
1024 when ERR_NICKNAMEINUSE
1025 # "* <nick> :Nickname is already in use"
1026 data[:nick] = argv[1]
1027 data[:message] = argv[2]
1028 handle(:nicktaken, data)
1029 when ERR_ERRONEUSNICKNAME
1030 # "* <nick> :Erroneous nickname"
1031 data[:nick] = argv[1]
1032 data[:message] = argv[2]
1033 handle(:badnick, data)
1035 data[:channel] = @server.get_channel(argv[1])
1036 data[:topic] = argv[2]
1039 data[:channel].topic.text = data[:topic]
1041 warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1044 handle(:topic, data)
1046 data[:nick] = @server.user(argv[0])
1047 data[:channel] = @server.get_channel(argv[1])
1048 data[:source] = @server.user(argv[2])
1049 data[:time] = Time.at(argv[3].to_i)
1052 data[:channel].topic.set_by = data[:nick]
1053 data[:channel].topic.set_on = data[:time]
1055 warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1058 handle(:topicinfo, data)
1060 # "( "=" / "*" / "@" ) <channel>
1061 # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
1062 # - "@" is used for secret channels, "*" for private
1063 # channels, and "=" for others (public channels).
1064 data[:channeltype] = argv[1]
1065 data[:channel] = argv[2]
1067 chan = @server.get_channel(data[:channel])
1069 warning "Received names #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1074 argv[3].scan(/\S+/).each { |u|
1075 # FIXME beware of servers that allow multiple prefixes
1076 if(u =~ /^(#{@server.supports[:prefix][:prefixes].join})?(.*)$/)
1079 users << [user, umode]
1084 u = @server.user(ar[0])
1085 chan.users << u unless chan.users.include?(u)
1087 m = @server.supports[:prefix][:prefixes].index(ar[1].to_sym)
1088 m = @server.supports[:prefix][:modes][m]
1089 chan.mode[m.to_sym].set(u)
1094 data[:channel] = argv[1]
1095 data[:users] = @tmpusers
1096 handle(:names, data)
1097 @tmpusers = Array.new
1098 when RPL_LUSERCLIENT
1099 # ":There are <integer> users and <integer>
1100 # services on <integer> servers"
1101 data[:message] = argv[1]
1102 handle(:luserclient, data)
1104 # "<integer> :operator(s) online"
1105 data[:ops] = argv[1].to_i
1106 handle(:luserop, data)
1107 when RPL_LUSERUNKNOWN
1108 # "<integer> :unknown connection(s)"
1109 data[:unknown] = argv[1].to_i
1110 handle(:luserunknown, data)
1111 when RPL_LUSERCHANNELS
1112 # "<integer> :channels formed"
1113 data[:channels] = argv[1].to_i
1114 handle(:luserchannels, data)
1116 # ":I have <integer> clients and <integer> servers"
1117 data[:message] = argv[1]
1118 handle(:luserme, data)
1120 # ":MOTD File is missing"
1121 data[:message] = argv[1]
1122 handle(:motd_missing, data)
1124 # ":Current local users: 3 Max: 4"
1125 data[:message] = argv[1]
1126 handle(:localusers, data)
1127 when RPL_GLOBALUSERS
1128 # ":Current global users: 3 Max: 4"
1129 data[:message] = argv[1]
1130 handle(:globalusers, data)
1132 # ":Highest connection count: 4 (4 clients) (251 since server was
1134 data[:message] = argv[1]
1135 handle(:statsconn, data)
1137 # "<nick> :- <server> Message of the Day -"
1138 if argv[1] =~ /^-\s+(\S+)\s/
1143 if(argv[1] =~ /^-\s+(.*)$/)
1151 data[:text] = argv[1]
1152 handle(:datastr, data)
1154 handle(:unknown, data)
1156 # end of numeric replies
1158 # you can either bind to 'PRIVMSG', to get every one and
1159 # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
1160 # etc and get it all nicely split up for you.
1163 data[:target] = @server.user_or_channel(argv[0])
1165 # The previous may fail e.g. when the target is a server or something
1166 # like that (e.g. $<mask>). In any of these cases, we just use the
1167 # String as a target
1168 # FIXME we probably want to explicitly check for the #<mask> $<mask>
1169 data[:target] = argv[0]
1171 data[:message] = argv[1]
1172 handle(:privmsg, data)
1175 if data[:target].kind_of?(Channel)
1176 handle(:public, data)
1182 data[:target] = @server.user_or_channel(argv[0])
1184 # The previous may fail e.g. when the target is a server or something
1185 # like that (e.g. $<mask>). In any of these cases, we just use the
1186 # String as a target
1187 # FIXME we probably want to explicitly check for the #<mask> $<mask>
1188 data[:target] = argv[0]
1190 data[:message] = argv[1]
1193 handle(:notice, data)
1195 # "server notice" (not from user, noone to reply to)
1196 handle(:snotice, data)
1199 data[:channel] = @server.channel(argv[0])
1200 data[:target] = @server.user(argv[1])
1201 data[:message] = argv[2]
1203 @server.delete_user_from_channel(data[:target], data[:channel])
1204 if data[:target] == @client
1205 @server.delete_channel(data[:channel])
1210 data[:channel] = @server.channel(argv[0])
1211 data[:message] = argv[1]
1213 @server.delete_user_from_channel(data[:source], data[:channel])
1214 if data[:source] == @client
1215 @server.delete_channel(data[:channel])
1220 data[:message] = argv[0]
1221 data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1222 list << ch if ch.users.include?(data[:source])
1226 @server.delete_user(data[:source])
1230 data[:channel] = @server.channel(argv[0])
1231 data[:channel].users << data[:source]
1235 data[:channel] = @server.channel(argv[0])
1236 data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
1237 data[:channel].topic.replace(data[:topic])
1239 handle(:changetopic, data)
1241 data[:target] = @server.user(argv[0])
1242 data[:channel] = @server.channel(argv[1])
1244 handle(:invite, data)
1246 data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1247 list << ch if ch.users.include?(data[:source])
1251 data[:newnick] = argv[0]
1252 data[:oldnick] = data[:source].nick.dup
1253 data[:source].nick = data[:newnick]
1255 debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1259 # MODE ([+-]<modes> (<params>)*)*
1260 # When a MODE message is received by a server,
1261 # Type C will have parameters too, so we must
1262 # be able to consume parameters for all
1265 data[:channel] = @server.user_or_channel(argv[0])
1266 data[:modestring] = argv[1..-1].join(" ")
1271 # data[:modes] is an array where each element
1272 # is either a flag which doesn't need parameters
1273 # or an array with a flag which needs parameters
1274 # and the corresponding parameter
1276 # array of indices in data[:modes] where parameters
1278 who_wants_params = []
1280 argv[1..-1].each { |arg|
1281 setting = arg[0].chr
1282 if "+-".include?(setting)
1283 arg[1..-1].each_byte { |b|
1286 when *@server.supports[:chanmodes][:typea]
1287 data[:modes] << [setting + m]
1288 who_wants_params << data[:modes].length - 1
1289 when *@server.supports[:chanmodes][:typeb]
1290 data[:modes] << [setting + m]
1291 who_wants_params << data[:modes].length - 1
1292 when *@server.supports[:chanmodes][:typec]
1294 data[:modes] << [setting + m]
1295 who_wants_params << data[:modes].length - 1
1297 data[:modes] << setting + m
1299 when *@server.supports[:chanmodes][:typed]
1300 data[:modes] << setting + m
1301 when *@server.supports[:prefix][:modes]
1302 data[:modes] << [setting + m]
1303 who_wants_params << data[:modes].length - 1
1305 warn "Unknown mode #{m} in #{serverstring}"
1309 idx = who_wants_params.shift
1311 warn "Oops, problems parsing #{serverstring}"
1314 data[:modes][idx] << arg
1319 data[:modes].each { |mode|
1322 set = mode[0][0].chr == "+" ? :set : :reset
1323 key = mode[0][1].chr.to_sym
1325 data[:channel].mode[key].send(set, val)
1327 set = mode[0].chr == "+" ? :set : :reset
1328 key = mode[1].chr.to_sym
1329 data[:channel].mode[key].send(set)
1335 handle(:unknown, data)
1341 # key:: server event name
1342 # data:: hash containing data about the event, passed to the proc
1343 # call client's proc for an event, if they set one as a handler
1344 def handle(key, data)
1345 if(@handlers.has_key?(key))
1346 @handlers[key].call(data)