slack-ruby-bot 0.10.5 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +52 -16
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +3 -0
  5. data/README.md +92 -5
  6. data/Rakefile +1 -1
  7. data/examples/inventory/Gemfile +1 -1
  8. data/examples/market/Gemfile +1 -1
  9. data/examples/market/marketbot.rb +1 -1
  10. data/examples/minimal/Gemfile +1 -1
  11. data/examples/weather/Gemfile +1 -1
  12. data/lib/slack-ruby-bot.rb +0 -1
  13. data/lib/slack-ruby-bot/app.rb +10 -8
  14. data/lib/slack-ruby-bot/client.rb +8 -6
  15. data/lib/slack-ruby-bot/commands/base.rb +41 -8
  16. data/lib/slack-ruby-bot/commands/help.rb +4 -4
  17. data/lib/slack-ruby-bot/commands/{help → support}/attrs.rb +1 -1
  18. data/lib/slack-ruby-bot/commands/support/help.rb +80 -0
  19. data/lib/slack-ruby-bot/commands/support/match.rb +22 -0
  20. data/lib/slack-ruby-bot/config.rb +1 -1
  21. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/not_respond.rb +5 -3
  22. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb +5 -3
  23. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb +2 -2
  24. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_messages.rb +2 -2
  25. data/lib/slack-ruby-bot/rspec/support/spec_helpers.rb +3 -1
  26. data/lib/slack-ruby-bot/server.rb +1 -1
  27. data/lib/slack-ruby-bot/version.rb +1 -1
  28. data/slack-ruby-bot.gemspec +2 -2
  29. data/spec/slack-ruby-bot/commands/attachment_spec.rb +102 -0
  30. data/spec/slack-ruby-bot/commands/bot_message_spec.rb +1 -1
  31. data/spec/slack-ruby-bot/commands/commands_regexp_escape_spec.rb +6 -0
  32. data/spec/slack-ruby-bot/commands/{help → support}/attrs_spec.rb +1 -1
  33. data/spec/slack-ruby-bot/{support/commands_helper_spec.rb → commands/support/help_spec.rb} +23 -9
  34. data/spec/slack-ruby-bot/commands/support/match_spec.rb +54 -0
  35. data/spec/slack-ruby-bot/hooks/hook_support_spec.rb +1 -1
  36. data/spec/slack-ruby-bot/hooks/message_spec.rb +1 -1
  37. data/spec/slack-ruby-bot/mvc/controller/controller_to_command_spec.rb +2 -2
  38. data/spec/slack-ruby-bot/rspec/respond_with_error_spec.rb +8 -3
  39. data/spec/slack-ruby-bot/rspec/respond_with_slack_message_spec.rb +11 -0
  40. data/spec/slack-ruby-bot/rspec/respond_with_slack_messages_spec.rb +15 -0
  41. data/spec/slack-ruby-bot/server_spec.rb +2 -2
  42. metadata +28 -23
  43. data/lib/slack-ruby-bot/support/commands_helper.rb +0 -76
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e3a131c7f852f22c4039f8a40e25a7a914d40a0f
4
- data.tar.gz: 1f31b3ef7089c492712d5cc5bc0edec6947a7e5a
3
+ metadata.gz: 9478d61ec600a816259bd73d8aab8120a1d76ed2
4
+ data.tar.gz: c1d005b0e9c308af61dbe3d8ec112f3a8e72b62a
5
5
  SHA512:
6
- metadata.gz: 6d721d75a37a031b6e12003f47a4ba68e7e60be61abd26f2b76999768448b18ef8ab7bc01394aab7047978ef7ee5845ea79a11540232fe7abc2d508c44076188
7
- data.tar.gz: '09ca8c66e5ffe883faa832208fe341379131ed710f7ed6945959564d92ce2a9e8ed2196f96a262aace493b043c7d4852f3f6d25bc82dc5e698c754ee7b869328'
6
+ metadata.gz: ce48f28806019da9be29699e48d7aa292d70b462df75d6e5eccf0f5a7c40dde8eab1f99b3465779c55ec4c14d64c5a91f2cbbb1b370d4e570f4185701ab22720
7
+ data.tar.gz: 6d13d2fe47144b8cbec5740b6598aa3b7e0f8bdf1010b27ffb41d313eb33f3d67da6e745cffc54221bdede92584a183f48decb3b5a1daeec84ecf866ec423887
@@ -1,11 +1,25 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2017-10-15 13:34:54 -0700 using RuboCop version 0.38.0.
3
+ # on 2018-03-29 16:47:34 +0200 using RuboCop version 0.51.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # Offense count: 2
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
12
+ # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
13
+ Layout/IndentHeredoc:
14
+ Exclude:
15
+ - 'lib/slack-ruby-bot/commands/help.rb'
16
+ - 'spec/slack-ruby-bot/commands/help_spec.rb'
17
+
18
+ # Offense count: 1
19
+ Lint/DuplicateMethods:
20
+ Exclude:
21
+ - 'lib/slack-ruby-bot/hooks/set.rb'
22
+
9
23
  # Offense count: 1
10
24
  Lint/HandleExceptions:
11
25
  Exclude:
@@ -15,31 +29,57 @@ Lint/HandleExceptions:
15
29
  Metrics/AbcSize:
16
30
  Max: 40
17
31
 
32
+ # Offense count: 30
33
+ # Configuration parameters: CountComments, ExcludedMethods.
34
+ Metrics/BlockLength:
35
+ Max: 131
36
+
18
37
  # Offense count: 1
19
38
  # Configuration parameters: CountComments.
20
39
  Metrics/ClassLength:
21
- Max: 104
40
+ Max: 131
22
41
 
23
42
  # Offense count: 2
24
43
  Metrics/CyclomaticComplexity:
25
- Max: 13
44
+ Max: 16
26
45
 
27
- # Offense count: 218
28
- # Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
46
+ # Offense count: 249
47
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
29
48
  # URISchemes: http, https
30
49
  Metrics/LineLength:
31
- Max: 147
50
+ Max: 151
32
51
 
33
- # Offense count: 10
52
+ # Offense count: 11
34
53
  # Configuration parameters: CountComments.
35
54
  Metrics/MethodLength:
36
- Max: 19
55
+ Max: 27
37
56
 
38
57
  # Offense count: 2
39
58
  Metrics/PerceivedComplexity:
40
- Max: 13
59
+ Max: 14
60
+
61
+ # Offense count: 1
62
+ # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
63
+ # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
64
+ Naming/FileName:
65
+ Exclude:
66
+ - 'lib/slack-ruby-bot.rb'
41
67
 
42
- # Offense count: 31
68
+ # Offense count: 2
69
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
70
+ # SupportedStyles: snake_case, normalcase, non_integer
71
+ Naming/VariableNumber:
72
+ Exclude:
73
+ - 'spec/slack-ruby-bot/hooks/set_spec.rb'
74
+
75
+ # Offense count: 1
76
+ # Cop supports --auto-correct.
77
+ # Configuration parameters: AutoCorrect.
78
+ Performance/HashEachMethods:
79
+ Exclude:
80
+ - 'lib/slack-ruby-bot/hooks/set.rb'
81
+
82
+ # Offense count: 32
43
83
  Style/Documentation:
44
84
  Enabled: false
45
85
 
@@ -49,12 +89,8 @@ Style/DoubleNegation:
49
89
  - 'lib/slack-ruby-bot/commands/base.rb'
50
90
 
51
91
  # Offense count: 1
52
- # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts.
53
- Style/FileName:
54
- Exclude:
55
- - 'lib/slack-ruby-bot.rb'
56
-
57
- # Offense count: 1
92
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
93
+ # SupportedStyles: module_function, extend_self
58
94
  Style/ModuleFunction:
59
95
  Exclude:
60
96
  - 'lib/slack-ruby-bot/config.rb'
@@ -1,3 +1,11 @@
1
+ ### 0.11.0 (04/02/2018)
2
+
3
+ * [#182](https://github.com/slack-ruby/slack-ruby-bot/pull/182): Refactor CommandsHelper class and Help module - [@mdudzinski](https://github.com/mdudzinski).
4
+ * [#180](https://github.com/slack-ruby/slack-ruby-bot/pull/180): Allow to respond to text in attachments #177 - [@mdudzinski](https://github.com/mdudzinski).
5
+ * [#173](https://github.com/slack-ruby/slack-ruby-bot/pull/173): Exposing SlackRubyBot::CommandsHelper.find_command_help_attrs - [@alexagranov](https://github.com/alexagranov).
6
+ * [#179](https://github.com/slack-ruby/slack-ruby-bot/pull/179): Allow multiline expression - [@tiagotex](https://github.com/tiagotex).
7
+ * [#183](https://github.com/slack-ruby/slack-ruby-bot/pull/183): Add missing test dependency to readme.md - [@hoshinotsuyoshi](https://github.com/hoshinotsuyoshi).
8
+
1
9
  ### 0.10.5 (10/15/2017)
2
10
 
3
11
  * Refactored `SlackRubyBot::MVC::Controller::Base`, consolidated ivar handling, centralized object allocations and DRYed up the code - [@chuckremes](https://github.com/chuckremes).
data/Gemfile CHANGED
@@ -3,6 +3,9 @@ source 'http://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem ENV['CONCURRENCY'], require: false if ENV.key?('CONCURRENCY')
6
+
7
+ # rubocop:enable Bundler/OrderedGems
8
+
6
9
  gem 'giphy', require: false if ENV.key?('WITH_GIPHY')
7
10
 
8
11
  group :test do
data/README.md CHANGED
@@ -19,7 +19,9 @@ If you are not familiar with Slack bots or Slack API concepts, you might want to
19
19
 
20
20
  ## Stable Release
21
21
 
22
- You're reading the documentation for the **next** release of slack-ruby-bot. Please see the documentation for the [last stable release, v0.10.1](https://github.com/slack-ruby/slack-ruby-bot/tree/v0.10.1) unless you're integrating with HEAD. See [CHANGELOG](CHANGELOG.md) for a history of changes and [UPGRADING](UPGRADING.md) for how to upgrade to more recent versions.
22
+ You're reading the documentation for the **next** release of slack-ruby-bot.
23
+ Please see the documentation for the [last stable release, v0.10.5](https://github.com/slack-ruby/slack-ruby-bot/tree/v0.10.5) unless you're integrating with HEAD.
24
+ See [CHANGELOG](CHANGELOG.md) for a history of changes and [UPGRADING](UPGRADING.md) for how to upgrade to more recent versions.
23
25
 
24
26
  ## Usage
25
27
 
@@ -104,6 +106,28 @@ end
104
106
 
105
107
  Operator match data includes `match['operator']` and `match['expression']`. The `bot` match always checks against the `SlackRubyBot::Config.user` setting.
106
108
 
109
+ ### Threaded Messages
110
+
111
+ To reply to a message in a thread you must provide a reference to the first message that initiated the thread, which is available as either `data.ts` if no threaded messages have been sent, or `data.thread_ts` if the message being replied to is already in a thread. See [message-threading](https://api.slack.com/docs/message-threading) for more information.
112
+
113
+ ```ruby
114
+ command 'reply in thread' do |client, data, match|
115
+ client.say(
116
+ channel: data.channel,
117
+ text: "let's avoid spamming everyone, I will tell you what you need in this thread",
118
+ thread_ts: data.thread_ts || data.ts
119
+ )
120
+ end
121
+ ```
122
+
123
+ _Note that sending a message using only `thread_ts: data.ts` can cause some permanent issues where Slack will keep reporting inaccessible messages as unread. At the time of writing the slack team is still having problems clearing those notifications. As recommended by the slack documentation ..._
124
+
125
+ > A true parent's thread_ts should be used when replying. Providing a child's message ID will result in a new, detached thread breaking all context and sense.
126
+
127
+ _... the replies to a thread should always be sent to the message `ts` that started the thread, available as `thread_ts` for subsequent messages. Hence `data.thread_ts || data.ts`._
128
+
129
+ For additional options, including broadcasting, see [slack-ruby-client#chat_postMessage](https://github.com/slack-ruby/slack-ruby-client/blob/41539c647ac877400f20aa338aa42d2ebfd2866b/lib/slack/web/api/endpoints/chat.rb#L105).
130
+
107
131
  ### Bot Aliases
108
132
 
109
133
  A bot will always respond to its name (eg. `rubybot`) and Slack ID (eg. `@rubybot`), but you can specify multiple aliases via the `SLACK_RUBY_BOT_ALIASES` environment variable or via an explicit configuration.
@@ -154,6 +178,46 @@ end
154
178
 
155
179
  See [examples/market](examples/market/marketbot.rb) for a working example.
156
180
 
181
+ ### Matching text in message attachments
182
+
183
+ You can respond to text in [attachments](https://api.slack.com/docs/message-attachments) with
184
+ `attachment`. It will scan `text`, `pretext` and `title` fields in each attachment until a first
185
+ match is found.
186
+
187
+ For example you can match [this example attachment](http://goo.gl/K0cLkH)
188
+ by its `title` with the following bot:
189
+
190
+ ```ruby
191
+ class Attachment < SlackRubyBot::Bot
192
+ attachment 'Slack API Documentation' do |client, data, match|
193
+ client.say(channel: data.channel, text: "Matched by #{match.attachment_field}.")
194
+ client.say(channel: data.channel, text: "The attachment's text: #{match.attachment.text}.")
195
+ end
196
+ end
197
+ ```
198
+
199
+ You can also define which fields in attachment object should be scanned.
200
+
201
+ Scan only a single field:
202
+
203
+ ```ruby
204
+ class Attachment < SlackRubyBot::Bot
205
+ attachment 'Slack API Documentation', :title do |client, data, match|
206
+ # implementation details
207
+ end
208
+ end
209
+ ```
210
+
211
+ Scan multiple fields:
212
+
213
+ ```ruby
214
+ class Attachment < SlackRubyBot::Bot
215
+ attachment 'Slack API Documentation', %i[text pretext author_name] do |client, data, match|
216
+ # implementation details
217
+ end
218
+ end
219
+ ```
220
+
157
221
  ### Providing description for your bot and commands
158
222
 
159
223
  You can specify help information for bot or commands with `help` block, for example:
@@ -195,6 +259,22 @@ class Deploy < SlackRubyBot::Commands::Base
195
259
  end
196
260
  ```
197
261
 
262
+ ### Customize your command help output
263
+
264
+ If you've used the `help` block described above to document your
265
+ commands, you can provide your own implementation of outputting help
266
+ for commands like so:
267
+
268
+ ```ruby
269
+ class Market < SlackRubyBot::Bot
270
+ command 'help' do |client, data, match|
271
+ user_command = match[:expression]
272
+ help_attrs = SlackRubyBot::Commands::Support::Help.instance.find_command_help_attrs(user_command)
273
+ client.say(channel: data.channel, text: "#{help_attrs.command_desc}\n\n#{help_attrs.command_long_desc}"
274
+ end
275
+ end
276
+ ```
277
+
198
278
  ### SlackRubyBot::Commands::Base
199
279
 
200
280
  The `SlackRubyBot::Bot` class is DSL sugar deriving from `SlackRubyBot::Commands::Base`. For more involved bots you can organize the bot implementation into subclasses of `SlackRubyBot::Commands::Base` manually. By default a command class responds, case-insensitively, to its name. A class called `Phone` that inherits from `SlackRubyBot::Commands::Base` responds to `phone` and `Phone` and calls the `call` method when implemented.
@@ -290,8 +370,8 @@ Hooks are event handlers and respond to Slack RTM API [events](https://api.slack
290
370
 
291
371
  A Hook Handler is any object that respond to a `call` message, like a proc, instance of an object, class with a `call` class method, etc.
292
372
 
293
- Hooks can be registered using different methods based on user preference / use case.
294
- Currently someone can use one of the following methods:
373
+ Hooks can be registered using different methods based on user preference / use case.
374
+ Currently someone can use one of the following methods:
295
375
 
296
376
  * Pass `hooks` in `SlackRubyBot::Server` initialization.
297
377
  * Register `hooks` on `SlackRubyBot::Server` using `on` class method.
@@ -350,8 +430,8 @@ module MyBot
350
430
  # data['user']['id'] contains the user ID
351
431
  # data['user']['name'] contains the new user name
352
432
  end
353
-
354
- on 'user_change', ->(client, data) {
433
+
434
+ on 'user_change', ->(client, data) {
355
435
  # data['user']['id'] contains the user ID
356
436
  # data['user']['name'] contains the new user name
357
437
  }
@@ -420,6 +500,12 @@ end
420
500
 
421
501
  For an example of advanced integration that supports multiple teams, see [slack-gamebot](https://github.com/dblock/slack-gamebot) and [playplay.io](http://playplay.io) that is built on top of it.
422
502
 
503
+ ### Proxy Configuration
504
+
505
+ There are [several proxy options](https://github.com/slack-ruby/slack-ruby-client#web-client-options) that can be configured on `Slack::Web::Client`. You can also control what proxy options are used by modifying the `http_proxy` environment variable per [Net::HTTP's documentation](https://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html#class-Net::HTTP-label-Proxies).
506
+
507
+ Note that Docker on OSX seems to incorrectly set the proxy, [causing `Faraday::ConnectionFailed`](https://github.com/slack-ruby/slack-ruby-bot/issues/155), `ERROR -- : Failed to open TCP connection to : (getaddrinfo: Name or service not known)`. You might need to manually unset `http_proxy` in that case, eg. `http_proxy="" bundle exec ruby ./my_bot.rb`.
508
+
423
509
  ### Model-View-Controller Design
424
510
 
425
511
  The `command` method is essentially a controller method that receives input from the outside and acts upon it. Complex behaviors could lead to a long and difficult-to-understand `command` block. A complex `command` block is a candidate for separation into classes conforming to the Model-View-Controller pattern popularized by Rails.
@@ -564,6 +650,7 @@ Require `slack-ruby-bot/rspec` in your `spec_helper.rb` along with the following
564
650
 
565
651
  ```ruby
566
652
  group :development, :test do
653
+ gem 'rack-test'
567
654
  gem 'rspec'
568
655
  gem 'vcr'
569
656
  gem 'webmock'
data/Rakefile CHANGED
@@ -15,5 +15,5 @@ unless ENV['RACK_ENV'] == 'production'
15
15
  require 'rubocop/rake_task'
16
16
  RuboCop::RakeTask.new
17
17
 
18
- task default: [:rubocop, :spec]
18
+ task default: %i[rubocop spec]
19
19
  end
@@ -1,5 +1,5 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'slack-ruby-bot', path: '../..'
4
3
  gem 'celluloid-io'
4
+ gem 'slack-ruby-bot', path: '../..'
5
5
  gem 'sqlite3'
@@ -1,5 +1,5 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'slack-ruby-bot', path: '../..'
4
3
  gem 'faye-websocket'
4
+ gem 'slack-ruby-bot', path: '../..'
5
5
  gem 'yahoo-finance'
@@ -5,7 +5,7 @@ SlackRubyBot::Client.logger.level = Logger::WARN
5
5
 
6
6
  class MarketBot < SlackRubyBot::Bot
7
7
  scan(/([A-Z]{2,5}+)/) do |client, data, stocks|
8
- YahooFinance::Client.new.quotes(stocks, [:name, :symbol, :last_trade_price, :change, :change_in_percent]).each do |quote|
8
+ YahooFinance::Client.new.quotes(stocks, %i[name symbol last_trade_price change change_in_percent]).each do |quote|
9
9
  next if quote.symbol == 'N/A'
10
10
  client.web_client.chat_postMessage(
11
11
  channel: data.channel,
@@ -1,4 +1,4 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'slack-ruby-bot', path: '../..'
4
3
  gem 'celluloid-io'
4
+ gem 'slack-ruby-bot', path: '../..'
@@ -1,4 +1,4 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'slack-ruby-bot', path: '../..'
4
3
  gem 'celluloid-io'
4
+ gem 'slack-ruby-bot', path: '../..'
@@ -19,7 +19,6 @@ module SlackRubyBot
19
19
  end
20
20
 
21
21
  require 'slack-ruby-client'
22
- require 'slack-ruby-bot/support/commands_helper'
23
22
  require 'slack-ruby-bot/commands'
24
23
  require 'slack-ruby-bot/client'
25
24
  require 'slack-ruby-bot/server'
@@ -22,14 +22,16 @@ module SlackRubyBot
22
22
  private
23
23
 
24
24
  def hello(client, _data)
25
- SlackRubyBot.configure do |config|
26
- config.url = "https://#{client.team.domain}.slack.com"
27
- config.team = client.team.name
28
- config.team_id = client.team.id
29
- config.user = client.self.name
30
- config.user_id = client.self.id
31
- logger.info "Welcome #{config.user} to the #{config.team} team."
32
- end if client.team && client.self
25
+ if client.team && client.self
26
+ SlackRubyBot.configure do |config|
27
+ config.url = "https://#{client.team.domain}.slack.com"
28
+ config.team = client.team.name
29
+ config.team_id = client.team.id
30
+ config.user = client.self.name
31
+ config.user_id = client.self.id
32
+ logger.info "Welcome #{config.user} to the #{config.team} team."
33
+ end
34
+ end
33
35
  super
34
36
  end
35
37
 
@@ -48,12 +48,14 @@ module SlackRubyBot
48
48
  keywords = options.delete(:gif)
49
49
  # text
50
50
  text = options.delete(:text)
51
- gif = begin
52
- Giphy.random(keywords)
53
- rescue StandardError => e
54
- logger.warn "Giphy.random: #{e.message}"
55
- nil
56
- end if keywords && send_gifs?
51
+ if keywords && send_gifs?
52
+ gif = begin
53
+ Giphy.random(keywords)
54
+ rescue StandardError => e
55
+ logger.warn "Giphy.random: #{e.message}"
56
+ nil
57
+ end
58
+ end
57
59
  text = [text, gif && gif.image_url.to_s].compact.join("\n")
58
60
  message({ text: text }.merge(options))
59
61
  end
@@ -1,8 +1,10 @@
1
+ require_relative 'support/match'
2
+ require_relative 'support/help'
3
+
1
4
  module SlackRubyBot
2
5
  module Commands
3
6
  class Base
4
7
  include Loggable
5
- class_attribute :routes
6
8
 
7
9
  class << self
8
10
  attr_accessor :command_classes
@@ -32,7 +34,7 @@ module SlackRubyBot
32
34
  end
33
35
 
34
36
  def help(&block)
35
- CommandsHelper.instance.capture_help(self, &block)
37
+ Support::Help.instance.capture_help(self, &block)
36
38
  end
37
39
 
38
40
  def command_name_from_class
@@ -46,24 +48,32 @@ module SlackRubyBot
46
48
 
47
49
  def command(*values, &block)
48
50
  values = values.map { |value| value.is_a?(Regexp) ? value.source : Regexp.escape(value) }.join('|')
49
- match Regexp.new("^#{bot_matcher}[\\s]+(?<command>#{values})([\\s]+(?<expression>.*)|)$", Regexp::IGNORECASE), &block
51
+ match Regexp.new("^#{bot_matcher}[\\s]+(?<command>#{values})([\\s]+(?<expression>.*)|)$", Regexp::IGNORECASE | Regexp::MULTILINE), &block
50
52
  end
51
53
 
52
54
  def invoke(client, data)
53
55
  finalize_routes!
54
56
  expression, text = parse(client, data)
55
- return false unless expression
57
+ return false unless expression || data.attachments
56
58
  routes.each_pair do |route, options|
57
59
  match_method = options[:match_method]
58
60
  case match_method
59
61
  when :match
62
+ next unless expression
60
63
  match = route.match(expression)
61
64
  match ||= route.match(text) if text
62
65
  next unless match
63
66
  next if match.names.include?('bot') && !client.name?(match['bot'])
67
+ match = Support::Match.new(match)
64
68
  when :scan
69
+ next unless expression
65
70
  match = expression.scan(route)
66
71
  next unless match.any?
72
+ when :attachment
73
+ next unless data.attachments && !data.attachments.empty?
74
+ match, attachment, field = match_attachments(data, route, options[:fields_to_scan])
75
+ next unless match
76
+ match = Support::Match.new(match, attachment, field)
67
77
  end
68
78
  call_command(client, data, match, options[:block])
69
79
  return true
@@ -72,19 +82,30 @@ module SlackRubyBot
72
82
  end
73
83
 
74
84
  def match(match, &block)
75
- self.routes ||= ActiveSupport::OrderedHash.new
76
- self.routes[match] = { match_method: :match, block: block }
85
+ routes[match] = { match_method: :match, block: block }
77
86
  end
78
87
 
79
88
  def scan(match, &block)
80
- self.routes ||= ActiveSupport::OrderedHash.new
81
- self.routes[match] = { match_method: :scan, block: block }
89
+ routes[match] = { match_method: :scan, block: block }
90
+ end
91
+
92
+ def attachment(match, fields_to_scan = nil, &block)
93
+ fields_to_scan = [fields_to_scan] unless fields_to_scan.nil? || fields_to_scan.is_a?(Array)
94
+ routes[match] = {
95
+ match_method: :attachment,
96
+ block: block,
97
+ fields_to_scan: fields_to_scan
98
+ }
82
99
  end
83
100
 
84
101
  def bot_matcher
85
102
  '(?<bot>\S*)'
86
103
  end
87
104
 
105
+ def routes
106
+ @routes ||= ActiveSupport::OrderedHash.new
107
+ end
108
+
88
109
  private
89
110
 
90
111
  def call_command(client, data, match, block)
@@ -122,6 +143,18 @@ module SlackRubyBot
122
143
  command command_name_from_class
123
144
  end
124
145
 
146
+ def match_attachments(data, route, fields_to_scan = nil)
147
+ fields_to_scan ||= %i[pretext text title]
148
+ data.attachments.each do |attachment|
149
+ fields_to_scan.each do |field|
150
+ next unless attachment[field]
151
+ match = route.match(attachment[field])
152
+ return match, attachment, field if match
153
+ end
154
+ end
155
+ false
156
+ end
157
+
125
158
  # Intended to be overridden by subclasses to hook in an
126
159
  # authorization mechanism.
127
160
  def permitted?(_client, _data, _match)