Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,61 @@ protected virtual Task OnTeamsTeamUnarchivedAsync(TeamInfo teamInfo, ITurnContex
return Task.CompletedTask;
}

/// <summary>
/// Invoked when an event activity is received from the channel.
/// Event activities can be used to communicate many different things.
/// </summary>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <remarks>
/// In a derived class, override this method to add logic that applies to all event activities.
/// </remarks>
protected override Task OnEventActivityAsync(ITurnContext<IEventActivity> 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<MeetingStartEventDetails>(), turnContext, cancellationToken);
case "application/vnd.microsoft.meetingEnd":
return OnTeamsMeetingEndAsync(JObject.FromObject(turnContext.Activity.Value).ToObject<MeetingEndEventDetails>(), turnContext, cancellationToken);
}
}

return base.OnEventActivityAsync(turnContext, cancellationToken);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="meeting">The details of the meeting.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected virtual Task OnTeamsMeetingStartAsync(MeetingStartEventDetails meeting, ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="meeting">The details of the meeting.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected virtual Task OnTeamsMeetingEndAsync(MeetingEndEventDetails meeting, ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

Comment on lines +768 to +795
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: There's a couple of things I'm not sure about with these:

  1. Argument order. Some methods (like OnTeamsFileConsent) have turnContext first, while others (like OnTeamsChannelDeleted) have turnContext come after the method-specific arguments (like channelInfo and teamsInfo).
  2. Based on the other overrides and virtuals, it looks like not marking these methods as async is the right call. Just want to confirm.

/// <summary>
/// Safely casts an object to an object of type <typeparamref name="T"/> .
/// </summary>
Expand Down
16 changes: 16 additions & 0 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ public static async Task<TeamsMeetingParticipant> GetMeetingParticipantAsync(ITu
}
}

/// <summary>
/// Gets the information for the given meeting id.
/// </summary>
/// <param name="turnContext"> Turn context.</param>
/// <param name="meetingId"> The BASE64-encoded id of the Teams meeting.</param>
/// <param name="cancellationToken"> Cancellation token.</param>
/// <returns>Team Details.</returns>
public static async Task<MeetingInfo> 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);
}
}

/// <summary>
/// Gets the details for the given team id. This only works in teams scoped conversations.
/// </summary>
Expand Down
54 changes: 54 additions & 0 deletions libraries/Microsoft.Bot.Connector/Teams/TeamsOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,60 @@ public TeamsOperations(TeamsConnectorClient client)
return await GetResponseAsync<TeamDetails>(url, shouldTrace, invocationId).ConfigureAwait(false);
}

/// <summary>
/// Fetches details related to a meeting.
/// </summary>
/// <param name='meetingId'>
/// Meeting Id, encoded as a BASE64 string.
/// </param>
/// <param name='customHeaders'>
/// Headers that will be added to request.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <exception cref="HttpOperationException">
/// Thrown when the operation returned an invalid status code.
/// </exception>
/// <exception cref="SerializationException">
/// Thrown when unable to deserialize the response.
/// </exception>
/// <exception cref="ValidationException">
/// Thrown when an input value does not match the expected data type, range or pattern.
/// </exception>
/// <exception cref="System.ArgumentNullException">
/// Thrown when a required parameter is null.
/// </exception>
/// <returns>
/// A response object containing the response body and response headers.
/// </returns>
public async Task<HttpOperationResponse<MeetingInfo>> FetchMeetingInfoWithHttpMessagesAsync(string meetingId, Dictionary<string, List<string>> 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<string, object> tracingParameters = new Dictionary<string, object>();
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<MeetingInfo>(url, shouldTrace, invocationId, customHeaders).ConfigureAwait(false);
}

/// <summary>
/// Fetches Teams meeting participant details.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ public static partial class TeamsOperationsExtensions
}
}

/// <summary>
/// Fetches information related to a Teams meeting.
/// </summary>
/// <param name='operations'>
/// The operations group for this extension method.
/// </param>
/// <param name='meetingId'>
/// Meeting Id.
/// </param>
/// <param name='cancellationToken'>
/// The cancellation token.
/// </param>
/// <returns>The details related to a team.</returns>
public static async Task<MeetingInfo> 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.");
}

/// <summary>
/// Fetches participant details related to a Teams meeting.
/// </summary>
Expand Down
90 changes: 90 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/MeetingDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Copy link
Contributor Author

@mdrichardson mdrichardson Jun 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The inheritance around these new classes/interfaces is based around how Teams sends different but similar payloads for GetMeetingDetails and the meeting start/end events.

See review comments (some marked as resolved) for the JS PR for previous discussion.

// Licensed under the MIT License.
using System;
using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.Teams
{
/// <summary>
/// Specific details of a Teams meeting.
/// </summary>
public partial class MeetingDetails : MeetingDetailsBase
{
/// <summary>
/// Initializes a new instance of the <see cref="MeetingDetails"/> class.
/// </summary>
public MeetingDetails()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="MeetingDetails"/> class.
/// </summary>
/// <param name="id">The meeting's Id, encoded as a BASE64 string.</param>
/// <param name="msGraphResourceId">The MsGraphResourceId, used specifically for MS Graph API calls.</param>
/// <param name="scheduledStartTime">The meeting's scheduled start time, in UTC.</param>
/// <param name="scheduledEndTime">The meeting's scheduled end time, in UTC.</param>
/// <param name="joinUrl">The URL used to join the meeting.</param>
/// <param name="title">The title of the meeting.</param>
/// <param name="type">The meeting's type.</param>
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();
}

/// <summary>
/// Gets or sets the MsGraphResourceId, used specifically for MS Graph API calls.
/// </summary>
/// <value>
/// The MsGraphResourceId, used specifically for MS Graph API calls.
/// </value>
[JsonProperty(PropertyName = "msGraphResourceId")]
public string MsGraphResourceId { get; set; }

/// <summary>
/// Gets or sets the meeting's scheduled start time, in UTC.
/// </summary>
/// <value>
/// The meeting's scheduled start time, in UTC.
/// </value>
[JsonProperty(PropertyName = "scheduledStartTime")]
public DateTime ScheduledStartTime { get; set; }

/// <summary>
/// Gets or sets the meeting's scheduled end time, in UTC.
/// </summary>
/// <value>
/// The meeting's scheduled end time, in UTC.
/// </value>
[JsonProperty(PropertyName = "scheduledEndTime")]
public DateTime ScheduledEndTime { get; set; }

/// <summary>
/// Gets or sets the meeting's type.
/// </summary>
/// <value>
/// The meeting's type.
/// </value>
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }

/// <summary>
/// An initialization method that performs custom operations like setting defaults.
/// </summary>
partial void CustomInit();
}
}
71 changes: 71 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/MeetingDetailsBase.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Specific details of a Teams meeting.
/// </summary>
public partial class MeetingDetailsBase
{
/// <summary>
/// Initializes a new instance of the <see cref="MeetingDetailsBase"/> class.
/// </summary>
internal MeetingDetailsBase()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="MeetingDetailsBase"/> class.
/// </summary>
/// <param name="id">The meeting's Id, encoded as a BASE64 string.</param>
/// <param name="joinUrl">The URL used to join the meeting.</param>
/// <param name="title">The title of the meeting.</param>
internal MeetingDetailsBase(
string id,
Uri joinUrl = null,
string title = null)
{
Id = id;
JoinUrl = joinUrl;
Title = title;

CustomInit();
}

/// <summary>
/// Gets or sets the meeting's Id, encoded as a BASE64 string.
/// </summary>
/// <value>
/// The meeting's Id, encoded as a BASE64 string.
/// </value>
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }

/// <summary>
/// Gets or sets the URL used to join the meeting.
/// </summary>
/// <value>
/// The URL used to join the meeting.
/// </value>
[JsonProperty(PropertyName = "joinUrl")]
public Uri JoinUrl { get; set; }

/// <summary>
/// Gets or sets the title of the meeting.
/// </summary>
/// <value>
/// The title of the meeting.
/// </value>
[JsonProperty(PropertyName = "title")]
public string Title { get; set; }

/// <summary>
/// An initialization method that performs custom operations like setting defaults.
/// </summary>
partial void CustomInit();
}
}
56 changes: 56 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/MeetingEndEventDetails.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Specific details of a Teams meeting end event.
/// </summary>
public partial class MeetingEndEventDetails : MeetingEventDetails
{
/// <summary>
/// Initializes a new instance of the <see cref="MeetingEndEventDetails"/> class.
/// </summary>
public MeetingEndEventDetails()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="MeetingEndEventDetails"/> class.
/// </summary>
/// <param name="id">The meeting's Id, encoded as a BASE64 string.</param>
/// <param name="joinUrl">The URL used to join the meeting.</param>
/// <param name="title">The title of the meeting.</param>
/// <param name="meetingType">The meeting's type.</param>
/// <param name="endTime">Timestamp for the meeting end, in UTC.</param>
public MeetingEndEventDetails(
string id,
Uri joinUrl = null,
string title = null,
string meetingType = "Scheduled",
DateTime endTime = default)
: base(id, joinUrl, title, meetingType)
{
EndTime = endTime;

CustomInit();
}

/// <summary>
/// Gets or sets the meeting's end time, in UTC.
/// </summary>
/// <value>
/// The meeting's end time, in UTC.
/// </value>
[JsonProperty(PropertyName = "EndTime")]
public DateTime EndTime { get; set; }

/// <summary>
/// An initialization method that performs custom operations like setting defaults.
/// </summary>
partial void CustomInit();
}
}
Loading