I'm creating a chatbot in Facebook messenger. I want the chatbot to be able to support several users, obviously. The first time a user accesses the chatbot they are supposed to create a user account. The process for creating the user account is as follows:
- When an unregistered user interacts with the chatbot, it gets the user's first name from the Facebook Graph API, and then says "Hello (first name)". It explains that the user needs to create a user profile.
- The chatbot asks the user what they want to be called. The options are either (first name) or "Something else"
- If the user chooses (first name), the chatbot goes straight to number (5).
- If the user chooses "Something else", the chatbot asks what they want to be called, and they type it in.
- The chatbot asks the user to select their gender (male, female, other, prefer not to say)
- The chatbot asks the user to select their age (from a selection of ranges e.g. 18-25, 26-30 etc.)
- Once the profile has been created the chatbot tells the user that in future if they want to talk to the chatbot, they can just say "hi" or "hello".
- The chatbot then asks the user what they want to do now and presents the main functions.
Here is what is supposed to happen normally when any user interacts with the chatbot:
- User interacts with chatbot.
- Chatbot checks to see if user is loaded into memory
- If user is loaded into memory, chatbot interacts with user as normal
- If user is not loaded, the chatbot tries to get the user from a database
- If the user exists in the database, the user's profile is loaded into memory and the chatbot interacts with the user as normal
- If the user doesn't exist in the database, they have never used the chatbot before, so they have to create a user profile.
The problem is, this ISN'T happening. When more than one user tries to set up their profile at the same time, the following is happening (let's assume two users - "user A" and "user B":
For person A:
The chatbot gets the user's first name, and says "Hello (person A)", and explains that they need to create a user profile.
The chatbot then asks the user what they want to be called. The options it gives are either (person B) or "Something else".
No matter what the user chooses, they cannot progress any further, because the chatbot keeps repeating stage 2.
So, my code:
User data is stored in a "data" object in a file called user.js:
data: {
fbID: undefined, // senderID (user's Facebook PSID)
firstName: undefined, // User's first name (only used during user profile creation)
userID: undefined, // User ID (i.e. first name or nickname)
gender: undefined, // Gender
age: undefined, // Age
}
User.js also contains a "reload" function that is used to reload a user. You'll see how this works later:
reload: function(data) {
self.data.fbID = data.fbid; /** @namespace data.fbid */
self.data.userID = data.userid; /** @namespace data.userid */
self.data.gender = data.gender; /** @namespace data.gender */
self.data.age = data.age; /** @namespace data.age */
console.log("Profile reloaded for user '%s'.", self.data.fbID);
},
When the user sends a message to the chatbot, this triggers a webhook event, within app.js:
// Accept POST requests to webhook
app.post('/webhook/', function (req, res) {
// Parse the request body from the POST
let body = req.body;
// Check the webhook event is from a Page subscription
if (body.object === 'page') {
body.entry.forEach(function(entry) { /** @namespace body.entry */
// Gets the body of the webhook event
let webhookEvent = entry.messaging[0]; /** @param entry.messaging[] */
// Get sender ID
let senderID = webhookEvent.sender.id;
// Set session ID
session.set(senderID);
// Handle webhook event
handler.handleWebhookEvent(senderID, webhookEvent);
});
// Return a '200 OK' response to all events
res.status(200).send('EVENT_RECEIVED');
} else {
// Return a '404 Not Found' if event is not from a page subscription
res.sendStatus(404);
}
});
The webhook event is passed to handler.js, where it is handled by the handleWebhookEvent function:
handleWebhookEvent: function(senderID, webhookEvent) {
let greetingText, delay;
let handleEvent = function() {
/**
* @param webhookEvent
* @param webhookEvent.message
* @param webhookEvent.optin
* @param webhookEvent.delivery
* @param webhookEvent.postback
* @param webhookEvent.read
* @param webhookEvent.account_linking
* */
if (webhookEvent.optin) {
handleOptIn(webhookEvent);
} else if (webhookEvent.message) {
handleMessage(senderID, webhookEvent.message);
} else if (webhookEvent.delivery) {
handleDelivery(webhookEvent);
} else if (webhookEvent.postback) {
handlePostback(senderID, webhookEvent.postback);
} else if (webhookEvent.read) {
handleRead(webhookEvent);
} else if (webhookEvent.account_linking) {
handleAccountLink(senderID, webhookEvent.account_linking);
} else {
console.log("Webhook received unknown event: ", webhookEvent);
}
};
if (common.isDefined(user.data.fbID) && user.data.fbID === senderID) {
// User is loaded - handle events as normal
console.log("User '%s' is loaded.", senderID);
//logging.logEvent(senderID, webhookEvent, function() {
handleEvent();
//});
} else { // User is not loaded
getUser(senderID, function(result) { // First try and load user
if (common.isDefined(result)) { // Result returned
user.reload(result);
//logging.logEvent(senderID, webhookEvent, function() {
handleEvent();
//});
} else {
// User was not found in database. We need to
create a new profile for the user
common.getFBUser(senderID, function(name) {
console.log("User '%s' was not found in database.", senderID);
user.data.fbID = senderID;
user.data.firstName = name; // Stores user's first name
in the user object
greetingText = strings.initialGreeting.replace("%USER",
user.data.firstName);
delay = common.setDelay(greetingText);
messaging.sendTextMessage(senderID, greetingText);
setTimeout(function() {
// Pause before kicking off the 'uprofile' intent
dialogflow.sendRequest(session.getIDs(), senderID,
strings.createUser);
}, delay);
});
}
});
}
}
I think the issue here is that I thought that by using an object within user.js, the chatbot would just create multiple instances of this object, one for each user. Clearly I'm mistaken. What would be the best way to create multiple instances and thus prevent a situation where the user data object is being shared by multiple users? Or should I just dispense with objects entirely and simply read from the database each time? I want to avoid doing this as it will require a lot of reads/writes.