プッシュ通知

タスクの期限が近づいたときにリマインダーを送信するなど、必要に応じてアクションからユーザーに通知をプッシュできます。

このガイドでは、アクションのプッシュ通知の設定方法を説明するための参考例として、Actions on Google のヒントサンプルを使用します。ユーザーがこのアクションを呼び出すと、独自のアクションを開発するためのヒントを聞きたいかどうか尋ねられます。ユーザーはヒントの特定のカテゴリまたはランダムに選択されたカテゴリを選ぶことができ、最新のヒントを聞くこともできます。

前提条件

ユーザーがアシスタントから受け取った通知をタップすると呼び出されるトリガー インテントとして、アクション プロジェクト内の 1 つ以上のアクションを設定しておく必要があります。

プッシュ通知からデフォルト ウェルカム インテントをトリガーするようにアクションを設定することはできません。

コンソールの設定

アクションにプッシュ通知のサポートを追加するには:

  1. Actions Console にアクセスし、[Build] > [Actions]([ビルド] > [アクション])に移動します。

  2. プッシュ通知を有効にする追加のトリガー インテントに一致するアクションをクリックします。

    Actions on Google のヒントサンプルでは、「tell_latest_tip」を選択します。

  3. [User engagement] セクションで [Would you like to send push notifications] をオンにします。

  4. コンテンツのタイトルを入力します。

    Actions on Google のヒントサンプルでは、タイトルは「New tip added」にします。

  5. [Save] をクリックします。

インポート

以降のセクションのために、フルフィルメント コードで次のインポートを宣言する必要があります。

Dialogflow
const {
  dialogflow,
  UpdatePermission,
  Suggestions
} = require('actions-on-google');
const app = dialogflow();
Actions SDK
const {
  actionssdk,
  UpdatePermission,
  Suggestions
} = require('actions-on-google');
const app = actionssdk();

オプトイン ユーザー

プッシュ通知をユーザーに送信する前に、ユーザーにオプトインを求める必要があります。その方法として、ユーザーにオプトインの候補ワードを提示できます。ユーザーから許可を得たら、そのユーザー ID と、通知を送信する対象のインテントを保存する必要があります。

オプトイン用の候補ワードを表示する

ユーザーがアクションからプッシュ通知を受信できるようにするには、プッシュ通知のオプトインを促す候補ワードを提示する必要があります。

次のコード スニペットは、「Alert me of new tips」という候補ワードをテキスト レスポンスとともにユーザーに送信します。

Node.js
conv.ask('I can send you alerts. Would you like that?');
conv.ask(new Suggestions('Alert me of new tips'));
Dialogflow JSON

下記の JSON は Webhook レスポンスを示します。

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "richResponse": {
        "items": [
          {
            "simpleResponse": {
              "textToSpeech": "I can send you alerts. Would you like that?"
            }
          }
        ],
        "suggestions": [
          {
            "title": "Alert me of new tips"
          }
        ]
      }
    }
  }
}
Actions SDK JSON

以下の JSON は Webhook レスポンスを示します。

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.TEXT"
        }
      ],
      "inputPrompt": {
        "richInitialPrompt": {
          "items": [
            {
              "simpleResponse": {
                "textToSpeech": "I can send you alerts. Would you like that?"
              }
            }
          ],
          "suggestions": [
            {
              "title": "Alert me of new tips"
            }
          ]
        }
      }
    }
  ]
}

ユーザーがこの候補ワードをタップしたら、UPDATE の許可を求める必要があります。次のコードは、Node.js クライアント ライブラリの askForUpdatePermission 関数を使用してこれを行う方法を示しています。

Node.js
  1. Dialogflow コンソールでエージェントを開き、更新用に構成するインテントを選択します。
  2. [Response] で [Google Assistant] タブを開きます。
  3. [Add message content] をクリックして [Suggestion chips] を選択します。
  4. 候補ワードのテキストを、ユーザーにオプトインを促す文言に設定します。Actions on Google のヒントサンプルでは、「Alert me of new tips」に設定されています。
  5. 別の Dialogflow インテント(setup_push など)を追加し、それに対応するアクション(setup.push など)を設定します。このインテントのユーザー表現は、オプトイン用の候補ワードのテキスト(Actions on Google のヒントサンプルでは「Alert me of new tips」)と一致させる必要があります。
次のスニペットは、Node.js 用の Actions on Google クライアント ライブラリを使用して許可を求める方法を示します。
app.intent('setup_push', (conv) => {
  conv.ask(new UpdatePermission({
    intent: 'tell_latest_tip'
  }));
});
JSON

以下の JSON は、Dialogflow を使用した Webhook レスポンスを示します。

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.PERMISSION",
        "data": {
          "@type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
          "permissions": [
            "UPDATE"
          ],
          "updatePermissionValueSpec": {
            "intent": "tell_latest_tip"
          }
        }
      }
    }
  }
}
Java
  1. Dialogflow コンソールでエージェントを開き、更新用に構成するインテントを選択します。
  2. [Response] で [Google Assistant] タブを開きます。
  3. [Add message content] をクリックして [Suggestion chips] を選択します。
  4. 候補ワードのテキストを、ユーザーにオプトインを促す文言に設定します。Actions on Google のヒントサンプルでは、「Alert me of new tips」に設定されています。
  5. 別の Dialogflow インテント(setup_push など)を追加し、それに対応するアクション(setup.push など)を設定します。このインテントのユーザー表現は、オプトイン用の候補ワードのテキスト(Actions on Google のヒントサンプルでは「Alert me of new tips」)と一致させる必要があります。
次のスニペットは、Actions on Google Java/Kotlin クライアント ライブラリを使用して許可を求める方法を示します。
ResponseBuilder responseBuilder = getResponseBuilder(request);
return responseBuilder.add(new UpdatePermission().setIntent("tell_latest_tip")).build();
Actions SDK Node.js

ユーザー表現がプッシュ通知のオプトイン プロンプトの値と一致する場合に許可を求める関数をトリガーするように NLU ソリューションを構成する必要があります。文字列のマッチングに基づく基本的な例を以下に示します。

app.intent('actions.intent.TEXT', (conv) => {
  if (conv.input.raw === 'Alert me of new tips') {
    conv.ask(new UpdatePermission({
      intent: 'tell_latest_tip'
    }));
  }
});
Actions SDK Java

ユーザー表現がプッシュ通知のオプトイン プロンプトの値と一致する場合に許可を求める関数をトリガーするように NLU ソリューションを構成する必要があります。文字列のマッチングに基づく基本的な例を以下に示します。

ResponseBuilder responseBuilder = getResponseBuilder(request);
return responseBuilder.add(new UpdatePermission().setIntent("tell_latest_tip")).build();
Actions SDK JSON

以下の JSON は、Actions SDK を使用した Webhook レスポンスを示します。

{
  "expectUserResponse": true,
  "expectedInputs": [
    {
      "possibleIntents": [
        {
          "intent": "actions.intent.PERMISSION",
          "inputValueData": {
            "@type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
            "permissions": [
              "UPDATE"
            ],
            "updatePermissionValueSpec": {
              "intent": "tell_latest_tip"
            }
          }
        }
      ]
    }
  ]
}

有料会員登録の最終処理

Node.js Webhook から有料会員登録の最終処理を行うには、ユーザーのユーザー ID と、ユーザーが選択したインテントを保存する必要があります。ユーザーが許可を与えた場合、その両方が引数として渡されます。

Dialogflow でアクションを作成している場合は、次のことを行う必要があります。

  • actions_intent_PERMISSION を処理するインテントを追加します。
  • インテントのアクション名を、後で Webhook によってフィルタリングできる名前に設定します。

次のコードは、finish.push.setup というアクション名を持つ finish_push_setup という名前の Dialogflow インテントを処理する方法を示します。

Node.js
app.intent('finish_push_setup', (conv) => {
  if (conv.arguments.get('PERMISSION')) {
    const userID = conv.arguments.get('UPDATES_USER_ID');
    // Save intent and userID in your db
    conv.close(`Ok, I'll start alerting you.`);
  } else {
    conv.close(`Ok, I won't alert you.`);
  }
});
Java
ResponseBuilder responseBuilder = getResponseBuilder(request);

Argument permission = request.getArgument(ConstantsKt.ARG_PERMISSION);
if (permission != null) {
  Argument userId = request.getArgument(ConstantsKt.ARG_UPDATES_USER_ID);
  // code to save intent and userID in your db
  responseBuilder.add("Ok, I'll start alerting you.").endConversation();
} else {
  responseBuilder.add("Ok, I won't alert you.");
}
return responseBuilder.build();
Dialogflow JSON

以下の JSON は、Dialogflow を使用した Webhook レスポンスを示します。

{
  "responseId": "",
  "queryResult": {
    "queryText": "",
    "action": "",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "fulfillmentText": "",
    "fulfillmentMessages": [],
    "outputContexts": [],
    "intent": {
      "name": "finish_push_setup",
      "displayName": "finish_push_setup"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {},
    "languageCode": ""
  },
  "originalDetectIntentRequest": {
    "source": "google",
    "version": "2",
    "payload": {
      "isInSandbox": true,
      "surface": {
        "capabilities": [
          {
            "name": "actions.capability.SCREEN_OUTPUT"
          },
          {
            "name": "actions.capability.AUDIO_OUTPUT"
          },
          {
            "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
          },
          {
            "name": "actions.capability.WEB_BROWSER"
          }
        ]
      },
      "inputs": [
        {
          "rawInputs": [],
          "intent": "",
          "arguments": [
            {
              "name": "UPDATES_USER_ID",
              "textValue": "abcd1234-EFGH_789"
            }
          ]
        }
      ],
      "user": {},
      "conversation": {},
      "availableSurfaces": [
        {
          "capabilities": [
            {
              "name": "actions.capability.SCREEN_OUTPUT"
            },
            {
              "name": "actions.capability.AUDIO_OUTPUT"
            },
            {
              "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
            },
            {
              "name": "actions.capability.WEB_BROWSER"
            }
          ]
        }
      ]
    }
  },
  "session": ""
}
Actions SDK JSON

以下の JSON は、Actions SDK を使用した Webhook レスポンスを示します。

{
  "user": {},
  "device": {},
  "surface": {
    "capabilities": [
      {
        "name": "actions.capability.SCREEN_OUTPUT"
      },
      {
        "name": "actions.capability.AUDIO_OUTPUT"
      },
      {
        "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
      },
      {
        "name": "actions.capability.WEB_BROWSER"
      }
    ]
  },
  "conversation": {},
  "inputs": [
    {
      "rawInputs": [],
      "intent": "actions.intent.PERMISSION",
      "arguments": [
        {
          "name": "UPDATES_USER_ID",
          "textValue": "abcd1234-EFGH_789"
        }
      ]
    }
  ],
  "availableSurfaces": [
    {
      "capabilities": [
        {
          "name": "actions.capability.SCREEN_OUTPUT"
        },
        {
          "name": "actions.capability.AUDIO_OUTPUT"
        },
        {
          "name": "actions.capability.MEDIA_RESPONSE_AUDIO"
        },
        {
          "name": "actions.capability.WEB_BROWSER"
        }
      ]
    }
  ]
}

通知を送信する

Actions API を使用してプッシュ通知をユーザーに送信できます。この API を使用するには、Google Cloud プロジェクトで API を有効にし、JSON サービス アカウント キーを設定してダウンロードする必要があります。このコードサンプルの手順のステップ 8 をご覧ください。

次に、Google OAuth2 クライアント ライブラリを使用してサービス アカウント キーからアクセス トークンを取得し、そのトークンを使用して Actions API へのリクエストを認証します。

サービス アカウント キーを取得する

  1. 次の URL にアクセスします。最後の「example-project-1」は、Actions Console のプロジェクト ID に置き換えてください。https://console.developers.google.com/apis/api/actions.googleapis.com/overview?project=example-project-1
  2. [有効にする] ボタンが表示されている場合はクリックします。表示されていない場合は、手順 3 に進みます。
  3. 次の URL にアクセスします。最後の「example-project-1」は、Actions Console のプロジェクト ID に置き換えてください。https://console.developers.google.com/apis/credentials?project=example-project-1
  4. [認証情報を作成] > [サービス アカウント キー] をクリックします。
  5. [Service Account](サービス アカウント)の下の選択ボックスをクリックし、[サービス アカウント](新しいサービス アカウント)をクリックします。
  6. サービス アカウントに「notifications」などの名前を付け、プロジェクト オーナー を [Role](役割)として設定します。
  7. JSON キータイプを選択して [作成] をクリックします。JSON サービス アカウント キーがローカルマシンにダウンロードされます。

キーを交換してアクセス トークンを取得し、通知を送信する

Actions API によって通知を送信するには、サービス アカウント キーを交換してアクセス トークンを取得する必要があります。これには Google API クライアント ライブラリを使用することをおすすめします。以下の一連のコード スニペットでは、Google API Node.js クライアント ライブラリを使用しています。

  1. 次のコマンドを実行して、Google API クライアント ライブラリとリクエストをインストールします。npm install googleapis request --save
  2. 次のコードを使用して、サービス アカウント キーからアクセス トークンを取得し、プッシュ通知を送信します。
Node.js
const {JWT} = require('google-auth-library');
const request = require('request');
const key = require(PATH_TO_KEY);

let jwtClient = new JWT(
  key.client_email, null, key.private_key,
  ['https://www.googleapis.com/auth/actions.fulfillment.conversation'],
  null
);

jwtClient.authorize((authErr, tokens) => {
  let notification = {
    userNotification: {
      title: 'A new tip is added',
    },
    target: {
      userId: 'abcd1234-EFGH_789',
      intent: 'tell_latest_tip',
      // Expects a IETF BCP-47 language code (i.e. en-US)
      locale: 'en-US'
    },
  };

  request.post('https://actions.googleapis.com/v2/conversations:send', {
    'auth': {
      'bearer': tokens.access_token,
    },
    'json': true,
    'body': {
      'customPushMessage': notification
    },
  }, (reqErr, httpResponse, body) => {
    console.log(httpResponse.statusCode + ': ' + httpResponse.statusMessage);
  });
});
Java
package snippets;

import com.google.actions.api.ActionRequest;
import com.google.actions.api.ActionResponse;
import com.google.actions.api.ConstantsKt;
import com.google.actions.api.DialogflowApp;
import com.google.actions.api.ForIntent;
import com.google.actions.api.response.ResponseBuilder;
import com.google.actions.api.response.helperintent.RegisterUpdate;
import com.google.actions.api.response.helperintent.UpdatePermission;
import com.google.api.services.actions_fulfillment.v2.model.Argument;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

public class Notifications extends DialogflowApp {

  @ForIntent("ask_for_update_permissions")
  public ActionResponse askForUpdatePermissions(ActionRequest request) {
    ResponseBuilder responseBuilder = getResponseBuilder(request);
    return responseBuilder.add(new UpdatePermission().setIntent("tell_latest_tip")).build();
  }

  @ForIntent("setup_push")
  public ActionResponse setupPush(ActionRequest request) {
    ResponseBuilder responseBuilder = getResponseBuilder(request);
    return responseBuilder.add(new UpdatePermission().setIntent("tell_latest_tip")).build();

    // TODO: Unclear what needs to be done here. See JS snippet below.

    /*
    const userInput = conv.input.raw;
    if (userInput === 'Alert me of new tips') {
      app.intent('setup_push', (conv) => {
          conv.ask(new UpdatePermission({intent: 'tell_latest_tip'}));
    });
    }
    */
  }

  @ForIntent("finish_push_setup")
  public ActionResponse finishPushSetup(ActionRequest request) {
    ResponseBuilder responseBuilder = getResponseBuilder(request);

    Argument permission = request.getArgument(ConstantsKt.ARG_PERMISSION);
    if (permission != null) {
      Argument userId = request.getArgument(ConstantsKt.ARG_UPDATES_USER_ID);
      // code to save intent and userID in your db
      responseBuilder.add("Ok, I'll start alerting you.").endConversation();
    } else {
      responseBuilder.add("Ok, I won't alert you.");
    }
    return responseBuilder.build();
  }

  public void sendNotification(String title, String userId, String intent) throws IOException {
    Preconditions.checkNotNull(title, "title cannot be null.");
    Preconditions.checkNotNull(userId, "userId cannot be null.");
    Preconditions.checkNotNull(intent, "intent cannot be null.");

    PushNotification notification = createNotification(title, userId, intent);
    HttpPost request = new HttpPost("https://actions.googleapis.com/v2/conversations:send");

    String token = getAccessToken();

    request.setHeader("Content-type", "application/json");
    request.setHeader("Authorization", "Bearer " + token);

    StringEntity entity = new StringEntity(new Gson().toJson(notification));
    entity.setContentType(ContentType.APPLICATION_JSON.getMimeType());
    request.setEntity(entity);
    HttpClient httpClient = HttpClientBuilder.create().build();
    httpClient.execute(request);
  }

  private String getAccessToken() throws IOException {
    AccessToken token = loadCredentials().refreshAccessToken();
    return token.getTokenValue();
  }

  private ServiceAccountCredentials loadCredentials() throws IOException {
    String actionsApiServiceAccountFile =
        this.getClass().getClassLoader().getResource("service-account.json").getFile();
    InputStream actionsApiServiceAccount = new FileInputStream(actionsApiServiceAccountFile);
    ServiceAccountCredentials serviceAccountCredentials =
        ServiceAccountCredentials.fromStream(actionsApiServiceAccount);
    return (ServiceAccountCredentials)
        serviceAccountCredentials.createScoped(
            Collections.singleton(
                "https://www.googleapis.com/auth/actions.fulfillment.conversation"));
  }

  private PushNotification createNotification(String title, String userId, String intent) {
    Notification notification = new Notification(title);
    Target target = new Target(userId, intent);
    PushMessage message = new PushMessage(notification, target);
    boolean isInSandbox = true;
    return new PushNotification(message, isInSandbox);
  }

  final class Notification {

    private final String title;

    Notification(String title) {
      this.title = title;
    }

    String getTitle() {
      return title;
    }
  }

  final class PushMessage {

    private final Notification userNotification;
    private final Target target;

    PushMessage(Notification userNotification, Target target) {
      this.userNotification = userNotification;
      this.target = target;
    }

    Notification getUserNotification() {
      return userNotification;
    }

    Target getTarget() {
      return target;
    }
  }

  final class PushNotification {

    private final PushMessage customPushMessage;
    private boolean isInSandbox;

    PushNotification(PushMessage customPushMessage, boolean isInSandbox) {
      this.customPushMessage = customPushMessage;
      this.isInSandbox = isInSandbox;
    }

    PushMessage getCustomPushMessage() {
      return customPushMessage;
    }

    boolean getIsInSandbox() {
      return isInSandbox;
    }
  }

  final class Target {

    private final String userId;
    private final String intent;

    Target(String userId, String intent) {
      this.userId = userId;
      this.intent = intent;
    }

    String getUserId() {
      return userId;
    }

    String getIntent() {
      return intent;
    }
  }

  @ForIntent("setup_alerts")
  public ActionResponse setupAlerts(ActionRequest request) {
    ResponseBuilder responseBuilder = getResponseBuilder(request);
    String category = (String) request.getParameter("category");
    return responseBuilder
        .add(
            new RegisterUpdate()
                .setIntent("tell_tip")
                .setFrequency("DAILY")
                .setArguments(
                    Arrays.asList(new Argument().setName("category").setTextValue(category))))
        .build();
  }
}

プッシュ通知をテストする

プッシュ通知をテストしたいものの、独自のアクションがない場合は、Actions on Google のヒントサンプルのセットアップと実行の手順に沿ってこのサンプルのテスト バージョンをデプロイできます。

プッシュ通知からデフォルト ウェルカム インテントをトリガーするようにアクションを設定することはできないことを忘れないでください。