@resultcrafter/aimanager-instagram-connector 0.3.0 → 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.
@@ -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.data);
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) + "-fbm-" + channel.page_id + "-" + channel.sender_id;
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
- //messaging_product: TiledeskInstagramTranslator.INSTAGRAM_MESSAGING_PRODUCT,
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: "template",
85
+ type: "video",
86
86
  payload: {
87
- template_type: "open_graph",
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
- instagram_message.type = 'document'
115
- instagram_message.document = {
116
- link: tiledeskChannelMessage.metadata.src,
117
- caption: tiledeskChannelMessage.metadata.name || tiledeskChannelMessage.text
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("(fbm) Message received from AI Manager");
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,171 +391,129 @@ 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("(fbm) Skip same sender");
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("(fbm) Skip subtype (info)");
358
- return res.sendStatus(200);
359
- }
360
-
361
- if (attributes && attributes.subtype === "private") {
362
- winston.verbose("(wab) Skip subtype (private)");
363
- return res.sendStatus(200);
364
- }
365
-
366
- if (attributes && attributes.subtype === 'info/support') {
367
- winston.debug("(fbm) Skip subtype: " + attributes.subtype);
399
+ if (attributes && (attributes.subtype === "info" || attributes.subtype === "private" || attributes.subtype === 'info/support')) {
400
+ winston.debug("(ig) Skip subtype: " + attributes.subtype);
368
401
  return res.sendStatus(200);
369
402
  }
370
403
 
371
- let recipient_id = tiledeskChannelMessage.recipient;
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);
404
+ var parsed = parseRecipient(tiledeskChannelMessage.recipient);
405
+ var entry_id = parsed.entry_id;
406
+ var ig_receiver = parsed.ig_receiver;
375
407
 
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
- settings.expired === true) {
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 exoìpiration message" });
429
+ return res.status(500).send({ success: false, error: "Error sending expiration message" });
414
430
  }
415
431
  }
416
432
 
417
- const page_detail = settings.pages.find(p => p.id === page_id);
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
- let i = 0;
424
- async function execute(command) {
425
- // message
454
+ var cmdIdx = 0;
455
+ function executeCommand(command) {
426
456
  if (command.type === "message") {
427
- let tiledeskCommandMessage = await messageHandler.generateMessageObject(command);
428
- winston.debug("(fbm) message generated from command: ", tiledeskCommandMessage)
429
-
430
-
431
- let messagesList = await messageHandler.splitMessageFromTiledesk(tiledeskCommandMessage);
432
-
433
- let j = 0;
434
- async function send(message) {
435
- let instagramJsonMessage = await tlr.toInstagram(message, instagram_receiver)
436
- winston.verbose("(fbm) instagramJsonMessage" + JSON.stringify(instagramJsonMessage));
437
-
438
-
439
- if (instagramJsonMessage) {
440
- fbClient.send(instagramJsonMessage, page_detail.access_token).then((response) => {
441
- winston.debug("(fbm) Message sent to Instagram!" + response.status + response.statusText);
442
- j += 1;
443
- if (j < messagesList.length) {
444
- send(messagesList[j])
445
- } else {
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
- winston.debug("(fbm) End of commands")
477
+ cmdIdx++;
478
+ if (cmdIdx < commands.length) executeCommand(commands[cmdIdx]);
452
479
  }
453
- }
454
- }).catch((err) => {
455
- winston.error("(fbm) error send message: ", err?.response?.data || err.message || err);
456
- });
457
- } else {
458
- winston.error("(fbm) instagramJsonMessage is undefined!")
459
- }
460
- }
461
- send(messagesList[0]);
462
- }
463
-
464
- // wait
465
- if (command.type === "wait") {
466
- setTimeout(() => {
467
- i += 1;
468
- if (i < commands.length) {
469
- execute(commands[i]);
470
- } else {
471
- winston.debug("(fbm) End of commands")
472
- }
473
- }, command.time)
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);
474
490
  }
475
491
  }
476
- execute(commands[0]);
477
- }
478
-
479
- else if (tiledeskChannelMessage.text || tiledeskChannelMessage.metadata) {
480
-
481
- let messagesList = await messageHandler.splitMessageFromTiledesk(tiledeskChannelMessage);
482
- winston.debug("(fbm) message splitted in " + messagesList.length + " messages");
483
-
484
- let j = 0;
485
- async function send(message) {
486
- let instagramJsonMessage = await tlr.toInstagram(message, instagram_receiver);
487
- winston.verbose("(fbm) instagramJsonMessage", instagramJsonMessage)
488
-
489
- if (instagramJsonMessage) {
490
- fbClient.send(instagramJsonMessage, page_detail.access_token).then((response) => {
491
- winston.verbose("(fbm) Message sent to Instagram!" + response.status + " " + response.statusText);
492
- j += 1;
493
- if (j < messagesList.length) {
494
- send(messagesList[j])
495
- } else {
496
- winston.debug("(fbm) End of splitted message")
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
+ });
497
503
  }
498
- }).catch((err) => {
499
- winston.error("(fbm) error send message: ", err)
500
- })
501
- } else {
502
- winston.error("(fbm) instagramJsonMessage is undefined!")
504
+ });
503
505
  }
504
- }
505
- send(messagesList[0]);
506
-
507
- }
508
-
509
- else {
510
- winston.debug("(fbm) no command, no text --> skip")
506
+ sendNext(messagesList[0]);
507
+ });
511
508
  }
512
509
 
513
510
  return res.status(200).send({ message: "sent" });
514
511
  })
515
512
 
513
+ function isValidIGSID(id) {
514
+ return /^\d{10,20}$/.test(String(id));
515
+ }
516
+
516
517
  function convertTestWebhook(body) {
517
518
  if (body.field && body.value) {
518
519
  winston.info("(ig) Converting test webhook from Meta dashboard");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resultcrafter/aimanager-instagram-connector",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "AI Manager Instagram DM connector",
5
5
  "main": "index.js",
6
6
  "scripts": {