grape-slack-bot 1.8.2 → 2.0.1
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 +4 -4
- data/CHANGELOG.md +49 -23
- data/README.md +153 -121
- data/grape-slack-bot.gemspec +18 -10
- data/lib/slack_bot/api_client.rb +161 -60
- data/lib/slack_bot/args.rb +2 -0
- data/lib/slack_bot/callback.rb +29 -4
- data/lib/slack_bot/callback_storage.rb +3 -3
- data/lib/slack_bot/config.rb +4 -0
- data/lib/slack_bot/errors.rb +20 -0
- data/lib/slack_bot/grape_extension.rb +138 -77
- data/lib/slack_bot/interaction.rb +38 -44
- data/lib/slack_bot/logger.rb +15 -0
- data/lib/slack_bot/menu_options.rb +4 -0
- data/lib/slack_bot/view.rb +6 -2
- data/lib/slack_bot.rb +4 -1
- data/sig/slack_bot.rbs +379 -0
- metadata +159 -37
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
require "active_support"
|
|
2
2
|
require "active_support/core_ext/object"
|
|
3
|
+
require "active_support/security_utils"
|
|
3
4
|
|
|
4
5
|
module SlackBot
|
|
5
6
|
module GrapeHelpers
|
|
7
|
+
# Slack recommends rejecting requests older than 5 minutes
|
|
8
|
+
TIMESTAMP_TOLERANCE_SECONDS = 300
|
|
9
|
+
# Minimum length for Slack signing secret (Slack's requirement)
|
|
10
|
+
MIN_SIGNING_SECRET_LENGTH = 32
|
|
11
|
+
|
|
6
12
|
def fetch_team_id
|
|
7
13
|
params.dig("team_id") || params.dig("team", "id")
|
|
8
14
|
end
|
|
@@ -13,32 +19,12 @@ module SlackBot
|
|
|
13
19
|
|
|
14
20
|
def verify_slack_signature!
|
|
15
21
|
slack_signing_secret = ENV["SLACK_SIGNING_SECRET"]
|
|
16
|
-
timestamp =
|
|
17
|
-
slack_signature =
|
|
18
|
-
if slack_signing_secret.blank? || timestamp.blank? || slack_signature.blank?
|
|
19
|
-
raise SlackBot::Errors::SignatureAuthenticationError.new("Missing signature headers")
|
|
20
|
-
end
|
|
22
|
+
timestamp = slack_request_header("x-slack-request-timestamp", "X-Slack-Request-Timestamp")
|
|
23
|
+
slack_signature = slack_request_header("x-slack-signature", "X-Slack-Signature")
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
sig_basestring = "v0:#{timestamp}:#{request_body}"
|
|
27
|
-
my_signature =
|
|
28
|
-
"v0=" +
|
|
29
|
-
OpenSSL::HMAC.hexdigest(
|
|
30
|
-
OpenSSL::Digest.new("sha256"),
|
|
31
|
-
slack_signing_secret,
|
|
32
|
-
sig_basestring
|
|
33
|
-
)
|
|
34
|
-
if ActiveSupport::SecurityUtils.secure_compare(
|
|
35
|
-
my_signature,
|
|
36
|
-
slack_signature
|
|
37
|
-
)
|
|
38
|
-
true
|
|
39
|
-
else
|
|
40
|
-
raise SlackBot::Errors::SignatureAuthenticationError.new("Signature mismatch")
|
|
41
|
-
end
|
|
25
|
+
validate_signature_headers!(slack_signing_secret, timestamp, slack_signature)
|
|
26
|
+
validate_request_timestamp!(timestamp)
|
|
27
|
+
verify_signature_match!(slack_signing_secret, timestamp, slack_signature)
|
|
42
28
|
end
|
|
43
29
|
|
|
44
30
|
def verify_slack_team!
|
|
@@ -61,11 +47,9 @@ module SlackBot
|
|
|
61
47
|
end
|
|
62
48
|
|
|
63
49
|
def verify_current_user!
|
|
64
|
-
if current_user
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
raise SlackBot::Errors::UserAuthenticationError.new("User is not authorized")
|
|
68
|
-
end
|
|
50
|
+
return true if current_user
|
|
51
|
+
|
|
52
|
+
raise SlackBot::Errors::UserAuthenticationError.new("User is not authorized")
|
|
69
53
|
end
|
|
70
54
|
|
|
71
55
|
def events_callback(params)
|
|
@@ -73,7 +57,7 @@ module SlackBot
|
|
|
73
57
|
|
|
74
58
|
SlackBot::DevConsole.log_input "SlackApi::Events#events_callback: #{params.inspect}"
|
|
75
59
|
handler = config.find_event_handler(params[:event][:type].to_sym)
|
|
76
|
-
return if handler.blank?
|
|
60
|
+
return false if handler.blank?
|
|
77
61
|
|
|
78
62
|
event = handler.new(params: params, current_user: current_user)
|
|
79
63
|
event.call
|
|
@@ -84,36 +68,124 @@ module SlackBot
|
|
|
84
68
|
{challenge: params[:challenge]}
|
|
85
69
|
end
|
|
86
70
|
|
|
71
|
+
def validate_callback_user!(callback, user)
|
|
72
|
+
if callback.user_id != user.id
|
|
73
|
+
raise SlackBot::Errors::CallbackUserMismatchError.new("Callback user is not equal to action user")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
87
77
|
def handle_block_actions_view(view:, user:, params:)
|
|
88
|
-
|
|
78
|
+
callback = find_callback!(view: view, user: user)
|
|
79
|
+
log_callback_check(callback, user)
|
|
80
|
+
validate_callback_user!(callback, user)
|
|
81
|
+
|
|
82
|
+
interaction_klass = callback_interaction_klass(callback)
|
|
83
|
+
return false if interaction_klass.blank?
|
|
84
|
+
|
|
85
|
+
interaction_klass.new(current_user: user, params: params, callback: callback, config: config).call
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def slack_request_header(*names)
|
|
91
|
+
names.each do |name|
|
|
92
|
+
header = request.headers[name]
|
|
93
|
+
return header if header
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def validate_signature_headers!(slack_signing_secret, timestamp, slack_signature)
|
|
100
|
+
raise SlackBot::Errors::SignatureAuthenticationError.new("Missing signature headers") if slack_signing_secret.blank? || timestamp.blank? || slack_signature.blank?
|
|
101
|
+
return if slack_signing_secret.start_with?("test_") || slack_signing_secret.length >= MIN_SIGNING_SECRET_LENGTH
|
|
102
|
+
|
|
103
|
+
raise SlackBot::Errors::SignatureAuthenticationError.new("Invalid signing secret format")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate_request_timestamp!(timestamp)
|
|
107
|
+
request_timestamp = timestamp.to_i
|
|
108
|
+
current_timestamp = Time.now.to_i
|
|
109
|
+
return if (current_timestamp - request_timestamp).abs <= TIMESTAMP_TOLERANCE_SECONDS
|
|
110
|
+
|
|
111
|
+
raise SlackBot::Errors::SignatureAuthenticationError.new("Request timestamp too old")
|
|
112
|
+
end
|
|
89
113
|
|
|
90
|
-
|
|
114
|
+
def verify_signature_match!(slack_signing_secret, timestamp, slack_signature)
|
|
115
|
+
return true if ActiveSupport::SecurityUtils.secure_compare(computed_signature(slack_signing_secret, timestamp), slack_signature)
|
|
116
|
+
|
|
117
|
+
raise SlackBot::Errors::SignatureAuthenticationError.new("Signature mismatch")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def computed_signature(slack_signing_secret, timestamp)
|
|
121
|
+
sig_basestring = "v0:#{timestamp}:#{request_body_content}"
|
|
122
|
+
"v0=" + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), slack_signing_secret, sig_basestring)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def request_body_content
|
|
126
|
+
return "" unless request.body
|
|
127
|
+
|
|
128
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
129
|
+
body_content = request.body.read
|
|
130
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
131
|
+
body_content
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def find_callback!(view:, user:)
|
|
135
|
+
callback = SlackBot::Callback.find(view&.dig("callback_id"), user: user, config: config)
|
|
91
136
|
raise SlackBot::Errors::CallbackNotFound.new if callback.blank?
|
|
92
137
|
|
|
138
|
+
callback
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def log_callback_check(callback, user)
|
|
93
142
|
SlackBot::DevConsole.log_check "SlackApi::Interactions##{__method__}: #{callback.id} #{callback.payload} #{callback.user_id} #{user&.id}"
|
|
143
|
+
end
|
|
94
144
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
145
|
+
def callback_interaction_klass(callback)
|
|
146
|
+
handler_class_obj = callback.handler_class
|
|
147
|
+
handler_class_obj&.interaction_klass if handler_class_obj&.respond_to?(:interaction_klass)
|
|
148
|
+
end
|
|
98
149
|
|
|
99
|
-
|
|
100
|
-
|
|
150
|
+
def parse_interaction_payload!(raw_payload)
|
|
151
|
+
JSON.parse(raw_payload)
|
|
152
|
+
rescue JSON::ParserError => e
|
|
153
|
+
raise SlackBot::Errors::InvalidPayloadError.new("Invalid JSON payload: #{e.message}")
|
|
154
|
+
end
|
|
101
155
|
|
|
102
|
-
|
|
156
|
+
def resolve_action_user(payload)
|
|
157
|
+
resolve_user_session(payload.dig("user", "team_id"), payload.dig("user", "id"))
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def blank_slack_response!
|
|
161
|
+
body false
|
|
162
|
+
status 200
|
|
103
163
|
end
|
|
104
164
|
end
|
|
105
165
|
|
|
106
166
|
module GrapeExtension
|
|
107
167
|
def self.included(base)
|
|
168
|
+
configure_base!(base)
|
|
169
|
+
add_commands_resource!(base)
|
|
170
|
+
add_interactions_resource!(base)
|
|
171
|
+
add_events_resource!(base)
|
|
172
|
+
add_menu_options_resource!(base)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def self.configure_base!(base)
|
|
108
176
|
base.format :json
|
|
109
177
|
base.content_type :json, "application/json"
|
|
110
|
-
base.use ActionDispatch::RemoteIp
|
|
178
|
+
base.use ActionDispatch::RemoteIp if defined?(ActionDispatch::RemoteIp)
|
|
111
179
|
base.helpers SlackBot::GrapeHelpers
|
|
112
|
-
|
|
180
|
+
base.rescue_from SlackBot::Error do |e|
|
|
181
|
+
error!({error: e.message}, 200)
|
|
182
|
+
end
|
|
113
183
|
base.before do
|
|
114
184
|
verify_slack_signature!
|
|
115
185
|
end
|
|
186
|
+
end
|
|
116
187
|
|
|
188
|
+
def self.add_commands_resource!(base)
|
|
117
189
|
base.resource :commands do
|
|
118
190
|
post ":url_token" do
|
|
119
191
|
command_config = config.find_slash_command_config(params[:url_token], params[:command], params[:text])
|
|
@@ -123,79 +195,68 @@ module SlackBot
|
|
|
123
195
|
args = params[:text].gsub(/^#{command_config.full_token}\s?/, "")
|
|
124
196
|
SlackBot::DevConsole.log_input "SlackApi::SlashCommands#post: #{command_config.url_token} | #{command_config.full_token} | #{args}"
|
|
125
197
|
|
|
126
|
-
action =
|
|
127
|
-
command_klass.new(
|
|
128
|
-
current_user: current_user,
|
|
129
|
-
params: params,
|
|
130
|
-
args: args,
|
|
131
|
-
config: config
|
|
132
|
-
)
|
|
198
|
+
action = command_klass.new(current_user: current_user, params: params, args: args, config: config)
|
|
133
199
|
verify_slack_team! if action.only_slack_team?
|
|
134
200
|
verify_direct_message_channel! if action.only_direct_message?
|
|
135
201
|
verify_current_user! if action.only_user?
|
|
136
202
|
|
|
137
203
|
result = action.call
|
|
138
|
-
return
|
|
204
|
+
return blank_slack_response! unless result
|
|
139
205
|
|
|
140
206
|
result
|
|
141
207
|
end
|
|
142
208
|
end
|
|
209
|
+
end
|
|
143
210
|
|
|
211
|
+
def self.add_interactions_resource!(base)
|
|
144
212
|
base.resource :interactions do
|
|
145
213
|
post do
|
|
146
|
-
payload =
|
|
214
|
+
payload = parse_interaction_payload!(params[:payload])
|
|
215
|
+
action_user = resolve_action_user(payload)&.user
|
|
147
216
|
|
|
148
|
-
|
|
149
|
-
resolve_user_session(
|
|
150
|
-
payload.dig("user", "team_id"),
|
|
151
|
-
payload.dig("user", "id")
|
|
152
|
-
)
|
|
153
|
-
action_user = action_user_session&.user
|
|
154
|
-
|
|
155
|
-
action_type = payload["type"]
|
|
156
|
-
result = case action_type
|
|
217
|
+
result = case payload["type"]
|
|
157
218
|
when "block_actions", "view_submission"
|
|
158
|
-
handle_block_actions_view(
|
|
159
|
-
view: payload["view"],
|
|
160
|
-
user: action_user,
|
|
161
|
-
params: params
|
|
162
|
-
)
|
|
219
|
+
handle_block_actions_view(view: payload["view"], user: action_user, params: params)
|
|
163
220
|
else
|
|
164
|
-
raise "
|
|
221
|
+
raise SlackBot::Errors::UnknownActionTypeError.new(payload["type"])
|
|
165
222
|
end
|
|
166
223
|
|
|
167
|
-
return
|
|
224
|
+
return blank_slack_response! if result.blank? || result == false
|
|
168
225
|
|
|
169
226
|
result
|
|
170
227
|
end
|
|
171
228
|
end
|
|
229
|
+
end
|
|
172
230
|
|
|
231
|
+
def self.add_events_resource!(base)
|
|
173
232
|
base.resource :events do
|
|
174
233
|
post do
|
|
175
|
-
result =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
234
|
+
result = case params[:type]
|
|
235
|
+
when "url_verification"
|
|
236
|
+
url_verification(params)
|
|
237
|
+
when "event_callback"
|
|
238
|
+
events_callback(params)
|
|
239
|
+
else
|
|
240
|
+
raise SlackBot::Errors::UnknownActionTypeError.new(params[:type])
|
|
241
|
+
end
|
|
182
242
|
|
|
183
|
-
return
|
|
243
|
+
return blank_slack_response! if result.blank? || result == false
|
|
184
244
|
|
|
185
245
|
result
|
|
186
246
|
end
|
|
187
247
|
end
|
|
248
|
+
end
|
|
188
249
|
|
|
250
|
+
def self.add_menu_options_resource!(base)
|
|
189
251
|
base.resource :menu_options do
|
|
190
252
|
get do
|
|
191
253
|
SlackBot::DevConsole.log_input "SlackApi::MenuOptions#get: #{params.inspect}"
|
|
192
254
|
|
|
193
|
-
|
|
194
|
-
menu_options_klass = config.find_menu_options(action_id)
|
|
255
|
+
menu_options_klass = config.find_menu_options(params[:action_id])
|
|
195
256
|
raise SlackBot::Errors::MenuOptionsNotImplemented.new if menu_options_klass.blank?
|
|
196
257
|
|
|
197
258
|
menu_options = menu_options_klass.new(current_user: current_user, params: params, config: config).call
|
|
198
|
-
return
|
|
259
|
+
return blank_slack_response! if menu_options.blank?
|
|
199
260
|
|
|
200
261
|
menu_options
|
|
201
262
|
end
|
|
@@ -10,55 +10,20 @@ module SlackBot
|
|
|
10
10
|
include SlackBot::Concerns::ViewKlass
|
|
11
11
|
|
|
12
12
|
def self.open_modal(callback:, trigger_id:, view:)
|
|
13
|
-
view =
|
|
14
|
-
response =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if !response.ok?
|
|
18
|
-
raise SlackBot::Errors::OpenModalError.new(response.error, data: response.data, payload: view)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
view_id = response.data.dig("view", "id")
|
|
22
|
-
if callback.present? && view_id.present?
|
|
23
|
-
callback.view_id = view_id
|
|
24
|
-
callback.save
|
|
25
|
-
end
|
|
26
|
-
SlackViewsReply.new(callback&.id, view_id)
|
|
13
|
+
view = modal_payload(callback, view)
|
|
14
|
+
response = SlackBot::ApiClient.new.views_open(trigger_id: trigger_id, view: view)
|
|
15
|
+
build_view_reply(response: response, callback: callback, payload: view, error_class: SlackBot::Errors::OpenModalError)
|
|
27
16
|
end
|
|
28
17
|
|
|
29
18
|
def self.update_modal(callback:, view_id:, view:)
|
|
30
|
-
view =
|
|
31
|
-
response =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if !response.ok?
|
|
35
|
-
raise SlackBot::Errors::UpdateModalError.new(response.error, data: response.data, payload: view)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
view_id = response.data.dig("view", "id")
|
|
39
|
-
if callback.present? && view_id.present?
|
|
40
|
-
callback.view_id = view_id
|
|
41
|
-
callback.save
|
|
42
|
-
end
|
|
43
|
-
SlackViewsReply.new(callback&.id, view_id)
|
|
19
|
+
view = modal_payload(callback, view)
|
|
20
|
+
response = SlackBot::ApiClient.new.views_update(view_id: view_id, view: view)
|
|
21
|
+
build_view_reply(response: response, callback: callback, payload: view, error_class: SlackBot::Errors::UpdateModalError)
|
|
44
22
|
end
|
|
45
23
|
|
|
46
24
|
def self.publish_view(user_id:, view:, callback: nil, metadata: nil)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
response =
|
|
50
|
-
SlackBot::ApiClient.new.views_publish(user_id: user_id, view: view)
|
|
51
|
-
|
|
52
|
-
if !response.ok?
|
|
53
|
-
raise SlackBot::Errors::PublishViewError.new(response.error, data: response.data, payload: view)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
view_id = response.data.dig("view", "id")
|
|
57
|
-
if callback.present? && view_id.present?
|
|
58
|
-
callback.view_id = view_id
|
|
59
|
-
callback.save
|
|
60
|
-
end
|
|
61
|
-
SlackViewsReply.new(callback&.id, view_id)
|
|
25
|
+
response = SlackBot::ApiClient.new.views_publish(user_id: user_id, view: publish_payload(callback, metadata, view))
|
|
26
|
+
build_view_reply(response: response, callback: callback, payload: view, error_class: SlackBot::Errors::PublishViewError)
|
|
62
27
|
end
|
|
63
28
|
|
|
64
29
|
attr_reader :current_user, :params, :callback, :config
|
|
@@ -73,6 +38,31 @@ module SlackBot
|
|
|
73
38
|
nil
|
|
74
39
|
end
|
|
75
40
|
|
|
41
|
+
def self.modal_payload(callback, view)
|
|
42
|
+
view.merge(type: "modal", callback_id: callback&.id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.publish_payload(callback, metadata, view)
|
|
46
|
+
view = view.merge(callback_id: callback.id) if callback.present?
|
|
47
|
+
view = view.merge(private_metadata: metadata) if metadata.present?
|
|
48
|
+
view
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.build_view_reply(response:, callback:, payload:, error_class:)
|
|
52
|
+
raise error_class.new(response.error, data: response.data, payload: payload) unless response.ok?
|
|
53
|
+
|
|
54
|
+
view_id = response.data.dig("view", "id")
|
|
55
|
+
persist_view_id(callback, view_id)
|
|
56
|
+
SlackViewsReply.new(callback&.id, view_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.persist_view_id(callback, view_id)
|
|
60
|
+
return unless callback.present? && view_id.present?
|
|
61
|
+
|
|
62
|
+
callback.view_id = view_id
|
|
63
|
+
callback.save
|
|
64
|
+
end
|
|
65
|
+
|
|
76
66
|
private
|
|
77
67
|
|
|
78
68
|
def interaction_type
|
|
@@ -149,7 +139,11 @@ module SlackBot
|
|
|
149
139
|
end
|
|
150
140
|
|
|
151
141
|
def payload
|
|
152
|
-
@payload ||=
|
|
142
|
+
@payload ||= begin
|
|
143
|
+
JSON.parse(params[:payload])
|
|
144
|
+
rescue JSON::ParserError => e
|
|
145
|
+
raise SlackBot::Errors::InvalidPayloadError.new("Invalid JSON payload: #{e.message}")
|
|
146
|
+
end
|
|
153
147
|
end
|
|
154
148
|
end
|
|
155
149
|
end
|
data/lib/slack_bot/logger.rb
CHANGED
|
@@ -4,5 +4,20 @@ module SlackBot
|
|
|
4
4
|
puts args.inspect if args.any?
|
|
5
5
|
puts kwargs.inspect if kwargs.any?
|
|
6
6
|
end
|
|
7
|
+
|
|
8
|
+
def error(*args, **kwargs)
|
|
9
|
+
puts args.inspect if args.any?
|
|
10
|
+
puts kwargs.inspect if kwargs.any?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def warn(*args, **kwargs)
|
|
14
|
+
puts args.inspect if args.any?
|
|
15
|
+
puts kwargs.inspect if kwargs.any?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def debug(*args, **kwargs)
|
|
19
|
+
puts args.inspect if args.any?
|
|
20
|
+
puts kwargs.inspect if kwargs.any?
|
|
21
|
+
end
|
|
7
22
|
end
|
|
8
23
|
end
|
data/lib/slack_bot/view.rb
CHANGED
|
@@ -22,14 +22,18 @@ module SlackBot
|
|
|
22
22
|
super
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
26
|
+
(@context.is_a?(Hash) && @context.key?(method_name.to_sym)) || super
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
def text_modal
|
|
26
30
|
{
|
|
27
31
|
title: {
|
|
28
32
|
type: "plain_text",
|
|
29
|
-
text: context
|
|
33
|
+
text: context&.dig(:title) || ""
|
|
30
34
|
},
|
|
31
35
|
blocks: [
|
|
32
|
-
{type: "section", text: {type: "mrkdwn", text: context
|
|
36
|
+
{type: "section", text: {type: "mrkdwn", text: context&.dig(:text) || ""}}
|
|
33
37
|
]
|
|
34
38
|
}
|
|
35
39
|
end
|
data/lib/slack_bot.rb
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "openssl"
|
|
3
|
+
|
|
1
4
|
require "slack_bot/logger"
|
|
2
5
|
require "slack_bot/dev_console"
|
|
3
6
|
|
|
@@ -22,5 +25,5 @@ require "slack_bot/pager"
|
|
|
22
25
|
require "slack_bot/grape_extension"
|
|
23
26
|
|
|
24
27
|
module SlackBot
|
|
25
|
-
VERSION = "
|
|
28
|
+
VERSION = "2.0.1".freeze
|
|
26
29
|
end
|