Skip to content

Plugins and Functions

Marius Ursache edited this page Aug 8, 2015 · 10 revisions

Functions

This is where things get interesting. Replies can execute a custom function where they have access to the entire message object, the user object and other resources like the fact databases. You can also pass parameters to the functions from captured input parts:

+ * weather in <name>?
- ^getWeather(<cap1>)

Functions are stored in plugins (sets of related functions), which are imported from the plugins folder at load time and are async Node.js functions:

exports.getWeather = function(city, cb) {
  cb(null, "It is probably sunny in " + city);
}

Multiple functions

You can have more than one function in a reply, so this is also valid:

+ It is ^callFunction1() and ^callFunction2()

Nesting functions

The results of the callback will replace the function name. You can even nest functions within callback replies, so this is valid too.

+ ^nestedAFunction()
exports.nestedAFunction = function(cb) {
  cb(null, "We ^nestedBFunction()");
}
exports.nestedBFunction = function(cb) {
  cb(null, "are done");
}

The final reply will be We are done. The above example is contrived, remember you can also require the module and call the function directly from within the plugin code. This is also true for other NPM modules.

There will be an entire new section on custom functions, but for now, explore the inside the plugins folder and review the test suite to get a better picture of some of the things possible.

Function examples

These functions are shipped with SuperScript:

  • ^getWeather(location) will do this
  • time.js plugin has these functions for today’s date:
    • ^getSeason() returns season (spring, summer, fall, winter)
    • ^getDate() returns date (3, January 2015)
    • ^getDateTomorrow() returns tommorrow's date (4, January 2015)
    • ^getTime() returns "The time is 9:00"
    • ^getTimeOfDay() returns "morning", "afternoon" or "night"
    • ^getDayOfWeek() returns day of week (Monday, Tuesday)
    • ^getMonth() returns month (January)
  • math.js plugin allows performing math functions
    • ^evaluateExpression(parameter) returns "I think it is (answer)" or "What do I look like, a computer?" if not valid.
    • ^numToRoman(parameter) returns "I think it is (the roman number)"
    • ^numToHex(parameter) returns "I think it is (hex equivalent)"
    • ^numToBinary(parameter) returns "I think it is (binary equivalent)"
    • ^numMissing(parameters) returns "I think it is (number)" for a trigger like "what number is missing from 1, 3, 5, 7"
    • ^numSequence(parameters) returns "I think it is N" to find a number missing from an arithmetic or geometric sequence

Filters

Filters are funtions used to filter out replies or triggers from being used. They are like using conditional logic in your app, and are actually functions (plugins) which return a TRUE or FALSE value. In the SuperScript Editor, filters have their own text box with an auto complete.

+ i am *1
- {^functionX(true)}  Yes, you are <cap>.
- {^functionX(false)} You’re lying, you’re not <cap>!

In this example the ^functionX() is evaluated when we are making the decision to pick a reply. Lets see a real example based on one of the Loebner questions.

+ my name is <name>
- {^hasName(false)} ^save(name,<cap1>) Nice to meet you, <cap1>!
- {^hasName(true)}  I know, you already told me your name!

^hasName checks to see if we know the user’s name, and if we don’t, (false) then we save the name using the ^save, then say Nice to meet you, Name!. Then when we hit this trigger again, ^hasName(true) will be filtered in, and reply with a cheeky remark, I know, you already told me your name!.

Filter functions are not just for replies, and they can be applied to triggers too:

+ I like * 
- I like <cap1>, too!

The syntax is identical to filters in replies, this function ^not(fish) will return false if the message contains the word fish.

Out of bound data

We can pass extra metadata back to the client, using a specific function, ^addMessageProp(key,value). The reply object can have extra properties added on from any reply.

+ can you (text/sms) me the name of the place
- {^hasNumber(false)} Can I have your phone number, please?
- {^hasNumber(true)}  ^addMessageProp(medium,txt) The Canteen

In the above example, if the user asks Can you text me the name of the place?, the system will check if the system has the user’s phone number (using the ^hasNumber filter). If the user does not have a phone number, the reply will be Can I have your phone number, please? If the user has a phone number, the generated reply object looks like this:

{ 
  string: "34345 Old Yale Rd.", 
  medium: "txt", 
  createdAt: "..." 
}

and is sent to user’s mobile phone number as text (if the system has this defined). The function can be used from any reply, or you can augment the message props variable directly from within any custom function: this.message.props['key'] = value;

Another example can be found here, and returns the color as a HEX RGB value, updating the page background color. You can think of this channel as anything non-text or non-verbal (perhaps an avatar emoji or something else to represent mood etc.).

Another frequent case can be injecting HTML content in the reply:

+ show * car
- Here’s my car: ^addMessageProp(img,http://www.exoticspotter.com/images/19/18281.jpg)

Rendering these props should be addressed by the client layer (web interface, mobile app, SMS etc.)

Topic functions

^respond()

The ^respond(topic_name) function is used in the reply it takes the current input and check it against the selected topic more matches. Once it gets to the end of the topic it will unwind its way back and continue checking in the normal path.

> topic random

  + * i *
  - ^respond(i_topic)

  + * you *
  - ^respond(you_topic)

< topic

> topic i_topic

  + i am not *
  - No, you aren’t!

  + i am *1
  - Yes, you are!

< topic

> topic you_topic

  + you are not *
  - No, I am not!

  + you are *1
  - Yes, I am!

< topic

When the user says I am not a robot! the system will match it with the * i * rule and look first for a reply in gambits in the i_topic topic

###^topicRedirect(topic_name,trigger)

^topicRedirect(topic,trigger) is used in the reply and is very similar to reply redirects except they redirect you to a reply in a specific topic.

> topic random

  + say something random
  - ^topicRedirect(something_random,__say__)

< topic

> topic something_random

  + __say__
  - Wind on the face is not good for you.
  - I watched Mr. Dressup as a kid.

< topic

Writing plugins

What is this

Custom functions are called in their own scope. This is done to give you control over most of the important bits of the system.

this.message

We expose the entire message object which is broken down 12 different ways and includes all the words in raw and lemma form, all the parts-of-speech and even compound 'things' like entities and dates.

this.user

The user who sent the message as we know them. This will allow us to see the past messages in their history, how many conversation replies we have had, and when the conversation started.

We also have direct access to the user’s memory.

this.user.memory
this.user.memory.db

Also, the entire message and reply history from the user (an array of past utterances and replies can accessed using

this.user.__history__

From the user memory you can query a sub-level graph DB for known facts the user may have shared or save new ones.

this.botfacts
this.botfacts.db 

This is identical to the sub-level graph for the user except it contains only things about the bot and can be loaded on init.

this.facts
this.facts.db

This is similar to the users sub-level graph except it contains all the global facts.

this.topicSystem

We expose the topic system to the plugins in case you want to create a new topic reply or trigger dynamically.

Because you are in the fun wild world of Node.js you can also install any library from NPM and use it, and since the plugs are async, you can even make calls to Databases, API's or other remote web services.

Making a custom function (or object) available from within a plugin

For this you just need to pass it into the bot options, when creating the new superscript bot object. Here is an example:

var superscript = require("superscript");

var options = {};
options['factSystem'] = factSystem;
options['mongoose'] = mongoose;
options['scope'] = {
  'customFunction': function(a, b) { return a + b }
}

new superscript(options, function(err, bot) {
   // bot stuff, request, reply
});

The keyname to use is 'scope' key, this will mixin to the plugins. Then from your custom plugin you have the function available. i.e..: this.customFunction()

This is useful, if you want some resource to be available in all the plugins and want to get it initialized at the start of the bot server. Another way to make some resource available in all the plugins is to declare and initialize it in one of the plugins and export it as a module, which can be require()'ed by other plugins by referencing to that plugin file. But that will introduce dependencies among plugins, which will be hard to document and they loose their modularity. Here is the line that calls the plugin: https://github.com/silentrob/superscript/blob/master/lib/processtags.js#L51-L52

Clone this wiki locally