I want to proactively send a message to a group that the bot was added to (not a team, a simple group chat)
What I'm doing so far is saving (in memory) the conversation Id in a singleton dictionary
and then issue the notification to all conversations.
This is the API endpoint code..
public CINotificationsController(
IBotFrameworkHttpAdapter adapter,
IBot bot,
IConfiguration config,
ConcurrentDictionary<string, ConversationReference> conversationReferences,
ILogger<CINotificationsController> logger)
{
_Adapter = adapter;
_Bot = bot;
_ConversationReferences = conversationReferences;
_Logger = logger;
_AppId = config.GetSection("MicrosoftAppId").Value;
}
[HttpPost]
public async Task<IActionResult> PostAsync(CINotification notificationData)
{
_Logger.LogInformation($"Got CI notification, {JsonConvert.SerializeObject(notificationData)}");
var jobName = notificationData.JobName;
var culpritsEmails = notificationData.Commiter;
foreach (var conv in _ConversationReferences.Values)
{
await ((BotAdapter)_Adapter).ContinueConversationAsync(_AppId, conv, GetBotCallBack(notificationData, conv), default);
}
return Ok(culpritsEmails);
}
private BotCallbackHandler GetBotCallBack(CINotification notificationData, ConversationReference conv)
{
return async (ctx, cts) =>
{
_Logger.LogDebug($"conversationId:[{conv.ActivityId}], serviceUrl:[{conv.ServiceUrl}]");
var mention = new Mention();
if (!string.IsNullOrEmpty(notificationData.Commiter))
{
var membersByEmail = (await TeamsInfo.GetMembersAsync(ctx, cts)).ToDictionary(k => k.Email, v => v, StringComparer.OrdinalIgnoreCase);
_Logger.LogDebug($"members:[{string.Join(",", membersByEmail.Keys)}]");
if (membersByEmail.TryGetValue(notificationData.Commiter, out var teamMemeber))
{
mention.Mentioned = teamMemeber;
mention.Text = $"<at>{teamMemeber.Name}</at>";
}
_Logger.LogDebug($"got mentions: {mention.Text}");
}
var msgText = $"{mention.Text} {notificationData.Message}";
_Logger.LogDebug($"Sending message text: {msgText}");
var replyActivity = MessageFactory.Text(msgText);
replyActivity.Entities.Add(mention);
await ctx.SendActivityAsync(replyActivity);
};
}
Of course an in memory dictionary _ConversationReferences is a bad idea and I am looking for some way (API, something form the SDK ) to be able to list all groups the bot was added to or all conversation that were started ..
Do I have to store it in some DB or do i have a different option ?
I don't think such an API call exists. You can get from the Graph API, for instance, a list of all apps installed into a team, but there's no such thing for group chats or 1-1 chats.
However, you -have- all that information already - simply store it your side, with so many good storage options these days (cloud and otherwise). In that case, it's fully under your control anyway.
You can do it with this code
// Create or get existing chat conversation with user
var response = client.Conversations.CreateOrGetDirectConversation(activity.Recipient, activity.From, activity.GetTenantId());
// Construct the message to post to conversation
Activity newActivity = new Activity()
{
Text = "Hello",
Type = ActivityTypes.Message,
Conversation = new ConversationAccount
{
Id = response.Id
},
};
// Post the message to chat conversation with user
await client.Conversations.SendToConversationAsync(newActivity, response.Id);
Got it here
https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages?tabs=dotnet
Related
I have added my app to specific team in MS Teams and added my bot there. Now, I am trying to create a chat between specific persons who were added to the 'General' channel using bot to communicate with only certain persons. For now, I am testing it by typing a command to that channel and processing activity like:
public async Task Handle(CancellationToken cancellationToken)
{
// Get all members who were added to that channel
var members = new List<TeamsChannelAccount>();
string continuationToken = null;
do
{
var currentPage = await TeamsInfo.GetPagedMembersAsync(_turnContext, 100, continuationToken, cancellationToken);
continuationToken = currentPage.ContinuationToken;
members.AddRange(currentPage.Members);
}
while (continuationToken != null);
// Start a new conversation with all of them
var createdConversation = await _connectorClient.StartConversation(members,
_turnContext.Activity.ServiceUrl,
_turnContext.Activity.Conversation.TenantId,
MessageFactory.Text("test proactive mssages"),
_turnContext.Activity.Recipient);
}
where StartConversation is:
public async Task<ConversationResourceResponse> StartConversation(List<TeamsChannelAccount> members,
string serviceUrl,
string tenantId,
Activity activity,
ChannelAccount botAccount,
CancellationToken cancellationToken = default)
{
AppCredentials.TrustServiceUrl(serviceUrl, DateTime.MaxValue);
var connectorClient = new ConnectorClient(new Uri(serviceUrl), new MicrosoftAppCredentials(_botId, _botPassword));
try
{
var conversationParams = new ConversationParameters(
true,
botAccount,
members.Select(x => new ChannelAccount(x.Id, x.Name)).ToList(),
"Test proactive group message",
activity,
new TeamsChannelData()
{
Tenant = new TenantInfo(tenantId)
}
, tenantId);
return await connectorClient.Conversations.CreateConversationAsync(conversationParams, cancellationToken);
}
catch (Exception ex)
{
throw;
}
}
When I test this code, I receive the exception with the following message: Operation returned an invalid status code 'BadRequest' with Error Code "BadSyntax" and Message "Incorrect conversation creation parameters".
I'm not sure if that possible to create such conversations in MS Teams or not. The most confusing part for me is isGroup argument in ConversationParameters - doesn't that indicate that created conversation should be group chat? I've tried to create direct conversation with my account by setting isGroup to false and leaving only my TeamsChannelAccount in members and that worked
I am using Microsoft.Bot.Builder.Integration.AspNet.Core 4.14.1
Thanks for reading and help!
We tried it at our end and faced the same issue. Maybe you can try creating groups using deeplink https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-links#deep-linking-to-a-chat
Thanks
we are currently developing some automation with the botframework.
At some point in the conversation, we sent some data through a service bus for processing and wait for a response and then want to continue with the conversation. We already implemented the part where we wait for an response entry in the service bus subscription and then we want to send an Activity from type Event to the bot.
We did the same steps with the proactive message as described in other posts.
We are able to recreate the botclient and conversation reference and all, but in the end when we send the activity, we always send it to the user and not to the bot. But this doesn't trigger the "EventActivityPrompt".
The only way where we achieved the desired outcome was when we made a post to api/messages, but this is too complicated for our taste, and we are looking for an easier way over the botClient (or similar technology)
Has anyone some good ideas? :)
ServiceBusReceiver Message Processing:
private static async Task ProcessMessagesAsync(Message message, CancellationToken token)
{
// Process the message.
Console.WriteLine($"Received message: SequenceNumber:{message.SystemProperties.SequenceNumber} Body:{Encoding.UTF8.GetString(message.Body)}");
_logger?.LogInformation("Received message '{id}' with label '{label}' from queue.", message.MessageId, message.Label);
var data = JsonSerializer.Deserialize<BotCarLicensingOrderRpaRequest>(message.Body);
data.AdditionalData.TryGetValue("ServiceUrl", out var serviceUrl);
data.AdditionalData.TryGetValue("ChannelId", out var channelId);
data.AdditionalData.TryGetValue("BotId", out var botId);
data.AdditionalData.TryGetValue("UserId", out var userId);
data.AdditionalData.TryGetValue("ReplyToId", out var replyToId);
var conversationReference = _offTurnConversationService.CreateSyntheticConversationReference(
channelId?.ToString(),
data.ConversationId,
serviceUrl?.ToString());
conversationReference.User = new ChannelAccount()
{
Id = userId?.ToString(),
Role = "user"
};
conversationReference.Bot = new ChannelAccount
{
Id = botId?.ToString(),
Role = "bot"
};
var activity = (Activity)Activity.CreateEventActivity();
activity.Text = "success";
activity.ChannelId = channelId?.ToString();
activity.ServiceUrl = serviceUrl?.ToString();
activity.RelatesTo = conversationReference;
activity.Conversation = new ConversationAccount
{
Id = data.ConversationId
};
activity.ReplyToId = replyToId?.ToString();
activity.ApplyConversationReference(conversationReference, true);
// Complete the message so that it is not received again.
// This can be done only if the subscriptionClient is created in ReceiveMode.PeekLock mode (which is the default).
await _messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
// This "works" but is complicated, as we have to set up a whole HTTP call
await _offTurnConversationService.SendActivityToBotAsync(activity);
// This just sends the Event to the user, no matter how I set up the conversation
// reference regarding From/Recipient
// And it doesn't help in continuing the conversation
await _offTurnConversationService.SendToConversationThroughPipelineAsync(
async (turnContext, cancellationToken) =>
{
await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken);
},
conversationReference);
// Note: Use the cancellationToken passed as necessary to determine if the subscriptionClient has already been closed.
// If subscriptionClient has already been closed, you can choose to not call CompleteAsync() or AbandonAsync() etc.
// to avoid unnecessary exceptions.
}
OffTurnConversationService:
public ConversationReference CreateSyntheticConversationReference(string channelId, string conversationId, string serviceUrl)
{
ArgumentGuard.NotNull(channelId, nameof(channelId));
ArgumentGuard.NotNull(conversationId, nameof(conversationId));
ArgumentGuard.NotNull(serviceUrl, nameof(serviceUrl));
if (string.IsNullOrEmpty(_botOptions.CurrentValue.BotId))
{
throw new InvalidOperationException("A valid bot id must be configured in your bot options in order to create a synthetic conversation reference.");
}
// WARNING: This implementation works for directline and webchat.
// Changes could be necessary for other channels.
var supportedChannels = new List<string>()
{
Channels.Directline,
Channels.Webchat
};
if (supportedChannels.Any(c => c.Equals(channelId, StringComparison.OrdinalIgnoreCase)))
{
_logger.LogWarning(
"The synthetic conversation reference created for channel {UsedChannel} might not work properly, " +
"because it's not supported and tested. Supported channels are {SupportedChannel}.",
channelId,
string.Join(",", supportedChannels));
}
var conversationReference = new ConversationReference()
{
Conversation = new ConversationAccount()
{
Id = conversationId
},
Bot = new ChannelAccount()
{
Id = _botOptions.CurrentValue.BotId,
Name = _botOptions.CurrentValue.BotId
},
ChannelId = channelId,
ServiceUrl = serviceUrl
};
return conversationReference;
}
public virtual async Task SendActivityToBotAsync(IActivity activity)
{
// Create the new request to POST to the client
var forwardRequest = new HttpRequestMessage()
{
RequestUri = new Uri(_botOptions.CurrentValue.ReplyServiceUrl),
Method = HttpMethod.Post,
};
// Change the host for the request to be the forwarding URL.
forwardRequest.Headers.Host = forwardRequest.RequestUri.Host;
// If the child bot is not running on local mode (no app-id/password),
// we're going send an authentication header.
OAuthResponse authToken = await GetTokenAsync(_botOptions.CurrentValue.MicrosoftAppId, _botOptions.CurrentValue.MicrosoftAppPassword);
forwardRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken.AccessToken);
// Altered activity to JSON content
var json = JsonConvert.SerializeObject(activity);
var content = new StringContent(json, Encoding.UTF8, "application/json");
forwardRequest.Content = content;
using var client = new HttpClient();
var response = await client.SendAsync(forwardRequest);
if (!response.IsSuccessStatusCode)
{
string message = $"Failed to send activity '{activity.Id}' to client bot. {response.ReasonPhrase}";
throw new Exception(message);
}
}
public virtual async Task SendToConversationThroughPipelineAsync(
BotCallbackHandler callback,
ConversationReference conversationReference)
{
ArgumentGuard.NotNull(callback, nameof(callback));
ArgumentGuard.NotNull(conversationReference, nameof(conversationReference));
// Avoiding 401 "Unauthorized" errors
TrustServiceUrl(conversationReference.ServiceUrl);
// Reuse adapter with its pipeline to send responses back to the user (like pro-active messages)
await ((BotAdapter)_botFrameworkHttpAdapter).ContinueConversationAsync(
_botOptions.CurrentValue.MicrosoftAppId,
conversationReference,
callback,
default);
}
I have 2 Vue.js clients that share the same backend server (ASP.NET Core). One of the clients is an e-commerce site, and the other client is the admin dashboard that manages the orders.
I have implemented SignalR to allow live order updates on the order dashboard table. The updates currently work in real time without the need to refresh the browser when I update an order's status on the admin dashboard, e.g. when updating from "Out for Delivery" to "Delivered" on browser 1, browser 2 displays the changes immediately without having to refresh the page.
Order updated via dashboard, SignalR event received
However, when I create a new order from the e-commerce site, even though I have checked that my code enters the Hub method to broadcast the event and the new order details to all clients, the order dashboard does not update/refresh, and it does not receive any events as well when I checked the dev tools. I would appreciate some help on this problem.
Hub methods in mounted()
// Establish hub connection
this.connection = await OrderHub.connectToOrderHub();
// Establish hub methods
this.connection.on("OneOrder", order => {
console.log("OneOrder called");
console.log(order);
this.getAllOrders();
});
this.connection.on("MultipleOrders", orders => {
console.log("MultipleOrders called");
console.log(orders);
this.getAllOrders();
});
// start the connection
this.connection
.start()
.then(() => {
console.log("Connection to hub started");
})
.catch(err => console.log(err));
orderHub.js
const signalR = require("#aspnet/signalr");
class OrderHub {
async connectToOrderHub() {
return new signalR.HubConnectionBuilder()
.withUrl("https://localhost:44393/order-hub")
.configureLogging(signalR.LogLevel.Error)
.build();
}
}
export default new OrderHub();
OrderHub.cs in server side
public interface IOrderHub
{
Task NotifyOneChange(Order newOrder);
Task NotifyMultipleChanges(List<Order> newOrders);
}
public class OrderHub : Hub, IOrderHub
{
private readonly IHubContext<OrderHub> _hubContext;
public OrderHub(IHubContext<OrderHub> hubContext)
{
_hubContext = hubContext;
}
public async Task NotifyOneChange(Order newOrder)
{
await _hubContext.Clients.All.SendAsync("OneOrder", newOrder);
}
public async Task NotifyMultipleChanges(List<Order> newOrders)
{
await _hubContext.Clients.All.SendAsync("MultipleOrders", newOrders);
}
}
Create Order method
public async Task<Order> Create(Order order)
{
Order newOrder;
try
{
List<string> imgKeys = new List<string>();
foreach (OrderItem item in order.OrderItems)
{
imgKeys.Add(item.OrderImageKey);
}
// make images on s3 permanent
List<string> imgUrls = await _s3Service.CopyImagesAsync(imgKeys);
// put the new images url into object
for (int i = 0; i < order.OrderItems.Count; i++)
{
order.OrderItems.ElementAt(i).OrderImageUrl = imgUrls.ElementAt(i);
}
// create new order object to be added
newOrder = new Order()
{
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
OrderSubtotal = decimal.Parse(order.OrderSubtotal.ToString()),
OrderTotal = decimal.Parse(order.OrderTotal.ToString()),
ReferenceNo = order.ReferenceNo,
Request = order.Request,
Email = EncryptString(order.EmailString, encryptionKey),
UpdatedById = order.UpdatedById,
DeliveryTypeId = order.DeliveryTypeId,
Address = order.Address,
StatusId = 1,
OrderItems = order.OrderItems
};
// add to database
await _context.Orders.AddAsync(newOrder);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
throw new AppException("Unable to create product record.", new { message = ex.Message });
}
await _orderHub.NotifyOneChange(newOrder); // it steps inside & executes the method, but client side does not receive any events
// returns product once done
return newOrder;
}
I managed to figure out the cause of this issue. SignalR seems to break when the message is over a certain size, and this issue is referenced here. To fix this, I decided to pass in the order's ID instead of the whole order object, and do another GET call to retrieve the details in a separate API call to keep the SignalR message small.
I am developing a chat bot using the bot framework in Azure, I am currently trying to use my chat bots response as a user trigger word to store the users question in table storage, for example when the bot responds with "I’m Sorry, I don’t have an answer for you. Please try and rephrase your question" it logs the users first question for example "How do I fly?".
Any help with this would be much appreciated!
This is one way to accomplish this. You can store every question text in a Dictionary and send it to permanent storage if the query is not correctly answered.
First, create a static dictionary to hold the values:
public static class Utils
{
public static Dictionary<string, string> MessageDictionary = new Dictionary<string, string>();
}
Second, in your messages controller, you can store every message from every user when you bot receives it like this:
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
var userId = activity.From.Id;
var message = activity.Text;
if (!Utils.MessageDictionary.ContainsKey(userId))
{
ConnectorClient connector = new ConnectorClient(new System.Uri(activity.ServiceUrl));
var reply = activity.CreateReply();
//save all incoming messages to a dictionary
Utils.MessageDictionary.Add(userId, message);
// this can be removed it just confirms it was saved
reply.Text = $"Message saved {userId} - {Utils.MessageDictionary[userId]}";
await connector.Conversations.ReplyToActivityAsync(reply);
}
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Nest create a class that inherits from IBotToUser that can intercept a message before it goes out to the user. Here we are going to save the message to permanent storage if the text returned tothe user is the text you provided "I’m Sorry, I don’t have an answer for you. Please try and rephrase your question":
public sealed class CustomBotToUser : IBotToUser
{
private readonly IBotToUser inner;
private readonly IConnectorClient client;
public CustomBotToUser(IBotToUser inner, IConnectorClient client)
{
SetField.NotNull(out this.inner, nameof(inner), inner);
SetField.NotNull(out this.client, nameof(client), client);
}
public async Task PostAsync(IMessageActivity message,
CancellationToken cancellationToken = default(CancellationToken))
{
if (message.Text == "I’m Sorry, I don’t have an answer for you. Please try and rephrase your question")
{
//save to permanant storage here
//if you would like to use a database
//I have a very simple database bot example here
//https://github.com/JasonSowers/DatabaseBotExample
}
//user is the recipient
var userId = message.Recipient.Id;
//remove entry from dictionary
Utils.MessageDictionary.Remove(userId);
//this is just for testing purposes and can be removed
try
{
await inner.PostAsync($"{userId} - {Utils.MessageDictionary[userId]}");
}
catch (Exception e)
{
await inner.PostAsync($"No entry found for {userId}");
}
await inner.PostAsync((Activity) message, cancellationToken);
}
public IMessageActivity MakeMessage()
{
return inner.MakeMessage();
}
}
You will also need to register this class in your Global.asax using Autofac making the Aplication_Start() method look something like this:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
// Bot Storage: Here we register the state storage for your bot.
// Default store: volatile in-memory store - Only for prototyping!
// We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own!
// For samples and documentation, see: [https://github.com/Microsoft/BotBuilder-Azure](https://github.com/Microsoft/BotBuilder-Azure)
var store = new InMemoryDataStore();
// Other storage options
// var store = new TableBotDataStore("...DataStorageConnectionString..."); // requires Microsoft.BotBuilder.Azure Nuget package
// var store = new DocumentDbBotDataStore("cosmos db uri", "cosmos db key"); // requires Microsoft.BotBuilder.Azure Nuget package
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
builder
.RegisterType<CustomBotToUser>()
.Keyed<IBotToUser>(typeof(LogBotToUser));
});
}
where this is the important part for the Global.asax code I am sharing:
builder
.RegisterType<CustomBotToUser>()
.Keyed<IBotToUser>(typeof(LogBotToUser));
I want to connect my Azure QnA Chat Bot with the translation layer cognitive system. I am using this page as a reference: https://learn.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-csharp-translate
I am doing it in C# and on the online code editor of Microsoft Azure.
Unfortunately, I can not connect to the translation layer (at least it looks like that).
When I tried to debug it, I can see that it stops at this specific part:
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
I checked the network timeout errors and there are many (20). All of them say "There was an error sending this message to your bot: HTTP status code GatewayTimeout".
I can "build.cmd" normally, without any errors, and when I try to do Debug.WriteLine or Console.WriteLine, nothing is printed out (I even tried in VS and Emulator)
The only thing that I am doing differently, compared to the above link, is that I defined the "host" and "key" outside of the private method:
private static async Task<string> TranslateQuestionToEnglish (...)
So, I take any word and want to translate it into English.
When I take out those two lines of the code, and test a method with static values, it obviously works (all together with QnA and everything else).
Later on, I am calling this method in "Task MessageReceivedAsync".
I created a translation cognitive service, and the only thing that I took from there is the first key from "Keys" and used it here in this method. Is that the only thing that I need from created cognitive service??
Another thing that I am not sure about, and if that thing is making a problems is that when I go to all resources, I can see that my qnatestbot(web app bot) and translator_test(cognitive services) are of type "global" location, while my qnatestbot(app service) is of type "west europe" location. Can the thing that they are in different regions make a problems? Should I put all of them in West Europe (since I am in Germany)?
Although, now that I look at the translator_test(cognitive services) endpoint, I can see that it is ...api.congitivemicrosft.com/.../v1.0.
But, when I was creating a resource it was automatically created like this, without specifying it from my side? How can I change that?
I hope that somebody successfully came across such an issue and can help me. Thank you in advance
I want to connect my Azure QnA Chat Bot with the translation layer cognitive system. I am using this page as a reference: https://learn.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-csharp-translate
I try to create a sample to achieve your requirement: translate user inputs to English and pass translation text to QnAMaker dialog, the sample works fine both on local and Azure, you can refer to it.
In MessagesController:
[BotAuthentication]
public class MessagesController : ApiController
{
static string uri = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=en";
static string key = "{the_key}";
/// <summary>
/// POST: api/Messages
/// receive a message from a user and send replies
/// </summary>
/// <param name="activity"></param>
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
if (activity.Text != null)
{
var textinEN = await TranslateQuestionToEnglish(activity.Text);
activity.Text = textinEN;
}
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private static async Task<string> TranslateQuestionToEnglish(string text)
{
System.Object[] body = new System.Object[] { new { Text = text } };
var requestBody = JsonConvert.SerializeObject(body);
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(uri);
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
request.Headers.Add("Ocp-Apim-Subscription-Key", key);
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
dynamic jsonResponse = JsonConvert.DeserializeObject(responseBody);
var textinen = jsonResponse[0]["translations"][0]["text"].Value;
return textinen;
}
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
In dialog:
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
/* Wait until the first message is received from the conversation and call MessageReceviedAsync
* to process that message. */
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
/* When MessageReceivedAsync is called, it's passed an IAwaitable<IMessageActivity>. To get the message,
* await the result. */
var message = await result;
var qnaAuthKey = GetSetting("QnAAuthKey");
var qnaKBId = Utils.GetAppSetting("QnAKnowledgebaseId");
var endpointHostName = Utils.GetAppSetting("QnAEndpointHostName");
// QnA Subscription Key and KnowledgeBase Id null verification
if (!string.IsNullOrEmpty(qnaAuthKey) && !string.IsNullOrEmpty(qnaKBId))
{
// Forward to the appropriate Dialog based on whether the endpoint hostname is present
if (string.IsNullOrEmpty(endpointHostName))
await context.Forward(new BasicQnAMakerPreviewDialog(), AfterAnswerAsync, message, CancellationToken.None);
else
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
else
{
await context.PostAsync("Please set QnAKnowledgebaseId, QnAAuthKey and QnAEndpointHostName (if applicable) in App Settings. Learn how to get them at https://aka.ms/qnaabssetup.");
}
}
private async Task AfterAnswerAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
// wait for the next user message
context.Wait(MessageReceivedAsync);
}
public static string GetSetting(string key)
{
var value = Utils.GetAppSetting(key);
if (String.IsNullOrEmpty(value) && key == "QnAAuthKey")
{
value = Utils.GetAppSetting("QnASubscriptionKey"); // QnASubscriptionKey for backward compatibility with QnAMaker (Preview)
}
return value;
}
}
// Dialog for QnAMaker Preview service
[Serializable]
public class BasicQnAMakerPreviewDialog : QnAMakerDialog
{
// Go to https://qnamaker.ai and feed data, train & publish your QnA Knowledgebase.
// Parameters to QnAMakerService are:
// Required: subscriptionKey, knowledgebaseId,
// Optional: defaultMessage, scoreThreshold[Range 0.0 – 1.0]
public BasicQnAMakerPreviewDialog() : base(new QnAMakerService(new QnAMakerAttribute(RootDialog.GetSetting("QnAAuthKey"), Utils.GetAppSetting("QnAKnowledgebaseId"), "No good match in FAQ.", 0.5)))
{ }
}
// Dialog for QnAMaker GA service
[Serializable]
public class BasicQnAMakerDialog : QnAMakerDialog
{
// Go to https://qnamaker.ai and feed data, train & publish your QnA Knowledgebase.
// Parameters to QnAMakerService are:
// Required: qnaAuthKey, knowledgebaseId, endpointHostName
// Optional: defaultMessage, scoreThreshold[Range 0.0 – 1.0]
public BasicQnAMakerDialog() : base(new QnAMakerService(new QnAMakerAttribute(RootDialog.GetSetting("QnAAuthKey"), Utils.GetAppSetting("QnAKnowledgebaseId"), "No good match in FAQ.", 0.5, 1, Utils.GetAppSetting("QnAEndpointHostName"))))
{ }
}
Test result:
Note: We can use ConfigurationManager.AppSettings["QnAKnowledgebaseId"]; to access QnAKnowledgebaseId etc settings from web.config if run bot application on local. For more information, please refer to this SO thread.