slackify 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +26 -10
- data/app/controllers/slackify/slack_controller.rb +7 -4
- data/lib/slackify.rb +1 -4
- data/lib/slackify/configuration.rb +36 -3
- data/lib/slackify/engine.rb +1 -0
- data/lib/slackify/exceptions.rb +4 -0
- data/lib/slackify/handlers.rb +1 -1
- data/lib/slackify/handlers/base.rb +13 -0
- data/lib/slackify/handlers/factory.rb +27 -0
- data/lib/slackify/handlers/unhandled_handler.rb +4 -0
- data/lib/slackify/handlers/validator.rb +3 -0
- data/lib/slackify/router.rb +32 -0
- metadata +3 -2
- data/lib/slackify/handlers/configuration.rb +0 -94
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e158dfddb3b113177499b3247dbc9a4a7a4f9fff55f87ecafb21064aa4549325
|
4
|
+
data.tar.gz: cb03ce87203fc97ca0f4d7a728f25b6f729288e539b1530b39e6d51f59f6020a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '093e9a1f4ab6e37c0020fb699d45ed392922477a195d72ba415ce702b3c2096663b6beba81364e61aae513ce0e3693f5463bd6a36a5ba48d2bc9888e4a48c856'
|
7
|
+
data.tar.gz: a0f3caca091ceef0e50e84e5d1a6b6c139dcf7de5982babec1b43e1f21d1376b1bdb76f223b4e43b4b9ddf925c66fb7906bf80aa5f9d3dc9df8e85e1631f5954
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## V0.3.0
|
2
|
+
|
3
|
+
* Add code documentation and improve exception message
|
4
|
+
* Add whitelisting of bot ids in the configuration through `whitelisted_bot_ids=`
|
5
|
+
* Refactored Handler configuration into `Slackify::Router` and `Slackify::Handlers::Factory`
|
6
|
+
* Improved testing
|
7
|
+
* Remove the need to perform `Slackify.load_handler`
|
8
|
+
* **Breaking change:** Given that we now load the handlers on `Slack.configure`, the configuration step done in `config/application.rb` will have to be done in an initializer to have all the handler class loaded.
|
9
|
+
|
1
10
|
## V0.2.0
|
2
11
|
|
3
12
|
Update `custom_event_subtype_handlers` to `custom_message_subtype_handlers` and add support for `custom_event_type_handlers`. This is a breaking change since we rename the field that was previously used. To fix, update any calls from `custom_event_subtype_handlers` to `custom_message_subtype_handlers` and you should be good to go.
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
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)
|
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) ![Gem downloads](https://img.shields.io/gem/dt/slackify)
|
2
2
|
|
3
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
4
|
|
@@ -218,29 +218,45 @@ First, you'll need to create a new app on slack. Head over to [slack api](https:
|
|
218
218
|
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.
|
219
219
|
|
220
220
|
6. **Configure Slackify**
|
221
|
+
Add a new initializer with the following code
|
222
|
+
|
221
223
|
```ruby
|
224
|
+
# config/initializers/slackify.rb
|
222
225
|
Slackify.configure do |config|
|
223
226
|
config.slack_bot_token = "xoxb-sdkjlkjsdflsd..."
|
224
227
|
config.slack_secret_token = "1234dummysecret"
|
225
228
|
end
|
226
229
|
```
|
227
230
|
|
228
|
-
7. **
|
231
|
+
7. **Define handlers specific subtypes** (Optional)
|
232
|
+
You can set custom [message subtype](https://api.slack.com/events/message) handlers or custom [event type](https://api.slack.com/events) handlers inside your configuration
|
233
|
+
|
229
234
|
```ruby
|
230
235
|
# config/initializers/slackify.rb
|
231
|
-
Slackify.
|
236
|
+
Slackify.configure do |config|
|
237
|
+
config.slack_bot_token = "xoxb-sdkjlkjsdflsd..."
|
238
|
+
config.slack_secret_token = "1234dummysecret"
|
239
|
+
config.custom_message_subtype_handlers = {
|
240
|
+
file_share: ImageHandler,
|
241
|
+
channel_join: JoinHandler,
|
242
|
+
...
|
243
|
+
}
|
244
|
+
config.custom_event_type_handlers = {
|
245
|
+
app_mention: ...
|
246
|
+
}
|
247
|
+
end
|
232
248
|
```
|
233
249
|
|
234
|
-
8. **
|
250
|
+
8. **Handle bot messages** (Highly optional)
|
251
|
+
If you want your bot to accept other bot messages (which you probably should not do), you can. In the configuration step, you can set an array of whitelisted bot ids.
|
235
252
|
|
236
253
|
```ruby
|
237
254
|
# config/initializers/slackify.rb
|
238
|
-
Slackify.
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
}
|
255
|
+
Slackify.configure do |config|
|
256
|
+
...
|
257
|
+
config.whitelisted_bot_ids = ['abc123', 'def456']
|
258
|
+
...
|
259
|
+
end
|
244
260
|
```
|
245
261
|
|
246
262
|
**At this point, you are ready to go 😄**
|
@@ -59,10 +59,13 @@ module Slackify
|
|
59
59
|
return
|
60
60
|
end
|
61
61
|
|
62
|
-
return if params[:event][:subtype] == "bot_message" ||
|
62
|
+
return if (params[:event][:subtype] == "bot_message" ||
|
63
|
+
params[:event].key?(:bot_id) ||
|
64
|
+
params[:event][:hidden]) &&
|
65
|
+
Slackify.configuration.whitelisted_bot_ids.exclude?(params.dig(:event, :bot_id))
|
63
66
|
|
64
67
|
command = params[:event][:text]
|
65
|
-
Slackify.
|
68
|
+
Slackify::Router.call_command(command, params[:slack])
|
66
69
|
rescue RuntimeError => e
|
67
70
|
raise e unless e.message == "Component not found for a command message"
|
68
71
|
end
|
@@ -76,7 +79,7 @@ module Slackify
|
|
76
79
|
class_name, method_name = callback_id.split('#')
|
77
80
|
class_name = class_name.camelize
|
78
81
|
|
79
|
-
raise Exceptions::HandlerNotSupported, class_name unless
|
82
|
+
raise Exceptions::HandlerNotSupported, "#{class_name} is not a subclass of Slackify::Handlers::Base" unless
|
80
83
|
Handlers::Base.supported_handlers.include?(class_name)
|
81
84
|
|
82
85
|
class_name.constantize.method(method_name)
|
@@ -85,7 +88,7 @@ module Slackify
|
|
85
88
|
def verify_handler_slash_permission(handler_class, handler_method)
|
86
89
|
handler_class = handler_class.camelize
|
87
90
|
|
88
|
-
raise Exceptions::HandlerNotSupported, handler_class unless
|
91
|
+
raise Exceptions::HandlerNotSupported, "#{handler_class} is not a subclass of Slackify::Handlers::Base" unless
|
89
92
|
Handlers::Base.supported_handlers.include?(handler_class)
|
90
93
|
|
91
94
|
handler = handler_class.constantize
|
data/lib/slackify.rb
CHANGED
@@ -4,6 +4,7 @@ require 'slackify/configuration'
|
|
4
4
|
require 'slackify/engine'
|
5
5
|
require 'slackify/exceptions'
|
6
6
|
require 'slackify/handlers'
|
7
|
+
require 'slackify/router'
|
7
8
|
|
8
9
|
module Slackify
|
9
10
|
class << self
|
@@ -19,9 +20,5 @@ module Slackify
|
|
19
20
|
def configure
|
20
21
|
yield(configuration)
|
21
22
|
end
|
22
|
-
|
23
|
-
def load_handlers
|
24
|
-
@configuration.handlers = Handlers::Configuration.new
|
25
|
-
end
|
26
23
|
end
|
27
24
|
end
|
@@ -3,42 +3,75 @@
|
|
3
3
|
require 'slack'
|
4
4
|
|
5
5
|
module Slackify
|
6
|
+
# Where the configuration for Slackify lives
|
6
7
|
class Configuration
|
7
8
|
attr_reader :custom_message_subtype_handlers, :slack_bot_token, :unhandled_handler, :custom_event_type_handlers
|
8
|
-
attr_accessor :handlers, :slack_secret_token, :slack_client
|
9
|
+
attr_accessor :handlers, :slack_secret_token, :slack_client, :whitelisted_bot_ids
|
9
10
|
|
10
11
|
def initialize
|
11
12
|
@slack_bot_token = nil
|
12
13
|
@slack_secret_token = nil
|
13
|
-
@handlers =
|
14
|
+
@handlers = generate_handlers
|
14
15
|
@slack_client = nil
|
15
16
|
@custom_message_subtype_handlers = {}
|
16
17
|
@custom_event_type_handlers = {}
|
17
18
|
@unhandled_handler = Handlers::UnhandledHandler
|
19
|
+
@whitelisted_bot_ids = []
|
18
20
|
end
|
19
21
|
|
22
|
+
# Set your own unhandled handler
|
20
23
|
def unhandled_handler=(handler)
|
21
|
-
raise
|
24
|
+
raise HandlerNotSupported, "#{handler.class} is not a subclass of Slackify::Handlers::Base" unless
|
22
25
|
handler < Handlers::Base
|
23
26
|
|
24
27
|
@unhandled_handler = handler
|
25
28
|
end
|
26
29
|
|
30
|
+
# Remove unhandled handler. The bot will not reply if the message doesn't
|
31
|
+
# match any regex
|
27
32
|
def remove_unhandled_handler
|
28
33
|
@unhandled_handler = nil
|
29
34
|
end
|
30
35
|
|
36
|
+
# Set the token that we will use to connect to slack
|
31
37
|
def slack_bot_token=(token)
|
32
38
|
@slack_bot_token = token
|
33
39
|
@slack_client = Slack::Web::Client.new(token: token).freeze
|
34
40
|
end
|
35
41
|
|
42
|
+
# Set a handler for a specific message subtype
|
43
|
+
# That handler will have to implement `self.handle_event(params)`
|
44
|
+
# see https://api.slack.com/events/message
|
36
45
|
def custom_message_subtype_handlers=(event_subtype_hash)
|
37
46
|
@custom_message_subtype_handlers = event_subtype_hash.with_indifferent_access
|
38
47
|
end
|
39
48
|
|
49
|
+
# Set a handler for a event type
|
50
|
+
# That handler will have to implement `self.handle_event(params)`
|
51
|
+
# see https://api.slack.com/events
|
40
52
|
def custom_event_type_handlers=(event_type_hash)
|
41
53
|
@custom_event_type_handlers = event_type_hash.with_indifferent_access
|
42
54
|
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Convert a hash to a list of lambda functions that will be called to handle
|
59
|
+
# the user messages
|
60
|
+
def generate_handlers
|
61
|
+
generated_handlers = []
|
62
|
+
read_handlers_yaml.each do |handler_hash|
|
63
|
+
handler = Handlers::Factory.for(handler_hash)
|
64
|
+
generated_handlers << handler
|
65
|
+
end
|
66
|
+
|
67
|
+
generated_handlers
|
68
|
+
end
|
69
|
+
|
70
|
+
# Reads the config/handlers.yml configuration
|
71
|
+
def read_handlers_yaml
|
72
|
+
raise 'config/handlers.yml does not exist' unless File.exist?("#{Rails.root}/config/handlers.yml")
|
73
|
+
|
74
|
+
YAML.load_file("#{Rails.root}/config/handlers.yml") || []
|
75
|
+
end
|
43
76
|
end
|
44
77
|
end
|
data/lib/slackify/engine.rb
CHANGED
data/lib/slackify/exceptions.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Slackify
|
4
|
+
# When things go boom, we need these.
|
4
5
|
module Exceptions
|
6
|
+
# You tried to call a class that was not extending the base handler
|
5
7
|
class HandlerNotSupported < StandardError; end
|
8
|
+
# You handler is failing validations
|
6
9
|
class InvalidHandler < StandardError; end
|
10
|
+
# The handler method was not whitelisted to be a slash command
|
7
11
|
class MissingSlashPermission < StandardError; end
|
8
12
|
end
|
9
13
|
end
|
data/lib/slackify/handlers.rb
CHANGED
@@ -2,16 +2,25 @@
|
|
2
2
|
|
3
3
|
module Slackify
|
4
4
|
module Handlers
|
5
|
+
# Base handler class that any user defined handlers must inherit from
|
5
6
|
class Base
|
6
7
|
@@supported_handlers = []
|
7
8
|
|
8
9
|
class << self
|
9
10
|
attr_reader :allowed_slash_methods
|
10
11
|
|
12
|
+
# Get the slack client that you can use to perform slack api calls
|
13
|
+
# @see https://github.com/slack-ruby/slack-ruby-client
|
11
14
|
def slack_client
|
12
15
|
Slackify.configuration.slack_client
|
13
16
|
end
|
14
17
|
|
18
|
+
# Enables a method to be called for a slash command.
|
19
|
+
#
|
20
|
+
# More context:
|
21
|
+
# Slash commands have extra validations. To call a slash command, it
|
22
|
+
# needs to call a method on a supported handler and that handler needs
|
23
|
+
# to explicitly specify which methods are for slash command.
|
15
24
|
def allow_slash_method(element)
|
16
25
|
if @allowed_slash_methods
|
17
26
|
@allowed_slash_methods.push(*element)
|
@@ -20,10 +29,14 @@ module Slackify
|
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
32
|
+
# Any class inheriting from Slackify::Handler::Base will be added to
|
33
|
+
# the list of supported handlers
|
23
34
|
def inherited(subclass)
|
24
35
|
@@supported_handlers.push(subclass.to_s)
|
25
36
|
end
|
26
37
|
|
38
|
+
# Show a list of the handlers supported by the app. Since we do
|
39
|
+
# metaprogramming, we want to ensure we can only call defined handlers
|
27
40
|
def supported_handlers
|
28
41
|
@@supported_handlers
|
29
42
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slackify
|
4
|
+
module Handlers
|
5
|
+
# Creates the handler structs
|
6
|
+
class Factory
|
7
|
+
def self.for(configuration)
|
8
|
+
Validator.verify_handler_integrity(configuration)
|
9
|
+
|
10
|
+
handler = OpenStruct.new
|
11
|
+
handler.name = configuration.keys.first
|
12
|
+
handler.commands = []
|
13
|
+
|
14
|
+
configuration[handler.name]['commands']&.each do |command|
|
15
|
+
built_command = OpenStruct.new
|
16
|
+
built_command.regex = command['regex']
|
17
|
+
built_command.handler = handler.name.camelize.constantize.method(command['action'])
|
18
|
+
built_command.description = command['description']
|
19
|
+
handler.commands << built_command.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
handler.commands.freeze
|
23
|
+
handler.freeze
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
module Slackify
|
4
4
|
module Handlers
|
5
|
+
# Default handler for any text message that is not handler by other handlers
|
6
|
+
# This can easily be replaced by setting you own unhandled handler when
|
7
|
+
# configuring slackify. Use `#unhandled_handler=` to set it. Your handler
|
8
|
+
# must implement a `self.unhandled` method.
|
5
9
|
class UnhandledHandler < Base
|
6
10
|
def self.unhandled(params)
|
7
11
|
slack_client.chat_postMessage(
|
@@ -2,7 +2,10 @@
|
|
2
2
|
|
3
3
|
module Slackify
|
4
4
|
module Handlers
|
5
|
+
# Simple validator for handlers. It will blow your app up on the
|
6
|
+
# configuration step instead of crashing when handling production requests.
|
5
7
|
class Validator
|
8
|
+
# Checks if your handler hash is valid. It's pass or raise 🧨💥
|
6
9
|
def self.verify_handler_integrity(handler)
|
7
10
|
handler_name = handler.keys.first
|
8
11
|
handler_class = handler_name.camelize.constantize
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Slackify
|
4
|
+
# In charge of routing a message to its proper handler
|
5
|
+
class Router
|
6
|
+
class << self
|
7
|
+
# List all available commands
|
8
|
+
def all_commands
|
9
|
+
Slackify.configuration.handlers.collect(&:commands).flatten
|
10
|
+
end
|
11
|
+
|
12
|
+
# Find the matching command based on the message string
|
13
|
+
def matching_command(message)
|
14
|
+
all_commands.each { |command| return command if command.regex.match? message }
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Call command based on message string
|
19
|
+
def call_command(message, params)
|
20
|
+
command = matching_command(message)
|
21
|
+
if command.nil?
|
22
|
+
return unless Slackify.configuration.unhandled_handler
|
23
|
+
|
24
|
+
Slackify.configuration.unhandled_handler.unhandled(params)
|
25
|
+
else
|
26
|
+
new_params = params.merge(command_arguments: command.regex.match(message).named_captures)
|
27
|
+
command.handler.call(new_params)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slackify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Leger
|
@@ -114,9 +114,10 @@ files:
|
|
114
114
|
- lib/slackify/exceptions.rb
|
115
115
|
- lib/slackify/handlers.rb
|
116
116
|
- lib/slackify/handlers/base.rb
|
117
|
-
- lib/slackify/handlers/
|
117
|
+
- lib/slackify/handlers/factory.rb
|
118
118
|
- lib/slackify/handlers/unhandled_handler.rb
|
119
119
|
- lib/slackify/handlers/validator.rb
|
120
|
+
- lib/slackify/router.rb
|
120
121
|
homepage: https://github.com/jusleg/slackify
|
121
122
|
licenses:
|
122
123
|
- MIT
|
@@ -1,94 +0,0 @@
|
|
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
|
-
def bot_auth_test
|
47
|
-
@bot_id = Slackify.configuration.slack_client.auth_test.fetch('user_id')
|
48
|
-
Rails.logger.debug("[Slackify] Bot successfully authenticated to Slack")
|
49
|
-
true
|
50
|
-
rescue KeyError
|
51
|
-
raise "[Slackify] The bot did not successfully authenticate to Slack"
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def environment_configurations
|
57
|
-
if %w(production staging development).exclude?(Rails.env) || skip_auth?
|
58
|
-
@bot_id = 'dummy_bot_id'
|
59
|
-
else
|
60
|
-
bot_auth_test
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def skip_auth?
|
65
|
-
ENV['SLACKIFY_AUTH_SKIP'] == '1'
|
66
|
-
end
|
67
|
-
|
68
|
-
def read_handlers_yaml
|
69
|
-
raise 'config/handlers.yml does not exist' unless File.exist?("#{Rails.root}/config/handlers.yml")
|
70
|
-
|
71
|
-
YAML.load_file("#{Rails.root}/config/handlers.yml") || []
|
72
|
-
end
|
73
|
-
|
74
|
-
def generate_handler_from_yaml(handler_yaml)
|
75
|
-
Validator.verify_handler_integrity(handler_yaml)
|
76
|
-
|
77
|
-
handler = OpenStruct.new
|
78
|
-
handler.name = handler_yaml.keys.first
|
79
|
-
handler.commands = []
|
80
|
-
|
81
|
-
handler_yaml[handler.name]['commands']&.each do |command|
|
82
|
-
built_command = OpenStruct.new
|
83
|
-
built_command.regex = command['regex']
|
84
|
-
built_command.handler = handler.name.camelize.constantize.method(command['action'])
|
85
|
-
built_command.description = command['description']
|
86
|
-
handler.commands << built_command.freeze
|
87
|
-
end
|
88
|
-
|
89
|
-
handler.commands.freeze
|
90
|
-
handler.freeze
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|