Hi I'm using C# in Visual studio with Microsoft's bot emulator and I'm wondering how can I prompt the user for a number input in the first message and store it in the conversation. Here's what I have so far in my MessagesController:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.LuisHandler());
}
else
if (activity.Type == ActivityTypes.ConversationUpdate)
{
if (activity.MembersAdded.Any(o => o.Id == activity.Recipient.Id))
{
PromptDialog.Number(activity,setUserID,"Please enter your number code","Error please enter the number again",3,"",0,999);
}
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
This is obviously not the correct way to do it since both the activity and setUserId attributes display an error, how can I fix this?
Also, this my SetUserId method:
public void setUserID(int id, Activity act)
{
IDialogContext cxt = act;
cxt.UserData.SetValue("userId", id);
}
Where id will be the number provided by the user and I save it in the context of the conversation to use it in queries later, how can I achieve this behavior?
how can I prompt the user for a number input in the first message and store it in the conversation.
The PromptDialog.Number() method accepts IDialogContext object as a parameter, so we can not directly call it from Messages controller (outside of Dialog) to prompt for a number.
public static void Number(IDialogContext context, ResumeAfter<long> resume, string prompt, string retry = null, int attempts = 3, string speak = null, long? min = null, long? max = null);
To achieve the requirement: prompting the user for a number input in the first message and storing it in the conversation, you can try the following sample as an workaround.
In Messages controller:
//...
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
IConversationUpdateActivity update = message;
var client = new ConnectorClient(new System.Uri(message.ServiceUrl), new MicrosoftAppCredentials());
if (update.MembersAdded != null && update.MembersAdded.Any())
{
foreach (var newMember in update.MembersAdded)
{
if (newMember.Id != message.Recipient.Id)
{
var reply = message.CreateReply();
reply.Text = $"Welcome {newMember.Name}! Please enter your number code!";
client.Conversations.ReplyToActivityAsync(reply);
}
}
}
}
In dialog:
[Serializable]
public class RootDialog : IDialog<object>
{
bool thenumberisentered = false;
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (!thenumberisentered)
{
string pattern = #"^(?:0|[1-9]\d{0,2})$";
System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
if (rgx.IsMatch(activity.Text))
{
//save the number code as user data here
thenumberisentered = true;
await context.PostAsync($"The number code you entered is: {activity.Text}");
}
else
{
PromptDialog.Number(context, setUserID, "Please enter your number code", "Error please enter the number again", 2, "", 0, 999);
return;
}
}
else
{
await context.PostAsync($"You sent {activity.Text}");
}
context.Wait(MessageReceivedAsync);
}
private async Task setUserID(IDialogContext context, IAwaitable<long> result)
{
long numcode = await result;
await context.PostAsync($"The number code you entered is: {numcode}");
thenumberisentered = true;
//save the number code as user data here
context.Wait(MessageReceivedAsync);
}
}
Test result:
Related
My telegram bot is necessary so that the user can answer questions in order and save these answers in the same order for a specific user in parallel.
static readonly ConcurrentDictionary<int, string[]> Answers = new ConcurrentDictionary<int, string[]>();
static void Main(string[] args)
{
try
{
Task t1 = CreateHostBuilder(args).Build().RunAsync();
Task t2 = BotOnMessage();
await Task.WhenAll(t1, t2);
}
catch (Exception ex)
{
Console.WriteLine("Error" + ex);
}
}
here is my BotOnMessage() method to receive and process messages from users
async static Task BotOnMessage()
{
int offset = 0;
int timeout = 0;
try
{
await bot.SetWebhookAsync("");
while (true)
{
var updates = await bot.GetUpdatesAsync(offset, timeout);
foreach (var update in updates)
{
var message = update.Message;
if (message.Text == "/start")
{
Registration(message.Chat.Id.ToString(), message.Chat.FirstName.ToString(), createdDateNoTime.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture));
var replyKeyboard = new ReplyKeyboardMarkup
{
Keyboard = new[]
{
new[]
{
new KeyboardButton("eng"),
new KeyboardButton("ger")
},
}
};
replyKeyboard.OneTimeKeyboard = true;
await bot.SendTextMessageAsync(message.Chat.Id, "choose language", replyMarkup: replyKeyboard);
}
switch (message.Text)
{
case "eng":
var replyKeyboardEN = new ReplyKeyboardMarkup
{
Keyboard = new[]
{
new[]
{
new KeyboardButton("choice1"),
new KeyboardButton("choice2")
},
}
};
replyKeyboardEN.OneTimeKeyboard = true;
await bot.SendTextMessageAsync(message.Chat.Id, "Enter choice", replyMarkup: replyKeyboardEN);
await AnonymEN();
break;
case "ger":
var replyKeyboardGR = new ReplyKeyboardMarkup
{
Keyboard = new[]
{
new[]
{
new KeyboardButton("choice1.1"),
new KeyboardButton("choice2.2")
},
}
};
replyKeyboardGR.OneTimeKeyboard = true;
await bot.SendTextMessageAsync(message.Chat.Id, "Enter choice", replyMarkup: replyKeyboardGR);
await AnonymGR();
break;
}
offset = update.Id + 1;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error" + ex);
}
}
and AnonymEN() method for eng case in switch. The problem appears here when I call this method from switch case in BotOnMessage(). Until switch (message.Text) multiple users can asynchronously send messages and get response. When first user enters AnonymEN() second user can't get response from this method until first user will finish it till the end. Also I call BotOnMessage() in the end of AnonymEN() to get back for initial point with possibility to start bot again. For the ordered structure of questions and answers I used ConcurrentDictionary way from here Save user messages sent to bot and send finished form to other user. Any suggestion and solution how to edit code to make this bot available for multiple users at one time?
async static Task AnonymEN()
{
int offset = 0;
int timeout = 0;
try
{
await bot.SetWebhookAsync("");
while (true)
{
var updates = await bot.GetUpdatesAsync(offset, timeout);
foreach (var update in updates)
{
var message = update.Message;
int userId = (int)message.From.Id;
if (message.Type == MessageType.Text)
{
if (Answers.TryGetValue(userId, out string[] answers))
{
var title = message.Text;
if (answers[0] == null)
{
answers[0] = message.Text;
await bot.SendTextMessageAsync(message.Chat, "Enter age");
}
else
{
SaveMessage(message.Chat.Id.ToString(), "anonym", "anonym", "anonym", answers[0].ToString(), title.ToString(), createdDateNoTime.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture));
Answers.TryRemove(userId, out string[] _);
await bot.SendTextMessageAsync(message.Chat.Id, "ty for request click /start");
await BotOnMessage();
}
}
else if (message.Text == "choice1")
{
Answers.TryAdd(userId, new string[1]);
await bot.SendTextMessageAsync(message.Chat.Id, "Enter name");
}
}
offset = update.Id + 1;
}
}
}
catch (Exception ex)
{
Console.WriteLine("Error" + ex);
}
}
I can see multiple issues with your code:
It is hard to read. While this is a personal preference I strongly advise to write short concise methods that have 1 responsibility. This will make it easier to understand and maintain your code. https://en.wikipedia.org/wiki/Single-responsibility_principle
Everything is static. This makes it very hard to keep track of any state such as language that should be tracked per user.
Using infinite loops and recursion with no escape. I highly doubt this was intended but you could get an infinite chain of calls like this BotOnMessage -> AnonymEN -> BotOnMessage -> AnonymEN. I think you want to exit the AnonymEN function using either a return, break or while(someVar) approach instead of calling the BotOnMessage function.
If two users are sending messages you get mixed responses. Example message flow user1: /start, user1: eng, user2: hello. The bot will now give an english response to user2. I'm sure this is not intended
The code below is a minimal example that addresses the issues I mentioned. It is not perfect code but should help you get started.
private Dictionaty<string, UserSession> userSessions = new ();
async Task BotOnMessage()
{
try
{
while(true)
{
var message = await GetMessage(timeout);
var userSession = GetUserSession(message.user);
userSession.ProcessMessage(message);
}
}
catch(){}
}
async void GetUserSession(string user)
{
if(!userSessions.HasKey(user))
{
userSessions[user](new Session());
}
return userSessions[user];
}
public class UserSession
{
public async Task ProcessMessage(message)
{
// Existing message processing code goes here.
// Do not use a loop or recursion.
// Instead track the state (e.g. languge) using fields.
}
}
How can I get the nickname of the author of the message in Discord using Discord.net 2.2.0.
private async Task MessageReceivedAsync(SocketMessage message)
{
if (StartBit == 0)
{
await message.Channel.SendMessageAsync("Test");
}
StartBit = 1;
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id)
return;
var UName = message.Author.Username;
var UID = message.Author.Id;
}
A long search and reading of the documentation unfortunately gave me nothing.
If you want to get the authors nick name you can cast it to SocketGuildUser, but beware, it can be null if it's a DM.
var UNick = (message.Author as SocketGuildUser).Nickname;
Also you probably should check if the message is from a user and not system
if (!(sockMessage is SocketUserMessage msg))
return;
So your code will look something like this
private async Task MessageReceivedAsync(SocketMessage sockMessage)
{
// Check if a user posted the message
if (!(sockMessage is SocketUserMessage msg))
return;
// Check if it is not a DM
if (!(msg.Author as SocketGuildUser author))
return;
if (StartBit == 0)
{
await msg.Channel.SendMessageAsync("Test");
}
StartBit = 1;
// I usualy check if the author is not bot, but you can change that back
if (msg.Author.IsBot)
return;
var UName = author.Username;
var UNick = author.Nickname;
var UID = author.Id;
}
Credit to Anu6is
Try casting your message to an SocketUserMessage. I have attached the correct code for that and please edit your post, so that the code is presented correctly.
private async Task MessageReceivedAsync(SocketMessage msg)
{
SocketUserMessage message = msg as SocketUserMessage;
if (StartBit == 0)
{
await message.Channel.SendMessageAsync("Test");
}
StartBit = 1;
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id) return;
var UName = message.Author.Username;
var UID = message.Author.Id;
}
This question already has answers here:
Cannot implicitly convert type from Task<>
(5 answers)
Closed 3 years ago.
I want to get the string from the variable "inputtext" in ShowKeyboard(...) but I don't know how to do that. I always get an error message in this line of code:
KeyboardTextUsername = NewGetKeyboard(Tokenusername, "Registration", "Username", "Choose a username", false);
Error CS0029: Cannot implicitly convert type
'System.Threading.Tasks.Task' to 'string'
EDIT: After changing the code, I get this error message:
KeyboardTextUsername = await NewGetKeyboard(Tokenusername, "Registration", "Username", "Choose a username", false);
Error CS4033: The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
What am I doing wrong? I don't know how to resolve this problem.
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
TouchPanel.EnabledGestures = GestureType.Tap;
UsernameRectangle = new Microsoft.Xna.Framework.Rectangle(400, 230, 150, 100);
CursorRectangle = new Microsoft.Xna.Framework.Rectangle(-150, -100, 10, 10);
}
protected override void Update(GameTime gameTime)
{
while (TouchPanel.IsGestureAvailable)
{
GestureSample gs = TouchPanel.ReadGesture();
switch (gs.GestureType)
{
case GestureType.Tap:
CursorRectangle = new Microsoft.Xna.Framework.Rectangle((int)gs.Position.X, (int)gs.Position.Y, 10, 10);
CheckButtonPressed = true;
break;
}
}
if ((UsernameRectangle.Intersects(CursorRectangle)) && (CheckButtonPressed == true))
{
KeyboardTextUsername = await NewGetKeyboard(Tokenusername, "Registration", "Username", "Choose a username", false);
}
CheckButtonPressed = false;
base.Update(gameTime);
}
public async Task<string> NewGetKeyboard(string Token, string MessageBoxTitle, string MessageBoxDescription, string Text, bool IsPassword)
{
string keyboardtext = "";
string savedtext = await Getlogin(Token);
if (savedtext == "")
savedtext = Text;
keyboardtext = await ShowKeyboard(Token, MessageBoxTitle, MessageBoxDescription, savedtext, IsPassword);
return keyboardtext;
}
public async Task<string> Getlogin(string token)
{
string Text = "";
try
{
Text = await SecureStorage.GetAsync(token);
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
Console.WriteLine("secure storage not supported on this device");
}
return Text;
}
private async Task<string> ShowKeyboard(string token, string messageboxtitle, string messageboxdescription, string text, bool ispassword)
{
string inputtext = "";
await Task.Run(async () =>
{
var result = await KeyboardInput.Show(messageboxtitle, messageboxdescription, text, ispassword);
if (null != result)
{
inputtext = result;
}
});
try
{
await SecureStorage.SetAsync(token, inputtext);
}
catch (Exception ex)
{
// Possible that device doesn't support secure storage on device.
Console.WriteLine("secure storage not supported on this device");
}
return inputtext;
}
You can't just call await outside of a method like that, that goes for your conditions as well. await needs to be within an async method; and conditions need to be within a method (be it async, or regular (sync) method). In this case, it'll reside in your async method.
public async Task<string> CallNewGetKeyboard(UsernameRectangle userRec, bool CheckButtonPressed, string tokenUserName, string messageBoxTitle, string messageBoxDescription, string messageBoxText, bool isPassword)
{
if ((userRec.Intersects(CursorRectangle)) && (CheckButtonPressed == true))
{
var KeyboardTextUsername = await NewGetKeyboard(tokenUsername, messageBoxTitle, messageBoxDescription, messageBoxText, isPassword);
return KeyboardTextUsername;
}
}
Here's a bit of a shorter version of the above code..
public async Task<string> CallNewGetKeyboard(UsernameRectangle userRec, bool CheckButtonPressed, string tokenUserName, string messageBoxTitle, string messageBoxDescription, string messageBoxText, bool isPassword)
{
//I took out ==true because it's redundant
if ((userRec.Intersects(CursorRectangle)) && (CheckButtonPressed))
{
//and return the data without assigning it
return await NewGetKeyboard(tokenUsername, messageBoxTitle, messageBoxDescription, messageBoxText, isPassword);
}
}
NewGetKeyboard is an async method to it should be called using the await keyword
protected void async MyButtonClickHandler(object sender, EventArgs args)
{
...
KeyboardTextUsername = await NewGetKeyboard(Tokenusername, "Registration", "Username", "Choose a username", false);
...
}
If you dont want to make it async
KeyboardTextUsername = NewGetKeyboard(Tokenusername, "Registration", "Username", "Choose a username", false).GetAwaiter().GetResult();
I have a choice prompt and I wanted to make it so that even if a user types something else that synonyms with the choice the dialog can still move on. I tried doing this but its not working.
public class InitialQuestions : WaterfallDialog
{
public InitialQuestions(string dialogId, IEnumerable<WaterfallStep> steps = null)
: base(dialogId, steps)
{
AddStep(async (stepContext, cancellationToken) =>
{
var choices = new[] { "Agree" };
return await stepContext.PromptAsync(
"choicePrompt",
new PromptOptions
{
Prompt = MessageFactory.Text(string.Empty),
Choices = ChoiceFactory.ToChoices(choices),
RetryPrompt = MessageFactory.Text("Click Agree to proceed."),
});
});
AddStep(async (stepContext, cancellationToken) =>
{
var response = (stepContext.Result as FoundChoice).Value.ToLower();
var textResponse = (stepContext.Result as FoundChoice).ToString().ToLower();
if (response == "agree" || textResponse == "okay" || textResponse == "ok")
{
return await stepContext.NextAsync();
}
else
{
return await stepContext.ReplaceDialogAsync(InitialQuestions.Id);
}
});
}
public static string Id => "initialQuestions";
public static InitialQuestions Instance { get; } = new InitialQuestions(Id);
}
A choice prompt has to validate the user input by comparing it to a list of choices and the dialog will not proceed until valid input is supplied. You're trying to validate the input in the next step, but the next step won't be reached until the input is already validated and that's why textResponse will never be "okay" or "ok".
Luckily, choice prompts have a builtin way of providing synonyms for each choice. Instead of
Choices = ChoiceFactory.ToChoices(choices),
you could do something like
Choices = new List<Choice>
{
new Choice
{
Value = "Agree",
Synonyms = new List<string>
{
"Okay",
"OK",
},
},
},
Below is my Dialog.
[Serializable]
public class EmailDialog : IDialog<object>
{
async Task IDialog<object>.StartAsync(IDialogContext context)
{
context?.Wait(RequestEmailAddress);
}
private async Task RequestEmailAddress(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
context.Wait(RequestEmailAddress);
var thumbNailCard = new ThumbnailCard
{
Title = "BotFramework Thumbnail Card",
Subtitle = "Your bots — wherever your users are talking",
Text = "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.",
Images = new List<CardImage> { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.botframework.com/en-us/") }
};
var resultMessage = context.MakeMessage();
resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
resultMessage.Attachments = new List<Attachment> { thumbNailCard.ToAttachment() };
await context.PostAsync(resultMessage);
}
}
After the card is sent, any user input is hitting the REST API endpoint and the method RequestEmailAddress is not called at all.
NOTE: Initially I forgot to add context.Wait(RequestEmailAddress); and did so only after a couple of run. Could it be the bot has already ran into infinite loop of not knowing the context?
I even tried clearing up the stack and clearing up the bot state. Nothing helped.
EDIT: Adding the message controller part
private async Task<Activity> OnHandleActivityType(Activity activity)
{
switch (activity.Type)
{
case ActivityTypes.ContactRelationUpdate:
OnContactRelationUpdate(activity);
break;
case ActivityTypes.ConversationUpdate:
OnConversationUpdate(activity);
break;
case ActivityTypes.DeleteUserData:
break;
case ActivityTypes.EndOfConversation:
break;
case ActivityTypes.Event:
break;
case ActivityTypes.Invoke:
break;
case ActivityTypes.Message:
OnMessageReceived(activity);
break;
case ActivityTypes.Ping:
break;
case ActivityTypes.Typing:
break;
default:
throw new NotImplementedException();
}
return null;
}
private async void OnMessageReceived(Activity activity)
{
var message = activity.Text;
if (message == AppConstants.RegisterEmail))
{
await Conversation.SendAsync(activity, () => new EmailDialog());
}
}
The context.Wait(RequestEmailAddress); call should be at the end of the RequestEmailAddress method.
private async Task RequestEmailAddress(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
var thumbNailCard = new ThumbnailCard
{
Title = "BotFramework Thumbnail Card",
Subtitle = "Your bots — wherever your users are talking",
Text = "Build and connect intelligent bots to interact with your users naturally wherever they are, from text/sms to Skype, Slack, Office 365 mail and other popular services.",
Images = new List<CardImage> { new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, "Get Started", value: "https://docs.botframework.com/en-us/") }
};
var resultMessage = context.MakeMessage();
resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel;
resultMessage.Attachments = new List<Attachment> { thumbNailCard.ToAttachment() };
await context.PostAsync(resultMessage);
context.Wait(RequestEmailAddress);
}
Also, incoming messages will always hit your endpoint (because it's the entry point). Then they will be delivered to the dialog. That means that you need to update the OnMessageReceived method on your controller and remove the if check for the message text match otherwise the user message won't be delivered anywhere:
private async void OnMessageReceived(Activity activity)
{
await Conversation.SendAsync(activity, () => new EmailDialog());
}