@tiledesk/tiledesk-whatsapp-connector 0.1.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/index.js ADDED
@@ -0,0 +1,600 @@
1
+ "use strict";
2
+ const express = require("express");
3
+ const app = express();
4
+ const router = express.Router();
5
+ const bodyParser = require("body-parser");
6
+ //const request = require("request");
7
+ //const multer = require('multer');
8
+ //const jwt = require('jsonwebtoken');
9
+ //const { v4: uuidv4 } = require('uuid');
10
+ //const axios = require("axios").default;
11
+ //const mongoose = require("mongoose");
12
+ const appRoot = require('app-root-path');
13
+ const handlebars = require('handlebars');
14
+ const fs = require('fs');
15
+ //const Path = require('path');
16
+ //const FormData = require('form-data');
17
+
18
+ // tiledesk clients
19
+ const { TiledeskClient } = require('@tiledesk/tiledesk-client');
20
+ const { TiledeskWhatsappTranslator } = require('./tiledesk/TiledeskWhatsappTranslator');
21
+ const { TiledeskSubscriptionClient } = require('./tiledesk/TiledeskSubscriptionClient');
22
+ const { TiledeskWhatsapp } = require('./tiledesk/TiledeskWhatsapp');
23
+ const { TiledeskChannel } = require('./tiledesk/TiledeskChannel');
24
+
25
+ // mongo
26
+ const { KVBaseMongo } = require('@tiledesk/tiledesk-kvbasemongo');
27
+ const kvbase_collection = 'kvstore';
28
+
29
+ router.use(bodyParser.json());
30
+ router.use(bodyParser.urlencoded({ extended: true }));
31
+
32
+ //const TOKEN = process.env.WHATSAPP_TOKEN; // From FB DevX getting started page
33
+ //const VERIFY_TOKEN = process.env.VERIFY_TOKEN; // Arbitrary, used with webhook configuration in FB DevX
34
+ //const BASE_URL = process.env.BASE_URL;
35
+ //const API_URL = process.env.API_URL;
36
+ //const project_id = process.env.PROJECT_ID;
37
+ //const token_jwt = process.env.TOKEN_JWT
38
+ //const GRAPH_URL = process.env.GRAPH_URL;
39
+ //const MONGODB_URL = process.env.MONGODB_URL;
40
+
41
+
42
+ var API_URL = null;
43
+ var GRAPH_URL = null;
44
+ var BASE_URL = null;
45
+ var log = false;
46
+
47
+ const db = new KVBaseMongo(kvbase_collection);
48
+ /*
49
+ db.connect(MONGODB_URL, () => {
50
+ console.log("KVBaseMongo successfully connected.");
51
+ var server = app.listen(process.env.PORT || 1337, function() {
52
+ var port = server.address().port;
53
+ console.log("App now running on port", port);
54
+ });
55
+ })
56
+ */
57
+
58
+ router.get('/', async (req, res) => {
59
+ res.send('Home works!')
60
+ })
61
+
62
+ router.get('/configure', async (req, res) => {
63
+ console.log("\n/configure");
64
+ if (log) {
65
+ console.log("/configure query: ", req.query);
66
+ }
67
+
68
+ let projectId = "";
69
+ let token = "";
70
+
71
+ projectId = req.query.project_id;
72
+ token = req.query.token
73
+
74
+ let CONTENT_KEY = "whatsapp-" + projectId;
75
+
76
+ let settings = await db.get(CONTENT_KEY);
77
+ console.log("[KVDB] settings: ", settings);
78
+
79
+ let proxy_url = BASE_URL + "/webhook/" + projectId;
80
+ console.log("proxy_url: ", proxy_url);
81
+
82
+ if (settings) {
83
+
84
+ readHTMLFile('/configure.html', (err, html) => {
85
+ if (err) {
86
+ console.log("(ERROR) Read html file: ", err);
87
+ }
88
+
89
+ var template = handlebars.compile(html);
90
+ var replacements = {
91
+ project_id: projectId,
92
+ token: token,
93
+ proxy_url: proxy_url,
94
+ wab_token: settings.wab_token,
95
+ verify_token: settings.verify_token,
96
+ subscription_id: settings.subscriptionId,
97
+ }
98
+ if (log) {
99
+ console.log("Replacements: ", replacements);
100
+ }
101
+ var html = template(replacements);
102
+ res.send(html);
103
+
104
+ })
105
+
106
+ } else {
107
+
108
+ readHTMLFile('/configure.html', (err, html) => {
109
+
110
+ if (err) {
111
+ console.log("(ERROR) Read html file: ", err);
112
+ }
113
+
114
+ var template = handlebars.compile(html);
115
+ var replacements = {
116
+ project_id: projectId,
117
+ token: token,
118
+ proxy_url: proxy_url,
119
+ }
120
+ if (log) {
121
+ console.log("Replacements: ", replacements);
122
+ }
123
+ var html = template(replacements);
124
+ res.send(html);
125
+
126
+ })
127
+ }
128
+ })
129
+
130
+ router.post('/update', async (req, res) => {
131
+ console.log("\n/update");
132
+ if (log) {
133
+ console.log("/update body: ", req.body);
134
+ }
135
+
136
+ let projectId = req.body.project_id;
137
+ let token = req.body.token;
138
+ let wab_token = req.body.wab_token;
139
+ let verify_token = req.body.verify_token;
140
+
141
+ let CONTENT_KEY = "whatsapp-" + projectId;
142
+ let settings = await db.get(CONTENT_KEY);
143
+
144
+ let proxy_url = BASE_URL + "/webhook/" + projectId;
145
+
146
+ if (settings) {
147
+
148
+ settings.wab_token = wab_token;
149
+ settings.verify_token = verify_token;
150
+
151
+ await db.set(CONTENT_KEY, settings);
152
+
153
+ readHTMLFile('/configure.html', (err, html) => {
154
+ if (err) {
155
+ console.log("(ERROR) Read html file: ", err);
156
+ }
157
+
158
+ var template = handlebars.compile(html);
159
+ var replacements = {
160
+ project_id: projectId,
161
+ token: token,
162
+ proxy_url: proxy_url,
163
+ wab_token: settings.wab_token,
164
+ verify_token: settings.verify_token,
165
+ subscription_id: settings.subscriptionId,
166
+ }
167
+ if (log) {
168
+ console.log("Replacements: ", replacements);
169
+ }
170
+
171
+ var html = template(replacements);
172
+ res.send(html);
173
+ })
174
+
175
+ } else {
176
+
177
+ const tdClient = new TiledeskSubscriptionClient({ API_URL: API_URL, project_id: projectId, token: token, log: false })
178
+
179
+ const subscription_info = {
180
+ target: BASE_URL + "/tiledesk",
181
+ event: 'message.create.request.channel.whatsapp'
182
+ }
183
+
184
+ /*
185
+ // callback
186
+ await tdClient.subscribe(projectId, data, (err, data) => {
187
+ // code here
188
+ })
189
+ */
190
+
191
+ // promise
192
+ tdClient.subscribe(subscription_info).then((data) => {
193
+ let subscription = data;
194
+ if (log) {
195
+ console.log("\nSubscription: ", subscription)
196
+ }
197
+
198
+ let settings = {
199
+ project_id: projectId,
200
+ token: token,
201
+ proxy_url: proxy_url,
202
+ subscriptionId: subscription._id,
203
+ secret: subscription.secret,
204
+ wab_token: wab_token,
205
+ verify_token: verify_token,
206
+ }
207
+
208
+ db.set(CONTENT_KEY, settings)
209
+ //let cnt = db.get(CONTENT_KEY);
210
+
211
+ readHTMLFile('/configure.html', (err, html) => {
212
+ if (err) {
213
+ console.log("(ERROR) Read html file: ", err);
214
+ }
215
+
216
+ var template = handlebars.compile(html);
217
+ var replacements = {
218
+ project_id: projectId,
219
+ token: token,
220
+ proxy_url: proxy_url,
221
+ wab_token: settings.wab_token,
222
+ verify_token: settings.verify_token,
223
+ subscription_id: settings.subscriptionId,
224
+ }
225
+ if (log) {
226
+ console.log("Replacements: ", replacements);
227
+ }
228
+
229
+ var html = template(replacements);
230
+ res.send(html);
231
+ })
232
+
233
+ }).catch((err) => {
234
+ console.log("\n (ERROR) Subscription: ", err)
235
+ })
236
+
237
+ }
238
+ })
239
+
240
+ router.post('\n/disconnect', async (req, res) => {
241
+ console.log("/disconnect")
242
+ if (log) {
243
+ console.log("/disconnect body: ", req.body)
244
+ }
245
+
246
+ let projectId = req.body.project_id;
247
+ let token = req.body.token;
248
+ let subscriptionId = req.body.subscription_id;
249
+
250
+ let CONTENT_KEY = "whatsapp-" + projectId;
251
+ await db.remove(CONTENT_KEY);
252
+ console.log("Content deleted.");
253
+
254
+ let proxy_url = BASE_URL + "/webhook/" + projectId;
255
+
256
+ const tdClient = new TiledeskSubscriptionClient({ API_URL: API_URL, project_id: projectId, token: token, log: false })
257
+
258
+ /*
259
+ // callback
260
+ tdClient.unsubsribe(projectId, subscriptionId, (err, data) => {
261
+ // code here
262
+ })
263
+ */
264
+
265
+ tdClient.unsubscribe(subscriptionId).then((data) => {
266
+
267
+ readHTMLFile('/configure.html', (err, html) => {
268
+
269
+ if (err) {
270
+ console.log("(ERROR) Read html file: ", err);
271
+ }
272
+
273
+ var template = handlebars.compile(html);
274
+ var replacements = {
275
+ project_id: projectId,
276
+ token: token,
277
+ proxy_url: proxy_url,
278
+ }
279
+ if (log) {
280
+ console.log("Replacements: ", replacements);
281
+ }
282
+
283
+ var html = template(replacements);
284
+ res.send(html);
285
+ })
286
+
287
+ }).catch((err) => {
288
+ console.error("(ERROR) Unsubscribe: ", err);
289
+ })
290
+
291
+ })
292
+
293
+ router.post('/tiledesk', async (req, res) => {
294
+
295
+ console.log("\n/tiledesk")
296
+ if (log) {
297
+ console.log("/tiledesk tiledeskChannelMessage: ", req.body.payload);
298
+ }
299
+
300
+ var tiledeskChannelMessage = req.body.payload;
301
+ //console.log("(TILEDESK) Payload: ", JSON.stringify(req.body.payload));
302
+
303
+ var projectId = req.body.payload.id_project;
304
+ console.log("(TILEDESK) PROJECT ID: ", projectId);
305
+
306
+ // get settings from mongo
307
+ let CONTENT_KEY = "whatsapp-" + projectId;
308
+ let settings = await db.get(CONTENT_KEY);
309
+ let wab_token = settings.wab_token;
310
+
311
+ var text = req.body.payload.text;
312
+ console.log("(TILEDESK) TEXT: ", text);
313
+
314
+ var attributes = req.body.payload.attributes;
315
+ console.log("(TILEDESK) ATTRIBUTES: ", attributes)
316
+
317
+ var sender_id = req.body.payload.sender;
318
+ console.log("(TILEDESK) SENDER ID: ", sender_id);
319
+
320
+ if (sender_id.indexOf("wab") > -1) {
321
+ console.log("Skip same sender");
322
+ return res.send(200);
323
+ }
324
+
325
+ if (attributes && attributes.subtype === "info") {
326
+ console.log("(TILEDESK) Skip subtype (info) ");
327
+ return res.send(200);
328
+ }
329
+
330
+ let recipient_id = tiledeskChannelMessage.recipient;
331
+ console.log("(Tiledesk) Recipient_id: ", recipient_id);
332
+
333
+ //let last = recipient_id.lastIndexOf("-");
334
+
335
+ let whatsapp_receiver = recipient_id.substring(recipient_id.lastIndexOf("-") + 1);
336
+ console.log("(Tiledesk) whatsapp_receiver: ", whatsapp_receiver);
337
+
338
+ //let last2 = recipient_id.lastIndexOf("wab-");
339
+
340
+ let phone_number_id = recipient_id.substring(recipient_id.lastIndexOf("wab-") + 4, recipient_id.lastIndexOf("-"));
341
+ console.log("(Tiledesk) phone_number_id: ", phone_number_id)
342
+
343
+ const tlr = new TiledeskWhatsappTranslator({ channelMessage: tiledeskChannelMessage });
344
+
345
+ const whatsappJsonMessage = tlr.toWhatsapp(whatsapp_receiver);
346
+ console.log("whatsappJsonMessage: ", whatsappJsonMessage);
347
+
348
+ if (whatsappJsonMessage) {
349
+ const twClient = new TiledeskWhatsapp({ token: settings.wab_token, GRAPH_URL: GRAPH_URL });
350
+
351
+ twClient.sendMessage(phone_number_id, whatsappJsonMessage).then((response) => {
352
+ console.log("Send message response: ", response.status, response.statusText);
353
+ }).catch((err) => {
354
+ console.error("ERROR Send message: ", err);
355
+ })
356
+
357
+ /*
358
+ sendMessage(phone_number_id, whatsappJsonMessage, wab_token).then((response) => {
359
+ console.log("sendMessage() response: ", response);
360
+ }).catch((err) => {
361
+ console.log("ERROR sendMessage(): ", err);
362
+ })
363
+ */
364
+ } else {
365
+ console.error("Whatsapp Json Message is mandatory!")
366
+ }
367
+ // richiamo toWhatsapp;
368
+ // sendMessage
369
+ //callSendAPI(project_id, phone_number_id, to, payload);
370
+ return res.send(200);
371
+ })
372
+
373
+ // Endpoint for Whatsapp Business
374
+ // Accepts POST requests at /webhook endpoint
375
+ router.post("/webhook/:project_id", async (req, res) => {
376
+ // Parse the request body from the POST
377
+ let body = req.body;
378
+ let projectId = req.params.project_id;
379
+
380
+ // Check the Incoming webhook message
381
+ // console.log("\n***Body: ", JSON.stringify(req.body, null, 2));
382
+
383
+ // info on WhatsApp text message payload: https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#text-messages
384
+ if (req.body.object) {
385
+ if (
386
+ req.body.entry &&
387
+ req.body.entry[0].changes &&
388
+ req.body.entry[0].changes[0] &&
389
+ req.body.entry[0].changes[0].value.messages &&
390
+ req.body.entry[0].changes[0].value.messages[0]
391
+ ) {
392
+
393
+ console.log("\n***Body: ", JSON.stringify(req.body, null, 2));
394
+
395
+ //let originalWhatsappMessage = req.body;
396
+ let whatsappChannelMessage = req.body.entry[0].changes[0].value.messages[0];
397
+
398
+ let firstname = req.body.entry[0].changes[0].value.contacts[0].profile.name;
399
+ console.log("(WAB) firstname: ", firstname)
400
+
401
+ let message_info = {
402
+ channel: "whatsapp",
403
+ whatsapp: {
404
+ phone_number_id: req.body.entry[0].changes[0].value.metadata.phone_number_id,
405
+ from: req.body.entry[0].changes[0].value.messages[0].from,
406
+ firstname: req.body.entry[0].changes[0].value.contacts[0].profile.name,
407
+ lastname: " "
408
+ }
409
+ }
410
+
411
+ let CONTENT_KEY = "whatsapp-" + projectId;
412
+
413
+ let settings = await db.get(CONTENT_KEY);
414
+ if (log) {
415
+ console.log("[KVDB] settings: ", settings);
416
+ }
417
+
418
+ if (!settings) {
419
+ console.log("No settings found. Exit..");
420
+ res.sendStatus(200);
421
+ return;
422
+ }
423
+
424
+ let tiledeskJsonMessage;
425
+
426
+ const tlr = new TiledeskWhatsappTranslator({ channelMessage: whatsappChannelMessage });
427
+
428
+ if ((whatsappChannelMessage.type == 'text')) {
429
+ console.log("message type text")
430
+ tiledeskJsonMessage = tlr.toTiledesk(firstname);
431
+
432
+ }
433
+ else if (whatsappChannelMessage.type == 'interactive') {
434
+ console.log("message type interactive")
435
+ tiledeskJsonMessage = tlr.toTiledesk(firstname);
436
+
437
+ }
438
+ else if ((whatsappChannelMessage.type == 'image') || (whatsappChannelMessage.type == 'video') || (whatsappChannelMessage.type == 'document')) {
439
+ let media;
440
+ const util = new TiledeskWhatsapp({ token: settings.wab_token, GRAPH_URL: GRAPH_URL })
441
+
442
+ if (whatsappChannelMessage.type == 'image') {
443
+ media = whatsappChannelMessage.image;
444
+ console.log("media_id: ", media.id);
445
+
446
+ const filename = await util.downloadMedia(media.id);
447
+ console.log("File position: ", filename);
448
+
449
+ const image_url = await util.uploadMedia(filename, "images");
450
+ console.log("image_url: ", image_url)
451
+
452
+ tiledeskJsonMessage = tlr.toTiledesk(firstname, image_url);
453
+
454
+ }
455
+
456
+ if (whatsappChannelMessage.type == 'video') {
457
+ media = whatsappChannelMessage.video;
458
+
459
+ const filename = await util.downloadMedia(media.id);
460
+ console.log("File position: ", filename);
461
+
462
+ const media_url = await util.uploadMedia(filename, "files");
463
+ console.log("image_url: ", media_url)
464
+
465
+ tiledeskJsonMessage = tlr.toTiledesk(firstname, media_url);
466
+ }
467
+
468
+ if (whatsappChannelMessage.type == 'document') {
469
+ media = whatsappChannelMessage.document;
470
+
471
+ const filename = await util.downloadMedia(media.id);
472
+ console.log("File position: ", filename);
473
+
474
+ const media_url = await util.uploadMedia(filename, "files");
475
+ console.log("image_url: ", media_url)
476
+
477
+ tiledeskJsonMessage = tlr.toTiledesk(firstname, media_url);
478
+ }
479
+
480
+ } else {
481
+ // unsupported. Try anyway to send something.
482
+ }
483
+
484
+ console.log("tiledeskJsonMessage: ", tiledeskJsonMessage);
485
+
486
+ // tdClient.signInWithCustomToken
487
+ // tdClient.getRequests
488
+ const tdChannel = new TiledeskChannel({ settings: settings, API_URL: API_URL })
489
+ const response = await tdChannel.send(tiledeskJsonMessage, message_info);
490
+ if (log) {
491
+ console.log("Send response: ", response)
492
+ }
493
+
494
+ }
495
+ res.sendStatus(200);
496
+ } else {
497
+ // Return a '404 Not Found' if event is not from a WhatsApp API
498
+ res.sendStatus(404);
499
+ }
500
+ });
501
+
502
+ // Accepts GET requests at the /webhook endpoint. You need this URL to setup webhook initially.
503
+ // info on verification request payload: https://developers.facebook.com/docs/graph-api/webhooks/getting-started#verification-requests
504
+ router.get("/webhook/:project_id", async (req, res) => {
505
+ /**
506
+ * UPDATE YOUR VERIFY TOKEN
507
+ *This will be the Verify Token value when you set up webhook
508
+ **/
509
+
510
+ // Parse params from the webhook verification request
511
+ let mode = req.query["hub.mode"];
512
+ let token = req.query["hub.verify_token"];
513
+ let challenge = req.query["hub.challenge"];
514
+
515
+ let CONTENT_KEY = "whatsapp-" + req.params.project_id;
516
+
517
+ let settings = await db.get(CONTENT_KEY);
518
+
519
+ if (!settings || !settings.verify_token) {
520
+ console.error("No settings found! Unable to verify token.")
521
+ res.sendStatus(403);
522
+ } else {
523
+ let VERIFY_TOKEN = settings.verify_token;
524
+
525
+ // Check if a token and mode were sent
526
+ if (mode && token) {
527
+ // Check the mode and token sent are correct
528
+ if (mode === "subscribe" && token === VERIFY_TOKEN) {
529
+ // Respond with 200 OK and challenge token from the request
530
+ console.log("WEBHOOK_VERIFIED");
531
+ res.status(200).send(challenge);
532
+ } else {
533
+ // Responds with '403 Forbidden' if verify tokens do not match
534
+ res.sendStatus(403);
535
+ }
536
+ }
537
+
538
+ }
539
+
540
+ });
541
+
542
+
543
+ // *****************************
544
+ // ********* FUNCTIONS *********
545
+ // *****************************
546
+
547
+ function startApp(settings, callback) {
548
+ console.log("Starting Whatsapp App");
549
+
550
+ if (!settings.MONGODB_URL) {
551
+ throw new Error("settings.MONGODB_URL is mandatory");
552
+ }
553
+
554
+ if (!settings.API_URL) {
555
+ throw new Error("settings.API_URL is mandatory");
556
+ } else {
557
+ API_URL = settings.API_URL;
558
+ console.log("API_URL: ", API_URL);
559
+ }
560
+
561
+ if (!settings.BASE_URL) {
562
+ throw new Error("settings.BASE_URL is mandatory");
563
+ } else {
564
+ BASE_URL = settings.BASE_URL;
565
+ console.log("BASE_URL: ", BASE_URL);
566
+ }
567
+
568
+ if (!settings.GRAPH_URL) {
569
+ throw new Error("settings.GRAPH_URL is mandatory");
570
+ } else {
571
+ GRAPH_URL = settings.GRAPH_URL;
572
+ console.log("GRAPH_URL: ", GRAPH_URL);
573
+ }
574
+
575
+ if (settings.log) {
576
+ log = settings.log;
577
+ }
578
+
579
+ db.connect(settings.MONGODB_URL, () => {
580
+ console.log("KVBaseMongo successfully connected.");
581
+ if (callback) {
582
+ callback();
583
+ }
584
+ })
585
+ }
586
+
587
+ function readHTMLFile(templateName, callback) {
588
+ console.log("Reading file: ", templateName)
589
+ fs.readFile(appRoot + '/template/' + templateName, { encoding: 'utf-8' },
590
+ function(err, html) {
591
+ if (err) {
592
+ throw err;
593
+ //callback(err);
594
+ } else {
595
+ callback(null, html)
596
+ }
597
+ })
598
+ }
599
+
600
+ module.exports = { router: router, startApp: startApp};
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@tiledesk/tiledesk-whatsapp-connector",
3
+ "version": "0.1.0",
4
+ "description": "Tiledesk Whatsapp connector",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "mocha",
8
+ "start": "node index.js"
9
+ },
10
+ "keywords": [],
11
+ "author": "Giovanni Troisi",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "@tiledesk/tiledesk-client": "^0.8.29",
15
+ "@tiledesk/tiledesk-kvbasemongo": "^1.0.0",
16
+ "app-root-path": "^3.0.0",
17
+ "axios": "^0.27.2",
18
+ "body-parser": "^1.20.0",
19
+ "dotenv": "^16.0.2",
20
+ "express": "^4.18.1",
21
+ "form-data": "^4.0.0",
22
+ "handlebars": "^4.7.7",
23
+ "jsonwebtoken": "^8.5.1",
24
+ "mongoose": "^6.5.0",
25
+ "multer": "^1.4.5-lts.1",
26
+ "uuid": "^8.3.2"
27
+ },
28
+ "devDependencies": {
29
+ "mocha": "^8.2.1"
30
+ }
31
+ }
package/publish.sh ADDED
@@ -0,0 +1,10 @@
1
+ #npm version patch
2
+ version=`node -e 'console.log(require("./package.json").version)'`
3
+ echo "version $version"
4
+
5
+ if [ "$version" != "" ]; then
6
+ git tag -a "$version" -m "`git log -1 --format=%s`"
7
+ echo "Created a new tag, $version"
8
+ git push --tags
9
+ npm publish --access public
10
+ fi