Class: Cinch::Bot

Inherits:
Object
  • Object
show all
Defined in:
lib/cinch/bot.rb

Instance Attribute Summary collapse

Helper methods collapse

Sending messages collapse

Events & Plugins collapse

Bot Control collapse

Channel Control collapse

Instance Method Summary collapse

Constructor Details

#initialize { ... } ⇒ Bot

Returns a new instance of Bot.

Yields:



481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/cinch/bot.rb', line 481

def initialize(&b)
  @logger = Logger::FormattedLogger.new($stderr)
  @events = {}
  @config = OpenStruct.new({
                             :server => "localhost",
                             :port   => 6667,
                             :ssl    => OpenStruct.new({
                                                         :use => false,
                                                         :verify => false,
                                                         :client_cert => nil,
                                                         :ca_path => "/etc/ssl/certs",
                                                       }),
                             :password => nil,
                             :nick   => "cinch",
                             :nicks  => nil,
                             :realname => "cinch",
                             :user => "cinch",
                             :verbose => true,
                             :messages_per_second => 0.5,
                             :server_queue_size => 10,
                             :strictness => :forgiving,
                             :message_split_start => '... ',
                             :message_split_end   => ' ...',
                             :max_messages => nil,
                             :plugins => OpenStruct.new({
                                                          :plugins => [],
                                                          :prefix  => /^!/,
                                                          :suffix  => nil,
                                                          :options => Hash.new {|h,k| h[k] = {}},
                                                        }),
                             :channels => [],
                             :encoding => :irc,
                             :reconnect => true,
                             :local_host => nil,
                             :timeouts => OpenStruct.new({
                                                           :read => 240,
                                                           :connect => 10,
                                                         }),
                             :ping_interval => 120,
                           })

  @semaphores_mutex = Mutex.new
  @semaphores = Hash.new { |h,k| h[k] = Mutex.new }
  @plugins = []
  @callback = Callback.new(self)
  @channels = []
  @handler_threads = []
  @quitting = false

  @user_manager = UserManager.new(self)
  @channel_manager = ChannelManager.new(self)

  instance_eval(&b) if block_given?

  on :connect do
    bot.config.channels.each do |channel|
      bot.join channel
    end
  end
end

Instance Attribute Details

#channel_managerChannelManager (readonly)

Returns:



65
66
67
# File 'lib/cinch/bot.rb', line 65

def channel_manager
  @channel_manager
end

#channelsArray<Channel> (readonly)

Returns All channels the bot currently is in.

Returns:

  • (Array<Channel>)

    All channels the bot currently is in



44
45
46
# File 'lib/cinch/bot.rb', line 44

def channels
  @channels
end

#configConfig (readonly)

Returns:

  • (Config)


38
39
40
# File 'lib/cinch/bot.rb', line 38

def config
  @config
end

#handler_threadsArray<Thread> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Array<Thread>)


59
60
61
# File 'lib/cinch/bot.rb', line 59

def handler_threads
  @handler_threads
end

#hostString (readonly)

Returns the bot’s hostname.

Returns:

  • (String)

    the bot’s hostname



46
47
48
# File 'lib/cinch/bot.rb', line 46

def host
  @host
end

#ircIRC (readonly)

Returns:



40
41
42
# File 'lib/cinch/bot.rb', line 40

def irc
  @irc
end

#last_connection_was_successfulBoolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


68
69
70
# File 'lib/cinch/bot.rb', line 68

def last_connection_was_successful
  @last_connection_was_successful
end

#loggerLogger

Returns:



42
43
44
# File 'lib/cinch/bot.rb', line 42

def logger
  @logger
end

#maskMask (readonly)

Returns:



48
49
50
# File 'lib/cinch/bot.rb', line 48

def mask
  @mask
end

#nick=(new_nick) ⇒ String #nickString

The bot’s nickname.

Overloads:

Returns:



551
552
553
# File 'lib/cinch/bot.rb', line 551

def nick
  @nick
end

#pluginsArray<Plugin> (readonly)

Returns All registered plugins.

Returns:

  • (Array<Plugin>)

    All registered plugins



56
57
58
# File 'lib/cinch/bot.rb', line 56

def plugins
  @plugins
end

#quittingBoolean (readonly)

Returns whether the bot is in the process of disconnecting.

Returns:

  • (Boolean)

    whether the bot is in the process of disconnecting



61
62
63
# File 'lib/cinch/bot.rb', line 61

def quitting
  @quitting
end

#realnameString (readonly)

Returns:



52
53
54
# File 'lib/cinch/bot.rb', line 52

def realname
  @realname
end

#signed_on_atTime (readonly)

Returns:

  • (Time)


54
55
56
# File 'lib/cinch/bot.rb', line 54

def signed_on_at
  @signed_on_at
end

#userString (readonly)

Returns:



50
51
52
# File 'lib/cinch/bot.rb', line 50

def user
  @user
end

#user_managerUserManager (readonly)

Returns:



63
64
65
# File 'lib/cinch/bot.rb', line 63

def user_manager
  @user_manager
end

Instance Method Details

#action(recipient, text) ⇒ void

This method returns an undefined value.

Invoke an action (/me) in/to a recipient (a channel or user). You should be using Channel#action and User#action instead.

Parameters:

  • recipient (String)

    the recipient

  • text (String)

    the message to send

See Also:



262
263
264
# File 'lib/cinch/bot.rb', line 262

def action(recipient, text)
  raw("PRIVMSG #{recipient} :\001ACTION #{text}\001")
end

#Channel(channel) ⇒ Channel

Helper method for turning a String into a #Channel object.

Examples:

on :message, /^please join (#.+)$/ do |m, target|
  Channel(target).join
end

Parameters:

  • channel (String)

    a channel name

Returns:



80
81
82
83
# File 'lib/cinch/bot.rb', line 80

def Channel(channel)
  return channel if channel.is_a?(Channel)
  @channel_manager.find_ensured(channel)
end

#configure {|config| ... } ⇒ void

This method returns an undefined value.

This method is used to set a bot’s options. It indeed does nothing else but yielding #config, but it makes for a nice DSL.

Yield Parameters:

  • config (Struct)

    the bot’s config



383
384
385
# File 'lib/cinch/bot.rb', line 383

def configure(&block)
  @callback.instance_exec(@config, &block)
end

#debug(msg) ⇒ void

This method returns an undefined value.

This method can be used by plugins to log custom messages.

Parameters:

  • message (String)

    The message to log

See Also:



470
471
472
# File 'lib/cinch/bot.rb', line 470

def debug(msg)
  @logger.debug(msg)
end

#dispatch(event, msg = nil, *arguments) ⇒ void

This method returns an undefined value.

Parameters:

  • event (Symbol)

    The event type

  • msg (Message, nil) (defaults to: nil)

    The message which is responsible for and attached to the event, or nil.

  • *arguments (Array)

    A list of additional arguments to pass to event handlers



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/cinch/bot.rb', line 338

def dispatch(event, msg = nil, *arguments)
  if handlers = find(event, msg)
    handlers.each do |handler|
      regexps, args, block = *handler
      # calling Message#match multiple times is not a problem
      # because we cache the result
      if msg
        regexp = regexps.find { |rx|
          msg.match(rx.to_r(msg), event)
        }
        captures = msg.match(regexp.to_r(msg), event).captures
      else
        captures = []
      end

      invoke(block, args, msg, captures, arguments)
    end
  end
end

#generate_next_nick(base = nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Try to create a free nick, first by cycling through all available alternatives and then by appending underscores.

Parameters:

  • base (String) (defaults to: nil)

    The base nick to start trying from

Returns:

  • String



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/cinch/bot.rb', line 572

def generate_next_nick(base = nil)
  nicks = @config.nicks || []

  if base
    # if `base` is not in our list of nicks to try, assume that it's
    # custom and just append an underscore
    if !nicks.include?(base)
      return base + "_"
    else
      # if we have a base, try the next nick or append an
      # underscore if no more nicks are left
      new_index = nicks.index(base) + 1
      if nicks[new_index]
        return nicks[new_index]
      else
        return base + "_"
      end
    end
  else
    # if we have no base, try the first possible nick
    new_nick = @config.nicks ? @config.nicks.first : @config.nick
  end
end

#haltvoid

This method returns an undefined value.

Stop execution of the current #on handler.



148
149
150
# File 'lib/cinch/bot.rb', line 148

def halt
  throw :halt
end

#helpers { ... } ⇒ void

This method returns an undefined value.

Define helper methods in the context of the bot.

Yields:

  • Expects a block containing method definitions



103
104
105
# File 'lib/cinch/bot.rb', line 103

def helpers(&b)
  Callback.class_eval(&b)
end

#join(channel, key = nil) ⇒ void

This method returns an undefined value.

Join a channel.

Parameters:

  • channel (String, Channel)

    either the name of a channel or a #Channel object

  • key (String) (defaults to: nil)

    optionally the key of the channel

See Also:



452
453
454
# File 'lib/cinch/bot.rb', line 452

def join(channel, key = nil)
  Channel(channel).join(key)
end

#msg(recipient, text, notice = false) ⇒ void Also known as: privmsg, send

This method returns an undefined value.

Sends a PRIVMSG to a recipient (a channel or user). You should be using Channel#send and User#send instead.

Parameters:

  • recipient (String)

    the recipient

  • text (String)

    the message to send

  • notice (Boolean) (defaults to: false)

    Use NOTICE instead of PRIVMSG?

See Also:



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/cinch/bot.rb', line 174

def msg(recipient, text, notice = false)
  text = text.to_s
  split_start = @config.message_split_start || ""
  split_end   = @config.message_split_end   || ""
  command = notice ? "NOTICE" : "PRIVMSG"

  text.split(/\r\n|\r|\n/).each do |line|
    maxlength = 510 - (":" + " #{command} " + " :").size
    maxlength = maxlength - self.mask.to_s.length - recipient.to_s.length
    maxlength_without_end = maxlength - split_end.bytesize

    if line.bytesize > maxlength
      splitted = []

      while line.bytesize > maxlength_without_end
        pos = line.rindex(/\s/, maxlength_without_end)
        r = pos || maxlength_without_end
        splitted << line.slice!(0, r) + split_end.tr(" ", "\u00A0")
        line = split_start.tr(" ", "\u00A0") + line.lstrip
      end

      splitted << line
      splitted[0, (@config.max_messages || splitted.size)].each do |string|
        string.tr!("\u00A0", " ") # clean string from any non-breaking spaces
        raw("#{command} #{recipient} :#{string}")
      end
    else
      raw("#{command} #{recipient} :#{line}")
    end
  end
end

#notice(recipient, text) ⇒ void

This method returns an undefined value.

Sends a NOTICE to a recipient (a channel or user). You should be using Channel#notice and User#notice instead.

Parameters:

  • recipient (String)

    the recipient

  • text (String)

    the message to send

See Also:



217
218
219
# File 'lib/cinch/bot.rb', line 217

def notice(recipient, text)
  msg(recipient, text, true)
end

#on(event, regexps = [], *args) {|*args| ... } ⇒ void

This method returns an undefined value.

Registers a handler.

Parameters:

  • event (String, Symbol, Integer)

    the event to match. Available events are all IRC commands in lowercase as symbols, all numeric replies, and the following:

    - :channel (a channel message)
    - :private (a private message)
    - :message (both channel and private messages)
    - :error   (handling errors, use a numeric error code as `match`)
    - :ctcp    (ctcp requests, use a ctcp command as `match`)
    
  • match (Regexp, String, Integer)

    every message of the right event will be checked against this argument and the event will only be called if it matches

Yield Parameters:

  • *args (String)

    each capture group of the regex will be one argument to the block. It is optional to accept them, though



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/cinch/bot.rb', line 307

def on(event, regexps = [], *args, &block)
  regexps = [*regexps]
  regexps = [//] if regexps.empty?

  event = event.to_sym

  regexps.map! do |regexp|
    pattern = case regexp
             when Pattern
               regexp
             when Regexp
               Pattern.new(nil, regexp, nil)
             else
               if event == :ctcp
                 Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}(?:$| .+)/, nil)
               else
                 Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}/, /$/)
               end
             end
    debug "[on handler] Registering handler with pattern `#{pattern.inspect}`, reacting on `#{event}`"
    pattern
  end
  (@events[event] ||= []) << [regexps, args, block]
end

#part(channel, reason = nil) ⇒ void

This method returns an undefined value.

Part a channel.

Parameters:

  • channel (String, Channel)

    either the name of a channel or a #Channel object

  • reason (String) (defaults to: nil)

    an optional reason/part message

See Also:



462
463
464
# File 'lib/cinch/bot.rb', line 462

def part(channel, reason = nil)
  Channel(channel).part(reason)
end

#quit(message = nil) ⇒ void

This method returns an undefined value.

Disconnects from the server.

Parameters:

  • message (String) (defaults to: nil)

    The quit message to send while quitting



391
392
393
394
395
# File 'lib/cinch/bot.rb', line 391

def quit(message = nil)
  @quitting = true
  command = message ? "QUIT :#{message}" : "QUIT"
  raw command
end

#raw(command) ⇒ void

This method returns an undefined value.

Sends a raw message to the server.

Parameters:

  • command (String)

    The message to send.

See Also:



160
161
162
# File 'lib/cinch/bot.rb', line 160

def raw(command)
  @irc.message(command)
end

#register_plugin(plugin) ⇒ void

This method returns an undefined value.

Registers a plugin.

Parameters:

  • plugin (Class<Plugin>)

    The plugin class to register



371
372
373
# File 'lib/cinch/bot.rb', line 371

def register_plugin(plugin)
  @plugins << plugin.new(self)
end

#register_pluginsvoid

This method returns an undefined value.

Register all plugins from ‘@config.plugins.plugins`.



361
362
363
364
365
# File 'lib/cinch/bot.rb', line 361

def register_plugins
  @config.plugins.plugins.each do |plugin|
    register_plugin(plugin)
  end
end

#safe_action(recipient, text) ⇒ void

TODO:

Handle mIRC color codes more gracefully.

This method returns an undefined value.

Like #action, but remove any non-printable characters from ‘text`. The purpose of this method is to send text from untrusted sources, like other users or feeds.

Note: this will break any mIRC color codes embedded in the string.

Parameters:

  • recipient (String)

    the recipient

  • text (String)

    the message to send

See Also:



279
280
281
# File 'lib/cinch/bot.rb', line 279

def safe_action(recipient, text)
  action(recipient, Cinch.filter_string(text))
end

#safe_msg(recipient, text) ⇒ void Also known as: safe_privmsg, safe_send

TODO:

Handle mIRC color codes more gracefully.

This method returns an undefined value.

Like #msg, but remove any non-printable characters from ‘text`. The purpose of this method is to send text of untrusted sources, like other users or feeds.

Note: this will break any mIRC color codes embedded in the string.

Parameters:

  • recipient (String)

    the recipient

  • text (String)

    the message to send

  • notice (Boolean)

    Use NOTICE instead of PRIVMSG?

See Also:



234
235
236
# File 'lib/cinch/bot.rb', line 234

def safe_msg(recipient, text)
  msg(recipient, Cinch.filter_string(text))
end

#safe_notice(recipient, text) ⇒ void

TODO:

Handle mIRC color codes more gracefully.

This method returns an undefined value.

Like #safe_msg but for notices.

Parameters:

  • recipient (String)

    the recipient

  • text (String)

    the message to send

  • notice (Boolean)

    Use NOTICE instead of PRIVMSG?

See Also:



249
250
251
# File 'lib/cinch/bot.rb', line 249

def safe_notice(recipient, text)
  msg(recipient, Cinch.filter_string(text), true)
end

#secure?Boolean

Returns True if the bot is using SSL to connect to the server.

Returns:

  • (Boolean)

    True if the bot is using SSL to connect to the server.



598
599
600
# File 'lib/cinch/bot.rb', line 598

def secure?
  @config[:ssl] == true || (@config[:ssl].is_a?(Hash) && @config[:ssl][:use])
end

#start(plugins = true) ⇒ void

Connects the bot to a server.

Connects the bot to a server.

Parameters:

  • plugins (Boolean) (defaults to: true)

    Automatically register plugins from ‘@config.plugins.plugins`?

  • plugins (Boolean) (defaults to: true)

    Automatically register plugins from ‘@config.plugins.plugins`?

Returns:

  • (void)
  • (void)


407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/cinch/bot.rb', line 407

def start(plugins = true)
  @reconnects = 0
  register_plugins if plugins

  begin
    @user_manager.each do |user|
      user.in_whois = false
      user.unsync_all
    end # reset state of all users

    @channel_manager.each do |channel|
      channel.unsync_all
    end # reset state of all channels

    @logger.debug "Connecting to #{@config.server}:#{@config.port}"
    @irc = IRC.new(self)
    @irc.connect

    if @config.reconnect && !@quitting
      # double the delay for each unsuccesful reconnection attempt
      if @last_connection_was_successful
        @reconnects = 0
        @last_connection_was_successful = false
      else
        @reconnects += 1
      end

      # Sleep for a few seconds before reconnecting to prevent being
      # throttled by the IRC server
      wait = 2**@reconnects
      @logger.debug "Waiting #{wait} seconds before reconnecting"
      sleep wait
    end
  end while @config.reconnect and not @quitting
end

#strict?Boolean

Returns True if the bot reports ISUPPORT violations as exceptions.

Returns:

  • (Boolean)

    True if the bot reports ISUPPORT violations as exceptions.



476
477
478
# File 'lib/cinch/bot.rb', line 476

def strict?
  @config.strictness == :strict
end

#synchronize(name) { ... } ⇒ void

This method returns an undefined value.

Since Cinch uses threads, all handlers can be run simultaneously, even the same handler multiple times. This also means, that your code has to be thread-safe. Most of the time, this is not a problem, but if you are accessing stored data, you will most likely have to synchronize access to it. Instead of managing all mutexes yourself, Cinch provides a synchronize method, which takes a name and block.

Synchronize blocks with the same name share the same mutex, which means that only one of them will be executed at a time.

Examples:

configure do |c|
  
  @i = 0
end

on :channel, /^start counting!/ do
  synchronize(:my_counter) do
    10.times do
      val = @i
      # at this point, another thread might've incremented :i already.
      # this thread wouldn't know about it, though.
      @i = val + 1
    end
  end
end

Parameters:

  • name (String, Symbol)

    a name for the synchronize block.

Yields:



138
139
140
141
142
143
# File 'lib/cinch/bot.rb', line 138

def synchronize(name, &block)
  # Must run the default block +/ fetch in a thread safe way in order to
  # ensure we always get the same mutex for a given name.
  semaphore = @semaphores_mutex.synchronize { @semaphores[name] }
  semaphore.synchronize(&block)
end

#unknown?false

This method is only provided in order to give Bot and User a common interface.

Returns:

  • (false)

    Always returns ‘false`.

See Also:



607
608
609
# File 'lib/cinch/bot.rb', line 607

def unknown?
  false
end

#User(user) ⇒ User

Helper method for turning a String into an #User object.

Examples:

on :message, /^tell me everything about (.+)$/ do |m, target|
  user = User(target)
  m.reply "%s is named %s and connects from %s" % [user.nick, user.name, user.host]
end

Parameters:

  • user (String)

    a user’s nickname

Returns:



94
95
96
97
# File 'lib/cinch/bot.rb', line 94

def User(user)
  return user if user.is_a?(User)
  @user_manager.find_ensured(user)
end