slack-ruby-bot 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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