@tiledesk/tiledesk-server 2.2.14 → 2.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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("Error savig the message", {err:err, message: message, newMessage: newMessage});
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