chat_sdk-slack 1.1.0 → 1.2.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/lib/chat_sdk/slack/adapter.rb +111 -11
- data/lib/chat_sdk/slack/block_kit_renderer.rb +63 -12
- data/lib/chat_sdk/slack/event_parser.rb +33 -2
- data/lib/chat_sdk/slack/format_converter.rb +17 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49b9759d50173e826b41f885fcba2f2c276d28574f187ac70bd1932d3da28ab4
|
|
4
|
+
data.tar.gz: 80c4806743ab979c9a6f024c5f7d8081152cdf8eaacb63607728e78518b88a5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c79dccb6431aa33e0c9aa756287b7ec4342e97d4733b42e91f94e5ecfe44267e73dd404890dc13d600700985c6d958f03acad36f52121ec62ba010eb14145019
|
|
7
|
+
data.tar.gz: cebaf0452264d3b98802a1774fd834d77fa3be5b83a9f38848890c0b8ec742f976a0e510edf520f97f06e0375ca33c5fdce5af845043381cdb8a0ba1ee3b351d
|
|
@@ -9,11 +9,14 @@ 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, proxy: nil)
|
|
12
|
+
client_id: nil, client_secret: nil, proxy: nil, bot_user_id: 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
|
+
@bot_user_id = bot_user_id || ENV["SLACK_BOT_USER_ID"]
|
|
18
|
+
@bot_user_ids = {}
|
|
19
|
+
@bot_user_id_mutex = Mutex.new
|
|
17
20
|
@proxy = proxy || ENV["HTTPS_PROXY"] || ENV["https_proxy"]
|
|
18
21
|
|
|
19
22
|
unless @bot_token || @client_id
|
|
@@ -35,6 +38,8 @@ module ChatSDK
|
|
|
35
38
|
|
|
36
39
|
# Returns the per-request client (multi-workspace) or the static client (single-workspace).
|
|
37
40
|
def client
|
|
41
|
+
return @client unless @client_id
|
|
42
|
+
|
|
38
43
|
::Thread.current[:chat_sdk_slack_client] || @client
|
|
39
44
|
end
|
|
40
45
|
|
|
@@ -54,6 +59,7 @@ module ChatSDK
|
|
|
54
59
|
"bot_user_id" => bot_user_id,
|
|
55
60
|
"team_name" => team_name
|
|
56
61
|
})
|
|
62
|
+
invalidate_bot_user_id(team_id)
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def get_installation(team_id)
|
|
@@ -64,6 +70,7 @@ module ChatSDK
|
|
|
64
70
|
|
|
65
71
|
def delete_installation(team_id)
|
|
66
72
|
@team_clients.delete(team_id)
|
|
73
|
+
invalidate_bot_user_id(team_id)
|
|
67
74
|
return unless @state
|
|
68
75
|
|
|
69
76
|
@state.delete(installation_key(team_id))
|
|
@@ -153,7 +160,8 @@ module ChatSDK
|
|
|
153
160
|
resolve_team_client(team_id) if team_id
|
|
154
161
|
end
|
|
155
162
|
|
|
156
|
-
EventParser.parse(parsed)
|
|
163
|
+
events = EventParser.parse(parsed)
|
|
164
|
+
classify_message_events(events, parsed)
|
|
157
165
|
end
|
|
158
166
|
|
|
159
167
|
# Outbound
|
|
@@ -177,7 +185,7 @@ module ChatSDK
|
|
|
177
185
|
)
|
|
178
186
|
end
|
|
179
187
|
|
|
180
|
-
def edit_message(channel_id:, message_id:, message
|
|
188
|
+
def edit_message(channel_id:, message_id:, message:, **_options)
|
|
181
189
|
msg = ChatSDK::PostableMessage.from(message)
|
|
182
190
|
params = {channel: channel_id, ts: message_id}
|
|
183
191
|
|
|
@@ -186,7 +194,7 @@ module ChatSDK
|
|
|
186
194
|
client.chat_update(**params)
|
|
187
195
|
end
|
|
188
196
|
|
|
189
|
-
def delete_message(channel_id:, message_id
|
|
197
|
+
def delete_message(channel_id:, message_id:, **_options)
|
|
190
198
|
client.chat_delete(channel: channel_id, ts: message_id)
|
|
191
199
|
end
|
|
192
200
|
|
|
@@ -201,17 +209,19 @@ module ChatSDK
|
|
|
201
209
|
end
|
|
202
210
|
|
|
203
211
|
def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil)
|
|
204
|
-
|
|
212
|
+
content = io.read
|
|
213
|
+
io.rewind if io.respond_to?(:rewind)
|
|
214
|
+
params = {channel_id: channel_id, filename: filename, content: content}
|
|
205
215
|
params[:thread_ts] = thread_id if thread_id
|
|
206
216
|
params[:initial_comment] = comment if comment
|
|
207
|
-
client.
|
|
217
|
+
client.files_upload_v2(**params)
|
|
208
218
|
end
|
|
209
219
|
|
|
210
|
-
def add_reaction(channel_id:, message_id:, emoji
|
|
220
|
+
def add_reaction(channel_id:, message_id:, emoji:, **_options)
|
|
211
221
|
client.reactions_add(channel: channel_id, timestamp: message_id, name: emoji)
|
|
212
222
|
end
|
|
213
223
|
|
|
214
|
-
def remove_reaction(channel_id:, message_id:, emoji
|
|
224
|
+
def remove_reaction(channel_id:, message_id:, emoji:, **_options)
|
|
215
225
|
client.reactions_remove(channel: channel_id, timestamp: message_id, name: emoji)
|
|
216
226
|
end
|
|
217
227
|
|
|
@@ -315,11 +325,21 @@ module ChatSDK
|
|
|
315
325
|
result["channel"]
|
|
316
326
|
end
|
|
317
327
|
|
|
318
|
-
def start_typing(channel_id:, thread_id: nil)
|
|
328
|
+
def start_typing(channel_id:, thread_id: nil, message_id: nil)
|
|
319
329
|
# Slack doesn't have a native typing indicator API for bots
|
|
320
330
|
# This is a no-op but the capability is declared for streaming support
|
|
321
331
|
end
|
|
322
332
|
|
|
333
|
+
def concurrency_scope(event)
|
|
334
|
+
return unless @client_id
|
|
335
|
+
|
|
336
|
+
raw = event.raw.is_a?(Hash) ? event.raw : {}
|
|
337
|
+
raw_event = raw.is_a?(Hash) ? raw["event"] || raw[:event] || raw : {}
|
|
338
|
+
raw_team = raw.is_a?(Hash) ? raw["team"] || raw[:team] : nil
|
|
339
|
+
raw_team_id = raw_team.is_a?(Hash) ? raw_team["id"] || raw_team[:id] : raw_team
|
|
340
|
+
raw_team_id || raw["team_id"] || raw[:team_id] || raw_event["team_id"] || raw_event[:team_id] || raw_event["team"] || raw_event[:team]
|
|
341
|
+
end
|
|
342
|
+
|
|
323
343
|
# Slack-specific: receive real-time events via Socket Mode WebSocket.
|
|
324
344
|
# Requires the optional 'faye-websocket' gem and an app-level token (xapp-*).
|
|
325
345
|
# Not part of the base adapter contract.
|
|
@@ -331,9 +351,18 @@ module ChatSDK
|
|
|
331
351
|
def start_socket_mode(app_token: nil, &block)
|
|
332
352
|
app_token ||= ENV["SLACK_APP_TOKEN"]
|
|
333
353
|
raise ChatSDK::ConfigurationError, "Slack app_token required for socket mode" unless app_token
|
|
354
|
+
raise ArgumentError, "start requires a block" unless block
|
|
334
355
|
|
|
335
356
|
@socket_mode = SocketMode.new(app_token: app_token, bot_client: client)
|
|
336
|
-
@socket_mode.start
|
|
357
|
+
@socket_mode.start do |event|
|
|
358
|
+
if @client_id
|
|
359
|
+
::Thread.current[:chat_sdk_slack_client] = nil
|
|
360
|
+
team_id = concurrency_scope(event)
|
|
361
|
+
resolve_team_client(team_id) if team_id
|
|
362
|
+
end
|
|
363
|
+
classify_message_events([event], event.raw)
|
|
364
|
+
block.call(event)
|
|
365
|
+
end
|
|
337
366
|
end
|
|
338
367
|
|
|
339
368
|
def stop_socket_mode
|
|
@@ -361,13 +390,83 @@ module ChatSDK
|
|
|
361
390
|
options.empty? ? ::Slack::Web::Client.new : ::Slack::Web::Client.new(options)
|
|
362
391
|
end
|
|
363
392
|
|
|
393
|
+
def classify_message_events(events, payload)
|
|
394
|
+
events.each do |event|
|
|
395
|
+
next unless %i[mention subscribed_message direct_message].include?(event.type)
|
|
396
|
+
|
|
397
|
+
event.message.is_mention = classify_mention(event, payload)
|
|
398
|
+
end
|
|
399
|
+
events
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def classify_mention(event, payload)
|
|
403
|
+
raw_event = payload["event"] || {}
|
|
404
|
+
return true if raw_event["type"] == "app_mention"
|
|
405
|
+
|
|
406
|
+
bot_user_id = resolve_bot_user_id(event)
|
|
407
|
+
return true if event.type == :mention && bot_user_id.nil?
|
|
408
|
+
return nil if bot_user_id.nil?
|
|
409
|
+
|
|
410
|
+
text = raw_event["text"].to_s
|
|
411
|
+
masked_text = ChatSDK::Format::Mentions.mask_code_spans(text)
|
|
412
|
+
text_mentions_bot = /<@#{Regexp.escape(bot_user_id)}(?:\|[^>]*)?>/.match?(masked_text)
|
|
413
|
+
text_mentions_bot || rich_text_mentions_bot?(raw_event["blocks"], bot_user_id)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def resolve_bot_user_id(event)
|
|
417
|
+
team_id = concurrency_scope(event) if @client_id
|
|
418
|
+
if @client_id
|
|
419
|
+
installation = get_installation(team_id) if team_id
|
|
420
|
+
return installation["bot_user_id"] if installation && installation["bot_user_id"]
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
return @bot_user_id if @bot_user_id
|
|
424
|
+
|
|
425
|
+
current_client = if @client_id
|
|
426
|
+
resolve_team_client(team_id) if team_id
|
|
427
|
+
else
|
|
428
|
+
@client
|
|
429
|
+
end
|
|
430
|
+
return unless current_client
|
|
431
|
+
|
|
432
|
+
@bot_user_id_mutex.synchronize do
|
|
433
|
+
cache_key = @client_id ? (team_id || :default) : :default
|
|
434
|
+
return @bot_user_ids[cache_key] if @bot_user_ids.key?(cache_key)
|
|
435
|
+
|
|
436
|
+
bot_user_id = begin
|
|
437
|
+
current_client.auth_test["user_id"]
|
|
438
|
+
rescue ::Slack::Web::Api::Errors::SlackError, Faraday::Error, SocketError, Timeout::Error
|
|
439
|
+
nil
|
|
440
|
+
end
|
|
441
|
+
@bot_user_ids[cache_key] = bot_user_id if bot_user_id
|
|
442
|
+
bot_user_id
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def invalidate_bot_user_id(team_id)
|
|
447
|
+
@bot_user_id_mutex.synchronize { @bot_user_ids.delete(team_id) }
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def rich_text_mentions_bot?(blocks, bot_user_id)
|
|
451
|
+
Array(blocks).any? { |block| rich_text_element_mentions_bot?(block, bot_user_id) }
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def rich_text_element_mentions_bot?(element, bot_user_id)
|
|
455
|
+
return false unless element.is_a?(Hash)
|
|
456
|
+
return false if %w[rich_text_preformatted preformatted code].include?(element["type"])
|
|
457
|
+
return false if element.dig("style", "code")
|
|
458
|
+
return true if element["type"] == "user" && element["user_id"] == bot_user_id
|
|
459
|
+
|
|
460
|
+
Array(element["elements"]).any? { |child| rich_text_element_mentions_bot?(child, bot_user_id) }
|
|
461
|
+
end
|
|
462
|
+
|
|
364
463
|
def resolve_team_client(team_id)
|
|
365
464
|
return unless @client_id && @state
|
|
366
465
|
|
|
367
466
|
cached = @team_clients[team_id]
|
|
368
467
|
if cached
|
|
369
468
|
::Thread.current[:chat_sdk_slack_client] = cached
|
|
370
|
-
return
|
|
469
|
+
return cached
|
|
371
470
|
end
|
|
372
471
|
|
|
373
472
|
installation = get_installation(team_id)
|
|
@@ -376,6 +475,7 @@ module ChatSDK
|
|
|
376
475
|
new_client = build_client(token: installation["bot_token"])
|
|
377
476
|
@team_clients[team_id] = new_client
|
|
378
477
|
::Thread.current[:chat_sdk_slack_client] = new_client
|
|
478
|
+
new_client
|
|
379
479
|
end
|
|
380
480
|
|
|
381
481
|
def installation_key(team_id)
|
|
@@ -15,7 +15,15 @@ module ChatSDK
|
|
|
15
15
|
def render_card(node)
|
|
16
16
|
@used_native_table = false
|
|
17
17
|
@chart_count = 0
|
|
18
|
-
|
|
18
|
+
blocks = []
|
|
19
|
+
if node.attributes[:title]
|
|
20
|
+
blocks << {type: "header", text: {type: "plain_text", text: truncate_text(node.attributes[:title], 150)}}
|
|
21
|
+
end
|
|
22
|
+
if node.attributes[:subtitle]
|
|
23
|
+
blocks << {type: "context", elements: [{type: "mrkdwn", text: truncate_mrkdwn(node.attributes[:subtitle], 3000)}]}
|
|
24
|
+
end
|
|
25
|
+
blocks.concat(node.children.map { |child| render_node(child) }.compact)
|
|
26
|
+
blocks
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def render_node(node)
|
|
@@ -28,19 +36,20 @@ module ChatSDK
|
|
|
28
36
|
when :actions then render_actions(node)
|
|
29
37
|
when :table then render_table(node)
|
|
30
38
|
when :chart then render_chart(node)
|
|
39
|
+
when :text_input then {type: "section", text: {type: "mrkdwn", text: truncate_mrkdwn(node.attributes[:label], 3000)}}
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
42
|
|
|
34
43
|
def render_text(node)
|
|
35
44
|
{
|
|
36
45
|
type: "section",
|
|
37
|
-
text: {type: "mrkdwn", text: node.attributes[:content]}
|
|
46
|
+
text: {type: "mrkdwn", text: truncate_mrkdwn(node.attributes[:content], 3000)}
|
|
38
47
|
}
|
|
39
48
|
end
|
|
40
49
|
|
|
41
50
|
def render_image(node)
|
|
42
51
|
block = {type: "image", image_url: node.attributes[:url]}
|
|
43
|
-
block[:alt_text] = node.attributes[:alt] || " "
|
|
52
|
+
block[:alt_text] = truncate_text(node.attributes[:alt] || " ", 2000)
|
|
44
53
|
block
|
|
45
54
|
end
|
|
46
55
|
|
|
@@ -48,7 +57,7 @@ module ChatSDK
|
|
|
48
57
|
{
|
|
49
58
|
type: "section",
|
|
50
59
|
fields: node.children.map do |field|
|
|
51
|
-
{type: "mrkdwn", text: "*#{field.attributes[:label]}*\n#{field.attributes[:value]}"}
|
|
60
|
+
{type: "mrkdwn", text: truncate_mrkdwn("*#{field.attributes[:label]}*\n#{field.attributes[:value]}", 2000)}
|
|
52
61
|
end
|
|
53
62
|
}
|
|
54
63
|
end
|
|
@@ -57,7 +66,7 @@ module ChatSDK
|
|
|
57
66
|
block = {type: "section"}
|
|
58
67
|
text_children = node.children.select { |c| c.type == :text }
|
|
59
68
|
if text_children.any?
|
|
60
|
-
block[:text] = {type: "mrkdwn", text: text_children.map { |t| t.attributes[:content] }.join("\n")}
|
|
69
|
+
block[:text] = {type: "mrkdwn", text: truncate_mrkdwn(text_children.map { |t| t.attributes[:content] }.join("\n"), 3000)}
|
|
61
70
|
end
|
|
62
71
|
block
|
|
63
72
|
end
|
|
@@ -80,7 +89,7 @@ module ChatSDK
|
|
|
80
89
|
def render_button(node)
|
|
81
90
|
btn = {
|
|
82
91
|
type: "button",
|
|
83
|
-
text: {type: "plain_text", text: node.attributes[:text]},
|
|
92
|
+
text: {type: "plain_text", text: truncate_text(node.attributes[:text], 75)},
|
|
84
93
|
action_id: node.attributes[:id]
|
|
85
94
|
}
|
|
86
95
|
btn[:value] = node.attributes[:value] if node.attributes[:value]
|
|
@@ -89,16 +98,19 @@ module ChatSDK
|
|
|
89
98
|
elsif node.attributes[:style] == :danger
|
|
90
99
|
btn[:style] = "danger"
|
|
91
100
|
end
|
|
101
|
+
btn[:visible_to_user_ids] = node.attributes[:visible_to] if node.attributes[:visible_to]&.any?
|
|
92
102
|
btn
|
|
93
103
|
end
|
|
94
104
|
|
|
95
105
|
def render_link_button(node)
|
|
96
|
-
{
|
|
106
|
+
button = {
|
|
97
107
|
type: "button",
|
|
98
|
-
text: {type: "plain_text", text: node.attributes[:text]},
|
|
108
|
+
text: {type: "plain_text", text: truncate_text(node.attributes[:text], 75)},
|
|
99
109
|
url: node.attributes[:url],
|
|
100
110
|
action_id: node.attributes[:id] || "link_#{node.attributes[:url].hash.abs}"
|
|
101
111
|
}
|
|
112
|
+
button[:visible_to_user_ids] = node.attributes[:visible_to] if node.attributes[:visible_to]&.any?
|
|
113
|
+
button
|
|
102
114
|
end
|
|
103
115
|
|
|
104
116
|
def render_select(node)
|
|
@@ -108,18 +120,18 @@ module ChatSDK
|
|
|
108
120
|
options: node.children.map { |opt| render_option(opt) }
|
|
109
121
|
}
|
|
110
122
|
if node.attributes[:placeholder]
|
|
111
|
-
sel[:placeholder] = {type: "plain_text", text: node.attributes[:placeholder]}
|
|
123
|
+
sel[:placeholder] = {type: "plain_text", text: truncate_text(node.attributes[:placeholder], 150)}
|
|
112
124
|
end
|
|
113
125
|
sel
|
|
114
126
|
end
|
|
115
127
|
|
|
116
128
|
def render_option(node)
|
|
117
129
|
opt = {
|
|
118
|
-
text: {type: "plain_text", text: node.attributes[:text]},
|
|
130
|
+
text: {type: "plain_text", text: truncate_text(node.attributes[:text], 75)},
|
|
119
131
|
value: node.attributes[:value]
|
|
120
132
|
}
|
|
121
133
|
if node.attributes[:description]
|
|
122
|
-
opt[:description] = {type: "plain_text", text: node.attributes[:description]}
|
|
134
|
+
opt[:description] = {type: "plain_text", text: truncate_text(node.attributes[:description], 75)}
|
|
123
135
|
end
|
|
124
136
|
opt
|
|
125
137
|
end
|
|
@@ -190,7 +202,46 @@ module ChatSDK
|
|
|
190
202
|
end
|
|
191
203
|
|
|
192
204
|
def fallback_block(text)
|
|
193
|
-
{type: "section", text: {type: "mrkdwn", text: "```#{text}```"}}
|
|
205
|
+
{type: "section", text: {type: "mrkdwn", text: truncate_mrkdwn("```#{text}```", 3000)}}
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def truncate_text(text, limit)
|
|
209
|
+
text = text.to_s
|
|
210
|
+
return text if text.length <= limit
|
|
211
|
+
|
|
212
|
+
"#{text[0, limit - 1]}…"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def truncate_mrkdwn(text, limit)
|
|
216
|
+
text = text.to_s
|
|
217
|
+
return text if text.length <= limit
|
|
218
|
+
|
|
219
|
+
prefix = clean_prefix(text[0, limit - 1])
|
|
220
|
+
4.times do
|
|
221
|
+
bold_close = prefix.count("*").odd? ? "*" : ""
|
|
222
|
+
fence_close = open_fence?(prefix) ? "\n```" : ""
|
|
223
|
+
available = limit - 1 - bold_close.length - fence_close.length
|
|
224
|
+
next_prefix = clean_prefix(text[0, available])
|
|
225
|
+
break if next_prefix == prefix
|
|
226
|
+
|
|
227
|
+
prefix = next_prefix
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
bold_close = prefix.count("*").odd? ? "*" : ""
|
|
231
|
+
fence_close = open_fence?(prefix) ? "\n```" : ""
|
|
232
|
+
suffix = "#{bold_close}…#{fence_close}"
|
|
233
|
+
prefix = clean_prefix(prefix[0, limit - suffix.length]) while prefix.length + suffix.length > limit
|
|
234
|
+
"#{prefix}#{suffix}"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def clean_prefix(text)
|
|
238
|
+
text
|
|
239
|
+
.sub(/<(?:[@#!]|[a-z][a-z\d+.-]*(?::|$))[^>]*\z/i, "")
|
|
240
|
+
.sub(/&(?:#?[a-z\d]*)?\z/i, "")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def open_fence?(text)
|
|
244
|
+
text.scan("```").length.odd?
|
|
194
245
|
end
|
|
195
246
|
end
|
|
196
247
|
end
|
|
@@ -50,7 +50,7 @@ module ChatSDK
|
|
|
50
50
|
)
|
|
51
51
|
message = ChatSDK::Message.new(
|
|
52
52
|
id: event["ts"],
|
|
53
|
-
text: event
|
|
53
|
+
text: message_text(event),
|
|
54
54
|
author: author,
|
|
55
55
|
thread_id: event["thread_ts"] || event["ts"],
|
|
56
56
|
channel_id: event["channel"],
|
|
@@ -80,7 +80,7 @@ module ChatSDK
|
|
|
80
80
|
)
|
|
81
81
|
message = ChatSDK::Message.new(
|
|
82
82
|
id: event["ts"],
|
|
83
|
-
text: event
|
|
83
|
+
text: message_text(event),
|
|
84
84
|
author: author,
|
|
85
85
|
thread_id: event["thread_ts"] || event["ts"],
|
|
86
86
|
channel_id: event["channel"],
|
|
@@ -130,6 +130,37 @@ module ChatSDK
|
|
|
130
130
|
)]
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
def message_text(event)
|
|
134
|
+
blocks = event["blocks"]
|
|
135
|
+
text = if blocks.is_a?(Array) && !blocks.empty? && blocks.all? { |block| %w[section header context image divider actions table].include?(block["type"]) }
|
|
136
|
+
blocks.filter_map { |block| block_text(block) }.join("\n")
|
|
137
|
+
else
|
|
138
|
+
event["text"] || ""
|
|
139
|
+
end
|
|
140
|
+
FormatConverter.new.normalize_inbound_text(text)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def block_text(block)
|
|
144
|
+
case block["type"]
|
|
145
|
+
when "section", "header"
|
|
146
|
+
texts = [block.dig("text", "text")]
|
|
147
|
+
texts.concat(Array(block["fields"]).map { |field| field["text"] })
|
|
148
|
+
texts.compact.reject(&:empty?).join("\n")
|
|
149
|
+
when "context"
|
|
150
|
+
Array(block["elements"]).filter_map { |element| element.dig("text") }.reject(&:empty?).join("\n")
|
|
151
|
+
when "image"
|
|
152
|
+
block["alt_text"]
|
|
153
|
+
when "actions"
|
|
154
|
+
Array(block["elements"]).filter_map do |element|
|
|
155
|
+
element.dig("text", "text") || element.dig("placeholder", "text")
|
|
156
|
+
end.reject(&:empty?).join(" ")
|
|
157
|
+
when "table"
|
|
158
|
+
Array(block["rows"]).map do |row|
|
|
159
|
+
Array(row).map { |cell| cell.dig("text") || cell.to_s }.join(" | ")
|
|
160
|
+
end.join("\n")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
133
164
|
def parse_message_deleted(event, payload)
|
|
134
165
|
previous = event["previous_message"]
|
|
135
166
|
message_id = event["deleted_ts"] || previous&.dig("ts")
|
|
@@ -19,6 +19,23 @@ module ChatSDK
|
|
|
19
19
|
parts.map.with_index { |part, i| i.odd? ? part : convert_md_to_mrkdwn(part) }.join
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def normalize_inbound_text(text)
|
|
23
|
+
return "" if text.nil? || text.empty?
|
|
24
|
+
|
|
25
|
+
split_code_blocks(text).map.with_index do |part, index|
|
|
26
|
+
if index.odd?
|
|
27
|
+
part
|
|
28
|
+
else
|
|
29
|
+
part
|
|
30
|
+
.gsub("<!here>", "@here")
|
|
31
|
+
.gsub("<!channel>", "@channel")
|
|
32
|
+
.gsub("<!everyone>", "@everyone")
|
|
33
|
+
.gsub(/<!subteam\^[^|>]+\|(@[^>]+)>/, '\1')
|
|
34
|
+
.gsub(/<(?:mailto|tel):[^>|]+\|([^>]+)>/, '\1')
|
|
35
|
+
end
|
|
36
|
+
end.join
|
|
37
|
+
end
|
|
38
|
+
|
|
22
39
|
private
|
|
23
40
|
|
|
24
41
|
# ── Slack mrkdwn → Markdown ──────────────────────────────────────────
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chat_sdk-slack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Quentin Rousseau
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '1.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '1.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: slack-ruby-client
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|