@gamastudio/sendwave-provider 0.0.6 → 0.0.7-dev1
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/build/core/interface/parser.interface.d.ts +3 -1
- package/build/core/interface/types.d.ts +12 -0
- package/build/package.json +2 -2
- package/build/provider/core.js +28 -1
- package/build/provider/provider.d.ts +3 -0
- package/build/provider/provider.js +157 -31
- package/build/provider/sender.d.ts +2 -0
- package/build/provider/sender.js +113 -7
- package/build/utils/detectorMedia.d.ts +19 -2
- package/build/utils/detectorMedia.js +98 -14
- package/package.json +2 -2
|
@@ -10,6 +10,7 @@ export interface GlobalVendorArgs {
|
|
|
10
10
|
url?: string;
|
|
11
11
|
apiKey: string;
|
|
12
12
|
port?: number;
|
|
13
|
+
host?: string;
|
|
13
14
|
delay?: number;
|
|
14
15
|
linkPreview?: boolean;
|
|
15
16
|
message?: {
|
|
@@ -25,6 +26,17 @@ export interface GlobalVendorArgs {
|
|
|
25
26
|
endTimeout?: number;
|
|
26
27
|
warningMessage?: string;
|
|
27
28
|
};
|
|
29
|
+
payloadLimits?: {
|
|
30
|
+
json?: string;
|
|
31
|
+
urlencoded?: string;
|
|
32
|
+
text?: string;
|
|
33
|
+
media?: {
|
|
34
|
+
image?: string;
|
|
35
|
+
video?: string;
|
|
36
|
+
audio?: string;
|
|
37
|
+
document?: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
28
40
|
}
|
|
29
41
|
export interface Message {
|
|
30
42
|
type: string;
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamastudio/sendwave-provider",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7-dev1",
|
|
4
4
|
"description": "Librería para interactuar con Sendwave usando configuración dinámica.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"author": "Ameth Galarcio <amethgm@gmail.com>",
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@builderbot/bot": "^1.
|
|
25
|
+
"@builderbot/bot": "^1.2.9",
|
|
26
26
|
"@gamastudio/colorslog": "^0.1.6",
|
|
27
27
|
"@types/mime-types": "^2.1.4",
|
|
28
28
|
"axios": "^1.11.0",
|
package/build/provider/core.js
CHANGED
|
@@ -54,8 +54,35 @@ class SendWaveCore extends node_events_1.EventEmitter {
|
|
|
54
54
|
}
|
|
55
55
|
catch (e) {
|
|
56
56
|
console.error("[IncomingMsg Error]:", e);
|
|
57
|
+
// Handle PayloadTooLargeError specifically
|
|
58
|
+
if (e.name === 'PayloadTooLargeError' || e.type === 'entity.too.large') {
|
|
59
|
+
console.error(`[PayloadTooLarge] Request from ${req.ip || 'unknown'} exceeded size limit`);
|
|
60
|
+
res.statusCode = 413;
|
|
61
|
+
res.setHeader('Content-Type', 'application/json');
|
|
62
|
+
return res.end(JSON.stringify({
|
|
63
|
+
error: 'Payload too large',
|
|
64
|
+
message: 'El archivo o mensaje es demasiado grande',
|
|
65
|
+
code: 'PAYLOAD_TOO_LARGE'
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
// Handle timeout errors
|
|
69
|
+
if (e.type === 'request.timeout') {
|
|
70
|
+
console.error(`[RequestTimeout] Request from ${req.ip || 'unknown'} timed out`);
|
|
71
|
+
res.statusCode = 408;
|
|
72
|
+
res.setHeader('Content-Type', 'application/json');
|
|
73
|
+
return res.end(JSON.stringify({
|
|
74
|
+
error: 'Request timeout',
|
|
75
|
+
message: 'La solicitud ha tardado demasiado',
|
|
76
|
+
code: 'REQUEST_TIMEOUT'
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
57
79
|
res.statusCode = 500;
|
|
58
|
-
|
|
80
|
+
res.setHeader('Content-Type', 'application/json');
|
|
81
|
+
return res.end(JSON.stringify({
|
|
82
|
+
error: 'Internal Server Error',
|
|
83
|
+
message: 'Error interno del servidor',
|
|
84
|
+
code: 'INTERNAL_ERROR'
|
|
85
|
+
}));
|
|
59
86
|
}
|
|
60
87
|
};
|
|
61
88
|
this.messageBuffers = {};
|
|
@@ -14,6 +14,7 @@ export declare class SendWaveProvider extends ProviderClass<SendWaveCore> {
|
|
|
14
14
|
sender: SenderMessage;
|
|
15
15
|
globalVendorArgs: GlobalVendorArgs;
|
|
16
16
|
constructor(args: Partial<GlobalVendorArgs>);
|
|
17
|
+
private setupGlobalErrorHandlers;
|
|
17
18
|
protected initVendor(): Promise<any>;
|
|
18
19
|
protected beforeHttpServerInit: () => Promise<void>;
|
|
19
20
|
protected getApiQrConnect: () => Promise<void>;
|
|
@@ -22,6 +23,7 @@ export declare class SendWaveProvider extends ProviderClass<SendWaveCore> {
|
|
|
22
23
|
path: string;
|
|
23
24
|
}): Promise<string>;
|
|
24
25
|
createInstance(): void;
|
|
26
|
+
private parseSize;
|
|
25
27
|
busEvents: () => ({
|
|
26
28
|
event: string;
|
|
27
29
|
func: (payload: BotContext) => void;
|
|
@@ -48,6 +50,7 @@ export declare class SendWaveProvider extends ProviderClass<SendWaveCore> {
|
|
|
48
50
|
sendButton(...args: Parameters<SenderMessage["sendButton"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
49
51
|
sendReaction(...args: Parameters<SenderMessage["sendReaction"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
50
52
|
sendLocation(...args: Parameters<SenderMessage["sendLocation"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
53
|
+
sendMedia(...args: Parameters<SenderMessage["sendMedia"]>): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
51
54
|
/**
|
|
52
55
|
* Reset user timeout for queue flow system
|
|
53
56
|
*/
|
|
@@ -27,6 +27,7 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
27
27
|
name: "bot",
|
|
28
28
|
url: "https://sendwave-api.gamastudio.co",
|
|
29
29
|
port: 3000,
|
|
30
|
+
host: "0.0.0.0", // Listen on all interfaces by default
|
|
30
31
|
apiKey: "",
|
|
31
32
|
delay: 0,
|
|
32
33
|
linkPreview: true,
|
|
@@ -40,10 +41,38 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
40
41
|
endTimeout: 2 * 60 * 1000, // 2 minutes
|
|
41
42
|
warningMessage: "⏳ Parece que has estado inactivo. ¿Sigues ahí?",
|
|
42
43
|
},
|
|
44
|
+
payloadLimits: {
|
|
45
|
+
json: '50mb',
|
|
46
|
+
urlencoded: '50mb',
|
|
47
|
+
text: '10mb',
|
|
48
|
+
media: {
|
|
49
|
+
image: '10mb',
|
|
50
|
+
video: '100mb',
|
|
51
|
+
audio: '50mb',
|
|
52
|
+
document: '25mb',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
43
55
|
};
|
|
44
56
|
this.beforeHttpServerInit = async () => {
|
|
57
|
+
// Configure body parser with large payload limits
|
|
58
|
+
const payloadLimits = this.globalVendorArgs.payloadLimits || {};
|
|
45
59
|
this.server = this.server
|
|
46
|
-
.use(body_parser_1.default.json(
|
|
60
|
+
.use(body_parser_1.default.json({
|
|
61
|
+
limit: payloadLimits.json || '50mb',
|
|
62
|
+
type: 'application/json'
|
|
63
|
+
}))
|
|
64
|
+
.use(body_parser_1.default.urlencoded({
|
|
65
|
+
limit: payloadLimits.urlencoded || '50mb',
|
|
66
|
+
extended: true
|
|
67
|
+
}))
|
|
68
|
+
.use(body_parser_1.default.text({
|
|
69
|
+
limit: payloadLimits.text || '10mb',
|
|
70
|
+
type: 'text/*'
|
|
71
|
+
}))
|
|
72
|
+
.use(body_parser_1.default.raw({
|
|
73
|
+
limit: payloadLimits.json || '50mb',
|
|
74
|
+
type: 'application/octet-stream'
|
|
75
|
+
}))
|
|
47
76
|
.use((req, _, next) => {
|
|
48
77
|
req["globalVendorArgs"] = this.globalVendorArgs;
|
|
49
78
|
return next();
|
|
@@ -117,6 +146,11 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
117
146
|
];
|
|
118
147
|
this.initAll = (port, opts) => {
|
|
119
148
|
this.globalVendorArgs.port = port;
|
|
149
|
+
// Override the server binding to listen on all interfaces
|
|
150
|
+
if (this.globalVendorArgs.host === "0.0.0.0") {
|
|
151
|
+
process.env.HOST = "0.0.0.0";
|
|
152
|
+
process.env.HOSTNAME = "0.0.0.0";
|
|
153
|
+
}
|
|
120
154
|
const methods = {
|
|
121
155
|
sendMessage: this.sendMessage,
|
|
122
156
|
sendList: this.sendList,
|
|
@@ -150,6 +184,12 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
150
184
|
});
|
|
151
185
|
this.afterHttpServerInit();
|
|
152
186
|
});
|
|
187
|
+
})
|
|
188
|
+
.catch((error) => {
|
|
189
|
+
console.error("[Provider Error]:", error);
|
|
190
|
+
if (error.type === 'entity.too.large') {
|
|
191
|
+
console.error("[Provider] PayloadTooLarge error caught at provider level");
|
|
192
|
+
}
|
|
153
193
|
});
|
|
154
194
|
return;
|
|
155
195
|
};
|
|
@@ -174,6 +214,27 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
174
214
|
// Puedes emitir eventos a websockets aquí, notificar a frontends, etc.
|
|
175
215
|
});
|
|
176
216
|
this.sender = new sender_1.SenderMessage(this.globalVendorArgs);
|
|
217
|
+
// Add global error handlers for payload errors
|
|
218
|
+
this.setupGlobalErrorHandlers();
|
|
219
|
+
}
|
|
220
|
+
setupGlobalErrorHandlers() {
|
|
221
|
+
// Handle uncaught PayloadTooLarge errors
|
|
222
|
+
process.on('uncaughtException', (error) => {
|
|
223
|
+
if (error.name === 'PayloadTooLargeError' || error.type === 'entity.too.large') {
|
|
224
|
+
console.error('[Global] PayloadTooLarge error intercepted:', error.message);
|
|
225
|
+
// Don't exit process for this type of error
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// For other uncaught exceptions, log and continue
|
|
229
|
+
console.error('[Global] Uncaught exception:', error);
|
|
230
|
+
});
|
|
231
|
+
process.on('unhandledRejection', (reason) => {
|
|
232
|
+
if (reason && (reason.name === 'PayloadTooLargeError' || reason.type === 'entity.too.large')) {
|
|
233
|
+
console.error('[Global] PayloadTooLarge rejection intercepted:', reason.message);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
console.error('[Global] Unhandled rejection:', reason);
|
|
237
|
+
});
|
|
177
238
|
}
|
|
178
239
|
initVendor() {
|
|
179
240
|
const vendor = new core_1.SendWaveCore(this.globalVendorArgs.port, this.queue, this.globalVendorArgs);
|
|
@@ -223,6 +284,8 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
223
284
|
this.globalVendorArgs.url.trim()
|
|
224
285
|
? this.globalVendorArgs.url.trim()
|
|
225
286
|
: "https://sendwave-api.gamastudio.co";
|
|
287
|
+
const payloadLimits = this.globalVendorArgs.payloadLimits || {};
|
|
288
|
+
const maxContentLength = this.parseSize(payloadLimits.json || '50mb');
|
|
226
289
|
this.sendwaveApi = axios_1.default.create({
|
|
227
290
|
baseURL: url,
|
|
228
291
|
httpsAgent: new https_1.Agent({ rejectUnauthorized: false }),
|
|
@@ -230,8 +293,46 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
230
293
|
"Content-Type": "application/json",
|
|
231
294
|
apikey: this.globalVendorArgs.apiKey,
|
|
232
295
|
},
|
|
296
|
+
// Large file support configuration
|
|
297
|
+
maxContentLength: maxContentLength,
|
|
298
|
+
maxBodyLength: maxContentLength,
|
|
299
|
+
timeout: 300000, // 5 minutes timeout for large files
|
|
300
|
+
});
|
|
301
|
+
// Add request interceptor for large file logging
|
|
302
|
+
this.sendwaveApi.interceptors.request.use((config) => {
|
|
303
|
+
const contentLength = config.headers['content-length'] ||
|
|
304
|
+
(config.data ? JSON.stringify(config.data).length : 0);
|
|
305
|
+
if (contentLength > 1024 * 1024) { // Log requests larger than 1MB
|
|
306
|
+
console.log(`[LargeRequest] Sending ${Math.round(contentLength / 1024 / 1024 * 100) / 100}MB to ${config.url}`);
|
|
307
|
+
}
|
|
308
|
+
return config;
|
|
309
|
+
}, (error) => Promise.reject(error));
|
|
310
|
+
// Add response interceptor for error handling
|
|
311
|
+
this.sendwaveApi.interceptors.response.use((response) => response, (error) => {
|
|
312
|
+
var _a;
|
|
313
|
+
if (error.code === 'ECONNABORTED') {
|
|
314
|
+
console.error('[RequestTimeout] Large file upload timed out');
|
|
315
|
+
}
|
|
316
|
+
else if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 413) {
|
|
317
|
+
console.error('[PayloadTooLarge] Server rejected large payload');
|
|
318
|
+
}
|
|
319
|
+
return Promise.reject(error);
|
|
233
320
|
});
|
|
234
321
|
}
|
|
322
|
+
parseSize(size) {
|
|
323
|
+
const units = {
|
|
324
|
+
'b': 1,
|
|
325
|
+
'kb': 1024,
|
|
326
|
+
'mb': 1024 * 1024,
|
|
327
|
+
'gb': 1024 * 1024 * 1024
|
|
328
|
+
};
|
|
329
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
330
|
+
if (!match)
|
|
331
|
+
return 50 * 1024 * 1024; // Default 50MB
|
|
332
|
+
const value = parseFloat(match[1]);
|
|
333
|
+
const unit = match[2] || 'b';
|
|
334
|
+
return Math.floor(value * units[unit]);
|
|
335
|
+
}
|
|
235
336
|
async listenOnEvents(vendor) {
|
|
236
337
|
const listEvents = this.busEvents();
|
|
237
338
|
for (const { event, func } of listEvents) {
|
|
@@ -246,38 +347,60 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
246
347
|
});
|
|
247
348
|
}
|
|
248
349
|
async sendMessage(from, text, options) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
350
|
+
var _a, _b;
|
|
351
|
+
try {
|
|
352
|
+
options = { ...options["options"] };
|
|
353
|
+
let mss = { from, text, options };
|
|
354
|
+
if (options.media) {
|
|
355
|
+
// Initialize detector with payload limits from global args
|
|
356
|
+
if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
|
|
357
|
+
detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
|
|
358
|
+
}
|
|
359
|
+
const { media, mediaType, fileName, size } = await detectorMedia_1.detectorMedia.processMedia(options.media);
|
|
360
|
+
switch (mediaType) {
|
|
361
|
+
case "image":
|
|
362
|
+
return await this.sender.sendImage({
|
|
363
|
+
...mss,
|
|
364
|
+
url: media,
|
|
365
|
+
text: text,
|
|
366
|
+
});
|
|
367
|
+
case "video":
|
|
368
|
+
return await this.sender.sendVideo({
|
|
369
|
+
...mss,
|
|
370
|
+
url: media,
|
|
371
|
+
text: text,
|
|
372
|
+
});
|
|
373
|
+
case "audio":
|
|
374
|
+
return await this.sender.sendVoice({ ...mss, url: media });
|
|
375
|
+
case "document":
|
|
376
|
+
return await this.sender.sendFile({
|
|
377
|
+
...mss,
|
|
378
|
+
url: media,
|
|
379
|
+
fileName,
|
|
380
|
+
text: text,
|
|
381
|
+
});
|
|
382
|
+
default:
|
|
383
|
+
return await this.sender.sendText(mss);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
return await this.sender.sendText(mss);
|
|
277
388
|
}
|
|
278
389
|
}
|
|
279
|
-
|
|
280
|
-
|
|
390
|
+
catch (error) {
|
|
391
|
+
// Enhanced error handling for large files
|
|
392
|
+
if (error.message && error.message.includes('exceeds limit')) {
|
|
393
|
+
console.error(`[SendMessage Error] File size validation failed: ${error.message}`);
|
|
394
|
+
throw new Error(`File too large: ${error.message}`);
|
|
395
|
+
}
|
|
396
|
+
else if (error.message && error.message.includes('too large')) {
|
|
397
|
+
console.error(`[SendMessage Error] Large file error: ${error.message}`);
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
console.error(`[SendMessage Error] ${error.message || 'Unknown error'}`);
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
281
404
|
}
|
|
282
405
|
}
|
|
283
406
|
// Métodos de conveniencia para mantener la API limpia
|
|
@@ -314,6 +437,9 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
314
437
|
sendLocation(...args) {
|
|
315
438
|
return this.sender.sendLocation(...args);
|
|
316
439
|
}
|
|
440
|
+
sendMedia(...args) {
|
|
441
|
+
return this.sender.sendMedia(...args);
|
|
442
|
+
}
|
|
317
443
|
// Queue Flow Management Methods
|
|
318
444
|
/**
|
|
319
445
|
* Reset user timeout for queue flow system
|
|
@@ -3,10 +3,12 @@ export declare class SenderMessage implements ProviderInterface {
|
|
|
3
3
|
private sendwaveApi?;
|
|
4
4
|
private globalVendorArgs?;
|
|
5
5
|
constructor(args: Partial<GlobalVendorArgs>);
|
|
6
|
+
private parseSize;
|
|
6
7
|
sendPresence(data: SendPresence): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
7
8
|
sendText(data: SendMessage): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
8
9
|
sendList(data: SendList): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
9
10
|
sendMedia(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
11
|
+
private getDefaultMimeType;
|
|
10
12
|
sendFile(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
11
13
|
sendImage(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
|
12
14
|
sendAudio(data: SendMedia): Promise<import("axios").AxiosResponse<any, any, {}> | undefined>;
|
package/build/provider/sender.js
CHANGED
|
@@ -9,6 +9,8 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
9
9
|
const detectorMedia_1 = require("../utils/detectorMedia");
|
|
10
10
|
class SenderMessage {
|
|
11
11
|
constructor(args) {
|
|
12
|
+
const payloadLimits = args.payloadLimits || {};
|
|
13
|
+
const maxContentLength = this.parseSize(payloadLimits.json || "50mb");
|
|
12
14
|
this.sendwaveApi = axios_1.default.create({
|
|
13
15
|
baseURL: args.url,
|
|
14
16
|
httpsAgent: new https_1.Agent({ rejectUnauthorized: false }),
|
|
@@ -16,9 +18,26 @@ class SenderMessage {
|
|
|
16
18
|
"Content-Type": "application/json",
|
|
17
19
|
apikey: args.apiKey,
|
|
18
20
|
},
|
|
21
|
+
maxContentLength: maxContentLength,
|
|
22
|
+
maxBodyLength: maxContentLength,
|
|
23
|
+
timeout: 300000, // 5 minutes for large files
|
|
19
24
|
});
|
|
20
25
|
this.globalVendorArgs = args;
|
|
21
26
|
}
|
|
27
|
+
parseSize(size) {
|
|
28
|
+
const units = {
|
|
29
|
+
b: 1,
|
|
30
|
+
kb: 1024,
|
|
31
|
+
mb: 1024 * 1024,
|
|
32
|
+
gb: 1024 * 1024 * 1024,
|
|
33
|
+
};
|
|
34
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
35
|
+
if (!match)
|
|
36
|
+
return 50 * 1024 * 1024; // Default 50MB
|
|
37
|
+
const value = parseFloat(match[1]);
|
|
38
|
+
const unit = match[2] || "b";
|
|
39
|
+
return Math.floor(value * units[unit]);
|
|
40
|
+
}
|
|
22
41
|
async sendPresence(data) {
|
|
23
42
|
var _a, _b;
|
|
24
43
|
try {
|
|
@@ -96,10 +115,50 @@ class SenderMessage {
|
|
|
96
115
|
}
|
|
97
116
|
}
|
|
98
117
|
async sendMedia(data) {
|
|
99
|
-
var _a, _b;
|
|
118
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
100
119
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
// Initialize detector with payload limits from global args
|
|
121
|
+
if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
|
|
122
|
+
detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
|
|
123
|
+
}
|
|
124
|
+
let media;
|
|
125
|
+
let mimeType;
|
|
126
|
+
let mediaType;
|
|
127
|
+
// If mediaType is already specified (from sendImage, sendVideo, etc), use it
|
|
128
|
+
if (data.mediaType) {
|
|
129
|
+
console.log(`[SendMedia] Using predefined mediaType: ${data.mediaType}`);
|
|
130
|
+
// Just validate size and get media content without detection
|
|
131
|
+
if (detectorMedia_1.detectorMedia.isRemoteUrl(data.url)) {
|
|
132
|
+
// For remote URLs, validate size and use URL directly
|
|
133
|
+
await detectorMedia_1.detectorMedia.validateUrlSize(data.url, data.mediaType);
|
|
134
|
+
media = data.url;
|
|
135
|
+
// Get actual MIME type from server if possible
|
|
136
|
+
try {
|
|
137
|
+
const { mimeType: detectedMime } = await detectorMedia_1.detectorMedia.getMimeTypeFromUrl(data.url);
|
|
138
|
+
mimeType = detectedMime || this.getDefaultMimeType(data.mediaType);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
mimeType = this.getDefaultMimeType(data.mediaType);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// For local files, convert to base64
|
|
146
|
+
const result = await detectorMedia_1.detectorMedia.convertPathToBase64(data.url);
|
|
147
|
+
media = result.media;
|
|
148
|
+
mimeType = result.mimeType;
|
|
149
|
+
}
|
|
150
|
+
mediaType = data.mediaType;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// If no mediaType specified, detect it (for generic sendMedia calls)
|
|
154
|
+
console.log(`[SendMedia] Auto-detecting mediaType for: ${data.url}`);
|
|
155
|
+
const result = await detectorMedia_1.detectorMedia.processMedia(data.url);
|
|
156
|
+
media = result.media;
|
|
157
|
+
mimeType = result.mimeType;
|
|
158
|
+
mediaType = result.mediaType;
|
|
159
|
+
}
|
|
160
|
+
console.log(`[SendMedia] Final - MediaType: ${mediaType}, MimeType: ${mimeType}`);
|
|
161
|
+
return await ((_c = this.sendwaveApi) === null || _c === void 0 ? void 0 : _c.post(`/message/sendMedia/${(_d = this.globalVendorArgs) === null || _d === void 0 ? void 0 : _d.name}`, {
|
|
103
162
|
number: data.from.split("@")[0],
|
|
104
163
|
caption: data.text,
|
|
105
164
|
mediatype: mediaType,
|
|
@@ -110,9 +169,35 @@ class SenderMessage {
|
|
|
110
169
|
}));
|
|
111
170
|
}
|
|
112
171
|
catch (e) {
|
|
113
|
-
|
|
172
|
+
// Enhanced error handling for large files
|
|
173
|
+
if (e.message && e.message.includes("exceeds limit")) {
|
|
174
|
+
console.error(`[SendMedia Error] File size validation failed: ${e.message}`);
|
|
175
|
+
throw new Error(`File too large: ${e.message}`);
|
|
176
|
+
}
|
|
177
|
+
else if (((_e = e.response) === null || _e === void 0 ? void 0 : _e.status) === 413) {
|
|
178
|
+
console.error("[SendMedia Error] Server rejected large payload");
|
|
179
|
+
throw new Error("File too large for server");
|
|
180
|
+
}
|
|
181
|
+
else if (e.code === "ECONNABORTED") {
|
|
182
|
+
console.error("[SendMedia Error] Request timeout - file may be too large");
|
|
183
|
+
throw new Error("Upload timeout - file may be too large");
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const errorMsg = ((_h = (_g = (_f = e.response) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.response) === null || _h === void 0 ? void 0 : _h.message) || e.message || "Unknown error";
|
|
187
|
+
console.error(`[SendMedia Error] ${errorMsg}`);
|
|
188
|
+
throw new Error(errorMsg);
|
|
189
|
+
}
|
|
114
190
|
}
|
|
115
191
|
}
|
|
192
|
+
getDefaultMimeType(mediaType) {
|
|
193
|
+
const defaults = {
|
|
194
|
+
image: 'image/jpeg',
|
|
195
|
+
video: 'video/mp4',
|
|
196
|
+
audio: 'audio/mpeg',
|
|
197
|
+
document: 'application/octet-stream'
|
|
198
|
+
};
|
|
199
|
+
return defaults[mediaType] || 'application/octet-stream';
|
|
200
|
+
}
|
|
116
201
|
async sendFile(data) {
|
|
117
202
|
try {
|
|
118
203
|
return await this.sendMedia({
|
|
@@ -159,22 +244,43 @@ class SenderMessage {
|
|
|
159
244
|
}
|
|
160
245
|
}
|
|
161
246
|
async sendVoice(data) {
|
|
162
|
-
var _a, _b;
|
|
247
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
163
248
|
try {
|
|
249
|
+
// Initialize detector with payload limits from global args
|
|
250
|
+
if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
|
|
251
|
+
detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
|
|
252
|
+
}
|
|
164
253
|
const { media } = await detectorMedia_1.detectorMedia.processMedia(data.url);
|
|
165
254
|
await this.sendPresence({
|
|
166
255
|
from: data.from,
|
|
167
256
|
presence: "recording",
|
|
168
257
|
delay: data.delay || 2000,
|
|
169
258
|
});
|
|
170
|
-
return await ((
|
|
259
|
+
return await ((_c = this.sendwaveApi) === null || _c === void 0 ? void 0 : _c.post(`/message/sendWhatsAppAudio/${(_d = this.globalVendorArgs) === null || _d === void 0 ? void 0 : _d.name}`, {
|
|
171
260
|
number: data.from,
|
|
172
261
|
audio: media,
|
|
173
262
|
...this.globalVendorArgs,
|
|
174
263
|
}));
|
|
175
264
|
}
|
|
176
265
|
catch (e) {
|
|
177
|
-
|
|
266
|
+
// Enhanced error handling for large audio files
|
|
267
|
+
if (e.message && e.message.includes("exceeds limit")) {
|
|
268
|
+
console.error(`[SendVoice Error] File size validation failed: ${e.message}`);
|
|
269
|
+
throw new Error(`Audio file too large: ${e.message}`);
|
|
270
|
+
}
|
|
271
|
+
else if (((_e = e.response) === null || _e === void 0 ? void 0 : _e.status) === 413) {
|
|
272
|
+
console.error("[SendVoice Error] Server rejected large payload");
|
|
273
|
+
throw new Error("Audio file too large for server");
|
|
274
|
+
}
|
|
275
|
+
else if (e.code === "ECONNABORTED") {
|
|
276
|
+
console.error("[SendVoice Error] Request timeout - audio file may be too large");
|
|
277
|
+
throw new Error("Upload timeout - audio file may be too large");
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
const errorMsg = ((_h = (_g = (_f = e.response) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.response) === null || _h === void 0 ? void 0 : _h.message) || e.message || "Unknown error";
|
|
281
|
+
console.error(`[SendVoice Error] ${errorMsg}`);
|
|
282
|
+
throw new Error(errorMsg);
|
|
283
|
+
}
|
|
178
284
|
}
|
|
179
285
|
}
|
|
180
286
|
async sendButton(data) {
|
|
@@ -3,19 +3,36 @@ interface DetectorMediaProps {
|
|
|
3
3
|
mimeType?: string;
|
|
4
4
|
mediaType?: string;
|
|
5
5
|
fileName?: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
}
|
|
8
|
+
interface MediaLimits {
|
|
9
|
+
image?: string;
|
|
10
|
+
video?: string;
|
|
11
|
+
audio?: string;
|
|
12
|
+
document?: string;
|
|
6
13
|
}
|
|
7
14
|
export declare class DetectorMedia {
|
|
8
15
|
static instance: DetectorMedia;
|
|
9
|
-
|
|
16
|
+
private mediaLimits;
|
|
17
|
+
constructor(limits?: MediaLimits);
|
|
18
|
+
static getInstance(limits?: MediaLimits): DetectorMedia;
|
|
19
|
+
updateLimits(limits: MediaLimits): void;
|
|
20
|
+
private parseSize;
|
|
21
|
+
private validateFileSize;
|
|
22
|
+
validateUrlSize(url: string, mediaType: string): Promise<number>;
|
|
10
23
|
isRemoteUrl(url: string): boolean;
|
|
11
24
|
convertPathToBase64(filePath: string): Promise<DetectorMediaProps>;
|
|
12
|
-
getMimeTypeFromUrl(url: string): Promise<
|
|
25
|
+
getMimeTypeFromUrl(url: string): Promise<{
|
|
26
|
+
mediaType: string | null;
|
|
27
|
+
mimeType: string | null;
|
|
28
|
+
}>;
|
|
13
29
|
extractFileNameFromUrl(url: string): Promise<string>;
|
|
14
30
|
/**
|
|
15
31
|
* Método unificado que acepta path o url y devuelve:
|
|
16
32
|
* - media (base64 si es path, url si es remota)
|
|
17
33
|
* - mimeType
|
|
18
34
|
* - fileName
|
|
35
|
+
* - size
|
|
19
36
|
*/
|
|
20
37
|
processMedia(input: string): Promise<DetectorMediaProps>;
|
|
21
38
|
mapMimeToCategory(mimeType: string): "image" | "video" | "audio" | "document" | "unknown";
|
|
@@ -9,12 +9,79 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const mime_types_1 = __importDefault(require("mime-types"));
|
|
10
10
|
const axios_1 = __importDefault(require("axios"));
|
|
11
11
|
class DetectorMedia {
|
|
12
|
-
|
|
12
|
+
constructor(limits) {
|
|
13
|
+
this.mediaLimits = {
|
|
14
|
+
image: "10mb",
|
|
15
|
+
video: "100mb",
|
|
16
|
+
audio: "50mb",
|
|
17
|
+
document: "25mb",
|
|
18
|
+
...limits,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
static getInstance(limits) {
|
|
13
22
|
if (!DetectorMedia.instance) {
|
|
14
|
-
DetectorMedia.instance = new DetectorMedia();
|
|
23
|
+
DetectorMedia.instance = new DetectorMedia(limits);
|
|
15
24
|
}
|
|
16
25
|
return DetectorMedia.instance;
|
|
17
26
|
}
|
|
27
|
+
updateLimits(limits) {
|
|
28
|
+
this.mediaLimits = { ...this.mediaLimits, ...limits };
|
|
29
|
+
}
|
|
30
|
+
parseSize(size) {
|
|
31
|
+
const units = {
|
|
32
|
+
b: 1,
|
|
33
|
+
kb: 1024,
|
|
34
|
+
mb: 1024 * 1024,
|
|
35
|
+
gb: 1024 * 1024 * 1024,
|
|
36
|
+
};
|
|
37
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
38
|
+
if (!match)
|
|
39
|
+
return 10 * 1024 * 1024; // Default 10MB
|
|
40
|
+
const value = parseFloat(match[1]);
|
|
41
|
+
const unit = match[2] || "b";
|
|
42
|
+
return Math.floor(value * units[unit]);
|
|
43
|
+
}
|
|
44
|
+
validateFileSize(filePath, mediaType) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
promises_1.default.stat(filePath)
|
|
47
|
+
.then((stats) => {
|
|
48
|
+
const fileSize = stats.size;
|
|
49
|
+
const limitStr = this.mediaLimits[mediaType] || "10mb";
|
|
50
|
+
const limit = this.parseSize(limitStr);
|
|
51
|
+
if (fileSize > limit) {
|
|
52
|
+
const fileSizeMB = Math.round((fileSize / 1024 / 1024) * 100) / 100;
|
|
53
|
+
const limitMB = Math.round((limit / 1024 / 1024) * 100) / 100;
|
|
54
|
+
reject(new Error(`File size ${fileSizeMB}MB exceeds limit of ${limitMB}MB for ${mediaType} files`));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
resolve();
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.catch(reject);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async validateUrlSize(url, mediaType) {
|
|
64
|
+
try {
|
|
65
|
+
const response = await axios_1.default.head(url);
|
|
66
|
+
const contentLength = parseInt(response.headers["content-length"] || "0");
|
|
67
|
+
if (contentLength > 0) {
|
|
68
|
+
const limitStr = this.mediaLimits[mediaType] || "10mb";
|
|
69
|
+
const limit = this.parseSize(limitStr);
|
|
70
|
+
if (contentLength > limit) {
|
|
71
|
+
const fileSizeMB = Math.round((contentLength / 1024 / 1024) * 100) / 100;
|
|
72
|
+
const limitMB = Math.round((limit / 1024 / 1024) * 100) / 100;
|
|
73
|
+
throw new Error(`Remote file size ${fileSizeMB}MB exceeds limit of ${limitMB}MB for ${mediaType} files`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return contentLength;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.message.includes("exceeds limit")) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
18
85
|
isRemoteUrl(url) {
|
|
19
86
|
try {
|
|
20
87
|
const u = new URL(url);
|
|
@@ -25,28 +92,36 @@ class DetectorMedia {
|
|
|
25
92
|
}
|
|
26
93
|
}
|
|
27
94
|
async convertPathToBase64(filePath) {
|
|
28
|
-
const fileBuffer = await promises_1.default.readFile(filePath);
|
|
29
95
|
const mediaType = this.mapMimeToCategory(mime_types_1.default.lookup(filePath).toString());
|
|
30
96
|
if (!mediaType)
|
|
31
97
|
throw new Error("No se pudo determinar el tipo MIME del archivo");
|
|
98
|
+
// Validate file size before processing
|
|
99
|
+
await this.validateFileSize(filePath, mediaType);
|
|
100
|
+
const fileBuffer = await promises_1.default.readFile(filePath);
|
|
32
101
|
const base64Data = fileBuffer.toString("base64");
|
|
33
102
|
const media = base64Data;
|
|
34
103
|
const fileName = path_1.default.basename(filePath);
|
|
104
|
+
const stats = await promises_1.default.stat(filePath);
|
|
35
105
|
return {
|
|
36
106
|
media,
|
|
37
107
|
mediaType,
|
|
38
108
|
fileName,
|
|
39
109
|
mimeType: mime_types_1.default.lookup(filePath).toString(),
|
|
110
|
+
size: stats.size,
|
|
40
111
|
};
|
|
41
112
|
}
|
|
42
113
|
async getMimeTypeFromUrl(url) {
|
|
43
114
|
try {
|
|
44
115
|
const res = await axios_1.default.head(url);
|
|
45
|
-
const
|
|
46
|
-
|
|
116
|
+
const contentType = res.headers["content-type"];
|
|
117
|
+
const mediaType = this.mapMimeToCategory(contentType);
|
|
118
|
+
return {
|
|
119
|
+
mediaType: mediaType || null,
|
|
120
|
+
mimeType: contentType || null,
|
|
121
|
+
};
|
|
47
122
|
}
|
|
48
123
|
catch {
|
|
49
|
-
return null;
|
|
124
|
+
return { mediaType: null, mimeType: null };
|
|
50
125
|
}
|
|
51
126
|
}
|
|
52
127
|
async extractFileNameFromUrl(url) {
|
|
@@ -60,32 +135,41 @@ class DetectorMedia {
|
|
|
60
135
|
* - media (base64 si es path, url si es remota)
|
|
61
136
|
* - mimeType
|
|
62
137
|
* - fileName
|
|
138
|
+
* - size
|
|
63
139
|
*/
|
|
64
140
|
async processMedia(input) {
|
|
65
141
|
if (this.isRemoteUrl(input)) {
|
|
66
|
-
const mediaType = await this.getMimeTypeFromUrl(input);
|
|
67
|
-
if (!mediaType)
|
|
142
|
+
const { mediaType, mimeType } = await this.getMimeTypeFromUrl(input);
|
|
143
|
+
if (!mediaType || !mimeType)
|
|
68
144
|
throw new Error("No se pudo determinar el tipo MIME de la URL remota");
|
|
145
|
+
// Validate remote file size
|
|
146
|
+
const size = await this.validateUrlSize(input, mediaType);
|
|
69
147
|
const fileName = await this.extractFileNameFromUrl(input);
|
|
70
148
|
return {
|
|
71
149
|
media: input,
|
|
72
|
-
mimeType:
|
|
150
|
+
mimeType: mimeType,
|
|
73
151
|
fileName,
|
|
74
152
|
mediaType,
|
|
153
|
+
size,
|
|
75
154
|
};
|
|
76
155
|
}
|
|
77
156
|
return this.convertPathToBase64(input);
|
|
78
157
|
}
|
|
79
158
|
mapMimeToCategory(mimeType) {
|
|
80
|
-
if (mimeType
|
|
159
|
+
if (!mimeType) {
|
|
160
|
+
return "unknown";
|
|
161
|
+
}
|
|
162
|
+
const normalizedMimeType = mimeType.toLowerCase().split(";")[0].trim();
|
|
163
|
+
if (normalizedMimeType.startsWith("image/"))
|
|
81
164
|
return "image";
|
|
82
|
-
if (
|
|
165
|
+
if (normalizedMimeType.startsWith("video/"))
|
|
83
166
|
return "video";
|
|
84
|
-
if (
|
|
167
|
+
if (normalizedMimeType.startsWith("audio/"))
|
|
85
168
|
return "audio";
|
|
86
|
-
if (
|
|
169
|
+
if (normalizedMimeType.startsWith("application/") ||
|
|
170
|
+
normalizedMimeType.startsWith("text/"))
|
|
87
171
|
return "document";
|
|
88
|
-
return "
|
|
172
|
+
return "document"; // Default to document instead of unknown for better compatibility
|
|
89
173
|
}
|
|
90
174
|
}
|
|
91
175
|
exports.DetectorMedia = DetectorMedia;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamastudio/sendwave-provider",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7-dev1",
|
|
4
4
|
"description": "Librería para interactuar con Sendwave usando configuración dinámica.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"author": "Ameth Galarcio <amethgm@gmail.com>",
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@builderbot/bot": "^1.
|
|
25
|
+
"@builderbot/bot": "^1.2.9",
|
|
26
26
|
"@gamastudio/colorslog": "^0.1.6",
|
|
27
27
|
"@types/mime-types": "^2.1.4",
|
|
28
28
|
"axios": "^1.11.0",
|