Help us understand the problem. What is going on with this article?

Clova公式SDK(Node.js)の使い方まとめ

More than 1 year has passed since last update.

はじめに

Clova Extensions Kit(以下CEK)が公開され、公式SDKもリリースされたものの、チュートリアルで使われてなかったり、ドキュメントも見当たらなかったり、サーバはご自由にという状況で戸惑った方も少なからずいらっしゃるのではないでしょうか(私がそうです)。
というわけでひとまず公式SDKのNode.js版ソースを流し読みしてみたので、使い方について簡単にまとめて説明してみようと思います。

※AWS(Lambda)、Firebase(Functions)での動かし方について↓の記事にまとめました。
Clova公式SDK(Node.js)のAWS、Firebaseでの動かし方

※2018/8/20 アップデートによって追加されたメソッドの説明を追記しました。

setSessionAttributesgetSessionAttributesは、OSSで初プルリクしたものが無事マージされました…!)

公式SDK

2018/7/20時点ではNode.js版Swift版が公開されています。
今後も随時追加、更新されていくそうです。

今回はAlexaスキル、Google Assistantアプリの開発でもお馴染みのNode.js版を説明していきます。

インストール

npm installでさくっといけます。

$ npm i @line/clova-cek-sdk-nodejs

使い方

require

書くまでもないですが一応。

const clova = require('@line/clova-cek-sdk-nodejs');

handler

ClientクラスのconfigureSkillメソッドでSkillConfiguratorクラスのインスタンスを生成してます。
そのあとに各リクエストイベントでの処理をonメソッドの引数に渡していきます。

const handler = clova.Client
  .configureSkill()
  .onLaunchRequest(launchHandler)
  .onIntentRequest(intentHandler)
  .onSessionEndedRequest(sessionEndedHandler)
  .handle();

Clovaではリクエストが以下の4タイプあります。

リクエスト 内容
LaunchRequest スキル起動時のリクエスト。
IntentRequest スキル起動~終了までの間呼ばれ続けるリクエスト。主にここをあれこれしてく。
SessionEndedRequest スキル終了時のリクエスト。応答を返したりはできない。
EventRequest ユーザの発話の有無に関わらず、クライアントの状態の変化や、それに伴うリクエストをExtensionに渡すために使用されるリクエスト。ハードウェアボタンとか?

このうちEventRequestを除く3つのリクエストのイベントが用意されています。
引数のハンドラー関数にリクエストが呼ばれた際の処理を書いていきます。

Contextクラス

各リクエストイベントから呼ばれるハンドラー関数の引数としてresponseHelperが渡されます。
このresponseHelperContextクラスのインスタンスで、インテント名やスロットを取得したりする色んな便利メソッドが詰まってるので簡単に説明していきます。

getSessionId()

セッションIDを取得します。

getIntentName()

インテント名を取得します。
onIntentRequestで拾ったリクエストは基本的にインテント名でswitchして処理を分岐させていきます。

getSlots()

スロットオブジェクトをごっそり取得します。

getSlot(slotName)

指定のスロット情報のみを取得します。

endSession()

このリクエストでスキルを終了させます。
Clovaから返事を返してそのままスキル終了させたいときはこのメソッドを入れときます。
スキルを継続したい場合はなにもしなくておっけー。

setSessionAttributes(sessionAttributes)

任意の情報をセッション変数に保持させます。
引数のsessionAttributesはオブジェクトです。
フラグ等、スキル実行中に継続して保持したい情報はこのsessionAttributesに格納しておきます。

また@h-takauma さんの「Clova審査一発通過のための注意点」の記事でも書かれていますが、Clovaスキルは現在の仕様上、スキルを終了してもセッションが終了されません。
つまりスキル終了後すぐに再びスキルを呼び出すとセッション情報が残ったままスキルが実行されます。

プログラムの書き方によっては思わぬバグの原因にもなりますので、LaunchRequest時(スキル起動時)に明示的にクリアさせておくと良いでしょう。

  ...

  .onLaunchRequest(responseHelper => {
    // sessionAttributesを明示的にクリア
    responseHelper.setSessionAttributes({})
    clova.SpeechBuilder.createSpeechText('スキル実行!')
  })

  ...

getSessionAttributes()

セッション変数を取得します。
オブジェクトがまるっと返ってきます。

getUser()

ユーザー情報を取得します。
返ってくるのはUser型オブジェクトです。

// User型
{
  "userId": "xxxxxxxx",
  "accessToken": "xxxxxxxx"
}

accessTokenがない場合はuserIdのみ返ってきます。

返答メソッド

Clovaからの返答には主に以下の3タイプあり、それぞれのsetメソッドが用意されています。
responseHelperから同様に呼び出せます。

返答タイプ 内容
SimpleSpeech 単文タイプの音声情報です。最も基本となるタイプです。
SpeechList 複文タイプの音声情報です。複数の文章を出力する際に使用されます。
SpeechSet 複合タイプの音声情報です。画面を持たないクライアントデバイスに、要約音声情報と詳細音声情報を渡す際に使用されます。

また、それぞれのメソッドの最後の引数にrepromptをboolean型で指定でき(省略した場合はfalse)、trueを指定した場合はユーザーが無反応の状態で一定時間経過後にClovaが聞き返しを行ってくれて、入力待ち時間を最大1回延長できます。
通常の返答とリプロンプトはそれぞれ個別にメソッドを実行しないとダメです。

// まずこっちで返答して、
responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechText('通常の返答だよ!')
);

// ユーザーが無反応の状態で一定時間経過後にこっちを喋る
responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechText('リプロンプトだよ!'), true
);

// さらにユーザーが無反応の状態で一定時間経過するとスキルが終了される

setSimpleSpeech(speechInfo[, reprompt])

文章または音声データ(mp3とか)を返答させます。
引数のspeechInfoSpeechInfoObjectという形式のオブジェクトになります。
なお後述するSpeechBuilderクラスにvalueの指定のみでSpeechInfoObjectオブジェクトを生成するメソッドがあります。

// 文章
{
   "type": "PlainText",
   "lang": "ja",
   "value": "返答させる文章"
}

// 音声データ
{
  "type": "URL",
  "lang": "" ,
  "value": "https://www.example.com/sound.mp3"
}

setSpeechList(speechInfo[, reprompt])

複数の文章や音声データのURLを返答させます。
引数のspeechInfoは配列になります。

setSpeechSet(speechInfoBrief, speechInfoVerbose[, reprompt])

画面があるデバイスとないデバイスで出力を出し分けるっぽいです。
多分動きとしては画面がある場合はspeechInfoBriefで、ない場合はspeechInfoVerboseで応答するみたいです。
現状画面なしデバイスしかなく、Clova Friendsで試した限りはspeechInfoVerboseの応答のみ返ってきました。
対話モデル画面のテストからだと「応答がありません。(undefined)」となってしまいます。

speechInfoBriefはSpeechInfoObject形式のオブジェクトを渡します。
speechInfoVerboseはSpeechInfoObjectとは少し異なる形式のオブジェクトとなります。
SimpleSpeechとSpeechListのどちらかを以下の形式で渡します。

// SimpleSpeech
{
  type: 'SimpleSpeech', 
  values: SpeechInfoObject
}

// SpeechList
{
  type: 'SpeechList', 
  values: Array<SpeechInfoObject>
}

Clova.SpeechBuilderクラス

文章と音声データそれぞれのSpeechInfoObjectオブジェクトを生成するスタティックメソッドがあります。

createSpeechText(value[, lang])

文章のSpeechInfoObjectオブジェクトを生成してくれます。
また省略可能の第二引数で言語を指定できます。

responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechText('文章だよ!')
)

createSpeechUrl(value[, lang])

音声データのSpeechInfoObjectオブジェクトを生成してくれます。

responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechUrl('https://www.example.com/sound.mp3')
)

Clova.Middleware(MiddlewareOptions)

Extension IDが正しいか検証するっぽいメソッド。

const clovaMiddleware = clova.Middleware({ applicationId: "YOUR_EXTENSION_ID" });
app.post('/clova', clovaMiddleware, clovaSkillHandler);

なおID検証しなくてもおっけー。

app.post('/clova', bodyParser.json(), clovaSkillHandler);

サンプルコード

以上を把握するとGitHubのREADMEのサンプルはさくっと読めるんじゃないでしょうか。

const clova = require('@line/clova-cek-sdk-nodejs');
const express = require('express');
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;
    }
  })
  .onSessionEndedRequest(responseHelper => {
    const sessionId = responseHelper.getSessionId();

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

const app = new express();
const clovaMiddleware = clova.Middleware({ applicationId: "YOUR_APPLICATION_ID" });
// Use `clovaMiddleware` if you want to verify signature and applicationId.
// Please note `applicationId` is required when using this middleware.
app.post('/clova', clovaMiddleware, clovaSkillHandler);

// Or you can simply use `bodyParser.json()` to accept any request without verifying, e.g.,
app.post('/clova', bodyParser.json(), clovaSkillHandler);

おわりに

今回まとめた情報はここのソースを読み解いたものです。
コード量も少なく、TypeScriptで書かれていますがTypeScript全く触ったことない私でもなんとなく読めたので、一度さくっと目を通してみるのも楽しいかもしれません。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした