slack-ruby-bot 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +13 -4
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +57 -3
  5. data/RELEASING.md +67 -0
  6. data/TUTORIAL.md +49 -2
  7. data/examples/minimal/pongbot.rb +1 -1
  8. data/examples/weather/Gemfile +3 -0
  9. data/examples/weather/weatherbot.rb +14 -0
  10. data/lib/slack-ruby-bot.rb +3 -2
  11. data/lib/slack-ruby-bot/app.rb +1 -1
  12. data/lib/slack-ruby-bot/commands/about.rb +2 -2
  13. data/lib/slack-ruby-bot/commands/base.rb +50 -19
  14. data/lib/slack-ruby-bot/commands/help.rb +1 -1
  15. data/lib/slack-ruby-bot/commands/hi.rb +1 -1
  16. data/lib/slack-ruby-bot/commands/unknown.rb +3 -1
  17. data/lib/slack-ruby-bot/hooks/message.rb +6 -80
  18. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb +2 -3
  19. data/lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb +1 -1
  20. data/lib/slack-ruby-bot/version.rb +1 -1
  21. data/screenshots/weather.gif +0 -0
  22. data/slack-ruby-bot.gemspec +1 -1
  23. data/spec/slack-ruby-bot/commands/commands_precedence_spec.rb +22 -0
  24. data/spec/slack-ruby-bot/commands/commands_spaces_spec.rb +20 -0
  25. data/spec/slack-ruby-bot/commands/commands_spec.rb +4 -4
  26. data/spec/slack-ruby-bot/commands/commands_with_block_spec.rb +23 -0
  27. data/spec/slack-ruby-bot/commands/empty_text_spec.rb +4 -4
  28. data/spec/slack-ruby-bot/commands/not_implemented_spec.rb +15 -0
  29. data/spec/slack-ruby-bot/commands/operators_spec.rb +3 -3
  30. data/spec/slack-ruby-bot/commands/operators_with_block_spec.rb +23 -0
  31. data/spec/slack-ruby-bot/rspec/respond_with_error_spec.rb +3 -3
  32. metadata +45 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 83ecb7fe802af333ec3950f148959ce2c19095cc
4
- data.tar.gz: da223c787bfe98e74543f1cc878ae04272c9b554
3
+ metadata.gz: e767ee739a4eb2e86b7b03affd315f7d20fdc866
4
+ data.tar.gz: 5123671ea14bf10c590955d6a2391b436d3578bf
5
5
  SHA512:
6
- metadata.gz: 0a29f81647a5b1b1d4156f4d51668697ce44ac6c80cba4ac4feb2f375c2d3eb4c31ac9af952dbae030e0c9646e4ec59ac3818d9bbfb2d989cbe24f0796453080
7
- data.tar.gz: bd3d763b1334da9ccc10712f5b26bfc7f2f6942cdea9f9013edfeb3304881eca2bf8ad12cfd1e154339cd683bc365a24bb51ce4b6f8027064d3dfbe5d5dbc1ab
6
+ metadata.gz: d13566b081e81835c685886917ba096bc0ec3c525287feab3ca50c6691f48139710ed4d0fb65c7e7369aee9a8608d83099f5e14b28c8a7d3fe5f85380beac3f4
7
+ data.tar.gz: 4b6ed30645c1a9cc2ffaecd1924db4fd71bf6d4c845bc716e6107e76876066964f1f711ed0fc380d546ae192ab4ca1ca080fb89b15d95a59b02274a65d4b2d9b
@@ -1,5 +1,5 @@
1
1
  # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2015-07-10 08:49:06 -0400 using RuboCop version 0.32.1.
2
+ # on 2015-07-19 12:16:52 -0400 using RuboCop version 0.32.1.
3
3
  # The point is for the user to remove these configuration records
4
4
  # one by one as the offenses are removed from the code base.
5
5
  # Note that changes in the inspected code, or installation of new
@@ -10,18 +10,22 @@ Lint/HandleExceptions:
10
10
  Enabled: false
11
11
 
12
12
  # Offense count: 1
13
+ Lint/UselessAccessModifier:
14
+ Enabled: false
15
+
16
+ # Offense count: 2
13
17
  Metrics/AbcSize:
14
18
  Max: 21
15
19
 
16
- # Offense count: 24
20
+ # Offense count: 37
17
21
  # Configuration parameters: AllowURI, URISchemes.
18
22
  Metrics/LineLength:
19
23
  Max: 142
20
24
 
21
- # Offense count: 1
25
+ # Offense count: 2
22
26
  # Configuration parameters: CountComments.
23
27
  Metrics/MethodLength:
24
- Max: 17
28
+ Max: 18
25
29
 
26
30
  # Offense count: 15
27
31
  Style/Documentation:
@@ -32,6 +36,11 @@ Style/Documentation:
32
36
  Style/FileName:
33
37
  Enabled: false
34
38
 
39
+ # Offense count: 1
40
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
41
+ Style/MethodName:
42
+ Enabled: false
43
+
35
44
  # Offense count: 1
36
45
  Style/ModuleFunction:
37
46
  Enabled: false
@@ -1,3 +1,9 @@
1
+ ### 0.3.0 (7/19/2015)
2
+
3
+ * [#5](https://github.com/dblock/slack-ruby-bot/issues/5): Added support for free-formed routes via `match` - [@dblock](https://github.com/dblock).
4
+ * [#6](https://github.com/dblock/slack-ruby-bot/issues/6): Commands and operators take blocks - [@dblock](https://github.com/dblock).
5
+ * [#4](https://github.com/dblock/slack-ruby-bot/issues/4): Messages are posted with `as_user: true` by default - [@dblock](https://github.com/dblock).
6
+
1
7
  ### 0.2.0 (7/10/2015)
2
8
 
3
9
  * Sending `send_message` with nil or empty text will yield `Nothing to see here.` with a GIF instead of `no_text` - [@dblock](https://github.com/dblock).
data/README.md CHANGED
@@ -28,7 +28,7 @@ module PongBot
28
28
  end
29
29
 
30
30
  class Ping < SlackRubyBot::Commands::Base
31
- def self.call(data, command, arguments)
31
+ command 'ping' do |data, _match|
32
32
  send_message data.channel, 'pong'
33
33
  end
34
34
  end
@@ -54,23 +54,69 @@ The following examples of production-grade bots based on slack-ruby-bot are list
54
54
 
55
55
  ### Commands and Operators
56
56
 
57
- Bots are addressed by name and respond to commands and operators. 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`. To respond to custom commands and to disable automatic class name matching, use the `command` keyword. The following command responds to `call` and `呼び出し` (call in Japanese).
57
+ Bots are addressed by name and respond to commands and operators. 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.
58
+
59
+ ```ruby
60
+ class Phone < SlackRubyBot::Commands::Base
61
+ command 'call'
62
+
63
+ def self.call(data, _match)
64
+ send_message data.channel, 'called'
65
+ end
66
+ end
67
+ ```
68
+
69
+ To respond to custom commands and to disable automatic class name matching, use the `command` keyword. The following command responds to `call` and `呼び出し` (call in Japanese).
58
70
 
59
71
  ```ruby
60
72
  class Phone < SlackRubyBot::Commands::Base
61
73
  command 'call'
62
74
  command '呼び出し'
75
+
76
+ def self.call(data, _match)
77
+ send_message data.channel, 'called'
78
+ end
79
+ end
80
+ ```
81
+
82
+ You can combine multiple commands and use a block to implement them.
83
+
84
+ ```ruby
85
+ class Phone < SlackRubyBot::Commands::Base
86
+ command 'call', '呼び出し' do |data, _match|
87
+ send_message data.channel, 'called'
88
+ end
63
89
  end
64
90
  ```
65
91
 
92
+ Command match data includes `match['bot']`, `match['command']` and `match['expression']`. The `bot` match always checks against the `SlackRubyBot::Config.user` setting.
93
+
66
94
  Operators are 1-letter long and are similar to commands. They don't require addressing a bot nor separating an operator from its arguments. The following class responds to `=2+2`.
67
95
 
68
96
  ```ruby
69
97
  class Calculator < SlackRubyBot::Commands::Base
70
- operator '='
98
+ operator '=' do |_data, _match|
99
+ # implementation detail
100
+ end
101
+ end
102
+ ```
103
+
104
+ Operator match data includes `match['operator']` and `match['expression']`. The `bot` match always checks against the `SlackRubyBot::Config.user` setting.
105
+
106
+ ### Generic Routing
107
+
108
+ Commands and operators are generic versions of bot routes. You can respond to just about anything by defining a custom route.
109
+
110
+ ```ruby
111
+ class Weather < SlackRubyBot::Commands::Base
112
+ match /^How is the weather in (<?location>\w*)\?$/ do |data, match|
113
+ send_message data.channel, "The weather in #{match[:location]} is nice."
114
+ end
71
115
  end
72
116
  ```
73
117
 
118
+ ![](screenshots/weather.gif)
119
+
74
120
  ### Built-In Commands
75
121
 
76
122
  Slack-ruby-bot comes with several built-in commands. You can re-define built-in commands, normally, as described above.
@@ -87,6 +133,14 @@ Politely says 'hi' back.
87
133
 
88
134
  Get help.
89
135
 
136
+ ### RSpec Shared Behaviors
137
+
138
+ Slack-ruby-bot ships with a number of shared RSpec behaviors that can be used in your RSpec tests. Require 'slack-ruby-bot/rspec' in your `spec_helper.rb`.
139
+
140
+ * [behaves like a slack bot](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/it_behaves_like_a_slack_bot.rb): A bot quacks like a Slack Ruby bot.
141
+ * [respond with slack message](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb): The bot responds with a message.
142
+ * [respond with error](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb): An exception is raised inside a bot command.
143
+
90
144
  ## Contributing
91
145
 
92
146
  See [CONTRIBUTING](CONTRIBUTING.md).
@@ -0,0 +1,67 @@
1
+ # Releasing Slack-Ruby-Bot
2
+
3
+ There're no particular rules about when to release slack-ruby-bot. Release bug fixes frequenty, features not so frequently and breaking API changes rarely.
4
+
5
+ ### Release
6
+
7
+ Run tests, check that all tests succeed locally.
8
+
9
+ ```
10
+ bundle install
11
+ rake
12
+ ```
13
+
14
+ Check that the last build succeeded in [Travis CI](https://travis-ci.org/dblock/slack-ruby-bot) for all supported platforms.
15
+
16
+ Increment the version, modify [lib/slack-ruby-bot/version.rb](lib/slack-ruby-bot/version.rb).
17
+
18
+ * Increment the third number if the release has bug fixes and/or very minor features, only (eg. change `0.2.1` to `0.2.2`).
19
+ * Increment the second number if the release contains major features or breaking API changes (eg. change `0.2.1` to `0.3.0`).
20
+
21
+ Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version.
22
+
23
+ ```
24
+ ### 0.2.2 (7/10/2015)
25
+ ```
26
+
27
+ Remove the line with "Your contribution here.", since there will be no more contributions to this release.
28
+
29
+ Commit your changes.
30
+
31
+ ```
32
+ git add CHANGELOG.md lib/slack-ruby-bot/version.rb
33
+ git commit -m "Preparing for release, 0.2.2."
34
+ git push origin master
35
+ ```
36
+
37
+ Release.
38
+
39
+ ```
40
+ $ rake release
41
+
42
+ slack-ruby-bot 0.2.2 built to pkg/slack-ruby-bot-0.2.2.gem.
43
+ Tagged v0.2.2.
44
+ Pushed git commits and tags.
45
+ Pushed slack-ruby-bot 0.2.2 to rubygems.org.
46
+ ```
47
+
48
+ ### Prepare for the Next Version
49
+
50
+ Add the next release to [CHANGELOG.md](CHANGELOG.md).
51
+
52
+ ```
53
+ Next Release
54
+ ============
55
+
56
+ * Your contribution here.
57
+ ```
58
+
59
+ Increment the third version number in [lib/slack-ruby-bot/version.rb](lib/slack-ruby-bot/version.rb).
60
+
61
+ Comit your changes.
62
+
63
+ ```
64
+ git add CHANGELOG.md lib/slack-ruby-bot/version.rb
65
+ git commit -m "Preparing for next development iteration, 0.2.3."
66
+ git push origin master
67
+ ```
@@ -51,7 +51,7 @@ Create a folder called `slack-mathbot/commands` and inside of it create `calcula
51
51
  module SlackMathbot
52
52
  module Commands
53
53
  class Calculate < SlackRubyBot::Commands::Base
54
- def self.call(data, command, arguments)
54
+ command 'calculate' do |data, _match|
55
55
  send_message data.channel, '4'
56
56
  end
57
57
  end
@@ -139,10 +139,57 @@ Run `foreman start`. Your bot should be running.
139
139
  14:32:36 web.1 | I, [2015-07-10T14:32:36.766955 #98948] INFO -- : Successfully connected to https://xyz.slack.com/.
140
140
  ```
141
141
 
142
- ### Test
142
+ ### Try
143
143
 
144
144
  Invite the bot to a channel via `/invite [bot name]` and send it a `calculate` command with `[bot name] calculate 2+2`. It will respond with `4` from the code above.
145
145
 
146
+ ### Write Tests
147
+
148
+ #### Spec Helper
149
+
150
+ Create `spec/spec_helper.rb` that includes the bot files and shared RSpec support from slack-ruby-bot.
151
+
152
+ ```ruby
153
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
154
+
155
+ require 'slack-ruby-bot/rspec'
156
+ require 'slack-mathbot'
157
+ ```
158
+
159
+ #### Test the Bot Application
160
+
161
+ Create a test for the bot application itself in `spec/slack-mathbot/app_spec.rb`.
162
+
163
+ ```ruby
164
+ require 'spec_helper'
165
+
166
+ describe SlackMathbot::App do
167
+ def app
168
+ SlackMathbot::App.new
169
+ end
170
+ it_behaves_like 'a slack ruby bot'
171
+ end
172
+ ```
173
+
174
+ #### Test a Command
175
+
176
+ Create a test for the `calculate` command in `spec/slack-mathbot/commands/calculate_spec.rb`. The bot is addressed by its user name.
177
+
178
+ ```ruby
179
+ require 'spec_helper'
180
+
181
+ describe SlackMathbot::Commands::Calculate do
182
+ def app
183
+ SlackMathbot::App.new
184
+ end
185
+ it 'returns 4' do
186
+ expect(message: "#{SlackRubyBot.config.user} calculate 2+2", channel: 'channel').to respond_with_slack_message('4')
187
+ end
188
+ end
189
+ ```
190
+
191
+ See [lib/slack-ruby-bot/rspec/support/slack-ruby-bot](lib/slack-ruby-bot/rspec/support/slack-ruby-bot) for other shared RSpec behaviors.
192
+
146
193
  ### Deploy
147
194
 
148
195
  See [DEPLOYMENT](DEPLOYMENT.md) for how to deploy your bot to production.
@@ -5,7 +5,7 @@ module PongBot
5
5
  end
6
6
 
7
7
  class Ping < SlackRubyBot::Commands::Base
8
- def self.call(data, _command, _arguments)
8
+ def self.call(data, _match)
9
9
  send_message data.channel, 'pong'
10
10
  end
11
11
  end
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'slack-ruby-bot', path: '../..'
@@ -0,0 +1,14 @@
1
+ require 'slack-ruby-bot'
2
+
3
+ module WeatherBot
4
+ class App < SlackRubyBot::App
5
+ end
6
+
7
+ class Weather < SlackRubyBot::Commands::Base
8
+ match(/^How is the weather in (?<location>\w*)\?$/i) do |data, match|
9
+ send_message data.channel, "The weather in #{match[:location]} is nice."
10
+ end
11
+ end
12
+ end
13
+
14
+ WeatherBot::App.instance.run
@@ -4,8 +4,6 @@ require 'slack-ruby-bot/version'
4
4
  require 'slack-ruby-bot/about'
5
5
  require 'slack-ruby-bot/config'
6
6
  require 'slack-ruby-bot/hooks'
7
- require 'slack-ruby-bot/commands'
8
- require 'slack-ruby-bot/app'
9
7
 
10
8
  module SlackRubyBot
11
9
  class << self
@@ -18,3 +16,6 @@ module SlackRubyBot
18
16
  end
19
17
  end
20
18
  end
19
+
20
+ require 'slack-ruby-bot/commands'
21
+ require 'slack-ruby-bot/app'
@@ -57,7 +57,7 @@ module SlackRubyBot
57
57
  rescue StandardError => e
58
58
  logger.error e
59
59
  begin
60
- Slack.chat_postMessage(channel: data['channel'], text: e.message) if data.key?('channel')
60
+ Slack.chat_postMessage(channel: data['channel'], text: e.message, as_user: true) if data.key?('channel')
61
61
  rescue
62
62
  # ignore
63
63
  end
@@ -1,10 +1,10 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
3
  class Default < Base
4
- command 'default'
5
4
  command 'about'
5
+ match(/^(?<bot>\w*)$/)
6
6
 
7
- def self.call(data, _command, _arguments)
7
+ def self.call(data, _match)
8
8
  send_message_with_gif data.channel, SlackRubyBot::ABOUT, 'selfie'
9
9
  end
10
10
  end
@@ -1,18 +1,17 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
3
  class Base
4
- class_attribute :operators
5
- class_attribute :commands
4
+ class_attribute :routes
6
5
 
7
- def self.send_message(channel, text)
6
+ def self.send_message(channel, text, options = { as_user: true })
8
7
  if text && text.length > 0
9
- Slack.chat_postMessage(channel: channel, text: text)
8
+ chat_postMessage({ channel: channel, text: text }.merge(options))
10
9
  else
11
- send_message_with_gif channel, 'Nothing to see here.', 'nothing'
10
+ send_message_with_gif channel, 'Nothing to see here.', 'nothing', options
12
11
  end
13
12
  end
14
13
 
15
- def self.send_message_with_gif(channel, text, keywords)
14
+ def self.send_message_with_gif(channel, text, keywords, options = { as_user: true })
16
15
  gif = begin
17
16
  Giphy.random(keywords)
18
17
  rescue StandardError => e
@@ -20,7 +19,7 @@ module SlackRubyBot
20
19
  nil
21
20
  end
22
21
  text = text + "\n" + gif.image_url.to_s if gif
23
- send_message channel, text
22
+ send_message channel, text, options
24
23
  end
25
24
 
26
25
  def self.logger
@@ -30,26 +29,58 @@ module SlackRubyBot
30
29
  end
31
30
  end
32
31
 
33
- def self.responds_to_command?(command)
34
- commands ? commands.include?(command) : command == default_command_name
35
- end
36
-
37
32
  def self.default_command_name
38
33
  name && name.split(':').last.downcase
39
34
  end
40
35
 
41
- def self.responds_to_operator?(operator)
42
- operators && operators.include?(operator)
36
+ def self.operator(*values, &block)
37
+ values.each do |value|
38
+ match Regexp.new("^(?<operator>\\#{value})(?<expression>.*)$", Regexp::IGNORECASE), &block
39
+ end
40
+ end
41
+
42
+ def self.command(*values, &block)
43
+ values.each do |value|
44
+ match Regexp.new("^(?<bot>\\w*)[\\s]+(?<command>#{value})$", Regexp::IGNORECASE), &block
45
+ match Regexp.new("^(?<bot>\\w*)[\\s]+(?<command>#{value})[\\s]+(?<expression>.*)$", Regexp::IGNORECASE), &block
46
+ end
47
+ end
48
+
49
+ def self.invoke(data)
50
+ self.finalize_routes!
51
+ expression = data.text
52
+ called = false
53
+ routes.each_pair do |route, method|
54
+ match = route.match(expression)
55
+ next unless match
56
+ next if match.names.include?('bot') && match['bot'].downcase != SlackRubyBot.config.user
57
+ called = true
58
+ if method
59
+ method.call(data, match)
60
+ elsif self.respond_to?(:call)
61
+ send(:call, data, match)
62
+ else
63
+ fail NotImplementedError, data.text
64
+ end
65
+ break
66
+ end
67
+ called
68
+ end
69
+
70
+ def self.match(match, &block)
71
+ self.routes ||= {}
72
+ self.routes[match] = block
43
73
  end
44
74
 
45
- def self.operator(value)
46
- self.operators ||= []
47
- self.operators << value.to_s
75
+ private
76
+
77
+ def self.chat_postMessage(message)
78
+ Slack.chat_postMessage(message)
48
79
  end
49
80
 
50
- def self.command(value)
51
- self.commands ||= []
52
- self.commands << value.to_s
81
+ def self.finalize_routes!
82
+ return if self.routes && self.routes.any?
83
+ command default_command_name
53
84
  end
54
85
  end
55
86
  end
@@ -1,7 +1,7 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
3
  class Help < Base
4
- def self.call(data, _command, _arguments)
4
+ def self.call(data, _match)
5
5
  send_message_with_gif data.channel, 'See https://github.com/dblock/slack-ruby-bot, please.', 'help'
6
6
  end
7
7
  end
@@ -1,7 +1,7 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
3
  class Hi < Base
4
- def self.call(data, _command, _arguments)
4
+ def self.call(data, _match)
5
5
  send_message_with_gif data.channel, "Hi <@#{data.user}>!", 'hi'
6
6
  end
7
7
  end
@@ -1,7 +1,9 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
3
  class Unknown < Base
4
- def self.call(data, _command, _arguments)
4
+ match(/^(?<bot>\w*)[\s]*(?<expression>.*)$/)
5
+
6
+ def self.call(data, _match)
5
7
  send_message_with_gif data.channel, "Sorry <@#{data.user}>, I don't understand that command!", 'idiot'
6
8
  end
7
9
  end
@@ -5,62 +5,15 @@ module SlackRubyBot
5
5
 
6
6
  def message(data)
7
7
  data = Hashie::Mash.new(data)
8
- klass, command, arguments = text_to_class(data.text)
9
- return unless klass
10
- klass.call(data, command, arguments)
8
+ data.text.strip!
9
+ result = child_command_classes.detect { |d| d.invoke(data) }
10
+ result ||= built_in_command_classes.detect { |d| d.invoke(data) }
11
+ result ||= SlackRubyBot::Commands::Unknown.tap { |d| d.invoke(data) }
12
+ result
11
13
  end
12
14
 
13
15
  private
14
16
 
15
- #
16
- # Lookup a class that can execute a command or an operator.
17
- #
18
- # @param [String] text Free-formed text from Slack.
19
- #
20
- # @return [SlackRubyBot::Commands::Base] A child class of SlackRubyBot::Commands::Base.
21
- #
22
- def text_to_class(text)
23
- return unless text
24
- command, arguments = parse_command(text)
25
- if command
26
- [command_to_class(command) || SlackRubyBot::Commands::Unknown, command, arguments]
27
- else
28
- command, arguments = parse_operator(text)
29
- [operator_to_class(command), command, arguments]
30
- end
31
- end
32
-
33
- #
34
- # Split text into a command and its arguments.
35
- #
36
- # @param [String] text Free-formed text from Slack, eg. `bot do something`.
37
- #
38
- # @return [Array] An array of command and arguments or nil.
39
- #
40
- def parse_command(text)
41
- return unless text
42
- parts = text.split.reject(&:blank?)
43
- return unless parts && parts.any?
44
- # command is [bot] <command> (argument1, ...)
45
- bot_or_operator = parts.first.downcase
46
- return unless SlackRubyBot.config.user == bot_or_operator
47
- command = parts[1].try(:downcase) || 'default'
48
- arguments = parts[2..parts.length]
49
- [command, arguments]
50
- end
51
-
52
- #
53
- # Split text into an operator and its arguments.
54
- #
55
- # @param [String] text Free-formed text from Slack, eg. =2+2.
56
- #
57
- # @return [Array] An array and arguments or nil.
58
- #
59
- def parse_operator(text)
60
- return unless text && text.length > 0
61
- [text[0], text[1..text.length].split.reject(&:blank?)]
62
- end
63
-
64
17
  #
65
18
  # All commands.
66
19
  #
@@ -88,34 +41,7 @@ module SlackRubyBot
88
41
  #
89
42
  def built_in_command_classes
90
43
  command_classes.select do |k|
91
- k.name && k.name.starts_with?('SlackRubyBot::Commands::')
92
- end
93
- end
94
-
95
- #
96
- # Converts a command to a class.
97
- #
98
- # @param [String] command Command tet.
99
- #
100
- # @return [SlackRubyBot::Commands::Base] A command implementation.
101
- #
102
- def command_to_class(command)
103
- # prioritize implementations to built-in classes
104
- klass = child_command_classes.detect { |d| d.responds_to_command?(command) }
105
- klass ||= built_in_command_classes.detect { |d| d.responds_to_command?(command) }
106
- klass
107
- end
108
-
109
- #
110
- # Converts an operator to a class.
111
- #
112
- # @param [String] operator Operator text.
113
- #
114
- # @return [<type>] An operator implementation.
115
- #
116
- def operator_to_class(operator)
117
- command_classes.detect do |k|
118
- k.responds_to_operator?(operator)
44
+ k.name && k.name.starts_with?('SlackRubyBot::Commands::') && k != SlackRubyBot::Commands::Unknown
119
45
  end
120
46
  end
121
47
  end
@@ -1,14 +1,13 @@
1
1
  require 'rspec/expectations'
2
2
 
3
- RSpec::Matchers.define :respond_with_error do |expected|
3
+ RSpec::Matchers.define :respond_with_error do |error, error_message|
4
4
  match do |actual|
5
5
  channel, user, message = parse(actual)
6
- app = SlackRubyBot::App.new
7
6
  allow(Giphy).to receive(:random)
8
7
  begin
9
8
  expect do
10
9
  app.send(:message, text: message, channel: channel, user: user)
11
- end.to raise_error ArgumentError, expected
10
+ end.to raise_error error, error_message
12
11
  rescue RSpec::Expectations::ExpectationNotMetError => e
13
12
  @error_message = e.message
14
13
  raise e
@@ -4,7 +4,7 @@ RSpec::Matchers.define :respond_with_slack_message do |expected|
4
4
  match do |actual|
5
5
  channel, user, message = parse(actual)
6
6
  allow(Giphy).to receive(:random)
7
- expect(SlackRubyBot::Commands::Base).to receive(:send_message).with(channel, expected)
7
+ expect(SlackRubyBot::Commands::Base).to receive(:chat_postMessage).with(channel: channel, text: expected, as_user: true)
8
8
  app.send(:message, text: message, channel: channel, user: user)
9
9
  true
10
10
  end
@@ -1,3 +1,3 @@
1
1
  module SlackRubyBot
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
Binary file
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
13
13
  s.require_paths = ['lib']
14
14
  s.homepage = 'http://github.com/dblock/slack-ruby-bot'
15
15
  s.licenses = ['MIT']
16
- s.summary = 'Generic bot framework written in Ruby.'
16
+ s.summary = 'The easiest way to write a Slack bot in Ruby.'
17
17
  s.add_dependency 'hashie'
18
18
  s.add_dependency 'slack-api', '~> 1.1.6'
19
19
  s.add_dependency 'activesupport'
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe SlackRubyBot::Commands do
4
+ let! :command do
5
+ Class.new(SlackRubyBot::Commands::Base) do
6
+ command 'tomato' do |data, match|
7
+ send_message data.channel, "#{match[:command]}: #{match[:expression]}", as_user: true
8
+ end
9
+
10
+ command 'tomatoes' do |data, match|
11
+ send_message data.channel, "#{match[:command]}: #{match[:expression]}", as_user: true
12
+ end
13
+ end
14
+ end
15
+ def app
16
+ SlackRubyBot::App.new
17
+ end
18
+ it 'matches commands' do
19
+ expect(message: "#{SlackRubyBot.config.user} tomato red").to respond_with_slack_message('tomato: red')
20
+ expect(message: "#{SlackRubyBot.config.user} tomatoes green").to respond_with_slack_message('tomatoes: green')
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe SlackRubyBot::Commands do
4
+ let! :command do
5
+ Class.new(SlackRubyBot::Commands::Base) do
6
+ command 'space' do |data, match|
7
+ send_message data.channel, "#{match[:command]}: #{match[:expression]}", as_user: true
8
+ end
9
+ end
10
+ end
11
+ def app
12
+ SlackRubyBot::App.new
13
+ end
14
+ it 'matches leading spaces' do
15
+ expect(message: " #{SlackRubyBot.config.user} space red").to respond_with_slack_message('space: red')
16
+ end
17
+ it 'matches trailing spaces' do
18
+ expect(message: "#{SlackRubyBot.config.user} space red ").to respond_with_slack_message('space: red')
19
+ end
20
+ end
@@ -6,8 +6,8 @@ describe SlackRubyBot::Commands do
6
6
  command 'sayhi'
7
7
  command 'saybye'
8
8
 
9
- def self.call(data, command, arguments)
10
- send_message data.channel, "#{command}: #{arguments}"
9
+ def self.call(data, match)
10
+ send_message data.channel, "#{match[:command]}: #{match[:expression]}", as_user: true
11
11
  end
12
12
  end
13
13
  end
@@ -15,7 +15,7 @@ describe SlackRubyBot::Commands do
15
15
  SlackRubyBot::App.new
16
16
  end
17
17
  it 'supports multiple commands' do
18
- expect(message: "#{SlackRubyBot.config.user} sayhi arg1 arg2").to respond_with_slack_message('sayhi: ["arg1", "arg2"]')
19
- expect(message: "#{SlackRubyBot.config.user} saybye arg1 arg2").to respond_with_slack_message('saybye: ["arg1", "arg2"]')
18
+ expect(message: "#{SlackRubyBot.config.user} sayhi arg1 arg2").to respond_with_slack_message('sayhi: arg1 arg2')
19
+ expect(message: "#{SlackRubyBot.config.user} saybye arg1 arg2").to respond_with_slack_message('saybye: arg1 arg2')
20
20
  end
21
21
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe SlackRubyBot::Commands do
4
+ let! :command do
5
+ Class.new(SlackRubyBot::Commands::Base) do
6
+ command 'sayhi' do |data, match|
7
+ send_message data.channel, "#{match[:command]}: #{match[:expression]}", as_user: true
8
+ end
9
+
10
+ command 'one', 'two' do |data, match|
11
+ send_message data.channel, "#{match[:command]}: #{match[:expression]}", as_user: true
12
+ end
13
+ end
14
+ end
15
+ def app
16
+ SlackRubyBot::App.new
17
+ end
18
+ it 'supports multiple commands' do
19
+ expect(message: "#{SlackRubyBot.config.user} sayhi arg1 arg2").to respond_with_slack_message('sayhi: arg1 arg2')
20
+ expect(message: "#{SlackRubyBot.config.user} one arg1 arg2").to respond_with_slack_message('one: arg1 arg2')
21
+ expect(message: "#{SlackRubyBot.config.user} two arg1 arg2").to respond_with_slack_message('two: arg1 arg2')
22
+ end
23
+ end
@@ -3,9 +3,9 @@ require 'spec_helper'
3
3
  describe SlackRubyBot::Commands do
4
4
  let! :command do
5
5
  Class.new(SlackRubyBot::Commands::Base) do
6
- command 'test'
6
+ command 'empty_text'
7
7
 
8
- def self.call(data, _command, _arguments)
8
+ def self.call(data, _match)
9
9
  send_message data.channel, nil
10
10
  end
11
11
  end
@@ -15,7 +15,7 @@ describe SlackRubyBot::Commands do
15
15
  end
16
16
  it 'sends default text' do
17
17
  allow(Giphy).to receive(:random)
18
- expect(SlackRubyBot::Commands::Base).to receive(:send_message_with_gif).with('channel', 'Nothing to see here.', 'nothing')
19
- app.send(:message, text: "#{SlackRubyBot.config.user} test", channel: 'channel', user: 'user')
18
+ expect(SlackRubyBot::Commands::Base).to receive(:send_message_with_gif).with('channel', 'Nothing to see here.', 'nothing', as_user: true)
19
+ app.send(:message, text: "#{SlackRubyBot.config.user} empty_text", channel: 'channel', user: 'user')
20
20
  end
21
21
  end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe SlackRubyBot::Commands::Base do
4
+ let! :command do
5
+ Class.new(SlackRubyBot::Commands::Base) do
6
+ command 'not_implemented'
7
+ end
8
+ end
9
+ def app
10
+ SlackRubyBot::App.new
11
+ end
12
+ it 'raises not implemented' do
13
+ expect(message: "#{SlackRubyBot.config.user} not_implemented").to respond_with_error(NotImplementedError, 'rubybot not_implemented')
14
+ end
15
+ end
@@ -6,8 +6,8 @@ describe SlackRubyBot::Commands do
6
6
  operator '='
7
7
  operator '-'
8
8
 
9
- def self.call(data, command, arguments)
10
- send_message data.channel, "#{command}: #{arguments}"
9
+ def self.call(data, match)
10
+ send_message data.channel, "#{match[:operator]}: #{match[:expression]}", as_user: true
11
11
  end
12
12
  end
13
13
  end
@@ -15,6 +15,6 @@ describe SlackRubyBot::Commands do
15
15
  SlackRubyBot::App.new
16
16
  end
17
17
  it 'supports operators' do
18
- expect(message: '=2+2').to respond_with_slack_message('=: ["2+2"]')
18
+ expect(message: '=2+2').to respond_with_slack_message('=: 2+2')
19
19
  end
20
20
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe SlackRubyBot::Commands do
4
+ let! :command do
5
+ Class.new(SlackRubyBot::Commands::Base) do
6
+ operator '=' do |data, match|
7
+ send_message data.channel, "#{match[:operator]}: #{match[:expression]}", as_user: true
8
+ end
9
+
10
+ operator '+', '-' do |data, match|
11
+ send_message data.channel, "#{match[:operator]}: #{match[:expression]}", as_user: true
12
+ end
13
+ end
14
+ end
15
+ def app
16
+ SlackRubyBot::App.new
17
+ end
18
+ it 'supports operator blocks' do
19
+ expect(message: '=2+2').to respond_with_slack_message('=: 2+2')
20
+ expect(message: '+2+2').to respond_with_slack_message('+: 2+2')
21
+ expect(message: '-2+2').to respond_with_slack_message('-: 2+2')
22
+ end
23
+ end
@@ -5,8 +5,8 @@ describe RSpec do
5
5
  Class.new(SlackRubyBot::Commands::Base) do
6
6
  command 'raise'
7
7
 
8
- def self.call(_data, command, _arguments)
9
- fail ArgumentError, command
8
+ def self.call(_data, match)
9
+ fail ArgumentError, match[:command]
10
10
  end
11
11
  end
12
12
  end
@@ -14,6 +14,6 @@ describe RSpec do
14
14
  SlackRubyBot::App.new
15
15
  end
16
16
  it 'respond_with_error' do
17
- expect(message: "#{SlackRubyBot.config.user} raise").to respond_with_error('raise')
17
+ expect(message: "#{SlackRubyBot.config.user} raise").to respond_with_error(ArgumentError, 'raise')
18
18
  end
19
19
  end
metadata CHANGED
@@ -1,153 +1,153 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slack-ruby-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Doubrovkine
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-10 00:00:00.000000000 Z
11
+ date: 2015-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: slack-api
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.1.6
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.1.6
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activesupport
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: giphy
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 2.0.2
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 2.0.2
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: websocket-driver
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: 0.5.4
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.5.4
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rack-test
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - '>='
115
+ - - ">="
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - '>='
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: vcr
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - '>='
129
+ - - ">="
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - '>='
136
+ - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: webmock
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - '>='
143
+ - - ">="
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - '>='
150
+ - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
@@ -170,22 +170,25 @@ executables: []
170
170
  extensions: []
171
171
  extra_rdoc_files: []
172
172
  files:
173
- - .gitignore
174
- - .rspec
175
- - .rubocop.yml
176
- - .rubocop_todo.yml
177
- - .travis.yml
173
+ - ".gitignore"
174
+ - ".rspec"
175
+ - ".rubocop.yml"
176
+ - ".rubocop_todo.yml"
177
+ - ".travis.yml"
178
178
  - CHANGELOG.md
179
179
  - CONTRIBUTING.md
180
180
  - DEPLOYMENT.md
181
181
  - Gemfile
182
182
  - LICENSE.md
183
183
  - README.md
184
+ - RELEASING.md
184
185
  - Rakefile
185
186
  - TUTORIAL.md
186
187
  - examples/minimal/Gemfile
187
188
  - examples/minimal/Gemfile.lock
188
189
  - examples/minimal/pongbot.rb
190
+ - examples/weather/Gemfile
191
+ - examples/weather/weatherbot.rb
189
192
  - lib/config/application.rb
190
193
  - lib/config/boot.rb
191
194
  - lib/config/environment.rb
@@ -217,14 +220,20 @@ files:
217
220
  - lib/slack_ruby_bot.rb
218
221
  - screenshots/demo.gif
219
222
  - screenshots/register-bot.png
223
+ - screenshots/weather.gif
220
224
  - slack-ruby-bot.gemspec
221
225
  - spec/slack-ruby-bot/app_spec.rb
222
226
  - spec/slack-ruby-bot/commands/about_spec.rb
227
+ - spec/slack-ruby-bot/commands/commands_precedence_spec.rb
228
+ - spec/slack-ruby-bot/commands/commands_spaces_spec.rb
223
229
  - spec/slack-ruby-bot/commands/commands_spec.rb
230
+ - spec/slack-ruby-bot/commands/commands_with_block_spec.rb
224
231
  - spec/slack-ruby-bot/commands/empty_text_spec.rb
225
232
  - spec/slack-ruby-bot/commands/help_spec.rb
226
233
  - spec/slack-ruby-bot/commands/hi_spec.rb
234
+ - spec/slack-ruby-bot/commands/not_implemented_spec.rb
227
235
  - spec/slack-ruby-bot/commands/operators_spec.rb
236
+ - spec/slack-ruby-bot/commands/operators_with_block_spec.rb
228
237
  - spec/slack-ruby-bot/commands/unknown_spec.rb
229
238
  - spec/slack-ruby-bot/rspec/respond_with_error_spec.rb
230
239
  - spec/slack-ruby-bot/version_spec.rb
@@ -239,28 +248,33 @@ require_paths:
239
248
  - lib
240
249
  required_ruby_version: !ruby/object:Gem::Requirement
241
250
  requirements:
242
- - - '>='
251
+ - - ">="
243
252
  - !ruby/object:Gem::Version
244
253
  version: '0'
245
254
  required_rubygems_version: !ruby/object:Gem::Requirement
246
255
  requirements:
247
- - - '>='
256
+ - - ">="
248
257
  - !ruby/object:Gem::Version
249
258
  version: 1.3.6
250
259
  requirements: []
251
260
  rubyforge_project:
252
- rubygems_version: 2.4.5
261
+ rubygems_version: 2.2.2
253
262
  signing_key:
254
263
  specification_version: 4
255
- summary: Generic bot framework written in Ruby.
264
+ summary: The easiest way to write a Slack bot in Ruby.
256
265
  test_files:
257
266
  - spec/slack-ruby-bot/app_spec.rb
258
267
  - spec/slack-ruby-bot/commands/about_spec.rb
268
+ - spec/slack-ruby-bot/commands/commands_precedence_spec.rb
269
+ - spec/slack-ruby-bot/commands/commands_spaces_spec.rb
259
270
  - spec/slack-ruby-bot/commands/commands_spec.rb
271
+ - spec/slack-ruby-bot/commands/commands_with_block_spec.rb
260
272
  - spec/slack-ruby-bot/commands/empty_text_spec.rb
261
273
  - spec/slack-ruby-bot/commands/help_spec.rb
262
274
  - spec/slack-ruby-bot/commands/hi_spec.rb
275
+ - spec/slack-ruby-bot/commands/not_implemented_spec.rb
263
276
  - spec/slack-ruby-bot/commands/operators_spec.rb
277
+ - spec/slack-ruby-bot/commands/operators_with_block_spec.rb
264
278
  - spec/slack-ruby-bot/commands/unknown_spec.rb
265
279
  - spec/slack-ruby-bot/rspec/respond_with_error_spec.rb
266
280
  - spec/slack-ruby-bot/version_spec.rb