- General Overview
- High Level Concepts 1. Channels 2. Triggers 3. Actions 4. Tasks
- Technical Overview
- Whenbot Gem
- Channels 1. Service 2. Triggers 3. Actions
Whenbot is an open source clone of "If This, Then That" (ifttt.com) for a single user. Whenbot allows a user to create Triggers to be monitored for specific events, and Actions to occur once a Trigger fires.
For example, whenever you post an Instagram photo, create a Tumblr blog post.
At a high level, Whenbot can be broken down into four main concepts:
- Channels
- Triggers
- Actions
- Tasks
Channels are the web services that Whenbot can connect to, such as Gmail, Twitter, Instagram, and so on.
Each Channel can contain Triggers, or Actions, or both. Every Channel should have at least one Trigger or Action.
Triggers are the events that Whenbot will watch for. For example, a Trigger can be set to watch for you to receive a new email.
Each Trigger is tied to a Channel. When a Trigger is "fired" (i.e. the conditions for a trigger are met), an Action is ready to be performed.
An Action is what Whenbot does once a Trigger is fired. For example, you could have an Action setup to create a new Tumblr post whenever you publish a new Instagram photo.
Actions are tied to Channels as well. Just like Triggers, you can't have an Action without a Channel.
A Task is the term we use to describe the overall concept of a Trigger, the Trigger's settings (e.g. specific words to watch for), the Action that will be run when a Trigger is fired, and the Action's settings.
In terms of the internal design, Whenbot has the following main components:
- Whenbot Gem
- Channels 1. Service 2. Triggers 3. Actions
Lets go a little deeper...
This is is the control center of Whenbot. It houses the logic for creating new Tasks. It manages the Channels, Triggers and Actions, querying and invoking them as needed.
The Whenbot Gem is also responsible for relaying webhook data to the appropriate Trigger. It does this by registering itself to receive webhook results via the following route:
# routes.rb
match '/whenbot/:channel(/:trigger)/callback', to: 'whenbot#callback'
Whenever new callback data is received from a web service, the Gem will look at the parameters found in the URL, and send the data along to the appropriate Channel and, if given, a specific Trigger.
The Gem also manages the scheduling of the polling for all the Triggers, calling the appropriate Action once a saved Trigger has been matched, and the User Interface for creating Tasks as a whole.
Channels are the web services that Whenbot can connect to. This houses any Triggers and/or Actions belonging to the Channel, and the code to connect to and query the web service itself.
Here's a sample outline of the modules and classes that would be found in a Channel that has both a Trigger and an Action:
module Whenbot
module GmailChannel
class Service
# ...
end
module Triggers
class NewEmailTrigger
# ...
end
end
module Actions
class SendEmailAction
# ...
end
end
end
end
Channels are each developed separately. A Channel author may choose to develop a set of Triggers and/or Actions as well.
A User can install a new Channel in two steps:
- Add a Channel's gem to the Gemfile. For example,
``gem 'whenbot-gmail'``
(Remember to run ``bundle install`` afterwards.)
- Add the following to
config/initializers/whenbot.rb
:
```ruby
Whenbot.config do |config|
config.add_channel Whenbot::GmailChannel
end
```
Once a Channel is registered with the Whenbot Gem, the Whenbot Gem can detect whether a Channel has any Triggers or Actions automagically.
The Service class handles all of the web service related responsibilities. This includes connecting to the service, making appropriate calls to the API, setting up any webhooks, and any additional methods needed for other functionality (e.g. polling calls).
The Service class is contained in the Whenbot::<channel_name>Channel
module:
module Whenbot
module GmailChannel
class Service
# Service methods
end
end
end
The Service class must contain a method called callback
:
def self.callback(data)
# ...
end
Note that when registering for a webhook, or when polling your service, you must set the callback to /whenbot/<your_channel_name>/callback
. The Whenbot Gem will catch these requests, and relay the webhook data to your Channel's Service.callback
method
With this approach, your callback
method will be responsible for determining which Trigger to call, based on the data received in the webhook.
If it's appropriate, however, it is recommended that you add the Trigger to the callback URL.
For instance, when polling or registering with Gmail to receive notification of any new emails that are received, the NewEmailTrigger
in the GmailChannel
would register its callback as follows:
/whenbot/gmail/new_email/callback
In this case, the GmailChannel::Triggers::NewEmailTrigger.callback
method would receive the data from the webhook.
N.B. Triggers can also be called "Channel Triggers," since each Trigger belongs to a specific Channel.
Once a Task has be saved by the User, a Trigger's most important job is to check any incoming callback data, as relayed by Whenbot, and call match_found
if any of the saved Triggers came back as a match.
Triggers have a few methods that are called by the Whenbot Gem:
module Whenbot
module GmailChannel
module Triggers
class NewEmailFrom
include Whenbot::Trigger
# Technical Note:
# We can replace the display_title, description and paramters
# methods with something like:
#
# option :display_title, "New email from"
# option :description, "Triggers whenever you receive a new email from "\
# "a specified email address."
#
# "option" would be a method that's automatically mixed-in to
# this class. Thoughts?
# Used by the UI to show the Triggers in a list
def self.display_title
"New email from"
end
# Additional description of this Trigger, shown in the UI
def self.description
"Triggers whenever you receive a new email from a specified email address."
end
# A form will be automatically generated when the User is
# creating a Task, to get the required parameters.
# These parameters will be saved to the database.
#
# Returns: Hash of parameters to be obtained from the
# user when setting up this Trigger.
def self.parameters
{
email_address: {
label: 'Email',
input_type: :text, # can also be :select, :checkbox, etc.
help_text: 'Email to watch for', # optional.
optional: false # :optional is optional ;)
}
}
end
# Returns: true if this Trigger with the specified
# params is a polling trigger. Otherwise, false.
def self.is_polling_trigger?(params)
# Check the given params hash and return
# true if this trigger can only be polled, and
# false if it's possible to setup a webhook.
# If false is returned, create_webhook_for(params)
# will get called by the Gem.
end
# Optional. Only needed if this is a polling Trigger.
#
# Polls the web service for this Trigger, with
# the given params. This method is called by
# Whenbot on regular intervals.
def self.poll(params)
# Note: When creating a webhook or polling a service, it is
# important to set the callback URL to
# /webhooks/<channel_name>/<trigger_name>/callback
# For example, this Trigger would use:
# /webhooks/gmail/new_email/callback
#
# Tip: If your service happens to be able to use the
# resulting poll data for more than one Trigger, you
# can set the callback path to your Service.callback
# method, and have that method call each of your
# Triggers
end
# Optional. Only needed if this is Trigger can be watched via a webhook.
#
# Creates a webhook on the server for this Trigger, with
# the given params.
#
# Returns: id (string) => an identifier given by the webservice,
# that is used to uniquely identify this hook.
def self.create_webhook_for(params)
# Note: When creating a webhook or polling a service, it is
# important to set the callback URL to
# /webhooks/<channel_name>/<trigger_name>/callback
# For example, this Trigger would use:
# /webhooks/gmail/new_email/callback
#
# Tip: If your service happens to be able to use the
# resulting webhook data for more than one Trigger, you
# can set the callback path to your Service.callback
# method, and have that method call each of your
# Triggers
end
# Cancels a webhook that has been setup on the server
# id => The unique id returned from create_webhook_for
def self.cancel_webhook_for(id, params)
# Optional. Only needed if this Trigger uses webhooks.
end
# Called by Whenbot whenever a new response is received
# from a web service for this Trigger.
# body (string) => Result of request.body.read
# headers (hash) => Result of request.headers
# returns:
# matches (array) => any matches found with the given inputs
# response_body (string) => (optional), useful if you need to
# return a specific response to the
# web service.
# status (symbol or integer) => (optional) http status code
def self.callback(params, body, headers)
#
end
end
end
end
end
Actions are the events that happen once a Trigger is fired.
The only required methods are self.parameters
and perform
. Here's an example Action class:
module Whenbot
module TwitterChannel
module Actions
class PostTweetAction
option :display_image, "twitter.png"
# Could also replace the methods below with something like:
#
# option :display_title, "Post a new Tweet"
# option :description, "This Action will post a new tweet, with "\
# "the text you specify"
#
# parameter :message, { label: 'Tweet text', input_type: :text }
def display_title
"Post a new Tweet"
end
def description
"This Action will post a new tweet, with the text you specify"
end
def parameters
{
message:
{
label: 'Tweet text',
input_type: :text
}
}
end
# Do the Action
# params => This action's parameter data, as given by the user
# match_data => Data from the Trigger, for use by the Action
def perform(params, match_data)
# Connect to the webservice and perform the action
end
end
end
end
end
Coming...
From https://github.com/ottawaruby/whenbot/wiki/Whenbot-UI-app-outline