Alexaが暇そうだったので、勉強会の発表者選定を任せることにした話

## Alexa に任せることにした背景

皆さん、最近ちゃんと Alexa に話かけていますか？

私が所属するチームでは週次のチームミーティングに合わせて、
気になる技術トピックや検証・調査した技術などを週に 1 名、
担当者を決めて 20~30 分程度で共有する勉強会を実施しています。

これまで次回以降の勉強会のメンバーを決めるのは今回発表者による指名制でした。
次の勉強会の指名を受けた私はなんのトピックにしようかなと考えてコーヒーを準備しているときに、
彼と目が合ったのです。
そう、職場に導入されて三か月の間、大分暇そうだった Alexa に。
当初は「ドラえもんの真似して」「今日は何の日？」と言ってもてはやされた彼も
今は何も言わずただコーヒーマシンの横で静かに佇んでいるだけでした。

「Alexa、勉強会の指名やってみないか？」、そう問いかけた私に
「わかりませんでした。すいません」とつんつんしている彼に
勉強会を指名してもらうことを、この時決めました。

## Alexa を動かすための前準備

準備をするにあたって AWS アカウントのみならず、その他二つのアカウントが必要です。
実はこの準備が一番大変だったり。

- 用意するもの　&　必要な前準備
    - Amazon アカウント及び　 Amazon Developer アカウント
        - まず、Amazon.co.jp のアカウントを準備します。（Amazon.com ではないので注意）
        - 次に Amazon 開発者ポータル（ https://developer.amazon.com/ja/ ）を開き、右上の「Developer Console」をクリックし上記の Amazon.co.jp のアカウントを使ってログインします。
        - 開発者アカウントの申請フォームに移るのでこの画面から申請します。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/7e8948fb-61c0-1449-7aac-fbd7afdaaf58.png)

    - Alexa 対応端末 - 今回は職場にある Amazon echo を使用しました。
        - Amazon Alexa のサイトから（ https://alexa.amazon.co.jp/spa/index.html ）
        - 上記の Amazon.co.jp のアカウントと同じメールアドレスに、機器を紐づけます。
    - AWS アカウント
今回 AWS Lambda と Amazon DynamoDB を利用するため、それらを利用可能な権限を持った AWS アカウント用意します。

## 構成と動作イメージ

### 以下が大まかな動作の流れになります。

- ① 職場の Amazon Echo の Alexa に「Alexa、次回の勉強会」と尋ねる。
- ② Amazon Developer アカウント上の Alexa Skills が「勉強会スキル」を起動する。
- ③ 「勉強会スキル」が AWS アカウント上の Lambda に JSON でアクションを送る
- ④ Lambda が DynamoDB に直近の勉強会の発表者を問い合わせる。
- ⑤ DynamoDB が直近の発表者を返す。
- ⑥ Lamda が発表者をランダムに選定し、Alexa Skill「勉強会スキル」に発表者の情報を含めた JSON を返す。
- ⑦「勉強会スキル」が Amazon Echo の Alexa に返答内容を返す。
- ⑧ Alexa が次の発表者を発表してくれる。

<img width="700" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/e0ce7343-1b01-0cc0-afcf-baaa1e577075.png">

## 作成手順
GitHubで提供されているAlexa豆知識スキルを一部改良して、本アプリを作成しました。
Alexa 豆知識スキルの作成
https://github.com/alexa/skill-sample-nodejs-fact/blob/ja-JP/instructions/1-voice-user-interface.md

### Amazon Developer アカウント側の構築
- 1. Amazon Developer アカウントからAlexaをクリックします。
<img width="656" alt="dev2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/9b52cc44-eca3-104f-d849-799e895dc7c8.png">
- 2.　「スキル開発を始める」をクリックして、開発者コンソールを開きます。
<img width="801" alt="dev3.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/0f52518a-55fa-b4ae-9746-41bd40ae7c07.png">
- 3. Alexa スキルの一覧の画面から「スキルの作成」をクリックして、スキルを作成します。
 <img width="949" alt="alexa画面1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/1026151c-f1ce-1368-5499-6f8125fb14b6.png">
- 4. スキル名を記載します。今回は「勉強会スキル」とします。
- 5. 言語は「日本語（日本）」とします
- 6. スキルに追加するモデルを選択は「カスタム」とします。
- 7.スキルのバックエンドリソースをホスティングする方法を選択は「ユーザー定義のプロビジョニング」とします。
<img width="687" alt="dev4.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/55caebc7-731a-677a-7754-8cc2c3438534.png">

- 8.スキルを作成します。
     JSON エディターに以下の JSON ファイルを添付します。
     スキルをJSONで記載したり、GUIにて設定することもできます。
   「アレクサ、次回の勉強会」というと起動するスキルです。


```JSON:
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "次回の勉強会",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "GetNewFactIntent",
                    "slots": [],
                    "samples": [
                        "決めて",
                        "教えて",
                        "話して",
                        "聞かせて",
                        "なんか教えて",
                        "なんか言って"
                    ]
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                }
            ],
            "types": []
        }
    }
}
```

- 9. モデルのビルドをクリックします。

### AWS 側の構築
#### DynamoDB を準備
今回は東京リージョンを利用します。

- 1. テーブルの作成をクリックします。
- 2. テーブル名「alexa-study-dynamo」、パーティションキーを 「partition」（数値）、ソートキーを 「history_key」（数値）としてテーブル作成をします。

 <img width="794" alt="dynamo1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/22b7a7eb-961e-cda3-4c94-39e69cd1b420.png">

#### Lambda 関数準備
こちらも東京リージョンを利用します。

- 1. 関数の作成をクリックします。
<img width="680" alt="landa1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/7ae93323-74e7-10a7-f046-edd8e8412548.png">

- 2. 「一から作成」「設計図の使用」「Serverless Application Model の参照」の中から、「Serverless Application Model の参照」を選択します。

 <img width="938" alt="landa2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/2e50a6ea-f29b-1265-5f82-fade54954b19.png">

- 3. 検索窓から「alexa-skills-kit-nodejs-factskill」を検索し、選択します。
- 4. アプリケーション名を「alexa-study-lambda」として、デプロイをクリックします。
<img width="919" alt="lambda4.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/c0df0704-0ba3-ba9a-3407-fcfc2397ef06.png">
- 5. 作った Lambda アプリケーションをアプリケーションから検索して選択します。
<img width="820" alt="lambda10.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/2d49d91a-0383-ce8b-742e-5eaf03b3633c.png">
- 6. 以下のコードを関数コード部分にコピー&ペーストします。このコードは上記の「Alexa 豆知識スキル」のコードを、勉強会向けに DynamoDB との連携も含めて改変したものになります。

```node.js:
const Alexa = require("ask-sdk");
const AWS = require("aws-sdk");
const dynamoDB = new AWS.DynamoDB.DocumentClient({
  region: "ap-northeast-1"
});

const GetStudyHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return (
      request.type === "LaunchRequest" ||
      (request.type === "IntentRequest" &&
        request.intent.name === "GetNewFactIntent")
    );
  },
  async handle(handlerInput) {
    // DynamoDBから前回3回の発表者を取得
    const params1 = {
      TableName: "alexa-study-dynamo",
      ExpressionAttributeNames: { "#name0": "partition" },
      ExpressionAttributeValues: { ":value0": 0 },
      KeyConditionExpression: "#name0 = :value0",
      ScanIndexForward: false,
      Limit: 3
    };

    const result = await dynamoDB.query(params1).promise();

    console.log(result);
    let last_time0, last_time1, last_time2, current_history_key;
    if (result.Count == 1) {
      last_time0 = result.Items[0].member_id;
      current_history_key = result.Items[0].history_key;
    }
    if (result.Count == 2) {
      last_time0 = result.Items[0].member_id;
      last_time1 = result.Items[1].member_id;
      current_history_key = result.Items[0].history_key;
    }
    if (result.Count >= 3) {
      last_time0 = result.Items[0].member_id;
      last_time1 = result.Items[1].member_id;
      last_time2 = result.Items[2].member_id;
      current_history_key = result.Items[0].history_key;
    }
    const memberArr = data;
    let nextIndex; // ランダムに発表者を選定し、前回3回と被っていなければ確定する。

    while (true) {
      nextIndex = Math.floor(Math.random() * memberArr.length);
      if (
        nextIndex !== last_time0 &&
        nextIndex !== last_time1 &&
        nextIndex !== last_time2
      ) {
        break;
      }
    }
    const nextMember = memberArr[nextIndex];
    const speechOutput = GET_MESSAGE + nextMember + GET_MESSAGE_AFTER;

    // DynamoDBに次回発表者を登録
    const params2 = {
      TableName: "alexa-study-dynamo", // DynamoDBのテーブル名
      Item: {
        partition: 0,
        history_key: current_history_key + 1,
        member_id: nextIndex
      }
    };

    const result2 =await dynamoDB.put(params2).promise();

    // 次回発表者とメッセージを沿えてAlexaに返す
    return handlerInput.responseBuilder
      .speak(speechOutput)
      .withSimpleCard(SKILL_NAME, nextMember)
      .getResponse();
  }
};

const HelpHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return (
      request.type === "IntentRequest" &&
      request.intent.name === "AMAZON.HelpIntent"
    );
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak(HELP_MESSAGE)
      .reprompt(HELP_REPROMPT)
      .getResponse();
  }
};

const ExitHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return (
      request.type === "IntentRequest" &&
      (request.intent.name === "AMAZON.CancelIntent" ||
        request.intent.name === "AMAZON.StopIntent")
    );
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder.speak(STOP_MESSAGE).getResponse();
  }
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === "SessionEndedRequest";
  },
  handle(handlerInput) {
    console.log(
      `Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`
    );

    return handlerInput.responseBuilder.getResponse();
  }
};

const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak("Sorry, an error occurred.")
      .reprompt("Sorry, an error occurred.")
      .getResponse();
  }
};

// スキル情報とメッセージ（一部そのままです。）
const SKILL_NAME = "Study";
const GET_MESSAGE = "次の勉強会は";
const GET_MESSAGE_AFTER = "せいぜい頑張ってください。　素晴らしい内容期待してます。";
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!";

// グループメンバーの情報を記載
const data = [
  "頼れるリーダー あさだ さん です",
  "ミスターフルスタック　かわしま さん です",
  "データベースマスター　たかだ　あきひろ さん です",
  "ネットワークマスター　たかだ ひろゆき です",
  "えいぎょうしゅっしん　あらかわ　さん です",
  "アジュールマスター　もちだ　さん です",
  "GCPマスター　はら さん です",
  "AWSマスター　みつうら さん です",
  "期待のしんじん　いいだ さん です"
];
const skillBuilder = Alexa.SkillBuilders.standard();

exports.handler = skillBuilder
  .addRequestHandlers(
    GetStudyHandler,
    HelpHandler,
    ExitHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

```

下記の部分がチームメンバーを記載しているところですのでご自身のチームメンバーの値に変えてください。
ハードコーディングをしているのは決してめんどくさかったからではなく、
DynamoDBへ問い合わせに行く時間の節約を考慮したスピード重視の設計です。
（すいません、嘘です、力尽きました。）

```node.js:
const data = [
  "頼れるリーダー あさだ さん です",
  "ミスターフルスタック　かわしま さん です",
  "データベースマスター　たかだ　あきひろ さん です",
  "ネットワークマスター　たかだ ひろゆき です",
  "えいぎょうしゅっしん　あらかわ　さん です",
  "アジュールマスター　もちだ　さん です",
  "GCPマスター　はら さん です",
  "AWSマスター　みつうら さん です",
  "期待のしんじん　いいだ さん です"
];
```
- 7. Lambda アプリケーションに DynamoDB のテーブルに対する書き込み・読み込み権限を付与します。実行ロールから IAM コンソールに移動します。DynamoDBのテーブルへの読み書き権限のあるポリシーをアタッチしてください。
- 8. この Lambda のページの Amazon Resource Name (ARN)が表示されています。
     この値を Alexa skitt との接続で利用しますので、arn: で始まる値をコピーしてください。
<img width="897" alt="lambda8.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/287c0d56-fc0b-f88e-6ab9-fde4a86fbe71.png">



### AWS Developer アカウント側と AWS 環境の接続

- 1. Alexa developer console 上でビルドタブをクリックし、その後左側からエンドポイントを選択してください。
- 2. エンドポイントのページにて、先ほどコピーした arn:から始まる値を貼り付けます。
     デフォルトの地域に張り付けた後、上部にある「エンドポイントを保存」をクリックします。
<img width="869" alt="dev_ale1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/72d569f3-f411-7712-de6e-dfa9a6c1735e.png">

### テスト

- Alexa に「Alexa、次回の勉強会」と尋ねてみてください。
  ランダムにチームメンバーの名前が返ってきたらきたら構築完了です。

- Alexa developper console 上のテストタブでもテスト可能です。

#####　以上で完成です。

## 終わりに

こちらのアプリ以外にも、下期のキックオフの司会進行を Alexa にお任せしました。
Alexa に指示されて働く日も近いかも。
![ikko_160829jinkoutinou001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529900/c73c2107-3dda-8f48-0c72-59c0521e9fd1.jpeg)