Skip to content

Creating a Birdwatcher Module

Michael Henriksen edited this page Oct 13, 2016 · 2 revisions

Creating a new module in Birdwatcher is pretty simple if you know a bit of Ruby. There a lot of helper methods and classes baked into Birdwatcher that you can use to output information to the console, querying the Twitter API, performing HTTP requests, natural language parsing of dates, and much more. A good place to start is to check out the documentation for the Birdwatcher::Module class here.

Module Directory Structure

All Birdwatcher modules live inside the lib/birdwatcher/modules directory. The main module directory is split up into sub directories that act as namespacing for the modules. If a module is manipulating users, it should live in the users/ directory. If it manipulates statuses, it should live in the statuses/ directory, and so on. The namespace directory and the filename without the .rb extension will also automatically become the module's path that will be used to reference the module inside the console, so make sure you name the file in a descriptive way.

Template Module Class

When you have found the right location for the new module, you can use this template to get a skeleton module up and running:

module Birdwatcher
  module Modules
    class MyNewModule < Birdwatcher::Module # Remember to rename the class
      self.meta = {
        :name        => "My New Module",
        :description => "Does something awesome.",
        :author      => "YOUR NAME <EMAIL>",
        :options     => {
          "USERS" => { # Just an example option; remove if not needed
            :value       => nil,
            :description => "Space-separated list of users to process (all users if empty)",
            :required    => false
          }
        }
      }

      def self.info
        # This is where you put additional information about the module.
        # It is optional to define this method, but encouraged to give helpful information.
      end

      def run
        # This method is called when the user runs the module.
        info("My New Module was executed!")
      end
    end
  end
end

All modules should live inside the Birdwatcher::Modules namespace and must be a subclass of the Birdwatcher::Module class. This is also what makes all the helper methods available for you to use in the #run method.

It is also required that all modules define a meta hash which is used by the framework to display human readable information about the module. The meta hash must have the keys :name, :description, :author and :options. If any of these are missing, Birdwatcher will throw an error next time it is started. The :options hash is where you define any options your module might need. The keys function as the option name and must be in UPPERCASE and the value hash is where you define any default value as well as a description and indication of wether the option is required to be set or not. The value hash must have the keys :default, :description and :required. To get an idea of how to define these options, you can browse some of the existing modules.

The #run method is where all the magic should happen. This method is called when a user issues the run command for your module.

Helper Method Highlights

Here are some helper methods that might be useful to you:

Outputting to the Console

To output information to the console, you can use the #info, #warn, #error and #fatal methods. They all require one argument which is the string you want to have outputted. The message will automatically get the right styling.

Another special method is the #task method which is useful if you have a longer running job and want to indicate to the user that it's being worked on. The method accepts a message to display while the task is running and a code block to execute. Here is an example:

task("Doing something that takes a while...") do
   do_long_running_task
end

When the code block is done executing, the method will append done after the message or failed in case an exception was raised inside the code block.

Asking the User for Confirmation

If your module is about to do something that you want the user to confirm (e.g. a destructive action like overriding a file), you can use the #confirm method. It requires an argument which is the question to ask the user. The method will handle user input and return true if the user said Yes or false if the user said No:

if confirm("Do you want to divide by zero?")
  0 / 0
end

Getting an Option Setting

If your module has defined any options, you can retrieve the value that the user has set with the #option_setting method:

screen_names = option_setting("USERS").split(" ")

Please keep in mind that the method will only return the raw value that was given. You will have to handle type casting, splitting, etc.

Getting a Twitter Client

Birdwatcher uses the excellent Twitter gem to communicate with the Twitter API. if you want to get a Twitter client, you can use the #twitter_client method. This will automatically instantiate a new Twitter::REST::Client configured with a random key pair from the configuration.

statuses = twitter_client.user_timeline("twitter")

Getting the Current Workspace

The current workspace is an instance of Birdwatcher::Models::Workspace which is a sub class of Sequel::Model. The current workspace model instance can be accessed with the current_workspace method:

users_in_workspace = current_workspace.users_dataset.order("screen_name")

For more information about how to work with Sequel models, have a look at the documentation.

Doing Concurrent Work

If your module does a lot of work, you can speed things up with concurrent threads. The method #thread_pool can give you a pool of threads to do the work in:

screen_names = option_setting("USERS").split(" ")
threads      = thread_pool
screen_names.each do |screen_name|
  threads.process do
    do_something(screen_name)
  end
end
threads.shutdown # Always remember the `#shutdown` call!

I hope this article gave you a bit of help getting started making your own Birdwatcher modules. Remember to check the documentation and go look at the code for existing modules for inspiration.