Twilio + Google Apps Scriptで着信拒否つき留守電サービスを作る

格安SIMにしちゃうと留守電代金も惜しくなる

docomoからiijに移行してから、もうすぐ2年ぐらいにはなるのですけど、そういえば留守電的なのを契約してなかったんですよね。
一応プランとしては存在するのだけど、月額300円はちょっと課金するにも惜しいなと。
料金・仕様 | タイプD | IIJmio

まぁ、無いときは作ることにします。
今回は、業務でガチャガチャいじったこともあり、TwilioとGoogle Apps Scriptで実装することにしましょう。

Twilioとは?

最近はやりのクラウドですね。クラウド電話APIといいまして、クラウド経由でWebサービスと電話を連携すれば便利じゃね?
みたいなサービスです。国内だとKDDIさんが提供しています。基本的には従量課金。
詳細は本家のWebでも見て下さい。

Twilio for KDDI Web Communications | クラウド電話API

Google Appsとは?

GoogleVBAみたいなもんです。
Google 様のアナルを舐めれば幸せになれる の各種サービスを便利に使える言語 でして、サーバー側でJavascript的なものが動いているという認識で大丈夫かと思います。
Googleサービスだと、GmailだのカレンダーだのDriveだのいろいろとあるわけですが、そこら辺を使ってゴチャゴチャできるわけです。
詳細は下記あたりで。

Apps Script  |  Google Developers

まずはTwilio側の準備

Twilioアカウントを作る

Twilioのサイトにアクセスし、右上の[サインアップ]から登録します。
途中でSMS認証を求められるので、携帯電話番号を一つ用意しないといけません。

Twilio for KDDI Web Communications | クラウド電話API
f:id:kana_yaz:20170222013603p:plain f:id:kana_yaz:20170222013555p:plain

認証を終了するとチュートリアルが云々と出ますので、見るといいかと思います。
GetStartedで飛ばしてもいいですけど。
f:id:kana_yaz:20170222013602p:plain

Twilioのダッシュボードはこんな感じです。
f:id:kana_yaz:20170222013604p:plain

電話番号を取得する

今回の留守電は、自分のケータイに着信した電話を、時間をトリガーにしてTwilio側に転送する形になります。
取りあえず一つ、電話番号を取得します。
コンソールダッシュボードの[プログラマブルVoice]をクリック。

電話番号ダッシュボードが表示されます。 赤色の[始めましょう]をクリック。 f:id:kana_yaz:20170222013600p:plain

初期状態だと番号が払い出されていないですので、 [最初のTwilio電話番号を取得]をクリック。 f:id:kana_yaz:20170222013556p:plain

いきなり番号を提示されます。表示された番号が気にくわないなら、「別の番号を検索する」リンクをクリックすれば、選択画面に移行します。 f:id:kana_yaz:20170222013557p:plain

取得できる番号が羅列されます。どれでもいいんじゃね、とは思いますが、特定の番号が含まれる電話番号なども検索可能です。 f:id:kana_yaz:20170222013558p:plain

無事に取得できました。 f:id:kana_yaz:20170222013559p:plain

続いてGoogleDrive側の下準備

Web版のGoogleDriveが使える状態を前提に進めます。
f:id:kana_yaz:20170222013605p:plain

‘Twilio'フォルダと'Record'フォルダを作る

正直フォルダ階層の概念はGoogleAppsScriptには存在しないのですが、人間様がわかりづらいですので、ルートの直下に「Twilio」とでもフォルダを作りましょう。今回はそこにいろいろとファイルを置いていきます。
作成したフォルダの直下に「Record」というフォルダも作りましょう。そこに録音データを保存していきます。

Apps Scriptの準備

まずはApps Scriptを使えるようにします。
Web版GoogleDriveの[新規]→[その他]→[アプリを追加]とクリック。
f:id:kana_yaz:20170222013542p:plain

ドライブにアプリを追加、というウィンドウが表示されますので、検索欄に'script'と入力。
Google Apps Scriptがサジェストされますので、[接続]をクリック。
ドライブに接続されました、というウィンドウが表示されれば問題なしです。次へ進みます。
f:id:kana_yaz:20170222013543p:plain f:id:kana_yaz:20170222013544p:plain f:id:kana_yaz:20170222013545p:plain

記録用に使うスプレッドシートを作る

今回はログをGoogleスプレッドシートに記録していきます。Excelみたいなやつですね。
スクショは忘れましたが、マイドライブの[新規]→[Googleスプレッドシート]とクリックすれば作成できます。

新規作成できたら、シート名を「twilio」に設定し、「log」というシートと「blocking」というシートの2つ作ります。
「log」が電話ログを記録するシート、「blocking」が拒否する電話番号を記載するシートです。
f:id:kana_yaz:20170222013541p:plain

もしかしたら「blocking」シート側に何もはいってないとエラー出ちゃうかもしれませんので、 何か適当に拒否したい人間の番号を1列目に入れておいてください。
親とか実家とか

スクリプトファイルを作る

今回は、一番最初に電話を受けるスクリプト、留守電を録音するスクリプト、の2つファイルを作ります。
マイドライブの[新規]→[その他]→[Google Apps Script]とクリック。
f:id:kana_yaz:20170222013546p:plain

エディタが開きますので、'twilio'とでも名付けましょう。
作成直後は'myFunction'みたいなやつが出てきますが、不要です。
f:id:kana_yaz:20170222013538p:plain

同じ事を繰り返して、もう一つは'twilio_record'にでもしましょう。

Moment.js を使えるようにする

日付云々でラクなライブラリ、Moment.jsを使えるようにしましょう。
各々のスクリプトファイルで必要になりますので、このタイミングで使えるようにしましょう。

メニューバーの[リソース]→[ライブラリ]とクリック。
「含まれているライブラリ」ウィンドウが表示されますので、 [ライブラリを検索]テキストボックスに下記のプロジェクトキーを入力し、[選択]をクリックします。 バージョンのプルダウンから最新版を選び、[保存]で確定させてください。

MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48

f:id:kana_yaz:20170222013549p:plain よくよく考えれば、今回のスクリプトには必須ではないので、そこら辺は適時でお願いします。

構成の確認

こんな感じですかね。 f:id:kana_yaz:20170222021535p:plain

必要になるもののメモ

Twilioの ‘ACCOUNT SID'と'AUTH TOKEN’

Twilioのコンソールダッシュボードに書いてありますので、コピーしておきましょう。 f:id:kana_yaz:20170222013548p:plain

‘Record'フォルダの'FolderID’

録音データの保存先として作成した'Record'フォルダのIDです。 GoogleDriveで'Record'フォルダを開き、アドレスバーに書いてある英数字の羅列をコピーしておきましょう。 f:id:kana_yaz:20170222022846p:plain

‘twilio'スプレッドシートのID

フォルダと同じく、先ほど作成したスプレッドシートのIDもコピーします。 フォルダIDと同じく、アドレスバーに書いてある英数字をコピー。

コードをガリガリ書いていく

あとはひたすらコードを書いていきます。   詳細の解説は気が向いたら書くことにします。

‘twilio_record'スクリプト

まずは録音側。

//Twilio
var twilioSid='{ twilio の ACCOUNT SID }';
var twilioAuth='{ twilio の AUTH_TOKEN }';


function doGet() {
  return;
}



function doPost(e){
  
  var fileName = Moment.moment().format('YYYY/MM/DD-hhmmss');

  var recordUrl = e.parameter['RecordingUrl'];

  var caller=e.parameter['Caller'];
  caller=caller.replace('+81','0');

  var result = UrlFetchApp.fetch(recordUrl);
  var fileBlob = result.getBlob();
  fileBlob.setName(fileName);

  //GoogleDriveに録音データを保存
  var outputDir = DriveApp.getFolderById('{ GoogleDriveのDriveID }');
  var recordDetails = outputDir.createFile(fileBlob);
  var sharingUrl = recordDetails.getUrl();

  //Twilioサーバーの録音データを消去
  delRecord(recordUrl);
  
  //メールを送信
  sendMail(caller,Moment.moment().format('YYYY/MM/DD hh:mm'),sharingUrl);
  
  var out = ContentService.createTextOutput(buildXml());
  out.setMimeType(ContentService.MimeType.XML);
  return out;

}


function buildXml(){
  
 var outString = '';
  
  outString +='<?xml version="1.0" encoding="UTF-8"?>';
  outString += '<Response>';
  outString += '<Say language="ja-jp" voice="woman">';
  outString +='メッセージをお預かりいたしました。 ';
  outString +='お電話ありがとうございました。';
  outString += '</Say>';
  outString += '<Hangup />';
  outString += '</Response>';
  
  return outString;

}

function delRecord(urlData){

  urlData+=".json";
  var options = {
    method: "delete",
    headers: {
      Authorization: " Basic " + Utilities.base64Encode(twilioSid + ":" + twilioAuth)
    },muteHttpExceptions: true
  };
  var result = UrlFetchApp.fetch(urlData,options);

}

function sendMail(callerString,dateString,urlString){
  
  var mailTo='{ 通知メールを飛ばすメールアドレス }';
  var mailSubject='留守電通知';
  var mailBody='';
  
  mailBody += '留守電がありました\n';
  mailBody += '日時 : ' + dateString + '\n';
  mailBody += '相手先 : ' + callerString + '\n';
  mailBody += '録音データ : ' + urlString + '\n';
  
  MailApp.sendEmail(mailTo,mailSubject,mailBody);
  
}

最後のおまじない

編集が終わったら、メニューバーの[公開]→[ウェブアプリケーションとして導入]をクリック。 f:id:kana_yaz:20170222013551p:plain

現在のウェブアプリケーションのURLに書いてあるURLをコピーしましょう。次のスクリプトファイルで使います。
アプリケーションにアクセスできるユーザーは、「全員(匿名ユーザーを含む)」にしないとうまくいきません。

承認を求められるので[許可を確認]をクリック。 f:id:kana_yaz:20170222013539p:plain

なぜだか一旦無効にしないとうまくいかない、GoogleAppsScriptの謎。
ということで、再度メニューバーから[公開]→[ウェブアプリケーションとして導入]をクリック。 開いたら[ウェブアプリケーションを無効にする]をクリックし、[はい]で決定。 f:id:kana_yaz:20170222022847p:plain

再びメニューバーから[公開]→[ウェブアプリケーションとして導入]をクリックし、公開し直します。

よくわかんないですが、おまじないみたいなもの なので忘れないように。

‘twilio'スクリプト

var Sheets=SpreadsheetApp.openById('{ スプレッドシートのID }');
var logSheet=Sheets.getSheetByName('log');
var blockSheet=Sheets.getSheetByName('blocking');


function doGet(e){
  return;
  
}

function doPost(e){
  return receive_(e);
}

function receive_(e){
  
  //発信者情報を取得
  var caller=String(e.parameter['Caller']);
  caller=caller.replace('+81','0');
  
  //BlockingListの判定
  var blockArr=blockSheet.getRange(1,1,blockSheet.getLastRow(),1).getValues();
  var blockRes=blockArr[0].filter(function(element,index){
    if(String(element) ==String(caller)){
      return true;
    }
  });
  
  
  var out;
  var setRow=logSheet.getLastRow()+1;
  if(blockRes.length==0){ //Block判定ではない
    logSheet.getRange(setRow,1).setValue(Moment.moment().format('YYYY/MM/DD hh:mm'));
    logSheet.getRange(setRow,2).setValue(caller);
    logSheet.getRange(setRow,3).setValue('record');
    var result=buildXml();
    out = ContentService.createTextOutput(result);
  }else{ //Block判定
    logSheet.getRange(setRow,1).setValue(Moment.moment().format('YYYY/MM/DD hh:mm'));
    logSheet.getRange(setRow,2).setValue(caller);
    logSheet.getRange(setRow,3).setValue('blocked');
    var result=buildXmlBlock();
    out = ContentService.createTextOutput(result);
  }
  out.setMimeType(ContentService.MimeType.XML);
  
  return out;
  
}

function buildXml(){
  
 var outString = '';
  
  outString +='<?xml version="1.0" encoding="UTF-8"?>';
  outString += '<Response>';
  outString += '<Say language="ja-jp" voice="woman">';
  outString += 'ただいま、電話に出ることができません。 ';
  outString += 'メッセージを録音します。 ';
  outString += '「ピー」という音の後に、60秒以内で、お名前、お電話番号、ご用件を録音し、';
  outString += '最後にシャープを押してください。';
  outString += '</Say>';
  outString += '<Record action="{ twilio_recordスクリプトのウェブ公開URL }" finishOnKey="#" maxLength="60" method="post" timeout="75" />';
  outString += '</Response>';
  
  return outString;  
  
}

function buildXmlBlock(){
  
 var outString = '';
  
  outString +='<?xml version="1.0" encoding="UTF-8"?>';
  outString += '<Response>';
  outString += '<Reject />';
  outString += '</Response>';
  
  return outString;  
  
}

こちらも最後のおまじない

編集が終わったら、メニューバーの[公開]→[ウェブアプリケーションとして導入]をクリック。 f:id:kana_yaz:20170222013551p:plain

現在のウェブアプリケーションのURLに書いてあるURLをコピーしましょう。Twilioのページで使います。
アプリケーションにアクセスできるユーザーは、「全員(匿名ユーザーを含む)」にしないとうまくいきません。

承認を求められるので[許可を確認]をクリック。 f:id:kana_yaz:20170222013539p:plain

なぜだか一旦無効にしないとうまくいかない、GoogleAppsScriptの謎。
ということで、再度メニューバーから[公開]→[ウェブアプリケーションとして導入]をクリック。 開いたら[ウェブアプリケーションを無効にする]をクリックし、[はい]で決定。 f:id:kana_yaz:20170222022847p:plain

再びメニューバーから[公開]→[ウェブアプリケーションとして導入]をクリックし、公開し直します。

よくわかんないですが、おまじないみたいなもの なので忘れないように。

スクリプトのURLをTwilioに反映

Twilioのコンソールダッシュボードから電話番号ページに飛び、先ほど取得した番号を選択します。 f:id:kana_yaz:20170222013601p:plain

‘twilio'スクリプトウェブアプリケーションURLを、[A CALL COMES IN]項目のURL欄に貼りつけます。
POSTかGETを選べますが、POSTにしといてください。[保存]を押して終了。 f:id:kana_yaz:20170222013536p:plain

動作確認

試しに、先ほど作った番号に電話してみましょう。


twilio_record_test

メール通知が来ます

留守電の記録&ブロックが発生すると、メールで通知が来るようにしました。
GoogleDriveへのリンクも貼ってあります。 f:id:kana_yaz:20170222033042j:plain

GoogleDriveに録音データがアップされます

録音ファイルはこちらに。
f:id:kana_yaz:20170222033044j:plain

スプレッドシートには

記録されてますね。ブロックされたものは'blocked'表記になります。 f:id:kana_yaz:20170222033043j:plain

誰でも簡単に

こういうものが作れる時代って便利ですね。実際にコード書いてた時間なんでせいぜい30分ですし。
コードを書いている時間が30分に対して、この記事を書いた時間が1時間以上かかってるんですけどね!!