Is there a proper way for bot to start a conversation with PromptDialog.Choice in the Direct Line channel?
I am trying an ugly hack with catching the fist ConversationUpdate activity and creating a fake message from user to initialize the dialog like this:
IMessageActivity greetingMessage = Activity.CreateMessageActivity();
greetingMessage.From = message.Recipient;//from bot
greetingMessage.Recipient = userAccount;//to user
greetingMessage.Conversation = message.Conversation;
greetingMessage.Text = "Hello, I am a bot";
greetingMessage.Locale = "en-us";
greetingMessage.Id = Guid.NewGuid().ToString();
await connector.Conversations.SendToConversationAsync((Activity)greetingMessage);
IMessageActivity dialogEntryMessage = Activity.CreateMessageActivity();
dialogEntryMessage.Recipient = message.Recipient;//to bot
dialogEntryMessage.From = message.From;//from user
dialogEntryMessage.Conversation = message.Conversation;
dialogEntryMessage.Text = "any text";
dialogEntryMessage.Locale = "en-us";
dialogEntryMessage.ChannelId = message.ChannelId;
dialogEntryMessage.ServiceUrl = message.ServiceUrl;
dialogEntryMessage.Id = Guid.NewGuid().ToString();
dialogEntryMessage.ReplyToId = greetingMessage.Id;
await Conversation.SendAsync(dialogEntryMessage, () => new Dialogs.RootDialog());
Where message is a ConversationUpdate message from. In the RootDialog I start with a PromptDialog.Choice.
It works in the emulator, but in Direct Line channel bot doesn't remember the dialog state and when user choose one of dialog options and send his first real message, root dialog starts again from the PromptDialog.Choice, so it appears twice.
Update
I found a relevant blogpost from Microsoft: https://blog.botframework.com/2018/07/12/how-to-properly-send-a-greeting-message-and-common-issues-from-customers/
in Direct Line channel bot doesn't remember the dialog state and when user choose one of dialog options and send his first real message, root dialog starts again from the PromptDialog.Choice, so it appears twice.
I can reproduce same issue on my side, and I find that ConversationUpdate handler will be executed when both bot and user is added to the conversation.
To solve the issue, you can refer to the following code sample.
In MessagesController:
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
if (update.MembersAdded != null && update.MembersAdded.Any())
{
foreach (var newMember in update.MembersAdded)
{
if (newMember.Name == "{your_botid_here}")
{
IMessageActivity greetingMessage = Activity.CreateMessageActivity();
//...
//your code logic
//...
IMessageActivity dialogEntryMessage = Activity.CreateMessageActivity();
dialogEntryMessage.Recipient = message.Recipient;//to bot
dialogEntryMessage.From = message.From;//from user
dialogEntryMessage.Conversation = message.Conversation;
dialogEntryMessage.Text = "show choices";
dialogEntryMessage.Locale = "en-us";
dialogEntryMessage.ChannelId = message.ChannelId;
dialogEntryMessage.ServiceUrl = message.ServiceUrl;
dialogEntryMessage.Id = System.Guid.NewGuid().ToString();
dialogEntryMessage.ReplyToId = greetingMessage.Id;
await Conversation.SendAsync(dialogEntryMessage, () => new Dialogs.RootDialog());
}
}
}
}
In RootDialog:
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var mes = activity.Text.ToLower();
string[] choices = new string[] { "choice 1", "choice 2" };
if (Array.IndexOf(choices, mes) > -1)
{
await context.PostAsync($"You selected {mes}");
}
else if(mes == "show choices")
{
PromptDialog.Choice(context, resumeAfterPrompt, choices, "please choose an option.");
}
else
{
await context.PostAsync($"You sent {activity.Text} which was {length} characters.");
context.Wait(MessageReceivedAsync);
}
}
private async Task resumeAfterPrompt(IDialogContext context, IAwaitable<string> result)
{
string choice = await result;
await context.PostAsync($"You selected {choice}");
}
Test result:
Related
I've been trying to build a bot that uses QnA to anser the user based on categories.
I've managed to connect to QnA correctly and get the first level prompted to the user, however, the problem appears when I try to send to QnA another response from the user.
Here's the dialog that runs smoothly on it's first request.
private async Task<DialogTurnResult> InicioRRHHDialog(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
string intentForQna = IntentEnum.DISPATCH_RRHH;
var castContextForQna = (WaterfallStepContext)stepContext.Parent;
var contextForNextStep = stepContext.Context;
await GetQnaConnection(castContextForQna, intentForQna);
return await stepContext.NextAsync(contextForNextStep, cancellationToken);
}
This is the GetQnaConnection being called.
private async Task GetQnaConnection(WaterfallStepContext stepContext, string intent)
{
servicioQNA = new QnAMakerService(this._qnaService.Url, _qnaRRHHkbId, this._qnaService.QnaEndPointKey);
var mensaje = await servicioQNA.QueryQnAServiceAsync(intent, null);
await ShowResults(stepContext, mensaje);
}
And lastly the ShowResults method that I use to send the activity back to the user.
private static async Task ShowResults(WaterfallStepContext stepContext, QnAResult[] mensaje)
{
Activity outputActivity = null;
QnABotState newState = null;
var qnaAnswer = mensaje[0].Answer;
var prompts = mensaje[0].Context?.Prompts;
if (prompts == null || prompts.Length < 1)
outputActivity = MessageFactory.Text(qnaAnswer);
else
{
newState = new QnABotState
{
PreviousQnaId = mensaje[0].Id,
PreviousUserQuery = stepContext.Result.ToString()
};
outputActivity = CardHelper.GetHeroCard(qnaAnswer, prompts);
}
var outputs = new Activity[] { outputActivity };
foreach (var activity in outputs)
await stepContext.Context.SendActivityAsync(activity).ConfigureAwait(false);
}
Am I handling the Qna connection correctly?
Please let me know if we can disable the card buttons in bot framework or any other scenario through which my below requirement can be fulfilled.
Below is the code which I am trying to execute in Bot Framework and LUIS.
[LuisIntent("OrderStatus")]
public async Task Status(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
var message = await activity;
List<CardAction> cardButtons = new List<CardAction>();
CardAction cityBtn1 = new CardAction()
{
Title = "Laptop",
Value = "Laptop"
};
cardButtons.Add(cityBtn1);
CardAction cityBtn2 = new CardAction()
{
Title = "Smart Phone",
Value = "Smart Phone",
};
cardButtons.Add(cityBtn2);
CardAction cityBtn3 = new CardAction()
{
Title = "Pendrive",
Value = "Pendrive"
};
cardButtons.Add(cityBtn3);
CardAction cityBtn4 = new CardAction()
{
Title = "No option",
Value = "No option"
};
cardButtons.Add(cityBtn4);
var reply = context.MakeMessage();
HeroCard plCard = new HeroCard()
{
Title = "Please select from the following option?",
Buttons = cardButtons
};
Attachment plAttachment = plCard.ToAttachment();
reply.Attachments.Add(plCard.ToAttachment());
await context.PostAsync(reply);
context.Wait(MessageReceivedAsync);
}
private const string Yes = "Yes";
private const string No = "No";
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> awaitresult)
{
var selectedCard = await awaitresult as Activity;
string result = string.Empty;
var selectedText = selectedCard.Text;
if (selectedText.ToLower() == "Laptop".ToLower())
{
result = "Your laptop will be delivered by the end of this week";
}
else if (selectedText.ToLower() == "high water content".ToLower())
{
result = "Your smart phone will be delivered by the end of this week";
}
else if (selectedText.ToLower() == "Incompatible fuel or high water content".ToLower())
{
result = "Your pendrive will be delivered by the end of this week";
}
else if (selectedText.ToLower() == "No option".ToLower())
{
result = "Please visit our official website for more options";
}
await context.PostAsync(result);
PromptDialog.Choice(context, this.AfterMenuSelection, new List<string>() { Yes, No }, "Do you have any other issue?");
}
private async Task AfterMenuSelection(IDialogContext context, IAwaitable<string> result)
{
var optionSelected = await result;
switch (optionSelected)
{
case Yes:
await context.PostAsync("Please post your issue");
break;
case No:
await context.PostAsync("Thanks for contacting us");
break;
}
context.EndConversation("End");
}
So when the user click on one button, he should not be able to click any of the buttons for the second time.
Please help me with this issue.
You might want to try Suggested Actions instead. Per the docs:
The support varies depending the channel you are using, so you might want to check the Channel Inspector too.
I have a method collecting user data from MS Teams, while this code works as intended, with this method added it makes a previously working method crash.
public async void GetUsers()
{
string teamId = "TeamsID";
string tenantId = "TenantID";
var connector = new ConnectorClient(new Uri(Instance.Activity.ServiceUrl));
members = await connector.Conversations.GetTeamsConversationMembersAsync(teamId, tenantId);
Instance.EmailList.Clear();
foreach (var member in members)
{
Instance.EmailList.Add(member.Email);
}
}
I believe that the Line:
members = await connector.Conversations.GetTeamsConversationMembersAsync(teamId, tenantId);
While receiving the user information, makes the bot think that a user is inputing, causing my later methods to trigger without user input, and crashing because there is no input or because the input if the chunk of data that is the user data.
This is just my theory and I may be incorrect.
The following is the method that crashes:
async Task ReplyToQuestions(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var AnswerHolder = await argument;
Answers.Add(AnswerHolder.Text);
Answers[AnswerCounter] = Answers[AnswerCounter].First().ToString().ToUpper() + Answers[AnswerCounter].Substring(1);
var isQuit = AnswerHolder.Text;
var isQuit2 = Regex.Match(isQuit, #"\b(Quit|quit|QUIT)\b").Value;
Regex rgx = new Regex(#"\b(Quit|quit|QUIT)\b");
if (rgx.IsMatch(isQuit2)) // checks for the user trying to quit/restart bot
{
await context.PostAsync(string.Format("Exiting current process. Restarting."));
context.Done(isQuit); // returns to the start of dialog (StartAsync)
}
else
{
if (QuestionCounter < 5)
{
await context.PostAsync(string.Format($"Question {QuestionCounter + 1}: {Question[QuestionCounter]}"));
}
AnswerCounter += 1;
QuestionCounter += 1;
if (AnswerCounter < 5)
{
context.Wait(ReplyToQuestions);
}
else if (AnswerCounter == 5)
{
PostToChannel($"{Name}'s answers to the questions are as follows: \n\nQuestion1 Answer: {Answers[0]} \n\nQuestion2 Answer: {Answers[1]} \n\n" +
$"Question3 Answer: {Answers[2]} \n\nQuestion4 Answer: {Answers[3]} \n\nQuestion5 Answer: {Answers[4]} \n\n", context);
await context.PostAsync(string.Format("Your answers have been posted to the 'What's Up' channel."));
AnswerCounter = 0;
QuestionCounter = 1;
context.Done(Answers[4]);
}
else
{
await context.PostAsync($"{AnswerCounter}");
await context.PostAsync(string.Format("Oh no! it appears something has gone wrong. please try re-entering your answers"));
AnswerCounter = 0;
QuestionCounter = 1;
context.Wait(ReplyToQuestions);
}
}
}
And the code that calls it:
async Task Choice(IDialogContext context, IAwaitable<IMessageActivity> argument) // this method recives and validates a question
{
var Choice = await argument;
var isQuit = Choice.Text;
var isQuit2 = Regex.Match(isQuit, #"\b(Quit|quit|QUIT)\b").Value;
Regex rgx = new Regex(#"\b(Quit|quit|QUIT)\b");
var isEnter = Regex.Match(isQuit, #"\b(Enter|ENTER|enter)\b").Value;
Regex rgx2 = new Regex(#"\b(Enter|ENTER|enter)\b");
var isReply = Regex.Match(isQuit, #"\b(Reply|REPLY|reply)\b").Value;
Regex rgx3 = new Regex(#"\b(Reply|REPLY|reply)\b");
GetUsers();
if (rgx.IsMatch(isQuit2)) // if the user choses to quit
{
await context.PostAsync(string.Format("Exiting current process. Restarting."));
context.Done(isQuit); // restarts the program, calls the first method
}
else if (rgx2.IsMatch(isEnter)) // if the user choses to quit
{
await context.PostAsync(string.Format("Please enter your custom question."));
context.Wait(EnterQuestion);
}
else if (rgx3.IsMatch(isReply)) // if the user choses to quit
{
Answers.Clear();
await context.PostAsync(string.Format("Please answer the following questions:"));
await context.PostAsync(string.Format($"Question 1: {Question[0]}"));
context.Wait(ReplyToQuestions);
}
else
{
await context.PostAsync(string.Format("sorry this was not a choice, try again."));
}
}
Does anyone know a way I can fix this? As I have spent 2 full days on this without success.
I'm not sure what error you are seeing. But, the method being used to retrieve conversation members has been deprecated: https://msdn.microsoft.com/en-us/microsoft-teams/botapis#net-example The note on that page should state:
you should migrate your code to use GetConversationMembersAsync(conversationId)
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var members = await connector.Conversations.GetConversationMembersAsync(activity.Conversation.Id);
Hi I created my first test bot using Microsoft BotFramework in C#.
in private async Task< Activity > HandleSystemMessage(Activity message) in if (message.Type == ActivityTypes.ConversationUpdate) normally it should notify a new member added to group or someone hit the start button of bot in Telegram Messenger. When I test it in debug mode using BotFramework emulator everything works perfectly but after I publish it I see that after hitting start button in Telegram messenger my code didn't run.
My code in ActivationType.ConversationUpdate
foreach (var item in message.MembersAdded)
{
try
{
using (var dbcontext = new WatermarkBotDBEntities())
{
dbcontext.BotUsers.Add(new BotUser()
{
AddedFriends = 0,
ConversationID = message.Conversation.Id,
ServiceUrl = message.ServiceUrl,
UserID = message.From.Id
});
dbcontext.SaveChanges();
if (Request.RequestUri.Query != "")
{
var u = dbcontext.BotUsers.Where(x => x.BotSalCode == Request.RequestUri.Query.Replace("?start=", string.Empty)).FirstOrDefault();
u.AddedFriends++;
dbcontext.Entry(u).State = System.Data.Entity.EntityState.Modified;
if (u != null)
{
var connector = new ConnectorClient(new Uri(u.ServiceUrl));
IMessageActivity newMessage = Activity.CreateMessageActivity();
newMessage.Type = ActivityTypes.Message;
//newMessage.From = new ChannelAccount("<BotId>", "<BotName>");
newMessage.From = new ChannelAccount("c3e7mhdafcecn7ng3", "Bot");
newMessage.Conversation = new ConversationAccount(false, u.ConversationID);
newMessage.Recipient = new ChannelAccount(u.UserID);
if (u.AddedFriends <= 2)
newMessage.Text = $"SomeText.";
else newMessage.Text = "SomeTex";
await connector.Conversations.SendToConversationAsync((Activity)newMessage);
dbcontext.SaveChanges();
}
}
}
}
catch (Exception ex)
{
}
So how is it possible to detect hitting start in telegram ?
Regards
I realize this is not a complete answer, but I wanted to share this code with you in case it may help. Below is the recommended way to send a welcome message, you may be able to repurpose this code for your use.
else if (message.Type == ActivityTypes.ConversationUpdate || message.Type == ActivityTypes.Message)
{
IConversationUpdateActivity iConversationUpdated = message as IConversationUpdateActivity;
if (iConversationUpdated != null)
{
ConnectorClient connector = new ConnectorClient(new System.Uri(message.ServiceUrl));
foreach (var member in iConversationUpdated.MembersAdded ?? System.Array.Empty<ChannelAccount>())
{
// if the bot is added, then
if (member.Id == iConversationUpdated.Recipient.Id)
{
var reply = ((Activity)iConversationUpdated).CreateReply(
$"Hi! I'm Botty McBot.");
await connector.Conversations.ReplyToActivityAsync(reply);
}
}
}
}
This is the answer I found for my question after lots of testing :
In MessagesController class in public async Task<HttpResponseMessage> Post([FromBody]Activity activity) function that defined by default in a BotFramework Application you have to do something like this :
if (activity.Type == ActivityTypes.Message)
{
if (activity.Text.StartsWith("/start"))
{
//This will return you the start parameter of a link like : http://telegram.me/botname?start=Parameter
var Parameter = activity.Text.Replace("/start ", "");
}
}
and if you want to send a welcome message so you can surely use the way that #JasonSowers told and use his code to send your message .
Best Regards
I have created a Bot Framework project and am using LUIS intents and entities to populate the fields of an AdaptiveCard, which then gets attached to an Activity and posted to the user. I've placed a SubmitAction as part of the card, and the relevant code is as follows:
[LuisIntent("Query GPN")]
public async Task QueryGPN(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
EntityRecommendation GPN;
AdaptiveCard gpnCard = new AdaptiveCard();
gpnCard.Body.Add(new TextBlock()
{
Text = "GPN Lookup Form",
Size = TextSize.Large,
Weight = TextWeight.Bolder
});
gpnCard.Body.Add(new TextBlock()
{
Text = "GPN",
Weight = TextWeight.Bolder
});
TextInput gpnInput = new TextInput()
{
Id = "GPN",
IsMultiline = false
};
gpnCard.Body.Add(gpnInput);
gpnCard.Actions.Add(new SubmitAction()
{
Title = "Submit"
});
if (result.TryFindEntity("GPN", out GPN))
{
await context.PostAsync($"You want to know about a GPN {GPN.Entity}? Prepopulating form.");
gpnInput.Value = GPN.Entity;
}
else
{
await context.PostAsync("It seems like you wanted to know about a GPN, but I couldn't find a GPN in your request.");
}
Attachment gpnCardAttachment = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = gpnCard
};
IMessageActivity gpnFormMessage = context.MakeMessage();
gpnFormMessage.Attachments = new List<Attachment>();
gpnFormMessage.Attachments.Add(gpnCardAttachment);
await context.PostAsync(gpnFormMessage);
context.Done<IMessageActivity>(null);
}
I'm trying to retrieve the contents of this form when the user clicks on the Submit button, which seems to send a message Activity to the Post method in my MessagesController. However, the contents of this message activity is null. How do I retrieve the payload associated with the submit action from this message activity? In the example above, how would I retrieve the value for the "GPN" key? I've tried looking in the attachments but it doesn't seem to be there as well:
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
try
{
IMessageActivity message = await result;
if(message.Text == null)
{
await context.PostAsync("Received null message.");
if (message.Attachments != null)
{
await context.PostAsync($"Attachments: {message.Attachments.Count}");
}
else
{
await context.PostAsync($"No attachments.");
}
if(message.ChannelData != null)
{
await context.PostAsync("ChannelData contains something");
}
else
{
await context.PostAsync($"No channel data.");
}
context.Wait(MessageReceivedAsync);
}
else
{
await context.Forward(new IntentDialog(), IntentDialogAfter, message, CancellationToken.None);
}
}
catch (Exception e)
{
await context.PostAsync(e.Message);
}
}
Try checking the Value property of the message on the MessageReceivedAsync after clicking the Submit button.
Here is an example using Submit button and reading the values.