LoginSignup
2
2

More than 5 years have passed since last update.

LINE ClovaとkintoneをSDK使わず連携してみた

Last updated at Posted at 2018-09-21

はじめに

先週、cybozu developer networkにて、
LINE Clovaとkintoneを使って在庫管理をしよう!
という LINEのスマートスピーカー Clovakintone を連携した記事を公開しました!

上の記事ではそれぞれのSDKを使ってかなりぱぱっと作ったのですが、
元々SDKなしで実装していたので、この記事ではSDKは使わずLambdaのcallbackで実装したものを紹介します!
(kintoneもSDKではなくrequestモジュールでやります)

作る連携シナリオ

流石にdevnet記事と同じ内容だと飽きちゃうので、今回は、

  • Clovaに話しかけてkintoneのアプリ内を検索
  • もし該当するレコードが1件あれば、そのレコードの内容を話す
  • もし該当するレコードがなければ、「ないよ」と話す
  • もし該当するレコードが複数あれば、「複数あるよ」と話す

って感じに作ってみました。超シンプルですね!( ̄ー ̄)

kintoneの設定

アプリの設定についてです!
今回はアプリストアにある「FAQ」を利用します!

適当にサンプルデータを入れてください
2018-09-19_18h25_32.png

そして、フィールドコードが初期値のままだと扱いづらいので、
それぞれ、

フィールド名 フィールドコード
質問 question
回答 answer

とします。
あとは APIトークン (権限:取得のみ) を発行してkintone側の準備は完了です!

LINE Clovaの設定

かなり設定数が多いのでちょっと大変ですが、細かいところは上のdevnetの記事をご覧ください!
→ devnetの記事は超丁寧に書いたつもりです (^o^)

大事なところとしては、

  • 呼び出し名 を設定 (これは当然)
  • ExtensionサーバーのURL
    • AWS API GatewayのURLを記述してください
  • 対話モデル (スピーカーの対話モデル)
    • スロット (インテントの設定より先にこちらをやると楽です!)
    • インテント

などがあります!

スロット

今回作成したカスタムスロットは以下です。
2018-09-19_15h44_12.png

とりあえずで3種類単語を用意して、すべて1つのスロット(SearchVal)に格納されるようにしています。

インテント

カスタムインテントは以下です。
2018-09-19_15h44_03.png

実際にClovaに向けて発言しそうなサンプルを記述しました。

AWS の設定

中間サーバーとしてAWSのLambda (と API Gateway) を利用します。
こちらも上記のdevnet記事内で説明しているので細かい設定は割愛します。

API Gatewayの設定

認証をオープンにする or CORS設定をする必要があるかと思います!
※ CORSについては こちら に説明があります!

Lambda関数の作成

プログラムは Node.js で記述します!
Nodeのバージョンは 8.10 としてください!
→ async/await を利用します

コードの詳細は次へ

コード

Lambda関数にのせるNode.jsのコードです!
こちらのコードはGitHubに置いてあります。

httpリクエストのためにrequest-promiseモジュールを利用したので、

  • request
  • request-promise

をnpmでインストールしてください m(_ _)m

const request = require('request-promise');

// kintoneから該当するレコードの件数を返す処理
const getkintone = val => {
  // kintone用パラメータ
  const DOMAIN = '<domain>'; // sample.cybozu.com
  const URL = 'https://' + DOMAIN + '/k/v1/records.json';
  const APP_ID = '<app ID>';
  const API_TOKEN = '<API Token>';
  const headers = { 'X-Cybozu-API-Token': API_TOKEN };

  // 発話した単語をkintoneの文字列複数行フィールドでLike検索
  const params = {
    app: APP_ID,
    query: 'question like "' + val + '"',
    totalCount: true,
  };
  const options = {
    url: URL,
    method: 'GET',
    headers: headers,
    'Content-Type': 'application/json',
    json: params,
  };

  let text;
  // 該当するレコードを取得
  return request(options)
    .then(resp => {
      switch (resp.records.length) {
        case 0:
          text = val + 'に該当するレコードはありませんでした。';
          break;
        case 1:
          text = resp.records[0].answer.value;
          break;
        default:
          text = val + 'に該当するレコードが' + resp.totalCount + '件ありました!';
          break;
      }
      return text;
    })
    .catch(err => {
      console.log(err);
    });
};

// Clovaに返す処理
const setClova = async (text, bool, callback) => {
  // Clovaに返すJSON
  const body = {
    version: '1.0',
    sessionAttributes: {},
    response: {
      outputSpeech: {
        type: 'SimpleSpeech',
        values: {
          type: 'PlainText',
          lang: 'ja',
          value: text,
        },
      },
      card: {},
      directives: [],
      shouldEndSession: bool,
    },
  };

  const res = {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json;charset=UTF-8',
    },
    body: JSON.stringify(body),
  };
  callback(null, res);
};

exports.handler = async (event, conetext, callback) => {
  const req = JSON.parse(event.body).request;
  let text, word, val;

  // ユーザのアクションによって処理を振り分ける
  switch (req.type) {
    case 'LaunchRequest':
      text = 'kintoneで何を検索したいですか?';
      break;
    case 'SessionEndedRequest':
      text = 'さようなら!';
      break;
    case 'IntentRequest':
      // スロットの中身を格納
      word = req.intent.slots;
      // キーワードがわからなかった場合
      if (!word) {
        text = 'すみません。その言葉はまだ理解できません。';
        break;
      }
      // スロット内のキーワードが認識できていれば
      val = word.SearchVal.value;
      text = await getkintone(val);
      setClova(text, true, callback);
      break;
    default:
      text = 'kintoneで調べたい単語を教えてください';
      break;
  }
  // 対話を続けるので false を返す
  setClova(text, false, callback);
};

プチ解説

コードについてちょっとだけ解説します!

kintone からレコード取得

「発話されたキーワード(val) → kintone側の質問フィールド(question)をlike検索」
するような処理をクエリに追加してレコードの取得をしています。

  const params = {
    app: APP_ID,
    query: 'question like "' + val + '"',
    totalCount: true,
  };

そして、取得したレコードが、

  • 0 の場合 → 該当するレコードがなかった旨を返す
  • 1 の場合 → そのレコード内の回答フィールド(answer)の値を返す
  • 1以上の場合 → 複数該当するレコードがあった旨を返す

と分けて処理してみました!

  let text;
  // 該当するレコードを取得
  return request(options)
    .then(resp => {
      switch (resp.records.length) {
        case 0:
          text = val + 'に該当するレコードはありませんでした。';
          break;
        case 1:
          text = resp.records[0].answer.value;
          break;
        default:
          text = val + 'に該当するレコードが' + resp.totalCount + '件ありました!';
          break;
      }
      return text;
    })
    .catch(err => {
      console.log(err);
    });

※ エラー処理はかなーり雑です。。
※ async/await を使うためにプロミスを返すようにしています。

Clovaに発話させる処理

ここの部分は、
AWS LambdaでLINE Clovaのスキルを作成する
を参考にさせていただきました!

Lambdaのイベントハンドラー

Clovaが受け取ったユーザーの発話情報は、

exports.handler = async (event, conetext, callback) => {

のeventの中に格納されています。そのため、このeventの中の情報を取得して
分岐処理をさせています。

おわりに

ということで、
ClovaとkintoneをSDKなしで連携してみました!!

思ったより難しくはなかったですが、やはりSDKを使わないと設定部分の記述量が多くなりますね。。

それでは!≧(+・` ཀ・´)≦

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2