chat_sdk-teams 1.2.0 → 1.4.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/teams/activity_parser.rb +39 -0
- data/lib/chat_sdk/teams/adapter.rb +22 -11
- data/lib/chat_sdk/teams/adaptive_card_renderer.rb +89 -19
- data/lib/chat_sdk/teams.rb +2 -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: f16daba221cded2918a20444a752ac5c47d62579091e385fc09a916f9886cee9
|
|
4
|
+
data.tar.gz: 84470e0eaf8edc9eaeafd70d5e541ed0553c1ede14a1ee627f735d9f31aa8081
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea22ecba125cdbc9865e043d570ea2641cd4df7bd6cb841fed57e2fe999a9027c1194b874d07e6757ca9033268ec82466ec982ea41e1362695b356dd0f33f9e9
|
|
7
|
+
data.tar.gz: 140ab32b11655a4732c06d15d0911600d6730d92f8f49963340b7e7a056b9e3050e893cd67e923bd32dccf4ab7b06e7d1797538c267322fea093c379e2a55702
|
|
@@ -133,6 +133,10 @@ module ChatSDK
|
|
|
133
133
|
action_id ||= action["verb"] if action.is_a?(Hash)
|
|
134
134
|
action_id ||= data["actionId"] || data["action"] ||
|
|
135
135
|
(action unless action.is_a?(Hash)) || activity["name"] || "invoke"
|
|
136
|
+
if action_id == AUTO_SUBMIT_ACTION_ID
|
|
137
|
+
return auto_submit_actions(data, activity, conversation_id, user)
|
|
138
|
+
end
|
|
139
|
+
|
|
136
140
|
value = data["value"] || (nested_data unless nested_data.is_a?(Hash)) || data
|
|
137
141
|
value = value.is_a?(Hash) ? JSON.generate(value) : value.to_s
|
|
138
142
|
|
|
@@ -223,6 +227,41 @@ module ChatSDK
|
|
|
223
227
|
values.empty? ? nil : values
|
|
224
228
|
end
|
|
225
229
|
|
|
230
|
+
def auto_submit_actions(data, activity, conversation_id, user)
|
|
231
|
+
values = auto_submit_values(data)
|
|
232
|
+
values = nil if values.empty?
|
|
233
|
+
|
|
234
|
+
data.each_with_object([]) do |(action_id, input_value), events|
|
|
235
|
+
next if %w[actionId msteams].include?(action_id)
|
|
236
|
+
|
|
237
|
+
events << ChatSDK::Events::Action.new(
|
|
238
|
+
action_id: action_id,
|
|
239
|
+
value: submit_value_to_string(input_value),
|
|
240
|
+
values: values,
|
|
241
|
+
user: user,
|
|
242
|
+
thread_id: conversation_id,
|
|
243
|
+
channel_id: conversation_id,
|
|
244
|
+
platform: :teams,
|
|
245
|
+
adapter_name: :teams,
|
|
246
|
+
raw: activity
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def auto_submit_values(data)
|
|
252
|
+
data.each_with_object({}) do |(key, value), inputs|
|
|
253
|
+
next if %w[actionId msteams].include?(key)
|
|
254
|
+
|
|
255
|
+
input_value = submit_value_to_string(value)
|
|
256
|
+
inputs[key] = input_value unless input_value.nil?
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def submit_value_to_string(value)
|
|
261
|
+
return value if value.is_a?(String)
|
|
262
|
+
value.to_s if value.is_a?(Numeric)
|
|
263
|
+
end
|
|
264
|
+
|
|
226
265
|
def bot_mentioned?(activity, bot_app_id)
|
|
227
266
|
return false unless bot_app_id
|
|
228
267
|
|
|
@@ -6,6 +6,7 @@ module ChatSDK
|
|
|
6
6
|
module Teams
|
|
7
7
|
class Adapter < ChatSDK::Adapter::Base
|
|
8
8
|
DEFAULT_SERVICE_URL = "https://smba.trafficmanager.net/teams/"
|
|
9
|
+
USER_SERVICE_URL_TTL = 30 * 24 * 60 * 60
|
|
9
10
|
VERIFIED_JWT_CLAIMS_ENV_KEY = "chat_sdk.teams.verified_jwt_claims"
|
|
10
11
|
|
|
11
12
|
capabilities :edit_messages, :delete_messages, :threads, :direct_messages,
|
|
@@ -58,8 +59,10 @@ module ChatSDK
|
|
|
58
59
|
|
|
59
60
|
conversation_id = activity.dig("conversation", "id")
|
|
60
61
|
claims = rack_request.env[VERIFIED_JWT_CLAIMS_ENV_KEY]
|
|
61
|
-
if
|
|
62
|
-
register_service_url(conversation_id, activity["serviceUrl"])
|
|
62
|
+
if trusted_service_url?(activity["serviceUrl"], claims)
|
|
63
|
+
register_service_url(conversation_id, activity["serviceUrl"]) if conversation_id
|
|
64
|
+
user_id = activity.dig("from", "id")
|
|
65
|
+
@state&.set(user_service_url_key(user_id), activity["serviceUrl"], ttl: USER_SERVICE_URL_TTL) if user_id
|
|
63
66
|
end
|
|
64
67
|
|
|
65
68
|
ActivityParser.parse(activity, bot_app_id: @app_id)
|
|
@@ -149,7 +152,7 @@ module ChatSDK
|
|
|
149
152
|
|
|
150
153
|
def open_dm(user_id)
|
|
151
154
|
require_capability!(:direct_messages)
|
|
152
|
-
service_url = @service_urls.values.first || @default_service_url
|
|
155
|
+
service_url = @state&.get(user_service_url_key(user_id)) || @service_urls.values.first || @default_service_url
|
|
153
156
|
|
|
154
157
|
payload = {
|
|
155
158
|
"bot" => {"id" => @app_id},
|
|
@@ -208,8 +211,10 @@ module ChatSDK
|
|
|
208
211
|
@state&.set(service_url_key(conversation_id), service_url)
|
|
209
212
|
end
|
|
210
213
|
|
|
211
|
-
def webhook_response(events)
|
|
212
|
-
|
|
214
|
+
def webhook_response(events, rack_request = nil)
|
|
215
|
+
invoke_activity = rack_request&.respond_to?(:body) && invoke_activity?(rack_request)
|
|
216
|
+
invoke_event = events.any? { |event| event.raw.is_a?(Hash) && event.raw["type"] == "invoke" }
|
|
217
|
+
return unless invoke_activity || invoke_event
|
|
213
218
|
|
|
214
219
|
body = {
|
|
215
220
|
"statusCode" => 200,
|
|
@@ -221,6 +226,13 @@ module ChatSDK
|
|
|
221
226
|
|
|
222
227
|
private
|
|
223
228
|
|
|
229
|
+
def invoke_activity?(rack_request)
|
|
230
|
+
activity = read_json_body(rack_request)
|
|
231
|
+
activity.is_a?(Hash) && activity["type"] == "invoke"
|
|
232
|
+
rescue JSON::ParserError
|
|
233
|
+
false
|
|
234
|
+
end
|
|
235
|
+
|
|
224
236
|
def build_activity(postable_message)
|
|
225
237
|
activity = {"type" => "message"}
|
|
226
238
|
|
|
@@ -248,12 +260,7 @@ module ChatSDK
|
|
|
248
260
|
end
|
|
249
261
|
|
|
250
262
|
def service_url_for(channel_id)
|
|
251
|
-
url = @service_urls[channel_id]
|
|
252
|
-
if !url && @state
|
|
253
|
-
url = @state.get(service_url_key(channel_id))
|
|
254
|
-
@service_urls[channel_id] = url if url
|
|
255
|
-
end
|
|
256
|
-
url ||= @default_service_url
|
|
263
|
+
url = @service_urls[channel_id] || @state&.get(service_url_key(channel_id)) || @default_service_url
|
|
257
264
|
unless url
|
|
258
265
|
raise ChatSDK::PlatformError.new(
|
|
259
266
|
"No service URL for conversation #{channel_id}. Register it first or receive an activity.",
|
|
@@ -267,6 +274,10 @@ module ChatSDK
|
|
|
267
274
|
"chat_sdk:teams:service_url:#{conversation_id}"
|
|
268
275
|
end
|
|
269
276
|
|
|
277
|
+
def user_service_url_key(user_id)
|
|
278
|
+
"chat_sdk:teams:user_service_url:#{user_id}"
|
|
279
|
+
end
|
|
280
|
+
|
|
270
281
|
def trusted_service_url?(service_url, claims)
|
|
271
282
|
return false unless service_url.is_a?(String)
|
|
272
283
|
|
|
@@ -6,7 +6,10 @@ module ChatSDK
|
|
|
6
6
|
def render(node)
|
|
7
7
|
case node.type
|
|
8
8
|
when :card then render_card(node)
|
|
9
|
-
else
|
|
9
|
+
else
|
|
10
|
+
rendered = render_node(node)
|
|
11
|
+
elements = rendered.is_a?(Array) ? rendered.flatten.compact : [rendered].compact
|
|
12
|
+
wrap_card(elements)
|
|
10
13
|
end
|
|
11
14
|
end
|
|
12
15
|
|
|
@@ -15,6 +18,13 @@ module ChatSDK
|
|
|
15
18
|
def render_card(node)
|
|
16
19
|
body = []
|
|
17
20
|
pending_separator = false
|
|
21
|
+
if node.attributes[:image_url] && !node.attributes[:image_url].empty?
|
|
22
|
+
body << {
|
|
23
|
+
"type" => "Image",
|
|
24
|
+
"url" => node.attributes[:image_url],
|
|
25
|
+
"size" => "Stretch"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
18
28
|
|
|
19
29
|
node.children.each do |child|
|
|
20
30
|
if child.type == :divider
|
|
@@ -25,16 +35,15 @@ module ChatSDK
|
|
|
25
35
|
element = render_node(child)
|
|
26
36
|
next unless element
|
|
27
37
|
|
|
38
|
+
elements = element.is_a?(Array) ? element.compact : [element]
|
|
39
|
+
next if elements.empty?
|
|
40
|
+
|
|
28
41
|
if pending_separator
|
|
29
|
-
|
|
42
|
+
elements.first["separator"] = true
|
|
30
43
|
pending_separator = false
|
|
31
44
|
end
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
body.concat(element)
|
|
35
|
-
else
|
|
36
|
-
body << element
|
|
37
|
-
end
|
|
46
|
+
body.concat(elements)
|
|
38
47
|
end
|
|
39
48
|
|
|
40
49
|
wrap_card(body, width: node.attributes[:width])
|
|
@@ -54,6 +63,7 @@ module ChatSDK
|
|
|
54
63
|
def render_node(node)
|
|
55
64
|
case node.type
|
|
56
65
|
when :text then render_text(node)
|
|
66
|
+
when :link then render_link(node)
|
|
57
67
|
when :divider then nil # handled in render_card
|
|
58
68
|
when :image then render_image(node)
|
|
59
69
|
when :fields then render_fields(node)
|
|
@@ -62,6 +72,7 @@ module ChatSDK
|
|
|
62
72
|
when :button then render_action_submit(node)
|
|
63
73
|
when :link_button then render_action_open_url(node)
|
|
64
74
|
when :select then render_select(node)
|
|
75
|
+
when :radio_select then render_radio_select(node)
|
|
65
76
|
when :text_input then render_text_input(node)
|
|
66
77
|
when :table then render_table(node)
|
|
67
78
|
when :chart then render_chart(node)
|
|
@@ -69,11 +80,22 @@ module ChatSDK
|
|
|
69
80
|
end
|
|
70
81
|
|
|
71
82
|
def render_text(node)
|
|
72
|
-
{
|
|
83
|
+
text = {
|
|
73
84
|
"type" => "TextBlock",
|
|
74
85
|
"text" => node.attributes[:content],
|
|
75
86
|
"wrap" => true
|
|
76
87
|
}
|
|
88
|
+
text["weight"] = "Bolder" if node.attributes[:style] == :bold
|
|
89
|
+
text["isSubtle"] = true if node.attributes[:style] == :muted
|
|
90
|
+
text
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def render_link(node)
|
|
94
|
+
{
|
|
95
|
+
"type" => "TextBlock",
|
|
96
|
+
"text" => "[#{node.attributes[:label]}](#{node.attributes[:url]})",
|
|
97
|
+
"wrap" => true
|
|
98
|
+
}
|
|
77
99
|
end
|
|
78
100
|
|
|
79
101
|
def render_image(node)
|
|
@@ -124,19 +146,37 @@ module ChatSDK
|
|
|
124
146
|
end
|
|
125
147
|
|
|
126
148
|
def render_actions(node)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
end
|
|
149
|
+
elements = []
|
|
150
|
+
actions = []
|
|
151
|
+
has_inputs = false
|
|
152
|
+
has_buttons = false
|
|
133
153
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
node.children.each do |child|
|
|
155
|
+
case child.type
|
|
156
|
+
when :button
|
|
157
|
+
has_buttons = true
|
|
158
|
+
actions << render_action_submit(child)
|
|
159
|
+
when :link_button
|
|
160
|
+
actions << render_action_open_url(child)
|
|
161
|
+
when :select
|
|
162
|
+
has_inputs = true
|
|
163
|
+
elements << render_select(child)
|
|
164
|
+
when :radio_select
|
|
165
|
+
has_inputs = true
|
|
166
|
+
elements << render_radio_select(child)
|
|
167
|
+
end
|
|
139
168
|
end
|
|
169
|
+
|
|
170
|
+
if has_inputs && !has_buttons
|
|
171
|
+
actions << {
|
|
172
|
+
"type" => "Action.Submit",
|
|
173
|
+
"title" => "Submit",
|
|
174
|
+
"data" => {"actionId" => AUTO_SUBMIT_ACTION_ID}
|
|
175
|
+
}
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
elements << {"type" => "ActionSet", "actions" => actions} unless actions.empty?
|
|
179
|
+
elements
|
|
140
180
|
end
|
|
141
181
|
|
|
142
182
|
def render_action_submit(node)
|
|
@@ -163,6 +203,11 @@ module ChatSDK
|
|
|
163
203
|
}
|
|
164
204
|
action["id"] = node.attributes[:id] if node.attributes[:id]
|
|
165
205
|
action["tooltip"] = node.attributes[:tooltip] if node.attributes[:tooltip]
|
|
206
|
+
if node.attributes[:style] == :primary
|
|
207
|
+
action["style"] = "positive"
|
|
208
|
+
elsif node.attributes[:style] == :danger
|
|
209
|
+
action["style"] = "destructive"
|
|
210
|
+
end
|
|
166
211
|
action
|
|
167
212
|
end
|
|
168
213
|
|
|
@@ -178,6 +223,30 @@ module ChatSDK
|
|
|
178
223
|
end
|
|
179
224
|
}
|
|
180
225
|
input["placeholder"] = node.attributes[:placeholder] if node.attributes[:placeholder]
|
|
226
|
+
if [node.attributes[:label], node.attributes[:initial_option], node.attributes[:optional]].any? { |value| !value.nil? }
|
|
227
|
+
input["label"] = node.attributes[:label] if node.attributes[:label]
|
|
228
|
+
input["style"] = "compact"
|
|
229
|
+
input["isRequired"] = !node.attributes[:optional]
|
|
230
|
+
end
|
|
231
|
+
input["value"] = node.attributes[:initial_option] unless node.attributes[:initial_option].nil?
|
|
232
|
+
input
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def render_radio_select(node)
|
|
236
|
+
input = {
|
|
237
|
+
"type" => "Input.ChoiceSet",
|
|
238
|
+
"id" => node.attributes[:id],
|
|
239
|
+
"label" => node.attributes[:label],
|
|
240
|
+
"style" => "expanded",
|
|
241
|
+
"isRequired" => !node.attributes[:optional],
|
|
242
|
+
"choices" => node.children.map do |option|
|
|
243
|
+
{
|
|
244
|
+
"title" => option.attributes[:text],
|
|
245
|
+
"value" => option.attributes[:value]
|
|
246
|
+
}
|
|
247
|
+
end
|
|
248
|
+
}
|
|
249
|
+
input["value"] = node.attributes[:initial_option] unless node.attributes[:initial_option].nil?
|
|
181
250
|
input
|
|
182
251
|
end
|
|
183
252
|
|
|
@@ -191,6 +260,7 @@ module ChatSDK
|
|
|
191
260
|
}
|
|
192
261
|
input["placeholder"] = node.attributes[:placeholder] if node.attributes[:placeholder]
|
|
193
262
|
input["value"] = node.attributes[:value] unless node.attributes[:value].nil?
|
|
263
|
+
input["maxLength"] = node.attributes[:max_length] unless node.attributes[:max_length].nil?
|
|
194
264
|
input
|
|
195
265
|
end
|
|
196
266
|
|
data/lib/chat_sdk/teams.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chat_sdk-teams
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.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: '1.
|
|
18
|
+
version: '1.3'
|
|
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: '1.
|
|
25
|
+
version: '1.3'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: faraday
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|