Bot Framework Proactive Message Passing Id Into Conversation State - botframework

I have a SMS / Twilio Channel that I'm using to send out a Proactive message to the user. To send the Proactive message I'm calling a API method passing in MySpecialId which is used later in the conversation.
I want to save this MySpecialId into the conversation but at the point I have the MySpecialId the conversation doesn't exist yet, and I don't have a turnContext, so I can't really save it yet.
Is there a way to pass this Id from my API method into the BotCallback? I created a quick example. (Here is the original example I'm using https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/16.proactive-messages)
Thanks
[HttpGet("{number}")]
public async Task<IActionResult> Get(string MySpecialId)
{
//For Twillio Channel
MicrosoftAppCredentials.TrustServiceUrl("https://sms.botframework.com/");
var NewConversation = new ConversationReference
{
User = new ChannelAccount { Id = $"+1{PHONENUMBERTOTEXTHERE}" },
Bot = new ChannelAccount { Id = "+1MYPHONENUMBERHERE" },
Conversation = new ConversationAccount { Id = $"+1{PHONENUMBERTOTEXTHERE}" },
ChannelId = "sms",
ServiceUrl = "https://sms.botframework.com/"
};
BotAdapter ba = (BotAdapter)_HttpAdapter;
await ((BotAdapter)_HttpAdapter).ContinueConversationAsync(_AppId, NewConversation, BotCallback, default(CancellationToken));
return new ContentResult()
{
Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
};
}
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
try
{
var MyConversationState = _ConversationState.CreateProperty<MyConversationData>(nameof(MyConversationData));
var ConversationState = await MyConversationState.GetAsync(turnContext, () => new MyConversationData(), cancellationToken);
//********************************************************************************************************************
ConversationState.MySpecialId = //HOW DO I GET MySpecialId FROM THE GET METHOD ABOVE HERE?
//********************************************************************************************************************
await _ConversationState.SaveChangesAsync((turnContext, false, cancellationToken);
await turnContext.SendActivityAsync("Starting proactive message bot call back");
}
catch (Exception ex)
{
this._Logger.LogError(ex.Message);
}
}

I don't believe that you can. Normally, you would do something like this by passing values through Activity.ChannelData, but ChannelData doesn't exist on ConversationReference.
Per comments below, #Ryan pointed out that you can pass data on ConversationAccount.Properties. Note, however, this is currently only available in the C# SDK. I've opened an issue to bring this into the Node SDK, but ETA is unknown at this point.
Instead, I'd suggest using a something more native to C#, like:
Create a ConcurrentDictionary
private ConcurrentDictionary<string, string> _idMap;
Map MySpecialId to Conversation.Id (in your Get function)
_idMap.AddOrUpdate(conversationReference.Conversation.Id, MySpecialId, (key, newValue) => MySpecialId);
Access the MySpecialId from the Activity.Conversation.Id (in BotCallback)
var ConversationState = await MyConversationState.GetAsync(turnContext, () => new MyConversationData(), cancellationToken);
ConversationState.MySpecialId = _idMap.GetValueOrDefault(turnContext.Activity.Conversation.Id);
Save ConversationState
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
There's other ways you could do this and some validation checks you'll need to add, but this should get you started.

Related

Skill bot sending proactive messages to a user

I have developed a bot which sends Proactive messages to user
I also cretaed bots which trigger skills.
I was trying to write something where a skills bot/Dialog would be able to send proactive message received via webhooks to the user and continue with the existing Skill dialog.
I am not able to quite understand that. If anyone could help me there.
I used the sample from here to create a simple Skill bot which saves the ConversationReference of the current Activity and calls a service in OnMessageActivityReceived()
// Save ConversationReference
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
// Calling external service
HttpResponseMessage responsemMsg = await client.PostAsync(RequestURI, stringContent);
if (responsemMsg.IsSuccessStatusCode)
{
var apiResponse = await responsemMsg.Content.ReadAsStringAsync();
//Post the API response to bot again
await turnContext.SendActivityAsync(MessageFactory.Text($"Message Sent {turnContext.Activity.Text}"), cancellationToken);
}
The called service eventually emits an event which calls an action in the NotifyController in my Skills Bot. It tries to grab the ConverstaionReference and send the activity using the TurnContext.
//I am using the Skill Bot Id for _appId parameter
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
{
await context.SendActivityAsync(proMsg.eventName);
await context.SendActivityAsync(proMsg.context.ToString());
}, new System.Threading.CancellationToken());
This SendActivity fails and OnTurnError from the Skill Bot handles the exception.
I am not sure where excatly I am going wrong. I am new to the Bot framework learning.
My issue got resolved by using the ContinueConversation() overload with ClaimsIdentity and setting the right claims for audience, appid etc. It was basically authentication issue.
public virtual Task ContinueConversationAsync(ClaimsIdentity claimsIdentity, ConversationReference reference, string audience, BotCallbackHandler callback, CancellationToken cancellationToken);
This is how I created the ClaimsIdentity:
System.Collections.Generic.List<Claim> lclaim = new System.Collections.Generic.List<Claim>
{
new Claim(AuthenticationConstants.VersionClaim, "2.0"),
new Claim(AuthenticationConstants.AudienceClaim, <SkillBotId>),
new Claim(AuthenticationConstants.AppIdClaim, <SkillBotId>),
new Claim(AuthenticationConstants.AuthorizedParty, <SkillBotId>),
};
ClaimsIdentity ci = new ClaimsIdentity(lclaim);
async Task BotCallBack(ITurnContext turnContext, CancellationToken token)
{
<code to send activity back to parent bot>
}
await ((BotAdapter)this.botFrameworkHttpAdapter).ContinueConversationAsync(
ci,
conversationData.ConversationReference,
<ParentBotId>,
callback: BotCallBack,
default).ConfigureAwait(false);

Users have to sign in every time?

Right now, our users sign in every time they want to chat with our bot. It can be very annoying for them. Here's some information about the bot:
The bot is accessed using the Direct Line channel.
When I use the "Test in Web Chat" feature in Azure, I stay signed in every time. So it works fine there.
We are using the BotFramework-WebChat component for the UI.
I followed this tutorial to generate a user token. I generated a user ID like this:
var userId = "dl_" + new Random().Next() + new DateTime().Ticks;
Here is the code to authenticate users:
public GreetingDialog(IConfiguration configuration, IBotTelemetryClient telemetryClient)
: base(INTENTS.GREETING, configuration["ConnectionName"])
{
TelemetryClient = telemetryClient;
AddDialog(new OAuthPrompt(
nameof(OAuthPrompt),
new OAuthPromptSettings
{
ConnectionName = ConnectionName,
Text = "👋 Welcome! Please Sign In.",
Title = "Sign In",
Timeout = 30000,
})
{
TelemetryClient = telemetryClient
});
AddDialog(new WaterfallDialog(INTENTS.GREETING, new WaterfallStep[] {
PromptStepAsync,
GreetStepAsync,
})
{
TelemetryClient = telemetryClient
});
InitialDialogId = INTENTS.GREETING;
}
private async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}
private async Task<DialogTurnResult> GreetStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
{
// ...
}
The problem is that every time you generate a user Id randomly, a new conversation is created. Being a new conversation, the bot cannot find the previous session.
If you want to do a test you can assign a static ID user:
var userId = "dl_123456789";
It shouldn't ask you to log in again.
To make a complete solution, the userId should be tied to some distinctive credentials in your application (ex: username/password)

Invokeresponse in botframework v4

How can I return an InvokeResponse in botframework v4 for C#? I need this to respond to compose extension activity messages. In the old framework this was done by returning in the response a composeExtension object from the controller.
How can this be done when implementing the IBot interface.
In the old framework there were MS Teams extensions, not available for the new framework version.
To respond to an invoke activity you have to set the "BotFrameworkAdapter.InvokeResponse" in turnContext.TurnState like in the below example
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// do stuff
}
if (turnContext.Activity.Type == ActivityTypes.Invoke)
{
// do stuff
var invokeResponse = new InvokeResponse()
{
Body = response,
Status = (int)HttpStatusCode.OK
};
var activity = new Activity();
activity.Value = invokeResponse;
// set the response
turnCoontext.TurnState.Add<InvokeResponse>(InvokeReponseKey, activity);
}
}
For what I think you are asking:
There is an example of handling an invoke response in this sample. In your OnTurnAsync you need to catch the Invoke activity and do whatever it is you need to do with the activity like in the sample.
I'm unsure which SDK you are using as you did not include it in your question but, a simple example in C# (Node would be similar) might look like this:
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
if (turnContext.Activity.Type == ActivityTypes.Message)
{
//do stuff
}
if (turnContext.Activity.Type == ActivityTypes.Invoke)
{
//do stuff
}
}
From the BF SDK v4 code here:
https://github.com/Microsoft/botbuilder-dotnet/blob/4bb6b8d5faa4b252379ac331d6f5140ea27c177b/libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs#L216
https://github.com/Microsoft/botbuilder-dotnet/blob/4bb6b8d5faa4b252379ac331d6f5140ea27c177b/libraries/Microsoft.Bot.Builder/BotFrameworkAdapter.cs#L285
what you do is use ITurnContext to "reply" with a fake activity of type ActivityTypesEx.InvokeResponse, setting Activity.Value to an InvokeResponse object with your desired status code and payload.

Modify the destination for the response

I use Microsoft Bot Framework. From: https://dev.botframework.com/
AND
Microsoft Bot Emulator (V4 Preview) version 4.0.15-alpha. From: https://github.com/microsoft/botframework-emulator
I created a new C# project with "Bot Application" template. I run this project. I launched two entities of the Emulator.
Now I receive the message from the first Emulator entity but I want to send the response to the second Emulator entity. How can I do this?
This is the function where I try to modify the destination (the code that is commented) but does not work.
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
//activity.Recipient.Id = "default-user";
//activity.ServiceUrl = "http://localhost:52234";
//activity.Conversation.Id = "e7bbb310-a93c-11e8-8dcc-7d6fd69e3901|livechat";
//activity.ReplyToId = "6cc291f0-a93d-11e8-9634-9f01a6c082d4";
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
I launched two entities of the Emulator. Now I receive the message from the first Emulator entity but I want to send the response to the second Emulator entity. How can I do this?
Before you write code to send message to another emulator, you need to get the value of ServiceUrl and conversationId etc. And then you could refer to the following code to send message to the specified conversation.
In dialog:
await context.PostAsync($"{this.count++}: You said {activity.Text}");
var userAccount = new ChannelAccount(name: "User", id: "default-user");
var botAccount = new ChannelAccount(name: "Bot", id: "default-bot");
var connector = new ConnectorClient(new Uri("{your_ServiceUrl_here}"));
IMessageActivity message = Activity.CreateMessageActivity();
message.From = botAccount;
message.Recipient = userAccount;
//specify conversationId
message.Conversation = new ConversationAccount(id: "{your_conversationId_here}");
message.Text = $"You said {activity.Text} from emulator1";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
context.Wait(MessageReceivedAsync);
Test result:

sponsored messages on facebook with bot framework

How can I send a message to the user without the user sending me a message? Like for example CNN bot is sending messages every day in the morning by itself. How can I do that in the bot framework?
See this.
In fact, you do not strictly need to receive a message from the user first, but addressing manually can be error-prone (you have to know the user's and bot's channel account, the service URL, etc.)
And in turn (per #thegaram's message), that only works for some channels. For example, Skype requires that the user contact the bot before the bot can message the user.
Once contacted, you can store the user's channelAccount data once they contact you and use that to send them proactive messages. For example if the user has subscribed to hear sports scores for a particular team over time.
Any sort of unsolicited spam messages of course are prohibited by the policies of the Bot Framework (and most of the channels).
Yes you can do that. We called it Greeting from Bot. I have done it and sharing a sample code with you.
Write this code in your messageController or first dialog used in bot.
if (activity.Text == null)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isActivityTyping = activity.CreateReply();
isActivityTyping.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isActivityTyping);
await Conversation.SendAsync(activity, () => new Dialogs.GreetDialog());
}
after this code you need to create a dialog GreetDialog. Below is the cs file code for your reference.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace GPP.Bot.Dialogs
{
[Serializable]
internal class GreetDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(Greeting);
}
private async Task Greeting(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
if (string.IsNullOrEmpty(message.Text))
{
// Hero Card
var cardMsg = context.MakeMessage();
var attachment = BotWelcomeCard("Hello, I am a bot. Right now I am on training and in a prototype state", "");
cardMsg.Attachments.Add(attachment);
await context.PostAsync(cardMsg);
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
else
{
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
}
private static Attachment BotWelcomeCard(string responseFromQNAMaker, string userQuery)
{
var heroCard = new HeroCard
{
Title = userQuery,
Subtitle = "",
Text = responseFromQNAMaker,
Images = new List<CardImage> { new CardImage("https://i2.wp.com/lawyerist.com/lawyerist/wp-content/uploads/2016/08/docubot.gif?fit=322%2C294&ssl=1") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.ImBack, "Show Menu", value: "Show Bot Menu") }
};
return heroCard.ToAttachment();
}
private async Task AfterGreetingDialogCompleted(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(new object());
}
}
}
this is a working code. Do let me know in case you face ant issue.
~cheers :)

Resources