Class: EM::IrcBot

Inherits:
Object
  • Object
show all
Defined in:
lib/em/irc_bot.rb

Overview

A straightforward, if slightly bare-bones, EventMachine-based IRC bot framework.

IRC bots. We love 'em. Chatops wouldn't exist without 'em. Sometimes, when you're feeling a bit of self-loathing, you might decide you want to write one using EventMachine. Make life a bit easier on yourself, and use this class.

Basic usage is pretty simple: create a new instance, passing in all sorts of options to specify the server to talk to and the channels to join. Then specify what to respond to, and how to respond to it, by using the #on method to register callbacks in response to all lines which match a given regular expression. That's... pretty much it. The rest is up to you.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(nick, opts = {}) ⇒ IrcBot

Create a new IRC bot.

If you want to have the bot connect to the server immediately upon creation, pass the :server and :port (and optionally :tls) options. Otherwise, you can ask the bot to connect any later time with #connect.

Parameters:

  • nick (String)

    The IRC 'nickname' of the bot.

  • opts (Hash) (defaults to: {})

    Additional configuration options.

Options Hash (opts):

  • :serverpass (String)

    The server-level password needed to access the server. If you don't know you need this, you almost certainly don't. Typically, only private IRC servers use this.

  • :username (String)

    The 'username' of the bot, as seen by the IRC network. This defaults to "em-irc-bot" if you don't set it.

  • :realname (String)

    The 'realname' of the bot, as seen by the IRC network. This defaults to "EM::IrcBot" if you don't set it.

  • :channels (Array<String>)

    A list of channels to automatically join on startup. The name must include the leading #.

  • :server (String)

    The server to connect to. If you don't specify this, you can trigger a connection by calling #connect.

  • :port (Fixnum)

    The port on the server to connect to. If you don't specify this, you can trigger a connection by calling #connect.

  • :tls (Boolean)

    Whether to use TLS on the connection to the server. This option is only useful when :server and :port are set as options to the constructor.

  • :logger (Logger)

    A logger to use. All communication between the bot and server will be logged at INFO priority, so consider that when setting the logger's priority. If no logger is specified, a default logger will be setup to send only fatal errors to $stderr.

  • :command_prefix (String)

    The string which must precede any commands sent to the bot in-channel. The default is "!".



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/em/irc_bot.rb', line 74

def initialize(nick, opts = {})
	@nick             = nick
	@ready            = false
	@backlog          = ""
	@line_handlers    = {}
	@privmsg_handlers = {}
	@commands         = {}

	@serverpass = opts[:serverpass]
	@username   = opts[:username] || "em-irc-bot"
	@realname   = opts[:realname] || "EM::IrcBot"
	@channels   = opts[:channels] || []
	@log        = opts[:logger]   || Logger.new($stderr).tap do |l|
	                                   l.level = Logger::FATAL
	                                   l.formatter = proc { |s, dt, p, m| "#{m}\n" }
	                                 end
	@cmd_prefix = opts[:command_prefix] || "!"

	if opts[:server] and opts[:port]
		connect(opts[:server], opts[:port], opts[:tls])
	end

	on(/^ping( |$)/i) do |s, _|
		s.send_line "PONG"
	end

	on(/^:[^\s]+ PRIVMSG /, &method(:do_privmsg))

	listen_for(/./, &method(:command_handler))
end

Instance Attribute Details

#nickString (readonly)

This bot's nick.

Returns:

  • (String)


25
26
27
# File 'lib/em/irc_bot.rb', line 25

def nick
  @nick
end

Instance Method Details

#command(cmd, &blk) {|The, The, The| ... } ⇒ Object

Register a callback to be executed on a given command.

A command is anything said in-channel which is prefixed by the :command_prefix option passed to the bot's constructor ("!" by default), or anything at all said to the bot privately.

Only one callback can be registered for a given command. If you register for the same command twice, only the last callback will be activated.

Parameters:

  • cmd (String)

    The command to call back for. It must have no whitespace.

  • blk (Proc)

    The callback.

Yield Parameters:

  • The (EM::IrcBot::Message)

    full message that was received. This is given so you can reply through it

  • The (String)

    command itself. Just in case you have multiple commands pointing to the same block, so you can differentiate between them.

  • The (Array<String>)

    remaining arguments that were passed to the command. All command arguments are strictly whitespace-separated. There is no way to pass an argument containing whitespace.

Returns:

  • void



166
167
168
# File 'lib/em/irc_bot.rb', line 166

def command(cmd, &blk)
	@commands[cmd] = blk
end

#connect(host, port, tls = false) ⇒ Object

Connect to an IRC server.

If a connection is already established, the bot will be disconnect and then a new connection made to the specified server.

Parameters:

  • host (String)

    The name or address of a server to connect to.

  • port (Fixnum)

    The port number to connect to.

  • tls (Boolean) (defaults to: false)

    Whether or not to use TLS over the connection.

Returns:

  • void



119
120
121
122
123
124
125
# File 'lib/em/irc_bot.rb', line 119

def connect(host, port, tls = false)
	@host = host
	@port = port
	@tls  = tls
	
	reconnect
end

#join(ch) ⇒ Object

Make the bot join a channel.

Parameters:

  • ch (String)

    The name of the channel to join, including the leading #.

Returns:

  • void



134
135
136
# File 'lib/em/irc_bot.rb', line 134

def join(ch)
	send_line("JOIN #{ch}")
end

#listen_for(match, opts = {}, &blk) {|an, Any| ... } ⇒ Object

Note:

This method only matches against the message body -- what a user "normally" sees in their IRC client as conversation. While this is usually what you want, if you want your bot to respond to control data, like join/part, you'll want to use #on instead of this method.

Register a new callback for a PRIVMSG seen by the bot.

The fundamental way of causing the bot to interact with its environment is to watch for incoming messages, and run all callbacks associated with regular expressions which match the line that was received. This method registers those callbacks.

Parameters:

  • match (Regexp)

    A regular expression to match against the content of every message. Nothing special is done to the regex, so you'll probably want to anchor it at the beginning in most cases.

  • opts (Hash) (defaults to: {})

    Zero or more optional parameters that can alter the way that the handler behaves, or the circumstances in which it is invoked.

  • blk (Proc)

    A callback to be executed when an incoming message matches the regexp.

Yield Parameters:

  • an (EM::IrcBot::Message)

    object which contains the message, as well as information about the sender, and has some convenience methods for easily replying.

  • Any (String)

    captured subexpressions in match will be passed as additional arguments to the callback.

Returns:

  • void



203
204
205
206
# File 'lib/em/irc_bot.rb', line 203

def listen_for(match, opts = {}, &blk)
	@log.debug { "Setting handler for #{match.inspect}, opts: #{opts.inspect}" }
	@privmsg_handlers[match] = blk
end

#on(match, &blk) {|The, The| ... } ⇒ Object

Register a new callback to handle a line sent to the bot.

This method matches against everything in the data line; you'll need to handle all of the protocol internal parts yourself. In general, you probably want to use #listen_for instead.

Parameters:

  • match (Regexp)

    A regular expression to match against the line.

  • blk (Proc)

    The callback to be executed when an incoming line matches the regexp.

Yield Parameters:

  • The (EM::IrcBot)

    bot instance that generated the message; this allows you to call back into the bot to send replies.

  • The (String)

    line of data which was sent.



224
225
226
227
# File 'lib/em/irc_bot.rb', line 224

def on(match, &blk)
	@log.debug { "Setting 'on' handler for #{match.inspect}" }
	@line_handlers[match] = blk
end

#on_once(match, opts = {}, &blk) ⇒ Object

Register a new "one-shot" callback to handle a single line sent to the bot.

Sometimes, you want to respond to only the "next" message matching a particular regex. That's what this method is for. The first line that matches the regex will cause the callback to be called (in the same manner as #on), and then the handler will be deleted.

See Also:

  • EM::IrcBot.{{#on}


239
240
241
242
243
244
# File 'lib/em/irc_bot.rb', line 239

def on_once(match, opts = {}, &blk)
	on(match) do |*args|
		blk.call(*args)
		@line_handlers.delete(match)
	end
end

#readyObject

:nodoc:

Callback used by ConnHandler to signal to the bot that the connection is established.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/em/irc_bot.rb', line 283

def ready
	@log.debug "Ready to rock and/or roll"
	send_line("PASS #{@serverpass}") if @serverpass
	send_line("NICK #{@nick}")
	send_line("USER #{@username} 0 * :#{@realname}")
	
	on_once(/^:#{@nick} /) do
		@channels.each do |ch|
			join(ch)
		end
	end

	@ready = true
end

#ready?Boolean

Tell whether the bot is ready to do things.

Returns:

  • (Boolean)


302
303
304
# File 'lib/em/irc_bot.rb', line 302

def ready?
	@ready
end

#receive_data(s) ⇒ Object

:nodoc:

Callback used by ConnHandler to give us data that has come from the server.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/em/irc_bot.rb', line 311

def receive_data(s)
	s = @backlog + s
	
	while (i = s.index("\r\n")) do
		l = s[0..i-1]
		s = s[i+2..-1]

		@log.info "<< #{l}"
		@line_handlers.each_pair do |re, blk|
			blk.call(self, l) if re =~ l
		end
	end

	@backlog = s
end

#say(target, msg) ⇒ Object

Send a message to someone (or a channel)

Parameters:

  • target (String)

    the nick or channel to send the message to.

  • msg (String)

    the message to send.



274
275
276
# File 'lib/em/irc_bot.rb', line 274

def say(target, msg)
	send_line("PRIVMSG #{target} :#{msg}")
end

#send_line(s) ⇒ Object

Send a (raw) line to the server.

This method does nothing to your line, except terminate it. In general, you should rarely, if ever, use this method yourself.

Parameters:

  • s (String)

    The line to send to the server.

Returns:

  • void

Raises:

  • (ArgumentError)

    if the line you want to send has a newline or carriage return in it. That is not allowed.



258
259
260
261
262
263
264
265
266
# File 'lib/em/irc_bot.rb', line 258

def send_line(s)
	if s =~ /[\r\n]/
		raise ArgumentError,
		      "Line contained NL or CR"
	end

	@log.info ">> #{s}"
	@conn.send_data("#{s}\r\n")
end

#unbind(*args) ⇒ Object

:nodoc:

Callback used by ConnHandler to tell us that our connection has gone away.



332
333
334
335
336
337
338
# File 'lib/em/irc_bot.rb', line 332

def unbind(*args)
	@log.debug "Unbind called: #{args.inspect}"
	@ready = false
	@conn = nil

	EM.add_timer(1) { reconnect }
end