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
@@ -1,6 +1,6 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
- class HelpCommand < Base
3
+ class Help < Base
4
4
  help do
5
5
  title 'help'
6
6
  desc 'Shows help information.'
@@ -10,7 +10,7 @@ module SlackRubyBot
10
10
  command = match[:expression]
11
11
 
12
12
  text = if command.present?
13
- CommandsHelper.instance.command_full_desc(command)
13
+ Support::Help.instance.command_full_desc(command)
14
14
  else
15
15
  general_text
16
16
  end
@@ -22,8 +22,8 @@ module SlackRubyBot
22
22
  private
23
23
 
24
24
  def general_text
25
- bot_desc = CommandsHelper.instance.bot_desc_and_commands
26
- other_commands_descs = CommandsHelper.instance.other_commands_descs
25
+ bot_desc = Support::Help.instance.bot_desc_and_commands
26
+ other_commands_descs = Support::Help.instance.other_commands_descs
27
27
  <<TEXT
28
28
  #{bot_desc.join("\n")}
29
29
 
@@ -1,6 +1,6 @@
1
1
  module SlackRubyBot
2
2
  module Commands
3
- module Help
3
+ module Support
4
4
  class Attrs
5
5
  attr_accessor :command_name, :command_desc, :command_long_desc
6
6
  attr_reader :klass, :commands
@@ -0,0 +1,80 @@
1
+ require 'singleton'
2
+ require_relative 'attrs'
3
+
4
+ module SlackRubyBot
5
+ module Commands
6
+ module Support
7
+ class Help
8
+ include Singleton
9
+ attr_reader :commands_help_attrs
10
+
11
+ def initialize
12
+ @commands_help_attrs = []
13
+ end
14
+
15
+ def capture_help(klass, &block)
16
+ k = Commands::Support::Attrs.new(klass)
17
+ k.instance_eval(&block)
18
+ @commands_help_attrs << k
19
+ end
20
+
21
+ def bot_desc_and_commands
22
+ collect_help_attrs(bot_help_attrs) do |help_attrs|
23
+ bot_commands_descs = collect_name_and_desc(help_attrs.commands)
24
+ "#{command_name_and_desc(help_attrs)}\n\n*Commands:*\n#{bot_commands_descs.join("\n")}"
25
+ end
26
+ end
27
+
28
+ def other_commands_descs
29
+ collect_name_and_desc(other_commands_help_attrs)
30
+ end
31
+
32
+ def command_full_desc(name)
33
+ unescaped_name = Slack::Messages::Formatting.unescape(name)
34
+ help_attrs = find_command_help_attrs(unescaped_name)
35
+ return "There's no command *#{unescaped_name}*" unless help_attrs
36
+ return "There's no description for command *#{unescaped_name}*" if help_attrs.command_long_desc.blank?
37
+ "#{command_name_and_desc(help_attrs)}\n\n#{help_attrs.command_long_desc}"
38
+ end
39
+
40
+ def find_command_help_attrs(name)
41
+ help_attrs = commands_help_attrs.find { |k| k.command_name == name }
42
+ return help_attrs if help_attrs
43
+ commands_help_attrs.each { |k| k.commands.each { |c| return c if c.command_name == name } }
44
+ nil
45
+ end
46
+
47
+ private
48
+
49
+ def collect_help_attrs(help_attrs)
50
+ help_attrs_with_present_names(help_attrs).map do |ha|
51
+ yield(ha)
52
+ end
53
+ end
54
+
55
+ def collect_name_and_desc(help_attrs)
56
+ collect_help_attrs(help_attrs) do |ha|
57
+ command_name_and_desc(ha)
58
+ end
59
+ end
60
+
61
+ def command_name_and_desc(help_attrs)
62
+ desc = help_attrs.command_desc.present? ? " - #{help_attrs.command_desc}" : ''
63
+ "*#{help_attrs.command_name}*#{desc}"
64
+ end
65
+
66
+ def help_attrs_with_present_names(help_attrs)
67
+ help_attrs.select { |k| k.command_name.present? }
68
+ end
69
+
70
+ def bot_help_attrs
71
+ commands_help_attrs.select { |k| k.klass.ancestors.include?(SlackRubyBot::Bot) }
72
+ end
73
+
74
+ def other_commands_help_attrs
75
+ commands_help_attrs.select { |k| k.klass.ancestors.include?(SlackRubyBot::Commands::Base) && !k.klass.ancestors.include?(SlackRubyBot::Bot) }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,22 @@
1
+ module SlackRubyBot
2
+ module Commands
3
+ module Support
4
+ class Match
5
+ extend Forwardable
6
+
7
+ delegate MatchData.public_instance_methods(false) => :@match_data
8
+
9
+ attr_reader :attachment, :attachment_field
10
+
11
+ def initialize(match_data, attachment = nil, attachment_field = nil)
12
+ unless match_data.is_a? MatchData
13
+ raise ArgumentError, 'match_data should be a type of MatchData'
14
+ end
15
+ @match_data = match_data
16
+ @attachment = attachment
17
+ @attachment_field = attachment_field
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,7 +2,7 @@ module SlackRubyBot
2
2
  module Config
3
3
  extend self
4
4
 
5
- ATTRS = [:token, :url, :aliases, :user, :user_id, :team, :team_id, :allow_message_loops, :send_gifs, :logger].freeze
5
+ ATTRS = %i[token url aliases user user_id team team_id allow_message_loops send_gifs logger].freeze
6
6
  attr_accessor(*ATTRS)
7
7
 
8
8
  def allow_message_loops?
@@ -9,10 +9,10 @@ RSpec::Matchers.define :not_respond do
9
9
  end
10
10
 
11
11
  message_command = SlackRubyBot::Hooks::Message.new
12
- channel, user, message = parse(actual)
12
+ channel, user, message, attachments = parse(actual)
13
13
 
14
14
  expect(client).not_to receive(:message)
15
- message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user))
15
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
16
16
  true
17
17
  end
18
18
 
@@ -20,6 +20,8 @@ RSpec::Matchers.define :not_respond do
20
20
 
21
21
  def parse(actual)
22
22
  actual = { message: actual } unless actual.is_a?(Hash)
23
- [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message]]
23
+ attachments = actual[:attachments]
24
+ attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
25
+ [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
24
26
  end
25
27
  end
@@ -9,13 +9,13 @@ RSpec::Matchers.define :respond_with_error do |error, error_message|
9
9
  end
10
10
 
11
11
  message_command = SlackRubyBot::Hooks::Message.new
12
- channel, user, message = parse(actual)
12
+ channel, user, message, attachments = parse(actual)
13
13
 
14
14
  allow(Giphy).to receive(:random) if defined?(Giphy)
15
15
 
16
16
  begin
17
17
  expect do
18
- message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user))
18
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
19
19
  end.to raise_error error, error_message
20
20
  rescue RSpec::Expectations::ExpectationNotMetError => e
21
21
  @error_message = e.message
@@ -33,6 +33,8 @@ RSpec::Matchers.define :respond_with_error do |error, error_message|
33
33
 
34
34
  def parse(actual)
35
35
  actual = { message: actual } unless actual.is_a?(Hash)
36
- [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message]]
36
+ attachments = actual[:attachments]
37
+ attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
38
+ [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
37
39
  end
38
40
  end
@@ -14,12 +14,12 @@ RSpec::Matchers.define :respond_with_slack_message do |expected|
14
14
  end
15
15
 
16
16
  message_command = SlackRubyBot::Hooks::Message.new
17
- channel, user, message = parse(actual)
17
+ channel, user, message, attachments = parse(actual)
18
18
 
19
19
  allow(Giphy).to receive(:random) if defined?(Giphy)
20
20
 
21
21
  allow(client).to receive(:message)
22
- message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user))
22
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
23
23
  @messages = client.test_messages
24
24
  expect(client).to have_received(:message).with(hash_including(channel: channel, text: expected)).once
25
25
  true
@@ -15,12 +15,12 @@ RSpec::Matchers.define :respond_with_slack_messages do |expected|
15
15
  end
16
16
 
17
17
  message_command = SlackRubyBot::Hooks::Message.new
18
- channel, user, message = parse(actual)
18
+ channel, user, message, attachments = parse(actual)
19
19
 
20
20
  allow(Giphy).to receive(:random) if defined?(Giphy)
21
21
 
22
22
  allow(client).to receive(:message)
23
- message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user))
23
+ message_command.call(client, Hashie::Mash.new(text: message, channel: channel, user: user, attachments: attachments))
24
24
  @messages = client.test_messages
25
25
  @responses = []
26
26
  expected.each do |exp|
@@ -4,7 +4,9 @@ module SlackRubyBot
4
4
 
5
5
  def parse(actual)
6
6
  actual = { message: actual } unless actual.is_a?(Hash)
7
- [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message]]
7
+ attachments = actual[:attachments]
8
+ attachments = [attachments] unless attachments.nil? || attachments.is_a?(Array)
9
+ [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message], attachments]
8
10
  end
9
11
  end
10
12
  end
@@ -6,7 +6,7 @@ module SlackRubyBot
6
6
 
7
7
  include SlackRubyBot::Hooks::HookSupport
8
8
 
9
- TRAPPED_SIGNALS = %w(INT TERM).freeze
9
+ TRAPPED_SIGNALS = %w[INT TERM].freeze
10
10
 
11
11
  def initialize(options = {})
12
12
  @token = options[:token]
@@ -1,3 +1,3 @@
1
1
  module SlackRubyBot
2
- VERSION = '0.10.5'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
@@ -16,10 +16,10 @@ Gem::Specification.new do |s|
16
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-ruby-client', '>= 0.6.0'
19
+ s.add_development_dependency 'rack-test'
19
20
  s.add_development_dependency 'rake'
20
21
  s.add_development_dependency 'rspec'
21
- s.add_development_dependency 'rack-test'
22
+ s.add_development_dependency 'rubocop', '0.51.0'
22
23
  s.add_development_dependency 'vcr'
23
24
  s.add_development_dependency 'webmock'
24
- s.add_development_dependency 'rubocop', '0.38.0'
25
25
  end
@@ -0,0 +1,102 @@
1
+ describe SlackRubyBot::Commands do
2
+ context 'optional fields to scan' do
3
+ let! :command do
4
+ Class.new(SlackRubyBot::Commands::Base) do
5
+ attachment(/Match by text3 field/, :text3) do |client, data, _match|
6
+ client.say(text: "The matched field: 'text3'", channel: data.channel)
7
+ end
8
+ attachment(/Match by either title or text2 field/, %i[title text2]) do |client, data, match|
9
+ client.say(text: "The matched field: '#{match.attachment_field}'", channel: data.channel)
10
+ end
11
+ end
12
+ end
13
+
14
+ it 'matches by only text3 field' do
15
+ attachment = {
16
+ text3: 'Match by text3 field'
17
+ }
18
+ expect(attachments: attachment).to respond_with_slack_message("The matched field: 'text3'")
19
+ end
20
+
21
+ it 'matches by text2 field' do
22
+ attachment = {
23
+ text2: 'Match by either title or text2 field'
24
+ }
25
+ expect(attachments: attachment).to respond_with_slack_message("The matched field: 'text2'")
26
+ end
27
+ it 'matches by title field' do
28
+ attachment = {
29
+ title: 'Match by either title or text2 field'
30
+ }
31
+ expect(attachments: attachment).to respond_with_slack_message("The matched field: 'title'")
32
+ end
33
+ it 'does not match by default pretext field' do
34
+ attachment = {
35
+ pretext: 'Match by either title or text2 field'
36
+ }
37
+ expect(attachments: attachment).to not_respond
38
+ end
39
+ end
40
+
41
+ context 'default fields to scan' do
42
+ let! :command do
43
+ Class.new(SlackRubyBot::Commands::Base) do
44
+ attachment(/New comment matched by pretext #(?<id>\d+)/) do |client, data, match|
45
+ client.say(text: "The id: #{match[:id]}", channel: data.channel)
46
+ client.say(text: "Comment: '#{match.attachment.text}'", channel: data.channel)
47
+ client.say(text: "The matched field: '#{match.attachment_field}'", channel: data.channel)
48
+ end
49
+ attachment(/New comment matched by text #(?<id>\d+)/) do |client, data, match|
50
+ client.say(text: "The id: #{match[:id]}", channel: data.channel)
51
+ client.say(text: "Comment: '#{match.attachment.title}'", channel: data.channel)
52
+ client.say(text: "The matched field: '#{match.attachment_field}'", channel: data.channel)
53
+ end
54
+ attachment(/New comment matched by title #(?<id>\d+)/) do |client, data, match|
55
+ client.say(text: "The id: #{match[:id]}", channel: data.channel)
56
+ client.say(text: "Comment: '#{match.attachment.pretext}'", channel: data.channel)
57
+ client.say(text: "The matched field: '#{match.attachment_field}'", channel: data.channel)
58
+ end
59
+ end
60
+ end
61
+ def app
62
+ SlackRubyBot::App.new
63
+ end
64
+
65
+ let :expected_responses do
66
+ [
67
+ 'The id: 12345',
68
+ "Comment: 'foo bar'"
69
+ ]
70
+ end
71
+ it 'matches pretext' do
72
+ expected_responses << "The matched field: 'pretext'"
73
+ attachment = {
74
+ pretext: 'New comment matched by pretext #12345',
75
+ text: 'foo bar'
76
+ }
77
+ expect(attachments: attachment).to respond_with_slack_messages(expected_responses)
78
+ end
79
+ it 'matches text' do
80
+ expected_responses << "The matched field: 'text'"
81
+ attachment = {
82
+ text: 'New comment matched by text #12345',
83
+ title: 'foo bar'
84
+ }
85
+ expect(attachments: attachment).to respond_with_slack_messages(expected_responses)
86
+ end
87
+ it 'matches title' do
88
+ expected_responses << "The matched field: 'title'"
89
+ attachment = {
90
+ title: 'New comment matched by title #12345',
91
+ pretext: 'foo bar'
92
+ }
93
+ expect(attachments: attachment).to respond_with_slack_messages(expected_responses)
94
+ end
95
+ it 'does not respond' do
96
+ attachment = {
97
+ text: 'no match'
98
+ }
99
+ expect(attachments: attachment).to not_respond
100
+ end
101
+ end
102
+ end
@@ -1,4 +1,4 @@
1
- describe SlackRubyBot::Commands::Help do
1
+ describe SlackRubyBot::Commands::Unknown do
2
2
  def app
3
3
  SlackRubyBot::App.new
4
4
  end
@@ -6,10 +6,16 @@ describe SlackRubyBot::Commands do
6
6
  end
7
7
  end
8
8
  end
9
+
9
10
  def app
10
11
  SlackRubyBot::App.new
11
12
  end
13
+
12
14
  it 'does not return error' do
13
15
  expect(message: "#{SlackRubyBot.config.user} ( /").to respond_with_slack_message('(: /')
14
16
  end
17
+
18
+ it 'allows multiline expression' do
19
+ expect(message: "#{SlackRubyBot.config.user} (\n1\n2").to respond_with_slack_message("(: 1\n2")
20
+ end
15
21
  end
@@ -1,4 +1,4 @@
1
- describe SlackRubyBot::Commands::Help::Attrs do
1
+ describe SlackRubyBot::Commands::Support::Attrs do
2
2
  let(:help_attrs) { described_class.new('WeatherBot') }
3
3
 
4
4
  it 'captures commands help attributes' do
@@ -1,10 +1,10 @@
1
- describe SlackRubyBot::CommandsHelper do
1
+ describe SlackRubyBot::Commands::Support::Help do
2
2
  let(:bot_class) { Testing::WeatherBot }
3
3
  let(:command_class) { Testing::HelloCommand }
4
- let(:commands_helper) { described_class.instance }
4
+ let(:help) { described_class.instance }
5
5
 
6
6
  describe '#capture_help' do
7
- let(:help_attrs) { commands_helper.commands_help_attrs }
7
+ let(:help_attrs) { help.commands_help_attrs }
8
8
  let(:bot_help_attrs) { help_attrs.find { |k| k.klass == bot_class } }
9
9
  let(:command_help_attrs) { help_attrs.find { |k| k.klass == command_class } }
10
10
 
@@ -59,8 +59,22 @@ describe SlackRubyBot::CommandsHelper do
59
59
  end
60
60
  end
61
61
 
62
+ describe '#find_command_help_attrs' do
63
+ let(:hello_help_attrs) { help.find_command_help_attrs('hello') }
64
+
65
+ before(:each) do
66
+ command_class
67
+ end
68
+
69
+ it 'returns help attrs for hello command' do
70
+ expect(hello_help_attrs.command_name).to eq('hello')
71
+ expect(hello_help_attrs.command_desc).to eq('Says hello.')
72
+ expect(hello_help_attrs.command_long_desc).to eq('The long description')
73
+ end
74
+ end
75
+
62
76
  describe '#bot_desc_and_commands' do
63
- let(:bot_desc_and_commands) { commands_helper.bot_desc_and_commands }
77
+ let(:bot_desc_and_commands) { help.bot_desc_and_commands }
64
78
  let(:bot_desc) { bot_desc_and_commands.first }
65
79
 
66
80
  it 'returns bot name and description' do
@@ -107,7 +121,7 @@ describe SlackRubyBot::CommandsHelper do
107
121
  end
108
122
  end
109
123
 
110
- let(:other_commands_descs) { commands_helper.other_commands_descs }
124
+ let(:other_commands_descs) { help.other_commands_descs }
111
125
  let(:hello_command_desc) { other_commands_descs.find { |desc| desc =~ /\*hello\*/ } }
112
126
 
113
127
  it 'returns command name and description' do
@@ -122,7 +136,7 @@ describe SlackRubyBot::CommandsHelper do
122
136
  describe '#command_full_desc' do
123
137
  context 'for bot commands' do
124
138
  it 'returns long description' do
125
- full_desc = commands_helper.command_full_desc("What's the weather in &lt;city&gt;?")
139
+ full_desc = help.command_full_desc("What's the weather in &lt;city&gt;?")
126
140
  expect(full_desc).to include "Accurate 10 Day Weather Forecasts for thousands of places around the World.\n" \
127
141
  'We provide detailed Weather Forecasts over a 10 day period updated four times a day.'
128
142
  end
@@ -130,18 +144,18 @@ describe SlackRubyBot::CommandsHelper do
130
144
 
131
145
  context 'for other commands' do
132
146
  it 'returns long description' do
133
- full_desc = commands_helper.command_full_desc('hello')
147
+ full_desc = help.command_full_desc('hello')
134
148
  expect(full_desc).to include 'The long description'
135
149
  end
136
150
  end
137
151
 
138
152
  it 'returns correct message if there is no such command' do
139
- full_desc = commands_helper.command_full_desc('deploy')
153
+ full_desc = help.command_full_desc('deploy')
140
154
  expect(full_desc).to eq "There's no command *deploy*"
141
155
  end
142
156
 
143
157
  it 'returns correct message if there is no long description for the command' do
144
- full_desc = commands_helper.command_full_desc('clouds')
158
+ full_desc = help.command_full_desc('clouds')
145
159
  expect(full_desc).to eq "There's no description for command *clouds*"
146
160
  end
147
161
  end