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