์๋ฒ์์ Steem API ๊ฐ๋ฐ์ด ์๋ฃ๋์๊ณ , ์ด์ ๋ Dialogflow์ ์ด๋ฅผ ์ฐ๋ํด๋ณผ ์ฐจ๋ก์ ๋๋ค.
์ด๋ฒ ํฌ์คํธ์์๋ Dialogflow ์นํ ์ ํ ์๋ฒ์ ํ๋ก์ธ์ค ์๋ํฌ์ธํธ์ ์ฐ๋ํด๋ณด๊ฒ ์ต๋๋ค. ์ด๋ฏธ ์์, Steem API๋ฅผ ์์ฑํ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ฐ๋ํ๋ ์์ ์ ๊ทธ๋ฆฌ ์ด๋ ต์ง ์์ ๊ฒ์ ๋๋ค.
์ฐ์ , ์๋ก์ด ํ์ผ์ธ dialogflow.js ๋ฅผ ๋ง๋ค๊ณ , ์ด๋ฅผ ๋ฉ์ธ app.js์์ ์ฐ๊ฒฐํฉ๋๋ค.
-dialogflow.js
module.exports = function(app, key) {
const request = require('request');
}
-app.js
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const app = express();
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json());
app.use((err, req, res, next) => next());
const key = JSON.parse(fs.readFileSync('./.key', 'utf8'));
require('./steemapi')(app, key);
require('./dialogflow')(app, key);
const server = app.listen(3000, function(){
console.log("Express server has started on port 3000")
});
๋ฉ์ธ 3000ํฌํธ๋ก ์๋ฒ๋ฅผ ์คํํ๊ณ , dialogflow.js์ express์ ๋ ํผ๋ฐ์ค๊ฐ๊ณผ key ์ค๋ธ์ ํธ ๊ฐ์ ์ ๋ฌํฉ๋๋ค.
dialogflow.js์์ ๋ค์๊ณผ ๊ฐ์ด /process์ ๋ํ POST ํต์ ์ ์ํ ์๋ํฌ์ธํธ๋ก ์ฝ๋๋ฅผ ๊ตฌํํฉ๋๋ค.
app.post('/process', (req, res) => {
const body = req.body;
console.log(JSON.stringify(body));
if(!body){
notUnderstood(res);
return;
}
const intentName = body.result.metadata.intentName;
if(intentName=='view_intent'){
} else if(intentName=='vote_intent'){
} else {
console.log(` No intent matched.`);
notUnderstood(res);
}
});
ํด๋น ์ฝ๋๋ Dialogflow V1 API๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ฑ๋์์ต๋๋ค. ๋ฐ๋ผ์, Dialogflow ์ฝ์์ ์ค์ ์ผ๋ก ๋ค์ด๊ฐ, API version > V1 API ๊ฐ ์ฒดํฌ๋์๋์ง ํ์ธํด๋ณด์ธ์.
๋ค์, ์์ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด, Dialogflow๋ก๋ถํฐ ์นํ ์ ํตํด ์ ๋ฌ๋ ๋ถ์๋ Object ๊ฐ์ ๊ฐ์ง๊ณ ํด๋น ์ธํ ํธ๋ช ๊ณผ ์ฐ๊ด ์ํฐํฐ ๋ฑ์ ์ถ์ถํ๋ฉด ๋ฉ๋๋ค.
ํด๋น ์ธํ ํธ ๋ช ์
const intentName = body.result.metadata.intentName;
์ผ๋ก ๊ฐ์ ธ์ฌ ์ ์๊ณ , ์ด๋ ์ฐ๋ฆฌ๊ฐ Dialogflow์์ ์ค์ ํ๋ ์ธํ ํธ๋ช ์ด๋ฆ๊ณผ ๊ฐ์ต๋๋ค. ์ด ์ธํ ํธ ๋ช ์ โview_intentโ ์ธ์ง, โvote_intentโ์ธ์ง์ ๋ฐ๋ผ ๋ก์ง์ ๋ถ๊ธฐํ๋ฉด ๋ฉ๋๋ค.
๋ง์ฝ, ํด๋น ์์ฒญ๊ฐ์ด ์๊ฑฐ๋ ์ด๋ ์ธํ ํธ์๋ ์ํ์ง ์๋๋ค๋ฉด, notUnderstood ํจ์๋ฅผ ํธ์ถํด์ค๋๋ค.
const notUnderstood = (res) => {
const msg = '์ฃ์กํด์. ์์๋ฃ์ง ๋ชปํ์ด์. ๋ค์ ๋งํด์ค๋์?';
res.json({ 'speech': msg, 'displayText': msg });
}
์ฌ๊ธฐ์, ์๋ต๊ฐ์ ๊ฒฝ์ฐ ์ด์ ๊ฐ์ ํฌ๋งท์ผ๋ก ์ ๋ฌํ์ฌ ์ถํ, dialogflow์ ๋ค๋ฅธ ์๋ํํฐ์ ์ฐ๋๋์์ ๋, ํด๋น ๋ฌธ์ฅ์ ์ถ๋ ฅํ๋๋ก ํฉ๋๋ค. ์ด์ ๋ํ ์์ฒญ ๋ฐ ์๋ต๊ฐ์ ๋ํ ์์ธ์ ๋ณด๋ ๋ค์ ๋งํฌ(https://dialogflow.com/docs/fulfillment)์ ์ ๋์์์ต๋๋ค.
๋ง์ฝ, ์ธ์๋ ์ธํ ํธ ๋ช ์ด โview_intentโ๋ผ๋ฉด,
if(intentName=='view_intent'){
const discuss_option = body.result.parameters.discuss_option;
actionOnViewIntent(discuss_option, res);
}
์ธ์๋ ์ํฐํฐ์ธ discuss_option์ actionOnViewIntent ํจ์์ ๋๊ฒจ์ค๋๋ค.
const actionOnViewIntent = (discuss_option, res) => {
const st_query = {"tag": "kr", "limit": 10};
if(discuss_option=='์ต์ '){
app.getDiscussionsByCreated(st_query).then((result) => {
let msg = "";
result.forEach((entry) => {
const url = "https://steemit.com/"+entry.url;
msg += entry.title +"\n URL: "+url;
});
res.json({ 'speech': msg, 'displayText': msg });
}).catch((err) => {
notUnderstood(res);
});
} else if(discuss_option=='ํซํ'){
app.getDiscussionsByHot(st_query).then((result) => {
let msg = "";
result.forEach((entry) => {
const url = "https://steemit.com/"+entry.url;
msg += entry.title +"\n URL: "+url;
});
res.json({ 'speech': msg, 'displayText': msg });
}).catch((err) => {
notUnderstood(res);
});
}
}
์ด๋ฏธ ์ ์ํ discuss_option์ ์ํฐํฐ๋ก์, โ์ต์ '์ธ์ง, โํซํ'์ธ์ง์ ๋ฐ๋ผ ๋ค๋ฅธ Steem API๋ฅผ ๋ถ๋ฌ์ค๊ฒ ๋์ง์. ๋ฌผ๋ก , ์ด API๋ ์ ํฌ์คํธ์์ ์ด๋ฏธ ๋ง๋ค์ด ๋ณด์์ต๋๋ค.
โ์ต์ '์ด๋ฉด, app.getDiscussionsByCreated() ํจ์๋ฅผ ํธ์ถํ๊ณ , โํซํ'์ด๋ฉด app.getDiscussionsByHot() ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์ด์ ๋ฐ๋ผ ๋์ค๋ ๊ฒฐ๊ณผ ๋ฐฐ์ด์ ํ๋์ ๋ฌธ์์ด๋ก ํฉ์น๊ณ JSON ํํ๋ก ๋ฐํํ๊ฒ ๋ฉ๋๋ค.
์ธํ ํธ๊ฐ โvote_intentโ์ด๋ผ๋ฉด, ๋ณดํ ๊ด๋ จํ ์ก์ ์ ์ํํด์ผ๊ฒ ์ง์.
} else if(intentName=='vote_intent'){
const tag_option = body.result.parameters.tag_option;
if(tag_option=='ํ๊ทธ') {
const resolvedQuery = body.result.resolvedQuery;
const arrTags = resolvedQuery.split(" ").filter((value) => value != tag_option);
if(arrTags[0]){
actionOnVoteIntent(arrTags[0], res);
} else {
notUnderstood(res);
}
}
}
์ ์ํ ์ํฐํฐ์ธ tag_option์ด โํ๊ทธ'์ธ ๊ฒฝ์ฐ, ํด๋น ์ก์ ์ ์ํํ๊ฒ๋ฉ๋๋ค. ๋ค๋ฅธ ์ก์ ์ ๊ฒฝ์ฐ๋ ์ด์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ์ฃ .
์ด๋ ์์ โview_intentโ๋ฅผ ์ฒ๋ฆฌํ ๋ฐฉ์๊ณผ ๋ค๋ฅธ๋ฐ. resolvedQuery๋ ์ ์ ๊ฐ ์ฟผ๋ฆฌํ ์ ์ฒด ๋ฌธ์ฅ์ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋๋ค. ์ฆ, ๊ทธ ๋ฌธ์ฅ ์ค์์ โํ๊ทธ'๋ฅผ ์ ์ธํ ๋๋จธ์ง ๋จ์ด๋ฅผ ์ถ์ถํ๋ ๊ฒ์ธ๋ฐ์. ๊ทธ๋ฌ๋ฉด, ์ฟผ๋ฆฌํ ํ๊ทธ ๋จ์ด๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋ฉ๋๋ค. ์ด๋ Dialogflow์ ํ์ต์ํค์ง ์์๊ธฐ์, ์ง์ ๋ฌธ์ฅ์ ๊ฐ์ง๊ณ ์ถ์ถํ ๊ฒ์ ๋๋ค. ์ด ๋ถ๋ถ์ ์ถํ์ ๋ณ๊ฒฝ๋ ์ฌ์ง๊ฐ ์์ ์๋ ์์ด์. ์๋ฌดํผ, ์ด ํ๊ทธ๊ฐ ์์ผ๋ฉด, actionOnVoteIntent ํจ์๋ก ํด๋น ํ๊ทธ๋ฅผ ์ ๋ฌํฉ๋๋ค.
const actionOnVoteIntent = (tag, res) => {
app.getDiscussionsByTag(tag).then((post) => {
console.log(JSON.stringify(post));
app.vote(post, 100).then((post) => {
app.comment(tag, post).then((post) => {
let msg = `${tag}์ ์๋ ๋ณดํ
์ด ์๋ฃ๋์์ต๋๋ค! `;
const url = "https://steemit.com/"+post.url;
msg += post.title +"\n URL: "+url;
res.json({ 'speech': msg, 'displayText': msg });
}).catch((err) => {
const errorMsg = `๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก, ํ๋ก์ธ์ค๊ฐ ์๋ฃ๋์ง ๋ชปํ์ต๋๋ค : ${err}`;
res.json({ 'speech': errorMsg, 'displayText': errorMsg});
});
}).catch((err) => {
const errorMsg = `๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก, ํ๋ก์ธ์ค๊ฐ ์๋ฃ๋์ง ๋ชปํ์ต๋๋ค : ${err}`;
res.json({ 'speech': errorMsg, 'displayText': errorMsg});
});
}).catch((err) => {
console.error(err);
notUnderstood(res);
});
}
์์ ๋ง๋ API์ธ app.getDiscussionsByTag ์ ํ๊ทธ๋ฅผ ์ ๋ฌํ๊ณ , ์ด์ ๋ํ ํ๊ทธ๋ฅผ ๊ฐ์ง ์ต์ ํฌ์คํธ๊ฐ ๋ฐํ๋๋ฉด, app.vote(post, 100) ๋ฅผ ํตํด, ๋ณดํ ์ ํฉ๋๋ค. ์ฌ๊ธฐ์ 100์ ๋ณดํ ์จ๋ก ์์๋ก ๋ฃ์ ๊ฐ์ ๋๋ค. ๋ณดํ ์ด ์ฑ๊ณตํ๋ฉด, ์ฐจ๋ก๋ก app.comment(tag, post)๋ฅผ ํตํด, ํด๋น ํฌ์คํธ์ ์ฝ๋ฉํธ๋ฅผ ๋ฌ๊ฒ ๋๊ณ , ์ด๊ฒ์ด ์ฑ๊ณตํ๋ฉด, JSON ํํ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. ์ค๋ฅ์ ๋ฐ๋ฅธ ์๋ฌ ๋ฉ์์ง๋ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ฐํํ๊ตฌ์.
์ด์ , ์๋ฒ๋ฅผ ์คํํด๋ณด๊ณ , ํ ์คํธ๋ฅผ ์ํด์ Postman ๋ฑ์ REST์ฉ ๋ฆฌํ์คํธ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์๋๋ฐ, ์ฌ๊ธฐ์๋ ์๋ตํ๊ฒ ์ต๋๋ค. ๋ํ, Firebase Cloud Function์ ํ์ฉํ์ฌ ๊ตฌ์ถํ๋ฉด, ์๋ฒ ๋ฐฐํฌ๊ฐ ํธ๋ฆฌํ๊ฒ ์ด๋ฃจ์ด์ง๋๋ค. ์ด ๋ด์ฉ๋ค์ โ๋๊ตฌ๋ ์ฝ๊ฒ ๋ฐฐ์ฐ๋ ์ฑ๋ด ์๋น์ค' ์ฑ ์ ์ฐธ๊ณ ํ๋ฉด ๋ณด๋ค ์์ธํ ๋ด์ฉ์ ํ์ตํ ์ ์์ต๋๋ค.
์ด์ ์๋ฒ์ ๋ฐฐํฌํ์ฌ ํด๋น ์๋ํฌ์ธํธ์ ์ ๊ทผํ ์ ์๋ค๊ณ ๊ฐ์ ํ๊ณ , ์ด์ด๊ฐ๋ณด๊ฒ ์ต๋๋ค.
Dialogflow ์ฝ์์ ๋ค์ ๋ค์ด๊ฐ์, ์ฐ๋ฆฌ๊ฐ ์์ ๋ง๋ ์์ด์ ํธ์ Fulfilment ํ์ด์ง๋ก ๋ค์ด๊ฐ๋ด ๋๋ค.
๊ฑฐ๊ธฐ์, Webhook์ ํ์ฑํํ๊ณ , URL ์ธํ๋์ ๋ฐฐํฌํ ์๋ฒ URL์ ์๋ํฌ์ธํธ๋ฅผ ์ ๋ ฅํฉ๋๋ค. ์ด๋ ์์ ๋ง๋ path ์ธ /process ๊ฐ ๋๊ฒ ์ฃ .
์ด์ , ํ๋จ์ Save ๋ฒํผ์ ๋๋ฅด๊ณ , ์ค๋ฅธ์ชฝ์ Try it now ๋์ ํตํด ํ ์คํธ๋ฅผ ์งํํด๋ณผ๊ฒ์.
โ์ต์ ์ ํฌ์คํธ' ๋ผ๋ ์ฟผ๋ฆฌ๋ฅผ ์ ๋ ฅํ๊ณ ์ํฐ๋ฅผ ์ณ ๋ด ์๋ค.
๊ทธ๋ฌ๋ฉด, ์์ ํ ์คํธํ ๋ ์๋ฌด๋ฐ ์๋ต๊ฐ์ด ์์๋ Response ๋์ ๋ค์๊ณผ ๊ฐ์ด ์๋ต ๋ด์ฉ์ด ๋์ค๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ด๋ ํด๋น ์ฟผ๋ฆฌ๊ฐ Dialogflow์ ๋ถ์๋ ํ, ์ฐ๋ฆฌ๊ฐ ์ฐ๋ํ ์นํ
์๋ฒ๋ฅผ ํตํด ์ ๋ฌ๋๊ณ , ์ด๋ฅผ ์ฒ๋ฆฌํ์ฌ ๋์จ ๊ฒฐ๊ณผ๋ฌผ์
๋๋ค.
์๋, Inline Editor์ Firebase Cloud Function์ ํตํด ๊ตฌํํ ์ ์๋ ์นํ
์ด๋ฉฐ, ์ฌ๊ธฐ์ ์ฝ๋๋ฅผ ์
๋ ฅํ๋ ๊ฒ ๋ง์ผ๋ก๋ ๋น ๋ฅด๊ฒ ์นํ
์๋ฒ๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
๋ณดํ ๋ ๋น์ทํ ๋ฐฉ์์ผ๋ก ํ ์คํธํ ์ ์์ต๋๋ค. โํ๊ทธ kr-devโ์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ํ ์คํธํด๋ณด์ธ์. ๋ง์ฝ, ์ ์๋๋ฉด, ๋ณดํ ์จ์ ์กฐ์ ํ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์์ต๋๋ค.
์ด๋ฒ ํฌ์คํธ์์ ๊ธฐ ๊ตฌ์ถ๋ ์๋ฒ API์ Dialogflow๋ฅผ ์ฐ๋ํ๊ณ , ํ ์คํธํด๋ณด์์ต๋๋ค. ์ด์ ๊ฑฐ์ ๋ง๋ฌด๋ฆฌ ๋จ๊ณ์ ์๋ค์~!
๋ค์ ๋ง์ง๋ง ํฌ์คํธ์์๋ Dialogflow์ integration ์ ํ์ฉํ์ฌ ์ปค๋ฎค๋์ผ์ด์ ๋ฉ์ ์ ์ธ ์ฌ๋๊ณผ ํตํฉํด๋ณด๊ฒ ์ต๋๋ค.
http://bit.ly/2tQKV9J : ๋๊ตฌ๋ ์ฝ๊ฒ ๋ฐฐ์ฐ๋ ์ฑ๋ด ์๋น์ค
๋ณด๋ค ๊ด์ฌ์๋ ๋ถ๋ค์ ์ด ์ฑ ์์ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค~.