Script node

📘

Don't have an Instabot account yet?

Start your free trial today!

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:

  1. Script node methods
  1. Examples and use-cases

📘

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

📘

More information about 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:

// 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
233

display the output of a 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

868