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