Clova Extensions Kit（CEK）とTwilio Functionsを連携して、ClovaからStudioを呼び出す

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

LINE Clovaを使ってカスタムスキルを作成するには、LINEからアクセスが可能なサーバーが必要です。
ネットの情報を見ていると、herokuをつかったり、Lambdaを使ったり、Firebaseを使ったりする記事がありますが、それだったらTwilio Functionsでも多分できるはず。

[Twilio Functionsとは](https://jp.twilio.com/docs/runtime/functions)

## やりたいこと

例えば、Clovaに向かって「○○さんに電話をかけて」と呼びかけると、特定の人に電話をかけて、特定のメッセージを流すような連携を想定しています。もちろん、herokuとかlambdaとか使わずに、Twilioだけで完結させます。
実際に電話をかけるところなどは、Twilio Studioに任せるので、今回はそのStudioのフローをClovaスキルから呼び出すところを解説します。

## Clova側で必要となる情報

この記事では、Clovaの対話モデルの作成についての詳細は解説しませんが、後ほどTwilio側で必要となる以下の情報だけは事前に控えておいてください。

|項目|説明|
|:--|:--|
|Extension ID|スキルの基本設定画面で確認ができます。スキルを特定するために指定した文字列です。|
|カスタムインテント名|Twilio側で、どのインテントとして反応したかを取得するために必要です。今回は、「CallToTaget」という名前のインテントを作成したものとしてすすめます。|
|スロット名|カスタムインテントを作成するときに指定したスロット名です。今回は、「Who」という名前のスロットを作成したものとしてすすめます。|

ちなみに、今回はこんな感じのカスタムインテントを作成してあります。
![スクリーンショット 2018-09-13 13.55.42.png](https://qiita-image-store.s3.amazonaws.com/0/86046/4d7ced74-16d3-a393-f2c3-9f74e2b1e1b6.png)

スロット「Who」には、以下のようなカスタムスロットタイプを指定してあります。

![スクリーンショット 2018-09-13 13.57.37.png](https://qiita-image-store.s3.amazonaws.com/0/86046/0e5abc8e-a4de-f9ec-b784-6aeff28924a0.png)

これにより、「かつみさんに電話して」だけでなく、「おじさんに電話」と発話してもCallToTargetインテントが発火するようになっています。

## Twilio側で準備しておくもの

今回はTwilio Studioを呼び出すようにしますので、予め電話をかけたり、メッセージを送信するStudioフローを作っておく必要があります。
Studioフローに関しては、以下の記事を参考にしてください。

[Twilio FunctionからStudio Flowを呼び出す](https://qiita.com/mobilebiz/items/c2c766fb9c3da9693ca0)

Twilio側で準備しておく以下の情報を表にまとめました。

|項目|説明|
|:--|:--|
|Flow SID|Studioの管理画面から確認することができます。呼び出したいフローのSID（FWから始まる文字列になります）。|
|発信者番号|Twilioで購入した番号です。電話をかける場合は050番号、SMSを送信する場合はUS番号になります。|

## Twilio Functionsの設定

今回はCEKのSDK（Node.js）を利用するので、その準備諸々をしておきます。

- Twilioの管理コンソールにログインします。
- **Runtime**の**Functions**の中の**Configure**を選択します。
- **Enable ACCOUNT_SID and AUTH_TOKEN**のチェックを入れます。
- **Environmental Variables**の赤いプラスアイコンを3回押して、KEYとVALUEを以下のように設定します。大文字小文字に注意してください。

|KEY|VALUE|
|:--|:--|
|APPLICATION_ID|先程控えておいた、ClovaスキルのExtension ID|
|FROM_NUMBER|Twilioで購入した番号をE.164形式（+81〜とか、+1〜とか）|
|FLOW_SID|作成したStudioのFlow SID（FWから始まる文字列）|

<img width="807" alt="スクリーンショット 2018-09-13 14.44.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/4ecf441f-cf36-146d-bbb1-acce4fa4b4bf.png">

- **Dependencies**の＋アイコンを一回押して、以下の値を設定します。

|NAME|VERSION|
|:--|:--|
|@line/clova-cek-sdk-nodejs|1.1.0|

- また、Twilioのバージョンを、3.19.2にあげます。

<img width="798" alt="スクリーンショット 2018-09-13 14.45.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/57f7b8bc-6a2b-fa6d-288d-985bec282b47.png">

- **Save**ボタンを押して、設定情報を保存します。

## Twilio Functionの作成

- 管理コンソールの**Functions**から**Manage**を選択します。
- 赤いプラスアイコンをクリックするか、「Create a new function」を選択して、新しいFunctionを作成します。
- **New Function**ダイアログでは、**Blank**を選択し、**Create**ボタンを押します。
- **FUNCTION NAME**欄に「executeFlow」、**PATH**欄に「/executeFlow」と入力します。
- **CODE**欄に予め書かれているコードをすべて削除し、以下のコードを貼り付けます。

```javascript
const clova = require('@line/clova-cek-sdk-nodejs');
let twilioContext;

const clovaSkillHandler = clova.Client
    .configureSkill() 
    .onLaunchRequest(responseHelper => {
        responseHelper.setSimpleSpeech({
            lang: 'ja',
            type: 'PlainText',
            value: 'だれだれさんに電話してと言ってみてください',
        });
    })
    .onIntentRequest(async responseHelper => {
        const intent = responseHelper.getIntentName();
        console.log(`intent: ${intent}`);
        const sessionId = responseHelper.getSessionId();

        switch (intent) {
            case 'CallToTarget':  // カスタムインテント名に合わせます
                console.log(`Who: ${responseHelper.getSlot('Who')}`);  // スロット名に合わせます
                const TO = '+8190XXXXXXXX';     // 発信したい相手先の番号を指定します
                const MESSAGE = 'こんにちは！私はクローバです。';   // 相手が応答したときに流すメッセージを指定します
                await executeFlow(TO, MESSAGE)
                .then(() => {
                    responseHelper.setSimpleSpeech({
                        lang: 'ja',
                        type: 'PlainText',
                        value: `${responseHelper.getSlot('Who')}に電話をかけています`,
                    });
                })
                .catch((err) => {
                    console.log(err);
                    responseHelper.setSimpleSpeech({
                        lang: 'ja',
                        type: 'PlainText',
                        value: `電話発信に失敗しました`,
                    });
                });
                break;
            default:
                responseHelper.setSimpleSpeech(
                    clova.SpeechBuilder.createSpeechText('よくわかりませんでした')
                );
                break;
        }
    })
    .onSessionEndedRequest(responseHelper => {
        const sessionId = responseHelper.getSessionId();

    });

// Studio フローの呼び出し
const executeFlow = (to, message) => {
    return new Promise((resolve, reject) => {
        const twilioClient = twilioContext.getTwilioClient();
        twilioClient.studio.flows(twilioContext.FLOW_SID)
        .executions
        .create({
            from: twilioContext.FROM_NUMBER,
            to: to,
            parameters: {
                'message': message    
            }
        })
        .then(execution => {
            console.log(`Flow executed. ${execution.sid}`);
            resolve();
        })
        .catch(err => {
            console.log(`Flow ecexute error: ${err}`);
            reject();
        });
    }); 
}

exports.handler = async function(context, event, callback) {
    twilioContext = context;
    
    let ctx = new clova.Context(event);
    const requestType = ctx.requestObject.request.type;

    const requestHandler = clovaSkillHandler.config.requestHandlers[requestType];
    
    if (requestHandler) {
        await requestHandler.call(ctx, ctx);
        callback(null, JSON.stringify(ctx.responseObject));
    } else {
        callback(new Error(`Unable to find requestHandler for '${requestType}'`));
    }
};
```

※Functionsの画面上では一部にWarningが表示されますが、動作には問題がありません。

- **SAVE**ボタンを押して、コードを保存します。
- しばらくして、デプロイ完了の緑色のバナーが表示されたら、作業はすべて終了です。
- PATH欄の右側にあるコピーアイコンをクリックして、このFunctionのURLをコピーします。

## Clova側の設定

最後にClova側のスキルで、サーバー設定を変更します。

- Clova Developer Centerにログインし、当該スキルの設定画面を開きます。
- サーバー設定タブに移動し、**ExtensionサーバーのURL**欄に、先程作成したFunctionのURLを貼り付けます。
- **保存**ボタンを押します。

以上で設定はすべて終了になります。