Zayo
Zayo is an extensible Slack bot that can be taught to do almost anything using an easy-to-read Ruby DSL.
Skills
Skills are single-file modules that teach the bot to do something. An example skill looks like this:
Skill.define 'ask for food' do
on_start do
schedule.cron '50 11 * * 1-5', :ask_for_food
end
def ask_for_food
in_channel '#random-food' do
= say "Who wants to go have lunch?"
schedule.at 10.minutes.from_now, :lets_go,
end
end
def lets_go()
reactions = .reactions
if reactions.any?
say "Let's go!"
else
.react_with ':forever-alone:'
end
end
end
The above skill will ask Who wants to go have lunch?
in #random-food
at 11:50 every work day. 10 minutes after that, it will either say Let's go!
or react with :forever-alone:
depending on if there are any reactions below its first message.
Understanding message intent
Using Wit.ai, Zayo can infer user intent from free-text messages. This is typically how interactions with Zayo are started.
To teach Zayo to understand a query, just provide some examples in the skill code, then attach code whenever this intent is detected in a message:
Skill.define 'just talking' do
teach do
intent 'talkative/hi' do
sample 'hi'
sample 'hey'
sample 'hello'
sample 'hello, zayo'
end
intent 'talkative/introduce_yourself' do
sample 'introduce yourself'
sample 'please introduce yourself'
sample 'please make an introduction'
sample 'who are you?'
end
end
on_intent 'talkative/hi' do
say 'Hey :)'
end
on_intent 'talkative/introduce_yourself' do
say 'I am Zayo, nice to meet you! :)'
end
end
Intents can also extract parts of the message (called "entities" in Wit.ai):
Skill.define 'just talking' do
teach do
entity :name, lookups: ['free-text']
intent 'talkative/introduction' do
sample('hey, I am Georgi') { entity :name, 'Georgi' }
sample('I am Batman') { entity :name, 'Batman' }
sample('my name is Superman') { entity :name, 'Superman' }
end
end
on_intent 'talkative/introduction' do |event|
say "Nice to meet you, #{event.entities.single.name}! I am Zayo"
end
end
Interactions and context
Zayo can interact with Slack users by asking questions, replying in threads, posting messages and reacting to messages. A Zayo command does not need to have all of its parameters in a single message. For example:
Skill.define 'just talking' do
teach do
entity :name, lookups: ['free-text']
intent 'talkative/introduction' do
sample 'have we been introduced?'
sample "let's have an introduction"
sample "I don't know you"
sample('hey, I am Georgi') { entity :name, 'Georgi' }
sample('I am Batman') { entity :name, 'Batman' }
sample('my name is Superman') { entity :name, 'Superman' }
end
end
on_intent 'talkative/introduction' do |event|
name = event.entities.single.name
if name
say "Nice to meet you, #{name}! I am Zayo"
else
say 'I am Zayo'
name = ask_for_string 'What is your name?'
say "Nice to meet you, #{name}!"
end
end
end
The script for this interaction can proceed in two different ways, depending on whether the user's name is known in the original message or not:
> (user) Hey, I am Batman.
> (zayo) Nice to meet you, Batman! I am Zayo
or
> (user) Have we been introduced?
> (zayo) I am Zayo
> (zayo) What is your name?
> (user) Batman
> (zayo) Nice to meet you, Batman!
Persistent state
Skills can persist data such as lists, counters, etc. Persistent storage is Zayo's "memory":
Skill.define 'names' do
teach do
entity :name, lookups: ['free-text']
intent 'talkative/introduction' do
sample 'have we been introduced?'
sample "let's have an introduction"
sample "I don't know you"
sample('hey, I am Georgi') { entity :name, 'Georgi' }
sample('I am Batman') { entity :name, 'Batman' }
sample('my name is Superman') { entity :name, 'Superman' }
end
intent 'talkative/hi' do
sample 'hi'
sample 'hey'
end
end
on_intent 'talkative/introduction' do |event|
name = event.entities.single.name
storage do |store|
store[:names] ||= {}
store[:names][context.user.id] = name
end
if name
say "Nice to meet you, #{name}! I am Zayo"
else
say 'I am Zayo'
name = ask_for_string 'What is your name?'
say "Nice to meet you, #{name}!"
end
end
on_intent 'talkative/hi' do |event|
name = storage { |store| (store[:names] || {})[context.user.id] }
if name
say "Hey, #{name}!"
else
say "Hello :)"
end
end
end
Extracting dates and time from messages
Skills can parse natural-language date and time input. This is done with Duckling (more specifically, a fork to support BG input: https://github.com/georgyangelov/duckling/tree/add-bg-time).
Skill.define 'reminder' do
teach do
entity :about, lookups: ['free-text']
intent 'reminder/new' do
sample 'remind me'
sample('remind me to do the dishes') { entity :about, 'do the dishes' }
sample('remind me tomorrow to buy a beer') { entity :about, 'buy a beer' }
sample('remind me to call Joe in 10 minutes') { entity :about, 'call Joe' }
sample('remind me to empty the trash can tomorrow') { entity :about, 'empty the trash can' }
end
end
on_intent 'reminder/new' do |event|
about = event.entities.single.name
about = ask_for_string 'What should I remind you about?' if about.blank?
time = event.extracted_time future: true
time = ask_for_time 'When should I remind you?' unless time
if time.past?
say 'I can\'t remind in the past, unfortunately :disappointed:'
next
end
schedule.at time, :remind, about
end
def remind(about)
say "Hey! You wanted me to remind you about `#{about}`"
end
end
extracted_time
and ask_for_time
try to disambiguate any datetimes they are not sure about. For example, if given tuesday
as an input, it will give the user a couple of options (this tuesday, next tuesday) and ask for a choice. Another example is for times such as at 10
- it will ask if this is at 10pm
or at 10am
.
API Docs
Want to learn more? Take a look at: