@gamastudio/sendwave-provider 0.0.6 → 0.0.7-dev
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 +168 -31
- package/build/provider/sender.d.ts +1 -0
- package/build/provider/sender.js +79 -8
- package/build/utils/detectorMedia.d.ts +15 -1
- package/build/utils/detectorMedia.js +78 -3
- package/package.json +2 -2
- package/queue.class.log +0 -0
|
@@ -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-dev",
|
|
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,
|
|
@@ -144,12 +178,24 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
144
178
|
.then(() => {
|
|
145
179
|
this.beforeHttpServerInit();
|
|
146
180
|
this.start(methods, (routes) => {
|
|
181
|
+
// Log the actual server binding info
|
|
182
|
+
console.log(`🌐 Server binding: ${this.globalVendorArgs.host || 'localhost'}:${this.globalVendorArgs.port}`);
|
|
183
|
+
if (this.globalVendorArgs.host === "0.0.0.0") {
|
|
184
|
+
console.log(`🔗 External access: Server accessible from all network interfaces`);
|
|
185
|
+
console.log(`📡 Webhook URL for external services: http://<your-ip>:${this.globalVendorArgs.port}/webhook`);
|
|
186
|
+
}
|
|
147
187
|
this.emit("notice", {
|
|
148
188
|
title: "🛜 HTTP Server ON ",
|
|
149
189
|
instructions: routes,
|
|
150
190
|
});
|
|
151
191
|
this.afterHttpServerInit();
|
|
152
192
|
});
|
|
193
|
+
})
|
|
194
|
+
.catch((error) => {
|
|
195
|
+
console.error("[Provider Error]:", error);
|
|
196
|
+
if (error.type === 'entity.too.large') {
|
|
197
|
+
console.error("[Provider] PayloadTooLarge error caught at provider level");
|
|
198
|
+
}
|
|
153
199
|
});
|
|
154
200
|
return;
|
|
155
201
|
};
|
|
@@ -174,6 +220,27 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
174
220
|
// Puedes emitir eventos a websockets aquí, notificar a frontends, etc.
|
|
175
221
|
});
|
|
176
222
|
this.sender = new sender_1.SenderMessage(this.globalVendorArgs);
|
|
223
|
+
// Add global error handlers for payload errors
|
|
224
|
+
this.setupGlobalErrorHandlers();
|
|
225
|
+
}
|
|
226
|
+
setupGlobalErrorHandlers() {
|
|
227
|
+
// Handle uncaught PayloadTooLarge errors
|
|
228
|
+
process.on('uncaughtException', (error) => {
|
|
229
|
+
if (error.name === 'PayloadTooLargeError' || error.type === 'entity.too.large') {
|
|
230
|
+
console.error('[Global] PayloadTooLarge error intercepted:', error.message);
|
|
231
|
+
// Don't exit process for this type of error
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// For other uncaught exceptions, log and continue
|
|
235
|
+
console.error('[Global] Uncaught exception:', error);
|
|
236
|
+
});
|
|
237
|
+
process.on('unhandledRejection', (reason) => {
|
|
238
|
+
if (reason && (reason.name === 'PayloadTooLargeError' || reason.type === 'entity.too.large')) {
|
|
239
|
+
console.error('[Global] PayloadTooLarge rejection intercepted:', reason.message);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
console.error('[Global] Unhandled rejection:', reason);
|
|
243
|
+
});
|
|
177
244
|
}
|
|
178
245
|
initVendor() {
|
|
179
246
|
const vendor = new core_1.SendWaveCore(this.globalVendorArgs.port, this.queue, this.globalVendorArgs);
|
|
@@ -223,6 +290,8 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
223
290
|
this.globalVendorArgs.url.trim()
|
|
224
291
|
? this.globalVendorArgs.url.trim()
|
|
225
292
|
: "https://sendwave-api.gamastudio.co";
|
|
293
|
+
const payloadLimits = this.globalVendorArgs.payloadLimits || {};
|
|
294
|
+
const maxContentLength = this.parseSize(payloadLimits.json || '50mb');
|
|
226
295
|
this.sendwaveApi = axios_1.default.create({
|
|
227
296
|
baseURL: url,
|
|
228
297
|
httpsAgent: new https_1.Agent({ rejectUnauthorized: false }),
|
|
@@ -230,8 +299,46 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
230
299
|
"Content-Type": "application/json",
|
|
231
300
|
apikey: this.globalVendorArgs.apiKey,
|
|
232
301
|
},
|
|
302
|
+
// Large file support configuration
|
|
303
|
+
maxContentLength: maxContentLength,
|
|
304
|
+
maxBodyLength: maxContentLength,
|
|
305
|
+
timeout: 300000, // 5 minutes timeout for large files
|
|
306
|
+
});
|
|
307
|
+
// Add request interceptor for large file logging
|
|
308
|
+
this.sendwaveApi.interceptors.request.use((config) => {
|
|
309
|
+
const contentLength = config.headers['content-length'] ||
|
|
310
|
+
(config.data ? JSON.stringify(config.data).length : 0);
|
|
311
|
+
if (contentLength > 1024 * 1024) { // Log requests larger than 1MB
|
|
312
|
+
console.log(`[LargeRequest] Sending ${Math.round(contentLength / 1024 / 1024 * 100) / 100}MB to ${config.url}`);
|
|
313
|
+
}
|
|
314
|
+
return config;
|
|
315
|
+
}, (error) => Promise.reject(error));
|
|
316
|
+
// Add response interceptor for error handling
|
|
317
|
+
this.sendwaveApi.interceptors.response.use((response) => response, (error) => {
|
|
318
|
+
var _a;
|
|
319
|
+
if (error.code === 'ECONNABORTED') {
|
|
320
|
+
console.error('[RequestTimeout] Large file upload timed out');
|
|
321
|
+
}
|
|
322
|
+
else if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 413) {
|
|
323
|
+
console.error('[PayloadTooLarge] Server rejected large payload');
|
|
324
|
+
}
|
|
325
|
+
return Promise.reject(error);
|
|
233
326
|
});
|
|
234
327
|
}
|
|
328
|
+
parseSize(size) {
|
|
329
|
+
const units = {
|
|
330
|
+
'b': 1,
|
|
331
|
+
'kb': 1024,
|
|
332
|
+
'mb': 1024 * 1024,
|
|
333
|
+
'gb': 1024 * 1024 * 1024
|
|
334
|
+
};
|
|
335
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
336
|
+
if (!match)
|
|
337
|
+
return 50 * 1024 * 1024; // Default 50MB
|
|
338
|
+
const value = parseFloat(match[1]);
|
|
339
|
+
const unit = match[2] || 'b';
|
|
340
|
+
return Math.floor(value * units[unit]);
|
|
341
|
+
}
|
|
235
342
|
async listenOnEvents(vendor) {
|
|
236
343
|
const listEvents = this.busEvents();
|
|
237
344
|
for (const { event, func } of listEvents) {
|
|
@@ -246,38 +353,65 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
246
353
|
});
|
|
247
354
|
}
|
|
248
355
|
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
|
-
|
|
356
|
+
var _a, _b;
|
|
357
|
+
try {
|
|
358
|
+
options = { ...options["options"] };
|
|
359
|
+
let mss = { from, text, options };
|
|
360
|
+
if (options.media) {
|
|
361
|
+
// Initialize detector with payload limits from global args
|
|
362
|
+
if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
|
|
363
|
+
detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
|
|
364
|
+
}
|
|
365
|
+
const { media, mediaType, fileName, size } = await detectorMedia_1.detectorMedia.processMedia(options.media);
|
|
366
|
+
// Log large media processing
|
|
367
|
+
if (size && size > 1024 * 1024) {
|
|
368
|
+
const sizeMB = Math.round(size / 1024 / 1024 * 100) / 100;
|
|
369
|
+
console.log(`[SendMessage] Processing ${mediaType} file: ${sizeMB}MB`);
|
|
370
|
+
}
|
|
371
|
+
switch (mediaType) {
|
|
372
|
+
case "image":
|
|
373
|
+
return await this.sender.sendImage({
|
|
374
|
+
...mss,
|
|
375
|
+
url: media,
|
|
376
|
+
text: text,
|
|
377
|
+
});
|
|
378
|
+
case "video":
|
|
379
|
+
return await this.sender.sendVideo({
|
|
380
|
+
...mss,
|
|
381
|
+
url: media,
|
|
382
|
+
text: text,
|
|
383
|
+
});
|
|
384
|
+
case "audio":
|
|
385
|
+
return await this.sender.sendVoice({ ...mss, url: media });
|
|
386
|
+
case "document":
|
|
387
|
+
return await this.sender.sendFile({
|
|
388
|
+
...mss,
|
|
389
|
+
url: media,
|
|
390
|
+
fileName,
|
|
391
|
+
text: text,
|
|
392
|
+
});
|
|
393
|
+
default:
|
|
394
|
+
return await this.sender.sendText(mss);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
return await this.sender.sendText(mss);
|
|
277
399
|
}
|
|
278
400
|
}
|
|
279
|
-
|
|
280
|
-
|
|
401
|
+
catch (error) {
|
|
402
|
+
// Enhanced error handling for large files
|
|
403
|
+
if (error.message && error.message.includes('exceeds limit')) {
|
|
404
|
+
console.error(`[SendMessage Error] File size validation failed: ${error.message}`);
|
|
405
|
+
throw new Error(`File too large: ${error.message}`);
|
|
406
|
+
}
|
|
407
|
+
else if (error.message && error.message.includes('too large')) {
|
|
408
|
+
console.error(`[SendMessage Error] Large file error: ${error.message}`);
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
console.error(`[SendMessage Error] ${error.message || 'Unknown error'}`);
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
281
415
|
}
|
|
282
416
|
}
|
|
283
417
|
// Métodos de conveniencia para mantener la API limpia
|
|
@@ -314,6 +448,9 @@ class SendWaveProvider extends bot_1.ProviderClass {
|
|
|
314
448
|
sendLocation(...args) {
|
|
315
449
|
return this.sender.sendLocation(...args);
|
|
316
450
|
}
|
|
451
|
+
sendMedia(...args) {
|
|
452
|
+
return this.sender.sendMedia(...args);
|
|
453
|
+
}
|
|
317
454
|
// Queue Flow Management Methods
|
|
318
455
|
/**
|
|
319
456
|
* Reset user timeout for queue flow system
|
|
@@ -3,6 +3,7 @@ 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>;
|
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,19 @@ 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
|
+
const { media, mimeType, mediaType, size } = await detectorMedia_1.detectorMedia.processMedia(data.url);
|
|
125
|
+
// Log large media processing
|
|
126
|
+
if (size && size > 1024 * 1024) {
|
|
127
|
+
const sizeMB = Math.round(size / 1024 / 1024 * 100) / 100;
|
|
128
|
+
console.log(`[SendMedia] Processing ${mediaType} file: ${sizeMB}MB`);
|
|
129
|
+
}
|
|
130
|
+
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
131
|
number: data.from.split("@")[0],
|
|
104
132
|
caption: data.text,
|
|
105
133
|
mediatype: mediaType,
|
|
@@ -110,7 +138,24 @@ class SenderMessage {
|
|
|
110
138
|
}));
|
|
111
139
|
}
|
|
112
140
|
catch (e) {
|
|
113
|
-
|
|
141
|
+
// Enhanced error handling for large files
|
|
142
|
+
if (e.message && e.message.includes('exceeds limit')) {
|
|
143
|
+
console.error(`[SendMedia Error] File size validation failed: ${e.message}`);
|
|
144
|
+
throw new Error(`File too large: ${e.message}`);
|
|
145
|
+
}
|
|
146
|
+
else if (((_e = e.response) === null || _e === void 0 ? void 0 : _e.status) === 413) {
|
|
147
|
+
console.error('[SendMedia Error] Server rejected large payload');
|
|
148
|
+
throw new Error('File too large for server');
|
|
149
|
+
}
|
|
150
|
+
else if (e.code === 'ECONNABORTED') {
|
|
151
|
+
console.error('[SendMedia Error] Request timeout - file may be too large');
|
|
152
|
+
throw new Error('Upload timeout - file may be too large');
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
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';
|
|
156
|
+
console.error(`[SendMedia Error] ${errorMsg}`);
|
|
157
|
+
throw new Error(errorMsg);
|
|
158
|
+
}
|
|
114
159
|
}
|
|
115
160
|
}
|
|
116
161
|
async sendFile(data) {
|
|
@@ -159,22 +204,48 @@ class SenderMessage {
|
|
|
159
204
|
}
|
|
160
205
|
}
|
|
161
206
|
async sendVoice(data) {
|
|
162
|
-
var _a, _b;
|
|
207
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
163
208
|
try {
|
|
164
|
-
|
|
209
|
+
// Initialize detector with payload limits from global args
|
|
210
|
+
if ((_b = (_a = this.globalVendorArgs) === null || _a === void 0 ? void 0 : _a.payloadLimits) === null || _b === void 0 ? void 0 : _b.media) {
|
|
211
|
+
detectorMedia_1.detectorMedia.updateLimits(this.globalVendorArgs.payloadLimits.media);
|
|
212
|
+
}
|
|
213
|
+
const { media, size } = await detectorMedia_1.detectorMedia.processMedia(data.url);
|
|
214
|
+
// Log large audio processing
|
|
215
|
+
if (size && size > 1024 * 1024) {
|
|
216
|
+
const sizeMB = Math.round(size / 1024 / 1024 * 100) / 100;
|
|
217
|
+
console.log(`[SendVoice] Processing audio file: ${sizeMB}MB`);
|
|
218
|
+
}
|
|
165
219
|
await this.sendPresence({
|
|
166
220
|
from: data.from,
|
|
167
221
|
presence: "recording",
|
|
168
222
|
delay: data.delay || 2000,
|
|
169
223
|
});
|
|
170
|
-
return await ((
|
|
224
|
+
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
225
|
number: data.from,
|
|
172
226
|
audio: media,
|
|
173
227
|
...this.globalVendorArgs,
|
|
174
228
|
}));
|
|
175
229
|
}
|
|
176
230
|
catch (e) {
|
|
177
|
-
|
|
231
|
+
// Enhanced error handling for large audio files
|
|
232
|
+
if (e.message && e.message.includes('exceeds limit')) {
|
|
233
|
+
console.error(`[SendVoice Error] File size validation failed: ${e.message}`);
|
|
234
|
+
throw new Error(`Audio file too large: ${e.message}`);
|
|
235
|
+
}
|
|
236
|
+
else if (((_e = e.response) === null || _e === void 0 ? void 0 : _e.status) === 413) {
|
|
237
|
+
console.error('[SendVoice Error] Server rejected large payload');
|
|
238
|
+
throw new Error('Audio file too large for server');
|
|
239
|
+
}
|
|
240
|
+
else if (e.code === 'ECONNABORTED') {
|
|
241
|
+
console.error('[SendVoice Error] Request timeout - audio file may be too large');
|
|
242
|
+
throw new Error('Upload timeout - audio file may be too large');
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
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';
|
|
246
|
+
console.error(`[SendVoice Error] ${errorMsg}`);
|
|
247
|
+
throw new Error(errorMsg);
|
|
248
|
+
}
|
|
178
249
|
}
|
|
179
250
|
}
|
|
180
251
|
async sendButton(data) {
|
|
@@ -3,10 +3,23 @@ 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
|
+
private validateUrlSize;
|
|
10
23
|
isRemoteUrl(url: string): boolean;
|
|
11
24
|
convertPathToBase64(filePath: string): Promise<DetectorMediaProps>;
|
|
12
25
|
getMimeTypeFromUrl(url: string): Promise<string | null>;
|
|
@@ -16,6 +29,7 @@ export declare class DetectorMedia {
|
|
|
16
29
|
* - media (base64 si es path, url si es remota)
|
|
17
30
|
* - mimeType
|
|
18
31
|
* - fileName
|
|
32
|
+
* - size
|
|
19
33
|
*/
|
|
20
34
|
processMedia(input: string): Promise<DetectorMediaProps>;
|
|
21
35
|
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).then((stats) => {
|
|
47
|
+
const fileSize = stats.size;
|
|
48
|
+
const limitStr = this.mediaLimits[mediaType] || '10mb';
|
|
49
|
+
const limit = this.parseSize(limitStr);
|
|
50
|
+
if (fileSize > limit) {
|
|
51
|
+
const fileSizeMB = Math.round(fileSize / 1024 / 1024 * 100) / 100;
|
|
52
|
+
const limitMB = Math.round(limit / 1024 / 1024 * 100) / 100;
|
|
53
|
+
reject(new Error(`File size ${fileSizeMB}MB exceeds limit of ${limitMB}MB for ${mediaType} files`));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
}).catch(reject);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async validateUrlSize(url, mediaType) {
|
|
62
|
+
try {
|
|
63
|
+
const response = await axios_1.default.head(url);
|
|
64
|
+
const contentLength = parseInt(response.headers['content-length'] || '0');
|
|
65
|
+
if (contentLength > 0) {
|
|
66
|
+
const limitStr = this.mediaLimits[mediaType] || '10mb';
|
|
67
|
+
const limit = this.parseSize(limitStr);
|
|
68
|
+
if (contentLength > limit) {
|
|
69
|
+
const fileSizeMB = Math.round(contentLength / 1024 / 1024 * 100) / 100;
|
|
70
|
+
const limitMB = Math.round(limit / 1024 / 1024 * 100) / 100;
|
|
71
|
+
throw new Error(`Remote file size ${fileSizeMB}MB exceeds limit of ${limitMB}MB for ${mediaType} files`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return contentLength;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.message.includes('exceeds limit')) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
// If we can't get size, assume it's valid but warn
|
|
81
|
+
console.warn(`[DetectorMedia] Could not validate size for URL: ${url}`);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
18
85
|
isRemoteUrl(url) {
|
|
19
86
|
try {
|
|
20
87
|
const u = new URL(url);
|
|
@@ -25,18 +92,22 @@ 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) {
|
|
@@ -60,18 +131,22 @@ class DetectorMedia {
|
|
|
60
131
|
* - media (base64 si es path, url si es remota)
|
|
61
132
|
* - mimeType
|
|
62
133
|
* - fileName
|
|
134
|
+
* - size
|
|
63
135
|
*/
|
|
64
136
|
async processMedia(input) {
|
|
65
137
|
if (this.isRemoteUrl(input)) {
|
|
66
138
|
const mediaType = await this.getMimeTypeFromUrl(input);
|
|
67
139
|
if (!mediaType)
|
|
68
140
|
throw new Error("No se pudo determinar el tipo MIME de la URL remota");
|
|
141
|
+
// Validate remote file size
|
|
142
|
+
const size = await this.validateUrlSize(input, mediaType);
|
|
69
143
|
const fileName = await this.extractFileNameFromUrl(input);
|
|
70
144
|
return {
|
|
71
145
|
media: input,
|
|
72
146
|
mimeType: mime_types_1.default.lookup(input).toString(),
|
|
73
147
|
fileName,
|
|
74
148
|
mediaType,
|
|
149
|
+
size,
|
|
75
150
|
};
|
|
76
151
|
}
|
|
77
152
|
return this.convertPathToBase64(input);
|
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-dev",
|
|
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/queue.class.log
DELETED
|
File without changes
|