How to disable button after single use in a Adaptive Cards - c#

I am using Adaptive Cards in my LUIS agent. Once a user has filled in all the details and submits the card the submit button should get disabled to prevent duplicate usage of the button.
We also would like to know how to hide a button when displaying a nested Adaptive Card on a button click.
I tried validating the card using the input values made by the user but i am looking for a better and optimal solution for this
P.s working on bot framework v4 API

In a channel like Teams, your bot can call the update activity API and edit the card in the conversation history that way. Web Chat does not support updating or deleting activities out of the box, but if you fork the Web Chat repo you can modify it to do whatever you want. This is essentially the same as creating your own Direct Line client while using Web Chat as a starting point.
For clarity, I want to briefly mention that Web Chat is not really a channel. Direct Line is the channel, and Web Chat is a Direct Line client. The client application is what is ultimately responsible for rendering cards and handling their interactivity.
There is a way to effectively disable Adaptive Card submit actions in any channel using bot state. If you put identifying information in the submit action's data, then you can have your bot remember that the button has already been clicked. If you make sure the bot doesn't do anything when the button is clicked again then it's effectively disabled even though it doesn't look any different.
If you're interested in more Adaptive Card functionality becoming available as a NuGet package, please show some support for my Bot Builder Community idea. If you would like to understand more about using Adaptive Cards with the Bot Framework in general then have a look at my latest blog post.

In webchat, hiding/disabling submit button can be handled in "Incoming Activity" event of Azure bot. You can get 'your_submit_button_id' from JSON file of adaptive card.
const store = window.WebChat.createStore(
{},
function (_ref) {
const dispatch = _ref.dispatch;
return function (next) {
return function (action) {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: { language: window.navigator.language }
}
});
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const event = new Event('webchatincomingactivity');
event.data = action.payload.activity;
/* hiding/disabling button code starts here */
if(event.data.value!=undefined && event.data.value.id=='your_submit_button_id')
{
var btnArr=document.getElementsByClassName("ac-pushButton");
btnArr[btnArr.length-1].style.display='none';
//btnArr[btnArr.length-1].disabled = true;
}
window.dispatchEvent(event);
}
return next(action);
};
};
});

Related

Shifting from chatbot view to ionic mobile app page automatically when reaching a step in dialogue flow

I am using MS chatbot-framework V3 and embedding it in my ionic 3 mobile app via Direct line.
What I am aiming to is when reaching the end of conversation, the chatbot is terminated and shifting to another page in the mobile app with passing some values from chatbot to that page.
When you reach the end of your conversation, you can send an event activity to your DirectLine Client with channel data from the conversation, and once the client receives the event, you can transition to the next view in your app with the channel data. See the code snippets below.
Sending Transition Event
In the bot, we are going to send back channel events to DirectLine with the data we have gathered in the chat. Basically, you just need to send an activity with the type property set to 'event' and the name attribute set to some string value - we are going to use 'transition' in this case. The conversation data is going to be encapsulated in the activity's channel data.
// End of conversation
var reply = turnContext.Activity.CreateReply();
reply.Name = "transition";
reply.Type = "event";
reply.ChannelData = JObject.FromObject( new {
user = new {
name = "TJ",
location = "Seattle"
}
});
await turnContext.SendActivityAsync(reply, cancellationToken: cancellationToken);
Listen for Transition Event
On the client side, we are going to filter the incoming activities to listen for our 'transition' event from the bot. When it's received, you can transition to the next view and pass along the channel data which contains the conversation data.
import { DirectLine } from 'botframework-directlinejs';
var directLine = new DirectLine({
secret: "<DIRECTLINE_SECRET"
});
directLine.activity$
.filter(activity => activity.type === 'event' && activity.name === 'transition')
.subscribe( activity => { /* Initiate transition to next view with activity.channelData */ });
Hope this helps!

Resume Bot Framework dialog when triggered by external service

The Scenario
I have a bot built using the Bot Framework with a series of dialogs. One of these dialogs gives the user the option of inputting some complex data via a web page by presenting a button to them. Clicking the button they are then taken to the site, fill out the data, save and are then directed back to the bot.
I want my bot to pause the dialog until it receives an event from my web page telling me the user has saved the data and then continue asking the user questions.
Before
I had a version implemented whereby I would store a ConversationReference before the user clicked the button and then when the external event happened I would send the cards and next messages I wanted to show (not in a dialog) from a webhook, that was fine but it got quite complicated/messy - I'd rather keep the whole app in one continuous dialog.
Idea 1: Use DirectLine API
I did some research and many people were suggesting using the DirectLine API. So I implemented this:
public async Task SendEventAsync(InternalEventMessage message, ConversationReference reference) {
var client = new DirectLineClient(!String.IsNullOrEmpty(_settings.DirectLineSecret) ? _settings.DirectLineSecret : null);
if (_settings.SiteUrl.Contains("localhost")) {
client.BaseUri = new Uri(_settings.DirectLineServiceUrl);
}
var eventMessage = Activity.CreateEventActivity();
//Wrong way round?!?
eventMessage.From = reference.Bot;
eventMessage.Type = ActivityTypes.Event;
eventMessage.Value = message;
var conversation = await client.Conversations.PostActivityAsync(reference.Conversation.Id, eventMessage as Activity);
}
This uses the DirectLine client to send an event message to the serviceUrl using a stored ConversationReference, basically imitating a user (bot and user seem to be the wrong way round in the SDK). Checking for localhost was so that the DirectLine library pointed at the emulator server rather than https://directline.botframework.com.
In my dialog I call:
//method above shows input button and links to web page
context.Wait(WaitForAddressInput);
}
private async Task WaitForAddressInput(IDialogContext context, IAwaitable<IActivity> result) {
var message = await result;
switch (message.Type) {
case ActivityTypes.Message:
//TODO: Add response
break;
case ActivityTypes.Event:
var eventMessage = message as IEventActivity;
if (((JObject)eventMessage.Value).ToObject<InternalEventMessage>().Type == EventType.AddressInputComplete) {
_addressResult = (await _tableService.ReadOrderById(Order.OrderId)).Address;
await context.PostAsync($"Great}");
context.Done(_addressResult);
}
break;
}
}
This waits for any message from the user after the button has been shown and if our event matches then we proceed with the dialog.
This works locally using the emulator but, frustratingly, doesn't live. It fails to recognise channels created via webchat or Messenger. That is explained here: Microsoft Bot Framework DirectLine Can't Access Conversations
For security reasons, you can't use DirectLine to spy on messages from
another conversation.
So I can't access a channel that I haven't created using DirectLine.
Idea 2: BotConnector
So I thought I'd try the BotConnector using similar code:
public async Task SendEventAsync(InternalEventMessage message, Microsoft.Bot.Connector.DirectLine.ConversationReference reference) {
var botAccount = new ChannelAccount(reference.User.Id, reference.User.Name);
var userAccount = new ChannelAccount(reference.Bot.Id, reference.Bot.Name);
MicrosoftAppCredentials.TrustServiceUrl(reference.ServiceUrl);
var connector = new ConnectorClient(new Uri(reference.ServiceUrl), new MicrosoftAppCredentials("xxxxxxxxxxxxxxxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxx"));
connector.Credentials.InitializeServiceClient();
var eventMessage = Activity.CreateMessageActivity();
eventMessage.Recipient = botAccount;
eventMessage.From = userAccount;
eventMessage.Type = ActivityTypes.Event;
eventMessage.Conversation = new ConversationAccount(id: reference.Conversation.Id);
eventMessage.ServiceUrl = reference.ServiceUrl;
eventMessage.Timestamp = DateTimeOffset.UtcNow;
eventMessage.LocalTimestamp = DateTime.Now;
eventMessage.ChannelId = reference.ChannelId;
var result = await connector.Conversations.SendToConversationAsync(eventMessage as Microsoft.Bot.Connector.Activity);
}
This doesn't crash and I can see the event appear in the emulator request console but nothing happens, it seems to be ignored!
Idea 3: Try to imitate the bot service calling my bot
I haven't tried this yet because I think it might be the most time consuming but I was reading here about the service authentication and wondered if it would be possible to imitate the hosted bot service sending a message and send my event that way with the required data?
This seems like a fairly common scenario so I'm surprised I haven't come across a way to do this yet. If anyone has any other ideas on how I can send an event message to my bot from an external service then I'd love to hear it.
Update:
See my answer below Eric's to see what I did.
Idea 1:
DirectLine is a channel, not a library to use in order to connect to channels. (For instance: you would not use Facebook Messenger to connect to Skype) DirectLineClient is useful for creating a client application that connects to the DirectLine channel through the Direct Line connector service.
Idea 2:
This method should work. In fact, the BotAuth library uses this method for the MagicNumber login flow within the CallbackController: https://github.com/MicrosoftDX/botauth/blob/9a0a9f1b665f4aa95b6d60d09346dda90d8b314e/CSharp/BotAuth/Controllers/CallbackController.cs
For your scenario, you should be able to construct a CardAction of type ActionTypes.OpenUrl that contains a value with the ConversationReference encoded in the url. Clicking the button will call an mvc controller that displays a page (saving the ConversationReference in a cookie or something) and when the user finishes adding the address on the page, use the ConversationReference to send an event to the bot (similar to how BotAuth resumes the conversation in the CallbackController).
Idea 3:
This would bypass the connector services, and is not a supported scenario. The link you shared explains the details of how authentication works in the Bot Framework, not how to bypass the connector services.
Eric's answer led me to solve the issue using the BotAuth example but, for completeness, here is what I did using Idea 2.
I created a CallbackController on my Bot Framework endpoint and then used the following code to send an event back to the awaiting dialog:
MicrosoftAppCredentials.TrustServiceUrl(reference.ServiceUrl);
var message = reference.GetPostToBotMessage();
message.Value = new InternalEventMessage(type);
message.Type = ActivityTypes.Event;
await Conversation.ResumeAsync(reference, message);
The dialog awaits with this code and continues:
context.Wait(WaitForAddressInput);
}
private async Task WaitForAddressInput(IDialogContext context,
IAwaitable<IActivity> result)
{
var message = await result;
switch (message.Type)
{
case ActivityTypes.Message:
//TODO: Add response
break;
case ActivityTypes.Event:
//Process event and continue!
break;
}
}
This is the most complicated issue I've had with the Bot Framework and I found the docs a little lacking. Hope this helps someone!

How to setup buttons with "imBack" type in Slack channel using ms bot builder

I've developed few bots using ms botbuilder, there was no problems with Buttons with "imBack" type on Skype, FB, Telegram and web chat.
However trying to make it work on Slack i found (and seems like its not documented) that you need extra configuring of Interactive Messages in APP admin panel. So here's my questions regarding this issue:
is it true that its the only way?
If so what's the real reason for that, everything works just fine in all other channels.
Is there a way to setup it programmatically, cos it will be real bad for distribution of bot?
even when i setup endpoint in admin panel looks like it makes request that is barely compatible with how ms builder works (it sends some GET where we need post with relevant info)
Code sample:
...
var reply = activity.CreateReply("hi, can i ask you few question?");
reply.Attachments = new List<Attachment>(){};
var buttonSelect = new CardAction
{
Value = $" /startCommand",
Type = "imBack",
Title = "ok, ask me"
};
var cardForButton = new ThumbnailCard { Buttons = new List<CardAction> { buttonSelect } };
reply.Attachments.Add(cardForButton.ToAttachment());
await connector.Conversations.CreateDirectConversationAsync(botaccount, userAccount, reply);
...
2 things you can try that are possible solutions
You probably did, but did you activate interactive messages in your slack api dashboard?
There is a token (Verification Token) that you need to get from the oauth section of the Slack api site for your app. you need to include that with the other information here https://slack.botframework.com/Dev/?botId={yourBotID}
Actually all needed to be done is put https://slack.botframework.com/api/Actions in Request URL when enabling Interactive Messages in Slack, not sure that it was documented earlier on Ms bot framework panel, but it is now. Issue resolved.

how to get back the "payload" of the button " Get Started " in c#

I work with Microsoft Bot Framework in Messenger and I would like to know how to get back the "payload" of the "Get Started" button. Which function I should create in C#?
You don't have to - the payload is returned as a regular message, so you can just use this:
if (activity.ChannelId == "facebook" && activity.Text == "GET_STARTED_PAYLOAD")
{
// etc
Note that the documentation lists some restrictions on this feature:
The welcome screen is only shown the first time the user interacts with the Page on Messenger.
Only admins, developers, and testers of the app can see it when the app is in development mode.
Your app must be subscribed to postback webook events.

Ionic sending Parse push notification with attached url

I've been busy with a new application based on ionic. It's a new environment so I have a lot to explore.
I'd walked into some problems and search over the whole internet to find a solution and couldn't find one solving my problem. So I hope one of you guys know a solution.
I am trying to send notifications to my device using parse. That works fine, but now I want to send an uri attached with it, so that I can open a specific page clicking on the notification that has been recievied.
The problem is that I have no clue how to correctly send an attached uri and recieve that uri on the device to do something with it.
I hope some of you knows a solution.
I'm using ASP.NET to send the notification with a working initialized client.
var parsePush = new ParsePush();
// sending to all with your own data
parsePush.Data = new Dictionary<string, object>
{
{"alert", txtMessage.Text},// this is replacement for parsePush.Alert
{"sound", "notify.caf"},// for ios
{"badge", "Increment"}// for ios notification count increment on icon
//{"category", "http://google.nl" }
};
parsePush.SendAsync().Wait();
For the ionic side I don't have an event to recieve the notification and work with it. I just get the notification and get redirected to the home page of the app.
Please inform me if there are unclear details.
Thanks a lot!
I fixed this problem by searching over the whole internet.
The first thing I did was uncommenthing the category in my parsePush.data on the server side (see code in the question). In the category I define the uri. The next thing I did was adding "ParsePushPlugin.on" to my app.js. Those event are used when a notification is recieved or opened.
I added this code to my app.js inside the .run -> $ionicPlatform.ready.
ParsePushPlugin.getInstallationId(function (id) {
alert(id);
storage.set('parseId', id);
}, function (e) {
alert('error');
});
ParsePushPlugin.on('receivePN', function (pn) {
alert('yo i got this push notification:' + JSON.stringify(pn));
});
//customEvt can be any string of your choosing, i.e., chat, system, upvote, etc.
ParsePushPlugin.on('receivePN:chat', function (pn) {
alert('yo i can also use custom event to keep things like chat modularized');
});
ParsePushPlugin.on('openPN', function (pn) {
//you can do things like navigating to a different view here
//alert('Yo, I get this when the user clicks open a notification from the tray');
$state.go(pn.category)
});
As you can see "ParsePushPlugin.on('openPn')" has pn as parameters which contains all the data from the recieved and opened notification. This notification also contains the "category (uri)" which is passed from the server side.

Categories