@peopl-health/nexus 3.3.9 → 3.3.10
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.
- package/examples/basic-usage.js +0 -1
- package/lib/adapters/BaileysProvider.js +1 -1
- package/lib/adapters/TwilioProvider.js +111 -250
- package/lib/adapters/index.js +6 -1
- package/lib/assistants/BaseAssistant.js +15 -146
- package/lib/config/awsConfig.js +5 -3
- package/lib/config/llmConfig.js +24 -55
- package/lib/controllers/assistantController.js +49 -54
- package/lib/controllers/bugReportController.js +18 -22
- package/lib/controllers/caseDocumentationController.js +12 -19
- package/lib/controllers/conversationController.js +50 -225
- package/lib/controllers/interactionController.js +2 -2
- package/lib/controllers/mediaController.js +25 -66
- package/lib/controllers/messageController.js +114 -232
- package/lib/controllers/messageStatusController.js +32 -62
- package/lib/controllers/patientController.js +1 -1
- package/lib/controllers/qualityMessageController.js +21 -15
- package/lib/controllers/templateController.js +76 -247
- package/lib/controllers/templateFlowController.js +36 -90
- package/lib/controllers/threadController.js +30 -85
- package/lib/controllers/uploadController.js +31 -54
- package/lib/{utils/messageParser.js → core/MessageParser.js} +16 -35
- package/lib/core/NexusMessaging.js +266 -452
- package/lib/helpers/assistantHelper.js +12 -41
- package/lib/helpers/baileysHelper.js +39 -94
- package/lib/helpers/filesHelper.js +36 -103
- package/lib/helpers/llmsHelper.js +48 -135
- package/lib/helpers/mediaHelper.js +10 -21
- package/lib/helpers/messageHelper.js +52 -88
- package/lib/helpers/messageStatusHelper.js +11 -42
- package/lib/helpers/metaFlowHelper.js +3 -1
- package/lib/helpers/mongoHelper.js +8 -21
- package/lib/helpers/processHelper.js +65 -189
- package/lib/helpers/qrHelper.js +1 -1
- package/lib/helpers/templateFlowControllerHelper.js +15 -44
- package/lib/helpers/templateRecoveryHelper.js +16 -28
- package/lib/helpers/threadHelper.js +20 -45
- package/lib/helpers/threadRecoveryHelper.js +11 -20
- package/lib/helpers/twilioHelper.js +23 -92
- package/lib/helpers/twilioMediaHelper.js +125 -0
- package/lib/helpers/whatsappHelper.js +8 -22
- package/lib/index.js +2 -23
- package/lib/memory/DefaultMemoryManager.js +47 -83
- package/lib/models/templateModel.js +9 -25
- package/lib/observability/telemetry.js +40 -72
- package/lib/providers/OpenAIAssistantsProvider.js +62 -169
- package/lib/providers/OpenAIResponsesProvider.js +90 -186
- package/lib/providers/OpenAIResponsesProviderTools.js +21 -25
- package/lib/providers/createLLMProvider.js +4 -7
- package/lib/services/airtableService.js +42 -61
- package/lib/services/assistantService.js +330 -49
- package/lib/services/conversationService.js +91 -63
- package/lib/services/metaFlowService.js +18 -47
- package/lib/services/metaService.js +50 -82
- package/lib/services/{preprocessingHooks.js → preprocessingService.js} +1 -1
- package/lib/services/twilioService.js +8 -51
- package/lib/storage/MongoStorage.js +24 -50
- package/lib/templates/predefinedTemplates.js +15 -73
- package/lib/templates/templateStructure.js +32 -99
- package/lib/utils/dateUtils.js +11 -28
- package/lib/utils/{outputSanitizer.js → formatUtils.js} +2 -4
- package/lib/utils/{retryHelper.js → retryUtils.js} +2 -8
- package/lib/utils/sanitizerUtils.js +62 -0
- package/lib/utils/scheduleUtils.js +8 -18
- package/lib/utils/tracingDecorator.js +6 -21
- package/package.json +1 -17
- package/lib/config/configLoader.js +0 -38
- package/lib/core/index.js +0 -7
- package/lib/helpers/twilioMediaProcessor.js +0 -167
- package/lib/services/assistantServiceCore.js +0 -327
- package/lib/utils/index.js +0 -11
- package/lib/utils/inputSanitizer.js +0 -56
- /package/lib/{core → adapters}/MessageProvider.js +0 -0
- /package/lib/utils/{errorHandler.js → errorUtils.js} +0 -0
package/examples/basic-usage.js
CHANGED
|
@@ -172,7 +172,6 @@ async function startServer() {
|
|
|
172
172
|
// Example using Airtable
|
|
173
173
|
app.get('/airtable-test', async (req, res) => {
|
|
174
174
|
try {
|
|
175
|
-
const calendarBase = nexus.getAirtableBase('calendar');
|
|
176
175
|
// Use calendarBase to interact with Airtable
|
|
177
176
|
res.json({ success: true, message: 'Airtable connection ready' });
|
|
178
177
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { logger } = require('../utils/logger');
|
|
2
2
|
const { calculateDelay } = require('../utils/scheduleUtils');
|
|
3
3
|
|
|
4
|
-
const { MessageProvider } = require('../
|
|
4
|
+
const { MessageProvider } = require('../adapters/MessageProvider');
|
|
5
5
|
|
|
6
6
|
class BaileysProvider extends MessageProvider {
|
|
7
7
|
constructor(config) {
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
const twilio = require('twilio');
|
|
3
4
|
|
|
4
5
|
const runtimeConfig = require('../config/runtimeConfig');
|
|
5
6
|
const { generatePresignedUrl } = require('../config/awsConfig');
|
|
6
7
|
|
|
7
|
-
const { sanitizeMediaFilename } = require('../utils/
|
|
8
|
+
const { sanitizeMediaFilename } = require('../utils/sanitizerUtils');
|
|
8
9
|
const { validateMedia, getMediaType } = require('../utils/mediaValidator');
|
|
9
10
|
const { logger } = require('../utils/logger');
|
|
10
11
|
const { calculateDelay } = require('../utils/scheduleUtils');
|
|
11
12
|
|
|
13
|
+
const { ScheduledMessage } = require('../models/agendaMessageModel');
|
|
14
|
+
|
|
12
15
|
const { ensureWhatsAppFormat } = require('../helpers/twilioHelper');
|
|
13
16
|
const { uploadMediaToS3, getFileExtension } = require('../helpers/mediaHelper');
|
|
14
17
|
|
|
15
|
-
const { MessageProvider } = require('../
|
|
18
|
+
const { MessageProvider } = require('../adapters/MessageProvider');
|
|
16
19
|
|
|
17
20
|
class TwilioProvider extends MessageProvider {
|
|
18
21
|
constructor(config) {
|
|
@@ -30,7 +33,6 @@ class TwilioProvider extends MessageProvider {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
try {
|
|
33
|
-
const twilio = require('twilio');
|
|
34
36
|
this.twilioClient = twilio(this.accountSid, this.authToken);
|
|
35
37
|
this.isConnected = true;
|
|
36
38
|
} catch (error) {
|
|
@@ -89,7 +91,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
if (fileUrl && fileType !== 'text') {
|
|
92
|
-
const mediaPrep = await this.
|
|
94
|
+
const mediaPrep = await this._prepareOutboundMedia(messageData, formattedCode);
|
|
93
95
|
const outboundMediaUrl = mediaPrep.mediaUrl || fileUrl;
|
|
94
96
|
messageParams.mediaUrl = [outboundMediaUrl];
|
|
95
97
|
if (!messageParams.body || messageParams.body.trim() === '') {
|
|
@@ -108,69 +110,42 @@ class TwilioProvider extends MessageProvider {
|
|
|
108
110
|
throw new Error('Message must have body, media URL, or content SID');
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
const saveMessage = async (body, result) => {
|
|
114
|
+
if (!this.messageStorage?.saveMessage) return;
|
|
115
|
+
try {
|
|
116
|
+
await this.messageStorage.saveMessage({
|
|
117
|
+
...messageData,
|
|
118
|
+
body,
|
|
119
|
+
code: formattedCode,
|
|
120
|
+
from: formattedFrom,
|
|
121
|
+
messageId: result.sid,
|
|
122
|
+
provider: 'twilio',
|
|
123
|
+
timestamp: new Date(),
|
|
124
|
+
fromMe: true,
|
|
125
|
+
processed: messageData.processed ?? false,
|
|
126
|
+
statusInfo: {
|
|
127
|
+
status: result.status?.toLowerCase() || null,
|
|
128
|
+
updatedAt: result.dateCreated || new Date()
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logger.error('[TwilioProvider] Storage failed:', err);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
111
136
|
let result;
|
|
112
|
-
const chunks = messageParams.body
|
|
113
|
-
? this.
|
|
114
|
-
: null;
|
|
137
|
+
const chunks = messageParams.body?.length > 1600 && !messageParams.mediaUrl && !messageParams.contentSid
|
|
138
|
+
? this._splitMessageAtWordBoundaries(messageParams.body) : null;
|
|
115
139
|
|
|
116
140
|
if (chunks) {
|
|
117
141
|
for (let i = 0; i < chunks.length; i++) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
try {
|
|
122
|
-
await this.messageStorage.saveMessage({
|
|
123
|
-
...messageData,
|
|
124
|
-
body: chunks[i],
|
|
125
|
-
code: formattedCode,
|
|
126
|
-
from: formattedFrom,
|
|
127
|
-
messageId: result.sid,
|
|
128
|
-
provider: 'twilio',
|
|
129
|
-
timestamp: new Date(),
|
|
130
|
-
fromMe: true,
|
|
131
|
-
processed: messageData.processed !== undefined ? messageData.processed : false,
|
|
132
|
-
statusInfo: {
|
|
133
|
-
status: result.status ? result.status.toLowerCase() : null,
|
|
134
|
-
updatedAt: result.dateCreated || new Date()
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
logger.info('[TwilioProvider] Message chunk persisted', { messageId: result.sid, chunk: i + 1, total: chunks.length });
|
|
138
|
-
} catch (storageError) {
|
|
139
|
-
logger.error('TwilioProvider storage failed:', storageError);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (i < chunks.length - 1) {
|
|
143
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
144
|
-
}
|
|
142
|
+
result = await this.twilioClient.messages.create({ ...messageParams, body: chunks[i] });
|
|
143
|
+
await saveMessage(chunks[i], result);
|
|
144
|
+
if (i < chunks.length - 1) await new Promise(r => setTimeout(r, 100));
|
|
145
145
|
}
|
|
146
146
|
} else {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
result = await this.twilioClient.messages.create(messageParams);
|
|
150
|
-
} catch (error) {
|
|
151
|
-
throw new Error(`Twilio send failed: ${error.message}`);
|
|
152
|
-
}
|
|
153
|
-
if (this.messageStorage && typeof this.messageStorage.saveMessage === 'function') {
|
|
154
|
-
try {
|
|
155
|
-
await this.messageStorage.saveMessage({
|
|
156
|
-
...messageData,
|
|
157
|
-
code: formattedCode,
|
|
158
|
-
from: formattedFrom,
|
|
159
|
-
messageId: result.sid,
|
|
160
|
-
provider: 'twilio',
|
|
161
|
-
timestamp: new Date(),
|
|
162
|
-
fromMe: true,
|
|
163
|
-
processed: messageData.processed !== undefined ? messageData.processed : false,
|
|
164
|
-
statusInfo: {
|
|
165
|
-
status: result.status ? result.status.toLowerCase() : null,
|
|
166
|
-
updatedAt: result.dateCreated || new Date()
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
logger.info('[TwilioProvider] Message persisted successfully', { messageId: result.sid });
|
|
170
|
-
} catch (storageError) {
|
|
171
|
-
logger.error('TwilioProvider storage failed:', storageError);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
147
|
+
result = await this.twilioClient.messages.create(messageParams);
|
|
148
|
+
await saveMessage(messageParams.body, result);
|
|
174
149
|
}
|
|
175
150
|
|
|
176
151
|
return {
|
|
@@ -232,61 +207,31 @@ class TwilioProvider extends MessageProvider {
|
|
|
232
207
|
});
|
|
233
208
|
|
|
234
209
|
const updateStatus = async (status, messageId = null, error = null) => {
|
|
235
|
-
if (!scheduledMessage) return;
|
|
236
|
-
|
|
237
210
|
const now = new Date();
|
|
238
|
-
const errorCode = error?.code || error?.status || error?.errorCode || null;
|
|
239
|
-
const errorMessage = error?.message || error?.statusMessage || null;
|
|
240
|
-
const statusEntry = { status, at: now, errorCode, errorMessage };
|
|
241
|
-
const baseUpdate = {
|
|
242
|
-
status,
|
|
243
|
-
lastStatus: status,
|
|
244
|
-
lastStatusAt: now,
|
|
245
|
-
errorCode,
|
|
246
|
-
errorMessage
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
if (messageId) baseUpdate.wa_id = messageId;
|
|
250
|
-
|
|
251
|
-
const ScheduledMessageModel = (() => {
|
|
252
|
-
if (scheduledMessage.constructor && typeof scheduledMessage.constructor.updateOne === 'function') {
|
|
253
|
-
return scheduledMessage.constructor;
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
return require('../models/agendaMessageModel').ScheduledMessage;
|
|
257
|
-
} catch (e) {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
})();
|
|
261
|
-
|
|
262
|
-
if (!ScheduledMessageModel) {
|
|
263
|
-
logger.warn('[TwilioProvider] Scheduled message model unavailable for status update');
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const query = (() => {
|
|
268
|
-
if (scheduledMessage._id) return { _id: scheduledMessage._id };
|
|
269
|
-
if (messageId) return { wa_id: messageId };
|
|
270
|
-
if (scheduledMessage.wa_id) return { wa_id: scheduledMessage.wa_id };
|
|
271
|
-
return null;
|
|
272
|
-
})();
|
|
273
|
-
|
|
274
|
-
if (!query) {
|
|
275
|
-
logger.warn('[TwilioProvider] Scheduled message status update skipped: no identifier', {
|
|
276
|
-
hasId: Boolean(scheduledMessage._id),
|
|
277
|
-
messageId,
|
|
278
|
-
existingWaId: scheduledMessage.wa_id
|
|
279
|
-
});
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
211
|
try {
|
|
284
|
-
await
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
212
|
+
await ScheduledMessage.updateOne(
|
|
213
|
+
{ _id: scheduledMessage._id },
|
|
214
|
+
{
|
|
215
|
+
$set: {
|
|
216
|
+
status,
|
|
217
|
+
lastStatus: status,
|
|
218
|
+
lastStatusAt: now,
|
|
219
|
+
...(messageId && { wa_id: messageId }),
|
|
220
|
+
errorCode: error?.code || null,
|
|
221
|
+
errorMessage: error?.message || null
|
|
222
|
+
},
|
|
223
|
+
$push: {
|
|
224
|
+
statusHistory: {
|
|
225
|
+
status,
|
|
226
|
+
at: now,
|
|
227
|
+
errorCode: error?.code || null,
|
|
228
|
+
errorMessage: error?.message || null
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.warn('[TwilioProvider] Failed to update scheduled message status', err?.message);
|
|
290
235
|
}
|
|
291
236
|
};
|
|
292
237
|
|
|
@@ -319,8 +264,8 @@ class TwilioProvider extends MessageProvider {
|
|
|
319
264
|
return true;
|
|
320
265
|
}
|
|
321
266
|
|
|
322
|
-
async
|
|
323
|
-
const bucketName = runtimeConfig.get('AWS_S3_BUCKET_NAME')
|
|
267
|
+
async _prepareOutboundMedia(messageData, formattedCode) {
|
|
268
|
+
const bucketName = runtimeConfig.get('AWS_S3_BUCKET_NAME');
|
|
324
269
|
const fileUrl = messageData.fileUrl;
|
|
325
270
|
|
|
326
271
|
if (!fileUrl) {
|
|
@@ -346,7 +291,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
346
291
|
const response = await axios.get(fileUrl, { responseType: 'arraybuffer' });
|
|
347
292
|
const buffer = Buffer.from(response.data);
|
|
348
293
|
let contentType = messageData.contentType || response.headers['content-type'];
|
|
349
|
-
const declaredType =
|
|
294
|
+
const declaredType = messageData.fileType || null;
|
|
350
295
|
|
|
351
296
|
if (!contentType) {
|
|
352
297
|
const fallbackType = declaredType ? {
|
|
@@ -368,17 +313,15 @@ class TwilioProvider extends MessageProvider {
|
|
|
368
313
|
});
|
|
369
314
|
}
|
|
370
315
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return null;
|
|
316
|
+
let existingFileName = messageData.fileName;
|
|
317
|
+
if (!existingFileName) {
|
|
318
|
+
try {
|
|
319
|
+
existingFileName = new URL(fileUrl).pathname.split('/').pop() || null;
|
|
320
|
+
} catch {
|
|
321
|
+
existingFileName = null;
|
|
378
322
|
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const baseName = existingFileName ? existingFileName.split('.').slice(0, -1).join('.') : `${mediaType}_${Date.now()}`;
|
|
323
|
+
}
|
|
324
|
+
const baseName = existingFileName?.split('.').slice(0, -1).join('.') || `${mediaType}_${Date.now()}`;
|
|
382
325
|
const sanitizedBase = sanitizeMediaFilename(baseName) || `${mediaType}_${Date.now()}`;
|
|
383
326
|
const extension = getFileExtension(contentType) || 'bin';
|
|
384
327
|
const uploadId = uuidv4();
|
|
@@ -420,7 +363,7 @@ class TwilioProvider extends MessageProvider {
|
|
|
420
363
|
}
|
|
421
364
|
}
|
|
422
365
|
|
|
423
|
-
|
|
366
|
+
_splitMessageAtWordBoundaries(text, maxLength = 1600) {
|
|
424
367
|
if (!text || text.length <= maxLength) return [text];
|
|
425
368
|
const chunks = [];
|
|
426
369
|
let remaining = text;
|
|
@@ -492,15 +435,15 @@ class TwilioProvider extends MessageProvider {
|
|
|
492
435
|
return null;
|
|
493
436
|
}
|
|
494
437
|
|
|
495
|
-
let textContent = this.
|
|
438
|
+
let textContent = this._extractTextFromTemplate(template);
|
|
496
439
|
|
|
497
440
|
if (!textContent) {
|
|
498
441
|
logger.warn('[TwilioProvider] No text content found in template:', contentSid);
|
|
499
442
|
return null;
|
|
500
443
|
}
|
|
501
444
|
|
|
502
|
-
if (variables &&
|
|
503
|
-
return this.
|
|
445
|
+
if (variables && Object.keys(variables).length > 0) {
|
|
446
|
+
return this._renderTemplateWithVariables(textContent, variables);
|
|
504
447
|
}
|
|
505
448
|
|
|
506
449
|
return textContent.trim();
|
|
@@ -510,90 +453,42 @@ class TwilioProvider extends MessageProvider {
|
|
|
510
453
|
}
|
|
511
454
|
}
|
|
512
455
|
|
|
513
|
-
|
|
456
|
+
_extractTextFromTemplate(template) {
|
|
514
457
|
const types = template.types || {};
|
|
515
458
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
459
|
+
const appendItems = (text, items, formatter) => {
|
|
460
|
+
const formatted = (items || []).filter(i => i.title).map(formatter).join('\n');
|
|
461
|
+
return formatted ? text + (text ? '\n\n' : '') + formatted : text;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
if (types['twilio/text']) return types['twilio/text'].body || '';
|
|
519
465
|
|
|
520
466
|
if (types['twilio/quick-reply']) {
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if (quickReply.actions && Array.isArray(quickReply.actions)) {
|
|
525
|
-
const options = quickReply.actions
|
|
526
|
-
.filter(action => action.title)
|
|
527
|
-
.map(action => `• ${action.title}`)
|
|
528
|
-
.join('\n');
|
|
529
|
-
if (options) {
|
|
530
|
-
text += (text ? '\n\n' : '') + options;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return text;
|
|
467
|
+
const qr = types['twilio/quick-reply'];
|
|
468
|
+
return appendItems(qr.body || '', qr.actions, a => `• ${a.title}`);
|
|
535
469
|
}
|
|
536
470
|
|
|
537
471
|
if (types['twilio/list']) {
|
|
538
472
|
const list = types['twilio/list'];
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (list.items && Array.isArray(list.items)) {
|
|
542
|
-
const items = list.items
|
|
543
|
-
.filter(item => item.title)
|
|
544
|
-
.map((item, index) => `${index + 1}. ${item.title}`)
|
|
545
|
-
.join('\n');
|
|
546
|
-
if (items) {
|
|
547
|
-
text += (text ? '\n\n' : '') + items;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return text;
|
|
473
|
+
return appendItems(list.body || '', list.items, (item, i) => `${i + 1}. ${item.title}`);
|
|
552
474
|
}
|
|
553
475
|
|
|
554
476
|
if (types['twilio/button']) {
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (button.actions && Array.isArray(button.actions)) {
|
|
559
|
-
const buttons = button.actions
|
|
560
|
-
.filter(action => action.title)
|
|
561
|
-
.map(action => `[${action.title}]`)
|
|
562
|
-
.join(' ');
|
|
563
|
-
if (buttons) {
|
|
564
|
-
text += (text ? '\n\n' : '') + buttons;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
return text;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (types['twilio/flows']) {
|
|
572
|
-
return types['twilio/flows'].body || '';
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (types['twilio/media']) {
|
|
576
|
-
return types['twilio/media'].caption || '';
|
|
477
|
+
const btn = types['twilio/button'];
|
|
478
|
+
const buttons = (btn.actions || []).filter(a => a.title).map(a => `[${a.title}]`).join(' ');
|
|
479
|
+
return buttons ? (btn.body || '') + ((btn.body && buttons) ? '\n\n' : '') + buttons : (btn.body || '');
|
|
577
480
|
}
|
|
578
481
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (type && typeof type === 'object' && type.body) {
|
|
582
|
-
return type.body;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
482
|
+
if (types['twilio/flows']) return types['twilio/flows'].body || '';
|
|
483
|
+
if (types['twilio/media']) return types['twilio/media'].caption || '';
|
|
585
484
|
|
|
586
|
-
|
|
485
|
+
const fallback = Object.values(types).find(t => t?.body);
|
|
486
|
+
return fallback?.body || '';
|
|
587
487
|
}
|
|
588
488
|
|
|
589
|
-
|
|
590
|
-
if (!templateBody
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (!variables || typeof variables !== 'object') {
|
|
595
|
-
return templateBody;
|
|
596
|
-
}
|
|
489
|
+
_renderTemplateWithVariables(templateBody, variables) {
|
|
490
|
+
if (!templateBody) return '';
|
|
491
|
+
if (!variables) return templateBody;
|
|
597
492
|
|
|
598
493
|
try {
|
|
599
494
|
let rendered = templateBody;
|
|
@@ -620,60 +515,26 @@ class TwilioProvider extends MessageProvider {
|
|
|
620
515
|
}
|
|
621
516
|
if (!sid) throw new Error('Content SID is required');
|
|
622
517
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
let processedApprovalRequest = null;
|
|
626
|
-
|
|
627
|
-
try {
|
|
628
|
-
const approvalRequest = await this.twilioClient.content.v1
|
|
629
|
-
.contents(sid)
|
|
630
|
-
.approvalFetch()
|
|
631
|
-
.fetch();
|
|
632
|
-
|
|
633
|
-
if (approvalRequest) {
|
|
634
|
-
processedApprovalRequest = {
|
|
635
|
-
sid: approvalRequest.sid,
|
|
636
|
-
status: 'UNKNOWN'
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
if (approvalRequest.whatsapp) {
|
|
640
|
-
processedApprovalRequest.status = approvalRequest.whatsapp.status?.toUpperCase() || 'UNKNOWN';
|
|
641
|
-
processedApprovalRequest.category = approvalRequest.whatsapp.category;
|
|
642
|
-
processedApprovalRequest.name = approvalRequest.whatsapp.name;
|
|
643
|
-
processedApprovalRequest.rejectionReason = approvalRequest.whatsapp.rejection_reason;
|
|
644
|
-
processedApprovalRequest.contentType = approvalRequest.whatsapp.content_type;
|
|
645
|
-
} else {
|
|
646
|
-
processedApprovalRequest.status = approvalRequest.status || 'PENDING';
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
if (processedApprovalRequest.status === 'approved') {
|
|
650
|
-
processedApprovalRequest.status = 'APPROVED';
|
|
651
|
-
} else if (processedApprovalRequest.status === 'rejected') {
|
|
652
|
-
processedApprovalRequest.status = 'REJECTED';
|
|
653
|
-
} else if (processedApprovalRequest.status === 'pending') {
|
|
654
|
-
processedApprovalRequest.status = 'PENDING';
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (approvalRequest.dateCreated || approvalRequest.date_created) {
|
|
658
|
-
processedApprovalRequest.dateCreated = approvalRequest.dateCreated || approvalRequest.date_created;
|
|
659
|
-
}
|
|
518
|
+
const content = await this.twilioClient.content.v1.contents(sid).fetch();
|
|
519
|
+
let approvalRequest = null;
|
|
660
520
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
521
|
+
try {
|
|
522
|
+
const req = await this.twilioClient.content.v1.contents(sid).approvalFetch().fetch();
|
|
523
|
+
if (req) {
|
|
524
|
+
const wa = req.whatsapp;
|
|
525
|
+
approvalRequest = {
|
|
526
|
+
sid: req.sid,
|
|
527
|
+
status: (wa?.status || req.status || 'UNKNOWN').toUpperCase(),
|
|
528
|
+
...(wa && { category: wa.category, name: wa.name, rejectionReason: wa.rejection_reason, contentType: wa.content_type }),
|
|
529
|
+
dateCreated: req.dateCreated || req.date_created,
|
|
530
|
+
dateUpdated: req.dateUpdated || req.date_updated
|
|
531
|
+
};
|
|
667
532
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
content,
|
|
671
|
-
approvalRequest: processedApprovalRequest
|
|
672
|
-
};
|
|
673
|
-
} catch (error) {
|
|
674
|
-
logger.error('Error checking approval status:', error);
|
|
675
|
-
throw error;
|
|
533
|
+
} catch (err) {
|
|
534
|
+
logger.warn('Approval request fetch failed:', err?.message);
|
|
676
535
|
}
|
|
536
|
+
|
|
537
|
+
return { content, approvalRequest };
|
|
677
538
|
}
|
|
678
539
|
|
|
679
540
|
async submitForApproval(contentSid, name, category) {
|
package/lib/adapters/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
const { TwilioProvider } = require('./TwilioProvider');
|
|
2
2
|
const { BaileysProvider } = require('./BaileysProvider');
|
|
3
|
+
const { MessageProvider } = require('./MessageProvider');
|
|
4
|
+
const { createProvider, registerProvider } = require('./registry');
|
|
3
5
|
|
|
4
6
|
module.exports = {
|
|
5
7
|
TwilioProvider,
|
|
6
|
-
BaileysProvider
|
|
8
|
+
BaileysProvider,
|
|
9
|
+
MessageProvider,
|
|
10
|
+
createProvider,
|
|
11
|
+
registerProvider
|
|
7
12
|
};
|