How to Build a Skill
Before you start coding the skill
- Pick a name for the skill. Skill names should be unique so pick something that nobody else will take accidentally.
- Create a file in
skills/
with the skill name (use snake_case). - Use that file for the skill's code. Whenever the code there changes, you need to rebuild and restart the docker container (
docker-compose up -d --build
).
Creating the skill
All skill code should be defined inside of a Skill.define
block:
Skill.define 'skill name' do
# Skill code here
end
Directly in the skill's define block, you can attach listeners (SkillActions.listen_for, SkillActions.on_intent, SkillActions.on_reaction), provide Wit.ai examples (SkillActions.teach) or hook into the skill lifecycle (SkillActions.on_start, SkillActions.on_stop).
For all available top-level actions and how to use them, see the class methods on SkillActions.
Interactions and context
Whenever some of the above listeners get triggered, the code provided to them is executed in an interaction. The interaction is a "thread" (specifically, a Fiber / Green Thread) and provides a context for some actions inside of the handler code.
For example, the following code will know where to reply to the user's original ping
message:
Skill.define 'skill name' do
listen_for 'ping' do
say 'pong'
end
end
Using this implicit context, the say
method knows to either reply in a thread (if on a public channel) or send a direct message (if the ping
is a direct message). See SlackMessageAbilities#say for more details on the behaviour.
The interaction context includes the original message that started the interaction, the user who sent that message, what channel it was on, etc.
Abilities
Abilities are tools that skills can use to achieve their goal. In programmer terms, abilities are helper methods providing functionality. See SkillActions and all of its instance methods (including the ones included from modules) for a complete list and documentation on each one.
Some abilities are called directly (as with say
above), while others are used through an object (e.g. google_calendar.create_event
).
Schedules
Zayo can keep track of time and trigger some interactions at a specified moment.
Skill.define 'ping skill' do
listen_for 'ping' do
schedule.at 10.minutes.from_now, :pong
end
def pong
say 'pong'
end
end
The above skill listens for ping
(ping
on a DM or zayo, ping
in a public channel), then waits 10 minutes and responds with pong
.
The original interaction context is preserved and the say
method still knows where to send the message.
The schedules are also saved to persistent storage, so even if Zayo is restarted, the schedule will not be lost.
Switching channels
Sometimes you want to send a message to a different channel. This can be done using SkillActions#in_channel.
Accessing persistent storage
See SkillActions#storage. Storage is scoped to a skill, so that skills don't step on their toes. However, SkillActions#storage acts as a transaction so other skills trying to access the storage at the same time must wait. Keep the storage open as less time as possible.
Strings and Internationalization
Some methods (e.g. SlackMessageAbilities#say and SlackQuestionAbilities#ask_string) can accept both strings and symbols as a message argument.
If the argument is a string (say 'hi'
), it is sent as-is.
If the argument is a symbol (say :hi
), it is used as a key and a message is taken from config/string.yml
corresponding to that key (the top-level hi
key in this example). The symbols can include .
, which denotes a subkey. For example, say :'food.lets_go'
looks for the nested key lets_go
inside of food
.
If the key is set to a list instead of a string, one of the elements of the array is taken at random.
Configuring Wit.ai intents
TODO
Print natural language strings for dates
Instead of saying something like You will be reminded at 2019-10-05T10:09:00Z.
, you can say You will be reminded tomorrow at 10:09 in the morning.
. The SkillActions#humanize method can be used to transform dates and times to human-like strings:
Skill.define 'ping skill' do
listen_for 'ping' do
ping_time = 10.minutes.from_now
say "You will be reminded #{humanize(ping_time)}"
schedule.at ping_time, :pong
end
def pong
say 'pong'
end
end
I want to learn more!
You can take a look at what abilities there are - see the instance methods of SkillActions.