かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Azure Functions で clova-cek-sdk-nodejs を使ってみよう

前試したときはダメだったのですが azure-function-express というモジュールが先日 v2.0.0 にアップデートしてたので試してみました。環境は以下のような感じです。

  • Azure Functions Core Tools 2.0.1-beta.38
  • node.js v8.11.4

ただ、前と同じでノーレスポンスになってしまいダメだったのでコードを読んでみたところ原因がわかったので、それを踏まえた内容を以下に書きます。

package.json の中はこんな感じです。

{
  "name": "clova-with-azure-functions-v2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@line/clova-cek-sdk-nodejs": "^1.1.0",
    "azure-function-express": "^2.0.0",
    "express": "^4.16.3"
  }
}

この形に持っていくまでの手順は以下のような流れです。

mkdir my-clova-sample
cd my-clova-sample
func init                                 # 選択肢が出るので node を選択
func new                               # 選択肢が出るので HttpTrigger を選択して clova という名前で作成
npm init -y
npm i @line/clova-cek-sdk-nodejs
npm i azure-function-express
npm i express

clova フォルダにある function.json で HTTP で受け入れるリクエストが定義されているのですが、デフォルトだと GET, POST なので今回は GET が不要なので削除しましょう。以下のような感じにします。

{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "post"                                   // ここね
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

あとは、azure-function-express と clova-cek-sdk-nodejs のサイトを参考にしてさくっと書くだけです。ポイントは azure-function-express で渡ってくる req.body が string じゃなくて object に既に変換されている点です。生のデータはどうやってとれるのかというと req.rawBody です。clova-cek-sdk-nodejs のコードを見てると body には string とかみたいな生の値が入ってくるのを期待してるみたいなので、そこを調整してあげる必要があります。

なので、このような感じになります。

const createHandler = require("azure-function-express").createHandler;
const express = require("express");
const clova = require('@line/clova-cek-sdk-nodejs');
const bodyParser = require('body-parser');

const clovaSkillHandler = clova.Client
    .configureSkill()
    .onLaunchRequest(responseHelper => {
        responseHelper.setSimpleSpeech({
            lang: 'ja',
            type: 'PlainText',
            value: 'おはよう',
        });
    })
    .onIntentRequest(async responseHelper => {
        const intent = responseHelper.getIntentName();
        const sessionId = responseHelper.getSessionId();

        switch (intent) {
            case 'Clova.YesIntent':
                // Build speechObject directly for response
                responseHelper.setSimpleSpeech({
                    lang: 'ja',
                    type: 'PlainText',
                    value: 'はいはい',
                });
                break;
            case 'Clova.NoIntent':
                // Or build speechObject with SpeechBuilder for response
                responseHelper.setSimpleSpeech(
                    clova.SpeechBuilder.createSpeechText('いえいえ')
                );
                break;
            case 'ThrowDiceIntent':
                responseHelper.setSimpleSpeech(
                    clova.SpeechBuilder.createSpeechText('サイコロ!')
                );
                break;
        }
    })
    .onSessionEndedRequest(responseHelper => {
        const sessionId = responseHelper.getSessionId();

        // Do something on session end
    })
    .handle();

const clovaMiddleware = clova.Middleware({ applicationId: "<自分の Extension ID>" });

const app = express();
app.post('/api/clova', 
    (req, res, next) => { req.body = req.rawBody; next(); }, // Azure Functions がしてくれるパースは今回はいらないので rawBody で置き換え
    clovaMiddleware, 
    clovaSkillHandler);
module.exports = createHandler(app);

これで Azure Functions にデプロイすると動きます!ローカルで動かす場合は func host start して ngrok で出来ます。