@resultcrafter/aimanager-instagram-connector 0.2.1 → 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 (2) hide show
  1. package/index.js +127 -99
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -513,11 +513,31 @@ router.post('/tiledesk', async (req, res) => {
513
513
  return res.status(200).send({ message: "sent" });
514
514
  })
515
515
 
516
+ function convertTestWebhook(body) {
517
+ if (body.field && body.value) {
518
+ winston.info("(ig) Converting test webhook from Meta dashboard");
519
+ var messaging = [];
520
+ if (body.value && body.value.messages && body.value.messages.data) {
521
+ body.value.messages.data.forEach(function(msg) {
522
+ messaging.push({
523
+ sender: { id: msg.from ? msg.from.id : 'test_sender' },
524
+ message: { text: msg.text || '[Test message from Meta]' }
525
+ });
526
+ });
527
+ }
528
+ return {
529
+ object: 'instagram',
530
+ entry: [{ id: 'test_entry', messaging: messaging }]
531
+ };
532
+ }
533
+ return null;
534
+ }
535
+
516
536
  function verifyWebhookSignature(req) {
517
537
  var signature = req.headers['x-hub-signature-256'];
518
538
  if (!signature) {
519
- winston.error("(ig) Missing x-hub-signature-256 header");
520
- return false;
539
+ winston.warn("(ig) Missing x-hub-signature-256 header — processing anyway");
540
+ return true;
521
541
  }
522
542
  if (!APP_SECRET) {
523
543
  winston.warn("(ig) APP_SECRET not configured — skipping signature validation");
@@ -528,7 +548,7 @@ function verifyWebhookSignature(req) {
528
548
  var hmac = crypto.createHmac(algo, APP_SECRET);
529
549
  hmac.update(JSON.stringify(req.body));
530
550
  var computed = hmac.digest('hex');
531
- var valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(computed));
551
+ var valid = (expected === computed);
532
552
  if (!valid) {
533
553
  winston.error("(ig) Webhook signature mismatch — expected=" + expected + " computed=" + computed);
534
554
  if (process.env.DEBUG_SIGNATURES === 'true') {
@@ -538,12 +558,11 @@ function verifyWebhookSignature(req) {
538
558
  return valid;
539
559
  }
540
560
 
541
- router.post('/webhookFB', async (req, res) => {
542
-
543
- winston.verbose("(ig) Webhook received");
561
+ function processInstagramWebhook(req, res) {
562
+ winston.verbose("(ig) Processing Instagram webhook");
544
563
 
545
564
  if (!verifyWebhookSignature(req)) {
546
- return res.sendStatus(403);
565
+ winston.warn("(ig) Signature invalid — still responding 200 to keep webhook healthy");
547
566
  }
548
567
 
549
568
  res.status(200).send('EVENT_RECEIVED');
@@ -553,130 +572,139 @@ router.post('/webhookFB', async (req, res) => {
553
572
  let body = req.body;
554
573
  winston.verbose("(ig) Processing webhook asynchronously");
555
574
 
556
- let page_id = body.entry[0].id;
557
- let PAGE_KEY = "instagram-page-" + page_id;
558
- let info_settings = await db.get(PAGE_KEY);
559
-
560
- if (!info_settings) {
561
- winston.debug("(fbm) Facebook page not enabled --> Skip")
562
- return res.status(200).send('EVENT_RECEIVED');
563
- }
575
+ var testBody = convertTestWebhook(body);
576
+ if (testBody) {
577
+ winston.info("(ig) Using converted test webhook body");
578
+ body = testBody;
579
+ }
564
580
 
565
- let project_id = info_settings.project_id;
566
- winston.debug("(fbm) project_id: " + project_id);
581
+ if (body.object !== 'instagram' && body.object !== 'page') {
582
+ winston.debug("(ig) Unknown webhook object: " + body.object + " — skipping");
583
+ return;
584
+ }
567
585
 
568
- let CONTENT_KEY = "instagram-" + project_id;
569
- let settings = await db.get(CONTENT_KEY);
586
+ body.entry.forEach(async (entry) => {
587
+ if (entry.changes) {
588
+ winston.verbose("(ig) Non-message event (changes): " + JSON.stringify(entry.changes).substring(0, 200));
589
+ return;
590
+ }
570
591
 
571
- body.entry.forEach(async (entry) => {
592
+ if (!entry.messaging || !entry.messaging[0]) {
593
+ winston.verbose("(ig) Entry has no messaging events — skipping");
594
+ return;
595
+ }
572
596
 
573
- let instagramChannelMessage = entry.messaging[0];
574
- winston.debug("(fbm) webhook_event: ", instagramChannelMessage);
597
+ var entryId = entry.id;
598
+ winston.debug("(ig) Entry ID: " + entryId);
575
599
 
576
- const tlr = new TiledeskInstagramTranslator();
577
- const tdChannel = new TiledeskChannel({ settings: settings, API_URL: API_URL })
578
- const fbClient = new FacebookClient({ GRAPH_URL: GRAPH_URL, FB_APP_ID: FB_APP_ID, APP_SECRET: APP_SECRET, BASE_URL: BASE_URL });
600
+ var PAGE_KEY = "instagram-page-" + entryId;
601
+ var info_settings = await db.get(PAGE_KEY);
579
602
 
580
- const page = settings.pages.find(p => p.id === page_id);
603
+ if (!info_settings) {
604
+ winston.debug("(ig) No settings found for entry " + entryId + " — checking project by scanning");
605
+ return;
606
+ }
581
607
 
582
- let user_info = await fbClient.getUserInfo(page.access_token, instagramChannelMessage.sender.id).catch((err) => {
583
- winston.error("(fbm) getUserInfo error: ", err?.response?.data || err.message || err);
584
- return { first_name: "User", last_name: instagramChannelMessage.sender.id };
585
- });
608
+ var project_id = info_settings.project_id;
609
+ winston.debug("(ig) project_id: " + project_id);
586
610
 
587
- instagramChannelMessage.sender.fullname = user_info.first_name + " " + user_info.last_name;
611
+ let CONTENT_KEY = "instagram-" + project_id;
612
+ let settings = await db.get(CONTENT_KEY);
588
613
 
589
- winston.debug("(fbm) page: " + page);
590
- winston.debug("(fbm) user_info: ", user_info);
614
+ var instagramChannelMessage = entry.messaging[0];
615
+ winston.debug("(ig) webhook_event: ", instagramChannelMessage);
591
616
 
592
- let message_info = {
593
- channel: TiledeskInstagramTranslator.CHANNEL_NAME,
594
- instagram: {
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
- }
617
+ const tlr = new TiledeskInstagramTranslator();
618
+ const tdChannel = new TiledeskChannel({ settings: settings, API_URL: API_URL });
619
+ const fbClient = new FacebookClient({ GRAPH_URL: GRAPH_URL, FB_APP_ID: FB_APP_ID, APP_SECRET: APP_SECRET, BASE_URL: BASE_URL });
601
620
 
602
- if (instagramChannelMessage.message &&
603
- instagramChannelMessage.message.attachments &&
604
- instagramChannelMessage.message.attachments.length > 1) {
621
+ const page = settings && settings.pages ? settings.pages.find(p => p.id === entryId) : null;
622
+ var pageAccessToken = page ? page.access_token : null;
605
623
 
606
- const messageHandler = new MessageHandler();
607
- let messagesList = await messageHandler.splitMessageFromInstagram(instagramChannelMessage);
624
+ if (!pageAccessToken && settings && settings.ig_oauth_token) {
625
+ pageAccessToken = settings.ig_oauth_token.access_token;
626
+ }
608
627
 
609
- for (let message of messagesList) {
628
+ var user_info = { first_name: "User", last_name: instagramChannelMessage.sender.id };
629
+ if (pageAccessToken) {
630
+ user_info = await fbClient.getUserInfo(pageAccessToken, instagramChannelMessage.sender.id).catch(function(err) {
631
+ winston.error("(ig) getUserInfo error: ", err?.response?.data || err.message || err);
632
+ return { first_name: "User", last_name: instagramChannelMessage.sender.id };
633
+ });
634
+ }
610
635
 
611
- let tiledeskJsonMessage = await tlr.toTiledesk(message);
612
- winston.verbose("(fbm) tiledeskJsonMessage: ", tiledeskJsonMessage);
636
+ instagramChannelMessage.sender.fullname = user_info.first_name + " " + user_info.last_name;
613
637
 
638
+ var message_info = {
639
+ channel: TiledeskInstagramTranslator.CHANNEL_NAME,
640
+ instagram: {
641
+ page_id: entryId,
642
+ sender_id: instagramChannelMessage.sender.id,
643
+ firstname: user_info.first_name,
644
+ lastname: user_info.last_name
645
+ }
646
+ };
647
+
648
+ if (instagramChannelMessage.message &&
649
+ instagramChannelMessage.message.attachments &&
650
+ instagramChannelMessage.message.attachments.length > 1) {
651
+
652
+ const messageHandler = new MessageHandler();
653
+ let messagesList = await messageHandler.splitMessageFromInstagram(instagramChannelMessage);
654
+
655
+ for (let message of messagesList) {
656
+ let tiledeskJsonMessage = await tlr.toTiledesk(message);
657
+ if (tiledeskJsonMessage) {
658
+ try {
659
+ await tdChannel.send(tiledeskJsonMessage, message_info, settings.department_id);
660
+ } catch (err) {
661
+ winston.error("(ig) Error sending split message: ", err);
662
+ }
663
+ }
664
+ }
665
+ } else {
666
+ let tiledeskJsonMessage = await tlr.toTiledesk(instagramChannelMessage);
614
667
  if (tiledeskJsonMessage) {
615
668
  try {
616
669
  await tdChannel.send(tiledeskJsonMessage, message_info, settings.department_id);
617
- winston.verbose("(fbm) Message sent to AI Manager")
618
670
  } catch (err) {
619
- return res.status(500).send({ success: false, error: "Error sending exoìpiration message" });
671
+ winston.error("(ig) Error sending message: ", err);
620
672
  }
621
- } else {
622
- winston.verbose("(fbm) tiledeskJsonMessage is undefined!")
623
- }
624
- }
625
-
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
673
  }
637
- } else {
638
- winston.verbose("(fbm) tiledeskJsonMessage is undefined!")
639
674
  }
640
- }
675
+ });
641
676
 
642
- })
643
677
  } catch (err) {
644
678
  winston.error("(ig) Async webhook processing error: ", err?.response?.data || err.message || err);
645
679
  }
646
680
  });
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) {
681
+ }
665
682
 
666
- // Checks the mode and token sent is correct
667
- if (mode === 'subscribe' && token === VERIFY_TOKEN) {
683
+ router.post('/webhookIG', function(req, res) {
684
+ winston.verbose("(ig) POST /webhookIG");
685
+ processInstagramWebhook(req, res);
686
+ });
668
687
 
669
- // Responds with the challenge token from the request
670
- winston.verbose('(fbm) Webhook verified');
671
- return res.status(200).send(challenge);
688
+ router.post('/webhookFB', function(req, res) {
689
+ winston.verbose("(ig) POST /webhookFB");
690
+ processInstagramWebhook(req, res);
691
+ });
672
692
 
673
- } else {
674
- // Responds with '403 Forbidden' if verify tokens do not match
675
- return res.sendStatus(403);
676
- }
693
+ function handleWebhookVerify(req, res) {
694
+ winston.verbose("(ig) Verify webhook... ", req.query);
695
+ var mode = req.query['hub.mode'];
696
+ var token = req.query['hub.verify_token'];
697
+ var challenge = req.query['hub.challenge'];
698
+ if (mode && token && mode === 'subscribe' && token === VERIFY_TOKEN) {
699
+ winston.info("(ig) Webhook verified");
700
+ return res.status(200).send(challenge);
677
701
  }
702
+ winston.warn("(ig) Webhook verify failed — mode=" + mode + " token match=" + (token === VERIFY_TOKEN));
678
703
  return res.sendStatus(403);
679
- })
704
+ }
705
+
706
+ router.get('/webhookIG', function(req, res) { handleWebhookVerify(req, res); });
707
+ router.get('/webhookFB', function(req, res) { handleWebhookVerify(req, res); });
680
708
 
681
709
  router.get('/auth/instagram/start', async (req, res) => {
682
710
  winston.verbose("(ig) /auth/instagram/start");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resultcrafter/aimanager-instagram-connector",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "AI Manager Instagram DM connector",
5
5
  "main": "index.js",
6
6
  "scripts": {