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