{"id":13509634,"url":"https://github.com/danielberkompas/telephonist","last_synced_at":"2025-04-22T21:44:13.486Z","repository":{"id":30624345,"uuid":"34179767","full_name":"danielberkompas/telephonist","owner":"danielberkompas","description":"Elixir state machines for Twilio calls","archived":false,"fork":false,"pushed_at":"2016-04-16T17:40:59.000Z","size":74,"stargazers_count":41,"open_issues_count":5,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-29T19:11:12.115Z","etag":null,"topics":["elixir","hex","twilio"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielberkompas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-04-18T19:31:27.000Z","updated_at":"2024-06-25T17:53:28.000Z","dependencies_parsed_at":"2022-09-05T01:01:38.703Z","dependency_job_id":null,"html_url":"https://github.com/danielberkompas/telephonist","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielberkompas%2Ftelephonist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielberkompas%2Ftelephonist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielberkompas%2Ftelephonist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielberkompas%2Ftelephonist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielberkompas","download_url":"https://codeload.github.com/danielberkompas/telephonist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249098513,"owners_count":21212510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","hex","twilio"],"created_at":"2024-08-01T02:01:10.728Z","updated_at":"2025-04-17T01:31:44.149Z","avatar_url":"https://github.com/danielberkompas.png","language":"Elixir","funding_links":[],"categories":["Third Party APIs"],"sub_categories":[],"readme":"Telephonist\n===========\n\n[![Hex Version](http://img.shields.io/hexpm/v/telephonist.svg)](https://hex.pm/packages/telephonist)\n[![Build Status](https://travis-ci.org/danielberkompas/telephonist.svg)](https://travis-ci.org/danielberkompas/telephonist)\n[![Inline docs](http://inch-ci.org/github/danielberkompas/telephonist.svg?branch=master)](http://inch-ci.org/github/danielberkompas/telephonist)\n[![Deps Status](https://beta.hexfaktor.org/badge/all/github/danielberkompas/telephonist.svg)](https://beta.hexfaktor.org/github/danielberkompas/telephonist)\n\nTelephonist makes it easy to design state machines for [Twilio][twilio] calls. \nThese state machines bring TwiML and logic together in one place, making call \nflows easier to maintain.\n\n## Installation\n\nGet it from [Hex](http://hex.pm) by adding it to your `deps` in `mix.exs`: \n\n```elixir\ndef deps do\n  [{:telephonist, \"~\u003e 0.1.2\"}]\nend\n```\n\nRun `mix deps.get` to install the package.  Then, add `:telephonist` to your \napplications list. For example:\n\n```elixir\ndef application do\n  [mod: {YourApp, []},\n   applications: [:logger, :telephonist]]\nend\n```\n\nThis will ensure that all Telephonist's processes are started and supervised\nproperly.\n\n## Usage\n\n\n### Basic Concepts\n\nLike most state machines, Telephonist state machines are based on two concepts: \nstate and transitions.\n\n#### State\n\nA state is represented by the `Telephonist.State` struct.\n\n```elixir\n%Telephonist.State{\n  machine: MachineName,\n  name: :state_name,\n  options: [],\n  twiml: \"\u003c?xml ...\"\n}\n```\n\nStates are primarily used to define what TwiML should be displayed to Twilio for\na given call at a particular time. Telephonist provides a simple macro to make \ngenerating and returning `Telephonist.State` structs easy:\n\n```elixir\ndefmodule CustomStateMachine do\n  use Telephonist.StateMachine, initial_state: :introduction\n\n  state :introduction, _twilio, _options do\n    say \"Welcome to my phone tree!\"\n  end\nend\n```\n\nThe `state/3` macro is just sugar, and defines a function like this:\n\n```elixir\ndef state(:introduction, _twilio, options) do\n  xml = twiml do\n    say \"Welcome to my phone tree!\"\n  end\n\n  %Telephonist.State{\n    machine: __MODULE__,\n    name: :introduction,\n    options: options,\n    twiml: xml\n  }\nend\n```\n\nThe three arguments are as follows:\n\n- `state_name`: the name of the state, obviously.\n- `twilio`: a map of parameters sent in from Twilio.\n- `options`: a map of custom options defined by you at various points during the\n  call's lifecycle.\n\nWhenever Telephonist wants to get a particular state out of your module, it will\ncall the `state/3` function generated by the `state/3` macro, like so:\n\n```elixir\n# twilio  -\u003e a map of parameters that came from Twilio\n# options -\u003e any custom options that are appended to the call over time\nCustomStateMachine.state(:introduction, twilio, options)\n```\n\nYou can pattern match with the `state/3` struct just like a function definition.\n\n```elixir\nstate :introduction, _twilio, %{error: msg} do\n  say \"An error occurred! #{msg}\"\nend\n\nstate :introduction, _twilio, _options do\n  say \"Welcome to my phone tree!\"\nend\n```\n\n### Transitions\n\nTransitions are handled through the `transition/3` function. It takes the same\nthree arguments as the `state/3` function or macro.\n\n- `state_name`: the name of the state that is being transitioned from.\n- `twilio`: a map of parameters passed in from Twilio.\n- `options`: a map of custom parameters defined by you.\n\nYou can define it on your state machines like so:\n\n```elixir\ndefmodule CustomCallFlow do\n  use Telephonist.StateMachine, initial_state: :choose_language\n\n  state :choose_language, twilio, options do\n    say \"#{options[:error]}\" # say any error, if present\n    gather timeout: 10 do\n      say \"For English, press 1\"\n      say \"Para español, presione 2\"\n    end\n  end\n\n  state :english, twilio, options do\n    say \"Proceeding in English...\"\n  end\n\n  state :spanish, twilio, options do\n    say \"Procediendo en español...\"\n  end\n\n  # If the user pressed \"1\" on their keypad, transition to English state\n  def transition(:choose_language, %{Digits: \"1\"} = twilio, options) do\n    state :english, twilio, options\n  end\n\n  # If the user pressed \"2\" on their keypad, transition to Spanish state\n  def transition(:choose_language, %{Digits: \"2\"} = twilio, options) do\n    state :spanish, twilio, options\n  end\n\n  # If neither of the above are true, append an error to the options and\n  # remain on the current state\n  def transition(:choose_language, twilio, options) do\n    options = Map.put(options, :error, \"You pressed an invalid digit. Please try again.\")\n    state :choose_language, twilio, options\n  end\nend\n```\n\nNote that `transition/3` must return a `Telephonist.State`. This is easily done\nby simply calling the `state/3` function. Also, note that you can easily switch\nto _another_ state machine by simply calling `state` on it:\n\n```elixir\ndef transition(:choose_language, %{Digits: \"1\"} = twilio, options) do\n  EnglishCallFlow.state(:introduction, twilio, options)\nend\n\ndef transition(:choose_language, %{Digits: \"2\"} = twilio, options) do\n  SpanishCallFlow.state(:introduction, twilio, options)\nend\n```\n\nControl of the call will then be passed to the other state machine. This allows\nyou to keep your state machines small, focused, and potentially reusable.\n\n#### on_complete/3\n\nWhen a call completes, Telephonist will call the `on_complete/3` callback. It\nwill receive the `Telephonist.State` of the call at the time it completed,\nTwilio's final request parameters, and the custom options the call accumulated\nduring its life:\n\n```elixir\ndef on_complete({sid, twilio_call_status, state}, twilio, options) do\n  :ok\nend\n```\n\nThis is a good place to put any cleanup logic that you need to perform after a\ncall completes.\n\n#### on_transition_error/4\n\nThis callback will be run if a transition fails due to an exception. This will\nmost often occur when you fail to define a transition or state, or if your\npattern matching left a case out. It provides you an opportunity to recover the\ncall and prevent the user from hearing a Twilio error message.\n\n```elixir\ndef on_transition_error(exception, state_name, twilio, options) do\n  # To prevent an error, return a new state:\n  state :recover, twilio, options\nend\n```\n\nThe default implementation of `on_transition_error/4` that comes with\n`Telephonist.StateMachine` will simply re-raise the error.\n\n### Processing Calls\n\nOnce you've defined a state machine, you can process calls through it using\n`Telephonist.CallProcessor`.\n\n```elixir\n# The web framework shown here is pseudo-code\ndef index(conn, twilio) do\n  options = %{} # Whatever I want to be able to use in my states and transitions\n  state = Telephonist.CallProcessor.process(MyStateMachine, twilio, options)\n  render conn, xml: state.twiml\nend\n```\n\nThat's it! New calls will start off in `MyStateMachine.initial_state` and\nprogress from there. \n\n### Subscribing to Events\n\nTelephonist publishes events via `GenEvent`. In fact, `Telephonist.Logger` is\nsimply a subscriber to these events. Look there for an example of how to\nimplement your own subscriber.\n\n## Other Twilio Libraries\n\nSee these other Elixir libraries I've written for Elixir:\n\n- [ExTwilio][ex_twilio]. A Twilio API client.\n- [ExTwiml][ex_twiml]. Render TwiML from Elixir. This is actually a dependency\n  of Telephonist, and is used in the `state/3` macro.\n\n## LICENSE\nTelephonist is under the MIT license. See the [LICENSE](/LICENSE.md) file for \nmore details.\n\n\n[ex_twilio]: https://github.com/danielberkompas/ex_twilio\n[ex_twiml]: https://github.com/danielberkompas/ex_twiml\n[twilio]: http://twilio.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielberkompas%2Ftelephonist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielberkompas%2Ftelephonist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielberkompas%2Ftelephonist/lists"}