BotFramework - Repeat last message using c# - c#

I'm building a bot using Microsoft Bot Framework using C#. I'm controlling my flow using Dialogs, and using quick replies as buttons, they dissapear when a new message is sent.
I also created a global command to help the user. When the user types "help" the bot will give some information about what it can do.
The problem is that if the user asks for help the quick replies will dissapear and the user might not know how to continue the dialog. I was wondering if there is any way to repeat the last sent message after the help is sent (the help is not a Dialog, the Scorable sends the message).
Thanks

Nothing stops you from keeping a record of the messages the user has sent. If you build your own UI you could do it in a common way, so pressing the up arrow for example could cycle the user through their commands.
activity.Text has the text command, just add that to a list of your own, use the bot state or whatever mechanism you want for that as you will need a storage mechanism.
There are other ways as well, don't forget that the state of each dialog is maintained so once you invoke it again it will continue from where it left off.

I ended following #Andrei Dragotoniu idea, but I'd like to provide some implementation details.
Short story
Save the last (or more if you want) sent message in some storage (you can use the conversation id to identify it). Resend it when needed.
Long story
To save the last message the simplest way I found was to create a IBotToUser implementation and add it to the IBotToUser chain.
public class StoreLastActivity : IBotToUser
{
private readonly IBotToUser inner;
public StoreLastActivity(IBotToUser inner)
{
SetField.NotNull(out this.inner, nameof(inner), inner);
}
public IMessageActivity MakeMessage()
{
return this.inner.MakeMessage();
}
public async Task PostAsync(IMessageActivity message, CancellationToken cancellationToken = default(CancellationToken))
{
// Save the message here!!!
await this.inner.PostAsync(message, cancellationToken);
}
}
In Global.asax.cs or in some Module class register the class and the new chain. Update the Conversation container.
Conversation.UpdateContainer(builder =>
{
// Registers the class the saves the last send message for each conversation.
builder
.RegisterKeyedType<StoreLastActivity, IBotToUser>()
.InstancePerLifetimeScope();
// Adds the class on the IBotToUser chain.
builder
.RegisterAdapterChain<IBotToUser>
(
typeof(AlwaysSendDirect_BotToUser),
typeof(AutoInputHint_BotToUser),
typeof(MapToChannelData_BotToUser),
typeof(StoreLastActivity),
typeof(LogBotToUser)
)
.InstancePerLifetimeScope();
}
This means that every message sent to the user will pass by StoreLastActivity.PostAsync, so you can save it anywhere and use message.Conversation.Id as an id. I saved the entire IMessageActivity as it not always just text, it may contains cards, buttons etc...
Now just retrieve the IMessageActivity and send it whenever you need.
If anyone got a simpler solution I'd like to hear it.
Thank you.

Related

How can you get the exception from a MassTransit courier routing slip activity?

I have a MassTransit routing slip with a couple of activities that works perfectly (I love MT) but I now want to add additional information to the failed events (i.e. error name and description).
At the moment I catch my custom exceptions in the activity and send back a faulted response, which kicks in all of the compensating activites as planned, but I can't seem to get the exception details in the subscriber (to add to the event I then send back to the saga).
My activity looks like this:
public async Task<ExecutionResult> Execute(ExecuteContext<ICreateLink> context)
{
var messageCommand = context.Arguments;
var command = new CreateLink(
messageCommand.LinkId,
messageCommand.GroupId);
try
{
await _commandDispatcher.ExecuteAsync(command).ConfigureAwait(false);
return context.Completed();
}
catch (Exception ex)
{
return context.Faulted(ex);
}
}
Then when building the routing slip I have:
builder.AddSubscription(
context.SourceAddress,
RoutingSlipEvents.ActivityFaulted,
RoutingSlipEventContents.All,
nameof(CreateLinkActivity),
x => x.Send<ICreateLinkFailed>(new CreateLinkFailed
{
LinkId = context.Message.LinkId,
LinkName = context.Message.Name
}));
I thought I would be able to access the exception information from the context but alas I can't seem to find it and this is the last piece of the puzzle for me.
I'm beginning to think that I'm not thinking about this right. Ultimately I want to pass the error type back to the routing slip and then to it's calling saga.
With your subscription, your event type can include properties with the same type/name as the built-in event that is published. Those properties will be added by MT to the event automatically (no need to map them in the Send call).
https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit/Courier/Contracts/RoutingSlipActivityFaulted.cs#L19
So in your case, the ExceptionInfo property could be copied into your event interface - and that data will be presented when the event is consumed.
Under the hood, MassTransit merges the JSON of the built-in event and your own event assignment into a combined JSON document.

C# Discord.NET: Why this ban module prevents anybody from interacting with my bot?

I am creating a Discord bot using the Discord.NET API. I have been implementing commands and modules to my bot for a while now, and I am attempting to add a ban command to my bot. I made the command so you have to have a role named "Bot Admin". Here is the code that I am using that seems to be causing the problem:
public class Ban : ModuleBase<SocketCommandContext>
{
[Command("ban")]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task banUser([Remainder]SocketGuildUser usertobehammered, string banre)
{
var rUser = Context.User as SocketGuildUser;
var role = Context.Guild.Roles.FirstOrDefault(x => x.Name == "Bot Admin");
if (rUser.Roles.Contains(role))
{
await ReplyAsync($"User {usertobehammered.Mention} has been banned.");
await Context.Guild.AddBanAsync(usertobehammered, 0, banre);
}
else
{
await ReplyAsync(":no_entry_sign: You need the Bot Admin role to do that!");
}
}
}
I don't know why, but with this here my bot will come online when I run it like normal, but attempting to run any commands will do nothing. Deleting the class allows users to use commands again.
Anybody know what the problem is here?
EDIT: Still don't know the cause of this, but I do have another command that uses the variables "rUser" and "role" which are also used here, but I don't believe that is the problem.
Figured it out myself, it was actually really simple. I just had to swap the SocketGuildUser and the string around so it would look like this:
[Command("ban")]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task banUser(string banre, [Remainder]SocketGuildUser usertobehammered)
Rather than the original which looks like this:
[Command("ban")]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task banUser([Remainder]SocketGuildUser usertobehammered, string banre)
I think the problem was that I believe Remainder puts anything after the first word into the next argument, which would make it so a SocketGuildUser is being put into a string, which without using something like "SocketGuildUser.mention" apparently makes the bot not respond to commands. People, please don't make this silly mistake I made. :$

QnAmaker change default answer when QnAmaker does not have any response

How to change default answer when QnAmaker does not have any response to display a prompt dialog.
For example:
User1: Hello
Bot : Hello there !!
User1: Do you sell cars?
Bot : No good match found !
Instead of No good match found, the bot should propose a list of services available.
Also, anytime a match is not found, the bot should again propose a list of services available.
How can this be achieve?
QnAMakerAttribute has a defaultMessage parameter allowing you to customize the 'no match' response text: https://github.com/Microsoft/BotBuilder-CognitiveServices/blob/7866f5a1bc970bdd0be341b9ba24c74067edf57c/CSharp/Library/QnAMaker/QnAMaker/QnAMakerService/QnAMakerAttribute.cs
public class BasicQnAMakerDialog : QnAMakerDialog
{
public BasicQnAMakerDialog() : base(
new QnAMakerService(
new QnAMakerAttribute(
Utils.GetAppSetting("QnASubscriptionKey"),
Utils.GetAppSetting("QnAKnowledgebaseId"),
"**List of services: one, two three**",
0.30,
8))) {}
}
There is also a pull request waiting to be merged that will allow overriding sending the default message: https://github.com/Microsoft/BotBuilder-CognitiveServices/pull/87 Until then, it seems your only option is to duplicate the QnAMakerDialog in your own code base: QnAMakerDialog source
Because there's a few different ways to use the QnA maker alongside the botframework there's quite a few differing suggestions for the issue you have at the moment but not as many proper guidelines from Microsoft around it at this time. (At least not that I've found)
I came across this issue listed against the QnA makers git repository: found here. There are a few different suggestions, so I'll list them below from least to most effort.
Option 1: [Assumes you're creating a base dialog class that connects to the QnA maker]
public BasicQnAMakerDialog() : base(new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnASubscriptionKey"], ConfigurationManager.AppSettings["QnAKnowledgebaseId"], "No good match in FAQ")))
Option 2: [Simply look out for the specific string that's returned by default and 'override' it]
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
if (reply.Text.equals("Not Match Found!))
reply.Text = "No good match in FAQ";
await context.PostAsync(reply);
}
There were instances back when QnAMaker was still in it's preview that option 1 wouldn't work as expected. Option 2 is not as neat in my opinion but it's a decent work around.
As Eric has said in his answer, there is an active pull request awaiting a merge against this issue in their git repo. So eventually this will be an easier thing to deal with. Until then hopefully the above two options will help.

How to structure communication between TcpListener and TcpClient after initial socket connection is made

So this is maybe pretty simple for network programming dudes but I'm a little confused about this. Let me try to ask given an example of usage.
Say I were to create a chat server with clients being able to join with a username. No accounts or passwords. Just username.
I have a working Echo Server example running that uses async calls so all that is good. Now I need to define some message handling between the client and the server.
Lets say the client now connects and it wants to get the list of connected users. How would I go about doing that?
My thought is that I create a function called BeginGetConnectedUsers and that will send message to the server. The server will then reply with a list of users but because I'm using async calls I'll now have my accept code look at the message and figure out that it is a reply from a BeginGetConnectedUsers so it will send the data to a EndGetConnectedUsers.
But I have no idea if this is a good way to do something like this?
But also with this design I'll have to pair every BeginGet function with an EndGet function. This could be made more readable with the async await style but though but that may not be preferable either.
I have no experience with how to structure the following communication between client and server when they have to start exchanging all the interesting data stuff...
And advice? Places to look? All my google searches that include the work TCPLISTENER will all show me examples of Echo Servers and I already know how to do that.
There are many posible implementations here.
I woudl implement an strategy pattern or a controller pattern. Other options are state machines (deterministic automatas) or even a simple and big switch case.
So basically you have only one function to receive the messages from the wire.
All the messages implements the same interface may be
IMessage<T>
{
string Type {get;set;}
T Data {get;set;}
}
So when you get the message you use the Type property to route the T Data to the actual method you want.
In a simple implementation using a controller, you anotate the controller methods with an attribute indicating the message type you want to manage:
class Controller
{
[Messagetype("GetConnectedUsersResponse")]
Response GetConnectedUsers(IEnumerable<User> users)
{
//...
}
[Messagetype("AnothermessageType")]
Response OtherStuffToDo(....)
{
//...
}
}
When you receives the message, by using some simple reflection you dynamically call to the method wich has the attibute with the matching message type attribute.
If you dont like reflection another option (among a lot of docens else) is to use an strategy patter
So you can register some message Handlers by key in your IoC container for example.
All hadlers implement a function lets say
interface MessageHandler<T>
{
Response Handle(T Data);
}
When you receive the message you just resolve the handler using your favourite IoC container (resolving by name is lately considered as an atipattern, so take it with a pinch of salt)
var handler = container.resolve(message.Type);
var response = handler.handle(message.Data);
In both implementations you should define how you respond (if you do) and adjust the "Response" return type (May be you just dont have response so it is void)

Botframework how to save selction

Due to lack of tutorials and information i am unable to find how i can save the information in bots. Lets say i ask user to make a selection like this:
public enum City
{
Cleveland, Columbus, Kentucky, Mason, Akron
};
[Serializable]
public class SandwichOrder
{
[Prompt("Please select what {&} you are in? {||}")]
public City? City;
public static IForm<SandwichOrder> BuildForm()
{
return new FormBuilder<SandwichOrder>()
.Message("Welcome to the my bot!")
.Build();
}
};
I just want to ask for city once how can i do that? How can i preserve the value of user selection and only call this method if it is first user interaction.
Controller class:
internal static IDialog<SandwichOrder> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(SandwichOrder.BuildForm));
}
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity != null)
{
// one of these will have an interface and process it
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
await Conversation.SendAsync(activity, MakeRootDialog);
break;
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
The SDK includes several ways of persisting data relative to a user or conversation:
userData stores information globally for the user across all conversations.
conversationData stores information globally for a single conversation. This data is visible to everyone within the conversation so care should be used to what’s stored there. It’s disabled by default and needs to be enabled using the bots persistConversationData setting.
privateConversationData stores information globally for a single conversation but its private data for the current user. This data spans all dialogs so it’s useful for storing temporary state that you want cleaned up when the conversation ends.
dialogData persists information for a single dialog instance. This is essential for storing temporary information in between the steps of a waterfall.
Bots built using Bot Builder are designed to be stateless so that they can easily be scaled to run across multiple compute nodes. Because of that you should generally avoid the temptation to save state using a global variable or function closure. Doing so will create issues when you want to scale out your bot. Instead leverage the data bags above to persist temporary and permanent state.
More info here:
https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#adding-dialogs-and-memory

Categories