@resultcrafter/aimanager-instagram-connector 0.2.1 → 0.4.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.
- package/aimanager/FacebookClient.js +18 -6
- package/aimanager/TiledeskChannel.js +1 -1
- package/aimanager/TiledeskInstagramTranslator.js +7 -27
- package/index.js +252 -223
- package/package.json +1 -1
|
@@ -45,21 +45,33 @@ class FacebookClient {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async send(message, page_access_token) {
|
|
48
|
-
|
|
49
48
|
winston.debug("(fbm) [FacebookClient] Sending message...");
|
|
50
|
-
|
|
51
49
|
return await axios({
|
|
52
50
|
url: this.graph_url + "me/messages?access_token=" + page_access_token,
|
|
53
|
-
header: {
|
|
54
|
-
'Content-Type': 'application/json'
|
|
55
|
-
},
|
|
51
|
+
header: { 'Content-Type': 'application/json' },
|
|
56
52
|
method: "POST",
|
|
57
53
|
data: message
|
|
58
54
|
}).then((response) => {
|
|
59
55
|
winston.debug("(fbm) [FacebookClient] Message sent!");
|
|
60
56
|
return response
|
|
61
57
|
}).catch((err) => {
|
|
62
|
-
winston.error("(fbm) [FacebookClient] error send message: ", err.response.
|
|
58
|
+
winston.error("(fbm) [FacebookClient] error send message: ", err.response?.data || err.message);
|
|
59
|
+
throw err;
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async sendToInstagram(message, accessToken) {
|
|
64
|
+
winston.debug("(ig) [FacebookClient] Sending to Instagram Graph API...");
|
|
65
|
+
return await axios({
|
|
66
|
+
url: "https://graph.instagram.com/v23.0/me/messages?access_token=" + accessToken,
|
|
67
|
+
header: { 'Content-Type': 'application/json' },
|
|
68
|
+
method: "POST",
|
|
69
|
+
data: message
|
|
70
|
+
}).then((response) => {
|
|
71
|
+
winston.debug("(ig) [FacebookClient] Instagram message sent!");
|
|
72
|
+
return response
|
|
73
|
+
}).catch((err) => {
|
|
74
|
+
winston.error("(ig) [FacebookClient] Instagram send error: ", err.response?.data || err.message);
|
|
63
75
|
throw err;
|
|
64
76
|
})
|
|
65
77
|
}
|
|
@@ -58,7 +58,7 @@ class TiledeskChannel {
|
|
|
58
58
|
|
|
59
59
|
} else if (messageInfo.channel == "instagram") {
|
|
60
60
|
channel = messageInfo.instagram;
|
|
61
|
-
new_request_id = "support-group-" + this.settings.project_id + "-" + uuidv4().substring(0, 8) + "-
|
|
61
|
+
new_request_id = "support-group-" + this.settings.project_id + "-" + uuidv4().substring(0, 8) + "-ig-" + channel.page_id + "-" + channel.sender_id;
|
|
62
62
|
|
|
63
63
|
} else {
|
|
64
64
|
winston.verbose("(fbm) [TiledeskChannel] Channel not supported");
|
|
@@ -50,7 +50,7 @@ const path = require('path');
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
let instagram_message = {
|
|
53
|
-
|
|
53
|
+
messaging_product: TiledeskInstagramTranslator.INSTAGRAM_MESSAGING_PRODUCT,
|
|
54
54
|
recipient: { id: instagram_receiver }
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -82,40 +82,20 @@ const path = require('path');
|
|
|
82
82
|
|
|
83
83
|
instagram_message.message = {
|
|
84
84
|
attachment: {
|
|
85
|
-
type: "
|
|
85
|
+
type: "video",
|
|
86
86
|
payload: {
|
|
87
|
-
|
|
88
|
-
elements: [
|
|
89
|
-
{
|
|
90
|
-
//media_type: "video",
|
|
91
|
-
url: tiledeskChannelMessage.metadata.src
|
|
92
|
-
//url: tiledeskChannelMessage.metadata.src
|
|
93
|
-
//url: // facebook url:
|
|
94
|
-
// https://developers.facebook.com/docs/instagram-platform/send-messages/template/media
|
|
95
|
-
// https://developers.facebook.com/docs/instagram-platform/reference/attachment-upload-api
|
|
96
|
-
}
|
|
97
|
-
]
|
|
87
|
+
url: tiledeskChannelMessage.metadata.src
|
|
98
88
|
}
|
|
99
89
|
}
|
|
100
90
|
}
|
|
101
|
-
|
|
102
|
-
/*
|
|
103
|
-
instagram_message.type = 'video'
|
|
104
|
-
instagram_message.video = {
|
|
105
|
-
link: tiledeskChannelMessage.metadata.src,
|
|
106
|
-
//caption: tiledeskChannelMessage.metadata.name || tiledeskChannelMessage.text
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
*/
|
|
110
91
|
}
|
|
111
92
|
|
|
112
93
|
else if (tiledeskChannelMessage.metadata.type && tiledeskChannelMessage.metadata.type.startsWith('application')) {
|
|
113
94
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
95
|
+
var docText = tiledeskChannelMessage.text || '';
|
|
96
|
+
if (tiledeskChannelMessage.metadata.name) docText += '\n' + tiledeskChannelMessage.metadata.name;
|
|
97
|
+
docText += '\n' + tiledeskChannelMessage.metadata.src;
|
|
98
|
+
instagram_message.message = { text: docText };
|
|
119
99
|
}
|
|
120
100
|
|
|
121
101
|
else {
|
package/index.js
CHANGED
|
@@ -328,17 +328,60 @@ router.post('/update_advanced', async (req, res) => {
|
|
|
328
328
|
})
|
|
329
329
|
})
|
|
330
330
|
|
|
331
|
+
function getOutboundToken(settings, entryId) {
|
|
332
|
+
if (settings && settings.pages && settings.pages.length > 0) {
|
|
333
|
+
var page = settings.pages.find(function(p) { return p.id === entryId; });
|
|
334
|
+
if (page && page.access_token) {
|
|
335
|
+
return { token: page.access_token, type: 'facebook_page' };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (settings && settings.ig_oauth_token && settings.ig_oauth_token.access_token) {
|
|
339
|
+
return { token: settings.ig_oauth_token.access_token, type: 'instagram_oauth' };
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function parseRecipient(recipient_id) {
|
|
345
|
+
var result = { entry_id: null, ig_receiver: null, prefix: 'ig' };
|
|
346
|
+
if (!recipient_id) return result;
|
|
347
|
+
|
|
348
|
+
if (recipient_id.indexOf('ig-') > -1) {
|
|
349
|
+
result.entry_id = recipient_id.substring(recipient_id.lastIndexOf('ig-') + 3, recipient_id.lastIndexOf('-'));
|
|
350
|
+
result.ig_receiver = recipient_id.substring(recipient_id.lastIndexOf('-') + 1);
|
|
351
|
+
result.prefix = 'ig';
|
|
352
|
+
} else if (recipient_id.indexOf('fbm-') > -1) {
|
|
353
|
+
result.entry_id = recipient_id.substring(recipient_id.lastIndexOf('fbm-') + 4, recipient_id.lastIndexOf('-'));
|
|
354
|
+
result.ig_receiver = recipient_id.substring(recipient_id.lastIndexOf('-') + 1);
|
|
355
|
+
result.prefix = 'fbm';
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function sendInstagramMessage(instagramJsonMessage, accessToken, authType, fbClient) {
|
|
361
|
+
if (!isValidIGSID(instagramJsonMessage.recipient.id)) {
|
|
362
|
+
winston.error("(ig) Invalid IGSID format: " + instagramJsonMessage.recipient.id);
|
|
363
|
+
return { success: false, error: "Invalid IGSID format" };
|
|
364
|
+
}
|
|
365
|
+
if (authType === 'instagram_oauth') {
|
|
366
|
+
return await fbClient.sendToInstagram(instagramJsonMessage, accessToken);
|
|
367
|
+
}
|
|
368
|
+
return await fbClient.send(instagramJsonMessage, accessToken);
|
|
369
|
+
}
|
|
370
|
+
|
|
331
371
|
router.post('/tiledesk', async (req, res) => {
|
|
332
|
-
winston.verbose("(
|
|
333
|
-
winston.debug("(fbm) Message received from AI Manager body: ", req.body);
|
|
372
|
+
winston.verbose("(ig) Message received from AI Manager");
|
|
334
373
|
|
|
335
374
|
let tiledeskChannelMessage = req.body.payload;
|
|
336
375
|
let project_id = req.body.payload.id_project;
|
|
337
376
|
|
|
338
|
-
// get settings from mongo
|
|
339
377
|
let CONTENT_KEY = "instagram-" + project_id;
|
|
340
378
|
let settings = await db.get(CONTENT_KEY);
|
|
341
379
|
|
|
380
|
+
if (!settings) {
|
|
381
|
+
winston.warn("(ig) No settings found for project " + project_id);
|
|
382
|
+
return res.sendStatus(200);
|
|
383
|
+
}
|
|
384
|
+
|
|
342
385
|
var text = req.body.payload.text;
|
|
343
386
|
let sender_id = req.body.payload.sender;
|
|
344
387
|
let attributes = req.body.payload.attributes;
|
|
@@ -348,176 +391,154 @@ router.post('/tiledesk', async (req, res) => {
|
|
|
348
391
|
commands = attributes.commands;
|
|
349
392
|
}
|
|
350
393
|
|
|
351
|
-
if (sender_id.indexOf("fbm") > -1) {
|
|
352
|
-
winston.debug("(
|
|
394
|
+
if (sender_id && (sender_id.indexOf("fbm") > -1 || sender_id.indexOf("ig") > -1)) {
|
|
395
|
+
winston.debug("(ig) Skip same sender");
|
|
353
396
|
return res.sendStatus(200);
|
|
354
397
|
}
|
|
355
398
|
|
|
356
|
-
if (attributes && attributes.subtype === "info") {
|
|
357
|
-
winston.debug("(
|
|
358
|
-
return res.sendStatus(200);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (attributes && attributes.subtype === "private") {
|
|
362
|
-
winston.verbose("(wab) Skip subtype (private)");
|
|
399
|
+
if (attributes && (attributes.subtype === "info" || attributes.subtype === "private" || attributes.subtype === 'info/support')) {
|
|
400
|
+
winston.debug("(ig) Skip subtype: " + attributes.subtype);
|
|
363
401
|
return res.sendStatus(200);
|
|
364
402
|
}
|
|
365
403
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
404
|
+
var parsed = parseRecipient(tiledeskChannelMessage.recipient);
|
|
405
|
+
var entry_id = parsed.entry_id;
|
|
406
|
+
var ig_receiver = parsed.ig_receiver;
|
|
370
407
|
|
|
371
|
-
|
|
372
|
-
let sender = tiledeskChannelMessage.sender;
|
|
373
|
-
let page_id = recipient_id.substring(recipient_id.lastIndexOf("fbm-") + 4, recipient_id.lastIndexOf("-"));
|
|
374
|
-
let instagram_receiver = recipient_id.substring(recipient_id.lastIndexOf("-") + 1);
|
|
375
|
-
|
|
376
|
-
winston.verbose("(fbm) sender: " + sender);
|
|
377
|
-
winston.verbose("(fbm) text: " + text);
|
|
378
|
-
winston.verbose("(fbm) attributes: ", attributes);
|
|
379
|
-
winston.verbose("(fbm) tiledesk sender_id: " + sender_id);
|
|
380
|
-
winston.verbose("(fbm) recipient_id: " + recipient_id);
|
|
381
|
-
winston.verbose("(fbm) page_id: " + page_id);
|
|
382
|
-
winston.verbose("(fbm) instagram_receiver: " + instagram_receiver);
|
|
408
|
+
winston.verbose("(ig) entry_id: " + entry_id + ", ig_receiver: " + ig_receiver + ", prefix: " + parsed.prefix);
|
|
383
409
|
|
|
384
410
|
// Return an info message option
|
|
385
|
-
if (settings.expired
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
winston.verbose("settings expired: " + settings.expired);
|
|
411
|
+
if (settings.expired === true) {
|
|
412
|
+
winston.verbose("(ig) Settings expired");
|
|
389
413
|
let tiledeskJsonMessage = {
|
|
390
414
|
text: 'Expired. Upgrade Plan.',
|
|
391
415
|
sender: "system",
|
|
392
416
|
senderFullname: "System",
|
|
393
|
-
attributes: {
|
|
394
|
-
subtype: 'info',
|
|
395
|
-
messagelabel: { key: 'PLAN_EXPIRED' } // for translation
|
|
396
|
-
},
|
|
417
|
+
attributes: { subtype: 'info', messagelabel: { key: 'PLAN_EXPIRED' } },
|
|
397
418
|
channel: { name: 'instagram' }
|
|
398
419
|
}
|
|
399
420
|
let message_info = {
|
|
400
421
|
channel: "instagram",
|
|
401
|
-
instagram: {
|
|
402
|
-
page_id: page_id,
|
|
403
|
-
sender_id: instagram_receiver
|
|
404
|
-
}
|
|
422
|
+
instagram: { page_id: entry_id, sender_id: ig_receiver }
|
|
405
423
|
}
|
|
406
|
-
|
|
407
424
|
const tdChannel = new TiledeskChannel({ settings: settings, API_URL: API_URL })
|
|
408
425
|
try {
|
|
409
426
|
await tdChannel.send(tiledeskJsonMessage, message_info, settings.department_id);
|
|
410
|
-
winston.verbose("(wab) Expiration message sent to AI Manager")
|
|
411
427
|
return res.sendStatus(200);
|
|
412
428
|
} catch (err) {
|
|
413
|
-
return res.status(500).send({ success: false, error: "Error sending
|
|
429
|
+
return res.status(500).send({ success: false, error: "Error sending expiration message" });
|
|
414
430
|
}
|
|
415
431
|
}
|
|
416
432
|
|
|
417
|
-
|
|
433
|
+
var tokenInfo = getOutboundToken(settings, entry_id);
|
|
434
|
+
if (!tokenInfo) {
|
|
435
|
+
winston.error("(ig) No access token available for outbound message (no pages, no OAuth)");
|
|
436
|
+
return res.status(200).send({ message: "skipped - no token" });
|
|
437
|
+
}
|
|
438
|
+
|
|
418
439
|
const fbClient = new FacebookClient({ GRAPH_URL: GRAPH_URL, FB_APP_ID: FB_APP_ID, APP_SECRET: APP_SECRET, BASE_URL: BASE_URL });
|
|
419
440
|
const messageHandler = new MessageHandler({ tiledeskChannelMessage: tiledeskChannelMessage });
|
|
420
441
|
const tlr = new TiledeskInstagramTranslator();
|
|
421
442
|
|
|
443
|
+
function sendMessageViaInstagram(jsonMsg, cb) {
|
|
444
|
+
sendInstagramMessage(jsonMsg, tokenInfo.token, tokenInfo.type, fbClient).then(function(response) {
|
|
445
|
+
winston.debug("(ig) Message sent via " + tokenInfo.type);
|
|
446
|
+
if (cb) cb(null, response);
|
|
447
|
+
}).catch(function(err) {
|
|
448
|
+
winston.error("(ig) Send error: ", err?.response?.data || err.message);
|
|
449
|
+
if (cb) cb(err);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
422
453
|
if (commands) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// message
|
|
454
|
+
var cmdIdx = 0;
|
|
455
|
+
function executeCommand(command) {
|
|
426
456
|
if (command.type === "message") {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
winston.debug("(fbm) End of splitted message -> go to next command")
|
|
447
|
-
i += 1;
|
|
448
|
-
if (i < commands.length) {
|
|
449
|
-
execute(commands[i]);
|
|
457
|
+
messageHandler.generateMessageObject(command).then(function(tiledeskCommandMessage) {
|
|
458
|
+
messageHandler.splitMessageFromTiledesk(tiledeskCommandMessage).then(function(messagesList) {
|
|
459
|
+
var msgIdx = 0;
|
|
460
|
+
function sendNextMessage(msg) {
|
|
461
|
+
tlr.toInstagram(msg, ig_receiver).then(function(instagramJsonMessage) {
|
|
462
|
+
if (instagramJsonMessage) {
|
|
463
|
+
sendMessageViaInstagram(instagramJsonMessage, function(err) {
|
|
464
|
+
if (!err) {
|
|
465
|
+
msgIdx++;
|
|
466
|
+
if (msgIdx < messagesList.length) {
|
|
467
|
+
sendNextMessage(messagesList[msgIdx]);
|
|
468
|
+
} else {
|
|
469
|
+
cmdIdx++;
|
|
470
|
+
if (cmdIdx < commands.length) {
|
|
471
|
+
executeCommand(commands[cmdIdx]);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
});
|
|
450
476
|
} else {
|
|
451
|
-
|
|
477
|
+
cmdIdx++;
|
|
478
|
+
if (cmdIdx < commands.length) executeCommand(commands[cmdIdx]);
|
|
452
479
|
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
sendNextMessage(messagesList[0]);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
} else if (command.type === "wait") {
|
|
486
|
+
setTimeout(function() {
|
|
487
|
+
cmdIdx++;
|
|
488
|
+
if (cmdIdx < commands.length) executeCommand(commands[cmdIdx]);
|
|
489
|
+
}, command.time);
|
|
462
490
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
491
|
+
}
|
|
492
|
+
executeCommand(commands[0]);
|
|
493
|
+
} else if (tiledeskChannelMessage.text || tiledeskChannelMessage.metadata) {
|
|
494
|
+
messageHandler.splitMessageFromTiledesk(tiledeskChannelMessage).then(function(messagesList) {
|
|
495
|
+
var msgIdx = 0;
|
|
496
|
+
function sendNext(msg) {
|
|
497
|
+
tlr.toInstagram(msg, ig_receiver).then(function(instagramJsonMessage) {
|
|
498
|
+
if (instagramJsonMessage) {
|
|
499
|
+
sendMessageViaInstagram(instagramJsonMessage, function() {
|
|
500
|
+
msgIdx++;
|
|
501
|
+
if (msgIdx < messagesList.length) sendNext(messagesList[msgIdx]);
|
|
502
|
+
});
|
|
472
503
|
}
|
|
473
|
-
}
|
|
504
|
+
});
|
|
474
505
|
}
|
|
475
|
-
|
|
476
|
-
|
|
506
|
+
sendNext(messagesList[0]);
|
|
507
|
+
});
|
|
477
508
|
}
|
|
478
509
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
let messagesList = await messageHandler.splitMessageFromTiledesk(tiledeskChannelMessage);
|
|
482
|
-
winston.debug("(fbm) message splitted in " + messagesList.length + " messages");
|
|
510
|
+
return res.status(200).send({ message: "sent" });
|
|
511
|
+
})
|
|
483
512
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
winston.verbose("(fbm) instagramJsonMessage", instagramJsonMessage)
|
|
513
|
+
function isValidIGSID(id) {
|
|
514
|
+
return /^\d{10,20}$/.test(String(id));
|
|
515
|
+
}
|
|
488
516
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
})
|
|
501
|
-
} else {
|
|
502
|
-
winston.error("(fbm) instagramJsonMessage is undefined!")
|
|
503
|
-
}
|
|
517
|
+
function convertTestWebhook(body) {
|
|
518
|
+
if (body.field && body.value) {
|
|
519
|
+
winston.info("(ig) Converting test webhook from Meta dashboard");
|
|
520
|
+
var messaging = [];
|
|
521
|
+
if (body.value && body.value.messages && body.value.messages.data) {
|
|
522
|
+
body.value.messages.data.forEach(function(msg) {
|
|
523
|
+
messaging.push({
|
|
524
|
+
sender: { id: msg.from ? msg.from.id : 'test_sender' },
|
|
525
|
+
message: { text: msg.text || '[Test message from Meta]' }
|
|
526
|
+
});
|
|
527
|
+
});
|
|
504
528
|
}
|
|
505
|
-
|
|
506
|
-
|
|
529
|
+
return {
|
|
530
|
+
object: 'instagram',
|
|
531
|
+
entry: [{ id: 'test_entry', messaging: messaging }]
|
|
532
|
+
};
|
|
507
533
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
winston.debug("(fbm) no command, no text --> skip")
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return res.status(200).send({ message: "sent" });
|
|
514
|
-
})
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
515
536
|
|
|
516
537
|
function verifyWebhookSignature(req) {
|
|
517
538
|
var signature = req.headers['x-hub-signature-256'];
|
|
518
539
|
if (!signature) {
|
|
519
|
-
winston.
|
|
520
|
-
return
|
|
540
|
+
winston.warn("(ig) Missing x-hub-signature-256 header — processing anyway");
|
|
541
|
+
return true;
|
|
521
542
|
}
|
|
522
543
|
if (!APP_SECRET) {
|
|
523
544
|
winston.warn("(ig) APP_SECRET not configured — skipping signature validation");
|
|
@@ -528,7 +549,7 @@ function verifyWebhookSignature(req) {
|
|
|
528
549
|
var hmac = crypto.createHmac(algo, APP_SECRET);
|
|
529
550
|
hmac.update(JSON.stringify(req.body));
|
|
530
551
|
var computed = hmac.digest('hex');
|
|
531
|
-
var valid =
|
|
552
|
+
var valid = (expected === computed);
|
|
532
553
|
if (!valid) {
|
|
533
554
|
winston.error("(ig) Webhook signature mismatch — expected=" + expected + " computed=" + computed);
|
|
534
555
|
if (process.env.DEBUG_SIGNATURES === 'true') {
|
|
@@ -538,12 +559,11 @@ function verifyWebhookSignature(req) {
|
|
|
538
559
|
return valid;
|
|
539
560
|
}
|
|
540
561
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
winston.verbose("(ig) Webhook received");
|
|
562
|
+
function processInstagramWebhook(req, res) {
|
|
563
|
+
winston.verbose("(ig) Processing Instagram webhook");
|
|
544
564
|
|
|
545
565
|
if (!verifyWebhookSignature(req)) {
|
|
546
|
-
|
|
566
|
+
winston.warn("(ig) Signature invalid — still responding 200 to keep webhook healthy");
|
|
547
567
|
}
|
|
548
568
|
|
|
549
569
|
res.status(200).send('EVENT_RECEIVED');
|
|
@@ -553,130 +573,139 @@ router.post('/webhookFB', async (req, res) => {
|
|
|
553
573
|
let body = req.body;
|
|
554
574
|
winston.verbose("(ig) Processing webhook asynchronously");
|
|
555
575
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
winston.debug("(fbm) Facebook page not enabled --> Skip")
|
|
562
|
-
return res.status(200).send('EVENT_RECEIVED');
|
|
563
|
-
}
|
|
576
|
+
var testBody = convertTestWebhook(body);
|
|
577
|
+
if (testBody) {
|
|
578
|
+
winston.info("(ig) Using converted test webhook body");
|
|
579
|
+
body = testBody;
|
|
580
|
+
}
|
|
564
581
|
|
|
565
|
-
|
|
566
|
-
|
|
582
|
+
if (body.object !== 'instagram' && body.object !== 'page') {
|
|
583
|
+
winston.debug("(ig) Unknown webhook object: " + body.object + " — skipping");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
567
586
|
|
|
568
|
-
|
|
569
|
-
|
|
587
|
+
body.entry.forEach(async (entry) => {
|
|
588
|
+
if (entry.changes) {
|
|
589
|
+
winston.verbose("(ig) Non-message event (changes): " + JSON.stringify(entry.changes).substring(0, 200));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
570
592
|
|
|
571
|
-
|
|
593
|
+
if (!entry.messaging || !entry.messaging[0]) {
|
|
594
|
+
winston.verbose("(ig) Entry has no messaging events — skipping");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
572
597
|
|
|
573
|
-
|
|
574
|
-
|
|
598
|
+
var entryId = entry.id;
|
|
599
|
+
winston.debug("(ig) Entry ID: " + entryId);
|
|
575
600
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
const fbClient = new FacebookClient({ GRAPH_URL: GRAPH_URL, FB_APP_ID: FB_APP_ID, APP_SECRET: APP_SECRET, BASE_URL: BASE_URL });
|
|
601
|
+
var PAGE_KEY = "instagram-page-" + entryId;
|
|
602
|
+
var info_settings = await db.get(PAGE_KEY);
|
|
579
603
|
|
|
580
|
-
|
|
604
|
+
if (!info_settings) {
|
|
605
|
+
winston.debug("(ig) No settings found for entry " + entryId + " — checking project by scanning");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
581
608
|
|
|
582
|
-
|
|
583
|
-
winston.
|
|
584
|
-
return { first_name: "User", last_name: instagramChannelMessage.sender.id };
|
|
585
|
-
});
|
|
609
|
+
var project_id = info_settings.project_id;
|
|
610
|
+
winston.debug("(ig) project_id: " + project_id);
|
|
586
611
|
|
|
587
|
-
|
|
612
|
+
let CONTENT_KEY = "instagram-" + project_id;
|
|
613
|
+
let settings = await db.get(CONTENT_KEY);
|
|
588
614
|
|
|
589
|
-
|
|
590
|
-
|
|
615
|
+
var instagramChannelMessage = entry.messaging[0];
|
|
616
|
+
winston.debug("(ig) webhook_event: ", instagramChannelMessage);
|
|
591
617
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
page_id: page_id,
|
|
596
|
-
sender_id: instagramChannelMessage.sender.id,
|
|
597
|
-
firstname: user_info.first_name,
|
|
598
|
-
lastname: user_info.last_name
|
|
599
|
-
}
|
|
600
|
-
}
|
|
618
|
+
const tlr = new TiledeskInstagramTranslator();
|
|
619
|
+
const tdChannel = new TiledeskChannel({ settings: settings, API_URL: API_URL });
|
|
620
|
+
const fbClient = new FacebookClient({ GRAPH_URL: GRAPH_URL, FB_APP_ID: FB_APP_ID, APP_SECRET: APP_SECRET, BASE_URL: BASE_URL });
|
|
601
621
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
instagramChannelMessage.message.attachments.length > 1) {
|
|
622
|
+
const page = settings && settings.pages ? settings.pages.find(p => p.id === entryId) : null;
|
|
623
|
+
var pageAccessToken = page ? page.access_token : null;
|
|
605
624
|
|
|
606
|
-
|
|
607
|
-
|
|
625
|
+
if (!pageAccessToken && settings && settings.ig_oauth_token) {
|
|
626
|
+
pageAccessToken = settings.ig_oauth_token.access_token;
|
|
627
|
+
}
|
|
608
628
|
|
|
609
|
-
|
|
629
|
+
var user_info = { first_name: "User", last_name: instagramChannelMessage.sender.id };
|
|
630
|
+
if (pageAccessToken) {
|
|
631
|
+
user_info = await fbClient.getUserInfo(pageAccessToken, instagramChannelMessage.sender.id).catch(function(err) {
|
|
632
|
+
winston.error("(ig) getUserInfo error: ", err?.response?.data || err.message || err);
|
|
633
|
+
return { first_name: "User", last_name: instagramChannelMessage.sender.id };
|
|
634
|
+
});
|
|
635
|
+
}
|
|
610
636
|
|
|
611
|
-
|
|
612
|
-
winston.verbose("(fbm) tiledeskJsonMessage: ", tiledeskJsonMessage);
|
|
637
|
+
instagramChannelMessage.sender.fullname = user_info.first_name + " " + user_info.last_name;
|
|
613
638
|
|
|
639
|
+
var message_info = {
|
|
640
|
+
channel: TiledeskInstagramTranslator.CHANNEL_NAME,
|
|
641
|
+
instagram: {
|
|
642
|
+
page_id: entryId,
|
|
643
|
+
sender_id: instagramChannelMessage.sender.id,
|
|
644
|
+
firstname: user_info.first_name,
|
|
645
|
+
lastname: user_info.last_name
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
if (instagramChannelMessage.message &&
|
|
650
|
+
instagramChannelMessage.message.attachments &&
|
|
651
|
+
instagramChannelMessage.message.attachments.length > 1) {
|
|
652
|
+
|
|
653
|
+
const messageHandler = new MessageHandler();
|
|
654
|
+
let messagesList = await messageHandler.splitMessageFromInstagram(instagramChannelMessage);
|
|
655
|
+
|
|
656
|
+
for (let message of messagesList) {
|
|
657
|
+
let tiledeskJsonMessage = await tlr.toTiledesk(message);
|
|
658
|
+
if (tiledeskJsonMessage) {
|
|
659
|
+
try {
|
|
660
|
+
await tdChannel.send(tiledeskJsonMessage, message_info, settings.department_id);
|
|
661
|
+
} catch (err) {
|
|
662
|
+
winston.error("(ig) Error sending split message: ", err);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
} else {
|
|
667
|
+
let tiledeskJsonMessage = await tlr.toTiledesk(instagramChannelMessage);
|
|
614
668
|
if (tiledeskJsonMessage) {
|
|
615
669
|
try {
|
|
616
670
|
await tdChannel.send(tiledeskJsonMessage, message_info, settings.department_id);
|
|
617
|
-
winston.verbose("(fbm) Message sent to AI Manager")
|
|
618
671
|
} catch (err) {
|
|
619
|
-
|
|
672
|
+
winston.error("(ig) Error sending message: ", err);
|
|
620
673
|
}
|
|
621
|
-
} else {
|
|
622
|
-
winston.verbose("(fbm) tiledeskJsonMessage is undefined!")
|
|
623
674
|
}
|
|
624
675
|
}
|
|
676
|
+
});
|
|
625
677
|
|
|
626
|
-
} else {
|
|
627
|
-
let tiledeskJsonMessage = await tlr.toTiledesk(instagramChannelMessage);
|
|
628
|
-
winston.verbose("(fbm) tiledeskJsonMessage: ", tiledeskJsonMessage);
|
|
629
|
-
|
|
630
|
-
if (tiledeskJsonMessage) {
|
|
631
|
-
try {
|
|
632
|
-
await tdChannel.send(tiledeskJsonMessage, message_info, settings.department_id);
|
|
633
|
-
winston.verbose("(fbm) Message sent to AI Manager")
|
|
634
|
-
} catch (err) {
|
|
635
|
-
return res.status(500).send({ success: false, error: "Error sending message" });
|
|
636
|
-
}
|
|
637
|
-
} else {
|
|
638
|
-
winston.verbose("(fbm) tiledeskJsonMessage is undefined!")
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
})
|
|
643
678
|
} catch (err) {
|
|
644
679
|
winston.error("(ig) Async webhook processing error: ", err?.response?.data || err.message || err);
|
|
645
680
|
}
|
|
646
681
|
});
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
router.get('/webhookFB', async (req, res) => {
|
|
650
|
-
|
|
651
|
-
winston.verbose("(fbm) Verify the webhook... ", req.query);
|
|
652
|
-
|
|
653
|
-
// Parse the query params
|
|
654
|
-
let mode = req.query['hub.mode'];
|
|
655
|
-
let token = req.query['hub.verify_token'];
|
|
656
|
-
let challenge = req.query['hub.challenge'];
|
|
657
|
-
|
|
658
|
-
winston.verbose("(fbm) token: " + token);
|
|
659
|
-
winston.verbose("(fbm) verify token: " + VERIFY_TOKEN);
|
|
660
|
-
winston.verbose("(fbm) mode: " + mode)
|
|
661
|
-
winston.verbose("(fbm) challenge: " + challenge)
|
|
662
|
-
|
|
663
|
-
// Checks if a token and mode is in the query string of the request
|
|
664
|
-
if (mode && token) {
|
|
682
|
+
}
|
|
665
683
|
|
|
666
|
-
|
|
667
|
-
|
|
684
|
+
router.post('/webhookIG', function(req, res) {
|
|
685
|
+
winston.verbose("(ig) POST /webhookIG");
|
|
686
|
+
processInstagramWebhook(req, res);
|
|
687
|
+
});
|
|
668
688
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
689
|
+
router.post('/webhookFB', function(req, res) {
|
|
690
|
+
winston.verbose("(ig) POST /webhookFB");
|
|
691
|
+
processInstagramWebhook(req, res);
|
|
692
|
+
});
|
|
672
693
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
694
|
+
function handleWebhookVerify(req, res) {
|
|
695
|
+
winston.verbose("(ig) Verify webhook... ", req.query);
|
|
696
|
+
var mode = req.query['hub.mode'];
|
|
697
|
+
var token = req.query['hub.verify_token'];
|
|
698
|
+
var challenge = req.query['hub.challenge'];
|
|
699
|
+
if (mode && token && mode === 'subscribe' && token === VERIFY_TOKEN) {
|
|
700
|
+
winston.info("(ig) Webhook verified");
|
|
701
|
+
return res.status(200).send(challenge);
|
|
677
702
|
}
|
|
703
|
+
winston.warn("(ig) Webhook verify failed — mode=" + mode + " token match=" + (token === VERIFY_TOKEN));
|
|
678
704
|
return res.sendStatus(403);
|
|
679
|
-
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
router.get('/webhookIG', function(req, res) { handleWebhookVerify(req, res); });
|
|
708
|
+
router.get('/webhookFB', function(req, res) { handleWebhookVerify(req, res); });
|
|
680
709
|
|
|
681
710
|
router.get('/auth/instagram/start', async (req, res) => {
|
|
682
711
|
winston.verbose("(ig) /auth/instagram/start");
|