node.jsbotframeworkdirect-line-botframework

How can I send a message from bot framework sdk after a period of inactivity? In nodejs


I am using the nodejs SDK for Bot Framework to develop a chatbot. I want to send a message to the user if they do not write in 5 minutes. I do not find an example in bot-framework documentation and, in stackoverflow there are not solutions for a started bot (I do not need it to start the conversation). Where do I need to create the code? I have an index.js and a dialog file. How can I set the timer and restart it when the user send a message? I'm using directline.

Thanks


Solution

  • There are two different ways you can approach this, one for directline only using events and one for all channels using setTimeout. The directline solution requires some code on your webchat client, but the latter requires you to save the conversation reference and start a new bot adapter. Both approaches could work.

    Directline Only

    You need to set up your webchat client to set up the timer and send an event to your bot if no activities are sent before the timer expires. You need to create a custom store to do this. Here is an example I used in the past:

                const store = window.WebChat.createStore({}, function(dispatch) { return function(next) { return function(action) {
                    if (action.type === 'WEB_CHAT/SEND_MESSAGE') {
                        // Message sent by the user
                        clearTimeout(interval);
                    } else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' && action.payload.activity.name !== "inactive") {
                        // Message sent by the bot
                        clearInterval(interval);
                        interval = setTimeout(function() {
                            
                            // Notify bot the user has been inactive
                            dispatch.dispatch({
                                type: 'WEB_CHAT/SEND_EVENT',
                                payload: {
                                    name: 'inactive',
                                    value: ''
                                }
                            });
                            
                        }, 300000)
                    }
    
                    return next(action);
                }}});
    

    This will send an event to your bot with the name 'inactive'. Now you need to set up your bot to handle it. So in your this.onEvent handler you need to do something like this:

    if (context.activity.name && context.activity.name === 'inactive') {
        await context.sendActivity({
            text: 'Are you still there? Is there anything else I can help you with?',
            name: 'inactive'
        });
    }
    

    All channels

    As I'm typing this up, I'm realizing you should be able to emit the event from your bot itself and forego starting a new bot adapter instance. But I haven't tried that before, so I'm providing my existing solution. But you may wish to experiment with emitting an inactive event if the timeout is reached instead of the actions below.

    That said, here is a solution you can use within your this.onMessage handler.

    // Inactivity messages
    // Reset the inactivity timer
    clearTimeout(this.inactivityTimer);
    this.inactivityTimer = setTimeout(async function(conversationReference) {
        console.log('User is inactive');
        try {
            const adapter = new BotFrameworkAdapter({
                appId: process.env.microsoftAppID,
                appPassword: process.env.microsoftAppPassword
            });
            await adapter.continueConversation(conversationReference, async turnContext => {
                await turnContext.sendActivity('Are you still there?');
            });
        } catch (error) {
            //console.log('Bad Request. Please ensure your message contains the conversation reference and message text.');
           console.log(error);
        }
    }, 300000, conversationData.conversationReference);
    

    Note that you have to get and save the conversationReference if you go this route, so that you can call continueConversation if the timer expires. I typically do this in my this.onMessage handler as well just to make sure I always have a valid conversation reference. You can get it with the below code (I'm assuming you already have your conversation state and state accessor defined).

    const conversationData = await this.dialogState.get(context, {});
    conversationData.conversationReference = TurnContext.getConversationReference(context.activity);
    

    Now as I mentioned in the first solution, I believe you should be able to send an inactivity event in your try block instead of initiating the bot adapter. If you try that and it works, please let me know so I can update this solution!