chat_sdk-slack 1.0.0 → 1.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d93aacf823c427212c01efbdf76255caf3cd4a534b033b5557a9bbff9aaa8b97
|
|
4
|
+
data.tar.gz: a82ac7a769d42943060042cfaf16179b1a7c3482d9bfd9b268631b59a9723fa8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 49b4f95d68bb451922802284fae8ef36f9dffbc72ae33c44943618e5ca4141410c9f58cc885d353b96a2004ee12544cab4739cfeb0c3ef8fc2557c8bb731f3d1
|
|
7
|
+
data.tar.gz: f1ad880423c30b689eff5476070d3227310752b3122fd97c17a3fe34d8b52a3b0e05f7c67688b1bb47511dc839c0a1704fc3cfb210395d6654a966e119ff199e
|
|
@@ -9,11 +9,12 @@ module ChatSDK
|
|
|
9
9
|
:scheduled_messages
|
|
10
10
|
|
|
11
11
|
def initialize(bot_token: nil, signing_secret: nil,
|
|
12
|
-
client_id: nil, client_secret: nil)
|
|
12
|
+
client_id: nil, client_secret: nil, proxy: nil)
|
|
13
13
|
@bot_token = bot_token || ENV["SLACK_BOT_TOKEN"]
|
|
14
14
|
@signing_secret = signing_secret || ENV["SLACK_SIGNING_SECRET"]
|
|
15
15
|
@client_id = client_id || ENV["SLACK_CLIENT_ID"]
|
|
16
16
|
@client_secret = client_secret || ENV["SLACK_CLIENT_SECRET"]
|
|
17
|
+
@proxy = proxy || ENV["HTTPS_PROXY"] || ENV["https_proxy"]
|
|
17
18
|
|
|
18
19
|
unless @bot_token || @client_id
|
|
19
20
|
raise ChatSDK::ConfigurationError, "Slack bot_token or client_id required"
|
|
@@ -24,7 +25,7 @@ module ChatSDK
|
|
|
24
25
|
::Slack.configure do |config|
|
|
25
26
|
config.token = @bot_token
|
|
26
27
|
end
|
|
27
|
-
@client =
|
|
28
|
+
@client = build_client(token: @bot_token)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
@renderer = BlockKitRenderer.new
|
|
@@ -71,7 +72,7 @@ module ChatSDK
|
|
|
71
72
|
def handle_oauth_callback(code:, redirect_uri: nil)
|
|
72
73
|
raise ChatSDK::ConfigurationError, "client_id required for OAuth" unless @client_id
|
|
73
74
|
|
|
74
|
-
temp_client =
|
|
75
|
+
temp_client = build_client
|
|
75
76
|
params = {
|
|
76
77
|
client_id: @client_id,
|
|
77
78
|
client_secret: @client_secret,
|
|
@@ -267,6 +268,10 @@ module ChatSDK
|
|
|
267
268
|
client.views_open(trigger_id: trigger_id, view: view)
|
|
268
269
|
end
|
|
269
270
|
|
|
271
|
+
def update_modal(view_id:, modal:)
|
|
272
|
+
client.views_update(view_id: view_id, view: @modal_renderer.render(modal))
|
|
273
|
+
end
|
|
274
|
+
|
|
270
275
|
def publish_home_view(user_id:, view:)
|
|
271
276
|
client.views_publish(user_id: user_id, view: view)
|
|
272
277
|
end
|
|
@@ -295,6 +300,21 @@ module ChatSDK
|
|
|
295
300
|
)
|
|
296
301
|
end
|
|
297
302
|
|
|
303
|
+
def send_to_response_url(response_url:, message:)
|
|
304
|
+
msg = ChatSDK::PostableMessage.from(message)
|
|
305
|
+
payload = {}
|
|
306
|
+
apply_message_params(payload, msg)
|
|
307
|
+
Faraday.post(response_url) do |req|
|
|
308
|
+
req.headers["Content-Type"] = "application/json"
|
|
309
|
+
req.body = JSON.generate(payload)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def fetch_thread(channel_id:, thread_id: nil)
|
|
314
|
+
result = client.conversations_info(channel: channel_id)
|
|
315
|
+
result["channel"]
|
|
316
|
+
end
|
|
317
|
+
|
|
298
318
|
def start_typing(channel_id:, thread_id: nil)
|
|
299
319
|
# Slack doesn't have a native typing indicator API for bots
|
|
300
320
|
# This is a no-op but the capability is declared for streaming support
|
|
@@ -312,8 +332,12 @@ module ChatSDK
|
|
|
312
332
|
app_token ||= ENV["SLACK_APP_TOKEN"]
|
|
313
333
|
raise ChatSDK::ConfigurationError, "Slack app_token required for socket mode" unless app_token
|
|
314
334
|
|
|
315
|
-
|
|
316
|
-
|
|
335
|
+
@socket_mode = SocketMode.new(app_token: app_token, bot_client: client)
|
|
336
|
+
@socket_mode.start(&block)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def stop_socket_mode
|
|
340
|
+
@socket_mode&.stop
|
|
317
341
|
end
|
|
318
342
|
|
|
319
343
|
def mention(user_id)
|
|
@@ -330,6 +354,13 @@ module ChatSDK
|
|
|
330
354
|
|
|
331
355
|
private
|
|
332
356
|
|
|
357
|
+
def build_client(token: nil)
|
|
358
|
+
options = {}
|
|
359
|
+
options[:token] = token if token
|
|
360
|
+
options[:proxy] = @proxy if @proxy
|
|
361
|
+
options.empty? ? ::Slack::Web::Client.new : ::Slack::Web::Client.new(options)
|
|
362
|
+
end
|
|
363
|
+
|
|
333
364
|
def resolve_team_client(team_id)
|
|
334
365
|
return unless @client_id && @state
|
|
335
366
|
|
|
@@ -342,7 +373,7 @@ module ChatSDK
|
|
|
342
373
|
installation = get_installation(team_id)
|
|
343
374
|
return unless installation
|
|
344
375
|
|
|
345
|
-
new_client =
|
|
376
|
+
new_client = build_client(token: installation["bot_token"])
|
|
346
377
|
@team_clients[team_id] = new_client
|
|
347
378
|
::Thread.current[:chat_sdk_slack_client] = new_client
|
|
348
379
|
end
|
|
@@ -13,6 +13,8 @@ module ChatSDK
|
|
|
13
13
|
private
|
|
14
14
|
|
|
15
15
|
def render_card(node)
|
|
16
|
+
@used_native_table = false
|
|
17
|
+
@chart_count = 0
|
|
16
18
|
node.children.map { |child| render_node(child) }.compact
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -24,6 +26,8 @@ module ChatSDK
|
|
|
24
26
|
when :fields then render_fields(node)
|
|
25
27
|
when :section then render_section(node)
|
|
26
28
|
when :actions then render_actions(node)
|
|
29
|
+
when :table then render_table(node)
|
|
30
|
+
when :chart then render_chart(node)
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
|
|
@@ -93,7 +97,7 @@ module ChatSDK
|
|
|
93
97
|
type: "button",
|
|
94
98
|
text: {type: "plain_text", text: node.attributes[:text]},
|
|
95
99
|
url: node.attributes[:url],
|
|
96
|
-
action_id: "link_#{node.attributes[:url].hash.abs}"
|
|
100
|
+
action_id: node.attributes[:id] || "link_#{node.attributes[:url].hash.abs}"
|
|
97
101
|
}
|
|
98
102
|
end
|
|
99
103
|
|
|
@@ -119,6 +123,75 @@ module ChatSDK
|
|
|
119
123
|
end
|
|
120
124
|
opt
|
|
121
125
|
end
|
|
126
|
+
|
|
127
|
+
def render_table(node)
|
|
128
|
+
headers = Array(node.attributes[:headers])
|
|
129
|
+
rows = Array(node.attributes[:rows])
|
|
130
|
+
char_count = (headers + rows.flatten).sum { |cell| cell.to_s.length }
|
|
131
|
+
if @used_native_table || rows.length > 100 || headers.length > 20 || char_count > 10_000
|
|
132
|
+
return fallback_block(ChatSDK::Cards::Renderer.new.render(node))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
@used_native_table = true
|
|
136
|
+
rendered_rows = [headers, *rows].map do |row|
|
|
137
|
+
Array(row).map { |cell| {type: "raw_text", text: cell.to_s.empty? ? " " : cell.to_s} }
|
|
138
|
+
end
|
|
139
|
+
return {type: "table", rows: rendered_rows} if rows.empty?
|
|
140
|
+
|
|
141
|
+
block = {
|
|
142
|
+
type: "data_table",
|
|
143
|
+
caption: node.attributes[:caption] || "Table",
|
|
144
|
+
rows: rendered_rows
|
|
145
|
+
}
|
|
146
|
+
block[:page_size] = node.attributes[:page_size].to_i.clamp(1, 100) if node.attributes[:page_size]
|
|
147
|
+
block
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def render_chart(node)
|
|
151
|
+
return fallback_block(node.fallback_text) if @chart_count >= 2
|
|
152
|
+
|
|
153
|
+
block = chart_block(node)
|
|
154
|
+
return fallback_block(node.fallback_text) unless block
|
|
155
|
+
|
|
156
|
+
@chart_count += 1
|
|
157
|
+
block
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def chart_block(node)
|
|
161
|
+
title = node.attributes[:title].to_s
|
|
162
|
+
return if title.empty? || title.length > 50
|
|
163
|
+
|
|
164
|
+
type = node.attributes[:chart_type].to_sym
|
|
165
|
+
if type == :pie
|
|
166
|
+
segments = Array(node.attributes[:segments])
|
|
167
|
+
return unless segments.length.between?(1, 12) && segments.all? { |segment| valid_chart_label?(value_for(segment, :label)) && value_for(segment, :value).to_f.positive? }
|
|
168
|
+
|
|
169
|
+
return {type: "data_visualization", title: title, chart: {type: "pie", segments: segments}}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
categories = Array(node.attributes[:categories])
|
|
173
|
+
series = Array(node.attributes[:series])
|
|
174
|
+
return unless %i[bar area line].include?(type)
|
|
175
|
+
return unless categories.length.between?(1, 20) && categories.all? { |category| valid_chart_label?(category) }
|
|
176
|
+
return unless series.length.between?(1, 12) && series.all? { |item| valid_chart_label?(value_for(item, :name)) }
|
|
177
|
+
|
|
178
|
+
axis = {categories: categories}
|
|
179
|
+
axis[:x_label] = node.attributes[:x_label] if node.attributes[:x_label]
|
|
180
|
+
axis[:y_label] = node.attributes[:y_label] if node.attributes[:y_label]
|
|
181
|
+
{type: "data_visualization", title: title, chart: {type: type.to_s, series: series, axis_config: axis}}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def valid_chart_label?(label)
|
|
185
|
+
label.to_s.length.between?(1, 20)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def value_for(hash, key)
|
|
189
|
+
hash[key] || hash[key.to_s]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def fallback_block(text)
|
|
193
|
+
{type: "section", text: {type: "mrkdwn", text: "```#{text}```"}}
|
|
194
|
+
end
|
|
122
195
|
end
|
|
123
196
|
end
|
|
124
197
|
end
|
|
@@ -68,6 +68,8 @@ module ChatSDK
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def parse_message_event(event, payload)
|
|
71
|
+
return parse_message_updated(event, payload) if event["subtype"] == "message_changed"
|
|
72
|
+
return parse_message_deleted(event, payload) if event["subtype"] == "message_deleted"
|
|
71
73
|
return [] if event["subtype"] && event["subtype"] != "file_share"
|
|
72
74
|
return [] if event["bot_id"]
|
|
73
75
|
|
|
@@ -108,6 +110,72 @@ module ChatSDK
|
|
|
108
110
|
end
|
|
109
111
|
end
|
|
110
112
|
|
|
113
|
+
def parse_message_updated(event, payload)
|
|
114
|
+
return [] if event["hidden"]
|
|
115
|
+
|
|
116
|
+
message_data = event["message"]
|
|
117
|
+
return [] unless message_data
|
|
118
|
+
|
|
119
|
+
channel_id = event["channel"] || message_data["channel"]
|
|
120
|
+
message = slack_message(message_data, channel_id)
|
|
121
|
+
previous = slack_message(event["previous_message"], channel_id) if event["previous_message"]
|
|
122
|
+
[ChatSDK::Events::MessageUpdated.new(
|
|
123
|
+
message: message,
|
|
124
|
+
previous_message: previous,
|
|
125
|
+
thread_id: message.thread_id,
|
|
126
|
+
channel_id: channel_id,
|
|
127
|
+
platform: :slack,
|
|
128
|
+
adapter_name: :slack,
|
|
129
|
+
raw: payload
|
|
130
|
+
)]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_message_deleted(event, payload)
|
|
134
|
+
previous = event["previous_message"]
|
|
135
|
+
message_id = event["deleted_ts"] || previous&.dig("ts")
|
|
136
|
+
return [] unless message_id
|
|
137
|
+
|
|
138
|
+
channel_id = event["channel"] || previous&.dig("channel")
|
|
139
|
+
previous_message = slack_message(previous, channel_id) if previous
|
|
140
|
+
thread_id = previous&.dig("thread_ts") || message_id
|
|
141
|
+
[ChatSDK::Events::MessageDeleted.new(
|
|
142
|
+
message_id: message_id,
|
|
143
|
+
previous_message: previous_message,
|
|
144
|
+
thread_id: thread_id,
|
|
145
|
+
channel_id: channel_id,
|
|
146
|
+
deleted_at: slack_time(event["event_ts"] || event["ts"]),
|
|
147
|
+
platform: :slack,
|
|
148
|
+
adapter_name: :slack,
|
|
149
|
+
raw: payload
|
|
150
|
+
)]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def slack_message(data, channel_id)
|
|
154
|
+
user_id = data["user"] || data.dig("bot_profile", "user_id") || "unknown"
|
|
155
|
+
ChatSDK::Message.new(
|
|
156
|
+
id: data["ts"],
|
|
157
|
+
text: data["text"] || "",
|
|
158
|
+
author: ChatSDK::Author.new(
|
|
159
|
+
id: user_id,
|
|
160
|
+
name: data["username"] || user_id,
|
|
161
|
+
platform: :slack,
|
|
162
|
+
bot: !!data["bot_id"],
|
|
163
|
+
system: user_id == "USLACK"
|
|
164
|
+
),
|
|
165
|
+
thread_id: data["thread_ts"] || data["ts"],
|
|
166
|
+
channel_id: channel_id,
|
|
167
|
+
platform: :slack,
|
|
168
|
+
timestamp: slack_time(data["ts"]),
|
|
169
|
+
raw: data
|
|
170
|
+
)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def slack_time(value)
|
|
174
|
+
Time.at(Float(value)) if value
|
|
175
|
+
rescue ArgumentError, TypeError
|
|
176
|
+
nil
|
|
177
|
+
end
|
|
178
|
+
|
|
111
179
|
def parse_reaction(event, payload, added:)
|
|
112
180
|
[ChatSDK::Events::Reaction.new(
|
|
113
181
|
emoji: event["reaction"],
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
3
5
|
module ChatSDK
|
|
4
6
|
module Slack
|
|
5
7
|
class ModalRenderer
|
|
@@ -49,10 +51,37 @@ module ChatSDK
|
|
|
49
51
|
}
|
|
50
52
|
el[:placeholder] = {type: "plain_text", text: node.attributes[:placeholder]} if node.attributes[:placeholder]
|
|
51
53
|
el
|
|
54
|
+
when :date
|
|
55
|
+
el = {type: "datepicker", action_id: node.attributes[:id]}
|
|
56
|
+
initial_date = valid_initial_date(node.attributes[:initial_value])
|
|
57
|
+
el[:initial_date] = initial_date if initial_date
|
|
58
|
+
el[:placeholder] = {type: "plain_text", text: node.attributes[:placeholder]} if node.attributes[:placeholder]
|
|
59
|
+
el
|
|
60
|
+
when :number
|
|
61
|
+
el = {
|
|
62
|
+
type: "number_input",
|
|
63
|
+
action_id: node.attributes[:id],
|
|
64
|
+
is_decimal_allowed: !!node.attributes[:decimal]
|
|
65
|
+
}
|
|
66
|
+
el[:min_value] = node.attributes[:min].to_s unless node.attributes[:min].nil?
|
|
67
|
+
el[:max_value] = node.attributes[:max].to_s unless node.attributes[:max].nil?
|
|
68
|
+
el[:initial_value] = node.attributes[:initial_value].to_s unless node.attributes[:initial_value].nil?
|
|
69
|
+
el[:placeholder] = {type: "plain_text", text: node.attributes[:placeholder]} if node.attributes[:placeholder]
|
|
70
|
+
el
|
|
52
71
|
end
|
|
53
72
|
block
|
|
54
73
|
end
|
|
55
74
|
|
|
75
|
+
def valid_initial_date(value)
|
|
76
|
+
return unless value
|
|
77
|
+
|
|
78
|
+
parsed = Date.iso8601(value.to_s)
|
|
79
|
+
value.to_s if parsed.iso8601 == value.to_s
|
|
80
|
+
rescue Date::Error
|
|
81
|
+
ChatSDK::Log.warn("Ignoring invalid modal initial date: #{value.inspect}")
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
56
85
|
def render_text(node)
|
|
57
86
|
{
|
|
58
87
|
type: "section",
|