CLOVA Chatbot Custom API
    • PDF

    CLOVA Chatbot Custom API

    • PDF

    Article Summary

    Custom Chatbotとの連携のために、作成した Stageの Invoke URLと Secret Keyをコピーして保存します。

    リクエスト

    Customチャネルから CLOVA Chatbotにリクエスト(Request)を送る方法について説明します。

    • Http Method: POST

    • Headers

    namevalue
    Content-Type"application/json;UTF-8"
    X-NCP-CHATBOT_SIGNATUREsignature of request body

    リクエスト Signatureヘッダ

    • Signatureの定義(X-NCP-CHATBOT_SIGNATURE)

      • Algorithm: HmacSHA256

      • SecretKey: CLOVA Chatbotのドメインビルダーで作成した SecretKeyです。

      • Sign content: Request body Stringを意味します。

    • X-NCP-CHATBOT_SIGNATURE: X-NCP-CHATBOT_SIGNATUREとは、チャットボットにリクエストを送るメッセージのことです。

      すなわち、チャットボットにリクエストを送る時、リクエストヘッダに Content-Type、X-NCP-CHATBOT_SIGNATUREが必要

      X-NCP-CHATBOT_SIGNATUREは、Sign content(Request body)を Base64でエンコードして設定します。

    言語別 Signature(Sign content) 作成サンプルコード

    詳細な「X-NCP-CHATBOT_SIGNATURE」設定およびユースケースについて説明します。

    • js code sample
    const HmacSHA256 = require('crypto-js/hmac-sha256');
    const EncBase64 = require('crypto-js/enc-base64');
    signatureHeader = HmacSHA256(requestBodyString, secretKey).toString(EncBase64);
    
    • java code sample
    byte[] secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyBytes, "HmacSHA256");
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(secretKeySpec);
    byte[] signature = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));
    String signatureHeader = Base64.getEncoder().encodeToString(signature);
    

    リクエストボディ

    Open

    • ウェルカムメッセージにレスポンス

    • trigger when open messenger, will response welcome message if have set in "Messenger Connection" -> "Custom"

    区分Json Model - Example
    Open{
    "version": "v2",
    "userId": "U47b00b58c90f8e47428af8b7bddcda3d",
    "userIp": "8.8.8.8",
    "timestamp": 12345678,
    "bubbles": [ {
    "type": "text",
    "data" : { "description" : "postback text of welcome action" } } ],
    "event": "open"
    }

    Openリクエストフィールドに関する詳細説明

    フィールド名データタイプ要否説明
    versionstringfalseバージョンは「v2」を基本とし、値が入力されていない場合は「v1」に設定
    userIdstringtrueボットとチャットするユーザーの固有 IDとして、最大で256文字を超えないこと
    ユーザーごとに固有 userIdの設定が必要
    userIpstringfalseユーザーの IPアドレスであり、必須ではない
    timestamplongtrueタイムスタンプ値(January 1, 1970, 00:00:00 GMT)
    bubblesarraytrueempty array "[]" or only one Text component caused by welcome action
    eventstringtrueイベントの value値を「open」に設定

    Send

    CLOVA Chatbotを介して、ユーザーの質問を送信します。

    sendを呼び出すとき、 useridの値は唯一のキー値であれば、何でも構いません。(例: 会員番号など)

    • userid: 唯一のキー値、 description: ユーザーの質問
    区分Json Model - Example
    Send{
    "version": "v2",
    "userId": "U47b00b58c90f8e47428af8b7bddcda3d",
    "userIp": "8.8.8.8",
    "timestamp": 12345678,
    "bubbles": [ {
    "type": "text",
    "data" : { "description" : "text content which is user input" } } ],
    "event": "send"
    }

    sendリクエストフィールドに関する詳細説明

    フィールド名データタイプ要否説明
    versionstringfalseバージョンは「v2」を基本とし、値が入力されていない場合は「v1」に設定
    userIdstringtrueボットとチャットするユーザーの固有 IDとして、最大で256文字を超えないこと
    ユーザーごとに固有 userIdの設定が必要
    userIpstringfalseユーザーの IPアドレスであり、必須ではない
    timestamplongtrueタイムスタンプ値(January 1, 1970, 00:00:00 GMT)
    bubblesarray[Text]true1つの Text Componentのみサポート
    1つ以上の Text Componentがある場合、最後の Componentをユーザーリクエストに使用
    eventstringtrueイベントの value値を「send」に設定

    getPersistentMenu

    固定メニューリストをインポートする

    if need show persistent menu but local cache not exists, could request PersistentMenu, will response persistentMenu field if fixed menu have set in messengers custom tab.

    固定メニューの表示が必要ですが、ローカルキャッシュが存在しない場合、 PersistentMenuをリクエストでき、固定された場合、 persistMenuフィールドにレスポンスします。メニューがメッセンジャーのユーザー定義タブから設定されました。

    区分Json Model - Example
    getPersistentMenu{
    "version": "v2",
    "userId": "U47b00b58c90f8e47428af8b7bddcda3d",
    "userIp": "8.8.8.8",
    "timestamp": 12345678,
    "bubbles": [],
    "event": "getPersistentMenu"
    }

    getPersistentMenuのリクエストフィールドに関する詳細説明

    フィールド名データタイプ要否説明
    versionstringfalseバージョンは「v2」を基本とし、値が入力されていない場合は「v1」に設定
    userIdstringtrueボットとチャットするユーザーの固有 IDとして、最大で256文字を超えないこと
    ユーザーごとに固有 userIdの設定が必要
    userIpstringfalseユーザーの IPアドレスであり、必須ではない
    timestamplongtrueタイムスタンプ値(January 1, 1970, 00:00:00 GMT)
    bubblesarraytrue空の Array値で設定「 [] 」
    eventstringtrueイベント value値を「getPersistentMenu」に設定

    レスポンス

    レスポンス結果の Status(成功および失敗)

    Success

    • チャットボットでクエリに成功した場合、Http Status Codeが200で返され、レスポンスフィールドは次の通りです。
    区分Json Model - Example
    Success{
    "version": "v2",
    "userId": "U47b00b58c90f8e47428af8b7bddcda3d",
    "sessionId": "34a59946-5dcb-4b72-9b63-a773c659702e",
    "timestamp": 12345678,
    "bubbles": [ // each component is a bubble ],
    "quickButtons": [ // some buttons ],
    "scenario": {
    "name": "analyzedScenarioName",
    "intent": [ // some scenario intent ] },
    "entities": [ {
    "word": "userInputWord",
    "name": "analyzedEntityName" } ],
    "keywords": [ {
    "keyword": "userInputKeyword",
    "group": "analyzedKeywordGroupName",
    "type": "analyzedKeywordType" } ],
    "persistentMenu": { // one template component },
    "event": "send"
    }

    レスポンス結果(成功)フィールドに関する詳細説明

    フィールド名データタイプ要否説明
    versionstringfalseバージョンは「v2」を基本とし、値が入力されていないリクエストは「v1」でレスポンスされる
    userIdstringtrueリクエストの際に設定した userId値と同様
    sessionIdstringfalse現在の session idであり、 CLOVA Chatbotで管理する値
    timestamplongtrueレスポンスタイムスタンプ値(milliseconds , January 1, 1970, 00:00:00 GMT)
    bubblesarray[Component]falseレスポンスコンポーネントの配列であり、各コンポーネントはチャットボットのレスポンス Bubbleとマッチングされる
    quickButtonsarray[Component]falseチャットボットの下部に設定された固定ボタン(quickButton)に関する情報
    scenariojsonObjectfalseユーザーのクエリと一致するシナリオの分析結果であり、シナリオ名および目的(会話タイプ)情報を提供
    entitiesarray[jsonObject]falseユーザーのクエリと一致するエンティティ(entity)の分析結果
    keywordsarray[jsonObject]falseユーザーのチャットにおいて、キーワードと一致する単語キーワードには「exactMatch」または「contain」、「exactMatch」の2つのタイプがある
    「exactMatch」はユーザーの入力がキーワードと完全一致することを意味し、「contain」はユーザーの入力にキーワードが含まれることを意味する
    persistentMenuTemplateComponentfalsepersistent menuを設定 PersistentMenu
    eventstringtrue固定されたレスポンス「send」
    slotNormalizerarray[jsonObject]falseユーザーのクエリとマッチしたシステムエンティティ Date、Time、People、Name
    normalizerstringfalseユーザーのクエリとマッチしたシステムエンティティ

    chatbot-03-clip_image002_ko

    Error

    Error

    • チャットボットでクエリに失敗した場合、Http Status Codeが500で返され、レスポンスフィールドは次の通りです。

    500エラーの他、詳細なエラーコードについては、文書の下部で詳しく説明します。

    HttpStatusCodeDescription
    500Internal server error

    Error Response Body:

    {
        "code": "500",
        "message": "Internal server error",
        "timestamp": 12345678
    }
    

    Component

    チャットボット応答のレスポンスコンポーネントは以下の jsonフォーマットに従います。

    {
      "type": "...",
      "title": "optional, short bold text",
      "subTitle": "optional, short gray text",
      "data" : {
        ...
      }
    }
    

    3つのコンポーネントタイプがあります。

    • Basic Component

    • Composite Component

    • Flex Component

    Basic Component

    Basicコンポーネントは Text、Image、Buttonの3つの基本コンポーネントを提供します。

    Text

    チャットボットが応答するテキストタイプのコンポーネントであり、タイトル、サブタイトル、そして詳細な説明と URLを設定ができます。

    • Textコンポーネントの構造

    chatbot-03-clip_image003_ko

    • Textコンポーネント Json
    区分Json Model - Example
    Text{ "type": "text", "title": "optional, short bold text", "subTitle": "optional, short gray text","data" : {"description" : "optional, a long text content","url" : "optional, a hyperlink at the bottom of description","urlAlias" :"optional, hyperlink show this alias","action": {Action Data} }}
    • Textコンポーネントに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruetext
    titlestringfalseshort bold text
    subTitlestringfalseshort gray text
    data.descriptionstringfalsea long text content
    data.urlstringfalsethe hyperlink jump url
    data.urlAliasstringfalsethe hyperlink show text
    data.actionActionfalsethe action of click on text or title

    Image

    Imageを含めるレスポンスタイプのコンポーネントであり、画像とともにタイトル、サブタイトル、そして詳細な説明と URLを設定することができます。

    • Imageコンポーネントの構造

    chatbot-03-clip_image004_ko

    • Imageコンポーネント Json
    区分Json Model - Example
    Image{
    "type": "image",
    "title": "optional, short bold text",
    "subTitle": "optional, short gray text",
    "data" : {
    "imageUrl" : "https://ssl.pstatic.net/CloudFunctions.png",
    "alt" : "optional, short hint show when hover on image",
    "imagePosition" : "top",
    "description" : "optional, details info of image",
    "url" : "optional, a hyperlink at the bottom of description",
    "urlAlias" : "optional, hyperlink show this alias",
    "action": {Action Data}
    }
    • Imageコンポーネントに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtrueimage
    titlestringfalseshort bold text
    subTitlestringfalseshort gray text
    data.imageUrlstringtrueimage url, must be https url
    data.altstringfalseshort hint text show hover on image
    data.imagePositionstringfalsetop / bottom / left / right, default is top
    data.descriptionstringfalsedetails info of image
    data.urlstringfalsethe hyperlink jump url
    data.urlAliasstringfalsethe hyperlink show text
    data.actionActionfalsethe action of click on image or title

    Button

    Imageを含めるレスポンスタイプのコンポーネントであり、画像とともにタイトル、サブタイトル、そして詳細な説明と URLを設定することができます。

    • Buttonコンポーネントの構造

    chatbot-03-clip_image005_ko

    • Buttonコンポーネント Json
    区分Json Model - Example
    Button (Basic Button){
    "type": "button",
    "title": "optional, text show on button",
    "subTitle": "optional, short gray text",
    "data" : {
    "type": "basic",
    "iconUrl" : "https://ssl.pstatic.net/CloudFunctions.png",
    "action": {Action Data}
    }
    }
    Button (Image Button){
    "type": "button",
    "title": "optional, text show on button",
    "subTitle": "optional, short gray text",
    "data" : {
    "type": "imageButton",
    "iconUrl" : "https://ssl.pstatic.net/CloudFunctions.png",
    "action": {Action Data}
    }
    }
    • Buttonコンポーネントに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruebutton
    titlestringfalsetext show on button
    subTitlestringfalseshort gray text
    data.typestringtruebasic or imageButton
    data.iconUrlstringfalsebutton icon url, must be https url
    data.actionActiontruethe action of click on button

    Composite Component

    composite componentは Templateと Carouselコンポーネントで構成されます。

    それぞれの特性に合わせてチャットボットの応答をチャットボットビルダーで設定でき、チャットボットビルダーで設定したレスポンス結果は、以下の json形式で返されます。

    Template Component

    テンプレートは基本的な要素で構成されます。テンプレートは、cover、contentTable、footTableの3つに分けられます。coverには主な内容が含まれます。contentTable、footTableはテーブルレイアウトです。

    • Template Componentの構造

    chatbot-03-clip_image006_ko

    • Template Componentの Json
    区分Json Model - Example
    Template{
    "type": "template",
    "title": "optional, short bold text",
    "subTitle": "optional, short gray text",
    "data":{
    "cover":{ // any basic component },
    "contentTableShowRows": 3, // if row count more than 3, should be fold "contentBackgroundImage":"https://ssl.pstatic.net/CloudFunctions.png", // optinal
    "contentTable":[ // table layout [ // first row {
    "colSpan": 1,
    "rowSpan": 2,
    "data":{ // any basic component type } },
    // other cells in first row ], // another rows ],
    "footTableShowRows":3, // if row count more than 3, should be fold
    "footBackgroundImage":"https://ssl.pstatic.net/CloudFunctions.png", // optinal
    "footTable":[ // table layout same as contentTable ] }
    }
    • Template Componentに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruetemplate
    titlestringfalseshort bold text
    subTitlestringfalseshort gray text
    data.coverBasic ComponentfalseText / Image / Button
    data.contentTableShowRowsintegerfalse最大行数を設定
    行数が最大値を超える場合、行数の最大値を表示して折りたたみ、展開ボタンを追加する
    設定しない場合、すべての行が表示される
    data.contentBackgroundImagestringfalseコンテンツテーブルに背景イメージを表示
    data.contentTablearray[][Cell]falseテーブルレイアウトは2次元のセル配列であり、セルデータは基本的な構成要素
    data.footTableShowRowsintegerfalse最大行数を設定
    行数が最大値を超える場合、行数の最大値を表示して折りたたみ、展開ボタンを追加する
    設定しない場合、すべての行が表示される
    data.footBackgroundImagestringfalsefootテーブルに背景イメージを表示
    data.footTablearray[][Cell]falsecontentTableと同じだが、contentTableがサポートできない場合を除き、常に存在しない
    • table layoutに関する詳細説明
    フィールド名データタイプ要否説明
    rowSpanintegertruespan row count
    colSpanintegertruespan column count
    dataBasic ComponenttrueText / Image / Button

    Carousel Component

    カルーセルレスポンスを設定できます。

    chatbot-03-clip_image007_ko

    • Carousel Componentの Json
    区分Json Model - Example
    Carousel{
    "type": "carousel",
    "title": "optional, short bold text",
    "subTitle": "optional, short gray text",
    "data" : {
    "cards": [ { // any component except carousel self and flex }, // more components
    ] }
    }
    • Carousel Componentに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruecarousel
    titlestringfalseshort bold text
    subTitlestringfalseshort gray text
    data.cardsarray[Component]trueコンポーネント配列
    Carouselと flexを除いたコンポーネント

    Flex Component

    すべての json形式のオブジェクトをサポートします。必要な独自の JSONスペックを定義します。

    例: FlexMessageContainerObject jsonを使用します。

    • Json in Flex
    assortmentJson Model - Example
    Flex{
    "type": "flex",
    "title": "not used",
    "subTitle": "required, alternative text",
    "data" : { // any json object }
    }
    • Flex Component Details
    Field namesData typeRequiredExplanation
    typestringtrueflex
    titlestringtruealternative text, show in chat list and push alert
    subTitlestringfalsenot used
    datajson objecttrueany json object. Example: line flex messsage could copy json from Flex Message Simulator

    Special Messenger Component

    1. LineFlex

      Line Messengerでサポートする LineFlex機能は共通 Flex Component形式に変更されており、スペックは同じです。

    2. LineSticker

      Line Messengerでサポートする LineSticker機能では sticker list形式に従ってステッカメッセージを設定します。

      • LineStickerの Json
    区分Json Model - Example
    LineSticker{
    "type": "line_sticker",
    "data" : {
    "packageId": "packageId of LINE",
    "stickerId": "stickerId of LINE"
    }
    }
    • LineStickerコンポーネントに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtrueline_sticker
    data.packageIdstringtruesticker's packageId of LINE, refer to sticker list
    data.stickerIdstringtruesticker's stickerId of LINE, refer to sticker list
    1. LineWorksSticker

      LINE WORKSでサポートする LineWorksSticker機能では、sticker list形式に従ってステッカメッセージを設定します。

      • LineWorksStickerの Json
    区分Json Model - Example
    LineWorksSticker{
    "type": "lineworks_sticker",
    "data" : {
    "packageId": "packageId of LINEWORKS",
    "stickerId": "stickerId of LINEWORKS"
    }
    }
    • LineWorksStickerコンポーネントに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruelineworks_sticker
    data.packageIdstringtruesticker's packageId of LINEWORKS, refer to sticker list
    data.stickerIdstringtruesticker's stickerId of LINEWORKS, refer to sticker list

    Action

    Actionは、すべての構成要素に対する共通データです。 Componentをクリックしたとき、実行するタスクを定義します。

    • Postback

      コンポーネントをクリックすると、postbackTextをチャットボットにポストバックし、そのポストバックがユーザーチャットとして表示します。

      • Postbackの Json
    区分Json Model - Example
    Postback{ "type": "line_sticker", "data" : { "packageId": "packageId of LINE", "stickerId": "stickerId of LINE" } }
    • Postbackコンポーネントに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruepostback
    data.postbackstringtruetext show as user chat, if send this field to chatbot, not affect previous features, but will not support some new features
    data.postbackFullstringtruepostback full content send to chatbot
    • Utterance

      コンポーネントをクリックすると、テキストをチャットボットにポストバック(Postback)し、そのテキストをユーザーチャットに表示します。

      • Utteranceの Json
    区分Json Model - Example
    Utterance{
    "type": "utterance",
    "data" : {
    "utteranceId" : 1,
    "text" : "text show in chat window",
    "postback" : "postback text"
    }
    }
    • Utterance Componentに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtrueutterance
    data.utteranceIdstringtrue
    data.textstringtruetext show in chat window as user input
    data.postbackstringtruepostback text send to chatbot
    • Link

      コンポーネントをクリックすると、URLに移動します。

      • Linkの Json
    区分Json Model - Example
    Link{
    "type": "link",
    "data" : {
    "url" : "http://www.ncloud.com",
    "mobileUrl" : "http://m.ncloud.com"
    }
    }
    • Link Componentに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruepostback
    data.urlstringtrueopen url
    data.mobileUrlstringfalseurl for mobile device
    • Phone

      コンポーネントをクリックすると、ダイヤルページに移動します。モバイルでのみサポートします。

      • Phone Componentの Json
    区分Json Model - Example
    Phone{
    "type": "phone",
    "data" : { "number" : "400-1111-1111", "name" : "Customer service" }
    }
    • Phone Componentに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruephone
    data.numberstringtruephone number
    data.namestringfalsecontact name
    • Welcome

      コンポーネントをクリックすると、open eventが送信されます。

      • Welcome Componentの Json
    区分Json Model - Example
    Welcome{ "type": "welcome", "data" : { "postback" : "postback text, optional" } }
    • Welcome Componentに関する詳細説明
    フィールド名データタイプ要否説明
    typestringtruewelcome
    data.postbackstringfalseポストバックテキストが open eventからチャットボットに送信

    Quick Button

    チャット画面の下にあるグループ固定ボタンです。

    Persistent Menu

    ユーザーがチャットバーのメニューボタンをタッチすると、固定メニューが表示されます。常にウェルカムレスポンスに含まれます。utilsは変更しないこと。他のレスポンスには permanentMenuコンテンツが含まれています。

    chatbot-03-clip_image023_ko

    Persistent menu is a Template Component , with these definition:

    • title will show on the chat bar.

    • no cover, cover should be discard.

    • no foot table, fields relate to foot area should be discard.

    • contentBackgroundImage is the background, if the components in the contentTable have image, will cover the background.

    エラーコード

    エラーレスポンスについて説明します。

    HttpStatusCodeDescription
    500Internal server error
    • Error Response Body:
     {
        "code": "1001",
        "message": "domain code test not found",
        "timestamp": 12345678
      }
    
    • Body Introduce
    FieldTypeMust ExistsDescription
    codestringtrueerror code
    eventstringtruefixed string value "send"
    timestamplongtrueresponse time milliseconds since January 1, 1970, 00:00:00 GMT

    エラーコード

    ErrorCodeDescription
    4000request param invalid
    4010Unauthorized
    4030Forbidden to access
    4031Signature validate failed
    4032timestamp exceeded time window(10000ms)
    1000version not support
    1001Not found domain code
    1002check url param is invalid
    5000Unknown service error
    5010Current protocol version not support this reply structure
    5020Calls to this api have exceeded the rate limit

    APIのユースケース

    以下は、言語別 CLOVA Custom API実装のユースケースです

    • JAVA - Chatbot Custom API call source
    package com.ncp.ai.demo.process;
    
    import android.media.MediaPlayer;
    import android.os.Environment;
    import android.util.Base64;
    
    import java.io.BufferedReader;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLEncoder;
    import java.security.Timestamp;
    import java.util.Date;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import android.util.Base64;
    
    import org.json.JSONArray;
    import org.json.JSONObject;
    
    public class ChatbotProc {
    
      public static String main(String voiceMessage, String apiURL, String secretKey) {
    
    
            String chatbotMessage = "";
    
            try {
                //String apiURL = "https://ex9av8bv0e.apigw.ntruss.com/custom_chatbot/prod/";
    
                URL url = new URL(apiURL);
    
                String message = getReqMessage(voiceMessage);
                System.out.println("##" + message);
    
                String encodeBase64String = makeSignature(message, secretKey);
    
                HttpURLConnection con = (HttpURLConnection)url.openConnection();
                con.setRequestMethod("POST");
                con.setRequestProperty("Content-Type", "application/json;UTF-8");
                con.setRequestProperty("X-NCP-CHATBOT_SIGNATURE", encodeBase64String);
    
                // post request
                con.setDoOutput(true);
                DataOutputStream wr = new DataOutputStream(con.getOutputStream());
                wr.write(message.getBytes("UTF-8"));
                wr.flush();
                wr.close();
                int responseCode = con.getResponseCode();
    
                BufferedReader br;
    
                if(responseCode==200) { // Normal call
                    System.out.println(con.getResponseMessage());
    
                    BufferedReader in = new BufferedReader(
                            new InputStreamReader(
                                    con.getInputStream()));
                    String decodedString;
                    while ((decodedString = in.readLine()) != null) {
                        chatbotMessage = decodedString;
                    }
                    //chatbotMessage = decodedString;
                    in.close();
    
                } else {  // Error occurred
                    chatbotMessage = con.getResponseMessage();
                }
            } catch (Exception e) {
                System.out.println(e);
            }
    
            return chatbotMessage;
        }
    
        public static String makeSignature(String message, String secretKey) {
    
            String encodeBase64String = "";
    
            try {
                byte[] secrete_key_bytes = secretKey.getBytes("UTF-8");
    
                SecretKeySpec signingKey = new SecretKeySpec(secrete_key_bytes, "HmacSHA256");
                Mac mac = Mac.getInstance("HmacSHA256");
                mac.init(signingKey);
    
                byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
                encodeBase64String = Base64.encodeToString(rawHmac, Base64.NO_WRAP);
    
                return encodeBase64String;
    
            } catch (Exception e){
                System.out.println(e);
            }
    
            return encodeBase64String;
    
        }
    
        public static String getReqMessage(String voiceMessage) {
    
            String requestBody = "";
    
            try {
    
                JSONObject obj = new JSONObject();
    
                long timestamp = new Date().getTime();
    
                System.out.println("##"+timestamp);
    
                obj.put("version", "v2");
                obj.put("userId", "U47b00b58c90f8e47428af8b7bddc1231heo2");
    //=> userId is a unique code for each chat user, not a fixed value, recommend use UUID. use different id for each user could help you to split chat history for users.
    
                obj.put("timestamp", timestamp);
    
                JSONObject bubbles_obj = new JSONObject();
    
                bubbles_obj.put("type", "text");
    
                JSONObject data_obj = new JSONObject();
                data_obj.put("description", voiceMessage);
    
                bubbles_obj.put("type", "text");
                bubbles_obj.put("data", data_obj);
    
                JSONArray bubbles_array = new JSONArray();
                bubbles_array.put(bubbles_obj);
    
                obj.put("bubbles", bubbles_array);
                obj.put("event", "send");
    
                requestBody = obj.toString();
    
            } catch (Exception e){
                System.out.println("## Exception : " + e);
            }
    
            return requestBody;
    
        }
    }
    
    • Example of calling ChatbotProc.java above in UI stage
    package com.example.user.ncpaidemo;
    
    import android.content.Context;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.Message;
    import android.speech.tts.Voice;
    import android.util.Log;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import com.naver.speech.clientapi.SpeechRecognitionResult;
    import com.ncp.ai.demo.process.ChatbotProc;
    import com.ncp.ai.demo.process.CsrProc;
    import com.ncp.ai.demo.process.CssProc;
    import com.ncp.ai.utils.AudioWriterPCM;
    
    import org.json.JSONArray;
    import org.json.JSONObject;
    
    import java.lang.ref.WeakReference;
    import java.util.List;
    
    public class VoiceChatbotActivity extends BaseActivity {
    
        private static final String TAG = VoiceChatbotActivity.class.getSimpleName();
        private RecognitionHandler handler;
        private CsrProc naverRecognizer;
        private TextView txtResult;
        private Button btnStart;
        private String mResult;
        private AudioWriterPCM writer;
        private String clientId;
        private String clientSecret;
        // Handle speech recognition Messages.
        private void handleMessage(Message msg) {
            switch (msg.what) {
                case R.id.clientReady: // Voice recognition ready
                    txtResult.setText("Connected");
                    writer = new AudioWriterPCM(Environment.getExternalStorageDirectory().getAbsolutePath() + "/NaverSpeechTest");
                    writer.open("Test");
                    break;
                case R.id.audioRecording:
                    writer.write((short[]) msg.obj);
                    break;
                case R.id.partialResult:
                    mResult = (String) (msg.obj);
                    mResult += mResult;
                    txtResult.setText(mResult);
                    break;
                case R.id.finalResult: // Final recognition result
                    SpeechRecognitionResult speechRecognitionResult1 = (SpeechRecognitionResult) msg.obj;
                    List<String> results1 = speechRecognitionResult1.getResults();
                    StringBuilder strBuf1 = new StringBuilder();
                    for(String result : results1) {
                        strBuf1.append(result);
                        //strBuf.append("\n");
                        break;
                    }
                    mResult = strBuf1.toString();
                    txtResult.setText(mResult);
    
                    requestChatbot();
    
                    break;
                case R.id.recognitionError:
                    if (writer != null) {
                        writer.close();
                    }
                    mResult = "Error code : " + msg.obj.toString();
                    txtResult.setText(mResult);
                    btnStart.setText(R.string.str_start);
                    btnStart.setEnabled(true);
                    break;
                case R.id.clientInactive:
                    if (writer != null) {
                        writer.close();
                    }
                    btnStart.setText(R.string.str_start);
                    btnStart.setEnabled(true);
                    break;
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_voice_chatbot);
            SharedPreferences sharedPref = getSharedPreferences("PREF", Context.MODE_PRIVATE);
    
            clientId = sharedPref.getString("application_client_id", "");
            clientSecret = sharedPref.getString("application_client_secret", "");
    
            txtResult = (TextView) findViewById(R.id.textViewVoiceChatbotResult);
            btnStart = (Button) findViewById(R.id.btn_voice_chatbot1);
            handler = new RecognitionHandler(this);
            //naverRecognizer = new CsrProc(this, handler, clientId);
            naverRecognizer = CsrProc.getCsrProc(this, clientId);
            naverRecognizer.setHandler(handler);
            btnStart.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    if (!naverRecognizer.getSpeechRecognizer().isRunning()) {
    
                        mResult = "";
                        txtResult.setText("Connecting...");
                        btnStart.setText(R.string.str_stop);
                        naverRecognizer.recognize();
                    } else {
                        Log.d(TAG, "stop and wait Final Result");
                        btnStart.setEnabled(false);
                        naverRecognizer.getSpeechRecognizer().stop();
                    }
                }
            });
    
            Button voiceChatbotReplay;
    
            voiceChatbotReplay = (Button) findViewById(R.id.btn_voice_chatbot_replay);
            voiceChatbotReplay.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
    
                    TextView txtResult = (TextView) findViewById(R.id.text_voice_chatbot_replay);
                    CSSExecute(txtResult.getText().toString());
    
                }
            });
        }
    
        private void requestChatbot() {
    
            SharedPreferences sharedPref = getSharedPreferences("PREF", Context.MODE_PRIVATE);
    
            String chatbotApiGwUrl = sharedPref.getString("chatbot_api_gw_url", "");
            String chatbotSecretKey = sharedPref.getString("chatbot_secret_key", "");
    
            TextView csrSourceText = (TextView)findViewById(R.id.textViewVoiceChatbotResult);
            String text = csrSourceText.getText().toString();
    
            VoiceChatbotActivity.VoiceChatbotTask task = new VoiceChatbotActivity.VoiceChatbotTask();
            task.execute(text, chatbotApiGwUrl, chatbotSecretKey);
        }
    
        @Override
        protected void onStart() {
            super.onStart(); // Voice recognition server initialization is here
            naverRecognizer.getSpeechRecognizer().initialize();
        }
        @Override
        protected void onResume() {
            super.onResume();
            mResult = "";
            txtResult.setText("");
            btnStart.setText(R.string.str_start);
            btnStart.setEnabled(true);
        }
    //    @Override
    //    protected void onStop() {
    //        System.out.println("voice chatbot End!!!");
    //        super.onStop(); // terminate voice recognition server
    //        naverRecognizer.getSpeechRecognizer().release();
    //    }
        // Declare handler for handling SpeechRecognizer thread's Messages.
        static class RecognitionHandler extends Handler {
            private final WeakReference<VoiceChatbotActivity> mActivity;
            RecognitionHandler(VoiceChatbotActivity activity) {
                mActivity = new WeakReference<VoiceChatbotActivity>(activity);
            }
            @Override
            public void handleMessage(Message msg) {
                VoiceChatbotActivity activity = mActivity.get();
                if (activity != null) {
                    activity.handleMessage(msg);
                }
            }
        }
    
        public class VoiceChatbotTask extends AsyncTask<String, String, String> {
    
            @Override
            public String doInBackground(String... strings) {
    
                return ChatbotProc.main(strings[0], strings[1], strings[2]);
            }
    
            @Override
            protected void onPostExecute(String result) {
    
                ReturnThreadResult(result);
            }
        }
    
        public String ReturnThreadResult(String result) {
    
            //{"version":"v2","userId":"U47b00b58c90f8e47428af8b7bddc1231heo2","sessionId":"617666","timestamp":1546593912020,
            // "bubbles":[{"type":"template","data":{"cover":{"type":"text","data":{"description":"b"}},"contentTable":[[{"rowSpan":1,"colSpan":1,"data":{"type":"button","title":"b","data":{"type":"basic","action":{"type":"link","data":{"url":"https://www.ncloud.com/product"}}}}}],[{"rowSpan":1,"colSpan":1,"data":{"type":"button","title": "b","data":{"type":"basic","action":{"type":"link","data":{"url":"https://www.ncloud.com/product"}}}}}]]}}],"event":"send"}
    
            //{"version":"v2","userId":"U47b00b58c90f8e47428af8b7bddc1231heo2","sessionId":"641799","timestamp":1546777198124,
            // "bubbles":[{"type":"text","data":{"description":"b"}}],"event":"send"}
            String chatbotMessage =  "";
            String rlt = result;
            try{
                JSONObject jsonObject = new JSONObject(rlt);
                JSONArray bubbles = jsonObject.getJSONArray("bubbles");
    
                for (int i =0; i < bubbles.length(); i++){
    
                    JSONObject bubble = bubbles.getJSONObject(i);
    
                    String chatType = bubble.getString("type");
    
                    if (chatType.equals("text")){
    
                        chatbotMessage = bubble.getJSONObject("data").getString("description");
    
                    }else if (chatType.equals("template")) {
    
                        chatbotMessage = bubble.getJSONObject("data").getJSONObject("cover").getJSONObject("data").getString("description");
    
                    }else {
                        chatbotMessage = "";
                    }
    
                    TextView txtResult = (TextView) findViewById(R.id.text_voice_chatbot_replay);
                    txtResult.setText(chatbotMessage);
    
                    break;
                }
    
            } catch (Exception e) {
                System.out.println(e);
            }
    
            CSSExecute(chatbotMessage);
    
            return chatbotMessage;
        }
    
        private void CSSExecute(String message) {
    
            VoiceChatbotActivity.NaverTTSTask tts = new VoiceChatbotActivity.NaverTTSTask();
            tts.execute(message, "mijin", clientId, clientSecret);
        }
    
        public class NaverTTSTask extends AsyncTask<String, String, String> {
    
            @Override
            public String doInBackground(String... strings) {
                System.out.println(strings[1]);
                CssProc.main(strings[0], strings[1], strings[2], strings[3]);
                return null;
            }
        }
    }
    
    
    • Python - CLOVA Chatbot Custom API v2
    import hashlib
    import hmac
    import base64
    import time
    import requests
    import json
    
    
    class ChatbotMessageSender:
    
        # chatbot api gateway url
        ep_path = ''
        # chatbot custom secret key
        secret_key = ''
    
        def req_message_send(self):
    
            timestamp = self.get_timestamp()
            request_body = {
                'version': 'v2',
                'userId': 'U47b00b58c90f8e47428af8b7bddcda3d1111111',
                'timestamp': timestamp,
                'bubbles': [
                    {
                        'type': 'text',
                        'data': {
                            'description': 'About Me'
                        }
                    }
                ],
                'event': 'send'
            }
    
            ## Request body
            encode_request_body = json.dumps(request_body).encode('UTF-8')
    
            ## make signature
            signature = self.make_signature(self.secret_key, encode_request_body)
    
            ## headers
            custom_headers = {
                'Content-Type': 'application/json;UTF-8',
                'X-NCP-CHATBOT_SIGNATURE': signature
            }
    
            print("## Timestamp : ", timestamp)
            print("## Signature : ", signature)
            print("## headers ", custom_headers)
            print("## Request Body : ", encode_request_body)
    
            ## POST Request
            response = requests.post(headers=custom_headers, url=self.ep_path, data=encode_request_body)
    
            return response
    
        @staticmethod
        def get_timestamp():
            timestamp = int(time.time() * 1000)
            return timestamp
    
        @staticmethod
        def make_signature(secret_key, request_body):
    
            secret_key_bytes = bytes(secret_key, 'UTF-8')
    
            signing_key = base64.b64encode(hmac.new(secret_key_bytes, request_body, digestmod=hashlib.sha256).digest())
    
            return signing_key
    
    
    if __name__ == '__main__':
    
        res = ChatbotMessageSender().req_message_send()
    
        print(res.status_code)
        if(res.status_code == 200):
            print(res.text)
            #print(res.read().decode("UTF-8"))
    
    • php
    
    function makeSignature($secretKey, $requestBody) {
      $signautue = base64_encode(hash_hmac('sha256', $requestBody, $secretKey,true));
      //echo "this is signiture : ".$signautue."\n";
      return $signautue;
    }
    
    
    try {
      // User IP
      if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) {
          $clientIpAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
      } else {
          $clientIpAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
      }
    
    
      $timestamp = "";
      $microtime = "";
      list($microtime,$timestamp) = explode(' ',microtime());
      $timestamp = $timestamp.substr($microtime, 2, 3);
    
      $url = "https://xi3mfpym2x.apigw.ntruss.com/send/beta/";
    
      $requestBody=  '{
        "version": "v2",
        "userId": "U47b00b58c90f8e47428af8b7bddcda3d231",
        "userIp": "'.$clientIpAddress.'",
        "timestamp": '.$timestamp.',
        "bubbles": [
          {
            "type": "text",
            "data" : {
              "description" : "postback text of welcome action"
            }
          }
        ],
        "event": "open"
      }';
    
      $secretKey = "YnVFTWZDemt3bUZhaEJlalN1Z3ZNY2pzeVp0aVRjd04=12";
      $signautue = makeSignature($secretKey,$requestBody);
    
      $is_post = true;
    
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
      curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody);
      $headers = array();
      $headers[] = "Content-Type:application/json; charset=utf-8";
      $headers[] = "X-NCP-CHATBOT_SIGNATURE: " .$signautue;
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    
      $response = curl_exec($ch);
      $err = curl_error($ch);
      curl_close ($ch);
      if ($err) {
        echo "cURL Error #:" . $err;
      } else {
        echo $response;
      }
    
    } catch(Exception $E) {
        echo "Response: ". $E->lastResponse . "\n";
    }
    ?>
    
    • swift
    import UIKit
    import CommonCrypto
    
    
    // chatbot custom secret key
    let secret_key = "";
    
    // chatbot api gateway url
    let invoke_url = "";
    
    class ViewController: UIViewController, URLSessionDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            demo()
        }
    
        func demo() {
            let timeInterval: TimeInterval = NSDate().timeIntervalSince1970
            let millisecond = CLongLong(round(timeInterval*1000))
            
            let body = ["bubbles":[["data": ["description":"test"],
                                    "type":"text"]],
                        "event":"send",
                        "timestamp":millisecond,
                        "userId":"test",
                        "version":"v2"] as NSDictionary
           
            do {
                let jsonDataFromBody = try JSONSerialization.data(withJSONObject: body, options: JSONSerialization.WritingOptions.prettyPrinted)
                let jsonBytes = [CUnsignedChar](jsonDataFromBody)
                let jsonBytesPointer = UnsafePointer<CUnsignedChar>(jsonBytes) //UnsafeRawPointer
                
                let cKey = secret_key.cString(using: String.Encoding.utf8)
                let digestLen = Int(CC_SHA256_DIGEST_LENGTH)
                let sha256 = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
                CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), cKey, secret_key.lengthOfBytes(using: String.Encoding.utf8), jsonBytesPointer, jsonBytes.count, sha256)
                
                let base64_sha256 = Data.init(bytes: sha256, count: Int(CC_SHA256_DIGEST_LENGTH)).base64EncodedString()
                
                httpsRequest("X-NCP-CHATBOT_SIGNATURE", base64_sha256, body: jsonDataFromBody)
                sha256.deallocate()
                
            } catch {
                print(error)
            }
            
        }
        
        func httpsRequest(_ signHeaderKey:String, _ signature:String, body:Data) {
            let url = URL.init(string: invoke_url)!
            var request = URLRequest.init(url: url)
            request.addValue(signature, forHTTPHeaderField: signHeaderKey)
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpMethod = "POST"
            request.httpBody = body
         
            let session = URLSession.shared
            let task = session.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
                print(String.init(data:data!, encoding: .utf8)!)
            }
            task.resume()
        }
        
        func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            if challenge.protectionSpace.authenticationMethod != NSURLAuthenticationMethodServerTrust {
                return
            }
            let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
            completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
        }
    
    }
    
    • objective-c
    #import "ViewController.h"
    #import <UIKit/UIKit.h>
    #import <CommonCrypto/CommonHMAC.h>
    
    // chatbot custom secret key
    static NSString *secret_key = @"";
    
    // chatbot api gateway url
    static NSString *invoke_url = @"";
    
    @interface ViewController () <NSURLSessionDelegate>
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self demo];
    }
    
    - (void)demo {
        NSTimeInterval time = [[NSDate date] timeIntervalSince1970]*1000;
        long long millisecond = [[NSNumber numberWithDouble:time] longLongValue];
        NSDictionary *body = @{@"bubbles":@[@{@"data":@{@"description":@"test"},
                                              @"type":@"text"}],
                               @"event":@"send",
                               @"timestamp":millisecond,
                               @"userId":@"U47b00b58c90f8e47428af8b7bddcda3d1111111",
                               @"version":@"v2"
                               };
        NSError *error;
    
        NSData *jsondataFrombody = [NSJSONSerialization dataWithJSONObject:body
                                                               options:0
                                                                 error:&error];
        
        const char *cKey = [secret_key cStringUsingEncoding:NSUTF8StringEncoding];
        NSMutableData* sha256 = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
        CCHmac(kCCHmacAlgSHA256, cKey, secret_key.length, jsondataFrombody.bytes, jsondataFrombody.length, sha256.mutableBytes);
        
        NSString *base64_sha256 = [sha256 base64EncodedStringWithOptions:0];
        
        [self httpsRequest:@"X-NCP-CHATBOT_SIGNATURE" signature:base64_sha256 bodyData:jsondataFrombody];
    }
    
    - (void)httpsRequest:(NSString *)signHeaderKey signature:(NSString *)sign bodyData:(NSData *)bodyData  {
        NSURL *url = [NSURL URLWithString:invoke_url];
        NSMutableURLRequest *mRequest = [NSMutableURLRequest requestWithURL:url];
        [mRequest setValue:sign forHTTPHeaderField:signHeaderKey];
        [mRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        mRequest.HTTPMethod = @"POST";
        mRequest.HTTPBody = bodyData;
        
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                              delegate:self
                                                         delegateQueue:[NSOperationQueue mainQueue]];//replace delegateQueue with your workingQueue
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:mRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"response: %@", [response description]);
            NSLog(@"response data:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        }];
        [dataTask resume];
    
    }
    
    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
        if (![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) {
            return;
        }
        NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    
    }
    
    @end
    

    この記事は役に立ちましたか?

    Changing your password will log you out immediately. Use the new password to log back in.
    First name must have atleast 2 characters. Numbers and special characters are not allowed.
    Last name must have atleast 1 characters. Numbers and special characters are not allowed.
    Enter a valid email
    Enter a valid password
    Your profile has been successfully updated.