@tiledesk/tiledesk-tybot-connector 0.2.601-rc1 → 0.3.0

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +378 -1
  2. package/ExtApi.js +6 -6
  3. package/TdCache copy.js +242 -0
  4. package/TdCache.js +81 -176
  5. package/TdCache_v3.js +261 -0
  6. package/TiledeskExpression.js +7 -3
  7. package/index.js +290 -43
  8. package/logs/app.log +279 -0
  9. package/models/IntentsMachineFactory.js +5 -2
  10. package/models/MockBotsDataSource.js +19 -11
  11. package/models/TiledeskChatbot.js +97 -79
  12. package/models/TiledeskChatbotConst.js +12 -17
  13. package/models/TiledeskChatbotUtil.js +353 -109
  14. package/models/TiledeskIntentsMachine.js +1 -1
  15. package/models/faqKbService.js +1 -1
  16. package/package.json +7 -6
  17. package/tiledeskChatbotPlugs/DirectivesChatbotPlug.js +172 -106
  18. package/tiledeskChatbotPlugs/Filler.js +13 -2
  19. package/tiledeskChatbotPlugs/TildeskContextForCodeOrchestrator.js +8 -0
  20. package/tiledeskChatbotPlugs/WebhookChatbotPlug.js +13 -7
  21. package/tiledeskChatbotPlugs/directives/DirAddTags.js +374 -0
  22. package/tiledeskChatbotPlugs/directives/DirAiPrompt.js +476 -0
  23. package/tiledeskChatbotPlugs/directives/DirAskGPT.js +16 -19
  24. package/tiledeskChatbotPlugs/directives/DirAskGPTV2.js +221 -34
  25. package/tiledeskChatbotPlugs/directives/DirAssign.js +0 -11
  26. package/tiledeskChatbotPlugs/directives/DirAssignFromFunction.js +11 -21
  27. package/tiledeskChatbotPlugs/directives/DirAssistant.js +728 -0
  28. package/tiledeskChatbotPlugs/directives/DirBrevo.js +353 -0
  29. package/tiledeskChatbotPlugs/directives/DirCaptureUserReply.js +3 -30
  30. package/tiledeskChatbotPlugs/directives/DirClearTranscript.js +22 -0
  31. package/tiledeskChatbotPlugs/directives/DirClose.js +16 -3
  32. package/tiledeskChatbotPlugs/directives/DirCode.js +1 -1
  33. package/tiledeskChatbotPlugs/directives/DirCondition.js +0 -26
  34. package/tiledeskChatbotPlugs/directives/DirConnectBlock.js +183 -0
  35. package/tiledeskChatbotPlugs/directives/DirContactUpdate.js +121 -0
  36. package/tiledeskChatbotPlugs/directives/DirCustomerio.js +5 -8
  37. package/tiledeskChatbotPlugs/directives/DirDeflectToHelpCenter.js +11 -1
  38. package/tiledeskChatbotPlugs/directives/DirDepartment.js +15 -6
  39. package/tiledeskChatbotPlugs/directives/DirFireTiledeskEvent.js +17 -6
  40. package/tiledeskChatbotPlugs/directives/DirForm.js +12 -2
  41. package/tiledeskChatbotPlugs/directives/DirGptTask.js +83 -38
  42. package/tiledeskChatbotPlugs/directives/DirGptTask_OLD.js +4 -7
  43. package/tiledeskChatbotPlugs/directives/DirHubspot.js +5 -8
  44. package/tiledeskChatbotPlugs/directives/DirIfOnlineAgents.js +14 -27
  45. package/tiledeskChatbotPlugs/directives/DirIfOnlineAgentsV2.js +278 -0
  46. package/tiledeskChatbotPlugs/directives/DirIfOpenHours.js +147 -51
  47. package/tiledeskChatbotPlugs/directives/DirIfOpenHours_OLD.js +125 -0
  48. package/tiledeskChatbotPlugs/directives/DirIntent.js +8 -36
  49. package/tiledeskChatbotPlugs/directives/DirJSONCondition.js +5 -26
  50. package/tiledeskChatbotPlugs/directives/DirMessage.js +19 -17
  51. package/tiledeskChatbotPlugs/directives/DirMessageToBot.js +136 -0
  52. package/tiledeskChatbotPlugs/directives/DirMoveToAgent.js +20 -87
  53. package/tiledeskChatbotPlugs/directives/DirMoveToUnassigned.js +59 -0
  54. package/tiledeskChatbotPlugs/directives/DirQapla.js +6 -9
  55. package/tiledeskChatbotPlugs/directives/DirRandomReply.js +17 -7
  56. package/tiledeskChatbotPlugs/directives/DirRemoveCurrentBot.js +17 -7
  57. package/tiledeskChatbotPlugs/directives/DirReplaceBot.js +11 -2
  58. package/tiledeskChatbotPlugs/directives/DirReplaceBotV2.js +135 -21
  59. package/tiledeskChatbotPlugs/directives/DirReplaceBotV3.js +163 -0
  60. package/tiledeskChatbotPlugs/directives/DirReply.js +42 -9
  61. package/tiledeskChatbotPlugs/directives/DirReplyV2.js +347 -0
  62. package/tiledeskChatbotPlugs/directives/DirSendEmail.js +13 -23
  63. package/tiledeskChatbotPlugs/directives/DirSendWhatsapp.js +247 -0
  64. package/tiledeskChatbotPlugs/directives/DirSetAttributeV2.js +202 -15
  65. package/tiledeskChatbotPlugs/directives/DirSetConversationTags.js +13 -4
  66. package/tiledeskChatbotPlugs/directives/DirWait.js +21 -4
  67. package/tiledeskChatbotPlugs/directives/DirWebRequest.js +1 -2
  68. package/tiledeskChatbotPlugs/directives/DirWebRequestV2.js +166 -103
  69. package/tiledeskChatbotPlugs/directives/DirWhatsappByAttribute.js +2 -60
  70. package/tiledeskChatbotPlugs/directives/Directives.js +16 -1
  71. /package/tiledeskChatbotPlugs/directives/{DirOfflineHours.js → DEPRECATED_DirOfflineHours.js} +0 -0
@@ -10,7 +10,6 @@ class DirWebRequestV2 {
10
10
  throw new Error('context object is mandatory.');
11
11
  }
12
12
  this.context = context;
13
- this.tdclient = context.tdclient;
14
13
  this.tdcache = context.tdcache;
15
14
  this.requestId = context.requestId;
16
15
  this.intentDir = new DirIntent(context);
@@ -35,36 +34,80 @@ class DirWebRequestV2 {
35
34
 
36
35
  async go(action, callback) {
37
36
  if (this.log) {console.log("webRequest action:", JSON.stringify(action));}
38
- let requestVariables = null;
37
+ let requestAttributes = null;
39
38
  if (this.tdcache) {
40
- requestVariables =
39
+ requestAttributes =
41
40
  await TiledeskChatbot.allParametersStatic(
42
41
  this.tdcache, this.requestId
43
42
  );
44
43
  }
45
44
  const filler = new Filler();
46
- const url = filler.fill(action.url, requestVariables);
45
+ const url = filler.fill(action.url, requestAttributes);
47
46
 
48
47
  let headers = {};
49
48
  if (action.headersString) {
50
49
  let headersDict = action.headersString
51
50
  for (const [key, value] of Object.entries(headersDict)) {
52
51
  if (this.log) {console.log("header:", key, "value:", value)}
53
- let filled_value = filler.fill(value, requestVariables);
52
+ let filled_value = filler.fill(value, requestAttributes);
54
53
  headers[key] = filled_value;
55
54
  }
56
55
  }
56
+
57
57
  let json = null;
58
- if (action.jsonBody && action.bodyType == "json") {
59
- if (this.log) {console.log("action.body is:", action.jsonBody);}
60
- let jsonBody = filler.fill(action.jsonBody, requestVariables);
61
- try {
62
- json = JSON.parse(jsonBody);
63
- if (this.log) {console.log("json is:", json);}
58
+ try {
59
+ if (action.jsonBody && action.bodyType == "json") {
60
+ if (this.log) {console.log("action.body is:", action.jsonBody);}
61
+ let jsonBody = filler.fill(action.jsonBody, requestAttributes);
62
+ try {
63
+ json = JSON.parse(jsonBody);
64
+ if (this.log) {console.log("json is:", json);}
65
+ }
66
+ catch(err) {
67
+ console.error("Error parsing webRequest jsonBody:", jsonBody);
68
+ }
69
+ }
70
+ else if (action.formData && action.bodyType == "form-data") {
71
+ let formData = filler.fill(action.formData, requestAttributes);
72
+ if (this.log) {console.log("action.body is form-data:", formData);}
73
+ // // fill
74
+ if (formData && formData.length > 0) {
75
+ for (let i = 0; i < formData.length; i++) {
76
+ let field = formData[i];
77
+ if (field.value) {
78
+ field.value = filler.fill(field.value, requestAttributes);
79
+ if (this.log) {console.log("field filled:", field.value);}
80
+ }
81
+ }
82
+ }
83
+ json = {};
84
+ for (let i = 0; i < formData.length; i++) {
85
+ let field = formData[i];
86
+ if (field.enabled && field.value && field.type === "URL") {
87
+ if (this.log) {console.log("Getting file:", field.value);}
88
+ let response = await axios.get(field.value,
89
+ {
90
+ responseType: 'stream'
91
+ }
92
+ );
93
+ let stream = response.data;
94
+ // if (this.log) {console.log("Stream data:", stream);}
95
+ json[field.name] = stream;
96
+ // process.exit(0);
97
+ }
98
+ else if (field.enabled && field.value && field.type === "Text") {
99
+ json[field.name] = field.value;
100
+ }
101
+ }
102
+ if (this.log) {console.log("final json:", json);}
64
103
  }
65
- catch(err) {
66
- console.error("Error parsing webRequest jsonBody:", jsonBody);
104
+ else {
105
+ if (this.log) {console.log("no action upload parts");}
67
106
  }
107
+
108
+ }
109
+ catch(error) {
110
+ console.error("Error", error);
68
111
  }
69
112
 
70
113
  // Condition branches
@@ -81,8 +124,9 @@ class DirWebRequestV2 {
81
124
  }
82
125
 
83
126
  let timeout = this.#webrequest_timeout(action, 20000, 1, 300000);
84
-
127
+
85
128
  if (this.log) {console.log("webRequest URL", url);}
129
+
86
130
  const HTTPREQUEST = {
87
131
  url: url,
88
132
  headers: headers,
@@ -90,6 +134,7 @@ class DirWebRequestV2 {
90
134
  method: action.method,
91
135
  timeout: timeout
92
136
  };
137
+
93
138
  if (this.log) {console.log("webRequest HTTPREQUEST", HTTPREQUEST);}
94
139
  this.#myrequest(
95
140
  HTTPREQUEST, async (err, res) => {
@@ -203,63 +248,13 @@ class DirWebRequestV2 {
203
248
  }
204
249
 
205
250
  #myrequest(options, callback) {
206
- if (this.log) {
207
- console.log("API URL:", options.url);
208
- console.log("** Options:", JSON.stringify(options));
209
- }
210
- let axios_options = {
211
- url: options.url,
212
- method: options.method,
213
- params: options.params,
214
- headers: options.headers,
215
- timeout: 20000
216
- }
217
- if (options.json !== null) {
218
- axios_options.data = options.json
219
- }
220
- if (this.log) {
221
- console.log("axios_options:", JSON.stringify(axios_options));
222
- }
223
- if (options.url.startsWith("https:")) {
224
- const httpsAgent = new https.Agent({
225
- rejectUnauthorized: false,
226
- });
227
- axios_options.httpsAgent = httpsAgent;
228
- }
229
- axios(axios_options)
230
- .then((res) => {
231
- if (this.log) {
232
- console.log("Success Response:", res);
233
- console.log("Response for url:", options.url);
234
- console.log("Response headers:\n", JSON.stringify(res.headers));
235
- }
236
- if (callback) {
237
- callback(null, res);
238
- }
239
- // if (callback) {
240
- // let data = null;
241
- // let status = 1000;
242
- // if (res) {
243
- // status = res.status;
244
- // data = res.data;
245
- // }
246
- // callback(
247
- // {
248
- // status: status,
249
- // data: data
250
- // }, null
251
- // );
252
- // }
253
-
254
- })
255
- .catch( (err) => {
251
+ try {
256
252
  if (this.log) {
257
- if (err.response) {
258
- console.log("Error Response data:", err.response.data);
259
- }
260
- // FIX THE STRINGIFY OF CIRCULAR STRUCTURE BUG - START
253
+ console.log("API URL:", options.url);
254
+ //console.log("** Options:", JSON.stringify(options));
255
+ // Stringify "options". FIX THE STRINGIFY OF CIRCULAR STRUCTURE BUG - START
261
256
  let cache = [];
262
- let error_log = JSON.stringify(err, function(key, value) { // try to use a separate function
257
+ let str_Options = JSON.stringify(options, function(key, value) { // try to use a separate function
263
258
  if (typeof value === 'object' && value != null) {
264
259
  if (cache.indexOf(value) !== -1) {
265
260
  return;
@@ -268,52 +263,120 @@ class DirWebRequestV2 {
268
263
  }
269
264
  return value;
270
265
  });
271
- console.error("An error occurred: ", error_log);
272
- // FIX THE STRINGIFY OF CIRCULAR STRUCTURE BUG - END
273
- // console.error("An error occurred:", JSON.stringify(err));
266
+ console.log("** Options:", str_Options);
274
267
  }
275
- if (callback) {
276
- let status = 1000;
277
- let cache = [];
278
- let str_error = JSON.stringify(err, function(key, value) { // try to use a separate function
279
- if (typeof value === 'object' && value != null) {
280
- if (cache.indexOf(value) !== -1) {
281
- return;
282
- }
283
- cache.push(value);
284
- }
285
- return value;
268
+ let axios_options = {
269
+ url: options.url,
270
+ method: options.method,
271
+ params: options.params,
272
+ headers: options.headers,
273
+ timeout: options.timeout,
274
+ maxContentLength: 10000000, // max 10mb response size
275
+ maxBodyLength: 10000000 // max 10mb request body size
276
+ }
277
+
278
+ if (options.json !== null) {
279
+ axios_options.data = options.json
280
+ }
281
+ // if (this.log) {
282
+ // console.log("axios_options:", JSON.stringify(axios_options));
283
+ // }
284
+ if (options.url.startsWith("https:")) {
285
+ const httpsAgent = new https.Agent({
286
+ rejectUnauthorized: false,
286
287
  });
287
- let error = JSON.parse(str_error) // "status" disappears without this trick
288
- let errorMessage = JSON.stringify(error);
289
- if (error.status) {
290
- status = error.status;
288
+ axios_options.httpsAgent = httpsAgent;
289
+ }
290
+
291
+ axios(axios_options)
292
+ .then((res) => {
293
+ if (this.log) {
294
+ console.log("Success Response:", res);
295
+ console.log("Response for url:", options.url);
296
+ console.log("Response headers:\n", JSON.stringify(res.headers));
291
297
  }
292
- if (error.message) {
293
- errorMessage = error.message;
298
+ if (callback) {
299
+ callback(null, res);
294
300
  }
295
- let data = null;
296
- if (err.response) {
297
- data = err.response.data;
301
+ })
302
+ .catch( (err) => {
303
+ if (this.log) {
304
+ if (err.response) {
305
+ console.log("Error Response data:", err.response.data);
306
+ }
307
+ // FIX THE STRINGIFY OF CIRCULAR STRUCTURE BUG - START
308
+ let cache = [];
309
+ let error_log = JSON.stringify(err, function(key, value) { // try to use a separate function
310
+ if (typeof value === 'object' && value != null) {
311
+ if (cache.indexOf(value) !== -1) {
312
+ return;
313
+ }
314
+ cache.push(value);
315
+ }
316
+ return value;
317
+ });
318
+ console.error("An error occurred: ", error_log);
319
+ // FIX THE STRINGIFY OF CIRCULAR STRUCTURE BUG - END
320
+ // console.error("An error occurred:", JSON.stringify(err));
298
321
  }
299
- callback(
300
- null, {
301
- status: status,
302
- data: data,
303
- error: errorMessage
322
+ if (callback) {
323
+ let status = 1000;
324
+ let cache = [];
325
+ let str_error = JSON.stringify(err, function(key, value) { // try to use a separate function
326
+ if (typeof value === 'object' && value != null) {
327
+ if (cache.indexOf(value) !== -1) {
328
+ return;
329
+ }
330
+ cache.push(value);
331
+ }
332
+ return value;
333
+ });
334
+ let error = JSON.parse(str_error) // "status" disappears without this trick
335
+ let errorMessage = JSON.stringify(error);
336
+ if (error.status) {
337
+ status = error.status;
304
338
  }
305
- );
306
- }
307
- });
339
+ if (error.message) {
340
+ errorMessage = error.message;
341
+ }
342
+ let data = null;
343
+ if (err.response) {
344
+ data = err.response.data;
345
+ }
346
+ callback(
347
+ null, {
348
+ status: status,
349
+ data: data,
350
+ error: errorMessage
351
+ }
352
+ );
353
+ }
354
+ });
355
+ }
356
+ catch(error) {
357
+ console.error("Error:", error);
358
+ }
308
359
  }
309
360
 
310
361
  #webrequest_timeout(action, default_timeout, min, max) {
311
362
  let timeout = default_timeout;
312
- if (action.settings && action.settings.timeout) {
313
- if (action.settings.timeout && typeof action.settings.timeout === "number" && action.settings.timeout > min && action.settings.timeout < max) {
314
- timeout = Math.round(action.timeout)
363
+ if (!action.settings) {
364
+ return timeout;
365
+ }
366
+ // console.log("default timeout:", timeout);
367
+ // console.log("action.settings:", action.settings);
368
+ // console.log("action.settings.timeout:", action.settings.timeout);
369
+ // console.log("typeof action.settings.timeout:", typeof action.settings.timeout);
370
+ // console.log("action.settings.timeout > min", action.settings.timeout > min)
371
+ // console.log("action.settings.timeout < max", action.settings.timeout < max)
372
+
373
+ if (action.settings.timeout) {
374
+ if ((typeof action.settings.timeout === "number") && action.settings.timeout > min && action.settings.timeout < max) {
375
+ timeout = Math.round(action.settings.timeout)
376
+ // console.log("new timeout:", timeout);
315
377
  }
316
378
  }
379
+ // console.log("returning timeout:", timeout);
317
380
  return timeout
318
381
  }
319
382
 
@@ -10,6 +10,7 @@ class DirWhatsappByAttribute {
10
10
  throw new Error('context object is mandatory');
11
11
  }
12
12
  this.context = context;
13
+ this.API_ENDPOINT = context.API_ENDPOINT;
13
14
  this.log = context.log;
14
15
  }
15
16
 
@@ -35,15 +36,12 @@ class DirWhatsappByAttribute {
35
36
  console.log("whatsapp by attributes action: ", JSON.stringify(action))
36
37
  }
37
38
 
38
- ///////////
39
-
40
39
  const whatsapp_api_url_pre = process.env.WHATSAPP_ENDPOINT;
41
- const server_base_url = process.env.API_URL || process.env.API_ENDPOINT;
42
40
 
43
41
  if (whatsapp_api_url_pre) {
44
42
  whatsapp_api_url = whatsapp_api_url_pre;
45
43
  } else {
46
- whatsapp_api_url = server_base_url + "/modules/whatsapp/api"
44
+ whatsapp_api_url = this.API_ENDPOINT + "/modules/whatsapp/api"
47
45
  }
48
46
  console.log("DirWhatsappByAttribute whatsapp_api_url: ", whatsapp_api_url);
49
47
 
@@ -94,62 +92,6 @@ class DirWhatsappByAttribute {
94
92
  }, true);
95
93
  })
96
94
 
97
-
98
- // if (process.env.API_URL) {
99
- // whatsapp_api_url = "https://tiledesk-whatsapp-connector.giovannitroisi3.repl.co/api";
100
- // // whatsapp_api_url = process.env.API_URL + "/modules/whatsapp";
101
- // console.log("(Tilebot) DirWhatsappByAttribute whatsapp_api_url: ", whatsapp_api_url);
102
- // } else {
103
- // console.error("(Tilebot) ERROR Missing whatsapp_api_url. Unable to use action WhatsApp By Attributes");
104
- // callback();
105
- // return;
106
- // }
107
- // if (action.attributeName) {
108
- // if (this.log) { console.log("whatsapp attributeName:", action.attributeName); }
109
- // let attribute_value = null;
110
- // if (this.context.tdcache) {
111
-
112
- // const attribute_value = await TiledeskChatbot.getParameterStatic(this.context.tdcache, this.context.requestId, action.attributeName)
113
- // if (this.log) { console.log("attribute_value:", JSON.stringify(attribute_value)); }
114
-
115
- // if (attribute_value == null) {
116
- // console.error("(Tilebot) attribute_value is undefined");
117
- // callback();
118
- // return;
119
- // }
120
-
121
- // const URL = whatsapp_api_url + '/tiledesk/broadcast';
122
- // const HTTPREQUEST = {
123
- // url: URL,
124
- // headers: {
125
- // 'Content-Type': 'application/json',
126
-
127
- // },
128
- // json: attribute_value,
129
- // method: 'POST'
130
- // };
131
- // let promise = new Promise((resolve, reject) => {
132
- // DirWhatsappByAttribute.myrequest(
133
- // HTTPREQUEST,
134
- // function (err, resbody) {
135
- // if (err) {
136
- // if (callback) {
137
- // callback(err);
138
- // }
139
- // reject(err);
140
- // }
141
- // else {
142
- // if (callback) {
143
- // callback(null, resbody);
144
- // }
145
- // console.log("(tybot) broadcast sent: ", resbody);
146
- // resolve(resbody);
147
- // }
148
- // }, true);
149
- // })
150
- // return promise;
151
- // }
152
- // }
153
95
  }
154
96
 
155
97
  // HTTP REQUEST
@@ -2,7 +2,7 @@ class Directives {
2
2
  static AGENT = 'agent';
3
3
  static CLOSE = 'close';
4
4
  static DEPARTMENT = 'department';
5
- static MESSAGE = 'message';
5
+ static MESSAGE = 'message'; // DEPRECATED
6
6
  static HMESSAGE = 'hmessage';
7
7
  static INTENT = 'intent';
8
8
  static REMOVE_CURRENT_BOT = "removecurrentbot";
@@ -27,22 +27,37 @@ class Directives {
27
27
  static RANDOM_REPLY = 'randomreply';
28
28
  static CODE = 'code';
29
29
  static WHATSAPP_ATTRIBUTE = 'whatsapp_attribute';
30
+ static SEND_WHATSAPP = 'send_whatsapp';
30
31
  static FORM = "form";
31
32
  static CAPTURE_USER_REPLY = "capture_user_reply";
32
33
  static REPLACE_BOT_V2 = "replacebotv2";
34
+ static REPLACE_BOT_V3 = "replacebotv3";
33
35
  /**** AI ****/
34
36
  static ASK_GPT = "askgpt";
35
37
  static ASK_GPT_V2 = "askgptv2";
36
38
  static GPT_TASK = "gpt_task";
39
+ static AI_PROMPT = "ai_prompt";
37
40
  /**** INTEGRATIONS ****/
38
41
  static QAPLA = 'qapla';
39
42
  static MAKE = 'make';
40
43
  static HUBSPOT = 'hubspot';
41
44
  static CUSTOMERIO = 'customerio';
45
+ static BREVO = 'brevo';
42
46
  /**** VOICE CHANNEL ****/
43
47
  static DTMF_FORM = 'dtmf_form';
44
48
  static DTMF_MENU = 'dtmf_menu';
45
49
  static BLIND_TRANSFER = 'blind_transfer';
50
+ static SPEECH_FORM = 'speech_form';
51
+ static PLAY_PROMPT = 'play_prompt';
52
+ static AUDIO_RECORD = 'audio_record';
53
+ static GPT_ASSISTANT = 'gpt_assistant';
54
+ static REPLY_V2 = 'replyv2';
55
+ static IF_ONLINE_AGENTS_V2 = "ifonlineagentsv2";
56
+ static CONTACT_UPDATE = "leadupdate";
57
+ static CLEAR_TRANSCRIPT = "clear_transcript";
58
+ static MOVE_TO_UNASSIGNED = "move_to_unassigned";
59
+ static CONNECT_BLOCK = "connect_block";
60
+ static ADD_TAGS = 'add_tags'
46
61
 
47
62
  // static WHEN_ONLINE_MOVE_TO_AGENT = "whenonlinemovetoagent"; // DEPRECATED?
48
63
  // static WHEN_OFFLINE_HOURS = "whenofflinehours"; // DEPRECATED // adds a message on top of the original message when offline hours opts: --replace