From 816d67b63e112f46ee45d9f19a591ff6807ffaaf Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 9 Jun 2021 16:52:37 -0700 Subject: [PATCH 01/10] add getMeetingDetails support --- .../Microsoft.Bot.Builder/Teams/TeamsInfo.cs | 16 +++ .../Teams/TeamsOperations.cs | 54 ++++++++ .../Teams/TeamsOperationsExtensions.cs | 24 ++++ .../Teams/MeetingDetailInfo.cs | 120 ++++++++++++++++++ .../Teams/MeetingDetails.cs | 67 ++++++++++ .../Teams/TeamsInfoTests.cs | 55 ++++++++ 6 files changed, 336 insertions(+) create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index 3c5730c4a3..e0e187cfe6 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -44,6 +44,22 @@ public static async Task GetMeetingParticipantAsync(ITu } } + /// + /// Gets the details for the given meeting id. + /// + /// Turn context. + /// The BASE64-encoded id of the Teams meeting. + /// Cancellation token. + /// Team Details. + public static async Task GetMeetingDetailsAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default) + { + meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting."); + using (var teamsClient = GetTeamsConnectorClient(turnContext)) + { + return await teamsClient.Teams.FetchMeetingDetailsAsync(meetingId, cancellationToken: cancellationToken).ConfigureAwait(false); + } + } + /// /// Gets the details for the given team id. This only works in teams scoped conversations. /// diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs index d6ed8edc91..07704828a5 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs @@ -154,6 +154,60 @@ public TeamsOperations(TeamsConnectorClient client) return await GetResponseAsync(url, shouldTrace, invocationId).ConfigureAwait(false); } + /// + /// Fetches details related to a meeting. + /// + /// + /// Meeting Id, encoded as a BASE64 string. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code. + /// + /// + /// Thrown when unable to deserialize the response. + /// + /// + /// Thrown when an input value does not match the expected data type, range or pattern. + /// + /// + /// Thrown when a required parameter is null. + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task> FetchMeetingDetailsWithHttpMessagesAsync(string meetingId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (meetingId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "meetingId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(CultureInfo.InvariantCulture); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("meetingId", meetingId); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(invocationId, this, "FetchMeetingDetails", tracingParameters); + } + + // Construct URL + var baseUrl = Client.BaseUri.AbsoluteUri; + var url = new System.Uri(new System.Uri(baseUrl + (baseUrl.EndsWith("/", System.StringComparison.InvariantCulture) ? string.Empty : "/")), "v1/meetings/{meetingId}").ToString(); + url = url.Replace("{meetingId}", System.Uri.EscapeDataString(meetingId)); + + return await GetResponseAsync(url, shouldTrace, invocationId, customHeaders).ConfigureAwait(false); + } + /// /// Fetches Teams meeting participant details. /// diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs index 080009b5f1..ba42e90f06 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs @@ -55,6 +55,30 @@ public static partial class TeamsOperationsExtensions } } + /// + /// Fetches details related to a Teams meeting. + /// + /// + /// The operations group for this extension method. + /// + /// + /// Meeting Id. + /// + /// + /// The cancellation token. + /// + /// The details related to a team. + public static async Task FetchMeetingDetailsAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken)) + { + if (operations is TeamsOperations teamsOperations) + { + using var result = await teamsOperations.FetchMeetingDetailsWithHttpMessagesAsync(meetingId, null, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + throw new InvalidOperationException("TeamsOperations with GetMeetingDetailsWithHttpMessagesAsync is required for FetchMeetingDetailsAsync."); + } + /// /// Fetches participant details related to a Teams meeting. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs new file mode 100644 index 0000000000..6178d98f85 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using System; + using Newtonsoft.Json; + + /// + /// Teams meeting details. + /// + public partial class MeetingDetailInfo + { + /// + /// Initializes a new instance of the class. + /// + public MeetingDetailInfo() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The MsGraphResourceId, used specifically for MS Graph API calls. + /// The meeting's scheduled start time, in UTC. + /// The meeting's scheduled end time, in UTC. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + public MeetingDetailInfo( + string id, + string msGraphResourceId = null, + DateTime scheduledStartTime = default, + DateTime scheduledEndTime = default, + Uri joinUrl = null, + string title = null, + string type = "Scheduled") + { + Id = id; + MsGraphResourceId = msGraphResourceId; + ScheduledStartTime = scheduledStartTime; + ScheduledEndTime = scheduledEndTime; + JoinUrl = joinUrl; + Title = title; + Type = type; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's Id, encoded as a BASE64 string. + /// + /// + /// The meeting's Id, encoded as a BASE64 string. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; set; } + + /// + /// Gets or sets the MsGraphResourceId, used specifically for MS Graph API calls. + /// + /// + /// The MsGraphResourceId, used specifically for MS Graph API calls. + /// + [JsonProperty(PropertyName = "msGraphResourceId")] + public string MsGraphResourceId { get; set; } + + /// + /// Gets or sets the meeting's scheduled start time, in UTC. + /// + /// + /// The meeting's scheduled start time, in UTC. + /// + [JsonProperty(PropertyName = "scheduledStartTime")] + public DateTime ScheduledStartTime { get; set; } + + /// + /// Gets or sets the meeting's scheduled end time, in UTC. + /// + /// + /// The meeting's scheduled end time, in UTC. + /// + [JsonProperty(PropertyName = "scheduledEndTime")] + public DateTime ScheduledEndTime { get; set; } + + /// + /// Gets or sets the URL used to join the meeting. + /// + /// + /// The URL used to join the meeting. + /// + [JsonProperty(PropertyName = "joinUrl")] + public Uri JoinUrl { get; set; } + + /// + /// Gets or sets the title of the meeting. + /// + /// + /// The title of the meeting. + /// + [JsonProperty(PropertyName = "title")] + public string Title { get; set; } + + /// + /// Gets or sets the meeting's type. + /// + /// + /// The meeting's type. + /// + [JsonProperty(PropertyName = "type")] + public string Type { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs new file mode 100644 index 0000000000..53c1e6d872 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using Newtonsoft.Json; + + /// + /// Teams meeting details. + /// + public partial class MeetingDetails + { + /// + /// Initializes a new instance of the class. + /// + public MeetingDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's detailed information. + /// Conversation Account for the meeting. + /// Information specific to this organizer of the specific meeting. + public MeetingDetails(MeetingDetailInfo details, ConversationAccount conversation = null, TeamsChannelAccount organizer = null) + { + Details = details; + Conversation = conversation; + Organizer = organizer; + CustomInit(); + } + + /// + /// Gets or sets the meeting's detailed information. + /// + /// + /// The meetings detailed information. + /// + [JsonProperty(PropertyName = "details")] + public MeetingDetailInfo Details { get; set; } + + /// + /// Gets or sets the Conversation Account for the meeting. + /// + /// + /// The Conversation Account for the meeting. + /// + [JsonProperty(PropertyName = "conversation")] + public ConversationAccount Conversation { get; set; } + + /// + /// Gets or sets the meeting organizer's user information. + /// + /// + /// The organizer's user information. + /// + [JsonProperty(PropertyName = "organizer")] + public TeamsChannelAccount Organizer { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs index c228be466d..02a77903c0 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs @@ -51,6 +51,37 @@ public async Task TestSendMessageToTeamsChannelAsync() await handler.OnTurnAsync(turnContext); } + [Fact] + public async Task TestGetMeetingDetailsAsync() + { + var baseUri = new Uri("https://test.coffee"); + var customHttpClient = new HttpClient(new RosterHttpMessageHandler()); + + // Set a special base address so then we can make sure the connector client is honoring this http client + customHttpClient.BaseAddress = baseUri; + var connectorClient = new ConnectorClient(new Uri("http://localhost/"), new MicrosoftAppCredentials(string.Empty, string.Empty), customHttpClient); + + var activity = new Activity + { + Type = "message", + Text = "Test-GetMeetingDetailsAsync", + ChannelId = Channels.Msteams, + ChannelData = new TeamsChannelData + { + Meeting = new TeamsMeetingInfo + { + Id = "meeting-id" + } + }, + }; + + var turnContext = new TurnContext(new SimpleAdapter(), activity); + turnContext.TurnState.Add(connectorClient); + + var handler = new TestTeamsActivityHandler(); + await handler.OnTurnAsync(turnContext); + } + [Fact] public async Task TestGetTeamDetailsAsync() { @@ -292,6 +323,9 @@ public override async Task OnTurnAsync(ITurnContext turnContext, CancellationTok case "Test-GetParticipantAsync": await CallTeamsInfoGetParticipantAsync(turnContext); break; + case "Test-GetMeetingDetailsAsync": + await CallTeamsInfoGetMeetingDetailsAsync(turnContext); + break; default: Assert.True(false); break; @@ -358,6 +392,15 @@ private async Task CallTeamsInfoGetParticipantAsync(ITurnContext turnContext) Assert.Equal("userPrincipalName-1", participant.User.UserPrincipalName); } + private async Task CallTeamsInfoGetMeetingDetailsAsync(ITurnContext turnContext) + { + var meeting = await TeamsInfo.GetMeetingDetailsAsync(turnContext); + + Assert.Equal("meeting-id", meeting.Details.Id); + Assert.Equal("organizer-id", meeting.Organizer.Id); + Assert.Equal("meetingConversationId-1", meeting.Conversation.Id); + } + private async Task CallGroupChatGetMembersAsync(ITurnContext turnContext) { var members = (await TeamsInfo.GetMembersAsync(turnContext)).ToArray(); @@ -528,6 +571,18 @@ protected override Task SendAsync(HttpRequestMessage reques response.Content = new StringContent(content.ToString()); } + // Get meeting details + else if (request.RequestUri.PathAndQuery.EndsWith("v1/meetings/meeting-id")) + { + var content = new JObject + { + new JProperty("details", new JObject(new JProperty("id", "meeting-id"))), + new JProperty("organizer", new JObject(new JProperty("id", "organizer-id"))), + new JProperty("conversation", new JObject(new JProperty("id", "meetingConversationId-1"))), + }; + response.Content = new StringContent(content.ToString()); + } + return Task.FromResult(response); } } From 568f6fa311d8254a4544621fcdb60dcaaaeecd00 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 10 Jun 2021 11:55:14 -0700 Subject: [PATCH 02/10] schema rename --- .../Microsoft.Bot.Builder/Teams/TeamsInfo.cs | 2 +- .../Teams/TeamsOperations.cs | 4 +- .../Teams/TeamsOperationsExtensions.cs | 2 +- .../Teams/MeetingDetailInfo.cs | 120 ------------------ .../Teams/MeetingDetails.cs | 93 +++++++++++--- .../Microsoft.Bot.Schema/Teams/MeetingInfo.cs | 67 ++++++++++ 6 files changed, 144 insertions(+), 144 deletions(-) delete mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index e0e187cfe6..2f3be478ba 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -51,7 +51,7 @@ public static async Task GetMeetingParticipantAsync(ITu /// The BASE64-encoded id of the Teams meeting. /// Cancellation token. /// Team Details. - public static async Task GetMeetingDetailsAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default) + public static async Task GetMeetingDetailsAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default) { meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting."); using (var teamsClient = GetTeamsConnectorClient(turnContext)) diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs index 07704828a5..0f71419a02 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs @@ -181,7 +181,7 @@ public TeamsOperations(TeamsConnectorClient client) /// /// A response object containing the response body and response headers. /// - public async Task> FetchMeetingDetailsWithHttpMessagesAsync(string meetingId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> FetchMeetingDetailsWithHttpMessagesAsync(string meetingId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) { if (meetingId == null) { @@ -205,7 +205,7 @@ public TeamsOperations(TeamsConnectorClient client) var url = new System.Uri(new System.Uri(baseUrl + (baseUrl.EndsWith("/", System.StringComparison.InvariantCulture) ? string.Empty : "/")), "v1/meetings/{meetingId}").ToString(); url = url.Replace("{meetingId}", System.Uri.EscapeDataString(meetingId)); - return await GetResponseAsync(url, shouldTrace, invocationId, customHeaders).ConfigureAwait(false); + return await GetResponseAsync(url, shouldTrace, invocationId, customHeaders).ConfigureAwait(false); } /// diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs index ba42e90f06..a5837a310d 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs @@ -68,7 +68,7 @@ public static partial class TeamsOperationsExtensions /// The cancellation token. /// /// The details related to a team. - public static async Task FetchMeetingDetailsAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task FetchMeetingDetailsAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken)) { if (operations is TeamsOperations teamsOperations) { diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs deleted file mode 100644 index 6178d98f85..0000000000 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailInfo.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Bot.Schema.Teams -{ - using System; - using Newtonsoft.Json; - - /// - /// Teams meeting details. - /// - public partial class MeetingDetailInfo - { - /// - /// Initializes a new instance of the class. - /// - public MeetingDetailInfo() - { - CustomInit(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The meeting's Id, encoded as a BASE64 string. - /// The MsGraphResourceId, used specifically for MS Graph API calls. - /// The meeting's scheduled start time, in UTC. - /// The meeting's scheduled end time, in UTC. - /// The URL used to join the meeting. - /// The title of the meeting. - /// The meeting's type. - public MeetingDetailInfo( - string id, - string msGraphResourceId = null, - DateTime scheduledStartTime = default, - DateTime scheduledEndTime = default, - Uri joinUrl = null, - string title = null, - string type = "Scheduled") - { - Id = id; - MsGraphResourceId = msGraphResourceId; - ScheduledStartTime = scheduledStartTime; - ScheduledEndTime = scheduledEndTime; - JoinUrl = joinUrl; - Title = title; - Type = type; - - CustomInit(); - } - - /// - /// Gets or sets the meeting's Id, encoded as a BASE64 string. - /// - /// - /// The meeting's Id, encoded as a BASE64 string. - /// - [JsonProperty(PropertyName = "id")] - public string Id { get; set; } - - /// - /// Gets or sets the MsGraphResourceId, used specifically for MS Graph API calls. - /// - /// - /// The MsGraphResourceId, used specifically for MS Graph API calls. - /// - [JsonProperty(PropertyName = "msGraphResourceId")] - public string MsGraphResourceId { get; set; } - - /// - /// Gets or sets the meeting's scheduled start time, in UTC. - /// - /// - /// The meeting's scheduled start time, in UTC. - /// - [JsonProperty(PropertyName = "scheduledStartTime")] - public DateTime ScheduledStartTime { get; set; } - - /// - /// Gets or sets the meeting's scheduled end time, in UTC. - /// - /// - /// The meeting's scheduled end time, in UTC. - /// - [JsonProperty(PropertyName = "scheduledEndTime")] - public DateTime ScheduledEndTime { get; set; } - - /// - /// Gets or sets the URL used to join the meeting. - /// - /// - /// The URL used to join the meeting. - /// - [JsonProperty(PropertyName = "joinUrl")] - public Uri JoinUrl { get; set; } - - /// - /// Gets or sets the title of the meeting. - /// - /// - /// The title of the meeting. - /// - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } - - /// - /// Gets or sets the meeting's type. - /// - /// - /// The meeting's type. - /// - [JsonProperty(PropertyName = "type")] - public string Type { get; set; } - - /// - /// An initialization method that performs custom operations like setting defaults. - /// - partial void CustomInit(); - } -} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs index 53c1e6d872..35483065ec 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs @@ -3,10 +3,11 @@ namespace Microsoft.Bot.Schema.Teams { + using System; using Newtonsoft.Json; /// - /// Teams meeting details. + /// Specific details of a Teams meeting. /// public partial class MeetingDetails { @@ -21,43 +22,95 @@ public MeetingDetails() /// /// Initializes a new instance of the class. /// - /// The meeting's detailed information. - /// Conversation Account for the meeting. - /// Information specific to this organizer of the specific meeting. - public MeetingDetails(MeetingDetailInfo details, ConversationAccount conversation = null, TeamsChannelAccount organizer = null) + /// The meeting's Id, encoded as a BASE64 string. + /// The MsGraphResourceId, used specifically for MS Graph API calls. + /// The meeting's scheduled start time, in UTC. + /// The meeting's scheduled end time, in UTC. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + public MeetingDetails( + string id, + string msGraphResourceId = null, + DateTime scheduledStartTime = default, + DateTime scheduledEndTime = default, + Uri joinUrl = null, + string title = null, + string type = "Scheduled") { - Details = details; - Conversation = conversation; - Organizer = organizer; + Id = id; + MsGraphResourceId = msGraphResourceId; + ScheduledStartTime = scheduledStartTime; + ScheduledEndTime = scheduledEndTime; + JoinUrl = joinUrl; + Title = title; + Type = type; + CustomInit(); } /// - /// Gets or sets the meeting's detailed information. + /// Gets or sets the meeting's Id, encoded as a BASE64 string. + /// + /// + /// The meeting's Id, encoded as a BASE64 string. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; set; } + + /// + /// Gets or sets the MsGraphResourceId, used specifically for MS Graph API calls. + /// + /// + /// The MsGraphResourceId, used specifically for MS Graph API calls. + /// + [JsonProperty(PropertyName = "msGraphResourceId")] + public string MsGraphResourceId { get; set; } + + /// + /// Gets or sets the meeting's scheduled start time, in UTC. + /// + /// + /// The meeting's scheduled start time, in UTC. + /// + [JsonProperty(PropertyName = "scheduledStartTime")] + public DateTime ScheduledStartTime { get; set; } + + /// + /// Gets or sets the meeting's scheduled end time, in UTC. + /// + /// + /// The meeting's scheduled end time, in UTC. + /// + [JsonProperty(PropertyName = "scheduledEndTime")] + public DateTime ScheduledEndTime { get; set; } + + /// + /// Gets or sets the URL used to join the meeting. /// /// - /// The meetings detailed information. + /// The URL used to join the meeting. /// - [JsonProperty(PropertyName = "details")] - public MeetingDetailInfo Details { get; set; } + [JsonProperty(PropertyName = "joinUrl")] + public Uri JoinUrl { get; set; } /// - /// Gets or sets the Conversation Account for the meeting. + /// Gets or sets the title of the meeting. /// /// - /// The Conversation Account for the meeting. + /// The title of the meeting. /// - [JsonProperty(PropertyName = "conversation")] - public ConversationAccount Conversation { get; set; } + [JsonProperty(PropertyName = "title")] + public string Title { get; set; } /// - /// Gets or sets the meeting organizer's user information. + /// Gets or sets the meeting's type. /// /// - /// The organizer's user information. + /// The meeting's type. /// - [JsonProperty(PropertyName = "organizer")] - public TeamsChannelAccount Organizer { get; set; } + [JsonProperty(PropertyName = "type")] + public string Type { get; set; } /// /// An initialization method that performs custom operations like setting defaults. diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs new file mode 100644 index 0000000000..73d8a4b185 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using Newtonsoft.Json; + + /// + /// General information about a Teams meeting. + /// + public partial class MeetingInfo + { + /// + /// Initializes a new instance of the class. + /// + public MeetingInfo() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's detailed information. + /// Conversation Account for the meeting. + /// Information specific to this organizer of the specific meeting. + public MeetingInfo(MeetingDetails details, ConversationAccount conversation = null, TeamsChannelAccount organizer = null) + { + Details = details; + Conversation = conversation; + Organizer = organizer; + CustomInit(); + } + + /// + /// Gets or sets the meeting's detailed information. + /// + /// + /// The meetings detailed information. + /// + [JsonProperty(PropertyName = "details")] + public MeetingDetails Details { get; set; } + + /// + /// Gets or sets the Conversation Account for the meeting. + /// + /// + /// The Conversation Account for the meeting. + /// + [JsonProperty(PropertyName = "conversation")] + public ConversationAccount Conversation { get; set; } + + /// + /// Gets or sets the meeting organizer's user information. + /// + /// + /// The organizer's user information. + /// + [JsonProperty(PropertyName = "organizer")] + public TeamsChannelAccount Organizer { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} From 9d4d37a613d81bd261b270af6d958646c2e9d859 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 10 Jun 2021 12:00:28 -0700 Subject: [PATCH 03/10] doc comment change --- libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs index 73d8a4b185..7d98ac5d5b 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs @@ -33,10 +33,10 @@ public MeetingInfo(MeetingDetails details, ConversationAccount conversation = nu } /// - /// Gets or sets the meeting's detailed information. + /// Gets or sets the specific details of a Teams meeting. /// /// - /// The meetings detailed information. + /// The specific details of a Teams meeting. /// [JsonProperty(PropertyName = "details")] public MeetingDetails Details { get; set; } From 6c9569587888722432858670ec1a5e3e3733a4a1 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 10 Jun 2021 16:01:20 -0700 Subject: [PATCH 04/10] GetMeetingDetails -> GetMeetingInfo --- libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs | 4 ++-- .../Microsoft.Bot.Connector/Teams/TeamsOperations.cs | 4 ++-- .../Teams/TeamsOperationsExtensions.cs | 6 +++--- .../Teams/TeamsInfoTests.cs | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index 2f3be478ba..65d939e580 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -51,12 +51,12 @@ public static async Task GetMeetingParticipantAsync(ITu /// The BASE64-encoded id of the Teams meeting. /// Cancellation token. /// Team Details. - public static async Task GetMeetingDetailsAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default) + public static async Task GetMeetingInfoAsync(ITurnContext turnContext, string meetingId = null, CancellationToken cancellationToken = default) { meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("The meetingId can only be null if turnContext is within the scope of a MS Teams Meeting."); using (var teamsClient = GetTeamsConnectorClient(turnContext)) { - return await teamsClient.Teams.FetchMeetingDetailsAsync(meetingId, cancellationToken: cancellationToken).ConfigureAwait(false); + return await teamsClient.Teams.FetchMeetingInfoAsync(meetingId, cancellationToken: cancellationToken).ConfigureAwait(false); } } diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs index 0f71419a02..9b0beab02f 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs @@ -181,7 +181,7 @@ public TeamsOperations(TeamsConnectorClient client) /// /// A response object containing the response body and response headers. /// - public async Task> FetchMeetingDetailsWithHttpMessagesAsync(string meetingId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> FetchMeetingInfoWithHttpMessagesAsync(string meetingId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) { if (meetingId == null) { @@ -197,7 +197,7 @@ public TeamsOperations(TeamsConnectorClient client) Dictionary tracingParameters = new Dictionary(); tracingParameters.Add("meetingId", meetingId); tracingParameters.Add("cancellationToken", cancellationToken); - ServiceClientTracing.Enter(invocationId, this, "FetchMeetingDetails", tracingParameters); + ServiceClientTracing.Enter(invocationId, this, "FetchMeetingInfo", tracingParameters); } // Construct URL diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs index a5837a310d..3bdbc9aa76 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs @@ -68,15 +68,15 @@ public static partial class TeamsOperationsExtensions /// The cancellation token. /// /// The details related to a team. - public static async Task FetchMeetingDetailsAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task FetchMeetingInfoAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken)) { if (operations is TeamsOperations teamsOperations) { - using var result = await teamsOperations.FetchMeetingDetailsWithHttpMessagesAsync(meetingId, null, cancellationToken).ConfigureAwait(false); + using var result = await teamsOperations.FetchMeetingInfoWithHttpMessagesAsync(meetingId, null, cancellationToken).ConfigureAwait(false); return result.Body; } - throw new InvalidOperationException("TeamsOperations with GetMeetingDetailsWithHttpMessagesAsync is required for FetchMeetingDetailsAsync."); + throw new InvalidOperationException("TeamsOperations with GetMeetingInfoWithHttpMessagesAsync is required for FetchMeetingInfoAsync."); } /// diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs index 02a77903c0..12b071f131 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs @@ -52,7 +52,7 @@ public async Task TestSendMessageToTeamsChannelAsync() } [Fact] - public async Task TestGetMeetingDetailsAsync() + public async Task TestGetMeetingInfoAsync() { var baseUri = new Uri("https://test.coffee"); var customHttpClient = new HttpClient(new RosterHttpMessageHandler()); @@ -64,7 +64,7 @@ public async Task TestGetMeetingDetailsAsync() var activity = new Activity { Type = "message", - Text = "Test-GetMeetingDetailsAsync", + Text = "Test-GetMeetingInfoAsync", ChannelId = Channels.Msteams, ChannelData = new TeamsChannelData { @@ -323,8 +323,8 @@ public override async Task OnTurnAsync(ITurnContext turnContext, CancellationTok case "Test-GetParticipantAsync": await CallTeamsInfoGetParticipantAsync(turnContext); break; - case "Test-GetMeetingDetailsAsync": - await CallTeamsInfoGetMeetingDetailsAsync(turnContext); + case "Test-GetMeetingInfoAsync": + await CallTeamsInfoGetMeetingInfoAsync(turnContext); break; default: Assert.True(false); @@ -392,9 +392,9 @@ private async Task CallTeamsInfoGetParticipantAsync(ITurnContext turnContext) Assert.Equal("userPrincipalName-1", participant.User.UserPrincipalName); } - private async Task CallTeamsInfoGetMeetingDetailsAsync(ITurnContext turnContext) + private async Task CallTeamsInfoGetMeetingInfoAsync(ITurnContext turnContext) { - var meeting = await TeamsInfo.GetMeetingDetailsAsync(turnContext); + var meeting = await TeamsInfo.GetMeetingInfoAsync(turnContext); Assert.Equal("meeting-id", meeting.Details.Id); Assert.Equal("organizer-id", meeting.Organizer.Id); From 6ca8c40edcb20062d6c5274bea3e6a5e45fd941e Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 10 Jun 2021 16:04:32 -0700 Subject: [PATCH 05/10] doc changes --- libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs | 2 +- .../Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs index 65d939e580..748f4dcf9c 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs @@ -45,7 +45,7 @@ public static async Task GetMeetingParticipantAsync(ITu } /// - /// Gets the details for the given meeting id. + /// Gets the information for the given meeting id. /// /// Turn context. /// The BASE64-encoded id of the Teams meeting. diff --git a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs index 3bdbc9aa76..f53048a595 100644 --- a/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs +++ b/libraries/Microsoft.Bot.Connector/Teams/TeamsOperationsExtensions.cs @@ -56,7 +56,7 @@ public static partial class TeamsOperationsExtensions } /// - /// Fetches details related to a Teams meeting. + /// Fetches information related to a Teams meeting. /// /// /// The operations group for this extension method. From c5f5628a8eb453d32216f550b96e047733e19ee5 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 21 Jun 2021 10:50:47 -0700 Subject: [PATCH 06/10] add event schemas --- .../Teams/MeetingDetails.cs | 33 +-------- .../Teams/MeetingDetailsBase.cs | 72 +++++++++++++++++++ .../Teams/MeetingEndEventDetails.cs | 57 +++++++++++++++ .../Teams/MeetingEventDetails.cs | 55 ++++++++++++++ .../Teams/MeetingStartEventDetails.cs | 57 +++++++++++++++ 5 files changed, 243 insertions(+), 31 deletions(-) create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs create mode 100644 libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs index 35483065ec..5b6cf348ef 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs @@ -9,7 +9,7 @@ namespace Microsoft.Bot.Schema.Teams /// /// Specific details of a Teams meeting. /// - public partial class MeetingDetails + public partial class MeetingDetails : MeetingDetailsBase { /// /// Initializes a new instance of the class. @@ -37,27 +37,16 @@ public MeetingDetails( Uri joinUrl = null, string title = null, string type = "Scheduled") + : base(id, joinUrl, title) { - Id = id; MsGraphResourceId = msGraphResourceId; ScheduledStartTime = scheduledStartTime; ScheduledEndTime = scheduledEndTime; - JoinUrl = joinUrl; - Title = title; Type = type; CustomInit(); } - /// - /// Gets or sets the meeting's Id, encoded as a BASE64 string. - /// - /// - /// The meeting's Id, encoded as a BASE64 string. - /// - [JsonProperty(PropertyName = "id")] - public string Id { get; set; } - /// /// Gets or sets the MsGraphResourceId, used specifically for MS Graph API calls. /// @@ -85,24 +74,6 @@ public MeetingDetails( [JsonProperty(PropertyName = "scheduledEndTime")] public DateTime ScheduledEndTime { get; set; } - /// - /// Gets or sets the URL used to join the meeting. - /// - /// - /// The URL used to join the meeting. - /// - [JsonProperty(PropertyName = "joinUrl")] - public Uri JoinUrl { get; set; } - - /// - /// Gets or sets the title of the meeting. - /// - /// - /// The title of the meeting. - /// - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } - /// /// Gets or sets the meeting's type. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs new file mode 100644 index 0000000000..3be50877d1 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using System; + using Newtonsoft.Json; + + /// + /// Specific details of a Teams meeting. + /// + public partial class MeetingDetailsBase + { + /// + /// Initializes a new instance of the class. + /// + internal MeetingDetailsBase() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + internal MeetingDetailsBase( + string id, + Uri joinUrl = null, + string title = null) + { + Id = id; + JoinUrl = joinUrl; + Title = title; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's Id, encoded as a BASE64 string. + /// + /// + /// The meeting's Id, encoded as a BASE64 string. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; set; } + + /// + /// Gets or sets the URL used to join the meeting. + /// + /// + /// The URL used to join the meeting. + /// + [JsonProperty(PropertyName = "joinUrl")] + public Uri JoinUrl { get; set; } + + /// + /// Gets or sets the title of the meeting. + /// + /// + /// The title of the meeting. + /// + [JsonProperty(PropertyName = "title")] + public string Title { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs new file mode 100644 index 0000000000..78d61212eb --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using System; + using Newtonsoft.Json; + + /// + /// Specific details of a Teams meeting end event. + /// + public partial class MeetingEndEventDetails : MeetingEventDetails + { + /// + /// Initializes a new instance of the class. + /// + public MeetingEndEventDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + /// Timestamp for the meeting end, in UTC. + public MeetingEndEventDetails( + string id, + Uri joinUrl = null, + string title = null, + string meetingType = "Scheduled", + DateTime endTime = default) + : base(id, joinUrl, title, meetingType) + { + EndTime = endTime; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's end time, in UTC. + /// + /// + /// The meeting's end time, in UTC. + /// + [JsonProperty(PropertyName = "EndTime")] + public DateTime EndTime { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs new file mode 100644 index 0000000000..748827b3e9 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using System; + using Newtonsoft.Json; + + /// + /// Specific details of a Teams meeting. + /// + public partial class MeetingEventDetails : MeetingDetailsBase + { + /// + /// Initializes a new instance of the class. + /// + internal MeetingEventDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + internal MeetingEventDetails( + string id, + Uri joinUrl = null, + string title = null, + string meetingType = "Scheduled") + : base(id, joinUrl, title) + { + MeetingType = meetingType; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's type. + /// + /// + /// The meeting's type. + /// + [JsonProperty(PropertyName = "MeetingType")] + public string MeetingType { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs new file mode 100644 index 0000000000..24e89575c8 --- /dev/null +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Schema.Teams +{ + using System; + using Newtonsoft.Json; + + /// + /// Specific details of a Teams meeting start event. + /// + public partial class MeetingStartEventDetails : MeetingEventDetails + { + /// + /// Initializes a new instance of the class. + /// + public MeetingStartEventDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The meeting's Id, encoded as a BASE64 string. + /// The URL used to join the meeting. + /// The title of the meeting. + /// The meeting's type. + /// Timestamp for the meeting start, in UTC. + public MeetingStartEventDetails( + string id, + Uri joinUrl = null, + string title = null, + string meetingType = "Scheduled", + DateTime startTime = default) + : base(id, joinUrl, title, meetingType) + { + StartTime = startTime; + + CustomInit(); + } + + /// + /// Gets or sets the meeting's start time, in UTC. + /// + /// + /// The meeting's start time, in UTC. + /// + [JsonProperty(PropertyName = "StartTime")] + public DateTime StartTime { get; set; } + + /// + /// An initialization method that performs custom operations like setting defaults. + /// + partial void CustomInit(); + } +} From f72691209c0c9a85c0bbbae79d7f837008979aad Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 21 Jun 2021 11:15:29 -0700 Subject: [PATCH 07/10] add meeting event handlers --- .../Teams/TeamsActivityHandler.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs index 414775c535..bf1ad77a44 100644 --- a/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs +++ b/libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs @@ -738,6 +738,61 @@ protected virtual Task OnTeamsTeamUnarchivedAsync(TeamInfo teamInfo, ITurnContex return Task.CompletedTask; } + /// + /// Invoked when an event activity is received from the channel. + /// Event activities can be used to communicate many different things. + /// + /// A strongly-typed context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + /// + /// In a derived class, override this method to add logic that applies to all event activities. + /// + protected override Task OnEventActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + if (turnContext.Activity.ChannelId == Channels.Msteams) + { + switch (turnContext.Activity.Name) + { + case "application/vnd.microsoft.meetingStart": + return OnTeamsMeetingStartAsync(JObject.FromObject(turnContext.Activity.Value).ToObject(), turnContext, cancellationToken); + case "application/vnd.microsoft.meetingEnd": + return OnTeamsMeetingEndAsync(JObject.FromObject(turnContext.Activity.Value).ToObject(), turnContext, cancellationToken); + } + } + + return base.OnEventActivityAsync(turnContext, cancellationToken); + } + + /// + /// Invoked when a Teams Meeting Start event activity is received from the connector. + /// Override this in a derived class to provide logic for when a meeting is started. + /// + /// The details of the meeting. + /// A strongly-typed context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + protected virtual Task OnTeamsMeetingStartAsync(MeetingStartEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + /// Invoked when a Teams Meeting End event activity is received from the connector. + /// Override this in a derived class to provide logic for when a meeting is ended. + /// + /// The details of the meeting. + /// A strongly-typed context object for this turn. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// A task that represents the work queued to execute. + protected virtual Task OnTeamsMeetingEndAsync(MeetingEndEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + /// /// Safely casts an object to an object of type . /// From 75056271d9d960e15796d30ba8ae23562a71063c Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 21 Jun 2021 11:39:45 -0700 Subject: [PATCH 08/10] add meeting event tests --- .../Teams/TeamsActivityHandlerTests.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs index 96a957884e..8043a2ede2 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs @@ -1051,6 +1051,93 @@ void CaptureSend(Activity[] arg) Assert.Equal(200, ((InvokeResponse)activitiesToSend[0].Value).Status); } + [Fact] + public async Task TestOnEventActivity() + { + // Arrange + var activity = new Activity + { + ChannelId = Channels.Directline, + Type = ActivityTypes.Event + }; + + var turnContext = new TurnContext(new SimpleAdapter(), activity); + + // Act + var bot = new TestActivityHandler(); + await ((IBot)bot).OnTurnAsync(turnContext); + + // Assert + Assert.Single(bot.Record); + Assert.Equal("OnEventActivityAsync", bot.Record[0]); + } + + [Fact] + public async Task TestMeetingStartEvent() + { + // Arrange + var activity = new Activity + { + ChannelId = Channels.Msteams, + Type = ActivityTypes.Event, + Name = "application/vnd.microsoft.meetingStart", + Value = JObject.Parse(@"{""StartTime"":""2021-06-05T00:01:02.0Z""}"), + }; + + Activity[] activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + + var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity); + + // Act + var bot = new TestActivityHandler(); + await ((IBot)bot).OnTurnAsync(turnContext); + + // Assert + Assert.Equal(2, bot.Record.Count); + Assert.Equal("OnEventActivityAsync", bot.Record[0]); + Assert.Equal("OnTeamsMeetingStartAsync", bot.Record[1]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("6/5/2021 12:01:02 AM", activitiesToSend[0].Text); + } + + [Fact] + public async Task TestMeetingEndEvent() + { + // Arrange + var activity = new Activity + { + ChannelId = Channels.Msteams, + Type = ActivityTypes.Event, + Name = "application/vnd.microsoft.meetingEnd", + Value = JObject.Parse(@"{""EndTime"":""2021-06-05T01:02:03.0Z""}"), + }; + + Activity[] activitiesToSend = null; + void CaptureSend(Activity[] arg) + { + activitiesToSend = arg; + } + + var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity); + + // Act + var bot = new TestActivityHandler(); + await ((IBot)bot).OnTurnAsync(turnContext); + + // Assert + Assert.Equal(2, bot.Record.Count); + Assert.Equal("OnEventActivityAsync", bot.Record[0]); + Assert.Equal("OnTeamsMeetingEndAsync", bot.Record[1]); + Assert.NotNull(activitiesToSend); + Assert.Single(activitiesToSend); + Assert.Equal("6/5/2021 1:02:03 AM", activitiesToSend[0].Text); + } + private class NotImplementedAdapter : BotAdapter { public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken) @@ -1296,6 +1383,26 @@ protected override Task OnTeamsTabSubmitAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + Record.Add(MethodBase.GetCurrentMethod().Name); + return base.OnEventActivityAsync(turnContext, cancellationToken); + } + + protected override Task OnTeamsMeetingStartAsync(MeetingStartEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + Record.Add(MethodBase.GetCurrentMethod().Name); + turnContext.SendActivityAsync(meeting.StartTime.ToString()); + return Task.CompletedTask; + } + + protected override Task OnTeamsMeetingEndAsync(MeetingEndEventDetails meeting, ITurnContext turnContext, CancellationToken cancellationToken) + { + Record.Add(MethodBase.GetCurrentMethod().Name); + turnContext.SendActivityAsync(meeting.EndTime.ToString()); + return Task.CompletedTask; + } } private class RosterHttpMessageHandler : HttpMessageHandler From 5ad1f9929e62ccba46bf8401a47d233cb3345803 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 22 Jun 2021 11:00:44 -0700 Subject: [PATCH 09/10] fix tests for MacOS --- .../Teams/TeamsActivityHandlerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs index 8043a2ede2..3656360178 100644 --- a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs +++ b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs @@ -1102,7 +1102,7 @@ void CaptureSend(Activity[] arg) Assert.Equal("OnTeamsMeetingStartAsync", bot.Record[1]); Assert.NotNull(activitiesToSend); Assert.Single(activitiesToSend); - Assert.Equal("6/5/2021 12:01:02 AM", activitiesToSend[0].Text); + Assert.Contains("12:01:02 AM", activitiesToSend[0].Text); // Date format differs between OSs, so we just Assert.Contains instead of Assert.Equals } [Fact] @@ -1135,7 +1135,7 @@ void CaptureSend(Activity[] arg) Assert.Equal("OnTeamsMeetingEndAsync", bot.Record[1]); Assert.NotNull(activitiesToSend); Assert.Single(activitiesToSend); - Assert.Equal("6/5/2021 1:02:03 AM", activitiesToSend[0].Text); + Assert.Contains("1:02:03 AM", activitiesToSend[0].Text); // Date format differs between OSs, so we just Assert.Contains instead of Assert.Equals } private class NotImplementedAdapter : BotAdapter From 28a365946493cc1f71a3a4d6a991baf45000bd4c Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 22 Jun 2021 11:45:10 -0700 Subject: [PATCH 10/10] move usings outside namespace --- libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs | 5 ++--- libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs | 5 ++--- .../Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs | 5 ++--- libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs | 5 ++--- libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs | 3 +-- .../Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs | 5 ++--- 6 files changed, 11 insertions(+), 17 deletions(-) diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs index 5b6cf348ef..5136ca26c2 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using Newtonsoft.Json; namespace Microsoft.Bot.Schema.Teams { - using System; - using Newtonsoft.Json; - /// /// Specific details of a Teams meeting. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs index 3be50877d1..5ab4005602 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using Newtonsoft.Json; namespace Microsoft.Bot.Schema.Teams { - using System; - using Newtonsoft.Json; - /// /// Specific details of a Teams meeting. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs index 78d61212eb..da02524727 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using Newtonsoft.Json; namespace Microsoft.Bot.Schema.Teams { - using System; - using Newtonsoft.Json; - /// /// Specific details of a Teams meeting end event. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs index 748827b3e9..bf8f62c031 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using Newtonsoft.Json; namespace Microsoft.Bot.Schema.Teams { - using System; - using Newtonsoft.Json; - /// /// Specific details of a Teams meeting. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs index 7d98ac5d5b..a5c4e507c5 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Newtonsoft.Json; namespace Microsoft.Bot.Schema.Teams { - using Newtonsoft.Json; - /// /// General information about a Teams meeting. /// diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs index 24e89575c8..2fb1ab260e 100644 --- a/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs +++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; +using Newtonsoft.Json; namespace Microsoft.Bot.Schema.Teams { - using System; - using Newtonsoft.Json; - /// /// Specific details of a Teams meeting start event. ///