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