rez_core 2.2.190 → 2.2.191
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/dist/module/communication/communication.module.js +4 -1
- package/dist/module/communication/communication.module.js.map +1 -1
- package/dist/module/communication/controller/calender-event.controller.d.ts +31 -0
- package/dist/module/communication/controller/calender-event.controller.js +51 -0
- package/dist/module/communication/controller/calender-event.controller.js.map +1 -0
- package/dist/module/communication/controller/communication.controller.d.ts +8 -1
- package/dist/module/communication/controller/communication.controller.js +27 -0
- package/dist/module/communication/controller/communication.controller.js.map +1 -1
- package/dist/module/communication/dto/create-config.dto.d.ts +40 -0
- package/dist/module/communication/dto/create-config.dto.js +24 -1
- package/dist/module/communication/dto/create-config.dto.js.map +1 -1
- package/dist/module/communication/factories/whatsapp.factory.d.ts +3 -1
- package/dist/module/communication/factories/whatsapp.factory.js +13 -3
- package/dist/module/communication/factories/whatsapp.factory.js.map +1 -1
- package/dist/module/communication/service/calendar-event.service.d.ts +32 -0
- package/dist/module/communication/service/calendar-event.service.js +115 -0
- package/dist/module/communication/service/calendar-event.service.js.map +1 -0
- package/dist/module/communication/service/communication.service.d.ts +8 -0
- package/dist/module/communication/service/communication.service.js +56 -1
- package/dist/module/communication/service/communication.service.js.map +1 -1
- package/dist/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.d.ts +15 -0
- package/dist/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.js +233 -16
- package/dist/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.js.map +1 -1
- package/dist/module/workflow/service/task.service.js +0 -1
- package/dist/module/workflow/service/task.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/module/communication/communication.module.ts +5 -1
- package/src/module/communication/controller/calender-event.controller.ts +31 -0
- package/src/module/communication/controller/communication.controller.ts +29 -0
- package/src/module/communication/dto/create-config.dto.ts +71 -0
- package/src/module/communication/factories/whatsapp.factory.ts +13 -2
- package/src/module/communication/service/calendar-event.service.ts +123 -0
- package/src/module/communication/service/communication.service.ts +91 -2
- package/src/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.ts +373 -19
- package/src/module/workflow/service/task.service.ts +1 -1
|
@@ -1,11 +1,83 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import axios, { AxiosResponse } from 'axios';
|
|
2
3
|
import {
|
|
3
4
|
CommunicationStrategy,
|
|
4
5
|
CommunicationResult,
|
|
5
6
|
} from '../communication.strategy';
|
|
6
7
|
|
|
8
|
+
interface WhatsAppConfig {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
phoneNumberId: string;
|
|
11
|
+
apiVersion?: string;
|
|
12
|
+
businessAccountId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface WhatsAppMessage {
|
|
16
|
+
messaging_product: string;
|
|
17
|
+
to: string;
|
|
18
|
+
type: string;
|
|
19
|
+
text?: {
|
|
20
|
+
body: string;
|
|
21
|
+
preview_url?: boolean;
|
|
22
|
+
};
|
|
23
|
+
template?: {
|
|
24
|
+
name: string;
|
|
25
|
+
language: {
|
|
26
|
+
code: string;
|
|
27
|
+
};
|
|
28
|
+
components?: any[];
|
|
29
|
+
};
|
|
30
|
+
image?: {
|
|
31
|
+
link?: string;
|
|
32
|
+
caption?: string;
|
|
33
|
+
};
|
|
34
|
+
document?: {
|
|
35
|
+
link?: string;
|
|
36
|
+
caption?: string;
|
|
37
|
+
filename?: string;
|
|
38
|
+
};
|
|
39
|
+
audio?: {
|
|
40
|
+
link: string;
|
|
41
|
+
};
|
|
42
|
+
video?: {
|
|
43
|
+
link?: string;
|
|
44
|
+
caption?: string;
|
|
45
|
+
};
|
|
46
|
+
location?: {
|
|
47
|
+
latitude: number;
|
|
48
|
+
longitude: number;
|
|
49
|
+
name?: string;
|
|
50
|
+
address?: string;
|
|
51
|
+
};
|
|
52
|
+
interactive?: {
|
|
53
|
+
type: string;
|
|
54
|
+
body?: {
|
|
55
|
+
text: string;
|
|
56
|
+
};
|
|
57
|
+
footer?: {
|
|
58
|
+
text: string;
|
|
59
|
+
};
|
|
60
|
+
action: any;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface WhatsAppApiResponse {
|
|
65
|
+
messaging_product: string;
|
|
66
|
+
contacts: Array<{
|
|
67
|
+
input: string;
|
|
68
|
+
wa_id: string;
|
|
69
|
+
}>;
|
|
70
|
+
messages: Array<{
|
|
71
|
+
id: string;
|
|
72
|
+
message_status?: string;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
|
|
7
76
|
@Injectable()
|
|
8
77
|
export class WhatsAppCloudStrategy implements CommunicationStrategy {
|
|
78
|
+
private readonly logger = new Logger(WhatsAppCloudStrategy.name);
|
|
79
|
+
private readonly baseUrl = 'https://graph.facebook.com';
|
|
80
|
+
|
|
9
81
|
async sendMessage(
|
|
10
82
|
to: string,
|
|
11
83
|
message: string,
|
|
@@ -16,34 +88,316 @@ export class WhatsAppCloudStrategy implements CommunicationStrategy {
|
|
|
16
88
|
throw new Error('Invalid WhatsApp Cloud API configuration');
|
|
17
89
|
}
|
|
18
90
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
91
|
+
const whatsappConfig = config as WhatsAppConfig;
|
|
92
|
+
const apiVersion = whatsappConfig.apiVersion || 'v18.0';
|
|
93
|
+
const url = `${this.baseUrl}/${apiVersion}/${whatsappConfig.phoneNumberId}/messages`;
|
|
22
94
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
95
|
+
// Determine message type and prepare payload
|
|
96
|
+
const payload = this.prepareMessagePayload(to, message, config);
|
|
97
|
+
|
|
98
|
+
const response: AxiosResponse<WhatsAppApiResponse> = await axios.post(
|
|
99
|
+
url,
|
|
100
|
+
payload,
|
|
101
|
+
{
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `Bearer ${whatsappConfig.accessToken}`,
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
},
|
|
106
|
+
timeout: 30000,
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (response.data?.messages?.[0]?.id) {
|
|
111
|
+
this.logger.log(
|
|
112
|
+
`WhatsApp message sent successfully to ${to}, messageId: ${response.data.messages[0].id}`,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
messageId: response.data.messages[0].id,
|
|
118
|
+
provider: 'whatsapp-cloud',
|
|
119
|
+
service: 'API',
|
|
120
|
+
timestamp: new Date(),
|
|
121
|
+
};
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error('Invalid response from WhatsApp API');
|
|
124
|
+
}
|
|
30
125
|
} catch (error) {
|
|
126
|
+
this.logger.error(
|
|
127
|
+
`Failed to send WhatsApp message to ${to}`,
|
|
128
|
+
error.response?.data || error.message,
|
|
129
|
+
);
|
|
130
|
+
|
|
31
131
|
return {
|
|
32
132
|
success: false,
|
|
33
|
-
provider: 'whatsapp',
|
|
133
|
+
provider: 'whatsapp-cloud',
|
|
34
134
|
service: 'API',
|
|
35
|
-
error: error
|
|
135
|
+
error: this.extractErrorMessage(error),
|
|
36
136
|
timestamp: new Date(),
|
|
37
137
|
};
|
|
38
138
|
}
|
|
39
139
|
}
|
|
40
140
|
|
|
141
|
+
private prepareMessagePayload(
|
|
142
|
+
to: string,
|
|
143
|
+
message: string,
|
|
144
|
+
config: any,
|
|
145
|
+
): WhatsAppMessage {
|
|
146
|
+
const basePayload: WhatsAppMessage = {
|
|
147
|
+
messaging_product: 'whatsapp',
|
|
148
|
+
to: this.formatPhoneNumber(to),
|
|
149
|
+
type: 'text',
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Handle different message types based on config
|
|
153
|
+
if (config.messageType) {
|
|
154
|
+
switch (config.messageType) {
|
|
155
|
+
case 'template':
|
|
156
|
+
return {
|
|
157
|
+
...basePayload,
|
|
158
|
+
type: 'template',
|
|
159
|
+
template: {
|
|
160
|
+
name: config.templateName || 'hello_world',
|
|
161
|
+
language: {
|
|
162
|
+
code: config.languageCode || 'en_US',
|
|
163
|
+
},
|
|
164
|
+
components: config.templateComponents || [],
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
case 'image':
|
|
169
|
+
return {
|
|
170
|
+
...basePayload,
|
|
171
|
+
type: 'image',
|
|
172
|
+
image: {
|
|
173
|
+
link: config.mediaUrl,
|
|
174
|
+
caption: message || config.caption,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
case 'document':
|
|
179
|
+
return {
|
|
180
|
+
...basePayload,
|
|
181
|
+
type: 'document',
|
|
182
|
+
document: {
|
|
183
|
+
link: config.mediaUrl,
|
|
184
|
+
caption: message || config.caption,
|
|
185
|
+
filename: config.filename,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
case 'audio':
|
|
190
|
+
return {
|
|
191
|
+
...basePayload,
|
|
192
|
+
type: 'audio',
|
|
193
|
+
audio: {
|
|
194
|
+
link: config.mediaUrl,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
case 'video':
|
|
199
|
+
return {
|
|
200
|
+
...basePayload,
|
|
201
|
+
type: 'video',
|
|
202
|
+
video: {
|
|
203
|
+
link: config.mediaUrl,
|
|
204
|
+
caption: message || config.caption,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
case 'location':
|
|
209
|
+
return {
|
|
210
|
+
...basePayload,
|
|
211
|
+
type: 'location',
|
|
212
|
+
location: {
|
|
213
|
+
latitude: config.latitude,
|
|
214
|
+
longitude: config.longitude,
|
|
215
|
+
name: config.locationName,
|
|
216
|
+
address: config.locationAddress,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
case 'interactive':
|
|
221
|
+
return {
|
|
222
|
+
...basePayload,
|
|
223
|
+
type: 'interactive',
|
|
224
|
+
interactive: {
|
|
225
|
+
type: config.interactiveType || 'button',
|
|
226
|
+
body: {
|
|
227
|
+
text: message,
|
|
228
|
+
},
|
|
229
|
+
footer: config.footer ? { text: config.footer } : undefined,
|
|
230
|
+
action: config.interactiveAction,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
default:
|
|
235
|
+
// Fall back to text message
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Default text message
|
|
241
|
+
return {
|
|
242
|
+
...basePayload,
|
|
243
|
+
text: {
|
|
244
|
+
body: message,
|
|
245
|
+
preview_url: config.previewUrl !== false, // Default true
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private formatPhoneNumber(phoneNumber: string): string {
|
|
251
|
+
// Remove any non-digit characters except +
|
|
252
|
+
let formatted = phoneNumber.replace(/[^\d+]/g, '');
|
|
253
|
+
|
|
254
|
+
// If it doesn't start with +, add country code if provided
|
|
255
|
+
if (!formatted.startsWith('+')) {
|
|
256
|
+
formatted = `+${formatted}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Remove + for WhatsApp API (it expects numbers without +)
|
|
260
|
+
return formatted.substring(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private extractErrorMessage(error: any): string {
|
|
264
|
+
if (error.response?.data?.error?.message) {
|
|
265
|
+
return error.response.data.error.message;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (error.response?.data?.error?.error_user_msg) {
|
|
269
|
+
return error.response.data.error.error_user_msg;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (error.response?.data?.message) {
|
|
273
|
+
return error.response.data.message;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (error.message) {
|
|
277
|
+
return error.message;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return 'Unknown WhatsApp API error';
|
|
281
|
+
}
|
|
282
|
+
|
|
41
283
|
validateConfig(config: any): boolean {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
284
|
+
if (!config || typeof config !== 'object') {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Required fields
|
|
289
|
+
if (!config.accessToken || !config.phoneNumberId) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Validate message type specific requirements
|
|
294
|
+
if (config.messageType) {
|
|
295
|
+
switch (config.messageType) {
|
|
296
|
+
case 'template':
|
|
297
|
+
if (!config.templateName) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
case 'image':
|
|
303
|
+
case 'document':
|
|
304
|
+
case 'audio':
|
|
305
|
+
case 'video':
|
|
306
|
+
if (!config.mediaUrl) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
case 'location':
|
|
312
|
+
if (
|
|
313
|
+
typeof config.latitude !== 'number' ||
|
|
314
|
+
typeof config.longitude !== 'number'
|
|
315
|
+
) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'interactive':
|
|
321
|
+
if (!config.interactiveAction) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async getPhoneNumberInfo(config: WhatsAppConfig): Promise<any> {
|
|
332
|
+
try {
|
|
333
|
+
const apiVersion = config.apiVersion || 'v18.0';
|
|
334
|
+
const url = `${this.baseUrl}/${apiVersion}/${config.phoneNumberId}`;
|
|
335
|
+
|
|
336
|
+
const response = await axios.get(url, {
|
|
337
|
+
headers: {
|
|
338
|
+
Authorization: `Bearer ${config.accessToken}`,
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return response.data;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
this.logger.error(
|
|
345
|
+
'Failed to get phone number info',
|
|
346
|
+
error.response?.data || error.message,
|
|
347
|
+
);
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Failed to get phone number info: ${this.extractErrorMessage(error)}`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async getBusinessProfile(config: WhatsAppConfig): Promise<any> {
|
|
355
|
+
try {
|
|
356
|
+
const apiVersion = config.apiVersion || 'v18.0';
|
|
357
|
+
const url = `${this.baseUrl}/${apiVersion}/${config.phoneNumberId}/whatsapp_business_profile`;
|
|
358
|
+
|
|
359
|
+
const response = await axios.get(url, {
|
|
360
|
+
headers: {
|
|
361
|
+
Authorization: `Bearer ${config.accessToken}`,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return response.data;
|
|
366
|
+
} catch (error) {
|
|
367
|
+
this.logger.error(
|
|
368
|
+
'Failed to get business profile',
|
|
369
|
+
error.response?.data || error.message,
|
|
370
|
+
);
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Failed to get business profile: ${this.extractErrorMessage(error)}`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async updateBusinessProfile(
|
|
378
|
+
config: WhatsAppConfig,
|
|
379
|
+
profileData: any,
|
|
380
|
+
): Promise<any> {
|
|
381
|
+
try {
|
|
382
|
+
const apiVersion = config.apiVersion || 'v18.0';
|
|
383
|
+
const url = `${this.baseUrl}/${apiVersion}/${config.phoneNumberId}/whatsapp_business_profile`;
|
|
384
|
+
|
|
385
|
+
const response = await axios.post(url, profileData, {
|
|
386
|
+
headers: {
|
|
387
|
+
Authorization: `Bearer ${config.accessToken}`,
|
|
388
|
+
'Content-Type': 'application/json',
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return response.data;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
this.logger.error(
|
|
395
|
+
'Failed to update business profile',
|
|
396
|
+
error.response?.data || error.message,
|
|
397
|
+
);
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Failed to update business profile: ${this.extractErrorMessage(error)}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
48
402
|
}
|
|
49
403
|
}
|