Category Archives: Twilio

Twilioコールセンターアプリ「Runa-CCA」バージョン2.0リリース(簡易CRMとポップアップ機能追加)

Twilioコールセンターアプリ「Runa-CCA」バージョン2.0をリリースしました!
簡易CRMとお客様情報ポップアップ機能追加という、大きな変更だったので、メジャーバージョンを上げました。

簡易CRMはその名の通り簡易な顧客情報管理機能です。
応対履歴も登録可能です。
顧客情報・応対履歴のカラムは今後増やしていきたいと思います。
ソフトフォンからも顧客情報・応対履歴の閲覧・編集を可能にしました。JQueryでAjaxを使って、画面を更新させています。ソースコードに無駄があるような気がするので、これもブラッシュアップしたいところです。

お客様情報のポップアップ画面です。

Runa-CCA_PopUp

残念ながらTwilioは、お客様がQueueから出たとき(オペレータがお客様をQueueから出したとき)にイベントを送りません。以下のような作りにして無理くりポップアップさせています。

・オペレータのCallSidを取得。オペレータはQueueに発信します。発信するとその発信呼のCallSidを取得できます。TwilioのConnectionのオブジェクトのプロパティ(connection.parameters.CallSid)として取得可能です。
・Twilioでは、お客様とオペレータがそれぞれ別のTwiML動詞を使って同じQueueに入ります。そうすることで、オペレータはお客様をQueueから出すことができます。オペレータがQueue動詞を使ってQueueに入るとき、urlを指定します。urlには、お客様がQueueから出るときに実行させるTwiML文書のURLを指定します。このURLで指定されるPHPプログラムで、コールの情報を取得し、DBに格納します。ここで、お客様の電話番号である「From」と、「DequeingCallSid」という値がDBに格納されることになります。「DequeingCallSid」というのは、発信者をキューから外した通話のCallSid、すなわち、オペレータのCallSidになります。
・オペレータとお客様との通話が確立した後に、オペレータが「お客様情報ポップアップ」ボタンを押すと、オペレータのCallSid(DequeingCallSid)を検索キーにして、お客様の電話番号(From)を見つけて、それをもとにお客様の情報を表示します。関連して、お客様が入っていたキューの情報と、お客様が電話した電話番号(To)も取得して表示させます。これにより、
 ・お客様はどなたなのか?
 ・お客様はどの電話番号とどのキューに電話をかけてきたのか?
 をオペレータがすぐわかるようにしています。
・お客様の電話番号は一意ではありません。例えば、お父さんとお母さんが居て、家の電話からコールセンタに電話してきたとすると、1つの電話番号で2人が検索されます。そのため、ポップアップ表示ではお客様のリストを表示する仕様となっています。
・お客様が電話番号を通知してくるとは限りません。その場合は、お客様のお名前や電話番号、お客様IDをお伺いし、検索します。登録されていないお客様の場合は新規に登録を行います。

うーん、無理くりだな。
手動なのをポップアップとは言わない気もしますが、まあ許してくださいませ。

追加した機能の一覧は以下の通りです。

  • 簡易CRMとお客様情報ポップアップ
    • お客様情報の検索・追加・変更・削除機能を追加。
    • 応対履歴登録機能を追加。
    • お客様情報ポップアップ機能を追加。Twilioはキューから出るときにイベントを送らないので、手動でボタンを押して情報を取得する必要があります。
  • その他
    • twilio.jsを1.2に変更。
    • デザイン統一(シンプル化)。

ソースコードは以下から。

Twilioコールセンターアプリ「Runa-CCA」バージョン1.2リリース(ソフトフォンへ機能追加)

Twilioコールセンターアプリ「Runa-CCA」バージョン1.2リリースしました。

ソフトフォンにミュート/ミュート解除・DTMF信号送信用ダイヤルパッド機能を搭載しました。
以下のイメージになります。

Runa-CCA_Softphone1

Runa-CCA_Softphone2-Mute

Runa-CCA_Softphone3-Dialpad

コールセンターには他にも、保留・転送・会議・モニタ機能が必要になります。
JavaScriptライブラリ「twilio.js」の充実に期待!というところでしょうか。

README.mdにも書いてありますが、他にも変更していて、変更点をまとめると以下の通りです。

  • ソフトフォン
    • ミュート/ミュート解除、ダイヤルパッド(DTMF送信用)ボタンを追加。
    • ボタンの表示・非表示を追加(通話設立前はHangUpボタン非表示等)。
    • ソフトフォン小型化。
    • ソフトフォン起動前に、オペレータが持っているキュー情報を更新する仕様に変更。
  • Webクライアント
    • キューID(TwilioのFriendly Name)の空白を非許容に変更(CSSと相性悪いので)。a-Z0-9に加えて#_-を許容。
    • キューを持っていないオペレータの表示がエラーになっていたので、エラーにならないように修正。
  • その他
    • サンプルSQLのキュー作成部分を削除(Webクライアントからキューを作成してください)。

ソースコードは以下から。

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム(4.コールフロー(1.コールフロー起動まで編))

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム
1.アプリ紹介編
2.環境設定
 1.ライブラリインストール編
 2.Apache mod_rewrite設定編
 3.CSSとJavaScript編
3.“Twilioコールフローフレームワーク”編

の続きです。

コールフローを作ります。
Twilioからリクエストされることで、コールフローが起動します。以下のような流れ。

  1. お客様が050番号に電話する。
  2. Twilioによって、050番号の「音声通話」に登録されているRequest URLが、サーバーアプリ(Runa-CCA)にリクエストされる。
  3. Runa-CCAでリクエストを処理する。

「050番号の「音声通話」に登録されているRequest URL」ですが、Runa-CCAでは新サービス・製品窓口のコールフロー(NewService)をサンプルとして用意していて、「http://ドメイン名/runa-cca/twilio/callflow/newservice/main」になります。このURLは、Apache mod_rewrite設定の通り、index.phpで処理されることになります。

“Twilioコールフローフレームワーク”編」に書いた通り、index.phpは、Slimのオブジェクトを作って、ルータ(\Runa_CCA\Route.php)に処理を渡す役割を負います。具体的にはindex.phpの中の、

\Runa_CCA\Route::registration($app);

によって、ルータに処理が渡されます。
なお、index.phpでsession_start()とかしているのはWebアプリケーション機能用でして、コールフローのみであれば不要になります。

ルータ(\Runa_CCA\Route.php)のregistrationメソッドには以下のように書いてあります。
Request URLに従って、コントローラ(\Runa_CCA\Controller\CallFlow\NewService.php)のstart(“main”)メソッドを呼びます。
GETとPOSTの両方に対応するようにしているのはテスト用です。テスト終わったらPOSTで統一するべきだと思います。
main以外は別途Twilioからリクエストされるものになります。

        // CallFlow
        // "NewService"
        $app->map ('/twilio/callflow/newservice/main', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("main");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/wait', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("wait");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/info', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("info");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/enqueaction', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("enqueaction");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/guidance', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("guidance");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/statuscallback', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("statuscallback");
                    })->via('GET', 'POST');

コントローラ(\Runa_CCA\Controller\CallFlow\NewService.php)では、まず、Slimのインスタンスを取得し、Requestで送られてきたパラメータを全て取得します。

        // Get Parameters
        $app = \Slim\Slim::getInstance();
        $params = $app->request->params();

続いて、RequestがTwilioから来たものなのかどうかを検証します。
検証処理については、別記事「Twilioにおける認証(TwilioからのRequestデータ検証)について」に記載の通りです。

if((new \Runa_CCA\Model\Twilio())->validateTwilioRequest($app, $params)){

Trueが返ってきたら、Switch文で判定して、Modelクラスを呼びます。ここからがコールフローの本体です。コールフローでの処理が終わって、ModelクラスからTwiMLオブジェクト(\Services_Twilio_Twimlオブジェクト)が返ってきたら、それをTwiMLを作るVewクラスに渡します。

                    // Go to Main CallFlow
                    case "main" :
                    
                        $response = (new \Runa_CCA\Model\CallFlow\NewService\Main())->main($params);
                        (new \Runa_CCA\View\Twiml($app))->createTwiml($response);
                        break;

実は、ここのエラー処理には問題ありなのです。
Webアプリケーション機能とエラー処理を共有しておりまして、日本語でエラーページを返す仕様にしています。
しかしそうすると、Twilioホームページでログ確認するときに文字化けしてしまうんです。コールフロー用のエラーページを英語で作らないといけません。この点は別記事に書いておきました。
別途修正予定です。

TwiMLを作るTwiMLクラスですが、以下のようにTemplateを呼び出し、Twigでレンダリングさせます。

class Twiml {

    public function __construct($app){
        
        $this->app = $app;
        
    }

    public function createTwiml($twilioObj){
        
        $this->app->render(
            'TwiML/twiml.twig', 
            [
                'twiml'         => $twilioObj,
            ]
        ); 
        
    } 
}

Twigを使用している場合は、

{% autoescape true %}
ほげほげ
{% endautoescape %}

でエスケープされます。
しかし、TwiMLを出力させるので、オートエスケープをオンにしてしまうと、TwiMLのタグがすべてエスケープされてしまいます。そのため、

{% autoescape false %}
{{ twiml }}
{% endautoescape %}

として、エスケープしないようにしています。

しかし、例えばお客様から電話機で番号入力させて、それをTwilioで読み上げたりするような場合は、エスケープする必要があります。
twilio-phpで自動でエスケープしてくれるのですが、\Services_Twilio_Twimlクラスの__callマジックメソッドのコメントを読むと、「経験ない開発者の中にはアンパサンドをエスケープしない人がいる」といった記載がありました。twilio-phpには頼らずエスケープ処理せい、ということなんでしょう。

Runa-CCAでは、\Base\Conf内にエスケープするメソッドを用意しておりまして、TwiMLを作成するときにはそのメソッドを実行してエスケープするようにしています。

次回はコールフローの中身を見ていく予定です。

Twilioコールセンターアプリ「Runa-CCA」バージョン1.1.1リリース(ウォールボード機能搭載)

Twilioコールセンターアプリ「Runa-CCA」バージョン1.1.1リリースしました。

ウォールボード機能を搭載しました。
以下のイメージになります。

Runa-CCA_1_1_1_Wallboard

管理画面(Configurator)に「ウォールボード」リンクがあります。
そこをクリックすると、自分が所属しているキューのリアルタイム情報が表示されます。
表示項目は画像をご覧の通り。
画面は10秒更新にしました。

あと、TwiML出力方法の変更。Webクライアント側と合わせてTwigを利用するようにしました。不要ファイルの削除と、細かなバグ修正を実施しています。
アップしたとたんにバグを見つけたので、最新リリースは1.1.1になります。
もっと試験しないとな。

GitHubにアップしております。ソースコードは以下から。

Twilioのエラーページで日本語を使ってはいけません

Twilioのホームページでログを確認出来ます。

しかし、日本語の表示ができません。日本語のエラーページを返すようにしておいてからログ確認すると、以下のように文字化けしてしまいます。

<!DOCTYPE html>
<html lang="ja-JP">
    <head>
        <meta charset="utf-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
                <meta name="viewport" content="width=device-width, initial-scale=1">
                    <title>Runa-CCA</title>
                </head>
                <body>
                    <h1>?????????</h1>
      ???????????????????????????????????????????????????
  
                </body>
            </html>

Twilioでエラーページを返したい場合は英語にしておかないと、あとでデバッグしたときに何がなんだかわからなくなります。
Runa-CCAのバージョン1.0は日本語のエラーページを返すので、上記のような文字化けをしてしまうんですよね。直さなければ。

ちょっとした情報共有でした。

Twilioにおける認証(TwilioからのRequestデータ検証)について

Twilioは、

電話する→Twilioが電話番号にひも付けられたURLをリクエストする

という仕組みになっています。
Twilioはインターネット経由でリクエストしてきますので、リクエストを受けるアプリケーション側からすると、セキュリティ的にかなりの脅威です。対処しなければなりません。

対処の方法として以下のものがあります。

    1. Basic(基本)認証を使う
    2. アプリ側で対処する

前に作ったTwilio-MiniCCでは、Basic(基本)認証を前提にしていました。Twilio設定画面の電話番号のURL登録のところで、

http://A:B@C/D/index.php
A: Basic認証のユーザ名
B: Basic認証のパスワード
C: Webサーバのドメイン名
D: アプリを置いた場所

としてあげれば、基本認証がかかります。これでも十分かもしれません。

が、Runa-CCAはWebクライアント機能と、Twilioから呼び出されるコールフロー機能の2つを持っています。そして、PHPマイクロフレームワークSlimを使って、URLをリライトしてルーティングしていて、URLをWebクライアント機能とコールフロー機能とで共用していたため、Basic認証は使いづらいものでした(コールフロー側のみならず、Webクライアント側にもBasic認証がかかってしまう)。そこで、暫定策として、Runa-CCAのバージョン0.8では、Requestで送られてくるAccountSidを使って認証をかけていました。

見落とししていたのですが、Twilioにはもう一つの方法があります。「Validating Requests are coming from Twilio」です(日本語版もあります)。Twilioのダッシュボードに行くと得られるAUTH TOKENと、Requestで送られる値とをハッシュした値を、Twilioがヘッダーに付けてくれます。Requestを受けるアプリ側でも同じように値をハッシュして、それがTwilioの送るハッシュと値が合っていれば、そのRequestはTwilioから送られたものだと判定できます。

具体的なコーディング方法はこちらに書いてありますが、誤解を招く記載です。TwilioはPOSTデータ全てを使ってハッシュ値を算出するので、検証する側もPOSTデータ全てを使わなければなりません。$validator->validate()の3つ目の引数には、POSTデータをそのまま入れればよろしい。

Slimを使ってサンプルを作ってみましたので、公開します。

これを、

<?php
namespace Sample;

require '../vendor/autoload.php'; 

$app = \Slim\Slim::getInstance();
$params = $app->request->params();

$result = validateTwilioRequest($app, $params);

とすればTrue/Falseが返ってくるはずです。
ちなみに、Runa-CCAのVersion 1.0では、\Runa_CCA\Model\Twilioクラスに実装されています。コメントにミスがあったのと、ちょっと余計な処理しているので、別途直さなくては。あうあう。

ご参考まで。

Twilioコールセンターアプリ「Runa-CCA」バージョン1.0リリース

Twilioコールセンター(コンタクトセンター)用アプリケーションを作っていて、機能や画面イメージはこちらで紹介しています。
修正を行い、GitHubタグを1.0にしました。
0.8から以下を更新しています。

  • Webクライアントのキュー管理機能をTwilioと統合。Twilio REST APIを使用し、WebクライアントからTwilioのQueue追加・変更・削除が可能になりました。
  • Webクライアントのオペレータ管理・キュー管理において、トランザクション管理を実装。TwilioのREST APIが動作しなかった場合にもDBロールバックを行うようにしました。
  • Webクライアントの管理画面(Configurator)権限を以下の通りに整理しました。
    • Operator … 何も出来ない(自分のパスワード変更のみ)
    • Supervisor … オペレータ管理
    • SystemAdmin … キュー管理+オペレータ管理
  • Twilioからリクエストされる部分について、Twilio推奨の認証(検証)方法を導入しました。
  • 上記実現のため、ソースコードならびにDB構造の変更を行っています。

ソフトフォン機能の充実に進みたいところなのですが、JQueryとか学ばないといかんかなあ。
まずはその前に今までやってきたことのまとめだ。

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム(3.”Twilioコールフローフレームワーク”編)

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム
 1.アプリ紹介編
 2.環境設定
  1.ライブラリインストール編
  2.Apache mod_rewrite設定編
  3.CSSとJavaScript編

の続きです。

前回までで設置と設定が終わったはず。
今回は、Runa-CCAの「Webクライアント」機能と「コールフロー」機能のうちの、「コールフロー」機能の作りについて書きます。

Twilioのコールフロー機能を考えるにあたって、フレームワーク化したいなあと思いました。

お客様がコールセンターの電話番号に電話すると、Twilioの050の電話番号に登録されている「音声通話」のURLに対して、Twilioがリクエストを投げます。そのリクエストを受けて、コールフローが起動します。
Twilioの設定で、ある050番号の音声通話のURLに、例えば「http://Runa-CCAのドメイン名/runa-cca/twilio/callflow/newservice/main」を登録しておきます。そうすると、お客様がその050番号に電話してきたら、Twilioによって音声通話のURLがリクエストされるわけです。「Apache mod_rewrite設定編」で設定した通り、/runa-ccaに来たURLは、Apacheの機能で/runa-cca/index.phpにリライトされ、処理されることになります。

ここからが”Twilioコールフローフレームワーク”になります。
以下のような構成にしました。

  • 前提
    1. コールセンターには、コールフローがいくつもある。
    2. コールセンターにおいて、コールフローはビジネスロジックである。
    3.  コールセンターによってはコールフローはたくさんあります。数十とかあります。そしてコールフローは、コールセンター独自の仕組みであり、電話インタフェースと統計データベースとのやり取りを制御するものでもありますので、ビジネスロジックであろうと思います。ま、ビジネスロジックの定義は色々のようですが。

  • “Twilioコールフローフレームワーク”
    1. index.phpは、Slimのオブジェクトを作って、ルータ(\Runa_CCA\Route.php)に登録して、Slimを実行させる。(それだけしかしない)
    2. Route.phpは、URLに応じ、適切なController(コントローラ)にルーティングする。(それだけしかしない)前提1の通り、コールセンターにはコールフローがいくつもある。そのため、Controllerフォルダ配下にコールフロー毎にフォルダを作って、コールフロー毎のコントローラを一括管理する。言うまでもなくnamespaceも別にする。コールフローの場合、Route.phpは\Runa_CCA\Controller\CallFlow配下の適切なコールフローを呼び出す。
    3. コールフロー毎のコントローラが行うのは、Model配下のコールフロービジネスロジックの呼び出しと、Twilio用のTwiML作成Viewクラスの呼び出しだけ。(それだけしかしない)
    4. 前提2の通り、コールフローはビジネスロジック。そのため、コールフロー本体はModel(モデル)として整理。前提1の通り、コールフローはたくさんあるので、Model配下にコールフロー毎のフォルダを作って、そこに一括管理し、コールフローのIVR処理や統計データベースへのデータ保存といった処理を実行する。(ここでがっつりと処理を実施する)
    5. Modelのコールフローが処理を終えたら、Controllerに一度戻し、ControllerからViewが呼ばれて、TwiMLが作成され、TwilioにResponseとして返される。(それだけしかしない)

まあ大した話ではないのですが、そんな構造にしてみたわけです。
フォルダ構造は以下の通りになります。

/var/www/html/runa-cca/
 |-htdocs ... 公開用ディレクトリ
 |   |-index.php ... すべての入り口
 |
 |-lib ... 作成したクラスライブラリ
 |   |-Base ... 設定クラス置き場
 |   |-Runa_CCA ... Runa-CCAクラス置き場
 |       |-Route.php ... いわゆるルータ
 |       |
 |       |-Controller ... コントローラクラス置き場
 |       |   |-CallFlow ... コールフロー毎のコントローラクラス置き場
 |       |       |-NewService ... コールフロー「NewService」のコントローラクラス置き場
 |       |
 |       |-Model ... モデルクラス置き場
 |       |   |-CallFlow ... コールフロー毎のモデルクラス置き場
 |       |       |-NewService ... コールフロー「NewService」のモデルクラス置き場
 |       |
 |       |-Model ... ビュークラス置き場
 |
 |-templates ... テンプレートファイル

次回からはソースを追いかけながらコールフローの流れを書きます。

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム(2.環境設定:3.CSSとJavascript編)

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム
 1.アプリ紹介編
 2.環境設定
  1.ライブラリインストール編
  2.Apache mod_rewrite設定編

の続きです。

WebアプリケーションにはCSSフレームワークのBootstrapを使っています。bootstrapサイトからダウンロードし、

/var/www/html/runa-cca/
 |-htdocs ... 公開用ディレクトリ
 |   |-vendor
 |       |-bootstrap ... ここ!

に展開します。Bootstrap単体ではなく、テーマであるBootswatchを使っています。Bootswatchサイトからbootstrap.min.cssと、bootstrap.cssとをダウンロードし、/htdocs/vendor/bootstrap配下のファイルを置き換えます。

ほかに、

・JQuery
・Twilio.js(Twilioクライアントライブラリ)

を使っていますが、ソース指定で利用するため、インストールは不要です。

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム(2.環境設定:2.Apache mod_rewrite設定編)

WebサーバにApacheを使っている場合の設定は、SlimのGitHubに極めてシンプルにまとめられています。

以下のように.htaccessに書いてね、とのことです。さっぱりとした説明ですな。

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]

Slimのパッケージにもサンプルが付いてきます。
RewriteEngine Onとありますので、mod_rewriteを使うということがわかります。
むろん、.htaccessを使うので、ApacheにAllowOverrideの設定が必要になります。

まず、Apacheのconfファイルの設定は、エイリアスを切る場合だと、

Alias /runa-cca/ "/var/www/html/runa-cca/"
<Directory "/var/www/html/runa-cca/">
     AllowOverride All
</Location>

のようになります。

.htaccessをどう書くかなのですが、以下のようにしました。

RewriteEngine On
RewriteBase /runa-cca
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

「RewriteEngine On」は、Apacheでmod_rewriteを使うために必要です。
「RewriteBase /runa-cca」は、エイリアスを切っているので必要になります。URL書き換えのベースとなります。

実験してみたのですが、RewriteBaseが無いと、

Request exceeded the limit of 10 internal redirects due to probable configuration error.

というエラーがapache_error.logに出力されます。

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/runa-cca index.php [QSA,L]

と書くと、エラーは止まります。しかし、Runa-CCAのインデックスページ(/runa-cca/)からログインしたとき(/runa-cca/login)にファイルが見つからないと怒られます。
ということで、RewiteBaseは設定が必要。

「RewriteCond」と「RewriteRule」ですが、入り口としては「Apache mod_rewrite Introduction」のページが参考になります。わかりやすく説明されていました。私が使ったのはApache 2.2系列ですが、ドキュメントは新しい方がいいかなあと思って、2.4系列のURLをご紹介しております。

「RewriteCond」は「Apache Module mod_rewrite」を参照。細かく書いてあります。

「RewriteCond %{REQUEST_FILENAME} !-f」は、指定されたファイルが存在しない場合
「RewriteCond %{REQUEST_FILENAME} !-d」は、指定されたフォルダが存在しない場合

です。
Slimのサンプルに「!-d」が無い理由はわかりませんでした。私は付けておくことにしました。

「RewriteRule」は「RewriteRule Flags」を参照。これまた細かく書いてあります。
「RewriteRule ^ index.php [QSA,L]」は、すべてをindex.phpにまわせ、クエリーストリング(/pages/123?one=twoのone=two)は付けたままにしろ(QSA|qsappend)、書き換えルールはここでストーップ(L|last)という意味です。「^」の解釈に悩みましたが、「.*」にしても同じ動作でした。すべて、ということでしょう。

調べるの大変でした・・・。