티스토리 뷰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 | 'use strict'; const uuid = require('uuid').v4; const _ = require('lodash'); const request = require('request'); // request const Promise = require('promise'); var xml2js = require('xml2js'); var parser = new xml2js.Parser(); exports.clova_icn = (httpReq, httpRes) => { let endText = ' 하실 말을 해 주세요.'; // 퀄리티 용 // 취업좀 시켜주세요 ㅜㅜ //switch : GET: Web Page console.log('Request body: ' + JSON.stringify(httpReq.body)); let requestBody = httpReq.body; console.log('Request method: ' + httpReq.method); // data 집합. JSON으로 처리 let arrays = [{ code: "터미널1게이트1", // 건드리지 말것 name: "터미널1 게이트1", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1-1.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널1의 게이트1 대기인원과 혼잡도", etc: '' }, { code: "터미널1게이트2", name: "터미널1 게이트2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1-2.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널1의 게이트2 대기인원과 혼잡도", etc: '' }, { code: "터미널1게이트3", name: "터미널1 게이트3", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1-3.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널1의 게이트3 대기인원과 혼잡도", etc: '' }, { code: "터미널1게이트4", name: "터미널1 게이트4", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1-4.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널1의 게이트4 대기인원과 혼잡도", etc: '' }, { code: "터미널1게이트5", name: "터미널1 게이트5", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1-5.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널1의 게이트5 대기인원과 혼잡도", etc: '' }, { code: "터미널1게이트6", name: "터미널1 게이트5", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1-6.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널1의 게이트6 대기인원과 혼잡도", etc: '' }, { code: "터미널2게이트1", name: "터미널2 게이트1", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2-1.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널2의 게이트1 대기인원과 혼잡도", etc: '' }, { code: "터미널2게이트2", name: "터미널2 게이트2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2-2.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널2의 게이트2 대기인원과 혼잡도", etc: '' }, { code: "터미널2게이트3", name: "터미널2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널2", etc: '더미' }, { code: "터미널2게이트4", name: "터미널2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널2", etc: '더미' }, { code: "터미널2게이트5", name: "터미널2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널2", etc: '더미' }, { code: "터미널2게이트6", name: "터미널2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2.jpg", infoText: "게이트의 위치는 사진과 같습니다.", title: "터미널2", etc: '더미' }, { code: "터미널1", name: "터미널1", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal1.jpg", infoText: "터미널의 위치는 사진과 같습니다.", title: "터미널1", etc: '' }, { code: "터미널2", name: "터미널2", imageLink: "https://storage.googleapis.com/finalrussianroulette.appspot.com/incheonAirWait/terminal2.jpg", infoText: "터미널의 위치는 사진과 같습니다.", title: "터미널2", etc: '' }]; // getTerminalGateXmlConnect function getTerminalGateXmlConnect(terminalNum, callback) { console.log("terminalNum: " + terminalNum); let insertTerminal = ''; //터미널에 따른 링크 생성시 터미널 번호 붙임 if (terminalNum == "터미널1") { insertTerminal = 1; } else { insertTerminal = 2; } var url = "http://openapi.airport.kr/openapi/service/StatusOfDepartures/getDeparturesCongestion?ServiceKey=h8JLcESwAVXzmfaef3OAz81CQZ1uW5S8fgY7Et46VPk2hAdlqCBHbHPskMq4wO9NDf32iV7yqiZSgnAjVWtP7g%3D%3D&terno=" + insertTerminal; console.log("url: " + url); // Get xml data request({ url: url, encoding: null, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/603.1.1 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7' } }, function(err, resp, body) { if (err) { callback(err, { code: 400, name: '', status: 'fail' }); return; } // xml parsing section let xml = body.toString(); console.log("body: " + body.toString()); parser.parseString(xml, function(err, result) { console.log(result); console.log(JSON.stringify(result)); // < SearchSTNInfoByFRCodeService > // <list_total_count>1</list_total_count> // <RESULT> // <CODE>INFO-000</CODE> // <MESSAGE>정상 처리되었습니다</MESSAGE> // </RESULT> var original = result.response.body[0].items[0].item[0]; var status = result.response.header[0].resultCode[0]; console.log("status: " + status); if (status === '00') { //00 에러없음 //search arrays //미래 api를 위해서 검색시 다 가져오기 let getValue = arrays.find(item => { return item.code == terminalNum; }); console.log(JSON.stringify(getValue)); //터미널1과 2의 정보는 다름으로 그냥 이렇게 처리 //push로 처리할수 있지만 시각적으로 보기 위해서. if (insertTerminal == '1') { callback(null, { code: 200, gateinfo1: original.gateinfo1[0], gateinfo2: original.gateinfo2[0], gateinfo3: original.gateinfo3[0], gateinfo4: original.gateinfo4[0], status: 'success', cgtdt: original.cgtdt[0], cgthm: original.cgthm[0], imageLink: getValue.imageLink, title: getValue.title, name: getValue.name, infoText: getValue.infoText, staionCode: getValue.code }); } else { //terminal2 callback(null, { code: 200, gateinfo1: original.gateinfo1[0], gateinfo2: original.gateinfo2[0], status: 'success', cgtdt: original.cgtdt[0], cgthm: original.cgthm[0], imageLink: getValue.imageLink, title: getValue.title, name: getValue.name, infoText: getValue.infoText, staionCode: getValue.code }); } } else { callback(err, { code: 400, name: '', status: 'fail' }); } }); }); } //getStationXmlConnect // Promise로 처리 function getInfo(terminalNum) { return new Promise(function(resolved, rejected) { getTerminalGateXmlConnect(terminalNum, function(err, result) { if (err) { rejected(err); } else { resolved(result); } }); }); } //GET POST 관리 || httpReq.method switch (httpReq.method) { case 'GET': // policy를 위한 페이지임 httpRes.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); var title = 'Private Policy'; var body = '<p>Private Policy</p>'; var code = [ '<!DOCTYPE html>', '<html>', '<head>', '<meta charset="utf-8" />', '<title>' + title + '</title>', '</head>', '<body>', body, '</body>', '</html>' ].join('\n'); httpRes.write(code, "utf8"); httpRes.end(); break; // Get break default: // post //========================================================================== //=========================여기서 부터 POST 처리============================= //========================================================================== console.log("Post come") //request const requests = httpReq.body.request; //context const contexts = httpReq.body.context; //session const sessions = httpReq.body.session; //sessionId const sessionId = sessions.sessionId; //userId const userid = sessions.user.userId; //accessToken const accessToken = sessions.user.accessToken; //newComer : 이걸 이용하면 방문한 사람이면 실행시 설명 생략 가능. const newComer = sessions.new; //true false //DEBUG console.log("newComer: " + newComer) console.log(`HttpRequest: ${JSON.stringify(requests)}, ${JSON.stringify(contexts)}`) //response json 필드. 여기서 json을 만들어준다. function makeJson(str, endField) { let JsonField = { "version": "0.1.0", "sessionAttributes": {}, "response": { "outputSpeech": { "type": "SimpleSpeech", "values": { "type": "PlainText", "lang": "ko", "value": str } }, "card": {}, "directives": [], "shouldEndSession": endField } }; return JsonField; } //이미지필드, 그런데 안되는데? function makeJsonCard(str, card, endField) { let JsonField = { "version": "0.1.0", "sessionAttributes": {}, "response": { "outputSpeech": { "type": "SimpleSpeech", "values": { "type": "PlainText", "lang": "ko", "value": str } }, "card": card, "directives": [], "shouldEndSession": endField } }; return JsonField; } // Welcome launchRequest 처리 function function launchRequest(httpRes) { const Endfiled = false; let displayText = ''; if (newComer) { //true displayText = '안녕하세요 인천공항 출국장 앱입니다. 이 앱은 인천 공항의 출국장 대기인원을 알려드리는 앱입니다. 터미널1 터미널2 가 지원되고 있습니다.' + endText; } else { displayText = '안녕하세요 인천공항 출국장 앱입니다. 이미 사용해 보셨으니 아시죠?' + endText; } let result = makeJson(displayText, Endfiled); return httpRes.send(result); } // launchRequest // 대화 이해 실패시 불러옴. function fallback(httpRes) { const Endfiled = false; let displayText = '죄송합니다 잘 이해하지 못했습니다. 터미널1 혹은 터미널2 이렇게 말해주세요.' + endText; let result = makeJson(displayText, Endfiled); return httpRes.send(result); } //터미널 정보 불러오는 부분 Async && Await처리 // Await는 new Promise의 resolve를 받고 있습니다. async function terminal_info(slots) { const Endfiled = false; let displayText = ''; const result = await getInfo(slots); let imageLink = ''; if (result.code != 200) { //문제있음 displayText = "현재 서버에 문제가 있어서 연결할 수 없습니다. 다음에 다시 시도해 주세요." + endText; } else { // no problem //gate terminal 1: 1~4 terminal2: 1~2 let gateinfo1 = result.gateinfo1; let gateinfo2 = result.gateinfo2; let gateinfo3 = ''; let gateinfo4 = ''; imageLink = result.imageLink; let cgtdt = result.cgtdt; //date let cgthm = result.cgthm; // time: ex 2305 console.log("let result : " + result); console.log("let result stringify : " + JSON.stringify(result)); //text make if (slots == '터미널1') { gateinfo3 = result.gateinfo3; gateinfo4 = result.gateinfo4; displayText = slots + '의 전체 대기인원은 \n게이트2 : ' + gateinfo1 + '명 \n게이트3 : ' + gateinfo2 + '명 \n게이트4 : ' + gateinfo3 + '명 \n게이트5 : ' + gateinfo4 + '명 입니다. ' + endText; } else { // 터미널2 displayText = slots + '의 전체 대기인원은 \n게이트1 : ' + gateinfo1 + '명 \n게이트2 : ' + gateinfo2 + '명 입니다. ' + endText; } console.log("displayText : " + displayText); } //Card Image 만들기 let card = { "type": "ImageList", "thumbImageUrlList": [{ "imageReference": { "type": "string", "value": "" }, "imageTitle": { "type": "string", "value": "터미널 결과다" }, "imageUrl": { "type": "url", "value": "" }, "referenceText": { "type": "string", "value": "터미널 결과" }, "referenceUrl": { "type": "url", "value": "" }, "thumbImageUrl": { "type": "url", "value": imageLink } }] } let resultJson = makeJsonCard(displayText, card, Endfiled); return httpRes.send(resultJson); } function guide_func(httpRes) { const Endfiled = false; let displayText = '도움말입니다. 이 앱은 인천 공항 출국장의 대기인원을 알려드리는 앱입니다. 터미널1의 게이트2부터 5까지 지원되며 터미널2의 게이트1,2 가 지원됩니다. 대기인원이 0명이면 운영하지 않는 것일수도 있습니다. ' + endText; let resultJson = makeJson(displayText, Endfiled); return httpRes.send(resultJson); } //End text 처리 function SessionEndedRequest(httpRes) { //시작후 바로 종료할 예정이기 때문에 true로 한다. 대화 이어나갈 거면 false const Endfiled = true; let displayText = '인천공항 출국장 앱을 종료합니다. 이용해 주셔서 감사합니다!'; let result = makeJson(displayText, Endfiled); return httpRes.send(result); } // SessionEndedRequest // 메인 intent switch로 구분합니다. // 아직 클로바가 버그 많아서 intentsName이 이상한게 들어오는데 // 그냥 default로 처리하는게 좋을거 같습니다. function intent_select(httpRes) { const intentsName = requests.intent.name; console.log("intents : " + intentsName); switch (intentsName) { case 'TerminalInfo': const slots = requests.intent.slots.Terminal.value; if (slots === null) { //slots에 없는데 들어오는 버그 있음. 막아야 함. fallback(httpRes); } else { console.log("slots: " + slots); terminal_info(slots); } break; //'Clova.xxxxxx 붙은건 기본 처리 Built-in Intent 가 작동한 것. //그냥 default로 처리하는 것도 가능하지만 //도움말과(GuideIntent) 지정되지 않은 대화 - 대화이해실패 (NoIntent)는 // 처리하는게 좋습니다. //도움말 부분 case 'Clova.GuideIntent': guide_func(httpRes); break; //fallback 부분 case 'Clova.NoIntent': fallback(httpRes); break; // 취소 , 이것도 의도 아닌데 이렇게 들어옴. 막아야함 case 'Clova.CancelIntent': fallback(httpRes); break; case 'exit': // 몇가지 명령어가 먹히지 않은 관계로 수작업 처리 SessionEndedRequest(httpRes); break; default: fallback(httpRes); break; } } //type name const LAUNCH_REQUEST = 'LaunchRequest'; const INTENT_REQUEST = 'IntentRequest'; const SESSION_ENDED_REQUEST = 'SessionEndedRequest'; // Intent가 오는 부분 switch (requests.type) { // 최초 실행시 오는 intent. LaunchRequest만 쓴다. case LAUNCH_REQUEST: return launchRequest(httpRes) //INTENT_REQUEST의 경우 하위 function에서 switch로 intent를 처리합니다. case INTENT_REQUEST: return intent_select(httpRes) case SESSION_ENDED_REQUEST: return SessionEndedRequest(httpRes) } //switch requests.type break; // default end } //switch } | cs |
클로바 인천공항 출국장 챗봇 앱 코드이다.
클로바 개발 페이지에는 아예 서버 한대를 돌리는 구조가 예시인데..
이건 돈이 많이 들어서 개인 개발자는 그렇게 까지 못할거같다.
그래서 클라우드 중에서 가장 싼걸로 돌리는방법이다.
저걸로 심시가 통과되었으니 문제는 없는 코드일것이다.
맨 처음은 Intent가 오는 부분 으로 시작되는 Switch부분이다. 여기서 시작, 일반 인텐트, 종료 인텐트로 나뉜다.
Json으로 오는 걸 분해해서 쓴다.
function intent_select(httpRes) 에서는 일반 인텐트를 구분한다. 여기서도 Switch문
코드에 보다시피 아직은 버그가 많은지 Intent를 완전하게 이해를 하지 못하고 있다.
그전에 한국어 인식부터가 애매한것도 있고.
나름 개발자 페이지 보면서 이해하는데 시간을 걸렸지만 이런식으로 만들면 되는 거 같다.
'프로그래밍 > 챗봇 개발' 카테고리의 다른 글
[구글 어시스턴트] Built-in intents 적용하기 (0) | 2018.11.07 |
---|---|
[구글 어시스턴트] 위치정보 얻기 & FollowUp 인텐트 사용방법 (0) | 2018.10.11 |
구글 어시스턴트 모두의 마피아 제작 후기 (2) | 2018.09.05 |
[구글 어시스턴트] dialogflow + account link 앱 만들기 (0) | 2018.06.21 |
[구글 어시스턴트] 순수하게 응답을 json으로 return 하여 보내고싶을때. (0) | 2018.06.13 |