Class: Irc::Bot::Plugins::BotModule

Inherits:
Object
  • Object
show all
Defined in:
lib/rbot/plugins.rb,
lib/rbot/core/utils/extends.rb,
lib/rbot/core/utils/filters.rb

Overview

BotModule is the base class for the modules that enhance the rbot

functionality. Rather than subclassing BotModule, however, one should
subclass either CoreBotModule (reserved for system modules) or Plugin
(for user plugins).

A BotModule interacts with Irc events by defining one or more of the following
methods, which get called as appropriate when the corresponding Irc event
happens.

map(template, options)::
map!(template, options)::
   map is the new, cleaner way to respond to specific message formats without
   littering your plugin code with regexps, and should be used instead of
   #register() and #privmsg() (see below) when possible.

   The difference between map and map! is that map! will not register the new
   command as an alternative name for the plugin.

   Examples:

     plugin.map 'karmastats', :action => 'karma_stats'

     # while in the plugin...
     def karma_stats(m, params)
       m.reply "..."
     end

     # the default action is the first component
     plugin.map 'karma'

     # attributes can be pulled out of the match string
     plugin.map 'karma for :key'
     plugin.map 'karma :key'

     # while in the plugin...
     def karma(m, params)
       item = params[:key]
       m.reply 'karma for #{item}'
     end

     # you can setup defaults, to make parameters optional
     plugin.map 'karma :key', :defaults => {:key => 'defaultvalue'}

     # the default auth check is also against the first component
     # but that can be changed
     plugin.map 'karmastats', :auth => 'karma'

     # maps can be restricted to public or private message:
     plugin.map 'karmastats', :private => false
     plugin.map 'karmastats', :public => false

   See MessageMapper#map for more information on the template format and the
   allowed options.

listen(UserMessage)::
                       Called for all messages of any type. To
                       differentiate them, use message.kind_of? It'll be
                       either a PrivMessage, NoticeMessage, KickMessage,
                       QuitMessage, PartMessage, JoinMessage, NickMessage,
                       etc.

ctcp_listen(UserMessage)::
                       Called for all messages that contain a CTCP command.
                       Use message.ctcp to get the CTCP command, and
                       message.message to get the parameter string. To reply,
                       use message.ctcp_reply, which sends a private NOTICE
                       to the sender.

message(PrivMessage)::
                       Called for all PRIVMSG. Hook on this method if you
                       need to handle PRIVMSGs regardless of whether they are
                       addressed to the bot or not, and regardless of

privmsg(PrivMessage)::
                       Called for a PRIVMSG if the first word matches one
                       the plugin #register()ed for. Use m.plugin to get
                       that word and m.params for the rest of the message,
                       if applicable.

unreplied(PrivMessage)::
                       Called for a PRIVMSG which has not been replied to.

notice(NoticeMessage)::
                       Called for all Notices. Please notice that in general
                       should not be replied to.

kick(KickMessage)::
                       Called when a user (or the bot) is kicked from a
                       channel the bot is in.

invite(InviteMessage)::
                       Called when the bot is invited to a channel.

join(JoinMessage)::
                       Called when a user (or the bot) joins a channel

part(PartMessage)::
                       Called when a user (or the bot) parts a channel

quit(QuitMessage)::
                       Called when a user (or the bot) quits IRC

nick(NickMessage)::
                       Called when a user (or the bot) changes Nick
modechange(ModeChangeMessage)::
                       Called when a User or Channel mode is changed
topic(TopicMessage)::
                       Called when a user (or the bot) changes a channel
                       topic

welcome(WelcomeMessage)::
                       Called when the welcome message is received on
                       joining a server succesfully.

motd(MotdMessage)::
                       Called when the Message Of The Day is fully
                       recevied from the server.

connect::              Called when a server is joined successfully, but
                       before autojoin channels are joined (no params)

set_language(String)::
                       Called when the user sets a new language
                       whose name is the given String

save::                 Called when you are required to save your plugin's
                       state, if you maintain data between sessions

cleanup::              called before your plugin is "unloaded", prior to a
                       plugin reload or bot quit - close any open
                       files/connections or flush caches here

Direct Known Subclasses

CoreBotModule, Plugin

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBotModule

Initialise your bot module. Always call super if you override this method, as important variables are set up for you:

@bot

the rbot instance

@registry

the botmodule’s registry, which can be used to store permanent data (see Registry::Accessor for additional documentation)

Other instance variables which are defined and should not be overwritten byt the user, but aren’t usually accessed directly, are:

@manager

the plugins manager instance

@botmodule_triggers

an Array of words this plugin #register()ed itself for

@handler

the MessageMapper that handles this plugin’s maps



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/rbot/plugins.rb', line 182

def initialize
  @manager = Plugins::manager
  @bot = @manager.bot
  @priority = nil

  @botmodule_triggers = Array.new

  @handler = MessageMapper.new(self)
  @registry = Registry::Accessor.new(@bot, self.class.to_s.gsub(/^.*::/, ""))

  @manager.add_botmodule(self)
  if self.respond_to?('set_language')
    self.set_language(@bot.lang.language)
  end
end

Instance Attribute Details

#botObject (readonly)

the associated bot



155
156
157
# File 'lib/rbot/plugins.rb', line 155

def bot
  @bot
end

#handlerObject (readonly)

the message map handler



161
162
163
# File 'lib/rbot/plugins.rb', line 161

def handler
  @handler
end

#registryObject (readonly)

the plugin registry



158
159
160
# File 'lib/rbot/plugins.rb', line 158

def registry
  @registry
end

Instance Method Details

#botmodule_classObject

Returns the symbol :BotModule



205
206
207
# File 'lib/rbot/plugins.rb', line 205

def botmodule_class
  :BotModule
end

#call_event(ev, *args) ⇒ Object

Signal to other BotModules that an even happened.



236
237
238
# File 'lib/rbot/plugins.rb', line 236

def call_event(ev, *args)
  @bot.plugins.delegate('event_' + ev.to_s.gsub(/[^\w\?!]+/, '_'), *(args.push Hash.new))
end

#cleanupObject

Method called to cleanup before the plugin is unloaded. If you overload this method to handle additional cleanup tasks, remember to call super() so that the default cleanup actions are taken care of as well.



221
222
223
224
# File 'lib/rbot/plugins.rb', line 221

def cleanup
  # debug "Closing #{@registry}"
  @registry.close
end

#datafile(*fname) ⇒ Object

Filename for a datafile built joining the botclass, plugin dirname and actual file name



375
376
377
# File 'lib/rbot/plugins.rb', line 375

def datafile(*fname)
  @bot.path dirname, *fname
end

#default_auth(cmd, val, chan = "*") ⇒ Object

Sets the default auth for command path cmd to val on channel chan: usually chan is either “*” for everywhere, public and private (in which case it can be omitted) or “?” for private communications



277
278
279
280
281
282
283
284
285
# File 'lib/rbot/plugins.rb', line 277

def default_auth(cmd, val, chan="*")
  case cmd
  when "*", ""
    c = nil
  else
    c = cmd
  end
  Auth::defaultbotuser.set_default_permission(propose_default_path(c), val)
end

#define_filter(filter, &block) ⇒ Object

define a filter defaulting to the default filter group for this BotModule



166
167
168
# File 'lib/rbot/core/utils/filters.rb', line 166

def define_filter(filter, &block)
  @bot.register_filter(filter, self.filter_group, &block)
end

#dirnameObject

Directory name to be joined to the botclass to access data files. By default this is the plugin name itself, but may be overridden, for example by plugins that share their datafiles or for backwards compatibilty



369
370
371
# File 'lib/rbot/plugins.rb', line 369

def dirname
  name
end

#do_map(silent, *args) ⇒ Object

Auxiliary method called by #map and #map!



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/rbot/plugins.rb', line 259

def do_map(silent, *args)
  @handler.map(self, *args)
  # register this map
  map = @handler.last
  name = map.items[0]
  self.register name, :auth => nil, :hidden => silent
  @manager.register_map(self, map)
  unless self.respond_to?('privmsg')
    def self.privmsg(m) #:nodoc:
      handle(m)
    end
  end
end

#fake_message(string, opts = {}) ⇒ Object

Sometimes plugins need to create a new fake message based on an existing message: for example, this is done by alias, linkbot, reaction and remotectl.

This method simplifies the message creation, including a recursion depth check.

In the options you can specify the :bot, the :server, the :source, the :target, the message :class and whether or not to :delegate. To initialize these entries from an existing message, you can use :from

Additionally, if :from is given, the reply method of created message is overriden to reply to :from instead. The #in_thread attribute for created mesage is also copied from :from

If you don’t specify a :from you should specify a :source.

Raises:



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
# File 'lib/rbot/core/utils/extends.rb', line 485

def fake_message(string, opts={})
  if from = opts[:from]
    o = {
      :bot => from.bot, :server => from.server, :source => from.source,
      :target => from.target, :class => from.class, :delegate => true,
      :depth => from.recurse_depth + 1
    }.merge(opts)
  else
    o = {
      :bot => @bot, :server => @bot.server, :target => @bot.myself,
      :class => PrivMessage, :delegate => true, :depth => 1
    }.merge(opts)
  end
  raise RecurseTooDeep if o[:depth] > MAX_RECURSE_DEPTH
  new_m = o[:class].new(o[:bot], o[:server], o[:source], o[:target], string)
  new_m.recurse_depth = o[:depth]
  if from
    # the created message will reply to the originating message
    class << new_m
      self
    end.send(:define_method, :reply) do |*args|
      debug "replying to '#{from.message}' with #{args.first}"
      from.reply(*args)
    end
    # the created message will follow originating message's in_thread
    new_m.in_thread = from.in_thread if from.respond_to?(:in_thread)
  end
  return new_m unless o[:delegate]
  method = o[:class].to_s.gsub(/^Irc::|Message$/,'').downcase
  method = 'privmsg' if method == 'priv'
  o[:bot].plugins.irc_delegate(method, new_m)
end

#filter_groupObject

read accessor for the default filter group for this BotModule



155
156
157
# File 'lib/rbot/core/utils/filters.rb', line 155

def filter_group
  @filter_group ||= name
end

#filter_group=(name) ⇒ Object

write accessor for the default filter group for this BotModule



160
161
162
# File 'lib/rbot/core/utils/filters.rb', line 160

def filter_group=(name)
  @filter_group = name
end

#flush_registryObject

Method called to flush the registry, thus ensuring that the botmodule’s permanent data is committed to disk



212
213
214
215
# File 'lib/rbot/plugins.rb', line 212

def flush_registry
  # debug "Flushing #{@registry}"
  @registry.flush
end

#handle(m) ⇒ Object

Handle an Irc::PrivMessage for which this BotModule has a map. The method is called automatically and there is usually no need to call it explicitly.



230
231
232
# File 'lib/rbot/plugins.rb', line 230

def handle(m)
  @handler.handle(m)
end

#help(plugin, topic) ⇒ Object

Return a help string for your module. For complex modules, you may wish to break your help into topics, and return a list of available topics if topic is nil. plugin is passed containing the matching prefix for this message - if your plugin handles multiple prefixes, make sure you return the correct help for the prefix requested



313
314
315
# File 'lib/rbot/plugins.rb', line 313

def help(plugin, topic)
  "no help"
end

#load_filters(options = {}) ⇒ Object

load filters associated with the BotModule by looking in the path(s) specified by the :path option, defaulting to

  • Config::datadir/filters/<name>.rb

  • botclass/filters/<name>.rb

(note that as <name> we use #dirname() rather than #name(), since we’re looking for datafiles; this is only relevant for the very few plugins whose dirname differs from name)



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/rbot/core/utils/filters.rb', line 177

def load_filters(options={})
  case options[:path]
  when nil
    file = "#{self.dirname}.rb"
    paths = [
      File.join(Config::datadir, 'filters', file),
      @bot.path('filters', file)
    ]
  when Array
    paths = options[:path]
  else
    paths = [options[:path]]
  end

  paths.each do |file|
    instance_eval(File.read(file), file) if File.exist?(file)
  end
end

#map(*args) ⇒ Object

call-seq: map(template, options)

This is the preferred way to register the BotModule so that it responds to appropriately-formed messages on Irc.



245
246
247
# File 'lib/rbot/plugins.rb', line 245

def map(*args)
  do_map(false, *args)
end

#map!(*args) ⇒ Object

call-seq: map!(template, options)

This is the same as map but doesn’t register the new command as an alternative name for the plugin.



254
255
256
# File 'lib/rbot/plugins.rb', line 254

def map!(*args)
  do_map(true, *args)
end

#nameObject

Return an identifier for this plugin, defaults to a list of the message prefixes handled (used for error messages etc)



294
295
296
# File 'lib/rbot/plugins.rb', line 294

def name
  self.class.to_s.downcase.sub(/^#<module:.*?>::/,"").sub(/(plugin|module)?$/,"")
end

#priorityObject

Changing the value of @priority directly will cause problems, Please use priority=.



200
201
202
# File 'lib/rbot/plugins.rb', line 200

def priority
  @priority ||= 1
end

#priority=(prio) ⇒ Object

Define the priority of the module. During event delegation, lower priority modules will be called first. Default priority is 1



358
359
360
361
362
363
# File 'lib/rbot/plugins.rb', line 358

def priority=(prio)
  if @priority != prio
    @priority = prio
    @bot.plugins.mark_priorities_dirty
  end
end

#propose_default_path(cmd) ⇒ Object

Gets the default command path which would be given to command cmd



288
289
290
# File 'lib/rbot/plugins.rb', line 288

def propose_default_path(cmd)
  [name, cmd].compact.join("::")
end

#register(cmd, opts = {}) ⇒ Object

Register the plugin as a handler for messages prefixed cmd.

This can be called multiple times for a plugin to handle multiple message prefixes.

This command is now superceded by the #map() command, which should be used instead whenever possible.

Raises:

  • (ArgumentError)


325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/rbot/plugins.rb', line 325

def register(cmd, opts={})
  raise ArgumentError, "Second argument must be a hash!" unless opts.kind_of?(Hash)
  who = @manager.who_handles?(cmd)
  if who
    raise "Command #{cmd} is already handled by #{who.botmodule_class} #{who}" if who != self
    return
  end
  if opts.has_key?(:auth)
    @manager.register(self, cmd, opts[:auth])
  else
    @manager.register(self, cmd, propose_default_path(cmd))
  end
  @botmodule_triggers << cmd unless opts.fetch(:hidden, false)
end

#to_sObject

Just calls name



299
300
301
# File 'lib/rbot/plugins.rb', line 299

def to_s
  name
end

#to_symObject

Intern the name



304
305
306
# File 'lib/rbot/plugins.rb', line 304

def to_sym
  self.name.to_sym
end

#usage(m, params = {}) ⇒ Object

Default usage method provided as a utility for simple plugins. The MessageMapper uses ‘usage’ as its default fallback method.



343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/rbot/plugins.rb', line 343

def usage(m, params = {})
  if params[:failures].respond_to? :find
    friendly = params[:failures].find do |f|
      f.kind_of? MessageMapper::FriendlyFailure
    end
    if friendly
      m.reply friendly.friendly
      return
    end
  end
  m.reply(_("incorrect usage, ask for help using '%{command}'") % {:command => "#{@bot.nick}: help #{m.plugin}"})
end