GoogleAssistantからWebhookでLineの特定人物へPush通知


アンドロイドスマホに搭載のGoogleAssistantもしくはGoogleHome系のアプリを作りました。

実際の図

GoogleAssistantで"アイで"と発話すると"hello"と返し、Line側には"アイで"がPUSH通知されます。

そもそも

GoogleAssistant→DialogFlow→LineはIntegrationsで設定すればすぐできますが、
テキストを返すことはできません。
またIntegrationsの方法は自分のLINEへはPUSH通知できますが、特定の人へPUSH通知はできません。

基本、google homeで子供の宿題管理をするの実装手順にてできました。うまくいかなかった箇所を本記事に加えてます。

フロー図

google homeで子供の宿題管理をするからお借りして一部付け加えてます。

DialogFlow

テストでIntentのパラメータを$anyにしてます。
これで”アイで”と発話すればこのIntentが起動します。

Lambda

なぜLambdaを経由しているのかというと、GASでデプロイしたエンドポイントは、302リダイレクトをしてしまい、DialogflowのWebhookとして使えなかったからです。

一番のハマりポイント

google homeで子供の宿題管理をする通り実行するとLambda関数にて下記エラーが出ます。

該当箇所↓
body = json.dumps(json.loads(event["body"])).encode("utf-8")

ん?event["body"]ってなに?
jsonデータ見ても”request”なんだけど…

色々調べてみると下記に書いてありました。

API Gatewayの「Lambda プロキシ統合の使用」オプションより

結論から言うと、API Gatewayにてプロキシ統合の使用をチェックすればOKです。

試しにPOSTしてみたところ、Lambdaのeventには以下の値が設定されていました。

hoge.json
{
    resource: '/mook',
    path: '/mook',
    httpMethod: 'POST',
    headers: { Accept: ' application/json' },
    queryStringParameters: { param1: 'value1' },
    pathParameters: null,
    stageVariables: null,
    requestContext: {
        accountId: 'xxxxxxxxxxxx',
        resourceId: 'xxxxxx',
        stage: 'test-invoke-stage',
        requestId: 'test-invoke-request',
        identity: {
            cognitoIdentityPoolId: null,
            accountId: 'xxxxxxxxxxxx',
            cognitoIdentityId: null,
            caller: 'xxxxxxxxxxxx',
            apiKey: 'test-invoke-api-key',
            sourceIp: 'test-invoke-source-ip',
            cognitoAuthenticationType: null,
            cognitoAuthenticationProvider: null,
            userArn: 'arn:aws:iam::xxxxxxxxxxxx:root',
            userAgent: 'Apache-HttpClient/4.5.x (Java/1.8.0_102)',
            user: 'xxxxxxxxxxxx'
        },
        resourcePath: '/mook',
        httpMethod: 'POST',
        apiId: 'xxxxxxxxxx'
    },
    body: '{"Message": "Hello World"}'
}   

API Gatewayがこれまで丸めていたHTTPヘッダーの情報をかなりきちんと得ることができます。

Spreadsheet with GAS

下記GASのコードです

code.gs
// プロパティ取得
const PROPERTIES = PropertiesService.getScriptProperties();

const LINE_CHANNEL_ACCESS_TOKEN = PROPERTIES.getProperty('LINE_CHANNEL_ACCESS_TOKEN');//GLG_test_bot
const USER_ID = PROPERTIES.getProperty('your_USER_ID');

function doPost(e) {
 try {
   let request = JSON.parse(e.postData.getDataAsString());
   let any = request.queryResult.parameters.any
   switch (any) {
     case 'アイ':
       let result = { "fulfillmentText": "hello" };//fulfillmentTextが大事!
       pushMessage(any)
       return returnAsJSON(result);
     default:
       return returnAsJSON({ "fulfillmentText": "ごめんね。よくわからなかったよ" });
   }
 } catch (ex) {
   Logger.log("Error at doPost : %s", ex)
   doc.getBody().appendParagraph(Logger.getLog())
   return returnAsJSON({ "fulfillmentText": "エラーが発生しました。ログで確認できます" });
 }
}

function returnAsJSON(obj) {
 return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
}

function pushMessage(message) {
 let postData = {
   "to": USER_ID,
   "messages": [{
     "type": "text",
     "text": message,
   }]
 };

 let url = "https://api.line.me/v2/bot/message/push";
 let headers = {
   "Content-Type": "application/json",
   'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN,
 };

 let options = {
   "method": "post",
   "headers": headers,
   "payload": JSON.stringify(postData)
 };
 let response = UrlFetchApp.fetch(url, options);
}

GASはウェブ公開にてデプロイし、そのあと出てくるURLをLambda側にコピペすればOK

※Lineについて別途設定が必要ですが、特別なことはやっていないので設定等は割愛。
LineBotでググればいくらで出てきます。

あとはDialogFlow側でテストし、AWSのcloudwatchのログなどを見ながら実装する。

今後の展望

GASが使えるのでスプレッドシートと連携して色々できます。

参考:

google homeで子供の宿題管理をする
初めてのサーバーレスアプリケーション開発 ~API GatewayからLambdaを呼び出す~ | Developers.IO