slackify 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2d1e93f4b0ce3d49b5f99a9299b8d6a188ec05214dbd64d862270d57d1b1d676
4
+ data.tar.gz: b86db43ec488d1a2bb92cbba7cb3c3daae39aca2b5ea01808d3ca60b26574ce3
5
+ SHA512:
6
+ metadata.gz: 9d6829fab21c24bbc5af54344ff4ca1e868506ea12c2556c4013440ffff034d7a026430571285808e2d0f742c88ba548ff55680348d69974a9a0c765926a738f
7
+ data.tar.gz: c5b55b8c19a0d0d24b71792292248da8da76af43ba35594448c1a5322a4b986fb7a04449982230acce954bea52fb4eee19c476ea1a8e2e68813b8272ccbc86b7
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ ## V0.1.2
2
+
3
+ * Renaming the gem from toddlerbot to slackify.
4
+ * Cleanup of `lib/slackify` folder.
5
+ * 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.
6
+
7
+ ## V0.1.1
8
+
9
+ Fix constantize vulnerability in the slack controller by introducing supported handler through `#inherited` or `Toddlerbot::BaseHandler`.
10
+
11
+ ## V0.1.0
12
+
13
+ First release. Everything is new.
data/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # 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)
2
+
3
+ Slackify is a gem that allows to build slackbots on Rails using the [Event API](https://api.slack.com/events-api) from Slack.
4
+
5
+ ## Table of Contents
6
+ * [How does it work](#how-does-it-work)
7
+ * [Handlers](#handlers)
8
+ * [Plain messages](#handling-plain-messages)
9
+ * [Interactive messages](#handling-interactive-messages)
10
+ * [Slash Command](#handling-slash-commands)
11
+ * [Custom handler for event subtypes](#custom-handler-for-event-subtypes)
12
+ * [Slack client](#slack-client)
13
+ * [Sending a simple message](#sending-a-simple-message)
14
+ * [Sending an interactive message](#sending-an-interactive-message)
15
+ * [Slack 3 second reply window](#slack-3-seconds-reply-window)
16
+ * [How to run your own slackify](#how-to-run-your-own-slackify)
17
+ * [Initial Setup](#initial-setup)
18
+ * [Slack Setup](#slack-setup)
19
+
20
+ # How does it work
21
+ The core logic of the bot resides in its handlers. When the app starts, a list of handler gets initialized from a config file (`config/handlers.yml`). This initializes all the plain message handlers. Out of the box, the application supports three types of events
22
+
23
+ 1. [Plain messages](#handling-plain-messages)
24
+ 2. [Interactive messages](#handling-interactive-messages)
25
+ 3. [Slash Command](#handling-slash-commands)
26
+
27
+ ## Handlers
28
+ ### Handling plain messages
29
+ These are the basic handlers. They use a regex to identify if they should be called. When a message event gets sent to the bots, the slack controller sends the message to the list of handlers. The message will be checked against the regex of every handler until there is a match. When there is a match, the handler will get called with all the parameters provided by slack. If no handler matches the command, the unhandled handler will be called instead.
30
+
31
+ Those handlers are configured via the `config/handlers.yml` configuration file. Let's dissect the configuration of a handler.
32
+
33
+ ```yaml
34
+ -
35
+ repeat_handler:
36
+ commands:
37
+ -
38
+ name: Repeat
39
+ description: "`repeat [sentence]`: Repeats the sentence you wrote"
40
+ regex: !ruby/regexp '/^repeat (?<sentence>.+)/i'
41
+ action: repeat
42
+ ```
43
+
44
+ The ruby class `repeat_handler.rb` would look something like this:
45
+
46
+ ```ruby
47
+ class RepeatHandler < Slackify::Handlers::Base
48
+ class << self
49
+ def repeat(params)
50
+ slack_client.chat_postMessage(
51
+ as_user: true,
52
+ channel: params[:event][:user],
53
+ text: "you just said: #{params[:command_arguments][:sentence]}",
54
+ )
55
+ end
56
+ end
57
+ end
58
+ ```
59
+
60
+ `config/handlers.yml` is configured to be an array of handlers. This examples only shows one handler. The top level key refers to the name of the handler in snake_case. In this case `repeat_handler` refers to RepeatHandler. This handler can handle multiple commands. In our example, it only handles one command: repeat. A command is defined by a `name`, `description`, `regex` and `action`. The name and description are more there to allow you to implement a custom help handler that would display all the commands. The core part is the regex and the action. If the regex match, the action is the method that will be called in the handler. In this example, if the regex matches, we'll call `RepeatHandler#repeat`.
61
+
62
+ To add a new handler, you can add a new file under `app/handlers/` and start adding new commands. You will also need to update the `config/handlers.yml` configuration to register the command.
63
+
64
+ **Note:** The regex supports [named capture](https://www.regular-expressions.info/named.html). In this example, we have a name example of `sentence`. When the handler command will be called, a key in the parameter hash will be added: `command_arguments`. This key will point to a hash of the capture name and value. In this case, `command_arguments => {sentence: "the sentence you wrote"}`
65
+
66
+ ### Handling interactive messages
67
+ 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.
68
+
69
+ ### Handling slash commands
70
+ 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.
71
+
72
+ You will need to whitelist the method in the handler to indicate it can be used as a slash command using `allow_slash_method`
73
+
74
+ ```ruby
75
+ class DummyHandler < Slackify::Handlers::Base
76
+ allow_slash_method :slash_command
77
+
78
+ class << self
79
+
80
+ def slash_command(_params)
81
+ "dummy_handler slash_command() was called"
82
+ end
83
+ end
84
+ end
85
+ ```
86
+
87
+ ### Custom handler for event subtypes
88
+
89
+ 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.
90
+
91
+ ```ruby
92
+ Slackify.configuration.custom_event_subtype_handlers = {
93
+ file_share: ImageHandler
94
+ }
95
+ ```
96
+
97
+ In this example, all events of subtype `file_share` will be sent to the `ImageHandler` class.
98
+
99
+ ## Slack client
100
+ 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`
101
+
102
+ ### Sending a simple message
103
+ ```ruby
104
+ slack_client.chat_postMessage(channel: 'MEMBER ID OR CHANNEL ID', text: 'Hello World', as_user: true)
105
+ ```
106
+
107
+ ### Sending an interactive message
108
+ ```ruby
109
+ slack_client.chat_postMessage(
110
+ channel: 'MEMBER ID OR CHANNEL ID',
111
+ as_user: true,
112
+ attachments: [{
113
+ "fallback": "Would you recommend it to customers?",
114
+ "title": "Would you recommend it to customers?",
115
+ "callback_id": "repeat_handler#repeat",
116
+ "color": "#3AA3E3",
117
+ "attachment_type": "default",
118
+ "actions": [
119
+ {
120
+ "name": "recommend",
121
+ "text": "Recommend",
122
+ "type": "button",
123
+ "value": "recommend"
124
+ },
125
+ {
126
+ "name": "no",
127
+ "text": "No",
128
+ "type": "button",
129
+ "value": "No"
130
+ }
131
+ ]
132
+ }]
133
+ )
134
+ ```
135
+
136
+ ## Slack 3 seconds reply window
137
+ 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**.
138
+
139
+ # How to run your own slackify
140
+ ## Initial Setup
141
+ 1. Install slackify in your app by adding the following line in your `Gemfile`:
142
+
143
+ ```ruby
144
+ gem "slackify"
145
+ ```
146
+
147
+ 2. run the following command in your terminal:
148
+
149
+ ```console
150
+ bundle install
151
+ ```
152
+
153
+ 3. Add handlers to your application. Remember to make them extend `Slackify::Handlers::Base`
154
+
155
+ 4. Create a `config/handlers.yml` file and define your triggers for specific commands.
156
+
157
+ 5. [Proceed to connect your bot to slack](#slack-setup)
158
+
159
+
160
+ ## Slack Setup
161
+ First, you'll need to create a new app on slack. Head over to [slack api](https://api.slack.com/apps) and create a new app.
162
+
163
+ 1. **Set Slack Secret Token**
164
+
165
+ 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.
166
+
167
+ 2. **Add a bot user**
168
+
169
+ 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.
170
+
171
+ 3. **Enable events subscription**
172
+
173
+ 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`.
174
+
175
+ 4. **Activate the interactive components**
176
+
177
+ Under the feature section, click "interactive components". Turn the feature on and use your ngrok url followed by `/slackify/interactive`. Save the setting.
178
+
179
+ 5. **Install the App**
180
+
181
+ 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.
182
+
183
+ 6. **Configure Slackify**
184
+ ```ruby
185
+ Slackify.configure do |config|
186
+ config.slack_bot_token = "xoxb-sdkjlkjsdflsd..."
187
+ config.slack_secret_token = "1234dummysecret"
188
+ end
189
+ ```
190
+
191
+ 7. **Add an initializer**
192
+ ```ruby
193
+ # config/initializers/slackify.rb
194
+ Slackify.load_handlers
195
+ ```
196
+
197
+ 8. **Define handlers specific subtypes** (Optional)
198
+
199
+ ```ruby
200
+ # config/initializers/slackify.rb
201
+ Slackify.load_handlers
202
+ Slackify.configuration.custom_event_subtype_handlers = {
203
+ file_share: ImageHandler,
204
+ channel_join: JoinHandler,
205
+ ...
206
+ }
207
+ ```
208
+
209
+ **At this point, you are ready to go 😄**
210
+
211
+
212
+ # LICENSE
213
+ Copyright (c) 2019 Justin Léger, Michel Chatmajian. See [LICENSE](https://github.com/jusleg/slackify/blob/master/LICENSE) for further details.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'bundler/gem_tasks'
10
+
11
+ require 'rake/testtask'
12
+
13
+ Rake::TestTask.new(:test) do |t|
14
+ t.libs << 'test'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = false
17
+ end
18
+
19
+ task default: :test
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SlackTokenVerify
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :verify_token
8
+ end
9
+
10
+ private
11
+
12
+ MAX_TS_DIFF_SECONDS = 30.seconds
13
+
14
+ def verify_token
15
+ hmac_header = request.headers["X-Slack-Signature"]
16
+ timestamp = request.headers["X-Slack-Request-Timestamp"]
17
+ request_body = request.raw_post
18
+
19
+ time_diff = (Time.now.to_i - timestamp.to_i).abs
20
+ if time_diff > MAX_TS_DIFF_SECONDS
21
+ message = "Slack webhook secret timestamp over time limit, limit is #{MAX_TS_DIFF_SECONDS}, "\
22
+ "time difference is #{time_diff}"
23
+ logger.warn(message)
24
+
25
+ return head :unauthorized
26
+ end
27
+
28
+ signature = "v0:#{timestamp}:#{request_body}"
29
+ calculated_hmac = "v0=" +
30
+ OpenSSL::HMAC.hexdigest("sha256", Slackify.configuration.slack_secret_token, signature)
31
+
32
+ return if ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
33
+
34
+ message = "Invalid Slack signature received"
35
+ logger.warn(message)
36
+
37
+ head :unauthorized
38
+ end
39
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ class SlackController < ActionController::API
5
+ include SlackTokenVerify
6
+
7
+ SLACK_TIMEOUT_SECONDS = 3.seconds
8
+
9
+ def event_callback
10
+ if params[:type] == "url_verification"
11
+ render plain: params["challenge"]
12
+ elsif params[:event][:type] == "message"
13
+ handle_direct_message_event
14
+ head :ok
15
+ else
16
+ head :bad_request
17
+ end
18
+ end
19
+
20
+ def interactive_callback
21
+ parsed_payload = JSON.parse(params[:payload])
22
+ response = handler_from_callback_id(parsed_payload["callback_id"]).call(parsed_payload)
23
+ if !response.nil?
24
+ Timeout.timeout(SLACK_TIMEOUT_SECONDS) do
25
+ render json: response
26
+ end
27
+ else
28
+ head :ok
29
+ end
30
+ rescue Timeout::Error
31
+ raise Timeout::Error, "Slack interactive callback timed out for #{parsed_payload['callback_id']}"
32
+ end
33
+
34
+ def slash_command_callback
35
+ handler_class = params[:handler_class]
36
+ handler_method = params[:handler_method]
37
+ verify_handler_slash_permission(handler_class, handler_method)
38
+
39
+ response = handler_class.camelize.constantize.method(handler_method).call(params)
40
+ if !response.nil?
41
+ Timeout.timeout(SLACK_TIMEOUT_SECONDS) do
42
+ render json: response
43
+ end
44
+ else
45
+ head :ok
46
+ end
47
+ rescue Timeout::Error
48
+ raise Timeout::Error, "Slack slash command callback timed out for command #{params[:command]}"
49
+ end
50
+
51
+ private
52
+
53
+ def handle_direct_message_event
54
+ if handler = Slackify.configuration.custom_event_subtype_handlers[params[:event][:subtype]]
55
+ handler.handle_event(params[:slack])
56
+ head :ok
57
+ return
58
+ end
59
+
60
+ return if params[:event][:subtype] == "bot_message" || params[:event].key?(:bot_id) || params[:event][:hidden]
61
+
62
+ command = params[:event][:text]
63
+ Slackify.configuration.handlers.call_command(command, params[:slack])
64
+ rescue RuntimeError => e
65
+ raise e unless e.message == "Component not found for a command message"
66
+ end
67
+
68
+ def handler_from_callback_id(callback_id)
69
+ class_name, method_name = callback_id.split('#')
70
+ class_name = class_name.camelize
71
+
72
+ raise Exceptions::HandlerNotSupported, class_name unless
73
+ Handlers::Base.supported_handlers.include?(class_name)
74
+
75
+ class_name.constantize.method(method_name)
76
+ end
77
+
78
+ def verify_handler_slash_permission(handler_class, handler_method)
79
+ handler_class = handler_class.camelize
80
+
81
+ raise Exceptions::HandlerNotSupported, handler_class unless
82
+ Handlers::Base.supported_handlers.include?(handler_class)
83
+
84
+ handler = handler_class.constantize
85
+ raise Exceptions::MissingSlashPermission, "#{handler_class}##{handler_method} is missing slash permission" unless
86
+ handler < Handlers::Base && handler.allowed_slash_methods.include?(handler_method.to_sym)
87
+ end
88
+ end
89
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ post '/slackify/event', to: 'slackify/slack#event_callback'
5
+ post '/slackify/interactive', to: 'slackify/slack#interactive_callback'
6
+ post '/slackify/slash/:handler_class/:handler_method', to: 'slackify/slack#slash_command_callback'
7
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slack'
4
+
5
+ module Slackify
6
+ class Configuration
7
+ attr_reader :custom_event_subtype_handlers, :slack_bot_token, :unhandled_handler
8
+ attr_accessor :handlers, :slack_secret_token, :slack_client
9
+
10
+ def initialize
11
+ @slack_bot_token = nil
12
+ @slack_secret_token = nil
13
+ @handlers = nil
14
+ @slack_client = nil
15
+ @custom_event_subtype_handlers = {}
16
+ @unhandled_handler = Handlers::UnhandledHandler
17
+ end
18
+
19
+ def unhandled_handler=(handler)
20
+ raise Exceptions::InvalidHandler, "#{handler.class} is not a subclass of Slackify::Handlers::Base" unless
21
+ handler.is_a?(Handlers::Base)
22
+
23
+ @unhandled_handler = handler
24
+ end
25
+
26
+ def disable_unhandled_handler
27
+ @unhandled_handler = nil
28
+ end
29
+
30
+ def slack_bot_token=(token)
31
+ @slack_bot_token = token
32
+ @slack_client = Slack::Web::Client.new(token: token).freeze
33
+ end
34
+
35
+ def custom_event_subtype_handlers=(event_subtype_hash)
36
+ @custom_event_subtype_handlers = event_subtype_hash.with_indifferent_access
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ class Engine < Rails::Engine
5
+ isolate_namespace Slackify
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ module Exceptions
5
+ class HandlerNotSupported < StandardError; end
6
+ class InvalidHandler < StandardError; end
7
+ class MissingSlashPermission < StandardError; end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ module Handlers
5
+ class Base
6
+ @@supported_handlers = []
7
+
8
+ class << self
9
+ attr_reader :allowed_slash_methods
10
+
11
+ def slack_client
12
+ Slackify.configuration.slack_client
13
+ end
14
+
15
+ def allow_slash_method(element)
16
+ if @allowed_slash_methods
17
+ @allowed_slash_methods.push(*element)
18
+ else
19
+ @allowed_slash_methods = Array(element)
20
+ end
21
+ end
22
+
23
+ def inherited(subclass)
24
+ @@supported_handlers.push(subclass.to_s)
25
+ end
26
+
27
+ def supported_handlers
28
+ @@supported_handlers
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module Slackify
6
+ module Handlers
7
+ class Configuration
8
+ attr_reader :bot_id
9
+ attr_accessor :handlers
10
+ delegate :each, to: :@handlers
11
+
12
+ def initialize
13
+ @handlers = []
14
+
15
+ read_handlers_yaml.each do |handler_yaml|
16
+ handler = generate_handler_from_yaml(handler_yaml)
17
+ @handlers << handler
18
+ end
19
+
20
+ environment_configurations
21
+ end
22
+
23
+ def all_commands
24
+ @handlers.collect(&:commands).flatten
25
+ end
26
+
27
+ def matching_command(message)
28
+ all_commands.each { |command| return command if command.regex.match? message }
29
+ nil
30
+ end
31
+
32
+ def call_command(message, params)
33
+ return if params.dig(:event, :user) == @bot_id || params.dig(:event, :message, :user) == @bot_id
34
+
35
+ command = matching_command(message)
36
+ if command.nil?
37
+ return unless Slackify.configuration.unhandled_handler
38
+
39
+ Slackify.configuration.unhandled_handler.unhandled(params)
40
+ else
41
+ new_params = params.merge(command_arguments: command.regex.match(message).named_captures)
42
+ command.handler.call(new_params)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def environment_configurations
49
+ @bot_id =
50
+ case Rails.env
51
+ when 'production', 'staging', 'development'
52
+ Slackify.configuration.slack_client.auth_test['user_id']
53
+ when 'test'
54
+ ''
55
+ end
56
+ end
57
+
58
+ def read_handlers_yaml
59
+ raise 'config/handlers.yml does not exist' unless File.exist?("#{Rails.root}/config/handlers.yml")
60
+
61
+ YAML.load_file("#{Rails.root}/config/handlers.yml") || []
62
+ end
63
+
64
+ def generate_handler_from_yaml(handler_yaml)
65
+ Validator.verify_handler_integrity(handler_yaml)
66
+
67
+ handler = OpenStruct.new
68
+ handler.name = handler_yaml.keys.first
69
+ handler.commands = []
70
+
71
+ handler_yaml[handler.name]['commands']&.each do |command|
72
+ built_command = OpenStruct.new
73
+ built_command.regex = command['regex']
74
+ built_command.handler = handler.name.camelize.constantize.method(command['action'])
75
+ built_command.description = command['description']
76
+ handler.commands << built_command.freeze
77
+ end
78
+
79
+ handler.commands.freeze
80
+ handler.freeze
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ module Handlers
5
+ class UnhandledHandler < Base
6
+ def self.unhandled(params)
7
+ slack_client.chat_postMessage(
8
+ as_user: true,
9
+ channel: params[:event][:user],
10
+ text: "This command is not currently handled at the moment",
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ module Handlers
5
+ class Validator
6
+ def self.verify_handler_integrity(handler)
7
+ handler_name = handler.keys.first
8
+ handler_class = handler_name.camelize.constantize
9
+
10
+ unless handler[handler_name].key?('commands') && handler.dig(handler_name, 'commands')&.any?
11
+ raise Exceptions::InvalidHandler, "#{handler_name} doesn't have any command specified"
12
+ end
13
+
14
+ handler_errors = []
15
+
16
+ handler.dig(handler_name, 'commands').each do |command|
17
+ command_errors = []
18
+
19
+ unless command['regex'].is_a?(Regexp)
20
+ command_errors.append('No regex was provided.')
21
+ end
22
+
23
+ unless !command['action'].to_s.strip.empty? && handler_class.respond_to?(command['action'])
24
+ command_errors.append('No valid action was provided.')
25
+ end
26
+
27
+ handler_errors.append("[#{command['name']}]: #{command_errors.join(' ')}") unless command_errors.empty?
28
+ end
29
+
30
+ unless handler_errors.empty?
31
+ raise Exceptions::InvalidHandler, "#{handler_name} is not valid: #{handler_errors.join(' ')}"
32
+ end
33
+ rescue NameError
34
+ raise Exceptions::InvalidHandler, "#{handler_name} is not defined"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'handlers/base'
4
+ require_relative 'handlers/configuration'
5
+ require_relative 'handlers/unhandled_handler'
6
+ require_relative 'handlers/validator'
7
+
8
+ module Slackify
9
+ module Handlers
10
+ end
11
+ end
data/lib/slackify.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'slackify/configuration'
4
+ require 'slackify/engine'
5
+ require 'slackify/exceptions'
6
+ require 'slackify/handlers'
7
+
8
+ module Slackify
9
+ class << self
10
+ attr_writer :configuration
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
14
+
15
+ def reset
16
+ @configuration = Configuration.new
17
+ end
18
+
19
+ def configure
20
+ yield(configuration)
21
+ end
22
+
23
+ def load_handlers
24
+ @configuration.handlers = Handlers::Configuration.new
25
+ end
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slackify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Justin Leger
8
+ - Michel Chatmajian
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-03-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: slack-ruby-client
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: minitest
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: mocha
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rubocop
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ description: Slackbot framework for Rails using the Events API
99
+ email: hey@justinleger.ca
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - CHANGELOG.md
105
+ - README.md
106
+ - Rakefile
107
+ - app/controllers/concerns/slack_token_verify.rb
108
+ - app/controllers/slackify/slack_controller.rb
109
+ - config/routes.rb
110
+ - lib/slackify.rb
111
+ - lib/slackify/configuration.rb
112
+ - lib/slackify/engine.rb
113
+ - lib/slackify/exceptions.rb
114
+ - lib/slackify/handlers.rb
115
+ - lib/slackify/handlers/base.rb
116
+ - lib/slackify/handlers/configuration.rb
117
+ - lib/slackify/handlers/unhandled_handler.rb
118
+ - lib/slackify/handlers/validator.rb
119
+ homepage: https://github.com/jusleg/slackify
120
+ licenses:
121
+ - MIT
122
+ metadata:
123
+ source_code_uri: https://github.com/jusleg/slackify
124
+ changelog_uri: https://github.com/jusleg/slackify/blob/master/CHANGELOG.md
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.7.3
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Rails slackbot framework
145
+ test_files: []