0

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:

  1. How to configure my Angular app to notify my bot of a new proactive message I want to send.
  2. How to configure the bot to accept some custom parameters (Teams user ID, message).
Ryan Buening
  • 1,559
  • 2
  • 22
  • 60

2 Answers2

3

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 Giesenow
  • 9,809
  • 2
  • 10
  • 24
  • I've updated my question above. Does that help explain my scenario any better? – Ryan Buening Feb 03 '20 at 15:43
  • 1
    yeah, it does add a bit, but I think what I'm trying to explain is, you need to -have- a bot in order to send a pro-active message, but only because you get certain details from -within- your bot when it gets registered/added to the channel. After that, once you have the conversation reference, etc., you actually don't need to send the pro-active message from -within- the bot at all! Your message can be sent from any code, as long as you have the values your need for certain variables. – Hilton Giesenow Feb 03 '20 at 16:35
  • 1
    As a result, rather that trying to craft a special message to -call- your bot from Angular, you can create another api endpoint specially for your Angular code to call, and from there, just send the proactive message. To the Teams user, it will appear -as if it came from the bot- – Hilton Giesenow Feb 03 '20 at 16:36
  • I think that makes sense. Are there any examples of this? How do I need to structure my endpoint? I tried leaving my controller as-is based on this example (https://kutt.it/jTig42), and used Postman to send a POST to my `api/messages` endpoint but I receive a 400 Bad Request. – Ryan Buening Feb 03 '20 at 17:30
  • 1
    @RyanBuening You need to write a controller that handles POST requests. [Here's a sample](https://github.com/microsoft/BotBuilder-Samples/blob/master/samples/csharp_dotnetcore/16.proactive-messages/Controllers/NotifyController.cs). Note, however, that this sample sends a proactive message to ALL users. You'll want to instead accept a conversationId or some other way of looking up the conversationReferece, like Hilton's example. – mdrichardson Feb 03 '20 at 17:37
  • 1
    you -could- use api/messages, but I'd suggest creating a separate endpoint. In that case, it's really nothing special, just a normal .net web api controller - see @mdrichardson-MSFT's comment above – Hilton Giesenow Feb 03 '20 at 17:58
  • Am I able to proactively message a Teams user that has installed my custom bot by only using their Teams User ID? Or is it a requirement to also have a ConversationReference and/or ConversationId? – Ryan Buening Feb 03 '20 at 21:12
  • @RyanBuening You need some of their Teams info. [See here](https://stackoverflow.com/questions/57496329/proactive-messaging-bot-in-teams-without-mentioning-the-bot-beforehand/57499608#57499608) – mdrichardson Feb 03 '20 at 21:49
  • Thanks for your help. I have questions that a StackOverflow comment won't do great at formatting. I've created a gist [here](https://gist.github.com/ryanbuening/91c2cf76fbb6ad89335158b8bd2e25e2) if you're able to take a look. Thanks again. – Ryan Buening Feb 04 '20 at 16:47
  • @RyanBuening I added an additional answer that addresses all of your questions there. – mdrichardson Feb 04 '20 at 19:50
  • Hi, I'm trying to do the same thing and the way I see it, I can either somehow create the conversation from the bot or from the controller. I don't understand how you "call" the bot from the controller... I obviously can't call IBotFrameworkHttpAdapter.ProcessAsync using a custom POST message. And if I call CreateConversationAsync from the controller, I need to provide the audience parameter which is not documented: how do I populate it? – laurian Jan 05 '22 at 01:19
  • @laurian, please ask your question as a proper question on the site, then it will get seen properly by all users and we can give you a more complete answer. However, in brief, you need to remember that a "bot" is just a particular endpoint that accepts messages in a specific format, and can call into other services in the bot framework backend (e.g. to reply, or to send a proactive message). However, it can comfortably just be -one- endpoint in your application. You could create another api endpoint that your front end uses, that can do -anything- you want. – Hilton Giesenow Jan 05 '22 at 08:01
  • This could be, for example, to send a proactive message - they don't need to be sent from -within- your bot at all, they just need to call the bot framework endpoints. I've tried to illustrate the point in this sample - https://github.com/pnp/teams-dev-samples/tree/main/samples/bot-proactive-messaging – Hilton Giesenow Jan 05 '22 at 08:02
  • @HiltonGiesenow as requested, I created a question: https://stackoverflow.com/questions/70599634/teams-bot-create-conversation-in-controller - my problem is creating a conversation, not continuing one. – laurian Jan 06 '22 at 17:44
1

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:

  1. Get the Proactive Sample working and dig through the code until you really understand how the API part works.

  2. Get the Teams Sample working, then try to make it message individual users.

  3. 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.

mdrichardson
  • 7,141
  • 1
  • 7
  • 21
  • Thanks. You've answered the questions I had and your resources will help me implement my scenario. I'll definitely dig through those samples some more. – Ryan Buening Feb 04 '20 at 20:07
  • A followup question I have is regarding `ConversationReference`. Is it a requirement to persist ConversationReferences? According to the [docs](https://kutt.it/B0Jrq4) it says `A valid conversation reference is needed to send proactive messages to users.` However, in `BotCallback`, I can set `ConversationReference conversationReference = null;` and send the proactive message successfully. I'm building the `ConversationParameters` with the valid Teams ID. Is there a way to statically build a `ConversationReference` and use it to pass into `ContinueConversationAsync` to send a proactive message? – Ryan Buening Feb 06 '20 at 17:43
  • @RyanBuening I'm a bit surprised you can call it, even if null...unless maybe you've previously messaged the user or the information lives in TurnContext somewhere. I think it would likely be best to build it every time, or store it on a per-user basis just to make sure it works consistently across package and API updates. – mdrichardson Feb 06 '20 at 18:22
  • Ok. When I add a new Teams member to a Team where I have my bot installed, the `conversationReference.User.Id` in [`AddConversationReference()`](https://kutt.it/KDxuCk) is **my** Teams ID, so it's not creating a reference for the added user. I'm assuming this means that every added member needs to `@mention` the bot and send a message (to kick-off `AddConversationReference()`) before I can send a proactive message to them? – Ryan Buening Feb 06 '20 at 20:11
  • But what I also have discovered is that I can send a proactive message to another Teams user without using their specific conversation reference. So I'm very confused as to what `ConversationReference.Conversation` in `ContinueConversationAsync` is actually used for. – Ryan Buening Feb 06 '20 at 20:35
  • @RyanBuening It's so that once you have a ConversationReference, you don't need to build it again. You can just call `ContinueConversation` with it. That's the idea, anyway. Teams handles some of this stuff a little differently than other channels. – mdrichardson Feb 12 '20 at 18:44