DirectLine API never returns a conversation - c#

So I'm using Microsofts Bot framework and their DirectLine api to talk to it. I do this beacuse I need to send a notification to the bot. The class below is called by my endpoint that I have in my backend. So when I call my notify endpoint, this class is invoked and is supposed to start a conversation with the bot to trigger certain events in it. The problem is that it doesn't seem to work as expected. When I run the code and make a request to my endpoint, it get's stuck at var conversation = await client.Conversations.StartConversationAsync();
the await keyword stops the execution until it is finished, problem is that it never finishes. BUT I can see in the debug window that the request is sent with a 201 created statuscode, so it should finish, but it never does. Not sure what to do here.
private static async Task StartBotConversation()
{
string directLineSecret = "SECRECT";
string fromUser = "DirectLineSampleClientUser";
DirectLineClient client = new DirectLineClient(directLineSecret);
Debug.WriteLine("Before starting con ");
var conversation = await client.Conversations.StartConversationAsync();
Debug.WriteLine("After starting con");
Activity userMessage = new Activity
{
From = new ChannelAccount(fromUser),
Text = "ERROR1337",
Type = ActivityTypes.Trigger
};
Debug.WriteLine("Before posting activity");
await client.Conversations.PostActivityAsync(conversation.ConversationId, userMessage);
Debug.WriteLine("After posting activity");
}

Do this : BotConversation = await Client.Conversations
.StartConversationAsync().ConfigureAwait(false);
It worked for me, I hope it helps you.

Related

Microsoft Teams bot sending proactive messages to each member of a team

I am building a bot using the Microsoft C# bot framework v4. When the bot is first added to a team, I want it to proactively 1:1 message each member of the team, introducing itself. I know it should be possible to start a chat with a user, even if the user has not previously interacted with the bot. This process is described in the documentation:
When using proactive messaging to send a welcome message to a user you must keep in mind that for most people receiving the message they will have no context for why they are receiving it. This is also the first time they will have interacted with your app; it is your opportunity to create a good first impression. The best welcome messages will include:
Why are they receiving this message. It should be very clear to the user why they are receiving the message. If your bot was installed in a channel and you sent a welcome message to all users, let them know what channel it was installed in and potentially who installed it.
To me this indicates that I should be able to initiate a chat message with each member of the channel, but I cannot get the bot to message anyone in the channel other than me. In the TeamsConversationBot sample provided by Microsoft, there is a MessageAllMembers() method that seems to be what I am looking for, however when I call it in my code, and add the bot to a team in Teams, the bot only messages me.
Here is the MessageAllMembers() code I am using:
private async Task MessageAllMembersAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var Id = turnContext.Activity.TeamsGetChannelId();
var serviceUrl = turnContext.Activity.ServiceUrl;
var credentials = new MicrosoftAppCredentials(_configuration["MicrosoftAppId"], _configuration["MicrosoftAppPassword"]);
ConversationReference conversationReference = null;
var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);
foreach (var teamMember in members)
{
if (teamMember.Id != turnContext.Activity.Recipient.Id) {
var proactiveMessage = MessageFactory.Text($"Hello {teamMember.GivenName} {teamMember.Surname}. I'm a Teams conversation bot.");
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new ChannelAccount[] { teamMember },
TenantId = turnContext.Activity.Conversation.TenantId,
};
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
Id,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
_configuration["MicrosoftAppId"],
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
}
}
await turnContext.SendActivityAsync(MessageFactory.Text("All messages have been sent."), cancellationToken);
}
It is not throwing any exceptions, it's just not doing what I expect.

How to send a 'conversationUpdate' to Microsoft Teams from bot manually?

I have a bot written with the help of bot framework v4. The bot is integrated with Microsoft Teams. I want to send a welcome message to the user when the user installed the bot and joins the 1:1 conversation. In Teams the conversationUpdate is fired exactly once (this is when the suer joins the 1:1 conversation) and then never again for that user. My idea was to write a function that is triggered by a chat message to send the updateConversation activity manually to debug the welcome message.
I failed so far and got a
BadArgument: Unknown activity type exception.
I have tried using the Microsoft.Bot.Builder.Teams nuget using the ConnectorClient to send the conversationUpdate activity to the conversation.
Also I set up a console application and tried using the v3/directline/conversations/{conversationId}/activities and got a Forbidden error.
private async Task SendConversationUpdateToTeamsAsync(ITurnContext turnContext, CancellationToken cToken = default)
{
var connectorClient = turnContext.TurnState.Get<IConnectorClient>();
var conversationUpdateMessage = new Activity
{
Type = ActivityTypes.ConversationUpdate,
Id = turnContext.Activity.Id,
ServiceUrl = turnContext.Activity.ServiceUrl,
From = turnContext.Activity.From,
Recipient = turnContext.Activity.Recipient,
Conversation = turnContext.Activity.Conversation,
ChannelData = turnContext.Activity.ChannelData,
ChannelId = turnContext.Activity.ChannelId,
Timestamp = turnContext.Activity.Timestamp,
MembersAdded = new List<ChannelAccount>
{
turnContext.Activity.From,
turnContext.Activity.Recipient
},
};
var result = await connectorClient.Conversations.SendToConversationAsync(conversationUpdateMessage, cToken);
}
I expect that sending a conversationUpdate manually to debug the behavior in Teams works. Creating new users in the office portal and installing the bot for them to debug the conversationUpdate behavior is no option for me, because it is to time consuming. If there is another workaround to trigger the conversationUpdate in Teams please let me know.
I'm not sure of a way to force a ConversationUpdate to be sent in the way you're attempting to. Instead, I'd just throw something like this in OnMessageAsync():
if (turnContext.Activity.Text == "fakeConversationUpdate")
{
var fakeTurnContext = new TurnContext(turnContext.Adapter, MessageFactory.Text(string.Empty));
fakeTurnContext.Activity.AsConversationUpdateActivity();
fakeTurnContext.Activity.Type = ActivityTypes.ConversationUpdate;
fakeTurnContext.Activity.MembersAdded = new List<ChannelAccount>()
{
new ChannelAccount()
{
Id = "fakeUserId",
Name = "fakeUserName"
}
};
await OnConversationUpdateActivityAsync(new DelegatingTurnContext<IConversationUpdateActivity>(fakeTurnContext), cancellationToken);
}
Then to debug, you just write "fakeConversationUpdate" (which you can change/customize) to the bot in chat and it will send your fakeTurnContext (which you can change/customize) through OnConversationUpdateActivityAsync()

Unable to update previous messages after reconnection

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.

Elasticsearch: Waiting for Long Running requests to complete

What is the best approach for knowing when a long running Elasticsearch request is complete?
Today I have a process that periodically purges ~100K documents from an AWS hosted ES that contains a total of ~60M documents.
var settings = new ConnectionSettings(new Uri("https://mycompany.es.aws.com"));
settings.RequestTimeout(TimeSpan.FromMinutes(3)); // not sure this helps
var client = new ElasticClient(settings);
var request = new DeleteByQueryRequest("MyIndex") { ... };
// this call will return an IsValid = true, httpstatus = 504 after ~60s,
var response = await client.DeleteByQueryAsync(request);
Even with timeout set to 3 minutes, the call always returns in ~60s with an empty response and a 504 status code. Though through Kibana, I can see that the delete action continues (and properly completes) over the next several minutes.
Is there a better way to request and monitor (wait for completion) a long running ES request?
UPDATE
Based on Simon Lang's response I updated my code to make use of ES Tasks. The final solution looks something like this...
var settings = new ConnectionSettings(new Uri("https://mycompany.es.aws.com"));
settings.RequestTimeout(TimeSpan.FromMinutes(3)); // not sure this helps
var client = new ElasticClient(settings);
var request = new DeleteByQueryRequest("MyIndex")
{
Query = ...,
WaitForCompletion = false
};
var response = await client.DeleteByQueryAsync(request);
if (response.IsValid)
{
var taskCompleted = false;
while (!taskCompleted)
{
var taskResponse = await client.GetTaskAsync(response.Task);
taskCompleted = taskResponse.Completed;
if (!taskCompleted)
{
await Task.Delay(5000);
}
}
}
I agree with #LeBigCat that the timeout comes from AWS and it is not a NEST problem.
But to address your question:
The _delete_by_query request supports the wait_for_completion parameter. If you set it to false, the request returns immediately with a task id. You then can request the task status by the task api.
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
This isnot a nest - elastic problem, the default timeout in nest query is 0 (no timeout).
You got timeout from amazon server (60s default)
https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/ts-elb-error-message.html
This link explain everything you need to know :)
Regarding #Simon Lang's answer, this is also the case with the _update_by_query api. For those unfamiliar with the _tasks api, you can query for your task in Kibana. The string returned by the update or delete by query will be of the form:
{
"tasks" : "nodeId:taskId"
}
and you can view the status of the task using this command in Kibana:
GET _tasks/nodeId:taskId

Bot Framework Sending Unnecessary Error Messages

I create a bot, called picturesaver, using Microsoft's Bot Framework, I added a GroupMe channel, and I have it hosted in Azure. The bot works perfectly, saving pictures to Google Drive.
However, the bot gives an error saying "Service Error:POST to picturesaver timed out after 15s" Is it possible to extend the timeout time? Or even stop the bot from posting anything at all. Could this be an Azure issue or is it a GroupMe issue?
If your bot performs an operation that takes longer than 15 seconds to process a message, you can process the message on another thread, and acknowledge the call right away. Something like:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
if ([determine if this will take > 15s])
{
// process the message asyncronously
Task.Factory.StartNew(async () => await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()));
}
else
{
//process the message normally
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
return Request.CreateResponse(HttpStatusCode.OK); //ack the call
}
This will avoid the 15 second timeout between connector and bot.
Edit: the above will not scale, and is just using a Task.Factory. Please refer to https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-long-operations-guidance for the recommended guidance on processing long operations from a bot.
The Bot Connector service has a 15s timeout so you need to make sure any async API calls are handled in that timeframe, or make sure your bot responds with some kind of message if it's waiting for some other operation to complete. Currently the 15s timeout cannot be modified.
The solution to process the message on another thread, and acknowledge the call right away is good only for a bot on an App Service.
But as for a Functions Bot doing so will finish the Azure Function if I immediately return from this method.
I tried it. The Azure Function stops running, and the real response to the chat never comes. So it's not a solution at all for the Function Bots.
I ended up with this code for a Functions Bot, which resolves this problem.
Using Azure Queues
public static class Functions
{
[FunctionName("messages")]
[return: Queue("somequeue")]
public static async Task<MessagePayload> Messages([HttpTrigger
(WebHookType = "genericJson")]HttpRequestMessage req) =>
// return from this Azure Function immediately to avoid timeout warning message
// in the chat.
// just put the request into "somequeue".
// We can't pass the whole request via the Queue, so pass only what we need for
// the message to be processed by Bot Framework
new MessagePayload
{
RequestUri = req.RequestUri,
Content = await req.Content.ReadAsStringAsync(),
AuthScheme = req.Headers.Authorization.Scheme,
AuthParameter = req.Headers.Authorization.Parameter
};
// Do the actual message processing in another Azure Function, which is
// triggered by a message enqueued in the Azure Queue "somequeue"
[FunctionName("processTheMessage")]
public static async Task ProcessTheMessage([QueueTrigger("somequeue")]
MessagePayload payload, TraceWriter logger)
{
// we don't want the queue to process this message 5 times if it fails,
// so we won't throw any exceptions here at all, but we'll handle them properly.
try
{
// recreate the request
var request = new HttpRequestMessage
{
Content = new StringContent(payload.Content),
RequestUri = payload.RequestUri
};
request.Headers.Authorization = new
AuthenticationHeaderValue(payload.AuthScheme, payload.AuthParameter);
// initialize dependency injection container, services, etc.
var initializer = new SomeInitializer(logger);
initializer.Initialize();
// handle the request in a usual way and reply back to the chat
await initializer.HandleRequestAsync(request);
}
catch (Exception ex)
{
try
{
// TODO: handle the exception
}
catch (Exception anotherException)
{
// swallow any exceptions in the exceptions handler?
}
}
}
}
[Serializable]
public class MessagePayload
{
public string Content { get; set; }
public string AuthParameter { get; set; }
public string AuthScheme { get; set; }
public Uri RequestUri { get; set; }
}
(Be sure to use different Azure Queues for local development with Bot Framework emulator and for a cloud-deployed Function App. Otherwise, the messages sent to your bot from real customers may be processed locally while you are debugging on your machine)
Using an HTTP request
Of course, the same can be done without using an Azure Queue with a direct call to another Azure Function's public URL - https://<my-bot>.azurewebsites.net/api/processTheMessage?code=<function-secret>. This call has to be done on another thread, without waiting for the result in the messages function.
[FunctionName("messages")]
public static async Task Run([HttpTrigger(WebHookType = "genericJson")]
HttpRequestMessage req)
{
// return from this Azure Function immediately to avoid timeout warning message
// in the chat.
using (var client = new HttpClient())
{
string secret = ConfigurationManager.AppSettings["processMessageHttp_secret"];
// change the RequestUri of the request to processMessageHttp Function's
// public URL, providing the secret code, stored in app settings
// with key 'processMessageHttp_secret'
req.RequestUri = new Uri(req.RequestUri.AbsoluteUri.Replace(
req.RequestUri.PathAndQuery, $"/api/processMessageHttp?code={secret}"));
// don't 'await' here. Simply send.
#pragma warning disable CS4014
client.SendAsync(req);
#pragma warning restore CS4014
// wait a little bit to ensure the request is sent. It will not
// send the request at all without this line, because it would
// terminate this Azure Function immediately
await Task.Delay(500);
}
}
[FunctionName("processMessageHttp")]
public static async Task ProcessMessageHttp([HttpTrigger(WebHookType = "genericJson")]
HttpRequestMessage req,
Microsoft.Extensions.Logging.ILogger log)
{
// first and foremost: initialize dependency
// injection container, logger, services, set default culture/language, etc.
var initializer = FunctionAppInitializer.Initialize(log);
// handle the request in a usual way and reply back to the chat
await initializer.HandleRequest(req);
}

Categories

Resources