class ProtonBot::Plug

Plug. This class includes socket, writer and reader threads, user&channel-data, API for interacting with server etc. @!attribute [r] bot

@return [ProtonBot::Bot] Plug's bot.

@!attribute [r] db

@return [Heliodor::DB] Plug's Heliodor-powered database.
@see http://www.rubydoc.info/gems/heliodor

@!attribute [r] sock

@return [TCPSocket,SSLSocket] Plug's socket. May be SSL-socket.

@!attribute [r] rsock

@return [TCPSocket] Always non-SSL socket.

@!attribute [r] is_ssl

@return [Boolean] True if connection is SSL-powered

@!attribute [r] name

@return [String] Plug's name.

@!attribute [r] conf

@return [Hash<String>] Config.

@!attribute [r] rloop

@return [Thread] Reader-loop-thread.

@!attribute [r] wloop

@return [Thread] Writer-loop-thread.

@!attribute [r] log

@return [ProtonBot::LogWrapper] Log wrapper.

@!attribute [r] queue

@return [Queue] Queue.

@!attribute [r] chans

@return [Hash<String>] Channel data.

@!attribute [r] users

@return [Hash<String>] User data (by nick).

@!attribute [r] event_locks

@return [Array<EventLock>] Event locks.

@!attribute [rw] running

@return [Boolean] Returns `true` if bot still runs 
  and processes messages from server.

@!attribute [r] user

@return [String] Plug's username.

@!attribute [rw] nick

@return [String] Plug's nickname.

@!attribute [r] rnam

@return [String] Plug's realname.

Attributes

bot[R]
chans[R]
conf[R]
db[R]
event_locks[R]
is_ssl[R]
log[R]
name[R]
nick[RW]
queue[R]
rloop[R]
rnam[R]
rsock[R]
running[RW]
sock[R]
use_sasl[RW]
user[R]
users[R]
wloop[R]

Public Class Methods

new(bot, name, conf) click to toggle source

@param bot [Bot] @param name [String] @param conf [Hash<String>]

# File lib/protonbot/plug.rb, line 49
def initialize(bot, name, conf)
  @bot         = bot
  @name        = name
  @db          = @bot.dbs[@name]
  @log         = @bot._log.wrap("!#{@name}")
  @conf        = conf
  @nick        = conf['nick']
  @user        = conf['user']
  @rnam        = conf['rnam']
  @sock        = nil
  @running     = false
  @queue       = Queue.new
  @chans       = {}
  @users       = {}
  @event_locks = []
  if @conf['sasl'] and @conf['sasl_user'] and @conf['pass']
    @use_sasl = true
  else
    @use_sasl = false
  end
end
process_colors(string) click to toggle source

Colorizes given string. @param string [String] @return [String] Output

# File lib/protonbot/plug_io.rb, line 74
def self.process_colors(string)
  string
    .gsub("%C%",     "%C?")
    .gsub(",%",      ",?")
    .gsub("%C",      "\x03")
    .gsub("%B",      "\x02")
    .gsub("%I",      "\x10")
    .gsub("%U",      "\x1F")
    .gsub("%N",      "\x0F")
    .gsub("?WHITE",  "0")
    .gsub("?BLACK",  "1")
    .gsub("?BLUE",   "2")
    .gsub("?GREEN",  "3")
    .gsub("?RED",    "4")
    .gsub("?BROWN",  "5")
    .gsub("?PURPLE", "6")
    .gsub("?ORANGE", "7")
    .gsub("?YELLOW", "8")
    .gsub("?LGREEN", "9")
    .gsub("?CYAN"  , "10")
    .gsub("?LCYAN",  "11")
    .gsub("?LBLUE",  "12")
    .gsub("?PINK",   "13")
    .gsub("?GREY",   "14")
    .gsub("?LGREY",  "15")
end
ssplit(string) click to toggle source

Splits given string each 399 bytes and on newlines @param string [String] @return [String] Output

# File lib/protonbot/plug_io.rb, line 58
def self.ssplit(string)
  out = []
  arr = string.split("\n\r")
  arr.each do |i|
    items = i.scan(/.{,399}/)
    items.delete('')
    items.each do |i2|
      out << i2
    end
  end
  out
end

Public Instance Methods

action(target, msg) click to toggle source

Sends CTCP ACTION to given target @param target [String] @param msg [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 102
def action(target, msg)
  ctcp(target, "ACTION #{msg}")
  self
end
ban(chan, nick) click to toggle source

Sets ban on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 166
def ban(chan, nick)
  usermode(chan, nick, '+b')
  self
end
change_nick(to) click to toggle source

Changes current nick to given. If successful, `unick` event will be emitted @param to [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 45
def change_nick(to)
  write("NICK #{to}")
  self
end
chanmode(chan, mode) click to toggle source

Sets mode on given channel @param chan [String] @param mode [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 139
def chanmode(chan, mode)
  write("MODE #{chan} #{mode}")
  self
end
connect!() click to toggle source

Connects to server, introduces and starts reader and writer threads. @return [Plug] self

# File lib/protonbot/plug.rb, line 73
def connect!
  if @conf['ssl'] == nil or @conf['ssl'] == false
    @sock = @rsock = TCPSocket.new(@conf['host'], @conf['port'])
    @is_ssl = false
  else
    @rsock = TCPSocket.new(@conf['host'], @conf['port'])
    @ssl_ctx = OpenSSL::SSL::SSLContext.new
    @ssl_ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
    @ssl_ctx.ssl_version = :SSLv23
    @ssl_ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.expand_path(@conf['ssl_crt']))) if
      @conf['ssl_crt']
    @ssl_ctx.key = OpenSSL::PKey::RSA.new(File.read(File.expand_path(@conf['ssl_key']))) if
      @conf['ssl_key']
    @sock = OpenSSL::SSL::SSLSocket.new(@rsock, @ssl_ctx)
    @sock.sync = true
    @sock.connect
    @is_ssl = true
  end

  @running = true

  @rloop = Thread.new { readloop }
  @wloop = Thread.new { writeloop }

  log.info("Started plug `#{name}` successfully!")

  introduce

  self
end
ctcp(target, msg) click to toggle source

Sends CTCP to given target. @param target [String] @param msg [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 93
def ctcp(target, msg)
  write("PRIVMSG #{target} :\x01#{self.class.process_colors(msg)}\x01")
  self
end
deop(chan, nick) click to toggle source

Deops given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 211
def deop(chan, nick)
  usermode(chan, nick, '-o')
  self
end
devoice(chan, nick) click to toggle source

Devoices on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 229
def devoice(chan, nick)
  usermode(chan, nick, '-v')
  self
end
emit(dat = {}) click to toggle source

Emits passed event - calls first matching hook from each plugin @param dat [Hash] Event hash @return [Plug] self

# File lib/protonbot/plug_events.rb, line 5
def emit(dat = {})
  hooks = []
  bot.plugins.each do |_, p|
    hooks += p.hooks
  end
  hooks = hooks.keep_if do |hook|
    dat >= hook.pattern
  end
  hooks.each do |h|
    canrun = true
    h.chain.each do |l|
      next unless canrun
      canrun = l.call(dat, h)
    end
    h.block.call(dat) if canrun
  end
  event_locks.each_with_index do |el, k|
    if dat >= el.pattern
      event_locks.delete_at(k)
      el.unlock
    end
  end
  self
end
emitt(dat = {}) click to toggle source

Emits passed event in new thread @param dat [Hash] Event hash @return [Plug] self

# File lib/protonbot/plug_events.rb, line 33
def emitt(dat = {})
  d = dat.clone
  Thread.new do
    begin
      emit(d)
    rescue => e
      log_err(e)
    end
  end
  self
end
excempt(chan, nick) click to toggle source

Sets excempt on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 184
def excempt(chan, nick)
  usermode(chan, nick, '+e')
  self
end
gethost(nick) click to toggle source

Gets hostname for given nickname using WHOIS and event lock @param nick [String] @return [String] host

# File lib/protonbot/plug_utils.rb, line 13
def gethost(nick)
  if @users[nick] and @users[nick][:host]
    @users[nick][:host]
  else
    Thread.new do
      sleep(1)
      whois(nick)
    end
    wait_for(type: :code_whoisuser, nick: nick)
  end
  @users[nick][:host]
end
getuser(nick) click to toggle source

Gets username for given nickname using WHOIS and event lock @param nick [String] @return [String] user

# File lib/protonbot/plug_utils.rb, line 29
def getuser(nick)
  if @users[nick] and @users[nick][:user]
    @users[nick][:user]
  else
    Thread.new do
      sleep(1)
      whois(nick)
    end
    wait_for(type: :code, code: ProtonBot::Numeric::WHOISUSER)
    @users[nick][:user]
  end
end
inspect() click to toggle source

@return [String] out

# File lib/protonbot/plug.rb, line 119
def inspect
  %(<#ProtonBot::Plug:#{object_id.to_s(16)} @name=#{name} @bot=#{bot}>)
end
introduce() click to toggle source

Sends credentials to server (PASS, NICK, USER).

# File lib/protonbot/plug_io.rb, line 48
def introduce
  write_("PASS #{@conf['pass']}") if @conf['pass'] and !@use_sasl
  write_("CAP REQ :sasl") if @use_sasl
  write_("NICK #{@conf['nick']}")
  write_("USER #{@conf['user']} 0 * :#{@conf['rnam']}")
end
invite(chan, nick) click to toggle source

Invites user to given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 120
def invite(chan, nick)
  write("INVITE #{nick} #{chan}")
  self
end
join(chan, pass = '') click to toggle source

Joins given channel and uses password if it's provided @param chan [String] @param pass [String] Password @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 54
def join(chan, pass = '')
  write("JOIN #{chan} #{pass}")
  self
end
kick(chan, nick, reason = 'Default Kick Reason') click to toggle source

Kicks given user from given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 238
def kick(chan, nick, reason = 'Default Kick Reason')
  write("KICK #{chan} #{nick} :#{reason}")
  self
end
log_err(e) click to toggle source

Logs given error object to cosole @param e [Exception] @return [Plug] self

# File lib/protonbot/plug.rb, line 107
def log_err(e)
  @log.error('Error!')
  @log.error("> Inspect: #{e.inspect}")
  @log.error("> Message: #{e.message}")
  @log.error('> Backtrace:')
  e.backtrace.each do |i|
    @log.error(">> #{i}")
  end
  self
end
nctcp(target, msg) click to toggle source

Sends NCTCP to given target. @param target [String] @param msg [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 111
def nctcp(target, msg)
  write("NOTICE #{target} :\x01#{self.class.process_colors(msg)}\x01")
  self
end
notice(target, msg) click to toggle source

Sends notice to given target. Splits it each 399 bytes. @param target [String] @param msg [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 82
def notice(target, msg)
  self.class.ssplit(self.class.process_colors(msg)).each do |m|
    write("NOTICE #{target} :\u200B#{m}")
  end
  self
end
op(chan, nick) click to toggle source

Ops given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 202
def op(chan, nick)
  usermode(chan, nick, '+o')
  self
end
part(chan, reason = 'Parting...') click to toggle source

Parts given channel with given reason @param chan [String] @param reason [String]

# File lib/protonbot/plug_utils.rb, line 62
def part(chan, reason = 'Parting...')
  write("PART #{chan} :#{reason}")
  self
end
privmsg(target, msg) click to toggle source

Sends message to given target. Splits it each 399 bytes. @param target [String] @param msg [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 71
def privmsg(target, msg)
  self.class.ssplit(self.class.process_colors(msg)).each do |m|
    write("PRIVMSG #{target} :\u200B#{m}")
  end
  self
end
quiet(chan, nick) click to toggle source

Sets quiet on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 148
def quiet(chan, nick)
  usermode(chan, nick, '+q')
  self
end
readloop() click to toggle source

Main read-loop. Reads data from socket and emits event `raw`

# File lib/protonbot/plug_io.rb, line 3
def readloop
  while @running
    if s = @sock.gets
      s = s.force_encoding(@conf['encoding'])
      s = s[0..-3]
      log.info "R > #{s}"
      begin
        emit(type: :raw, raw_data: s.clone, plug: self, db: db, bot: bot)
      rescue => e
        log_err(e)
      end
    else
      @running = false
    end
  end
  log.info 'Connection closed.'
end
remove(chan, nick, reason = 'Default Remove Reason') click to toggle source

Removes given user from given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 247
def remove(chan, nick, reason = 'Default Remove Reason')
  write("REMOVE #{chan} #{nick} :#{reason}")
  self
end
thrjoin() click to toggle source

@!api private

# File lib/protonbot/plug.rb, line 124
def thrjoin
  until @rloop && @rloop.status == 'run'
    sleep(0.1)
  end
  until @wloop && @wloop.status == 'run'
    sleep(0.1)
  end
  @bot.plugthrs[@name].join
  @rloop.join
  @wloop.join
end
unban(chan, nick) click to toggle source

Removes ban on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 175
def unban(chan, nick)
  usermode(chan, nick, '-b')
  self
end
unexcempt(chan, nick) click to toggle source

Removes excempt on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 193
def unexcempt(chan, nick)
  usermode(chan, nick, '-e')
  self
end
unquiet(chan, nick) click to toggle source

Removes quiet on given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 157
def unquiet(chan, nick)
  usermode(chan, nick, '-q')
  self
end
usermode(chan, nick, mode) click to toggle source

Sets mode on given user at given channel @param chan [String] @param nick [String] @param mode [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 130
def usermode(chan, nick, mode)
  write("MODE #{chan} #{mode} #{nick}")
  self
end
voice(chan, nick) click to toggle source

Voices given user at given channel @param chan [String] @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 220
def voice(chan, nick)
  usermode(chan, nick, '+v')
  self
end
wait_for(pattern) click to toggle source

Creates EventLock with given pattern. @param pattern [Hash] @return [Plug] self

# File lib/protonbot/plug_events.rb, line 48
def wait_for(pattern)
  ProtonBot::EventLock.new(self, pattern)
  self
end
whois(nick) click to toggle source

Sends WHOIS message to the server @param nick [String] @return [Plug] self

# File lib/protonbot/plug_utils.rb, line 5
def whois(nick)
  write("WHOIS #{nick}")
  self
end
write(s) click to toggle source

Adds given message to the queue @param s [String] Message @return [Plug] self

# File lib/protonbot/plug_io.rb, line 33
def write(s)
  @queue << s
  self
end
write_(s) click to toggle source

Sends message to server without using queue. @param s [String] Message @return [Plug] self

# File lib/protonbot/plug_io.rb, line 41
def write_(s)
  s = s.encode(@conf['encoding'], s.encoding)
  @sock.puts s
  log.info "W > #{s}"
end
writeloop() click to toggle source

Main write-loop. Reads data from queue and sends it to server

# File lib/protonbot/plug_io.rb, line 22
def writeloop
  while @running || !@wqueue.empty?
    s = @queue.pop
    write_(s)
    sleep(@conf['queue_delay'])
  end
end