X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Firc.rb;h=c57b252f58973801f7f5b8a6e234a56903c1185e;hb=2a3bc07d139593f1f57e23cfbd08602d29a8b26c;hp=31d432fd7c7af6a25b3fe712bcc92cb9a7fa5808;hpb=b3300cabeda816b79a592c3e7704abbbd3b5505c;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/irc.rb b/lib/rbot/irc.rb index 31d432fd..c57b252f 100644 --- a/lib/rbot/irc.rb +++ b/lib/rbot/irc.rb @@ -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 @@ -19,6 +21,21 @@ 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 @@ -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 @@ -256,7 +273,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 @@ -493,8 +516,8 @@ class Regexp HEX_DIGITS = /#{HEX_DIGIT}+/ HEX_OCTET = /#{HEX_DIGIT}#{HEX_DIGIT}?/ DEC_OCTET = /[01]?\d?\d|2[0-4]\d|25[0-5]/ - DEC_IP_ADDR = /#{DEC_OCTET}.#{DEC_OCTET}.#{DEC_OCTET}.#{DEC_OCTET}/ - HEX_IP_ADDR = /#{HEX_OCTET}.#{HEX_OCTET}.#{HEX_OCTET}.#{HEX_OCTET}/ + DEC_IP_ADDR = /#{DEC_OCTET}\.#{DEC_OCTET}\.#{DEC_OCTET}\.#{DEC_OCTET}/ + HEX_IP_ADDR = /#{HEX_OCTET}\.#{HEX_OCTET}\.#{HEX_OCTET}\.#{HEX_OCTET}/ IP_ADDR = /#{DEC_IP_ADDR}|#{HEX_IP_ADDR}/ # IPv6, from Resolv::IPv6, without the \A..\z anchors @@ -540,7 +563,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 +571,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. @@ -922,7 +945,7 @@ module Irc class User < Netmask alias :to_s :nick - attr_accessor :real_name + attr_accessor :real_name, :idle_since, :signon # Create a new IRC User from a given Netmask (or anything that can be converted # into a Netmask) provided that the given Netmask does not have globs. @@ -934,6 +957,8 @@ module Irc raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if host.has_irc_glob? && host != "*" @away = false @real_name = String.new + @idle_since = nil + @signon = nil end # The nick of a User may be changed freely, but it must not contain glob patterns. @@ -1039,6 +1064,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 @@ -1101,6 +1134,16 @@ module Irc end + # Hash of modes. Subclass of Hash that defines any? and all? + # to check if boolean modes (Type D) are set + class ModeHash < Hash + def any?(*ar) + !!ar.find { |m| s = m.to_sym ; self[s] && self[s].set? } + end + def all?(*ar) + !ar.find { |m| s = m.to_sym ; !(self[s] && self[s].set?) } + end + end # Channel modes of type A manipulate lists # @@ -1286,15 +1329,25 @@ 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 + attr_accessor :creation_time, :url def inspect str = self.__to_s__[0..-2] str << " on server #{server}" if server str << " @name=#{@name.inspect} @topic=#{@topic.text.inspect}" str << " @users=[#{user_nicks.sort.join(', ')}]" + str << " (created on #{creation_time})" if creation_time + str << " (URL #{url})" if url str << ">" end @@ -1325,7 +1378,7 @@ 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 else @@ -1341,7 +1394,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 !~ /^[&#+!]/ + warn "Unknown channel prefix #{name[0,1]}" if name !~ /^[&#+!]/ raise ArgumentError, "Invalid character in #{name.inspect}" if name =~ /[ \x07,]/ init_server_or_casemap(opts) @@ -1357,7 +1410,13 @@ module Irc } # Flags - @mode = {} + @mode = ModeHash.new + + # creation time, only on some networks + @creation_time = nil + + # URL, only on some networks + @url = nil end # Removes a user from the channel @@ -1372,31 +1431,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 @@ -1466,7 +1525,6 @@ module Irc class Server attr_reader :hostname, :version, :usermodes, :chanmodes - alias :to_s :hostname attr_reader :supports, :capabilities attr_reader :channels, :users @@ -1497,6 +1555,10 @@ module Irc str << ">" end + def to_s + hostname.nil? ? "" : 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. @@ -1615,7 +1677,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 =~ /^-(.*)/ @@ -1627,7 +1689,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) { @@ -1665,7 +1735,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) { @@ -1706,8 +1776,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. @@ -1772,7 +1846,7 @@ 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. #