slack-ruby-bot 0.10.5 → 0.11.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 (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