Rails から twilio を使って電話発信してみた ( SMS は対象外ね )

# Rails で twilio を使って電話発信

今作ってるシステムで、予約時に電話で知らせるっていう機能を作るために、
twilio を使って実装した時のメモ。

## twilio アカウント作成

[twilio](http://twilio.kddi-web.com/) でアカウントを作成。

初期登録時は無料トライアルで、検証済み番号にだけ電話でき、
クレジットカードを登録すると、有料会員になり、自由に電話をかけられるようになります。

ここで、発信用に TWILIO 電話番号を購入しときます。
下記は購入した番号の詳細画面です。

![TwilioPhoneNumber.png](https://qiita-image-store.s3.amazonaws.com/0/40603/e824cf4c-8531-49d9-3be9-410fd3ffb3eb.png)

このページの Request URL に TwiML の URL を設定しておくと、この番号に電話がかかってきた際に、自動音声を流すことが出来ます。
今回は、用途が発信専用のため、その旨のメッセージを流す TwiML の URL を設定しておきました。

また、購入した電話番号と、API クレデンシャル ( AccountSID、 AuthToken ) は API 実行時に必要で、
API クレデンシャルは、管理ページのアカウントから確認でき、本番用、テスト用があり、
テスト用は実際には電話はかからないようです。

## gem をインストール

Gemfile に `gem 'twilio-ruby'` を追記して `bundle install`

## config ファイル作成
環境によって、設定を分けるために、config ファイルを用意。
内容は、上でチェックした情報をそのまま記載。

```yaml:config/twilio.yml
default: &default
  account_sid:  ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  auth_token:   yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
  from_tel:     '05000000000'

development:
  <<: *default

production:
  <<: *default

test:
  <<: *default
```

## initializer を用意
Rails の Application.config から参照できるよう twilio.yml を起動時に読み込む initializer を用意。
Hoge の部分は自身のアプリケーション名にして下さい。

```ruby:config/initializers/twilio.rb
path = Rails.root.join('config/twilio.yml')
if path.exist?
  Hoge::Application.config.twilio_config = YAML.load(path.read)[Rails.env].to_options
else
  Hoge::Application.config.twilio_config = {}
end
```

## TwiML 作成
TwiML とは、twilio で電話やSMSが着信した場合の制御を行うためのXML形式のファイルです。
XML ファイルで専用のタグをかくことによって、色々操作できます。


```xml:reserved.xml
<Response>
  <Pause length="2"/>
  <Say voice="woman" language="ja-jp">こちらは、ホゲホゲです。ユーザー様より予約がはいりました。管理画面から予約の確認をお願いします。</Say>
</Response>
```

予約が入ったことを知らせるメッセージを再生したいので、上記は、通話開始後、２秒待って、自動音声の女性の声で、日本語で「こちらは、ホゲホゲです。ユーザー様より予約がはいりました。管理画面から予約の確認をお願いします。」と読み上げる指示になっています。
Pause を入れないと、通話開始後すぐに自動音声が再生されてしまい、「こちらはホゲホゲです。」の部分が聞き取れなかったので、入れました。

下記は、TwiML で使えるタグです

|タグ名|タグの説明|
|------|--------|
|Say|テキストを読み上げ|
|Play|発信者に音声ファイルを再生|
|Dial|通話に参加者を追加|
|Record|発信者の声を録音|
|Gather|発信者がキーパッドでダイヤルした数字を収集|
|Sms|通話中に SMS メッセージを送信|
|Hangup|通話を終了|
|Queue|発信者をキューに追加|
|Redirect|コールフローを別の TwiML 文書へリダイレクト|
|Pause|次の指示を実行する前にポーズを挿入|
|Reject|着信通話を拒否します。通話は課金されない|

この TwiML 、すご～くめんどくさいのが、このファイルを外部アクセス可能なサーバーに置かなきゃいけないところです。
API 実行時に、このファイルの URL も送信し、それを twilio のサーバーが読み取り、その指示にしたがって処理します。

Rails でこの TwiML を返す controller を書いてもいいのですが、それも結構めんどくさい。
しかも、これって一旦作成したらあんまり変更する機会無いよねっていう場合は、下記のサイトを利用すれば楽ちんです。

このサイトですが、指定した TwiML を返す URL を生成してくれます。

https://www.twilio.com/labs/twimlets/echo

上記ページの TwiML のテキストエリアに、上記の reserved.xml の中身を貼り付けると下記のような URL が表示されます。

http://twimlets.com/echo?Twiml=%3CResponse%3E%0A%20%20%3CPause%20length%3D%222%22%2F%3E%0A%20%20%3CSay%20voice%3D%22woman%22%20language%3D%22ja-jp%22%3E%E3%81%93%E3%81%A1%E3%82%89%E3%81%AF%E3%80%81%E3%83%9B%E3%82%B2%E3%83%9B%E3%82%B2%E3%81%A7%E3%81%99%E3%80%82%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E6%A7%98%E3%82%88%E3%82%8A%E4%BA%88%E7%B4%84%E3%81%8C%E3%81%AF%E3%81%84%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82%E7%AE%A1%E7%90%86%E7%94%BB%E9%9D%A2%E3%81%8B%E3%82%89%E4%BA%88%E7%B4%84%E3%81%AE%E7%A2%BA%E8%AA%8D%E3%82%92%E3%81%8A%E9%A1%98%E3%81%84%E3%81%97%E3%81%BE%E3%81%99%E3%80%82%3C%2FSay%3E%0A%3C%2FResponse%3E&

長いですがこれを API 実行時の送信してやればちゃんと処理してくれます。
今回は、予約を知らせる電話だけで、複雑な TwiML を書く必要もないため、これを利用して実装しました。

ちなみに、TwiML を作成するためのモジュールが twilio-ruby ( Twilio::TwiML::Response ) に含まれてます。
今回は使用してませんが。

## 発信処理実装
今回は発信履歴 ( API の実行履歴 ) を残しておくために、モデルを追加して、
after_craete で実際に発信処理 ( twilio API 実行 ) を行って、結果を保存する実装になっています。

実行時のパラメータ、結果を保持するために、下記 attribute を追加してます。

|attribute|description|
|-----------|------------|
|to_tel|発信先電話番号|
|from_tel|発信元電話番号|
|url|TWiML の URL|
|result|API の実行結果 ( 成功:0, 失敗:1 )|
|path|API成功時に返却されるログのパス|
|error_code|API 失敗時に返される twilio のエラーコード|
|error_message |エラーメッセージ|

成功時に返却される path は `https://api.twilio.com` 後につなげてブラウザでアクセスすると、XML 形式で表示されます。
Basic 認証で、ID/PASS を聞かれますが、account_sid/auth_token を入力すると見れます。

なお、Twiio での電話番号については、「E.164形式」という国際電話で架けられる形式を使用しますので、
送信時に変換してます。

```ruby:app/models/twilio_call.rb
class TwilioCall < ActiveRecord::Base

  validates :from_tel, :to_tel, :url, presence: true

  after_create do
    call
  end

  def self.twilio_client
    account_sid = Hoge::Application.config.twilio_config[:account_sid]
    auth_token = Hoge::Application.config.twilio_config[:auth_token]
    @client ||= Twilio::REST::Client.new account_sid, auth_token
  end


  private
  def call
    TwilioCall.twilio_client.account.calls.create(
      from: from_tel.gsub('-', '').gsub(/\A0/,'+81'), # 発信元電話番号
      to:   to_tel.gsub('-', '').gsub(/\A0/,'+81'),   # 発信先電話番号
      url:  url                                       # TwiML URL
    )
    update(result: 0, path: result.uri) # 成功した場合は、ログのパスを保存
  rescue
    code = $!.code rescue nil # TimeOut 等の場合は、code が返ってこないので
    # 失敗した場合は、エラーコードと、エラーメッセージを保存
    update(result: 1, error_code: code, error_message: $!.message)
  end
end
```

不正な電話番号に発信した場合や、twilio サーバー側でエラーが出た場合には、
Twilio::REST::RequestError の例外が発生するので、rescue します。

通信ができなかったりして、TimeOut することもあるので、念のため全ての例外を拾うようにしてます。

また、発信先が不正な電話番号の場合は、Twilio::REST::RequestError と共に code で `21211` が返るので、それをフックして、
電話番号変えてくれなどのメールをユーザーに送ることも可能です。

実装自体はホント簡単でした。
実装するまでの調べる時間のほうがかかったな…  

## 参考URL
[twilio](http://twilio.kddi-web.com/)
[twilio ドキュメント](https://jp.twilio.com/docs)
[twilio TwiML](https://www.twilio.com/docs/api/twiml)
[twilio エラーコード](https://www.twilio.com/docs/errors/reference)
[TwiML 作成サイト](https://www.twilio.com/labs/twimlets/echo)