X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Firc.rb;h=17b7bc3e4b0cee684e9f44a37d1701b10666c744;hb=a19f7bfb97e5f36e6b282fcc0982584838e86a0a;hp=fe1aa9fa323943772b0210496529f5bdd31a76ea;hpb=6f9bfa43ac907700fcba394e0f6b9d987b1192fb;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/irc.rb b/lib/rbot/irc.rb index fe1aa9fa..17b7bc3e 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 @@ -16,11 +18,24 @@ # This module defines the fundamental building blocks for IRC # # Author:: Giuseppe Bilotta (giuseppe.bilotta@gmail.com) -# Copyright:: Copyright (c) 2006 Giuseppe Bilotta -# License:: GPLv2 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 @@ -33,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 @@ -116,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 @@ -129,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 @@ -141,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 @@ -181,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 @@ -258,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 @@ -495,8 +517,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 @@ -525,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}+/ @@ -542,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})+/ @@ -550,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. @@ -618,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 @@ -812,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? @@ -924,7 +952,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. @@ -936,6 +964,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. @@ -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 @@ -1103,6 +1141,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 # @@ -1288,15 +1336,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 @@ -1327,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 @@ -1343,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) @@ -1359,7 +1417,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 @@ -1374,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 @@ -1468,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 @@ -1499,6 +1562,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. @@ -1597,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 @@ -1605,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 @@ -1617,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 =~ /^-(.*)/ @@ -1629,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) { @@ -1638,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 } @@ -1667,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) { @@ -1708,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. @@ -1774,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+ @@ -1793,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 @@ -1898,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