Class: Autumn::Leaf

Inherits:
Object
  • Object
show all
Includes:
Anise::Annotation
Defined in:
lib/autumn/leaf.rb

Overview

This is the superclass that all Autumn leaves use. To write a leaf, sublcass this class and implement methods for each of your leaf’s commands. Your leaf’s repertoire of commands is derived from the names of the methods you write. For instance, to have your leaf respond to a “!hello” command in IRC, write a method like so:

def hello_command(stem, sender, reply_to, msg)
  stem.message "Why hello there!", reply_to
end

You can also implement this method as:

def hello_command(stem, sender, reply_to, msg)
  return "Why hello there!"
end

Methods of the form [word]_command tell the leaf to respond to commands in IRC of the form “![word]”. They should accept four parameters:

  1. the Stem that received the message,

  2. the sender hash for the person who sent the message (see below),

  3. the “reply-to” string (either the name of the channel that the command was typed on, or the nick of the person that whispered the message), and

  4. any text following the command. For instance, if the person typed “!eat A tasty slice of pizza”, the last parameter would be “A tasty slice of pizza”. This is nil if no text was supplied with the command.

Sender hashes: A “sender hash” is a hash with the following keys: nick (the user’s nickname), user (the user’s username), and host (the user’s hostname). Any of these fields except nick could be nil. Sender hashes are used throughout the Stem and Leaf classes, as well as other classes; they always have the same keys.

If your *_command method returns a string, it will be sent as an IRC message to “reply-to” parameter.If your leaf needs to respond to more complicated commands, you will have to override the did_receive_channel_message method (see below). If you like, you can remove the quit_command method in your subclass, for instance, to prevent the leaf from responding to !quit. You can also protect that method using filters (see “Filters”).

If you want to separate view logic from the controller, you can use ERb to template your views. See the render method for more information.

Hook Methods

Aside from adding your own *_command-type methods, you should investigate overriding the “hook” methods, such as will_start_up, did_start_up, did_receive_private_message, did_receive_channel_message, etc. There’s a laundry list of so-named methods you can override. Their default implementations do nothing, so there’s no need to call super.

Stem Convenience Methods

Most of the IRC actions (such as joining and leaving a channel, setting a topic, etc.) are part of a Stem object. If your leaf is only running off of one stem, you can call these stem methods directly, as if they were methods in the Leaf class. Otherwise, you will need to specify which stem to perform these IRC actions on. Usually, the stem is given to you, as a parameter for your *_command method, for instance.

For the sake of convenience, you can make Stem method calls on the stems attribute; these calls will be forwarded to every stem in the stems attribute. For instance, to broadcast a message to all servers and all channels:

stems.message "Ready for orders!"

Filters

Like Ruby on Rails, you can add filters to each of your commands to be executed before or after the command is run. You can do this using the before_filter and after_filter methods, just like in Rails. Filters are run in the order they are added to the chain. Thus, if you wanted to run your preload filter before you ran your cache filter, you’d write the calls in this order:

class MyLeaf < Leaf
  before_filter :my_preload
  before_filter :my_cache
end

See the documentation for the before_filter and after_filter methods and the README file for more information on filters.

Authentication

If a leaf is initialized with a hash for the authentication option, the values of that hash are used to choose an authenticator that will be run before each command. This authenticator will determine whether or not the user can run that command. The options that can be specified in this hash are:

type

The name of a class in the Autumn::Authentication module, in snake_case. Thus, if you wanted to use the Autumn::Authentication::Password class, which does password-based authentication, you’d set this value to password.

only

A list of protected commands for which authentication is required; all other commands are unprotected.

except

A list of unprotected commands; all other commands require authentication.

silent

Normally, when someone fails to authenticate himself before running a protected command, the leaf responds with an error message (e.g., “You have to authenticate with a password first”). Set this to true to suppress this behaivor.

In addition, you can also specify any custom options for your authenticator. These options are passed to the authenticator’s initialize method. See the classes in the Autumn::Authentication module for such options.

If you annotate a command method as protected, the authenticator will be run unconditionally, regardless of the only or except options:

class Controller < Autumn::Leaf
  def destructive_command(stem, sender, reply_to, msg)
    # ...
  end
  ann :destructive_command, :protected => true
end

Logging

Autumn comes with a framework for logging as well. It’s very similar to the Ruby on Rails logging framework. To log an error message:

logger.error "Quiz data is missing!"

By default the logger will only log info events and above in production seasons, and will log all messages for debug seasons. (See the README for more on seasons.) To customize the logger, and for more information on logging, see the LogFacade class documentation.

Colorizing and Formatting Text

The Autumn::Formatting module contains sub-modules which handle formatting for different clients (such as mIRC-style formatting, the most common). The specific formatting module that’s included depends on the leaf’s initialization options; see initialize.

Direct Known Subclasses

ChannelLeaf, Controller

Constant Summary collapse

DEFAULT_COMMAND_PREFIX =

Default for the command_prefix init option.

'!'
@@view_alias =
Hash.new { |h,k| k }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Leaf

Instantiates a leaf. This is generally handled by the Foliater class. Valid options are:

command_prefix

The string that must precede all command names (default “!”)

responds_to_private_messages

If true, the bot responds to known commands sent in private messages.

logger

The LogFacade instance for this leaf.

database

The name of a custom database connection to use.

formatter

The name of an Autumn::Formatting class to use as the formatter (chooses Autumn::Formatting::DEFAULT by default).

As well as any user-defined options you want.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/autumn/leaf.rb', line 178

def initialize(opts={})
  @port = opts[:port]
  @options = opts
  @options[:command_prefix] ||= DEFAULT_COMMAND_PREFIX
  @break_flag = false
  @logger = options[:logger]
  
  @stems = Set.new
  # Let the stems array respond to methods as if it were a single stem
  class << @stems
    def method_missing(meth, *args)
      if all? { |stem| stem.respond_to? meth } then
        collect { |stem| stem.send(meth, *args) }
      else
        super
      end
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args) ⇒ Object

Simplifies method calls for one-stem leaves.



207
208
209
210
211
212
213
# File 'lib/autumn/leaf.rb', line 207

def method_missing(meth, *args) # :nodoc:
  if stems.size == 1 and stems.only.respond_to? meth then
    stems.only.send meth, *args
  else
    super
  end
end

Instance Attribute Details

#loggerObject (readonly)

The LogFacade instance for this leaf.



158
159
160
# File 'lib/autumn/leaf.rb', line 158

def logger
  @logger
end

#optionsObject (readonly)

The configuration for this leaf.



162
163
164
# File 'lib/autumn/leaf.rb', line 162

def options
  @options
end

#stemsObject (readonly)

The Stem instances running this leaf.



160
161
162
# File 'lib/autumn/leaf.rb', line 160

def stems
  @stems
end

Instance Method Details

#database(dbname = nil, &block) ⇒ Object

Performs the block in the context of a database, referenced by symbol. For instance, if you had defined in database.yml a connection named “scorekeeper”, you could access that connection like so:

database(:scorekeeper) do
  [...]
end

If your database is named after your leaf (as in the example above for a leaf named “Scorekeeper”), it will automatically be set as the database context for the scope of all hook, filter and command methods. However, if your database connection is named differently, or if you are working in a method not invoked by the Leaf class, you will need to set the connection using this method.

If you omit the dbname parameter, it will try to guess the name of your database connection using the leaf’s name and the leaf’s class name.

If the database connection cannot be found, the block is executed with no database scope.



317
318
319
320
321
322
323
324
# File 'lib/autumn/leaf.rb', line 317

def database(dbname=nil, &block)
  dbname ||= database_name
  if dbname then
    repository dbname, &block
  else
    yield
  end
end

#database_nameObject

Trues to guess the name of the database connection this leaf is using. Looks for database connections named after either this leaf’s identifier or this leaf’s class name. Returns nil if no suitable connection is found.



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/autumn/leaf.rb', line 330

def database_name # :nodoc:
  return nil unless Module.constants.include? 'DataMapper' or Module.constants.include? :DataMapper
  raise "No such database connection #{options[:database]}" if options[:database] and DataMapper::Repository.adapters[options[:database]].nil?
  # Custom database connection specified
  return options[:database].to_sym if options[:database]
  # Leaf config name
  return leaf_name.to_sym if DataMapper::Repository.adapters[leaf_name.to_sym]
  # Leaf config name, underscored
  return leaf_name.methodize.to_sym if DataMapper::Repository.adapters[leaf_name.methodize.to_sym]
  # Leaf class name
  return self.class.to_s.to_sym if DataMapper::Repository.adapters[self.class.to_s.to_sym]
  # Leaf class name, underscored
  return self.class.to_s.methodize.to_sym if DataMapper::Repository.adapters[self.class.to_s.methodize.to_sym]
  # I give up
  return nil
end

#inspectObject

:nodoc:



347
348
349
# File 'lib/autumn/leaf.rb', line 347

def inspect # :nodoc:
  "#<#{self.class.to_s} #{leaf_name}>"
end

#irc_invite_event(stem, sender, arguments) ⇒ Object

:nodoc:



261
262
263
# File 'lib/autumn/leaf.rb', line 261

def irc_invite_event(stem, sender, arguments) # :nodoc:
  database { someone_did_invite stem, sender, arguments[:recipient], arguments[:channel] }
end

#irc_join_event(stem, sender, arguments) ⇒ Object

:nodoc:



234
235
236
# File 'lib/autumn/leaf.rb', line 234

def irc_join_event(stem, sender, arguments) # :nodoc:
  database { someone_did_join_channel stem, sender, arguments[:channel] }
end

#irc_kick_event(stem, sender, arguments) ⇒ Object

:nodoc:



265
266
267
# File 'lib/autumn/leaf.rb', line 265

def irc_kick_event(stem, sender, arguments) # :nodoc:
  database { someone_did_kick stem, sender, arguments[:channel], arguments[:recipient], arguments[:message] }
end

#irc_mode_event(stem, sender, arguments) ⇒ Object

:nodoc:



242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/autumn/leaf.rb', line 242

def irc_mode_event(stem, sender, arguments) # :nodoc:
  database do
    if arguments[:recipient] then
      gained_usermodes(stem, arguments[:mode]) { |prop| someone_did_gain_usermode stem, arguments[:recipient], prop, arguments[:parameter], sender }
      lost_usermodes(stem, arguments[:mode]) { |prop| someone_did_lose_usermode stem, arguments[:recipient], prop, arguments[:parameter], sender }
    elsif arguments[:parameter] and stem.server_type.privilege_mode?(arguments[:mode]) then
      gained_privileges(stem, arguments[:mode]) { |prop| someone_did_gain_privilege stem, arguments[:channel], arguments[:parameter], prop, sender }
      lost_privileges(stem, arguments[:mode]) { |prop| someone_did_lose_privilege stem, arguments[:channel], arguments[:parameter], prop, sender }
    else
      gained_properties(stem, arguments[:mode]) { |prop| channel_did_gain_property stem, arguments[:channel], prop, arguments[:parameter], sender }
      lost_properties(stem, arguments[:mode]) { |prop| channel_did_lose_property stem, arguments[:channel], prop, arguments[:parameter], sender }
    end
  end
end

#irc_nick_event(stem, sender, arguments) ⇒ Object

:nodoc:



279
280
281
# File 'lib/autumn/leaf.rb', line 279

def irc_nick_event(stem, sender, arguments) # :nodoc:
  database { nick_did_change stem, sender, arguments[:nick] }
end

#irc_notice_event(stem, sender, arguments) ⇒ Object

:nodoc:



269
270
271
272
273
274
275
276
277
# File 'lib/autumn/leaf.rb', line 269

def irc_notice_event(stem, sender, arguments) # :nodoc:
  database do
    if arguments[:recipient] then
      did_receive_notice stem, sender, arguments[:recipient], arguments[:message]
    else
      did_receive_notice stem, sender, arguments[:channel], arguments[:message]
    end
  end
end

#irc_part_event(stem, sender, arguments) ⇒ Object

:nodoc:



238
239
240
# File 'lib/autumn/leaf.rb', line 238

def irc_part_event(stem, sender, arguments) # :nodoc:
  database { someone_did_leave_channel stem, sender, arguments[:channel] }
end

#irc_privmsg_event(stem, sender, arguments) ⇒ Object

:nodoc:



222
223
224
225
226
227
228
229
230
231
232
# File 'lib/autumn/leaf.rb', line 222

def irc_privmsg_event(stem, sender, arguments) # :nodoc:
  database do
    if arguments[:channel] then
      command_parse stem, sender, arguments
      did_receive_channel_message stem, sender, arguments[:channel], arguments[:message]
    else
      command_parse stem, sender, arguments if options[:respond_to_private_messages]
      did_receive_private_message stem, sender, arguments[:message]
    end
  end
end

#irc_quit_event(stem, sender, arguments) ⇒ Object

:nodoc:



283
284
285
# File 'lib/autumn/leaf.rb', line 283

def irc_quit_event(stem, sender, arguments) # :nodoc:
  database { someone_did_quit stem, sender, arguments[:message] }
end

#irc_topic_event(stem, sender, arguments) ⇒ Object

:nodoc:



257
258
259
# File 'lib/autumn/leaf.rb', line 257

def irc_topic_event(stem, sender, arguments) # :nodoc:
  database { someone_did_change_topic stem, sender, arguments[:channel], arguments[:topic] }
end

#preconfigureObject

:nodoc:



198
199
200
201
202
203
# File 'lib/autumn/leaf.rb', line 198

def preconfigure # :nodoc:
  if options[:authentication] then
    @authenticator = Autumn::Authentication.const_get(options[:authentication]['type'].camelcase).new(options[:authentication].rekey(&:to_sym))
    stems.add_listener @authenticator
  end
end

#stem_ready(stem) ⇒ Object

METHODS INVOKED BY STEM #########################



217
218
219
220
# File 'lib/autumn/leaf.rb', line 217

def stem_ready(stem) # :nodoc:
  return unless Thread.exclusive { stems.ready?.all? }
  database { startup_check }
end

#will_start_upObject

Invoked just before the leaf starts up. Override this method to do any pre-startup tasks you need. The leaf is fully initialized and all methods and helper objects are available.



293
294
# File 'lib/autumn/leaf.rb', line 293

def will_start_up
end