Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/danielberkompas/telephonist

Elixir state machines for Twilio calls
https://github.com/danielberkompas/telephonist

elixir hex twilio

Last synced: about 2 months ago
JSON representation

Elixir state machines for Twilio calls

Lists

README

        

Telephonist
===========

[![Hex Version](http://img.shields.io/hexpm/v/telephonist.svg)](https://hex.pm/packages/telephonist)
[![Build Status](https://travis-ci.org/danielberkompas/telephonist.svg)](https://travis-ci.org/danielberkompas/telephonist)
[![Inline docs](http://inch-ci.org/github/danielberkompas/telephonist.svg?branch=master)](http://inch-ci.org/github/danielberkompas/telephonist)
[![Deps Status](https://beta.hexfaktor.org/badge/all/github/danielberkompas/telephonist.svg)](https://beta.hexfaktor.org/github/danielberkompas/telephonist)

Telephonist makes it easy to design state machines for [Twilio][twilio] calls.
These state machines bring TwiML and logic together in one place, making call
flows easier to maintain.

## Installation

Get it from [Hex](http://hex.pm) by adding it to your `deps` in `mix.exs`:

```elixir
def deps do
[{:telephonist, "~> 0.1.2"}]
end
```

Run `mix deps.get` to install the package. Then, add `:telephonist` to your
applications list. For example:

```elixir
def application do
[mod: {YourApp, []},
applications: [:logger, :telephonist]]
end
```

This will ensure that all Telephonist's processes are started and supervised
properly.

## Usage

### Basic Concepts

Like most state machines, Telephonist state machines are based on two concepts:
state and transitions.

#### State

A state is represented by the `Telephonist.State` struct.

```elixir
%Telephonist.State{
machine: MachineName,
name: :state_name,
options: [],
twiml: " a map of parameters that came from Twilio
# options -> any custom options that are appended to the call over time
CustomStateMachine.state(:introduction, twilio, options)
```

You can pattern match with the `state/3` struct just like a function definition.

```elixir
state :introduction, _twilio, %{error: msg} do
say "An error occurred! #{msg}"
end

state :introduction, _twilio, _options do
say "Welcome to my phone tree!"
end
```

### Transitions

Transitions are handled through the `transition/3` function. It takes the same
three arguments as the `state/3` function or macro.

- `state_name`: the name of the state that is being transitioned from.
- `twilio`: a map of parameters passed in from Twilio.
- `options`: a map of custom parameters defined by you.

You can define it on your state machines like so:

```elixir
defmodule CustomCallFlow do
use Telephonist.StateMachine, initial_state: :choose_language

state :choose_language, twilio, options do
say "#{options[:error]}" # say any error, if present
gather timeout: 10 do
say "For English, press 1"
say "Para español, presione 2"
end
end

state :english, twilio, options do
say "Proceeding in English..."
end

state :spanish, twilio, options do
say "Procediendo en español..."
end

# If the user pressed "1" on their keypad, transition to English state
def transition(:choose_language, %{Digits: "1"} = twilio, options) do
state :english, twilio, options
end

# If the user pressed "2" on their keypad, transition to Spanish state
def transition(:choose_language, %{Digits: "2"} = twilio, options) do
state :spanish, twilio, options
end

# If neither of the above are true, append an error to the options and
# remain on the current state
def transition(:choose_language, twilio, options) do
options = Map.put(options, :error, "You pressed an invalid digit. Please try again.")
state :choose_language, twilio, options
end
end
```

Note that `transition/3` must return a `Telephonist.State`. This is easily done
by simply calling the `state/3` function. Also, note that you can easily switch
to _another_ state machine by simply calling `state` on it:

```elixir
def transition(:choose_language, %{Digits: "1"} = twilio, options) do
EnglishCallFlow.state(:introduction, twilio, options)
end

def transition(:choose_language, %{Digits: "2"} = twilio, options) do
SpanishCallFlow.state(:introduction, twilio, options)
end
```

Control of the call will then be passed to the other state machine. This allows
you to keep your state machines small, focused, and potentially reusable.

#### on_complete/3

When a call completes, Telephonist will call the `on_complete/3` callback. It
will receive the `Telephonist.State` of the call at the time it completed,
Twilio's final request parameters, and the custom options the call accumulated
during its life:

```elixir
def on_complete({sid, twilio_call_status, state}, twilio, options) do
:ok
end
```

This is a good place to put any cleanup logic that you need to perform after a
call completes.

#### on_transition_error/4

This callback will be run if a transition fails due to an exception. This will
most often occur when you fail to define a transition or state, or if your
pattern matching left a case out. It provides you an opportunity to recover the
call and prevent the user from hearing a Twilio error message.

```elixir
def on_transition_error(exception, state_name, twilio, options) do
# To prevent an error, return a new state:
state :recover, twilio, options
end
```

The default implementation of `on_transition_error/4` that comes with
`Telephonist.StateMachine` will simply re-raise the error.

### Processing Calls

Once you've defined a state machine, you can process calls through it using
`Telephonist.CallProcessor`.

```elixir
# The web framework shown here is pseudo-code
def index(conn, twilio) do
options = %{} # Whatever I want to be able to use in my states and transitions
state = Telephonist.CallProcessor.process(MyStateMachine, twilio, options)
render conn, xml: state.twiml
end
```

That's it! New calls will start off in `MyStateMachine.initial_state` and
progress from there.

### Subscribing to Events

Telephonist publishes events via `GenEvent`. In fact, `Telephonist.Logger` is
simply a subscriber to these events. Look there for an example of how to
implement your own subscriber.

## Other Twilio Libraries

See these other Elixir libraries I've written for Elixir:

- [ExTwilio][ex_twilio]. A Twilio API client.
- [ExTwiml][ex_twiml]. Render TwiML from Elixir. This is actually a dependency
of Telephonist, and is used in the `state/3` macro.

## LICENSE
Telephonist is under the MIT license. See the [LICENSE](/LICENSE.md) file for
more details.

[ex_twilio]: https://github.com/danielberkompas/ex_twilio
[ex_twiml]: https://github.com/danielberkompas/ex_twiml
[twilio]: http://twilio.com