3

Solved

Thanks to user MarkMoose I realized that my database table was not storing the full IDs. Future debuggers: If that does not help you solve your problem please look at my conversation with MarkMoose, they walked me through very useful troubleshooting steps.

I am trying to create a Microsoft Teams bot hosted on Azure using Microsoft Bot SDK Version 4.0.

The flow is as follows

  1. Web app alarm triggers. Sends a POST request to the bot that contains (all this data gathered from a previous message from the user)

    • To ID (of recipient user)
    • To Name (of recipient user)
    • From ID (my bot)
    • From Name (my bot)
    • channel ID
    • conversation ID
    • Service URL (of recipient user)
  2. Bot extracts that info from JSON forms new message activity

  3. Bot sends activity to the user

Problem: When the bot attempts to create a ConversationAccount object using the credentials listed above it throws the following error:

Exception caught: Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'BadRequest'

Here is the part of the relevant part of the code.

Note specifically the following lines:

var account = new MicrosoftAppCredentials(botCreds["App ID"], botCreds["App Password"]);
var jwtToken = await account.GetTokenAsync();
ConnectorClient connector = new ConnectorClient(new System.Uri(serviceURL), account);

These are the lines that slightly differ when I found other peoples solutions to my problem. In my current version of the code I am using this posts solution. I have also tried the DelegatingHandler class he created but it throws the same error.

/// <summary>
/// Sends a message to a user or group chat.
/// </summary>
/// <param name="forwardContext">JSON object containing credentials for destination chat.</param>
/// <param name="messageToSend">The message to forward.</param>
/// <returns></returns>
private async Task ForwardMessage(JToken forwardContext, string messageToSend)
{
    // Collect data from JSON input
    var restCmd = forwardContext;
    var toId = (string) restCmd["toId"];
    var toName = (string) restCmd["toName"];
    var fromId = (string) restCmd["fromId"];
    var fromName = (string) restCmd["fromName"];
    var channelId = (string) restCmd["channel"];
    var serviceURL = (string) restCmd["serviceURL"];
    var conversationId = (string) restCmd["conversation"];
    var cred_str = $@"toId: {toId}
    toName: {toName}
    fromId: {fromId}
    fromName: {fromName}
    channelId: {channelId}
    serviceURL: {serviceURL}
    conversationId: {conversationId}";
    _logger.LogInformation(cred_str);
    _logger.LogInformation($"Forwarding the following message to {toName}: {messageToSend}");

    Dictionary<string, string> botCreds = GetBotCredentials();

    // Create relevant accounts
    ChannelAccount userAccount = new ChannelAccount(name: toName, id: toId);
    ChannelAccount botAccount = new ChannelAccount(name: fromName, id: fromId);
    if (!MicrosoftAppCredentials.IsTrustedServiceUrl(serviceURL))
    {
        _logger.LogInformation($"Adding to trusted service urls: {serviceURL}");

        // Register the service URL as trusted
        MicrosoftAppCredentials.TrustServiceUrl(serviceURL);
    }
    MicrosoftAppCredentials.TrustServiceUrl(serviceURL);
    var account = new MicrosoftAppCredentials(botCreds["App ID"], botCreds["App Password"]);
    var jwtToken = await account.GetTokenAsync();
    ConnectorClient connector = new ConnectorClient(new System.Uri(serviceURL), account);

    // Create a new message activity
    IMessageActivity message = Activity.CreateMessageActivity();

    conversationId = (
        await connector
        .Conversations
        .CreateDirectConversationAsync(botAccount, userAccount)).Id;

    // Set relevant message details
    message.From = botAccount;
    message.Recipient = userAccount;
    message.Text = messageToSend;
    message.Locale = "en-Us";
    message.ChannelId = channelId;

    // Create a new converstaion and add it to the message.
    message.Conversation = new ConversationAccount(id: conversationId);
    await connector.Conversations.SendToConversationAsync((Activity) message);
}

And here is my code for gathering the information used above. This function gets called when the user first interacts with the bot.

/// <summary>
    /// Called only when the !setup command is sent to the bot.
    /// Updates the chats info in the DB.
    /// </summary>
    /// <param name="activity">Activity of the message the "!setup" command was sent in.</param>
    /// <returns>True if the update query executed fine.</returns>
    private bool SetupCommand(Activity activity)
    {
        // Connect to the database
        this.database = new DBConnection(serverIP, databaseName, userName, password, _logger);
        this.database.Connect();
        var tableName = "ms_teams_chats";

        // Data gathered from Activity for database.

        // User ID
        string toId = activity.From.Id;

        // User Name
        string toName = activity.From.Name;

        // Bot ID
        string fromId = activity.Recipient.Id;

        // Bot Name
        string fromName = activity.Recipient.Name;

        // Users service URL
        string serviceURL = activity.ServiceUrl;

        // The platform the message came from. Example: 'skype'
        string channelId = activity.ChannelId;
        string conversationID = activity.Conversation.Id;

        string conversationName = activity.Conversation.Name;
        bool isGroupChat = activity.Conversation.IsGroup ?? false;

        string upsertQuery = string.Empty;
        upsertQuery = $@"
            INSERT INTO {tableName} 
                (user_id, user_name, assoc_bot_id, assoc_bot_name, service_url, channel_id, conversation_id, is_group_chat)
            VALUES (
                '{toId}', '{toName}', '{fromId}', '{fromName}', '{serviceURL}', '{channelId}', '{conversationID}', {isGroupChat}
            )
            ON DUPLICATE KEY UPDATE
            user_id = '{toId}',
            user_name = '{toName}',
            assoc_bot_id = '{fromId}',
            assoc_bot_name = '{fromName}',
            service_url = '{serviceURL}',
            channel_id = '{channelId}',
            conversation_id = '{conversationID}',
            is_group_chat = {isGroupChat}
        ";
        try
        {
            this.database.ExecuteNonQuery(upsertQuery);
        }
        catch (System.Exception e)
        {
            _logger.LogError($"Could not update users information. \nError:{e.ToString()}");
            return false;
        }

        return true;
    }
  • 1
    Have you tried setting `message.ChannelId`? In [the doc](https://learn.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-connector?view=azure-bot-service-3.0) they have it. – Alex Sikilinda Nov 07 '18 at 21:03
  • Yes, sorry it seems that while formatting I mustve deleted the line. It is there, I updated the post. i get the same error. – Peter Andreoli Nov 07 '18 at 21:13
  • Could you please try [this sample code](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bot-conversations/bots-conv-proactive#net-example-from-this-samplehttpsgithubcomofficedevmicrosoft-teams-sample-complete-csharpblob32c39268d60078ef54f21fb3c6f42d122b97da22template-bot-master-csharpsrcdialogsexamplesteamsproactivemsgto1to1dialogcs)? – Wajeed Shaikh Nov 13 '18 at 16:30

1 Answers1

2

It seems you are struggling with the same issue I had last week. it seems that the CreateDirectConversationAsync does not work in MS Teams as Teams also needs a tennantId. I found a statement about this here: https://github.com/Microsoft/BotBuilder/issues/2944

the answer mentions a nuget package (Microsoft.Bot.Connector.Teams) that is no longer available in V4 of the SDK. but as I see that you already got a conversationId from your JSON input, this should not be a problem. just use the conversationId you passed in the JSON. if you would do this, your code could look something like:

private static async Task SendProActiveMessgae()private async Task ForwardMessage(JToken forwardContext, string messageToSend)
{
    // Collect data from JSON input
    var restCmd = forwardContext;
    var toId = (string) restCmd["toId"];
    var toName = (string) restCmd["toName"];
    var fromId = (string) restCmd["fromId"];
    var fromName = (string) restCmd["fromName"];
    var serviceURL = (string) restCmd["serviceURL"]
    var conversationId = (string) restCmd["conversation"];

    var uri = new Uri(serviceURL);
    var appId = "APP ID";
    var appSecret = "APP PASSWORD";
    ConnectorClient connector = new ConnectorClient(uri, appId, appSecret);

    var activity = new Activity()
    {
        Type = ActivityTypes.Message,
        From = new ChannelAccount(fromId, fromName),
        Recipient = new ChannelAccount(toId, toName),
        Conversation = new ConversationAccount(false, "personal", conversationId),
        Text = messageToSend
    };
    try
    {
        MicrosoftAppCredentials.TrustServiceUrl(serviceURL);
        await connector.Conversations.SendToConversationAsync(conversationId, activity);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
MarkMoose
  • 91
  • 6
  • Unfortunately I still get a `Forbidden` error. Im very new at this so I'm not sure how to debug this. Any other ideas? Thanks for your help. – Peter Andreoli Nov 12 '18 at 19:19
  • I didn't know Teams required different information about the team/group chat. Do you know where I can find out more about what I'd need? You mentioned a Tenant ID, should I collect that as well? I havent found documentation that uses that. – Peter Andreoli Nov 12 '18 at 19:35
  • hmm... a Forbidden error can be caused by so many things. so first things first: you asume you deployed the bot to azure (since you already had an app id and secret) and the data in your json came directly from a conversationreference object from your bot? and it's a reference to a 1 on 1 chat in Teams? Could you tell me the serviceUrl you got? I haven't used a Tenant ID yet. but I found some extra reading material here [link](https://learn.microsoft.com/nl-nl/microsoftteams/platform/concepts/bots/bot-conversations/bots-conv-proactive#starting-11-conversations) – MarkMoose Nov 12 '18 at 20:13
  • and if nothing seems to work. maybe try and use a program like postman to directly talk to the REST api of your bot. (the Microsoft.Bot.Connector namespace only provides a wrapper to this REST api for C#) more information about the REST api can be found [here](https://learn.microsoft.com/nl-nl/azure/bot-service/rest-api/bot-framework-rest-connector-quickstart?view=azure-bot-service-4.0) – MarkMoose Nov 12 '18 at 20:17
  • Sure. Here is the service URL of my user. https://smba.trafficmanager.net/amer/ Also, yes to your assumptions. I've deployed on Azure, which is where I got my app ID and secret. The data in the JSON is taken directly from the activity sent to the bot. If you want the code for that I can post it. Only thing I can think of is maybe I missed a step thats supposed to be taken before I try to send the proactive message – Peter Andreoli Nov 12 '18 at 20:23
  • please share your code. there should not be an extra step in order to send proactive messages. (at least, I cant remember I took one) – MarkMoose Nov 12 '18 at 20:37
  • Ok, added to the original post. – Peter Andreoli Nov 12 '18 at 20:40
  • I noticed your following comment in the original post: _this function gets called when the user first interacts with the bot._ meaning that the from is the user, and the to is the bot. I have never tried to send a proactive message as the user to the bot and I can imagine that this is not allowed. can you confirm that you have the user name and guid in the from fields? if so, try to flip the to and from values in the code I provided ( so var toId = (string) restCmd["fromid"]; and vice versa) – MarkMoose Nov 12 '18 at 20:56
  • Edit: I noticed I repeated what you said. I meant that Im not sending a proactive message as the user. The user is the one who sent my bot a message. I wish the problem was as simple as that. This is called when the user messages the bot. So the variable `toId` is the users ID. I labeled it 'to' because I started by using [this tutorial](https://learn.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-proactive-messages?view=azure-bot-service-3.0#send-an-ad-hoc-proactive-message) and thats how they have it set up. – Peter Andreoli Nov 12 '18 at 21:07
  • Essentially the user sends a '!setup' message to the bot, then the bot calls that function and documents the credentials – Peter Andreoli Nov 12 '18 at 21:10
  • whelp that were all my hopeful sugestions.. could you try a couple of REST calls (as I mentioned before) 1. a POST message to https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token with the following x-www-form-urlencoded body: grant_type:client_credentials client_id:YOURCLIENTID client_secret:YOURCLIENTSECRET scope:https://api.botframework.com/.default – MarkMoose Nov 12 '18 at 22:26
  • 2. a POST message to https://smba.trafficmanager.net/emea/v3/conversations/YOURCONVERSATIONIDf/activities with the following json: { "type": "message", "from": { "id": "FROMID", "name": "FROMNAME" }, "conversation": { "id": "CONVERSATIONID" }, "recipient": { "id": "TOID", "name": "TONAME" }, "text": "My bot's reply" } – MarkMoose Nov 12 '18 at 22:29
  • Thats ok, thanks for all you've done to try to help me. I tried 1. and found that my ID and password are fine. For 2, what am I filling in the caps words with? my users info, or the bot? Also, what does this post request do? – Peter Andreoli Nov 13 '18 at 15:42
  • okay, good to hear that there are no authentication issues so far. replace FROMID and FROMNAME with the id and name of the bot (name can be left empty) and TOID and TONAME with the id and name of the user. and offcourse replace the CONVERSATIONID with your conversationId. I noticed that the url was cut off so be aware that the url is: [https://]smba.trafficmanager.net/emea/v3/conversations/CONVERSATIONID/activities. this request will send a proactive message to the user. also don't forget to use the bearer token in the Authorization header of the request. – MarkMoose Nov 13 '18 at 16:04
  • Ok so I performed that post request and I receive an error response labeled "BadArgument" with the message "Failed to decrypt conversation id". [https://pastebin.com/raw/47kTEykZ](Heres the request I sent). Obviously I removed the ends of all the IDs and the bearer token. I dont really see a problem with what I sent though – Peter Andreoli Nov 13 '18 at 17:28
  • at least now we know where the issue lies. looking at your conversationId, it does looks different from the IDs generated by my bot. When looking at [this](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bot-conversations/bots-conversations) Teams documentation that according to the schema, an ID should look something like a:17I0kl9EkpE1O9PH5TWrzrLNwnWWcfrU7QZjKR0WSfOpzbfcAg2IaydGElSo10tVr4C7Fc6GtieTJX663WuJCc1uA83n4CSrHSgGBj5XNYLcVlJAs2ZX8DbYBPck201w- does your conversationId match this formatting? – MarkMoose Nov 13 '18 at 19:17
  • Well, its def not that long. Mines only about 32 characters! And I know why! In the table my coworker set up the IDs only have a max of 32 characters. I just updated the table to compensate for the long IDs, and it works! Thank you so much @MarkMoose! Last question, do you know where I can find documentation for handling team chats/group chats? What kind of info I'll need to keep in my database and such. I've only found documentation for 1 on 1. Again, thank you for helping me troubleshoot this. – Peter Andreoli Nov 13 '18 at 20:11
  • 1
    you're welcome! according to the documentation, groupchat is limited to developer preview, but will be released to the production version of Teams shortly. but you could send messages to multiple users using channel conversations (also called team chat) I haven't looked into this functionality, but I would start reading [here](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bot-conversations/bots-conversations) – MarkMoose Nov 13 '18 at 20:35