I have created a Teams bot in .NET Core from following the sample found here: https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/57.teams-conversation-bot
This is working and is running locally with ngrok. I have a controller with a route of api/messages:
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter Adapter;
private readonly IBot Bot;
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
Adapter = adapter;
Bot = bot;
}
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await Adapter.ProcessAsync(Request, Response, Bot);
}
}
I now want to call a POST to api/messages from my Angular client using TypeScript to send a proactive message to a specific Teams user.
I did figure out how to set the ConversationParameters in TeamsConversationBot.cs to a specific Teams user by doing the following:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("[insert unique Teams user guid here]") },
TenantId = turnContext.Activity.Conversation.TenantId,
};
but what I'm struggling with is how to build a JSON request that sends the Teams user guid (and maybe a couple other details) to my api/messages route from TypeScript.
How do I go about doing this? What parameters/body do I need to send? I haven't been able to find samples online that show how to do this.
Update below for added clarification
I am building a web chat app using Angular for our customers. What I'm trying to do is send a proactive message to our internal employees, who are using Microsoft Teams, when a customer performs some action via the chat app (initiates a conversation, sends a message, etc.).
I've built a Teams bot using .NET Core using this sample: https://kutt.it/ZCftjJ. Modifiying that sample, I can hardcode my Teams user ID and the proactive message is showing up successfully in Teams:
var proactiveMessage = MessageFactory.Text($"This is a proactive message.");
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("insert Teams ID here") },
TenantId = turnContext.Activity.Conversation.TenantId,
};
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(teamsChannelId, serviceUrl, credentials, conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(_appId, conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
What I'm struggling with is:
How to configure my Angular app to notify my bot of a new proactive message I want to send.
How to configure the bot to accept some custom parameters (Teams user ID, message).
It sounds like you've got some progress with pro-active messaging already. Is it working 100%? If not, I've covered the topic a few times here on stack overflow - here's an example that might help: Programmatically sending a message to a bot in Microsoft Teams
However, with regards -trigging- the pro-active message, the truth is you can do it from anywhere/in any way. For instance, I have Azure Functions that run on their own schedules, and pro-active send messages as if they're from the bot, even though the code isn't running inside the bot at all. You haven't fully described where the Angular app fits into the picture (like who's using it for what), but as an example in your scenario, you could create another endpoint inside your bot controller, and do the work inside there directly (e.g. add something like below:)
[HttpPost]
public async Task ProActiveMessage([FromQuery]string conversationId)
{
//retrieve conversation details by id from storage (e.g. database)
//send pro-active message
//respond with something back to the Angular client
}
hope that helps,
Hilton's answer is still good, but the part about proactively messaging them without prior interaction requires too long of a response. So, responding to your latest comments:
Yes, the bot needs to be installed for whatever team the user resides in that you want to proactively message. It won't have permissions to do so, otherwise.
You don't need to override OnMembersAddedAsync; just query the roster (see below).
You don't need a conversation ID to do this. I'd make your API, instead, accept their Teams ID. You can get this by querying the Teams Roster, which you'll need to do in advance and store in a hash table or something...maybe a database if your team size is sufficiently large.
As far as required information, you need enough to build the ConversationParameters:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new ChannelAccount[] { teamMember },
TenantId = turnContext.Activity.Conversation.TenantId,
};
...which you then use to CreateConversationAsync:
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
_appId,
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
Yes, you can modify that sample. It returns a Bad Request because only a particular schema is allowed on /api/messages. You'll need to add your own endpoint. Here's an example of NotifyController, which one of our other samples uses. You can see that it accepts GET requests. You'd just need to modify that our build your own that accepts POST requests.
All of this being said, all of this seems like it may be a bigger task than you're ready for. Nothing wrong with that; that's how we learn. Instead of jumping straight into this, I'd start with:
Get the Proactive Sample working and dig through the code until you really understand how the API part works.
Get the Teams Sample working, then try to make it message individual users.
Then build your bot that messages users without prior interaction.
If you run into trouble feel free to browse my answers. I've answered similar questions to this, a lot. Be aware, however, that we've switched from the Teams Middleware that I mention in some of my answers to something more integrated into the SDK. Our Teams Samples (samples 50-60) show how to do just about everything.
Related
In our Teams calling bot, we would like to transfer certain calls to specific Teams users, PSTN, but also to an other Teams calling bot and/or voicemail.
For specific Teams users and PSTN we got it working. If we want to transfer a call to another application, we can do so by using its pstn number. But ideally we would also like to transfer using its objectId.
I tried using a transferrequest like this:
var requestBody = new CallTransferRequestBody()
{
TransferTarget = new InvitationParticipantInfo()
{
Identity = new IdentitySet()
{
AdditionalData = new Dictionary<string, object>()
}
}
};
requestBody.TransferTarget.Identity.Application = new Identity { Id = transferTargetId };
//this line does not make any difference
requestBody.TransferTarget.Identity.Application.SetTenantId(tenantId);
But this results in a "Request authorization tenant mismatch." error. Is it possible to directly transfer to another application?
I haven't tried voicemail boxes yet, but if any info on how to transfer to those, is appreciated.
Basically we can transfer an active peer-to-peer call. This is only supported if both the transferee and transfer target are Microsoft Teams users that belong to the same tenant.
However for redirecting call to call queue or auto attendants, you can use the "applicationInstance" identity. The bot is expected to redirect the call before the call times out. The current timeout value is 15 seconds.
const requestBody = {
"targets": [{
"#odata.type": "#microsoft.graph.invitationParticipantInfo",
"identity": {
"#odata.type": "#microsoft.graph.identitySet",
"applicationInstance": {
"#odata.type": "#microsoft.graph.identity",
"displayName": "Call Queue",
"id": queueId
}
}
}],}
Please refer to the documentation here: https://learn.microsoft.com/en-us/graph/api/call-redirect?view=graph-rest-beta&tabs=csharp#request
The redirect API is still having that limitation from my understanding.
But that should work with the new Transfer API:
https://learn.microsoft.com/en-us/graph/api/call-transfer?view=graph-rest-beta&tabs=http
I am exploring the Microsoft Bot Builder SDK to create a chat bot that integrates with MS Teams. Most of the provided samples do not have any authentication mechanisms and the samples that reference OAuth seem to do so for allowing the bot to access a resource using the on-behalf-of flow. Is correct way to think of the security model is that the bot should be considered public and any non-public information accessed is done from the context of the calling user?
The Bot Framework has three kinds of authentication/authorization to consider:
Bot auth - Microsoft app ID and password
Client auth - Direct Line secret/token, or various mechanisms for other channels
User auth - OAuth cards/prompts/tokens
Unfortunately there's some inconsistency in the documentation about which is which, but I've just raised an issue about that here: https://github.com/MicrosoftDocs/bot-docs/issues/1745
In any case, there's no need to think of all bots as "public." The Bot Builder SDK authenticates both incoming messages and outgoing messages using its app ID and password. This means any unauthorized messages sent to the bot's endpoint will be rejected, and no other bot can impersonate yours.
In general you should have the user sign in if you want the bot to access secure information on the user's behalf. But since you mentioned wanting to restrict bot access to specific tenants, I can briefly explain how to do that. You can find middleware here that does it in C#, and here's a modified version of the code that I think improves on it by using a hash set instead of a dictionary:
public class TeamsTenantFilteringMiddleware : IMiddleware
{
private readonly HashSet<string> tenantMap;
public TeamsTenantFilteringMiddleware(IEnumerable<string> allowedTenantIds)
{
if (allowedTenantIds == null)
{
throw new ArgumentNullException(nameof(allowedTenantIds));
}
this.tenantMap = new HashSet<string>(allowedTenantIds);
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
if (!turnContext.Activity.ChannelId.Equals(Channels.Msteams, StringComparison.OrdinalIgnoreCase))
{
await next(cancellationToken).ConfigureAwait(false);
return;
}
TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
string tenantId = teamsChannelData?.Tenant?.Id;
if (string.IsNullOrEmpty(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id is missing.");
}
if (!this.tenantMap.Contains(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id '" + tenantId + "' is not allowed access.");
}
await next(cancellationToken).ConfigureAwait(false);
}
}
I've got a Bot Framework V3 bot code base that is working in a half dozen or so different customer Teams tenants, and on our internal Teams tenant without issues.
In one particular customer tenant, attempts to create a proactive message to a Teams Channel are failing with a ConversationNotFound 404 error, when I call ConnectorClient.Conversations.CreateConversationAsync().
My code to create the conversation and post an activity in the channel looks like this:
var teamsChannelId = "19:deadbeef1234#thread.skype"; // insert the real channel ID obtained from lookups against Graph API...
var botCredentials = new MicrosoftAppCredentials(/* Bot ID & password */);
MicrosoftAppCredentials.TrustServiceUrl("https://smba.trafficmanager.net/amer/", DateTime.MaxValue);
using (var connectorClient = new ConnectorClient(new Uri("https://smba.trafficmanager.net/amer/"), botCredentials)) {
var botId = new ChannelAccount("28:" + botCredentials.MicrosoftAppId);
var msg = Activity.CreateMessageActivity();
msg.From = botId;
var card = MakeCard(); // builds an AdaptiveCard...
msg.Attachments.Add(new Attachment(AdaptiveCard.ContentType, content: card));
var parameters = new ConversationParameters() {
Bot = botId,
ChannelData = new TeamsChannelData() {
Channel = new ChannelInfo(teamsChannelId)
},
Activity = (Activity)msg
};
// This throws an Microsoft.Bot.Connector.ErrorResponseException with the code "ConversationNotFound"
ConversationResourceResponse convoResponse = await connectorClient .Conversations.CreateConversationAsync(parameters);
}
As I mentioned initially, this code may not be perfect, but it is working on a number of different Teams and Azure environments, but failing in this particular environment. The HTTP response from Bot Framework looks like this:
"Response": {
"StatusCode": 404,
"ReasonPhrase": "Not Found",
"Content": "{\"error\":{\"code\":\"ConversationNotFound\",\"message\":\"Conversation not found.\"}}",
"Headers": {
"Date": [
"Wed, 04 Sep 2019 14:43:24 GMT"
],
"Server": [
"Microsoft-HTTPAPI/2.0"
],
"Content-Length": [
"77"
],
"Content-Type": [
"application/json; charset=utf-8"
]
}
Stack Trace:
Microsoft.Bot.Connector.ErrorResponseException: Operation returned an invalid status code 'NotFound'
at Microsoft.Bot.Connector.Conversations.<CreateConversationWithHttpMessagesAsync>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Bot.Connector.ConversationsExtensions.<CreateConversationAsync>d__3.MoveNext()
The bot is able to handle incoming 1-1 chat conversations without issue over webchat, directline and the Teams connectors, so I don't think there are any issues with the bot credentials, or the bot registration configuration.
The bot has been added as an app for Microsoft Teams, uploaded to the tenant, and added to the appropriate Team.
I've explored the possibility that the region of the Bot Framework registration in Azure might be causing an issue, but I've reproduced the client's configuration on our end, and can't reproduce the problem.
Any suggestions would be very welcome.
I have a feeling your parameters is missing the Tenant. This may explain why it fails on some tenants and not others. Try something like this:
var parameters = new ConversationParameters
{
Members = new[] { new ChannelAccount(userId) },
ChannelData = new TeamsChannelData
{
Tenant = new TenantInfo(activity.Conversation.TenantId),
},
};
#Trinetra-MSFT is also correct. You should not hard-code the service URL; some of your users may be outside /amer.
Although possible to some extent, "Proactive Messaging" shouldn't be thought of as "messaging users who have not spoken with the bot", so much as "messaging a user about something not related to a previous conversation". Generally speaking, proactive messaging needs to be done by saving a conversation reference from a user that the bot has had a past conversation with. This is how the Bot Framework, specifically, defines Proactive Messaging.
For Teams, per Proactive Messaging for Bots:
Bots can create new conversations with an individual Microsoft Teams user as long as your bot has user information obtained through previous addition in a personal, groupChat or team scope. This information enables your bot to proactively notify them. For instance, if your bot was added to a team, it could query the team roster and send users individual messages in personal chats, or a user could #mention another user to trigger the bot to send that user a direct message.
See this SO answer for additional help. Note: it's written for a V4 bot, so you may need to make some adjustments.
Let me know if you run into issues and I will adjust my answer accordingly.
I was able to convert my EchoBot to interact with QnAMaker as per instructions here on my local development system but when I publish the same using kudu repo (tried using Azure DevOps service Ci/CD pipeline but it does not work [in preview] because after deployment the bot just hangs on portal and never able to test it on web chat.. so gave up and used recommended kudu repo), I do not get the correct answer to my response. For every question I send, it is unable to detect the QnAMaker service. And I am returning error message from the code that says no QnaMaker answer was found.
How do I troubleshoot to identify the cause of this?
My bot file seems to be working fine locally and I am able to get the answer from QnAMaker locally but not after publishing the code to my Web App Bot in Azure.
I feel like Botframework V4 (using .net) is not very straight forward and the instruction on the portal (document) is still kind of evolving or sometime incomprehensible.
Here is the snapshot from my emulator while testing the chat locally:
And here is the snapshot of production endpoint (using the same questions on portal) with my error msg from OnTurnAsync function:
My .bot has all the services defined and local bot is working fine.
This is the code in my ChatBot class:
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
// Handle Message activity type, which is the main activity type for shown within a conversational interface
// Message activities may contain text, speech, interactive cards, and binary or unknown attachments.
// see https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// Get the conversation state from the turn context.
var state = await _accessors.CounterState.GetAsync(turnContext, () => new CounterState());
// Bump the turn count for this conversation.
state.TurnCount++;
// Set the property using the accessor.
await _accessors.CounterState.SetAsync(turnContext, state);
// Save the new turn count into the conversation state.
await _accessors.ConversationState.SaveChangesAsync(turnContext);
// Echo back to the user whatever they typed.
//var responseMessage = $"Turn {state.TurnCount}: You sent '{turnContext.Activity.Text}'\n";
//await turnContext.SendActivityAsync(responseMessage);
// QnAService
foreach(var qnaService in _qnaServices)
{
var response = await qnaService.GetAnswersAsync(turnContext);
if (response != null && response.Length > 0)
{
await turnContext.SendActivityAsync(
response[0].Answer,
cancellationToken: cancellationToken);
return;
}
}
var msg = "No QnA Maker answers were found. Something went wrong...!!";
await turnContext.SendActivityAsync(msg, cancellationToken: cancellationToken);
}
else
{
await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
}
}
I am using directline V3 for testing out a bot inside MS Teams.
This is a bot showing some messages inside MS Teams.
Is there a way to read all the messages which are already posted in the bot without knowing their respective Conversation IDs. How to read all the conversations from the bot show in the attached screenshot.
On bot side, if we want to save and retrieve all the conversation history, in C# we can implement the IActivityLogger interface, and log the data in Task LogAsync(IActivity activity) for example:
public class ActivityLogger : IActivityLogger
{
public Task LogAsync(IActivity activity)
{
IMessageActivity msg = activity.AsMessageActivity();
//log here
return null;
}
}
So if you save data in Azure SQL Database, you can refer to Saving Bot Activities in Azure SQL Database, and here are some official examples.
Then in node.js, you can intercept and log messages using middleware:
bot.use({
botbuilder: function (session, next) {
myMiddleware.logIncomingMessage(session, next);
},
send: function (event, next) {
myMiddleware.logOutgoingMessage(event, next);
}
})