Script node
Don't have an Instabot account yet?
The Script Node is an advanced node type that greatly extends the power of your bot by allowing you to insert and execute any JavaScript silently within the bot as the visitor is talking to the bot. See here for how to access and add a script node into your bot
Contents:
- 1a. Standard user-properties: getting and setting
- 1b. Custom user-properties: getting and setting
- 1c. Get user's response from a previous node
- 1d. Set the return result of the script node
- 1e. Pass data between multiple script nodes
- 1f. Get completed goals
- 1g. Build an array of values that you can later use to programmatically populate multiple-choice options
- 1h. Show an image
- 1i. Get chat id
- 1j. Properties substitutions
- 1k. Call webhook
- 1l. Subscribe to events
- 1m. Get list of passed nodes
- 1n. Trigger goal notification
- 1o. Execute background work during typing animation
- 1p. Other
- 2a. Get info from the current page
- 2b. GET request to a REST API
- 2c. POST request to a REST API
- 2d. Recommendation bot
- 2e. Send user down one bot-path if on mobile, a different both-path if on desktop
Script node requires Advanced tier
Using advanced functionality in Instabot such as the Script node requires your Instabot account to be on the Advanced tier or higher.
To upgrade to the Advanced tier, click here, or emails [email protected]!
1. Script node methods
These are the base-level methods that will let you get data in/out of the script node to manipulate. These methods form the basis to achieve beautifully complex behavior and logic in the bot - a few examples of this can be found in the examples and use-cases section
Make sure you always call
next();
when you want your script node execution to end - if you don't, the bot won't actually continue for the end-user - the bot will stall and look like it's just typing indefinitely
1a. Getting and setting standard user-properties
Get a standard property for this end-user
// === GET all standard properties for this end-user === //
var user = context.getUser();
console.log("User's name is: " + user.name);
// must call next() for the bot to continue
next();
/*
available fields in the user object
- description (string)
- email (string)
- firstLoginTime (datetime) - eg: "2017-12-20T18:43:07.62Z"
- firstName (string)
- friendlyName (string)
- fullName (string)
- ipAddress (string) - IP address of this end-user
- lastActivityDate (datetime) - eg: "2019-02-04T20:24:52.372Z"
- lastLoginTime (datetime) - eg: "2017-12-20T18:43:07.62Z"
- lastSeenDate (datetime) - eg: ""2019-02-04T20:24:11.973Z"
*/
Standard user properties are case sensitive - ie:
name != Name != NAME
To avoid error, make sure you check the case and spelling of that specific user-property in the custom user-properties management page
Set a standard property for this end-user
// === SET a standard property for this user === //
context.setStandardProperty('name', 'John Doe')
.then(function() {
// setting standard prop was successful
console.log("Standard property successfully set");
// must call next() for the bot to continue
next();
}
);
1b. Getting and setting custom user-properties
Get a custom user-property for this end-user
var props = context.getCustomProperties();
var customPropValue = props["customPropName"].value[0];
Custom user properties are case sensitive - ie:
utm_campaign != UTM_campaign != UTM_CAMPAIGN
To avoid error, make sure you check the case and spelling of that specific user-property in the custom user-properties management page
Set a custom user-property for this end-user
To set a custom user-property, that user-property must first be created in the user-property management page before setting it via the Script node
// === SET custom property for this user === //
var customPropertyKey = "utm_campaign";
var customPropertyValue = "My Campaign";
context.setCustomProperty(customPropertyKey, customPropertyValue)
.then(function() {
console.log("Custom property '" + customPropertyKey + "' successfully set to '" + customPropertyValue + "'");
// must call next() for the bot to continue
next();
}
);
// or set multiple custom properties
var customPropperties = {
"utm_campaign1": "My Campaign1",
"utm_campaign2": "My Campaign2",
};
context.setCustomProperties(customPropperties)
.then(function() {
// must call next() for the bot to continue
next();
}
);
1c. Get the user's answer to a specific bot node
Let's say you have a bot with a free-text node that asks the user what city they live in - the script node can fetch and ingest the user's answer from that node, then you can do anything you want with it (search an external database, etc)
When using this call with certain node types, they exhibit different behaviors - for example:
- Dialogflow node - access the full JSON response payload returned from detectIntent() call
// eg: fetch the user's answer to a bot node called "getcity"
var nodename = "get city";
var nodenameresponse = context.getResponseByNodeName(nodename).value;
next();
1d. Set the return result of the script node
Sometimes you may use a script node to calculate something behind the scenes, then use that calculated result somewhere else in the bot. To do this, you will use setResult()
- here is an example snippet that determines if the visitor is currently on a mobile device or not.
For example, you can use this snippet snippet to (see recipe here)
var user = context.getUser();
// true or false
var isMobile = user.requestInfo.device.isMobile;
context.setResult({ value: isMobile});
next();
setResult()
will not display the set value to the visitor. The set result of this call is only accessible from within the builder.If you want to display the output/result of the script node:
- a. call
setResult()
at the end of your script node- b. in the node you want to display the script node result, use the
@
syntax to reference the name of the script node
1e. Passing data between script nodes
// in script node 1:
var viewBag = context.getViewBag();
console.log(viewBag.someValue); // will display nothing
viewBag.someValue = 'something';
// in script node 2:
var viewBag = context.getViewBag();
console.log(viewBag.someValue); // will display 'something'
1f. Get completed goals for this user
var user = context.getUser();
var numgoalscompleted;
// check if goalCompletions obj exists
if (user.goalCompletions) {
if (user.goalCompletions.length) {
numgoalscompleted = user.goalCompletions.length;
console.log("this user has completed " + numgoalscompleted.toString() + " different goals");
for (i = 0; i < numgoalscompleted; i++) {
console.log("this user has completed the goal '" + user.goalCompletions[i].goal.name +
"' a total of " + (user.goalCompletions[i].count).toString() + " times");
}
}
} else {
// this visitor has not completed any goals
}
next();
1g. Build an array of values that you can later use to programmatically populate multiple-choice options
This is typically used to parse out specific values returned from an API, and pushing those values into a custom webhook so that you can programmatically populate multiple-choice options in a later node
var url = "https://jsonplaceholder.typicode.com/posts";
$.ajax({
type: "GET",
url: url,
contentType: "application/json; charset=utf-8",
success: function(data) {
var stuffFromApi = {
items: []
};
// parse values from the payload, and push them onto the customwebhook
for (i = 0; i < data.length; i++) {
stuffFromApi.items.push({
userId: data[i].userId,
title: data[i].title,
body: data[i].body
});
}
console.log("stuffFromApi = ", stuffFromApi);
// use this webhook to programmatically populate MC-options in a later node (https://docs.instabot.io/docs/webhooks#section-use-a-webhook-in-your-conversation)
context.setWebhookResult("MyCustomWebhook", stuffFromApi.items);
next();
},
error: function(err) {
console.log('error : ' + err);
next();
}
});
1h. Show an image
var url = "https://instabot.io/Content/images/home/home-2-min.png";
var container = $("<div>")
.addClass("message-image")
.css("background-image", "url('" + url + "')");
context.hideTyping();
context.buildUI(container);
context.showTyping();
next();
1i. Get chat id
context.getChatId()
may not always return value since it is created asynhcronously, so it is not recommended to use it at the beginning of the conversation
var chatId = context.getChatId();
next();
1j. Properties substitutions
If you need to replace Instabot's special syntax in some text you can use the following methods
var text = "Hi {FULLNAME},\
age custom property is {CUSTPROP:AGE},\
response to node with name '1. Free Text' is @1__Free_Text,\
text action is {Action:link|https://instabot.io|click here|_blank}";
text = context.substitutions.applySystemProperties(text);
text = context.substitutions.applyCustomProperties(text);
text = context.substitutions.applyUserResponses(text);
text = context.substitutions.applyTextActions(text);
next();
1k. Call webhook
context.callWebhook('My Webhook Name', { param1: 'some value 1', param2: 'some value 2' })
.then(function (result) {
// process result
next();
});
1l. Subscribe to events
context.emitter.on('conversation-started', function (data) {
var chatId = data.objectId;
});
context.emitter.on('user-request-sent', function (data) {
var request = data.request;
});
context.emitter.on('user-response-sent', function (data) {
var response = data.response;
});
context.emitter.on('widget-closed', function (data) {
var isClosedByUser = data.userAction;
});
next();
1m. Get list of passed nodes
var requests = context.getRequests();
requests.forEach(function (r) {
console.log(JSON.stringify(r.Request));
});
next();
1n. Trigger goal notification
If a goal is attached to the script node or plugin node notification can be triggered via script
context.api.processGoal().then(next);
1o. Execute background work during typing animation
context.executeBackgroundWork(function (d) {
// do some api request and then
d.resolve();
}).then(function () {
//after typing and api request
next();
});
1p. Other
var appInfo = context.appInfo;
/*
applicationName: string;
applicationId: number;
devCompanyId: number;
usePersistentBot: boolean;
*/
var node = context.request;
/*
objectId: number;
name: string;
messageText: string;
promptType: number;
*/
var settings = context.settings;
/*
TypingDelay: number;
CloseDelay: number;
*/
var conversation = context.getConversation();
/*
objectId: number;
name: string;
bot: { objectId: number };
type: number;
status: number;
*/
2. Examples and use-cases
Below are code samples for different use-cases you can accomplish using the script node.
2a. Get info from the current page or DOM
Sometimes there's runtime information in the URL of your bot launch that could be valuable to parse and insert programmatically into the bot content.
For example, let's say you run a real-estate site that provides visitors with condo listings, and your site's condo search results are formatted in a URL as: condos.com/search?city=miami&state=fl&brs=4
. Then, you can
- add a script node that contains regex to parse out the query parameters city, state and brs
- write each as custom user-properties
- greet your users in the bot by saying something like: looks like you're looking for a condo with 4 bedrooms in Miami, FL - I can help!
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
console.log("foo is: " + getParameterByName("foo"));
// make sure you call next() at the return of your script!!
next();
Or, let's say you want to grab an element on the actual DOM of the page. Here's an example that grabs the innerText value of the h1 tag on a page and writes it as a custom user-property.
var h1tag = document.getElementsByTagName('h1')[0].innerText;
// make sure you've previously created a custom user-property called 'h1tag' before you write to it
var customPropertyKey = "h1tag";
var customPropertyValue = h1tag;
context.setCustomProperty(customPropertyKey, customPropertyValue)
.then(function() {
// setting custom user-prop was successful
// must call next() for the bot to continue
next();
}
.fail(function() {
// setting custom user-prop not successful
next();
}.always(function() {
// setting custom user-prop don't care
next();
})));
2b. GET request to a REST API
// in this example, I am making a GET request to an ip2location API to do a lookup of this user's physical location based on their IP address
$.ajax({
type: 'GET',
url: "https://api.ipgeolocation.io/ipgeo/",
data: {
q: {
"urlKey1":"value1",
"urlKey2":"value2"
},
beforeSend: function(request) {
request.setRequestHeader("requestHeader1", "headerValue1");
}
}).done(function(result) {
if(!result[0]) {
// if the GET request returns a JSON object
console.log("GET result - JSON object: " + result[0]
}
next();
});
2c. POST request to a REST API
// in this example, I am creating a new entry in my database in restdb.io via a POST request
var props = context.getCustomProperties();
console.log("piece-of-info-1: " + props["piece-of-info-1"].value);
console.log("piece-of-info-2" + props["piece-of-info-2"].value);
console.log("piece-of-info-3" + props["piece-of-info-3"].value);
var user = context.getUser();
console.log(user.ipAddress);
$.ajax({
type: 'POST',
url: "https://12345.restdb.io/rest/my-cool-collection",
contentType: "application/json; charset=utf-8",
dataType: "json",
beforeSend: function(request) {
request.setRequestHeader("header1", "header1-value");
request.setRequestHeader("header2", "header2-value");
},
data: JSON.stringify({
"piece-of-info-1": props["piece-of-info-1"].value,
"piece-of-info-2": props["piece-of-info-2"].value,
"piece-of-info-3": props["piece-of-info-3"].value,
"ipaddress": user.ipAddress
})
}).done(function(result) {
console.log("POST result: " + JSON.stringify(result));
// make sure you call next() at the return of your script!!
next();
});
2d. Recommendation bot - calculate point-totals and make a recommendation based off of the user's answers
var props = context.getCustomProperties();
var useranswer;
var categoryPoints = [0, 0, 0, 0, 0, 0, 0];
var categories = ["recommendation-category-1", "recommendation-category-2", "3", "4", "5", "6", "7"];
// question 1
useranswer = props["question1"].value;
if (useranswer == "question1-answer1") {
categoryPoints[3] += 1;
} else if (useranswer == "question1-answer2") {
categoryPoints[0] += 1;
} else if (useranswer == "question1-answer3") {
categoryPoints[0] += 1;
} else {
console.log("something went wrong... wasn't able to match to a user answer for question 1");
}
// question 2
useranswer = props["question2"].value;
if (useranswer == "question2-answer1") {
categoryPoints[6] += 1;
} else if (useranswer == "question2-answer2") {
categoryPoints[5] += 1;
} else if (useranswer == "question2-answer3") {
categoryPoints[3] += 1;
} else {
console.log("something went wrong... wasn't able to match to a user answer for question 2");
}
// more question parsing
// ...
// ...
// tally the results
var highestTotalIndex = 0;
points.forEach(function(item, index, array) {
console.log(categories[index], item);
if (item > highestTotalIndex) {
highestTotalIndex = index;
}
});
console.log("category w/ the highest point total = ", categories[highestTotalIndex]);
context.setCustomProperty('CATEGORYREC', categories[highestTotalIndex])
.then(function() {
console.log("Custom property successfully set");
});
context.setCustomProperty('CATEGORYTOTAL', categoryPoints[highestTotalIndex])
.then(function() {
console.log("Custom property successfully set");
next();
});
2e. Send user down one bot-path if on mobile, a different both-path if on desktop
You can use the script node to determine if the user is currently on a mobile device or not, then send the user down a specific path if on mobile, and a different path if on desktop.
a. Add a script node that checks if user is on mobile or not
var user = context.getUser();
// true or false
var isMobile = user.requestInfo.device.isMobile;
context.setResult({ value: isMobile});
next();
b. Add a conditional logic node after the script node to send the user down a specific path if mobile, and a different path if not
Updated almost 2 years ago