chat_sdk-slack 1.0.1 → 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,
|
|
@@ -353,6 +354,13 @@ module ChatSDK
|
|
|
353
354
|
|
|
354
355
|
private
|
|
355
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
|
+
|
|
356
364
|
def resolve_team_client(team_id)
|
|
357
365
|
return unless @client_id && @state
|
|
358
366
|
|
|
@@ -365,7 +373,7 @@ module ChatSDK
|
|
|
365
373
|
installation = get_installation(team_id)
|
|
366
374
|
return unless installation
|
|
367
375
|
|
|
368
|
-
new_client =
|
|
376
|
+
new_client = build_client(token: installation["bot_token"])
|
|
369
377
|
@team_clients[team_id] = new_client
|
|
370
378
|
::Thread.current[:chat_sdk_slack_client] = new_client
|
|
371
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",
|