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
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,25 @@
|
|
|
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
|
+
|
|
16
|
+
## 2.0.1 (2026-05-31)
|
|
17
|
+
|
|
18
|
+
- Allow `grape` 3.x by expanding the runtime dependency to `< 4.0`
|
|
19
|
+
- Replace SimpleCov and JUnit formatter test wiring with Polyrun coverage and failure reporting
|
|
20
|
+
- Move lint and type-check execution into Polyrun `before_suite` hooks
|
|
21
|
+
- Refresh development and release tooling, CI, and repository maintenance configuration
|
|
22
|
+
|
|
3
23
|
## 2.0.0 (2025-11-06)
|
|
4
24
|
|
|
5
25
|
- Fix status code handling for empty/false responses - ensure 200 OK instead of 204 No Content
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# grape-slack-bot.rb
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/grape-slack-bot) [](https://github.com/amkisko/grape-slack-bot.rb/actions/workflows/test.yml)
|
|
3
|
+
[](https://badge.fury.io/rb/grape-slack-bot) [](https://github.com/amkisko/grape-slack-bot.rb/actions/workflows/test.yml)
|
|
4
4
|
|
|
5
5
|
Extensible Slack bot implementation gem for [ruby-grape](https://github.com/ruby-grape/grape) with support for slash commands, interactive components, events, and views.
|
|
6
6
|
|
|
@@ -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 ||=
|
|
@@ -509,11 +512,16 @@ The gem implements Slack's signature verification with the following security fe
|
|
|
509
512
|
|
|
510
513
|
## Development
|
|
511
514
|
|
|
512
|
-
```
|
|
515
|
+
```sh
|
|
513
516
|
bundle install
|
|
514
|
-
bundle exec
|
|
515
|
-
bundle exec
|
|
516
|
-
|
|
517
|
+
bundle exec polyrun hook run before_suite
|
|
518
|
+
bundle exec polyrun parallel-rspec --workers 1 --merge-failures
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
To generate a local coverage report with Polyrun:
|
|
522
|
+
|
|
523
|
+
```sh
|
|
524
|
+
POLYRUN_COVERAGE=1 bundle exec polyrun parallel-rspec --workers 1 --merge-failures
|
|
517
525
|
```
|
|
518
526
|
|
|
519
527
|
For development and testing purposes you can use [Cloudflare Argo Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps) to expose your local development environment to the internet.
|
|
@@ -528,7 +536,7 @@ For easiness of getting information, most of endpoints have `SlackBot::DevConsol
|
|
|
528
536
|
|
|
529
537
|
### Code Quality
|
|
530
538
|
|
|
531
|
-
The gem uses
|
|
539
|
+
The gem uses a StandardRB-compatible RuboCop configuration for consistent code style, with Polyrun orchestrating the lint and type-check hooks. Run `bundle exec polyrun hook run before_suite` to execute the configured checks.
|
|
532
540
|
|
|
533
541
|
The gem includes [RBS](https://github.com/ruby/rbs) type signatures in the `sig/` directory for better type checking and IDE support. Type signatures are included in the gem package.
|
|
534
542
|
|
|
@@ -557,7 +565,7 @@ gem push grape-slack-bot-*.gem
|
|
|
557
565
|
Or use the release script:
|
|
558
566
|
|
|
559
567
|
```sh
|
|
560
|
-
usr/bin/release.
|
|
568
|
+
usr/bin/release.rb
|
|
561
569
|
```
|
|
562
570
|
|
|
563
571
|
## License
|
data/grape-slack-bot.gemspec
CHANGED
|
@@ -23,26 +23,29 @@ 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
|
-
gem.add_runtime_dependency "grape", ">= 1.6", "<
|
|
34
|
-
gem.add_runtime_dependency "faraday", "
|
|
32
|
+
gem.add_runtime_dependency "grape", ">= 1.6", "< 4.0"
|
|
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
|
-
gem.add_development_dependency "rspec", "~> 3
|
|
36
|
+
gem.add_development_dependency "rspec", "~> 3"
|
|
37
|
+
gem.add_development_dependency "polyrun", "~> 1.5.0"
|
|
38
38
|
gem.add_development_dependency "webmock", "~> 3"
|
|
39
|
-
gem.add_development_dependency "rake", "~> 13
|
|
40
|
-
gem.add_development_dependency "
|
|
41
|
-
gem.add_development_dependency "
|
|
42
|
-
gem.add_development_dependency "
|
|
43
|
-
gem.add_development_dependency "standard", "~>
|
|
44
|
-
gem.add_development_dependency "
|
|
45
|
-
gem.add_development_dependency "
|
|
46
|
-
gem.add_development_dependency "
|
|
47
|
-
gem.add_development_dependency "
|
|
39
|
+
gem.add_development_dependency "rake", "~> 13"
|
|
40
|
+
gem.add_development_dependency "standard", "~> 1.52"
|
|
41
|
+
gem.add_development_dependency "standard-custom", "~> 1.0"
|
|
42
|
+
gem.add_development_dependency "standard-performance", "~> 1.8"
|
|
43
|
+
gem.add_development_dependency "standard-rspec", "~> 0.3"
|
|
44
|
+
gem.add_development_dependency "rubocop-rspec", "~> 3.8"
|
|
45
|
+
gem.add_development_dependency "rubocop-thread_safety", "~> 0.7"
|
|
46
|
+
gem.add_development_dependency "appraisal", "~> 2"
|
|
47
|
+
gem.add_development_dependency "bundler-audit", "~> 0.9"
|
|
48
|
+
gem.add_development_dependency "memory_profiler", "~> 1"
|
|
49
|
+
gem.add_development_dependency "rbs", "~> 3"
|
|
50
|
+
gem.add_development_dependency "rack-test", "~> 2"
|
|
48
51
|
end
|
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}"
|
|
@@ -47,76 +63,37 @@ module SlackBot
|
|
|
47
63
|
REQUEST_TIMEOUT = 30
|
|
48
64
|
# Connection timeout in seconds
|
|
49
65
|
CONNECTION_TIMEOUT = 10
|
|
66
|
+
TOKEN_FORMAT = /\Axox[bpa]-/
|
|
50
67
|
|
|
51
68
|
attr_reader :client
|
|
52
|
-
def initialize(authorization_token: ENV["SLACK_BOT_API_TOKEN"])
|
|
53
|
-
authorization_token_available = !authorization_token.nil? && authorization_token.is_a?(String) && !authorization_token.empty?
|
|
54
|
-
unless authorization_token_available
|
|
55
|
-
raise SlackBot::Errors::SlackApiError.new("Slack bot API token is not set")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Validate token format
|
|
59
|
-
# Bot tokens: xoxb-, User tokens: xoxp-, App tokens: xoxa-
|
|
60
|
-
# For this gem, we primarily expect bot tokens (xoxb-)
|
|
61
|
-
# Allow test tokens (starting with "test_") for testing purposes
|
|
62
|
-
unless authorization_token.start_with?("test_") || authorization_token.match?(/\Axox[bpa]-/)
|
|
63
|
-
raise SlackBot::Errors::SlackApiError.new("Invalid Slack API token format")
|
|
64
|
-
end
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
conn.adapter Faraday.default_adapter
|
|
70
|
-
conn.url_prefix = SLACK_API_BASE_URL
|
|
71
|
-
conn.headers["Content-Type"] = "application/json; charset=utf-8"
|
|
72
|
-
conn.headers["Authorization"] = "Bearer #{authorization_token}"
|
|
73
|
-
conn.options.timeout = REQUEST_TIMEOUT
|
|
74
|
-
conn.options.open_timeout = CONNECTION_TIMEOUT
|
|
75
|
-
end
|
|
70
|
+
def initialize(authorization_token: ENV["SLACK_BOT_API_TOKEN"])
|
|
71
|
+
validate_authorization_token!(authorization_token)
|
|
72
|
+
@client = build_client(authorization_token)
|
|
76
73
|
end
|
|
77
74
|
|
|
78
75
|
def views_open(trigger_id:, view:)
|
|
79
|
-
|
|
80
|
-
client.post("views.open", {trigger_id: trigger_id, view: view}.to_json)
|
|
81
|
-
rescue Faraday::Error => e
|
|
82
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
83
|
-
end
|
|
76
|
+
perform_request { client.post("views.open", {trigger_id: trigger_id, view: view}.to_json) }
|
|
84
77
|
end
|
|
85
78
|
|
|
86
79
|
def views_update(view_id:, view:)
|
|
87
|
-
|
|
88
|
-
client.post("views.update", {view_id: view_id, view: view}.to_json)
|
|
89
|
-
rescue Faraday::Error => e
|
|
90
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
91
|
-
end
|
|
80
|
+
perform_request { client.post("views.update", {view_id: view_id, view: view}.to_json) }
|
|
92
81
|
end
|
|
93
82
|
|
|
94
83
|
def chat_post_message(channel:, text:, blocks:)
|
|
95
|
-
|
|
96
|
-
client.post("chat.postMessage", {channel: channel, text: text, blocks: blocks}.to_json)
|
|
97
|
-
rescue Faraday::Error => e
|
|
98
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
99
|
-
end
|
|
84
|
+
perform_request { client.post("chat.postMessage", {channel: channel, text: text, blocks: blocks}.to_json) }
|
|
100
85
|
end
|
|
101
86
|
|
|
102
87
|
def chat_update(channel:, ts:, text:, blocks:)
|
|
103
|
-
|
|
104
|
-
client.post("chat.update", {channel: channel, ts: ts, text: text, blocks: blocks}.to_json)
|
|
105
|
-
rescue Faraday::Error => e
|
|
106
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
107
|
-
end
|
|
88
|
+
perform_request { client.post("chat.update", {channel: channel, ts: ts, text: text, blocks: blocks}.to_json) }
|
|
108
89
|
end
|
|
109
90
|
|
|
110
91
|
def chat_delete(channel:, ts:)
|
|
111
|
-
|
|
112
|
-
client.post("chat.delete", {channel: channel, ts: ts}.to_json)
|
|
113
|
-
rescue Faraday::Error => e
|
|
114
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
115
|
-
end
|
|
92
|
+
perform_request { client.post("chat.delete", {channel: channel, ts: ts}.to_json) }
|
|
116
93
|
end
|
|
117
94
|
|
|
118
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)
|
|
119
|
-
|
|
96
|
+
perform_request do
|
|
120
97
|
client.post("chat.unfurl", {
|
|
121
98
|
channel: channel,
|
|
122
99
|
ts: ts,
|
|
@@ -128,98 +105,92 @@ module SlackBot
|
|
|
128
105
|
user_auth_required: user_auth_required,
|
|
129
106
|
user_auth_url: user_auth_url
|
|
130
107
|
}.to_json)
|
|
131
|
-
rescue Faraday::Error => e
|
|
132
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
133
108
|
end
|
|
134
109
|
end
|
|
135
110
|
|
|
136
111
|
def chat_schedule_message(channel:, text:, post_at:, blocks: nil)
|
|
137
|
-
|
|
138
|
-
client.post("chat.scheduleMessage", {channel: channel, text: text, post_at: post_at, blocks: blocks}.to_json)
|
|
139
|
-
rescue Faraday::Error => e
|
|
140
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
141
|
-
end
|
|
112
|
+
perform_request { client.post("chat.scheduleMessage", {channel: channel, text: text, post_at: post_at, blocks: blocks}.to_json) }
|
|
142
113
|
end
|
|
143
114
|
|
|
144
115
|
def scheduled_messages_list(channel: nil, cursor: nil, latest: nil, limit: nil, oldest: nil, team_id: nil)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
channel: channel,
|
|
148
|
-
cursor: cursor,
|
|
149
|
-
latest: latest,
|
|
150
|
-
limit: limit,
|
|
151
|
-
oldest: oldest,
|
|
152
|
-
team_id: team_id
|
|
153
|
-
}.to_json)
|
|
154
|
-
rescue Faraday::Error => e
|
|
155
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
156
|
-
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) }
|
|
157
118
|
end
|
|
158
119
|
|
|
159
120
|
def chat_delete_scheduled_message(channel:, scheduled_message_id:)
|
|
160
|
-
|
|
161
|
-
client.post("chat.deleteScheduledMessage", {channel: channel, scheduled_message_id: scheduled_message_id}.to_json)
|
|
162
|
-
rescue Faraday::Error => e
|
|
163
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
164
|
-
end
|
|
121
|
+
perform_request { client.post("chat.deleteScheduledMessage", {channel: channel, scheduled_message_id: scheduled_message_id}.to_json) }
|
|
165
122
|
end
|
|
166
123
|
|
|
167
124
|
def chat_get_permalink(channel:, message_ts:)
|
|
168
|
-
|
|
169
|
-
client.post("chat.getPermalink", {channel: channel, message_ts: message_ts}.to_json)
|
|
170
|
-
rescue Faraday::Error => e
|
|
171
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
172
|
-
end
|
|
125
|
+
perform_request { client.post("chat.getPermalink", {channel: channel, message_ts: message_ts}.to_json) }
|
|
173
126
|
end
|
|
174
127
|
|
|
175
128
|
def users_info(user_id:)
|
|
176
|
-
|
|
177
|
-
client.post("users.info", {user: user_id}.to_json)
|
|
178
|
-
rescue Faraday::Error => e
|
|
179
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
180
|
-
end
|
|
129
|
+
perform_request { client.post("users.info", {user: user_id}.to_json) }
|
|
181
130
|
end
|
|
182
131
|
|
|
183
132
|
def views_publish(user_id:, view:)
|
|
184
|
-
|
|
185
|
-
client.post("views.publish", {user_id: user_id, view: view}.to_json)
|
|
186
|
-
rescue Faraday::Error => e
|
|
187
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
188
|
-
end
|
|
133
|
+
perform_request { client.post("views.publish", {user_id: user_id, view: view}.to_json) }
|
|
189
134
|
end
|
|
190
135
|
|
|
191
136
|
def users_list(cursor: nil, limit: 200, include_locale: nil, team_id: nil)
|
|
192
|
-
args =
|
|
193
|
-
|
|
194
|
-
args[:limit] = limit if limit
|
|
195
|
-
args[:include_locale] = include_locale if include_locale
|
|
196
|
-
args[:team_id] = team_id if team_id
|
|
197
|
-
ApiResponse.new do
|
|
198
|
-
client.post("users.list", args.to_json)
|
|
199
|
-
rescue Faraday::Error => e
|
|
200
|
-
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
201
|
-
end
|
|
137
|
+
args = compact_payload(cursor: cursor, limit: limit, include_locale: include_locale, team_id: team_id)
|
|
138
|
+
perform_request { client.post("users.list", args.to_json) }
|
|
202
139
|
end
|
|
203
140
|
|
|
204
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)
|
|
205
|
-
args =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
142
|
+
args = compact_payload(
|
|
143
|
+
channel: channel,
|
|
144
|
+
user: user,
|
|
145
|
+
text: text,
|
|
146
|
+
as_user: as_user,
|
|
147
|
+
attachments: attachments,
|
|
148
|
+
blocks: blocks,
|
|
149
|
+
icon_emoji: icon_emoji,
|
|
150
|
+
icon_url: icon_url,
|
|
151
|
+
link_names: link_names,
|
|
152
|
+
parse: parse,
|
|
153
|
+
thread_ts: thread_ts,
|
|
154
|
+
username: username
|
|
155
|
+
)
|
|
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
|
|
220
164
|
rescue Faraday::Error => e
|
|
221
165
|
raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
|
|
222
166
|
end
|
|
223
167
|
end
|
|
168
|
+
|
|
169
|
+
def validate_authorization_token!(authorization_token)
|
|
170
|
+
raise SlackBot::Errors::SlackApiError.new("Slack bot API token is not set") unless valid_authorization_token?(authorization_token)
|
|
171
|
+
return if authorization_token.start_with?("test_") || authorization_token.match?(TOKEN_FORMAT)
|
|
172
|
+
|
|
173
|
+
raise SlackBot::Errors::SlackApiError.new("Invalid Slack API token format")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def valid_authorization_token?(authorization_token)
|
|
177
|
+
authorization_token.is_a?(String) && !authorization_token.empty?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def build_client(authorization_token)
|
|
181
|
+
Faraday.new do |conn|
|
|
182
|
+
conn.request :url_encoded
|
|
183
|
+
conn.adapter Faraday.default_adapter
|
|
184
|
+
conn.url_prefix = SLACK_API_BASE_URL
|
|
185
|
+
conn.headers["Content-Type"] = "application/json; charset=utf-8"
|
|
186
|
+
conn.headers["Authorization"] = "Bearer #{authorization_token}"
|
|
187
|
+
conn.options.timeout = REQUEST_TIMEOUT
|
|
188
|
+
conn.options.open_timeout = CONNECTION_TIMEOUT
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def compact_payload(**payload)
|
|
193
|
+
payload.compact
|
|
194
|
+
end
|
|
224
195
|
end
|
|
225
196
|
end
|
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
|