From 5659ef2f6717cd713d5202e54a5c4573eeaa3a45 Mon Sep 17 00:00:00 2001 From: Giuseppe Bilotta Date: Fri, 4 Aug 2006 23:42:14 +0000 Subject: Commit [369] was bogus. This one cleans up and sanitizes the Irc framework. --- lib/rbot/irc.rb | 818 ++++++++++++++++++++++++++++++++++------------------ lib/rbot/rfc2812.rb | 10 +- 2 files changed, 539 insertions(+), 289 deletions(-) (limited to 'lib/rbot') diff --git a/lib/rbot/irc.rb b/lib/rbot/irc.rb index 802ffd87..69b5d239 100644 --- a/lib/rbot/irc.rb +++ b/lib/rbot/irc.rb @@ -17,6 +17,10 @@ require 'singleton' + +# The Irc module is used to keep all IRC-related classes +# in the same namespace +# module Irc @@ -27,7 +31,7 @@ module Irc # This is however not the same on all IRC servers: some use standard ASCII # casemapping, other do not consider ^ as the uppercase of # ~ - # + # class Casemap @@casemaps = {} @@ -37,9 +41,11 @@ module Irc def initialize(name, upper, lower) @key = name.to_sym raise "Casemap #{name.inspect} already exists!" if @@casemaps.has_key?(@key) - @@casemaps[@key][:upper] = upper - @@casemaps[@key][:lower] = lower - @@casemaps[@key][:casemap] = self + @@casemaps[@key] = { + :upper => upper, + :lower => lower, + :casemap => self + } end # Returns the Casemap with the given name @@ -61,10 +67,31 @@ module Irc end # Return a Casemap based on the receiver + # def to_irc_casemap self end + # A Casemap is represented by its lower/upper mappings + # + def inspect + "#<#{self.class}:#{'0x%x'% self.object_id}: #{upper.inspect} ~(#{self})~ #{lower.inspect}>" + end + + # As a String we return our name + # + def to_s + @key.to_s + end + + # Raise an error if _arg_ and self are not the same Casemap + # + def must_be(arg) + other = arg.to_irc_casemap + raise "Casemap mismatch (#{self} != #{other})" unless self == other + return true + end + end # The rfc1459 casemap @@ -75,7 +102,9 @@ module Irc def initialize super('rfc1459', "\x41-\x5e", "\x61-\x7e") end + end + RfcCasemap.instance # The strict-rfc1459 Casemap # @@ -83,18 +112,106 @@ module Irc include Singleton def initialize - super('rfc1459', "\x41-\x5d", "\x61-\x7d") + super('strict-rfc1459', "\x41-\x5d", "\x61-\x7d") end + end + StrictRfcCasemap.instance # The ascii Casemap # - class StrictRfcCasemap < Casemap + class AsciiCasemap < Casemap include Singleton def initialize super('ascii', "\x41-\x5a", "\x61-\x7a") end + + end + AsciiCasemap.instance + + + # This module is included by all classes that are either bound to a server + # or should have a casemap. + # + module ServerOrCasemap + + attr_reader :server + + # This method initializes the instance variables @server and @casemap + # according to the values of the hash keys :server and :casemap in _opts_ + # + def init_server_or_casemap(opts={}) + @server = opts.fetch(:server, nil) + raise TypeError, "#{@server} is not a valid Irc::Server" if @server and not @server.kind_of?(Server) + + @casemap = opts.fetch(:casemap, nil) + if @server + if @casemap + @server.casemap.must_be(@casemap) + @casemap = nil + end + else + @casemap = (@casemap || 'rfc1459').to_irc_casemap + end + end + + # This is an auxiliary method: it returns true if the receiver fits the + # server and casemap specified in _opts_, false otherwise. + # + def fits_with_server_and_casemap?(opts={}) + srv = opts.fetch(:server, nil) + cmap = opts.fetch(:casemap, nil) + cmap = cmap.to_irc_casemap unless cmap.nil? + + if srv.nil? + return true if cmap.nil? or cmap == casemap + else + return true if srv == @server and (cmap.nil? or cmap == casemap) + end + return false + end + + # Returns the casemap of the receiver, by looking at the bound + # @server (if possible) or at the @casemap otherwise + # + def casemap + @server.casemap rescue @casemap + end + + # Returns a hash with the current @server and @casemap as values of + # :server and :casemap + # + def server_and_casemap + {:server => @server, :casemap => @casemap} + end + + # We allow up/downcasing with a different casemap + # + def irc_downcase(cmap=casemap) + self.to_s.irc_downcase(cmap) + end + + # Up/downcasing something that includes this module returns its + # Up/downcased to_s form + # + def downcase + self.irc_downcase + end + + # We allow up/downcasing with a different casemap + # + def irc_upcase(cmap=casemap) + self.to_s.irc_upcase(cmap) + end + + # Up/downcasing something that includes this module returns its + # Up/downcased to_s form + # + def upcase + self.irc_upcase + end + end end @@ -108,7 +225,7 @@ class String # This method returns the Irc::Casemap whose name is the receiver # def to_irc_casemap - Casemap.get(self) rescue raise TypeError, "Unkown Irc::Casemap #{self.inspect}" + Irc::Casemap.get(self) rescue raise TypeError, "Unkown Irc::Casemap #{self.inspect}" end # This method returns a string which is the downcased version of the @@ -181,6 +298,7 @@ class String } Regexp.new(regmask) end + end @@ -245,6 +363,7 @@ class ArrayOf < Array # This method is similar to the above, except that it raises an exception # if the receiver is not valid + # def validate raise TypeError unless valid? end @@ -324,19 +443,15 @@ class ArrayOf < Array end -# The Irc module is used to keep all IRC-related classes -# in the same namespace -# module Irc # A Netmask identifies each user by collecting its nick, username and # hostname in the form nick!user@host # - # Netmasks can also contain glob patterns in any of their components; in this - # form they are used to refer to more than a user or to a user appearing - # under different - # forms. + # Netmasks can also contain glob patterns in any of their components; in + # this form they are used to refer to more than a user or to a user + # appearing under different forms. # # Example: # * *!*@* refers to everybody @@ -344,6 +459,11 @@ module Irc # regardless of the nick used. # class Netmask + + # Netmasks have an associated casemap unless they are bound to a server + # + include ServerOrCasemap + attr_reader :nick, :user, :host # Create a new Netmask from string _str_, which must be in the form @@ -355,38 +475,23 @@ module Irc # casemap is used. If both a server and a casemap are specified, the # casemap must match the server's casemap or an exception will be raised. # - # The nick is downcased following IRC rules and according to the given casemap, which defaults to rfc1459. - # - # FIXME check if user and host need to be downcased too. - # # Empty +nick+, +user+ or +host+ are converted to the generic glob pattern # - def initialize(str, opts={}) + def initialize(str="", opts={}) + debug "String: #{str.inspect}, options: #{opts.inspect}" - # First of all, check for server/casemap option, since this is used to - # downcase the nick + # First of all, check for server/casemap option # - @server = opts.fetch(:server, nil) - raise TypeError, "#{@server} is not a valid Irc::Server" if @server and not @server.kind_of?(Server) - - @casemap = opts.fetch(:casemap, nil) - if @server - if @casemap - raise "Casemap mismatch (#{@server.casemap} != #{@casemap})" unless @server.casemap == @casemap - @casemap = nil - end - else - @casemap |= 'rfc1459' - end + init_server_or_casemap(opts) # Now we can see if the given string _str_ is an actual Netmask if str.respond_to?(:to_str) case str.to_str when /^(?:(\S+?)(?:!(\S+)@(?:(\S+))?)?)?$/ # We do assignment using our internal methods - nick = $1 - user = $2 - host = $3 + self.nick = $1 + self.user = $2 + self.host = $3 else raise ArgumentError, "#{str.to_str.inspect} does not represent a valid #{self.class}" end @@ -395,46 +500,59 @@ module Irc end end - # Converts the receiver into a Netmask with the given (optional) server/casemap association + # A Netmask is easily converted to a String for the usual representation + # + def fullform + "#{nick}!#{user}@#{host}" + end + alias :to_s :fullform + + # Converts the receiver into a Netmask with the given (optional) + # server/casemap association. We return self unless a conversion + # is needed (different casemap/server) + # + # Subclasses of Netmask will return a new Netmask # def to_irc_netmask(opts={}) - srv = opts.fetch(:server, nil) - cmap = opts.fetch(:casemap, nil) - return self if srv.nil? and cmap.nil? - return self if srv.nil? and cmap == casemap - raise - if srv and @server - @server = srv + if self.class == Netmask + return self if fits_with_server_and_casemap?(opts) end + return self.fullform.to_irc_netmask(opts) end - # Returns the casemap of the Netmask, by looking at the originating server (if possible) - # or at the @casemap otherwise + # Converts the receiver into a User with the given (optional) + # server/casemap association. We return self unless a conversion + # is needed (different casemap/server) # - def casemap - @server.casemap rescue @casemap + def to_irc_user(opts={}) + self.fullform.to_irc_user(opts) end + # Inspection of a Netmask reveals the server it's bound to (if there is + # one), its casemap and the nick, user and host part + # def inspect str = "<#{self.class}:#{'0x%x' % self.object_id}:" str << " @server=#{@server}" if @server str << " @nick=#{@nick.inspect} @user=#{@user.inspect}" - str << " @host=#{@host.inspect} casemap=#{casemap}>" + str << " @host=#{@host.inspect} casemap=#{casemap.inspect}>" str end - # Equality: two Netmasks are equal if they have the same @nick, @user, @host and casemap + # Equality: two Netmasks are equal if they downcase to the same thing + # + # TODO we may want it to try other.to_irc_netmask # def ==(other) - self.class == other.class && @nick == other.nick && @user == other.user && @host == other.host && casemap == other.casemap + return false unless other.kind_of?(self.class) + self.downcase == other.downcase end - # This method changes the nick of the Netmask, downcasing the argument - # following IRC rules and defaulting to the generic glob pattern if - # the result is the null string. + # This method changes the nick of the Netmask, defaulting to the generic + # glob pattern if the result is the null string. # def nick=(newnick) - @nick = newnick.to_s.irc_downcase(casemap) + @nick = newnick.to_s @nick = "*" if @nick.empty? end @@ -454,6 +572,21 @@ module Irc @host = "*" if @host.empty? end + # We can replace everything at once with data from another Netmask + # + def replace(other) + case other + when Netmask + nick = other.nick + user = other.user + host = other.host + @server = other.server + @casemap = other.casemap unless @server + else + replace(other.to_irc_netmask(server_and_casemap)) + end + end + # This method checks if a Netmask is definite or not, by seeing if # any of its components are defined by globs # @@ -461,18 +594,12 @@ module Irc return @nick.has_irc_glob? || @user.has_irc_glob? || @host.has_irc_glob? end - # A Netmask is easily converted to a String for the usual representation - # - def fullform - return "#{nick}!#{user}@#{host}" - end - alias :to_s :fullform - # This method is used to match the current Netmask against another one # # The method returns true if each component of the receiver matches the - # corresponding component of the argument. By _matching_ here we mean that - # any netmask described by the receiver is also described by the argument. + # corresponding component of the argument. By _matching_ here we mean + # that any netmask described by the receiver is also described by the + # argument. # # In this sense, matching is rather simple to define in the case when the # receiver has no globs: it is just necessary to check if the argument @@ -484,12 +611,12 @@ module Irc # # The more complex case in which both the receiver and the argument have # globs is not handled yet. - # + # def matches?(arg) cmp = arg.to_irc_netmask(:casemap => casemap) [:nick, :user, :host].each { |component| - us = self.send(component) - them = cmp.send(component) + us = self.send(component).irc_downcase(casemap) + them = cmp.send(component).irc_downcase(casemap) raise NotImplementedError if us.has_irc_glob? && them.has_irc_glob? return false if us.has_irc_glob? && !them.has_irc_glob? return false unless us =~ them.to_irc_regexp @@ -500,15 +627,17 @@ module Irc # Case equality. Checks if arg matches self # def ===(arg) - Netmask.new(arg).matches?(self) + arg.to_irc_netmask(:casemap => casemap).matches?(self) end + # Sorting is done via the fullform + # def <=>(arg) case arg when Netmask - self.fullform <=> arg.fullform + self.fullform.irc_downcase(casemap) <=> arg.fullform.irc_downcase(casemap) else - self.to_s <=> arg.to_s + self.downcase <=> arg.downcase end end @@ -521,19 +650,45 @@ module Irc # Create a new NetmaskList, optionally filling it with the elements from # the Array argument fed to it. + # def initialize(ar=[]) super(Netmask, ar) end + end +end - # An IRC User is identified by his/her Netmask (which must not have - # globs). In fact, User is just a subclass of Netmask. However, - # a User will not allow one's host or user data to be changed. +class String + + # We keep extending String, this time adding a method that converts a + # String into an Irc::Netmask object + # + def to_irc_netmask(opts={}) + Irc::Netmask.new(self, opts) + end + +end + + +module Irc + + + # An IRC User is identified by his/her Netmask (which must not have globs). + # In fact, User is just a subclass of Netmask. + # + # Ideally, the user and host information of an IRC User should never + # change, and it shouldn't contain glob patterns. However, IRC is somewhat + # idiosincratic and it may be possible to know the nick of a User much before + # its user and host are known. Moreover, some networks (namely Freenode) may + # change the hostname of a User when (s)he identifies with Nickserv. + # + # As a consequence, we must allow changes to a User host and user attributes. + # We impose a restriction, though: they may not contain glob patterns, except + # for the special case of an unknown user/host which is represented by a *. # - # Due to the idiosincrasies of the IRC protocol, we allow - # the creation of a user with an unknown mask represented by the - # glob pattern *@*. Only in this case they may be set. + # It is possible to create a totally unknown User (e.g. for initializations) + # by setting the nick to * too. # # TODO list: # * see if it's worth to add the other USER data @@ -545,7 +700,8 @@ module Irc # 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. # - def initialize(str="", casemap=nil) + def initialize(str="", opts={}) + debug "String: #{str.inspect}, options: #{opts.inspect}" super raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if nick.has_irc_glob? && nick != "*" raise ArgumentError, "#{str.inspect} must not have globs (unescaped * or ?)" if user.has_irc_glob? && user != "*" @@ -553,32 +709,35 @@ module Irc @away = false end - # We only allow the user to be changed if it was "*". Otherwise, - # we raise an exception if the new host is different from the old one + # The nick of a User may be changed freely, but it must not contain glob patterns. + # + def nick=(newnick) + raise "Can't change the nick to #{newnick}" if defined?(@nick) and newnick.has_irc_glob? + super + end + + # We have to allow changing the user of an Irc User due to some networks + # (e.g. Freenode) changing hostmasks on the fly. We still check if the new + # user data has glob patterns though. # def user=(newuser) - if user == "*" - super - else - raise "Can't change the username of user #{self}" if user != newuser - end + raise "Can't change the username to #{newuser}" if defined?(@user) and newuser.has_irc_glob? + super end - # We only allow the host to be changed if it was "*". Otherwise, - # we raise an exception if the new host is different from the old one + # We have to allow changing the host of an Irc User due to some networks + # (e.g. Freenode) changing hostmasks on the fly. We still check if the new + # host data has glob patterns though. # def host=(newhost) - if host == "*" - super - else - raise "Can't change the hostname of user #{self}" if host != newhost - end + raise "Can't change the hostname to #{newhost}" if defined?(@host) and newhost.has_irc_glob? + super end # Checks if a User is well-known or not by looking at the hostname and user # def known? - return user!="*" && host!="*" + return nick!= "*" && user!="*" && host!="*" end # Is the user away? @@ -597,6 +756,33 @@ module Irc @away = false end end + + # Since to_irc_user runs the same checks on server and channel as + # to_irc_netmask, we just try that and return self if it works. + # + # Subclasses of User will return self if possible. + # + def to_irc_user(opts={}) + return self if fits_with_server_and_casemap?(opts) + return self.fullform.to_irc_user(opts) + end + + # We can replace everything at once with data from another User + # + def replace(other) + case other + when User + nick = other.nick + user = other.user + host = other.host + @server = other.server + @casemap = other.casemap unless @server + @away = other.away + else + replace(other.to_irc_user(server_and_casemap)) + end + end + end @@ -606,201 +792,267 @@ module Irc # Create a new UserList, optionally filling it with the elements from # the Array argument fed to it. + # def initialize(ar=[]) super(User, ar) end - end + end - # A ChannelTopic represents the topic of a channel. It consists of - # the topic itself, who set it and when - class ChannelTopic - attr_accessor :text, :set_by, :set_on - alias :to_s :text +end - # Create a new ChannelTopic setting the text, the creator and - # the creation time - def initialize(text="", set_by="", set_on=Time.new) - @text = text - @set_by = set_by - @set_on = Time.new - end +class String - # Replace a ChannelTopic with another one - def replace(topic) - raise TypeError, "#{topic.inspect} is not an Irc::ChannelTopic" unless topic.kind_of?(ChannelTopic) - @text = topic.text.dup - @set_by = topic.set_by.dup - @set_on = topic.set_on.dup - end + # We keep extending String, this time adding a method that converts a + # String into an Irc::User object + # + def to_irc_user(opts={}) + debug "opts = #{opts.inspect}" + Irc::User.new(self, opts) end +end - # Mode on a channel - class ChannelMode - def initialize(ch) - @channel = ch - end - end - +module Irc - # Channel modes of type A manipulate lists + # An IRC Channel is identified by its name, and it has a set of properties: + # * a Channel::Topic + # * a UserList + # * a set of Channel::Modes # - class ChannelModeTypeA < ChannelMode - def initialize(ch) - super - @list = NetmaskList.new - end + # The Channel::Topic and Channel::Mode classes are defined within the + # Channel namespace because they only make sense there + # + class Channel - def set(val) - nm = @channel.server.new_netmask(val) - @list << nm unless @list.include?(nm) - end - def reset(val) - nm = @channel.server.new_netmask(val) - @list.delete(nm) - end - end + # Mode on a Channel + # + class Mode + def initialize(ch) + @channel = ch + end - # Channel modes of type B need an argument - # - class ChannelModeTypeB < ChannelMode - def initialize(ch) - super - @arg = nil end - def set(val) - @arg = val - end - def reset(val) - @arg = nil if @arg == val - end - end + # Channel modes of type A manipulate lists + # + class ModeTypeA < Mode + def initialize(ch) + super + @list = NetmaskList.new + end + + def set(val) + nm = @channel.server.new_netmask(val) + @list << nm unless @list.include?(nm) + end + + def reset(val) + nm = @channel.server.new_netmask(val) + @list.delete(nm) + end - # Channel modes that change the User prefixes are like - # Channel modes of type B, except that they manipulate - # lists of Users, so they are somewhat similar to channel - # modes of type A - # - class ChannelUserMode < ChannelModeTypeB - def initialize(ch) - super - @list = UserList.new end - def set(val) - u = @channel.server.user(val) - @list << u unless @list.include?(u) + + # Channel modes of type B need an argument + # + class ModeTypeB < Mode + def initialize(ch) + super + @arg = nil + end + + def set(val) + @arg = val + end + + def reset(val) + @arg = nil if @arg == val + end + end - def reset(val) - u = @channel.server.user(val) - @list.delete(u) + + # Channel modes that change the User prefixes are like + # Channel modes of type B, except that they manipulate + # lists of Users, so they are somewhat similar to channel + # modes of type A + # + class UserMode < ModeTypeB + def initialize(ch) + super + @list = UserList.new + end + + def set(val) + u = @channel.server.user(val) + @list << u unless @list.include?(u) + end + + def reset(val) + u = @channel.server.user(val) + @list.delete(u) + end + end - end - # Channel modes of type C need an argument when set, - # but not when they get reset - # - class ChannelModeTypeC < ChannelMode - def initialize(ch) - super - @arg = false + + # Channel modes of type C need an argument when set, + # but not when they get reset + # + class ModeTypeC < Mode + def initialize(ch) + super + @arg = false + end + + def status + @arg + end + + def set(val) + @arg = val + end + + def reset + @arg = false + end + end - def set(val) - @arg = val + + # Channel modes of type D are basically booleans + # + class ModeTypeD < Mode + def initialize(ch) + super + @set = false + end + + def set? + return @set + end + + def set + @set = true + end + + def reset + @set = false + end + end - def reset - @arg = false + + # A Topic represents the topic of a channel. It consists of + # the topic itself, who set it and when + # + class Topic + attr_accessor :text, :set_by, :set_on + alias :to_s :text + + # Create a new Topic setting the text, the creator and + # the creation time + # + def initialize(text="", set_by="", set_on=Time.new) + @text = text + @set_by = set_by.to_irc_user + @set_on = set_on + end + + # Replace a Topic with another one + # + def replace(topic) + raise TypeError, "#{topic.inspect} is not of class #{self.class}" unless topic.kind_of?(self.class) + @text = topic.text.dup + @set_by = topic.set_by.dup + @set_on = topic.set_on.dup + end + + # Returns self + # + def to_irc_channel_topic + self + end + end + end - # Channel modes of type D are basically booleans - class ChannelModeTypeD < ChannelMode - def initialize(ch) - super - @set = false - end +end - def set? - return @set - end - def set - @set = true - end +class String - def reset - @set = false - end + # Returns an Irc::Channel::Topic with self as text + # + def to_irc_channel_topic + Irc::Channel::Topic.new(self) end +end - # An IRC Channel is identified by its name, and it has a set of properties: - # * a topic - # * a UserList - # * a set of modes + +module Irc + + + # Here we start with the actual Channel class # class Channel - attr_reader :name, :topic, :mode, :users, :server + + include ServerOrCasemap + attr_reader :name, :topic, :mode, :users alias :to_s :name def inspect str = "<#{self.class}:#{'0x%x' % self.object_id}:" - str << " on server #{server}" + str << " on server #{server}" if server str << " @name=#{@name.inspect} @topic=#{@topic.text.inspect}" str << " @users=<#{@users.sort.join(', ')}>" str end + # Returns self + # + def to_irc_channel + self + end + # Creates a new channel with the given name, optionally setting the topic # and an initial users list. # # No additional info is created here, because the channel flags and userlists # allowed depend on the server. # - # FIXME doesn't check if users have the same casemap as the channel yet - # - def initialize(server, name, topic=nil, users=[]) - raise TypeError, "First parameter must be an Irc::Server" unless server.kind_of?(Server) + def initialize(name, topic=nil, users=[], opts={}) raise ArgumentError, "Channel name cannot be empty" if name.to_s.empty? - raise ArgumentError, "Unknown channel prefix #{name[0].chr}" if name !~ /^[&#+!]/ + warn "Unknown channel prefix #{name[0].chr}" if name !~ /^[&#+!]/ raise ArgumentError, "Invalid character in #{name.inspect}" if name =~ /[ \x07,]/ - @server = server + init_server_or_casemap(opts) - @name = name.irc_downcase(casemap) + @name = name - @topic = topic || ChannelTopic.new + @topic = (topic.to_irc_channel_topic rescue Channel::Topic.new) - case users - when UserList - @users = users - when Array - @users = UserList.new(users) - else - raise ArgumentError, "Invalid user list #{users.inspect}" - end + @users = UserList.new + + users.each { |u| + @users << u.to_irc_user(server_and_casemap) + } # Flags @mode = {} end - # Returns the casemap of the originating server - def casemap - return @server.casemap - end - # Removes a user from the channel # def delete_user(user) @mode.each { |sym, mode| - mode.reset(user) if mode.kind_of?(ChannelUserMode) + mode.reset(user) if mode.kind_of?(UserMode) } @users.delete(user) end @@ -829,7 +1081,7 @@ module Irc name[0] = 0x21 end - # A channel is safe if it has the '#' prefix + # A channel is normal if it has the '#' prefix # def normal? name[0] = 0x23 @@ -840,6 +1092,7 @@ module Irc def create_mode(sym, kl) @mode[sym.to_sym] = kl.new(self) end + end @@ -849,11 +1102,30 @@ module Irc # Create a new ChannelList, optionally filling it with the elements from # the Array argument fed to it. + # def initialize(ar=[]) super(Channel, ar) end + + end + +end + + +class String + + # We keep extending String, this time adding a method that converts a + # String into an Irc::Channel object + # + def to_irc_channel(opts={}) + Irc::Channel.new(self, opts) end +end + + +module Irc + # An IRC Server represents the Server the client is connected to. # @@ -866,31 +1138,32 @@ module Irc attr_reader :channels, :users def channel_names - @channels.map { |ch| ch.name } + @channels.map { |ch| ch.downcase } end def user_nicks - @users.map { |u| u.nick } + @users.map { |u| u.downcase } end def inspect - chans = @channels.map { |ch| - ch.inspect + chans, users = [@channels, @users].map {|d| + d.sort { |a, b| + a.downcase <=> b.downcase + }.map { |x| + x.inspect + } } - users = @users.map { |u| - u.inspect - }.sort str = "<#{self.class}:#{'0x%x' % self.object_id}:" + str << " @hostname=#{hostname}" str << " @channels=#{chans}" str << " @users=#{users}>" str end - # Create a new Server, with all instance variables reset - # to nil (for scalar variables), the channel and user lists - # are empty, and @supports is initialized to the default values - # for all known supported features. + # 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. # def initialize @hostname = @version = @usermodes = @chanmodes = nil @@ -1002,10 +1275,6 @@ module Irc when :casemapping, :network noval_warn(key, val) { @supports[key] = val - @users.each { |u| - debug "Resetting casemap of #{u} from #{u.casemap} to #{val}" - u.casemap = val - } } when :chanlimit, :idchan, :maxlist, :targmax noval_warn(key, val) { @@ -1083,7 +1352,7 @@ module Irc # Returns the casemap of the server. # def casemap - @supports[:casemapping] || 'rfc1459' + @supports[:casemapping] end # Returns User or Channel depending on what _name_ can be @@ -1110,24 +1379,22 @@ module Irc # Checks if the receiver already has a channel with the given _name_ # def has_channel?(name) - channel_names.index(name.to_s) + channel_names.index(name.downcase) end alias :has_chan? :has_channel? # Returns the channel with name _name_, if available # def get_channel(name) - idx = channel_names.index(name.to_s) + idx = has_channel?(name) channels[idx] if idx end alias :get_chan :get_channel - # Create a new Channel object and add it to the list of - # Channels on the receiver, unless the channel - # was present already. In this case, the default action is - # to raise an exception, unless _fails_ is set to false - # - # The Channel is automatically created with the appropriate casemap + # Create a new Channel object bound to the receiver and add it to the + # list of Channels on the receiver, unless the channel was + # present already. In this case, the default action is to raise an + # exception, unless _fails_ is set to false # def new_channel(name, topic=nil, users=[], fails=true) ex = get_chan(name) @@ -1159,13 +1426,13 @@ module Irc # So far, everything is fine. Now create the actual Channel # - chan = Channel.new(self, name, topic, users) + chan = Channel.new(name, topic, users, :server => self) # We wade through +prefix+ and +chanmodes+ to create appropriate # lists and flags for this channel @supports[:prefix][:modes].each { |mode| - chan.create_mode(mode, ChannelUserMode) + chan.create_mode(mode, Channel::UserMode) } if @supports[:prefix][:modes] @supports[:chanmodes].each { |k, val| @@ -1173,19 +1440,19 @@ module Irc case k when :typea val.each { |mode| - chan.create_mode(mode, ChannelModeTypeA) + chan.create_mode(mode, Channel::ModeTypeA) } when :typeb val.each { |mode| - chan.create_mode(mode, ChannelModeTypeB) + chan.create_mode(mode, Channel::ModeTypeB) } when :typec val.each { |mode| - chan.create_mode(mode, ChannelModeTypeC) + chan.create_mode(mode, Channel::ModeTypeC) } when :typed val.each { |mode| - chan.create_mode(mode, ChannelModeTypeD) + chan.create_mode(mode, Channel::ModeTypeD) } end end @@ -1216,31 +1483,23 @@ module Irc # Checks if the receiver already has a user with the given _nick_ # def has_user?(nick) - user_nicks.index(nick.to_s) + user_nicks.index(nick.downcase) end # Returns the user with nick _nick_, if available # def get_user(nick) - idx = user_nicks.index(nick.to_s) + idx = has_user?(nick) @users[idx] if idx end - # Create a new User object and add it to the list of - # Users on the receiver, unless the User - # was present already. In this case, the default action is - # to raise an exception, unless _fails_ is set to false - # - # The User is automatically created with the appropriate casemap + # Create a new User object bound to the receiver and add it to the list + # of Users on the receiver, unless the User was present + # already. In this case, the default action is to raise an exception, + # unless _fails_ is set to false # def new_user(str, fails=true) - case str - when User - tmp = str - else - tmp = User.new(str, self.casemap) - end - # debug "Creating or selecting user #{tmp.inspect} from #{str.inspect}" + tmp = str.to_irc_user(:server => self) old = get_user(tmp.nick) if old # debug "User already existed as #{old.inspect}" @@ -1251,8 +1510,7 @@ module Irc raise "User #{tmp} already exists on server #{self}" if fails end if old != tmp - old.user = tmp.user - old.host = tmp.host + old.replace(tmp) # debug "User improved to #{old.inspect}" end end @@ -1269,19 +1527,22 @@ module Irc # new_user(_str_, +false+) # def user(str) - u = new_user(str, false) - debug "Server user #{u.inspect} from #{str.inspect}" - u + new_user(str, false) + end + + # Deletes User _user_ from Channel _channel_ + # + def delete_user_from_channel(user, channel) + channel.delete_user(user) end # Remove User _someuser_ from the list of Users. # _someuser_ must be specified with the full Netmask. # def delete_user(someuser) - idx = has_user?(someuser.nick) + idx = has_user?(someuser) raise "Tried to remove unmanaged user #{user}" unless idx have = self.user(someuser) - raise "User #{someuser.nick} has inconsistent Netmasks! #{self} knows #{have} but access was tried with #{someuser}" if have != someuser && have.user != "*" && have.host != "*" @channels.each { |ch| delete_user_from_channel(have, ch) } @@ -1291,11 +1552,7 @@ module Irc # Create a new Netmask object with the appropriate casemap # def new_netmask(str) - if str.kind_of?(Netmask ) - raise "Wrong casemap for Netmask #{str.inspect}" if str.casemap != self.casemap - return str - end - Netmask.new(str, self.casemap) + str.to_irc_netmask(:server => self) end # Finds all Users on server whose Netmask matches _mask_ @@ -1305,7 +1562,7 @@ module Irc @users.inject(UserList.new) { |list, user| if user.user == "*" or user.host == "*" - list << user if user.nick =~ nm.nick.to_irc_regexp + list << user if user.nick.downcase =~ nm.nick.downcase.to_irc_regexp else list << user if user.matches?(nm) end @@ -1313,16 +1570,7 @@ module Irc } end - # Deletes User from Channel - # - def delete_user_from_channel(user, channel) - channel.delete_user(user) - end - end end -if $0 == __FILE__ - puts Casemap.gets('rfc1459') -end diff --git a/lib/rbot/rfc2812.rb b/lib/rbot/rfc2812.rb index 8a83ec88..af88d4be 100644 --- a/lib/rbot/rfc2812.rb +++ b/lib/rbot/rfc2812.rb @@ -820,8 +820,8 @@ module Irc # create a new IrcClient instance def initialize - @server = Server.new # The Server - @client = User.new # The User representing the client on this Server + @server = Server.new # The Server + @client = @server.user("") # The User representing the client on this Server @handlers = Hash.new @@ -1002,12 +1002,13 @@ module Irc chan = @server.get_channel(data[:channel]) unless chan - warning "Received topic #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on" + warning "Received names #{data[:topic].inspect} for channel #{data[:channel].inspect} I was not on" return end users = [] argv[3].scan(/\S+/).each { |u| + # FIXME beware of servers that allow multiple prefixes if(u =~ /^(#{@server.supports[:prefix][:prefixes].join})?(.*)$/) umode = $1 user = $2 @@ -1111,7 +1112,7 @@ module Irc when User handle(:notice, data) else - # "server notice" (not from user, noone to reply to + # "server notice" (not from user, noone to reply to) handle(:snotice, data) end when 'KICK' @@ -1163,6 +1164,7 @@ module Irc when 'NICK' data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch| list << ch if ch.users.include?(data[:source]) + list } data[:newnick] = argv[0] -- cgit v1.2.3