]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/rfc2812.rb
Penalty-based flood protection
[user/henk/code/ruby/rbot.git] / lib / rbot / rfc2812.rb
1 class ::String
2   # Calculate the penalty which will be assigned to this message
3   # by the IRCd
4   def irc_send_penalty
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
9
10     cmd, pars = self.split($;,2)
11     debug "cmd: #{cmd}, pars: #{pars.inspect}"
12     case cmd.to_sym
13     when :KICK
14       chan, nick, msg = pars.split
15       chan = chan.split(',')
16       nick = nick.split(',')
17       penalty += nick.length
18       penalty *= chan.length
19     when :MODE
20       chan, modes, argument = pars.split
21       extra = 0
22       if modes
23         extra = 1
24         if argument
25           extra += modes.split(/\+|-/).length
26         else
27           extra += 3 * modes.split(/\+|-/).length
28         end
29       end
30       if argument
31         extra += 2 * argument.split.length
32       end
33       penalty += extra * chan.split.length
34     when :TOPIC
35       penalty += 1
36       penalty += 2 unless pars.split.length < 2
37     when :PRIVMSG, :NOTICE
38       dests = pars.split($;,2).first
39       penalty += dests.split(',').length
40     when :WHO
41       # I'm too lazy to implement this one correctly
42       penalty += 5
43     when :AWAY, :JOIN, :VERSION, :TIME, :TRACE, :WHOIS, :DNS
44       penalty += 2
45     when :INVITE, :NICK
46       penalty += 3
47     when :ISON
48       penalty += 1
49     else # Unknown messages
50       penalty += 1
51     end
52     if penalty > 99
53       debug "Wow, more than 99 secs of penalty!"
54       penalty = 99
55     end
56     if penalty < 2
57       debug "Wow, less than 2 secs of penalty!"
58       penalty = 2
59     end
60     debug "penalty: #{penalty}"
61     return penalty
62   end
63 end
64
65 module Irc
66   # RFC 2812   Internet Relay Chat: Client Protocol
67   #
68   RPL_WELCOME=001
69   # "Welcome to the Internet Relay Network
70   # <nick>!<user>@<host>"
71   RPL_YOURHOST=002
72   # "Your host is <servername>, running version <ver>"
73   RPL_CREATED=003
74   # "This server was created <date>"
75   RPL_MYINFO=004
76   # "<servername> <version> <available user modes>
77   # <available channel modes>"
78   #
79   # - The server sends Replies 001 to 004 to a user upon
80   # successful registration.
81   #
82   # RPL_BOUNCE=005
83   # # "Try server <server name>, port <port number>"
84   RPL_ISUPPORT=005
85   # "005 nick PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
86   #
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.
90   #
91   RPL_USERHOST=302
92   # ":*1<reply> *( " " <reply> )"
93   #
94   # - Reply format used by USERHOST to list replies to
95   # the query list.  The reply string is composed as
96   # follows:
97   #
98   # reply = nickname [ "*" ] "=" ( "+" / "-" ) hostname
99   #
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
103   # respectively.
104   #
105   RPL_ISON=303
106   # ":*1<nick> *( " " <nick> )"
107   #
108   # - Reply format used by ISON to list replies to the
109   # query list.
110   #
111   RPL_AWAY=301
112   # "<nick> :<away message>"
113   RPL_UNAWAY=305
114   # ":You are no longer marked as being away"
115   RPL_NOWAWAY=306
116   # ":You have been marked as being away"
117   #
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.
124   #
125   RPL_WHOISUSER=311
126   # "<nick> <user> <host> * :<real name>"
127   RPL_WHOISSERVER=312
128   # "<nick> <server> :<server info>"
129   RPL_WHOISOPERATOR=313
130   # "<nick> :is an IRC operator"
131   RPL_WHOISIDLE=317
132   # "<nick> <integer> :seconds idle"
133   RPL_ENDOFWHOIS=318
134   # "<nick> :End of WHOIS list"
135   RPL_WHOISCHANNELS=319
136   # "<nick> :*( ( "@" / "+" ) <channel> " " )"
137   #
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.
152   #
153   RPL_WHOWASUSER=314
154   # "<nick> <user> <host> * :<real name>"
155   RPL_ENDOFWHOWAS=369
156   # "<nick> :End of WHOWAS"
157   #
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).
164   #
165   RPL_LISTSTART=321
166   # Obsolete. Not used.
167   #
168   RPL_LIST=322
169   # "<channel> <# visible> :<topic>"
170   RPL_LISTEND=323
171   # ":End of LIST"
172   #
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.
177   #
178   RPL_UNIQOPIS=325
179   # "<channel> <nickname>"
180   #
181   RPL_CHANNELMODEIS=324
182   # "<channel> <mode> <mode params>"
183   #
184   RPL_NOTOPIC=331
185   # "<channel> :No topic is set"
186   RPL_TOPIC=332
187   # "<channel> :<topic>"
188   #
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
192   # RPL_NOTOPIC.
193   #
194   RPL_TOPIC_INFO=333
195   # <channel> <set by> <unixtime>
196   RPL_INVITING=341
197   # "<channel> <nick>"
198   #
199   # - Returned by the server to indicate that the
200   # attempted INVITE message was successful and is
201   # being passed onto the end client.
202   #
203   RPL_SUMMONING=342
204   # "<user> :Summoning user to IRC"
205   #
206   # - Returned by a server answering a SUMMON message to
207   # indicate that it is summoning that user.
208   #
209   RPL_INVITELIST=346
210   # "<channel> <invitemask>"
211   RPL_ENDOFINVITELIST=347
212   # "<channel> :End of channel invite list"
213   #
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.
220   #
221   RPL_EXCEPTLIST=348
222   # "<channel> <exceptionmask>"
223   RPL_ENDOFEXCEPTLIST=349
224   # "<channel> :End of channel exception list"
225   #
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.
232   #
233   RPL_VERSION=351
234   # "<version>.<debuglevel> <server> :<comments>"
235   #
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".
241   #
242   # The "comments" field may contain any comments about
243   # the version or further version details.
244   #
245   RPL_WHOREPLY=352
246   # "<channel> <user> <host> <server> <nick>
247   # ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
248   # :<hopcount> <real name>"
249   #
250   RPL_ENDOFWHO=315
251   # "<name> :End of WHO list"
252   #
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
259   # the item.
260   #
261   RPL_NAMREPLY=353
262   # "( "=" / "*" / "@" ) <channel>
263   # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
264   # - "@" is used for secret channels, "*" for private
265   # channels, and "=" for others (public channels).
266   #
267   RPL_ENDOFNAMES=366
268   # "<channel> :End of NAMES list"
269   #
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
278   # the end.
279   #
280   RPL_LINKS=364
281   # "<mask> <server> :<hopcount> <server info>"
282   RPL_ENDOFLINKS=365
283   # "<mask> :End of LINKS list"
284   #
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.
288   #
289   RPL_BANLIST=367
290   # "<channel> <banmask>"
291   RPL_ENDOFBANLIST=368
292   # "<channel> :End of channel ban list"
293   #
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.
300   #
301   RPL_INFO=371
302   # ":<string>"
303   RPL_ENDOFINFO=374
304   # ":End of INFO list"
305   #
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
309   # replies.
310   #
311   RPL_MOTDSTART=375
312   # ":- <server> Message of the day - "
313   RPL_MOTD=372
314   # ":- <text>"
315   RPL_ENDOFMOTD=376
316   # ":End of MOTD command"
317   #
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).
324   #
325   RPL_YOUREOPER=381
326   # ":You are now an IRC operator"
327   #
328   # - RPL_YOUREOPER is sent back to a client which has
329   # just successfully issued an OPER message and gained
330   # operator status.
331   #
332   RPL_REHASHING=382
333   # "<config file> :Rehashing"
334   #
335   # - If the REHASH option is used and an operator sends
336   # a REHASH message, an RPL_REHASHING is sent back to
337   # the operator.
338   #
339   RPL_YOURESERVICE=383
340   # "You are service <servicename>"
341   #
342   # - Sent by the server to a service upon successful
343   # registration.
344   #
345   RPL_TIME=391
346   # "<server> :<string showing server's local time>"
347   #
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
352   # time string.
353   #
354   RPL_USERSSTART=392
355   # ":UserID   Terminal  Host"
356   RPL_USERS=393
357   # ":<username> <ttyline> <hostname>"
358   RPL_ENDOFUSERS=394
359   # ":End of users"
360   RPL_NOUSERS=395
361   # ":Nobody logged in"
362   #
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
368   # RPL_ENDOFUSERS.
369   #
370   RPL_TRACELINK=200
371   # "Link <version & debug level> <destination>
372   # <next server> V<protocol version>
373   # <link uptime in seconds> <backstream sendq>
374   # <upstream sendq>"
375   RPL_TRACECONNECTING=201
376   # "Try. <class> <server>"
377   RPL_TRACEHANDSHAKE=202
378   # "H.S. <class> <server>"
379   RPL_TRACEUNKNOWN=203
380   # "???? <class> [<client IP address in dot form>]"
381   RPL_TRACEOPERATOR=204
382   # "Oper <class> <nick>"
383   RPL_TRACEUSER=205
384   # "User <class> <nick>"
385   RPL_TRACESERVER=206
386   # "Serv <class> <int>S <int>C <server>
387   # <nick!user|*!*>@<host|server> V<protocol version>"
388   RPL_TRACESERVICE=207
389   # "Service <class> <name> <type> <active type>"
390   RPL_TRACENEWTYPE=208
391   # "<newtype> 0 <client name>"
392   RPL_TRACECLASS=209
393   # "Class <class> <count>"
394   RPL_TRACERECONNECT=210
395   # Unused.
396   RPL_TRACELOG=261
397   # "File <logfile> <debug level>"
398   RPL_TRACEEND=262
399   # "<server name> <version & debug level> :End of TRACE"
400   #
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.
417   #
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.
422   #
423   RPL_LOCALUSERS=265
424   # ":Current local  users: 3  Max: 4"
425   RPL_GLOBALUSERS=266
426   # ":Current global users: 3  Max: 4"
427   RPL_STATSCONN=250
428   # "::Highest connection count: 4 (4 clients) (251 since server was
429   # (re)started)"
430   RPL_STATSLINKINFO=211
431   # "<linkname> <sendq> <sent messages>
432   # <sent Kbytes> <received messages>
433   # <received Kbytes> <time open>"
434   #
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.
445   #
446   RPL_STATSCOMMANDS=212
447   # "<command> <count> <byte count> <remote count>"
448   #
449   # - reports statistics on commands usage.
450   #
451   RPL_ENDOFSTATS=219
452   # "<stats letter> :End of STATS report"
453   #
454   RPL_STATSUPTIME=242
455   # ":Server Up %d days %d:%02d:%02d"
456   #
457   # - reports the server uptime.
458   #
459   RPL_STATSOLINE=243
460   # "O <hostmask> * <name>"
461   #
462   # - reports the allowed hosts from where user may become IRC
463   # operators.
464   #
465   RPL_UMODEIS=221
466   # "<user mode string>"
467   #
468   # - To answer a query about a client's own mode,
469   # RPL_UMODEIS is sent back.
470   #
471   RPL_SERVLIST=234
472   # "<name> <server> <mask> <type> <hopcount> <info>"
473   #
474   RPL_SERVLISTEND=235
475   # "<mask> <type> :End of service listing"
476   #
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.
483   #
484   RPL_LUSERCLIENT=251
485   # ":There are <integer> users and <integer>
486   # services on <integer> servers"
487   RPL_LUSEROP=252
488   # "<integer> :operator(s) online"
489   RPL_LUSERUNKNOWN=253
490   # "<integer> :unknown connection(s)"
491   RPL_LUSERCHANNELS=254
492   # "<integer> :channels formed"
493   RPL_LUSERME=255
494   # ":I have <integer> clients and <integer>
495   # servers"
496   #
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
504   # is found for them.
505   #
506   RPL_ADMINME=256
507   # "<server> :Administrative info"
508   RPL_ADMINLOC1=257
509   # ":<admin info>"
510   RPL_ADMINLOC2=258
511   # ":<admin info>"
512   RPL_ADMINEMAIL=259
513   # ":<admin info>"
514   #
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)
524   # in RPL_ADMINEMAIL.
525   #
526   RPL_TRYAGAIN=263
527   # "<command> :Please wait a while and try again."
528   #
529   # - When a server drops a command without processing it,
530   # it MUST use the reply RPL_TRYAGAIN to inform the
531   # originating client.
532   #
533   # 5.2 Error Replies
534   #
535   # Error replies are found in the range from 400 to 599.
536   #
537   ERR_NOSUCHNICK=401
538   # "<nickname> :No such nick/channel"
539   #
540   # - Used to indicate the nickname parameter supplied to a
541   # command is currently unused.
542   #
543   ERR_NOSUCHSERVER=402
544   # "<server name> :No such server"
545   #
546   # - Used to indicate the server name given currently
547   # does not exist.
548   #
549   ERR_NOSUCHCHANNEL=403
550   # "<channel name> :No such channel"
551   #
552   # - Used to indicate the given channel name is invalid.
553   #
554   ERR_CANNOTSENDTOCHAN=404
555   # "<channel name> :Cannot send to channel"
556   #
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
561   # that channel.
562   #
563   ERR_TOOMANYCHANNELS=405
564   # "<channel name> :You have joined too many channels"
565   #
566   # - Sent to a user when they have joined the maximum
567   # number of allowed channels and they try to join
568   # another channel.
569   #
570   ERR_WASNOSUCHNICK=406
571   # "<nickname> :There was no such nickname"
572   #
573   # - Returned by WHOWAS to indicate there is no history
574   # information for that nickname.
575   #
576   ERR_TOOMANYTARGETS=407
577   # "<target> :<error code> recipients. <abort message>"
578   #
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.
582   #
583   # - Returned to a client which trying to send a
584   # PRIVMSG/NOTICE to too many recipients.
585   #
586   # - Returned to a client which is attempting to JOIN a safe
587   # channel using the shortname when there are more than one
588   # such channel.
589   #
590   ERR_NOSUCHSERVICE=408
591   # "<service name> :No such service"
592   #
593   # - Returned to a client which is attempting to send a SQUERY
594   # to a service which does not exist.
595   #
596   ERR_NOORIGIN=409
597   # ":No origin specified"
598   #
599   # - PING or PONG message missing the originator parameter.
600   #
601   ERR_NORECIPIENT=411
602   # ":No recipient given (<command>)"
603   ERR_NOTEXTTOSEND=412
604   # ":No text to send"
605   ERR_NOTOPLEVEL=413
606   # "<mask> :No toplevel domain specified"
607   ERR_WILDTOPLEVEL=414
608   # "<mask> :Wildcard in toplevel domain"
609   ERR_BADMASK=415
610   # "<mask> :Bad Server/host mask"
611   #
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.
617   #
618   ERR_UNKNOWNCOMMAND=421
619   # "<command> :Unknown command"
620   #
621   # - Returned to a registered client to indicate that the
622   # command sent is unknown by the server.
623   #
624   ERR_NOMOTD=422
625   # ":MOTD File is missing"
626   #
627   # - Server's MOTD file could not be opened by the server.
628   #
629   ERR_NOADMININFO=423
630   # "<server> :No administrative info available"
631   #
632   # - Returned by a server in response to an ADMIN message
633   # when there is an error in finding the appropriate
634   # information.
635   #
636   ERR_FILEERROR=424
637   # ":File error doing <file op> on <file>"
638   #
639   # - Generic error message used to report a failed file
640   # operation during the processing of a message.
641   #
642   ERR_NONICKNAMEGIVEN=431
643   # ":No nickname given"
644   #
645   # - Returned when a nickname parameter expected for a
646   # command and isn't found.
647   #
648   ERR_ERRONEUSNICKNAME=432
649   # "<nick> :Erroneous nickname"
650   #
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.
654   #
655   ERR_NICKNAMEINUSE=433
656   # "<nick> :Nickname is already in use"
657   #
658   # - Returned when a NICK message is processed that results
659   # in an attempt to change to a currently existing
660   # nickname.
661   #
662   ERR_NICKCOLLISION=436
663   # "<nick> :Nickname collision KILL from <user>@<host>"
664   #
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).
668   #
669   ERR_UNAVAILRESOURCE=437
670   # "<nick/channel> :Nick/channel is temporarily unavailable"
671   #
672   # - Returned by a server to a user trying to join a channel
673   # currently blocked by the channel delay mechanism.
674   #
675   # - Returned by a server to a user trying to change nickname
676   # when the desired nickname is blocked by the nick delay
677   # mechanism.
678   #
679   ERR_USERNOTINCHANNEL=441
680   # "<nick> <channel> :They aren't on that channel"
681   #
682   # - Returned by the server to indicate that the target
683   # user of the command is not on the given channel.
684   #
685   ERR_NOTONCHANNEL=442
686   # "<channel> :You're not on that channel"
687   #
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.
691   #
692   ERR_USERONCHANNEL=443
693   # "<user> <channel> :is already on channel"
694   #
695   # - Returned when a client tries to invite a user to a
696   # channel they are already on.
697   #
698   ERR_NOLOGIN=444
699   # "<user> :User not logged in"
700   #
701   # - Returned by the summon after a SUMMON command for a
702   # user was unable to be performed since they were not
703   # logged in.
704   #
705   #
706   ERR_SUMMONDISABLED=445
707   # ":SUMMON has been disabled"
708   #
709   # - Returned as a response to the SUMMON command.  MUST be
710   # returned by any server which doesn't implement it.
711   #
712   ERR_USERSDISABLED=446
713   # ":USERS has been disabled"
714   #
715   # - Returned as a response to the USERS command.  MUST be
716   # returned by any server which does not implement it.
717   #
718   ERR_NOTREGISTERED=451
719   # ":You have not registered"
720   #
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.
724   #
725   ERR_NEEDMOREPARAMS=461
726   # "<command> :Not enough parameters"
727   #
728   # - Returned by the server by numerous commands to
729   # indicate to the client that it didn't supply enough
730   # parameters.
731   #
732   ERR_ALREADYREGISTRED=462
733   # ":Unauthorized command (already registered)"
734   #
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).
738   #
739   ERR_NOPERMFORHOST=463
740   # ":Your host isn't among the privileged"
741   #
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
745   # is tried.
746   #
747   ERR_PASSWDMISMATCH=464
748   # ":Password incorrect"
749   #
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.
753   #
754   ERR_YOUREBANNEDCREEP=465
755   # ":You are banned from this server"
756   #
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.
760   #
761   ERR_YOUWILLBEBANNED=466
762   #
763   # - Sent by a server to a user to inform that access to the
764   # server will soon be denied.
765   #
766   ERR_KEYSET=467
767   # "<channel> :Channel key already set"
768   ERR_CHANNELISFULL=471
769   # "<channel> :Cannot join channel (+l)"
770   ERR_UNKNOWNMODE=472
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)"
778   ERR_BADCHANMASK=476
779   # "<channel> :Bad Channel Mask"
780   ERR_NOCHANMODES=477
781   # "<channel> :Channel doesn't support modes"
782   ERR_BANLISTFULL=478
783   # "<channel> <char> :Channel list is full"
784   #
785   ERR_NOPRIVILEGES=481
786   # ":Permission Denied- You're not an IRC operator"
787   #
788   # - Any command requiring operator privileges to operate
789   # MUST return this error to indicate the attempt was
790   # unsuccessful.
791   #
792   ERR_CHANOPRIVSNEEDED=482
793   # "<channel> :You're not channel operator"
794   #
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
798   # channel.
799   #
800   #
801   ERR_CANTKILLSERVER=483
802   # ":You can't kill a server!"
803   #
804   # - Any attempts to use the KILL command on a server
805   # are to be refused and this error returned directly
806   # to the client.
807   #
808   ERR_RESTRICTED=484
809   # ":Your connection is restricted!"
810   #
811   # - Sent by the server to a user upon connection to indicate
812   # the restricted nature of the connection (user mode "+r").
813   #
814   ERR_UNIQOPPRIVSNEEDED=485
815   # ":You're not the original channel operator"
816   #
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.
820   #
821   ERR_NOOPERHOST=491
822   # ":No O-lines for your host"
823   #
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
827   # returned.
828   #
829   ERR_UMODEUNKNOWNFLAG=501
830   # ":Unknown MODE flag"
831   #
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.
835   #
836   ERR_USERSDONTMATCH=502
837   # ":Cannot change mode for other users"
838   #
839   # - Error sent to any user trying to view or change the
840   # user mode for a user other than themselves.
841   #
842   # 5.3 Reserved numerics
843   #
844   # These numerics are not described above since they fall into one of
845   # the following categories:
846   #
847   # 1. no longer in use;
848   #
849   # 2. reserved for future planned use;
850   #
851   # 3. in current use but are part of a non-generic 'feature' of
852   # the current IRC server.
853   RPL_SERVICEINFO=231
854   RPL_ENDOFSERVICES=232
855   RPL_SERVICE=233
856   RPL_NONE=300
857   RPL_WHOISCHANOP=316
858   RPL_KILLDONE=361
859   RPL_CLOSING=362
860   RPL_CLOSEEND=363
861   RPL_INFOSTART=373
862   RPL_MYPORTIS=384
863   RPL_STATSCLINE=213
864   RPL_STATSNLINE=214
865   RPL_STATSILINE=215
866   RPL_STATSKLINE=216
867   RPL_STATSQLINE=217
868   RPL_STATSYLINE=218
869   RPL_STATSVLINE=240
870   RPL_STATSLLINE=241
871   RPL_STATSHLINE=244
872   RPL_STATSSLINE=244
873   RPL_STATSPING=246
874   RPL_STATSBLINE=247
875   ERR_NOSERVICEHOST=492
876   RPL_DATASTR=290
877
878   # implements RFC 2812 and prior IRC RFCs.
879   # clients register handler proc{}s for different server events and IrcClient
880   # handles dispatch
881   class IrcClient
882
883     attr_reader :server, :client
884
885     # create a new IrcClient instance
886     def initialize
887       @server = Server.new         # The Server
888       @client = @server.user("")   # The User representing the client on this Server
889
890       @handlers = Hash.new
891
892       # This is used by some messages to build lists of users that
893       # will be delegated when the ENDOF... message is received
894       @tmpusers = []
895     end
896
897     # key::   server event to handle
898     # value:: proc object called when event occurs
899     # set a handler for a server event
900     #
901     # ==server events currently supported:
902     #
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
917     #               the public ones...
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
929     def []=(key, value)
930       @handlers[key] = value
931     end
932
933     # key:: event name
934     # remove a handler for a server event
935     def deletehandler(key)
936       @handlers.delete(key)
937     end
938
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)
943       data = Hash.new
944       data[:serverstring] = serverstring
945
946       unless serverstring =~ /^(:(\S+)\s)?(\S+)(\s(.*))?/
947         raise "Unparseable Server Message!!!: #{serverstring}"
948       end
949
950       prefix, command, params = $2, $3, $5
951
952       if prefix != nil
953         data[:source] = prefix
954         if prefix =~ /^(\S+)!(\S+)$/
955           data[:source] = @server.user(prefix)
956         else
957           if @server.hostname && @server.hostname != data[:source]
958             warning "Unknown origin #{data[:source]} for message\n#{serverstring.inspect}"
959           else
960             @server.instance_variable_set(:@hostname, data[:source])
961           end
962           data[:source] = @server
963         end
964       end
965
966       # split parameters in an array
967       argv = []
968       params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params
969
970       case command
971       when 'PING'
972         data[:pingid] = argv[0]
973         handle(:ping, data)
974       when 'PONG'
975         data[:pingid] = argv[0]
976         handle(:pong, data)
977       when /^(\d+)$/            # numerical server message
978         num=command.to_i
979         case num
980         when RPL_WELCOME
981           # "Welcome to the Internet Relay Network
982           # <nick>!<user>@<host>"
983           case argv[1]
984           when /((\S+)!(\S+))/
985             data[:netmask] = $1
986             data[:nick] = $2
987             data[:address] = $3
988             @client = @server.user(data[:netmask])
989             set = true
990           when /Welcome to the Internet Relay Network\s(\S+)/
991             data[:nick] = $1
992           when /Welcome.*\s+(\S+)$/
993             data[:nick] = $1
994           when /^(\S+)$/
995             data[:nick] = $1
996           end
997           @client = @server.user(data[:nick]) unless set
998           handle(:welcome, data)
999         when RPL_YOURHOST
1000           # "Your host is <servername>, running version <ver>"
1001           handle(:yourhost, data)
1002         when RPL_CREATED
1003           # "This server was created <date>"
1004           data[:message] = argv[1]
1005           handle(:created, data)
1006         when RPL_MYINFO
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)
1015         when RPL_ISUPPORT
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
1020           # on this server"
1021           #
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)
1034         when RPL_TOPIC
1035           data[:channel] = @server.get_channel(argv[1])
1036           data[:topic] = argv[2]
1037
1038           if data[:channel]
1039             data[:channel].topic.text = data[:topic]
1040           else
1041             warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1042           end
1043
1044           handle(:topic, data)
1045         when RPL_TOPIC_INFO
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)
1050
1051           if data[:channel]
1052             data[:channel].topic.set_by = data[:nick]
1053             data[:channel].topic.set_on = data[:time]
1054           else
1055             warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1056           end
1057
1058           handle(:topicinfo, data)
1059         when RPL_NAMREPLY
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]
1066
1067           chan = @server.get_channel(data[:channel])
1068           unless chan
1069             warning "Received names #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on"
1070             return
1071           end
1072
1073           users = []
1074           argv[3].scan(/\S+/).each { |u|
1075             # FIXME beware of servers that allow multiple prefixes
1076             if(u =~ /^(#{@server.supports[:prefix][:prefixes].join})?(.*)$/)
1077               umode = $1
1078               user = $2
1079               users << [user, umode]
1080             end
1081           }
1082
1083           users.each { |ar|
1084             u = @server.user(ar[0])
1085             chan.users << u unless chan.users.include?(u)
1086             if ar[1]
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)
1090             end
1091           }
1092           @tmpusers += users
1093         when RPL_ENDOFNAMES
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)
1103         when RPL_LUSEROP
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)
1115         when RPL_LUSERME
1116           # ":I have <integer> clients and <integer> servers"
1117           data[:message] = argv[1]
1118           handle(:luserme, data)
1119         when ERR_NOMOTD
1120           # ":MOTD File is missing"
1121           data[:message] = argv[1]
1122           handle(:motd_missing, data)
1123         when RPL_LOCALUSERS
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)
1131         when RPL_STATSCONN
1132           # ":Highest connection count: 4 (4 clients) (251 since server was
1133           # (re)started)"
1134           data[:message] = argv[1]
1135           handle(:statsconn, data)
1136         when RPL_MOTDSTART
1137           # "<nick> :- <server> Message of the Day -"
1138           if argv[1] =~ /^-\s+(\S+)\s/
1139             server = $1
1140             @motd = ""
1141           end
1142         when RPL_MOTD
1143           if(argv[1] =~ /^-\s+(.*)$/)
1144             @motd << $1
1145             @motd << "\n"
1146           end
1147         when RPL_ENDOFMOTD
1148           data[:motd] = @motd
1149           handle(:motd, data)
1150         when RPL_DATASTR
1151           data[:text] = argv[1]
1152           handle(:datastr, data)
1153         else
1154           handle(:unknown, data)
1155         end
1156       # end of numeric replies
1157       when 'PRIVMSG'
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.
1161
1162         begin
1163           data[:target] = @server.user_or_channel(argv[0])
1164         rescue
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]
1170         end
1171         data[:message] = argv[1]
1172         handle(:privmsg, data)
1173
1174         # Now we split it
1175         if data[:target].kind_of?(Channel)
1176           handle(:public, data)
1177         else
1178           handle(:msg, data)
1179         end
1180       when 'NOTICE'
1181         begin
1182           data[:target] = @server.user_or_channel(argv[0])
1183         rescue
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]
1189         end
1190         data[:message] = argv[1]
1191         case data[:source]
1192         when User
1193           handle(:notice, data)
1194         else
1195           # "server notice" (not from user, noone to reply to)
1196           handle(:snotice, data)
1197         end
1198       when 'KICK'
1199         data[:channel] = @server.channel(argv[0])
1200         data[:target] = @server.user(argv[1])
1201         data[:message] = argv[2]
1202
1203         @server.delete_user_from_channel(data[:target], data[:channel])
1204         if data[:target] == @client
1205           @server.delete_channel(data[:channel])
1206         end
1207
1208         handle(:kick, data)
1209       when 'PART'
1210         data[:channel] = @server.channel(argv[0])
1211         data[:message] = argv[1]
1212
1213         @server.delete_user_from_channel(data[:source], data[:channel])
1214         if data[:source] == @client
1215           @server.delete_channel(data[:channel])
1216         end
1217
1218         handle(:part, data)
1219       when 'QUIT'
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])
1223           list
1224         }
1225
1226         @server.delete_user(data[:source])
1227
1228         handle(:quit, data)
1229       when 'JOIN'
1230         data[:channel] = @server.channel(argv[0])
1231         data[:channel].users << data[:source]
1232
1233         handle(:join, data)
1234       when 'TOPIC'
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])
1238
1239         handle(:changetopic, data)
1240       when 'INVITE'
1241         data[:target] = @server.user(argv[0])
1242         data[:channel] = @server.channel(argv[1])
1243
1244         handle(:invite, data)
1245       when 'NICK'
1246         data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
1247           list << ch if ch.users.include?(data[:source])
1248           list
1249         }
1250
1251         data[:newnick] = argv[0]
1252         data[:oldnick] = data[:source].nick.dup
1253         data[:source].nick = data[:newnick]
1254
1255         debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"
1256
1257         handle(:nick, data)
1258       when 'MODE'
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
1263         # but Type D modes
1264
1265         data[:channel] = @server.user_or_channel(argv[0])
1266         data[:modestring] = argv[1..-1].join(" ")
1267         case data[:channel]
1268         when User
1269           # TODO
1270         else
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
1275           data[:modes] = []
1276           # array of indices in data[:modes] where parameters
1277           # are needed
1278           who_wants_params = []
1279
1280           argv[1..-1].each { |arg|
1281             setting = arg[0].chr
1282             if "+-".include?(setting)
1283               arg[1..-1].each_byte { |b|
1284                 m = b.chr
1285                 case m.to_sym
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]
1293                   if setting == "+"
1294                     data[:modes] << [setting + m]
1295                     who_wants_params << data[:modes].length - 1
1296                   else
1297                     data[:modes] << setting + m
1298                   end
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
1304                 else
1305                   warn "Unknown mode #{m} in #{serverstring}"
1306                 end
1307               }
1308             else
1309               idx = who_wants_params.shift
1310               if idx.nil?
1311                 warn "Oops, problems parsing #{serverstring}"
1312                 break
1313               end
1314               data[:modes][idx] << arg
1315             end
1316           }
1317         end
1318
1319         data[:modes].each { |mode|
1320           case mode
1321           when Array
1322             set = mode[0][0].chr == "+" ? :set : :reset
1323             key = mode[0][1].chr.to_sym
1324             val = mode[1]
1325             data[:channel].mode[key].send(set, val)
1326           else
1327             set = mode[0].chr == "+" ? :set : :reset
1328             key = mode[1].chr.to_sym
1329             data[:channel].mode[key].send(set)
1330           end
1331         } if data[:modes]
1332
1333         handle(:mode, data)
1334       else
1335         handle(:unknown, data)
1336       end
1337     end
1338
1339     private
1340
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)
1347       end
1348     end
1349   end
1350 end