slack_interactive_client 0.1.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.
- checksums.yaml +7 -0
- data/lib/slack_interactive_client/base_interaction.rb +129 -0
- data/lib/slack_interactive_client/bot.rb +26 -0
- data/lib/slack_interactive_client/client.rb +88 -0
- data/lib/slack_interactive_client/concerns/slack_interaction_params.rb +49 -0
- data/lib/slack_interactive_client/concerns/slack_pattern_interaction.rb +38 -0
- data/lib/slack_interactive_client/configuration.rb +11 -0
- data/lib/slack_interactive_client/engine.rb +7 -0
- data/lib/slack_interactive_client/interactions/unknown_command_interaction.rb +14 -0
- data/lib/slack_interactive_client/message/compilation.rb +28 -0
- data/lib/slack_interactive_client/message.rb +187 -0
- data/lib/slack_interactive_client/message_stop_guard.rb +29 -0
- data/lib/slack_interactive_client/railtie.rb +6 -0
- data/lib/slack_interactive_client/response_client.rb +20 -0
- data/lib/slack_interactive_client/version.rb +5 -0
- data/lib/slack_interactive_client.rb +16 -0
- data/spec/base_interaction_spec.rb +19 -0
- data/spec/bot_spec.rb +20 -0
- data/spec/client_spec.rb +100 -0
- data/spec/message/compilation_spec.rb +57 -0
- data/spec/message_spec.rb +173 -0
- data/spec/message_stop_guard_spec.rb +15 -0
- data/spec/spec_helper.rb +16 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3441dda9fe55fa50b7780ddf42a8695425785bd91dce7d3993c84878cc6edee0
|
4
|
+
data.tar.gz: eca34dfd218a21d82031db317454ebd53f62c1196a0cdb8c9ebc487a64409da6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9682852dbf51d73af56389b70816bc50c321a2b61023f3d64abb8082366cfaf2f6b7ed83aef1edf8b9db55d07f16595a2fb77e09bb3a73cba29d7f22d2215c0c
|
7
|
+
data.tar.gz: 775ad4048c834105a706cef29ff8fad6a8eea2779ad4270317dbbb9bfb45eb4281f074c9513f967a3dc3c8734b02c35ff7dfd0cc11f333b384753ee4f012f6a4
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'csv'
|
5
|
+
require_relative 'concerns/slack_interaction_params'
|
6
|
+
require_relative 'concerns/slack_pattern_interaction'
|
7
|
+
|
8
|
+
module SlackInteractiveClient
|
9
|
+
class BaseInteraction
|
10
|
+
include ::SlackInteractionParams
|
11
|
+
include ::SlackPatternInteraction
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_reader :interaction_config, :interaction_channels, :pattern_string
|
15
|
+
|
16
|
+
# EXAMPLE
|
17
|
+
#
|
18
|
+
# interaction_options can_interact: { only: ['micah.bowie'] }, channels: []
|
19
|
+
def interaction_options(options = {})
|
20
|
+
set_interaction_config(options[:can_interact])
|
21
|
+
@interaction_channels = options[:channels].empty? ? :all : options[:channels]
|
22
|
+
end
|
23
|
+
|
24
|
+
def interaction_pattern(pattern_string = '')
|
25
|
+
@pattern_string = pattern_string.strip.squeeze
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_interaction_config(options)
|
31
|
+
@interaction_config ||= { all: [], only: [], except: [] }
|
32
|
+
return unless options.present?
|
33
|
+
|
34
|
+
if options == :all
|
35
|
+
@interaction_config[:all] << :all
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:only] && options.is_a?(Hash)
|
40
|
+
@interaction_config[:only] += Array(options[:only])
|
41
|
+
end
|
42
|
+
|
43
|
+
if options[:except] && options.is_a?(Hash)
|
44
|
+
@interaction_config[:except] += Array(options[:except])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader :interaction_params
|
50
|
+
def initialize(webhook_params = {})
|
51
|
+
@interaction_params = webhook_params
|
52
|
+
@pattern = self.class.pattern_string
|
53
|
+
@extracted_values = {}
|
54
|
+
extract_values(slack_message_text)
|
55
|
+
end
|
56
|
+
|
57
|
+
def call
|
58
|
+
unless can_interact?
|
59
|
+
send_message('You are not authorized to interact with this command or interaction is in an invalid channel.')
|
60
|
+
return
|
61
|
+
end
|
62
|
+
execute
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute
|
66
|
+
puts "Method not reimplemented by child"
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def direct_message?
|
72
|
+
slack_channel_name == ::SlackInteractionParams::DIRECT_MESSAGE_CHANNEL
|
73
|
+
end
|
74
|
+
|
75
|
+
def can_user_interact?
|
76
|
+
# If not configuration is set then we allow any user to interact
|
77
|
+
return true if self.class.interaction_config.nil?
|
78
|
+
|
79
|
+
all_conditions = self.class.interaction_config[:all]
|
80
|
+
only_conditions = self.class.interaction_config[:only]
|
81
|
+
except_conditions = self.class.interaction_config[:except]
|
82
|
+
|
83
|
+
return true if all_conditions.empty? && only_conditions.empty? && except_conditions.empty?
|
84
|
+
return true if all_conditions.include?(:all)
|
85
|
+
return true if only_conditions.include?(slack_user_name)
|
86
|
+
return false if except_conditions.include?(slack_user_name)
|
87
|
+
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
def interactive_channel?
|
92
|
+
# If not configuration is set then we allow any user to interact
|
93
|
+
return true if self.class.interaction_channels.nil?
|
94
|
+
|
95
|
+
interaction_channels = self.class.interaction_channels
|
96
|
+
|
97
|
+
return true if interaction_channels == :all
|
98
|
+
return true if interaction_channels == :direct_message && direct_message?
|
99
|
+
return false if interaction_channels == :direct_message && !direct_message?
|
100
|
+
return false unless interaction_channels.is_a?(Array)
|
101
|
+
return true if direct_message? && interaction_channels.include?(:direct_message)
|
102
|
+
return true if interaction_channels.include?(slack_channel_name)
|
103
|
+
|
104
|
+
false
|
105
|
+
end
|
106
|
+
|
107
|
+
def can_interact?
|
108
|
+
can_user_interact? && interactive_channel?
|
109
|
+
end
|
110
|
+
|
111
|
+
def send_message(message)
|
112
|
+
::SlackInteractiveClient::Client.send_message(message, slack_channel_id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def send_csv(csv_data)
|
116
|
+
csv_content ||= CSV.generate do |csv|
|
117
|
+
csv << csv_data.dig(:headers)
|
118
|
+
csv_data.dig(:rows).each { |row| csv << row }
|
119
|
+
end
|
120
|
+
csv_data[:content] = csv_content
|
121
|
+
|
122
|
+
::SlackInteractiveClient::Client.send_csv(csv_data, slack_channel_id)
|
123
|
+
end
|
124
|
+
|
125
|
+
def send_message_with_template(template, args)
|
126
|
+
::SlackInteractiveClient::Client.send_message_with_template(template, args.merge(channel: slack_channel_id))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# EXAMPLE
|
4
|
+
#
|
5
|
+
# SlackInteractiveClient::Bot.define do
|
6
|
+
# interaction :hello_world, HelloWorldInteraction
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
require 'active_support'
|
10
|
+
|
11
|
+
module SlackInteractiveClient
|
12
|
+
class Bot
|
13
|
+
class << self
|
14
|
+
attr_reader :interactions
|
15
|
+
|
16
|
+
def define(&block)
|
17
|
+
@interactions ||= HashWithIndifferentAccess.new
|
18
|
+
instance_exec(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def interaction(name, interaction_class)
|
22
|
+
@interactions[name] = interaction_class
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'slack-ruby-client'
|
4
|
+
require_relative 'configuration'
|
5
|
+
require_relative 'message'
|
6
|
+
require_relative 'message_stop_guard'
|
7
|
+
|
8
|
+
module SlackInteractiveClient
|
9
|
+
class Client
|
10
|
+
class << self
|
11
|
+
|
12
|
+
attr_writer :configuration
|
13
|
+
|
14
|
+
def configuration
|
15
|
+
@configuration ||= ::SlackInteractiveClient::Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def reset
|
19
|
+
@configuration = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield configuration
|
24
|
+
::Slack.configure do |config|
|
25
|
+
config.token = configuration.auth_token
|
26
|
+
config.logger = configuration.logger_instance
|
27
|
+
end
|
28
|
+
"SlackInteractiveClient::Client Configured!"
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_message_with_template(template_name, args = {})
|
32
|
+
compilation = ::SlackInteractiveClient::Message.compile(template_name, args)
|
33
|
+
|
34
|
+
web_client.chat_postMessage(
|
35
|
+
channel: compilation.channel,
|
36
|
+
blocks: compilation.blocks,
|
37
|
+
as_user: true,
|
38
|
+
)
|
39
|
+
|
40
|
+
if compilation.csv? && compilation.csv_data[:csv][:content]
|
41
|
+
web_client.files_upload(
|
42
|
+
channels: compilation.channel,
|
43
|
+
content: compilation.csv_data[:csv][:content],
|
44
|
+
title: compilation.csv_data[:csv][:title],
|
45
|
+
filename: compilation.csv_data[:csv][:filename],
|
46
|
+
initial_comment: compilation.csv_data[:csv][:comment],
|
47
|
+
as_user: true,
|
48
|
+
)
|
49
|
+
end
|
50
|
+
return "Message sent by: SlackInteractiveClient::Client"
|
51
|
+
rescue ::Slack::Web::Api::Errors::SlackError => e
|
52
|
+
puts "An error occurred when sending the message: #{e.message}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_message(message, channel)
|
56
|
+
scrubbed = ::SlackInteractiveClient::MessageStopGuard.scrubber.call(message)
|
57
|
+
web_client.chat_postMessage(
|
58
|
+
channel: channel,
|
59
|
+
blocks: [{ type: 'rich_text', elements: [ type: 'rich_text_section', elements: [ { type: 'text', text: scrubbed } ] ] }],
|
60
|
+
as_user: true,
|
61
|
+
)
|
62
|
+
return "Message sent by: SlackInteractiveClient::Client"
|
63
|
+
rescue ::Slack::Web::Api::Errors::SlackError => e
|
64
|
+
puts "An error occurred when sending the message: #{e.message}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def send_csv(csv_data, channel)
|
68
|
+
web_client.files_upload(
|
69
|
+
channels: channel,
|
70
|
+
content: csv_data[:content],
|
71
|
+
title: csv_data[:title],
|
72
|
+
filename: csv_data[:filename],
|
73
|
+
initial_comment: csv_data[:comment],
|
74
|
+
as_user: true,
|
75
|
+
)
|
76
|
+
return "Message sent by: SlackInteractiveClient::Client"
|
77
|
+
rescue ::Slack::Web::Api::Errors::SlackError => e
|
78
|
+
puts "An error occurred when sending the message: #{e.message}"
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def web_client
|
84
|
+
::Slack::Web::Client.new
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module SlackInteractionParams
|
6
|
+
DIRECT_MESSAGE_CHANNEL = "directmessage".freeze
|
7
|
+
|
8
|
+
def slack_command
|
9
|
+
interaction_params[:command]
|
10
|
+
end
|
11
|
+
|
12
|
+
def slack_user_name
|
13
|
+
interaction_params[:user_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def slack_token
|
17
|
+
interaction_params[:token]
|
18
|
+
end
|
19
|
+
|
20
|
+
def slack_team_id
|
21
|
+
interaction_params[:team_id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def slack_channel_id
|
25
|
+
interaction_params[:channel_id]
|
26
|
+
end
|
27
|
+
|
28
|
+
def slack_message_text
|
29
|
+
return nil unless interaction_params[:text].present? && interaction_params[:text].is_a?(String)
|
30
|
+
|
31
|
+
interaction_params[:text].strip.squeeze
|
32
|
+
end
|
33
|
+
|
34
|
+
def slack_user_id
|
35
|
+
interaction_params[:user_id]
|
36
|
+
end
|
37
|
+
|
38
|
+
def slack_response_url
|
39
|
+
interaction_params[:response_url]
|
40
|
+
end
|
41
|
+
|
42
|
+
def slack_channel_name
|
43
|
+
interaction_params[:channel_name]
|
44
|
+
end
|
45
|
+
|
46
|
+
def slack_team_domain
|
47
|
+
interaction_params[:team_domain]
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module SlackPatternInteraction
|
5
|
+
def method_missing(method_name, *arguments, &block)
|
6
|
+
if @extracted_values.has_key?(method_name.to_s)
|
7
|
+
@extracted_values[method_name.to_s]
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(method_name, include_private = false)
|
14
|
+
@extracted_values.has_key?(method_name.to_s) || super
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def extract_values(string)
|
20
|
+
return {} if string.blank?
|
21
|
+
# Find all dynamic parts of the pattern
|
22
|
+
dynamic_parts = @pattern.scan(/\$\[(.*?)\]/).flatten
|
23
|
+
|
24
|
+
# Create a regex pattern replacing dynamic parts with capture groups
|
25
|
+
regex_pattern = @pattern.gsub(/\$\[.*?\]/, '(\w+)')
|
26
|
+
regex = Regexp.new(regex_pattern)
|
27
|
+
|
28
|
+
# Match the string with the regex
|
29
|
+
match = string.match(regex)
|
30
|
+
|
31
|
+
return unless match
|
32
|
+
|
33
|
+
# Iterate over each capture and store in the hash
|
34
|
+
dynamic_parts.each_with_index do |variable_name, index|
|
35
|
+
@extracted_values[variable_name] = match[index + 1]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackInteractiveClient
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :auth_token, :redis_instance, :logger_instance, :base_controller_class, :signing_secret
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@base_controller_class = "ApplicationController"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../base_interaction"
|
4
|
+
|
5
|
+
module SlackInteractiveClient
|
6
|
+
class UnknownCommandInteraction < BaseInteraction
|
7
|
+
interaction_options can_interact: :all, channels: :all
|
8
|
+
|
9
|
+
def execute
|
10
|
+
response = "Hmmm. I don't know that command. If you think I should double check your slack configuration and your interaction definitions"
|
11
|
+
send_message(response)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackInteractiveClient
|
4
|
+
class Message
|
5
|
+
class Compilation
|
6
|
+
attr_reader :payload, :csv_message, :csv_data
|
7
|
+
def initialize(payload, csv_message = false, csv_data = {})
|
8
|
+
@payload = payload
|
9
|
+
@csv_message = csv_message
|
10
|
+
@csv_data = csv_data
|
11
|
+
end
|
12
|
+
|
13
|
+
def blocks
|
14
|
+
payload[:blocks]&.to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def channel
|
18
|
+
if payload[:channel].is_a?(Array)
|
19
|
+
payload[:channel].first
|
20
|
+
elsif payload[:channel].is_a?(String)
|
21
|
+
payload[:channel]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias csv? csv_message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require 'slack-ruby-client'
|
5
|
+
require_relative './message/compilation'
|
6
|
+
require_relative 'message_stop_guard'
|
7
|
+
|
8
|
+
module SlackInteractiveClient
|
9
|
+
class Message
|
10
|
+
class << self
|
11
|
+
def define(&block)
|
12
|
+
@templates ||= {}
|
13
|
+
instance_exec(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile(template_name, args = {})
|
17
|
+
template = @templates.fetch(template_name) do
|
18
|
+
raise ArgumentError, "Template #{template_name} not defined"
|
19
|
+
end
|
20
|
+
template.reset_message_blocks
|
21
|
+
template.compile(args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def template(name, &block)
|
25
|
+
instance = new
|
26
|
+
instance.instance_eval(&block)
|
27
|
+
@templates[name] = instance
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :name, :blocks, :environment, :channels, :tagged_users
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@blocks = []
|
35
|
+
@environment = nil
|
36
|
+
@channels = []
|
37
|
+
@tagged_users = []
|
38
|
+
@csv_data_block = []
|
39
|
+
@list_items = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def template(name, &block)
|
43
|
+
@name = name
|
44
|
+
instance_eval(&block) if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
def text(text = nil , &block)
|
48
|
+
@text_block = text.nil? ? block : Proc.new {|x| text }
|
49
|
+
end
|
50
|
+
|
51
|
+
def title(title = nil , &block)
|
52
|
+
@title_block = title.nil? ? block : Proc.new {|x| title }
|
53
|
+
end
|
54
|
+
|
55
|
+
def markdown_section(markdown = nil, &block)
|
56
|
+
@markdown_block = markdown.nil? ? block : Proc.new {|x| markdown }
|
57
|
+
end
|
58
|
+
|
59
|
+
def list_section(&block)
|
60
|
+
@list_block = block
|
61
|
+
end
|
62
|
+
|
63
|
+
def list_item(item)
|
64
|
+
@list_items << item
|
65
|
+
end
|
66
|
+
|
67
|
+
def environment(env)
|
68
|
+
@environment = env
|
69
|
+
end
|
70
|
+
|
71
|
+
def channel(channel)
|
72
|
+
@channels << channel
|
73
|
+
end
|
74
|
+
|
75
|
+
def mention(*users)
|
76
|
+
# Format the user mention strings
|
77
|
+
@tagged_users = users.flatten
|
78
|
+
@user_mentions = users.flatten.join(' ')
|
79
|
+
end
|
80
|
+
|
81
|
+
def csv_data(bool = false)
|
82
|
+
@include_csv_data = bool
|
83
|
+
end
|
84
|
+
|
85
|
+
def compile(args = {})
|
86
|
+
construct_environment_block if @environment
|
87
|
+
construct_title_block(args) if @title_block
|
88
|
+
construct_text_block(args) if @text_block
|
89
|
+
construct_markdown_block(args) if @markdown_block
|
90
|
+
construct_list_block(args) if @list_block
|
91
|
+
construct_csv(args) if @include_csv_data
|
92
|
+
add_user_mentions if @tagged_users.any?
|
93
|
+
|
94
|
+
message_payload = {
|
95
|
+
blocks: @blocks,
|
96
|
+
channel: args[:channel] || @channels,
|
97
|
+
}
|
98
|
+
|
99
|
+
::SlackInteractiveClient::Message::Compilation.new(
|
100
|
+
message_payload,
|
101
|
+
@include_csv_data,
|
102
|
+
@csv_data
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def reset_message_blocks
|
107
|
+
@blocks = []
|
108
|
+
@list_items = []
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def construct_environment_block
|
114
|
+
env = @environment.upcase
|
115
|
+
scrubbed = ::SlackInteractiveClient::MessageStopGuard.scrubber.call(env)
|
116
|
+
add_block(type: 'section', text: { type: 'mrkdwn', text: "Environment: [#{scrubbed}] \n" })
|
117
|
+
end
|
118
|
+
|
119
|
+
def construct_title_block(args)
|
120
|
+
text_content = @title_block.call(args)
|
121
|
+
scrubbed = ::SlackInteractiveClient::MessageStopGuard.scrubber.call(text_content)
|
122
|
+
add_block(type: 'header', text: { type: 'plain_text', text: scrubbed })
|
123
|
+
end
|
124
|
+
|
125
|
+
def construct_text_block(args)
|
126
|
+
text_content = @text_block.call(args)
|
127
|
+
scrubbed = ::SlackInteractiveClient::MessageStopGuard.scrubber.call(text_content)
|
128
|
+
add_block(type: 'rich_text', elements: [ type: 'rich_text_section', elements: [ { type: 'text', text: scrubbed } ] ])
|
129
|
+
end
|
130
|
+
|
131
|
+
def construct_markdown_block(args)
|
132
|
+
markdown_content = @markdown_block.call(args)
|
133
|
+
scrubbed = MessageStopGuard.scrubber.call(markdown_content)
|
134
|
+
add_block(type: 'section', text: { type: 'mrkdwn', text: "```#{scrubbed}```" })
|
135
|
+
end
|
136
|
+
|
137
|
+
def construct_list_block(args)
|
138
|
+
@list_block.call(args)
|
139
|
+
list_elements_content = construct_list_items_block
|
140
|
+
add_block(type: 'rich_text', elements: [{ type: "rich_text_list", style: "bullet", elements: list_elements_content }])
|
141
|
+
end
|
142
|
+
|
143
|
+
def construct_list_items_block
|
144
|
+
@list_items.map do |list_item|
|
145
|
+
scrubbed = ::SlackInteractiveClient::MessageStopGuard.scrubber.call(list_item)
|
146
|
+
{
|
147
|
+
"type": "rich_text_section",
|
148
|
+
"elements": [
|
149
|
+
{
|
150
|
+
"type": "text",
|
151
|
+
"text": scrubbed
|
152
|
+
},
|
153
|
+
]
|
154
|
+
}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_user_mentions
|
159
|
+
unless @tagged_users.empty?
|
160
|
+
# Format a string that includes all user mentions with Slack mention syntax
|
161
|
+
user_mentions = @tagged_users.map { |user| "<#{user}>" }.join(' ')
|
162
|
+
|
163
|
+
# Create a context block with all user mentions in one line
|
164
|
+
add_block({
|
165
|
+
type: 'context',
|
166
|
+
elements: [{ type: 'mrkdwn', text: "CC: #{user_mentions}" }]
|
167
|
+
})
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def construct_csv(args)
|
172
|
+
return unless @include_csv_data
|
173
|
+
|
174
|
+
data = args.slice(:csv)
|
175
|
+
@csv_content ||= CSV.generate do |csv|
|
176
|
+
csv << args.dig(:csv, :headers)
|
177
|
+
args.dig(:csv, :rows).each { |row| csv << row }
|
178
|
+
end
|
179
|
+
data[:csv][:content] = @csv_content
|
180
|
+
@csv_data = data
|
181
|
+
end
|
182
|
+
|
183
|
+
def add_block(block)
|
184
|
+
@blocks << block
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlackInteractiveClient
|
4
|
+
class MessageStopGuard
|
5
|
+
FILTERED_SUBSTITUTION = '[FILTERED]'
|
6
|
+
|
7
|
+
SENSITIVE_KEY_SUFFIXES = %w[
|
8
|
+
number numbers ssn email date_of_birth secret token salt identity identity_fingerprint
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
# Builds a list of regex patterns for sensitive values
|
12
|
+
def self.build_sensitive_value_patterns
|
13
|
+
SENSITIVE_KEY_SUFFIXES.map do |key_suffix|
|
14
|
+
/(?<key>#{key_suffix}[:\\'"=>\s]*)(?<value>[\w-]+)/i
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a lambda that scrubs sensitive information from a message
|
19
|
+
def self.scrubber
|
20
|
+
pii_patterns = build_sensitive_value_patterns
|
21
|
+
|
22
|
+
lambda do |raw_msg|
|
23
|
+
pii_patterns.reduce(raw_msg) do |msg, pii_regex|
|
24
|
+
msg.gsub(pii_regex) { |m| $~[:key] + FILTERED_SUBSTITUTION }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
|
5
|
+
module SlackInteractiveClient
|
6
|
+
class ResponseClient
|
7
|
+
class << self
|
8
|
+
# { "response_type": "in_channel", "response_type": "ephemeral" }
|
9
|
+
# { "text": "text" }
|
10
|
+
# { "blocks": [ ] }
|
11
|
+
# { "replace_original": false" },
|
12
|
+
def respond(response_url, body)
|
13
|
+
::HTTParty.post(response_url,
|
14
|
+
body: body.to_json,
|
15
|
+
headers: { 'Content-Type' => 'application/json' }
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/all'
|
5
|
+
|
6
|
+
require_relative "slack_interactive_client/configuration"
|
7
|
+
require_relative "slack_interactive_client/message_stop_guard"
|
8
|
+
require_relative "slack_interactive_client/client"
|
9
|
+
require_relative "slack_interactive_client/message/compilation"
|
10
|
+
require_relative "slack_interactive_client/message"
|
11
|
+
require_relative "slack_interactive_client/bot"
|
12
|
+
require_relative "slack_interactive_client/railtie"
|
13
|
+
require_relative "slack_interactive_client/response_client"
|
14
|
+
require_relative "slack_interactive_client/interactions/unknown_command_interaction"
|
15
|
+
require_relative "slack_interactive_client/base_interaction"
|
16
|
+
require_relative "slack_interactive_client/engine"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SlackInteractiveClient::BaseInteraction do
|
4
|
+
class TestInteraction < ::SlackInteractiveClient::BaseInteraction
|
5
|
+
interaction_pattern "to $[name] the state $[state_name]"
|
6
|
+
|
7
|
+
def execute
|
8
|
+
return "hey #{name} welcome to #{state_name}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
let(:interaction_params) { { text: "to micah the state TEXAS"} }
|
12
|
+
let(:interaction_class) { TestInteraction.new(interaction_params) }
|
13
|
+
|
14
|
+
describe '#call' do
|
15
|
+
it 'stores the interactions' do
|
16
|
+
expect(interaction_class.call).to eq("hey micah welcome to TEXAS")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/bot_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SlackInteractiveClient::Bot do
|
4
|
+
let(:message) { described_class.new }
|
5
|
+
|
6
|
+
class HelloWorldInteraction; end
|
7
|
+
|
8
|
+
describe '#define' do
|
9
|
+
before do
|
10
|
+
described_class.define do
|
11
|
+
interaction :hello_world, HelloWorldInteraction
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'stores the interactions' do
|
16
|
+
expect(described_class.interactions.size).to eq(1)
|
17
|
+
expect(described_class.interactions[:hello_world]).to eq(HelloWorldInteraction)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe SlackInteractiveClient::Client do
|
4
|
+
after do
|
5
|
+
described_class.reset
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#configuration' do
|
9
|
+
it 'creates a new instance of Configuration' do
|
10
|
+
expect(described_class.configuration).to be_instance_of(SlackInteractiveClient::Configuration)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#reset' do
|
15
|
+
it 'creates and assigns a new instance of Configuration' do
|
16
|
+
configuration = described_class.configuration
|
17
|
+
described_class.reset
|
18
|
+
expect(described_class.configuration).not_to eq(configuration)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#configure' do
|
23
|
+
let(:configuration_params) do
|
24
|
+
{ auth_token: "a-token", logger_instance: 'Rails logger' }
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'creates a new instance of Configuration from block' do
|
28
|
+
described_class.configure do |config|
|
29
|
+
config.auth_token = configuration_params[:auth_token]
|
30
|
+
config.logger_instance = configuration_params[:logger_instance]
|
31
|
+
end
|
32
|
+
|
33
|
+
expect(described_class.configuration.auth_token).to eq(configuration_params[:auth_token])
|
34
|
+
expect(described_class.configuration.logger_instance).to eq(configuration_params[:logger_instance])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#version' do
|
39
|
+
it 'has a version number' do
|
40
|
+
expect(SlackInteractiveClient::VERSION).not_to be nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#send_message' do
|
45
|
+
let(:template_name) { :test_template }
|
46
|
+
let(:args) { { some: 'argument' } }
|
47
|
+
let(:compilation) do
|
48
|
+
instance_double("SlackInteractiveClient::Message::Compilation",
|
49
|
+
channel: 'C1234567890',
|
50
|
+
blocks: [],
|
51
|
+
csv?: false)
|
52
|
+
end
|
53
|
+
|
54
|
+
before do
|
55
|
+
allow(SlackInteractiveClient::Message).to receive(:compile).and_return(compilation)
|
56
|
+
allow_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage)
|
57
|
+
allow_any_instance_of(Slack::Web::Client).to receive(:files_upload)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'compiles the message and sends it to Slack' do
|
61
|
+
expect(SlackInteractiveClient::Message).to receive(:compile).with(template_name, args)
|
62
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with(hash_including(:channel, :blocks, :as_user))
|
63
|
+
described_class.send_message(template_name, args)
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when the compilation includes csv data' do
|
67
|
+
let(:csv_data) do
|
68
|
+
{
|
69
|
+
csv: {
|
70
|
+
content: 'id,name\n1,Test',
|
71
|
+
title: 'Test CSV',
|
72
|
+
filename: 'test.csv',
|
73
|
+
comment: 'Here is your CSV!'
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
let(:compilation_with_csv) do
|
79
|
+
instance_double("SlackInteractiveClient::Message::Compilation",
|
80
|
+
channel: 'C1234567890',
|
81
|
+
blocks: [],
|
82
|
+
csv?: true,
|
83
|
+
csv_data: csv_data)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'uploads a CSV file along with the message' do
|
87
|
+
allow(SlackInteractiveClient::Message).to receive(:compile).and_return(compilation_with_csv)
|
88
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:files_upload).with(hash_including(:channels, :content, :title, :filename, :initial_comment, :as_user))
|
89
|
+
described_class.send_message(template_name, args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when Slack::Web::Api::Errors::SlackError is raised' do
|
94
|
+
it 'rescues from Slack::Web::Api::Errors::SlackError' do
|
95
|
+
allow(SlackInteractiveClient::Message).to receive(:compile).and_raise(Slack::Web::Api::Errors::SlackError.new('Error'))
|
96
|
+
expect { described_class.send_message(template_name, args) }.to_not raise_error
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe SlackInteractiveClient::Message::Compilation do
|
6
|
+
let(:payload) { { blocks: [{ type: 'section', text: { type: 'mrkdwn', text: 'Test message' } }], channel: ['#general'] } }
|
7
|
+
let(:csv_message) { true }
|
8
|
+
let(:csv_data) { { header: ['Column1', 'Column2'], rows: [['Data1', 'Data2'], ['Data3', 'Data4']] } }
|
9
|
+
|
10
|
+
subject(:compilation) { described_class.new(payload, csv_message, csv_data) }
|
11
|
+
|
12
|
+
describe '#initialize' do
|
13
|
+
it 'initializes with payload, csv_message, and csv_data' do
|
14
|
+
expect(compilation.payload).to eq(payload)
|
15
|
+
expect(compilation.csv_message).to be(csv_message)
|
16
|
+
expect(compilation.csv_data).to eq(csv_data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#blocks' do
|
21
|
+
context 'when blocks are present in payload' do
|
22
|
+
it 'returns a JSON representation of blocks' do
|
23
|
+
expect(compilation.blocks).to eq(payload[:blocks].to_json)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when blocks are absent in payload' do
|
28
|
+
let(:payload) { {} }
|
29
|
+
|
30
|
+
it 'returns nil' do
|
31
|
+
expect(compilation.blocks).to be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#channel' do
|
37
|
+
it 'returns the first channel from the payload' do
|
38
|
+
expect(compilation.channel).to eq(payload[:channel].first)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#csv?' do
|
43
|
+
context 'when csv_message is true' do
|
44
|
+
it 'returns true' do
|
45
|
+
expect(compilation.csv?).to be_truthy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when csv_message is false' do
|
50
|
+
let(:csv_message) { false }
|
51
|
+
|
52
|
+
it 'returns false' do
|
53
|
+
expect(compilation.csv?).to be_falsey
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SlackInteractiveClient::Message do
|
4
|
+
let(:message) { described_class.new }
|
5
|
+
|
6
|
+
describe '#define' do
|
7
|
+
it 'defines a new template with the given block' do
|
8
|
+
block_called = false
|
9
|
+
described_class.define { block_called = true }
|
10
|
+
expect(block_called).to be(true)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#compile' do
|
15
|
+
before do
|
16
|
+
described_class.define do
|
17
|
+
template :test_template do
|
18
|
+
text 'Test message'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'compiles the given template with arguments' do
|
24
|
+
expect { described_class.compile(:test_template) }.not_to raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'raises an error for an undefined template' do
|
28
|
+
expect { described_class.compile(:undefined_template) }.to raise_error(ArgumentError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#template' do
|
33
|
+
it 'creates a new template with the given name and block' do
|
34
|
+
block_called = false
|
35
|
+
described_class.template(:new_template) { block_called = true }
|
36
|
+
expect(block_called).to be(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#template' do
|
41
|
+
it 'assigns a name and evaluates the block' do
|
42
|
+
message.template(:test) { text 'Testing' }
|
43
|
+
expect(message.name).to eq(:test)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#text' do
|
48
|
+
it 'sets a text block' do
|
49
|
+
message.text 'Hello world'
|
50
|
+
compiled_message = message.compile
|
51
|
+
|
52
|
+
expect(compiled_message.blocks).to include('Hello world')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#title' do
|
57
|
+
it 'sets a title block' do
|
58
|
+
message.title 'Title'
|
59
|
+
compiled_message = message.compile
|
60
|
+
|
61
|
+
expect(compiled_message.blocks).to include('*Title*')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#markdown_section' do
|
66
|
+
it 'sets a markdown section block' do
|
67
|
+
message.markdown_section 'Some *markdown* content'
|
68
|
+
compiled_message = message.compile
|
69
|
+
|
70
|
+
expect(compiled_message.blocks).to include('Some *markdown* content')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#environment' do
|
75
|
+
it 'sets the environment' do
|
76
|
+
message.environment('development')
|
77
|
+
|
78
|
+
expect(message.instance_variable_get(:@environment)).to eq('development')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#channel' do
|
83
|
+
it 'adds channels to the message' do
|
84
|
+
message.channel('#general')
|
85
|
+
message.channel('#random')
|
86
|
+
|
87
|
+
expect(message.channels).to contain_exactly('#general', '#random')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#mention' do
|
92
|
+
it 'formats user mentions' do
|
93
|
+
message.mention('user1', 'user2')
|
94
|
+
compiled_message = message.compile
|
95
|
+
|
96
|
+
expect(compiled_message.payload[:blocks][0][:elements][0][:text]).to include('<user1> <user2>')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#csv_data' do
|
101
|
+
it 'includes csv data when set to true' do
|
102
|
+
message.csv_data(true)
|
103
|
+
compiled_message = message.compile(csv: { headers: ['one', 'two'], rows: [['1', '2'], ['3', '4']] })
|
104
|
+
|
105
|
+
expect(compiled_message.csv_data).to include(:csv)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#compile' do
|
110
|
+
it 'compiles the message with blocks, channels, and tagged users' do
|
111
|
+
message.text 'Hello world'
|
112
|
+
message.channel '#general'
|
113
|
+
message.mention 'user1'
|
114
|
+
|
115
|
+
compiled_message = message.compile
|
116
|
+
|
117
|
+
expect(compiled_message).to be_a(SlackInteractiveClient::Message::Compilation)
|
118
|
+
expect(compiled_message.channel).to eq('#general')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#reset_message_blocks' do
|
123
|
+
it 'clears all message blocks' do
|
124
|
+
message.text 'Hello world'
|
125
|
+
message.compile
|
126
|
+
|
127
|
+
expect(message.blocks).not_to be_empty
|
128
|
+
|
129
|
+
message.reset_message_blocks
|
130
|
+
|
131
|
+
expect(message.blocks).to be_empty
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#compile' do
|
136
|
+
subject(:message) { described_class.new }
|
137
|
+
|
138
|
+
let(:args) do
|
139
|
+
{
|
140
|
+
text: 'Hello, World!',
|
141
|
+
title: 'Test Message',
|
142
|
+
markdown: 'Some *markdown* content',
|
143
|
+
channel: '#general',
|
144
|
+
users: ['@user1', '@user2'],
|
145
|
+
csv: {
|
146
|
+
headers: ['header1', 'header2'],
|
147
|
+
rows: [['row1data1', 'row1data2'], ['row2data1', 'row2data2']],
|
148
|
+
}
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
before do
|
153
|
+
message.environment('test')
|
154
|
+
message.channel(args[:channel])
|
155
|
+
message.mention(args[:users])
|
156
|
+
message.text(args[:text])
|
157
|
+
message.title(args[:title])
|
158
|
+
message.markdown_section(args[:markdown])
|
159
|
+
message.csv_data(true)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'compiles the message payload with given arguments' do
|
163
|
+
compilation = message.compile(args)
|
164
|
+
|
165
|
+
expect(compilation).to be_an_instance_of(SlackInteractiveClient::Message::Compilation)
|
166
|
+
expect(compilation.blocks).to include('Hello, World!')
|
167
|
+
expect(compilation.blocks).to include('Test Message')
|
168
|
+
expect(compilation.blocks).to include('Some *markdown* content')
|
169
|
+
expect(compilation.csv_data).to match(hash_including(:csv))
|
170
|
+
expect(compilation.channel).to eq(args[:channel])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SlackInteractiveClient::MessageStopGuard do
|
4
|
+
let(:interaction_params) { { text: "to micah the state TEXAS"} }
|
5
|
+
let(:interaction_class) { TestInteraction.new(interaction_params) }
|
6
|
+
|
7
|
+
describe '#scrubber' do
|
8
|
+
it 'filters out the value' do
|
9
|
+
scrubbed = described_class.scrubber.call(%(ssn: 1234))
|
10
|
+
|
11
|
+
expect(scrubbed).to include("[FILTERED]")
|
12
|
+
expect(scrubbed).not_to include("1234")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "byebug"
|
3
|
+
require 'rails'
|
4
|
+
|
5
|
+
require "slack_interactive_client"
|
6
|
+
|
7
|
+
ENV['RAILS_ENV'] ||= 'test'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
11
|
+
config.disable_monkey_patching!
|
12
|
+
|
13
|
+
config.expect_with :rspec do |c|
|
14
|
+
c.syntax = :expect
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slack_interactive_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Micah Bowie
|
8
|
+
- Code Cowboy
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2023-12-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: slack-ruby-client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rails
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: httparty
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec-rails
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
description: Interact with your Rails app through Slack by defining dynamic Slack
|
85
|
+
slash commands, using a reusable message DSL, slack client, and more.
|
86
|
+
email:
|
87
|
+
- micahbowie20@gmail.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- lib/slack_interactive_client.rb
|
93
|
+
- lib/slack_interactive_client/base_interaction.rb
|
94
|
+
- lib/slack_interactive_client/bot.rb
|
95
|
+
- lib/slack_interactive_client/client.rb
|
96
|
+
- lib/slack_interactive_client/concerns/slack_interaction_params.rb
|
97
|
+
- lib/slack_interactive_client/concerns/slack_pattern_interaction.rb
|
98
|
+
- lib/slack_interactive_client/configuration.rb
|
99
|
+
- lib/slack_interactive_client/engine.rb
|
100
|
+
- lib/slack_interactive_client/interactions/unknown_command_interaction.rb
|
101
|
+
- lib/slack_interactive_client/message.rb
|
102
|
+
- lib/slack_interactive_client/message/compilation.rb
|
103
|
+
- lib/slack_interactive_client/message_stop_guard.rb
|
104
|
+
- lib/slack_interactive_client/railtie.rb
|
105
|
+
- lib/slack_interactive_client/response_client.rb
|
106
|
+
- lib/slack_interactive_client/version.rb
|
107
|
+
- spec/base_interaction_spec.rb
|
108
|
+
- spec/bot_spec.rb
|
109
|
+
- spec/client_spec.rb
|
110
|
+
- spec/message/compilation_spec.rb
|
111
|
+
- spec/message_spec.rb
|
112
|
+
- spec/message_stop_guard_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
114
|
+
homepage:
|
115
|
+
licenses: []
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 2.7.0
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubygems_version: 3.0.3.1
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: Interact with your Rails app through Slack by defining dynamic Slack slash
|
136
|
+
commands, using a reusable message DSL, slack client, and more.
|
137
|
+
test_files: []
|