IRC: support 'CASEMAPPING=charset' from some idiotic servers
[rbot] / lib / rbot / irc.rb
1 #-- vim:sw=2:et
2 # General TODO list
3 # * do we want to handle a Channel list for each User telling which
4 #   Channels is the User on (of those the client is on too)?
5 #   We may want this so that when a User leaves all Channels and he hasn't
6 #   sent us privmsgs, we know we can remove him from the Server @users list
7 #   FIXME for the time being, we do it with a method that scans the server
8 #   (if defined), so the method is slow and should not be used frequently.
9 # * Maybe ChannelList and UserList should be HashesOf instead of ArrayOf?
10 #   See items marked as TODO Ho.
11 #   The framework to do this is now in place, thanks to the new [] method
12 #   for NetmaskList, which allows retrieval by Netmask or String
13 #++
14 # :title: IRC module
15 #
16 # Basic IRC stuff
17 #
18 # This module defines the fundamental building blocks for IRC
19 #
20 # Author:: Giuseppe Bilotta (giuseppe.bilotta@gmail.com)
21
22 require 'singleton'
23
24 # The following monkeypatch is to fix a bug in Singleton where marshaling would
25 # fail when trying to restore a marshaled Singleton due to _load being declared
26 # private.
27 if RUBY_VERSION < '1.9'
28 module ::Singleton
29   public :_dump
30 end
31
32 class << Singleton
33   module SingletonClassMethods
34     public :_load
35   end
36 end
37 end
38
39 class Object
40
41   # We extend the Object class with a method that
42   # checks if the receiver is nil or empty
43   def nil_or_empty?
44     return true unless self
45     return true if self.respond_to? :empty? and self.empty?
46     return false
47   end
48
49   # We alias the to_s method to __to_s__ to make
50   # it accessible in all classes
51   alias :__to_s__ :to_s
52 end
53
54 # The Irc module is used to keep all IRC-related classes
55 # in the same namespace
56 #
57 module Irc
58
59
60   # Due to its Scandinavian origins, IRC has strange case mappings, which
61   # consider the characters <tt>{}|^</tt> as the uppercase
62   # equivalents of # <tt>[]\~</tt>.
63   #
64   # This is however not the same on all IRC servers: some use standard ASCII
65   # casemapping, other do not consider <tt>^</tt> as the uppercase of
66   # <tt>~</tt>
67   #
68   class Casemap
69     @@casemaps = {}
70
71     # Create a new casemap with name _name_, uppercase characters _upper_ and
72     # lowercase characters _lower_
73     #
74     def initialize(name, upper, lower)
75       @key = name.to_sym
76       raise "Casemap #{name.inspect} already exists!" if @@casemaps.has_key?(@key)
77       @@casemaps[@key] = {
78         :upper => upper,
79         :lower => lower,
80         :casemap => self
81       }
82     end
83
84     # Returns the Casemap with the given name
85     #
86     def Casemap.get(name)
87       @@casemaps[name.to_sym][:casemap]
88     end
89
90     # Retrieve the 'uppercase characters' of this Casemap
91     #
92     def upper
93       @@casemaps[@key][:upper]
94     end
95
96     # Retrieve the 'lowercase characters' of this Casemap
97     #
98     def lower
99       @@casemaps[@key][:lower]
100     end
101
102     # Return a Casemap based on the receiver
103     #
104     def to_irc_casemap
105       self
106     end
107
108     # A Casemap is represented by its lower/upper mappings
109     #
110     def inspect
111       self.__to_s__[0..-2] + " #{upper.inspect} ~(#{self})~ #{lower.inspect}>"
112     end
113
114     # As a String we return our name
115     #
116     def to_s
117       @key.to_s
118     end
119
120     # Two Casemaps are equal if they have the same upper and lower ranges
121     #
122     def ==(arg)
123       other = arg.to_irc_casemap
124       return self.upper == other.upper && self.lower == other.lower
125     end
126
127     # Give a warning if _arg_ and self are not the same Casemap
128     #
129     def must_be(arg)
130       other = arg.to_irc_casemap
131       if self == other
132         return true
133       else
134         warn "Casemap mismatch (#{self.inspect} != #{other.inspect})"
135         return false
136       end
137     end
138
139   end
140
141   # The rfc1459 casemap
142   #
143   class RfcCasemap < Casemap
144     include Singleton
145
146     def initialize
147       super('rfc1459', "\x41-\x5a\x7b-\x7e", "\x61-\x7a\x5b-\x5e")
148     end
149
150   end
151   RfcCasemap.instance
152
153   # The strict-rfc1459 Casemap
154   #
155   class StrictRfcCasemap < Casemap
156     include Singleton
157
158     def initialize
159       super('strict-rfc1459', "\x41-\x5a\x7b-\x7d", "\x61-\x7a\x5b-\x5d")
160     end
161
162   end
163   StrictRfcCasemap.instance
164
165   # The ascii Casemap
166   #
167   class AsciiCasemap < Casemap
168     include Singleton
169
170     def initialize
171       super('ascii', "\x41-\x5a", "\x61-\x7a")
172     end
173
174   end
175   AsciiCasemap.instance
176
177
178   # This module is included by all classes that are either bound to a server
179   # or should have a casemap.
180   #
181   module ServerOrCasemap
182
183     attr_reader :server
184
185     # This method initializes the instance variables @server and @casemap
186     # according to the values of the hash keys :server and :casemap in _opts_
187     #
188     def init_server_or_casemap(opts={})
189       @server = opts.fetch(:server, nil)
190       raise TypeError, "#{@server} is not a valid Irc::Server" if @server and not @server.kind_of?(Server)
191
192       @casemap = opts.fetch(:casemap, nil)
193       if @server
194         if @casemap
195           @server.casemap.must_be(@casemap)
196           @casemap = nil
197         end
198       else
199         @casemap = (@casemap || 'rfc1459').to_irc_casemap
200       end
201     end
202
203     # This is an auxiliary method: it returns true if the receiver fits the
204     # server and casemap specified in _opts_, false otherwise.
205     #
206     def fits_with_server_and_casemap?(opts={})
207       srv = opts.fetch(:server, nil)
208       cmap = opts.fetch(:casemap, nil)
209       cmap = cmap.to_irc_casemap unless cmap.nil?
210
211       if srv.nil?
212         return true if cmap.nil? or cmap == casemap
213       else
214         return true if srv == @server and (cmap.nil? or cmap == casemap)
215       end
216       return false
217     end
218
219     # Returns the casemap of the receiver, by looking at the bound
220     # @server (if possible) or at the @casemap otherwise
221     #
222     def casemap
223       return @server.casemap if defined?(@server) and @server
224       return @casemap
225     end
226
227     # Returns a hash with the current @server and @casemap as values of
228     # :server and :casemap
229     #
230     def server_and_casemap
231       h = {}
232       h[:server] = @server if defined?(@server) and @server
233       h[:casemap] = @casemap if defined?(@casemap) and @casemap
234       return h
235     end
236
237     # We allow up/downcasing with a different casemap
238     #
239     def irc_downcase(cmap=casemap)
240       self.to_s.irc_downcase(cmap)
241     end
242
243     # Up/downcasing something that includes this module returns its
244     # Up/downcased to_s form
245     #
246     def downcase
247       self.irc_downcase
248     end
249
250     # We allow up/downcasing with a different casemap
251     #
252     def irc_upcase(cmap=casemap)
253       self.to_s.irc_upcase(cmap)
254     end
255
256     # Up/downcasing something that includes this module returns its
257     # Up/downcased to_s form
258     #
259     def upcase
260       self.irc_upcase
261     end
262
263   end
264
265 end
266
267
268 # We start by extending the String class
269 # with some IRC-specific methods
270 #
271 class String
272
273   # This method returns the Irc::Casemap whose name is the receiver
274   #
275   def to_irc_casemap
276     Irc::Casemap.get(self) rescue raise TypeError, "Unkown Irc::Casemap #{self.inspect}"
277   end
278
279   # This method returns a string which is the downcased version of the
280   # receiver, according to the given _casemap_
281   #
282   #
283   def irc_downcase(casemap='rfc1459')
284     cmap = casemap.to_irc_casemap
285     self.tr(cmap.upper, cmap.lower)
286   end
287
288   # This is the same as the above, except that the string is altered in place
289   #
290   # See also the discussion about irc_downcase
291   #
292   def irc_downcase!(casemap='rfc1459')
293     cmap = casemap.to_irc_casemap
294     self.tr!(cmap.upper, cmap.lower)
295   end
296
297   # Upcasing functions are provided too
298   #
299   # See also the discussion about irc_downcase
300   #
301   def irc_upcase(casemap='rfc1459')
302     cmap = casemap.to_irc_casemap
303     self.tr(cmap.lower, cmap.upper)
304   end
305
306   # In-place upcasing
307   #
308   # See also the discussion about irc_downcase
309   #
310   def irc_upcase!(casemap='rfc1459')
311     cmap = casemap.to_irc_casemap
312     self.tr!(cmap.lower, cmap.upper)
313   end
314
315   # This method checks if the receiver contains IRC glob characters
316   #
317   # IRC has a very primitive concept of globs: a <tt>*</tt> stands for "any
318   # number of arbitrary characters", a <tt>?</tt> stands for "one and exactly
319   # one arbitrary character". These characters can be escaped by prefixing them
320   # with a slash (<tt>\\</tt>).
321   #
322   # A known limitation of this glob syntax is that there is no way to escape
323   # the escape character itself, so it's not possible to build a glob pattern
324   # where the escape character precedes a glob.
325   #
326   def has_irc_glob?
327     self =~ /^[*?]|[^\\][*?]/
328   end
329
330   # This method is used to convert the receiver into a Regular Expression
331   # that matches according to the IRC glob syntax
332   #
333   def to_irc_regexp
334     regmask = Regexp.escape(self)
335     regmask.gsub!(/(\\\\)?\\[*?]/) { |m|
336       case m
337       when /\\(\\[*?])/
338         $1
339       when /\\\*/
340         '.*'
341       when /\\\?/
342         '.'
343       else
344         raise "Unexpected match #{m} when converting #{self}"
345       end
346     }
347     Regexp.new("^#{regmask}$")
348   end
349
350 end
351
352
353 # ArrayOf is a subclass of Array whose elements are supposed to be all
354 # of the same class. This is not intended to be used directly, but rather
355 # to be subclassed as needed (see for example Irc::UserList and Irc::NetmaskList)
356 #
357 # Presently, only very few selected methods from Array are overloaded to check
358 # if the new elements are the correct class. An orthodox? method is provided
359 # to check the entire ArrayOf against the appropriate class.
360 #
361 class ArrayOf < Array
362
363   attr_reader :element_class
364
365   # Create a new ArrayOf whose elements are supposed to be all of type _kl_,
366   # optionally filling it with the elements from the Array argument.
367   #
368   def initialize(kl, ar=[])
369     raise TypeError, "#{kl.inspect} must be a class name" unless kl.kind_of?(Class)
370     super()
371     @element_class = kl
372     case ar
373     when Array
374       insert(0, *ar)
375     else
376       raise TypeError, "#{self.class} can only be initialized from an Array"
377     end
378   end
379
380   def inspect
381     self.__to_s__[0..-2].sub(/:[^:]+$/,"[#{@element_class}]\\0") + " #{super}>"
382   end
383
384   # Private method to check the validity of the elements passed to it
385   # and optionally raise an error
386   #
387   # TODO should it accept nils as valid?
388   #
389   def internal_will_accept?(raising, *els)
390     els.each { |el|
391       unless el.kind_of?(@element_class)
392         raise TypeError, "#{el.inspect} is not of class #{@element_class}" if raising
393         return false
394       end
395     }
396     return true
397   end
398   private :internal_will_accept?
399
400   # This method checks if the passed arguments are acceptable for our ArrayOf
401   #
402   def will_accept?(*els)
403     internal_will_accept?(false, *els)
404   end
405
406   # This method checks that all elements are of the appropriate class
407   #
408   def valid?
409     will_accept?(*self)
410   end
411
412   # This method is similar to the above, except that it raises an exception
413   # if the receiver is not valid
414   #
415   def validate
416     raise TypeError unless valid?
417   end
418
419   # Overloaded from Array#<<, checks for appropriate class of argument
420   #
421   def <<(el)
422     super(el) if internal_will_accept?(true, el)
423   end
424
425   # Overloaded from Array#&, checks for appropriate class of argument elements
426   #
427   def &(ar)
428     r = super(ar)
429     ArrayOf.new(@element_class, r) if internal_will_accept?(true, *r)
430   end
431
432   # Overloaded from Array#+, checks for appropriate class of argument elements
433   #
434   def +(ar)
435     ArrayOf.new(@element_class, super(ar)) if internal_will_accept?(true, *ar)
436   end
437
438   # Overloaded from Array#-, so that an ArrayOf is returned. There is no need
439   # to check the validity of the elements in the argument
440   #
441   def -(ar)
442     ArrayOf.new(@element_class, super(ar)) # if internal_will_accept?(true, *ar)
443   end
444
445   # Overloaded from Array#|, checks for appropriate class of argument elements
446   #
447   def |(ar)
448     ArrayOf.new(@element_class, super(ar)) if internal_will_accept?(true, *ar)
449   end
450
451   # Overloaded from Array#concat, checks for appropriate class of argument
452   # elements
453   #
454   def concat(ar)
455     super(ar) if internal_will_accept?(true, *ar)
456   end
457
458   # Overloaded from Array#insert, checks for appropriate class of argument
459   # elements
460   #
461   def insert(idx, *ar)
462     super(idx, *ar) if internal_will_accept?(true, *ar)
463   end
464
465   # Overloaded from Array#replace, checks for appropriate class of argument
466   # elements
467   #
468   def replace(ar)
469     super(ar) if (ar.kind_of?(ArrayOf) && ar.element_class <= @element_class) or internal_will_accept?(true, *ar)
470   end
471
472   # Overloaded from Array#push, checks for appropriate class of argument
473   # elements
474   #
475   def push(*ar)
476     super(*ar) if internal_will_accept?(true, *ar)
477   end
478
479   # Overloaded from Array#unshift, checks for appropriate class of argument(s)
480   #
481   def unshift(*els)
482     els.each { |el|
483       super(el) if internal_will_accept?(true, *els)
484     }
485   end
486
487   # We introduce the 'downcase' method, which maps downcase() to all the Array
488   # elements, properly failing when the elements don't have a downcase method
489   #
490   def downcase
491     self.map { |el| el.downcase }
492   end
493
494   # Modifying methods which we don't handle yet are made private
495   #
496   private :[]=, :collect!, :map!, :fill, :flatten!
497
498 end
499
500
501 # We extend the Regexp class with an Irc module which will contain some
502 # Irc-specific regexps
503 #
504 class Regexp
505
506   # We start with some general-purpose ones which will be used in the
507   # Irc module too, but are useful regardless
508   DIGITS = /\d+/
509   HEX_DIGIT = /[0-9A-Fa-f]/
510   HEX_DIGITS = /#{HEX_DIGIT}+/
511   HEX_OCTET = /#{HEX_DIGIT}#{HEX_DIGIT}?/
512   DEC_OCTET = /[01]?\d?\d|2[0-4]\d|25[0-5]/
513   DEC_IP_ADDR = /#{DEC_OCTET}\.#{DEC_OCTET}\.#{DEC_OCTET}\.#{DEC_OCTET}/
514   HEX_IP_ADDR = /#{HEX_OCTET}\.#{HEX_OCTET}\.#{HEX_OCTET}\.#{HEX_OCTET}/
515   IP_ADDR = /#{DEC_IP_ADDR}|#{HEX_IP_ADDR}/
516
517   # IPv6, from Resolv::IPv6, without the \A..\z anchors
518   HEX_16BIT = /#{HEX_DIGIT}{1,4}/
519   IP6_8Hex = /(?:#{HEX_16BIT}:){7}#{HEX_16BIT}/
520   IP6_CompressedHex = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)/
521   IP6_6Hex4Dec = /((?:#{HEX_16BIT}:){6,6})#{DEC_IP_ADDR}/
522   IP6_CompressedHex4Dec = /((?:#{HEX_16BIT}(?::#{HEX_16BIT})*)?)::((?:#{HEX_16BIT}:)*)#{DEC_IP_ADDR}/
523   IP6_ADDR = /(?:#{IP6_8Hex})|(?:#{IP6_CompressedHex})|(?:#{IP6_6Hex4Dec})|(?:#{IP6_CompressedHex4Dec})/
524
525   # We start with some IRC related regular expressions, used to match
526   # Irc::User nicks and users and Irc::Channel names
527   #
528   # For each of them we define two versions of the regular expression:
529   # * a generic one, which should match for any server but may turn out to
530   #   match more than a specific server would accept
531   # * an RFC-compliant matcher
532   #
533   module Irc
534
535     # Channel-name-matching regexps
536     CHAN_FIRST = /[#&+]/
537     CHAN_SAFE = /![A-Z0-9]{5}/
538     CHAN_ANY = /[^\x00\x07\x0A\x0D ,:]/
539     GEN_CHAN = /(?:#{CHAN_FIRST}|#{CHAN_SAFE})#{CHAN_ANY}+/
540     RFC_CHAN = /#{CHAN_FIRST}#{CHAN_ANY}{1,49}|#{CHAN_SAFE}#{CHAN_ANY}{1,44}/
541
542     # Nick-matching regexps
543     SPECIAL_CHAR = /[\x5b-\x60\x7b-\x7d]/
544     NICK_FIRST = /#{SPECIAL_CHAR}|[[:alpha:]]/
545     NICK_ANY = /#{SPECIAL_CHAR}|[[:alnum:]]|-/
546     GEN_NICK = /#{NICK_FIRST}#{NICK_ANY}+/
547     RFC_NICK = /#{NICK_FIRST}#{NICK_ANY}{0,8}/
548
549     USER_CHAR = /[^\x00\x0a\x0d @]/
550     GEN_USER = /#{USER_CHAR}+/
551
552     # Host-matching regexps
553     HOSTNAME_COMPONENT = /[[:alnum:]](?:[[:alnum:]]|-)*[[:alnum:]]*/
554     HOSTNAME = /#{HOSTNAME_COMPONENT}(?:\.#{HOSTNAME_COMPONENT})*/
555     HOSTADDR = /#{IP_ADDR}|#{IP6_ADDR}/
556
557     GEN_HOST = /#{HOSTNAME}|#{HOSTADDR}/
558
559     # # FreeNode network replaces the host of affiliated users with
560     # # 'virtual hosts'
561     # # FIXME we need the true syntax to match it properly ...
562     # PDPC_HOST_PART = /[0-9A-Za-z.-]+/
563     # PDPC_HOST = /#{PDPC_HOST_PART}(?:\/#{PDPC_HOST_PART})+/
564
565     # # NOTE: the final optional and non-greedy dot is needed because some
566     # # servers (e.g. FreeNode) send the hostname of the services as "services."
567     # # which is not RFC compliant, but sadly done.
568     # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/
569
570     # Sadly, different networks have different, RFC-breaking ways of cloaking
571     # the actualy host address: see above for an example to handle FreeNode.
572     # Another example would be Azzurra, wich also inserts a "=" in the
573     # cloacked host. So let's just not care about this and go with the simplest
574     # thing:
575     GEN_HOST_EXT = /\S+/
576
577     # User-matching Regexp
578     GEN_USER_ID = /(#{GEN_NICK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/
579
580     # Things such has the BIP proxy send invalid nicks in a complete netmask,
581     # so we want to match this, rather: this matches either a compliant nick
582     # or a a string with a very generic nick, a very generic hostname after an
583     # @ sign, and an optional user after a !
584     BANG_AT = /#{GEN_NICK}|\S+?(?:!\S+?)?@\S+?/
585
586     # # For Netmask, we want to allow wildcards * and ? in the nick
587     # # (they are already allowed in the user and host part
588     # GEN_NICK_MASK = /(?:#{NICK_FIRST}|[?*])?(?:#{NICK_ANY}|[?*])+/
589
590     # # Netmask-matching Regexp
591     # GEN_MASK = /(#{GEN_NICK_MASK})(?:(?:!(#{GEN_USER}))?@(#{GEN_HOST_EXT}))?/
592
593   end
594
595 end
596
597
598 module Irc
599
600
601   # A Netmask identifies each user by collecting its nick, username and
602   # hostname in the form <tt>nick!user@host</tt>
603   #
604   # Netmasks can also contain glob patterns in any of their components; in
605   # this form they are used to refer to more than a user or to a user
606   # appearing under different forms.
607   #
608   # Example:
609   # * <tt>*!*@*</tt> refers to everybody
610   # * <tt>*!someuser@somehost</tt> refers to user +someuser+ on host +somehost+
611   #   regardless of the nick used.
612   #
613   class Netmask
614
615     # Netmasks have an associated casemap unless they are bound to a server
616     #
617     include ServerOrCasemap
618
619     attr_reader :nick, :user, :host
620     alias :ident :user
621
622     # Create a new Netmask from string _str_, which must be in the form
623     # _nick_!_user_@_host_
624     #
625     # It is possible to specify a server or a casemap in the optional Hash:
626     # these are used to associate the Netmask with the given server and to set
627     # its casemap: if a server is specified and a casemap is not, the server's
628     # casemap is used. If both a server and a casemap are specified, the
629     # casemap must match the server's casemap or an exception will be raised.
630     #
631     # Empty +nick+, +user+ or +host+ are converted to the generic glob pattern
632     #
633     def initialize(str="", opts={})
634       # First of all, check for server/casemap option
635       #
636       init_server_or_casemap(opts)
637
638       # Now we can see if the given string _str_ is an actual Netmask
639       if str.respond_to?(:to_str)
640         case str.to_str
641           # We match a pretty generic string, to work around non-compliant
642           # servers
643         when /^(?:(\S+?)(?:(?:!(\S+?))?@(\S+))?)?$/
644           # We do assignment using our internal methods
645           self.nick = $1
646           self.user = $2
647           self.host = $3
648         else
649           raise ArgumentError, "#{str.to_str.inspect} does not represent a valid #{self.class}"
650         end
651       else
652         raise TypeError, "#{str} cannot be converted to a #{self.class}"
653       end
654     end
655
656     # A Netmask is easily converted to a String for the usual representation.
657     # We skip the user or host parts if they are "*", unless we've been asked
658     # for the full form
659     #
660     def to_s
661       ret = nick.dup
662       ret << "!" << user unless user == "*"
663       ret << "@" << host unless host == "*"
664       return ret
665     end
666
667     def fullform
668       "#{nick}!#{user}@#{host}"
669     end
670
671     alias :to_str :fullform
672
673     # This method downcases the fullform of the netmask. While this may not be
674     # significantly different from the #downcase() method provided by the
675     # ServerOrCasemap mixin, it's significantly different for Netmask
676     # subclasses such as User whose simple downcasing uses the nick only.
677     #
678     def full_irc_downcase(cmap=casemap)
679       self.fullform.irc_downcase(cmap)
680     end
681
682     # full_downcase() will return the fullform downcased according to the
683     # User's own casemap
684     #
685     def full_downcase
686       self.full_irc_downcase
687     end
688
689     # This method returns a new Netmask which is the fully downcased version
690     # of the receiver
691     def downcased
692       return self.full_downcase.to_irc_netmask(server_and_casemap)
693     end
694
695     # Converts the receiver into a Netmask with the given (optional)
696     # server/casemap association. We return self unless a conversion
697     # is needed (different casemap/server)
698     #
699     # Subclasses of Netmask will return a new Netmask, using full_downcase
700     #
701     def to_irc_netmask(opts={})
702       if self.class == Netmask
703         return self if fits_with_server_and_casemap?(opts)
704       end
705       return self.full_downcase.to_irc_netmask(server_and_casemap.merge(opts))
706     end
707
708     # Converts the receiver into a User with the given (optional)
709     # server/casemap association. We return self unless a conversion
710     # is needed (different casemap/server)
711     #
712     def to_irc_user(opts={})
713       self.fullform.to_irc_user(server_and_casemap.merge(opts))
714     end
715
716     # Inspection of a Netmask reveals the server it's bound to (if there is
717     # one), its casemap and the nick, user and host part
718     #
719     def inspect
720       str = self.__to_s__[0..-2]
721       str << " @server=#{@server}" if defined?(@server) and @server
722       str << " @nick=#{@nick.inspect} @user=#{@user.inspect}"
723       str << " @host=#{@host.inspect} casemap=#{casemap.inspect}"
724       str << ">"
725     end
726
727     # Equality: two Netmasks are equal if they downcase to the same thing
728     #
729     # TODO we may want it to try other.to_irc_netmask
730     #
731     def ==(other)
732       return false unless other.kind_of?(self.class)
733       self.downcase == other.downcase
734     end
735
736     # This method changes the nick of the Netmask, defaulting to the generic
737     # glob pattern if the result is the null string.
738     #
739     def nick=(newnick)
740       @nick = newnick.to_s
741       @nick = "*" if @nick.empty?
742     end
743
744     # This method changes the user of the Netmask, defaulting to the generic
745     # glob pattern if the result is the null string.
746     #
747     def user=(newuser)
748       @user = newuser.to_s
749       @user = "*" if @user.empty?
750     end
751     alias :ident= :user=
752
753     # This method changes the hostname of the Netmask, defaulting to the generic
754     # glob pattern if the result is the null string.
755     #
756     def host=(newhost)
757       @host = newhost.to_s
758       @host = "*" if @host.empty?
759     end
760
761     # We can replace everything at once with data from another Netmask
762     #
763     def replace(other)
764       case other
765       when Netmask
766         nick = other.nick
767         user = other.user
768         host = other.host
769         @server = other.server
770         @casemap = other.casemap unless @server
771       else
772         replace(other.to_irc_netmask(server_and_casemap))
773       end
774     end
775
776     # This method checks if a Netmask is definite or not, by seeing if
777     # any of its components are defined by globs
778     #
779     def has_irc_glob?
780       return @nick.has_irc_glob? || @user.has_irc_glob? || @host.has_irc_glob?
781     end
782
783     def generalize
784       u = user.dup
785       unless u.has_irc_glob?
786         u.sub!(/^[in]=/, '=') or u.sub!(/^\W(\w+)/, '\1')
787         u = '*' + u
788       end
789
790       h = host.dup
791       unless h.has_irc_glob?
792         if h.include? '/'
793           h.sub!(/x-\w+$/, 'x-*')
794         else
795           h.match(/^[^\.]+\.[^\.]+$/) or
796           h.sub!(/azzurra[=-][0-9a-f]+/i, '*') or # hello, azzurra, you suck!
797           h.sub!(/^(\d+\.\d+\.\d+\.)\d+$/, '\1*') or
798           h.sub!(/^[^\.]+\./, '*.')
799         end
800       end
801       return Netmask.new("*!#{u}@#{h}", server_and_casemap)
802     end
803
804     # This method is used to match the current Netmask against another one
805     #
806     # The method returns true if each component of the receiver matches the
807     # corresponding component of the argument. By _matching_ here we mean
808     # that any netmask described by the receiver is also described by the
809     # argument.
810     #
811     # In this sense, matching is rather simple to define in the case when the
812     # receiver has no globs: it is just necessary to check if the argument
813     # describes the receiver, which can be done by matching it against the
814     # argument converted into an IRC Regexp (see String#to_irc_regexp).
815     #
816     # The situation is also easy when the receiver has globs and the argument
817     # doesn't, since in this case the result is false.
818     #
819     # The more complex case in which both the receiver and the argument have
820     # globs is not handled yet.
821     #
822     def matches?(arg)
823       cmp = arg.to_irc_netmask(:casemap => casemap)
824       debug "Matching #{self.fullform} against #{arg.inspect} (#{cmp.fullform})"
825       [:nick, :user, :host].each { |component|
826         us = self.send(component).irc_downcase(casemap)
827         them = cmp.send(component).irc_downcase(casemap)
828         if us.has_irc_glob? && them.has_irc_glob?
829           next if us == them
830           warn NotImplementedError
831           return false
832         end
833         return false if us.has_irc_glob? && !them.has_irc_glob?
834         return false unless us =~ them.to_irc_regexp
835       }
836       return true
837     end
838
839     # Case equality. Checks if arg matches self
840     #
841     def ===(arg)
842       arg.to_irc_netmask(:casemap => casemap).matches?(self)
843     end
844
845     # Sorting is done via the fullform
846     #
847     def <=>(arg)
848       case arg
849       when Netmask
850         self.fullform.irc_downcase(casemap) <=> arg.fullform.irc_downcase(casemap)
851       else
852         self.downcase <=> arg.downcase
853       end
854     end
855
856   end
857
858
859   # A NetmaskList is an ArrayOf <code>Netmask</code>s
860   #
861   class NetmaskList < ArrayOf
862
863     # Create a new NetmaskList, optionally filling it with the elements from
864     # the Array argument fed to it.
865     #
866     def initialize(ar=[])
867       super(Netmask, ar)
868     end
869
870     # We enhance the [] method by allowing it to pick an element that matches
871     # a given Netmask, a String or a Regexp
872     # TODO take into consideration the opportunity to use select() instead of
873     # find(), and/or a way to let the user choose which one to take (second
874     # argument?)
875     #
876     def [](*args)
877       if args.length == 1
878         case args[0]
879         when Netmask
880           self.find { |mask|
881             mask.matches?(args[0])
882           }
883         when String
884           self.find { |mask|
885             mask.matches?(args[0].to_irc_netmask(:casemap => mask.casemap))
886           }
887         when Regexp
888           self.find { |mask|
889             mask.fullform =~ args[0]
890           }
891         else
892           super(*args)
893         end
894       else
895         super(*args)
896       end
897     end
898
899   end
900
901 end
902
903
904 class String
905
906   # We keep extending String, this time adding a method that converts a
907   # String into an Irc::Netmask object
908   #
909   def to_irc_netmask(opts={})
910     Irc::Netmask.new(self, opts)
911   end
912
913 end
914
915
916 module Irc
917
918
919   # An IRC User is identified by his/her Netmask (which must not have globs).
920   # In fact, User is just a subclass of Netmask.
921   #
922   # Ideally, the user and host information of an IRC User should never
923   # change, and it shouldn't contain glob patterns. However, IRC is somewhat
924   # idiosincratic and it may be possible to know the nick of a User much before
925   # its user and host are known. Moreover, some networks (namely Freenode) may
926   # change the hostname of a User when (s)he identifies with Nickserv.
927   #
928   # As a consequence, we must allow changes to a User host and user attributes.
929   # We impose a restriction, though: they may not contain glob patterns, except
930   # for the special case of an unknown user/host which is represented by a *.
931   #
932   # It is possible to create a totally unknown User (e.g. for initializations)
933   # by setting the nick to * too.
934   #
935   # TODO list:
936   # * see if it's worth to add the other USER data
937   # * see if it's worth to add NICKSERV status
938   #
939   class User < Netmask
940     alias :to_s :nick
941
942     attr_accessor :real_name, :idle_since, :signon
943
944     # Create a new IRC User from a given Netmask (or anything that can be converted
945     # into a Netmask) provided that the given Netmask does not have globs.
946     #
947     def initialize(str="", opts={})
948       super
949       raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if nick.has_irc_glob? && nick != "*"
950       raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if user.has_irc_glob? && user != "*"
951       raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if host.has_irc_glob? && host != "*"
952       @away = false
953       @real_name = String.new
954       @idle_since = nil
955       @signon = nil
956     end
957
958     # The nick of a User may be changed freely, but it must not contain glob patterns.
959     #
960     def nick=(newnick)
961       raise "Can't change the nick to #{newnick}" if defined?(@nick) and newnick.has_irc_glob?
962       super
963     end
964
965     # We have to allow changing the user of an Irc User due to some networks
966     # (e.g. Freenode) changing hostmasks on the fly. We still check if the new
967     # user data has glob patterns though.
968     #
969     def user=(newuser)
970       raise "Can't change the username to #{newuser}" if defined?(@user) and newuser.has_irc_glob?
971       super
972     end
973
974     # We have to allow changing the host of an Irc User due to some networks
975     # (e.g. Freenode) changing hostmasks on the fly. We still check if the new
976     # host data has glob patterns though.
977     #
978     def host=(newhost)
979       raise "Can't change the hostname to #{newhost}" if defined?(@host) and newhost.has_irc_glob?
980       super
981     end
982
983     # Checks if a User is well-known or not by looking at the hostname and user
984     #
985     def known?
986       return nick != "*" && user != "*" && host != "*"
987     end
988
989     # Is the user away?
990     #
991     def away?
992       return @away
993     end
994
995     # Set the away status of the user. Use away=(nil) or away=(false)
996     # to unset away
997     #
998     def away=(msg="")
999       if msg
1000         @away = msg
1001       else
1002         @away = false
1003       end
1004     end
1005
1006     # Since to_irc_user runs the same checks on server and channel as
1007     # to_irc_netmask, we just try that and return self if it works.
1008     #
1009     # Subclasses of User will return self if possible.
1010     #
1011     def to_irc_user(opts={})
1012       return self if fits_with_server_and_casemap?(opts)
1013       return self.full_downcase.to_irc_user(opts)
1014     end
1015
1016     # We can replace everything at once with data from another User
1017     #
1018     def replace(other)
1019       case other
1020       when User
1021         self.nick = other.nick
1022         self.user = other.user
1023         self.host = other.host
1024         @server = other.server
1025         @casemap = other.casemap unless @server
1026         @away = other.away?
1027       else
1028         self.replace(other.to_irc_user(server_and_casemap))
1029       end
1030     end
1031
1032     def modes_on(channel)
1033       case channel
1034       when Channel
1035         channel.modes_of(self)
1036       else
1037         return @server.channel(channel).modes_of(self) if @server
1038         raise "Can't resolve channel #{channel}"
1039       end
1040     end
1041
1042     def is_op?(channel)
1043       case channel
1044       when Channel
1045         channel.has_op?(self)
1046       else
1047         return @server.channel(channel).has_op?(self) if @server
1048         raise "Can't resolve channel #{channel}"
1049       end
1050     end
1051
1052     def is_voice?(channel)
1053       case channel
1054       when Channel
1055         channel.has_voice?(self)
1056       else
1057         return @server.channel(channel).has_voice?(self) if @server
1058         raise "Can't resolve channel #{channel}"
1059       end
1060     end
1061
1062     def channels
1063       if @server
1064         @server.channels.select { |ch| ch.has_user?(self) }
1065       else
1066         Array.new
1067       end
1068     end
1069   end
1070
1071
1072   # A UserList is an ArrayOf <code>User</code>s
1073   # We derive it from NetmaskList, which allows us to inherit any special
1074   # NetmaskList method
1075   #
1076   class UserList < NetmaskList
1077
1078     # Create a new UserList, optionally filling it with the elements from
1079     # the Array argument fed to it.
1080     #
1081     def initialize(ar=[])
1082       super(ar)
1083       @element_class = User
1084     end
1085
1086     # Convenience method: convert the UserList to a list of nicks. The indices
1087     # are preserved
1088     #
1089     def nicks
1090       self.map { |user| user.nick }
1091     end
1092
1093   end
1094
1095 end
1096
1097 class String
1098
1099   # We keep extending String, this time adding a method that converts a
1100   # String into an Irc::User object
1101   #
1102   def to_irc_user(opts={})
1103     Irc::User.new(self, opts)
1104   end
1105
1106 end
1107
1108 module Irc
1109
1110   # An IRC Channel is identified by its name, and it has a set of properties:
1111   # * a Channel::Topic
1112   # * a UserList
1113   # * a set of Channel::Modes
1114   #
1115   # The Channel::Topic and Channel::Mode classes are defined within the
1116   # Channel namespace because they only make sense there
1117   #
1118   class Channel
1119
1120
1121     # Mode on a Channel
1122     #
1123     class Mode
1124       attr_reader :channel
1125       def initialize(ch)
1126         @channel = ch
1127       end
1128
1129     end
1130
1131     # Hash of modes. Subclass of Hash that defines any? and all?
1132     # to check if boolean modes (Type D) are set
1133     class ModeHash < Hash
1134       def any?(*ar)
1135         !!ar.find { |m| s = m.to_sym ; self[s] && self[s].set? }
1136       end
1137       def all?(*ar)
1138         !ar.find { |m| s = m.to_sym ; !(self[s] && self[s].set?) }
1139       end
1140     end
1141
1142     # Channel modes of type A manipulate lists
1143     #
1144     # Example: b (banlist)
1145     #
1146     class ModeTypeA < Mode
1147       attr_reader :list
1148       def initialize(ch)
1149         super
1150         @list = NetmaskList.new
1151       end
1152
1153       def set(val)
1154         nm = @channel.server.new_netmask(val)
1155         @list << nm unless @list.include?(nm)
1156       end
1157
1158       def reset(val)
1159         nm = @channel.server.new_netmask(val)
1160         @list.delete(nm)
1161       end
1162
1163     end
1164
1165
1166     # Channel modes of type B need an argument
1167     #
1168     # Example: k (key)
1169     #
1170     class ModeTypeB < Mode
1171       def initialize(ch)
1172         super
1173         @arg = nil
1174       end
1175
1176       def status
1177         @arg
1178       end
1179       alias :value :status
1180
1181       def set(val)
1182         @arg = val
1183       end
1184
1185       def reset(val)
1186         @arg = nil if @arg == val
1187       end
1188
1189     end
1190
1191
1192     # Channel modes that change the User prefixes are like
1193     # Channel modes of type B, except that they manipulate
1194     # lists of Users, so they are somewhat similar to channel
1195     # modes of type A
1196     #
1197     class UserMode < ModeTypeB
1198       attr_reader :list
1199       alias :users :list
1200       def initialize(ch)
1201         super
1202         @list = UserList.new
1203       end
1204
1205       def set(val)
1206         u = @channel.server.user(val)
1207         @list << u unless @list.include?(u)
1208       end
1209
1210       def reset(val)
1211         u = @channel.server.user(val)
1212         @list.delete(u)
1213       end
1214
1215     end
1216
1217
1218     # Channel modes of type C need an argument when set,
1219     # but not when they get reset
1220     #
1221     # Example: l (limit)
1222     #
1223     class ModeTypeC < Mode
1224       def initialize(ch)
1225         super
1226         @arg = nil
1227       end
1228
1229       def status
1230         @arg
1231       end
1232       alias :value :status
1233
1234       def set(val)
1235         @arg = val
1236       end
1237
1238       def reset
1239         @arg = nil
1240       end
1241
1242     end
1243
1244
1245     # Channel modes of type D are basically booleans
1246     #
1247     # Example: m (moderate)
1248     #
1249     class ModeTypeD < Mode
1250       def initialize(ch)
1251         super
1252         @set = false
1253       end
1254
1255       def set?
1256         return @set
1257       end
1258
1259       def set
1260         @set = true
1261       end
1262
1263       def reset
1264         @set = false
1265       end
1266
1267     end
1268
1269
1270     # A Topic represents the topic of a channel. It consists of
1271     # the topic itself, who set it and when
1272     #
1273     class Topic
1274       attr_accessor :text, :set_by, :set_on
1275       alias :to_s :text
1276
1277       # Create a new Topic setting the text, the creator and
1278       # the creation time
1279       #
1280       def initialize(text="", set_by="", set_on=Time.new)
1281         @text = text
1282         @set_by = set_by.to_irc_netmask
1283         @set_on = set_on
1284       end
1285
1286       # Replace a Topic with another one
1287       #
1288       def replace(topic)
1289         raise TypeError, "#{topic.inspect} is not of class #{self.class}" unless topic.kind_of?(self.class)
1290         @text = topic.text.dup
1291         @set_by = topic.set_by.dup
1292         @set_on = topic.set_on.dup
1293       end
1294
1295       # Returns self
1296       #
1297       def to_irc_channel_topic
1298         self
1299       end
1300
1301     end
1302
1303   end
1304
1305 end
1306
1307
1308 class String
1309
1310   # Returns an Irc::Channel::Topic with self as text
1311   #
1312   def to_irc_channel_topic
1313     Irc::Channel::Topic.new(self)
1314   end
1315
1316 end
1317
1318
1319 module Irc
1320
1321
1322   # Here we start with the actual Channel class
1323   #
1324   class Channel
1325
1326     # Return the non-prefixed part of a channel name.
1327     # Also works with ## channels found on some networks
1328     # (e.g. FreeNode)
1329     def self.npname(str)
1330       return str.to_s.sub(/^[&#+!]+/,'')
1331     end
1332
1333     include ServerOrCasemap
1334     attr_reader :name, :topic, :mode, :users
1335     alias :to_s :name
1336     attr_accessor :creation_time, :url
1337
1338     def inspect
1339       str = self.__to_s__[0..-2]
1340       str << " on server #{server}" if server
1341       str << " @name=#{@name.inspect} @topic=#{@topic.text.inspect}"
1342       str << " @users=[#{user_nicks.sort.join(', ')}]"
1343       str << " (created on #{creation_time})" if creation_time
1344       str << " (URL #{url})" if url
1345       str << ">"
1346     end
1347
1348     # Returns self
1349     #
1350     def to_irc_channel
1351       self
1352     end
1353
1354     # TODO Ho
1355     def user_nicks
1356       @users.map { |u| u.downcase }
1357     end
1358
1359     # Checks if the receiver already has a user with the given _nick_
1360     #
1361     def has_user?(nick)
1362       @users.index(nick.to_irc_user(server_and_casemap))
1363     end
1364
1365     # Returns the user with nick _nick_, if available
1366     #
1367     def get_user(nick)
1368       idx = has_user?(nick)
1369       @users[idx] if idx
1370     end
1371
1372     # Adds a user to the channel
1373     #
1374     def add_user(user, opts={})
1375       silent = opts.fetch(:silent, false)
1376       if has_user?(user)
1377         warn "Trying to add user #{user} to channel #{self} again" unless silent
1378       else
1379         @users << user.to_irc_user(server_and_casemap)
1380       end
1381     end
1382
1383     # Creates a new channel with the given name, optionally setting the topic
1384     # and an initial users list.
1385     #
1386     # No additional info is created here, because the channel flags and userlists
1387     # allowed depend on the server.
1388     #
1389     def initialize(name, topic=nil, users=[], opts={})
1390       raise ArgumentError, "Channel name cannot be empty" if name.to_s.empty?
1391       warn "Unknown channel prefix #{name[0,1]}" if name !~ /^[&#+!]/
1392       raise ArgumentError, "Invalid character in #{name.inspect}" if name =~ /[ \x07,]/
1393
1394       init_server_or_casemap(opts)
1395
1396       @name = name
1397
1398       @topic = topic ? topic.to_irc_channel_topic : Channel::Topic.new
1399
1400       @users = UserList.new
1401
1402       users.each { |u|
1403         add_user(u)
1404       }
1405
1406       # Flags
1407       @mode = ModeHash.new
1408
1409       # creation time, only on some networks
1410       @creation_time = nil
1411
1412       # URL, only on some networks
1413       @url = nil
1414     end
1415
1416     # Removes a user from the channel
1417     #
1418     def delete_user(user)
1419       @mode.each { |sym, mode|
1420         mode.reset(user) if mode.kind_of?(UserMode)
1421       }
1422       @users.delete(user)
1423     end
1424
1425     # The channel prefix
1426     #
1427     def prefix
1428       name[0,1]
1429     end
1430
1431     # A channel is local to a server if it has the '&' prefix
1432     #
1433     def local?
1434       name[0,1] == '&'
1435     end
1436
1437     # A channel is modeless if it has the '+' prefix
1438     #
1439     def modeless?
1440       name[0,1] == '+'
1441     end
1442
1443     # A channel is safe if it has the '!' prefix
1444     #
1445     def safe?
1446       name[0,1] == '!'
1447     end
1448
1449     # A channel is normal if it has the '#' prefix
1450     #
1451     def normal?
1452       name[0,1] == '#'
1453     end
1454
1455     # Create a new mode
1456     #
1457     def create_mode(sym, kl)
1458       @mode[sym.to_sym] = kl.new(self)
1459     end
1460
1461     def modes_of(user)
1462       l = []
1463       @mode.map { |s, m|
1464         l << s if (m.class <= UserMode and m.list[user])
1465       }
1466       l
1467     end
1468
1469     def has_op?(user)
1470       @mode.has_key?(:o) and @mode[:o].list[user]
1471     end
1472
1473     def has_voice?(user)
1474       @mode.has_key?(:v) and @mode[:v].list[user]
1475     end
1476   end
1477
1478
1479   # A ChannelList is an ArrayOf <code>Channel</code>s
1480   #
1481   class ChannelList < ArrayOf
1482
1483     # Create a new ChannelList, optionally filling it with the elements from
1484     # the Array argument fed to it.
1485     #
1486     def initialize(ar=[])
1487       super(Channel, ar)
1488     end
1489
1490     # Convenience method: convert the ChannelList to a list of channel names.
1491     # The indices are preserved
1492     #
1493     def names
1494       self.map { |chan| chan.name }
1495     end
1496
1497   end
1498
1499 end
1500
1501
1502 class String
1503
1504   # We keep extending String, this time adding a method that converts a
1505   # String into an Irc::Channel object
1506   #
1507   def to_irc_channel(opts={})
1508     Irc::Channel.new(self, opts)
1509   end
1510
1511 end
1512
1513
1514 module Irc
1515
1516
1517   # An IRC Server represents the Server the client is connected to.
1518   #
1519   class Server
1520
1521     attr_reader :hostname, :version, :usermodes, :chanmodes
1522     alias :to_s :hostname
1523     attr_reader :supports, :capabilities
1524
1525     attr_reader :channels, :users
1526
1527     # TODO Ho
1528     def channel_names
1529       @channels.map { |ch| ch.downcase }
1530     end
1531
1532     # TODO Ho
1533     def user_nicks
1534       @users.map { |u| u.downcase }
1535     end
1536
1537     def inspect
1538       chans, users = [@channels, @users].map {|d|
1539         d.sort { |a, b|
1540           a.downcase <=> b.downcase
1541         }.map { |x|
1542           x.inspect
1543         }
1544       }
1545
1546       str = self.__to_s__[0..-2]
1547       str << " @hostname=#{hostname}"
1548       str << " @channels=#{chans}"
1549       str << " @users=#{users}"
1550       str << ">"
1551     end
1552
1553     # Create a new Server, with all instance variables reset to nil (for
1554     # scalar variables), empty channel and user lists and @supports
1555     # initialized to the default values for all known supported features.
1556     #
1557     def initialize
1558       @hostname = @version = @usermodes = @chanmodes = nil
1559
1560       @channels = ChannelList.new
1561
1562       @users = UserList.new
1563
1564       reset_capabilities
1565     end
1566
1567     # Resets the server capabilities
1568     #
1569     def reset_capabilities
1570       @supports = {
1571         :casemapping => 'rfc1459'.to_irc_casemap,
1572         :chanlimit => {},
1573         :chanmodes => {
1574           :typea => nil, # Type A: address lists
1575           :typeb => nil, # Type B: needs a parameter
1576           :typec => nil, # Type C: needs a parameter when set
1577           :typed => nil  # Type D: must not have a parameter
1578         },
1579         :channellen => 50,
1580         :chantypes => "#&!+",
1581         :excepts => nil,
1582         :idchan => {},
1583         :invex => nil,
1584         :kicklen => nil,
1585         :maxlist => {},
1586         :modes => 3,
1587         :network => nil,
1588         :nicklen => 9,
1589         :prefix => {
1590           :modes => [:o, :v],
1591           :prefixes => [:"@", :+]
1592         },
1593         :safelist => nil,
1594         :statusmsg => nil,
1595         :std => nil,
1596         :targmax => {},
1597         :topiclen => nil
1598       }
1599       @capabilities = {}
1600     end
1601
1602     # Convert a mode (o, v, h, ...) to the corresponding
1603     # prefix (@, +, %, ...). See also mode_for_prefix
1604     def prefix_for_mode(mode)
1605       return @supports[:prefix][:prefixes][
1606         @supports[:prefix][:modes].index(mode.to_sym)
1607       ]
1608     end
1609
1610     # Convert a prefix (@, +, %, ...) to the corresponding
1611     # mode (o, v, h, ...). See also prefix_for_mode
1612     def mode_for_prefix(pfx)
1613       return @supports[:prefix][:modes][
1614         @supports[:prefix][:prefixes].index(pfx.to_sym)
1615       ]
1616     end
1617
1618     # Resets the Channel and User list
1619     #
1620     def reset_lists
1621       @users.reverse_each { |u|
1622         delete_user(u)
1623       }
1624       @channels.reverse_each { |u|
1625         delete_channel(u)
1626       }
1627     end
1628
1629     # Clears the server
1630     #
1631     def clear
1632       reset_lists
1633       reset_capabilities
1634       @hostname = @version = @usermodes = @chanmodes = nil
1635     end
1636
1637     # This method is used to parse a 004 RPL_MY_INFO line
1638     #
1639     def parse_my_info(line)
1640       ar = line.split(' ')
1641       @hostname = ar[0]
1642       @version = ar[1]
1643       @usermodes = ar[2]
1644       @chanmodes = ar[3]
1645     end
1646
1647     def noval_warn(key, val, &block)
1648       if val
1649         yield if block_given?
1650       else
1651         warn "No #{key.to_s.upcase} value"
1652       end
1653     end
1654
1655     def val_warn(key, val, &block)
1656       if val == true or val == false or val.nil?
1657         yield if block_given?
1658       else
1659         warn "No #{key.to_s.upcase} value must be specified, got #{val}"
1660       end
1661     end
1662     private :noval_warn, :val_warn
1663
1664     # This method is used to parse a 005 RPL_ISUPPORT line
1665     #
1666     # See the RPL_ISUPPORT draft[http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt]
1667     #
1668     def parse_isupport(line)
1669       debug "Parsing ISUPPORT #{line.inspect}"
1670       ar = line.split(' ')
1671       reparse = []
1672       ar.each { |en|
1673         prekey, val = en.split('=', 2)
1674         if prekey =~ /^-(.*)/
1675           key = $1.downcase.to_sym
1676           val = false
1677         else
1678           key = prekey.downcase.to_sym
1679         end
1680         case key
1681         when :casemapping
1682           noval_warn(key, val) {
1683             if val == 'charset'
1684               reparse << "CASEMAPPING=(charset)"
1685             else
1686               @supports[key] = val.to_irc_casemap
1687             end
1688           }
1689         when :chanlimit, :idchan, :maxlist, :targmax
1690           noval_warn(key, val) {
1691             groups = val.split(',')
1692             groups.each { |g|
1693               k, v = g.split(':')
1694               @supports[key][k] = v.to_i || 0
1695               if @supports[key][k] == 0
1696                 warn "Deleting #{key} limit of 0 for #{k}"
1697                 @supports[key].delete(k)
1698               end
1699             }
1700           }
1701         when :chanmodes
1702           noval_warn(key, val) {
1703             groups = val.split(',')
1704             @supports[key][:typea] = groups[0].scan(/./).map { |x| x.to_sym}
1705             @supports[key][:typeb] = groups[1].scan(/./).map { |x| x.to_sym}
1706             @supports[key][:typec] = groups[2].scan(/./).map { |x| x.to_sym}
1707             @supports[key][:typed] = groups[3].scan(/./).map { |x| x.to_sym}
1708           }
1709         when :channellen, :kicklen, :modes, :topiclen
1710           if val
1711             @supports[key] = val.to_i
1712           else
1713             @supports[key] = nil
1714           end
1715         when :chantypes
1716           @supports[key] = val # can also be nil
1717         when :excepts
1718           val ||= 'e'
1719           @supports[key] = val
1720         when :invex
1721           val ||= 'I'
1722           @supports[key] = val
1723         when :maxchannels
1724           noval_warn(key, val) {
1725             reparse << "CHANLIMIT=(chantypes):#{val} "
1726           }
1727         when :maxtargets
1728           noval_warn(key, val) {
1729             @supports[:targmax]['PRIVMSG'] = val.to_i
1730             @supports[:targmax]['NOTICE'] = val.to_i
1731           }
1732         when :network
1733           noval_warn(key, val) {
1734             @supports[key] = val
1735           }
1736         when :nicklen
1737           noval_warn(key, val) {
1738             @supports[key] = val.to_i
1739           }
1740         when :prefix
1741           if val
1742             val.scan(/\((.*)\)(.*)/) { |m, p|
1743               @supports[key][:modes] = m.scan(/./).map { |x| x.to_sym}
1744               @supports[key][:prefixes] = p.scan(/./).map { |x| x.to_sym}
1745             }
1746           else
1747             @supports[key][:modes] = nil
1748             @supports[key][:prefixes] = nil
1749           end
1750         when :safelist
1751           val_warn(key, val) {
1752             @supports[key] = val.nil? ? true : val
1753           }
1754         when :statusmsg
1755           noval_warn(key, val) {
1756             @supports[key] = val.scan(/./)
1757           }
1758         when :std
1759           noval_warn(key, val) {
1760             @supports[key] = val.split(',')
1761           }
1762         else
1763           @supports[key] =  val.nil? ? true : val
1764         end
1765       }
1766       unless reparse.empty?
1767         reparse_str = reparse.join(" ")
1768         reparse_str.gsub!("(chantypes)",@supports[:chantypes])
1769         reparse_str.gsub!("(charset)",@supports[:charset] || 'rfc1459')
1770         parse_isupport(reparse_str)
1771       end
1772     end
1773
1774     # Returns the casemap of the server.
1775     #
1776     def casemap
1777       @supports[:casemapping]
1778     end
1779
1780     # Returns User or Channel depending on what _name_ can be
1781     # a name of
1782     #
1783     def user_or_channel?(name)
1784       if supports[:chantypes].include?(name[0])
1785         return Channel
1786       else
1787         return User
1788       end
1789     end
1790
1791     # Returns the actual User or Channel object matching _name_
1792     #
1793     def user_or_channel(name)
1794       if supports[:chantypes].include?(name[0])
1795         return channel(name)
1796       else
1797         return user(name)
1798       end
1799     end
1800
1801     # Checks if the receiver already has a channel with the given _name_
1802     #
1803     def has_channel?(name)
1804       return false if name.nil_or_empty?
1805       channel_names.index(name.irc_downcase(casemap))
1806     end
1807     alias :has_chan? :has_channel?
1808
1809     # Returns the channel with name _name_, if available
1810     #
1811     def get_channel(name)
1812       return nil if name.nil_or_empty?
1813       idx = has_channel?(name)
1814       channels[idx] if idx
1815     end
1816     alias :get_chan :get_channel
1817
1818     # Create a new Channel object bound to the receiver and add it to the
1819     # list of <code>Channel</code>s on the receiver, unless the channel was
1820     # present already. In this case, the default action is to raise an
1821     # exception, unless _fails_ is set to false.  An exception can also be
1822     # raised if _str_ is nil or empty, again only if _fails_ is set to true;
1823     # otherwise, the method just returns nil
1824     #
1825     def new_channel(name, topic=nil, users=[], fails=true)
1826       if name.nil_or_empty?
1827         raise "Tried to look for empty or nil channel name #{name.inspect}" if fails
1828         return nil
1829       end
1830       ex = get_chan(name)
1831       if ex
1832         raise "Channel #{name} already exists on server #{self}" if fails
1833         return ex
1834       else
1835
1836         prefix = name[0,1]
1837
1838         # Give a warning if the new Channel goes over some server limits.
1839         #
1840         # FIXME might need to raise an exception
1841         #
1842         warn "#{self} doesn't support channel prefix #{prefix}" unless @supports[:chantypes].include?(prefix)
1843         warn "#{self} doesn't support channel names this long (#{name.length} > #{@supports[:channellen]})" unless name.length <= @supports[:channellen]
1844
1845         # Next, we check if we hit the limit for channels of type +prefix+
1846         # if the server supports +chanlimit+
1847         #
1848         @supports[:chanlimit].keys.each { |k|
1849           next unless k.include?(prefix)
1850           count = 0
1851           channel_names.each { |n|
1852             count += 1 if k.include?(n[0])
1853           }
1854           # raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]
1855           warn "Already joined #{count}/#{@supports[:chanlimit][k]} channels with prefix #{k}, we may be going over server limits" if count >= @supports[:chanlimit][k]
1856         }
1857
1858         # So far, everything is fine. Now create the actual Channel
1859         #
1860         chan = Channel.new(name, topic, users, :server => self)
1861
1862         # We wade through +prefix+ and +chanmodes+ to create appropriate
1863         # lists and flags for this channel
1864
1865         @supports[:prefix][:modes].each { |mode|
1866           chan.create_mode(mode, Channel::UserMode)
1867         } if @supports[:prefix][:modes]
1868
1869         @supports[:chanmodes].each { |k, val|
1870           if val
1871             case k
1872             when :typea
1873               val.each { |mode|
1874                 chan.create_mode(mode, Channel::ModeTypeA)
1875               }
1876             when :typeb
1877               val.each { |mode|
1878                 chan.create_mode(mode, Channel::ModeTypeB)
1879               }
1880             when :typec
1881               val.each { |mode|
1882                 chan.create_mode(mode, Channel::ModeTypeC)
1883               }
1884             when :typed
1885               val.each { |mode|
1886                 chan.create_mode(mode, Channel::ModeTypeD)
1887               }
1888             end
1889           end
1890         }
1891
1892         @channels << chan
1893         # debug "Created channel #{chan.inspect}"
1894         return chan
1895       end
1896     end
1897
1898     # Returns the Channel with the given _name_ on the server,
1899     # creating it if necessary. This is a short form for
1900     # new_channel(_str_, nil, [], +false+)
1901     #
1902     def channel(str)
1903       new_channel(str,nil,[],false)
1904     end
1905
1906     # Remove Channel _name_ from the list of <code>Channel</code>s
1907     #
1908     def delete_channel(name)
1909       idx = has_channel?(name)
1910       raise "Tried to remove unmanaged channel #{name}" unless idx
1911       @channels.delete_at(idx)
1912     end
1913
1914     # Checks if the receiver already has a user with the given _nick_
1915     #
1916     def has_user?(nick)
1917       return false if nick.nil_or_empty?
1918       user_nicks.index(nick.irc_downcase(casemap))
1919     end
1920
1921     # Returns the user with nick _nick_, if available
1922     #
1923     def get_user(nick)
1924       idx = has_user?(nick)
1925       @users[idx] if idx
1926     end
1927
1928     # Create a new User object bound to the receiver and add it to the list
1929     # of <code>User</code>s on the receiver, unless the User was present
1930     # already. In this case, the default action is to raise an exception,
1931     # unless _fails_ is set to false. An exception can also be raised
1932     # if _str_ is nil or empty, again only if _fails_ is set to true;
1933     # otherwise, the method just returns nil
1934     #
1935     def new_user(str, fails=true)
1936       if str.nil_or_empty?
1937         raise "Tried to look for empty or nil user name #{str.inspect}" if fails
1938         return nil
1939       end
1940       tmp = str.to_irc_user(:server => self)
1941       old = get_user(tmp.nick)
1942       # debug "Tmp: #{tmp.inspect}"
1943       # debug "Old: #{old.inspect}"
1944       if old
1945         # debug "User already existed as #{old.inspect}"
1946         if tmp.known?
1947           if old.known?
1948             # debug "Both were known"
1949             # Do not raise an error: things like Freenode change the hostname after identification
1950             warning "User #{tmp.nick} has inconsistent Netmasks! #{self} knows #{old.inspect} but access was tried with #{tmp.inspect}" if old != tmp
1951             raise "User #{tmp} already exists on server #{self}" if fails
1952           end
1953           if old.fullform.downcase != tmp.fullform.downcase
1954             old.replace(tmp)
1955             # debug "Known user now #{old.inspect}"
1956           end
1957         end
1958         return old
1959       else
1960         warn "#{self} doesn't support nicknames this long (#{tmp.nick.length} > #{@supports[:nicklen]})" unless tmp.nick.length <= @supports[:nicklen]
1961         @users << tmp
1962         return @users.last
1963       end
1964     end
1965
1966     # Returns the User with the given Netmask on the server,
1967     # creating it if necessary. This is a short form for
1968     # new_user(_str_, +false+)
1969     #
1970     def user(str)
1971       new_user(str, false)
1972     end
1973
1974     # Deletes User _user_ from Channel _channel_
1975     #
1976     def delete_user_from_channel(user, channel)
1977       channel.delete_user(user)
1978     end
1979
1980     # Remove User _someuser_ from the list of <code>User</code>s.
1981     # _someuser_ must be specified with the full Netmask.
1982     #
1983     def delete_user(someuser)
1984       idx = has_user?(someuser)
1985       raise "Tried to remove unmanaged user #{user}" unless idx
1986       have = self.user(someuser)
1987       @channels.each { |ch|
1988         delete_user_from_channel(have, ch)
1989       }
1990       @users.delete_at(idx)
1991     end
1992
1993     # Create a new Netmask object with the appropriate casemap
1994     #
1995     def new_netmask(str)
1996       str.to_irc_netmask(:server => self)
1997     end
1998
1999     # Finds all <code>User</code>s on server whose Netmask matches _mask_
2000     #
2001     def find_users(mask)
2002       nm = new_netmask(mask)
2003       @users.inject(UserList.new) {
2004         |list, user|
2005         if user.user == "*" or user.host == "*"
2006           list << user if user.nick.irc_downcase(casemap) =~ nm.nick.irc_downcase(casemap).to_irc_regexp
2007         else
2008           list << user if user.matches?(nm)
2009         end
2010         list
2011       }
2012     end
2013
2014   end
2015
2016 end
2017