GoogleAppsScript
gas
spreadsheet
Alexa
AlexaSkillsKit

音声(Alexa)でSpreadsheetに赤ちゃんのトイレの時間を記録するまで

概要

Alexa を通じて音声で自前の Spreadsheet に記録できるようにするまでにいろいろあったのでメモ。

ちなみに、IFTTT を使えば繋ぎこみ自体は簡単なのだが、文章になってない日本文の認識にかなり難があり、断念した。Alexa に直接仕込んだほうが遥かにいい。

これを実現するために下記の構造を作る:

Alexa 実機
↓
Alexa Skills Kit
↓
AWS Lambda
↓
OAuth 2.0
↓
Google Cloud Platform
↓
Google Apps Script
↓
Google Spreadsheet

1-1. [Spreadsheet] 記録用の Spreadseet を用意

記録するためのスプレッドシートを用意しておく。中身は空っぽで良い。末尾に1行ずつ追加していく想定。

↓こういう感じ

2018/05/05 13:41:52 うんち 大

1-2. [Spreadsheet] 書き込むスクリプト作成

1行追記するためのスクリプトを用意する。
ツール > スクリプト エディタ から下記のソースを追加。Execution API と名付けて保存。

GoogleAppsScript
function appendRecords(data){
  var size = data.query_result.size;
  var mySheet = SpreadsheetApp.getActiveSheet();
  mySheet.appendRow([new Date(), 'うんち ' + size]);
}

function test_appendRecords(){
  appendRecords({
    query_result: {
      size: '大'
    }
  });
}

test_appendRecords を実行して行が追加されることを確認する。

1-3. [Spreadsheet] スクリプトをAPIとして公開する

外からこのAPIを叩けるようにする。
Google Apps Script の画面から 公開 > 実行可能 API として導入... を選択。

バージョン は適当に。
スクリプトにアクセスできるユーザー自分のみ に。

更新 のクリックでリロードみたいに画面更新されるが、自分の入力したバージョンの番号がプルダウンに加わっていることを確認する。

1-4. [Spreadsheet] 各種情報をメモしておく

あとで使うので。

Google Apps Script の画面から:

  • API ID : 公開 > 実行可能 API として導入... の画面の 現在の API ID
  • プロジェクトキー : ファイル > プロジェクトのプロパティ の画面の プロジェクト キー(サポート終了)
  • スクリプト ID : ファイル > プロジェクトのプロパティ の画面の スクリプト ID
  • OAuth スコープ : ファイル > プロジェクトのプロパティ の画面の スコープ タブのURL

1-5. [Spreadsheet] OAuth2 の設定画面に飛ぶ

API を叩く際の認証設定をする(ための画面に飛ぶ)。
Google Apps Script の画面から リソース > Cloud Platform プロジェクト... を選択。
ポップアップする画面の このスクリプトが現在関連付けられているプロジェクト 欄に Execution API - project-id-xxxxxxxxxxxxxxxxx のようなリンクが表示されているはず。これをクリック。

2-1. [Cloud Platform] Apps Script API を有効化

Spreadsheet の API をそもそも叩けるように有効化する。
左メニューから API とサービス > ライブラリ を選択して、Apps Script API を検索。
1件ヒットするので、それをクリック。有効化する。

2-2. [Cloud Platform] 認証情報の作成

API を叩く際の認証情報を作っておく。

左メニューから API とサービス > 認証情報 を選択して、認証情報を作成 プルダウンから OAuth クライアントID を選択。

下記のように選択:

  • アプリケーションの種類: ウェブアプリケーション
  • 名前: 適当
  • 制限事項:
    • 承認済みの JavaScript 生成元: https://developers.google.com
    • 承認済みのリダイレクト URI: https://developers.google.com/oauthplayground

JSON ができるので、ダウンロードしておく。

3-1. [OAuth 2.0 Playground] 画面開く

アクセスに必要なトークンを発行するための画面を開く。
https://developers.google.com/oauthplayground

3-2. [OAuth 2.0 Playground] 認証情報設定

前述2-2で発行した認証情報をセットする。
画面右上の Auth 2.0 Configration を開いて

  • Use your own OAuth credentials を ON
  • OAuth Client ID 欄に前述2-2でダウンロードしたJSON内の client_id の値を記入
  • OAuth Client secret 欄に前述2-2でダウンロードしたJSON内の client_secret の値を記入

3-3. [OAuth 2.0 Playground] トークン発行

アクセスに必要なトークンを発行する。
下記のように入力:

  • Step1:
    • https://www.googleapis.com/auth/spreadsheets (前述1-4のOAuth スコープ) を入力
    • Authorize APIs ボタンをクリック
  • Step2:
    • Authorization code: (上記のボタンで自動入力)
    • Exchange authorization code for tokens ボタンをクリック
    • Refresh token: (上記のボタンで自動入力)
    • Access token: (上記のボタンで自動入力)

あとで使うので Refresh tokenAccess token をメモしておく。

3-4. [OAuth 2.0 Playground] アクセステスト

下記のように入力:

  • Step3:
    • HTTP Method: POST
    • Request URI: https://script.googleapis.com/v1/scripts/{前述1-4のプロジェクトキー}:run
    • Enter request body クリックして下記RequestBodyを設定
    • Content-Type: application/json
    • Send the request クリック

{前述1-4のプロジェクトキー} は適宜置き換えること。

RequestBody
{
  "function": "appendRecords",
  "parameters": [{
    "query_result": {
      "size": "大"
    }
  }],
  "devMode": true
}

シートに行が追加されればOK。

4-1. [Alexa Skills Kit] 画面開く

Alexa Skills Kit を使って対話モデルを作成する。
https://developer.amazon.com/ja/alexa-skills-kit
にアクセスしてサインインする。

※注意: サインインするメアドは必ず所有してる Alexa で使ってるメアドと同じでないといけない。同じでないと作り直しになるので注意。

4-2. [Alexa Skills Kit] スキル開発を始める

  • スキル開発を始める クリック
  • スキルの作成 クリック
  • スキル名 は適当にあとで識別できるものを(でもあとで変えられない)
  • スキル作成時のデフォルト: 日本語
  • カスタム選択 して スキルを作成 クリック

4-3. [Alexa Skills Kit] 対話の設定

JSONエディター開いて下記入力:

{
    "interactionModel": {
        "languageModel": {
            "invocationName": "赤ちゃんのトイレ",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": [
                        "キャンセル"
                    ]
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": [
                        "使い方",
                        "ヘルプ"
                    ]
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": [
                        "ありがとう",
                        "終了",
                        "ストップ"
                    ]
                },
                {
                    "name": "RecordToilet",
                    "slots": [
                        {
                            "name": "Size",
                            "type": "Size"
                        }
                    ],
                    "samples": [
                        "{Size} を記録"
                    ]
                }
            ],
            "types": [
                {
                    "name": "Size",
                    "values": [
                        {
                            "id": "小",
                            "name": {
                                "value": "小",
                                "synonyms": [
                                    "しょう"
                                ]
                            }
                        },
                        {
                            "id": "中",
                            "name": {
                                "value": "中",
                                "synonyms": [
                                    "ちゅう"
                                ]
                            }
                        },
                        {
                            "id": "大",
                            "name": {
                                "value": "大",
                                "synonyms": [
                                    "だい"
                                ]
                            }
                        }
                    ]
                }
            ]
        }
    }
}
  • invocationName は「Alexa, 〇〇で大を記録」という発言の〇〇の部分になるので適宜変更。

4-4. [Alexa Skills Kit] スキルID のメモ

エンドポイントAWS LambdaのARN を選択。
スキルID はあとで AWS 側で使うのでメモ。

4-5. [Alexa Skills Kit] いったんビルド

ここまでをいったんビルドしておく。

5-1. [AWS/Lambda] 関数の作成

AWSで Alexa からの要求を処理する Lambda 関数を作成する。
このときの AWS のアカウントのメアドは、Alexa のメアドと違っていても構わない。

  • 関数の作成
  • 設計図 を選び、alexa-skill-kit-sdk-factskill を選択。
  • 名前、ロールは適当に。
  • 関数の作成 クリック

5-2. [AWS/Lambda] Node.JS の記述

  • アクション > 関数のエクスポート > デプロイパッケージのダウンロード でソースコードを取り出す。
  • 解凍したら package.json のあるフォルダで下記のコマンド実行
$ npm install --save googleapis
$ npm install --save google-auth-library
  • 完了したら index.js を下記のように書き換える:
/* eslint-disable  func-names */
/* eslint quote-props: ["error", "consistent"]*/
/**
 * This sample demonstrates a simple skill built with the Amazon Alexa Skills
 * nodejs skill development kit.
 * This sample supports multiple lauguages. (en-US, en-GB, de-DE).
 * The Intent Schema, Custom Slots and Sample Utterances for this skill, as well
 * as testing instructions are located at https://github.com/alexa/skill-sample-nodejs-fact
 **/

'use strict';
const Alexa = require('alexa-sdk');


//=========================================================================================================================================
const {google} = require('googleapis');
const {OAuth2Client} = require('google-auth-library');

const writeToSpreadsheet = (size) => {
    let scriptId = process.env['GOOGLE_SCRIPT_ID'];
    let functionName = "appendRecords";
    const auth = new OAuth2Client(process.env['GOOGLE_CLIENT_ID'], process.env['GOOGLE_CLIENT_SECRET']);
    auth.setCredentials({
        access_token: process.env['GOOGLE_ACCESS_TOKEN'],
        refresh_token: process.env['GOOGLE_REFRESH_TOKEN']
    });
    const script = google.script('v1');
    return new Promise((resolve, reject) => {
        script.scripts.run({
            auth: auth,
            scriptId: scriptId,
            resource: {
                function: functionName,
                parameters: [{
                    "query_result": {
                        "size": size
                    }
                }],
                devMode: true
            }
        }, (err, result) => {
            if (err) {
                console.log(err);
                reject(new Error(err));
            } else {
                resolve(result);
            }
        });
    });
};
//=========================================================================================================================================



//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 = process.env['ALEXA_APP_ID'];

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.APP_ID = APP_ID;
    const handlers = {
        'RecordToilet': function () {
            //let size = event.request.intent.slots.Size.name; //キー名
            // let size = event.request.intent.slots.Size.value;
            let size = event.request.intent.slots.Size.resolutions.resolutionsPerAuthority[0].values[0].value.id;
            console.log('size: ' + size);
            writeToSpreadsheet(size).then(() => {
                this.emit(':tell', size + 'を書き込みました');
            }).catch(error => {
                context.fail(error);
                this.emit(':tell', '書き込みに失敗しました');
            });
        },
        'AMAZON.HelpIntent': function () {
            this.emit(':tell', '大を記録、中を記録、小を記録のいずれかを言ってください');
        },
        // キャンセル(デフォルト)への返答
        'AMAZON.CancelIntent': function () {
            this.emit(':tell', 'キャンセルします');
        },
        // 対応できないアクションへの返答
        'AMAZON.StopIntent': function () {
            this.emit(':tell', 'もう一度お願いします');
        },
    };
    alexa.registerHandlers(handlers);
    alexa.execute();
};

5-3. [AWS/Lambda] Node.JS のアップロード

  • node_modules, index.js, package.json を選択して右クリック > 送る(N) > 圧縮 (zip 形式) フォルダー で圧縮

※ 注意: このZipを解凍したときに第一階層にフォルダができる形だとエラーになる。第一階層に上記3つが並ぶように、上記のように圧縮すること。

  • 関数コード > コード エントリ タイプ プルダウンで .ZIP ファイルをアップロード を選び、アップロード ボタンで上記の zip ファイルをアップロードし、保存 ボタンを押す。

5-4. [AWS/Lambda] 環境変数

Node.JS で使う環境変数を設定する。
ソース入れ替えは面倒なので、外部要因で変わりうる部分は環境変数に切り出しておくと楽。

下記の設定が必要:

変数名 値の元
GOOGLE_SCRIPT_ID 前述1-4の スクリプト ID
GOOGLE_CLIENT_ID 前述2-2でダウンロードしたJSON内の client_id
GOOGLE_CLIENT_SECRET 前述2-2でダウンロードしたJSON内の client_secret
GOOGLE_ACCESS_TOKEN 前述3-3の Access token
GOOGLE_REFRESH_TOKEN 前述3-3の Refresh token
ALEXA_APP_ID 前述4-4の スキルID

5-5. [AWS/Lambda] ARNのメモ

ARN を Alexa 側にもっていくのでメモ。
画面右上の arn:aws:lambda:ap-northeast-1:xxxxxx:function:xxxxxxxxxxxxxxxxx の文字列。

6-1. [Alexa Skills Kit] ARNを設定

  • 前述5-5の ARN を、エンドポイント > AWS LambdaのARN > デフォルトの地域 に転記。
  • エンドポイントを保存

6-2. [Alexa Skills Kit] ビルド

ここまでの内容をビルドして使えるようにする。

7-1. [AWS/Lambda] トリガーの設定

  • トリガーの追加 から Alexa Skills Kit を選ぶ。
  • スキル ID 検証 に前述4-4の スキルID の値を入力。
  • 保存

8-1. [Alexa Skills Kit] シミュレータでのテスト

  • Alexa シミュレータ で「赤ちゃんのトイレで大を記録」と入力してみる。
  • 「大を記録しました」と応答が来るのを確認
  • Spreadsheet を開いてデータが追記されているのを確認

9-1. [Amazon Alexa] 実機の設定画面開く

実機にスキルを追加するため、
https://alexa.amazon.co.jp
にアクセスして Alexa 実機の設定画面を開く。

スキル > 有効なスキル > DEV スキル タブ
に今回追加したものがあるので有効にする。

これで完了です!