Reputation: 3230
Using the "Hello World" Microsoft Teams application sample from here: https://github.com/OfficeDev/msteams-samples-hello-world-csharp
Trying to get a list of participants in a personal Microsoft Teams 1:1 chat after an action command from a messaging extension is invoked. Specifically, I need the e-mail address of the other participant, where I am the first participant.
This is the code from the messages controller:
[BotAuthentication]
public class MessagesController : ApiController
{
[HttpPost]
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
using (var connector = new ConnectorClient(new Uri(activity.ServiceUrl)))
{
if (activity.IsComposeExtensionQuery())
{
// Invoke the command handler
var response = await MessageExtension.HandleMessageExtensionQuery(connector, activity).ConfigureAwait(false);
return response != null
? Request.CreateResponse<ComposeExtensionResponse>(response)
: new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
await EchoBot.EchoMessage(connector, activity);
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
}
}
}
The code from MessageExtension.HandleMessageExtensionQuery
is as follows:
public static async Task<ComposeExtensionResponse> HandleMessageExtensionQuery(ConnectorClient connector, Activity activity)
{
var query = activity.GetComposeExtensionQueryData();
if (query == null)
{
return null;
}
// Exception thrown here - error 403, there is no additional data except "Operation returned an invalid status code 'Forbidden'"
var members = await connector.Conversations.GetConversationMembersAsync(activity.Conversation.Id);
var handler = GetCommandHandler(query.CommandId); // Gets a handler based on the command, irrelevant for this question
if (handler == null)
{
return null;
}
return await handler.HandleCommand(query, members); // Should handle the command, but never comes here if we are in a 1:1 conversation
}
The call to GetConversationMembersAsync
fails with the following message: Operation returned an invalid status code 'Forbidden'
if the command is invoked from a 1:1 personal conversation between two people.
The call doesn't fail if invoked from a group channel.
How to get the list of participants of a 1:1 personal conversation? Do I have to authenticate my user through the bot in order to do that, or do I have to grant my bot some particular permissions? Does my account need to have some particular permissions in order to do this?
EDIT - Added the app manifest
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "1ce95960-0417-4469-ab77-5052758a4e7e",
"packageName": "com.contoso.helloworld",
"developer": {
"name": "Contoso",
"websiteUrl": "https://8112abe3.ngrok.io",
"privacyUrl": "https://8112abe3.ngrok.io/privacy-policy",
"termsOfUseUrl": "https://8112abe3.ngrok.io/terms-service"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "Hello World",
"full": "Hello World App"
},
"description": {
"short": "Hello World App for Microsoft Teams",
"full": "This sample app provides a very simple app. You can extend this to add more content and capabilities."
},
"accentColor": "#60A18E",
"configurableTabs": [
{
"configurationUrl": "https://526d7c43.ngrok.io/configure",
"canUpdateConfiguration": true,
"scopes": [
"team",
"groupchat"
]
}
],
"staticTabs": [
{
"entityId": "com.contoso.helloworld.hellotab",
"name": "Hello Tab",
"contentUrl": "https://8112abe3.ngrok.io/hello",
"websiteUrl": "https://8112abe3.ngrok.io/hello",
"scopes": [
"personal"
]
}
],
"bots": [
{
"botId": "bfbcb607-5c29-4438-85a5-15e63fb0b273",
"scopes": [
"personal",
"team",
"groupchat"
],
"supportsFiles": false,
"isNotificationOnly": false
}
],
"composeExtensions": [
{
"botId": "bfbcb607-5c29-4438-85a5-15e63fb0b273",
"canUpdateConfiguration": true,
"commands": [
{
"id": "getRandomText",
"type": "query",
"title": "Get random text",
"description": "",
"initialRun": true,
"fetchTask": false,
"context": [
"commandBox",
"compose",
"message"
],
"parameters": [
{
"name": "cardTitle",
"title": "Subject",
"description": "",
"inputType": "text"
}
]
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"8112abe3.ngrok.io"
]
}
EDIT 2 - After trying, based on sample 51 - TeamsMessagingExtensionsAction
As suggested, I tried with the sample 51 called "TeamsMessagingExtensionsAction" The code is:
MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
var members = (await turnContext.TurnState.Get<IConnectorClient>().Conversations.GetConversationMembersAsync(
turnContext.Activity.Conversation.Id).ConfigureAwait(false)).ToList();
The exception along with the stack trace:
Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Forbidden'
at Microsoft.Bot.Connector.Conversations.GetConversationMembersWithHttpMessagesAsync(String conversationId, Dictionary`2 customHeaders, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Connector\Conversations.cs:line 1462
at Microsoft.BotBuilderSamples.Bots.TeamsMessagingExtensionsActionBot.ShareMessageCommand(ITurnContext`1 turnContext, MessagingExtensionAction action) in D:\Visual Studio Projects\botbuilder-samples\samples\csharp_dotnetcore\51.teams-messaging-extensions-action\Bots\TeamsMessagingExtensionsActionBot.cs:line 68
at Microsoft.BotBuilderSamples.Bots.TeamsMessagingExtensionsActionBot.OnTeamsMessagingExtensionSubmitActionAsync(ITurnContext`1 turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) in D:\Visual Studio Projects\botbuilder-samples\samples\csharp_dotnetcore\51.teams-messaging-extensions-action\Bots\TeamsMessagingExtensionsActionBot.cs:line 29
at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnTeamsMessagingExtensionSubmitActionDispatchAsync(ITurnContext`1 turnContext, MessagingExtensionAction action, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 201
at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnInvokeActivityAsync(ITurnContext`1 turnContext, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 88
at Microsoft.Bot.Builder.Teams.TeamsActivityHandler.OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\Teams\TeamsActivityHandler.cs:line 39
at Microsoft.Bot.Builder.BotFrameworkAdapter.TenantIdWorkaroundForTeamsMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\BotFrameworkAdapter.cs:line 1158
at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\MiddlewareSet.cs:line 55
at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in d:\a\1\s\libraries\Microsoft.Bot.Builder\BotAdapter.cs:line 182
EDIT 3 - Tried with the sample 57
So I have started with the sample 57 called "TeamsConversationBot" and TLDR; works in channel, doesn't work in a private conversation.
Here are the steps performed:
Note: ngrok was already started using the command ngrok http -host-header=rewrite 3978
and my existing bot registration was already configured to listen on the given URL
var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);
In Visual Studio, added the following method to TeamsConversationBot.cs:
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
var connector = turnContext.TurnState.Get<IConnectorClient>();
var conversation = turnContext.Activity.Conversation;
var members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
return await base.OnInvokeActivityAsync(turnContext, cancellationToken);
}
Set a breakpoint on the line which says var members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
and start the project
members
variable contained all members of the channel. So that works too!Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Forbidden'
Upvotes: 1
Views: 3244
Reputation: 3230
Actually, there is a way to get the list of participants in a 1:1 private conversation between two people when invoking an action-based messaging extension. The trick is to get the bot into the private conversation too.
I thought about that solution in the beginning but didn't know how, and since no one suggested it, I thought it wasn't possible in one go. But turns out I was wrong.
So in case anyone ever stumbles upon this kind of problem. here it is.
Originally I did this using a FetchTask action messaging extension (the action-based messaging extension where you don't supply the predefined list of parameters, but fetch the parameters using the bot).
But it can be done using a regular action with static parameters too, so let's make an example using the method OnInvokeActivityAsync
that I used in the question. See comments in the code below for an explanation.
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
var connector = turnContext.TurnState.Get<IConnectorClient>();
var conversation = turnContext.Activity.Conversation;
IList<ChannelAccount> members;
try
{
members = await connector.Conversations.GetConversationMembersAsync(conversation.Id, cancellationToken);
}
catch (ErrorResponseException ex)
{
// If the ErrorResponseException contains the response with status code 403, that means our bot is not a member of this conversation.
// In that case, return an adaptive card containing the prompt to add the bot to the current conversation.
// After accepting the prompt, the bot will be added to the conversation and we will be able to obtain the list of conversation participants.
if (ex.Response.StatusCode == HttpStatusCode.Forbidden)
{
return new InvokeResponse
{
Status = 200,
Body = AddBotToConversation()
};
}
throw;
}
// At this point, we have the list of conversation members
var otherMember = members.FirstOrDefault(x => x.Id != turnContext.Activity.From.Id);
return new InvokeResponse
{
Status = 200,
Body = await DoSomethingWithOtherMemberInformationAndReturnACard(otherMember, cancellationToken)
};
}
The AddBotToConversation
can be defined like this:
private MessagingExtensionActionResponse AddBotToConversation()
{
var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
{
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock("We need to add the bot to this conversation in order to perform the requested action"),
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction
{
Title = "Continue",
Data = new Dictionary<string, object>
{
// The magic happens here. This tells Teams to add this bot to the current conversation
["msteams"] = new Dictionary<string, bool>
{
["justInTimeInstall"] = true,
}
}
}
}
};
var invokeResponse = new MessagingExtensionActionResponse
{
Task = new TaskModuleContinueResponse
{
Value = new TaskModuleTaskInfo
{
Card = new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content = card
}
}
}
};
return invokeResponse;
}
Just make sure the messaging extension is action-based, not search-based. Search extensions will not support this approach.
EDIT Last but not least, don't forget to add the "groupchat"
scope to the "scopes"
collection of your bot under the "bots"
collection in the Teams App manifest.json file, or else you won't be able to add your bot to a private conversation.
Upvotes: 1
Reputation: 7241
Response to Edit 3
Sorry, it looks like I misunderstood what you were trying to do. You cannot do this with the Bot Framework. Technically, the bot is not part of a Personal conversation between you and another user. Therefore, it doesn't have permissions to get information about that conversation, even though it's available as a Messaging Extension.
You may be able to use this Graph API call, although you'll have to deal with getting the the auth token on your own:
https://graph.microsoft.com/beta/chats/<conversationId>/members
Making this an answer because it's too long to comment and I think it should accomplish what you need. Please let me know if this works or not and I can edit this
I didn't see anything on the backend by looking into your appId
, so I'm not sure of what the actual cause of the problem is.
You shouldn't need any permissions set at all -- those are only for OAuth, which GetConversationMembersAsync
doesn't need. You should only really need the scopes set in your manifest.json
, which appear to be set just fine.
However, for your App Registration, you may need to ensure that this is checked, under Authentication:
If it isn't you can manually adjust your App Registration's Manifest, by setting this:
"signInAudience": "AzureADandPersonalMicrosoftAccount",
If none of that works:
Please try Sample 57, which has a GetMembersAsync
function that accomplishes the same thing, but with the current BotFramework SDK. Just be sure to follow the README and set everything up as specified.
Let me know how it goes. If you need additional help, can you try explaining your reproduction steps in as much detail as possible? I'd like to try to reproduce this and want to make sure our steps match--it may also help me call out where you might be going wrong.
Upvotes: 1