slackify 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5f1105d73a8943514708110e7b544fbaf8e300770f902103dcca4da95bfc9fa
4
- data.tar.gz: b4a2b7dd7fcffed2cce55214fb96d2d09ea6ff40513d0360900187118857b6b8
3
+ metadata.gz: e3b94c8d8ee9aadee9d6c12cfa3e84ebd938e3b34908315e6add11a60d4ddca8
4
+ data.tar.gz: 5d04503efbf517d256d55bdb49977ef0e13b79d01615b34c7dd6df44c3fd6806
5
5
  SHA512:
6
- metadata.gz: a80fbbb042403ae528e9563c7d3cdaec8462e30a6aaf033d1c93c804b9e78cb58e1b03cbd1c324cab8329eb697da35fe5d81e47dffe633643fd39192d507a802
7
- data.tar.gz: f822f3cf7448b07f946f3a7d07b644d1ae70a57ee9cc8a9f06996c2de20f36561b4894294f5c350ab5c7a44394e75aed1e2a5ef1f127e1ee47cdac000975edbd
6
+ metadata.gz: a5cc3b8a43401712f68b2b818f7d5f8c3339a8c0cee57aed7bb2907d2553d8066ca7f8c27cd63744468b2b4c659d71719b2bf9ecba816f0947fcf00c8c7d95e0
7
+ data.tar.gz: d5c1bc32e1ec8c054d059418e7137b83420b0117835ba629b21f325943c8dafed174215922ea11c4d430ebdaa078340b3e327f62a7c370c98689e7bbe700205a
@@ -1,15 +1,23 @@
1
+ ## V0.4.0
2
+
3
+ - **BREAKING CHANGE:** Use `approved_bot_ids` instead of `whitelisted_bot_ids` ([6243c11](https://github.com/jusleg/slackify/commit'6243c11c3b5e49fa31feb2ebfd9394c362509524)) by [@DougEdey](https://github.com/DougEdey)
4
+ - Add support for named parameters in commands ([PR #12](https://github.com/jusleg/slackify/pull/12)) by [@DougEdey](https://github.com/DougEdey)
5
+ - Add support for custom parameters in commands ([PR #17](https://github.com/jusleg/slackify/pull/17)) by [@DougEdey](https://github.com/DougEdey)
6
+ - Tidy up some tests
7
+ - Force slack-ruby-client to be version 0.15.1 or above to allow for pagination of Conversations
8
+
1
9
  ## V0.3.2
2
10
 
3
- * Add support for interactive block payloads ([b6cf1db](https://github.com/jusleg/slackify/commit/b6cf1dbb47b832037ebff56054efa27c9e3251dc)) by [@drose-shopify](https://github.com/drose-shopify)
11
+ - Add support for interactive block payloads ([b6cf1db](https://github.com/jusleg/slackify/commit/b6cf1dbb47b832037ebff56054efa27c9e3251dc)) by [@drose-shopify](https://github.com/drose-shopify)
4
12
 
5
13
  ## V0.3.0
6
14
 
7
- * Add code documentation and improve exception message
8
- * Add whitelisting of bot ids in the configuration through `whitelisted_bot_ids=`
9
- * Refactored Handler configuration into `Slackify::Router` and `Slackify::Handlers::Factory`
10
- * Improved testing
11
- * Remove the need to perform `Slackify.load_handler`
12
- * **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.
15
+ - Add code documentation and improve exception message
16
+ - Add approval of bot ids in the configuration through `allowed_bot_ids=`
17
+ - Refactored Handler configuration into `Slackify::Router` and `Slackify::Handlers::Factory`
18
+ - Improved testing
19
+ - Remove the need to perform `Slackify.load_handler`
20
+ - **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.
13
21
 
14
22
  ## V0.2.0
15
23
 
@@ -24,13 +32,14 @@ Update how the `bot_id` is set in the handler configuration. You can disable the
24
32
  Custom unhandled_handler configuration fix. It wouldn't let you set a custom one as the validation was checking for `is_a?` instead of `<`
25
33
 
26
34
  ## V0.1.3
35
+
27
36
  Added `remove_unhandled_handler` as a configuration option to disable the unhandled handler.
28
37
 
29
38
  ## V0.1.2
30
39
 
31
- * Renaming the gem from toddlerbot to slackify.
32
- * Cleanup of `lib/slackify` folder.
33
- * 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.
40
+ - Renaming the gem from toddlerbot to slackify.
41
+ - Cleanup of `lib/slackify` folder.
42
+ - 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.
34
43
 
35
44
  ## V0.1.1
36
45
 
data/README.md CHANGED
@@ -3,23 +3,25 @@
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
 
5
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 message subtypes](#custom-handler-for-message-subtypes)
12
- * [Custom handler for event types](#custom-handler-for-event-types)
13
- * [Custom unhandled handler](#custom-unhandled-handler)
14
- * [Slack client](#slack-client)
15
- * [Sending a simple message](#sending-a-simple-message)
16
- * [Sending an interactive message](#sending-an-interactive-message)
17
- * [Slack 3 second reply window](#slack-3-seconds-reply-window)
18
- * [How to run your own slackify](#how-to-run-your-own-slackify)
19
- * [Initial Setup](#initial-setup)
20
- * [Slack Setup](#slack-setup)
6
+
7
+ - [How does it work](#how-does-it-work)
8
+ - [Handlers](#handlers)
9
+ - [Plain messages](#handling-plain-messages)
10
+ - [Interactive messages](#handling-interactive-messages)
11
+ - [Slash Command](#handling-slash-commands)
12
+ - [Custom handler for message subtypes](#custom-handler-for-message-subtypes)
13
+ - [Custom handler for event types](#custom-handler-for-event-types)
14
+ - [Custom unhandled handler](#custom-unhandled-handler)
15
+ - [Slack client](#slack-client)
16
+ - [Sending a simple message](#sending-a-simple-message)
17
+ - [Sending an interactive message](#sending-an-interactive-message)
18
+ - [Slack 3 second reply window](#slack-3-seconds-reply-window)
19
+ - [How to run your own slackify](#how-to-run-your-own-slackify)
20
+ - [Initial Setup](#initial-setup)
21
+ - [Slack Setup](#slack-setup)
21
22
 
22
23
  # How does it work
24
+
23
25
  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
24
26
 
25
27
  1. [Plain messages](#handling-plain-messages)
@@ -27,19 +29,19 @@ The core logic of the bot resides in its handlers. When the app starts, a list o
27
29
  3. [Slash Command](#handling-slash-commands)
28
30
 
29
31
  ## Handlers
32
+
30
33
  ### Handling plain messages
34
+
31
35
  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.
32
36
 
33
37
  Those handlers are configured via the `config/handlers.yml` configuration file. Let's dissect the configuration of a handler.
34
38
 
35
39
  ```yaml
36
- -
37
- repeat_handler:
40
+ - repeat_handler:
38
41
  commands:
39
- -
40
- name: Repeat
42
+ - name: Repeat
41
43
  description: "`repeat [sentence]`: Repeats the sentence you wrote"
42
- regex: !ruby/regexp '/^repeat (?<sentence>.+)/i'
44
+ regex: !ruby/regexp "/^repeat (?<sentence>.+)/i"
43
45
  action: repeat
44
46
  ```
45
47
 
@@ -65,20 +67,48 @@ To add a new handler, you can add a new file under `app/handlers/` and start add
65
67
 
66
68
  **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"}`
67
69
 
70
+ ### Handling messages with defined parameters
71
+
72
+ The regular expression matching in the previous example provides a mechanism to supply named parameters to a method. However, many handlers may want to use named & typed parameters. This can be done using the `base_command` and `parameters` options.
73
+
74
+ We can rewrite the command above to
75
+
76
+ ```yaml
77
+ - repeat_handler:
78
+ commands:
79
+ - name: Repeat
80
+ description: "`repeat [sentence]`: Repeats the sentence you wrote"
81
+ base_command: "repeat"
82
+ parameters:
83
+ - sentence: string
84
+ - times: 10
85
+ action: repeat
86
+ ```
87
+
88
+ And call the command with
89
+
90
+ `slackify repeat sentence="Why hullo there" times=10`
91
+
92
+ This will supply a command arguments that are typed and coerced to the defined types:
93
+
94
+ `command_arguments => {sentence: "Why hullo there", time: 10}`
95
+
68
96
  ### Handling interactive messages
97
+
69
98
  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.
70
99
 
71
100
  ### Handling slash commands
101
+
72
102
  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.
73
103
 
74
- You will need to whitelist the method in the handler to indicate it can be used as a slash command using `allow_slash_method`
104
+ You will need to allow the method in the handler to indicate it can be used as a slash command using `allow_slash_method`
75
105
 
76
106
  ```ruby
77
107
  class DummyHandler < Slackify::Handlers::Base
78
108
  allow_slash_method :slash_command
79
109
 
80
110
  class << self
81
-
111
+
82
112
  def slash_command(_params)
83
113
  "dummy_handler slash_command() was called"
84
114
  end
@@ -134,18 +164,21 @@ end
134
164
  ```
135
165
 
136
166
  ## Slack client
167
+
137
168
  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`
138
169
 
139
170
  ### Sending a simple message
171
+
140
172
  ```ruby
141
173
  slack_client.chat_postMessage(channel: 'MEMBER ID OR CHANNEL ID', text: 'Hello World', as_user: true)
142
174
  ```
143
175
 
144
176
  ### Sending an interactive message
177
+
145
178
  ```ruby
146
179
  slack_client.chat_postMessage(
147
- channel: 'MEMBER ID OR CHANNEL ID',
148
- as_user: true,
180
+ channel: 'MEMBER ID OR CHANNEL ID',
181
+ as_user: true,
149
182
  attachments: [{
150
183
  "fallback": "Would you recommend it to customers?",
151
184
  "title": "Would you recommend it to customers?",
@@ -171,10 +204,13 @@ slack_client.chat_postMessage(
171
204
  ```
172
205
 
173
206
  ## Slack 3 seconds reply window
207
+
174
208
  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**.
175
209
 
176
210
  # How to run your own slackify
211
+
177
212
  ## Initial Setup
213
+
178
214
  1. Install slackify in your app by adding the following line in your `Gemfile`:
179
215
 
180
216
  ```ruby
@@ -193,32 +229,32 @@ bundle install
193
229
 
194
230
  5. [Proceed to connect your bot to slack](#slack-setup)
195
231
 
196
-
197
232
  ## Slack Setup
233
+
198
234
  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.
199
235
 
200
236
  1. **Set Slack Secret Token**
201
237
 
202
- 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.
238
+ 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.
203
239
 
204
240
  2. **Add a bot user**
205
-
206
- 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.
241
+
242
+ 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.
207
243
 
208
244
  3. **Enable events subscription**
209
245
 
210
- 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`.
246
+ 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`.
211
247
 
212
248
  4. **Activate the interactive components**
213
-
214
- Under the feature section, click "interactive components". Turn the feature on and use your ngrok url followed by `/slackify/interactive`. Save the setting.
249
+
250
+ Under the feature section, click "interactive components". Turn the feature on and use your ngrok url followed by `/slackify/interactive`. Save the setting.
215
251
 
216
252
  5. **Install the App**
217
253
 
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.
254
+ 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
255
 
220
256
  6. **Configure Slackify**
221
- Add a new initializer with the following code
257
+ Add a new initializer with the following code
222
258
 
223
259
  ```ruby
224
260
  # config/initializers/slackify.rb
@@ -229,7 +265,7 @@ end
229
265
  ```
230
266
 
231
267
  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
268
+ 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
269
 
234
270
  ```ruby
235
271
  # config/initializers/slackify.rb
@@ -248,19 +284,19 @@ end
248
284
  ```
249
285
 
250
286
  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.
287
+ 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 approved bot ids.
252
288
 
253
289
  ```ruby
254
290
  # config/initializers/slackify.rb
255
291
  Slackify.configure do |config|
256
292
  ...
257
- config.whitelisted_bot_ids = ['abc123', 'def456']
293
+ config.approved_bot_ids = ['abc123', 'def456']
258
294
  ...
259
295
  end
260
296
  ```
261
297
 
262
298
  **At this point, you are ready to go 😄**
263
299
 
264
-
265
300
  # LICENSE
301
+
266
302
  Copyright (c) 2019 Justin Léger, Michel Chatmajian. See [LICENSE](https://github.com/jusleg/slackify/blob/master/LICENSE) for further details.
@@ -69,7 +69,7 @@ module Slackify
69
69
  return if (params[:event][:subtype] == "bot_message" ||
70
70
  params[:event].key?(:bot_id) ||
71
71
  params[:event][:hidden]) &&
72
- Slackify.configuration.whitelisted_bot_ids.exclude?(params.dig(:event, :bot_id))
72
+ Slackify.configuration.approved_bot_ids.exclude?(params.dig(:event, :bot_id))
73
73
 
74
74
  command = params[:event][:text]
75
75
  Slackify::Router.call_command(command, params[:slack])
@@ -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/parameter'
7
8
  require 'slackify/router'
8
9
 
9
10
  module Slackify
@@ -6,7 +6,7 @@ module Slackify
6
6
  # Where the configuration for Slackify lives
7
7
  class Configuration
8
8
  attr_reader :custom_message_subtype_handlers, :slack_bot_token, :unhandled_handler, :custom_event_type_handlers
9
- attr_accessor :handlers, :slack_secret_token, :slack_client, :whitelisted_bot_ids
9
+ attr_accessor :handlers, :slack_secret_token, :slack_client, :approved_bot_ids
10
10
 
11
11
  def initialize
12
12
  @slack_bot_token = nil
@@ -16,7 +16,7 @@ module Slackify
16
16
  @custom_message_subtype_handlers = {}
17
17
  @custom_event_type_handlers = {}
18
18
  @unhandled_handler = Handlers::UnhandledHandler
19
- @whitelisted_bot_ids = []
19
+ @approved_bot_ids = []
20
20
  end
21
21
 
22
22
  # Set your own unhandled handler
@@ -7,7 +7,7 @@ module Slackify
7
7
  class HandlerNotSupported < StandardError; end
8
8
  # You handler is failing validations
9
9
  class InvalidHandler < StandardError; end
10
- # The handler method was not whitelisted to be a slash command
10
+ # The handler method was not approved to be a slash command
11
11
  class MissingSlashPermission < StandardError; end
12
12
  end
13
13
  end
@@ -22,7 +22,7 @@ module Slackify
22
22
  # needs to call a method on a supported handler and that handler needs
23
23
  # to explicitly specify which methods are for slash command.
24
24
  def allow_slash_method(element)
25
- if @allowed_slash_methods
25
+ if defined?(@allowed_slash_methods) && @allowed_slash_methods
26
26
  @allowed_slash_methods.push(*element)
27
27
  else
28
28
  @allowed_slash_methods = Array(element)
@@ -16,6 +16,8 @@ module Slackify
16
16
  built_command.regex = command['regex']
17
17
  built_command.handler = handler.name.camelize.constantize.method(command['action'])
18
18
  built_command.description = command['description']
19
+ built_command.parameters = command['parameters']
20
+ built_command.base_command = command['base_command']
19
21
  handler.commands << built_command.freeze
20
22
  end
21
23
 
@@ -5,36 +5,83 @@ module Slackify
5
5
  # Simple validator for handlers. It will blow your app up on the
6
6
  # configuration step instead of crashing when handling production requests.
7
7
  class Validator
8
- # Checks if your handler hash is valid. It's pass or raise 🧨💥
9
- def self.verify_handler_integrity(handler)
10
- handler_name = handler.keys.first
11
- handler_class = handler_name.camelize.constantize
8
+ VALID_PARAMETER_TYPES = [:string, :int, :boolean, :float].freeze
12
9
 
13
- unless handler[handler_name].key?('commands') && handler.dig(handler_name, 'commands')&.any?
14
- raise Exceptions::InvalidHandler, "#{handler_name} doesn't have any command specified"
15
- end
10
+ class << self
11
+ # Checks if your handler hash is valid. It's pass or raise 🧨💥
12
+ def verify_handler_integrity(handler)
13
+ handler_name = handler.keys.first
14
+ handler_class = handler_name.camelize.constantize
15
+
16
+ unless handler[handler_name].key?('commands') && handler.dig(handler_name, 'commands')&.any?
17
+ raise Exceptions::InvalidHandler, "#{handler_name} doesn't have any command specified"
18
+ end
16
19
 
17
- handler_errors = []
20
+ handler_errors = []
18
21
 
19
- handler.dig(handler_name, 'commands').each do |command|
20
- command_errors = []
22
+ handler.dig(handler_name, 'commands').each do |command|
23
+ command_errors = []
21
24
 
22
- unless command['regex'].is_a?(Regexp)
23
- command_errors.append('No regex was provided.')
24
- end
25
+ unless command['regex'] || command['base_command']
26
+ command_errors.append('No regex or base command was provided.')
27
+ end
28
+
29
+ if command['regex'].present?
30
+ if command['base_command'].present?
31
+ command_errors.append('Regex and base_command cannot be used in the same handler.')
32
+ end
33
+
34
+ if command['parameters'].present?
35
+ command_errors.append('Regex and parameters cannot be used in the same handler.')
36
+ end
37
+
38
+ unless command['regex'].is_a?(Regexp)
39
+ command_errors.append('No regex was provided.')
40
+ end
41
+ end
25
42
 
26
- unless !command['action'].to_s.strip.empty? && handler_class.respond_to?(command['action'])
27
- command_errors.append('No valid action was provided.')
43
+ if command['base_command']
44
+ unless command['base_command'].is_a?(String)
45
+ command_errors.append('Invalid base command provided, it must be a string.')
46
+ end
47
+
48
+ if command['parameters'].present?
49
+ command_errors << validate_parameters(command['parameters'])
50
+ end
51
+ end
52
+
53
+ unless !command['action'].to_s.strip.empty? && handler_class.respond_to?(command['action'])
54
+ command_errors.append('No valid action was provided.')
55
+ end
56
+ command_errors = command_errors.flatten.compact
57
+ handler_errors.append("[#{command['name']}]: #{command_errors.join(' ')}") unless command_errors.empty?
28
58
  end
29
59
 
30
- handler_errors.append("[#{command['name']}]: #{command_errors.join(' ')}") unless command_errors.empty?
60
+ unless handler_errors.empty?
61
+ raise Exceptions::InvalidHandler, "#{handler_name} is not valid: #{handler_errors.join(' ')}"
62
+ end
63
+ rescue NameError
64
+ raise Exceptions::InvalidHandler, "#{handler_name} is not defined"
31
65
  end
32
66
 
33
- unless handler_errors.empty?
34
- raise Exceptions::InvalidHandler, "#{handler_name} is not valid: #{handler_errors.join(' ')}"
67
+ def validate_parameters(parameters)
68
+ errors = []
69
+ parameters.each do |parameter|
70
+ key = parameter.keys[0]
71
+ type = parameter.values[0]
72
+
73
+ next if VALID_PARAMETER_TYPES.include?(type.to_sym)
74
+
75
+ type.constantize
76
+ next if Slackify::Parameter.supported_parameters.include?(type)
77
+
78
+ errors << "Invalid parameter type for: #{key}, '#{type}'.\n"\
79
+ "If this is a custom parameter, make sure it inherits from Slackify::Parameter"
80
+ rescue NameError
81
+ errors << "Failed to find the custom class for: #{key}, '#{type}'."
82
+ end
83
+ errors
35
84
  end
36
- rescue NameError
37
- raise Exceptions::InvalidHandler, "#{handler_name} is not defined"
38
85
  end
39
86
  end
40
87
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Slackify
4
+ # Base parameter class that any user defined parameters must inherit from
5
+ class Parameter
6
+ @@supported_parameters = []
7
+
8
+ class << self
9
+ # Any class inheriting from Slackify::Parameter will be added to
10
+ # the list of supported parameters
11
+ def inherited(subclass)
12
+ @@supported_parameters.push(subclass.to_s)
13
+ end
14
+
15
+ # Show a list of the parameters supported by the app. Since we do
16
+ # metaprogramming, we want to ensure we can only call defined parameter
17
+ def supported_parameters
18
+ @@supported_parameters
19
+ end
20
+ end
21
+ end
22
+ end
@@ -11,7 +11,10 @@ module Slackify
11
11
 
12
12
  # Find the matching command based on the message string
13
13
  def matching_command(message)
14
- all_commands.each { |command| return command if command.regex.match? message }
14
+ all_commands.each do |command|
15
+ return command if command.regex.present? && command.regex.match?(message)
16
+ return command if command.base_command.present? && message.start_with?(command.base_command)
17
+ end
15
18
  nil
16
19
  end
17
20
 
@@ -23,10 +26,77 @@ module Slackify
23
26
 
24
27
  Slackify.configuration.unhandled_handler.unhandled(params)
25
28
  else
26
- new_params = params.merge(command_arguments: command.regex.match(message).named_captures)
29
+ new_params = params.merge( command_arguments: extract_arguments(message, command) )
30
+
27
31
  command.handler.call(new_params)
28
32
  end
29
33
  end
34
+
35
+ def extract_arguments(message, command)
36
+ if command.regex
37
+ command.regex.match(message).named_captures
38
+ else
39
+ raw_arguments = message.sub(/^#{command.base_command}/, '').strip
40
+ spec = {}
41
+ command.parameters.each do |parameter|
42
+ spec[parameter.keys[0].to_sym] = { type: parameter.values[0].to_sym }
43
+ end
44
+ parse_by_spec(spec, raw_arguments)
45
+ end
46
+ end
47
+
48
+ def parse_by_spec(spec, raw_arguments)
49
+ processed_args = {}
50
+
51
+ s = StringScanner.new(raw_arguments)
52
+ until s.eos?
53
+ # get the key, remove '=' and extra whitespace
54
+ current_key = s.scan_until(/=/)
55
+ break if current_key.nil?
56
+
57
+ current_key = current_key[0..-2].strip
58
+
59
+ # grab value accounting for any quotes
60
+ terminating_string = case s.peek(1)
61
+ when "'"
62
+ s.skip(/'/)
63
+ /'/
64
+ when '"'
65
+ s.skip(/"/)
66
+ /"/
67
+ else
68
+ / /
69
+ end
70
+ processed_args[current_key.to_sym] = if s.exist?(terminating_string)
71
+ # grab everything before the next instance of the terminating character
72
+ s.scan_until(terminating_string)[0..-2]
73
+ else
74
+ # this is probably wrong unless we were expecting a space, but hit eos
75
+ s.rest
76
+ end
77
+ end
78
+
79
+ # only pass on expected parameters for now.
80
+ processed_spec = {}
81
+ spec.each do |key, value|
82
+ # coerce to the expected type
83
+ type = value.fetch(:type, 'string')
84
+ processed_spec[key] = case type
85
+ when :int
86
+ processed_args[key].to_i
87
+ when :float
88
+ processed_args[key].to_f
89
+ when :boolean
90
+ ActiveModel::Type::Boolean.new.cast(processed_args[key])
91
+ when :string
92
+ processed_args[key]
93
+ else
94
+ Object.const_get(type).new(processed_args[key]).parse
95
+ end
96
+ end
97
+
98
+ processed_spec
99
+ end
30
100
  end
31
101
  end
32
102
  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.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Leger
@@ -27,6 +27,20 @@ dependencies:
27
27
  version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: slack-ruby-client
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.15.1
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.15.1
42
+ - !ruby/object:Gem::Dependency
43
+ name: strscan
30
44
  requirement: !ruby/object:Gem::Requirement
31
45
  requirements:
32
46
  - - ">="
@@ -117,6 +131,7 @@ files:
117
131
  - lib/slackify/handlers/factory.rb
118
132
  - lib/slackify/handlers/unhandled_handler.rb
119
133
  - lib/slackify/handlers/validator.rb
134
+ - lib/slackify/parameter.rb
120
135
  - lib/slackify/router.rb
121
136
  homepage: https://github.com/jusleg/slackify
122
137
  licenses: