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