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