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