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 .
///
diff --git a/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs b/libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
index 3c5730c4a3..748f4dcf9c 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 information for the given meeting id.
+ ///
+ /// Turn context.
+ /// The BASE64-encoded id of the Teams meeting.
+ /// Cancellation token.
+ /// Team Details.
+ 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.FetchMeetingInfoAsync(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..9b0beab02f 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> FetchMeetingInfoWithHttpMessagesAsync(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, "FetchMeetingInfo", 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..f53048a595 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 information 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 FetchMeetingInfoAsync(this ITeamsOperations operations, string meetingId, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (operations is TeamsOperations teamsOperations)
+ {
+ using var result = await teamsOperations.FetchMeetingInfoWithHttpMessagesAsync(meetingId, null, cancellationToken).ConfigureAwait(false);
+ return result.Body;
+ }
+
+ throw new InvalidOperationException("TeamsOperations with GetMeetingInfoWithHttpMessagesAsync is required for FetchMeetingInfoAsync.");
+ }
+
///
/// Fetches participant details related to a Teams meeting.
///
diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs
new file mode 100644
index 0000000000..5136ca26c2
--- /dev/null
+++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.Bot.Schema.Teams
+{
+ ///
+ /// Specific details of a Teams meeting.
+ ///
+ public partial class MeetingDetails : MeetingDetailsBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MeetingDetails()
+ {
+ 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 MeetingDetails(
+ string id,
+ string msGraphResourceId = null,
+ DateTime scheduledStartTime = default,
+ DateTime scheduledEndTime = default,
+ Uri joinUrl = null,
+ string title = null,
+ string type = "Scheduled")
+ : base(id, joinUrl, title)
+ {
+ MsGraphResourceId = msGraphResourceId;
+ ScheduledStartTime = scheduledStartTime;
+ ScheduledEndTime = scheduledEndTime;
+ Type = type;
+
+ CustomInit();
+ }
+
+ ///
+ /// 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 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/MeetingDetailsBase.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs
new file mode 100644
index 0000000000..5ab4005602
--- /dev/null
+++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.Bot.Schema.Teams
+{
+ ///
+ /// 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..da02524727
--- /dev/null
+++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.Bot.Schema.Teams
+{
+ ///
+ /// 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..bf8f62c031
--- /dev/null
+++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingEventDetails.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.Bot.Schema.Teams
+{
+ ///
+ /// 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/MeetingInfo.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs
new file mode 100644
index 0000000000..a5c4e507c5
--- /dev/null
+++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingInfo.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using Newtonsoft.Json;
+
+namespace Microsoft.Bot.Schema.Teams
+{
+ ///
+ /// 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 specific details of a Teams meeting.
+ ///
+ ///
+ /// The specific details of a Teams meeting.
+ ///
+ [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();
+ }
+}
diff --git a/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs
new file mode 100644
index 0000000000..2fb1ab260e
--- /dev/null
+++ b/libraries/Microsoft.Bot.Schema/Teams/MeetingStartEventDetails.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using Newtonsoft.Json;
+
+namespace Microsoft.Bot.Schema.Teams
+{
+ ///
+ /// 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();
+ }
+}
diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsActivityHandlerTests.cs
index 96a957884e..3656360178 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.Contains("12:01:02 AM", activitiesToSend[0].Text); // Date format differs between OSs, so we just Assert.Contains instead of Assert.Equals
+ }
+
+ [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.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
{
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
diff --git a/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs b/tests/Microsoft.Bot.Builder.Tests/Teams/TeamsInfoTests.cs
index c228be466d..12b071f131 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 TestGetMeetingInfoAsync()
+ {
+ 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-GetMeetingInfoAsync",
+ 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-GetMeetingInfoAsync":
+ await CallTeamsInfoGetMeetingInfoAsync(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 CallTeamsInfoGetMeetingInfoAsync(ITurnContext turnContext)
+ {
+ var meeting = await TeamsInfo.GetMeetingInfoAsync(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);
}
}