- Print
- PDF
CLOVA Chatbot Custom API
- Print
- PDF
To integrate with a custom chatbot, copy and store the invoke URL and secret key of the stage created.
Requests
Sending a request to CLOVA Chatbot from a custom channel.
Http Method: POST
Headers
name | value |
---|---|
Content-Type | "application/json;UTF-8" |
X-NCP-CHATBOT_SIGNATURE | signature of request body |
Request signature headers
Signature definition (X-NCP-CHATBOT_SIGNATURE)
Algorithm: HmacSHA256
SecretKey: it is the SecretKey created by the CLOVA Chatbot domain builder.
Sign content: it means the request body string.
X-NCP-CHATBOT_SIGNATURE
:X-NCP-CHATBOT_SIGNATURE
is a message that sends a request to the chatbot.
That is, when a request is sent to the chatbot, the request header requires content-type, X-NCP-CHATBOT_SIGNATURE, and
X-NCP-CHATBOT_SIGNATURE is set by encoding the sign content (request body) in Base64.
Sample code for creating the Signature (Sign content) for each language
It describes in detail the X-NCP-CHATBOT_SIGNATURE setting and example.
- 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);
Request body
Open
Response to welcome message
trigger when open messenger, will response welcome message if have set in "Messenger Connection" -> "Custom"
Classification | 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 request field details
Field name | Data type | Requirement status | Description |
---|---|---|---|
version | string | false | Version is "v2" by default and set to "v1" if no value is entered. |
userId | string | true | Unique ID of a user chatting with the chatbot. Must not exceed 256 characters. A unique userId must be set for each user. |
userIp | string | false | User’s IP address. Not required. |
timestamp | long | true | Time offset value (January 1, 1970, 00:00:00 GMT) |
bubbles | array | true | empty array "[]" or only one Text component caused by welcome action |
event | string | true | Sets event value to "open" |
Send
Sends a user’s question to CLOVA Chatbot.
When invoking send, the userId value can be set to anything as long as it is a unique key value. (e.g., member number)
- userid: unique key value, description: user query
Classification | 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 request field details
Field name | Data type | Requirement status | Description |
---|---|---|---|
version | string | false | Version is "v2" by default and set to "v1" if no value is entered. |
userId | string | true | Unique ID of a user chatting with the chatbot. Must not exceed 256 characters. A unique userId must be set for each user. |
userIp | string | false | User’s IP address. Not required. |
timestamp | long | true | Time offset value (January 1, 1970, 00:00:00 GMT) |
bubbles | array[Text] | true | Only one text component is supported. If there is more than one text component, the last component uses the user request. |
event | string | true | Sets event value to "send" |
getPersistentMenu
Calls persistent menu list.
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 can be requested if a persistent menu needs to be displayed but no local cache exists. Responds to the persistMenu field if persistent. The menu is set in the messenger customization tab.
Classification | Json Model - Example |
---|---|
getPersistentMenu | { "version": "v2", "userId": "U47b00b58c90f8e47428af8b7bddcda3d", "userIp": "8.8.8.8", "timestamp": 12345678, "bubbles": [], "event": "getPersistentMenu" } |
getPersistentMenu request field details
Field name | Data type | Requirement status | Description |
---|---|---|---|
version | string | false | Version is "v2" by default and set to "v1" if no value is entered. |
userId | string | true | Unique ID of a user chatting with the chatbot. Must not exceed 256 characters. A unique userId must be set for each user. |
userIp | string | false | User’s IP address. Not required. |
timestamp | long | true | Time offset value (January 1, 1970, 00:00:00 GMT) |
bubbles | array | true | Sets empty Array value "[]" |
event | string | true | Sets event value to "getPersistentMenu" |
Responses
Response result status (success/failure)
Success
- HTTP Status Code returns 200 if a chatbot query is successful, and the response fields are as follows:
Classification | 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" } |
Response result status field details (Success)
Field name | Data type | Requirement status | Description |
---|---|---|---|
version | string | false | Version is "v2" by default. Responds with "v1" if no value is entered for requests. |
userId | string | true | Same as the userId value set at the time of request. |
sessionId | string | false | Current session id. Value managed by CLOVA Chatbot. |
timestamp | long | true | Response time offset value (milliseconds, January 1, 1970, 00:00:00 GMT) |
bubbles | array[Component] | false | Array of response components. Each component matches the chatbot's response bubble. |
quickButtons | array[Component] | false | Information on the quickButton set at the bottom of the chatbot. |
scenario | jsonObject | false | Scenario analysis result matched to user query. Provides scenario name and intent (interaction type) information. |
entities | array[jsonObject] | false | Entity analysis result matched to user query. |
keywords | array[jsonObject] | false | The following keyword matches are possible during user chat: "exactMatch" or "contain", "exactMatch" "exactMatch" means the user input matches the keyword exactly, and "contain" means the user input contains the keyword. |
persistentMenu | TemplateComponent | false | Sets a persistent menu. PersistentMenu |
event | string | true | Fixed response of "send." |
slotNormalizer | array[jsonObject] | false | System entities that match user query. (Date, time, people, name) |
normalizer | string | false | System entities that match user query. |
Error
Error
- HTTP Status Code returns 500 if a chatbot query fails, and the response fields are as follows
(Errors other than 500 are detailed at the bottom of the article):
HttpStatusCode | Description |
---|---|
500 | Internal server error |
Error Response Body:
{
"code": "500",
"message": "Internal server error",
"timestamp": 12345678
}
Component
The chatbot’s response component follows the following json structure:
{
"type": "...",
"title": "optional, short bold text",
"subTitle": "optional, short gray text",
"data" : {
...
}
}
There are three types of components:
- Basic Component
- Composite Component
- Flex Component
Basic Component
Three basic components are provided: text, image, and button.
Text
Text-type chatbot response component. Title, subtitle, detailed description, and URL can be set.
- Text component structure
- Text component JSON
Classification | 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} }} |
- Detailed description of text component
Field name | Data type | Requirement status | Description |
---|---|---|---|
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-type chatbot response component. Title, subtitle, detailed description, and URL can be set along with the image.
- Image component structure
- Image component Json
Classification | 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} } |
- Detailed description of image component
Field name | Data type | Requirement status | Description |
---|---|---|---|
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-type chatbot response component. Title, subtitle, detailed description, and URL can be set along with the image.
- Button component structure
- Button component JSON
Classification | 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} } } |
- Detailed description of button component
Field name | Data type | Requirement status | Description |
---|---|---|---|
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
A composite component consists of template and carousel components.
The chatbot response can be set in Chatbot Builder according to specific characteristics. The response result set via Chatbot Builder is returned in the following JSON format:
Template Component
The template consists of basic building blocks. The template has three parts: cover, contentTable, and footTable. The cover is the main content. contentTable and footTable are table layouts.
- Template component structure
- Template component Json
Classification | 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 details
Field name | Data type | Requirement status | Description |
---|---|---|---|
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 | Sets the maximum number of rows. If the number of rows exceeds the max value, the maximum number is displayed and the rest are collapsed, requiring the addition of a scaling button. All rows are displayed if the value is not set. |
data.contentBackgroundImage | string | false | Displays background image in content table zone. |
data.contentTable | array[][Cell] | false | It is a table layout with a two-dimensional cell array where cell data are the basic building blocks. |
data.footTableShowRows | integer | false | Sets the maximum number of rows. If the number of rows exceeds the max value, the maximum number is displayed and the rest are collapsed, requiring the addition of a scaling button. All rows are displayed if the value is not set. |
data.footBackgroundImage | string | false | Displays background image in foot table zone. |
data.footTable | array[][Cell] | false | Same as contentTable, but is not always present unless contentTable is unable to support the content. |
- Table layout details
Field name | Data type | Requirement status | Description |
---|---|---|---|
rowSpan | integer | true | span row count |
colSpan | integer | true | span column count |
data | Basic Component | true | Text / Image / Button |
Carousel Component
You can set carousel responses.
- Carousel component JSON
Classification | 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 details
Field name | Data type | Requirement status | Description |
---|---|---|---|
type | string | true | carousel |
title | string | false | short bold text |
subTitle | string | false | short gray text |
data.cards | array[Component] | true | Component array Components other than carousel and flex. |
Flex Component
Supports all Json object formats. JSON specifications can be defined as needed.
Example: FlexMessageContainerObject JSON can be used.
- 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
The LineFlex function supported in Line messenger has been changed to a common Flex component format, but the specifications remain the same.
LineSticker
The LineSticker function supported in LINE messenger can set sticker messages according to the sticker list format.
- LineSticker Json
Classification | Json Model - Example |
---|---|
LineSticker | { "type": "line_sticker", "data" : { "packageId": "packageId of LINE", "stickerId": "stickerId of LINE" } } |
- LineSticker component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
type | string | true | line_sticker |
data.packageId | string | true | packageId of a sticker for LINE. See sticker list. |
data.stickerId | string | true | stickerId of a sticker for LINE. See sticker list. |
LineWorksSticker
The LineWorksSticker function supported in LINE WORKS can set sticker messages according to the corresponding format of the sticker list.
- LineWorksSticker Json
Classification | Json Model - Example |
---|---|
LineWorksSticker | { "type": "lineworks_sticker", "data" : { "packageId": "packageId of LINEWORKS", "stickerId": "stickerId of LINEWORKS" } } |
- LineWorksSticker component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
type | string | true | lineworks_sticker |
data.packageId | string | true | packageId of a sticker for LINE WORKS. See sticker list. |
data.stickerId | string | true | stickerId of a sticker for LINE WORKS. See sticker list. |
Action
An action is common data for all components. When a component is clicked, it defines the operation to be performed.
Clicking on the component will postback the postbackText to the chatbot and display the postback as user chat.
- Postback Json
Classification | Json Model - Example |
---|---|
Postback | { "type": "line_sticker", "data" : { "packageId": "packageId of LINE", "stickerId": "stickerId of LINE" } } |
- Postback component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
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 |
Clicking on the building block will postback the text to the chatbot and display the text as the user’s chat.
- Utterance Json
Classification | Json Model - Example |
---|---|
Utterance | { "type": "utterance", "data" : { "utteranceId" : 1, "text" : "text show in chat window", "postback" : "postback text" } } |
- Utterance component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
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 |
Classification | Json Model - Example |
---|---|
Link | { "type": "link", "data" : { "url" : "http://www.ncloud.com", "mobileUrl" : "http://m.ncloud.com" } } |
- Link component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
type | string | true | postback |
data.url | string | true | open url |
data.mobileUrl | string | false | url for mobile device |
Clicking the component will redirect the user to the dial page. This function is only supported on mobile devices.
- Phone Component JSON
Classification | Json Model - Example |
---|---|
Phone | { "type": "phone", "data" : { "number" : "400-1111-1111", "name" : "Customer service" } } |
- Phone component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
type | string | true | phone |
data.number | string | true | phone number |
data.name | string | false | contact name |
Classification | Json Model - Example |
---|---|
Welcome | { "type": "welcome", "data" : { "postback" : "postback text, optional" } } |
- Welcome component details
Field name | Data type | Requirement status | Description |
---|---|---|---|
type | string | true | welcome |
data.postback | string | false | Postback text sent to chatbot from an open event. |
Quick Button
Group-pinning button at the bottom of the chat window.
Persistent Menu
The persistent menu is displayed when the user touches the menu button in the chat bar. It is always included in the welcome response. permanentMenu contents are included in responses without changes to utils.
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.
Errors
An explanation of error responses.
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 |
- Errors
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 examples
The following are example implementations of the CLOVA Custom API for each language:
- 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