Watson์œผ๋กœ ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ์นด์นด์˜คํ†ก ChatBot

์ด ํฌ์ŠคํŒ…์€ 5. [์‘์šฉ] ํšŒ์˜์‹ค ์˜ˆ์•ฝ ์กฐํšŒ ๋ฐ ์ทจ์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ์— ์ด์–ด์ง€๋Š” ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค. ์ด ํฌ์ŠคํŒ…์—์„œ๋Š” ์ฑ—๋ด‡์„ ํ…”๋ ˆ๊ทธ๋žจ๊ณผ ์—ฐ๋™ํ•˜๊ณ , ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์ด์šฉํ•˜์—ฌ ์˜ˆ์•ฝ ์‹œ๊ฐ„์ด ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ์„ ์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค.

์ด์ „ ํฌ์ŠคํŒ…์„ ์™„๋ฃŒํ•˜์ง€ ๋ชปํ•˜์‹  ๋ถ„์€ tutorial6 ๋ธŒ๋žœ์น˜๋ฅผ checkoutํ•˜์—ฌ ์ง„ํ–‰ํ•˜์‹ญ์‹œ์˜ค.

< ์ด ํฌ์ŠคํŒ…๊ณผ ์—ฐ๊ฒฐ๋œ ๊ธ€ ๋ชฉ๋ก >

6. ์˜ˆ์•ฝ ์‹œ๊ฐ„์ด ๋˜๋ฉด ChatBot์ด ์•Œ๋ ค์ฃผ๊ธฐ (ํ…”๋ ˆ๊ทธ๋žจ)

6.1 ํ…”๋ ˆ๊ทธ๋žจ๊ณผ ์—ฐ๋™ํ•˜๊ธฐ

๋จผ์ € ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…”๋ ˆ๊ทธ๋žจ๊ณผ ์—ฐ๋™ํ•ฉ๋‹ˆ๋‹ค.

1) Node.js ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…”๋ ˆ๊ทธ๋žจ๊ณผ ์—ฐ๋™ํ•˜๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์—ฌ bot์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

2) 1๋ฒˆ ๋‹จ๊ณ„์—์„œ ์ƒ์„ฑํ•œ bot token์„ TELEGRAM_TOKEN์œผ๋กœ, ๊ฐ์ž ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ public url์„ PUBLIC_URL๋กœ .envํŒŒ์ผ์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

# Telegram
TELEGRAM_TOKEN=376884728:AAH0EcH3tPqBZ6G30tI8nEZCrchXXXXXXXX

# Public URL
PUBLIC_URL=https://meetingroom-reservation-with-camomile-xxxx.eu-gb.mybluemix.net

3) Command์ฐฝ์—์„œ ์•„๋ž˜ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์—ฌ node-telegram-bot-api ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

npm install node-telegram-bot-api --save

4) /api ๋ฐ‘์— telegram ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ  cron.js์™€ message.js๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

chatbot-tutorial
  - api
    - telegram
      - cron.js
      - message.js

4) /api/telegram/message.js๋ฅผ ํ†ตํ•ด ํ…”๋ ˆ๊ทธ๋žจ ๋ฉ”์‹ ์ €์™€ ๋Œ€ํ™” ์„œ๋น„์Šค๋ฅผ ์—ฐ๋™ํ•ฉ๋‹ˆ๋‹ค. ๋จผ์ € ํ•„์š”ํ•œ ๋ชจ๋“ˆ์„ import ํ•ฉ๋‹ˆ๋‹ค.

'use strict';

const conversation = require('../message');
const config = require('../../util/config');
const cloudant = require('../../util/db');
const db = cloudant.db;

const token = process.env.TELEGRAM_TOKEN;
const url = process.env.PUBLIC_URL;

5) token์„ ์ธ์ž๋กœ Telegram bot ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

const TelegramBot = require('node-telegram-bot-api');
let bot = new TelegramBot(token);

6) Telegram์— webhook url๋กœ ๋“ฑ๋กํ•  api๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

let postMessage = (req, res) => {
  bot.processUpdate(req.body);
  res.sendStatus(200);
};

module.exports = {
    'initialize' : (app, options) => {
        app.post(`/bot${token}`, postMessage);
    }
};

7) /api/index.js์— ํ•„์š”ํ•œ ๋ชจ๋“ˆ์„ importํ•˜๊ณ  webhook url์„ initializeํ•˜๋„๋ก ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

'use strict';

console.log('APIs initialize');

const conversation = require('./message');
const kakao_keyboard = require('./kakao/keyboard');
const kakao_message = require('./kakao/message');
const telegram_bot = require('./telegram/message');
const telegram_scheduler = require('./telegram/cron');

module.exports = {
    'initialize': (app, options) => {
        conversation.initialize(app, options);
        kakao_keyboard.initialize(app, options);
        kakao_message.initialize(app, options);
        telegram_bot.initialize(app, options);
    }
};

8) /api/telegram/message.js์— ์ƒ์„ฑํ•œ api์˜ url์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

bot.setWebHook(`${url}/bot${token}`);

9) ํ…”๋ ˆ๊ทธ๋žจ์œผ๋กœ ๋„์ฐฉํ•˜๋Š” ๋ฉ”์„ธ์ง€๋ฅผ Conversation api์˜ input text๋กœ ํ•˜๊ณ  Conversation api์˜ output text๋ฅผ ํ…”๋ ˆ๊ทธ๋žจ์œผ๋กœ ๋ณด๋‚ด๋Š” ๋ฉ”์„ธ์ง€๋กœ ํ•จ์œผ๋กœ์จ ๋‘ ์„œ๋น„์Šค๋ฅผ ์—ฐ๋™ํ•ฉ๋‹ˆ๋‹ค. ์ฑ„ํŒ…์ฐฝ id๋ฅผ key๋กœ ํ•˜์—ฌ ๋Œ€ํ™”์˜ context๋ฅผ cloudant์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

bot.on('message', msg => {
  let user_key = msg.chat.id;
  let content = {
    text : msg.text
  };

  db.get(user_key).then(doc => {
    conversation.getConversationResponse(content, doc.context).then(data => {
      db.insert(Object.assign(doc, {
        'context': Object.assign(data.context, {
          'timezone' : "Asia/Seoul"
        }),
      }));

      bot.sendMessage(user_key, getOutputText(data));
    }).catch(function(err){
      bot.sendMessage(user_key, JSON.stringify(err.message));
    });
  }).catch(function(err) {
    // first communication
    conversation.getConversationResponse(content, doc.context).then(data => {
      db.insert({
        '_id' : user_key+"", // cloudant์˜ doc id๋Š” ๋ฐ˜๋“œ์‹œ string ํƒ€์ž…์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
        'user_key' : user_key+"",
        'context': data.context,
        'type' : 'telegram'
      }).then(function(){
      }).catch(function(err){
        console.log(err)
      });
      
      bot.sendMessage(user_key, getOutputText(data));  

    }).catch(function(err){
      bot.sendMessage(user_key, JSON.stringify(err.message));
    });
    
  });
});

function getOutputText(data){
  var output = data.output;
  if(output.text && Array.isArray(output.text)){
    return output.text.join('\\n');
  }
  else if(output.text){
    return output.text;
  }
  else return "";
}

6.2 ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ํ†ตํ•ด ์˜ˆ์•ฝ ์•Œ๋ฆผ ์„œ๋น„์Šค ๊ฐœ๋ฐœ

1) ๋จผ์ € node-schedule ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

npm install node-schedule --save

2) cron.js์— ํ•„์š”ํ•œ ๋ชจ๋“ˆ์„ importํ•˜๊ณ  telegram์˜ bot instance๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

'use strict';

const schedule = require('node-schedule');
const request = require('request');
const moment = require('moment');
const TelegramBot = require('node-telegram-bot-api');

const cloudant = require('../../util/db');
const db = cloudant.db;
const config = require('../../util/config');
const token = process.env.TELEGRAM_TOKEN;

let bot = new TelegramBot(token);

3) Schedule Job์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ฒซ๋ฒˆ์งธ ์ธ์ž ๊ฐ’์„ 0์œผ๋กœ ์„ค์ •ํ•จ์œผ๋กœ์จ(‘0 * * * *’) ๋งค ์‹œ๊ฐ„ 0๋ถ„์— ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

 
let job = schedule.scheduleJob('0 * * * *', function(){
  console.log("I'm working :-D");

  // ํ•˜๋ฃป๋™์•ˆ์˜ ์˜ˆ์•ฝ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  let startTimestamp = new moment();
  let endTimestamp = new moment(startTimestamp).day(startTimestamp.day() + 1);
  //var endTimestamp = new moment(startTimestamp).month(startTimestamp.month() + 1);

  // /book/search/bysite API๋Š” site id, start time, end time์„ Query parameter๋กœ ๋ฐ›์•„ ํ•ด๋‹น ์‹œ๊ฐ„์˜ ์˜ˆ์•ฝ ๋ฆฌ์ŠคํŠธ๋ฅผ returnํ•ด์ฃผ๋Š” api์ž…๋‹ˆ๋‹ค.
  request({
    method : 'GET',
    url : process.env.RBS_URL + "/book/search/bysite",
    headers : {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    qs: {
      "siteid" : "camomile",
      "start" : startTimestamp.valueOf(),
      "end" : endTimestamp.valueOf()
    }
  }, function(err, httpResponse, body){
    body = JSON.parse(body);
    if(err){
        console.error(JSON.stringify(err));
    }
    else{
      if(body && body.length > 0){
        let resvs = [];
        for(let resv of body){
             // ์˜ˆ์•ฝ ์‹œ๊ฐ„์ด 1์‹œ๊ฐ„ ์ด๋‚ด๋กœ ๋‚จ์œผ๋ฉด ๋ฉ”์„ธ์ง€๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
             if(moment(resv.start).diff(moment(new Date()), 'minutes') < 60){
               let message = "[Meeting Alert] "+ moment(resv.start).utcOffset('+0900').format(config.dateTimeFormat) + " ~ " + moment(resv.end).utcOffset('+0900').format(config.dateTimeFormat) + ", " + resv.roomid + ", " + resv.purpose;
               let userId = resv.user.userid;

               //DB์—์„œ userId๊ฐ€ ๊ฐ™์€ document๋ฅผ ์ฐพ์•„ telegram์˜ chat id๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ด view ์ƒ์„ฑ์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ /util/db.js์— ์žˆ์Šต๋‹ˆ๋‹ค.
               db.view('context', 'telegram', {key: userId}).then(body => {
                 if(body.total_rows > 0){
                   let user_key = body.rows[0].id;
                   bot.sendMessage(user_key, message);
                 }
               });
              }
        }
      }
    }
  });
});

6.3 ํ…Œ์ŠคํŠธ

1) ๋จผ์ € ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•ด์„œ syntax error๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

npm start

2) ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ธ”๋ฃจ๋ฏน์Šค๋กœ ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค. webhook ๊ธฐ๋Šฅ์€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํผ๋ธ”๋ฆญ ๋„คํŠธ์›Œํฌ๋กœ ๊ณต๊ฐœ๋˜์–ด์•ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

cf push

3) ํ…”๋ ˆ๊ทธ๋žจ ๋ฉ”์‹ ์ €๋ฅผ ํ†ตํ•ด ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.

4) ์˜ˆ์•ฝ ์‹œ๊ฐ„ ์ „์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์•Œ๋ฆผ ๋ฉ”์„ธ์ง€๊ฐ€ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค.

์•ˆ๋…•ํ•˜์„ธ์š”. ์ด ํฌ์ŠคํŒ…์€ Watson์œผ๋กœ ์‰ฝ๊ฒŒ ๋งŒ๋“œ๋Š” ์นด์นด์˜คํ†ก ChatBot ์‹œ๋ฆฌ์ฆˆ์˜ ๋งˆ์ง€๋ง‰ ๊ธ€์ž…๋‹ˆ๋‹ค.
์ด ๊ฒŒ์‹œ๋ฌผ์ด ์ฑ—๋ด‡์„ ์ œ์ž‘ํ•˜์‹œ๋Š” ๋ถ„๋“ค์—๊ฒŒ ๋„์›€์ด ๋˜์…จ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์ด ์‹œ๋ฆฌ์ฆˆ๋ฅผ ์™„๋ฃŒํ•œ ์†Œ์Šค ์ฝ”๋“œ๋Š” ๋งํฌ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.