teams_rb 2.0.1

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE +21 -0
  4. data/README.md +500 -0
  5. data/docs/README.md +40 -0
  6. data/docs/essentials/README.md +13 -0
  7. data/docs/essentials/api-client.md +62 -0
  8. data/docs/essentials/app-authentication.md +27 -0
  9. data/docs/essentials/app-basics.md +50 -0
  10. data/docs/essentials/graph.md +41 -0
  11. data/docs/essentials/on-activity.md +72 -0
  12. data/docs/essentials/on-event.md +29 -0
  13. data/docs/essentials/proactive-messaging.md +44 -0
  14. data/docs/essentials/sending-messages.md +76 -0
  15. data/docs/essentials/sovereign-cloud.md +26 -0
  16. data/docs/getting-started/README.md +7 -0
  17. data/docs/getting-started/code-basics.md +61 -0
  18. data/docs/getting-started/quickstart.md +54 -0
  19. data/docs/getting-started/running-in-teams.md +44 -0
  20. data/docs/in-depth-guides/README.md +14 -0
  21. data/docs/in-depth-guides/adaptive-cards.md +62 -0
  22. data/docs/in-depth-guides/dialogs.md +77 -0
  23. data/docs/in-depth-guides/feedback.md +29 -0
  24. data/docs/in-depth-guides/meeting-events.md +27 -0
  25. data/docs/in-depth-guides/message-extensions.md +77 -0
  26. data/docs/in-depth-guides/message-reactions.md +29 -0
  27. data/docs/in-depth-guides/observability.md +28 -0
  28. data/docs/in-depth-guides/streaming.md +52 -0
  29. data/docs/in-depth-guides/tabs.md +59 -0
  30. data/docs/in-depth-guides/user-authentication.md +60 -0
  31. data/lib/teams/activity.rb +160 -0
  32. data/lib/teams/activity_context.rb +278 -0
  33. data/lib/teams/api/account.rb +90 -0
  34. data/lib/teams/api/activity_value.rb +49 -0
  35. data/lib/teams/api/bot_sign_in_client.rb +54 -0
  36. data/lib/teams/api/channel_data.rb +86 -0
  37. data/lib/teams/api/channel_info.rb +19 -0
  38. data/lib/teams/api/citation_appearance.rb +83 -0
  39. data/lib/teams/api/client.rb +28 -0
  40. data/lib/teams/api/conversation_account.rb +40 -0
  41. data/lib/teams/api/conversation_client.rb +156 -0
  42. data/lib/teams/api/conversation_reference.rb +83 -0
  43. data/lib/teams/api/conversation_resource.rb +19 -0
  44. data/lib/teams/api/meeting_client.rb +57 -0
  45. data/lib/teams/api/meeting_info.rb +26 -0
  46. data/lib/teams/api/meeting_notification_response.rb +29 -0
  47. data/lib/teams/api/meeting_participant.rb +33 -0
  48. data/lib/teams/api/message_activity.rb +201 -0
  49. data/lib/teams/api/message_extension.rb +110 -0
  50. data/lib/teams/api/model.rb +49 -0
  51. data/lib/teams/api/notification_info.rb +28 -0
  52. data/lib/teams/api/paged_members_result.rb +16 -0
  53. data/lib/teams/api/quoted_reply_entity.rb +65 -0
  54. data/lib/teams/api/reaction_client.rb +34 -0
  55. data/lib/teams/api/sent_activity.rb +48 -0
  56. data/lib/teams/api/task_module.rb +83 -0
  57. data/lib/teams/api/team_client.rb +45 -0
  58. data/lib/teams/api/team_details.rb +36 -0
  59. data/lib/teams/api/team_info.rb +44 -0
  60. data/lib/teams/api/tenant_info.rb +11 -0
  61. data/lib/teams/api/token.rb +81 -0
  62. data/lib/teams/api/typing_activity.rb +19 -0
  63. data/lib/teams/api/user_client.rb +83 -0
  64. data/lib/teams/app.rb +602 -0
  65. data/lib/teams/auth/client_secret_credentials.rb +7 -0
  66. data/lib/teams/auth/jwt_validator.rb +148 -0
  67. data/lib/teams/auth/token.rb +39 -0
  68. data/lib/teams/auth/token_manager.rb +68 -0
  69. data/lib/teams/cards/generated.rb +1764 -0
  70. data/lib/teams/cards/generated_base.rb +105 -0
  71. data/lib/teams/cards.rb +81 -0
  72. data/lib/teams/cloud_environment.rb +41 -0
  73. data/lib/teams/common/hashes.rb +20 -0
  74. data/lib/teams/common/http_client.rb +111 -0
  75. data/lib/teams/common/retry.rb +31 -0
  76. data/lib/teams/errors.rb +50 -0
  77. data/lib/teams/function_context.rb +82 -0
  78. data/lib/teams/graph/client.rb +73 -0
  79. data/lib/teams/http_stream.rb +460 -0
  80. data/lib/teams/rack_app.rb +62 -0
  81. data/lib/teams/response.rb +9 -0
  82. data/lib/teams/router.rb +171 -0
  83. data/lib/teams/storage/memory_store.rb +27 -0
  84. data/lib/teams/version.rb +5 -0
  85. data/lib/teams.rb +70 -0
  86. metadata +217 -0
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ # An attachment in a message extension result; preview: carries the
6
+ # optional preview card shown in the result list.
7
+ class MessagingExtensionAttachment
8
+ def initialize(content_type: nil, content: nil, content_url: nil, name: nil,
9
+ thumbnail_url: nil, preview: nil)
10
+ @content_type = content_type
11
+ @content = content
12
+ @content_url = content_url
13
+ @name = name
14
+ @thumbnail_url = thumbnail_url
15
+ @preview = preview
16
+ end
17
+
18
+ def to_h
19
+ body = {}
20
+ body["contentType"] = @content_type if @content_type
21
+ body["content"] = serialize(@content) if @content
22
+ body["contentUrl"] = @content_url if @content_url
23
+ body["name"] = @name if @name
24
+ body["thumbnailUrl"] = @thumbnail_url if @thumbnail_url
25
+ body["preview"] = serialize(@preview) if @preview
26
+ body
27
+ end
28
+
29
+ private
30
+
31
+ def serialize(value)
32
+ value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash)
33
+ value.is_a?(Hash) ? Common::Hashes.deep_stringify_keys(value) : value
34
+ end
35
+ end
36
+
37
+ # type: "result", "auth", "config", "message", "botMessagePreview", or
38
+ # "silentAuth"; attachment_layout: "list" or "grid".
39
+ class MessagingExtensionResult
40
+ def initialize(type: nil, attachment_layout: nil, attachments: nil,
41
+ suggested_actions: nil, text: nil, activity_preview: nil)
42
+ @type = type
43
+ @attachment_layout = attachment_layout
44
+ @attachments = attachments
45
+ @suggested_actions = suggested_actions
46
+ @text = text
47
+ @activity_preview = activity_preview
48
+ end
49
+
50
+ def to_h
51
+ body = {}
52
+ body["type"] = @type if @type
53
+ body["attachmentLayout"] = @attachment_layout if @attachment_layout
54
+ body["attachments"] = @attachments.map { |attachment| serialize(attachment) } if @attachments
55
+ body["suggestedActions"] = serialize(@suggested_actions) if @suggested_actions
56
+ body["text"] = @text if @text
57
+ body["activityPreview"] = serialize(@activity_preview) if @activity_preview
58
+ body
59
+ end
60
+
61
+ private
62
+
63
+ def serialize(value)
64
+ value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash)
65
+ value.is_a?(Hash) ? Common::Hashes.deep_stringify_keys(value) : value
66
+ end
67
+ end
68
+
69
+ # Response for the query-style message extension invokes (query,
70
+ # selectItem, queryLink, querySettingUrl, setting).
71
+ class MessagingExtensionResponse
72
+ def initialize(compose_extension, cache_info: nil)
73
+ @compose_extension = compose_extension
74
+ @cache_info = cache_info
75
+ end
76
+
77
+ def to_h
78
+ body = {}
79
+ if @compose_extension
80
+ body["composeExtension"] =
81
+ @compose_extension.respond_to?(:to_h) ? @compose_extension.to_h : @compose_extension
82
+ end
83
+ body["cacheInfo"] = Common::Hashes.deep_stringify_keys(@cache_info) if @cache_info
84
+ body
85
+ end
86
+ end
87
+
88
+ # Response for the action-style invokes (submitAction, fetchTask):
89
+ # either a dialog via task: (a TaskModuleContinueResponse /
90
+ # TaskModuleMessageResponse) or a result via compose_extension:.
91
+ class MessagingExtensionActionResponse
92
+ def initialize(task: nil, compose_extension: nil, cache_info: nil)
93
+ @task = task
94
+ @compose_extension = compose_extension
95
+ @cache_info = cache_info
96
+ end
97
+
98
+ def to_h
99
+ body = {}
100
+ body["task"] = @task.respond_to?(:to_h) ? @task.to_h : @task if @task
101
+ if @compose_extension
102
+ body["composeExtension"] =
103
+ @compose_extension.respond_to?(:to_h) ? @compose_extension.to_h : @compose_extension
104
+ end
105
+ body["cacheInfo"] = Common::Hashes.deep_stringify_keys(@cache_info) if @cache_info
106
+ body
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class Model
6
+ attr_reader :raw
7
+
8
+ def initialize(raw = {})
9
+ @raw = normalize_hash(raw)
10
+ end
11
+
12
+ def to_h
13
+ raw.dup
14
+ end
15
+
16
+ private
17
+
18
+ def read(*keys)
19
+ keys.each do |key|
20
+ return raw[key] if raw.key?(key)
21
+ return raw[key.to_s] if raw.key?(key.to_s)
22
+ return raw[key.to_sym] if raw.key?(key.to_sym)
23
+ end
24
+
25
+ nil
26
+ end
27
+
28
+ def normalize_hash(value)
29
+ value = value.to_h if value.respond_to?(:to_h)
30
+ return {} if value.nil?
31
+
32
+ value.each_with_object({}) do |(key, item), result|
33
+ result[key.to_s] = normalize_value(item)
34
+ end
35
+ end
36
+
37
+ def normalize_value(value)
38
+ case value
39
+ when Hash
40
+ normalize_hash(value)
41
+ when Array
42
+ value.map { |item| normalize_value(item) }
43
+ else
44
+ value
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class NotificationInfo < Model
6
+ def alert
7
+ read("alert")
8
+ end
9
+
10
+ def alert_in_meeting
11
+ read("alertInMeeting", "alert_in_meeting")
12
+ end
13
+
14
+ def external_resource_url
15
+ read("externalResourceUrl", "external_resource_url")
16
+ end
17
+
18
+ def to_h
19
+ body = raw.dup
20
+ body["alertInMeeting"] = alert_in_meeting unless alert_in_meeting.nil?
21
+ body["externalResourceUrl"] = external_resource_url if external_resource_url
22
+ body.delete("alert_in_meeting")
23
+ body.delete("external_resource_url")
24
+ body
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class PagedMembersResult < Model
6
+ def members
7
+ Array(read("members")).map { |member| Account.new(member) }
8
+ end
9
+
10
+ # Token to fetch the next page of members; nil on the last page.
11
+ def continuation_token
12
+ read("continuationToken", "continuation_token")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class QuotedReplyData < Model
6
+ def message_id
7
+ read("messageId", "message_id")
8
+ end
9
+
10
+ def sender_id
11
+ read("senderId", "sender_id")
12
+ end
13
+
14
+ def sender_name
15
+ read("senderName", "sender_name")
16
+ end
17
+
18
+ def preview
19
+ read("preview")
20
+ end
21
+
22
+ def time
23
+ read("time")
24
+ end
25
+
26
+ def is_reply_deleted
27
+ read("isReplyDeleted", "is_reply_deleted")
28
+ end
29
+
30
+ def validated_message_reference
31
+ read("validatedMessageReference", "validated_message_reference")
32
+ end
33
+
34
+ def to_h
35
+ body = {}
36
+ body["messageId"] = message_id if message_id
37
+ body["senderId"] = sender_id if sender_id
38
+ body["senderName"] = sender_name if sender_name
39
+ body["preview"] = preview if preview
40
+ body["time"] = time if time
41
+ body["isReplyDeleted"] = is_reply_deleted unless is_reply_deleted.nil?
42
+ body["validatedMessageReference"] = validated_message_reference unless validated_message_reference.nil?
43
+ body
44
+ end
45
+ end
46
+
47
+ class QuotedReplyEntity < Model
48
+ def type
49
+ "quotedReply"
50
+ end
51
+
52
+ def quoted_reply
53
+ value = read("quotedReply", "quoted_reply")
54
+ value.is_a?(QuotedReplyData) ? value : QuotedReplyData.new(value || {})
55
+ end
56
+
57
+ def to_h
58
+ {
59
+ "type" => type,
60
+ "quotedReply" => quoted_reply.to_h
61
+ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Teams
6
+ module Api
7
+ class ReactionClient
8
+ attr_reader :service_url, :http
9
+
10
+ def initialize(service_url:, http:)
11
+ @service_url = service_url.sub(%r{/+\z}, "")
12
+ @http = http
13
+ end
14
+
15
+ def add(conversation_id, activity_id, reaction_type)
16
+ http.put(path(conversation_id, activity_id, reaction_type))
17
+ end
18
+
19
+ def delete(conversation_id, activity_id, reaction_type)
20
+ http.delete(path(conversation_id, activity_id, reaction_type))
21
+ end
22
+
23
+ private
24
+
25
+ def path(conversation_id, activity_id, reaction_type)
26
+ "#{service_url}/v3/conversations/#{escape(conversation_id)}/activities/#{escape(activity_id)}/reactions/#{escape(reaction_type)}"
27
+ end
28
+
29
+ def escape(value)
30
+ URI.encode_www_form_component(value.to_s)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ # Represents an activity that was sent: the outbound activity merged with
6
+ # the server response, so the server-assigned id is always available.
7
+ # Mirrors the SDKs' SentActivity.
8
+ class SentActivity
9
+ attr_reader :raw
10
+
11
+ def self.merge(activity, response)
12
+ activity_hash = activity.respond_to?(:to_h) ? activity.to_h : activity
13
+ activity_hash = {} unless activity_hash.is_a?(Hash)
14
+ response_hash = response.is_a?(Hash) ? response : {}
15
+
16
+ new(activity_hash.merge(response_hash))
17
+ end
18
+
19
+ def initialize(raw = {})
20
+ @raw = raw
21
+ end
22
+
23
+ def id
24
+ raw["id"] || raw[:id]
25
+ end
26
+
27
+ def type
28
+ raw["type"]
29
+ end
30
+
31
+ def text
32
+ raw["text"]
33
+ end
34
+
35
+ def conversation_id
36
+ raw.dig("conversation", "id")
37
+ end
38
+
39
+ def [](key)
40
+ raw[key]
41
+ end
42
+
43
+ def to_h
44
+ raw.dup
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ # Dialog (task module) content definition: either a card dialog (card:)
6
+ # or a webpage dialog (url:). height/width take "small"/"medium"/"large"
7
+ # or pixel integers.
8
+ class TaskModuleTaskInfo
9
+ def initialize(title: nil, height: nil, width: nil, url: nil, card: nil,
10
+ fallback_url: nil, completion_bot_id: nil)
11
+ @title = title
12
+ @height = height
13
+ @width = width
14
+ @url = url
15
+ @card = card
16
+ @fallback_url = fallback_url
17
+ @completion_bot_id = completion_bot_id
18
+ end
19
+
20
+ def to_h
21
+ body = {}
22
+ body["title"] = @title if @title
23
+ body["height"] = @height if @height
24
+ body["width"] = @width if @width
25
+ body["url"] = @url if @url
26
+ body["card"] = card_attachment(@card) if @card
27
+ body["fallbackUrl"] = @fallback_url if @fallback_url
28
+ body["completionBotId"] = @completion_bot_id if @completion_bot_id
29
+ body
30
+ end
31
+
32
+ private
33
+
34
+ # Accepts a ready attachment hash, or a card (Cards::AdaptiveCard or
35
+ # hash) which is wrapped like MessageActivity#add_card.
36
+ def card_attachment(card)
37
+ card = card.to_h if card.respond_to?(:to_h) && !card.is_a?(Hash)
38
+ return Common::Hashes.deep_stringify_keys(card) if card.is_a?(Hash) && card.key?("contentType")
39
+
40
+ {
41
+ "contentType" => "application/vnd.microsoft.card.adaptive",
42
+ "content" => card.is_a?(Hash) ? Common::Hashes.deep_stringify_keys(card) : card
43
+ }
44
+ end
45
+ end
46
+
47
+ class TaskModuleContinueResponse
48
+ def initialize(value)
49
+ @value = value
50
+ end
51
+
52
+ def to_h
53
+ {
54
+ "type" => "continue",
55
+ "value" => @value.respond_to?(:to_h) ? @value.to_h : @value
56
+ }
57
+ end
58
+ end
59
+
60
+ class TaskModuleMessageResponse
61
+ def initialize(value)
62
+ @value = value
63
+ end
64
+
65
+ def to_h
66
+ { "type" => "message", "value" => @value.to_s }
67
+ end
68
+ end
69
+
70
+ class TaskModuleResponse
71
+ def initialize(task, cache_info: nil)
72
+ @task = task
73
+ @cache_info = cache_info
74
+ end
75
+
76
+ def to_h
77
+ body = { "task" => @task.respond_to?(:to_h) ? @task.to_h : @task }
78
+ body["cacheInfo"] = Common::Hashes.deep_stringify_keys(@cache_info) if @cache_info
79
+ body
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Teams
6
+ module Api
7
+ class TeamClient
8
+ attr_reader :service_url, :http
9
+
10
+ def initialize(service_url:, http:, logger: nil)
11
+ @service_url = service_url.sub(%r{/+\z}, "")
12
+ @http = http
13
+ @logger = logger
14
+ end
15
+
16
+ def get_by_id(team_id, service_url: nil)
17
+ path = "/v3/teams/#{escape(team_id)}"
18
+ url = absolute(path, service_url:)
19
+ @logger&.debug("Teams API GET #{url}")
20
+ TeamDetails.new(http.get(url))
21
+ end
22
+
23
+ # Returns the team's channels; the service wraps them in a
24
+ # "conversations" envelope, which all the SDKs unwrap.
25
+ def get_conversations(team_id, service_url: nil)
26
+ path = "/v3/teams/#{escape(team_id)}/conversations"
27
+ url = absolute(path, service_url:)
28
+ @logger&.debug("Teams API GET #{url}")
29
+ response = http.get(url)
30
+ conversations = response.is_a?(Hash) ? Array(response["conversations"]) : []
31
+ conversations.map { |channel| ChannelInfo.new(channel) }
32
+ end
33
+
34
+ private
35
+
36
+ def absolute(path, service_url: nil)
37
+ "#{(service_url || self.service_url).sub(%r{/+\z}, "")}#{path}"
38
+ end
39
+
40
+ def escape(value)
41
+ URI.encode_www_form_component(value.to_s)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class TeamDetails < Model
6
+ def id
7
+ read("id")
8
+ end
9
+
10
+ def name
11
+ read("name")
12
+ end
13
+
14
+ # "standard", "sharedChannel", or "privateChannel".
15
+ def type
16
+ read("type")
17
+ end
18
+
19
+ def aad_group_id
20
+ read("aadGroupId", "aad_group_id")
21
+ end
22
+
23
+ def channel_count
24
+ read("channelCount", "channel_count")
25
+ end
26
+
27
+ def member_count
28
+ read("memberCount", "member_count")
29
+ end
30
+
31
+ def tenant_id
32
+ read("tenantId", "tenant_id")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class TeamInfo < Model
6
+ def id
7
+ read("id")
8
+ end
9
+
10
+ def name
11
+ read("name")
12
+ end
13
+
14
+ def team_type
15
+ read("teamType", "team_type")
16
+ end
17
+
18
+ def member_count
19
+ read("memberCount", "member_count")
20
+ end
21
+
22
+ def channel_count
23
+ read("channelCount", "channel_count")
24
+ end
25
+
26
+ def aad_group_id
27
+ read("aadGroupId", "aad_group_id")
28
+ end
29
+
30
+ def to_h
31
+ body = raw.dup
32
+ body["teamType"] = team_type if team_type
33
+ body["memberCount"] = member_count if member_count
34
+ body["channelCount"] = channel_count if channel_count
35
+ body["aadGroupId"] = aad_group_id if aad_group_id
36
+ body.delete("team_type")
37
+ body.delete("member_count")
38
+ body.delete("channel_count")
39
+ body.delete("aad_group_id")
40
+ body
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class TenantInfo < Model
6
+ def id
7
+ read("id")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class TokenResponse < Model
6
+ def channel_id
7
+ read("channelId", "channel_id")
8
+ end
9
+
10
+ def connection_name
11
+ read("connectionName", "connection_name")
12
+ end
13
+
14
+ def token
15
+ read("token")
16
+ end
17
+
18
+ def expiration
19
+ read("expiration")
20
+ end
21
+
22
+ def properties
23
+ read("properties")
24
+ end
25
+ end
26
+
27
+ class TokenStatus < Model
28
+ def channel_id
29
+ read("channelId", "channel_id")
30
+ end
31
+
32
+ def connection_name
33
+ read("connectionName", "connection_name")
34
+ end
35
+
36
+ def has_token
37
+ read("hasToken", "has_token")
38
+ end
39
+
40
+ def service_provider_display_name
41
+ read("serviceProviderDisplayName", "service_provider_display_name")
42
+ end
43
+ end
44
+
45
+ class TokenExchangeResource < Model
46
+ def id
47
+ read("id")
48
+ end
49
+
50
+ def uri
51
+ read("uri")
52
+ end
53
+
54
+ def provider_id
55
+ read("providerId", "provider_id")
56
+ end
57
+ end
58
+
59
+ class TokenPostResource < Model
60
+ def sas_url
61
+ read("sasUrl", "sas_url")
62
+ end
63
+ end
64
+
65
+ class SignInUrlResponse < Model
66
+ def sign_in_link
67
+ read("signInLink", "sign_in_link")
68
+ end
69
+
70
+ def token_exchange_resource
71
+ value = read("tokenExchangeResource", "token_exchange_resource")
72
+ value ? TokenExchangeResource.new(value) : nil
73
+ end
74
+
75
+ def token_post_resource
76
+ value = read("tokenPostResource", "token_post_resource")
77
+ value ? TokenPostResource.new(value) : nil
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Teams
4
+ module Api
5
+ class TypingActivity
6
+ attr_reader :text
7
+
8
+ def initialize(text = nil)
9
+ @text = text
10
+ end
11
+
12
+ def to_h
13
+ body = { "type" => "typing" }
14
+ body["text"] = text if text
15
+ body
16
+ end
17
+ end
18
+ end
19
+ end