]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - lib/rbot/irc.rb
registry: add in-memory implementation for tests
[user/henk/code/ruby/rbot.git] / lib / rbot / irc.rb
index 2eb676e2ed0506ce465f59f492884044b5241362..17b7bc3e4b0cee684e9f44a37d1701b10666c744 100644 (file)
@@ -4,6 +4,8 @@
 #   Channels is the User on (of those the client is on too)?
 #   We may want this so that when a User leaves all Channels and he hasn't
 #   sent us privmsgs, we know we can remove him from the Server @users list
+#   FIXME for the time being, we do it with a method that scans the server
+#   (if defined), so the method is slow and should not be used frequently.
 # * Maybe ChannelList and UserList should be HashesOf instead of ArrayOf?
 #   See items marked as TODO Ho.
 #   The framework to do this is now in place, thanks to the new [] method
 
 require 'singleton'
 
+# The following monkeypatch is to fix a bug in Singleton where marshaling would
+# fail when trying to restore a marshaled Singleton due to _load being declared
+# private.
+if RUBY_VERSION < '1.9'
+module ::Singleton
+  public :_dump
+end
+
+class << Singleton
+  module SingletonClassMethods
+    public :_load
+  end
+end
+end
+
 class Object
 
   # We extend the Object class with a method that
@@ -31,7 +48,7 @@ class Object
 
   # We alias the to_s method to __to_s__ to make
   # it accessible in all classes
-  alias :__to_s__ :to_s 
+  alias :__to_s__ :to_s
 end
 
 # The Irc module is used to keep all IRC-related classes
@@ -114,7 +131,7 @@ module Irc
       if self == other
         return true
       else
-        warn "Casemap mismatch (#{self.inspect} != #{other.inspect})"
+        warning "Casemap mismatch (#{self.inspect} != #{other.inspect})"
         return false
       end
     end
@@ -127,7 +144,7 @@ module Irc
     include Singleton
 
     def initialize
-      super('rfc1459', "\x41-\x5e", "\x61-\x7e")
+      super('rfc1459', "\x41-\x5a\x7b-\x7e", "\x61-\x7a\x5b-\x5e")
     end
 
   end
@@ -139,7 +156,7 @@ module Irc
     include Singleton
 
     def initialize
-      super('strict-rfc1459', "\x41-\x5d", "\x61-\x7d")
+      super('strict-rfc1459', "\x41-\x5a\x7b-\x7d", "\x61-\x7a\x5b-\x5d")
     end
 
   end
@@ -179,6 +196,7 @@ module Irc
           @casemap = nil
         end
       else
+        warning 'casemap fallback to rfc1459 without hints, correct?'
         @casemap = (@casemap || 'rfc1459').to_irc_casemap
       end
     end
@@ -256,7 +274,13 @@ class String
   # This method returns the Irc::Casemap whose name is the receiver
   #
   def to_irc_casemap
-    Irc::Casemap.get(self) rescue raise TypeError, "Unkown Irc::Casemap #{self.inspect}"
+    begin
+      Irc::Casemap.get(self)
+    rescue
+      # raise TypeError, "Unkown Irc::Casemap #{self.inspect}"
+      error "Unkown Irc::Casemap #{self.inspect} requested, defaulting to rfc1459"
+      Irc::Casemap.get('rfc1459')
+    end
   end
 
   # This method returns a string which is the downcased version of the
@@ -523,7 +547,7 @@ class Regexp
     RFC_CHAN = /#{CHAN_FIRST}#{CHAN_ANY}{1,49}|#{CHAN_SAFE}#{CHAN_ANY}{1,44}/
 
     # Nick-matching regexps
-    SPECIAL_CHAR = /[\x5b-\x60\x7b-\x7d]/
+    SPECIAL_CHAR = /[\[-\`\{-\}]/
     NICK_FIRST = /#{SPECIAL_CHAR}|[[:alpha:]]/
     NICK_ANY = /#{SPECIAL_CHAR}|[[:alnum:]]|-/
     GEN_NICK = /#{NICK_FIRST}#{NICK_ANY}+/
@@ -540,7 +564,7 @@ class Regexp
     GEN_HOST = /#{HOSTNAME}|#{HOSTADDR}/
 
     # # FreeNode network replaces the host of affiliated users with
-    # # 'virtual hosts' 
+    # # 'virtual hosts'
     # # FIXME we need the true syntax to match it properly ...
     # PDPC_HOST_PART = /[0-9A-Za-z.-]+/
     # PDPC_HOST = /#{PDPC_HOST_PART}(?:\/#{PDPC_HOST_PART})+/
@@ -548,7 +572,7 @@ class Regexp
     # # NOTE: the final optional and non-greedy dot is needed because some
     # # servers (e.g. FreeNode) send the hostname of the services as "services."
     # # which is not RFC compliant, but sadly done.
-    # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/ 
+    # GEN_HOST_EXT = /#{PDPC_HOST}|#{GEN_HOST}\.??/
 
     # Sadly, different networks have different, RFC-breaking ways of cloaking
     # the actualy host address: see above for an example to handle FreeNode.
@@ -616,6 +640,12 @@ module Irc
     def initialize(str="", opts={})
       # First of all, check for server/casemap option
       #
+      debug 'new netmask "%s" casemap=%s server=%s server#casemap=%s' % [
+        str,
+        (opts[:casemap].class.to_s rescue 'null'),
+        (opts[:server].hostname.to_s rescue 'null'),
+        (opts[:server].casemap.class.to_s rescue 'null')
+      ]
       init_server_or_casemap(opts)
 
       # Now we can see if the given string _str_ is an actual Netmask
@@ -810,7 +840,7 @@ module Irc
         them = cmp.send(component).irc_downcase(casemap)
         if us.has_irc_glob? && them.has_irc_glob?
           next if us == them
-          warn NotImplementedError
+          warning NotImplementedError
           return false
         end
         return false if us.has_irc_glob? && !them.has_irc_glob?
@@ -1041,6 +1071,14 @@ module Irc
         raise "Can't resolve channel #{channel}"
       end
     end
+
+    def channels
+      if @server
+        @server.channels.select { |ch| ch.has_user?(self) }
+      else
+        Array.new
+      end
+    end
   end
 
 
@@ -1298,6 +1336,13 @@ module Irc
   #
   class Channel
 
+    # Return the non-prefixed part of a channel name.
+    # Also works with ## channels found on some networks
+    # (e.g. FreeNode)
+    def self.npname(str)
+      return str.to_s.sub(/^[&#+!]+/,'')
+    end
+
     include ServerOrCasemap
     attr_reader :name, :topic, :mode, :users
     alias :to_s :name
@@ -1340,9 +1385,9 @@ module Irc
     # Adds a user to the channel
     #
     def add_user(user, opts={})
-      silent = opts.fetch(:silent, false) 
+      silent = opts.fetch(:silent, false)
       if has_user?(user)
-        warn "Trying to add user #{user} to channel #{self} again" unless silent
+        warning "Trying to add user #{user} to channel #{self} again" unless silent
       else
         @users << user.to_irc_user(server_and_casemap)
       end
@@ -1356,7 +1401,7 @@ module Irc
     #
     def initialize(name, topic=nil, users=[], opts={})
       raise ArgumentError, "Channel name cannot be empty" if name.to_s.empty?
-      warn "Unknown channel prefix #{name[0].chr}" if name !~ /^[&#+!]/
+      warning "Unknown channel prefix #{name[0,1]}" if name !~ /^[&#+!]/
       raise ArgumentError, "Invalid character in #{name.inspect}" if name =~ /[ \x07,]/
 
       init_server_or_casemap(opts)
@@ -1393,31 +1438,31 @@ module Irc
     # The channel prefix
     #
     def prefix
-      name[0].chr
+      name[0,1]
     end
 
     # A channel is local to a server if it has the '&' prefix
     #
     def local?
-      name[0] == 0x26
+      name[0,1] == '&'
     end
 
     # A channel is modeless if it has the '+' prefix
     #
     def modeless?
-      name[0] == 0x2b
+      name[0,1] == '+'
     end
 
     # A channel is safe if it has the '!' prefix
     #
     def safe?
-      name[0] == 0x21
+      name[0,1] == '!'
     end
 
     # A channel is normal if it has the '#' prefix
     #
     def normal?
-      name[0] == 0x23
+      name[0,1] == '#'
     end
 
     # Create a new mode
@@ -1487,7 +1532,6 @@ module Irc
   class Server
 
     attr_reader :hostname, :version, :usermodes, :chanmodes
-    alias :to_s :hostname
     attr_reader :supports, :capabilities
 
     attr_reader :channels, :users
@@ -1518,6 +1562,10 @@ module Irc
       str << ">"
     end
 
+    def to_s
+      hostname.nil? ? "<no hostname>" : hostname
+    end
+
     # Create a new Server, with all instance variables reset to nil (for
     # scalar variables), empty channel and user lists and @supports
     # initialized to the default values for all known supported features.
@@ -1616,7 +1664,7 @@ module Irc
       if val
         yield if block_given?
       else
-        warn "No #{key.to_s.upcase} value"
+        warning "No #{key.to_s.upcase} value"
       end
     end
 
@@ -1624,7 +1672,7 @@ module Irc
       if val == true or val == false or val.nil?
         yield if block_given?
       else
-        warn "No #{key.to_s.upcase} value must be specified, got #{val}"
+        warning "No #{key.to_s.upcase} value must be specified, got #{val}"
       end
     end
     private :noval_warn, :val_warn
@@ -1636,7 +1684,7 @@ module Irc
     def parse_isupport(line)
       debug "Parsing ISUPPORT #{line.inspect}"
       ar = line.split(' ')
-      reparse = ""
+      reparse = []
       ar.each { |en|
         prekey, val = en.split('=', 2)
         if prekey =~ /^-(.*)/
@@ -1648,7 +1696,15 @@ module Irc
         case key
         when :casemapping
           noval_warn(key, val) {
-            @supports[key] = val.to_irc_casemap
+            if val == 'charset'
+              reparse << "CASEMAPPING=(charset)"
+            else
+              # TODO some servers offer non-standard CASEMAPPINGs in the form
+              # locale.charset[-options], which indicate an extended set of
+              # allowed characters (mostly for nicks). This might be supported
+              # with hooks for the unicode core module
+              @supports[key] = val.to_irc_casemap
+            end
           }
         when :chanlimit, :idchan, :maxlist, :targmax
           noval_warn(key, val) {
@@ -1657,7 +1713,8 @@ module Irc
               k, v = g.split(':')
               @supports[key][k] = v.to_i || 0
               if @supports[key][k] == 0
-                warn "Deleting #{key} limit of 0 for #{k}"
+                # If no argument is given for a particular command (e.g. "WHOIS:"),
+                #  that command does not have a limit on the number of targets.)
                 @supports[key].delete(k)
               end
             }
@@ -1686,7 +1743,7 @@ module Irc
           @supports[key] = val
         when :maxchannels
           noval_warn(key, val) {
-            reparse += "CHANLIMIT=(chantypes):#{val} "
+            reparse << "CHANLIMIT=(chantypes):#{val} "
           }
         when :maxtargets
           noval_warn(key, val) {
@@ -1727,8 +1784,12 @@ module Irc
           @supports[key] =  val.nil? ? true : val
         end
       }
-      reparse.gsub!("(chantypes)",@supports[:chantypes])
-      parse_isupport(reparse) unless reparse.empty?
+      unless reparse.empty?
+        reparse_str = reparse.join(" ")
+        reparse_str.gsub!("(chantypes)",@supports[:chantypes])
+        reparse_str.gsub!("(charset)",@supports[:charset] || 'rfc1459')
+        parse_isupport(reparse_str)
+      end
     end
 
     # Returns the casemap of the server.
@@ -1793,14 +1854,14 @@ module Irc
         return ex
       else
 
-        prefix = name[0].chr
+        prefix = name[0,1]
 
         # Give a warning if the new Channel goes over some server limits.
         #
         # FIXME might need to raise an exception
         #
-        warn "#{self} doesn't support channel prefix #{prefix}" unless @supports[:chantypes].include?(prefix)
-        warn "#{self} doesn't support channel names this long (#{name.length} > #{@supports[:channellen]})" unless name.length <= @supports[:channellen]
+        warning "#{self} doesn't support channel prefix #{prefix}" unless @supports[:chantypes].include?(prefix)
+        warning "#{self} doesn't support channel names this long (#{name.length} > #{@supports[:channellen]})" unless name.length <= @supports[:channellen]
 
         # Next, we check if we hit the limit for channels of type +prefix+
         # if the server supports +chanlimit+
@@ -1812,7 +1873,7 @@ module Irc
             count += 1 if k.include?(n[0])
           }
           # raise IndexError, "Already joined #{count} channels with prefix #{k}" if count == @supports[:chanlimit][k]
-          warn "Already joined #{count}/#{@supports[:chanlimit][k]} channels with prefix #{k}, we may be going over server limits" if count >= @supports[:chanlimit][k]
+          warning "Already joined #{count}/#{@supports[:chanlimit][k]} channels with prefix #{k}, we may be going over server limits" if count >= @supports[:chanlimit][k]
         }
 
         # So far, everything is fine. Now create the actual Channel
@@ -1917,7 +1978,7 @@ module Irc
         end
         return old
       else
-        warn "#{self} doesn't support nicknames this long (#{tmp.nick.length} > #{@supports[:nicklen]})" unless tmp.nick.length <= @supports[:nicklen]
+        warning "#{self} doesn't support nicknames this long (#{tmp.nick.length} > #{@supports[:nicklen]})" unless tmp.nick.length <= @supports[:nicklen]
         @users << tmp
         return @users.last
       end