Sunday, June 4, 2017

Azure Bot Service: A basic timer bot

So far in the process, we have created a bot that responds to user input via the prompt dialog process and then sends a request to azure automation via a webhook.  The next step to address in our bot is how we inform the user who made the request about the progress or, should the need arise, any errors.  The goal of this post is to build a very very basic timer bot that uses some concepts of proactive messages. I want to better understand the concepts in play, the data required, and the user experience.

The code I built below is derived from this example on github.   Here is the test code I wrote:


using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Connector;
using System.Threading;

namespace TestBot.Dialogs
{
    [Serializable]
    public class TimerDialog : IDialog<object>
    {
        private string fromId;
        private string fromName;
        private string toId;
        private string toName;
        private string serviceUrl;
        private string channelId;
        private string conversationId;
        private string message;

        public Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);

            return Task.CompletedTask;
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            var activity = await result;

            this.toId = activity.From.Id;
            this.toName = activity.From.Name;
            this.fromId = activity.Recipient.Id;
            this.fromName = activity.Recipient.Name;
            this.serviceUrl = activity.ServiceUrl;
            this.channelId = activity.ChannelId;
            this.conversationId = activity.Conversation.Id;

            PromptDialog.Number(context,this.AfterNumberGiven , "Set a timer for how many seconds?");
        }

        public async Task AfterNumberGiven(IDialogContext context, IAwaitable<long> result)
        {
            var amountOfTime = await result;
            var t = new Timer(new TimerCallback(TimerEvent));
            t.Change(amountOfTime*1000, Timeout.Infinite);
            this.message = $"Your {amountOfTime} second timer is up.  It started {DateTime.Now}";
            await context.PostAsync($"I will contact you in {amountOfTime} seconds");
            context.Done("");
        }

        public void TimerEvent(object target)
        {
            var userAccount = new ChannelAccount(this.toId, this.toName);
            var botAccount = new ChannelAccount(this.fromId, this.fromName);
            var connector = new ConnectorClient(new Uri(serviceUrl));

            var message = Activity.CreateMessageActivity();
            message.From = botAccount;
            message.Conversation = new ConversationAccount(id: this.conversationId);
            message.Text = this.message;
            message.Locale = "en-Us";
            connector.Conversations.SendToConversationAsync((Activity)message);
        }
    }
}

So the above code is actually quite simple.  Whereas the example on github went through the trouble of storing all data in a static object, I simply stored it in this dialog itself.  Based on my understanding of how state works, this dialog is essentially serialized into memory between each request.  Here is what the interaction looks like in the emulator.


You will note that I was able to kick off multiple timers and have them respond differently.  Okay, so here were some notes I made:

1)  I wanted to know more about the data required to continue a conversation.

Here is a snippit from the debugger of my interaction.  Please note that these were captured from the emulator.

  

The key pieces above are the channelId and the serviceURL.  These probably vary greatly based on the channel being selected and I wonder what they look like in slack, etc.

2)  What is the minimum set of information I need to issue a response?

In my use case, someone may send a request to my bot to kick off an automation job in a private message, but I might still want to broadcast that to the general channel in slack so that everyone knows the process of jobs.  You'll note in the code sample above, I actually don't use the toId/toName in my message response.  So it seems, at least in the emulator, you don't need to specify those fields.

When I tried removing the message.From property, the emulator generated a 400 error message.


When I decided to create a new direct conversation, the emulator essentially erased my previous history and replaced it with the new conversation.  This is good to know, so essentially, I do not NEED to keep the conversation id, although it  may lead to weird user behavior as state will be lost.

Ultimately, I guess that some of this behavior will be channel specific, and I'll need to invest further time with slack on all the different options.

This was a good first step and a very basic example on playing around with proactive messages.  Now that I have some understanding of what is involved,  I will now have to add some external storage, and a method for Azure Automation to communicate back to the user.  A future post for sure!

No comments:

Post a Comment