class Irc::Server

An IRC Server represents the Server the client is connected to.

Attributes

capabilities[R]
chanmodes[R]
channels[R]
hostname[R]
supports[R]
to_s[R]
usermodes[R]
users[R]
version[R]

Public Class Methods

new() click to toggle source

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.

# File lib/rbot/irc.rb, line 1563
def initialize
  @hostname = @version = @usermodes = @chanmodes = nil

  @channels = ChannelList.new

  @users = UserList.new

  reset_capabilities
end

Public Instance Methods

casemap() click to toggle source

Returns the casemap of the server.

# File lib/rbot/irc.rb, line 1786
def casemap
  @supports[:casemapping]
end
channel(str) click to toggle source

Returns the Channel with the given name on the server, creating it if necessary. This is a short form for #new_channel(str, nil, [], false)

# File lib/rbot/irc.rb, line 1912
def channel(str)
  new_channel(str,nil,[],false)
end
channel_names() click to toggle source

TODO Ho

# File lib/rbot/irc.rb, line 1534
def channel_names
  @channels.map { |ch| ch.downcase }
end
clear() click to toggle source

Clears the server

# File lib/rbot/irc.rb, line 1637
def clear
  reset_lists
  reset_capabilities
  @hostname = @version = @usermodes = @chanmodes = nil
end
delete_channel(name) click to toggle source

Remove Channel name from the list of Channels

# File lib/rbot/irc.rb, line 1918
def delete_channel(name)
  idx = has_channel?(name)
  raise "Tried to remove unmanaged channel #{name}" unless idx
  @channels.delete_at(idx)
end
delete_user(someuser) click to toggle source

Remove User someuser from the list of Users. someuser must be specified with the full Netmask.

# File lib/rbot/irc.rb, line 1993
def delete_user(someuser)
  idx = has_user?(someuser)
  raise "Tried to remove unmanaged user #{user}" unless idx
  have = self.user(someuser)
  @channels.each { |ch|
    delete_user_from_channel(have, ch)
  }
  @users.delete_at(idx)
end
delete_user_from_channel(user, channel) click to toggle source

Deletes User user from Channel channel

# File lib/rbot/irc.rb, line 1986
def delete_user_from_channel(user, channel)
  channel.delete_user(user)
end
find_users(mask) click to toggle source

Finds all Users on server whose Netmask matches mask

# File lib/rbot/irc.rb, line 2011
def find_users(mask)
  nm = new_netmask(mask)
  @users.inject(UserList.new) {
    |list, user|
    if user.user == "*" or user.host == "*"
      list << user if user.nick.irc_downcase(casemap) =~ nm.nick.irc_downcase(casemap).to_irc_regexp
    else
      list << user if user.matches?(nm)
    end
    list
  }
end
get_chan(name) click to toggle source
Alias for: get_channel
get_channel(name) click to toggle source

Returns the channel with name name, if available

# File lib/rbot/irc.rb, line 1821
def get_channel(name)
  return nil if name.nil_or_empty?
  idx = has_channel?(name)
  channels[idx] if idx
end
Also aliased as: get_chan
get_user(nick) click to toggle source

Returns the user with nick nick, if available

# File lib/rbot/irc.rb, line 1933
def get_user(nick)
  idx = has_user?(nick)
  @users[idx] if idx
end
has_chan?(name) click to toggle source
Alias for: has_channel?
has_channel?(name) click to toggle source

Checks if the receiver already has a channel with the given name

# File lib/rbot/irc.rb, line 1813
def has_channel?(name)
  return false if name.nil_or_empty?
  channel_names.index(name.irc_downcase(casemap))
end
Also aliased as: has_chan?
has_user?(nick) click to toggle source

Checks if the receiver already has a user with the given nick

# File lib/rbot/irc.rb, line 1926
def has_user?(nick)
  return false if nick.nil_or_empty?
  user_nicks.index(nick.irc_downcase(casemap))
end
inspect() click to toggle source
# File lib/rbot/irc.rb, line 1543
def inspect
  chans, users = [@channels, @users].map {|d|
    d.sort { |a, b|
      a.downcase <=> b.downcase
    }.map { |x|
      x.inspect
    }
  }

  str = self.__to_s__[0..-2]
  str << " @hostname=#{hostname}"
  str << " @channels=#{chans}"
  str << " @users=#{users}"
  str << ">"
end
mode_for_prefix(pfx) click to toggle source

Convert a prefix (@, +, %, …) to the corresponding mode (o, v, h, …). See also #prefix_for_mode

# File lib/rbot/irc.rb, line 1618
def mode_for_prefix(pfx)
  return @supports[:prefix][:modes][
    @supports[:prefix][:prefixes].index(pfx.to_sym)
  ]
end
new_channel(name, topic=nil, users=[], fails=true) click to toggle source

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. An exception can also be raised if str is nil or empty, again only if fails is set to true; otherwise, the method just returns nil

# File lib/rbot/irc.rb, line 1835
def new_channel(name, topic=nil, users=[], fails=true)
  if name.nil_or_empty?
    raise "Tried to look for empty or nil channel name #{name.inspect}" if fails
    return nil
  end
  ex = get_chan(name)
  if ex
    raise "Channel #{name} already exists on server #{self}" if fails
    return ex
  else

    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]

    # Next, we check if we hit the limit for channels of type +prefix+
    # if the server supports +chanlimit+
    #
    @supports[:chanlimit].keys.each { |k|
      next unless k.include?(prefix)
      count = 0
      channel_names.each { |n|
        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]
    }

    # So far, everything is fine. Now create the actual Channel
    #
    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, Channel::UserMode)
    } if @supports[:prefix][:modes]

    @supports[:chanmodes].each { |k, val|
      if val
        case k
        when :typea
          val.each { |mode|
            chan.create_mode(mode, Channel::ModeTypeA)
          }
        when :typeb
          val.each { |mode|
            chan.create_mode(mode, Channel::ModeTypeB)
          }
        when :typec
          val.each { |mode|
            chan.create_mode(mode, Channel::ModeTypeC)
          }
        when :typed
          val.each { |mode|
            chan.create_mode(mode, Channel::ModeTypeD)
          }
        end
      end
    }

    @channels << chan
    # debug "Created channel #{chan.inspect}"
    return chan
  end
end
new_netmask(str) click to toggle source

Create a new Netmask object with the appropriate casemap

# File lib/rbot/irc.rb, line 2005
def new_netmask(str)
  str.to_irc_netmask(:server => self)
end
new_user(str, fails=true) click to toggle source

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. An exception can also be raised if str is nil or empty, again only if fails is set to true; otherwise, the method just returns nil

# File lib/rbot/irc.rb, line 1945
def new_user(str, fails=true)
  if str.nil_or_empty?
    raise "Tried to look for empty or nil user name #{str.inspect}" if fails
    return nil
  end
  tmp = str.to_irc_user(:server => self)
  old = get_user(tmp.nick)
  # debug "Tmp: #{tmp.inspect}"
  # debug "Old: #{old.inspect}"
  if old
    # debug "User already existed as #{old.inspect}"
    if tmp.known?
      if old.known?
        # debug "Both were known"
        # Do not raise an error: things like Freenode change the hostname after identification
        warning "User #{tmp.nick} has inconsistent Netmasks! #{self} knows #{old.inspect} but access was tried with #{tmp.inspect}" if old != tmp
        raise "User #{tmp} already exists on server #{self}" if fails
      end
      if old.fullform.downcase != tmp.fullform.downcase
        old.replace(tmp)
        # debug "Known user now #{old.inspect}"
      end
    end
    return old
  else
    warn "#{self} doesn't support nicknames this long (#{tmp.nick.length} > #{@supports[:nicklen]})" unless tmp.nick.length <= @supports[:nicklen]
    @users << tmp
    return @users.last
  end
end
parse_isupport(line) click to toggle source

This method is used to parse a 005 RPL_ISUPPORT line

See the RPL_ISUPPORT draft

# File lib/rbot/irc.rb, line 1674
def parse_isupport(line)
  debug "Parsing ISUPPORT #{line.inspect}"
  ar = line.split(' ')
  reparse = []
  ar.each { |en|
    prekey, val = en.split('=', 2)
    if prekey =~ %r^-(.*)/
      key = $1.downcase.to_sym
      val = false
    else
      key = prekey.downcase.to_sym
    end
    case key
    when :casemapping
      noval_warn(key, val) {
        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) {
        groups = val.split(',')
        groups.each { |g|
          k, v = g.split(':')
          @supports[key][k] = v.to_i || 0
          if @supports[key][k] == 0
            warn "Deleting #{key} limit of 0 for #{k}"
            @supports[key].delete(k)
          end
        }
      }
    when :chanmodes
      noval_warn(key, val) {
        groups = val.split(',')
        @supports[key][:typea] = groups[0].scan(%r./).map { |x| x.to_sym}
        @supports[key][:typeb] = groups[1].scan(%r./).map { |x| x.to_sym}
        @supports[key][:typec] = groups[2].scan(%r./).map { |x| x.to_sym}
        @supports[key][:typed] = groups[3].scan(%r./).map { |x| x.to_sym}
      }
    when :channellen, :kicklen, :modes, :topiclen
      if val
        @supports[key] = val.to_i
      else
        @supports[key] = nil
      end
    when :chantypes
      @supports[key] = val # can also be nil
    when :excepts
      val ||= 'e'
      @supports[key] = val
    when :invex
      val ||= 'I'
      @supports[key] = val
    when :maxchannels
      noval_warn(key, val) {
        reparse << "CHANLIMIT=(chantypes):#{val} "
      }
    when :maxtargets
      noval_warn(key, val) {
        @supports[:targmax]['PRIVMSG'] = val.to_i
        @supports[:targmax]['NOTICE'] = val.to_i
      }
    when :network
      noval_warn(key, val) {
        @supports[key] = val
      }
    when :nicklen
      noval_warn(key, val) {
        @supports[key] = val.to_i
      }
    when :prefix
      if val
        val.scan(%r\((.*)\)(.*)/) { |m, p|
          @supports[key][:modes] = m.scan(%r./).map { |x| x.to_sym}
          @supports[key][:prefixes] = p.scan(%r./).map { |x| x.to_sym}
        }
      else
        @supports[key][:modes] = nil
        @supports[key][:prefixes] = nil
      end
    when :safelist
      val_warn(key, val) {
        @supports[key] = val.nil? ? true : val
      }
    when :statusmsg
      noval_warn(key, val) {
        @supports[key] = val.scan(%r./)
      }
    when :std
      noval_warn(key, val) {
        @supports[key] = val.split(',')
      }
    else
      @supports[key] =  val.nil? ? true : val
    end
  }
  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
parse_my_info(line) click to toggle source

This method is used to parse a 004 RPL_MY_INFO line

# File lib/rbot/irc.rb, line 1645
def parse_my_info(line)
  ar = line.split(' ')
  @hostname = ar[0]
  @version = ar[1]
  @usermodes = ar[2]
  @chanmodes = ar[3]
end
prefix_for_mode(mode) click to toggle source

Convert a mode (o, v, h, …) to the corresponding prefix (@, +, %, …). See also #mode_for_prefix

# File lib/rbot/irc.rb, line 1610
def prefix_for_mode(mode)
  return @supports[:prefix][:prefixes][
    @supports[:prefix][:modes].index(mode.to_sym)
  ]
end
reset_capabilities() click to toggle source

Resets the server capabilities

# File lib/rbot/irc.rb, line 1575
def reset_capabilities
  @supports = {
    :casemapping => 'rfc1459'.to_irc_casemap,
    :chanlimit => {},
    :chanmodes => {
      :typea => nil, # Type A: address lists
      :typeb => nil, # Type B: needs a parameter
      :typec => nil, # Type C: needs a parameter when set
      :typed => nil  # Type D: must not have a parameter
    },
    :channellen => 50,
    :chantypes => "#&!+",
    :excepts => nil,
    :idchan => {},
    :invex => nil,
    :kicklen => nil,
    :maxlist => {},
    :modes => 3,
    :network => nil,
    :nicklen => 9,
    :prefix => {
      :modes => [:o, :v],
      :prefixes => [:"@", :+]
    },
    :safelist => nil,
    :statusmsg => nil,
    :std => nil,
    :targmax => {},
    :topiclen => nil
  }
  @capabilities = {}
end
reset_lists() click to toggle source

Resets the Channel and User list

# File lib/rbot/irc.rb, line 1626
def reset_lists
  @users.reverse_each { |u|
    delete_user(u)
  }
  @channels.reverse_each { |u|
    delete_channel(u)
  }
end
user(str) click to toggle source

Returns the User with the given Netmask on the server, creating it if necessary. This is a short form for #new_user(str, false)

# File lib/rbot/irc.rb, line 1980
def user(str)
  new_user(str, false)
end
user_nicks() click to toggle source

TODO Ho

# File lib/rbot/irc.rb, line 1539
def user_nicks
  @users.map { |u| u.downcase }
end
user_or_channel(name) click to toggle source

Returns the actual User or Channel object matching name

# File lib/rbot/irc.rb, line 1803
def user_or_channel(name)
  if supports[:chantypes].include?(name[0])
    return channel(name)
  else
    return user(name)
  end
end
user_or_channel?(name) click to toggle source

Returns User or Channel depending on what name can be a name of

# File lib/rbot/irc.rb, line 1793
def user_or_channel?(name)
  if supports[:chantypes].include?(name[0])
    return Channel
  else
    return User
  end
end