Skip to content

Builtin functions

Expressions are used in places where you want to create dynamic content or perform calculations. They can occur as part of any Bubblescript statement. For instance, the argument to the say statement can contain an expression, to make it dynamic.

Consider this example:

name = "Pete"
say "Hello " + name + ", " + random(["how are you?", "what's up?", "how you doing?"])

This uses a variable (name), the string concatenation operator (+), and the random() function to generate a dynamic answer.

The following builtin functions are available to use in expressions:

String functions

capitalize(string)

Capitalizes the first word in the string

Examples:

iex> capitalize("this is a string")
"This is a string"

downcase(string)

Converts a string to lowercase

Examples:

iex> downcase("HOW ARE YOU?")
"how are you?"

html_entity_decode(string)

Decode HTML entities in a string.

html_entity_encode(string)

Encode HTML entities in a string.

pluralize(string)

Given a singular word, returns the plural version of it (according to English grammar)

Examples:

iex> pluralize("Dog")
"Dogs"

regex_replace(string, regex, replacement, options \\ [])

Replace matches of a regular expression in the given string with a replacement

Options:

  • flags: String with regex flags, defaults to "u" (unicode support). For case insensitive regexes, use "iu"

  • global: Boolean to check if regex needs to replace all occurrences or only the first one. Defaults to true.

Examples:

iex> regex_replace("hello to all the cats", "cat", "dog")
"hello to all the dogs"
iex> regex_replace("The cheese is 5 euros, eggs are 1 euro", "euros?", "pound")
"The cheese is 5 pound, eggs are 1 pound"

replace(string, pattern, replacement, options \\ [])

Replace string in the given string with a replacement. The pattern can be a string or a list of strings.

Examples:

iex> replace("abc", "a", "b")
"bbc"
iex> replace("abc", ["a", "b"], "")
"c"

singularize(string)

Given a plural word, returns the singular version of it (according to English grammar)

Examples:

iex> singularize("dogs")
"dog"

string(value)

Coerce the given value into a string value

trim(string)

Trim whitespace characters from both ends of a string.

Examples:

iex> trim("         slim me down        ")
"slim me down"

trim(string, what)

Trim given characters from both ends of a string.

Examples:

iex> trim("^^^Remove^^^", "^")
"Remove"

trim_leading(string)

Trim leading whitespace characters from a string.

Examples:

iex> trim_leading("      Leading")
"Leading"

trim_leading(string, what)

Trim given leading characters from a string

Examples:

iex> trim_leading("***Stars", "*")
"Stars"

trim_trailing(string)

Trim trailing whitespace characters from a string

Examples:

iex> trim_trailing("trailing   ")
"trailing"

trim_trailing(string, what)

Trim given trailing characters from a string

Examples:

iex> trim_trailing("trailing))))", ")")
"trailing"

truncate(value, length, append \\ "…")

Truncates a string down to the number of characters passed as the first parameter.

An appendinx, defailting to the ellipsis character(…), is appended to the truncated string. The final string length is at max length characters, including the appendix.

Examples:

iex> truncate("a very long string", 2)
"a…"

truncate_words(value, numwords, append \\ "…")

Truncates a string down to the number of words passed as the first parameter. An ellipsis (…) is appended to the truncated string.

Examples:

iex> truncate_words("Truncates a string down to the number of words passed", 3)
"Truncates a string…"
iex> truncate_words("Truncates a string down to the number of words passed", 3, ", read more")
"Truncates a string, read more"

upcase(string)

Converts a string to uppercase

Examples:

iex> upcase("am I shouting?")
"AM I SHOUTING?"

url_decode(string)

Decode escaped URL characters

Examples:

iex> url_decode("a%3Dvalue")
"a=value"

url_encode(var)

Converts any URL-unsafe characters in a string into percent-encoded characters. Note that url_encode will turn a space into a + sign instead of a percent-encoded character.

Examples:

iex> url_encode("var=this is a value")
"var%3Dthis+is+a+value"

url_escape(var)

Escapes any URL-unsafe characters in a string into percent-encoded characters, but leaves reserved characters alone (/, &, :, ?)

url_params(var)

Convert the given list of key/value pairs into a string which can be used in a URL query string.

Examples:

iex> url_params([page: 1, id: 2])
"page=1&id=2"

List (array) functions

difference(list1, list2)

Returns a list where the items in list2 are removed from list1.

Note that the resulting list will not be sorted.

Examples:

iex> difference([1, 2, 3], [2, 3])
[1]
iex> difference("abc", "ab")
"c"

filter(list, query)

Filter list of maps according to a MatchEngine query.

first(list)

Return the first item in a list

Examples:

iex> first([1, 2, 3])
1
iex> first("abc")
"a"

flatten(list)

Flatten a list using Elixir's List.flatten/1.

Examples:

iex> flatten([[1, 2], [3, 4]])
[1, 2, 3, 4]
iex> flatten([[1, 2], []])
[1, 2]
iex> flatten("abc")
["a", "b", "c"]

intersection(list1, list2)

Returns the set-intersection of two lists.

Note that the resulting list will not be sorted.

Examples:

iex> intersection([1, 2, 3], [2, 3]) 
[2, 3]
iex> intersection("abc", "ab")
"ab"

item(ets, index)

Return the n-th item from the given list (the first element is at index 0)

Examples:

iex> item([1, 2, 3, 4], 1)
2
iex> item([[1, 2], [3, 4]], 1)
[3, 4]
iex> item("abc", 0)
"a"

join(list, joiner)

Join the given list using a string.

Examples:

iex> join(["06", "121", "448", "66"], "-")
"06-121-448-66"
iex> join("abc", " ")
"a b c"

join(list, joiner, last_joiner)

Join the given list using a string.

The last_joiner string will be used to join the last two items in the list. This helps with joining strings in a "human readable" way:

Examples:

iex> "The candidates are: " + join(["John", "Mary", "Elsa"], ", ", " and ")
"The candidates are: John, Mary and Elsa"

join_and(input, opts \\ [])

Joins a list of items in the current users locale, or in the specified locale.

Given options are passed to join_localized.

Examples:

iex> join_and(["1", "2", "3"])
"1, 2, and 3"
iex> join_and("123")
"1, 2, and 3"

join_localized(list, opts \\ [])

Presents the list in a human localized to the language set by the locale option.

Options:

  • :locale - A locale string, default is "en".
  • :style - The style to join the list in. Defaults to :standard. Available: [:or, :or_narrow, :or_short, :standard, :standard_narrow, :standard_short, :unit, :unit_narrow, :unit_short]

Examples:

iex> join_localized([1, 2, 3, 4], locale: "en")
"1, 2, 3, and 4"
iex> join_localized(["John", "Mary", "Elsa"], locale: "fr")
"John, Mary et Elsa"
iex> join_localized(["John", "Mary", "Elsa"], locale: "fr", style: :or)
"John, Mary ou Elsa"
iex> join_localized(["John", "Mary", "Elsa"], locale: "en", style: :or)
"John, Mary, or Elsa"
iex> join_localized(["John", "Mary", "Elsa"], locale: "en", style: :unit)
"John, Mary, Elsa"
iex> join_localized(["John", "Mary", "Elsa"], locale: "en_GB.FORMAL", style: :unit)
"John, Mary, Elsa"
iex> join_localized("123", locale: "fr")
"1, 2 et 3"

join_or(input, opts \\ [])

Joins a list of items in the current users locale, or in the specified locale.

Given options are passed to join_localized.

This is a shorthand for join_localized(input, style: :or).

Examples:

iex> join_or(["1", "2", "3"])
"1, 2, or 3"
iex> join_or("123")
"1, 2, or 3"

keys(ets)

Return the keys of a map or keyword list.

When a plain list is given, returns a list with the list indexes ([0, 1, 2, ...]).

Raises an error on non list/map values.

Examples:

iex> keys(%{a: :b, b: :c})
[:a, :b]
iex> keys("abc")
[0, 1, 2]

last(list)

Return the last item from a list

Examples:

iex> last([1, 2, 3])
3
iex> last("abc")
"c"

pluck(list, keys)

Given a list of maps, return for each map the item with the given key.

Examples:

iex> pluck([%{name: "John", age: 42, job: "Designer"}, %{name: "Mary", age: 36, job: "Programmer"}], [:name, :job])
[%{name: "John", job: "Designer"}, %{name: "Mary", job: "Programmer"}]

score(list, query)

Score a list of maps according to a MatchEngine query.

shuffle(list)

Shuffle the given list, putting its contents in random order.

sort(enum, opts \\ [])

Sort a list.

Options:

  • :direction - either :asc or :desc
  • :field - when sorting a list of maps, specify which field in the maps to sort on.

Examples:

iex> sort([[num: 1], [num: 2], [num: 3], [num: 4], [num: 5]], field: :num, direction: :desc)
[[num: 5], [num: 4], [num: 3], [num: 2], [num: 1]]
iex> sort("edcba")
["a", "b", "c", "d", "e"]

split(string, pattern, opts \\ [])

Split the given string on the pattern, returning a list.

Options (doc):

  • :parts (positive integer or :infinity) - the string is split into at most as many parts as this options specifies. If :infinity, the string will be split into all possible parts. Defaults to :infinity.

  • :trim (boolean) - if true, empty strings are removed from the resulting list.

Examples:

iex> split("this will be a list", " ")
["this", "will", "be", "a", "list"]
iex> split("one, two, three", ",", parts: 2)
["one", " two, three"]
iex> split("one, two, three,", ",", trim: true)
["one", " two", " three"]

subset?(list1, list2)

Determine whether all items in list1 are also in list2. Returns true or false.

Examples:

iex> subset?([2, 3], [1, 2, 3])
true
iex> subset?([1, 2, 3], [1, 2, 3])
true
iex> subset?([], [1, 2, 3])
true
iex> subset?([4, 5], [1, 2, 3])
false
iex> subset?("abc", "abcd")
true

union(list1, list2)

Returns the set-union of two lists.

Note that the resulting list will not be sorted.

Examples:

iex> union([1, 2, 3], [2, 3])
[1, 2, 3]
iex> union([4, 5], [2, 3])
[2, 3, 4, 5]
iex> union("abc", "abcd")
"abcd"

uniq(list)

Return a list with unique elements (no duplicates)

Examples:

iex> uniq([1, 2, 2, 3, 3, 3])
[1, 2, 3]
iex> uniq("aaabbbccc")
["a", "b", "c"]

values(ets)

Return the values of a map or a keyword list.

When a plain list is given, returns the list itself.

Raises an error on non list/map values.

Examples:

iex> values(%{a: :b, b: :c})
[:b, :c]
iex> values("abc")
["a", "b", "c"]

Numeric functions

abs(value)

Return the abs of the given number.

ceil(value)

Return the ceil of the given number.

floor(value)

Return the floor of the given number.

max(a, b)

Return the max of the given numbers.

min(a, b)

Return the min of the given numbers.

modulo(a, b)

Return the modulo of the given numbers.

number(value)

Converts most values to their numeric counterpart. Returns nil when conversion fails.

parse_float(string, opts \\ [])

Attempt to parse a string to a float.

Options:

  • :allow_trailing: Whether or not to allow trailing non-integer text.

Examples:

iex> parse_float("1")
1.0
iex> parse_float("1a")
nil
iex> parse_float("1a", allow_trailing: true)
1.0
iex> parse_float("bla")
nil
iex> parse_float("43.3")
43.3
iex> parse_float("43.2hello")
nil
iex> parse_float("43.2hello", allow_trailing: true)
43.2

parse_int(string, opts \\ [])

Attempt to parse a string to an integer.

Options:

  • :radix: The radix to use when parsing. Defaults to 10.
  • :allow_trailing: Whether or not to allow trailing non-integer text.

Examples:

iex> parse_int("1")
1
iex> parse_int("1a")
nil
iex> parse_int("1a", allow_trailing: true)
1
iex> parse_int("bla")
nil
iex> parse_int("1011", radix: 2)
11

round(value)

Return the round of the given number.

Date functions

date_ceil(datetime, arg2)

Rounds the given datetime up to the nearest multiple of the given minutes.

:minutes can only be a number from 0..60.

Examples:

iex> date_ceil("2024-01-01T09:00:00Z", minutes: 15)
"2024-01-01T09:00:00+00:00"
iex> date_ceil("2024-01-01T09:03:14Z", minutes: 15)
"2024-01-01T09:15:00+00:00"
iex> date_ceil("2024-01-01T09:15:01Z", minutes: 15)
"2024-01-01T09:30:00+00:00"
iex> date_ceil("2024-01-01T09:55:01Z", minutes: 15)
"2024-01-01T10:00:00+00:00"

date_compare(left, right)

Compares two datetimes.

Returns :lt when the first datetime is before the second, :gt for vice versa, and :eq if they are the same. If either of the given dates is invalid, it returns :invalid_date instead.

Note that both UTC and Standard offsets will be taken into account when comparison is done.

date_diff(a, b, granularity)

Get the difference between two dates.

You need to specify a diff granularity, any of the following:

  • :years
  • :months
  • :calendar_weeks (weeks of the calendar as opposed to actual weeks in terms of days)
  • :weeks
  • :days
  • :hours
  • :minutes
  • :seconds
  • :milliseconds
  • :microseconds
  • :duration

and the result will be an integer value of those units or a Duration struct. The diff value will be negative if a comes before b, and positive if a comes after b. This behaviour mirrors compare/3.

See the Timex manual.

Examples:

iex> date_diff("2020-06-10T00:00:00+00:00", "2021-06-10T00:00:00+00:00", :years)
-1
iex> date_diff("2020-06-10T00:00:00+00:00", "2020-08-10T00:00:00+00:00", :months)
-2
iex> date_diff("2020-06-10T00:00:00+00:00", "2020-06-13T00:00:00+00:00", :days)
-3
iex> date_diff("2020-06-10T12:50:00+00:00", "2020-06-10T09:50:00+00:00", :hours)
3
iex> date_diff("2020-06-10T12:50:00+00:00", "2020-06-10T12:00:00+00:00", :minutes)
50
iex> date_diff("2020-06-10T12:50:00+00:00", "2020-06-10T12:50:10+00:00", :seconds)
-10

date_format(date, format_string, opts \\ [])

Formats an UTC ISO8601 time string.

Expects an ISO date string, and a format string. For the syntax of the date format string, see the documentation of the Timex date formatter.

Options:

  • :locale -- Select the locale to use while formatting, for formatting month names, day names et cetera.

  • :timezone -- Select the timezone used while formatting.

  • :formatter -- Specify the the formatter module to use. Set to :strftime to select the alternative strftime formatter.

Examples:

iex> date_format("1990-10-10T00", "{YYYY}/{M}/{D}")
"1990/10/10"
iex> date_format("2012-12-12T00", "{WDfull} {D} {Mfull} {YYYY}")
"Wednesday 12 December 2012"
iex> date_format(["2012-12-12T00", "2012-12-13T00"], "{WDfull} {D} {Mfull} {YYYY}")
["Wednesday 12 December 2012", "Thursday 13 December 2012"]
iex> date_format("2022-04-29", "{YYYY}/{0M}/{D}")
"2022/04/29"

date_now(timezone \\ nil)

Returns the current time as extended ISO8601 string.

When a timezone is passed in, the current date is returned in that timezone.

date_parse(value, opts \\ [])

Parse a given date into a UTC ISO8601 time string.

The input date needs to be a string or a list of strings. The Duckling date parser is used (unless the format option is specified), so a "human input" string ("tomorrow", "in 2 hours") can be used as input.

Options:

  • :format - when format is given, parsing is done using Timex.parse, otherwise, parsing is done using Duckling.
  • :locale - the locale used for parsing (in case of Ducking parser), defaults to en_US
  • :timezone - the timezone used for parsing (in case of Ducking parser), defaults to Europe/Amsterdam

When using format:, date_parse fails with a runtime error when failing to parse the date. When using the duckling option, it returns nil when the parsing fails.

Examples:

iex> date_parse("1990-01-01")
"1990-01-01T00:00:00.000+01:00"
iex> date_parse(["1990-01-01", "1990-01-02"])
["1990-01-01T00:00:00.000+01:00", "1990-01-02T00:00:00.000+01:00"]

date_part(date, part)

Get a component of the given datetime or a list of datetimes.

The component name is one of the following:

  • :year
  • :month
  • :day
  • :hour
  • :minute
  • :second

Returns nil when an invalid part was given.

Examples:

iex> date_part("2020-06-10T00", :year)
2020
iex> date_part(["2021-06-10T00", "2022-06-10T00"], :year)
[2021, 2022]
iex> date_part("2020-06-10T00", :month)
06
iex> date_part("2020-06-10T00", :day)
10
iex> date_part("2020-06-10T12:50:00+00:00", :hour)
12
iex> date_part("2020-06-10T12:50:00+00:00", :minute)
50
iex> date_part("2020-06-10T12:50:00+00:00", :second)
0

date_set(date, opts)

Return a new date/time value with the specified fields replaced by new values.

Values are automatically validated and clamped to good values by default.

See the Timex manual.

date_shift(date, opts)

Shifts the given datetime or a list of datetimes by the given duration.

A single function for adjusting the date using various units: duration, microseconds, seconds, minutes, hours, days, weeks, months, years.

The result of applying the shift will be the same type as that of the input, with the exception of shifting DateTimes, which may result in an AmbiguousDateTime if the shift moves to an ambiguous time period for the zone of that DateTime.

Valid units are:

  • years
  • months
  • weeks
  • days
  • hours
  • minutes
  • seconds
  • milliseconds
  • microseconds

See the Timex manual.

Examples:

iex> date_shift("2020-06-10T00:00:00+00:00", years: 1)
"2021-06-10T00:00:00+00:00"
iex> date_shift("2020-06-10T00:00:00+00:00", months: 2)
"2020-08-10T00:00:00+00:00"
iex> date_shift(["2020-06-10T00:00:00+00:00","2020-08-10T00:00:00+00:00"], months: 2)
["2020-08-10T00:00:00+00:00","2020-10-10T00:00:00+00:00"]
iex> date_shift("2020-06-10T00:00:00+00:00", days: 03)
"2020-06-13T00:00:00+00:00"
iex> date_shift("2020-06-10T12:50:00+00:00", hours: -3)
"2020-06-10T09:50:00+00:00"
iex> date_shift("2020-06-10T12:50:00+00:00", minutes: -50)
"2020-06-10T12:00:00+00:00"
iex> date_shift("2020-06-10T12:50:00+00:00", seconds: 10)
"2020-06-10T12:50:10+00:00"

date_truncate(date, precision)

Returns the given datetime or list of datetimes with the microsecond field truncated to the given precision.

The precision is one of the following:

  • :year
  • :month
  • :day
  • :hour
  • :minute
  • :second
  • :millisecond

Returns nil when an invalid date or invalid precision was given.

Examples:

iex> date_truncate("2020-06-10T12:50:00+00:00", :hour)
"2020-06-10T12:00:00+00:00"
iex> date_truncate(["2020-06-10", "2020-06-11"], :month)
["2020-06-01T00:00:00+00:00", "2020-06-01T00:00:00+00:00"]

relative_date(amount, unit, locale \\ "en", format \\ :default)

Formats a date to a string relative to the current moment.

Parameters:

  • amount - A number or list of numbers to use as the relative point.
  • unit - The time unit used for the formatting. Valid units are:
    • :second,
    • :minute,
    • :hour,
    • :day,
    • :week,
    • :month,
    • :year,
    • :quarter,
    • Days of the week are also units: :mon, :tue, :wed, :thu, :fri, :sat, :sun.
  • locale - the locale code for the language to format the date in, e.g. "en", "fr", "de"
  • format - the format that is used to format the date, can be :default, :narrow, or :short.

Examples:

iex> relative_date(1, :day, "en")
"tomorrow"
iex> relative_date(1, :hour, "fr")
"dans 1 heure"
iex> relative_date([1, 2, 3], :hour, "fr")
["dans 1 heure", "dans 2 heures", "dans 3 heures"]

Show functions

adaptive_card(card)

Creates and validates the data for an adaptive card. See https://adaptivecards.io/

attachment(value)

Constructs a new attachment.

audio(url)

Creates and validates a data structure for presenting an audio fragment.

The result is meant to be passed in to the show statement, e.g: show audio("http://example.com/…")

buttons(text, buttons)

Creates and validates a data structure for showing a buttons template

For more info on the exact data format see https://developers.facebook.com/docs/messenger-platform/reference/template/button

card_template(opts)

Creates a card template. A card template is a gallery template with only one item. This is not supported on Facebook Messenger, only on web and google assistant.

contact(attrs)

Creates a contact object.

A contact object can be shown to the user with show.

Examples:

Most fields are optional. This is an example of a full contact object:

iex> contact(%{
...>   name: %{
...>     first_name: "First",
...>     middle_name: "Middle",
...>     last_name: "Last",
...>     prefix: "Prefix",
...>     suffix: "Suffix",
...>     formatted_name: "Prefix First Middle Last Suffix"
...>   },
...>   birthday: "1970-01-01",
...>   emails: [
...>     %{type: "home", email: "home@example.com"},
...>     %{type: "work", email: "work@example.com"}
...>   ],
...>   org: %{
...>     company: "Company",
...>     department: "Department",
...>     title: "Role title"
...>   },
...>   phones: [
...>     %{type: "home", phone: "0612345678"},
...>     %{type: "work", phone: "0612345678"}
...>   ],
...>   urls: [
...>     %{type: "home", url: "https://example.com/home"},
...>     %{type: "work", url: "https://example.com/work"}
...>   ],
...>   addresses: [
...>     %{
...>       type: "work",
...>       street: "Street",
...>       city: "City",
...>       state: "State / Province",
...>       zip: "ZIP",
...>       country: "Country",
...>       country_code: "country code (e.g. nl, de, etc)",
...>     }
...>   ]
...> })

file(url)

Creates and validates a data structure for offering a file for download.

The result is meant to be passed in to the show statement, e.g: show file("http://example.com/…")

Creates and validates a data structure for showing a horizontal gallery template.

Options: pass in modal: false to prevent the elements showing in a modal gallery popup, but as large elements in the chat stream instead.

For more info on the exact data format see https://developers.facebook.com/docs/messenger-platform/reference/template/generic

image(url)

Creates and validates a data structure for showing an image.

The result is meant to be passed in to the show statement, e.g: show image("http://example.com/…")

input_method(type, config \\ nil)

Creates and validates a data structure for the given input method, to be used in an expecting: clause.

See the documentation for more details on different input methods.

list_template(elements, button \\ nil, top_element_style \\ "compact")

Creates and validates a data structure for showing a list template.

For more info on the exact data format see https://developers.facebook.com/docs/messenger-platform/reference/template/list

location(value, opts \\ [])

Convert the given value to a valid location structure

message(message, opts \\ %{})

Constructs a new message struct.

This does not perform any kind of NLP classification, so intent matching does not automatically work; you'll have to assign the intent manually.

template(type, payload)

Show a specific rich template in the chat stream.

The following templates are known:

  • buttons
  • gallery
  • list
  • adaptive_card
  • text

video(url)

Creates and validates a data structure for showing a video clip.

The result is meant to be passed in to the show statement, e.g: show video("http://example.com/…")

web(url)

Creates and validates a data structure for showing a website in an iframe.

The result is meant to be passed in to the show statement, e.g: show web("http://example.com/…")

Locale functions

describe_locale(locale \\ nil, opts \\ [])

Describe / parse the given locale and returns a map containing a language, region, label and region_label keys.

Options:

  • :locale - The output locale; defaults to the user's current locale.

Examples:

iex> describe_locale("en_US")
%{"language" => "en", "label" => "English", "region" => "US", "region_label" => "United States"}
iex> describe_locale("en_GB")
%{"language" => "en", "label" => "English", "region" => "GB", "region_label" => "United Kingdom"}

describe_locales(locales \\ [], opts \\ [])

Returns all the available languages from the list of locales of the current bot, if no list of locales is provided. The result is a list in which each item is a map containing a language, region, label and region_label key.

Options:

  • :locale - The output locale; defaults to the user's current locale.

Examples:

iex> describe_locales(["en_US"])
[%{"language" => "en", "label" => "English", "region" => "US", "region_label" => "United States"}]
iex> describe_locales(["en_GB"])
[%{"language" => "en", "label" => "English", "region" => "GB", "region_label" => "United Kingdom"}]
iex> describe_locales(["de_AT"], locale: "nl")
[%{"language" => "de", "label" => "Duits", "region" => "AT", "region_label" => "Oostenrijk"}]

extract_language(message)

Will extract the language from a given string and return the locale code."

Examples:

iex> extract_language("I want to speak French")
"fr"
iex> extract_language("Sprichst du Deutsch?")
"de"
iex> extract_language("Do you speak Italian?")
"it"

humanize_duration(value, opts)

Will format a time duration in a human form, spelling out the quarters and halves of hours.

Options:

  • :locale - supported locales are: "en", "fr", "de", "it", "es", "pt", "nl".
  • :format - describes the way numbers should be formatted. Available formats:
    • :standard, default, will represent the numbers as numbers: 1 will be formatted as "1".
    • :spellout, will spell the numbers in words: 1 will be formatted as "one".

Examples:

iex> humanize_duration(36, "hour", locale: "en")
"a day and a half"
iex> humanize_duration(1.5, "hour", locale: "en")
"an hour and a half"
iex> humanize_duration(150, "minute", locale: "en")
"2 and a half hours"
iex> humanize_duration(150, "minute", format: :spellout)
"two and a half hours"
iex> humanize_duration(1.5, "month", format: :spellout)
"a month and a half"
iex> humanize_duration(21.025, "month", format: :spellout)
"one year, eight months, and thirty-one days"
iex> humanize_duration(42, "month", format: :spellout)
"three and a half years"
iex> humanize_duration(0.5, "hour", locale: "nl")
"een half uur"
iex> humanize_duration(1.5, "hour", locale: "nl")
"anderhalf uur"
iex> humanize_duration(1.5, "minute", locale: "nl")
"anderhalve minuut"
iex> humanize_duration(1.575, "hour", locale: "nl", format: :spellout)
"een uur, vier­en­dertig minuten en dertig seconden"
iex> humanize_duration(0.5, "minute", locale: "nl")
"een halve minuut"
iex> humanize_duration(30, "minute", locale: "fr")
"une demi-heure"
iex> humanize_duration(18, "month", locale: "fr")
"un an et demi"
iex> humanize_duration(90, "minute", locale: "de")
"anderthalb Stunden"
iex> humanize_duration(150, "minute", locale: "de", format: :spellout)
"zweieinhalb Stunden"

humanize_locale(locale_to_humanize, opts \\ [])

Returns the name of a language in a specified language.

Options:

  • :locale - The output locale; defaults to the user's current locale.

Examples:

iex> humanize_locale("nl", locale: "nl")
"Nederlands"
iex> humanize_locale("en", locale: "fr")
"anglais"

locale_supported?(locale, available_locales \\ nil)

Returns true if the given locale is supported by the current bot.

When available_locales is left nil, it will default to the locales of the current bot. Otherwise, when it's a list, the function will check whether the language of the given locale matches one of the languages of the given locales.

number_format(amount, opts)

Formats a number into the locale-specific number format.

Options:

  • :locale - The output locale; defaults to the user's current locale.

For all other options, see the Cldr.Number documentation.

Examples:

iex> number_format(1000.01, locale: "nl")
"1.000,01"
iex> number_format(1000.99, locale: "en")
"1,000.99"
iex> number_format(9.99, locale: "nl", currency: :EUR, format: :currency)
"€ 9,99"
iex> number_format(1000.01, locale: "nl.FORMAL")
"1.000,01"

Miscelaneous helper functions

condition(expression, expression_tags, conversation_tags)

Evaluates conditions based on tags using "any of", "all of" or "none of" expressions.

Consider using flow_condition instead, which is compatible with the corresponding CMS UI field.

Parameters:

expression  :
   "any of"  = ( .. OR .. )
   "all of"  = ( .. AND .. )
   "none of" = NOT ( .. AND .. )
   "" || nil = "any of"
   all others lead to false

expression_tags:
   which tags should be in the conversation tags
   [] leads to true

conversation_tags:
   having all the tags to check
   [] leads to false

Examples:

iex> condition("any of", ["a", "b"], ["a", "c", "d"])
true
iex> condition("any of", ["a", "b"], ["c", "d"])
false
iex> condition("all of", ["a", "b"], ["a", "c", "d"])
false
iex> condition("all of", ["a", "b"], ["a", "b", "c", "d"])
true
iex> condition("none of", ["a", "b"], ["a", "b", "c", "d"])
false
iex> condition("none of", ["a", "b"], ["c", "d"])
true
iex> condition("all of", ["a", "b"], [])
false

contains(string, substring)

Return whether a given value is part of the given list, or whether a given value is a substring of the given string.

This is a polymorphic function. When one of the arguments is nil, it will return false. In other cases, when invalid values are passed in, it will raise a runtime error.

Examples:

iex> contains("my name is", "name")
true
iex> contains([1, 2, 3], 3)
true
iex> contains("", "a")
false
iex> contains([], [])
false

count(element, list)

Counts the amount of elements in a list or an enumerable

Examples:

iex> count("a", ["a", "b", "a", "c"])
2
iex> count("a", "banana")
3

Not enumerable, returns nil:

iex> count("a", false)
nil

distance(a, b)

Return the geographical distance (in meters) between two geographical locations.

The parameters need be valid geographical locations, e.g. maps with a lon and lat attribute.

Examples:

iex> distance([4.893414, 52.364909], [4.8952, 52.3702])
600.6996152390233

empty?(list)

Returns true when list or enumerable is empty.

Examples:

iex> empty?(%{})
true
iex> empty?(%{a: 1})
false
iex> empty?([])
true
iex> empty?([1, 2])
false
iex> empty?("hello")
false
iex> empty?("")
true
iex> empty?(nil)
true
iex> empty?(false)
true
iex> empty?(:some_other_atom)
false

entity(args)

Construct a new entity

event(name)

Construct an event for use in expecting:

extract(sentence, entity, scope)

Given an input sentence and a match specification, extracts the information from the sentence.

When the entity matches, a %Bubble.Message{} is returned with its captures filled with the information extracted from the entity.

extract_all(sentence, entities, scope)

Given an input sentence and a list of match specifications, returns a message object that is enriched with all return values from all the matching entities.

For entities that defined normal captures, the corresponding capture variables are filled.

@postcode entity(match: "/[0-9]{4}\s*[A-Za-z]{2}/[=pc]")
@yesno entity(match: "/yes|no/[=yn]")
message = extract_all("yes I want to 1061BM", [@postcode])
say(message.pc) # "1061BM"
say(message.yn) # "yes"

For entities that have a return: declaration, the message.returns is set to an array containing all the return values of all the matching entites.

@topic_sales entity(return: "sales", match: "price | buy | order")
@topic_service entity(return: "service", match: "problem | issue")
message = extract_all("I want to buy a problem", [@topic_sales, @topic_service])
say(join(message.returns, ",")) # "sales,service"

intent(args)

Construct a new intent.

Examples:

@my_intent intent(match: [
  # BML: https://developer.botsquad.com/bubblescript/bml/
  "[gpe]"

  # With an entity:
  entity(match: "[gpe]")
])

The following options can be given to the intent constructor:

  • match - a BML match expression.
  • id - the identifier of the intent.
  • label - the label that is used when the intent is used as part of a dialog label or in the expecting clause of an 'ask'.
  • dtmf - the DTMF key that can be pressed to trigger this intent.

intent(base, args)

Construct an intent based on an already existing intent, overwriting the properties that are given in the second argument.

@yes intent(label: "Yes")

dialog main do
  yes_with_dtmf = intent(@yes, dtmf: "1")
end

length(string)

Return the length of a string or the length of a list.

This is a polymorphic function: When passed in a string, it returns the number of characters in the string. When passed a list or a map, it returns the number of elements. For all other values, it returns 0.

Examples:

iex> length("hello")
5
iex> length([1, 2, 3])
3

random(number)

Return a random item.

This is a polymorphic function: When a number is given, returns a random number between 0 and that number. When a list is given, returns a random element from the list.

Examples:

iex> random(10)
2
iex> random([4, 2, 6, 9])
6

reverse(string)

Reverse the given list or string.

When a different value is passed in, it will raise a runtime error.

Examples:

iex> reverse("abcd")
"dcba"
iex> reverse([1, 2, 3])
[3, 2, 1]

slice(string, startpos, endpos \\ nil)

Slice a substring out of the given string, or a sublist out of the given list. Arrays and strings are 0-indexed, meaning that the first element has the position of 0.

If no endpos is given, the array or list will be sliced until the end.

When a different value is passed in, it will raise a runtime error.

Examples:

iex> slice("a slice of bread", 11)
"bread"
iex> slice("a slice of bread", 2, 5)
"slice"
iex> slice([1, 2, 3], 0, 1)
[1]

take_random(enumerable, count)

Take a number of items from a list, map or string at random.

Examples:

iex> take_random("hello", 2)
"ol"
iex> take_random([1, 2, 3], 2)
[3, 2]
iex> take_random(%{1 => "a", 2 => "b", 3 => "c"}, 2)
%{1 => "a", 3 => "c"}

uuid()

Generate a random, version 4, UUID.

valid_json_schema?(value, schema)

Confirm if a value is valid according to the given JSON schema.

If you need more extensive validation info, refer to validate_json_schema/2.

Examples:

iex> schema = %{
...>   "type" => "object",
...>   "properties" => %{
...>     "foo" => %{"type" => "number"}
...>   }
...> }
iex> valid_json_schema?(%{"foo" => 1}, schema)
true
iex> valid_json_schema?(%{"foo" => "1"}, schema)
false
iex> valid_json_schema?(%{"foo" => 1}, "not a schema")
:invalid_schema
iex> valid_json_schema?(%{"foo" => 1}, %{"type" => "invalid"})
:invalid_schema

validate_json_schema(value, schema)

Validate a value against a JSON schema.

Returns detailed error information when validation fails. See ExJsonSchema for more information.

Examples:

iex> schema = %{
...>   "type" => "object",
...>   "properties" => %{
...>     "foo" => %{"type" => "number"}
...>   }
...> }
iex> validate_json_schema(%{"foo" => 1}, schema)
[]
iex> validate_json_schema(%{"foo" => "hi"}, schema)
[
  %ExJsonSchema.Validator.Error{
    path: "#/foo",
    error: %ExJsonSchema.Validator.Error.Type{expected: ["number"], actual: "string"}
  }
]
iex> validate_json_schema(%{"foo" => 1}, "not a schema")
:invalid_schema
iex> validate_json_schema(%{"foo" => 1}, %{"type" => "invalid"})
:invalid_schema

Type introspection functions

boolean?(value)

Return true when the given value is a boolean.

float?(value)

Return true when the given value is a float.

integer?(value)

Return true when the given value is a integer.

list?(value)

Return true when the given value is a list.

map?(value)

Return true when the given value is a map.

number?(value)

Return true when the given value is a number (an integer or a float)

Examples:

iex> number?(1.1)
true
iex> number?(10)
true
iex> number?("lala")
false

string?(value)

Return true when the given value is a string.

tuple?(value)

Return true when the given value is a tuple.

typeof(v)

Return the type of the given argument, as a string

Examples:

iex> typeof "hello"
"string"
iex> typeof [1, 2, 3]
"list"
iex> typeof %{type: "x"}
"map"
iex> typeof {1, 2}
"tuple"
iex> typeof true
"boolean"
iex> typeof nil
nil
iex> typeof 1.1
"float"
iex> typeof 10
"integer"

XML functions

xml_build(data)

Build an XML string out of a data structure.

Examples:

iex> xml_build(["element", %{"attr" => "val"}, "content"]) "content"

xml_parse(data)

Parse an XML string into a data structure.

Examples:

iex> xml_parse("content") ["element", %{"attr" => "val"}, "content"]

xml_xpath_all(data, query)

Select a list of values or XML elements

Selects a list of values or XML elements from the given XML using an XPath expression. Always returns a list. In the case that no elements were selected, an empty list is returned.

xml_xpath_one(data, query)

Select a single value or XML element

Selects a single value or XML from the given XML data structure using an XPath expression. If the result of the expression is not exactly one single element, nil will be returned.

Additional builtin functions

These are additional functions which can be used inside the bot processes. They cover more specific tasks that are related to the DialoX platform, like token management, process management and communication.

_(msgid)

Mark a given string as translatable.

For example:

say _("Hello world")

For more information, see the translation guide.

__testing_name_lookup(identifier)

Look up a bot by testing name

__testing_phone_number_lookup(identifier)

Look up a bot by testing phone number

bml_escape(string)

Escape a BML string as a normal string.

iex> bml_escape(1) "\"1\""

iex> bml_escape("aa") "\"aa\""

iex> bml_escape("a\"a") "\"a\\"a\""

iex> bml_escape("a\na") "\"a\na\""

iex> bml_escape("#{bla}") "\"#{bla}\""

iex> bml_escape("[time] | do not want | private") "\"[time] | do not want | private\""

iex> bml_escape("is a [0] [0] /^reg/[=val]") "\"is a [0] [0] /^reg/[=val]\""

cancel_emit(schedule_id)

Will cancel the given scheduled emit. The identifier can be obtained from the emit statement; a scheduled emit (with in: or at: option) sets the variable with the name schedule_id in the bot script. You can use this variable to cancel it:

emit "status_update", [], in: 10
cancel_emit schedule_id

When you give the scheduled event a name, it can also be cancelled using that name:

emit "status_update", [], in: 10, name: "update"
cancel_emit "update"

chat_link(frontend, params \\ nil)

Generates a link to this bot for sharing purposes.

say "Reach me on the web on this link:"
say chat_link("web_pwa")

If you want a link to the same channel that the current user is chatting through, you can pass in the user.frontend variable:

say chat_link(user.frontend)

The second argument, params is used to add extra parameters to the chat link, for instance the g parameter to join a group.

say chat_link("web_pwa", g: group)

To create a link which leads into a users's session, use the user_id parameter:

say chat_link("web_pwa", user_id: some_user_id)

Account linking

To create a link which leads into a user's session and then aliases your current user ID to the new user ID, use user_id combined with alias_user_id (which is the "old" user id):

say chat_link("web_pwa", user_id: some_user_id, alias_user_id: user.user_id)

create_chargeable_event(event_type, opts \\ [])

Add a charge for a billable event.

create_todo(opts)

Create a new todo item.

Options:

  • :title: String with the title of the todo.
  • :description: The main body describing the work that needs to be done.
  • :tags: A list of tags to assign to the todo.
  • :start_at: When the todo should start.
  • :due_at: Deadline for when the todo needs to be a done.

delete_token(token_id)

Delete a given token ID

See API integrations

detect_language(text)

Detect the language of the given text, using the Google Natural Language processing API.

Only works when your account has Dialogflow support enabled.

end_session()

Stop the interaction.

Note: deprecated: use stop instead

escalate(reason \\ "Assistance required", opts \\ [])

Send an escalation message to the operators of this bot.

The operators which are on call are notified, either in the studio, by web push notification, or with email. The return value is true when an operator could be reached, or false if there were no operators on call.

The reason that can be passed in is supposed to be a human-readable message, describing why the bot needed to hand over the conversation. It is sent as the subject of the email and push notification.

Options can include:

  • :to - the audience of the escalation. This is either a role tag (to: "tag:finance"), which will send the escalation to all roles with the finance tag; an array of tags: to: ["tag:finance", "tag:frontdesk"]; or a user id of a user in the studio: to: "studio:68eafe876f8a6e87a6e"

  • :tag - the tag of the audience of the escalation. tag: "foo" is a shorthand notation for to: "tag:foo".

flow_condition?(expression, opts \\ [])

Check a given flow expression against the list of tags.

The expression argument is the internal data structure that is the output of the ui:widget: flow_condition CMS input control, consisting of a list of clauses which are evaluated in an OR'ed fashion.

Options:

  • :tags - the tags to check against; when not given, the current conversation.tags are used.

get_access_token(token_id)

Return the plain access token for the given token identifer. This access token is used in HTTP calls to perform requests to the integrated service.

See API integrations

get_available_operators()

List all operators which are on call, e.g. which have indicated to be available and are within their indicated office hours.

Returns a list of maps with the fields id, name, profile_picture, timezone and status. Status can be online when they are logged in into the studio, or offline when they are not logged in.

get_conversation_transcript()

Get a transcript of the current conversation as a text string.

It's possible for messages that have arrived right before calling this function to be missing from the transcript. This happens because the messages are fetched from Elasticsearch, which takes a bit of time to make the messages searchable after insertion. On most channels this won't be noticable, because we can wait a bit longer until they've been made searchable. However, on voice channels (such as phone calls), waiting is not an option, so you may encounter this issue there.

get_dialog_locale()

Return the locale in which the bot is currently talking to the user.

get_full_token(token_id)

Retrieves the full token information structure, mainly for inspection purposes.

See API integrations

get_next_office_hours_timeslot(office_hours, opts \\ [])

Get the next pair of opening and closing times from a given date. Returns a list containing two items: the opening datetime and the closing datetime.

The office_hours variable must be a 7-item array in which each item represents an opening/closing hour. The first (0th) element in the array is sunday, the last is saturday.

Options:

  • :timezone - The timezone to consider (defaults to user.timezone or bot.timezone)
  • :date - The reference date to consider (defaults to date_now())

get_operator_by_uuid(uuid)

Retrieve a studio user's details by its UUID

get_phone_number_type(phone, default_country \\ nil)

Try to determine for the given phone number what type of number it is.

If the given string cannot be parsed as a valid phone number (or something other than a string was passed in), then this function will return :error. Otherwise, it will return one of the following:

  • :fixed_line
  • :mobile
  • :fixed_line_or_mobile
  • :toll_fee
  • :premium_rate
  • :shared_cost
  • :voip
  • :personal_number
  • :pager
  • :uan
  • :voicemail
  • :unknown

get_token_info(token_id)

Return some basic information about the user that is connected to the specific token:

log get_token_info(user.tokens.google)

See API integrations

get_users_by_email(email)

Retrieve user details for all users that are registered with the given email address. Used for account linking.

glob_scripts(pattern, opts \\ [])

Return the titles of all scripts that match the given pattern.

Examples:

glob_scripts("flows/*")
glob_scripts("*.test")

**Options:**

- `:type` - restrict by valid script MIME type (`text/bubblescript`, `text/yaml+data`, `application/json+data`, etc)

group_name(parts)

Generates a deterministic group name for a list of identifiers based on a hash.

The identifiers can appear in any order, they are sorted before they are hashed. No ~ is prepended to the group name - you need to do this yourself.

Returns a link to this conversation in the DialoX Inbox.

json_build(data)

Convert a given value to a string encoded as JSON.

json_parse(data)

Convert the given JSON-encoded string to a data structure. Returns nil when decoding fails.

Link the given user address to the current user.

If there is no conversation (or actually, a user_frontend) yet for the given addr, a user_frontend entry will be created and linked to the given user.

In case the given address already belongs to the current user, nothing happens.

In case the given address belongs to another user, the target user is merged into the current user.

Returns true if linking was successful, or false when an error happened.

liquid(template, context)

Render a liquid template with the given variables.

log(value)

Logs a debugging statement in the chat session's stream.

mail(to, subject, message, opts \\ [])

Sends a notification e-mail to the given user.

mail("user@example.com", "Subject", "Hi there! This is your bot talking to you.")

This will send a pre-formatted HTML email, with the bot's name, profile picture and the message details.

HTML can not be used inside e-mail. The mail function is meant for basic notification and reminder messages.

The fourth argument can be given as options:

  • :cc - a list of email addresses to put in the CC field
  • :bcc - a list of email addresses to put in the BCC field
  • :link_url - can be used to render a button below the text, which will link to the given URL.
  • :link_caption - The optional caption of the button, defaults to "Open link"
  • :image_url - The URL of an image to show below the text (and below the button). When link_url is also set, the image links to the link URL as well.
  • :unsubscribe_url - The URL where the recipient can go to unsubscribe theirselves from messages like this. When set, an 'unsubscribe' option will be added to the mail message's footer.
  • :attachments - List of attachments to send with the email. Each entry must be a user-uploaded attachment or an object with type (one of image, audio, video, file) and url fields.
  • :locale - The locale to localize the unsubscribe link in. Defaults to en.
mail("user@example.com", "Subject", "Body", link_url: "http://example.com", image_url: "http://example.com/test.jpg")

See e-mail handling for more information on the mail capabilities of the platform.

mustache(template, context)

Render a mustache template with the given variables.

oauth_link(alias)

This function generates a link for the requested integration which starts the OAuth authorization flow.

The one required argument is the name of the provider or alias of which integration to start:

say oauth_link("google")

See API integrations

phone_format(phone, format \\ :national, default_country \\ nil)

Format a number to a specific format.

Returns :error if the given string cannot be parsed as a valid phone number.

The allowed formats are:

  • :e164
  • :international
  • :national
  • :rfc3966

Examples:

iex> phone_format("0612345678", :national)
"06 12345678"
iex> phone_format("+31612345678", :national)
"06 12345678"
iex> phone_format("0612345678", :e164)
"+31612345678"
iex> phone_format("0612345678", :rfc3966)
"tel:+31-6-12345678"

phone_parse(phone, default_country \\ nil)

Parse a phone number and return it in normalized, international format.

phone_parse("0612345678")"+31612345678"

When no country code is given in the input, the number is assumed to be local to the bot's region.

pre_escalate(reason \\ "Assistance required", opts \\ [])

Use pre-escalate to indicate to external inbox channels that a chat is being qualified and will soon be waiting for a chat operator.

query(recipient, name, payload \\ [])

Sends an event to the given user, and waits for the reply.

reply = query :master, "ping"
say reply

In the other script, define a task or dialog that catches the ping event, and use the reply construct to send a payload back:

task event: "ping" do
reply "pong"
end

Make sure that the handling task / dialog in the other process finishes quickly. If there is no reply received within 1 second, the value :timeout is returned in the calling process.

See Communication and coordination

read_script(title)

Read a script file by name, returning its text.

resolve_dialogflow_intent(text, lang \\ nil)

Check if the given text resolves to a dialogflow intent

If an intent matches it returns the intent structure, otherwise nil.

Like the global intent matcher, this function looks first in a locally defined Dialogflow agent (if the dialogflow Google Service account JSON file was found); otherwise it uses the globally-available Botsquad-Defaults Dialogflow agent.

resolve_gpt_local_intent(text, intents, opts \\ [])

Check if the given text resolves to any of the given intents using GPT

Options: - question: The question that the bot asks - prompt: Override the GPT prompt used (default: classify_local_intents) - locale: Override the locale to use

resolve_qna_intents(text, opts \\ [])

Check if the given text resolves to one or more QnA intents

Returns the list of matched intents, in order of confidence.

Options:

  • provider_agent - Name of an alternative qna provider_agent, when not given the default agent is used.

  • :locale - a locale string to override the locale that is selected for selecting the QnA model.

shorten_url(url, opts \\ [])

Create a shortened URL, for use in SMS messages and such.

The shortened URLs are scoped to the current bot and, as such, are removed when the bot is removed. Shortening the same URL again returns the same short URL.

Only HTTP and HTTPS URLs can be shortened. Giving invalid input to the function returns nil instead of a short URL.

Expiration date

It's possible to give a date at which point the URL stops working (and will return HTTP 404). The date given with :expires_at must be in the future. When using :ttl, it will add the given number of seconds to the current date.

Example:

shorten_url(url, ttl: 86400)
# The above :ttl is the same as:
shorten_url(url, expires_at: date_shift(date_now(), seconds: 86400))

sms(phone, message)

Send a notification SMS message to the given phone number.

The phone number must be specified in full international format, e.g. +31641345678. The sender of the SMS is the fixed DialoX notification number.

In order for this function to work, you need to have setup a Twilio connection to your existing SMS service. Read more about SMS handling

sms_notify(phone, message)

Deprecated, please use sms() instead

spawn_group(group \\ nil, context \\ %{})

Spawn a group process.

strip_emoji(string)

Remove any emoji from a given string.

strip_html(string)

Remove HTML tags from a given string.

strip_markdown(string)

Remove Markdown formatting from a given string.

strip_speechmarkdown(string)

Remove any Speech Markdown formatting from a given string.

tag?(tag)

Retrieve the value of a prefixed tag, or true / false for a normal tag, for the tags that are set on the current conversation.

tag "workflow::assigned"
say tag?("workflow")

Will say assigned.

tag "hello"
if tag?("hello"), do: say "Hello is set"

testing?()

Helper function for checking whether the bot is running as part of a test case

Returns true if that is the case, or false otherwise.

Using this function can help you switching parts of your bot (mocking data, HTTP calls etc.) when the bot tests are being run.

text_to_speech(smd, opts \\ [])

Perform text-to-speech on the given speech markdown string.

Returns the URL to the stored MP3 audio recording. Results are cached so that performing the same text-to-speech twice will return the same URL.

text to speech can fail because the requested locale does not have a corresponding voice configuration, or the Google TTS backend returns an error. In both cases, nil is returned to the caller.

Options:

  • :locale — The locale to speak in. Must have a voice configured in the corresponding connected channel. (if the conversation is not on a voice channel, it is assumed the REST API channel is enabled which also has a voices configuration).

transcribe(input, request_params \\ [])

Transcribe the given audio attachment or URL into a %Bubble.Message{} struct.

Options:

  • locale - use recognition in given locale (defaults to locale of conversation)
  • provider - Either google or microsoft_openai.
  • model - the name of the speech model (default: latest_long for Google, whisper-1 for Microsoft OpenAI)

See the transcribe REST API docs for more information about all

unidecode(text)

Transliterate a string with accented characters to a plain ASCII version without any accents or special symbols.

whatsapp(phone, template_id, opts \\ [])

Send the given Whatsapp template message to the given phone number.

The current bot must have a valid 360dialog Whatsapp connection setup up in order for this function to work. Furthermore, the given

The given whatsapp template identifier (template_id) must exist in the conversation's locale (or with the overridden locale: option).

Options:

  • :parameters - a map of numbered body parameters, e.g. %{"1" => "Pete", "2" => "example"}; can only used for basic text-only templates. Will be ignored if :components option is given.

  • :components - A full list of template component parameters (https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#template-object)

  • :locale - a locale string to override the locale that is selected for sending the template message.

within_office_hours(office_hours, opts \\ [])

Calculate whether the given date is within the given week day office hours.

The office_hours variable must be a 7-item array in which each item represents an opening/closing hour. The first (0th) element in the array is sunday, the last is saturday.

Options:

  • timezone - The timezone to consider (defaults to user.timezone or bot.timezone)
  • date - The reference date to consider (defaults to date_now())

write_script(title, contents, opts \\ [])

Write a script file by name.

This function is primarily meant for writing migrations that work on content scripts. It only works given the following conditions:

  • The script (by title) must already exist, or the type: option must be given
  • The function is called within the DialoX studio, in preview mode (debugging)
  • The user that calls the script must be allowed to change the script

The function only works for existing scripts. The content type of the script is unchanged, and the content must be formatted in the right format; e.g. the caller must know that the file is JSON, bubblescript, YAML, etc. The draft version of the script is written; e.g. a manual publish is still required to put the change 'live'.

Options:

  • :type - a valid script MIME type (text/bubblescript, text/yaml+data, application/json+data, etc)

yaml_build(data)

Convert a given value to a string encoded as YAML.

yaml_parse(string)

Convert the given JSON-encoded string to a data structure. Returns nil when decoding fails.

Key-value database functions

The following functions in the KV module are exposed as soon as a keyvalue_db integration is added to this bot (either directly or through installing a skill).

KV.count(kv, pattern \\ nil, opts \\ [])

Get the key count from the KV store

Returns the total number of keys that are in the store; if the pattern is given, it returns the number of keys that match the pattern.

KV.erase(kv, key, opts \\ [])

Erases the given key from the KV store.

Returns 1 when the key was erased, or 0 if no such key was found.

KV.erase_all(kv, opts \\ [])

Erases all keysfrom the KV store.

Returns the number of keys that were erased.

KV.erase_many(kv, pattern, opts \\ [])

Erases the keys matching the given pattern from the KV store.

For the 'pattern' argument, see the 'get_many' function.

Returns the number of keys that were erased.

KV.get(kv, key, opts \\ [])

Retrieve a single value for the given key from the KV store.

KV.get_all(kv, opts \\ [])

Retrieve all values from the KV store for the given pattern.

The return value of this function is always a map, in which the keys are all the keys in the store, and the values are their corresponding values.

KV.get_many(kv, pattern, opts \\ [])

Retrieve all values from the KV store for the given pattern.

The pattern can contain '%' characters which are used in the SQL 'like' expression that is used for the lookup. The pattern is case sensitive.

In addition to a single pattern, a list of patterns (or key names) can also be passed in.

The return value of this function is always a map, in which the keys are the keys that matched the patterns, and the values are their corresponding values.

KV.put(kv, key, value, opts \\ [])

Put a value in the given key in the KV store.

Options:

  • :ttl - Expiry value; can be a number (in seconds), or a time-interval string like "1d", "2h", "10s".

KV.put_many(kv, key_values, opts \\ [])

Put multiple key/value pairs in the given key in the KV store.

The key_values argument is a map with string keys, each value in the map will become an entry in the KV database with the corresponding key.

Options:

  • :ttl - Expiry value; can be a number (in seconds), or a time-interval string like "1d","2h","10s"`.

ChatGPT functions

This module contains functions to manipulate GPT prompts and perform GPT completions against the builtin OpenAPI integration. See https://developer.dialox.ai/platform/nlp/gpt/ for more information on how to work with GPT completions in general.

GPT.complete(prompt, bindings \\ %{}, opts \\ [])

Perform a GPT completion request

The full result of the GPT.complete call is a map array which contains the following:

  • text - The output text that GPT produced
  • json - A JSON deserialized version of the text; the runtime detects whether JSON is available in the result and, if so, parses it. The JSON message itself can be padded with arbitrary other texts.
  • usage - The total tokens that were used for this API call
  • request_time - The nr of milliseconds this request took
  • raw - The raw OpenAPI response

GPT.prompt(orig \\ %Prompt{}, args)

Construct a new prompt to be used in the GPT completion functions.

This function can be used to override certain properties of other prompts, or create new prompts altogether. However, prefer to use the builtins Prompts YAML files to define prompts, as this is more efficient and less error prone.

To set for instance an extra parameter on a prompt you could do the following:

myprompt = GPT.prompt(@prompts.original, endpoint_params: %{temperature: 1.3})

GPT.render_prompt(prompt, bindings \\ %{})

Render a prompt into its final form, an array of chat messages.

This function is mostly used for debugging and should not be needed in normal use cases.

Calendaring functions.

To create a named calendar with storage, create or update a YAML file named integrations and add:

- provider: calendar
  context: bot
  icon: calendar
  title: Booking
  alias: booking

This will create a new constant (@booking) that can be passed as the calendar argument to the various calendaring functions.

To also enable the studio interface to create slots with, create another YAML DDL file. Make sure storage.calendar_name and alias (from the integrations file) match.

type: calendar_events
# or type: calendar_slots
title: Booking
storage:
  type: calendar
  calendar_name: booking
calendar_events: # or calendar_slots:
  cms_only: true
  form:
    schema:
      type: object
      properties:
        tags:
          title: Tags
          default: []
          type: array
          items:
            type:
              string
    ui_schema:
      tags:
        "ui:field": tag_picker

Calendaring.add_event(calendar, event)

Add an event to the calendar, returning either the create event, or nil if the event was not added to the calendar. This can happen if a slot was passed and the slot is already at max capacity.

Options:

  • :title: A required string with a title for the event.
  • :description: An optional Markdown text field.
  • :from: The required starting date or datetime.
  • :until: The required ending date or datetime.
  • :metadata: A map with arbitrary data about the event. If a note_id key is set, it will be rendered as note in the calendar view.
  • :recurrence: Whether, and how often, the event should recur. Defaults to no recurrence.
  • :recurrence_until: ISO date or datetime until when the recurrence rule should last (exclusive).
  • :exclude_dates: A list of dates to exclude from the recurrence series.

Examples:

# Normal event.
add_event(title: "Lunch with Elvis", from: "2024-03-01T13:00:00Z", until: "2024-03-01T14:00:00Z")

# All day event.
add_event(title: "Kingsday", from: "2024-04-27", until: "2024-04-27")

# Recurring event, see add_slot for more examples.
add_event(
  title: "Lunch",
  from: "2024-01-01T12:00:00Z",
  until: "2024-01-01T13:00:00Z",
  recurrence: :weekdays
)

Calendaring.add_event(calendar, slot, opts)

Same as add_event/2 but inherits the :from and :until options from the slot. Unlike add_event/2, this will also check that the given slot has capacity for the event to planned.

Calendaring.add_slot(calendar, slot)

Add a slot to the calendar, returning the updated calendar.

Examples:

# From 10:00 to 11:00, mon-fri, starting today.
Calendar.add_slot(cal, from: "10:00", until: "11:00", recurrence: :weekdays)

# From 10:00, until an hour and a half later (11:30)
Calendar.add_slot(cal, from: "10:00", until: [hours: 1, minutes: 30])

# The month of April every weekday from 10:00 to 11:00 (UTC), except the 25th of April.
Calendar.add_slot(
  cal,
  from: "2024-04-01T10:00:00Z",
  until: [hours: 1],
  recurrence: :weekdays,
  recurrence_until: "2024-05-01",
  exclude_dates: ["2024-05-25"]
)

Options:

  • :from: Either a time string (ex: "10:00"), or an ISO datetime string. Required. When a time string is given, the bot's timezone will be used.
  • :until: Either a time string (ex: "11:00"), an ISO datetime string, or a duration shifted from the :from option. For example: until: [hours: 1]. Required. When a time string is given, the bot's timezone will be used.
  • :capacity: The number of events that fit within the slot.
  • :recurrence: Whether, and how often, the slot should recur. Defaults to no recurrence.
  • :recurrence_until: ISO date or datetime until when the recurrence rule should last (exclusive).
  • :exclude_dates: A list of dates to exclude from the recurrence series.

Calendaring.add_todo(calendar, opts)

Add a new todo to the calendar.

Options:

  • :title: An optional string title for the todo.
  • :description: An optional markdown text field.
  • :start_at: A datetime from when the todo becomes relevant.
  • :due_at: A datetime for the todo's deadline.
  • :tags: A list of tag strings.

Calendaring.add_todo(calendar, slot, opts)

Same as add_todo/2, except the start_at and due_at are autofilled by the slot.

Calendaring.available_slots(calendar, opts \\ [])

Return a list of slots that can still be filled.

Options:

  • :amount: The maximum number of slots to return. The actual returned amount may be less. Defaults to 5.
  • :skip: The number of slots to skip. Defaults to 0.
  • :until: Until when to generate the slots. Defaults to [weeks: 2].
  • :from: From when to start fetching the slots. Defaults to today.
  • :now: The current datetime to use as "now".
  • :ignore_capacity?: By default this function will ignore slots that have reached their capacity (the number of events planned within the slot is equal to its capacity). With this option you can ignore this check.
  • :constraints: Additional calendars to consider. The resulting slots won't overlap with events on any of the given calendars.

Calendaring.delete_event(calendar, event)

Deletes an event.

event = Calendaring.get_event(cal, id)
result = Calendaring.delete_event(event)

Calendaring.delete_slot(calendar, slot)

Delete a slot from the calendar.

cal = Calendaring.new_in_memory_calendar()
slot = Calendaring.add_slot(cal, from: "10:00", until: [hours: 1])
success = Calendaring.delete_slot(cal, slot)

Calendaring.delete_todo(calendar, todo)

Delete a todo.

Returns a boolean indicating if the deleting was successful.

Calendaring.get_event(calendar, id)

Get an event by ID.

Calendaring.get_slot(calendar, id)

Get a slot using it's ID.

Calendaring.get_todo(calendar, id)

Get a todo by ID.

Calendaring.list_events(calendar, opts \\ [])

Lists the events for the given calendar.

Options:

  • :from: a datetime from where to start searching. Defaults to now.
  • :until: an end datetime until where to search. Defaults to [weeks: 2].
  • :amount: The maximum amount of events to return. Defaults to nil.
  • :skip: The number of events to skip. Defaults to 0.
  • :user_id: The user to look for.

Calendaring.list_todos(calendar, opts \\ [])

List notes for the given calendar.

Options:

  • :amount: The number of todos to fetch.
  • :skip: The number of todos to skip from the front.
  • :due_before: A datetime before which the notes must be due.
  • :user_id: The user to look for.

Calendaring.new_in_memory_calendar()

Create a new temporary calendar.

Calendaring.suggest_events(calendar, duration, opts \\ [])

Find holes in the calendar where an event of the given size would still fit. This does not look at the slots of the calendar, just the events.

The resulting slots should not overlap with any other events in the same calendar.

Options:

  • :constraints: Additional calendars to consider. The resulting slots won't overlap with events on any of the given calendars.
  • :from: The moment from where to start looking for a slot. Defaults to now.
  • :until: Until when to start looking for holes. Example: [weeks: 2] for 2 hours after :from. Defaults to [weeks: 2].
  • :amount: The number of slots to return.
  • :skip: The number of slots to skip from the front.

Inbox commands

Example:

Add a "Thank you" message as suggestion to the operator input bar.

emit "$inbox", Inbox.suggest("Thank you"), to: :operator

Show a toast message to the user.

emit "$inbox", Inbox.toast("This person is not a paying member"), to: :operator

Note: This currently only works in the Inbox, not the studio inbox.

Inbox.form(opts)

Presents a form to the operator.

Options:

  • :schema: The JSON schema for the form. The root of the schema must be an object (that is, a schema with %{"type" => "object"} first).
  • :ui_schema: The UI schema to accompany the schema.
  • :data: Prefilled data for the form.
  • :event: The event (string) that will be called when the form has been filled. Required. The event.payload will be a map with an operator key containing the operator that submitted the form, and a form key with the validated data.
  • :cancel_event: The event (string) that will be called when the form has been cancelled. Optional. The event.payload will be a map with an operator key containing the operator that submitted the form, and a form key with the unfinished, non-validated data.

Inbox.suggest(message)

Suggest messages to the operator.

Examples:

emit "$inbox", Inbox.suggest("Did you turn the device off and back on?"), to: :operator
emit "$inbox", Inbox.suggest([
  "First suggestion",
  "Second suggestion"
]), to: :operator

Inbox.toast(text, opts \\ [])

Display a toast message to the operator.

The toast may either be a simple string, or a translations map where the key is the language ('en', 'nl', etc), and the value is the toast.

The message itself is rendered as markdown, so it may contain links.

Options:

  • :timeout: Automatically remove the toast after this many seconds. Defaults to no timeout.
  • :intent: Either :normal, :error, or :warning. Defaults to :normal.
  • :buttons: A list of commands that must also be present in the ui_commands.

Example:

emit "$inbox", Inbox.toast("Hello operator, this is bot"), to: :operator
# With translations, based on the operator's language:
emit "$inbox", Inbox.toast(%{"en" => "Hello operator", "nl" => "Hallo operator"}), to: :operator