@tiledesk/tiledesk-server 2.2.10 → 2.2.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,453 @@
1
+
2
+
3
+ 'use strict';
4
+
5
+ const Faq = require('../models/faq');
6
+ const Faq_kb = require('../models/faq_kb');
7
+ const MessageConstants = require('../models/messageConstants');
8
+ var winston = require('../config/winston');
9
+
10
+ var jwt = require('jsonwebtoken');
11
+ const uuidv4 = require('uuid/v4');
12
+
13
+ const { TiledeskChatbotUtil } = require('@tiledesk/tiledesk-chatbot-util');
14
+
15
+ var request = require('retry-request', {
16
+ request: require('request')
17
+ });
18
+
19
+
20
+ var webhook_origin = process.env.WEBHOOK_ORIGIN || "http://localhost:3000";
21
+ winston.debug("webhook_origin: "+webhook_origin);
22
+
23
+ class FaqBotSupport {
24
+
25
+
26
+
27
+
28
+ getMessage(key, lang, labelsObject) {
29
+
30
+
31
+ if (!lang) {
32
+ lang = "EN";
33
+ }
34
+
35
+ lang = lang.toUpperCase();
36
+
37
+ winston.debug('getMessage: ' + key + ' ' + lang+ ' ' + JSON.stringify(labelsObject) );
38
+
39
+ var label = "";
40
+
41
+ try {
42
+ // winston.debug("1");
43
+ label = labelsObject[lang][key];
44
+ // winston.debug("2");
45
+ } catch(e) {
46
+ // winston.debug("Error", e);
47
+ label = labelsObject["EN"][key];
48
+ }
49
+ winston.debug('label: ' + label );
50
+ return label;
51
+
52
+ }
53
+ // usa api di sponziello parseReply: https://github.com/Tiledesk/tiledesk-nodejs-libs/blob/master/tiledesk-chatbot-util/index.js
54
+
55
+ parseMicrolanguage(text, message, bot, faq, disableWebHook, json) {
56
+ var that = this;
57
+ return new Promise(async (resolve, reject) => {
58
+ winston.info('parseMicrolanguage message: ' + JSON.stringify(message) );
59
+
60
+ winston.info('text: '+text);
61
+ var commands = TiledeskChatbotUtil.findSplits(text)
62
+ winston.info('commands: ' + JSON.stringify(commands) );
63
+
64
+ let messageReply;
65
+ // da mettere anche in microlanguage = true
66
+ if (commands.length > 1) {
67
+ commands.forEach(command => {
68
+ if (command.type === "message" && command.text != null) {
69
+ let replyCommand = TiledeskChatbotUtil.parseReply(command.text);
70
+ winston.info('replyCommand: ' + JSON.stringify(replyCommand) );
71
+ let messageCommandReply = replyCommand.message;
72
+
73
+ //TODO merge degli attributi dopo. fai lo stesso codice di giù
74
+ command.message = messageCommandReply;
75
+ }
76
+ });
77
+
78
+ messageReply = message.toObject();
79
+ messageReply.attributes = {commands: commands};
80
+
81
+ winston.info('messageReply: ' + JSON.stringify(messageReply) );
82
+
83
+ } else {
84
+
85
+ var reply = TiledeskChatbotUtil.parseReply(text);
86
+ winston.debug('parseReply: ' + JSON.stringify(reply) );
87
+
88
+ messageReply = reply.message;
89
+
90
+ }
91
+
92
+
93
+
94
+
95
+ var msg_attributes = {"_raw_message": text};
96
+
97
+ // prendi attributi e li mergi
98
+ // metadata prendi da messageReply SOLO SE CI SONO (DIVERSI NULL). Se riesci fai il merge
99
+ // prendi type e text
100
+
101
+ // if (message && message.attributes) {
102
+ // for(const [key, value] of Object.entries(message.attributes)) {
103
+ // msg_attributes[key] = value
104
+ // }
105
+ // }
106
+
107
+ if (json && json.attributes) {
108
+ for(const [key, value] of Object.entries(json.attributes)) {
109
+ msg_attributes[key] = value
110
+ }
111
+ }
112
+
113
+ if (messageReply && messageReply.attributes) {
114
+ for(const [key, value] of Object.entries(messageReply.attributes)) {
115
+ msg_attributes[key] = value
116
+ }
117
+ }
118
+
119
+ messageReply.attributes = msg_attributes;
120
+
121
+ // not used in faqBotHandler but used when the message is returned by webhook (subscription). So you must clone(add) all message fields here.
122
+ // winston.debug('message.language: '+ message.language );
123
+ // if (message.language) {
124
+ // messageReply.language = message.language;
125
+ // }
126
+
127
+ if (json && json.language) {
128
+ messageReply.language = json.language;
129
+ }
130
+
131
+ if (json && json.type) {
132
+ messageReply.type = json.type;
133
+ }
134
+
135
+ if (json && json.metadata) {
136
+ messageReply.metadata = json.metadata;
137
+ }
138
+
139
+
140
+ winston.debug('faq: ', faq );
141
+ if (disableWebHook === false && bot.webhook_enabled ===true && (faq.webhook_enabled === true ))
142
+ //|| reply.webhook))
143
+ {
144
+
145
+ winston.debug("bot.webhook_url "+ bot.webhook_url)
146
+ var webhookurl = bot.webhook_url;
147
+
148
+
149
+ // winston.debug("reply.webhook "+ reply.webhook )
150
+
151
+ // if (reply.webhook) {
152
+ // if (reply.webhook === true) {
153
+ webhookurl = bot.webhook_url;
154
+ // } else {
155
+ // webhookurl = reply.webhook;
156
+ // }
157
+ // }
158
+
159
+ if (!webhookurl) {
160
+ winston.debug("webhookurl is undefined return standard");
161
+ return resolve(messageReply);
162
+ }
163
+
164
+ var botWithSecret = await Faq_kb.findById(bot._id).select('+secret').exec();
165
+
166
+ var signOptions = {
167
+ issuer: 'https://tiledesk.com',
168
+ subject: 'bot',
169
+ audience: 'https://tiledesk.com/bots/'+bot._id,
170
+ jwtid: uuidv4()
171
+ };
172
+
173
+ // TODO metti bot_? a user._id
174
+ var token = jwt.sign(bot.toObject(), botWithSecret.secret, signOptions);
175
+
176
+
177
+ winston.debug("webhookurl "+ webhookurl)
178
+
179
+ return request({
180
+ uri : webhookurl,
181
+ headers: {
182
+ 'Content-Type' : 'application/json',
183
+ 'User-Agent': 'tiledesk-bot',
184
+ 'Origin': webhook_origin
185
+ //'x-hook-secret': s.secret
186
+ },
187
+ method: 'POST',
188
+ json: true,
189
+ body: {payload:{text: text, bot: bot, message: message, intent: faq}, token: token},
190
+ // }).then(response => {
191
+ }, function(err, response, json){
192
+ if (err) {
193
+ winston.error("Error from webhook reply of getParsedMessage. Return standard reply", err);
194
+
195
+ return resolve(messageReply);
196
+
197
+ // return error
198
+ /*
199
+ var bot_answer = {};
200
+ bot_answer.text = err.toString();
201
+ if(response && response.text) {
202
+ bot_answer.text = bot_answer.text + ' '+response.text;
203
+ }
204
+ bot_answer.type = "text";
205
+
206
+ return resolve(bot_answer);
207
+ */
208
+ }
209
+ if (response.statusCode >= 400) {
210
+ winston.verbose("The ChatBot webhook return error http status code. Return standard reply", response);
211
+ return resolve(messageReply);
212
+ }
213
+
214
+ if (!json) { //the webhook return empty body
215
+ winston.verbose("The ChatBot webhook return no json. Return standard reply", response);
216
+ return resolve(messageReply);
217
+ }
218
+
219
+ winston.debug("webhookurl repl_message ", response);
220
+
221
+ var text = undefined;
222
+ if(json && json.text===undefined) {
223
+ winston.verbose("webhookurl json is defined but text not. return standard reply",{json:json, response:response});
224
+ // text = 'Field text is not defined in the webhook respose of the faq with id: '+ faq._id+ ". Error: " + JSON.stringify(response);
225
+ return resolve(messageReply);
226
+ }else {
227
+ text = json.text;
228
+ }
229
+ winston.debug("webhookurl text: "+ text);
230
+
231
+ // // let cloned_message = Object.assign({}, messageReply);
232
+ // let cloned_message = message;
233
+ // winston.debug("cloned_message : ",cloned_message);
234
+
235
+ // if (json.attributes) {
236
+ // if (!cloned_message.attributes) {
237
+ // cloned_message.attributes = {}
238
+ // }
239
+ // winston.debug("ChatBot webhook json.attributes: ",json.attributes);
240
+ // for(const [key, value] of Object.entries(json.attributes)) {
241
+ // cloned_message.attributes[key] = value
242
+ // }
243
+ // }
244
+
245
+ // winston.debug("cloned_message after attributes: ",cloned_message);
246
+
247
+ that.parseMicrolanguage(text, message, bot, faq, true, json).then(function(bot_answer) {
248
+ return resolve(bot_answer);
249
+ });
250
+ });
251
+ }
252
+
253
+ return resolve(messageReply);
254
+ });
255
+ }
256
+
257
+ getParsedMessage(text, message, bot, faq) {
258
+ return this.parseMicrolanguage(text, message, bot, faq, false);
259
+ // return this.parseMicrolanguageOld(text, message, bot, faq);
260
+ }
261
+
262
+ // parseMicrolanguageOld(text, message, bot, faq) {
263
+ // var that = this;
264
+ // // text = "*"
265
+ // return new Promise(function(resolve, reject) {
266
+ // winston.debug("getParsedMessage ******",text);
267
+ // var repl_message = {};
268
+
269
+ // // cerca i bottoni eventualmente definiti
270
+ // var button_pattern = /^\*.*/mg; // buttons are defined as a line starting with an asterisk
271
+ // var text_buttons = text.match(button_pattern);
272
+ // if (text_buttons) {
273
+ // var text_with_removed_buttons = text.replace(button_pattern,"").trim();
274
+ // repl_message.text = text_with_removed_buttons
275
+ // var buttons = []
276
+ // text_buttons.forEach(element => {
277
+ // winston.debug("button ", element)
278
+ // var remove_extra_from_button = /^\*/mg;
279
+ // var button_text = element.replace(remove_extra_from_button, "").trim()
280
+ // var button = {}
281
+ // button["type"] = "text"
282
+ // button["value"] = button_text
283
+ // buttons.push(button)
284
+ // });
285
+ // repl_message.attributes =
286
+ // {
287
+ // attachment: {
288
+ // type:"template",
289
+ // buttons: buttons
290
+ // }
291
+ // }
292
+ // repl_message.type = MessageConstants.MESSAGE_TYPE.TEXT;
293
+ // } else {
294
+ // // no buttons
295
+ // repl_message.text = text
296
+ // repl_message.type = MessageConstants.MESSAGE_TYPE.TEXT;
297
+ // }
298
+
299
+ // var image_pattern = /^\\image:.*/mg;
300
+ // var imagetext = text.match(image_pattern);
301
+ // if (imagetext && imagetext.length>0) {
302
+ // var imageurl = imagetext[0].replace("\\image:","").trim();
303
+ // winston.debug("imageurl ", imageurl)
304
+ // var text_with_removed_image = text.replace(image_pattern,"").trim();
305
+ // repl_message.text = text_with_removed_image + " " + imageurl
306
+ // repl_message.metadata = {src: imageurl, width:200, height:200};
307
+ // repl_message.type = MessageConstants.MESSAGE_TYPE.IMAGE;
308
+ // }
309
+
310
+ // var frame_pattern = /^\\frame:.*/mg;
311
+ // var frametext = text.match(frame_pattern);
312
+ // if (frametext && frametext.length>0) {
313
+ // var frameurl = frametext[0].replace("\\frame:","").trim();
314
+ // winston.debug("frameurl ", frameurl)
315
+ // // var text_with_removed_image = text.replace(frame_pattern,"").trim();
316
+ // // repl_message.text = text_with_removed_image + " " + imageurl
317
+ // repl_message.metadata = {src: frameurl};
318
+ // repl_message.type = MessageConstants.MESSAGE_TYPE.FRAME;
319
+ // }
320
+
321
+
322
+ // var webhook_pattern = /^\\webhook:.*/mg;
323
+ // var webhooktext = text.match(webhook_pattern);
324
+ // if (webhooktext && webhooktext.length>0) {
325
+ // var webhookurl = webhooktext[0].replace("\\webhook:","").trim();
326
+ // winston.debug("webhookurl ", webhookurl)
327
+
328
+ // return request({
329
+ // uri : webhookurl,
330
+ // headers: {
331
+ // 'Content-Type': 'application/json'
332
+ // },
333
+ // method: 'POST',
334
+ // json: true,
335
+ // body: {payload:{text: text, bot: bot, message: message, faq: faq}},
336
+ // // }).then(response => {
337
+ // }, function(err, response, json){
338
+ // if (err) {
339
+ // bot_answer.text = err +' '+ response.text;
340
+ // bot_answer.type = MessageConstants.MESSAGE_TYPE.TEXT;
341
+ // winston.error("Error from webhook reply of getParsedMessage", err);
342
+ // return resolve(bot_answer);
343
+ // }
344
+ // // if (response.statusCode >= 400) {
345
+ // // return reject(`HTTP Error: ${response.statusCode}`);
346
+ // // }
347
+ // winston.debug("webhookurl repl_message ", response);
348
+
349
+ // var text = undefined;
350
+ // if(json && json.text===undefined) {
351
+ // text = 'Field text is not defined in the webhook respose of the faq with id: '+ faq._id+ ". Error: " + JSON.stringify(response);
352
+ // }else {
353
+ // text = json.text;
354
+ // }
355
+
356
+
357
+ // that.getParsedMessage(text,message, bot, faq).then(function(bot_answer) {
358
+ // return resolve(bot_answer);
359
+ // });
360
+ // });
361
+
362
+ // }else {
363
+ // winston.debug("repl_message ", repl_message)
364
+ // return resolve(repl_message);
365
+ // }
366
+
367
+
368
+
369
+ // });
370
+ // }
371
+
372
+
373
+ getBotMessage(botAnswer, projectid, bot, message, threshold) {
374
+ var that = this;
375
+ return new Promise(function(resolve, reject) {
376
+
377
+ winston.debug('botAnswer', botAnswer);
378
+ // var found = false;
379
+ var bot_answer={};
380
+
381
+ if (!botAnswer ) {
382
+
383
+ var query = { "id_project": projectid, "id_faq_kb": bot._id, "question": "defaultFallback"};
384
+ winston.debug('query', query);
385
+
386
+
387
+ Faq.find(query)
388
+ .lean(). //fai cache
389
+ exec(function (err, faqs) {
390
+ if (err) {
391
+ return res.status(500).send({ success: false, msg: 'Error getting object.' });
392
+ }
393
+
394
+ winston.debug("faqs", faqs);
395
+
396
+ if (faqs && faqs.length>0) {
397
+ winston.debug("faqs exact", faqs);
398
+
399
+ bot_answer.text=faqs[0].answer;
400
+
401
+ winston.debug("bot_answer exact", bot_answer);
402
+ // found = true;
403
+ // return resolve(bot_answer);
404
+
405
+ if (message.channel.name == "chat21") { //why this contition on chat21 channel? bacause only chat21 support parsed replies?
406
+ winston.debug("faqBotSupport message.channel.name is chat21",message);
407
+ that.getParsedMessage(bot_answer.text,message, bot, faqs[0]).then(function(bot_answerres) {
408
+
409
+ bot_answerres.defaultFallback=true;
410
+
411
+ return resolve(bot_answerres);
412
+ });
413
+
414
+ } else {
415
+ winston.debug("faqBotSupport message.channel.name is not chat21 returning default",message);
416
+ return resolve(bot_answer);
417
+ }
418
+
419
+ } else {
420
+ var message_key = "DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE";
421
+ bot_answer.text = that.getMessage(message_key, message.language, faqBotSupport.LABELS);
422
+ bot_answer.defaultFallback = true;
423
+ // console.log("bot_answer ", bot_answer)
424
+ return resolve(bot_answer);
425
+ }
426
+ });
427
+ }
428
+
429
+
430
+ });
431
+
432
+ }
433
+
434
+
435
+
436
+ }
437
+
438
+
439
+ var faqBotSupport = new FaqBotSupport();
440
+
441
+ faqBotSupport.LABELS = {
442
+ EN : {
443
+ DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE: "I did not find an answer in the knowledge base. \n Please reformulate your question?"
444
+ },
445
+ IT : {
446
+ DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE: "Non sono in grado di fornirti una risposta adeguata. \n Prego riformula la domanda."
447
+ },
448
+ "IT-IT" : {
449
+ DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE: "Non sono in grado di fornirti una risposta adeguata. \n Prego riformula la domanda."
450
+ }
451
+ }
452
+
453
+ module.exports = faqBotSupport;
@@ -85,6 +85,7 @@ class FaqService {
85
85
  question: faq.question,
86
86
  answer: faq.answer,
87
87
  intent_display_name: faq.intent_display_name,
88
+ language: "en",
88
89
  id_project: projectid,
89
90
  topic: faq.topic,
90
91
  createdBy: created_by,
@@ -121,7 +121,7 @@ class MessageService {
121
121
 
122
122
  return newMessage.save(function(err, savedMessage) {
123
123
  if (err) {
124
- winston.error(err);
124
+ winston.error("Error saving the message", {err:err, message: message, newMessage: newMessage});
125
125
  return reject(err);
126
126
  }
127
127
  winston.verbose("Message created", savedMessage.toObject());
package/test/faqRoute.js CHANGED
@@ -38,7 +38,7 @@ describe('FaqKBRoute', () => {
38
38
  chai.request(server)
39
39
  .post('/'+ savedProject._id + '/faq_kb')
40
40
  .auth(email, pwd)
41
- .send({"name":"testbot", type: "external"})
41
+ .send({"name":"testbot", type: "internal"})
42
42
  .end((err, res) => {
43
43
  //console.log("res", res);
44
44
  console.log("res.body", res.body);
@@ -77,6 +77,60 @@ describe('FaqKBRoute', () => {
77
77
 
78
78
 
79
79
 
80
+ it('createWithLanguage', (done) => {
81
+
82
+
83
+ // this.timeout();
84
+
85
+ var email = "test-signup-" + Date.now() + "@email.com";
86
+ var pwd = "pwd";
87
+
88
+ userService.signup( email ,pwd, "Test Firstname", "Test lastname").then(function(savedUser) {
89
+ projectService.create("test-faqkb-create", savedUser._id).then(function(savedProject) {
90
+ chai.request(server)
91
+ .post('/'+ savedProject._id + '/faq_kb')
92
+ .auth(email, pwd)
93
+ .send({"name":"testbot", type: "internal", language: "it"})
94
+ .end((err, res) => {
95
+ //console.log("res", res);
96
+ console.log("res.body", res.body);
97
+ res.should.have.status(200);
98
+ res.body.should.be.a('object');
99
+ expect(res.body.name).to.equal("testbot");
100
+ expect(res.body.language).to.equal("it");
101
+ var id_faq_kb = res.body._id;
102
+
103
+ chai.request(server)
104
+ .post('/'+ savedProject._id + '/faq')
105
+ .auth(email, pwd)
106
+ .send({id_faq_kb: id_faq_kb, question: "question1", answer: "answer1"})
107
+ .end((err, res) => {
108
+ //console.log("res", res);
109
+ console.log("res.body", res.body);
110
+ res.should.have.status(200);
111
+ res.body.should.be.a('object');
112
+ expect(res.body.id_faq_kb).to.equal(id_faq_kb);
113
+ expect(res.body.question).to.equal("question1");
114
+ expect(res.body.answer).to.equal("answer1");
115
+ expect(res.body.intent_display_name).to.not.equal(undefined);
116
+ expect(res.body.webhook_enabled).to.equal(false);
117
+ expect(res.body.language).to.equal("it");
118
+ done();
119
+ });
120
+
121
+ });
122
+
123
+
124
+ });
125
+ });
126
+
127
+ });
128
+
129
+
130
+
131
+
132
+
133
+
80
134
  it('createWithIntentDisplayNameAndWebhookEnalbed', (done) => {
81
135
 
82
136
 
@@ -91,7 +145,7 @@ describe('FaqKBRoute', () => {
91
145
  chai.request(server)
92
146
  .post('/'+ savedProject._id + '/faq_kb')
93
147
  .auth(email, pwd)
94
- .send({"name":"testbot", type: "external"})
148
+ .send({"name":"testbot", type: "internal"})
95
149
  .end((err, res) => {
96
150
  //console.log("res", res);
97
151
  console.log("res.body", res.body);
@@ -143,7 +197,7 @@ describe('FaqKBRoute', () => {
143
197
  chai.request(server)
144
198
  .post('/'+ savedProject._id + '/faq_kb')
145
199
  .auth(email, pwd)
146
- .send({"name":"testbot", type: "external"})
200
+ .send({"name":"testbot", type: "internal"})
147
201
  .end((err, res) => {
148
202
  //console.log("res", res);
149
203
  console.log("res.body", res.body);
@@ -210,7 +264,7 @@ describe('FaqKBRoute', () => {
210
264
  chai.request(server)
211
265
  .post('/'+ savedProject._id + '/faq_kb')
212
266
  .auth(email, pwd)
213
- .send({"name":"testbot", type: "external"})
267
+ .send({"name":"testbot", type: "internal"})
214
268
  .end((err, res) => {
215
269
  //console.log("res", res);
216
270
  console.log("res.body", res.body);
@@ -243,6 +297,60 @@ describe('FaqKBRoute', () => {
243
297
 
244
298
  });
245
299
 
300
+
301
+
302
+
303
+
304
+
305
+
306
+ it('uploadcsvWithLanguage', (done) => {
307
+
308
+
309
+ // this.timeout();
310
+
311
+ var email = "test-signup-" + Date.now() + "@email.com";
312
+ var pwd = "pwd";
313
+
314
+ userService.signup( email ,pwd, "Test Firstname", "Test lastname").then(function(savedUser) {
315
+ projectService.create("test-uploadcsv", savedUser._id).then(function(savedProject) {
316
+
317
+ chai.request(server)
318
+ .post('/'+ savedProject._id + '/faq_kb')
319
+ .auth(email, pwd)
320
+ .send({"name":"testbot", type: "internal", language: "it"})
321
+ .end((err, res) => {
322
+ //console.log("res", res);
323
+ console.log("res.body", res.body);
324
+ res.should.have.status(200);
325
+ res.body.should.be.a('object');
326
+ expect(res.body.name).to.equal("testbot");
327
+ expect(res.body.language).to.equal("it");
328
+ var id_faq_kb = res.body._id;
329
+
330
+ chai.request(server)
331
+ .post('/'+ savedProject._id + '/faq/uploadcsv')
332
+ .auth(email, pwd)
333
+ .set('Content-Type', 'text/csv')
334
+ .attach('uploadFile', fs.readFileSync('./test/example-faqs.csv'), 'example-faqs.csv')
335
+ .field('id_faq_kb', id_faq_kb)
336
+ .field('delimiter', ';')
337
+ // .send({id_faq_kb: id_faq_kb})
338
+ .end((err, res) => {
339
+ console.log("err", err);
340
+ console.log("res.body", res.body);
341
+ res.should.have.status(200);
342
+ res.body.should.be.a('object');
343
+
344
+ done();
345
+ });
346
+
347
+ });
348
+
349
+ });
350
+ });
351
+
352
+ });
353
+
246
354
 
247
355
 
248
356