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