Botframework-sdk: [Skype Channel] Sending a chat message with a Skype calling bot in Node.js

Created on 8 Jan 2017  路  13Comments  路  Source: microsoft/botframework-sdk

In Node.js, I'm using botbuilder 3.5.1 (update it this week) and botbuilder-calling 3.0.1.

I adapted the code from the demo-skype-calling so my bot could send a chat message. My code is basically the same that the example:

const address = session.message.address;
delete address.conversation;

const msg = new builder.Message().address(address);
...
msg.text('...');
...
chatBot.send(msg, () => next());

The problem is that the message is not being sent. This is the error that shows up in the console:

Error: Request to 'https://skype.botframework.com/v3/conversations/29%3A164dpbJTTNgkqw1IzVyxfS8-fPKtl_oz257xjki_6bjGdt1v9e7FMasPoH7nNyIyc/activities' failed: [400] Bad Request
    at Request._callback (/Users/eh/Documents/Projects/skype-bot/node_modules/botbuilder/lib/bots/ChatConnector.js:434:46)
    at Request.self.callback (/Users/eh/Documents/Projects/skype-bot/node_modules/request/request.js:186:22)
    at emitTwo (events.js:106:13)
    at Request.emit (events.js:191:7)
    at Request.<anonymous> (/Users/eh/Documents/Projects/skype-bot/node_modules/request/request.js:1081:10)
    at emitOne (events.js:96:13)
    at Request.emit (events.js:188:7)
    at IncomingMessage.<anonymous> (/Users/eh/Documents/Projects/skype-bot/node_modules/request/request.js:1001:12)
    at IncomingMessage.g (events.js:286:16)
    at emitNone (events.js:91:20)

In the Bot Framework page for my bot, in the Skype channel issues, this is the error:

The conversationId 29:164dpbJTTNgkqw1IzVyxfS8-fPKtl_oz257xjki_6bjGdt1v9e7FMasPoH7nNyIyc and bot 504ecfbf-c1f0-4d14-bf56-8085e5d853f2 doesn't match a known conversation

Last week, the same code was working with botbuilder version 3.4.4, which also doesn't work right now, because another thing is that most of the time, calls are either being dropped after the first interaction with the user or the bot answers the call with "You can't talk to this bot just yet, but we're working on it". This started to happen this week too.

bug investigate

Most helpful comment

@eh3rrera The serviceUrl can be set while constructing the CallConnector.

const callingConnector = new calling.CallConnector({
  callbackUrl: process.env.CALLBACK_URL,
  appId: process.env.MICROSOFT_APP_ID,
  appPassword: process.env.MICROSOFT_APP_PASSWORD,
  serviceUrl: 'https://smba.trafficmanager.net'
});

This setup worked for me.

All 13 comments

This might be a bug @Stevenic might be able to add more

Let me forward to the dev team - this might be a server side configuration issue (ie our server).

I have a very similar problem that I posted about recently - has there been any update? This is holding up my development, and my manager is getting cranky :)

Hi. Sending a chat message still doesn't work (I upgraded to version 3.5.4).

Sometimes the speech recognition doesn't work either, the call is dropped or the bot cancels after three times (related to issue #2107?).

It seems to be more stable in version 3.5.3, however, at the end of my waterfall, the [400] Bad Request error always shows up.

Any updates on your side? @Stevenic or @jameslew

Thanks!

@eh3rrera , @amphora-ken Could you please give me your bots msa app id. The one in the error message (504ecfbf-c1f0-4d14-bf56-8085e5d853f2) wasn't found (probably deleted).

Thanks @konstlut. Yes, I recreated it to see if that could solve the problem (it didn't). Here's my current app id: a52cf47d-a471-4520-b797-6eb13202ec20

@eh3rrera So far I found some activity from your bot on Jan, 25: 4 messages were successfully delivered to your bot and 2 messages were sent back to Skype with no errors so far. Could you try reproducing the problem please?

Thanks @konstlut,

The first four times I tested today, I couldn't initiate a call.

In my ngrok console, I have the following:

HTTP Requests                                                                   
-------------                                                                   
POST /api/calls                200 OK                                  
POST /api/calls                200 OK                                  
POST /api/calls                200 OK                                  
POST /api/calls                200 OK                                  
POST /api/calls                200 OK                                  
POST /api/calls                200 OK                                  
POST /api/calls                200 OK                                  
POST /api/calls                200 OK 
POST /api/calls                403 Forbidden                                    
POST /api/calls                403 Forbidden                                    
POST /api/calls                403 Forbidden                                    
POST /api/calls                403 Forbidden                                    
POST /api/messages             202 Accepted                                     
POST /api/messages             202 Accepted                                     
POST /api/messages             202 Accepted

In the Inspect page, this is how a request that returns a 403 status looks like:

{
    "id": "0b8eaaed-9c5f-4741-ab93-8e3f24888adc",
    "participants": [
        {
            "identity": "29:1A3F5BYgHlvu-txlQtJB8HHTpE5cA3pk9lKsIzs9ZL6X14u5CJ_o7ZQwLZ_SAez-z",
            "languageId": "en",
            "originator": true
        },
        {
            "identity": "28:a52cf47d-a471-4520-b797-6eb13202ec20",
            "originator": false
        }
    ],
    "isMultiparty": false,
    "presentedModalityTypes": [
        "audio"
    ],
    "callState": "incoming"
}

Response:

HTTP/1.1 403 Forbidden
Date: Mon, 30 Jan 2017 16:09:29 GMT
Connection: keep-alive
Content-Length: 0

So I waited about 30 minutes and finally the call was successful. As you can see in the ngrok output, it always returned 200 OK when I interacted through voice or digits, but at the last step, when the chatbot wanted to send a chat message, this was the output on the terminal:

$ MICROSOFT_APP_PASSWORD=XXXXXXXX MICROSOFT_APP_ID=a52cf47d-a471-4520-b797-6eb13202ec20 CALLBACK_URL=https://73b8faa9.ngrok.io/api/calls node callingBot.js
restify listening to http://[::]:3978
Error: Request to 'https://skype.botframework.com/v3/conversations/29%3A1A3F5BYgHlvu-txlQtJB8HHTpE5cA3pk9lKsIzs9ZL6X14u5CJ_o7ZQwLZ_SAez-z/activities' failed: [400] Bad Request
    at Request._callback (/Users/eh/Documents/Projects/skype-bot/node_modules/botbuilder/lib/bots/ChatConnector.js:435:46)
    at Request.self.callback (/Users/eh/Documents/Projects/skype-bot/node_modules/request/request.js:186:22)
    at emitTwo (events.js:106:13)
    at Request.emit (events.js:191:7)
    at Request.<anonymous> (/Users/eh/Documents/Projects/skype-bot/node_modules/request/request.js:1081:10)
    at emitOne (events.js:96:13)
    at Request.emit (events.js:188:7)
    at IncomingMessage.<anonymous> (/Users/eh/Documents/Projects/skype-bot/node_modules/request/request.js:1001:12)
    at IncomingMessage.g (events.js:286:16)
    at emitNone (events.js:91:20)

I'm using botbuilder 3.5.3, botbuilder-calling 3.0.1, and Skype for Mac version 7.44 (364).

  1. You get 400 so most likely something is not ok with the payload you send. Normally together with a 4xx status code the HTTP response provides an error model in the response body. So it may be useful to look at it.
  2. I see you are sending messages to https://skype.botframework.com . Do you have this url hardcoded somewhere in your bot implementation? If yes, you shouldn't hardcore that value and use it from the incoming message serviceUrl field (the one you got from the user). If you don't have it hardcoded anywhere I was wondering how you create a messages call and the connector object. (For this bot I would expect this value to be something like https://smba.trafficmanager.net.)
  1. The same code was working fine in December. I don't know what changed. The error is thrown when bot.send(msg, () => next()); is executed. Logging the Error param just duplicates the stack trace already shown:
bot.send(msg, (err) => {
  console.log(err);
  next();
});
  1. It's not hardcoded. This is the source code:
const builder = require('botbuilder');
const mdb = require('moviedb')(process.env.MOVIE_DB_API_KEY);
const restify = require('restify');
const calling = require('botbuilder-calling');
const prompts = require('./prompts');

const server = restify.createServer();
server.listen(3978, () =>
  console.log('%s listening to %s', server.name, server.url)
);

const connector = new builder.ChatConnector({
  appId: process.env.MICROSOFT_APP_ID,
  appPassword: process.env.MICROSOFT_APP_PASSWORD
});
const bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());

const callingConnector = new calling.CallConnector({
  callbackUrl: process.env.CALLBACK_URL,
  appId: process.env.MICROSOFT_APP_ID,
  appPassword: process.env.MICROSOFT_APP_PASSWORD
});
const callingBot = new calling.UniversalCallBot(callingConnector);
server.post('/api/calls', callingConnector.listen());

const imagesBaseUrl = 'https://image.tmdb.org/t/p/';
const posterSize = 'w185';

const genres = {
  action: {
    id: 28,
  },
 // ...
};

bot.dialog('/', [
  session =>
    session.send('Hi! Call me'),
]);

callingBot.dialog('/', [
  (session) => {
    const promptList = [
      calling.Prompt.text(session, prompts.menu.prompt),
      calling.Prompt.text(session, prompts.menu.choices),
    ];

    calling.Prompts.choice(session, new calling.PlayPromptAction(session).prompts(promptList), [
      { name: 'movie', speechVariation: ['movie', 'film'] },
      { name: 'quit', speechVariation: ['quit', 'end call', 'hangup', 'goodbye'] },
    ]);
  },
  (session, results) => {
    if (results.response) {
      if (results.response.entity === 'movie') {
        session.beginDialog('/callingGenrePrompt');
      } else {
        session.endDialog(prompts.goodbye);
      }
    } else {
      session.endDialog(prompts.canceled);
    }
  },
  (session, result) => {
    if (result) {
      session.dialogData.genre = result.response.id;
    }
    session.beginDialog('/callingYearPrompt');
  },
  (session, result, next) => {
    if (result) {
      session.dialogData.year = result.response;
    }

    const address = session.message.address;
    delete address.conversation;

    mdb.discoverMovie({
      with_genres: session.dialogData.genre,
      primary_release_year: session.dialogData.year,
    }, (err, res) => {
      const msg = new builder.Message().address(address);
      // If there's no error
      if (!err) {
        const movies = res.results;
        // Choose a random movie from the array of movies
        const index = Math.floor(Math.random() * movies.length);

        msg.text(`I found this movie: \n\n**${movies[index].title}**\n\n*${movies[index].overview}*`);

        // If the movie has a poster image
        if (movies[index].poster_path) {
          // Add the image as a message attachment
          msg.attachments([{
            contentType: 'image/jpeg',
            contentUrl: `${imagesBaseUrl}${posterSize}${movies[index].poster_path}`,
          }]);
        }
      } else { // There's an error
        msg.text('Oops, an error ocurred trying to get the movie');
      }
      // Send message through chat bot
      bot.send(msg, () => next());
    });
  },
  session => session.endDialog(prompts.goodbye),
]);

callingBot.dialog('/callingGenrePrompt', [
  (session) => {
    calling.Prompts.choice(session, prompts.movie.prompt, [
      { name: 'action', dtmfVariation: '1' },
      //...
      { name: 'repeat', dtmfVariation: '0' },
    ]);
  },
  (session, results) => {
    if (results.response) {
      if (results.response.entity === 'repeat') {
        session.replaceDialog('/callingGenrePrompt');
      } else {
        const genreObj = genres[results.response.entity];
        session.send(prompts.result, results.response.entity);
        session.endDialogWithResult({ response: genreObj });
      }
    } else {
      session.endDialog(prompts.canceled);
    }
  },
]);

callingBot.dialog('/callingYearPrompt', [
  (session) => {
    calling.Prompts.digits(session, prompts.year.prompt, 4, { stopTones: '#' });
  },
  (session, results) => {
    if (results.response) {
      if (results.response === '0') {
        session.send(prompts.year.none);
        session.endDialogWithResult({ response: '' });
      } else if (results.response.length === 4) {
        session.send(prompts.result, results.response);
        session.endDialogWithResult({ response: results.response });
      } else {
        session.send(prompts.year.invalid);
        session.replaceDialog('/callingYearPrompt');
      }
    } else {
      session.endDialog(prompts.canceled);
    }
  },
]);

Thanks!

Any progress on this?

@eh3rrera The serviceUrl can be set while constructing the CallConnector.

const callingConnector = new calling.CallConnector({
  callbackUrl: process.env.CALLBACK_URL,
  appId: process.env.MICROSOFT_APP_ID,
  appPassword: process.env.MICROSOFT_APP_PASSWORD,
  serviceUrl: 'https://smba.trafficmanager.net'
});

This setup worked for me.

Thanks a lot @rnrneverdies!

It works for me too with version 3.7.0

Is this the official solution? This is how the CallConnector should be created?

Was this page helpful?
0 / 5 - 0 ratings