I'm currently busy designing a bot, that receives a project name and returns the status of it, however I can't get the prompting for the name to work.
Currently this is the method i'm using to prompt the user
[LuisIntent("ProjectInfo")]
public async Task projectInfo(IDialogContext context, LuisResult result, IAwaitable<string> Userresult)
{
await context.PostAsync($"Enter your project name");
var Promt = await Userresult;
string projectName = Promt.ToString().ToLower();
if(projectName != null)
{
TestInfo MI = new TestInfo();
if(MI.FindProject(projectName.ToString()) == 0)
{
await context.PostAsync($"Project Found. What do you want to know ?");
}
else
{
await context.PostAsync($"Project Not Found.");
}
}
context.Wait(MessageReceived);
}
With this current code I'm receiving a Exception: ProjectInfo [File of type 'text/plain'].
I have tried using a prompt dialog but that didn't seem to work ether. My end goal for this is to loop and prompt the user for a new project name until "Project Found" is displayed.
I'm not sure if I'm going about this the right way, if not any suggestions are welcome.
Unfortunately I've not come across your version yet but I can give you an example of a different approach.
Usually I prompt for simple texts something like this:
PromptDialog.Text(context, AfterPromptMethod, "Prompt text", attempts: 100);
Signature of AfterPromptMethod:
async Task AfterPromptMethod(IDialogContext context, IAwaitable<string> userInput)
With this you could do your logic in the AfterPromptMethod and loop back to the prompt in messageReceived.
Related
I'm trying to find a way to check if a user has X role and performing something if they don't, similar on how it logs it to the console if you use [RequireUserPermission(GuildPermission.Administrator)], just I don't want it to log to the console, EX:
if (has role)
{
// do stuff
} else
{
// do stuff
}
The command I'm trying to implement it into
[Command("clear")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Clear(int amount)
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await ((ITextChannel)Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
As akac pointed out the precondition you mentioned [RequireUserPermission(...)] checks if any of the roles assigned to a user gives them the permission to do a specific task. Consider the following example. You create a role called "Moderators" and enable the permission "Manage Messages". You then add the precondition [RequireUserPermission(ChannelPermission.ManageMessages)] to your Clear() method. Your Clear() method will now work for anybody in the "Moderators" role because they have permission to manage messages. It will also allow anybody in any other roles with the same permission to use it.
However, if you later decide you don't want "Moderators" to be able to manage messages and remove the permission from that role, your precondition will then automatically stop anyone in that role from using the Clear command.
If you check the users roles instead of their permissions, the users with the "Moderators" role would still be able to use the Clear command to delete messages even though you've removed the permission to manage messages from that role.
If the only reason you want to check the role instead of using the precondition to check their permissions is because you don't want it to log to the console, then this is probably the wrong approach for you. Instead you should consider sticking with the precondition and look at how you're handling the logging to prevent that message from being logged to the console.
If you would still like to check the user's roles, then here is an example of how you could do that in the Clear() method you provided. You will need to add using System.Linq; to the top of the file and replace "RoleName" in the second if statement with the name of the role you want to check.
public async Task Clear(int amount)
{
// Get the user that executed the command cast it to SocketGuildUser
// so we can access the Roles property
if (Context.User is SocketGuildUser user)
{
// Check if the user has the requried role
if (user.Roles.Any(r => r.Name == "RoleName"))
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await((ITextChannel) Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
else
{
await Context.Channel.SendMessageAsync("Sorry, you don't have permission to do that.");
}
}
}
I encountered this issue a couple of days ago, my solution was to create a custom attribute that inherited from Discord.Commands.PreconditionAttribute
I discovered this as I use dotPeek on my Visual Studio install to decompile and read how the command service actually works, as I wanted to know how their attributes worked as well. As part of the command service's execution of the command it checks all preconditions for each command, and only when each are satisfied does it execute the function.
This class has a function called CheckPermissionsAsync
Here is a sample that should work for you:
public class RequiresRoleAttribute : PreconditionAttribute
{
private string m_role;
public RequiresRoleAttribute (string role)
{
m_role = role;
}
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{
if (context.User is SocketGuildUser sgu)
{
if (sgu.Roles.Select(x => x.Name == m_name).Contains(m_name)
{
return PreconditionResult.FromSuccess();
}
}
return PreconditionResult.FromError($"The user doesn't have the role '{m_name}'");
}
}
And the use:
[Command("clear")]
[RequiresRole("RoleName")]
public async Task Clear(int amount)
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await ((ITextChannel)Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
Here is what you do:
You use the RequireRoles attribute.
It has 4 various ways: All, any, specified only, none
Let's say you want to make a ban command, but only want admin and mods to be able to use it. You would do this:
public class Moderation : BaseCommandModule
{
[Command("ban")]
[RequireRoles(RoleCheckMode.Any, "admin", "mod")]
public async Task BanCommand(CommandContext ctx, DiscordMember name, [RemainingText] string reason)
{
}
}
What are you doing here is basically using a precondition to check if any of the user's roles have the ManageRoles permission, the description is confusing.
As far as I know, Discord.net doesn't log such errors, only puts the default error message into the Result of the command, which is then usually sent to the channel as a response. There clearly has to be some place where your code logs such errors.
c# chat bot : is there any way that we can control choice prompt's RetryPrompt message dynamically? I am using bot framework 4.0.
There's a couple of different ways to do this, depending on how I'm interpreting your question.
The easiest is to just add a separate RetryPrompt. For example, if we want to do this to the Multi-Turn-Prompt sample, we just add the RetryPrompt property:
private static async Task<DialogTurnResult> TransportStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
// Running a prompt here means the next WaterfallStep will be run when the users response is received.
return await stepContext.PromptAsync(nameof(ChoicePrompt),
new PromptOptions
{
Prompt = MessageFactory.Text("Please enter your mode of transport."),
Choices = ChoiceFactory.ToChoices(new List<string> { "Car", "Bus", "Bicycle" }),
RetryPrompt = MessageFactory.Text("That wasn't a valid option. Try again.")
}, cancellationToken);
}
This produces:
The other alternative would be to do something like what #pkr2000 said (although a little different), and use a custom validator to dynamically add the RetryPrompt. Something like:
AddDialog(new ChoicePrompt(nameof(ChoicePrompt), ValidateChoicesAsync));
[...]
private static Task<bool> ValidateChoicesAsync(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)
{
if (!promptContext.Recognized.Succeeded)
{
promptContext.Options.RetryPrompt = MessageFactory.Text($"You said \"{ promptContext.Context.Activity.Text},\" which is invalid. Please try again.");
return Task.FromResult(false);
}
return Task.FromResult(true);
}
This produces:
You can do just about anything you want within the validator. Instead of using MessageFactory.Text(), you can pass in a completely different Activity like an Adaptive Card or something. You could also not set a RetryPrompt, instead changing the Prompt to whatever text/activity you want, return false, and then the user gets re-prompted with the new Prompt. It's really pretty limitless what you can do with a custom validator.
I need to prompt user to make a choice and keep the chosen value in a variable and use it at the end of conversation session.
var dialog = new PromptDialog.PromptChoice<string>(
new string[] {"A new request", "Current Request" },
"Which one would you like?",
"Sorry, that wans't a valid option", 1);
context.Call(dialog, ChoiceReceivedAsync);
context.Wait(this.MessageReceivedAsync);
}
private async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceivedAsync1);
return;
}
The above code displays choices but gives too manyattempts exception. Also I used the below code in MessageReceivedAsync1
var UserChose = await result;
but still result does not store the value.
Remove the context.Wait(this.MessageReceivedAsync); line that you have after the context.Call
context.Call is launching a new dialog (PromptChoice) and so you cannot do both (launch a new dialog and wait in the current dialog)
I started learn the Microsoft Bot Framework recently, so I started make a Chatbot and I guess I making it wrong
I making the chatbot this way:
--> I get the message's user
--> send to LUIS
--> get the intent and the entities
--> select my answer and send it.
it's ok, but get the following situation:
USER: I wanna change my email. --> intent : ChangeInfo entities:
email/value:email
CHATBOT: Tell me your new Email please. --> intent: noIntent
entities: noEntities
USER: email#email.com --> intent: IDon'tKnow entities:
email/value:email#email.com
I take this situation, when the USER send his email, I send to LUIs, but a email dont have a intent, just have a entity, but a email can be used in a lot Different situations, My question is, How My bot know the context of conversation to understand this email is for change email and not send a email, or update this email or another thing.
my code on gitHub here, its a ugly code, i know, but i make this just to understand the bot framework, after I will let this code more beautiful
This should be as simple as using a LuisDialog and a set of Prompts to manage the users flow. Below you will find some quick code I put together to show you how this could be done. You don't need extra steps or adding extra entities, or going to Luis with the email provided by the user.
I would recommend you to read a bit more about LuisDialog and Dialogs in general as the way you are using Luis in your controller I don't think is the way to go.
Here is a good Luis Sample and here a good one around multi-dialogs.
Sample Code
namespace MyNamespace
{
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Internals.Fibers;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using Microsoft.Bot.Connector;
[Serializable]
[LuisModel("YourModelId", "YourSubscriptionKey")]
public class MyLuisDialog : LuisDialog<object>
{
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
string message = "Não entendi, me diga com outras palavras!";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
}
[LuisIntent("ChangeInfo")]
public async Task ChangeInfo(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
// no need to go to luis again..
PromptDialog.Text(context, AfterEmailProvided, "Tell me your new email please?");
}
private async Task AfterEmailProvided(IDialogContext context, IAwaitable<string> result)
{
try
{
var email = await result;
// logic to store your email...
}
catch
{
// here handle your errors in case the user doesn't not provide an email
}
context.Wait(this.MessageReceived);
}
[LuisIntent("PaymentInfo")]
public async Task Payment(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
// logic to retrieve the current payment info..
var email = "test#email.com";
PromptDialog.Confirm(context, AfterEmailConfirmation, $"Is it {email} your current email?");
}
private async Task AfterEmailConfirmation(IDialogContext context, IAwaitable<bool> result)
{
try
{
var response = await result;
// if the way to store the payment email is the same as the one used to store the email when going through the ChangeInfo intent, then you can use the same After... method; otherwise create a new one
PromptDialog.Text(context, AfterEmailProvided, "What's your current email?");
}
catch
{
// here handle your errors in case the user doesn't not provide an email
}
context.Wait(this.MessageReceived);
}
}
}
In my bot flow, I'm using a step variable that I change from the front-end. And another step variable that I change from the bot. This helps me identify which step I am in the conversation. You can do the same to identify what your bot is asking the user.
var data = {step: "asked_email"};
var msg = builder.Message(session).addEntity(data).text("Your message.");
session.send(msg);
If you don't want to send a specific step to LUIS for recognition, you can handle that in the onBeginDialog handler:
intents.onBegin(function (session, args, next) {
if (session.message.step !== "email") {
next();
} else {
//Do something else and not go to LUIS.
session.endDialog();
}
});
You can find the reference to LUIS onBeginDialog here:
https://docs.botframework.com/en-us/node/builder/chat/IntentDialog/#onbegin--ondefault-handlers
Details about message data can be found here:
https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html#entities
I have a subdialog in a bot built using MS bot framework that starts as follows - the standard way:
public async Task StartAsync(IDialogContext context)
{
var msg = "Let's find your flights! Tell me the flight number, city or airline.";
var reply = context.MakeMessage();
reply.Text = msg;
//add quick replies here
await context.PostAsync(reply);
context.Wait(UserInputReceived);
}
This dialog is called using two different ways, depending on whether in the previous screen the user tapped a button that says "Flights" or immediately entered a flight number. Here is the code from the parent dialog:
else if (response.Text == MainOptions[2]) //user tapped a button
{
context.Call(new FlightsDialog(), ChildDialogComplete);
}
else //user entered some text instead of tapping a button
{
await context.Forward(new FlightsDialog(), ChildDialogComplete,
activity, CancellationToken.None);
}
Question: how can I know (from within the FlightsDialog) whether that dialog was called using context.Call() or context.Forward()? This is because in the case of context.Forward(), StartAsync() shouldn't output the prompt asking the user to enter the flight number - they already did this.
The best idea I have is to save a flag in the ConversationData or user data, as below, and access it from the IDialog, but I thought there could be a better way?
public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
{
StateClient client = activity.GetStateClient();
BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>(PropertyName, ValueToSet);
client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
Unfortunately Forward actually calls Call (and then does some other stuff afterwards), so your Dialog wouldn't be able to differentiate.
void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
{
var callRest = ToRest(child.StartAsync);
if (resume != null)
{
var doneRest = ToRest(resume);
this.wait = this.fiber.Call<DialogTask, object, R>(callRest, null, doneRest);
}
else
{
this.wait = this.fiber.Call<DialogTask, object>(callRest, null);
}
}
async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
{
IDialogStack stack = this;
stack.Call(child, resume);
await stack.PollAsync(token);
IPostToBot postToBot = this;
await postToBot.PostAsync(item, token);
}
From https://github.com/Microsoft/BotBuilder/blob/10893730134135dd4af4250277de8e1b980f81c9/CSharp/Library/Dialogs/DialogTask.cs#L196