class Cinch::IRC

This class manages the connection to the IRC server. That includes processing incoming and outgoing messages, creating Ruby objects and invoking plugins.

Constants

EXTERNAL_ENCODING_ON_LOAD

Use it to fix the encoding of SocketError message.

Attributes

bot[R]

@return [Bot]

isupport[R]

@return [ISupport]

network[R]

@return [Network] The detected network

Public Class Methods

new(bot) click to toggle source
# File lib/cinch/irc.rb, line 26
def initialize(bot)
  @bot      = bot
  @isupport = ISupport.new
end

Public Instance Methods

connect() click to toggle source

@api private @return [Boolean] True if the connection could be established

# File lib/cinch/irc.rb, line 50
def connect
  tcp_socket = nil

  begin
    Timeout.timeout(@bot.config.timeouts.connect) do
      tcp_socket = TCPSocket.new(@bot.config.server, @bot.config.port, @bot.config.local_host)
    end
  rescue Timeout::Error => e
    @bot.last_connection_error = e
    @bot.loggers.warn('Timed out while connecting')

    return false
  rescue SocketError => e
    # In a Windows environment, the encoding of SocketError message may be
    # ASCII-8BIT that causes Encoding::CompatibilityError. To prevent that
    # error, the error message must be encoded to UTF-8.
    e.message.force_encoding(EXTERNAL_ENCODING_ON_LOAD).encode!(Encoding::UTF_8)
    @bot.last_connection_error = e

    @bot.loggers.warn("Could not connect to the IRC server. Please check your network: #{e.message}")

    return false
  rescue => e
    @bot.last_connection_error = e
    @bot.loggers.exception(e)

    return false
  end

  if @bot.config.ssl.use
    setup_ssl(tcp_socket)
  else
    @socket = tcp_socket
  end

  @socket              = Net::BufferedIO.new(@socket)
  @socket.read_timeout = @bot.config.timeouts.read
  @queue               = MessageQueue.new(@socket, @bot)

  true
end
parse(input) click to toggle source

@api private @return [void]

# File lib/cinch/irc.rb, line 274
def parse(input)
  return if input.chomp.empty?

  @bot.loggers.incoming(input)

  msg          = Message.new(input, @bot)
  events       = [[:catchall]]

  if %w[001 002 003 004 422].include?(msg.command)
    @registration << msg.command
    if registered?
      events << [:connect]

      @bot.last_connection_was_successful = true
      @bot.last_connection_error = nil

      on_connect(msg, events)
    end
  end

  if %w[PRIVMSG NOTICE].include?(msg.command)
    events << [:ctcp] if msg.ctcp?
    events << if msg.channel?
                [:channel]
              else
                [:private]
              end

    if msg.command == 'PRIVMSG'
      events << [:message]
    end

    if msg.action?
      events << [:action]
    end
  end

  meth = "on_#{msg.command.downcase}"
  __send__(meth, msg, events) if respond_to?(meth, true)

  if msg.error?
    events << [:error]
  end

  events << [msg.command.downcase.to_sym]

  msg.events = events.map(&:first)
  events.each do |event, *args|
    @bot.handlers.dispatch(event, msg, *args)
  end
end
registered?() click to toggle source

@return [Boolean] true if we successfully registered yet

# File lib/cinch/irc.rb, line 327
def registered?
  (('001'..'004').to_a - @registration).empty? || @registration.include?('422')
end
send(msg) click to toggle source

Send a message to the server. @param [String] msg @return [void]

# File lib/cinch/irc.rb, line 334
def send(msg)
  @queue.queue(msg)
end
send_cap_end() click to toggle source

@since 2.0.0 @api private @return [void]

# File lib/cinch/irc.rb, line 145
def send_cap_end
  send 'CAP END'
end
send_cap_ls() click to toggle source

@api private @return [void] @since 2.0.0

# File lib/cinch/irc.rb, line 123
def send_cap_ls
  send 'CAP LS'
end
send_cap_req() click to toggle source

@api private @return [void] @since 2.0.0

# File lib/cinch/irc.rb, line 130
def send_cap_req
  caps = %i[away-notify multi-prefix sasl twitch.tv/tags] & @network.capabilities

  # InspIRCd doesn't respond to empty REQs, so send an END in that
  # case.
  if !caps.empty?
    send 'CAP REQ :' + caps.join(' ')
  else
    send_cap_end
  end
end
send_login() click to toggle source

@api private @return [void] @since 2.0.0

# File lib/cinch/irc.rb, line 152
def send_login
  send "PASS #{@bot.config.password}" if @bot.config.password
  send "NICK #{@bot.generate_next_nick!}"
  send "USER #{@bot.config.user} 0 * :#{@bot.config.realname}"
end
send_sasl() click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 238
def send_sasl
  if @bot.config.sasl.username && @sasl_current_method = @sasl_remaining_methods.pop
    @bot.loggers.info "[SASL] Trying to authenticate with #{@sasl_current_method.mechanism_name}"
    send "AUTHENTICATE #{@sasl_current_method.mechanism_name}"
  else
    send_cap_end
  end
end
setup() click to toggle source

@api private @return [void] @since 2.0.0

# File lib/cinch/irc.rb, line 41
def setup
  @registration  = []
  @network       = Network.new(:unknown, :unknown)
  @whois_updates = {}
  @in_lists      = Set.new
end
setup_ssl(socket) click to toggle source

@api private @return [void] @since 2.0.0

# File lib/cinch/irc.rb, line 95
def setup_ssl(socket)
  # require openssl in this method so the bot doesn't break for
  # people who don't have SSL but don't want to use SSL anyway.
  require 'openssl'

  ssl_context = OpenSSL::SSL::SSLContext.new

  if @bot.config.ssl.is_a?(Configuration::SSL)
    if @bot.config.ssl.client_cert
      ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@bot.config.ssl.client_cert))
      ssl_context.key  = OpenSSL::PKey::RSA.new(File.read(@bot.config.ssl.client_cert))
    end

    ssl_context.ca_path     = @bot.config.ssl.ca_path
    ssl_context.verify_mode = @bot.config.ssl.verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
  else
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end
  @bot.loggers.info "Using SSL with #{@bot.config.server}:#{@bot.config.port}"

  @socket      = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
  @socket.sync = true
  @socket.connect
end
socket() click to toggle source

@return [TCPSocket] @api private @since 2.0.0

# File lib/cinch/irc.rb, line 34
def socket
  @socket.io
end
start() click to toggle source

Establish a connection.

@return [void] @since 2.0.0

# File lib/cinch/irc.rb, line 251
def start
  setup
  if connect
    @sasl_remaining_methods = @bot.config.sasl.mechanisms.reverse
    send_cap_ls
    send_login

    reading_thread = start_reading_thread
    sending_thread = start_sending_thread
    ping_thread    = start_ping_thread
    quit_thread    = start_quit_thread

    reading_thread.join
    sending_thread.kill
    ping_thread.kill

    @bot.quit_queue.push([:stop])
    quit_thread.join
  end
end
start_ping_thread() click to toggle source

@api private @return [Thread] The ping thread. @since 2.0.0

# File lib/cinch/irc.rb, line 201
def start_ping_thread
  Thread.new do
    loop do
      sleep @bot.config.ping_interval
      # PING requires a single argument. In our case the value
      # doesn't matter though.
      send('PING 0')
    end
  end
end
start_quit_thread() click to toggle source

@api private @return [Thread] The quit thread. @since 2.0.0

# File lib/cinch/irc.rb, line 215
def start_quit_thread
  Thread.new do
    sent_quit = false

    loop do
      command, *args = @bot.quit_queue.pop
      case command
      when :stop
        break
      when :quit
        unless sent_quit
          message = args[0]
          command = message ? "QUIT :#{message}" : 'QUIT'
          send(command)

          sent_quit = true
        end
      end
    end
  end
end
start_reading_thread() click to toggle source

@api private @return [Thread] the reading thread @since 2.0.0

# File lib/cinch/irc.rb, line 161
def start_reading_thread
  Thread.new do
    begin
      while line = @socket.readline
        rescue_exception do
          line = Cinch::Utilities::Encoding.encode_incoming(line, @bot.config.encoding)
          parse line
        end
      end
    rescue Timeout::Error
      @bot.loggers.warn 'Connection timed out.'
    rescue EOFError
      @bot.loggers.warn 'Lost connection.'
    rescue => e
      @bot.loggers.exception(e)
    end

    @socket.close
    @bot.handlers.dispatch(:disconnect)
    # FIXME: won't we kill all :disconnect handlers here? prolly
    # not, as they have 10 seconds to finish. that should be
    # plenty of time
    @bot.handlers.stop_all
  end
end
start_sending_thread() click to toggle source

@api private @return [Thread] the sending thread @since 2.0.0

# File lib/cinch/irc.rb, line 190
def start_sending_thread
  Thread.new do
    rescue_exception do
      @queue.process!
    end
  end
end

Private Instance Methods

detect_network(msg, event) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 345
def detect_network(msg, event)
  old_network = @network
  new_network = nil
  new_ircd    = nil
  case event
  when '002'
    if msg.params.last =~ /^Your host is .+?, running version (.+)$/
      case Regexp.last_match(1)
      when /\+snircd\(/
        new_ircd = :snircd
      when /^u[\d\.]+$/
        new_ircd = :ircu
      when /^(.+?)-?\d+/
        new_ircd = Regexp.last_match(1).downcase.to_sym
      end
    elsif msg.params.last == 'Your host is jtvchat'
      new_network = :jtv
      new_ircd    = :jtv
    end
  when '004'
    if msg.params == %w[irc.tinyspeck.com IRC-SLACK gateway]
      new_network = :slack
      new_ircd = :slack
    end
  when '005'
    case @isupport['NETWORK']
    when 'NGameTV'
      new_network = :ngametv
      new_ircd    = :ngametv
    when nil
    else
      new_network = @isupport['NETWORK'].downcase.to_sym
    end
  end

  new_network ||= old_network.name
  new_ircd    ||= old_network.ircd

  if old_network.unknown_ircd? && new_ircd != :unknown
    @bot.loggers.info "Detected IRCd: #{new_ircd}"
  end
  if !old_network.unknown_ircd? && new_ircd != old_network.ircd
    @bot.loggers.info "Detected different IRCd: #{old_network.ircd} -> #{new_ircd}"
  end
  if old_network.unknown_network? && new_network != :unknown
    @bot.loggers.info "Detected network: #{new_network}"
  end
  if !old_network.unknown_network? && new_network != old_network.name
    @bot.loggers.info "Detected different network: #{old_network.name} -> #{new_network}"
  end

  @network.name = new_network
  @network.ircd = new_ircd
end
on_001(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 660
def on_001(msg, _events)
  # Ensure that we know our real, possibly truncated or otherwise
  # modified nick.
  @bot.set_nick msg.params.first
end
on_002(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 667
def on_002(msg, _events)
  detect_network(msg, '002')
end
on_004(msg, _events) click to toggle source

@since 2.2.6

# File lib/cinch/irc.rb, line 672
def on_004(msg, _events)
  detect_network(msg, '004')
end
on_005(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 676
def on_005(msg, _events)
  # ISUPPORT
  @isupport.parse(*msg.params[1..-2].map { |v| v.split(' ') }.flatten)
  detect_network(msg, '005')
end
on_301(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 683
def on_301(msg, _events)
  # RPL_AWAY
  user = User(msg.params[1])
  away = msg.params.last

  if @whois_updates[user]
    update_whois(user, { away: away })
  end
end
on_307(msg, _events) click to toggle source

@since 1.1.0

# File lib/cinch/irc.rb, line 694
def on_307(msg, _events)
  # RPL_WHOISREGNICK
  user = User(msg.params[1])
  update_whois(user, { registered: true })
end
on_311(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 700
def on_311(msg, _events)
  # RPL_WHOISUSER
  user = User(msg.params[1])
  update_whois(user, {
                 user: msg.params[2],
                 host: msg.params[3],
                 realname: msg.params[5],
               })
end
on_313(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 710
def on_313(msg, _events)
  # RPL_WHOISOPERATOR
  user = User(msg.params[1])
  update_whois(user, { oper?: true })
end
on_317(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 716
def on_317(msg, _events)
  # RPL_WHOISIDLE
  user = User(msg.params[1])
  update_whois(user, {
                 idle: msg.params[2].to_i,
                 signed_on_at: Time.at(msg.params[3].to_i),
               })
end
on_318(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 725
def on_318(msg, _events)
  # RPL_ENDOFWHOIS
  user = User(msg.params[1])
  user.end_of_whois(@whois_updates[user])
  @whois_updates.delete user
end
on_319(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 732
def on_319(msg, _events)
  # RPL_WHOISCHANNELS
  user     = User(msg.params[1])
  channels = msg.params[2].scan(/[#{@isupport["CHANTYPES"].join}][^ ]+/o).map { |c| Channel(c) }
  update_whois(user, { channels: channels })
end
on_324(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 739
def on_324(msg, _events)
  # RPL_CHANNELMODEIS
  modes     = {}
  arguments = msg.params[3..-1]

  msg.params[2][1..-1].split('').each do |mode|
    modes[mode] = if (@isupport['CHANMODES']['B'] + @isupport['CHANMODES']['C']).include?(mode)
                    arguments.shift
                  else
                    true
                  end
  end

  msg.channel.sync(:modes, modes, false)
end
on_330(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 755
def on_330(msg, _events)
  # RPL_WHOISACCOUNT
  user     = User(msg.params[1])
  authname = msg.params[2]
  update_whois(user, { authname: authname })
end
on_331(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 762
def on_331(msg, _events)
  # RPL_NOTOPIC
  msg.channel.sync(:topic, '')
end
on_332(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 767
def on_332(msg, _events)
  # RPL_TOPIC
  msg.channel.sync(:topic, msg.params[2])
end
on_352(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 772
def on_352(msg, _events)
  # RPL_WHOREPLY
  # "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>"
  _, channel, user, host, _, nick, _, hopsrealname = msg.params
  _, realname = hopsrealname.split(' ', 2)
  channel     = Channel(channel)
  user_object = User(nick)
  user_object.sync(:user, user, true)
  user_object.sync(:host, host, true)
  user_object.sync(:realname, realname, true)
end
on_353(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 803
def on_353(msg, _events)
  # RPL_NAMEREPLY
  unless @in_lists.include?(:names)
    msg.channel.clear_users
  end
  @in_lists << :names

  msg.params[3].split(' ').each do |user|
    m = user.match(/^([#{@isupport["PREFIX"].values.join}]+)/)
    if m
      prefixes = m[1].split('').map { |s| @isupport['PREFIX'].key(s) }
      nick = user[prefixes.size..-1]
    else
      nick = user
      prefixes = []
    end
    user = User(nick)
    user.online = true
    msg.channel.add_user(user, prefixes)
    user.channels_unsynced << msg.channel unless user.channels_unsynced.include?(msg.channel)
  end
end
on_354(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 784
def on_354(msg, _events)
  # RPL_WHOSPCRPL
  # We are using the following format: %acfhnru

  #                          _         user      host                                 nick      f account  realame
  # :leguin.freenode.net 354 dominikh_ ~a        ip-88-152-125-117.unitymediagroup.de dominikh_ H 0        :d
  # :leguin.freenode.net 354 dominikh_ ~FiXato   fixato.net                           FiXato    H FiXato   :FiXato, using WeeChat -- More? See: http://twitter
  # :leguin.freenode.net 354 dominikh_ ~dominikh cinch/developer/dominikh             dominikh  H DominikH :dominikh
  # :leguin.freenode.net 354 dominikh_ ~oddmunds s21-04214.dsl.no.powertech.net       oddmunds  H 0        :oddmunds

  _, channel, user, host, nick, _, account, realname = msg.params
  channel = Channel(channel)
  user_object = User(nick)
  user_object.sync(:user, user, true)
  user_object.sync(:host, host, true)
  user_object.sync(:realname, realname, true)
  user_object.sync(:authname, account == '0' ? nil : account, true)
end
on_366(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 826
def on_366(msg, _events)
  # RPL_ENDOFNAMES
  @in_lists.delete :names
  msg.channel.mark_as_synced(:users)
end
on_367(msg, _events) click to toggle source

@version 2.0.0

# File lib/cinch/irc.rb, line 833
def on_367(msg, _events)
  # RPL_BANLIST
  unless @in_lists.include?(:bans)
    msg.channel.bans_unsynced.clear
  end
  @in_lists << :bans

  mask = msg.params[2]
  if @network.jtv?
    # on the justin tv network, ban "masks" only consist of the
    # nick/username
    mask = '%s!%s@%s' % [mask, mask, mask + '.irc.justin.tv']
  end

  by = if msg.params[3]
         User(msg.params[3].split('!').first)
       end

  at  = Time.at(msg.params[4].to_i)
  ban = Ban.new(mask, by, at)
  msg.channel.bans_unsynced << ban
end
on_368(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 856
def on_368(msg, _events)
  # RPL_ENDOFBANLIST
  if @in_lists.include?(:bans)
    @in_lists.delete :bans
  else
    # we never received a ban, yet an end of list => no bans
    msg.channel.bans_unsynced.clear
  end

  msg.channel.mark_as_synced(:bans)
end
on_386(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 868
def on_386(msg, _events)
  # RPL_QLIST
  unless @in_lists.include?(:owners)
    msg.channel.owners_unsynced.clear
  end
  @in_lists << :owners

  owner = User(msg.params[2])
  msg.channel.owners_unsynced << owner
end
on_387(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 879
def on_387(msg, _events)
  # RPL_ENDOFQLIST
  if @in_lists.include?(:owners)
    @in_lists.delete :owners
  else
    # we never received an owner, yet an end of list -> no owners
    msg.channel.owners_unsynced.clear
  end

  msg.channel.mark_as_synced(:owners)
end
on_396(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 891
def on_396(msg, _events)
  # RPL_HOSTHIDDEN
  # note: designed for freenode
  User(msg.params[0]).sync(:host, msg.params[1], true)
end
on_401(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 897
def on_401(msg, _events)
  # ERR_NOSUCHNICK
  if user = @bot.user_list.find(msg.params[1])
    update_whois(user, { unknown?: true })
  end
end
on_402(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 904
def on_402(msg, _events)
  # ERR_NOSUCHSERVER

  if user = @bot.user_list.find(msg.params[1]) # not _ensured, we only want a user that already exists
    user.end_of_whois({ unknown?: true })
    @whois_updates.delete user
    # TODO: freenode specific, test on other IRCd
  end
end
on_433(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 914
def on_433(msg, _events)
  # ERR_NICKNAMEINUSE
  @bot.nick = @bot.generate_next_nick!(msg.params[1])
end
on_671(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 919
def on_671(msg, _events)
  user = User(msg.params[1])
  update_whois(user, { secure?: true })
end
on_730(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 925
def on_730(msg, _events)
  # RPL_MONONLINE
  msg.params.last.split(',').each do |mask|
    user = User(Mask.new(mask).nick)
    # User is responsible for emitting an event
    user.online = true
  end
end
on_731(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 935
def on_731(msg, _events)
  # RPL_MONOFFLINE
  msg.params.last.split(',').each do |nick|
    user = User(nick)
    # User is responsible for emitting an event
    user.online = false
  end
end
on_734(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 945
def on_734(msg, _events)
  # ERR_MONLISTFULL
  user = User(msg.params[2])
  user.monitored = false
end
on_903(_msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 952
def on_903(_msg, _events)
  # SASL authentication successful
  @bot.loggers.info "[SASL] SASL authentication with #{@sasl_current_method.mechanism_name} successful"
  send_cap_end
end
on_904(_msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 959
def on_904(_msg, _events)
  # SASL authentication failed
  @bot.loggers.info "[SASL] SASL authentication with #{@sasl_current_method.mechanism_name} failed"
  send_sasl
end
on_authenticate(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 966
def on_authenticate(msg, _events)
  send 'AUTHENTICATE ' + @sasl_current_method.generate(@bot.config.sasl.username,
                                                       @bot.config.sasl.password,
                                                       msg.params.last)
end
on_away(msg, events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 430
def on_away(msg, events)
  if msg.message.to_s.empty?
    # unaway
    msg.user.sync(:away, nil, true)
    events << [:unaway]
  else
    # away
    msg.user.sync(:away, msg.message, true)
    events << [:away]
  end
end
on_cap(msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 443
def on_cap(msg, _events)
  case msg.params[1]
  when 'LS'
    @network.capabilities.concat msg.message.split(' ').map(&:to_sym)
    send_cap_req
  when 'ACK'
    if @network.capabilities.include?(:sasl)
      send_sasl
    else
      send_cap_end
    end
  when 'NAK'
    send_cap_end
  end
end
on_connect(_msg, _events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 460
def on_connect(_msg, _events)
  @bot.modes = @bot.config.modes
end
on_join(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 464
def on_join(msg, _events)
  if msg.user == @bot
    @bot.channels << msg.channel
    msg.channel.sync_modes
  end
  msg.channel.add_user(msg.user)
  msg.user.online = true
end
on_kick(msg, events) click to toggle source
# File lib/cinch/irc.rb, line 473
def on_kick(msg, events)
  target = User(msg.params[1])
  if target == @bot
    @bot.channels.delete(msg.channel)
  end
  msg.channel.remove_user(target)

  set_leaving_user(msg, target, events)
end
on_kill(msg, events) click to toggle source
# File lib/cinch/irc.rb, line 483
def on_kill(msg, events)
  user = User(msg.params[1])

  @bot.channel_list.each do |channel|
    channel.remove_user(user)
  end

  user.unsync_all
  user.online = false

  set_leaving_user(msg, user, events)
end
on_mode(msg, events) click to toggle source

@version 1.1.0

# File lib/cinch/irc.rb, line 497
def on_mode(msg, events)
  if msg.channel?
    parse_channel_modes(msg, events)
    return
  end
  if msg.params.first == bot.nick
    parse_bot_modes(msg)
  end
end
on_nick(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 578
def on_nick(msg, _events)
  target = if msg.user == @bot
             # @bot.set_nick msg.params.last
             @bot
           else
             msg.user
           end

  target.update_nick(msg.params.last)
  target.online = true
end
on_part(msg, events) click to toggle source
# File lib/cinch/irc.rb, line 590
def on_part(msg, events)
  msg.channel.remove_user(msg.user)
  msg.user.channels_unsynced.delete msg.channel

  if msg.user == @bot
    @bot.channels.delete(msg.channel)
  end

  set_leaving_user(msg, msg.user, events)
end
on_ping(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 601
def on_ping(msg, _events)
  send "PONG :#{msg.params.first}"
end
on_privmsg(msg, events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 628
def on_privmsg(msg, events)
  if msg.user
    msg.user.online = true
  end

  if msg.message =~ /^\001DCC SEND (?:"([^"]+)"|(\S+)) (\S+) (\d+)(?: (\d+))?\001$/
    process_dcc_send(Regexp.last_match(1) || Regexp.last_match(2), Regexp.last_match(3), Regexp.last_match(4), Regexp.last_match(5), msg, events)
  end
end
on_quit(msg, events) click to toggle source
# File lib/cinch/irc.rb, line 609
def on_quit(msg, events)
  @bot.channel_list.each do |channel|
    channel.remove_user(msg.user)
  end
  msg.user.unsync_all
  msg.user.online = false

  set_leaving_user(msg, msg.user, events)

  if msg.message.downcase == 'excess flood' && msg.user == @bot
    @bot.warn ['Looks like your bot has been kicked because of excess flood.',
               "If you haven't modified the throttling options manually, please file a bug report at https://github.com/cinchrb/cinch/issues and include the following information:",
               "- Server: #{@bot.config.server}",
               "- Messages per second: #{@bot.config.messages_per_second}",
               "- Server queue size: #{@bot.config.server_queue_size}"]
  end
end
on_topic(msg, _events) click to toggle source
# File lib/cinch/irc.rb, line 605
def on_topic(msg, _events)
  msg.channel.sync(:topic, msg.params[1])
end
parse_bot_modes(msg) click to toggle source
# File lib/cinch/irc.rb, line 563
def parse_bot_modes(msg)
  modes, err = ModeParser.parse_modes(msg.params[1], msg.params[2..-1])
  unless err.nil?
    raise Exceptions::InvalidModeString, err
  end

  modes.each do |direction, mode, _|
    if direction == :add
      @bot.modes << mode unless @bot.modes.include?(mode)
    else
      @bot.modes.delete(mode)
    end
  end
end
parse_channel_modes(msg, events) click to toggle source
# File lib/cinch/irc.rb, line 507
def parse_channel_modes(msg, events)
  add_and_remove = @bot.irc.isupport['CHANMODES']['A'] + @bot.irc.isupport['CHANMODES']['B'] + @bot.irc.isupport['PREFIX'].keys

  param_modes = {
    add: @bot.irc.isupport['CHANMODES']['C'] + add_and_remove,
    remove: add_and_remove,
  }

  modes, err = ModeParser.parse_modes(msg.params[1], msg.params[2..-1], param_modes)
  unless err.nil?
    if @network.ircd != :slack || !err.is_a?(ModeParser::TooManyParametersError)
      raise Exceptions::InvalidModeString, err
    end
  end
  modes.each do |direction, mode, param|
    if @bot.irc.isupport['PREFIX'].keys.include?(mode)
      target = User(param)

      # (un)set a user-mode
      if direction == :add
        msg.channel.users[target] << mode unless msg.channel.users[target].include?(mode)
      else
        msg.channel.users[target].delete mode
      end

      user_events = {
        'o' => 'op',
        'v' => 'voice',
        'h' => 'halfop',
      }
      if user_events.key?(mode)
        event = (direction == :add ? '' : 'de') + user_events[mode]
        events << [event.to_sym, target]
      end
    elsif @bot.irc.isupport['CHANMODES']['A'].include?(mode)
      case mode
      when 'b'
        process_ban_mode(msg, events, param, direction)
      when 'q'
        process_owner_mode(msg, events, param, direction) if @network.owner_list_mode
      else
        raise Exceptions::UnsupportedMode, mode
      end
    else
      # channel options
      if direction == :add
        msg.channel.modes_unsynced[mode] = param.nil? ? true : param
      else
        msg.channel.modes_unsynced.delete(mode)
      end
    end
  end

  events << [:mode_change, modes]
end
process_ban_mode(msg, events, param, direction) click to toggle source
# File lib/cinch/irc.rb, line 400
def process_ban_mode(msg, events, param, direction)
  mask = param
  ban = Ban.new(mask, msg.user, Time.now)

  if direction == :add
    msg.channel.bans_unsynced << ban
    events << [:ban, ban]
  else
    msg.channel.bans_unsynced.delete_if { |b| b.mask == ban.mask }
    events << [:unban, ban]
  end
end
process_dcc_send(filename, ip, port, size, m, events) click to toggle source

@since 2.0.0

# File lib/cinch/irc.rb, line 639
def process_dcc_send(filename, ip, port, size, m, events)
  if ip =~ /^\d+$/
    # If ip is a single integer, assume it's a specification
    # compliant IPv4 address in network byte order. If it's any
    # other string, assume that it's a valid IPv4 or IPv6 address.
    # If it's not valid, let someone higher up the chain notice
    # that.
    ip   = ip.to_i
    ip   = [24, 16, 8, 0].collect { |b| (ip >> b) & 255 }.join('.')
  end

  port = port.to_i
  size = size.to_i

  @bot.loggers.info 'DCC: Incoming DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d' % [filename, size, ip, port]

  dcc = DCC::Incoming::Send.new(user: m.user, filename: filename, size: size, ip: ip, port: port)
  events << [:dcc_send, dcc]
end
process_owner_mode(msg, events, param, direction) click to toggle source
# File lib/cinch/irc.rb, line 413
def process_owner_mode(msg, events, param, direction)
  owner = User(param)
  if direction == :add
    msg.channel.owners_unsynced << owner unless msg.channel.owners_unsynced.include?(owner)
    events << [:owner, owner]
  else
    msg.channel.owners_unsynced.delete(owner)
    events << [:deowner, owner]
  end
end
set_leaving_user(_message, user, events) click to toggle source
# File lib/cinch/irc.rb, line 340
def set_leaving_user(_message, user, events)
  events << [:leaving, user]
end
update_whois(user, data) click to toggle source
# File lib/cinch/irc.rb, line 424
def update_whois(user, data)
  @whois_updates[user] ||= {}
  @whois_updates[user].merge!(data)
end