Using Bot Builder 4.11.1 .Net and seemed to have lost the ability to capture any unique identifier for each new session. I need a unique identifier to keep state with the AI engine I am using to respond to input. Any suggestions? So, to expand, if I have a slack bot, for example, each time a user logs into Slack and starts a conversation with MyBot, I need a new unique identifier.
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var welcomeText = "Hello and welcome!";
Random rnd1 = new Random();
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
}
}
}
}
Unless I'm missing something, you should be able to get the information you need from TurnContext. In my code I'm running this in onMessage only (as in my case I only will message the user if they have sent the bot a message), but I don't see why you couldn't use this in onMembersAdded as well. I don't know how channels like Slack work, but in Microsoft Teams the user is just "added" when they first talk to the bot, and you don't end up in onMembersAdded unless they remove and read the bot in Teams. So if you may want to grab the conversation reference in the future, you may want to have it in onMessage or in both places. Also if you need the activity ID for some reason, as this will obviously update with each activity (though I haven't had any need for this information). Here is how you get the conversation reference. I am storing this in my conversation state and have assumed you are familiar with that but let me know if you need further help there. I also store in an Azure table to be accessed outside of the bot (e.g. I have an Azure Function that uses this to send proactive followups).
const { TurnContext } = require('botbuilder');
const conversationData = await this.dialogState.get(context, {});
conversationData.conversationReference = TurnContext.getConversationReference(context.activity);
await this.conversationState.saveChanges(context);
And that's it! Here is a sample conversation reference. Note that if you are storing this in Azure Tables or similar, you'll likely need to stringify it and re-parse when you pull it out.
{
"activityId":"ACTIVITY_ID",
"user": {
"id":"USER_ID",
"name":"USER_NAME",
"aadObjectId":"AAD_OBJECT_ID",
"role":"user"
},
"bot": {
"id":"BOT_ID",
"name":"BOT_NAME"
},
"conversation": {
"conversationType":"personal",
"tenantId":"YOUR_TENANT_ID",
"id":"YOUR_CONVERSATION_ID"
},
"channelId":"msteams",
"locale":"en-US",
"serviceUrl":"https://smba.trafficmanager.net/amer/"
}
What you are looking for (I think) is conversationReference.conversation.id. Different channels are going to have different attributes in the conversation reference, but this ID should always be there.
Related
I am building an application where people can host events. It allows for users to follow the events and all the event followers can chat in a group. I am storing the FCM token for push notifications in a user collection in Firestore. But when I send the notification my current logic is not quite optimal and it takes several minutes for the notification to send to each user as I am first getting all user's tokens for each user's document and then combing those tokens in a list and send push notification to each token using a loop in my cloud function. I think it takes time for sending the messages and the group chat does not seem to be real-time because of that computation.
What I thought is to store the FCM tokens of each user inside every event he follows. So that when there is a chat message the list of tokens is fetched from the specific event and they are sent a multicast notification. But here again, I am not sure if it is a good approach because I will need to keep track of refreshed tokens and update the tokens in each document where it exists.
Let's say a user has joined 50 events and on app launch, his FCM token got refreshed now I will have to run a cloud function that will see if the token is renewed it should loop through all of those 50 events and update his token.
I want to know what can be the best approach for this use case.
Below is the code that I am using:
exports.sendNotification = functions.firestore
.document("event/{eventid}/{chat}/{chatid}")
.onCreate((snap, context) => {
processData(snap, context);
return null;
});
async function processData(snap, context) {
const doc = snap.data();
const eventID = context.params.eventid;
const senderID = doc.sender;
var senderName = doc.senderName;
await admin
.firestore()
.collection("event")
.doc(eventID)
.get()
.then(value => {
var joinedBy = value.data()["joinedBy"];
joinedBy.forEach(item => {
getTokens(uid, senderID, doc, eventName, senderName);
});
});
}
async function getTokens(uid, senderID, doc, eventName, senderName) {
await admin
.firestore()
.collection("people")
.doc(uid)
.get()
.then(value => {
var token = value.data()["pushToken"];
if (uid != senderID) {
if (token) {
const payload = {
notification: {
title: `${eventName} : ${senderName}`,
body: doc.text,
badge: "1",
sound: "default"
},
};
sendMessage(token, payload);
} else {
console.log("Can not find pushToken target user");
}
} else {
console.log("uid and sender id is same so notification not sent");
}
});
}
One thing to consider in your current approach is whether you really first need to read all tokens for the message and only then starting calling FCM. Might things get faster if you call FCM right away on each set of tokens you read, and thus perform some of the reads and FCM calls in parallel?
Another consideration is that you say that FCM's built-in topics work really fast for you. Internally FCM's topic subsystem associates a bulk of device IDs with the topic, and then reads the tokens for the topic as needed.
Is this something you can mimic for your app? For example, can you store the tokens in larger groups inside fewer documents, thus reducing the number of read operations you need to do.
Trying to create and debug Teams calling and meeting bot from Bot Framework v4 for CPI project. found here: CallingBotSample
I have gone through all the steps correctly But I have some problems which got me stuck.
Tunneling with Ngrok work fine (200 OK) for /api/callback and /api/messages
Problem 1: AdaptiveCard v1.3 is not showing when the Bot is starting.
Fact: I want the card to show every time the bot is starting as shown in this link: Calling Bot
Problem 2: OnMessageActivityAsync()get the request from user such as createcall However the turnContext.Activity.Text has the value But turnContext.Activity.Value returns null and that will result on Bot showing message but not calling or joining meeting.
Snip:
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(turnContext.Activity.Text))
{
//turnContext.Activity.Text = null;
dynamic value = turnContext.Activity.Value;
if (value != null)
{
string type = value["type"];
type = string.IsNullOrEmpty(type) ? "." : type.ToLower();
await SendReponse(turnContext, type, cancellationToken);
}
}
else
{
await SendReponse(turnContext, turnContext.Activity.Text.Trim().ToLower(), cancellationToken);
//await OnTurnAsync(turnContext, cancellationToken);
}
}
What I want is a 100% working calling bot that can join any user calls and record it
Does anyone have a subjection or a solution to my questions?
We tried to repro this issue at our end, but everything works for us. We are able to get Welcome card and calls are getting placed without any issue.We can record the call as well. Also able to join scheduled meeting and Invite participants to meetings as mentioned.
Please make sure you have enabled calling and configured proper endpoint on Teams channel page.
Microsoft Bot Framework V4, I have a waterfall Dialog defined in a dialog as below
var waterfallSteps = new WaterfallStep[]
{
CallConfirmAsync,
SimilarProductAsync,
CheckNewVersionAsync,
};
AddDialog(new WaterfallDialog("productenquiry", waterfallSteps));
After the execution of the first two waterfall steps, my conversation is stop due to unresponsiveness from user's end. So I want to resume from the third method when i.e., CheckNewVersionAsync when the user comes back again to the bot.
Can anyone please help me here.
So, at the bot level, this should happen automatically if you've configured the IStatePropertyAccessor<DialogState> with the ConversationState. No matter how long the user takes to respond, your WaterfallDialog will stay at the top of the stack and it will remember exactly what step it was on. Assuming your user comes back to the same conversation, then it will pick right up where it left off.
Given that, the fact that you are asking this question leads me to believe that perhaps you are using WebChat which doesn't maintain the same conversationId across page loads unless you set that up yourself. If that's the case, then I would suggest you ask another question about how to do that if you can't figure out how since that's a separate issue from the dialog state being persisted correctly.
Edit: Drew's answer is correct, but mine provides another potential solution. You can find more info here: Managing State. In particular:
User state is available in any turn that the bot is conversing with
that user on that channel, regardless of the conversation Conversation
state is available in any turn in a specific conversation, regardless
of user (i.e. group conversations) Private conversation state is
scoped to both the specific conversation and to that specific user
Tip
Both user and conversation state are scoped by channel. The same
person using different channels to access your bot appears as
different users, one for each channel, and each with a distinct user
state.
Additional Solution
This solution is best for if you're able to specify the from Id, but cannot ensure that conversation Id remains the same (see below, under Gotchas).
You could save what step the user is on in their UserState.
BasicBot
BasicBot does this with its GreetingState class.
From its GreetingDialog:
In the first step, it initializes the GreetingState, which tracks how far along in the dialog the user is by seeing what user variables have already been set:
private async Task<DialogTurnResult> InitializeStateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var greetingState = await UserProfileAccessor.GetAsync(stepContext.Context, () => null);
if (greetingState == null)
{
var greetingStateOpt = stepContext.Options as GreetingState;
if (greetingStateOpt != null)
{
await UserProfileAccessor.SetAsync(stepContext.Context, greetingStateOpt);
}
else
{
await UserProfileAccessor.SetAsync(stepContext.Context, new GreetingState());
}
}
return await stepContext.NextAsync();
}
And then in each step, it loads the GreetingState:
var greetingState = await UserProfileAccessor.GetAsync(stepContext.Context);
And checks to see if the step has already been completed with something like:
if (greetingState != null && !string.IsNullOrWhiteSpace(greetingState.Name) && !string.IsNullOrWhiteSpace(greetingState.City))
If there's no greetingState or .Name or .City exists, it prompts for them, and if they are already filled out, it moves on with:
return await stepContext.NextAsync();
At each step, it saves to the GreetingState with something like:
greetingState.Name = char.ToUpper(lowerCaseName[0]) + lowerCaseName.Substring(1);
await UserProfileAccessor.SetAsync(stepContext.Context, greetingState);
Simplifying for your use case
For you, if you don't need to save user information, you could create a simple Step class:
{
/// <summary>
/// User state properties for Waterfall Step.
/// </summary>
public class Step
{
public string StepNumber { get; set; }
}
}
Make the first step of your WaterfallDialog:
private async Task<DialogTurnResult> InitializeStateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var StepState = await UserProfileAccessor.GetAsync(stepContext.Context, () => null);
if (StepState == null)
{
var StepStateOpt = stepContext.Options as StepState;
if (StepStateOpt != null)
{
await UserProfileAccessor.SetAsync(stepContext.Context, StepStateOpt );
}
else
{
await UserProfileAccessor.SetAsync(stepContext.Context, new StepState());
}
}
return await stepContext.NextAsync();
}
On each step, load the current Step:
var stepState = await UserProfileAccessor.GetAsync(stepContext.Context);
Check to see if they're already past the current step:
if (stepState.StepNumber <= 2)
{
// ...do stuff
// Save that user has completed step
stepState.StepNumber++;
await UserProfileAccessor.SetAsync(stepContext.Context, stepState);
}
else
{
return await stepContext.NextAsync();
}
Gotchas
A couple big things to watch out for:
The UserState only persists for the same from ID and channel ID. Make sure that the user that leaves in the middle of a waterfall has the same from ID when they re-enter it and that they re-enter it from the same channel. This isn't the default for the Emulator--in the Emulator, when a session is restarted, a new from ID is created. (Note: Consider from ID to be synonymous with User ID. It just comes from Activity.From.Id)
The ConversationState only persists for the same conversation ID and channel ID. Persistence of the conversation ID within the channel varies by channel.
More info on the different IDs: ID fields in the Bot Framework.
Not sure how the create channel and create role isn't working inside the following code, towards the bottom. (EDIT: Nothing is sent to the console and nothing happens regardng the code. It is like it is entirely ignored.) This is a snippet from code that User A challenges User B. User B is messaged, alerting them that a challenge has been issued to them via a Private Message. If the challenge is accepted, I want the bot to 1)Make a role specifically for User A and User B named "User A vs User B" 2) take User A and User B and put them both into that new role and 3) Make a battlefield named "User A vs User B" inside a specific category inside the server the bot is on.
I am unsure if the problem lies in how the bot is trying to make the role and channel in a sever while the bot is talking to the user in a private message instead of on the server. I thought putting the "server" variable as the server ID would help but it doesn't seem to do anything after the accept message.
// Awaits reply from user
if (message.channel.id === '541736552582086656') return target.send("Do you accept the challenge? Please reply with 'accept' or 'deny'.")
.then((newmsg) => {
newmsg.channel.awaitMessages(response => response.content, {
max: 1,
time: 150000,
errors: ['time'],
}).then((collected) => {
// Grabs the first (and only) message from the collection.
const reply = collected.first();
if (reply.content === 'accept'){
reply.channel.send(`You have ***accepted *** the challenge from ${challenger}. Please wait while your battlefield is made...`);
message.author.send(`${target} has accepted your challenge! Please wait while the channel is made for your brawl...`)
/// Problems start here
function createChannel(message){
var server = "SERVER ID";
var name = `${target} vs ${challenger}`;
message.guild.createRole({
role: {
name: `${target} vs ${challenger}`,
color: "#00fffa",
permissions: [] }
}).then(role => {
target.addRole(role, name)
challenger.addRole(role, name)
.catch(error => client.catch(error))
}).catch(error => client.catch(error))
server.createChannel(Name, name).then(
(channel) => {
channel.setParent("CATEGORY ID")
})
} // problems end here
} else if (reply.content === 'deny') {
reply.channel.send("You have ***denied *** the challenge.")
} else {
reply.channel.send("Your response wasn't valid.");
}
})
})
}
I have been wondering if I need to go about making the channel and role in a different way since it is trying to be made from a private message and not inside the server..
Thanks for any and all help! I also apologize if I'm using stack overflow too much for problems like this... You guys are great at helping me see different ways to do things and what I'm doing wrong, so I am learning, but I don't want to feel like I'm abusing it too much.
I think the problem is the fact that you create a function called createChannel with the code to create a rol and channel, but you never call said function.
You can either call the function after you've declared it or (which is in my opinion better) you can remove the following lines
function createChannel(message){
} // problems end here
If my bot asks different questions and if the user answers each of them, how do I find out which answer relates to which question. There is a field called metadata that you can attach to the sendTextMessage API but when the user responds, this metadata comes in as undefined. Do you guys use any node-cache for tracking state or an FSM such as machina.js? How can I best figure out at what of the conversation we are currently stuck in?
When your app receives a message, there's no payload or metadata associated with it. This is as opposed to a quick-reply or post-back which can have a payload. The only way to associate a response with a question this is to manually track the conversation state in your app as suggested by #anshuman-dhamoon
To do this, it's best to maintain a state for each user, as well as the next state for each state.
// optionally store this in a database
const users = {}
// an object of state constants
const states = {
question1: 'question1',
question2: 'question2',
closing: 'closing',
}
// mapping of each to state to the message associated with each state
const messages = {
[states.question1]: 'How are you today?',
[states.question2]: 'Where are you from?',
[states.closing]: 'That\'s cool. It\'s nice to meet you!',
}
// mapping of each state to the next state
const nextStates = {
[states.question1]: states.question2,
[states.question2]: states.closing,
}
const receivedMessage = (event) => {
// keep track of each user by their senderId
const senderId = event.sender.id
if (!users[senderId].currentState){
// set the initial state
users[senderId].currentState = states.question1
} else {
// store the answer and update the state
users[senderId][users[senderId].currentState] = event.message.text
users[senderId].currentState = nextStates[users[senderId.currentState]]
}
// send a message to the user via the Messenger API
sendTextMessage(senderId, messages[users[senderId].currentState])
}
Note If you wanted, you can even make the values of nextStates into callable functions that take the answer of the current state and branch off into different conversation flows by passing the user to a different state depending on his/her response.
As per my knowledge,in Facebook chatbot you can send data from user to chatbot just by setting payload from postback buttons as they have given in API reference.
And chatbot won't store your session or any states/flags.you can set status or flags or arrays but all will be lost when you update your application or restart your server.
so,if you really want to set status you should use database for that.and senderID will remain same everytime so you can handle data from database by that particular id for particular user.
For more details checkout technical referance here.
I hope this will help you.if so,kindly mark it as an answer.
You can have a status code in your code, to keep track of where the user conversation with the bot is.
For eg. if you have 10 questions, keep statuscode = 0 initially, and ask the first question. When you receive a message to your webhook, check if statuscode==0, and store that user message as a response to your first question. Then increment statusCode=1 and ask the next question.
You can have multiple flags and statusCodes to deal with different conversation flows.
I'm running into this issue myself. Though it hasn't been mentioned at all in their documentation, I don't think attaching an in-memory database is out of the question. It seems that the user_id is the same regardless of when the conversation is initiated.
Making an API call every time the user rejoins the session would probably slow down the performance of the bot. Also, I noticed that you can't really construct a "pseudo-distributed database" by using the metadata key in the API if that is what you were suggesting. The metadata tag can be sent from Server -> Client (Messenger) but not from Client -> Server from what the documentation says.
I've spent some time working with this. The best solution is to use a database to track the user's conversation flow. The POST object contains the senders ID. You can use this ID to make a row in the database in which you'd definitely need to store this ID, any answers to the questions, and a field to keep track of of which step in the conversation.
Then you can use if statements in your code to return the proper responses. Some example code below:
if( $currentStep == '1' ){
// Ask Next Question
$message_to_reply = "Thank you! What's your name?";
$message_to_reply = '"text":"'.$message_to_reply.'"';
} elseif( $currentStep == '2' ){
// Ask Next Question
$message_to_reply = "Thank you! What's your email address?";
$message_to_reply = '"text":"'.$message_to_reply.'"';
} elseif( $currentStep == '3' ){
// Ask Next Question
$message_to_reply = "Thank you! What's your address?";
$message_to_reply = '"text":"'.$message_to_reply.'"';
}