Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Licensed under the MIT License.
// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using AdaptiveExpressions.Properties;
using Microsoft.Bot.Builder.Teams;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema.Teams;
using Newtonsoft.Json;

namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Actions
{
/// <summary>
/// Calls TeamsInfo.GetMeetingParticipantAsync and sets the result to a memory property.
/// </summary>
public class GetMeetingParticipant : Dialog
{
/// <summary>
/// Class identifier.
/// </summary>
[JsonProperty("$kind")]
public const string Kind = "Teams.GetMeetingParticipant";

/// <summary>
/// Initializes a new instance of the <see cref="GetMeetingParticipant"/> class.
/// </summary>
/// <param name="callerPath">Optional, source file full path.</param>
/// <param name="callerLine">Optional, line number in source file.</param>
[JsonConstructor]
public GetMeetingParticipant([CallerFilePath] string callerPath = "", [CallerLineNumber] int callerLine = 0)
: base()
{
this.RegisterSourceLocation(callerPath, callerLine);
}

/// <summary>
/// Gets or sets an optional expression which if is true will disable this action.
/// </summary>
/// <example>
/// "user.age > 18".
/// </example>
/// <value>
/// A boolean expression.
/// </value>
[JsonProperty("disabled")]
public BoolExpression Disabled { get; set; }

/// <summary>
/// Gets or sets property path to put the value in.
/// </summary>
/// <value>
/// Property path to put the value in.
/// </value>
[JsonProperty("property")]
public StringExpression Property { get; set; }

/// <summary>
/// Gets or sets the expression to get the value to use for meeting id.
/// </summary>
/// <value>
/// The expression to get the value to use for meeting id. If this is missing, then the current turn Activity.TeamsChannelData.Meeting.Id will be used.
/// </value>
[JsonProperty("meetingId")]
public StringExpression MeetingId { get; set; }

/// <summary>
/// Gets or sets the expression to get the value to use for participant id.
/// </summary>
/// <value>
/// The expression to get the value to use for participant id. If this is missing, then the current turn Activity.From.AadObjectId will be used.
/// </value>
[JsonProperty("participantId")]
public StringExpression ParticipantId { get; set; }

/// <summary>
/// Gets or sets the expression to get the value to use for tenant id.
/// </summary>
/// <value>
/// The expression to get the value to use for tenant id. If this is missing, then the current turn Activity.TeamsChannelData.Tenant.Id will be used.
/// </value>
[JsonProperty("tenantId")]
public StringExpression TenantId { get; set; }
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated

/// <summary>
/// Called when the dialog is started and pushed onto the dialog stack.
/// </summary>
/// <param name="dc">The <see cref="DialogContext"/> for the current turn of conversation.</param>
/// <param name="options">Optional, initial information to pass to the dialog.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
if (options is CancellationToken)
{
throw new ArgumentException($"{nameof(options)} cannot be a cancellation token");
}

if (this.Disabled != null && this.Disabled.GetValue(dc.State) == true)
{
return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}

if (dc.Context.Activity.ChannelId != Channels.Msteams)
{
throw new Exception("TeamsInfo.GetMeetingParticipantAsync() works only on the Teams channel.");
}

string meetingId = GetValueOrNull(dc, this.MeetingId) ?? dc.Context.Activity.TeamsGetMeetingInfo()?.Id;
string participantId = GetValueOrNull(dc, this.ParticipantId) ?? dc.Context.Activity.From.AadObjectId;
string tenantId = GetValueOrNull(dc, this.TenantId) ?? dc.Context.Activity.GetChannelData<TeamsChannelData>()?.Tenant?.Id;

var result = await TeamsInfo.GetMeetingParticipantAsync(dc.Context, meetingId, participantId, tenantId, cancellationToken: cancellationToken).ConfigureAwait(false);

dc.State.SetValue(this.Property.GetValue(dc.State), result);

return await dc.EndDialogAsync(result, cancellationToken: cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Builds the compute Id for the dialog.
/// </summary>
/// <returns>A string representing the compute Id.</returns>
protected override string OnComputeId()
{
return $"{this.GetType().Name}[{this.MeetingId?.ToString() ?? string.Empty},{this.ParticipantId?.ToString() ?? string.Empty},{this.TenantId?.ToString() ?? string.Empty},{this.Property?.ToString() ?? string.Empty}]";
}

private string GetValueOrNull(DialogContext dc, StringExpression stringExpression)
{
if (stringExpression != null)
{
var (value, valueError) = stringExpression.TryGetValue(dc.State);
if (valueError != null)
{
throw new Exception($"Expression evaluation resulted in an error. Expression: {stringExpression.ExpressionText}. Error: {valueError}");
}

return value as string;
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
</ItemGroup>

<ItemGroup>
<None Remove="Schemas\Actions\Teams.GetMeetingParticipant.schema" />
<None Remove="Schemas\TriggerConditions\Microsoft.OnInvokeActivity.schema" />
<None Remove="Schemas\TriggerConditions\Teams.OnChannelRestored.schema" />
<None Remove="Schemas\TriggerConditions\Teams.OnTeamArchived.schema" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema",
"$role": "implements(Microsoft.IDialog)",
"title": "Get Meeting Participant",
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
"description": "Get teams meeting partipant information.",
"type": "object",
"properties": {
"id": {
"type": "string",
"title": "Id",
"description": "Optional id for the dialog"
},
"property": {
"$ref": "schema:#/definitions/stringExpression",
"title": "Property",
"description": "Property (named location to store information).",
"examples": [
"user.participantInfo"
]
},
"meetingId": {
"$ref": "schema:#/definitions/stringExpression",
"title": "MeetingId",
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
"description": "Meeting Id or expression to a meetingId to use to get the participant information. If none is defined then the current turn Activity.TeamsChannelData.Meeting.Id will be used.",
"examples": [
"$lastActivity.teamsChannelData.meeting.id"
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
]
},
"participantId": {
"$ref": "schema:#/definitions/stringExpression",
"title": "ParticipantId",
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
"description": "Participant Id or expression to a participantId to use to get the participant information. If none is defined then the current turn Activity.From.AadObjectId will be used.",
"examples": [
"$lastActivity.from.aadObjectId"
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
]
},
"tenantId": {
"$ref": "schema:#/definitions/stringExpression",
"title": "TenantId",
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
"description": "Tenant Id or expression to a tenantId to use to get the participant information. If none is defined then the current turn Activity.TeamsChannelData.Tenant.Id will be used.",
"examples": [
"$lastActivity.teamsChannelData.tenant.id"
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
]
},
"disabled": {
"$ref": "schema:#/definitions/booleanExpression",
"title": "Disabled",
"description": "Optional condition which if true will disable this action.",
"examples": [
"user.age > 3"
Comment thread
EricDahlvang marked this conversation as resolved.
Outdated
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Collections.Generic;
using Microsoft.Bot.Builder.Dialogs.Adaptive.Actions;
using Microsoft.Bot.Builder.Dialogs.Debugging;
using Microsoft.Bot.Builder.Dialogs.Declarative;
using Microsoft.Bot.Builder.Dialogs.Declarative.Resources;
Expand Down Expand Up @@ -37,6 +38,9 @@ public virtual IEnumerable<DeclarativeType> GetDeclarativeTypes(ResourceExplorer
yield return new DeclarativeType<OnTeamsTeamRenamed>(OnTeamsTeamRenamed.Kind);
yield return new DeclarativeType<OnTeamsTeamRestored>(OnTeamsTeamRestored.Kind);
yield return new DeclarativeType<OnTeamsTeamUnarchived>(OnTeamsTeamUnarchived.Kind);

// Actions
yield return new DeclarativeType<GetMeetingParticipant>(GetMeetingParticipant.Kind);
}

public virtual IEnumerable<JsonConverter> GetConverters(ResourceExplorer resourceExplorer, SourceContext sourceContext)
Expand Down
11 changes: 11 additions & 0 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsActivityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ namespace Microsoft.Bot.Builder.Teams
/// </summary>
public static class TeamsActivityExtensions
{
/// <summary>
/// Gets the TeamsMeetingInfo object from the current activity.
/// </summary>
/// <param name="activity">This activity.</param>
/// <returns>The current activity's team's meeting, or null.</returns>
public static TeamsMeetingInfo TeamsGetMeetingInfo(this IActivity activity)
{
var channelData = activity.GetChannelData<TeamsChannelData>();
return channelData?.Meeting;
}

/// <summary>
/// Gets the Team's channel id from the current activity.
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ namespace Microsoft.Bot.Builder.Teams
/// </summary>
public static class TeamsInfo
{
/// <summary>
/// Gets the details for the given meeting participant. This only works in teams meeting scoped conversations.
/// </summary>
/// <param name="turnContext">Turn context.</param>
/// <param name="meetingId">The id of the Teams meeting. TeamsChannelData.Meeting.Id will be used if none provided.</param>
/// <param name="participantId">The id of the Teams meeting participant. From.AadObjectId will be used if none provided.</param>
/// <param name="tenantId">The id of the Teams meeting Tenant. TeamsChannelData.Tenant.Id will be used if none provided.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <remarks>InvalidOperationException will be thrown if meetingId, participantId or tenantId have not been
/// provided, and also cannot be retrieved from turnContext.Activity.</remarks>
/// <returns>Team participant channel account.</returns>
public static async Task<TeamsParticipantChannelAccount> GetMeetingParticipantAsync(ITurnContext turnContext, string meetingId = null, string participantId = null, string tenantId = null, CancellationToken cancellationToken = default)
{
meetingId ??= turnContext.Activity.TeamsGetMeetingInfo()?.Id ?? throw new InvalidOperationException("This method is only valid within the scope of a MS Teams Meeting.");
participantId ??= turnContext.Activity.From.AadObjectId ?? throw new InvalidOperationException($"{nameof(participantId)} is required.");
tenantId ??= turnContext.Activity.GetChannelData<TeamsChannelData>()?.Tenant?.Id ?? throw new InvalidOperationException($"{nameof(tenantId)} is required.");

#pragma warning disable CA2000 // Dispose objects before losing scope (we need to review this, disposing the connectorClient may have unintended consequences)
return await GetTeamsConnectorClient(turnContext).Teams.FetchParticipantAsync(meetingId, participantId, tenantId, cancellationToken).ConfigureAwait(false);
#pragma warning restore CA2000 // Dispose objects before losing scope
}

/// <summary>
/// Gets the details for the given team id. This only works in teams scoped conversations.
/// </summary>
Expand Down
Loading