「○○さん今どこ？」でAlexaがGoogleカレンダーから人を探してくれるカスタムスキル

​会社にあるAlexaのカスタムスキル作ったので投稿


Alexaやカスタムスキル の概要は以下リンク先を参照
[Alexaスキル開発トレーニングシリーズ 第1回 初めてのスキル開発](https://developer.amazon.com/ja/blogs/alexa/post/6e716e5c-55b0-445b-b936-9cfac4712e7b/training-1)

##目次
- 作成したカスタムスキル 
- わかったこと
- スキルの流れ
- 実装内容

##作成したカスタムスキル 
グーグルカレンダーの予定を見て、その人が今どこにいるのか探してくれる

Ex)
「○○さん今どこ？」
「○○さんは△時~△時まで□会議室で打ち合わせです」

「○○どこいる？」
「予定は入ってないので自席にいるんじゃないですか？」

[アレクサ、〇〇さんの今日の予定を教えて](http://azure.sios.jp/archives/8450)を参考にさせていただいています


##わかったこと
- 会社のAlexaということで何かを便利にできるカスタムスキルを考えたけど意外と思いつかない
→いいアイデアあったら色々聞きたい

- スマートスピーカーは片手間で動くのがいいところ
(PCで仕事しながらちょっと調べ物してもらえるとか)
- 細かい設定が必要なものには向かない(バーっとて入力した方が早い)
逆に簡単なもの向き(出退記録、とにかくランダムで今すぐ１時間会議室予約)
- LambdaとGAS間のセキュリティ設定,認証処理が謎(私にはOAuth認証理解できず...)


##スキルの流れ

<img width="1009" alt="スクリーンショット 2018-06-18 8.27.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/249053/eb4bdbd3-48de-6478-ed4d-d769fd41fd05.png">

**Lambda**

どこにいるか調べたい人の名前をGoogle Apps Script(以下GASと表記)に渡す

GASから帰ってきたJSONデータに合わせてAlexaが喋る内容を決めてあげる

**GAS**

現在時刻と、Lambdaから渡された名前の当てはまる予定をGoogle Clendarから​​探す

検索結果をJSONで返す



##実装内容

**Alexaサービス**​​([Amazon開発者ポータル](https://developer.amazon.com/ja/))

​​スキル名(スキルを呼び出すときに使う)
呼びかけ方(○○さん今どこ？、○○今どこ？、○○どこいる？...)
シノニム(苗字、名前、ニックネーム、どれで呼びかけても同一人物の名前として受け取るように設定)

シノニムの設定については[こちら](https://qiita.com/haruno/items/aaa7fd98815264208260)

<br>
**Lambda**


```index.js
'use strict';
const Alexa = require('alexa-sdk');
const http = require('https');

//=========================================================================================================================================
//TODO: The items below this comment need your attention.
//=========================================================================================================================================

//Replace with your app ID (OPTIONAL).  You can find this value at the top of your skill's page on http://developer.amazon.com.
//Make sure to enclose your value in quotes, like this: const APP_ID = 'amzn1.ask.skill.bb4045e6-b3e8-4133-b650-72923c5980f1';
const APP_ID = undefined;

const SKILL_NAME = 'Space Facts';
const GET_FACT_MESSAGE = "Here's your fact: ";
const HELP_MESSAGE = 'You can say tell me a space fact, or, you can say exit... What can I help you with?';
const HELP_REPROMPT = 'What can I help you with?';
const STOP_MESSAGE = 'Goodbye!';

//=========================================================================================================================================
//Editing anything below this line might break your skill.
//=========================================================================================================================================

const handlers = {
    'LaunchRequest': function () {
        // 追記：スキルが呼ばれた時
        this.emit(':ask', '誰を捜しますか？');
    },
    'TellMeYourCalendar': function () {
        // スロットの値(「Googleカレンダーの〇〇さんの今日の予定を教えて」の〇〇の部分)の値が
        // this.event.request.intent.slots.name.valueに入ってきます。
        // 日本語が入ってくる場合もあるので、URIエンコードします。
        var name = encodeURI(this.event.request.intent.slots.name.value);
        
        // Google Apps Scriptで導入した後に表示されたURL(現在のWebアプリケーションのURL)を
        // 設定します。
        // 追記：Google Apps ScriptのdoGetに名前をパラメーターとして渡す
        const uri = 'Google Apps ScriptのウェブアプリケーションURL' + '?name=' + name;

        // Google Apps ScriptのRest APIにアクセスして、JSONを取得する関数を呼びます。
        getHttp(uri,this);
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = HELP_MESSAGE;
        const reprompt = HELP_REPROMPT;

        this.response.speak(speechOutput).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
};

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.APP_ID = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

function getHttp(url,instance) {
    
    // この関数は、Google Apps ScriptのRest APIにアクセスして、JSONを取得して
    // 発話をする処理です。再帰的に呼び出しています。Google Apps Scriptは
    // 何回かリダイレクトを繰り返して、最後にJSONを表示します。
    // node.jsのHTTPクライアントライブラリは、リダイレクトを追従してくれないので、
    // 自分でその処理を入れる必要があります。ここでは、HTTPステータスコード301もしくは
    // 302が返ってきたら、この関数を再帰的に呼び出し、200が返ってきたら
    // 初めてJSONのParseを行います。
    var req = http.request(url, (res) => {

        if(res.statusCode == 301 ||res.statusCode == 302) { 
          // HTTPステータスコードがリダイレクトなので、この関数を再帰的に呼び出しています。
          getHttp(res.headers.location,instance);
        }  else if(res.statusCode == 200) {
          // HTTPステータスコードが200なので、JSONを解析する処理を実行します。
          var body = '';
           res.on('data', (chunk) => {
               body += chunk;
            });
        
            res.on('end', () => {
                var result = JSON.parse(body); 
                var speech = '';

                //追記：予定が入っていない時の発話設定
                if(body == '[]'){
                    speech += '予定はありません';
                } else {
                    // JSONに格納された予定をひとずつ取り出し、発話を作成しています。
                    for (var i in result) {
                        var startDate = new Date(result[i].start);
                        var endDate = new Date(result[i].end);
                        speech += startDate.getHours() + '時' + startDate.getMinutes() + '分から' + endDate.getHours() + '時' + endDate.getMinutes() + '分まで' + result[i].title + 'の予定があります。';
                        
                    }
                }

                // 実際に発話をする処理です
                instance.emit(":tell", speech);
            });
        
            res.on('error', (e) => {
                console.log('problem with request: ' + e.message);
            });

        }
        
    });
    req.end();
}
```


<br>
**GAS**
Webアプリケーションとして公開
Lambdaからアクセスするためにアクセス権を「(匿名含む)全員」に設定

<font color="Red">誰でもこのURLを叩けばカレンダーにアクセスできてしまうことになる。
セキュリティ的にはよろしくないので何らかの認証処理を入れるべし！</font>
<br>

```コード.js
function doGet(e) {
  
  // クエリパラメーター「name」で指定された、Googleカレンダー取得対象のユーザーの名前を取得する。
  var name = e.parameter.name;

  // 現時点のDateを生成する
  var nowDate = new Date();
  
  // 今日の日付の00:00:00時点のDateを生成する
  var startDate = new Date(nowDate);
  startDate.setHours(0);
  startDate.setMinutes(0);
  startDate.setSeconds(0);
  
  // 今日の日付の23:59:59時点のDateを生成する
  var endDate=new Date(startDate);
  endDate.setHours(23);
  endDate.setMinutes(59);
  endDate.setSeconds(59);
  
  // 指定したユーザーのGoogleカレンダーの予定を取得する。
  var cal = CalendarApp.getCalendarById('カレンダーに紐づいたGmailのアドレス');
 
  // 今日一日のGoogleカレンダーの予定を取得する
  var myEvents=cal.getEvents(startDate,endDate);

  // 今日一日のGoogleカレンダーのJSONを生成する。
  var body = '';
  num = 0;
  var result = [];
  var evtTitle = '';
  
  //予定が現在時刻に重なっている
  //タイトルに名前が含まれている
  //以上の場合にJSONに追加
  for(var i in myEvents){

    //位置ではカレンダーに「予定(名前)」形式で登録しているので、
　  //タイトルを読むのに不要な「(名前)」の部分を削除
    var evt = myEvents[i];
    evtTitle = evt.getTitle().replace(/\(.*?\)/,"");     
    
    if(Moment.moment(evt.getStartTime()).isBefore(nowDate.getTime()) && Moment.moment(evt.getEndTime()).isAfter(nowDate.getTime())){
      if(evt.getTitle().match(name)){  
        result[num] = {
          start: convertDate(evt.getStartTime()),
          end: convertDate(evt.getEndTime()),
          title: evtTitle
        }
        num++;
      }
    }
  }
  
  return ContentService
      .createTextOutput(JSON.stringify(result))
      .setMimeType(ContentService.MimeType.JSON);
  
}

function convertDate(date) {

  //moment.jsで日付の形式を合わせる(上記コメントアウトとやってることは一緒。便利)
  return Utilities.formatDate(date, 'JST', 'yyyy-MM-dd HH:mm:ss');


}

function toDoubleDigits(num) {
  num += "";
  if (num.length === 1) {
    num = "0" + num;
  }
  return num;     
}
```