@raevon/n8n-nodes-whatsapp 1.0.13 → 2.0.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.
@@ -1,8 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.WhatsAppConnect = void 0;
4
7
  const n8n_workflow_1 = require("n8n-workflow");
5
8
  const WhatsAppApiHelper_1 = require("./WhatsAppApiHelper");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ function expandHome(p) {
12
+ if (!p.startsWith('~'))
13
+ return p;
14
+ return node_path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', p.slice(1));
15
+ }
6
16
  class WhatsAppConnect {
7
17
  constructor() {
8
18
  this.description = {
@@ -12,7 +22,7 @@ class WhatsAppConnect {
12
22
  group: ['transform'],
13
23
  version: 1,
14
24
  subtitle: '={{$parameter["operation"]}}',
15
- description: 'Connect to WhatsApp — scan QR code on first run, then disconnects',
25
+ description: 'Connect to WhatsApp — starts background server, scan QR on first run',
16
26
  defaults: { name: 'WhatsApp Connect' },
17
27
  inputs: ['main'],
18
28
  outputs: ['main'],
@@ -24,8 +34,9 @@ class WhatsAppConnect {
24
34
  type: 'options',
25
35
  noDataExpression: true,
26
36
  options: [
27
- { name: 'Connect', value: 'connect', description: 'Connect to WhatsApp (scan QR on first run)', action: 'Connect to WhatsApp' },
28
- { name: 'Get Status', value: 'status', description: 'Get current connection status', action: 'Get connection status' },
37
+ { name: 'Connect', value: 'connect', description: 'Start WhatsApp server and connect', action: 'Connect to WhatsApp' },
38
+ { name: 'Get Status', value: 'status', description: 'Get server and connection status', action: 'Get status' },
39
+ { name: 'Sign Out', value: 'signOut', description: 'Stop server and delete session', action: 'Sign out' },
29
40
  ],
30
41
  default: 'connect',
31
42
  },
@@ -41,11 +52,11 @@ class WhatsAppConnect {
41
52
  for (let i = 0; i < items.length; i++) {
42
53
  try {
43
54
  if (operation === 'connect') {
44
- const result = await (0, WhatsAppApiHelper_1.connectOrGetQr)(cfg);
45
- // If connected, disconnect gracefully to avoid conflict with Send node
46
- if (result.connected) {
47
- await (0, WhatsAppApiHelper_1.disconnect)();
48
- }
55
+ // Start the background WhatsApp server
56
+ await (0, WhatsAppApiHelper_1.ensureServerRunning)(cfg);
57
+ // Connect to the server
58
+ const response = await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/connect`, { method: 'POST' });
59
+ const result = await response.json();
49
60
  returnData.push({
50
61
  json: {
51
62
  ...result,
@@ -55,25 +66,44 @@ class WhatsAppConnect {
55
66
  });
56
67
  }
57
68
  else if (operation === 'status') {
58
- const status = (0, WhatsAppApiHelper_1.getConnectionStatus)();
69
+ try {
70
+ const response = await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/status`);
71
+ const result = await response.json();
72
+ returnData.push({
73
+ json: { ...result, sessionPath: cfg.sessionPath },
74
+ pairedItem: { item: i },
75
+ });
76
+ }
77
+ catch {
78
+ returnData.push({
79
+ json: { status: 'server_not_running', connected: false, sessionPath: cfg.sessionPath },
80
+ pairedItem: { item: i },
81
+ });
82
+ }
83
+ }
84
+ else if (operation === 'signOut') {
85
+ try {
86
+ await fetch(`${(0, WhatsAppApiHelper_1.getServerUrl)(cfg)}/signout`, { method: 'POST' });
87
+ }
88
+ catch { }
89
+ // Delete session files
90
+ const resolvedPath = expandHome(cfg.sessionPath);
91
+ if (node_fs_1.default.existsSync(resolvedPath)) {
92
+ const files = node_fs_1.default.readdirSync(resolvedPath);
93
+ for (const file of files) {
94
+ node_fs_1.default.unlinkSync(node_path_1.default.join(resolvedPath, file));
95
+ }
96
+ node_fs_1.default.rmdirSync(resolvedPath);
97
+ }
59
98
  returnData.push({
60
- json: {
61
- ...status,
62
- sessionPath: cfg.sessionPath,
63
- },
99
+ json: { success: true, message: 'Signed out. Session deleted.', sessionPath: cfg.sessionPath },
64
100
  pairedItem: { item: i },
65
101
  });
66
102
  }
67
103
  }
68
104
  catch (error) {
69
105
  if (this.continueOnFail()) {
70
- returnData.push({
71
- json: {
72
- error: error.message,
73
- success: false,
74
- },
75
- pairedItem: { item: i },
76
- });
106
+ returnData.push({ json: { error: error.message, success: false }, pairedItem: { item: i } });
77
107
  continue;
78
108
  }
79
109
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
@@ -12,7 +12,7 @@ class WhatsAppSend {
12
12
  group: ['transform'],
13
13
  version: 1,
14
14
  subtitle: '={{$parameter["operation"]}}',
15
- description: 'Send WhatsApp messages with anti-ban protection',
15
+ description: 'Send WhatsApp messages via background server',
16
16
  defaults: { name: 'WhatsApp Send' },
17
17
  inputs: ['main'],
18
18
  outputs: ['main'],
@@ -38,7 +38,7 @@ class WhatsAppSend {
38
38
  type: 'string',
39
39
  default: '',
40
40
  required: true,
41
- description: 'Phone number (e.g., +1234567890) or WhatsApp JID (e.g., 1234567890@s.whatsapp.net)',
41
+ description: 'Phone number (e.g., +1234567890) or WhatsApp JID',
42
42
  },
43
43
  {
44
44
  displayName: 'Text',
@@ -64,7 +64,7 @@ class WhatsAppSend {
64
64
  type: 'string',
65
65
  default: '',
66
66
  displayOptions: { show: { operation: ['sendImage', 'sendDocument'] } },
67
- description: 'Optional caption for the image or document',
67
+ description: 'Optional caption',
68
68
  },
69
69
  {
70
70
  displayName: 'File Name',
@@ -75,21 +75,13 @@ class WhatsAppSend {
75
75
  displayOptions: { show: { operation: ['sendDocument'] } },
76
76
  description: 'Name of the file being sent',
77
77
  },
78
- {
79
- displayName: 'MIME Type',
80
- name: 'mimeType',
81
- type: 'string',
82
- default: '',
83
- displayOptions: { show: { operation: ['sendImage', 'sendDocument', 'sendAudio'] } },
84
- description: 'MIME type (auto-detected if empty)',
85
- },
86
78
  {
87
79
  displayName: 'Voice Note',
88
80
  name: 'ptt',
89
81
  type: 'boolean',
90
82
  default: false,
91
83
  displayOptions: { show: { operation: ['sendAudio'] } },
92
- description: 'Send as voice note (push-to-talk) instead of audio file',
84
+ description: 'Send as voice note',
93
85
  },
94
86
  {
95
87
  displayName: 'Latitude',
@@ -109,22 +101,6 @@ class WhatsAppSend {
109
101
  displayOptions: { show: { operation: ['sendLocation'] } },
110
102
  description: 'Location longitude',
111
103
  },
112
- {
113
- displayName: 'Location Name',
114
- name: 'locationName',
115
- type: 'string',
116
- default: '',
117
- displayOptions: { show: { operation: ['sendLocation'] } },
118
- description: 'Optional name for the location',
119
- },
120
- {
121
- displayName: 'Location Address',
122
- name: 'locationAddress',
123
- type: 'string',
124
- default: '',
125
- displayOptions: { show: { operation: ['sendLocation'] } },
126
- description: 'Optional address for the location',
127
- },
128
104
  ],
129
105
  };
130
106
  }
@@ -133,85 +109,69 @@ class WhatsAppSend {
133
109
  const returnData = [];
134
110
  const credentials = await this.getCredentials('whatsappApi');
135
111
  const cfg = await (0, WhatsAppApiHelper_1.getWhatsAppCredentials)(credentials);
136
- console.log(`[WhatsApp Send] Starting execution, recipient check: ${cfg.checkRecipientExists}`);
112
+ // Ensure server is running
113
+ await (0, WhatsAppApiHelper_1.ensureServerRunning)(cfg);
114
+ const serverUrl = (0, WhatsAppApiHelper_1.getServerUrl)(cfg);
137
115
  for (let i = 0; i < items.length; i++) {
138
116
  try {
139
117
  const operation = this.getNodeParameter('operation', i);
140
118
  const recipient = this.getNodeParameter('recipient', i);
141
- console.log(`[WhatsApp Send] Operation: ${operation}, To: ${recipient}`);
142
- let content = {};
119
+ let body = { to: recipient };
143
120
  switch (operation) {
144
121
  case 'sendText':
145
- content = { text: this.getNodeParameter('text', i) };
122
+ body.text = this.getNodeParameter('text', i);
146
123
  break;
147
124
  case 'sendImage': {
148
125
  const binaryProperty = this.getNodeParameter('binaryProperty', i);
149
126
  const buffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
150
- content = {
151
- image: buffer,
152
- caption: this.getNodeParameter('caption', i) || undefined,
153
- mimeType: this.getNodeParameter('mimeType', i) || undefined,
154
- };
127
+ body.image = buffer.toString('base64');
128
+ body.caption = this.getNodeParameter('caption', i) || undefined;
155
129
  break;
156
130
  }
157
131
  case 'sendDocument': {
158
132
  const binaryProperty = this.getNodeParameter('binaryProperty', i);
159
133
  const buffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
160
- content = {
161
- document: buffer,
162
- fileName: this.getNodeParameter('fileName', i),
163
- caption: this.getNodeParameter('caption', i) || undefined,
164
- mimeType: this.getNodeParameter('mimeType', i) || undefined,
165
- };
134
+ body.document = buffer.toString('base64');
135
+ body.fileName = this.getNodeParameter('fileName', i);
136
+ body.caption = this.getNodeParameter('caption', i) || undefined;
166
137
  break;
167
138
  }
168
139
  case 'sendAudio': {
169
140
  const binaryProperty = this.getNodeParameter('binaryProperty', i);
170
141
  const buffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
171
- content = {
172
- audio: buffer,
173
- ptt: this.getNodeParameter('ptt', i),
174
- mimeType: this.getNodeParameter('mimeType', i) || undefined,
175
- };
142
+ body.audio = buffer.toString('base64');
143
+ body.ptt = this.getNodeParameter('ptt', i);
176
144
  break;
177
145
  }
178
146
  case 'sendLocation':
179
- content = {
180
- location: {
181
- degreesLatitude: this.getNodeParameter('latitude', i),
182
- degreesLongitude: this.getNodeParameter('longitude', i),
183
- name: this.getNodeParameter('locationName', i) || undefined,
184
- address: this.getNodeParameter('locationAddress', i) || undefined,
185
- },
147
+ body.location = {
148
+ degreesLatitude: this.getNodeParameter('latitude', i),
149
+ degreesLongitude: this.getNodeParameter('longitude', i),
186
150
  };
187
151
  break;
188
152
  }
189
- const result = await (0, WhatsAppApiHelper_1.sendMessageWithAntiBan)(recipient, content, cfg);
153
+ const response = await fetch(`${serverUrl}/send`, {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify(body),
157
+ });
158
+ const result = await response.json();
159
+ if (!response.ok) {
160
+ throw new Error(result.error || 'Send failed');
161
+ }
190
162
  returnData.push({
191
- json: {
192
- ...result,
193
- operation,
194
- success: true,
195
- },
163
+ json: { ...result, operation, success: true },
196
164
  pairedItem: { item: i },
197
165
  });
198
166
  }
199
167
  catch (error) {
200
168
  if (this.continueOnFail()) {
201
- returnData.push({
202
- json: {
203
- error: error.message,
204
- success: false,
205
- },
206
- pairedItem: { item: i },
207
- });
169
+ returnData.push({ json: { error: error.message, success: false }, pairedItem: { item: i } });
208
170
  continue;
209
171
  }
210
172
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
211
173
  }
212
174
  }
213
- // Disconnect after sending — prevents conflict with other n8n workers
214
- await (0, WhatsAppApiHelper_1.disconnect)();
215
175
  return [returnData];
216
176
  }
217
177
  }
@@ -0,0 +1 @@
1
+ export {};