- 印刷する
- PDF
CLOVA Chatbot Custom API
- 印刷する
- PDF
Custom Chatbotとの連携のために、作成した Stageの Invoke URLと Secret Keyをコピーして保存します。
リクエスト
Customチャネルから CLOVA Chatbotにリクエスト(Request)を送る方法について説明します。
Http Method: POST
Headers
name | value |
---|---|
Content-Type | "application/json;UTF-8" |
X-NCP-CHATBOT_SIGNATURE | signature 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リクエストフィールドに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
version | string | false | バージョンは「v2」を基本とし、値が入力されていない場合は「v1」に設定 |
userId | string | true | ボットとチャットするユーザーの固有 IDとして、最大で256文字を超えないこと ユーザーごとに固有 userIdの設定が必要 |
userIp | string | false | ユーザーの IPアドレスであり、必須ではない |
timestamp | long | true | タイムスタンプ値(January 1, 1970, 00:00:00 GMT) |
bubbles | array | true | empty array "[]" or only one Text component caused by welcome action |
event | string | true | イベントの 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リクエストフィールドに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
version | string | false | バージョンは「v2」を基本とし、値が入力されていない場合は「v1」に設定 |
userId | string | true | ボットとチャットするユーザーの固有 IDとして、最大で256文字を超えないこと ユーザーごとに固有 userIdの設定が必要 |
userIp | string | false | ユーザーの IPアドレスであり、必須ではない |
timestamp | long | true | タイムスタンプ値(January 1, 1970, 00:00:00 GMT) |
bubbles | array[Text] | true | 1つの Text Componentのみサポート 1つ以上の Text Componentがある場合、最後の Componentをユーザーリクエストに使用 |
event | string | true | イベントの 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のリクエストフィールドに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
version | string | false | バージョンは「v2」を基本とし、値が入力されていない場合は「v1」に設定 |
userId | string | true | ボットとチャットするユーザーの固有 IDとして、最大で256文字を超えないこと ユーザーごとに固有 userIdの設定が必要 |
userIp | string | false | ユーザーの IPアドレスであり、必須ではない |
timestamp | long | true | タイムスタンプ値(January 1, 1970, 00:00:00 GMT) |
bubbles | array | true | 空の Array値で設定「 [] 」 |
event | string | true | イベント 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" } |
レスポンス結果(成功)フィールドに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
version | string | false | バージョンは「v2」を基本とし、値が入力されていないリクエストは「v1」でレスポンスされる |
userId | string | true | リクエストの際に設定した userId値と同様 |
sessionId | string | false | 現在の session idであり、 CLOVA Chatbotで管理する値 |
timestamp | long | true | レスポンスタイムスタンプ値(milliseconds , January 1, 1970, 00:00:00 GMT) |
bubbles | array[Component] | false | レスポンスコンポーネントの配列であり、各コンポーネントはチャットボットのレスポンス Bubbleとマッチングされる |
quickButtons | array[Component] | false | チャットボットの下部に設定された固定ボタン(quickButton)に関する情報 |
scenario | jsonObject | false | ユーザーのクエリと一致するシナリオの分析結果であり、シナリオ名および目的(会話タイプ)情報を提供 |
entities | array[jsonObject] | false | ユーザーのクエリと一致するエンティティ(entity)の分析結果 |
keywords | array[jsonObject] | false | ユーザーのチャットにおいて、キーワードと一致する単語キーワードには「exactMatch」または「contain」、「exactMatch」の2つのタイプがある 「exactMatch」はユーザーの入力がキーワードと完全一致することを意味し、「contain」はユーザーの入力にキーワードが含まれることを意味する |
persistentMenu | TemplateComponent | false | persistent menuを設定 PersistentMenu |
event | string | true | 固定されたレスポンス「send」 |
slotNormalizer | array[jsonObject] | false | ユーザーのクエリとマッチしたシステムエンティティ Date、Time、People、Name |
normalizer | string | false | ユーザーのクエリとマッチしたシステムエンティティ |
Error
Error
- チャットボットでクエリに失敗した場合、Http Status Codeが500で返され、レスポンスフィールドは次の通りです。
500エラーの他、詳細なエラーコードについては、文書の下部で詳しく説明します。
HttpStatusCode | Description |
---|---|
500 | Internal 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コンポーネントの構造
- 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コンポーネントに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | text |
title | string | false | short bold text |
subTitle | string | false | short gray text |
data.description | string | false | a long text content |
data.url | string | false | the hyperlink jump url |
data.urlAlias | string | false | the hyperlink show text |
data.action | Action | false | the action of click on text or title |
Image
Imageを含めるレスポンスタイプのコンポーネントであり、画像とともにタイトル、サブタイトル、そして詳細な説明と URLを設定することができます。
- Imageコンポーネントの構造
- 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コンポーネントに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | image |
title | string | false | short bold text |
subTitle | string | false | short gray text |
data.imageUrl | string | true | image url, must be https url |
data.alt | string | false | short hint text show hover on image |
data.imagePosition | string | false | top / bottom / left / right, default is top |
data.description | string | false | details info of image |
data.url | string | false | the hyperlink jump url |
data.urlAlias | string | false | the hyperlink show text |
data.action | Action | false | the action of click on image or title |
Button
Imageを含めるレスポンスタイプのコンポーネントであり、画像とともにタイトル、サブタイトル、そして詳細な説明と URLを設定することができます。
- Buttonコンポーネントの構造
- 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コンポーネントに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | button |
title | string | false | text show on button |
subTitle | string | false | short gray text |
data.type | string | true | basic or imageButton |
data.iconUrl | string | false | button icon url, must be https url |
data.action | Action | true | the action of click on button |
Composite Component
composite componentは Templateと Carouselコンポーネントで構成されます。
それぞれの特性に合わせてチャットボットの応答をチャットボットビルダーで設定でき、チャットボットビルダーで設定したレスポンス結果は、以下の json形式で返されます。
Template Component
テンプレートは基本的な要素で構成されます。テンプレートは、cover、contentTable、footTableの3つに分けられます。coverには主な内容が含まれます。contentTable、footTableはテーブルレイアウトです。
- Template Componentの構造
- 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に関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | template |
title | string | false | short bold text |
subTitle | string | false | short gray text |
data.cover | Basic Component | false | Text / Image / Button |
data.contentTableShowRows | integer | false | 最大行数を設定 行数が最大値を超える場合、行数の最大値を表示して折りたたみ、展開ボタンを追加する 設定しない場合、すべての行が表示される |
data.contentBackgroundImage | string | false | コンテンツテーブルに背景イメージを表示 |
data.contentTable | array[][Cell] | false | テーブルレイアウトは2次元のセル配列であり、セルデータは基本的な構成要素 |
data.footTableShowRows | integer | false | 最大行数を設定 行数が最大値を超える場合、行数の最大値を表示して折りたたみ、展開ボタンを追加する 設定しない場合、すべての行が表示される |
data.footBackgroundImage | string | false | footテーブルに背景イメージを表示 |
data.footTable | array[][Cell] | false | contentTableと同じだが、contentTableがサポートできない場合を除き、常に存在しない |
- table layoutに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
rowSpan | integer | true | span row count |
colSpan | integer | true | span column count |
data | Basic Component | true | Text / Image / Button |
Carousel Component
カルーセルレスポンスを設定できます。
- 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に関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | carousel |
title | string | false | short bold text |
subTitle | string | false | short gray text |
data.cards | array[Component] | true | コンポーネント配列 Carouselと flexを除いたコンポーネント |
Flex Component
すべての json形式のオブジェクトをサポートします。必要な独自の JSONスペックを定義します。
例: FlexMessageContainerObject jsonを使用します。
- Json in Flex
assortment | Json Model - Example |
---|---|
Flex | { "type": "flex", "title": "not used", "subTitle": "required, alternative text", "data" : { // any json object } } |
- Flex Component Details
Field names | Data type | Required | Explanation |
---|---|---|---|
type | string | true | flex |
title | string | true | alternative text, show in chat list and push alert |
subTitle | string | false | not used |
data | json object | true | any json object. Example: line flex messsage could copy json from Flex Message Simulator |
Special Messenger Component
LineFlex
Line Messengerでサポートする LineFlex機能は共通 Flex Component形式に変更されており、スペックは同じです。
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コンポーネントに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | line_sticker |
data.packageId | string | true | sticker's packageId of LINE, refer to sticker list |
data.stickerId | string | true | sticker's stickerId of LINE, refer to sticker list |
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コンポーネントに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | lineworks_sticker |
data.packageId | string | true | sticker's packageId of LINEWORKS, refer to sticker list |
data.stickerId | string | true | sticker's stickerId of LINEWORKS, refer to sticker list |
Action
Actionは、すべての構成要素に対する共通データです。 Componentをクリックしたとき、実行するタスクを定義します。
区分 | Json Model - Example |
---|---|
Postback | { "type": "line_sticker", "data" : { "packageId": "packageId of LINE", "stickerId": "stickerId of LINE" } } |
- Postbackコンポーネントに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | postback |
data.postback | string | true | text show as user chat, if send this field to chatbot, not affect previous features, but will not support some new features |
data.postbackFull | string | true | postback full content send to chatbot |
区分 | Json Model - Example |
---|---|
Utterance | { "type": "utterance", "data" : { "utteranceId" : 1, "text" : "text show in chat window", "postback" : "postback text" } } |
- Utterance Componentに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | utterance |
data.utteranceId | string | true | |
data.text | string | true | text show in chat window as user input |
data.postback | string | true | postback text send to chatbot |
区分 | Json Model - Example |
---|---|
Link | { "type": "link", "data" : { "url" : "http://www.ncloud.com", "mobileUrl" : "http://m.ncloud.com" } } |
- Link Componentに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | postback |
data.url | string | true | open url |
data.mobileUrl | string | false | url for mobile device |
区分 | Json Model - Example |
---|---|
Phone | { "type": "phone", "data" : { "number" : "400-1111-1111", "name" : "Customer service" } } |
- Phone Componentに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | phone |
data.number | string | true | phone number |
data.name | string | false | contact name |
区分 | Json Model - Example |
---|---|
Welcome | { "type": "welcome", "data" : { "postback" : "postback text, optional" } } |
- Welcome Componentに関する詳細説明
フィールド名 | データタイプ | 要否 | 説明 |
---|---|---|---|
type | string | true | welcome |
data.postback | string | false | ポストバックテキストが open eventからチャットボットに送信 |
Quick Button
チャット画面の下にあるグループ固定ボタンです。
Persistent Menu
ユーザーがチャットバーのメニューボタンをタッチすると、固定メニューが表示されます。常にウェルカムレスポンスに含まれます。utilsは変更しないこと。他のレスポンスには permanentMenuコンテンツが含まれています。
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.
エラーコード
エラーレスポンスについて説明します。
HttpStatusCode | Description |
---|---|
500 | Internal server error |
- Error Response Body:
{
"code": "1001",
"message": "domain code test not found",
"timestamp": 12345678
}
- Body Introduce
Field | Type | Must Exists | Description |
---|---|---|---|
code | string | true | error code |
event | string | true | fixed string value "send" |
timestamp | long | true | response time milliseconds since January 1, 1970, 00:00:00 GMT |
エラーコード
ErrorCode | Description |
---|---|
4000 | request param invalid |
4010 | Unauthorized |
4030 | Forbidden to access |
4031 | Signature validate failed |
4032 | timestamp exceeded time window(10000ms) |
1000 | version not support |
1001 | Not found domain code |
1002 | check url param is invalid |
5000 | Unknown service error |
5010 | Current protocol version not support this reply structure |
5020 | Calls 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