Skip to content

Statements

say

Sends the given message to the user.

say "Hello! Great that you're here."

Next to normal text you can also any UTF-8 characters as well as emoji

  say "😂 Very funny!"

You can also use string interpolation to create dynamic messages:

time_of_day = "morning"
say "Good #{time_of_day}!"

Will say "Good morning!".

Message formatting

URLs in text are automatically linked, and newline characters (\n) can be used to create a line break:

  say "Click this link:\n https://nu.nl"

When using web-based chat, you can use Markdown formatting for bold, italic, et cetera.

say "** bold text **"
say "* italic *"
say "`monospace text`"
say "This is a [link](https://nu.nl/)"

Message formatting for voice actions

On the voice channels, Google Assistant and Alexa, we convert markdown tags to SSML speech output.

The following conversions take place:

**emphasized speech**<emphasis level="strong">emphasized speech</emphasis>

**moderate speech**<emphasis level="moderate">moderate speech</emphasis>

_reduced speech_<emphasis level="reduced">reduced speech</emphasis>

Furthermore, certain punctuations are converted to pauses in the speech. The use of a , corresponds to a pause of 200ms; ... and correspond to a pause of 500ms.

Options

class:

Use the class: option to specify a specific presentation for the message. The class attribute will become part of the rendered HTML in the chat bubble. Combined with custom CSS rules, the class: option can be used to create a customized presentation for certain messages.

For example, this sets the class "large" on the message:

say "string", class: "large"

The predefined classes in Web client are:

  • large - font size 2x as large as normal
  • system - gray, smaller text, no bubble around text

as:

To let the bot impersonate another user, it is possible to specify the as: option, with a given user object.

The object needs to have at least a user_id and a first_name attribute. profile_picture can be used to specify the avatar.

person = [user_id: "arjan", first_name: "Arjan", profile_picture: "http://..."]
say "Hello my name is Arjan", as: person

Messages like this will still appear on the left side of the chat window, and thus are meant for simulating users other than the current user.

as: :user

To let the bot impersonate the current user:

say "Hello from yourself", as: :user

These messages will appear on the right side of the chat window.

typing_indicator:

To specify how long (in seconds) the typing indicator should show before displaying the message in the say statement.

say "This takes long", typing_indicator: 20

The given value is specified in seconds.

delay:

To specify how long the delay should be before showing the message:

say "one ", delay: 0
say "two", delay: 0
say "three", delay: 0

Note that with delay: 0 you will still see the typing indicator. However, delay: 0 can be combined with typing_indicator: 0 if you require so.

ask

ask presents a text message to the user, and wait for the response from the user. This response can be stored in a variable. When no variable name is given, the response of the user is stored in the variable named answer.

ask "how old are you?"
say answer

When used in an assignment, it is possible to store the response in another variable, like in the next example:

age = ask "how old are you?"
say age

Options

expecting:

When the expecting: option is set with a list of strings, entities or intents, the bot will repeat the question until the message is matched against the value(s) in the expecting option. This is used for validation purposes, when you need the user to type something specific to continue. The items in the expecting will also be displayed as quick-replies if they have a label attribute. If a media file is expected, expecting can be used to

The most simple case for using expecting is to present a list of quick reply buttons below the question, like this:

ask "Did you laugh?", expecting: ["Yes", "No"]

The bot does not continue until the user literally types the string "Yes" or "No". So this form does not use any form of fuzziness or intent matching.

To be a bit more "loose" in what is accepted, you can define Yes and No intents (either in the code or in the Intent Manager:

# define intents which use fuzzy string matching.
@yes intent(match: "yes|yep|sure|indeed", label: "Yes")
@no intent(match: "no|nope", label: "No")

dialog main do
  ask "Did you laugh?", expecting: [@yes, @no]
  branch do
    answer == @yes ->
      say "You laughed!"
    answer == @no ->
      say "Apparently you did not laugh."
  end
end

In above example, two intents are defined, with a label. These labels will be shown when the intents are given to the expecting clause of the ask.

expecting can also be used to present a specialized input widget or form control to the user.

  ask "Please choose an item",
    expecting: input_method("item_picker", items: ["One", "Two"])

expecting can also be used to extract an entity from the user using Duckling entity, for instance, to ask for a date:

  ask "When do you need the package?",
    expecting: entity(duckling: "time")

In above example, the answer.value variable will contain a valid ISO date based on what the user says ("tomorrow", etc).

Some other builtin validators for expecting are the following:

  • :file
  • :image
  • :location
  • :video
  • :text
  • :email
# Expect an image from the user
ask "Please upload a selfie", expecting: :image

# Match on any text input (same as when you omitted 'expecting')
ask "Where are you currently?", expecting: :text

# E-mail regular expression
ask "What is your email?", expecting: :email

# Trigger a file upload
ask "Which file do you want to upload?", expecting: :file

# Trigger a location picker
ask "Where are you currently?", expecting: :location

The built-in validators can also be combined in a list as follows:

# Ask the user to send some information about themselves
ask "Can you send me a picture or your location?", expecting: [:image, :location]

This will show both input widgets to the user.

help_dialog:

The help dialog is invoked when a user enters something that does not match the expected input:

dialog asking do
  ask "Did you laugh?", expecting: [@yes, @no], help_dialog: help
end

dialog help do
  say "You said: #{message}."
  say "Please answer yes or no instead."
end

The variable message is available in the help_dialog which contains the last utterance of the user.

An alternative to help_dialog is to define an inner dialog called __unknown__ to catch anything that does not match the expectation:

dialog asking do
  dialog __unknown__ do
    say "Please answer yes or no."
  end

  ask "Did you laugh?", expecting: [@yes, @no]
end

This is usually clearer than the help_dialog option and also allows you to give a more fine-grained reply.

quick_replies:

Quick-replies provide the user with answers to choose from. However, these options are mere hints, they are not used for validation unless specified with the expecting option.

# Only the quick-replies
ask "Did you laugh?", quick_replies: ["Yes", "No"]

# `quick-replies` with expecting as validation
ask "Did you laugh?", quick_replies: ["yep", "nah"], expecting: [@yes, @no]

# The `quick_replies:` are shown but `expecting:` is matched:
ask "What's your email?", quick_replies: ["no thanks"], expecting: [@email, @no]

It is possible to combine expecting: and quick_replies:; in that case, the given quick_replies are used but the validation is done on the expecting clause.

Small icon-sized images can be embedded in the quick replies like this:

ask "Please choose your option", quick_replies: [
  [title: "option 1", image_url: "http://via.placeholder.com/40x40"],
  [title: "option 2", image_url: "http://via.placeholder.com/40x40"] ]

When using specification method, the following options are available: - title:, the text shown to the user, - image_url:, the url of an image to use,

quick_replies_mode:

It is possible to let the quick replies be different every time the ask is displayed, while the ask is being repeated. This is typically used in a prompt to vary the quick replies slightly each time.

quick_replies_mode: :static — the default; just show the same quick replies each time.

quick_replies_mode: :cycle — each time the ask returns, the quick replies get cycled; the one that was the first in the previous prompt is now the last.

quick_replies_mode: :exhaust — each time the ask returns, the quick replies that was clicked last will have disappeared from the quick replies.

append_quick_replies:

An extra list of quick replies which are appended onto the normal quick replies. These quick replies do not participate in the :cycle and :exhaust quick replies modes.

timeout:

Sometimes, the user might not want to respond or forgets to respond to your question. For these cases you can add a timeout to the ask, so that the script continues after a short while. To prevent a script from "hanging", you can let the ask expire by supplying a timeout value which is then used as the answer.

@ask_timeout 30

# the ask dialog now overrides the timeout set as a constant
ask "How are you?", timeout: 4, timeout_value: :yes, expecting: [@yes, @no]

if answer == :yes do
  # this ask will have a timeout of 30 seconds 
  ask "..."
end

It is also possible to trigger a dialog on a timeout. E.g. for asking the user to stop after being idle.

ask "How are you?", timeout: 4, timeout_dialog: ask_for_stop

dialog ask_for_stop do
  say "From custom timeout dialog"
end

ask "How are you?", timeout: 4

dialog __ask_timeout__ do
  say "Hey, wake up!"
  ask "Do you want to continue?"
end

Note, that when using ask from within a __ask_timeout__ dialog (without a timeout value), it will not trigger a new ask timeout dialog, but that ask will block infinitely.

To set a global timeout on all asks, use the @ask_timeout constant.

show

show is the generic way of showing any media, other than plain text, to the user. For example, the following shows an image:

show image "http://via.placeholder.com/150x150"

See the UI elements section to see all the kinds of media that can be presented to the user.

prompt

A prompt is a special form of ask. It presents an automatic quick-replies menu based on its labeled inner dialogs. Paired with the continue statement, the prompt construct is an easy way to create an automatic navigational structure in your bot.

dialog simple_prompt do

  dialog label: "Tell a joke" do
    say "Here comes the joke…"
  end

  dialog label: "Order a pizza" do
    say "Please wait while ordering…"
  end

  dialog label: "Stop" do
    say "Alright"
    continue
  end

  prompt "What would you like to do?"

  say "See you next time"
end

The inner dialogs can be combined with intent trigger: arguments to make the prompt matching more fuzzy.

Using the continue statement in an inner dialog will cause the interpreter to continue the execution right after the last prompt statement that was encountered. In the example above, this is used to provide the "stop" option from the prompt.

invoke

Invoke is used to switch (temporarily) to a different dialog. It can be compared to a function call in a normal programming language. After the invoked dialog is finished, the original dialog will be continued after the invoke statement.

invoke mydialog

An invoke normally pushes the dialog on top of the current dialog stack, returning there after finishing.

Note that you can not use expressions in the name of the dialog, they always need to point to an existing dialog in one of the bot's scripts. The studio will show an error message when you have an invoke which points to a non-existing dialog.

Options

:replace

invoke mydialog, :replace

Giving the :replace option to invoke will cause it not to return to the calling dialog, it will replace the current dialog on the stack.

:reset

invoke mydialog, :reset

Giving the :reset option to invoke will cause the bot to stop after finishing the called dialog, it clears the dialog stack entirely.

message:

By invoking message:, you can internally simulate the sending of a user message.

It will cause the entered message (which can be an expression) to be handled by the global dialog message matching system, as if a user typed it. So in this example, the main dialog will output "Hi Pete!" when executed:

dialog main do
  invoke message: "my name is Pete"
end

dialog trigger: "pete" do
  say "Hi Pete!"
end

Invoke dialog with message trigger, resetting the stack

Resets the stack (just like invoke dialog, :reset would), and then performs the message matching just like `invoke message: "message" would.

dialog main do
  invoke reset: "my name is Pete"
end

stop

Abruptly stops the interaction with the bot, ending the chat session and the bot process. The next time the user talks, a new chat session is started.

dialog trigger: "bye" do
  say "Thank you for your time."
  stop
  say "This line is never executed"
end

reset

Stops the current dialog and clears the dialog stack. When a __root__ menu is defined, it is invoked.

dialog trigger: "bye" do
  say "Thank you for your time."
  reset
end

await

The await statement works similar to ask, except that no prompt or quick replies are shown while awaiting.

The first argument to await is the argument you would give to expecting: in an ask.

For example, the following waits for a valid email address and stores the global variable answer.

await @email

The following blocks until an event with the name "click" has been fired:

await event("click")

emit

The emit statement sends an event to the runtime that is executing the bot. These events can be used for instance to trigger backchannel events or trigger events on the website. Emits an event to the client, or to another chat process. An event consists of a named message (a string), plus an optional payload.

The event itself is a string; it can have an optional extra parameter which will be sent as payload.

dialog status_update do
  emit "status_update", text: "#{user.first_name} started using the bot"
end

dialog main do
  emit "start"
  say "Hello!"
  emit "end", [user_id: user.id]
end

See the documentation on Events and scheduling how events can be used on the platform.

pause

Will pause the execution of the dialog for the given amount of time, in seconds.

pause 3         # pause the dialog for 3 seconds

type

Shows the typing indicator for the given amount of time, in seconds.

type 3          # pause for 3 seconds, while showing typing indicator

random

A block that will choose a line within the do..end and execute that once.

say random 10

returns a random number (int) between 0 and 10

say random [1,2,3,4]

returns a random element from the list

random do
  say "Hi!"
  say "Hello!"
  say "Howdy!"
end

once

With once you can say things 'only once' stepping down a statement on every invocation.

once do
  say "Hi!"
  say "Hi again!"
after
  say "We meet again"
end

This can be used to only say 'Hi' the first time and second time say 'Hi again' for instance. Every itteration once will select the next statement down. When there is an after present the statement after will be invoked on every successive itteration. Without after the once block will not do anything (when exhausted)

You can provide a name to the variable that is used to store the counter. This can be used to reset it so that the once block cycles instead of getting exhausted.

once i do
  say "Hi!"
  say "Hi again!"
after
  say "We meet again"
  dialog.once.i = nil # reset this once block to start again (cycle)
end

remember / forget

remember is the way to persist information for a user or conversation in the platform's builtin contacts database (the CRM). All information stored by remember is saved with the bot's user and can be viewed in the studio. In a next conversation, these variables will be set again when new session starts. This way, you only have to ask a user his name once, for example.

variable = 123
remember variable
forget variable

NOTE: variables can be deep 'object maps':

car.brand = "Volvo"
car.engine.hp = 200

remember car

This remembers the object car and its nested values.

tag / untag

Sets a tag or clears a tag on the current conversation. Tags are a handy way to organize conversations, they can be used for filtering and finding users and conversations in the studio.

tag "lead"
untag "lead"

Tags are stored on two places: for the current conversation, the tags are collected in the conversation.tags variable; at the same time, tags are also collected in the user.tags variable. The difference between these is that the user variable is shared between all the conversations that the user is involved in, while conversation.tags are local to the current conversation.

Do not manipulate the conversation.tags or user.tags directly, always use tag and untag.

To display the tag counts in the same graph in analytics (for funnel analysis) you can use a namespace:

tag "funnel:1-signup"

All tags in the "funnel" namespace will be shown in the Analyze seection of the studio, in a separate section. To order the tags according to the funnel steps its best to number them.

log

Prints a message, for debugging purposes:

log "This is a log message"

Log messages can contain any valid expression and are logged in the Console tab in the Botsquad studio, and in the conversation's history of the inbox.

if

Make a decision, based on an expression

if exits != "Yes" do
  say "Lets continue"
end

else is supported as well:

if exits != "Yes" do
  say "Lets continue"
else
  say "Ok lets quit"
end

task

A task is a construct which is comparable to a dialog. However, tasks are meant for non-interactive computations, therefore, no statements are allowed that perform user-level side effects, like say, ask, show.

Typically, tasks are executed when an event comes in:

task event: "ping" do
  counter = counter + 1
end

The advantage of using tasks over dialogs here is that tasks execute outside of the current dialog context. Therefore, the current dialog continues undisturbed, without any other effects (like __returning__). They are also much more performant than dialogs.

Named tasks

Tasks can be named, just like dialogs:

task calculate do
  counter = counter + 1
  if counter > 10 do
    emit "full"
  end
end

perform

Analogous to invoke, a task can be executed by using the perform statement:

perform calculate

Tasks do not have arguments. As the DSL has a global scope, any parameters that are required inside the task should be defined just before the perform statement.

branch

As an extended version of the if statement, branch allows you to switch over multiple conditions. As soon as the first condition matches, the corresponding block is invoked, while the other blocks are skipped.

ask "Do you want to continue?", expecting: ["Yes", "No", "Maybe"]
branch do
  answer == "No" ->
    say "Sorry to hear"
  answer == "Yes" ->
    say "Great!"
  answer == "Maybe" ->
    say "Make up your mind!"
end

buttons

buttons "What do you want to do next?" do
  "Open CNN" ->
    url "http://cnn.com"
  "Say hello" ->
    postback "say_hello"
end

NOTE: though buttons are similar to quick_replies they invoke dialogs via a postback event: rather than trigger: and are not displayed as user message in the conversation.

template

  template do
  "The hat shop 1" ->
    image_url "http://via.placeholder.com/150x150"
    subtitle "We've got the right hat for everyone."
    default_action do
      open "http://www.redhat.com"
    end

    buttons do
    "View Website" ->
      open "http://www.redhat.com"
    "Say hello" ->
      postback "postback"
    end

  "The hat shop 2" ->
    image_url "http://via.placeholder.com/150x150"
    subtitle "We've got the better hat for everyone."
    default_action do
      open "http://www.redhat.com"
    end

    buttons do
    "View Website" ->
      open "http://www.redhat.com"
    "Say hello" ->
      postback "postback"
    end

  # ...
  end

Operators

= (assignment)

variable = ask "whats your name?"
variable = "string"
variable = 1
variable = [1,2,3]
variable = first [1,2,3]

NOTE: all Bubblescript variables are global and mutable.

addition (+)

The + operator has superpowers.

  • It performs addition on numbers: 1 + 3 == 3
  • It concatenates strings: "a" + "b" == "ab"
  • It can glue lists together: [1] + [2, 3] == [1, 2, 3]
  • It can even glue lists together when one element is not a list: 1 + [2, 3] == [1, 2, 3]
  • It merges maps: %{"a" => 2} == %{"a" => 1} + %{"a" => 2}
  • When it encounters nil on either side of the +, it ignores it: [1, 2] + nil == [1, 2]

substraction (-)

The - operator is superpowered as well.

  • It performs regular substraction: 3 - 2 == 1
  • When encountering strings which are numbers, it converts them to numbers: "100" - "10" == 90
  • It can be used to remove elements from a list: [1, 2] - [1] == [2]
  • It can be used to remove substrings from strings: "paap" - "aa" == "pp"

In all other cases, it raises an error, for instance when you try to substract a number from a string.

boolean operators

The following operators are supported:

a && b    # AND
a || b    # OR
!a        # NOT
a == b    # EQUALS
a != b    # NOT EQUALS
a <= b    # IS SMALLER OR EQUAL TO
a >= b    # IS LARGER OR EQUAL TO
a <  b    # IS SMALLER
a >  b    # IS LARGER