Actions on Googleでパーソナライズ化

## はじめに
あなたのGoogleアシスタントアクションで、ユーザ情報に適した情報を提供をしたいと思いませんか?  
例えば、連続で起動してくれているユーザには「毎日起動してくれて、ありがとう😸」って言ってみたり、ユーザの位置情報に応じた情報提供したり、ゲームでの最高得点を保存してみたり。

ユーザデータが保存できると作れるアクションの幅も広がるかと思います！💪
次から、Actions on Googleで利用できるユーザデータの保存方法を紹介していきます。

## 会話中だけデータ保存する
### 利用例: 
- フレーズを話したかフラグとして保存しておく。
- 1つ以上前の質問の回答を利用したい。

### 使い方
1つの会話中だけ(起動->終了まで)情報を保存したい時、 [conversationToken](https://developers.google.com/actions/reference/rest/Shared.Types/AppResponse#FIELDS.conversation_token)を利用してみましょう。
使い方はアクションのFullfillment処理で[Actions on Google Node.js client library](https://github.com/actions-on-google/actions-on-google-nodejs) の`conv.data`に対して、
新しい変数と値をセットするだけです。

↓のサンプルコードは`conv.data.count`を作って、呼ばれるたびにカウントアップしています。

```js
app.intent('今何回目', conv => {
  if (!conv.data.count) {
    conv.data.count = 0;
  }
  const count = ++conv.data.count;
  conv.ask("今は" + count + "回目ですよ");
});
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/86747/8e69db5f-b7ed-98bb-330e-3c1e4783857b.png" width="400" />




## アクションのユーザストレージに保存する
### 利用例 
- アクションの起動回数に応じて、起動時の会話を変更したい。
- 位置情報を毎回ユーザにリクエストしないようにしたい。

### 使い方
[ユーザストレージ](https://developers.google.com/actions/reference/rest/Shared.Types/AppResponse#FIELDS.user_storage)とは、Actions on Googleが提供してくれるアクションごとのデータ保存領域で、会話をまたいでデータを保存しておけます。
これも使い方は簡単で`conv.user.storage`に対して、新しい変数と値をセットするだけです。

:warning: **注意点** :warning:  

- 保存できるデータの最大サイズは**10KB**まで。超える場合は外部のDBの活用を考えましょう。
- ~~iOS, Androidでの共有はできない。~~ (Action起動であれば可能でしたので訂正です。)

```js
app.intent('住所を設定', conv => {
  // ユーザストレージのaddressに保存
  conv.user.storage.address = '今夜月の見える丘';
  conv.ask("おすすめの住所を設定しましたよ");
});

app.intent('どこ住んでるの?', conv => {
  // ユーザストレージのaddressを取得
  conv.ask("私は" + conv.user.storage.address + "に住んでますよ。とてもいいところです。");
});
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/86747/96ac0b0b-30f8-2162-38ac-bf55f64c8117.png" width="400" /> 



## `lastseen`の活用
`lastseen`はユーザが最後に訪れた時間をタイムスタンプで返してくれます。 
1週間ぶりに起動した場合に「2週間ぶりですね、お待ちしていましたよ🌝」みたいな愛着が湧くフレーズの制御ができます。

使うには、`conv.user.last.seen`にアクセスするだけです。
そのまま使うと`2018-12-09T13:59:07.000Z`の様にUTCなので、[Moment Timezone](https://momentjs.com/timezone/)等を使って簡単に利用できるようにすると良いでしょう。

```js
const moment = require('moment-timezone');

app.intent('Default Welcome Intent', conv => {
  // 前回の起動情報があるか確認
  if (conv.user.last.seen) {
    // 
    const lastSeen = moment(conv.user.last.seen).tz("Asia/Tokyo").format('YYYY年MM月DD日 HH:mm:ss');
    conv.ask(lastSeen + " 以来ですね。久しぶりだね、元気にしてたかい？");
  } else {
    conv.ask("Let's start developing on Actions on Google!");
  }
});
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/86747/589e6614-5e0c-3107-754c-239615fbb9a3.png" width="400" />


### ユーザIDの取得
ここまでは、Actions on Googleが提供してくれるストレージに関して書いてきましたが、外部のDBにデータを保存するためにはユーザのIDが必要になります。
ユーザIDはActions on Googleから提供されておらず、自分で作って管理する必要があります。
(2018年3月までは`conv.user.id`というプロパティがあったが、Deprecateになっており、来年2019年3月には削除される予定です。【参考:[Anonymous User Identity](https://developers.google.com/actions/identity/user-info)】 )

扱う場合は、ユーザIDの生成後はユーザストレージで管理といいでしょう。 
サンプルコードでは、ユーザIDを[uuid](https://www.npmjs.com/package/uuid) npmライブラリを使って生成しています。
ユーザストレージになければ生成し、セットしています。

```js
const uuid = require('uuid/v4');

// 例： uuidv4を用いて生成する。
const generateUUID = () => {
  return uuid();
}

let userId;
// ユーザIDがユーザストレージに保存済みかどうか
if ('userId' in conv.user.storage) {
  userId = conv.user.storage.userId;
} else {
  // なければ生成する
  userId = generateUUID();
  conv.user.storage.userId = userId
}

const data = "Something you want to save";
saveDataToDB(userId, data);
```

## 開発ノウハウ: ユーザストレージをリセットする方法
デバッグ時には、テストデータで何度もユーザストレージをリセットしたくなるはずです。
そんな時は、ユーザストレージを空オブジェクトで上書きしてあげましょう。

```js
conv.user.storage = {}; 
```

また、アクションがリリース済みならばActions Directoryのアクションの詳細画面下部にある「アプリをリセット」から可能です。

## まとめ
ちょっとした情報なら、外部のDBを使わなくても簡単に保存できます。 
ひと工夫入れて、ちょっと気が利くアシスタントを作ってみてはどうでしょうか。

## 参考リンク
- [Save Data in Conversation  |  Actions on Google  |  Google Developers](https://developers.google.com/actions/assistant/save-data)
- [General Best Practices  |  Actions on Google  |  Google Developers](https://developers.google.com/actions/assistant/best-practices#personalize_the_conversation_with_user_preferences)