strapi-plugin-magic-mail 2.2.5 → 2.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.
- package/README.md +168 -12
- package/dist/_chunks/{App-Bze8Ixs_.js → App-BMaJu77a.js} +787 -71
- package/dist/_chunks/{App-BZaHrE0R.mjs → App-DxiMl-Zd.mjs} +790 -74
- package/dist/_chunks/{de-DS04rP54.mjs → de-CpIQf94q.mjs} +21 -1
- package/dist/_chunks/{de-CN-G9j1S.js → de-YhjDItIL.js} +21 -1
- package/dist/_chunks/{en-BEFQJXvR.mjs → en-BHmOVzsP.mjs} +21 -1
- package/dist/_chunks/{en-BDc7Jk8u.js → en-BcdTnA2-.js} +21 -1
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +1102 -5
- package/dist/server/index.mjs +1100 -5
- package/package.json +10 -5
package/dist/server/index.js
CHANGED
|
@@ -5,6 +5,8 @@ const require$$1$1 = require("os");
|
|
|
5
5
|
const require$$0$3 = require("mustache");
|
|
6
6
|
const require$$1$2 = require("html-to-text");
|
|
7
7
|
const require$$2$2 = require("decode-html");
|
|
8
|
+
const require$$0$4 = require("path");
|
|
9
|
+
const require$$1$3 = require("fs");
|
|
8
10
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
9
11
|
const require$$0__default$1 = /* @__PURE__ */ _interopDefault(require$$0$2);
|
|
10
12
|
const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0$1);
|
|
@@ -12,6 +14,8 @@ const require$$1__default = /* @__PURE__ */ _interopDefault(require$$1$1);
|
|
|
12
14
|
const require$$0__default$2 = /* @__PURE__ */ _interopDefault(require$$0$3);
|
|
13
15
|
const require$$1__default$1 = /* @__PURE__ */ _interopDefault(require$$1$2);
|
|
14
16
|
const require$$2__default = /* @__PURE__ */ _interopDefault(require$$2$2);
|
|
17
|
+
const require$$0__default$3 = /* @__PURE__ */ _interopDefault(require$$0$4);
|
|
18
|
+
const require$$1__default$2 = /* @__PURE__ */ _interopDefault(require$$1$3);
|
|
15
19
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
16
20
|
function getDefaultExportFromCjs(x2) {
|
|
17
21
|
return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
|
|
@@ -265,7 +269,7 @@ const collectionName$5 = "magic_mail_routing_rules";
|
|
|
265
269
|
const info$5 = { "singularName": "routing-rule", "pluralName": "routing-rules", "displayName": "Email Routing Rule", "description": "Rules for routing emails to specific accounts" };
|
|
266
270
|
const options$5 = { "draftAndPublish": false };
|
|
267
271
|
const pluginOptions$5 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
268
|
-
const attributes$5 = { "name": { "type": "string", "required": true, "unique": true }, "description": { "type": "text" }, "isActive": { "type": "boolean", "default": true, "required": true }, "priority": { "type": "integer", "default": 1, "min": 1 }, "matchType": { "type": "enumeration", "enum": ["emailType", "subject", "recipient", "template", "custom"], "required": true, "default": "emailType" }, "matchValue": { "type": "text", "required": true }, "accountName": { "type": "string", "required": true }, "fallbackAccountName": { "type": "string" } };
|
|
272
|
+
const attributes$5 = { "name": { "type": "string", "required": true, "unique": true }, "description": { "type": "text" }, "isActive": { "type": "boolean", "default": true, "required": true }, "priority": { "type": "integer", "default": 1, "min": 1 }, "matchType": { "type": "enumeration", "enum": ["emailType", "subject", "recipient", "template", "custom"], "required": true, "default": "emailType" }, "matchValue": { "type": "text", "required": true }, "accountName": { "type": "string", "required": true }, "fallbackAccountName": { "type": "string" }, "whatsappFallback": { "type": "boolean", "default": false }, "whatsappPhoneField": { "type": "string" } };
|
|
269
273
|
const require$$1 = {
|
|
270
274
|
kind: kind$5,
|
|
271
275
|
collectionName: collectionName$5,
|
|
@@ -402,6 +406,88 @@ function requireController() {
|
|
|
402
406
|
strapi.log.error("[magic-mail] Error sending email:", err);
|
|
403
407
|
ctx.throw(500, err.message || "Failed to send email");
|
|
404
408
|
}
|
|
409
|
+
},
|
|
410
|
+
/**
|
|
411
|
+
* Send message via Email or WhatsApp (unified API)
|
|
412
|
+
* POST /api/magic-mail/send-message
|
|
413
|
+
* Body: { channel: 'email' | 'whatsapp' | 'auto', to, phoneNumber, subject, message, ... }
|
|
414
|
+
*/
|
|
415
|
+
async sendMessage(ctx) {
|
|
416
|
+
try {
|
|
417
|
+
const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
|
|
418
|
+
const result = await emailRouter2.sendMessage(ctx.request.body);
|
|
419
|
+
ctx.body = {
|
|
420
|
+
success: true,
|
|
421
|
+
...result
|
|
422
|
+
};
|
|
423
|
+
} catch (err) {
|
|
424
|
+
strapi.log.error("[magic-mail] Error sending message:", err);
|
|
425
|
+
ctx.throw(500, err.message || "Failed to send message");
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
/**
|
|
429
|
+
* Send WhatsApp message
|
|
430
|
+
* POST /api/magic-mail/send-whatsapp
|
|
431
|
+
* Body: { phoneNumber, message, templateId?, templateData? }
|
|
432
|
+
*/
|
|
433
|
+
async sendWhatsApp(ctx) {
|
|
434
|
+
try {
|
|
435
|
+
const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
|
|
436
|
+
const result = await emailRouter2.sendWhatsApp(ctx.request.body);
|
|
437
|
+
ctx.body = {
|
|
438
|
+
success: true,
|
|
439
|
+
...result
|
|
440
|
+
};
|
|
441
|
+
} catch (err) {
|
|
442
|
+
strapi.log.error("[magic-mail] Error sending WhatsApp:", err);
|
|
443
|
+
ctx.throw(500, err.message || "Failed to send WhatsApp message");
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
/**
|
|
447
|
+
* Get WhatsApp connection status
|
|
448
|
+
* GET /api/magic-mail/whatsapp/status
|
|
449
|
+
*/
|
|
450
|
+
async getWhatsAppStatus(ctx) {
|
|
451
|
+
try {
|
|
452
|
+
const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
|
|
453
|
+
const status = emailRouter2.getWhatsAppStatus();
|
|
454
|
+
ctx.body = {
|
|
455
|
+
success: true,
|
|
456
|
+
data: status
|
|
457
|
+
};
|
|
458
|
+
} catch (err) {
|
|
459
|
+
strapi.log.error("[magic-mail] Error getting WhatsApp status:", err);
|
|
460
|
+
ctx.body = {
|
|
461
|
+
success: false,
|
|
462
|
+
data: {
|
|
463
|
+
isConnected: false,
|
|
464
|
+
status: "error",
|
|
465
|
+
error: err.message
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
/**
|
|
471
|
+
* Check if phone number is on WhatsApp
|
|
472
|
+
* GET /api/magic-mail/whatsapp/check/:phoneNumber
|
|
473
|
+
*/
|
|
474
|
+
async checkWhatsAppNumber(ctx) {
|
|
475
|
+
try {
|
|
476
|
+
const { phoneNumber } = ctx.params;
|
|
477
|
+
if (!phoneNumber) {
|
|
478
|
+
ctx.throw(400, "Phone number is required");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const emailRouter2 = strapi.plugin("magic-mail").service("email-router");
|
|
482
|
+
const result = await emailRouter2.checkWhatsAppNumber(phoneNumber);
|
|
483
|
+
ctx.body = {
|
|
484
|
+
success: true,
|
|
485
|
+
data: result
|
|
486
|
+
};
|
|
487
|
+
} catch (err) {
|
|
488
|
+
strapi.log.error("[magic-mail] Error checking WhatsApp number:", err);
|
|
489
|
+
ctx.throw(500, err.message || "Failed to check phone number");
|
|
490
|
+
}
|
|
405
491
|
}
|
|
406
492
|
};
|
|
407
493
|
return controller;
|
|
@@ -2452,6 +2538,219 @@ function requireTest() {
|
|
|
2452
2538
|
};
|
|
2453
2539
|
return test;
|
|
2454
2540
|
}
|
|
2541
|
+
var whatsapp$1;
|
|
2542
|
+
var hasRequiredWhatsapp$1;
|
|
2543
|
+
function requireWhatsapp$1() {
|
|
2544
|
+
if (hasRequiredWhatsapp$1) return whatsapp$1;
|
|
2545
|
+
hasRequiredWhatsapp$1 = 1;
|
|
2546
|
+
whatsapp$1 = {
|
|
2547
|
+
/**
|
|
2548
|
+
* Check if WhatsApp/Baileys is available
|
|
2549
|
+
* GET /magic-mail/whatsapp/available
|
|
2550
|
+
*/
|
|
2551
|
+
async checkAvailable(ctx) {
|
|
2552
|
+
try {
|
|
2553
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2554
|
+
const available = await whatsappService.isAvailable();
|
|
2555
|
+
ctx.body = {
|
|
2556
|
+
success: true,
|
|
2557
|
+
data: {
|
|
2558
|
+
available,
|
|
2559
|
+
message: available ? "WhatsApp integration is available" : "Baileys not installed. Run: npm install @whiskeysockets/baileys pino qrcode"
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
} catch (error) {
|
|
2563
|
+
ctx.throw(500, error.message);
|
|
2564
|
+
}
|
|
2565
|
+
},
|
|
2566
|
+
/**
|
|
2567
|
+
* Get WhatsApp connection status
|
|
2568
|
+
* GET /magic-mail/whatsapp/status
|
|
2569
|
+
*/
|
|
2570
|
+
async getStatus(ctx) {
|
|
2571
|
+
try {
|
|
2572
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2573
|
+
const status = whatsappService.getStatus();
|
|
2574
|
+
const sessionInfo = await whatsappService.getSessionInfo();
|
|
2575
|
+
ctx.body = {
|
|
2576
|
+
success: true,
|
|
2577
|
+
data: {
|
|
2578
|
+
...status,
|
|
2579
|
+
session: sessionInfo
|
|
2580
|
+
}
|
|
2581
|
+
};
|
|
2582
|
+
} catch (error) {
|
|
2583
|
+
ctx.throw(500, error.message);
|
|
2584
|
+
}
|
|
2585
|
+
},
|
|
2586
|
+
/**
|
|
2587
|
+
* Connect to WhatsApp (generates QR code if needed)
|
|
2588
|
+
* POST /magic-mail/whatsapp/connect
|
|
2589
|
+
*/
|
|
2590
|
+
async connect(ctx) {
|
|
2591
|
+
try {
|
|
2592
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2593
|
+
const result = await whatsappService.connect();
|
|
2594
|
+
ctx.body = {
|
|
2595
|
+
success: result.success,
|
|
2596
|
+
data: result
|
|
2597
|
+
};
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
ctx.throw(500, error.message);
|
|
2600
|
+
}
|
|
2601
|
+
},
|
|
2602
|
+
/**
|
|
2603
|
+
* Disconnect from WhatsApp
|
|
2604
|
+
* POST /magic-mail/whatsapp/disconnect
|
|
2605
|
+
*/
|
|
2606
|
+
async disconnect(ctx) {
|
|
2607
|
+
try {
|
|
2608
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2609
|
+
const result = await whatsappService.disconnect();
|
|
2610
|
+
ctx.body = {
|
|
2611
|
+
success: result.success,
|
|
2612
|
+
data: result
|
|
2613
|
+
};
|
|
2614
|
+
} catch (error) {
|
|
2615
|
+
ctx.throw(500, error.message);
|
|
2616
|
+
}
|
|
2617
|
+
},
|
|
2618
|
+
/**
|
|
2619
|
+
* Send a test message
|
|
2620
|
+
* POST /magic-mail/whatsapp/send-test
|
|
2621
|
+
*/
|
|
2622
|
+
async sendTest(ctx) {
|
|
2623
|
+
try {
|
|
2624
|
+
const { phoneNumber, message } = ctx.request.body;
|
|
2625
|
+
if (!phoneNumber) {
|
|
2626
|
+
return ctx.badRequest("Phone number is required");
|
|
2627
|
+
}
|
|
2628
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2629
|
+
const testMessage = message || `[MagicMail Test] This is a test message sent at ${(/* @__PURE__ */ new Date()).toLocaleString()}`;
|
|
2630
|
+
const result = await whatsappService.sendMessage(phoneNumber, testMessage);
|
|
2631
|
+
ctx.body = {
|
|
2632
|
+
success: result.success,
|
|
2633
|
+
data: result
|
|
2634
|
+
};
|
|
2635
|
+
} catch (error) {
|
|
2636
|
+
ctx.throw(500, error.message);
|
|
2637
|
+
}
|
|
2638
|
+
},
|
|
2639
|
+
/**
|
|
2640
|
+
* Send a message using a template
|
|
2641
|
+
* POST /magic-mail/whatsapp/send-template
|
|
2642
|
+
*/
|
|
2643
|
+
async sendTemplateMessage(ctx) {
|
|
2644
|
+
try {
|
|
2645
|
+
const { phoneNumber, templateName, variables } = ctx.request.body;
|
|
2646
|
+
if (!phoneNumber || !templateName) {
|
|
2647
|
+
return ctx.badRequest("Phone number and template name are required");
|
|
2648
|
+
}
|
|
2649
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2650
|
+
const result = await whatsappService.sendTemplateMessage(phoneNumber, templateName, variables || {});
|
|
2651
|
+
ctx.body = {
|
|
2652
|
+
success: result.success,
|
|
2653
|
+
data: result
|
|
2654
|
+
};
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
ctx.throw(500, error.message);
|
|
2657
|
+
}
|
|
2658
|
+
},
|
|
2659
|
+
/**
|
|
2660
|
+
* Check if a phone number is on WhatsApp
|
|
2661
|
+
* POST /magic-mail/whatsapp/check-number
|
|
2662
|
+
*/
|
|
2663
|
+
async checkNumber(ctx) {
|
|
2664
|
+
try {
|
|
2665
|
+
const { phoneNumber } = ctx.request.body;
|
|
2666
|
+
if (!phoneNumber) {
|
|
2667
|
+
return ctx.badRequest("Phone number is required");
|
|
2668
|
+
}
|
|
2669
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2670
|
+
const result = await whatsappService.checkNumber(phoneNumber);
|
|
2671
|
+
ctx.body = {
|
|
2672
|
+
success: result.success,
|
|
2673
|
+
data: result
|
|
2674
|
+
};
|
|
2675
|
+
} catch (error) {
|
|
2676
|
+
ctx.throw(500, error.message);
|
|
2677
|
+
}
|
|
2678
|
+
},
|
|
2679
|
+
/**
|
|
2680
|
+
* Get all WhatsApp templates
|
|
2681
|
+
* GET /magic-mail/whatsapp/templates
|
|
2682
|
+
*/
|
|
2683
|
+
async getTemplates(ctx) {
|
|
2684
|
+
try {
|
|
2685
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2686
|
+
const templates = await whatsappService.getTemplates();
|
|
2687
|
+
ctx.body = {
|
|
2688
|
+
success: true,
|
|
2689
|
+
data: templates
|
|
2690
|
+
};
|
|
2691
|
+
} catch (error) {
|
|
2692
|
+
ctx.throw(500, error.message);
|
|
2693
|
+
}
|
|
2694
|
+
},
|
|
2695
|
+
/**
|
|
2696
|
+
* Save a WhatsApp template
|
|
2697
|
+
* POST /magic-mail/whatsapp/templates
|
|
2698
|
+
*/
|
|
2699
|
+
async saveTemplate(ctx) {
|
|
2700
|
+
try {
|
|
2701
|
+
const { templateName, templateContent } = ctx.request.body;
|
|
2702
|
+
if (!templateName || !templateContent) {
|
|
2703
|
+
return ctx.badRequest("Template name and content are required");
|
|
2704
|
+
}
|
|
2705
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2706
|
+
const result = await whatsappService.saveTemplate(templateName, templateContent);
|
|
2707
|
+
ctx.body = {
|
|
2708
|
+
success: result.success,
|
|
2709
|
+
data: result
|
|
2710
|
+
};
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
ctx.throw(500, error.message);
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2715
|
+
/**
|
|
2716
|
+
* Delete a WhatsApp template
|
|
2717
|
+
* DELETE /magic-mail/whatsapp/templates/:templateName
|
|
2718
|
+
*/
|
|
2719
|
+
async deleteTemplate(ctx) {
|
|
2720
|
+
try {
|
|
2721
|
+
const { templateName } = ctx.params;
|
|
2722
|
+
if (!templateName) {
|
|
2723
|
+
return ctx.badRequest("Template name is required");
|
|
2724
|
+
}
|
|
2725
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2726
|
+
const result = await whatsappService.deleteTemplate(templateName);
|
|
2727
|
+
ctx.body = {
|
|
2728
|
+
success: result.success,
|
|
2729
|
+
data: result
|
|
2730
|
+
};
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
ctx.throw(500, error.message);
|
|
2733
|
+
}
|
|
2734
|
+
},
|
|
2735
|
+
/**
|
|
2736
|
+
* Get session info
|
|
2737
|
+
* GET /magic-mail/whatsapp/session
|
|
2738
|
+
*/
|
|
2739
|
+
async getSession(ctx) {
|
|
2740
|
+
try {
|
|
2741
|
+
const whatsappService = strapi.plugin("magic-mail").service("whatsapp");
|
|
2742
|
+
const sessionInfo = await whatsappService.getSessionInfo();
|
|
2743
|
+
ctx.body = {
|
|
2744
|
+
success: true,
|
|
2745
|
+
data: sessionInfo
|
|
2746
|
+
};
|
|
2747
|
+
} catch (error) {
|
|
2748
|
+
ctx.throw(500, error.message);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
return whatsapp$1;
|
|
2753
|
+
}
|
|
2455
2754
|
var controllers;
|
|
2456
2755
|
var hasRequiredControllers;
|
|
2457
2756
|
function requireControllers() {
|
|
@@ -2465,6 +2764,7 @@ function requireControllers() {
|
|
|
2465
2764
|
const emailDesigner2 = requireEmailDesigner$1();
|
|
2466
2765
|
const analytics2 = requireAnalytics$1();
|
|
2467
2766
|
const test2 = requireTest();
|
|
2767
|
+
const whatsapp2 = requireWhatsapp$1();
|
|
2468
2768
|
controllers = {
|
|
2469
2769
|
controller: controller2,
|
|
2470
2770
|
accounts: accounts2,
|
|
@@ -2473,7 +2773,8 @@ function requireControllers() {
|
|
|
2473
2773
|
license: license2,
|
|
2474
2774
|
emailDesigner: emailDesigner2,
|
|
2475
2775
|
analytics: analytics2,
|
|
2476
|
-
test: test2
|
|
2776
|
+
test: test2,
|
|
2777
|
+
whatsapp: whatsapp2
|
|
2477
2778
|
};
|
|
2478
2779
|
return controllers;
|
|
2479
2780
|
}
|
|
@@ -2948,6 +3249,106 @@ function requireAdmin() {
|
|
|
2948
3249
|
policies: ["admin::isAuthenticatedAdmin"],
|
|
2949
3250
|
description: "Test template-version relations"
|
|
2950
3251
|
}
|
|
3252
|
+
},
|
|
3253
|
+
// WhatsApp Routes
|
|
3254
|
+
{
|
|
3255
|
+
method: "GET",
|
|
3256
|
+
path: "/whatsapp/available",
|
|
3257
|
+
handler: "whatsapp.checkAvailable",
|
|
3258
|
+
config: {
|
|
3259
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3260
|
+
description: "Check if WhatsApp/Baileys is available"
|
|
3261
|
+
}
|
|
3262
|
+
},
|
|
3263
|
+
{
|
|
3264
|
+
method: "GET",
|
|
3265
|
+
path: "/whatsapp/status",
|
|
3266
|
+
handler: "whatsapp.getStatus",
|
|
3267
|
+
config: {
|
|
3268
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3269
|
+
description: "Get WhatsApp connection status"
|
|
3270
|
+
}
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
method: "POST",
|
|
3274
|
+
path: "/whatsapp/connect",
|
|
3275
|
+
handler: "whatsapp.connect",
|
|
3276
|
+
config: {
|
|
3277
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3278
|
+
description: "Connect to WhatsApp (generates QR if needed)"
|
|
3279
|
+
}
|
|
3280
|
+
},
|
|
3281
|
+
{
|
|
3282
|
+
method: "POST",
|
|
3283
|
+
path: "/whatsapp/disconnect",
|
|
3284
|
+
handler: "whatsapp.disconnect",
|
|
3285
|
+
config: {
|
|
3286
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3287
|
+
description: "Disconnect from WhatsApp"
|
|
3288
|
+
}
|
|
3289
|
+
},
|
|
3290
|
+
{
|
|
3291
|
+
method: "POST",
|
|
3292
|
+
path: "/whatsapp/send-test",
|
|
3293
|
+
handler: "whatsapp.sendTest",
|
|
3294
|
+
config: {
|
|
3295
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3296
|
+
description: "Send a test WhatsApp message"
|
|
3297
|
+
}
|
|
3298
|
+
},
|
|
3299
|
+
{
|
|
3300
|
+
method: "POST",
|
|
3301
|
+
path: "/whatsapp/send-template",
|
|
3302
|
+
handler: "whatsapp.sendTemplateMessage",
|
|
3303
|
+
config: {
|
|
3304
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3305
|
+
description: "Send WhatsApp message using template"
|
|
3306
|
+
}
|
|
3307
|
+
},
|
|
3308
|
+
{
|
|
3309
|
+
method: "POST",
|
|
3310
|
+
path: "/whatsapp/check-number",
|
|
3311
|
+
handler: "whatsapp.checkNumber",
|
|
3312
|
+
config: {
|
|
3313
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3314
|
+
description: "Check if phone number is on WhatsApp"
|
|
3315
|
+
}
|
|
3316
|
+
},
|
|
3317
|
+
{
|
|
3318
|
+
method: "GET",
|
|
3319
|
+
path: "/whatsapp/templates",
|
|
3320
|
+
handler: "whatsapp.getTemplates",
|
|
3321
|
+
config: {
|
|
3322
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3323
|
+
description: "Get all WhatsApp message templates"
|
|
3324
|
+
}
|
|
3325
|
+
},
|
|
3326
|
+
{
|
|
3327
|
+
method: "POST",
|
|
3328
|
+
path: "/whatsapp/templates",
|
|
3329
|
+
handler: "whatsapp.saveTemplate",
|
|
3330
|
+
config: {
|
|
3331
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3332
|
+
description: "Save a WhatsApp message template"
|
|
3333
|
+
}
|
|
3334
|
+
},
|
|
3335
|
+
{
|
|
3336
|
+
method: "DELETE",
|
|
3337
|
+
path: "/whatsapp/templates/:templateName",
|
|
3338
|
+
handler: "whatsapp.deleteTemplate",
|
|
3339
|
+
config: {
|
|
3340
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3341
|
+
description: "Delete a WhatsApp message template"
|
|
3342
|
+
}
|
|
3343
|
+
},
|
|
3344
|
+
{
|
|
3345
|
+
method: "GET",
|
|
3346
|
+
path: "/whatsapp/session",
|
|
3347
|
+
handler: "whatsapp.getSession",
|
|
3348
|
+
config: {
|
|
3349
|
+
policies: ["admin::isAuthenticatedAdmin"],
|
|
3350
|
+
description: "Get WhatsApp session info"
|
|
3351
|
+
}
|
|
2951
3352
|
}
|
|
2952
3353
|
]
|
|
2953
3354
|
};
|
|
@@ -2961,6 +3362,7 @@ function requireContentApi() {
|
|
|
2961
3362
|
contentApi = {
|
|
2962
3363
|
type: "content-api",
|
|
2963
3364
|
routes: [
|
|
3365
|
+
// ============= EMAIL ROUTES =============
|
|
2964
3366
|
{
|
|
2965
3367
|
method: "POST",
|
|
2966
3368
|
path: "/send",
|
|
@@ -2971,7 +3373,45 @@ function requireContentApi() {
|
|
|
2971
3373
|
description: "Send email via MagicMail router"
|
|
2972
3374
|
}
|
|
2973
3375
|
},
|
|
2974
|
-
//
|
|
3376
|
+
// ============= UNIFIED MESSAGE ROUTE =============
|
|
3377
|
+
{
|
|
3378
|
+
method: "POST",
|
|
3379
|
+
path: "/send-message",
|
|
3380
|
+
handler: "controller.sendMessage",
|
|
3381
|
+
config: {
|
|
3382
|
+
auth: false,
|
|
3383
|
+
description: "Send message via Email or WhatsApp (unified API)"
|
|
3384
|
+
}
|
|
3385
|
+
},
|
|
3386
|
+
// ============= WHATSAPP ROUTES =============
|
|
3387
|
+
{
|
|
3388
|
+
method: "POST",
|
|
3389
|
+
path: "/send-whatsapp",
|
|
3390
|
+
handler: "controller.sendWhatsApp",
|
|
3391
|
+
config: {
|
|
3392
|
+
auth: false,
|
|
3393
|
+
description: "Send WhatsApp message"
|
|
3394
|
+
}
|
|
3395
|
+
},
|
|
3396
|
+
{
|
|
3397
|
+
method: "GET",
|
|
3398
|
+
path: "/whatsapp/status",
|
|
3399
|
+
handler: "controller.getWhatsAppStatus",
|
|
3400
|
+
config: {
|
|
3401
|
+
auth: false,
|
|
3402
|
+
description: "Get WhatsApp connection status"
|
|
3403
|
+
}
|
|
3404
|
+
},
|
|
3405
|
+
{
|
|
3406
|
+
method: "GET",
|
|
3407
|
+
path: "/whatsapp/check/:phoneNumber",
|
|
3408
|
+
handler: "controller.checkWhatsAppNumber",
|
|
3409
|
+
config: {
|
|
3410
|
+
auth: false,
|
|
3411
|
+
description: "Check if phone number is on WhatsApp"
|
|
3412
|
+
}
|
|
3413
|
+
},
|
|
3414
|
+
// ============= TRACKING ROUTES =============
|
|
2975
3415
|
{
|
|
2976
3416
|
method: "GET",
|
|
2977
3417
|
path: "/track/open/:emailId/:recipientHash",
|
|
@@ -3216,6 +3656,39 @@ function requireEmailRouter() {
|
|
|
3216
3656
|
emailData.html = html;
|
|
3217
3657
|
emailData.text = text;
|
|
3218
3658
|
emailData.subject = subject;
|
|
3659
|
+
let matchedRule = null;
|
|
3660
|
+
try {
|
|
3661
|
+
const allRules = await strapi2.documents("plugin::magic-mail.routing-rule").findMany({
|
|
3662
|
+
filters: { isActive: true },
|
|
3663
|
+
sort: [{ priority: "desc" }]
|
|
3664
|
+
});
|
|
3665
|
+
for (const rule of allRules) {
|
|
3666
|
+
let matches = false;
|
|
3667
|
+
switch (rule.matchType) {
|
|
3668
|
+
case "emailType":
|
|
3669
|
+
matches = rule.matchValue === type;
|
|
3670
|
+
break;
|
|
3671
|
+
case "recipient":
|
|
3672
|
+
matches = to && to.toLowerCase().includes(rule.matchValue.toLowerCase());
|
|
3673
|
+
break;
|
|
3674
|
+
case "subject":
|
|
3675
|
+
matches = subject && subject.toLowerCase().includes(rule.matchValue.toLowerCase());
|
|
3676
|
+
break;
|
|
3677
|
+
case "template":
|
|
3678
|
+
matches = emailData.template && emailData.template === rule.matchValue;
|
|
3679
|
+
break;
|
|
3680
|
+
case "custom":
|
|
3681
|
+
matches = emailData.customField && emailData.customField === rule.matchValue;
|
|
3682
|
+
break;
|
|
3683
|
+
}
|
|
3684
|
+
if (matches) {
|
|
3685
|
+
matchedRule = rule;
|
|
3686
|
+
break;
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
} catch (ruleError) {
|
|
3690
|
+
strapi2.log.warn("[magic-mail] [WARNING] Failed to check routing rules for WhatsApp fallback:", ruleError.message);
|
|
3691
|
+
}
|
|
3219
3692
|
try {
|
|
3220
3693
|
const licenseGuard2 = strapi2.plugin("magic-mail").service("license-guard");
|
|
3221
3694
|
if (priority === "high") {
|
|
@@ -3266,6 +3739,38 @@ function requireEmailRouter() {
|
|
|
3266
3739
|
};
|
|
3267
3740
|
} catch (error) {
|
|
3268
3741
|
strapi2.log.error("[magic-mail] [ERROR] Email send failed:", error);
|
|
3742
|
+
if (matchedRule?.whatsappFallback) {
|
|
3743
|
+
strapi2.log.info("[magic-mail] [FALLBACK] Email failed, attempting WhatsApp fallback...");
|
|
3744
|
+
try {
|
|
3745
|
+
const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
|
|
3746
|
+
const whatsappStatus = whatsapp2.getStatus();
|
|
3747
|
+
if (whatsappStatus.isConnected) {
|
|
3748
|
+
const phoneNumber = emailData.phoneNumber || emailData.whatsappPhone;
|
|
3749
|
+
if (phoneNumber) {
|
|
3750
|
+
const whatsappMessage = `*${subject}*
|
|
3751
|
+
|
|
3752
|
+
${text || "Email delivery failed. Please check your email settings."}`;
|
|
3753
|
+
const waResult = await whatsapp2.sendMessage(phoneNumber, whatsappMessage);
|
|
3754
|
+
if (waResult.success) {
|
|
3755
|
+
strapi2.log.info(`[magic-mail] [SUCCESS] WhatsApp fallback sent to ${phoneNumber}`);
|
|
3756
|
+
return {
|
|
3757
|
+
success: true,
|
|
3758
|
+
fallbackUsed: "whatsapp",
|
|
3759
|
+
phoneNumber
|
|
3760
|
+
};
|
|
3761
|
+
} else {
|
|
3762
|
+
strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback failed:", waResult.error);
|
|
3763
|
+
}
|
|
3764
|
+
} else {
|
|
3765
|
+
strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback enabled but no phone number provided");
|
|
3766
|
+
}
|
|
3767
|
+
} else {
|
|
3768
|
+
strapi2.log.warn("[magic-mail] [WARNING] WhatsApp fallback enabled but WhatsApp not connected");
|
|
3769
|
+
}
|
|
3770
|
+
} catch (waError) {
|
|
3771
|
+
strapi2.log.error("[magic-mail] [ERROR] WhatsApp fallback error:", waError.message);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3269
3774
|
throw error;
|
|
3270
3775
|
}
|
|
3271
3776
|
},
|
|
@@ -4174,6 +4679,168 @@ function requireEmailRouter() {
|
|
|
4174
4679
|
...headers
|
|
4175
4680
|
}
|
|
4176
4681
|
};
|
|
4682
|
+
},
|
|
4683
|
+
// ============================================================================
|
|
4684
|
+
// UNIFIED MESSAGE API - Send via Email OR WhatsApp
|
|
4685
|
+
// ============================================================================
|
|
4686
|
+
/**
|
|
4687
|
+
* Send a message via WhatsApp
|
|
4688
|
+
* Same pattern as send() but for WhatsApp
|
|
4689
|
+
* @param {Object} messageData - { phoneNumber, message, templateId, templateData }
|
|
4690
|
+
* @returns {Promise<Object>} Send result
|
|
4691
|
+
*/
|
|
4692
|
+
async sendWhatsApp(messageData) {
|
|
4693
|
+
const {
|
|
4694
|
+
phoneNumber,
|
|
4695
|
+
message,
|
|
4696
|
+
templateId = null,
|
|
4697
|
+
templateData = {}
|
|
4698
|
+
} = messageData;
|
|
4699
|
+
if (!phoneNumber) {
|
|
4700
|
+
throw new Error("Phone number is required for WhatsApp messages");
|
|
4701
|
+
}
|
|
4702
|
+
const cleanPhone = phoneNumber.replace(/[^\d]/g, "");
|
|
4703
|
+
if (cleanPhone.length < 10) {
|
|
4704
|
+
throw new Error("Invalid phone number format. Use format: 491234567890 (country code + number)");
|
|
4705
|
+
}
|
|
4706
|
+
const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
|
|
4707
|
+
const status = whatsapp2.getStatus();
|
|
4708
|
+
if (!status.isConnected) {
|
|
4709
|
+
throw new Error("WhatsApp is not connected. Please connect WhatsApp first in the admin panel.");
|
|
4710
|
+
}
|
|
4711
|
+
let finalMessage = message;
|
|
4712
|
+
if (templateId) {
|
|
4713
|
+
try {
|
|
4714
|
+
const template = await whatsapp2.getTemplate(templateId);
|
|
4715
|
+
if (template) {
|
|
4716
|
+
finalMessage = template.content;
|
|
4717
|
+
Object.keys(templateData).forEach((key) => {
|
|
4718
|
+
finalMessage = finalMessage.replace(new RegExp(`{{${key}}}`, "g"), templateData[key]);
|
|
4719
|
+
});
|
|
4720
|
+
}
|
|
4721
|
+
} catch (error) {
|
|
4722
|
+
strapi2.log.warn(`[magic-mail] WhatsApp template ${templateId} not found, using plain message`);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
if (!finalMessage) {
|
|
4726
|
+
throw new Error("Message content is required");
|
|
4727
|
+
}
|
|
4728
|
+
strapi2.log.info(`[magic-mail] [WHATSAPP] Sending message to ${cleanPhone}`);
|
|
4729
|
+
const result = await whatsapp2.sendMessage(cleanPhone, finalMessage);
|
|
4730
|
+
if (result.success) {
|
|
4731
|
+
strapi2.log.info(`[magic-mail] [SUCCESS] WhatsApp message sent to ${cleanPhone}`);
|
|
4732
|
+
return {
|
|
4733
|
+
success: true,
|
|
4734
|
+
channel: "whatsapp",
|
|
4735
|
+
phoneNumber: cleanPhone,
|
|
4736
|
+
jid: result.jid
|
|
4737
|
+
};
|
|
4738
|
+
} else {
|
|
4739
|
+
strapi2.log.error(`[magic-mail] [ERROR] WhatsApp send failed: ${result.error}`);
|
|
4740
|
+
throw new Error(result.error || "Failed to send WhatsApp message");
|
|
4741
|
+
}
|
|
4742
|
+
},
|
|
4743
|
+
/**
|
|
4744
|
+
* Unified send method - automatically chooses Email or WhatsApp
|
|
4745
|
+
* @param {Object} messageData - Combined email and WhatsApp data
|
|
4746
|
+
* @param {string} messageData.channel - 'email' | 'whatsapp' | 'auto' (default: 'auto')
|
|
4747
|
+
* @param {string} messageData.to - Email address (for email channel)
|
|
4748
|
+
* @param {string} messageData.phoneNumber - Phone number (for whatsapp channel)
|
|
4749
|
+
* @param {string} messageData.subject - Email subject
|
|
4750
|
+
* @param {string} messageData.message - Plain text message (used for WhatsApp, or as email text)
|
|
4751
|
+
* @param {string} messageData.html - HTML content (email only)
|
|
4752
|
+
* @param {string} messageData.templateId - Template ID (works for both channels)
|
|
4753
|
+
* @param {Object} messageData.templateData - Template variables
|
|
4754
|
+
* @returns {Promise<Object>} Send result with channel info
|
|
4755
|
+
*/
|
|
4756
|
+
async sendMessage(messageData) {
|
|
4757
|
+
const {
|
|
4758
|
+
channel = "auto",
|
|
4759
|
+
to,
|
|
4760
|
+
phoneNumber,
|
|
4761
|
+
subject,
|
|
4762
|
+
message,
|
|
4763
|
+
text,
|
|
4764
|
+
html,
|
|
4765
|
+
templateId,
|
|
4766
|
+
templateData,
|
|
4767
|
+
...rest
|
|
4768
|
+
} = messageData;
|
|
4769
|
+
let useChannel = channel;
|
|
4770
|
+
if (channel === "auto") {
|
|
4771
|
+
if (to && to.includes("@")) {
|
|
4772
|
+
useChannel = "email";
|
|
4773
|
+
} else if (phoneNumber) {
|
|
4774
|
+
useChannel = "whatsapp";
|
|
4775
|
+
} else {
|
|
4776
|
+
throw new Error("Either email (to) or phoneNumber is required");
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
strapi2.log.info(`[magic-mail] [SEND] Channel: ${useChannel}, to: ${to || phoneNumber}`);
|
|
4780
|
+
if (useChannel === "whatsapp") {
|
|
4781
|
+
if (!phoneNumber) {
|
|
4782
|
+
throw new Error("Phone number is required for WhatsApp channel");
|
|
4783
|
+
}
|
|
4784
|
+
return await this.sendWhatsApp({
|
|
4785
|
+
phoneNumber,
|
|
4786
|
+
message: message || text || subject,
|
|
4787
|
+
// Use message, fallback to text or subject
|
|
4788
|
+
templateId,
|
|
4789
|
+
templateData
|
|
4790
|
+
});
|
|
4791
|
+
} else {
|
|
4792
|
+
if (!to) {
|
|
4793
|
+
throw new Error("Email address (to) is required for email channel");
|
|
4794
|
+
}
|
|
4795
|
+
const result = await this.send({
|
|
4796
|
+
to,
|
|
4797
|
+
subject,
|
|
4798
|
+
text: text || message,
|
|
4799
|
+
html,
|
|
4800
|
+
templateId,
|
|
4801
|
+
templateData,
|
|
4802
|
+
phoneNumber,
|
|
4803
|
+
// Pass for WhatsApp fallback
|
|
4804
|
+
...rest
|
|
4805
|
+
});
|
|
4806
|
+
return {
|
|
4807
|
+
...result,
|
|
4808
|
+
channel: "email"
|
|
4809
|
+
};
|
|
4810
|
+
}
|
|
4811
|
+
},
|
|
4812
|
+
/**
|
|
4813
|
+
* Check WhatsApp connection status
|
|
4814
|
+
* @returns {Object} Connection status
|
|
4815
|
+
*/
|
|
4816
|
+
getWhatsAppStatus() {
|
|
4817
|
+
try {
|
|
4818
|
+
const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
|
|
4819
|
+
return whatsapp2.getStatus();
|
|
4820
|
+
} catch (error) {
|
|
4821
|
+
return {
|
|
4822
|
+
isConnected: false,
|
|
4823
|
+
status: "unavailable",
|
|
4824
|
+
error: error.message
|
|
4825
|
+
};
|
|
4826
|
+
}
|
|
4827
|
+
},
|
|
4828
|
+
/**
|
|
4829
|
+
* Check if a phone number is registered on WhatsApp
|
|
4830
|
+
* @param {string} phoneNumber - Phone number to check
|
|
4831
|
+
* @returns {Promise<Object>} Check result
|
|
4832
|
+
*/
|
|
4833
|
+
async checkWhatsAppNumber(phoneNumber) {
|
|
4834
|
+
try {
|
|
4835
|
+
const whatsapp2 = strapi2.plugin("magic-mail").service("whatsapp");
|
|
4836
|
+
return await whatsapp2.checkNumber(phoneNumber);
|
|
4837
|
+
} catch (error) {
|
|
4838
|
+
return {
|
|
4839
|
+
success: false,
|
|
4840
|
+
exists: false,
|
|
4841
|
+
error: error.message
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4177
4844
|
}
|
|
4178
4845
|
});
|
|
4179
4846
|
return emailRouter;
|
|
@@ -4870,7 +5537,7 @@ function requireOauth() {
|
|
|
4870
5537
|
});
|
|
4871
5538
|
return oauth;
|
|
4872
5539
|
}
|
|
4873
|
-
const version = "2.
|
|
5540
|
+
const version = "2.4.0";
|
|
4874
5541
|
const require$$2 = {
|
|
4875
5542
|
version
|
|
4876
5543
|
};
|
|
@@ -6174,6 +6841,434 @@ function requireAnalytics() {
|
|
|
6174
6841
|
});
|
|
6175
6842
|
return analytics;
|
|
6176
6843
|
}
|
|
6844
|
+
var whatsapp;
|
|
6845
|
+
var hasRequiredWhatsapp;
|
|
6846
|
+
function requireWhatsapp() {
|
|
6847
|
+
if (hasRequiredWhatsapp) return whatsapp;
|
|
6848
|
+
hasRequiredWhatsapp = 1;
|
|
6849
|
+
const path = require$$0__default$3.default;
|
|
6850
|
+
const fs = require$$1__default$2.default;
|
|
6851
|
+
let baileys = null;
|
|
6852
|
+
const loadBaileys = async () => {
|
|
6853
|
+
if (!baileys) {
|
|
6854
|
+
try {
|
|
6855
|
+
baileys = require("@whiskeysockets/baileys");
|
|
6856
|
+
if (process.env.DEBUG) {
|
|
6857
|
+
console.log("[MagicMail WhatsApp] Baileys loaded successfully");
|
|
6858
|
+
}
|
|
6859
|
+
return true;
|
|
6860
|
+
} catch (error) {
|
|
6861
|
+
console.warn("[MagicMail WhatsApp] Baileys not installed. WhatsApp features disabled.");
|
|
6862
|
+
console.warn("[MagicMail WhatsApp] Install with: npm install @whiskeysockets/baileys pino qrcode");
|
|
6863
|
+
return false;
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
return true;
|
|
6867
|
+
};
|
|
6868
|
+
whatsapp = ({ strapi: strapi2 }) => {
|
|
6869
|
+
let sock = null;
|
|
6870
|
+
let qrCode = null;
|
|
6871
|
+
let connectionStatus = "disconnected";
|
|
6872
|
+
let lastError = null;
|
|
6873
|
+
let eventListeners = [];
|
|
6874
|
+
let wasConnectedBefore = false;
|
|
6875
|
+
let reconnectAttempts = 0;
|
|
6876
|
+
const MAX_RECONNECT_ATTEMPTS = 3;
|
|
6877
|
+
const isDebugEnabled = async () => {
|
|
6878
|
+
try {
|
|
6879
|
+
const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
|
|
6880
|
+
const settings = await pluginStore.get({ key: "settings" });
|
|
6881
|
+
return settings?.whatsapp_debug === true;
|
|
6882
|
+
} catch {
|
|
6883
|
+
return false;
|
|
6884
|
+
}
|
|
6885
|
+
};
|
|
6886
|
+
const debugLog = async (message) => {
|
|
6887
|
+
if (await isDebugEnabled()) {
|
|
6888
|
+
strapi2.log.info(message);
|
|
6889
|
+
}
|
|
6890
|
+
};
|
|
6891
|
+
const getAuthPath = () => {
|
|
6892
|
+
const strapiRoot = strapi2.dirs?.app?.root || process.cwd();
|
|
6893
|
+
return path.join(strapiRoot, ".magicmail-whatsapp-auth");
|
|
6894
|
+
};
|
|
6895
|
+
const emit = (event, data) => {
|
|
6896
|
+
eventListeners.forEach((listener) => {
|
|
6897
|
+
try {
|
|
6898
|
+
listener(event, data);
|
|
6899
|
+
} catch (e) {
|
|
6900
|
+
console.error("[MagicMail WhatsApp] Event listener error:", e);
|
|
6901
|
+
}
|
|
6902
|
+
});
|
|
6903
|
+
};
|
|
6904
|
+
const service = {
|
|
6905
|
+
/**
|
|
6906
|
+
* Check if Baileys is available
|
|
6907
|
+
* @returns {Promise<boolean>} True if Baileys is installed
|
|
6908
|
+
*/
|
|
6909
|
+
async isAvailable() {
|
|
6910
|
+
return await loadBaileys();
|
|
6911
|
+
},
|
|
6912
|
+
/**
|
|
6913
|
+
* Get current connection status
|
|
6914
|
+
* @returns {object} Status object with status, qrCode, lastError, isConnected
|
|
6915
|
+
*/
|
|
6916
|
+
getStatus() {
|
|
6917
|
+
return {
|
|
6918
|
+
status: connectionStatus,
|
|
6919
|
+
qrCode,
|
|
6920
|
+
lastError,
|
|
6921
|
+
isConnected: connectionStatus === "connected"
|
|
6922
|
+
};
|
|
6923
|
+
},
|
|
6924
|
+
/**
|
|
6925
|
+
* Add event listener for WhatsApp events
|
|
6926
|
+
* @param {function} callback - Callback function(event, data)
|
|
6927
|
+
* @returns {function} Unsubscribe function
|
|
6928
|
+
*/
|
|
6929
|
+
on(callback) {
|
|
6930
|
+
eventListeners.push(callback);
|
|
6931
|
+
return () => {
|
|
6932
|
+
eventListeners = eventListeners.filter((l) => l !== callback);
|
|
6933
|
+
};
|
|
6934
|
+
},
|
|
6935
|
+
/**
|
|
6936
|
+
* Initialize WhatsApp connection
|
|
6937
|
+
* @returns {Promise<object>} Connection result with success status
|
|
6938
|
+
*/
|
|
6939
|
+
async connect() {
|
|
6940
|
+
const available = await loadBaileys();
|
|
6941
|
+
if (!available) {
|
|
6942
|
+
lastError = "Baileys not installed. Run: npm install @whiskeysockets/baileys pino qrcode";
|
|
6943
|
+
strapi2.log.error("[MagicMail WhatsApp] [ERROR] Baileys library not available");
|
|
6944
|
+
return { success: false, error: lastError };
|
|
6945
|
+
}
|
|
6946
|
+
if (sock && connectionStatus === "connected") {
|
|
6947
|
+
await debugLog("[MagicMail WhatsApp] Already connected");
|
|
6948
|
+
return { success: true, status: "already_connected" };
|
|
6949
|
+
}
|
|
6950
|
+
if (sock) {
|
|
6951
|
+
try {
|
|
6952
|
+
sock.end();
|
|
6953
|
+
} catch (e) {
|
|
6954
|
+
}
|
|
6955
|
+
sock = null;
|
|
6956
|
+
}
|
|
6957
|
+
return new Promise(async (resolve) => {
|
|
6958
|
+
try {
|
|
6959
|
+
connectionStatus = "connecting";
|
|
6960
|
+
emit("status", { status: connectionStatus });
|
|
6961
|
+
await debugLog("[MagicMail WhatsApp] Starting connection...");
|
|
6962
|
+
const authPath = getAuthPath();
|
|
6963
|
+
if (!fs.existsSync(authPath)) {
|
|
6964
|
+
fs.mkdirSync(authPath, { recursive: true });
|
|
6965
|
+
}
|
|
6966
|
+
await debugLog(`[MagicMail WhatsApp] Auth path: ${authPath}`);
|
|
6967
|
+
const { state, saveCreds } = await baileys.useMultiFileAuthState(authPath);
|
|
6968
|
+
await debugLog("[MagicMail WhatsApp] Auth state loaded");
|
|
6969
|
+
const pino = require("pino");
|
|
6970
|
+
const logger2 = pino({ level: "silent" });
|
|
6971
|
+
await debugLog("[MagicMail WhatsApp] Creating WhatsApp socket...");
|
|
6972
|
+
const makeSocket = baileys.default || baileys.makeWASocket;
|
|
6973
|
+
const browserConfig = baileys.Browsers.ubuntu("Chrome");
|
|
6974
|
+
await debugLog(`[MagicMail WhatsApp] Browser config: ${JSON.stringify(browserConfig)}`);
|
|
6975
|
+
sock = makeSocket({
|
|
6976
|
+
auth: state,
|
|
6977
|
+
logger: logger2,
|
|
6978
|
+
browser: browserConfig,
|
|
6979
|
+
syncFullHistory: false,
|
|
6980
|
+
markOnlineOnConnect: false,
|
|
6981
|
+
generateHighQualityLinkPreview: false,
|
|
6982
|
+
getMessage: async (key) => {
|
|
6983
|
+
return { conversation: "" };
|
|
6984
|
+
}
|
|
6985
|
+
});
|
|
6986
|
+
await debugLog("[MagicMail WhatsApp] Socket created, registering event handlers...");
|
|
6987
|
+
let resolved = false;
|
|
6988
|
+
const resolveOnce = (result) => {
|
|
6989
|
+
if (!resolved) {
|
|
6990
|
+
resolved = true;
|
|
6991
|
+
resolve(result);
|
|
6992
|
+
}
|
|
6993
|
+
};
|
|
6994
|
+
setTimeout(() => {
|
|
6995
|
+
if (!resolved) {
|
|
6996
|
+
strapi2.log.warn("[MagicMail WhatsApp] Connection timeout - no QR or connection");
|
|
6997
|
+
resolveOnce({ success: true, status: connectionStatus, qrCode });
|
|
6998
|
+
}
|
|
6999
|
+
}, 3e4);
|
|
7000
|
+
sock.ev.on("connection.update", async (update) => {
|
|
7001
|
+
await debugLog(`[MagicMail WhatsApp] connection.update: ${JSON.stringify(update)}`);
|
|
7002
|
+
const { connection, lastDisconnect, qr } = update;
|
|
7003
|
+
if (qr) {
|
|
7004
|
+
await debugLog("[MagicMail WhatsApp] QR code received");
|
|
7005
|
+
try {
|
|
7006
|
+
const QRCode = require("qrcode");
|
|
7007
|
+
qrCode = await QRCode.toDataURL(qr);
|
|
7008
|
+
connectionStatus = "qr_pending";
|
|
7009
|
+
emit("qr", { qrCode });
|
|
7010
|
+
emit("status", { status: connectionStatus });
|
|
7011
|
+
strapi2.log.info("[MagicMail WhatsApp] [SUCCESS] QR Code generated - scan with WhatsApp");
|
|
7012
|
+
resolveOnce({ success: true, status: connectionStatus, qrCode });
|
|
7013
|
+
} catch (qrError) {
|
|
7014
|
+
strapi2.log.error("[MagicMail WhatsApp] QR generation error:", qrError.message);
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
if (connection === "close") {
|
|
7018
|
+
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
7019
|
+
const isLoggedOut = statusCode === baileys.DisconnectReason.loggedOut;
|
|
7020
|
+
const isRestartRequired = statusCode === baileys.DisconnectReason.restartRequired;
|
|
7021
|
+
const isConnectionFailure = statusCode === 405;
|
|
7022
|
+
await debugLog(`[MagicMail WhatsApp] Connection closed - statusCode: ${statusCode}`);
|
|
7023
|
+
if (isLoggedOut) {
|
|
7024
|
+
connectionStatus = "disconnected";
|
|
7025
|
+
lastError = "Logged out from WhatsApp";
|
|
7026
|
+
qrCode = null;
|
|
7027
|
+
wasConnectedBefore = false;
|
|
7028
|
+
reconnectAttempts = 0;
|
|
7029
|
+
try {
|
|
7030
|
+
fs.rmSync(authPath, { recursive: true, force: true });
|
|
7031
|
+
} catch (e) {
|
|
7032
|
+
}
|
|
7033
|
+
strapi2.log.warn("[MagicMail WhatsApp] Logged out - auth cleared");
|
|
7034
|
+
} else if (isRestartRequired) {
|
|
7035
|
+
await debugLog("[MagicMail WhatsApp] Restart required - reconnecting...");
|
|
7036
|
+
connectionStatus = "connecting";
|
|
7037
|
+
setTimeout(() => {
|
|
7038
|
+
service.connect();
|
|
7039
|
+
}, 1e3);
|
|
7040
|
+
} else if (isConnectionFailure && reconnectAttempts < 2) {
|
|
7041
|
+
reconnectAttempts++;
|
|
7042
|
+
await debugLog(`[MagicMail WhatsApp] Connection rejected (405) - retrying (${reconnectAttempts}/2)`);
|
|
7043
|
+
try {
|
|
7044
|
+
fs.rmSync(authPath, { recursive: true, force: true });
|
|
7045
|
+
} catch (e) {
|
|
7046
|
+
}
|
|
7047
|
+
connectionStatus = "disconnected";
|
|
7048
|
+
qrCode = null;
|
|
7049
|
+
setTimeout(() => {
|
|
7050
|
+
service.connect();
|
|
7051
|
+
}, 3e3);
|
|
7052
|
+
} else if (isConnectionFailure) {
|
|
7053
|
+
connectionStatus = "disconnected";
|
|
7054
|
+
lastError = "WhatsApp connection rejected (405). Please try again later.";
|
|
7055
|
+
strapi2.log.error("[MagicMail WhatsApp] [ERROR] Connection rejected after retries.");
|
|
7056
|
+
resolveOnce({ success: false, status: connectionStatus, error: lastError });
|
|
7057
|
+
} else if (wasConnectedBefore && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
7058
|
+
reconnectAttempts++;
|
|
7059
|
+
connectionStatus = "connecting";
|
|
7060
|
+
await debugLog(`[MagicMail WhatsApp] Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`);
|
|
7061
|
+
setTimeout(() => {
|
|
7062
|
+
service.connect();
|
|
7063
|
+
}, 3e3 * reconnectAttempts);
|
|
7064
|
+
} else if (!wasConnectedBefore) {
|
|
7065
|
+
connectionStatus = "disconnected";
|
|
7066
|
+
qrCode = null;
|
|
7067
|
+
await debugLog("[MagicMail WhatsApp] Connection closed - waiting for QR scan");
|
|
7068
|
+
} else {
|
|
7069
|
+
connectionStatus = "disconnected";
|
|
7070
|
+
lastError = "Max reconnect attempts reached";
|
|
7071
|
+
strapi2.log.warn("[MagicMail WhatsApp] Max reconnect attempts reached");
|
|
7072
|
+
}
|
|
7073
|
+
emit("status", { status: connectionStatus, error: lastError });
|
|
7074
|
+
}
|
|
7075
|
+
if (connection === "open") {
|
|
7076
|
+
connectionStatus = "connected";
|
|
7077
|
+
qrCode = null;
|
|
7078
|
+
lastError = null;
|
|
7079
|
+
wasConnectedBefore = true;
|
|
7080
|
+
reconnectAttempts = 0;
|
|
7081
|
+
emit("status", { status: connectionStatus });
|
|
7082
|
+
strapi2.log.info("[MagicMail WhatsApp] [SUCCESS] Connected successfully!");
|
|
7083
|
+
resolveOnce({ success: true, status: connectionStatus });
|
|
7084
|
+
}
|
|
7085
|
+
});
|
|
7086
|
+
sock.ev.on("creds.update", saveCreds);
|
|
7087
|
+
} catch (error) {
|
|
7088
|
+
lastError = error.message;
|
|
7089
|
+
connectionStatus = "disconnected";
|
|
7090
|
+
strapi2.log.error("[MagicMail WhatsApp] Connection error:", error);
|
|
7091
|
+
resolve({ success: false, error: error.message });
|
|
7092
|
+
}
|
|
7093
|
+
});
|
|
7094
|
+
},
|
|
7095
|
+
/**
|
|
7096
|
+
* Disconnect WhatsApp and clear session
|
|
7097
|
+
* @returns {Promise<object>} Result with success status
|
|
7098
|
+
*/
|
|
7099
|
+
async disconnect() {
|
|
7100
|
+
if (sock) {
|
|
7101
|
+
try {
|
|
7102
|
+
await sock.logout();
|
|
7103
|
+
} catch (e) {
|
|
7104
|
+
}
|
|
7105
|
+
sock = null;
|
|
7106
|
+
}
|
|
7107
|
+
connectionStatus = "disconnected";
|
|
7108
|
+
qrCode = null;
|
|
7109
|
+
emit("status", { status: connectionStatus });
|
|
7110
|
+
strapi2.log.info("[MagicMail WhatsApp] Disconnected");
|
|
7111
|
+
return { success: true };
|
|
7112
|
+
},
|
|
7113
|
+
/**
|
|
7114
|
+
* Send a text message to a phone number
|
|
7115
|
+
* @param {string} phoneNumber - Phone number with country code (e.g., "491234567890")
|
|
7116
|
+
* @param {string} message - Message text
|
|
7117
|
+
* @returns {Promise<object>} Result with success status
|
|
7118
|
+
*/
|
|
7119
|
+
async sendMessage(phoneNumber, message) {
|
|
7120
|
+
if (connectionStatus !== "connected" || !sock) {
|
|
7121
|
+
return {
|
|
7122
|
+
success: false,
|
|
7123
|
+
error: "WhatsApp not connected. Please connect first."
|
|
7124
|
+
};
|
|
7125
|
+
}
|
|
7126
|
+
try {
|
|
7127
|
+
const formattedNumber = phoneNumber.replace(/[^\d]/g, "");
|
|
7128
|
+
const jid = `${formattedNumber}@s.whatsapp.net`;
|
|
7129
|
+
const [exists] = await sock.onWhatsApp(formattedNumber);
|
|
7130
|
+
if (!exists?.exists) {
|
|
7131
|
+
return {
|
|
7132
|
+
success: false,
|
|
7133
|
+
error: `Phone number ${phoneNumber} is not registered on WhatsApp`
|
|
7134
|
+
};
|
|
7135
|
+
}
|
|
7136
|
+
await sock.sendMessage(jid, { text: message });
|
|
7137
|
+
await debugLog(`[MagicMail WhatsApp] Message sent to ${formattedNumber}`);
|
|
7138
|
+
return { success: true, jid };
|
|
7139
|
+
} catch (error) {
|
|
7140
|
+
strapi2.log.error("[MagicMail WhatsApp] Send error:", error);
|
|
7141
|
+
return { success: false, error: error.message };
|
|
7142
|
+
}
|
|
7143
|
+
},
|
|
7144
|
+
/**
|
|
7145
|
+
* Send message using a template
|
|
7146
|
+
* @param {string} phoneNumber - Phone number
|
|
7147
|
+
* @param {string} templateName - Template identifier
|
|
7148
|
+
* @param {object} variables - Template variables to replace
|
|
7149
|
+
* @returns {Promise<object>} Result with success status
|
|
7150
|
+
*/
|
|
7151
|
+
async sendTemplateMessage(phoneNumber, templateName, variables = {}) {
|
|
7152
|
+
try {
|
|
7153
|
+
const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
|
|
7154
|
+
const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
|
|
7155
|
+
let template = templates[templateName];
|
|
7156
|
+
if (!template) {
|
|
7157
|
+
template = `*{{subject}}*
|
|
7158
|
+
|
|
7159
|
+
{{body}}`;
|
|
7160
|
+
}
|
|
7161
|
+
let message = template;
|
|
7162
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
7163
|
+
message = message.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
7164
|
+
}
|
|
7165
|
+
return this.sendMessage(phoneNumber, message);
|
|
7166
|
+
} catch (error) {
|
|
7167
|
+
return { success: false, error: error.message };
|
|
7168
|
+
}
|
|
7169
|
+
},
|
|
7170
|
+
/**
|
|
7171
|
+
* Check if a phone number is on WhatsApp
|
|
7172
|
+
* @param {string} phoneNumber - Phone number to check
|
|
7173
|
+
* @returns {Promise<object>} Result with exists boolean
|
|
7174
|
+
*/
|
|
7175
|
+
async checkNumber(phoneNumber) {
|
|
7176
|
+
if (connectionStatus !== "connected" || !sock) {
|
|
7177
|
+
return { success: false, error: "WhatsApp not connected" };
|
|
7178
|
+
}
|
|
7179
|
+
try {
|
|
7180
|
+
const formattedNumber = phoneNumber.replace(/[^\d]/g, "");
|
|
7181
|
+
const [result] = await sock.onWhatsApp(formattedNumber);
|
|
7182
|
+
return {
|
|
7183
|
+
success: true,
|
|
7184
|
+
exists: result?.exists || false,
|
|
7185
|
+
jid: result?.jid
|
|
7186
|
+
};
|
|
7187
|
+
} catch (error) {
|
|
7188
|
+
return { success: false, error: error.message };
|
|
7189
|
+
}
|
|
7190
|
+
},
|
|
7191
|
+
/**
|
|
7192
|
+
* Get session info
|
|
7193
|
+
* @returns {Promise<object|null>} Session info or null if not connected
|
|
7194
|
+
*/
|
|
7195
|
+
async getSessionInfo() {
|
|
7196
|
+
if (connectionStatus !== "connected" || !sock) {
|
|
7197
|
+
return null;
|
|
7198
|
+
}
|
|
7199
|
+
try {
|
|
7200
|
+
const user = sock.user;
|
|
7201
|
+
return {
|
|
7202
|
+
phoneNumber: user?.id?.split(":")[0] || user?.id?.split("@")[0],
|
|
7203
|
+
name: user?.name,
|
|
7204
|
+
platform: "WhatsApp Web"
|
|
7205
|
+
};
|
|
7206
|
+
} catch (error) {
|
|
7207
|
+
return null;
|
|
7208
|
+
}
|
|
7209
|
+
},
|
|
7210
|
+
/**
|
|
7211
|
+
* Reset connection state (for manual cleanup)
|
|
7212
|
+
*/
|
|
7213
|
+
reset() {
|
|
7214
|
+
sock = null;
|
|
7215
|
+
qrCode = null;
|
|
7216
|
+
connectionStatus = "disconnected";
|
|
7217
|
+
lastError = null;
|
|
7218
|
+
wasConnectedBefore = false;
|
|
7219
|
+
reconnectAttempts = 0;
|
|
7220
|
+
},
|
|
7221
|
+
/**
|
|
7222
|
+
* Save WhatsApp template
|
|
7223
|
+
* @param {string} templateName - Template identifier
|
|
7224
|
+
* @param {string} templateContent - Template content with {{variables}}
|
|
7225
|
+
* @returns {Promise<object>} Result with success status
|
|
7226
|
+
*/
|
|
7227
|
+
async saveTemplate(templateName, templateContent) {
|
|
7228
|
+
try {
|
|
7229
|
+
const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
|
|
7230
|
+
const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
|
|
7231
|
+
templates[templateName] = templateContent;
|
|
7232
|
+
await pluginStore.set({ key: "whatsapp_templates", value: templates });
|
|
7233
|
+
return { success: true };
|
|
7234
|
+
} catch (error) {
|
|
7235
|
+
return { success: false, error: error.message };
|
|
7236
|
+
}
|
|
7237
|
+
},
|
|
7238
|
+
/**
|
|
7239
|
+
* Get all WhatsApp templates
|
|
7240
|
+
* @returns {Promise<object>} Templates object
|
|
7241
|
+
*/
|
|
7242
|
+
async getTemplates() {
|
|
7243
|
+
try {
|
|
7244
|
+
const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
|
|
7245
|
+
const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
|
|
7246
|
+
return templates;
|
|
7247
|
+
} catch (error) {
|
|
7248
|
+
return {};
|
|
7249
|
+
}
|
|
7250
|
+
},
|
|
7251
|
+
/**
|
|
7252
|
+
* Delete a WhatsApp template
|
|
7253
|
+
* @param {string} templateName - Template identifier
|
|
7254
|
+
* @returns {Promise<object>} Result with success status
|
|
7255
|
+
*/
|
|
7256
|
+
async deleteTemplate(templateName) {
|
|
7257
|
+
try {
|
|
7258
|
+
const pluginStore = strapi2.store({ type: "plugin", name: "magic-mail" });
|
|
7259
|
+
const templates = await pluginStore.get({ key: "whatsapp_templates" }) || {};
|
|
7260
|
+
delete templates[templateName];
|
|
7261
|
+
await pluginStore.set({ key: "whatsapp_templates", value: templates });
|
|
7262
|
+
return { success: true };
|
|
7263
|
+
} catch (error) {
|
|
7264
|
+
return { success: false, error: error.message };
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
};
|
|
7268
|
+
return service;
|
|
7269
|
+
};
|
|
7270
|
+
return whatsapp;
|
|
7271
|
+
}
|
|
6177
7272
|
var services;
|
|
6178
7273
|
var hasRequiredServices;
|
|
6179
7274
|
function requireServices() {
|
|
@@ -6185,13 +7280,15 @@ function requireServices() {
|
|
|
6185
7280
|
const licenseGuard2 = requireLicenseGuard();
|
|
6186
7281
|
const emailDesigner2 = requireEmailDesigner();
|
|
6187
7282
|
const analytics2 = requireAnalytics();
|
|
7283
|
+
const whatsapp2 = requireWhatsapp();
|
|
6188
7284
|
services = {
|
|
6189
7285
|
"email-router": emailRouter2,
|
|
6190
7286
|
"account-manager": accountManager2,
|
|
6191
7287
|
oauth: oauth2,
|
|
6192
7288
|
"license-guard": licenseGuard2,
|
|
6193
7289
|
"email-designer": emailDesigner2,
|
|
6194
|
-
analytics: analytics2
|
|
7290
|
+
analytics: analytics2,
|
|
7291
|
+
whatsapp: whatsapp2
|
|
6195
7292
|
};
|
|
6196
7293
|
return services;
|
|
6197
7294
|
}
|