### Home Classes Methods

### In Files

- lib/rbot/rfc2812.rb

### Parent

Object

### Methods

- ::new
- #[]=
- #deletehandler
- #handle
- #parse_mode
- #process
- #reset

### Class/Module Index

Quicksearch

- Irc
- Irc::AsciiCasemap
- Irc::BanlistMessage
- Irc::BasicUserMessage
- Irc::Bot
- Irc::Bot::Auth
- Irc::Bot::Auth::BotOwnerClass
- Irc::Bot::Auth::BotUser
- Irc::Bot::Auth::Command
- Irc::Bot::Auth::DefaultBotUserClass
- Irc::Bot::Auth::InvalidPassword
- Irc::Bot::Auth::ManagerClass
- Irc::Bot::Auth::PermissionSet
- Irc::Bot::Config
- Irc::Bot::Config::ArrayValue
- Irc::Bot::Config::BooleanValue
- Irc::Bot::Config::EnumValue
- Irc::Bot::Config::FloatValue
- Irc::Bot::Config::IntegerValue
- Irc::Bot::Config::ManagerClass
- Irc::Bot::Config::StringValue
- Irc::Bot::Config::Value
- Irc::Bot::Config::Wizard
- Irc::Bot::DataFilter
- Irc::Bot::DataStream
- Irc::Bot::Language
- Irc::Bot::MessageMapper
- Irc::Bot::MessageMapper::Failure
- Irc::Bot::MessageMapper::FriendlyFailure
- Irc::Bot::MessageMapper::NoActionFailure
- Irc::Bot::MessageMapper::NoMatchFailure
- Irc::Bot::MessageMapper::NotPrivateFailure
- Irc::Bot::MessageMapper::NotPublicFailure
- Irc::Bot::MessageMapper::PartialMatchFailure
- Irc::Bot::MessageParameter
- Irc::Bot::MessageTemplate
- Irc::Bot::Plugins
- Irc::Bot::Plugins::BotModule
- Irc::Bot::Plugins::CoreBotModule
- Irc::Bot::Plugins::Plugin
- Irc::Bot::Plugins::PluginManagerClass
- Irc::Bot::Plugins::RecurseTooDeep
- Irc::Bot::Plugins::RemoteBotModule
- Irc::Bot::Plugins::RemoteCoreBotModule
- Irc::Bot::Plugins::RemotePlugin
- Irc::Bot::Registry
- Irc::Bot::Registry::Accessor
- Irc::Bot::RemoteDispatcher
- Irc::Bot::RemoteMessage
- Irc::Bot::RemoteObject
- Irc::Bot::Wordlist
- Irc::BotConfig
- Irc::Casemap
- Irc::Channel
- Irc::Channel::Mode
- Irc::Channel::ModeHash
- Irc::Channel::ModeTypeA
- Irc::Channel::ModeTypeB
- Irc::Channel::ModeTypeC
- Irc::Channel::ModeTypeD
- Irc::Channel::Topic
- Irc::Channel::UserMode
- Irc::ChannelList
- Irc::Client
- Irc::DBHash
- Irc::DBTree
- Irc::InviteMessage
- Irc::JoinMessage
- Irc::KickMessage
- Irc::MessageQueue
- Irc::ModeChangeMessage
- Irc::MotdMessage
- Irc::NamesMessage
- Irc::Netmask
- Irc::NetmaskDb
- Irc::NetmaskDb::Tree
- Irc::NetmaskList
- Irc::NickMessage
- Irc::NoSuchTargetMessage
- Irc::NoticeMessage
- Irc::PartMessage
- Irc::PrivMessage
- Irc::QueueRing
- Irc::QuitMessage
- Irc::RfcCasemap
- Irc::Server
- Irc::ServerOrCasemap
- Irc::Socket
- Irc::Socket::IdentityFilter
- Irc::StrictRfcCasemap
- Irc::TopicMessage
- Irc::UnknownMessage
- Irc::User
- Irc::UserList
- Irc::UserMessage
- Irc::Utils
- Irc::Utils::HttpUtil
- Irc::Utils::HttpUtil::CachedObject
- Irc::Utils::ParseTime
- Irc::WelcomeMessage
- Irc::WhoisMessage
- BDB
- BDB::CIBtree
- MonitorMixin
- MonitorMixin::ConditionVariable
- Net
- Net::HTTPResponse
- Regexp
- Regexp::Irc
- Singleton
- Singleton::SingletonClassMethods
- Timer
- Timer::Action
- TokyoCabinet
- TokyoCabinet::CIBDB
- UnicodePlugin
- UnicodePlugin::UnicodeFilter
- Array
- ArrayOf
- AuthModule
- BasicsModule
- ConditionVariable
- ConfigModule
- DottedIndex
- Enumerable
- Exception
- FiltersModule
- GetText
- GetTextVersionError
- HttpUtilPlugin
- IrcLogModule
- Module
- Numeric
- Object
- Range
- RemoteModule
- ServerError
- String
- Symbol
- UserDataModule
- WordlistModule
- YAML

No matching classes.

# Irc::Client

Implements RFC 2812 and prior IRC RFCs.

Clients should register Proc{}s to handle the various server events, and the Client class will handle dispatch.

### Attributes

server[R]

the Server we're connected to

user[R]

the User representing us on that server

### Public Class Methods

new() click to toggle source

Create a new Client instance

    # File lib/rbot/rfc2812.rb, line 961
    def initialize
      @server = Server.new         # The Server
      @user = @server.user("*!*@*")     # The User representing the client on this Server

      @handlers = Hash.new

      # This is used by some messages to build lists of users that
      # will be delegated when the ENDOF... message is received
      @tmpusers = []

      # Same as above, just for bans
      @tmpbans = []
    end

### Public Instance Methods

[]=(key, value) click to toggle source

key | server event to handle
value | proc object called when event occurs

set a handler for a server event

## server events currently supported:

TODO handle errors ERR_CHANOPRIVSNEEDED, ERR_CANNOTSENDTOCHAN

welcome | server welcome message on connect
yourhost | your host details (on connection)
created | when the server was started
isupport | information about what this server supports
ping | server pings you (default handler returns a pong)
nicktaken | you tried to change nick to one that's in use
badnick | you tried to change nick to one that's invalid
topic | someone changed the topic of a channel
topicinfo | on joining a channel or asking for the topic, tells you who set it and when
names | server sends list of channel members when you join
motd | server message of the day
privmsg | privmsg, the core of IRC, a message to you from someone
public | optionally instead of getting privmsg you can hook to only the public ones...
msg | or only the private ones, or both
kick | someone got kicked from a channel
part | someone left a channel
quit | someone quit IRC
join | someone joined a channel
changetopic | the topic of a channel changed
invite | you are invited to a channel
nick | someone changed their nick
mode | a mode change
notice | someone sends you a notice
unknown | any other message not handled by the above

    # File lib/rbot/rfc2812.rb, line 1015
    def []=(key, value)
      @handlers[key] = value
    end

deletehandler(key) click to toggle source

key | event name

remove a handler for a server event

    # File lib/rbot/rfc2812.rb, line 1021
    def deletehandler(key)
      @handlers.delete(key)
    end

process(serverstring) click to toggle source

takes a server string, checks for PING, PRIVMSG, NOTIFY, etc, and parses numeric server replies, calling the appropriate handler for each, and sending it a hash containing the data from the server

    # File lib/rbot/rfc2812.rb, line 1028
    def process(serverstring)
      data = Hash.new
      data[:serverstring] = serverstring

      unless serverstring.chomp =~ /^(:(\S+)\s)?(\S+)(\s(.*))?$/
        raise "Unparseable Server Message!!!: #{serverstring.inspect}"
      end

      prefix, command, params = $2, $3, $5

      if prefix != nil
        # Most servers will send a full nick!user@host prefix for
        # messages from users. Therefore, when the prefix doesn't match this
        # syntax it's usually the server hostname.
        #
        # This is not always true, though, since some servers do not send a
        # full hostmask for user messages.
        #
        if prefix =~ /^#{Regexp::Irc::BANG_AT}$/
          data[:source] = @server.user(prefix)
        else
          if @server.hostname
            if @server.hostname != prefix
              # TODO do we want to be able to differentiate messages that are passed on to us from /other/ servers?
              debug "Origin #{prefix} for message\n\t#{serverstring.inspect}\nis neither a user hostmask nor the server hostname\nI'll pretend that it's from the server anyway"
              data[:source] = @server
            else
              data[:source] = @server
            end
          else
            @server.instance_variable_set(:@hostname, prefix)
            data[:source] = @server
          end
        end
      end

      # split parameters in an array
      argv = []
      params.scan(/(?!:)(\S+)|:(.*)/) { argv << ($1 || $2) } if params

      if command =~ /^(\d+)$/ # Numeric replies
        data[:target] = argv[0]
        # A numeric reply /should/ be directed at the client, except when we're connecting with a used nick, in which case
        # it's directed at '*'
        not_us = !([@user.nick, '*'].include?(data[:target]))
        if not_us
          warning "Server reply #{serverstring.inspect} directed at #{data[:target]} instead of client (#{@user.nick})"
        end

        num=command.to_i
        case num
        when RPL_WELCOME
          data[:message] = argv[1]
          # "Welcome to the Internet Relay Network
          # <nick>!<user>@<host>"
          if not_us
            warning "Server thinks client (#{@user.inspect}) has a different nick"
            @user.nick = data[:target]
          end
          if data[:message] =~ /([^@!\s]+)(?:!([^@!\s]+?))?@(\S+)/
            nick = $1
            user = $2
            host = $3
            warning "Welcome message nick mismatch (#{nick} vs #{data[:target]})" if nick != data[:target]
            @user.user = user if user
            @user.host = host if host
          end
          handle(:welcome, data)
        when RPL_YOURHOST
          # "Your host is <servername>, running version <ver>"
          data[:message] = argv[1]
          handle(:yourhost, data)
        when RPL_CREATED
          # "This server was created <date>"
          data[:message] = argv[1]
          handle(:created, data)
        when RPL_MYINFO
          # "<servername> <version> <available user modes>
          # <available channel modes>"
          @server.parse_my_info(params.split(' ', 2).last)
          data[:servername] = @server.hostname
          data[:version] = @server.version
          data[:usermodes] = @server.usermodes
          data[:chanmodes] = @server.chanmodes
          handle(:myinfo, data)
        when RPL_ISUPPORT
          # "PREFIX=(ov)@+ CHANTYPES=#& :are supported by this server"
          # "MODES=4 CHANLIMIT=#:20 NICKLEN=16 USERLEN=10 HOSTLEN=63
          # TOPICLEN=450 KICKLEN=450 CHANNELLEN=30 KEYLEN=23 CHANTYPES=#
          # PREFIX=(ov)@+ CASEMAPPING=ascii CAPAB IRCD=dancer :are available
          # on this server"
          #
          @server.parse_isupport(argv[1..-2].join(' '))
          handle(:isupport, data)
        when ERR_NICKNAMEINUSE
          # "* <nick> :Nickname is already in use"
          data[:nick] = argv[1]
          data[:message] = argv[2]
          handle(:nicktaken, data)
        when ERR_ERRONEUSNICKNAME
          # "* <nick> :Erroneous nickname"
          data[:nick] = argv[1]
          data[:message] = argv[2]
          handle(:badnick, data)
        when RPL_TOPIC
          data[:channel] = @server.channel(argv[1])
          data[:topic] = argv[2]
          data[:channel].topic.text = data[:topic]

          handle(:topic, data)
        when RPL_TOPIC_INFO
          data[:nick] = @server.user(argv[0])
          data[:channel] = @server.channel(argv[1])

          # This must not be an IRC::User because it might not be an actual User,
          # and we risk overwriting valid User data
          data[:source] = argv[2].to_irc_netmask(:server => @server)

          data[:time] = Time.at(argv[3].to_i)

          data[:channel].topic.set_by = data[:source]
          data[:channel].topic.set_on = data[:time]

          handle(:topicinfo, data)
        when RPL_NAMREPLY
          # "( "=" / "*" / "@" ) <channel>
          # :[ "@" / "+" ] <nick> *( " " [ "@" / "+" ] <nick> )
          # - "@" is used for secret channels, "*" for private
          # channels, and "=" for others (public channels).
          data[:channeltype] = argv[1]
          data[:channel] = chan = @server.channel(argv[2])

          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
              users << [user, umode]
            end
          }

          users.each { |ar|
            u = @server.user(ar[0])
            chan.add_user(u, :silent => true)
            debug "Adding user #{u}"
            if ar[1]
              ms = @server.mode_for_prefix(ar[1].to_sym)
              debug "\twith mode #{ar[1]} (#{ms})"
              chan.mode[ms].set(u)
            end
          }
          @tmpusers += users
        when RPL_ENDOFNAMES
          data[:channel] = @server.channel(argv[1])
          data[:users] = @tmpusers
          handle(:names, data)
          @tmpusers = Array.new
        when RPL_BANLIST
          data[:channel] = @server.channel(argv[1])
          data[:mask] = argv[2]
          data[:by] = argv[3]
          data[:at] = argv[4]
          @tmpbans << data
        when RPL_ENDOFBANLIST
          data[:channel] = @server.channel(argv[1])
          data[:bans] = @tmpbans
          handle(:banlist, data)
          @tmpbans = Array.new
        when RPL_LUSERCLIENT
          # ":There are <integer> users and <integer>
          # services on <integer> servers"
          data[:message] = argv[1]
          handle(:luserclient, data)
        when RPL_LUSEROP
          # "<integer> :operator(s) online"
          data[:ops] = argv[1].to_i
          handle(:luserop, data)
        when RPL_LUSERUNKNOWN
          # "<integer> :unknown connection(s)"
          data[:unknown] = argv[1].to_i
          handle(:luserunknown, data)
        when RPL_LUSERCHANNELS
          # "<integer> :channels formed"
          data[:channels] = argv[1].to_i
          handle(:luserchannels, data)
        when RPL_LUSERME
          # ":I have <integer> clients and <integer> servers"
          data[:message] = argv[1]
          handle(:luserme, data)
        when ERR_NOMOTD
          # ":MOTD File is missing"
          data[:message] = argv[1]
          handle(:motd_missing, data)
        when RPL_LOCALUSERS
          # ":Current local  users: 3  Max: 4"
          data[:message] = argv[1]
          handle(:localusers, data)
        when RPL_GLOBALUSERS
          # ":Current global users: 3  Max: 4"
          data[:message] = argv[1]
          handle(:globalusers, data)
        when RPL_STATSCONN
          # ":Highest connection count: 4 (4 clients) (251 since server was
          # (re)started)"
          data[:message] = argv[1]
          handle(:statsconn, data)
        when RPL_MOTDSTART
          # "<nick> :- <server> Message of the Day -"
          if argv[1] =~ /^-\s+(\S+)\s/
            server = $1
          else
            warning "Server doesn't have an RFC compliant MOTD start."
          end
          @motd = ""
        when RPL_MOTD
          if(argv[1] =~ /^-\s+(.*)$/)
            @motd << $1
            @motd << "\n"
          end
        when RPL_ENDOFMOTD
          data[:motd] = @motd
          handle(:motd, data)
        when RPL_DATASTR
          data[:text] = argv[1]
          handle(:datastr, data)
        when RPL_AWAY
          data[:nick] = user = @server.user(argv[1])
          data[:message] = argv[-1]
          user.away = data[:message]
          handle(:away, data)
        when RPL_WHOREPLY
          data[:channel] = channel = @server.channel(argv[1])
          data[:user] = argv[2]
          data[:host] = argv[3]
          data[:userserver] = argv[4]
          data[:nick] = user = @server.user(argv[5])
          if argv[6] =~ /^(H|G)(\*)?(.*)?$/
            data[:away] = ($1 == 'G')
            data[:ircop] = $2
            data[:modes] = $3.scan(/./).map { |mode|
              m = @server.supports[:prefix][:prefixes].index(mode.to_sym)
              @server.supports[:prefix][:modes][m]
            } rescue []
          else
            warning "Strange WHO reply: #{serverstring.inspect}"
          end
          data[:hopcount], data[:real_name] = argv[7].split(" ", 2)

          user.user = data[:user]
          user.host = data[:host]
          user.away = data[:away] # FIXME doesn't provide the actual message
          # TODO ircop status
          # TODO userserver
          # TODO hopcount
          user.real_name = data[:real_name]

          channel.add_user(user, :silent=>true)
          data[:modes].map { |mode|
            channel.mode[mode].set(user)
          }

          handle(:who, data)
        when RPL_ENDOFWHO
          handle(:eowho, data)
        when RPL_WHOISUSER
          @whois ||= Hash.new
          @whois[:nick] = argv[1]
          @whois[:user] = argv[2]
          @whois[:host] = argv[3]
          @whois[:real_name] = argv[-1]

          user = @server.user(@whois[:nick])
          user.user = @whois[:user]
          user.host = @whois[:host]
          user.real_name = @whois[:real_name]
        when RPL_WHOISSERVER
          @whois ||= Hash.new
          @whois[:nick] = argv[1]
          @whois[:server] = argv[2]
          @whois[:server_info] = argv[-1]
          # TODO update user info
        when RPL_WHOISOPERATOR
          @whois ||= Hash.new
          @whois[:nick] = argv[1]
          @whois[:operator] = argv[-1]
          # TODO update user info
        when RPL_WHOISIDLE
          @whois ||= Hash.new
          @whois[:nick] = argv[1]
          user = @server.user(@whois[:nick])
          @whois[:idle] = argv[2].to_i
          user.idle_since = Time.now - @whois[:idle]
          if argv[-1] == 'seconds idle, signon time'
            @whois[:signon] = Time.at(argv[3].to_i)
            user.signon = @whois[:signon]
          end
        when RPL_ENDOFWHOIS
          @whois ||= Hash.new
          @whois[:nick] = argv[1]
          data[:whois] = @whois.dup
          @whois.clear
          handle(:whois, data)
        when RPL_WHOISCHANNELS
          @whois ||= Hash.new
          @whois[:nick] = argv[1]
          @whois[:channels] ||= []
          user = @server.user(@whois[:nick])
          argv[-1].split.each do |prechan|
            pfx = prechan.scan(/[#{@server.supports[:prefix][:prefixes].join}]/)
            modes = pfx.map { |p| @server.mode_for_prefix p }
            chan = prechan[pfx.length..prechan.length]

            channel = @server.channel(chan)
            channel.add_user(user, :silent => true)
            modes.map { |mode| channel.mode[mode].set(user) }

            @whois[:channels] << [chan, modes]
          end
        when RPL_CHANNELMODEIS
          parse_mode(serverstring, argv[1..-1], data)
          handle(:mode, data)
        when RPL_CREATIONTIME
          data[:channel] = @server.channel(argv[1])
          data[:time] = Time.at(argv[2].to_i)
          data[:channel].creation_time=data[:time]
          handle(:creationtime, data)
        when RPL_CHANNEL_URL
          data[:channel] = @server.channel(argv[1])
          data[:url] = argv[2]
          data[:channel].url=data[:url].dup
          handle(:channel_url, data)
        when ERR_NOSUCHNICK
          data[:target] = argv[1]
          data[:message] = argv[2]
          handle(:nosuchtarget, data)
          if user = @server.get_user(data[:target])
            @server.delete_user(user)
          end
        when ERR_NOSUCHCHANNEL
          data[:target] = argv[1]
          data[:message] = argv[2]
          handle(:nosuchtarget, data)
          if channel = @server.get_channel(data[:target])
            @server.delete_channel(channel)
          end
        else
          warning "Unknown message #{serverstring.inspect}"
          handle(:unknown, data)
        end
        return # We've processed the numeric reply
      end

      # Otherwise, the command should be a single word
      case command.to_sym
      when :PING
        data[:pingid] = argv[0]
        handle(:ping, data)
      when :PONG
        data[:pingid] = argv[0]
        handle(:pong, data)
      when :PRIVMSG
        # you can either bind to 'PRIVMSG', to get every one and
        # parse it yourself, or you can bind to 'MSG', 'PUBLIC',
        # etc and get it all nicely split up for you.

        begin
          data[:target] = @server.user_or_channel(argv[0])
        rescue
          # The previous may fail e.g. when the target is a server or something
          # like that (e.g. $<mask>). In any of these cases, we just use the
          # String as a target
          # FIXME we probably want to explicitly check for the #<mask> $<mask>
          data[:target] = argv[0]
        end
        data[:message] = argv[1]
        handle(:privmsg, data)

        # Now we split it
        if data[:target].kind_of?(Channel)
          handle(:public, data)
        else
          handle(:msg, data)
        end
      when :NOTICE
        begin
          data[:target] = @server.user_or_channel(argv[0])
        rescue
          # The previous may fail e.g. when the target is a server or something
          # like that (e.g. $<mask>). In any of these cases, we just use the
          # String as a target
          # FIXME we probably want to explicitly check for the #<mask> $<mask>
          data[:target] = argv[0]
        end
        data[:message] = argv[1]
        case data[:source]
        when User
          handle(:notice, data)
        else
          # "server notice" (not from user, noone to reply to)
          handle(:snotice, data)
        end
      when :KICK
        data[:channel] = @server.channel(argv[0])
        data[:target] = @server.user(argv[1])
        data[:message] = argv[2]

        @server.delete_user_from_channel(data[:target], data[:channel])
        if data[:target] == @user
          @server.delete_channel(data[:channel])
        end

        handle(:kick, data)
      when :PART
        data[:channel] = @server.channel(argv[0])
        data[:message] = argv[1]

        @server.delete_user_from_channel(data[:source], data[:channel])
        if data[:source] == @user
          @server.delete_channel(data[:channel])
        end

        handle(:part, data)
      when :QUIT
        data[:message] = argv[0]
        data[:was_on] = @server.channels.inject(ChannelList.new) { |list, ch|
          list << ch if ch.has_user?(data[:source])
          list
        }

        @server.delete_user(data[:source])

        handle(:quit, data)
      when :JOIN
        data[:channel] = @server.channel(argv[0])
        data[:channel].add_user(data[:source])

        handle(:join, data)
      when :TOPIC
        data[:channel] = @server.channel(argv[0])
        data[:topic] = Channel::Topic.new(argv[1], data[:source], Time.new)
        data[:channel].topic.replace(data[:topic])

        handle(:changetopic, data)
      when :INVITE
        data[:target] = @server.user(argv[0])
        data[:channel] = @server.channel(argv[1])

        handle(:invite, data)
      when :NICK
        data[:is_on] = @server.channels.inject(ChannelList.new) { |list, ch|
          list << ch if ch.has_user?(data[:source])
          list
        }

        data[:newnick] = argv[0]
        data[:oldnick] = data[:source].nick.dup
        data[:source].nick = data[:newnick]

        debug "#{data[:oldnick]} (now #{data[:newnick]}) was on #{data[:is_on].join(', ')}"

        handle(:nick, data)
      when :MODE
        parse_mode(serverstring, argv, data)
        handle(:mode, data)
      when :ERROR
        data[:message] = argv[1]
        handle(:error, data)
      else
        warning "Unknown message #{serverstring.inspect}"
        handle(:unknown, data)
      end
    end

reset() click to toggle source

Clear the server and reset the user

    # File lib/rbot/rfc2812.rb, line 976
    def reset
      @server.clear
      @user = @server.user("*!*@*")
    end

### Private Instance Methods

handle(key, data) click to toggle source

key | server event name
data | hash containing data about the event, passed to the proc

call client's proc for an event, if they set one as a handler

    # File lib/rbot/rfc2812.rb, line 1507
    def handle(key, data)
      if(@handlers.has_key?(key))
        @handlers[key].call(data)
      end
    end

parse_mode(serverstring, argv, data) click to toggle source

RPL_CHANNELMODEIS MODE ([+-]<modes> (<params>)*)* When a MODE message is received by a server, Type C will have parameters too, so we must be able to consume parameters for all but Type D modes

    # File lib/rbot/rfc2812.rb, line 1519
    def parse_mode(serverstring, argv, data)
      data[:target] = @server.user_or_channel(argv[0])
      data[:modestring] = argv[1..-1].join(" ")
      # data[:modes] is an array where each element
      # is an array with two elements, the first of which
      # is either :set or :reset, and the second symbol
      # is the mode letter. An optional third element
      # is present e.g. for channel modes that need
      # a parameter
      data[:modes] = []
      case data[:target]
      when User
        # User modes aren't currently handled internally,
        # but we still parse them and delegate to the client
        warning "Unhandled user mode message '#{serverstring}'"
        argv[1..-1].each { |arg|
          setting = arg[0].chr
          if "+-".include?(setting)
            setting = setting == "+" ? :set : :reset
            arg[1..-1].each_byte { |b|
              m = b.chr.intern
              data[:modes] << [setting, m]
            }
          else
            # Although typically User modes don't take an argument,
            # this is not true for all modes on all servers. Since
            # we have no knowledge of which modes take parameters
            # and which don't we just assign it to the last
            # mode. This is not going to do strange things often,
            # as usually User modes are only set one at a time
            warning "Unhandled user mode parameter #{arg} found"
            data[:modes].last << arg
          end
        }
      when Channel
        # array of indices in data[:modes] where parameters
        # are needed
        who_wants_params = []

        modes = argv[1..-1].dup
        debug modes
        getting_args = false
        while arg = modes.shift
          debug arg
          if getting_args
            # getting args for previously set modes
            idx = who_wants_params.shift
            if idx.nil?
              warning "Oops, problems parsing #{serverstring.inspect}"
              break
            end
            data[:modes][idx] << arg
            getting_args = false if who_wants_params.empty?
          else
            debug @server.supports[:chanmodes]
            setting = :set
            arg.each_byte do |c|
              m = c.chr.intern
              case m
              when :+
                setting = :set
              when :-
                setting = :reset
              else
                data[:modes] << [setting, m]
                case m
                when *@server.supports[:chanmodes][:typea]
                  who_wants_params << data[:modes].length - 1
                when *@server.supports[:chanmodes][:typeb]
                  who_wants_params << data[:modes].length - 1
                when *@server.supports[:chanmodes][:typec]
                  if setting == :set
                    who_wants_params << data[:modes].length - 1
                  end
                when *@server.supports[:chanmodes][:typed]
                  # Nothing to do
                when *@server.supports[:prefix][:modes]
                  who_wants_params << data[:modes].length - 1
                else
                  warning "Ignoring unknown mode #{m} in #{serverstring.inspect}"
                  data[:modes].pop
                end
              end
            end
            getting_args = true unless who_wants_params.empty?
          end
        end
        unless who_wants_params.empty?
          warning "Unhandled malformed modeline #{data[:modestring]} (unexpected empty arguments)"
          return
        end

        data[:modes].each { |mode|
          set, key, val = mode
          if val
            data[:target].mode[key].send(set, val)
          else
            data[:target].mode[key].send(set)
          end
        }
      else
        warning "Ignoring #{data[:modestring]} for unrecognized target #{argv[0]} (#{data[:target].inspect})"
      end
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.