Skip to content

Commit

Permalink
Merge pull request #3 from jusleg/v0.1.2
Browse files Browse the repository at this point in the history
Rename gem + cleanup
  • Loading branch information
jusleg committed Mar 23, 2019
2 parents 7a80a6c + ada7227 commit c3de318
Show file tree
Hide file tree
Showing 29 changed files with 451 additions and 389 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Style/MixinUsage:
Style/ClassVars:
Enabled: true
Exclude:
- lib/toddlerbot/handlers/base_handler.rb
- lib/slackify/handlers/base.rb

AllCops:
TargetRubyVersion: 2.5
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## V0.1.2

* Renaming the gem from toddlerbot to slackify.
* Cleanup of `lib/slackify` folder.
* Added a new configuration: unhandled_handler. You can specify a subclass of `Slackify::Handlers::Base` in the config. This class will be called with `#unhandled` when a message has no regex match.

## V0.1.1

Fix constantize vulnerability in the slack controller by introducing supported handler through `#inherited` or `Toddlerbot::BaseHandler`.
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
toddlerbot (0.1.1)
slackify (0.1.2)
rails
slack-ruby-client

Expand Down Expand Up @@ -158,7 +158,7 @@ DEPENDENCIES
mocha
rake
rubocop
toddlerbot!
slackify!

BUNDLED WITH
1.17.3
74 changes: 49 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ToddlerBot [![Build Status](https://travis-ci.org/jusleg/toddlerbot.svg?branch=master)](https://travis-ci.org/jusleg/toddlerbot) [![Gem Version](https://badge.fury.io/rb/toddlerbot.svg)](https://badge.fury.io/rb/toddlerbot)
# Slackify [![Build Status](https://travis-ci.org/jusleg/slackify.svg?branch=master)](https://travis-ci.org/jusleg/slackify) [![Gem Version](https://badge.fury.io/rb/slackify.svg)](https://badge.fury.io/rb/slackify)

Toddlerbot is a gem that allows to build slackbots on Rails using the [Event API](https://api.slack.com/events-api) from Slack.
Slackify is a gem that allows to build slackbots on Rails using the [Event API](https://api.slack.com/events-api) from Slack.

## Table of Contents
* [How does it work](#how-does-it-work)
Expand All @@ -9,11 +9,12 @@ Toddlerbot is a gem that allows to build slackbots on Rails using the [Event API
* [Interactive messages](#handling-interactive-messages)
* [Slash Command](#handling-slash-commands)
* [Custom handler for event subtypes](#custom-handler-for-event-subtypes)
* [Custom unhandled handler](#custom-unhandled-handler)
* [Slack client](#slack-client)
* [Sending a simple message](#sending-a-simple-message)
* [Sending an interactive message](#sending-an-interactive-message)
* [Slack 3 second reply window](#slack-3-seconds-reply-window)
* [How to run your own toddlerbot](#how-to-run-your-own-toddlerbot)
* [How to run your own slackify](#how-to-run-your-own-slackify)
* [Initial Setup](#initial-setup)
* [Slack Setup](#slack-setup)

Expand Down Expand Up @@ -44,7 +45,7 @@ Those handlers are configured via the `config/handlers.yml` configuration file.
The ruby class `repeat_handler.rb` would look something like this:

```ruby
class RepeatHandler < Toddlerbot::BaseHandler
class RepeatHandler < Slackify::Handlers::Base
class << self
def repeat(params)
slack_client.chat_postMessage(
Expand All @@ -67,12 +68,12 @@ To add a new handler, you can add a new file under `app/handlers/` and start add
When sending an interactive message to a user, slack let's you define the `callback_id`. The app uses the callback id to select the proper handler for the message that was sent. The callback id must follow the given format: `class_name#method_name`. For instance if you set the callback id to `repeat_handler#repeat`, then `RepeatHandler#repeat` will be called. Adding new handlers does not require to update the `config/handlers.yml` configuration. You only need to update the callback id to define the proper handler to be used when you send an interactive message.

### Handling slash commands
The code also has an example of a slash command and its handler (`slash_handler.rb`). To add a command on the bot, head to you app configuration on https://api.slack.com/apps and navigate to Slack Commands using the sidebar. Create a new one. The important part is to set the path properly. To bind with the demo handler, you would need to setup the path like this: `/toddlerbot/slash/slash_handler/example_slash`. The format is `/toddlerbot/slash/[handler_name]/[action_name]`. An app shouldn't have many slash commands. Keep in mind that adding a slash command means that the whole organization will see it.
The code also has an example of a slash command and its handler (`slash_handler.rb`). To add a command on the bot, head to you app configuration on https://api.slack.com/apps and navigate to Slack Commands using the sidebar. Create a new one. The important part is to set the path properly. To bind with the demo handler, you would need to setup the path like this: `/slackify/slash/slash_handler/example_slash`. The format is `/slackify/slash/[handler_name]/[action_name]`. An app shouldn't have many slash commands. Keep in mind that adding a slash command means that the whole organization will see it.

You will need to whitelist the method in the handler to indicate it can be used as a slash command using `allow_slash_method`

```ruby
class DummyHandler < Toddlerbot::BaseHandler
class DummyHandler < Slackify::Handlers::Base
allow_slash_method :slash_command
class << self
Expand All @@ -86,18 +87,41 @@ end

### Custom handler for event subtypes

If you wish to add more functionalities to your bot, you can specify define new behaviours for different event subtypes. You can specify a hash with the event subtype as a key and the handler class as the value. Toddlerbot will call `.handle_event` on your class and pass the controller params as parameters.
If you wish to add more functionalities to your bot, you can specify define new behaviours for different event subtypes. You can specify a hash with the event subtype as a key and the handler class as the value. Slackify will call `.handle_event` on your class and pass the controller params as parameters.

```ruby
Toddlerbot.configuration.custom_event_subtype_handlers = {
Slackify.configuration.custom_event_subtype_handlers = {
file_share: ImageHandler
}
```

In this example, all events of subtype `file_share` will be sent to the `ImageHandler` class.

### Custom unhandled handler

By default, the Slackify has a handler that will be called if there is no regex match for a plain text message. `Slackify::Handlers::UnhandledHandler#unhandled` is called and it replies to the user by saying "This command is not handled at the moment". You can update this in the configuration by specifying a handler class for `unhandled_handler` that responds to a class method named `unhandled`. If you do not wish to use any handler for unhandled message, you can call `remove_unhandled_handler`

```ruby
# config/application.rb
Slackify.configure do |config|
# ...
config.unhandled_handler = MyCustomHandlerClass # enables a custom unhandled handler
config.remove_unhandled_handler # removes the unhandled_handler
end
```

```ruby
# app/handlers/my_custom_handler_class.rb
class MyCustomHandlerClass
def self.unhandled(params)
# ...
end
end
```

## Slack client
In order to send messages, the [slack ruby client gem](https://github.com/slack-ruby/slack-ruby-client) was used. You can send plain text messages, images and interactive messages. Since the bot was envisioned being more repsonsive than proactive, the client was made available for handlers to call using the `slack_client` method. If you wish to send messages outside of handlers, you can get the slack client by calling `Toddlerbot.configuration.slack_client`
In order to send messages, the [slack ruby client gem](https://github.com/slack-ruby/slack-ruby-client) was used. You can send plain text messages, images and interactive messages. Since the bot was envisioned being more repsonsive than proactive, the client was made available for handlers to call using the `slack_client` method. If you wish to send messages outside of handlers, you can get the slack client by calling `Slackify.configuration.slack_client`

### Sending a simple message
```ruby
Expand Down Expand Up @@ -134,14 +158,14 @@ slack_client.chat_postMessage(
```

## Slack 3 seconds reply window
Slack introduced a [3 seconds reply window](https://api.slack.com/messaging/interactivity#response) for interactive messages. That means that if you reply to an interactive message or slash command event with a json, slack will show either update the attachment or send a new one without having to use `chat_postMessage`. If you wish to use this feature with Toddlerbot, you only need to return either a json of an attachment or a plain text string when you handler method is called. **Your method should always return `nil` otherwise**.
Slack introduced a [3 seconds reply window](https://api.slack.com/messaging/interactivity#response) for interactive messages. That means that if you reply to an interactive message or slash command event with a json, slack will show either update the attachment or send a new one without having to use `chat_postMessage`. If you wish to use this feature with Slackify, you only need to return either a json of an attachment or a plain text string when you handler method is called. **Your method should always return `nil` otherwise**.

# How to run your own toddlerbot
# How to run your own slackify
## Initial Setup
1. Install toddlerbot in your app by adding the following line in your `Gemfile`:
1. Install slackify in your app by adding the following line in your `Gemfile`:

```ruby
gem "toddlerbot"
gem "slackify"
```

2. run the following command in your terminal:
Expand All @@ -150,7 +174,7 @@ gem "toddlerbot"
bundle install
```

3. Add handlers to your application. Remember to make them extend `Toddlerbot::BaseHandler`
3. Add handlers to your application. Remember to make them extend `Slackify::Handlers::Base`

4. Create a `config/handlers.yml` file and define your triggers for specific commands.

Expand All @@ -162,44 +186,44 @@ First, you'll need to create a new app on slack. Head over to [slack api](https:

1. **Set Slack Secret Token**

In order to verify that the requets are coming from slack, we'll need to set the slack secret token in toddlerbot. This value can be found as the signing secret in the app credentials section of the basic information page.
In order to verify that the requets are coming from slack, we'll need to set the slack secret token in slackify. This value can be found as the signing secret in the app credentials section of the basic information page.

2. **Add a bot user**

Under the feature section, click on "bot users". Pick a name for you slack bot and toggle on "Always Show My Bot as Online". Save the setting.

3. **Enable events subscription**

Under the feature section, click "Events subscription". Turn the feature on and use your app url followed by `/toddlerbot/event`. [Ngrok](https://ngrok.com/) can easily get you a public url if you are developing locally. The app needs to be running when you configure this url. After the url is configured, under the section "Subscribe to Bot Events", add the bot user event `message.im`.
Under the feature section, click "Events subscription". Turn the feature on and use your app url followed by `/slackify/event`. [Ngrok](https://ngrok.com/) can easily get you a public url if you are developing locally. The app needs to be running when you configure this url. After the url is configured, under the section "Subscribe to Bot Events", add the bot user event `message.im`.

4. **Activate the interactive components**

Under the feature section, click "interactive components". Turn the feature on and use your ngrok url followed by `/toddlerbot/interactive`. Save the setting.
Under the feature section, click "interactive components". Turn the feature on and use your ngrok url followed by `/slackify/interactive`. Save the setting.

5. **Install the App**

Under the setting section, click "install app" and proceed to install the app to the workspace. Once the app is installed, go back to the "install app" page and copy the Bot User OAuth Access Token.

6. **Configure Toddlerbot**
6. **Configure Slackify**
```ruby
Toddlerbot.configure do |config|
Slackify.configure do |config|
config.slack_bot_token = "xoxb-sdkjlkjsdflsd..."
config.slack_secret_token = "1234dummysecret"
end
```

7. **Add an initializer**
```ruby
# config/initializers/toddlerbot.rb
Toddlerbot.load_handlers
# config/initializers/slackify.rb
Slackify.load_handlers
```

8. **Define handlers specific subtypes** (Optional)

```ruby
# config/initializers/toddlerbot.rb
Toddlerbot.load_handlers
Toddlerbot.configuration.custom_event_subtype_handlers = {
# config/initializers/slackify.rb
Slackify.load_handlers
Slackify.configuration.custom_event_subtype_handlers = {
file_share: ImageHandler,
channel_join: JoinHandler,
...
Expand All @@ -210,4 +234,4 @@ Toddlerbot.configuration.custom_event_subtype_handlers = {


# LICENSE
Copyright (c) 2019 Justin Léger, Michel Chatmajian. See [LICENSE](https://github.com/jusleg/toddlerbot/blob/master/LICENSE) for further details.
Copyright (c) 2019 Justin Léger, Michel Chatmajian. See [LICENSE](https://github.com/jusleg/slackify/blob/master/LICENSE) for further details.
2 changes: 1 addition & 1 deletion app/controllers/concerns/slack_token_verify.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def verify_token

signature = "v0:#{timestamp}:#{request_body}"
calculated_hmac = "v0=" +
OpenSSL::HMAC.hexdigest("sha256", Toddlerbot.configuration.slack_secret_token, signature)
OpenSSL::HMAC.hexdigest("sha256", Slackify.configuration.slack_secret_token, signature)

return if ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module Toddlerbot
module Slackify
class SlackController < ActionController::API
include SlackTokenVerify

Expand Down Expand Up @@ -51,7 +51,7 @@ def slash_command_callback
private

def handle_direct_message_event
if handler = Toddlerbot.configuration.custom_event_subtype_handlers[params[:event][:subtype]]
if handler = Slackify.configuration.custom_event_subtype_handlers[params[:event][:subtype]]
handler.handle_event(params[:slack])
head :ok
return
Expand All @@ -60,7 +60,7 @@ def handle_direct_message_event
return if params[:event][:subtype] == "bot_message" || params[:event].key?(:bot_id) || params[:event][:hidden]

command = params[:event][:text]
Toddlerbot.configuration.handlers.call_command(command, params[:slack])
Slackify.configuration.handlers.call_command(command, params[:slack])
rescue RuntimeError => e
raise e unless e.message == "Component not found for a command message"
end
Expand All @@ -69,21 +69,21 @@ def handler_from_callback_id(callback_id)
class_name, method_name = callback_id.split('#')
class_name = class_name.camelize

raise Toddlerbot::Exceptions::HandlerNotSupported, class_name unless
Toddlerbot::BaseHandler.supported_handlers.include?(class_name)
raise Exceptions::HandlerNotSupported, class_name unless
Handlers::Base.supported_handlers.include?(class_name)

class_name.constantize.method(method_name)
end

def verify_handler_slash_permission(handler_class, handler_method)
handler_class = handler_class.camelize

raise Toddlerbot::Exceptions::HandlerNotSupported, handler_class unless
Toddlerbot::BaseHandler.supported_handlers.include?(handler_class)
raise Exceptions::HandlerNotSupported, handler_class unless
Handlers::Base.supported_handlers.include?(handler_class)

handler = handler_class.constantize
raise Toddlerbot::Exceptions::MissingSlashPermission, "#{handler_class}##{handler_method} is missing slash permission" unless
handler < BaseHandler && handler.allowed_slash_methods.include?(handler_method.to_sym)
raise Exceptions::MissingSlashPermission, "#{handler_class}##{handler_method} is missing slash permission" unless
handler < Handlers::Base && handler.allowed_slash_methods.include?(handler_method.to_sym)
end
end
end
6 changes: 3 additions & 3 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

Rails.application.routes.draw do
post '/toddlerbot/event', to: 'toddlerbot/slack#event_callback'
post '/toddlerbot/interactive', to: 'toddlerbot/slack#interactive_callback'
post '/toddlerbot/slash/:handler_class/:handler_method', to: 'toddlerbot/slack#slash_command_callback'
post '/slackify/event', to: 'slackify/slack#event_callback'
post '/slackify/interactive', to: 'slackify/slack#interactive_callback'
post '/slackify/slash/:handler_class/:handler_method', to: 'slackify/slack#slash_command_callback'
end
27 changes: 27 additions & 0 deletions lib/slackify.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require 'slackify/configuration'
require 'slackify/engine'
require 'slackify/exceptions'
require 'slackify/handlers'

module Slackify
class << self
attr_writer :configuration
def configuration
@configuration ||= Configuration.new
end

def reset
@configuration = Configuration.new
end

def configure
yield(configuration)
end

def load_handlers
@configuration.handlers = Handlers::Configuration.new
end
end
end
16 changes: 14 additions & 2 deletions lib/toddlerbot/configuration.rb → lib/slackify/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

require 'slack'

module Toddlerbot
module Slackify
class Configuration
attr_reader :custom_event_subtype_handlers, :slack_bot_token
attr_reader :custom_event_subtype_handlers, :slack_bot_token, :unhandled_handler
attr_accessor :handlers, :slack_secret_token, :slack_client

def initialize
Expand All @@ -13,6 +13,18 @@ def initialize
@handlers = nil
@slack_client = nil
@custom_event_subtype_handlers = {}
@unhandled_handler = Handlers::UnhandledHandler
end

def unhandled_handler=(handler)
raise Exceptions::InvalidHandler, "#{handler.class} is not a subclass of Slackify::Handlers::Base" unless
handler.is_a?(Handlers::Base)

@unhandled_handler = handler
end

def remove_unhandled_handler
@unhandled_handler = nil
end

def slack_bot_token=(token)
Expand Down
4 changes: 2 additions & 2 deletions lib/toddlerbot/engine.rb → lib/slackify/engine.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Toddlerbot
module Slackify
class Engine < Rails::Engine
isolate_namespace Toddlerbot
isolate_namespace Slackify
end
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

module Toddlerbot
module Slackify
module Exceptions
class HandlerNotSupported < StandardError; end
class InvalidHandler < StandardError; end
Expand Down
11 changes: 11 additions & 0 deletions lib/slackify/handlers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative 'handlers/base'
require_relative 'handlers/configuration'
require_relative 'handlers/unhandled_handler'
require_relative 'handlers/validator'

module Slackify
module Handlers
end
end
Loading

0 comments on commit c3de318

Please sign in to comment.