grape-slack-bot 1.8.2 → 2.0.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.
@@ -16,90 +16,176 @@ module SlackBot
16
16
  data["error"]
17
17
  end
18
18
 
19
+ def rate_limited?
20
+ response.status == 429 || (data["ok"] == false && data["error"] == "rate_limited")
21
+ end
22
+
23
+ def retry_after
24
+ response.headers["Retry-After"]&.to_i || 60
25
+ end
26
+
27
+ def slack_error?
28
+ !ok? && error.present?
29
+ end
30
+
31
+ def authentication_error?
32
+ slack_error? && %w[invalid_auth account_inactive].include?(error)
33
+ end
34
+
19
35
  def data
20
36
  JSON.parse(response.body)
37
+ rescue JSON::ParserError => e
38
+ SlackBot::Logger.error("Failed to parse Slack API response: #{e.message}")
39
+ {"ok" => false, "error" => "invalid_json_response"}
21
40
  end
22
41
  end
23
42
 
24
43
  class ApiClient
44
+ # Slack API base URL
45
+ SLACK_API_BASE_URL = "https://slack.com/api/"
46
+ # Request timeout in seconds
47
+ REQUEST_TIMEOUT = 30
48
+ # Connection timeout in seconds
49
+ CONNECTION_TIMEOUT = 10
50
+
25
51
  attr_reader :client
26
52
  def initialize(authorization_token: ENV["SLACK_BOT_API_TOKEN"])
27
53
  authorization_token_available = !authorization_token.nil? && authorization_token.is_a?(String) && !authorization_token.empty?
28
- raise "Slack bot API token is not set" if !authorization_token_available
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
29
65
 
30
66
  @client =
31
67
  Faraday.new do |conn|
32
68
  conn.request :url_encoded
33
69
  conn.adapter Faraday.default_adapter
34
- conn.url_prefix = "https://slack.com/api/"
70
+ conn.url_prefix = SLACK_API_BASE_URL
35
71
  conn.headers["Content-Type"] = "application/json; charset=utf-8"
36
72
  conn.headers["Authorization"] = "Bearer #{authorization_token}"
73
+ conn.options.timeout = REQUEST_TIMEOUT
74
+ conn.options.open_timeout = CONNECTION_TIMEOUT
37
75
  end
38
76
  end
39
77
 
40
78
  def views_open(trigger_id:, view:)
41
- ApiResponse.new { client.post("views.open", {trigger_id: trigger_id, view: view}.to_json) }
79
+ ApiResponse.new do
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
42
84
  end
43
85
 
44
86
  def views_update(view_id:, view:)
45
- ApiResponse.new { client.post("views.update", {view_id: view_id, view: view}.to_json) }
87
+ ApiResponse.new do
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
46
92
  end
47
93
 
48
94
  def chat_post_message(channel:, text:, blocks:)
49
- ApiResponse.new { client.post("chat.postMessage", {channel: channel, text: text, blocks: blocks}.to_json) }
95
+ ApiResponse.new do
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
50
100
  end
51
101
 
52
102
  def chat_update(channel:, ts:, text:, blocks:)
53
- ApiResponse.new { client.post("chat.update", {channel: channel, ts: ts, text: text, blocks: blocks}.to_json) }
103
+ ApiResponse.new do
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
54
108
  end
55
109
 
56
110
  def chat_delete(channel:, ts:)
57
- ApiResponse.new { client.post("chat.delete", {channel: channel, ts: ts}.to_json) }
111
+ ApiResponse.new do
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
58
116
  end
59
117
 
60
118
  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)
61
- ApiResponse.new { client.post("chat.unfurl", {
62
- channel: channel,
63
- ts: ts,
64
- unfurls: unfurls,
65
- source: source,
66
- unfurl_id: unfurl_id,
67
- user_auth_blocks: user_auth_blocks,
68
- user_auth_message: user_auth_message,
69
- user_auth_required: user_auth_required,
70
- user_auth_url: user_auth_url
71
- }.to_json) }
119
+ ApiResponse.new do
120
+ client.post("chat.unfurl", {
121
+ channel: channel,
122
+ ts: ts,
123
+ unfurls: unfurls,
124
+ source: source,
125
+ unfurl_id: unfurl_id,
126
+ user_auth_blocks: user_auth_blocks,
127
+ user_auth_message: user_auth_message,
128
+ user_auth_required: user_auth_required,
129
+ user_auth_url: user_auth_url
130
+ }.to_json)
131
+ rescue Faraday::Error => e
132
+ raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
133
+ end
72
134
  end
73
135
 
74
136
  def chat_schedule_message(channel:, text:, post_at:, blocks: nil)
75
- ApiResponse.new { client.post("chat.scheduleMessage", {channel: channel, text: text, post_at: post_at, blocks: blocks}.to_json) }
137
+ ApiResponse.new do
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
76
142
  end
77
143
 
78
144
  def scheduled_messages_list(channel: nil, cursor: nil, latest: nil, limit: nil, oldest: nil, team_id: nil)
79
- ApiResponse.new { client.post("scheduled_messages.list", {
80
- channel: channel,
81
- cursor: cursor,
82
- latest: latest,
83
- limit: limit,
84
- oldest: oldest,
85
- team_id: team_id
86
- }.to_json) }
145
+ ApiResponse.new do
146
+ client.post("scheduled_messages.list", {
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
87
157
  end
88
158
 
89
159
  def chat_delete_scheduled_message(channel:, scheduled_message_id:)
90
- ApiResponse.new { client.post("chat.deleteScheduledMessage", {channel: channel, scheduled_message_id: scheduled_message_id}.to_json) }
160
+ ApiResponse.new do
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
91
165
  end
92
166
 
93
167
  def chat_get_permalink(channel:, message_ts:)
94
- ApiResponse.new { client.post("chat.getPermalink", {channel: channel, message_ts: message_ts}.to_json) }
168
+ ApiResponse.new do
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
95
173
  end
96
174
 
97
175
  def users_info(user_id:)
98
- ApiResponse.new { client.post("users.info", {user: user_id}.to_json) }
176
+ ApiResponse.new do
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
99
181
  end
100
182
 
101
183
  def views_publish(user_id:, view:)
102
- ApiResponse.new { client.post("views.publish", {user_id: user_id, view: view}.to_json) }
184
+ ApiResponse.new do
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
103
189
  end
104
190
 
105
191
  def users_list(cursor: nil, limit: 200, include_locale: nil, team_id: nil)
@@ -108,7 +194,11 @@ module SlackBot
108
194
  args[:limit] = limit if limit
109
195
  args[:include_locale] = include_locale if include_locale
110
196
  args[:team_id] = team_id if team_id
111
- ApiResponse.new { client.post("users.list", args.to_json) }
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
112
202
  end
113
203
 
114
204
  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)
@@ -125,7 +215,11 @@ module SlackBot
125
215
  args[:parse] = parse if parse
126
216
  args[:thread_ts] = thread_ts if thread_ts
127
217
  args[:username] = username if username
128
- ApiResponse.new { client.post("chat.postEphemeral", args.to_json) }
218
+ ApiResponse.new do
219
+ client.post("chat.postEphemeral", args.to_json)
220
+ rescue Faraday::Error => e
221
+ raise SlackBot::Errors::SlackApiError.new("Network error: #{e.message}")
222
+ end
129
223
  end
130
224
  end
131
225
  end
@@ -44,6 +44,8 @@ module SlackBot
44
44
  self.args = @parser.new(raw_args).call&.with_indifferent_access || {}
45
45
  end
46
46
 
47
+ attr_reader :raw_args
48
+
47
49
  def to_s
48
50
  @builder.new(args).call
49
51
  end
@@ -37,7 +37,8 @@ module SlackBot
37
37
  create(id: id, class_name: class_name, user: user, channel_id: channel_id, payload: payload, config: config, expires_in: expires_in, user_scope: user_scope)
38
38
  end
39
39
 
40
- attr_reader :id, :data, :args, :config, :expires_in, :user_scope
40
+ attr_reader :data, :args, :config, :expires_in, :user_scope
41
+ attr_accessor :id
41
42
  def initialize(id: nil, class_name: nil, user: nil, channel_id: nil, payload: nil, config: nil, expires_in: nil, user_scope: nil, view_id: nil)
42
43
  @id = id
43
44
  @data = {
@@ -50,7 +51,7 @@ module SlackBot
50
51
  @args = SlackBot::Args.new
51
52
  @config = config || SlackBot::Config.current_instance
52
53
  @expires_in = expires_in || CALLBACK_RECORD_EXPIRES_IN
53
- @user_scope = user_scope.nil? ? true : user_scope
54
+ @user_scope = user_scope.nil? || user_scope
54
55
  end
55
56
 
56
57
  def reload
@@ -102,18 +103,34 @@ module SlackBot
102
103
  @data[:user_id] = user&.id
103
104
  end
104
105
 
106
+ def class_name
107
+ data[:class_name]
108
+ end
109
+
105
110
  def class_name=(class_name)
106
111
  @data[:class_name] = class_name
107
112
  end
108
113
 
114
+ def channel_id
115
+ data[:channel_id]
116
+ end
117
+
109
118
  def channel_id=(channel_id)
110
119
  @data[:channel_id] = channel_id
111
120
  end
112
121
 
122
+ def payload
123
+ data[:payload]
124
+ end
125
+
113
126
  def payload=(payload)
114
127
  @data[:payload] = payload
115
128
  end
116
129
 
130
+ def view_id
131
+ data[:view_id]
132
+ end
133
+
117
134
  def view_id=(view_id)
118
135
  @data[:view_id] = view_id
119
136
  end
@@ -138,6 +155,10 @@ module SlackBot
138
155
  super
139
156
  end
140
157
 
158
+ def respond_to_missing?(method_name, include_private = false)
159
+ data.key?(method_name.to_sym) || (data[:payload].is_a?(Hash) && data[:payload].key?(method_name.to_s)) || super
160
+ end
161
+
141
162
  def read_view_callback_id
142
163
  return if view_id.blank?
143
164
 
@@ -159,13 +180,17 @@ module SlackBot
159
180
  end
160
181
 
161
182
  def storage_key
162
- raise "User is required for scoped callback" if user.blank?
183
+ if user.blank?
184
+ raise SlackBot::Errors::SlackApiError.new("User is required for scoped callback")
185
+ end
163
186
 
164
187
  "#{CALLBACK_KEY_PREFIX}:u#{user.id}:#{id}"
165
188
  end
166
189
 
167
190
  def view_storage_key
168
- raise "User is required for scoped callback" if user.blank?
191
+ if user.blank?
192
+ raise SlackBot::Errors::SlackApiError.new("User is required for scoped callback")
193
+ end
169
194
 
170
195
  "#{CALLBACK_KEY_PREFIX}:u#{user.id}:#{view_id}"
171
196
  end
@@ -1,15 +1,15 @@
1
1
  module SlackBot
2
2
  class CallbackStorage
3
3
  def read(*_args, **_kwargs)
4
- raise "Not implemented"
4
+ raise SlackBot::Errors::NotImplementedError.new("CallbackStorage#read must be implemented by subclass")
5
5
  end
6
6
 
7
7
  def write(*_args, **_kwargs)
8
- raise "Not implemented"
8
+ raise SlackBot::Errors::NotImplementedError.new("CallbackStorage#write must be implemented by subclass")
9
9
  end
10
10
 
11
11
  def delete(*_args, **_kwargs)
12
- raise "Not implemented"
12
+ raise SlackBot::Errors::NotImplementedError.new("CallbackStorage#delete must be implemented by subclass")
13
13
  end
14
14
  end
15
15
  end
@@ -80,11 +80,15 @@ module SlackBot
80
80
 
81
81
  def handler_class(class_name, klass)
82
82
  @handler_classes ||= {}
83
+ return if class_name.nil?
84
+
83
85
  @handler_classes[class_name.to_sym] = klass
84
86
  end
85
87
 
86
88
  def find_handler_class(class_name)
87
89
  @handler_classes ||= {}
90
+ return if class_name.nil?
91
+
88
92
  @handler_classes.fetch(class_name.to_sym)
89
93
  rescue KeyError
90
94
  raise SlackBot::Errors::HandlerClassNotFound.new(class_name, handler_classes: @handler_classes)
@@ -62,5 +62,25 @@ module SlackBot
62
62
 
63
63
  class PublishViewError < SlackResponseError
64
64
  end
65
+
66
+ class CallbackUserMismatchError < SlackBot::Error
67
+ end
68
+
69
+ class InvalidPayloadError < SlackBot::Error
70
+ end
71
+
72
+ class SlackApiError < SlackBot::Error
73
+ end
74
+
75
+ class UnknownActionTypeError < SlackBot::Error
76
+ attr_reader :action_type
77
+ def initialize(action_type)
78
+ @action_type = action_type
79
+ super("Unknown action type: #{action_type}")
80
+ end
81
+ end
82
+
83
+ class NotImplementedError < SlackBot::Error
84
+ end
65
85
  end
66
86
  end
@@ -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
@@ -19,9 +25,26 @@ module SlackBot
19
25
  raise SlackBot::Errors::SignatureAuthenticationError.new("Missing signature headers")
20
26
  end
21
27
 
22
- request.body.rewind
23
- request_body = request.body.read
24
- request.body.rewind
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
39
+
40
+ request_body = if request.body
41
+ request.body.rewind if request.body.respond_to?(:rewind)
42
+ body_content = request.body.read
43
+ request.body.rewind if request.body.respond_to?(:rewind)
44
+ body_content
45
+ else
46
+ ""
47
+ end
25
48
 
26
49
  sig_basestring = "v0:#{timestamp}:#{request_body}"
27
50
  my_signature =
@@ -61,11 +84,9 @@ module SlackBot
61
84
  end
62
85
 
63
86
  def verify_current_user!
64
- if current_user
65
- true
66
- else
67
- raise SlackBot::Errors::UserAuthenticationError.new("User is not authorized")
68
- end
87
+ return true if current_user
88
+
89
+ raise SlackBot::Errors::UserAuthenticationError.new("User is not authorized")
69
90
  end
70
91
 
71
92
  def events_callback(params)
@@ -73,7 +94,7 @@ module SlackBot
73
94
 
74
95
  SlackBot::DevConsole.log_input "SlackApi::Events#events_callback: #{params.inspect}"
75
96
  handler = config.find_event_handler(params[:event][:type].to_sym)
76
- return if handler.blank?
97
+ return false if handler.blank?
77
98
 
78
99
  event = handler.new(params: params, current_user: current_user)
79
100
  event.call
@@ -84,6 +105,12 @@ module SlackBot
84
105
  {challenge: params[:challenge]}
85
106
  end
86
107
 
108
+ def validate_callback_user!(callback, user)
109
+ if callback.user_id != user.id
110
+ raise SlackBot::Errors::CallbackUserMismatchError.new("Callback user is not equal to action user")
111
+ end
112
+ end
113
+
87
114
  def handle_block_actions_view(view:, user:, params:)
88
115
  callback_id = view&.dig("callback_id")
89
116
 
@@ -92,12 +119,11 @@ module SlackBot
92
119
 
93
120
  SlackBot::DevConsole.log_check "SlackApi::Interactions##{__method__}: #{callback.id} #{callback.payload} #{callback.user_id} #{user&.id}"
94
121
 
95
- if callback.user_id != user.id
96
- raise "Callback user is not equal to action user"
97
- end
122
+ validate_callback_user!(callback, user)
98
123
 
99
- interaction_klass = callback.handler_class&.interaction_klass
100
- return if interaction_klass.blank?
124
+ handler_class_obj = callback.handler_class
125
+ interaction_klass = handler_class_obj&.interaction_klass if handler_class_obj&.respond_to?(:interaction_klass)
126
+ return false if interaction_klass.blank?
101
127
 
102
128
  interaction_klass.new(current_user: user, params: params, callback: callback, config: config).call
103
129
  end
@@ -107,9 +133,16 @@ module SlackBot
107
133
  def self.included(base)
108
134
  base.format :json
109
135
  base.content_type :json, "application/json"
110
- base.use ActionDispatch::RemoteIp
136
+ base.use ActionDispatch::RemoteIp if defined?(ActionDispatch::RemoteIp)
111
137
  base.helpers SlackBot::GrapeHelpers
112
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
+ base.rescue_from SlackBot::Error do |e|
143
+ error!({error: e.message}, 200)
144
+ end
145
+
113
146
  base.before do
114
147
  verify_slack_signature!
115
148
  end
@@ -135,7 +168,11 @@ module SlackBot
135
168
  verify_current_user! if action.only_user?
136
169
 
137
170
  result = action.call
138
- return body false if !result
171
+ if !result
172
+ body false
173
+ status 200
174
+ return
175
+ end
139
176
 
140
177
  result
141
178
  end
@@ -143,7 +180,11 @@ module SlackBot
143
180
 
144
181
  base.resource :interactions do
145
182
  post do
146
- payload = JSON.parse(params[:payload])
183
+ begin
184
+ payload = JSON.parse(params[:payload])
185
+ rescue JSON::ParserError => e
186
+ raise SlackBot::Errors::InvalidPayloadError.new("Invalid JSON payload: #{e.message}")
187
+ end
147
188
 
148
189
  action_user_session =
149
190
  resolve_user_session(
@@ -161,10 +202,14 @@ module SlackBot
161
202
  params: params
162
203
  )
163
204
  else
164
- raise "Unknown action type: #{action_type}"
205
+ raise SlackBot::Errors::UnknownActionTypeError.new(action_type)
165
206
  end
166
207
 
167
- return body false if result.blank?
208
+ if result.blank? || result == false
209
+ body false
210
+ status 200
211
+ return
212
+ end
168
213
 
169
214
  result
170
215
  end
@@ -178,9 +223,15 @@ module SlackBot
178
223
  url_verification(params)
179
224
  when "event_callback"
180
225
  events_callback(params)
226
+ else
227
+ raise SlackBot::Errors::UnknownActionTypeError.new(params[:type])
181
228
  end
182
229
 
183
- return body false if result.blank?
230
+ if result.blank? || result == false
231
+ body false
232
+ status 200
233
+ return
234
+ end
184
235
 
185
236
  result
186
237
  end
@@ -195,7 +246,11 @@ module SlackBot
195
246
  raise SlackBot::Errors::MenuOptionsNotImplemented.new if menu_options_klass.blank?
196
247
 
197
248
  menu_options = menu_options_klass.new(current_user: current_user, params: params, config: config).call
198
- return body false if menu_options.blank?
249
+ if menu_options.blank?
250
+ body false
251
+ status 200
252
+ return
253
+ end
199
254
 
200
255
  menu_options
201
256
  end
@@ -149,7 +149,11 @@ module SlackBot
149
149
  end
150
150
 
151
151
  def payload
152
- @payload ||= JSON.parse(params[:payload])
152
+ @payload ||= begin
153
+ JSON.parse(params[:payload])
154
+ rescue JSON::ParserError => e
155
+ raise SlackBot::Errors::InvalidPayloadError.new("Invalid JSON payload: #{e.message}")
156
+ end
153
157
  end
154
158
  end
155
159
  end
@@ -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
@@ -6,5 +6,9 @@ module SlackBot
6
6
  @params = params
7
7
  @config = config || SlackBot::Config.current_instance
8
8
  end
9
+
10
+ def call
11
+ nil
12
+ end
9
13
  end
10
14
  end
@@ -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[:title]
33
+ text: context&.dig(:title) || ""
30
34
  },
31
35
  blocks: [
32
- {type: "section", text: {type: "mrkdwn", text: context[:text]}}
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 = "1.8.2".freeze
28
+ VERSION = "2.0.0".freeze
26
29
  end