Attentive
Attentive is a library for matching messages to natural-language listeners.
Usage
Its basic usage is like this:
include Attentive
listen_for "hi", context: { in: :any } do
puts "nice to meet you!"
end
hear! "hi!" # => "nice to meet you!"
In the snippet above,
- We defined a listener that is active in any context.
- We received a message.
- Attentive matched the message to our listener and invoked the block.
Optional Characters
You'll notice that we listened for "hi"
but heard "hi!"
. Attentive treats punctuation and emojis as optional; but we can make them required by putting them in the listener:
listen_for "hi!", context: { in: :any } do
puts "nice to meet you!"
end
hear! "hi" # => nothing happened, the listener is expecting the exclamation mark
It's best to leave all but the most necessary punctuation out of listeners.
Contractions and Abbreviations
Attentive understands contractions and abbreviations and can match those:
listen_for "hi", context: { in: :any } do
puts "nice to meet you!"
end
hear! "hello!" # => "nice to meet you!"
listen_for "what is for lunch", context: { in: :any } do
puts "HAMBURGERS!"
end
hear! "what's for lunch?" # => "HAMBURGERS!"
Although you can use contractions and abbreviations in listeners, it's a good habit not to. Attentive will not let you define listeners that use ambiguous contractions like
"where's"
("where's"
might be a contraction for"where is"
,"where does"
, or"where has"
, or"where was"
).
Listeners
Listeners are defined with three things:
- One or more phrases
- A set of contexts where they're active
- A block to be invoked when the listener is matched
Here's an example of a listener that matches more than one phrase:
listen_for "what is for lunch",
"what is for lunch {{date:core.date.relative.future}}",
"what is for lunch on {{date:core.date}}",
"show me the menu for {{date:core.date.relative.future}}",
"show me the menu for {{date:core.date}}" do
# ...
end
(In the example above, the phrases {{date:core.date.relative.future}}
and {{date:core.date}}
are entities: which we'll cover in a minute.)
Contexts
A listener can require that messages be heard in a certain context in order to be matched or it can ignore messages if they are heard in certain contexts.
The following is a listener that will only match messages heard in the "#general" channel and only then if the conversation is not "serious".
listen_for "ouch", context: { in: %i{general}, not_in: %i{serious} } do
puts "On a scale of 1 to 10, how would you rate your pain?"
end
hear! "ouch" # => message has no context, listener isn't triggered
hear! "ouch", contexts: %i{general} # => "On a scale of 1 to 10..."
hear! "ouch", contexts: %i{general serious} # => listener ignores "serious" messages
If you don't specify context requirements for listeners, Attentive requires conversation
and prohibits quotation
by default:
# These two are the same:
listen_for "ouch"
listen_for "ouch", context: { in: %i{conversation}, not_in: %i{quotation} }
Entities
Entities allow Attentive to match concepts rather than specific words.
There are built-in entities like core.date
, core.number
, and core.email
for recognizing dates, numbers, and email addresses (see Core Entities for a complete list); but you can also define entities for domain-specific concepts. For example:
Attentive::Entity.define "deweys.menu.beers",
"Bell's Oberon",
"Rogue Dead Guy Ale",
"Schalfly Dry Hopped IPA",
"4 Hands Contact High",
"Scrimshaw Pilsner"
Now we can take drink orders:
listen_for "I will have a pint of the {{deweys.menu.beers}}" do
puts "Good choice"
end
It is a good idea to namespace entities (i.e.
deweys.menu.beers
). Attentive's convention is to treat namespaces as a taxonomy for concepts.
Regular Expressions
As useful as enumerations are, entities can also be defined with regular expressions and with a block that converts the matched part of the message to a more useful value:
# Usernames can be up to 21 characters long.
# They can contain lowercase letters a to z
# (without accents), and numbers 0 to 9.
Attentive::Entity.define "slack.user", %q{(?<username>[a-z0-9]{1,21})} do |match|
Slack::User.find match["username"]
end
Whenever possible, though, prefer composing entities to using regular expressions. For example:
Attentive::Entity.define "core.date.relative.future", "next {{core.date.wday}}"is better than:
Attentive::Entity.define "core.date.relative.future", "next (?<weekday>(:sun|mon|tues|wednes|thurs|fri|satur)day)"
Installation
Add this line to your application's Gemfile:
gem 'attentive'
And then execute:
$ bundle
Or install it yourself as:
$ gem install attentive
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/houston/attentive.
License
The gem is available as open source under the terms of the MIT License.