grape-slack-bot 2.0.1 → 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 +13 -0
- data/README.md +8 -5
- data/grape-slack-bot.gemspec +4 -4
- data/lib/slack_bot/api_client.rb +38 -74
- 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 +21 -51
- 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 +11 -3
- 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 +29 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9361a3a86619e251da1df7649973cdd911619f2b55f18d5d622f8a1e6d5ee707
|
|
4
|
+
data.tar.gz: ebd8c29c0f6957bbaa0cf1fefbce369f7072b60d4b01cbae4037fa7a5fe3212b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f4bf3f86997214af675d7a21c40c4270fbb3e8c93093dbdacabf08da89a4ed054221e27345702e4e48da4c945ec12ef4291c7c46a412ee7a02d82fadd6e1db5
|
|
7
|
+
data.tar.gz: a2c0ea8a963da6d8aff07dca5bdb90c517e1ae44a64b34b5f1e70903279b33239f6182b1bd8ddef22de17d45c7eb0dda86f101d3ed0cfd4461ea080357e11db2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 2.1.0 (2026-07-10)
|
|
4
|
+
|
|
5
|
+
- Add `block_action` registration for message-level block action buttons outside modal callbacks
|
|
6
|
+
- Add `user_session_resolver` and `event_dispatcher` configuration hooks for session lookup and background event dispatch
|
|
7
|
+
- Acknowledge Slack event retries with an empty 200 response so duplicate deliveries do not run handlers twice
|
|
8
|
+
- Skip `bot_message` event payloads at ingress to avoid handler loops from bot echoes
|
|
9
|
+
- Retry Slack API requests once when rate limited, sleeping at most 3 seconds before the retry
|
|
10
|
+
- Fix `scheduled_messages_list` to send the `channel` argument to the Slack API
|
|
11
|
+
- Fix team verification when `SLACK_TEAM_ID` is unset so requests are rejected instead of matching any team
|
|
12
|
+
- Raise `SlackBot::Errors::ConfigurationError` when required configuration is missing
|
|
13
|
+
- Raise `SlackBot::Errors::BlockActionNotImplemented` when an unregistered block action is submitted
|
|
14
|
+
- Require `faraday` `>= 2.14.3`
|
|
15
|
+
|
|
3
16
|
## 2.0.1 (2026-05-31)
|
|
4
17
|
|
|
5
18
|
- Allow `grape` 3.x by expanding the runtime dependency to `< 4.0`
|
data/README.md
CHANGED
|
@@ -55,10 +55,18 @@ SlackBot::DevConsole.enabled = Rails.env.development?
|
|
|
55
55
|
SlackBot::Config.configure do
|
|
56
56
|
callback_storage Rails.cache
|
|
57
57
|
callback_user_finder ->(id) { User.active.find_by(id: id) }
|
|
58
|
+
user_session_resolver ->(team_id, user_id) do
|
|
59
|
+
uid = OmniAuth::Strategies::SlackOpenid.generate_uid(team_id, user_id)
|
|
60
|
+
UserSession.find_by(uid: uid, provider: UserSession.slack_openid_provider)
|
|
61
|
+
end
|
|
62
|
+
event_dispatcher ->(handler:, params:, current_user:) do
|
|
63
|
+
SlackEventJob.perform_later(handler.name, params.deep_stringify_keys, current_user&.id)
|
|
64
|
+
end
|
|
58
65
|
|
|
59
66
|
# Register event handlers
|
|
60
67
|
event :app_home_opened, MySlackBot::AppHomeOpenedEvent
|
|
61
68
|
interaction MySlackBot::AppHomeInteraction
|
|
69
|
+
block_action :approve_request, MySlackBot::ApproveRequestInteraction
|
|
62
70
|
|
|
63
71
|
# Register slash command handlers
|
|
64
72
|
slash_command_endpoint :game, MySlackBot::Game::MenuCommand do
|
|
@@ -74,11 +82,6 @@ class SlackBotApi < Grape::API
|
|
|
74
82
|
SlackBot::Config.current_instance
|
|
75
83
|
end
|
|
76
84
|
|
|
77
|
-
def resolve_user_session(team_id, user_id)
|
|
78
|
-
uid = OmniAuth::Strategies::SlackOpenid.generate_uid(team_id, user_id)
|
|
79
|
-
UserSession.find_by(uid: uid, provider: UserSession.slack_openid_provider)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
85
|
def current_user_session
|
|
83
86
|
# NOTE: fetch_team_id and fetch_user_id are provided by SlackBot::GrapeHelpers
|
|
84
87
|
@current_user_session ||=
|
data/grape-slack-bot.gemspec
CHANGED
|
@@ -23,19 +23,18 @@ Gem::Specification.new do |gem|
|
|
|
23
23
|
"rubygems_mfa_required" => "true"
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
gem.
|
|
27
|
-
gem.files = Dir.glob("lib/**/*.rb") + Dir.glob("bin/**/*") + Dir.glob("sig/**/*.rbs") + root_files
|
|
26
|
+
gem.files = Dir.glob("lib/**/*.rb") + Dir.glob("sig/**/*.rbs") + root_files
|
|
28
27
|
|
|
29
28
|
gem.required_ruby_version = ">= 3"
|
|
30
29
|
gem.require_paths = ["lib"]
|
|
31
30
|
|
|
32
31
|
gem.add_runtime_dependency "rack", "~> 3.0"
|
|
33
32
|
gem.add_runtime_dependency "grape", ">= 1.6", "< 4.0"
|
|
34
|
-
gem.add_runtime_dependency "faraday", "
|
|
33
|
+
gem.add_runtime_dependency "faraday", ">= 2.14.3", "< 3.0"
|
|
35
34
|
gem.add_runtime_dependency "activesupport", ">= 6.1", "< 9.0"
|
|
36
35
|
|
|
37
36
|
gem.add_development_dependency "rspec", "~> 3"
|
|
38
|
-
gem.add_development_dependency "polyrun", "~> 1.
|
|
37
|
+
gem.add_development_dependency "polyrun", "~> 1.5.0"
|
|
39
38
|
gem.add_development_dependency "webmock", "~> 3"
|
|
40
39
|
gem.add_development_dependency "rake", "~> 13"
|
|
41
40
|
gem.add_development_dependency "standard", "~> 1.52"
|
|
@@ -45,6 +44,7 @@ Gem::Specification.new do |gem|
|
|
|
45
44
|
gem.add_development_dependency "rubocop-rspec", "~> 3.8"
|
|
46
45
|
gem.add_development_dependency "rubocop-thread_safety", "~> 0.7"
|
|
47
46
|
gem.add_development_dependency "appraisal", "~> 2"
|
|
47
|
+
gem.add_development_dependency "bundler-audit", "~> 0.9"
|
|
48
48
|
gem.add_development_dependency "memory_profiler", "~> 1"
|
|
49
49
|
gem.add_development_dependency "rbs", "~> 3"
|
|
50
50
|
gem.add_development_dependency "rack-test", "~> 2"
|
data/lib/slack_bot/api_client.rb
CHANGED
|
@@ -2,7 +2,23 @@ require "faraday"
|
|
|
2
2
|
|
|
3
3
|
module SlackBot
|
|
4
4
|
class ApiResponse
|
|
5
|
+
# Cap in-request sleep so rate-limit retries do not hold web workers for Slack's default Retry-After.
|
|
6
|
+
RATE_LIMIT_RETRY_SLEEP_SECONDS = 3
|
|
7
|
+
|
|
5
8
|
attr_reader :response
|
|
9
|
+
|
|
10
|
+
def self.from_request(&block)
|
|
11
|
+
response = new(&block)
|
|
12
|
+
return response unless response.rate_limited?
|
|
13
|
+
|
|
14
|
+
Kernel.sleep(rate_limit_retry_sleep_seconds(response.retry_after))
|
|
15
|
+
new(&block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.rate_limit_retry_sleep_seconds(retry_after)
|
|
19
|
+
[retry_after.to_i, RATE_LIMIT_RETRY_SLEEP_SECONDS].min
|
|
20
|
+
end
|
|
21
|
+
|
|
6
22
|
def initialize(&block)
|
|
7
23
|
@response = block.call
|
|
8
24
|
SlackBot::DevConsole.log_output "#{self.class.name}: #{response.body}"
|
|
@@ -57,47 +73,27 @@ module SlackBot
|
|
|
57
73
|
end
|
|
58
74
|
|
|
59
75
|
def views_open(trigger_id:, view:)
|
|
60
|
-
|
|
61
|
-
client.post("views.open", {trigger_id: trigger_id, view: view}.to_json)
|
|
62
|
-
rescue Faraday::Error => e
|
|
63
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
64
|
-
end
|
|
76
|
+
perform_request { client.post("views.open", {trigger_id: trigger_id, view: view}.to_json) }
|
|
65
77
|
end
|
|
66
78
|
|
|
67
79
|
def views_update(view_id:, view:)
|
|
68
|
-
|
|
69
|
-
client.post("views.update", {view_id: view_id, view: view}.to_json)
|
|
70
|
-
rescue Faraday::Error => e
|
|
71
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
72
|
-
end
|
|
80
|
+
perform_request { client.post("views.update", {view_id: view_id, view: view}.to_json) }
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
def chat_post_message(channel:, text:, blocks:)
|
|
76
|
-
|
|
77
|
-
client.post("chat.postMessage", {channel: channel, text: text, blocks: blocks}.to_json)
|
|
78
|
-
rescue Faraday::Error => e
|
|
79
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
80
|
-
end
|
|
84
|
+
perform_request { client.post("chat.postMessage", {channel: channel, text: text, blocks: blocks}.to_json) }
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
def chat_update(channel:, ts:, text:, blocks:)
|
|
84
|
-
|
|
85
|
-
client.post("chat.update", {channel: channel, ts: ts, text: text, blocks: blocks}.to_json)
|
|
86
|
-
rescue Faraday::Error => e
|
|
87
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
88
|
-
end
|
|
88
|
+
perform_request { client.post("chat.update", {channel: channel, ts: ts, text: text, blocks: blocks}.to_json) }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def chat_delete(channel:, ts:)
|
|
92
|
-
|
|
93
|
-
client.post("chat.delete", {channel: channel, ts: ts}.to_json)
|
|
94
|
-
rescue Faraday::Error => e
|
|
95
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
96
|
-
end
|
|
92
|
+
perform_request { client.post("chat.delete", {channel: channel, ts: ts}.to_json) }
|
|
97
93
|
end
|
|
98
94
|
|
|
99
95
|
def chat_unfurl(channel:, ts:, unfurls:, source: nil, unfurl_id: nil, user_auth_blocks: nil, user_auth_message: nil, user_auth_required: nil, user_auth_url: nil)
|
|
100
|
-
|
|
96
|
+
perform_request do
|
|
101
97
|
client.post("chat.unfurl", {
|
|
102
98
|
channel: channel,
|
|
103
99
|
ts: ts,
|
|
@@ -109,73 +105,37 @@ module SlackBot
|
|
|
109
105
|
user_auth_required: user_auth_required,
|
|
110
106
|
user_auth_url: user_auth_url
|
|
111
107
|
}.to_json)
|
|
112
|
-
rescue Faraday::Error => e
|
|
113
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
114
108
|
end
|
|
115
109
|
end
|
|
116
110
|
|
|
117
111
|
def chat_schedule_message(channel:, text:, post_at:, blocks: nil)
|
|
118
|
-
|
|
119
|
-
client.post("chat.scheduleMessage", {channel: channel, text: text, post_at: post_at, blocks: blocks}.to_json)
|
|
120
|
-
rescue Faraday::Error => e
|
|
121
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
122
|
-
end
|
|
112
|
+
perform_request { client.post("chat.scheduleMessage", {channel: channel, text: text, post_at: post_at, blocks: blocks}.to_json) }
|
|
123
113
|
end
|
|
124
114
|
|
|
125
115
|
def scheduled_messages_list(channel: nil, cursor: nil, latest: nil, limit: nil, oldest: nil, team_id: nil)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
channel: channel,
|
|
129
|
-
cursor: cursor,
|
|
130
|
-
latest: latest,
|
|
131
|
-
limit: limit,
|
|
132
|
-
oldest: oldest,
|
|
133
|
-
team_id: team_id
|
|
134
|
-
}.to_json)
|
|
135
|
-
rescue Faraday::Error => e
|
|
136
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
137
|
-
end
|
|
116
|
+
args = compact_payload(channel: channel, cursor: cursor, limit: limit, latest: latest, oldest: oldest, team_id: team_id)
|
|
117
|
+
perform_request { client.post("scheduled_messages.list", args.to_json) }
|
|
138
118
|
end
|
|
139
119
|
|
|
140
120
|
def chat_delete_scheduled_message(channel:, scheduled_message_id:)
|
|
141
|
-
|
|
142
|
-
client.post("chat.deleteScheduledMessage", {channel: channel, scheduled_message_id: scheduled_message_id}.to_json)
|
|
143
|
-
rescue Faraday::Error => e
|
|
144
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
145
|
-
end
|
|
121
|
+
perform_request { client.post("chat.deleteScheduledMessage", {channel: channel, scheduled_message_id: scheduled_message_id}.to_json) }
|
|
146
122
|
end
|
|
147
123
|
|
|
148
124
|
def chat_get_permalink(channel:, message_ts:)
|
|
149
|
-
|
|
150
|
-
client.post("chat.getPermalink", {channel: channel, message_ts: message_ts}.to_json)
|
|
151
|
-
rescue Faraday::Error => e
|
|
152
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
153
|
-
end
|
|
125
|
+
perform_request { client.post("chat.getPermalink", {channel: channel, message_ts: message_ts}.to_json) }
|
|
154
126
|
end
|
|
155
127
|
|
|
156
128
|
def users_info(user_id:)
|
|
157
|
-
|
|
158
|
-
client.post("users.info", {user: user_id}.to_json)
|
|
159
|
-
rescue Faraday::Error => e
|
|
160
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
161
|
-
end
|
|
129
|
+
perform_request { client.post("users.info", {user: user_id}.to_json) }
|
|
162
130
|
end
|
|
163
131
|
|
|
164
132
|
def views_publish(user_id:, view:)
|
|
165
|
-
|
|
166
|
-
client.post("views.publish", {user_id: user_id, view: view}.to_json)
|
|
167
|
-
rescue Faraday::Error => e
|
|
168
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
169
|
-
end
|
|
133
|
+
perform_request { client.post("views.publish", {user_id: user_id, view: view}.to_json) }
|
|
170
134
|
end
|
|
171
135
|
|
|
172
136
|
def users_list(cursor: nil, limit: 200, include_locale: nil, team_id: nil)
|
|
173
137
|
args = compact_payload(cursor: cursor, limit: limit, include_locale: include_locale, team_id: team_id)
|
|
174
|
-
|
|
175
|
-
client.post("users.list", args.to_json)
|
|
176
|
-
rescue Faraday::Error => e
|
|
177
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
178
|
-
end
|
|
138
|
+
perform_request { client.post("users.list", args.to_json) }
|
|
179
139
|
end
|
|
180
140
|
|
|
181
141
|
def chat_post_ephemeral(channel:, user:, text:, as_user: nil, attachments: nil, blocks: nil, icon_emoji: nil, icon_url: nil, link_names: nil, parse: nil, thread_ts: nil, username: nil)
|
|
@@ -193,15 +153,19 @@ module SlackBot
|
|
|
193
153
|
thread_ts: thread_ts,
|
|
194
154
|
username: username
|
|
195
155
|
)
|
|
196
|
-
|
|
197
|
-
|
|
156
|
+
perform_request { client.post("chat.postEphemeral", args.to_json) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
def perform_request(&block)
|
|
162
|
+
ApiResponse.from_request do
|
|
163
|
+
block.call
|
|
198
164
|
rescue Faraday::Error => e
|
|
199
165
|
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
200
166
|
end
|
|
201
167
|
end
|
|
202
168
|
|
|
203
|
-
private
|
|
204
|
-
|
|
205
169
|
def validate_authorization_token!(authorization_token)
|
|
206
170
|
raise SlackBot::Errors::SlackApiError.new("Slack bot API token is not set") unless valid_authorization_token?(authorization_token)
|
|
207
171
|
return if authorization_token.start_with?("test_") || authorization_token.match?(TOKEN_FORMAT)
|
data/lib/slack_bot/callback.rb
CHANGED
|
@@ -94,7 +94,7 @@ module SlackBot
|
|
|
94
94
|
def user
|
|
95
95
|
@user ||= begin
|
|
96
96
|
user_id = data&.dig(:user_id)
|
|
97
|
-
config.
|
|
97
|
+
config.callback_user_finder!.call(user_id) if user_id.present?
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -137,7 +137,7 @@ module SlackBot
|
|
|
137
137
|
|
|
138
138
|
def handler_class=(handler_class)
|
|
139
139
|
new_class_name = handler_class&.name
|
|
140
|
-
config.find_handler_class(
|
|
140
|
+
config.find_handler_class(new_class_name)
|
|
141
141
|
|
|
142
142
|
self.class_name = new_class_name
|
|
143
143
|
end
|
|
@@ -162,7 +162,7 @@ module SlackBot
|
|
|
162
162
|
def read_view_callback_id
|
|
163
163
|
return if view_id.blank?
|
|
164
164
|
|
|
165
|
-
config.
|
|
165
|
+
config.callback_storage!.read(view_storage_key)
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
private
|
|
@@ -196,18 +196,18 @@ module SlackBot
|
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
def read_data
|
|
199
|
-
config.
|
|
199
|
+
config.callback_storage!.read(storage_key)
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
def write_data(data, expires_in: nil)
|
|
203
203
|
expires_in ||= CALLBACK_RECORD_EXPIRES_IN
|
|
204
|
-
config.
|
|
205
|
-
config.
|
|
204
|
+
config.callback_storage!.write(view_storage_key, id, expires_in: expires_in) if view_id.present?
|
|
205
|
+
config.callback_storage!.write(storage_key, data, expires_in: expires_in)
|
|
206
206
|
end
|
|
207
207
|
|
|
208
208
|
def delete_data
|
|
209
|
-
config.
|
|
210
|
-
config.
|
|
209
|
+
config.callback_storage!.delete(view_storage_key) if view_id.present?
|
|
210
|
+
config.callback_storage!.delete(storage_key)
|
|
211
211
|
end
|
|
212
212
|
end
|
|
213
213
|
end
|
data/lib/slack_bot/config.rb
CHANGED
|
@@ -17,11 +17,45 @@ module SlackBot
|
|
|
17
17
|
@callback_storage_instance = klass
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def callback_storage!
|
|
21
|
+
return @callback_storage_instance if @callback_storage_instance
|
|
22
|
+
|
|
23
|
+
raise SlackBot::Errors::ConfigurationError.new(
|
|
24
|
+
"callback_storage is not configured; call SlackBot::Config.configure { |config| config.callback_storage ... }"
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
20
28
|
attr_reader :callback_user_finder_method
|
|
21
29
|
def callback_user_finder(method_lambda)
|
|
22
30
|
@callback_user_finder_method = method_lambda
|
|
23
31
|
end
|
|
24
32
|
|
|
33
|
+
def callback_user_finder!
|
|
34
|
+
return @callback_user_finder_method if @callback_user_finder_method
|
|
35
|
+
|
|
36
|
+
raise SlackBot::Errors::ConfigurationError.new(
|
|
37
|
+
"callback_user_finder is not configured; call SlackBot::Config.configure { |config| config.callback_user_finder ... }"
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attr_reader :user_session_resolver_method
|
|
42
|
+
def user_session_resolver(method_lambda)
|
|
43
|
+
@user_session_resolver_method = method_lambda
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def user_session_resolver!
|
|
47
|
+
return @user_session_resolver_method if @user_session_resolver_method
|
|
48
|
+
|
|
49
|
+
raise SlackBot::Errors::ConfigurationError.new(
|
|
50
|
+
"user_session_resolver is not configured; call SlackBot::Config.configure { |config| config.user_session_resolver ... }"
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
attr_reader :event_dispatcher_method
|
|
55
|
+
def event_dispatcher(method_lambda)
|
|
56
|
+
@event_dispatcher_method = method_lambda
|
|
57
|
+
end
|
|
58
|
+
|
|
25
59
|
def interaction(interaction_klass, handler_name: nil)
|
|
26
60
|
handler_name ||= interaction_klass.name
|
|
27
61
|
handler_class(handler_name, interaction_klass)
|
|
@@ -78,6 +112,17 @@ module SlackBot
|
|
|
78
112
|
@menu_options[action_id.to_sym]
|
|
79
113
|
end
|
|
80
114
|
|
|
115
|
+
def block_action(action_id, interaction_klass, handler_name: nil)
|
|
116
|
+
@block_actions ||= {}
|
|
117
|
+
@block_actions[action_id.to_sym] = interaction_klass
|
|
118
|
+
interaction(interaction_klass, handler_name: handler_name)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def find_block_action(action_id)
|
|
122
|
+
@block_actions ||= {}
|
|
123
|
+
@block_actions[action_id.to_sym]
|
|
124
|
+
end
|
|
125
|
+
|
|
81
126
|
def handler_class(class_name, klass)
|
|
82
127
|
@handler_classes ||= {}
|
|
83
128
|
return if class_name.nil?
|
|
@@ -130,7 +175,10 @@ module SlackBot
|
|
|
130
175
|
end
|
|
131
176
|
|
|
132
177
|
def find_command_config(text)
|
|
133
|
-
|
|
178
|
+
return if routes.blank?
|
|
179
|
+
|
|
180
|
+
route_pattern = routes.keys.map { |route_key| Regexp.escape(route_key) }.join("|")
|
|
181
|
+
route_key = text.scan(/^(#{route_pattern})(?:\s|$)/).flatten.first
|
|
134
182
|
return if route_key.blank?
|
|
135
183
|
|
|
136
184
|
routes[route_key]
|
data/lib/slack_bot/errors.rb
CHANGED
|
@@ -18,6 +18,12 @@ module SlackBot
|
|
|
18
18
|
class MenuOptionsNotImplemented < SlackBot::Error
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
class BlockActionNotImplemented < SlackBot::Error
|
|
22
|
+
def initialize
|
|
23
|
+
super("Block action is not implemented")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
class CallbackNotFound < SlackBot::Error
|
|
22
28
|
end
|
|
23
29
|
|
|
@@ -82,5 +88,8 @@ module SlackBot
|
|
|
82
88
|
|
|
83
89
|
class NotImplementedError < SlackBot::Error
|
|
84
90
|
end
|
|
91
|
+
|
|
92
|
+
class ConfigurationError < SlackBot::Error
|
|
93
|
+
end
|
|
85
94
|
end
|
|
86
95
|
end
|
|
@@ -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)
|
|
@@ -27,15 +33,20 @@ module SlackBot
|
|
|
27
33
|
verify_signature_match!(slack_signing_secret, timestamp, slack_signature)
|
|
28
34
|
end
|
|
29
35
|
|
|
30
|
-
def verify_slack_team!
|
|
31
|
-
slack_team_id = ENV
|
|
32
|
-
|
|
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
|
|
33
40
|
true
|
|
34
41
|
else
|
|
35
42
|
raise SlackBot::Errors::TeamAuthenticationError.new("Team is not authorized")
|
|
36
43
|
end
|
|
37
44
|
end
|
|
38
45
|
|
|
46
|
+
def interaction_team_id(payload)
|
|
47
|
+
payload.dig("team", "id") || payload["team_id"] || payload.dig("user", "team_id")
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
def verify_direct_message_channel!
|
|
40
51
|
if params[:channel_name] == "directmessage"
|
|
41
52
|
true
|
|
@@ -52,15 +63,8 @@ module SlackBot
|
|
|
52
63
|
raise SlackBot::Errors::UserAuthenticationError.new("User is not authorized")
|
|
53
64
|
end
|
|
54
65
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
SlackBot::DevConsole.log_input "SlackApi::Events#events_callback: #{params.inspect}"
|
|
59
|
-
handler = config.find_event_handler(params[:event][:type].to_sym)
|
|
60
|
-
return false if handler.blank?
|
|
61
|
-
|
|
62
|
-
event = handler.new(params: params, current_user: current_user)
|
|
63
|
-
event.call
|
|
66
|
+
def slack_request_retry?
|
|
67
|
+
slack_request_header("x-slack-retry-num", "X-Slack-Retry-Num").present?
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
def url_verification(params)
|
|
@@ -68,23 +72,6 @@ module SlackBot
|
|
|
68
72
|
{challenge: params[:challenge]}
|
|
69
73
|
end
|
|
70
74
|
|
|
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
|
-
|
|
77
|
-
def handle_block_actions_view(view:, user:, params:)
|
|
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
75
|
private
|
|
89
76
|
|
|
90
77
|
def slack_request_header(*names)
|
|
@@ -131,22 +118,6 @@ module SlackBot
|
|
|
131
118
|
body_content
|
|
132
119
|
end
|
|
133
120
|
|
|
134
|
-
def find_callback!(view:, user:)
|
|
135
|
-
callback = SlackBot::Callback.find(view&.dig("callback_id"), user: user, config: config)
|
|
136
|
-
raise SlackBot::Errors::CallbackNotFound.new if callback.blank?
|
|
137
|
-
|
|
138
|
-
callback
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def log_callback_check(callback, user)
|
|
142
|
-
SlackBot::DevConsole.log_check "SlackApi::Interactions##{__method__}: #{callback.id} #{callback.payload} #{callback.user_id} #{user&.id}"
|
|
143
|
-
end
|
|
144
|
-
|
|
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
|
|
149
|
-
|
|
150
121
|
def parse_interaction_payload!(raw_payload)
|
|
151
122
|
JSON.parse(raw_payload)
|
|
152
123
|
rescue JSON::ParserError => e
|
|
@@ -212,14 +183,10 @@ module SlackBot
|
|
|
212
183
|
base.resource :interactions do
|
|
213
184
|
post do
|
|
214
185
|
payload = parse_interaction_payload!(params[:payload])
|
|
186
|
+
verify_slack_team!(interaction_team_id(payload))
|
|
215
187
|
action_user = resolve_action_user(payload)&.user
|
|
216
188
|
|
|
217
|
-
result =
|
|
218
|
-
when "block_actions", "view_submission"
|
|
219
|
-
handle_block_actions_view(view: payload["view"], user: action_user, params: params)
|
|
220
|
-
else
|
|
221
|
-
raise SlackBot::Errors::UnknownActionTypeError.new(payload["type"])
|
|
222
|
-
end
|
|
189
|
+
result = route_interaction(payload: payload, user: action_user, params: params)
|
|
223
190
|
|
|
224
191
|
return blank_slack_response! if result.blank? || result == false
|
|
225
192
|
|
|
@@ -231,6 +198,8 @@ module SlackBot
|
|
|
231
198
|
def self.add_events_resource!(base)
|
|
232
199
|
base.resource :events do
|
|
233
200
|
post do
|
|
201
|
+
return blank_slack_response! if slack_request_retry?
|
|
202
|
+
|
|
234
203
|
result = case params[:type]
|
|
235
204
|
when "url_verification"
|
|
236
205
|
url_verification(params)
|
|
@@ -252,6 +221,7 @@ module SlackBot
|
|
|
252
221
|
get do
|
|
253
222
|
SlackBot::DevConsole.log_input "SlackApi::MenuOptions#get: #{params.inspect}"
|
|
254
223
|
|
|
224
|
+
verify_slack_team!
|
|
255
225
|
menu_options_klass = config.find_menu_options(params[:action_id])
|
|
256
226
|
raise SlackBot::Errors::MenuOptionsNotImplemented.new if menu_options_klass.blank?
|
|
257
227
|
|
|
@@ -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,20 +9,28 @@ module SlackBot
|
|
|
9
9
|
|
|
10
10
|
include SlackBot::Concerns::ViewKlass
|
|
11
11
|
|
|
12
|
+
def self.api_client
|
|
13
|
+
@api_client ||= SlackBot::ApiClient.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.api_client=(client)
|
|
17
|
+
@api_client = client
|
|
18
|
+
end
|
|
19
|
+
|
|
12
20
|
def self.open_modal(callback:, trigger_id:, view:)
|
|
13
21
|
view = modal_payload(callback, view)
|
|
14
|
-
response =
|
|
22
|
+
response = api_client.views_open(trigger_id: trigger_id, view: view)
|
|
15
23
|
build_view_reply(response: response, callback: callback, payload: view, error_class: SlackBot::Errors::OpenModalError)
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def self.update_modal(callback:, view_id:, view:)
|
|
19
27
|
view = modal_payload(callback, view)
|
|
20
|
-
response =
|
|
28
|
+
response = api_client.views_update(view_id: view_id, view: view)
|
|
21
29
|
build_view_reply(response: response, callback: callback, payload: view, error_class: SlackBot::Errors::UpdateModalError)
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def self.publish_view(user_id:, view:, callback: nil, metadata: nil)
|
|
25
|
-
response =
|
|
33
|
+
response = api_client.views_publish(user_id: user_id, view: publish_payload(callback, metadata, view))
|
|
26
34
|
build_view_reply(response: response, callback: callback, payload: view, error_class: SlackBot::Errors::PublishViewError)
|
|
27
35
|
end
|
|
28
36
|
|
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
data/sig/slack_bot.rbs
CHANGED
|
@@ -23,6 +23,9 @@ module SlackBot
|
|
|
23
23
|
class MenuOptionsNotImplemented < Error
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
class BlockActionNotImplemented < Error
|
|
27
|
+
end
|
|
28
|
+
|
|
26
29
|
class CallbackNotFound < Error
|
|
27
30
|
end
|
|
28
31
|
|
|
@@ -74,6 +77,9 @@ module SlackBot
|
|
|
74
77
|
|
|
75
78
|
class NotImplementedError < Error
|
|
76
79
|
end
|
|
80
|
+
|
|
81
|
+
class ConfigurationError < Error
|
|
82
|
+
end
|
|
77
83
|
end
|
|
78
84
|
|
|
79
85
|
class Config
|
|
@@ -82,9 +88,18 @@ module SlackBot
|
|
|
82
88
|
|
|
83
89
|
attr_reader callback_storage_instance: untyped
|
|
84
90
|
def callback_storage: (untyped klass) -> void
|
|
91
|
+
def callback_storage!: () -> untyped
|
|
85
92
|
|
|
86
93
|
attr_reader callback_user_finder_method: Proc
|
|
87
94
|
def callback_user_finder: (Proc method_lambda) -> void
|
|
95
|
+
def callback_user_finder!: () -> Proc
|
|
96
|
+
|
|
97
|
+
attr_reader user_session_resolver_method: Proc
|
|
98
|
+
def user_session_resolver: (Proc method_lambda) -> void
|
|
99
|
+
def user_session_resolver!: () -> Proc
|
|
100
|
+
|
|
101
|
+
attr_reader event_dispatcher_method: Proc
|
|
102
|
+
def event_dispatcher: (Proc method_lambda) -> void
|
|
88
103
|
|
|
89
104
|
def interaction: (Class interaction_klass, ?handler_name: String?) -> void
|
|
90
105
|
def event_handlers: () -> Hash[Symbol, Class]
|
|
@@ -98,6 +113,9 @@ module SlackBot
|
|
|
98
113
|
def menu_options: (Symbol action_id, Class klass) -> void
|
|
99
114
|
def find_menu_options: (Symbol action_id) -> Class?
|
|
100
115
|
|
|
116
|
+
def block_action: (Symbol action_id, Class interaction_klass, ?handler_name: String?) -> void
|
|
117
|
+
def find_block_action: (Symbol action_id) -> Class?
|
|
118
|
+
|
|
101
119
|
def handler_class: (String class_name, Class klass) -> void
|
|
102
120
|
def find_handler_class: (String class_name) -> Class
|
|
103
121
|
end
|
|
@@ -157,9 +175,12 @@ module SlackBot
|
|
|
157
175
|
class ApiResponse
|
|
158
176
|
attr_reader response: Faraday::Response
|
|
159
177
|
|
|
178
|
+
def self.from_request: () { () -> Faraday::Response } -> ApiResponse
|
|
160
179
|
def initialize: () { () -> Faraday::Response } -> void
|
|
161
180
|
def ok?: () -> bool
|
|
162
181
|
def error: () -> String?
|
|
182
|
+
def rate_limited?: () -> bool
|
|
183
|
+
def retry_after: () -> Integer
|
|
163
184
|
def data: () -> Hash[String, untyped]
|
|
164
185
|
end
|
|
165
186
|
|
|
@@ -301,12 +322,19 @@ module SlackBot
|
|
|
301
322
|
def fetch_team_id: () -> String?
|
|
302
323
|
def fetch_user_id: () -> String?
|
|
303
324
|
def verify_slack_signature!: () -> bool
|
|
304
|
-
def verify_slack_team!: () -> bool
|
|
325
|
+
def verify_slack_team!: (?String? team_id) -> bool
|
|
326
|
+
def interaction_team_id: (Hash[String, untyped] payload) -> String?
|
|
305
327
|
def verify_direct_message_channel!: () -> bool
|
|
306
328
|
def verify_current_user!: () -> bool
|
|
329
|
+
def slack_request_retry?: () -> bool
|
|
330
|
+
def resolve_user_session: (String? team_id, String? user_id) -> untyped?
|
|
331
|
+
def resolve_event_user: (Hash[String, untyped] params) -> untyped?
|
|
307
332
|
def events_callback: (Hash[String, untyped] params) -> untyped
|
|
308
333
|
def url_verification: (Hash[String, untyped] params) -> Hash[String, String]
|
|
309
334
|
def handle_block_actions_view: (view: Hash[String, untyped]?, user: untyped, params: Hash[String, untyped]) -> untyped
|
|
335
|
+
def handle_block_actions_message: (payload: Hash[String, untyped], user: untyped, params: Hash[String, untyped]) -> untyped
|
|
336
|
+
def dispatch_event: (handler: Class, params: Hash[String, untyped], current_user: untyped) -> bool
|
|
337
|
+
def route_interaction: (payload: Hash[String, untyped], user: untyped, params: Hash[String, untyped]) -> untyped
|
|
310
338
|
end
|
|
311
339
|
|
|
312
340
|
module GrapeExtension
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: grape-slack-bot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -47,16 +47,22 @@ dependencies:
|
|
|
47
47
|
name: faraday
|
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
|
49
49
|
requirements:
|
|
50
|
-
- - "
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: 2.14.3
|
|
53
|
+
- - "<"
|
|
51
54
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: '
|
|
55
|
+
version: '3.0'
|
|
53
56
|
type: :runtime
|
|
54
57
|
prerelease: false
|
|
55
58
|
version_requirements: !ruby/object:Gem::Requirement
|
|
56
59
|
requirements:
|
|
57
|
-
- - "
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 2.14.3
|
|
63
|
+
- - "<"
|
|
58
64
|
- !ruby/object:Gem::Version
|
|
59
|
-
version: '
|
|
65
|
+
version: '3.0'
|
|
60
66
|
- !ruby/object:Gem::Dependency
|
|
61
67
|
name: activesupport
|
|
62
68
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -97,14 +103,14 @@ dependencies:
|
|
|
97
103
|
requirements:
|
|
98
104
|
- - "~>"
|
|
99
105
|
- !ruby/object:Gem::Version
|
|
100
|
-
version: 1.
|
|
106
|
+
version: 1.5.0
|
|
101
107
|
type: :development
|
|
102
108
|
prerelease: false
|
|
103
109
|
version_requirements: !ruby/object:Gem::Requirement
|
|
104
110
|
requirements:
|
|
105
111
|
- - "~>"
|
|
106
112
|
- !ruby/object:Gem::Version
|
|
107
|
-
version: 1.
|
|
113
|
+
version: 1.5.0
|
|
108
114
|
- !ruby/object:Gem::Dependency
|
|
109
115
|
name: webmock
|
|
110
116
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -231,6 +237,20 @@ dependencies:
|
|
|
231
237
|
- - "~>"
|
|
232
238
|
- !ruby/object:Gem::Version
|
|
233
239
|
version: '2'
|
|
240
|
+
- !ruby/object:Gem::Dependency
|
|
241
|
+
name: bundler-audit
|
|
242
|
+
requirement: !ruby/object:Gem::Requirement
|
|
243
|
+
requirements:
|
|
244
|
+
- - "~>"
|
|
245
|
+
- !ruby/object:Gem::Version
|
|
246
|
+
version: '0.9'
|
|
247
|
+
type: :development
|
|
248
|
+
prerelease: false
|
|
249
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
250
|
+
requirements:
|
|
251
|
+
- - "~>"
|
|
252
|
+
- !ruby/object:Gem::Version
|
|
253
|
+
version: '0.9'
|
|
234
254
|
- !ruby/object:Gem::Dependency
|
|
235
255
|
name: memory_profiler
|
|
236
256
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -299,6 +319,8 @@ files:
|
|
|
299
319
|
- lib/slack_bot/errors.rb
|
|
300
320
|
- lib/slack_bot/event.rb
|
|
301
321
|
- lib/slack_bot/grape_extension.rb
|
|
322
|
+
- lib/slack_bot/grape_helpers/dispatch.rb
|
|
323
|
+
- lib/slack_bot/grape_helpers/interactions.rb
|
|
302
324
|
- lib/slack_bot/interaction.rb
|
|
303
325
|
- lib/slack_bot/logger.rb
|
|
304
326
|
- lib/slack_bot/menu_options.rb
|