I have successfully got a simple "What's the weather" bot using Luis up and running on both Skype and Twilio and it's excellent.
I have now been tasked with setting up what I can only think of describing as a "conversation" bot.
I have looked at as many examples as I can find on the interweb but I am unsure how to approach developing it, and I don't know whether I should be using the FormBuilder for my scenario.
Here is a flow chart of part of what I am trying to do...
I have got my form working down to the part where it branches at "Bill Available"...
I cannot work out how to "change direction" based on the answer.
This, as suspected, turned out to be much easier that I thought..
Here is my solution...
This is my controller:
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
// check if activity is of type message
if (activity != null && activity.GetActivityType() == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.BillSharkDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
And here is the "Dialog" with just 2 steps...
[Serializable]
public class BillSharkDialog : IDialog<object>
{
Model.Customer customer = new Model.Customer();
public async Task StartAsync(IDialogContext context)
{
context.Wait(WelcomeMessageAsync);
}
public async Task WelcomeMessageAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
IMessageActivity message = await argument;
await context.PostAsync("We're excited to start helping you save! Let's start by getting your name?");
context.Wait(CaptureCustomerNameAsync);
}
public async Task CaptureCustomerNameAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
IMessageActivity message = await argument;
customer.customerName = message.Text;
await context.PostAsync($"Thanks {message.Text}. Now we need your email address?");
context.Wait(CaptureCustomerEmailAsync);
}
}
You can obviously change the route by checking the incoming message..
Here is an example:
public async Task DoesCustomerHaveBillAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
IMessageActivity message = await argument;
switch (message.Text.ToLower())
{
case "yes":
await context.PostAsync($"Great. Go ahead and take a picture of the first couple of pages and attach them to this conversation.\n\n\nWhen you have finished, please send the message 'finished'.");
context.Wait(CustomerHasBillAsync);
break;
case "no":
await context.PostAsync($"That's OK. Do you happen to have the login information for your provider account?");
context.Wait(CustomerDoesntHaveBillAsync);
break;
default:
await context.PostAsync($"Sorry, I didn't undestand. Please reply with 'yes' or 'no'.");
context.Wait(DoesCustomerHaveBillAsync);
break;
}
}
You could consider using the SetNext method of a FormBuilder field to decide what should be coming next based on another field value.
You can take a look to the ContosoFlowers sample. In the Order form; something similar is being done.
public static IForm<Order> BuildOrderForm()
{
return new FormBuilder<Order>()
.Field(nameof(RecipientFirstName))
.Field(nameof(RecipientLastName))
.Field(nameof(RecipientPhoneNumber))
.Field(nameof(Note))
.Field(new FieldReflector<Order>(nameof(UseSavedSenderInfo))
.SetActive(state => state.AskToUseSavedSenderInfo)
.SetNext((value, state) =>
{
var selection = (UseSaveInfoResponse)value;
if (selection == UseSaveInfoResponse.Edit)
{
state.SenderEmail = null;
state.SenderPhoneNumber = null;
return new NextStep(new[] { nameof(SenderEmail) });
}
else
{
return new NextStep();
}
}))
.Field(new FieldReflector<Order>(nameof(SenderEmail))
.SetActive(state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.SetNext(
(value, state) => (state.UseSavedSenderInfo == UseSaveInfoResponse.Edit)
? new NextStep(new[] { nameof(SenderPhoneNumber) })
: new NextStep()))
.Field(nameof(SenderPhoneNumber), state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.Field(nameof(SaveSenderInfo), state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.Build();
}
}
}
Related
I followed the code examples on https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-7.0#use-a-handler-for-one-requirement
This all appears to work, but in order to use async/await calls I had to make some changes to the example provided by Microsoft and as this is security related I a little unsure and would appreciate some clarification.
Basically the changes I made were
Changed "Task" to "async TASK" on function defination
Changed "return Task.CompletedTask" to just "return;" (1st instance)
Remove the 2nd "return Task.CompletedTask" at the end of the function as as dont think its needed
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SystemRoleRequirement2 requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.Name)) { return; } // Task.CompletedTask;
var Result = (await _idb.QueryAsync<int>(cSQL.Security.SystemRoleAccess2, "SecurityReadOnly", new { UserID = context.User.ReadID(), requirement.SystemRoleIDs }))
.SingleOrDefault();
if (Result > 0) context.Succeed(requirement);
//return Task.CompletedTask;
}
Can anyone confirm that this is the correct way to implement the security handler with await calls.
Given a method
private Task Foo(string input)
{
if (input is null)
{
return Task.Complete;
}
input += " is processed";
return Task.Complete;
}
The equivalent with async would be
private async Task Foo(string input)
{
if (input is null)
{
return;
}
input += " is processed";
return; // not needed as it's the last statement
}
So yes, your modifications are correct.
I've been working on a discord bot for the past few days, and I managed to get one of the functions working: (a command that sets a message it's supposed to dm users when they join). I can't seem to get the bot to send the actual message.
private async Task Join(SocketGuildUser UID)
{
if (UID.IsBot || UID.IsWebhook) return;
Welcometxt= File.ReadAllText([FILE]);
await UID.SendMessageAsync("Your Message Was Sucessfully set!");
}
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
var context = new SocketCommandContext(_client, message);
if (message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos))
{
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess) Console.WriteLine(result.ErrorReason);
}
}
When i check the logs it gives a null reference error with the var message and context, I tried googling the error and changing up the code but to no avail any advice? I believe the error is in one of these two methods but im not 100% positive
I guess you can use UserJoined event to achieve this.
Define event handler for UserJoined
public async Task UserJoined(SocketGuildUser user)
{
await user.SendMessageAsync("Hello");
}
Register it
private readonly DiscordSocketClient _client = new DiscordSocketClient();
private readonly CommandService _commandService = new CommandService();
public async Task MainAsync()
{
....
_client.UserJoined += UserJoined;
....
}
I'm not sure enough if this is related, but check if the Server Member Intents is on
This shouldn't be too difficult, but I'm new to C# and can't seem to fix it. I have a Prompt.Dialog.Choice to confirm a message if the user want the inputs to be sent to a database or not.
I'm stuck in the ChoiceReceivedAsync where I want to make actions weather or not the user answers "yes" or "no", but how do I call these answers in my if-else sentence?
Here's the relevant code:
public enum booleanChoice { Yes, No }
public async Task StartAsync(IDialogContext context)
{
//var message = await result as IMessageActivity;
await context.PostAsync("Do you want you message to be sent?");
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: (IEnumerable<booleanChoice>)Enum.GetValues(typeof(booleanChoice)),
prompt: " ",
retry: "Please try again.",
promptStyle: PromptStyle.Auto
);
}
public async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<booleanChoice> result)
{
booleanChoice response = await result;
//Here's where I'm stuck:
//if (PromptDialog.Choice == "Yes"){//send the inputs to the databse}
//else (PromptDialog.Choice == "No"){//exit or enter the message again}
}
You just have to compare your result (response) to your enum values, like the following:
public async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<booleanChoice> result)
{
booleanChoice response = await result;
if (response.Equals(booleanChoice.Yes))
{
//send the inputs to the databse}
else
{
//exit or enter the message again
}
}
I want my Discord bot to greet members when they join a channel. I have been unable to find an event that fires when this happens. I have tried myClient.UserJoined += MyMethod; and others but they never get fired as I hope. Here is my main code:
public class Program
{
private DiscordSocketClient _client;
private CommandService _commands;
private IServiceProvider _services;
static void Main(string[] args)
=> new Program().RunBotAsync().GetAwaiter().GetResult();
public async Task RunBotAsync()
{
_client = new DiscordSocketClient();
_commands = new CommandService();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.BuildServiceProvider();
string botToken = // removed
_client.Log += Log;
await RegisterCommandsAsync();
await _client.LoginAsync(TokenType.Bot, botToken);
await _client.StartAsync();
await Task.Delay(-1);
}
private Task Log(LogMessage arg)
{
Console.WriteLine(arg);
return Task.CompletedTask;
}
public async Task RegisterCommandsAsync()
{
_client.MessageReceived += HandleCommandAsync;
_client.UserJoined += JoinedAsync; // Something like this to notify bot when someone has joined chat?
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
private Task JoinedAsync(SocketGuildUser arg)
{
throw new NotImplementedException();
}
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
if(message is null || message.Author.IsBot)
{
return;
}
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos))
{
var context = new SocketCommandContext(_client, message);
await _commands.ExecuteAsync(context, argPos);
}
}
}
Thanks, and let me know if I can provide any more information.
Edit: The suggested link implements the UserJoined event, which only seems to trigger when a new member joins the channel. I need something that triggers everytime anyone logs in to the channel, even existing members.
Judging by the edit, I think you may have a slight mis conception of how the channels work.
Users join a guild, after which, they have become part of the guild.
After they join a guild, they are part of it, and the channels they are allowed to see. Hence there is no need to log into channels anymore.
Now what I think you want to achieve is sending a message in a channel / to a user whenever they switch from the offline state to the online state.
For this you could use the UserUpdated event. Where you can check the previous and the current status of a user, and send a message accordingly.
_client.UserUpdated += async (before, after) =>
{
// Check if the user was offline, and now no longer is
if(before.Status == UserStatus.Offline && after.Status != UserStatus.Offline)
{
// Find some channel to send the message to
var channel = e.Server.FindChannels("Hello-World", ChannelType.Text);
// Send the message you wish to send
await channel.SendMessage(after.Name + " has come online!");
}
}
I want to execute a list of tasks, and perform a synchronous action once any of them is completed, but I need to know which of them it was.
See my example, and look out for the comment in the code, that precedes a couple of lines I don't know how to achieve.
public async Task<bool> GreetAsync(string name)
{
if (name == null)
return false;
await InternalGreeter.GreetAsync(name);
return true;
}
public async Task GreetAllAsync()
{
var tasks = UserNames.Select(un => GreetAsync(un)).ToList();
while(tasks.Any())
{
var finished = await Task.WhenAny(tasks);
if(finished.Result)
{
//Here's what I'd like to achieve
var username = finished.Arguments[0];
WriteLine($"User {username} has been greeted.");
}
tasks.Remove(finished);
}
}
Based on this example.
In my real world scenario, I have a list of customers, which I have to walk thru them one by one and update a remote server on their credit status (the remote server doesn't support batch updates). After each of them has been updated, I have to mark in my database, that this customer has been accredited.
You almost never want to actually process a list of tasks one at a time as they complete like that. Instead, just introduce a higher-level operation and rewrite your Task.WhenAny to a Task.WhenAll to wait for those higher-level operations.
public async Task<bool> GreetAsync(string name)
{
if (name == null)
return false;
await InternalGreeter.GreetAsync(name);
return true;
}
private async Task<bool> GreetAndReportGreetedAsync(string name)
{
var result = await GreetAsync(name);
WriteLine($"User {name} has been greeted.");
return result;
}
public async Task GreetAllAsync()
{
await Task.WhenAll(UserNames.Select(un => GreetAsync(un));
}
Why not simply use ContinueWith? Something like this:
public async Task GreetAllAsync(List<string> UserNames)
{
var tasks = UserNames
.Select(un => GreetAsync(un)
.ContinueWith(x => {
Console.WriteLine(un + " has been greeted");
}));
await Task.WhenAll(tasks);
}