I have a simple bot with help of Luis. It's very basic code and I don't know why I get 412 error after I past message to Luis intent. My code look like:
MessageController:
if (activity.Type == ActivityTypes.Message)
{
// Get any saved values
StateClient sc = activity.GetStateClient();
await sc.BotState.GetUserDataAsync(activity.ChannelId,activity.From.Id);
var haveGreeting = userData.GetProperty<bool>("HaveGreeting");
// Create text for a reply message
StringBuilder strReplyMessage = new StringBuilder();
if (haveGreeting == false)
{
strReplyMessage.Append($"Hi, how are you today?");
userData.SetProperty("HaveGreeting", true);
}
else
{
await Conversation.SendAsync(activity, () => new MeBotLuisDialog());
}
// Save BotUserData
var botaData = await sc.BotState.SetUserDataAsync(activity.ChannelId,
activity.From.Id, userData);
// Create a reply message
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity replyMessage = activity.CreateReply(strReplyMessage.ToString());
await connector.Conversations.ReplyToActivityAsync(replyMessage);
}
Luis intent:
[LuisIntent("HowAreYou")]
public async Task HowAreYou(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
await context.PostAsync("Great! Thank for asking");
context.Wait(MessageReceived);
}
Please help!
Try adding the following code in your Global.asax.cs file
var builder = new ContainerBuilder();
builder
.Register(c => new CachingBotDataStore(c.Resolve<ConnectorStore>(), CachingBotDataStoreConsistencyPolicy.LastWriteWins))
.As<IBotDataStore<BotData>>()
.AsSelf()
.InstancePerLifetimeScope();
builder.Update(Conversation.Container);
And please, review the relevant technical FAQs around this issue:
What is an ETag? How does it relate to bot data bag storage?
What causes “precondition failed” (412) or “conflict” (409) HTTP errors?
How can I fix “precondition failed” (412) or “conflict” (409) HTTP errors?
Related
I created a bot with a command, that allows the user to configure some sort of 'feed' to their channel.
This feed is supposed to send a message, save guild, channel and message id. And in a stand-alone update cycle, try to update the message with new information.
This all works fairly well, as long as it is within the same session.
Say the bot losses it's connection due to a discord outage, and re-connects x amount of time later, the bot no longer seems to be able to find, and thus update the message anymore.
In particular, it seems to be unable to retrieve the message by id
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
It's worth to note that I make use of _settings which is persisted in json format, and is loaded again upon bot reboot.
I also confirmed that the message still exists in the server at the channel, with the same message id. And that the bot has permissions to view the message history of the channel.
Thus my question, how come the GetMessageAsync is unable to retrieve a previously posted message after reconnecting?
Initialy invoked command
public async Task BindPlaytimeFeedAsync(ICommandContext context)
{
var builder = await _scumService.GetTop25PlaytimeByDate(new DateTime(), DateTime.Now);
var message = await context.Channel.SendMessageAsync(null, false, builder.Build());
_settings.PlaytimeFeed = new MessageInfo()
{
GuildId = context.Guild.Id,
ChannelId = context.Channel.Id,
MessageId = message.Id,
};
var ptFeedMessage = await context.Channel.SendMessageAsync("Playtime feed is now bound to this channel (this message self-destructs in 5 seconds)");
await Task.Delay(5000);
await ptFeedMessage.DeleteAsync();
}
The refresh interval of the feed is defined alongside the bot itself using a timer as seen below.
...
_client = new DiscordSocketClient(
new DiscordSocketConfig
{
LogLevel = LogSeverity.Verbose,
AlwaysDownloadUsers = true, // Start the cache off with updated information.
MessageCacheSize = 1000
}
);
_service = ConfigureServices();
_feedInterval = new Timer(async (e) =>
{
Console.WriteLine("doing feed stuff");
await HandleFeedsAsync();
}, null, 15000, 300000);
CmdHandler = new CommandHandler(_service, state);
...
private async Task HandleFeedsAsync()
{
var botSettings = _service.GetService<ISettings>() as BotSettings;
await HandleKdFeedAsync(botSettings.KdFeed);
await HandlePlaytimeFeedAsync(botSettings.PlaytimeFeed);
await HandleWeeklyPlaytimeFeed(botSettings.WeeklyPlaytimeFeed);
await HandleAdminFeed(botSettings);
}
And ultimately the message is overwritten using the below snippet.
private async Task HandlePlaytimeFeedAsync(MessageInfo playtimeFeed)
{
if (playtimeFeed == null)
return;
var scumService = _service.GetService<ScumService>();
var guild = _client.GetGuild(playtimeFeed.GuildId);
var channel = guild.GetTextChannel(playtimeFeed.ChannelId);
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
if (message == null)
return;
var builder = await scumService.GetTop25PlaytimeByDate(new DateTime(), DateTime.Now);
await message.ModifyAsync(prop =>
{
prop.Embed = builder.Build();
});
}
var message = await channel.GetMessageAsync(playtimeFeed.MessageId) as SocketUserMessage;
The GetMessageAsync method attempts to retrieve a message from cache as a SocketUserMessage, if however the message is not found in cache, a rest request is performed which would return a RestUserMessge. By performing a soft cast on the result of GetMessageAsync, you can get null if/when a RestUserMessage is returned.
When the possibility exists that the message you are dealing with can be either a Socket entity or Rest entity, simply use the interface to interact with it -- IUserMessage.
In a dialog within my bot, I store a flag value in the ConversationData like so:
context.ConversationData.SetValue("SomeFlag", true);
Later, I need to check that flag in my MessagesController, before the message is dispatched to a dialog. As per this previous question I tried retrieving the ConversationData in via the StateClient like this:
public async Task<HttpResponseMessage> Post([FromBody] Activity incomingMessage)
{
StateClient stateClient = incomingMessage.GetStateClient();
BotData userData = await stateClient.BotState.GetConversationDataAsync(message.ChannelId, message.Conversation.Id);
bool finishedQuote = userData.GetProperty<bool>("SomeFlag");
//...
// do conditional logic, then dispatch to a dialog as normal
}
However, at runtime, the userData variable holds a BotData object where userData.Data is null, and I'm unable to retrieve any stored flags via GetProperty. I don't see anything in the relevant documentation that helps shed light on this issue - what might I be doing wrong here? Is there something I'm misunderstanding?
The following should work for what you need:
if (activity.Type == ActivityTypes.Message)
{
var message = activity as IMessageActivity;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = Address.FromActivity(message);
ConversationReference r = new ConversationReference();
var userData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
//you can get/set UserData, ConversationData, or PrivateConversationData like below
//set state data
userData.SetProperty("key 1", "value1");
userData.SetProperty("key 2", "value2");
//get state data
userData.GetProperty<string>("key 1");
userData.GetProperty<string>("key 2");
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None);
await botDataStore.FlushAsync(key, CancellationToken.None);
}
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
Initialize BotState object with StateClient as below. Try the below code
public static T GetStateData<T>(Activity activity, string key)
{
BotState botState = new BotState(activity.GetStateClient());
BotData botData = botState.GetConversationData(activity.ChannelId, activity.Conversation.Id);
return botData.GetProperty<T>(key);
}
I want my bot to display an introductory message when a user begins a new conversation. I've seen this working with bots in Skype where the bot sends a message before the user types anything.
I have got this working using the Bot Framework Channel Emulator with this code in the MessagesController class:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
await this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate)
{
var reply = message.CreateReply("Hello World!");
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
This displays 'Hello World!' at the beginning of a new conversation. No input required. However on Skype this introductory message does not appear. What I am misunderstanding here? I know it is possible.
Skype is throwing different ActivityTypes given the situation:
You will get a contactRelationUpdate after adding the bot in your contacts. Then we you start talking to the bot, there is no special Activity
When you start a conversation group with the bot included, you will get conversationUpdate
So if you want to welcome your user, you should add the contactRelationUpdate activity type in your test, like:
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.ConversationUpdate || message.Type == ActivityTypes.ContactRelationUpdate)
{
var reply = message.CreateReply("Hello World!");
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.SendToConversationAsync(reply);
}
}
Extract of the content of the message you receive when adding the bot:
Here From is my user and Recipient is bot. You can see that the Action value is add
I am trying to give intro for my BOT . when the user is added to their conversation/contact list. I have tried by handling ActivityTypes.ConversationUpdate message type. but it is not working in skype. is there any common way for this that should work in all channels.
this is the code i used:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Type == ActivityTypes.Message)
{
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// return our reply to the user
Activity reply = activity.CreateReply($"You sent {activity.Text} which was {length} characters");
await connector.Conversations.ReplyToActivityAsync(reply);
}
else if(activity.Type == ActivityTypes.ConversationUpdate)
{
StateClient stateClient = activity.GetStateClient();
BotData userData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
if (!userData.GetProperty<bool>("SentGreeting"))
{
Activity reply = activity.CreateReply($"Hi " + activity.From.Name + ", I'm the Microsoft Bot");
userData.SetProperty<bool>("SentGreeting", true);
await stateClient.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
await connector.Conversations.ReplyToActivityAsync(reply);
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
works here
thanks in advance.
The Skype channel doesn't send the ConversationUpdate message. The Bot Framework Emulator and other channels like WebChat do. There isn't a way to send a message to the user before receiving a message from the user in the Skype channel.
I am trying to communicate with echo service on a server using web sockets in my bot. I am using WebSocketSharp assembly to create web socket connection. I want to echo back whatever user types in the bot but, it never fires "ws.OnMessage" event and I get back no response. I tested the connection on the console application and every thing works fine there. Please suggest what I am doing wrong here.
Following is my MessageController
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new HumanCollaboratorDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Following is my HumanCollaboratorDialog class
[Serializable]
public class HumanCollaboratorDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
using (var ws = new WebSocket("ws://Some IP addrress:8080/human-collaborator/echo"))
{
ws.OnMessage += async (sender, e) =>
{
try
{
await context.PostAsync(e.Data);
}
catch (Exception ex)
{
await context.PostAsync($"Exception: {ex.Message}");
}
};
ws.ConnectAsync();
var msg = message.Text;
ws.Send(msg);
}
context.Wait(this.MessageReceivedAsync);
}
}
The "MessageReceivedAsync" is not the correct place to create a websocket. WebSockets in the bot framework are used for receiving messages in a Direct Line connection scenario. A StreamUrl obtained from a call to StartConversationAsync is used to create the web socket:
var token = await new DirectLineClient(dlSecret).Tokens.GenerateTokenForNewConversationAsync();
// Use token to create conversation
var directLineClient = new DirectLineClient(tokenResponse.Token);
var conversation = await directLineClient.Conversations.StartConversationAsync();
using (var webSocketClient = new WebSocket(conversation.StreamUrl))
{
webSocketClient.OnMessage += WebSocketClient_OnMessage;
webSocketClient.Connect();
etc.
Please see here: https://github.com/Microsoft/BotBuilder-Samples/blob/master/CSharp/core-DirectLineWebSockets/DirectLineClient/Program.cs