@fer2809fl/baileys 7.0.4 → 7.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +347 -50
- package/lib/Defaults/index.js +1 -1
- package/lib/Modded/message_builder.js +2356 -0
- package/lib/Socket/chats.d.ts +14 -0
- package/lib/Socket/chats.js +48 -0
- package/lib/Socket/index.d.ts +17 -0
- package/lib/Socket/messages-send.d.ts +18 -0
- package/lib/Socket/messages-send.js +50 -2
- package/lib/Utils/anti-ban.d.ts +41 -0
- package/lib/Utils/anti-ban.js +182 -0
- package/lib/Utils/banner.d.ts +8 -0
- package/lib/Utils/banner.js +76 -0
- package/lib/Utils/bot-utils.d.ts +57 -0
- package/lib/Utils/bot-utils.js +241 -0
- package/lib/Utils/enhanced-cache.d.ts +40 -0
- package/lib/Utils/enhanced-cache.js +242 -0
- package/lib/Utils/enhanced-logger.d.ts +41 -0
- package/lib/Utils/enhanced-logger.js +185 -0
- package/lib/Utils/index.d.ts +14 -0
- package/lib/Utils/index.js +13 -0
- package/lib/Utils/lid-utils.d.ts +139 -0
- package/lib/Utils/lid-utils.js +503 -0
- package/lib/Utils/message-queue.d.ts +47 -0
- package/lib/Utils/message-queue.js +226 -0
- package/lib/Utils/rich-message-utils.d.ts +21 -0
- package/lib/Utils/rich-message-utils.js +229 -0
- package/lib/Utils/rich-messages.d.ts +52 -0
- package/lib/Utils/rich-messages.js +185 -0
- package/lib/Utils/scheduled-messages.d.ts +122 -0
- package/lib/Utils/scheduled-messages.js +289 -0
- package/lib/Utils/smart-reconnect.d.ts +48 -0
- package/lib/Utils/smart-reconnect.js +207 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
- package/lib/Utils/use-sqlite-auth-state.js +95 -0
- package/lib/VoIP/audio-feeder.d.ts +15 -0
- package/lib/VoIP/audio-feeder.js +132 -0
- package/lib/VoIP/index.js +277 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +559 -0
- package/lib/VoIP/signaling.js +594 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +103 -0
- package/lib/VoIP/wasm-engine.js +1214 -0
- package/lib/VoIP/worker-bootstrap.js +1042 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/index.d.ts +41 -0
- package/lib/index.js +3 -0
- package/package.json +10 -2
|
@@ -170,6 +170,55 @@ export const BASH_KEYWORDS = new Set([
|
|
|
170
170
|
"declare",
|
|
171
171
|
"typeset",
|
|
172
172
|
]);
|
|
173
|
+
// -- Añadido en v7.0.5: mas lenguajes soportados para resaltado de código (portado desde el fork beta) --
|
|
174
|
+
export const RUST_KEYWORDS = new Set([
|
|
175
|
+
"as", "async", "await", "break", "const", "continue", "crate", "dyn", "else",
|
|
176
|
+
"enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop",
|
|
177
|
+
"match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self",
|
|
178
|
+
"static", "struct", "super", "trait", "true", "type", "unsafe", "use",
|
|
179
|
+
"where", "while",
|
|
180
|
+
]);
|
|
181
|
+
export const C_KEYWORDS = new Set([
|
|
182
|
+
"auto", "break", "case", "char", "const", "continue", "default", "do",
|
|
183
|
+
"double", "else", "enum", "extern", "float", "for", "goto", "if", "inline",
|
|
184
|
+
"int", "long", "register", "restrict", "return", "short", "signed",
|
|
185
|
+
"sizeof", "static", "struct", "switch", "typedef", "union", "unsigned",
|
|
186
|
+
"void", "volatile", "while",
|
|
187
|
+
]);
|
|
188
|
+
export const CPP_KEYWORDS = new Set([
|
|
189
|
+
...C_KEYWORDS, "alignas", "alignof", "and", "asm", "bool", "catch", "class",
|
|
190
|
+
"concept", "consteval", "constexpr", "constinit", "const_cast", "co_await",
|
|
191
|
+
"co_return", "co_yield", "decltype", "delete", "dynamic_cast", "explicit",
|
|
192
|
+
"export", "friend", "mutable", "namespace", "new", "noexcept", "not",
|
|
193
|
+
"nullptr", "operator", "private", "protected", "public", "reinterpret_cast",
|
|
194
|
+
"requires", "static_assert", "static_cast", "template", "this",
|
|
195
|
+
"thread_local", "throw", "true", "try", "typeid", "typename", "using",
|
|
196
|
+
"virtual", "wchar_t",
|
|
197
|
+
]);
|
|
198
|
+
export const CSHARP_KEYWORDS = new Set([
|
|
199
|
+
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char",
|
|
200
|
+
"checked", "class", "const", "continue", "decimal", "default", "delegate",
|
|
201
|
+
"do", "double", "else", "enum", "event", "explicit", "extern", "false",
|
|
202
|
+
"finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit",
|
|
203
|
+
"in", "int", "interface", "internal", "is", "lock", "long", "namespace",
|
|
204
|
+
"new", "null", "object", "operator", "out", "override", "params", "private",
|
|
205
|
+
"protected", "public", "readonly", "ref", "return", "sbyte", "sealed",
|
|
206
|
+
"short", "sizeof", "stackalloc", "static", "string", "struct", "switch",
|
|
207
|
+
"this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked",
|
|
208
|
+
"unsafe", "ushort", "using", "var", "virtual", "void", "volatile", "while",
|
|
209
|
+
]);
|
|
210
|
+
export const HTML_KEYWORDS = new Set([
|
|
211
|
+
"DOCTYPE", "html", "head", "body", "div", "span", "p", "a", "h1", "h2",
|
|
212
|
+
"h3", "h4", "h5", "h6", "ul", "ol", "li", "table", "tr", "td", "th", "form",
|
|
213
|
+
"input", "button", "select", "option", "textarea", "img", "video", "audio",
|
|
214
|
+
"source", "canvas", "svg", "script", "style", "link", "meta", "title",
|
|
215
|
+
]);
|
|
216
|
+
export const CSS_KEYWORDS = new Set([
|
|
217
|
+
"color", "background", "margin", "padding", "border", "display",
|
|
218
|
+
"position", "width", "height", "font", "text", "align", "flex", "grid",
|
|
219
|
+
"animation", "transition", "transform", "opacity", "overflow", "cursor",
|
|
220
|
+
"z-index",
|
|
221
|
+
]);
|
|
173
222
|
export const LANGUAGE_KEYWORDS = {
|
|
174
223
|
javascript: JS_KEYWORDS,
|
|
175
224
|
typescript: JS_KEYWORDS,
|
|
@@ -183,6 +232,15 @@ export const LANGUAGE_KEYWORDS = {
|
|
|
183
232
|
bash: BASH_KEYWORDS,
|
|
184
233
|
sh: BASH_KEYWORDS,
|
|
185
234
|
shell: BASH_KEYWORDS,
|
|
235
|
+
zsh: BASH_KEYWORDS,
|
|
236
|
+
rust: RUST_KEYWORDS,
|
|
237
|
+
c: C_KEYWORDS,
|
|
238
|
+
cpp: CPP_KEYWORDS,
|
|
239
|
+
"c++": CPP_KEYWORDS,
|
|
240
|
+
csharp: CSHARP_KEYWORDS,
|
|
241
|
+
"c#": CSHARP_KEYWORDS,
|
|
242
|
+
html: HTML_KEYWORDS,
|
|
243
|
+
css: CSS_KEYWORDS,
|
|
186
244
|
};
|
|
187
245
|
export var CodeHighlightType;
|
|
188
246
|
(function (CodeHighlightType) {
|
|
@@ -1141,4 +1199,131 @@ export const generateLinkContentV2 = (text, links, quoted, options = {}) => {
|
|
|
1141
1199
|
messageId: generateMessageIDV2(),
|
|
1142
1200
|
};
|
|
1143
1201
|
};
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Construye un mensaje que el cliente de WhatsApp renderiza como un WebView
|
|
1205
|
+
* embebido dentro del bocadillo del mensaje, usando el campo interno
|
|
1206
|
+
* `GenAIaeacdsnwHtmlPrimitive` del protocolo AIRichResponseMessage.
|
|
1207
|
+
*
|
|
1208
|
+
* El HTML vive dentro de `unifiedResponse.data` (bytes opacos para el protocolo
|
|
1209
|
+
* protobuf, pero parseados como JSON por el cliente de WhatsApp). El cliente
|
|
1210
|
+
* levanta un WebView sandboxed y le permite hacer fetch únicamente a los
|
|
1211
|
+
* dominios listados en `trustedSources`.
|
|
1212
|
+
*
|
|
1213
|
+
* Limitaciones estructurales conocidas (ver README sección "HTML embebido"):
|
|
1214
|
+
* - El WebView es efímero: NO confíes en `localStorage` para persistencia
|
|
1215
|
+
* entre sesiones. Mové la persistencia a un backend HTTP listado en
|
|
1216
|
+
* `trustedSources`.
|
|
1217
|
+
* - No existe un puente HTML → bot: el HTML no puede disparar eventos
|
|
1218
|
+
* directos al bot. Usá `fetch()` a tu backend y que el backend llame
|
|
1219
|
+
* `sock.sendMessage(...)` si necesitas reaccionar.
|
|
1220
|
+
* - El HTML no recibe identidad del usuario automáticamente: inyectá
|
|
1221
|
+
* `window.__INITIAL__ = { jid, token, ... }` al renderizar desde el bot.
|
|
1222
|
+
*
|
|
1223
|
+
* @param {string} html - Contenido HTML (con `<style>`, `<body>`, `<script>`).
|
|
1224
|
+
* @param {object} quoted - Mensaje al que responder (opcional).
|
|
1225
|
+
* @param {object} options - Opciones adicionales.
|
|
1226
|
+
* @param {string[]} options.trustedSources - Dominios que el WebView puede
|
|
1227
|
+
* contactar (fetch, imágenes, fuentes, etc.). Sin esquema (ej: "api.tuyo.com").
|
|
1228
|
+
* @param {string} options.headerText - Texto opcional arriba del HTML.
|
|
1229
|
+
* @param {string} options.footer - Texto opcional debajo del HTML.
|
|
1230
|
+
* @param {string} options.fallbackText - Texto mostrado en el submessage de
|
|
1231
|
+
* respaldo si el cliente no soporta HTML (default: "Contenido interactivo").
|
|
1232
|
+
* @returns {{ message: object, messageId: string }}
|
|
1233
|
+
*/
|
|
1234
|
+
export const generateHtmlContent = (html, quoted, options = {}) => {
|
|
1235
|
+
const {
|
|
1236
|
+
trustedSources = [],
|
|
1237
|
+
headerText,
|
|
1238
|
+
footer,
|
|
1239
|
+
fallbackText = "Contenido interactivo",
|
|
1240
|
+
} = options;
|
|
1241
|
+
|
|
1242
|
+
const sections = [];
|
|
1243
|
+
|
|
1244
|
+
if (headerText) {
|
|
1245
|
+
sections.push({
|
|
1246
|
+
view_model: {
|
|
1247
|
+
primitive: {
|
|
1248
|
+
text: headerText,
|
|
1249
|
+
__typename: "GenAIMarkdownTextUXPrimitive",
|
|
1250
|
+
},
|
|
1251
|
+
__typename: "GenAISingleLayoutViewModel",
|
|
1252
|
+
},
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
sections.push({
|
|
1257
|
+
view_model: {
|
|
1258
|
+
primitive: {
|
|
1259
|
+
__typename: "GenAIaeacdsnwHtmlPrimitive",
|
|
1260
|
+
payload: html,
|
|
1261
|
+
trusted_sources: trustedSources,
|
|
1262
|
+
},
|
|
1263
|
+
__typename: "GenAISingleLayoutViewModel",
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
if (footer) {
|
|
1268
|
+
sections.push({
|
|
1269
|
+
view_model: {
|
|
1270
|
+
primitive: {
|
|
1271
|
+
text: footer,
|
|
1272
|
+
__typename: "GenAIMarkdownTextUXPrimitive",
|
|
1273
|
+
},
|
|
1274
|
+
__typename: "GenAISingleLayoutViewModel",
|
|
1275
|
+
},
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const responseId = randomUUID();
|
|
1280
|
+
const unifiedData = {
|
|
1281
|
+
response_id: responseId,
|
|
1282
|
+
sections,
|
|
1283
|
+
version: "1",
|
|
1284
|
+
is_final: true,
|
|
1285
|
+
};
|
|
1286
|
+
const base64Data = Buffer.from(JSON.stringify(unifiedData)).toString(
|
|
1287
|
+
"base64",
|
|
1288
|
+
);
|
|
1289
|
+
|
|
1290
|
+
const submessages = [
|
|
1291
|
+
{
|
|
1292
|
+
messageType: 2, // AI_RICH_RESPONSE_TEXT
|
|
1293
|
+
messageText: fallbackText,
|
|
1294
|
+
},
|
|
1295
|
+
];
|
|
1296
|
+
|
|
1297
|
+
const ctxInfo = {
|
|
1298
|
+
isForwarded: true,
|
|
1299
|
+
forwardOrigin: 4, // META_AI
|
|
1300
|
+
};
|
|
1301
|
+
if (quoted?.key) {
|
|
1302
|
+
ctxInfo.participant =
|
|
1303
|
+
quoted.key.participant || quoted.sender || quoted.key.remoteJid;
|
|
1304
|
+
ctxInfo.quotedMessage = quoted.message;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const content = {
|
|
1308
|
+
messageContextInfo: {
|
|
1309
|
+
threadId: [],
|
|
1310
|
+
messageSecret: randomBytes(32),
|
|
1311
|
+
},
|
|
1312
|
+
botForwardedMessage: {
|
|
1313
|
+
message: {
|
|
1314
|
+
richResponseMessage: {
|
|
1315
|
+
messageType: 1, // AI_RICH_RESPONSE_TYPE_STANDARD
|
|
1316
|
+
submessages,
|
|
1317
|
+
unifiedResponse: { data: base64Data },
|
|
1318
|
+
contextInfo: ctxInfo,
|
|
1319
|
+
},
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
return {
|
|
1325
|
+
message: content,
|
|
1326
|
+
messageId: generateMessageIDV2(),
|
|
1327
|
+
};
|
|
1328
|
+
};
|
|
1144
1329
|
//# sourceMappingURL=rich-messages.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { WASocket } from '../Socket/index.js';
|
|
2
|
+
import type { AnyMessageContent } from '../Types/Message.js';
|
|
3
|
+
|
|
4
|
+
export interface ScheduledMessage {
|
|
5
|
+
id: string;
|
|
6
|
+
jid: string;
|
|
7
|
+
content: AnyMessageContent;
|
|
8
|
+
type: 'once' | 'recurring';
|
|
9
|
+
timestamp?: number;
|
|
10
|
+
cron?: string;
|
|
11
|
+
sent?: boolean;
|
|
12
|
+
sentAt?: number;
|
|
13
|
+
cancelled?: boolean;
|
|
14
|
+
cancelledAt?: number;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
lastSent?: number;
|
|
17
|
+
executionCount?: number;
|
|
18
|
+
lastError?: string;
|
|
19
|
+
errorCount?: number;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SchedulerStats {
|
|
24
|
+
total: number;
|
|
25
|
+
pending: number;
|
|
26
|
+
sent: number;
|
|
27
|
+
recurring: number;
|
|
28
|
+
cancelled: number;
|
|
29
|
+
errors: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ScheduledMessageFilter {
|
|
33
|
+
jid?: string;
|
|
34
|
+
type?: 'once' | 'recurring';
|
|
35
|
+
cancelled?: boolean;
|
|
36
|
+
pending?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Iniciar el scheduler de mensajes programados
|
|
41
|
+
* @param sock - Socket de conexión de WhatsApp
|
|
42
|
+
* @param intervalMs - Intervalo de revisión en ms (default: 30000)
|
|
43
|
+
*/
|
|
44
|
+
export declare const startScheduler: (sock: WASocket, intervalMs?: number) => void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Detener el scheduler
|
|
48
|
+
*/
|
|
49
|
+
export declare const stopScheduler: () => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Programar un mensaje para enviar en una fecha/hora específica
|
|
53
|
+
* @param jid - Destinatario
|
|
54
|
+
* @param content - Contenido del mensaje
|
|
55
|
+
* @param timestamp - Fecha/hora de envío (timestamp ms o Date)
|
|
56
|
+
* @param options - Opciones adicionales (id personalizado, etc.)
|
|
57
|
+
* @returns ID del mensaje programado
|
|
58
|
+
*/
|
|
59
|
+
export declare const scheduleMessage: (
|
|
60
|
+
jid: string,
|
|
61
|
+
content: AnyMessageContent,
|
|
62
|
+
timestamp: number | Date,
|
|
63
|
+
options?: { id?: string; [key: string]: any }
|
|
64
|
+
) => string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Programar un mensaje recurrente usando cron
|
|
68
|
+
* Formato: "MIN HOUR DOM MON DOW" (ej: "0 9 * * 1-5" = lunes a viernes a las 9am)
|
|
69
|
+
* @param jid - Destinatario
|
|
70
|
+
* @param content - Contenido del mensaje
|
|
71
|
+
* @param cron - Expresión cron
|
|
72
|
+
* @param options - Opciones adicionales
|
|
73
|
+
* @returns ID del mensaje programado
|
|
74
|
+
*/
|
|
75
|
+
export declare const recurringMessage: (
|
|
76
|
+
jid: string,
|
|
77
|
+
content: AnyMessageContent,
|
|
78
|
+
cron: string,
|
|
79
|
+
options?: { id?: string; [key: string]: any }
|
|
80
|
+
) => string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cancelar un mensaje programado
|
|
84
|
+
* @param id - ID del mensaje
|
|
85
|
+
* @returns true si se canceló
|
|
86
|
+
*/
|
|
87
|
+
export declare const cancelScheduledMessage: (id: string) => boolean;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Eliminar un mensaje programado permanentemente
|
|
91
|
+
* @param id - ID del mensaje
|
|
92
|
+
* @returns true si se eliminó
|
|
93
|
+
*/
|
|
94
|
+
export declare const deleteScheduledMessage: (id: string) => boolean;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Obtener todos los mensajes programados
|
|
98
|
+
* @param filter - Filtros opcionales
|
|
99
|
+
* @returns Lista de mensajes
|
|
100
|
+
*/
|
|
101
|
+
export declare const listScheduledMessages: (filter?: ScheduledMessageFilter) => ScheduledMessage[];
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Obtener un mensaje programado por ID
|
|
105
|
+
* @param id - ID del mensaje
|
|
106
|
+
* @returns Mensaje o null
|
|
107
|
+
*/
|
|
108
|
+
export declare const getScheduledMessage: (id: string) => ScheduledMessage | null;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Actualizar el contenido de un mensaje programado
|
|
112
|
+
* @param id - ID del mensaje
|
|
113
|
+
* @param updates - Campos a actualizar
|
|
114
|
+
* @returns true si se actualizó
|
|
115
|
+
*/
|
|
116
|
+
export declare const updateScheduledMessage: (id: string, updates: Partial<ScheduledMessage>) => boolean;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Obtener estadísticas del scheduler
|
|
120
|
+
* @returns Estadísticas
|
|
121
|
+
*/
|
|
122
|
+
export declare const getSchedulerStats: () => SchedulerStats;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
const SCHEDULED_PATH = join(process.cwd(), 'database', 'scheduled-messages.json');
|
|
5
|
+
const scheduled = new Map();
|
|
6
|
+
let _sock = null;
|
|
7
|
+
let _initialized = false;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse a cron expression (simple format: "MIN HOUR DOM MON DOW")
|
|
11
|
+
* Returns true if the current time matches the cron
|
|
12
|
+
*/
|
|
13
|
+
function matchCron(cron, date) {
|
|
14
|
+
if (!cron || typeof cron !== 'string') return false;
|
|
15
|
+
const parts = cron.trim().split(/\s+/);
|
|
16
|
+
if (parts.length !== 5) return false;
|
|
17
|
+
|
|
18
|
+
const [min, hour, dom, mon, dow] = parts;
|
|
19
|
+
const d = date || new Date();
|
|
20
|
+
|
|
21
|
+
const matchField = (field, value) => {
|
|
22
|
+
if (field === '*') return true;
|
|
23
|
+
if (field.includes(',')) return field.split(',').some(v => matchField(v.trim(), value));
|
|
24
|
+
if (field.includes('-')) {
|
|
25
|
+
const [start, end] = field.split('-').map(Number);
|
|
26
|
+
return value >= start && value <= end;
|
|
27
|
+
}
|
|
28
|
+
if (field.includes('/')) {
|
|
29
|
+
const [, step] = field.split('/');
|
|
30
|
+
return value % parseInt(step) === 0;
|
|
31
|
+
}
|
|
32
|
+
return parseInt(field) === value;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
matchField(min, d.getMinutes()) &&
|
|
37
|
+
matchField(hour, d.getHours()) &&
|
|
38
|
+
matchField(dom, d.getDate()) &&
|
|
39
|
+
matchField(mon, d.getMonth() + 1) &&
|
|
40
|
+
matchField(dow, d.getDay())
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadScheduled() {
|
|
45
|
+
try {
|
|
46
|
+
if (existsSync(SCHEDULED_PATH)) {
|
|
47
|
+
const data = JSON.parse(readFileSync(SCHEDULED_PATH, 'utf8'));
|
|
48
|
+
if (Array.isArray(data)) {
|
|
49
|
+
for (const msg of data) {
|
|
50
|
+
scheduled.set(msg.id, msg);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch { }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function saveScheduled() {
|
|
58
|
+
try {
|
|
59
|
+
const dirPath = join(process.cwd(), 'database');
|
|
60
|
+
if (!existsSync(dirPath)) mkdirSync(dirPath, { recursive: true });
|
|
61
|
+
const arr = Array.from(scheduled.values());
|
|
62
|
+
writeFileSync(SCHEDULED_PATH, JSON.stringify(arr, null, 2));
|
|
63
|
+
} catch { }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function generateId() {
|
|
67
|
+
return 'sch_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function processScheduled() {
|
|
71
|
+
if (!_sock) return;
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
let changed = false;
|
|
74
|
+
|
|
75
|
+
for (const [id, msg] of scheduled) {
|
|
76
|
+
if (msg.cancelled) continue;
|
|
77
|
+
|
|
78
|
+
const shouldSend =
|
|
79
|
+
(msg.type === 'once' && now >= msg.timestamp && !msg.sent) ||
|
|
80
|
+
(msg.type === 'recurring' && matchCron(msg.cron, new Date()));
|
|
81
|
+
|
|
82
|
+
if (shouldSend) {
|
|
83
|
+
try {
|
|
84
|
+
await _sock.sendMessage(msg.jid, msg.content);
|
|
85
|
+
changed = true;
|
|
86
|
+
|
|
87
|
+
if (msg.type === 'once') {
|
|
88
|
+
msg.sent = true;
|
|
89
|
+
msg.sentAt = now;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (msg.type === 'recurring') {
|
|
93
|
+
msg.lastSent = now;
|
|
94
|
+
msg.executionCount = (msg.executionCount || 0) + 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_sock.logger?.info?.({ id, jid: msg.jid }, 'scheduled message sent');
|
|
98
|
+
} catch (err) {
|
|
99
|
+
_sock.logger?.warn?.({ err, id }, 'failed to send scheduled message');
|
|
100
|
+
msg.lastError = err.message;
|
|
101
|
+
msg.errorCount = (msg.errorCount || 0) + 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (msg.type === 'once' && msg.sent) {
|
|
106
|
+
scheduled.delete(id);
|
|
107
|
+
changed = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (changed) saveScheduled();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let _interval = null;
|
|
115
|
+
|
|
116
|
+
function startScheduler(sock, intervalMs = 30000) {
|
|
117
|
+
_sock = sock;
|
|
118
|
+
if (!_initialized) {
|
|
119
|
+
loadScheduled();
|
|
120
|
+
_initialized = true;
|
|
121
|
+
}
|
|
122
|
+
if (_interval) clearInterval(_interval);
|
|
123
|
+
_interval = setInterval(processScheduled, intervalMs);
|
|
124
|
+
processScheduled();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function stopScheduler() {
|
|
128
|
+
if (_interval) {
|
|
129
|
+
clearInterval(_interval);
|
|
130
|
+
_interval = null;
|
|
131
|
+
}
|
|
132
|
+
_sock = null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Programar un mensaje para enviar en una fecha/hora específica
|
|
137
|
+
* @param {string} jid - Destinatario
|
|
138
|
+
* @param {Object} content - Contenido del mensaje (igual que sendMessage)
|
|
139
|
+
* @param {number|Date} timestamp - Fecha/hora de envío (timestamp ms o Date)
|
|
140
|
+
* @param {Object} options - Opciones adicionales
|
|
141
|
+
* @returns {string} ID del mensaje programado
|
|
142
|
+
*/
|
|
143
|
+
function scheduleMessage(jid, content, timestamp, options = {}) {
|
|
144
|
+
const id = options.id || generateId();
|
|
145
|
+
const ts = timestamp instanceof Date ? timestamp.getTime() : timestamp;
|
|
146
|
+
|
|
147
|
+
const msg = {
|
|
148
|
+
id,
|
|
149
|
+
jid,
|
|
150
|
+
content,
|
|
151
|
+
timestamp: ts,
|
|
152
|
+
type: 'once',
|
|
153
|
+
sent: false,
|
|
154
|
+
createdAt: Date.now(),
|
|
155
|
+
...options
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
scheduled.set(id, msg);
|
|
159
|
+
saveScheduled();
|
|
160
|
+
return id;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Programar un mensaje recurrente usando cron
|
|
165
|
+
* @param {string} jid - Destinatario
|
|
166
|
+
* @param {Object} content - Contenido del mensaje
|
|
167
|
+
* @param {string} cron - Expresión cron (MIN HOUR DOM MON DOW)
|
|
168
|
+
* @param {Object} options - Opciones adicionales
|
|
169
|
+
* @returns {string} ID del mensaje programado
|
|
170
|
+
*/
|
|
171
|
+
function recurringMessage(jid, content, cron, options = {}) {
|
|
172
|
+
const id = options.id || generateId();
|
|
173
|
+
|
|
174
|
+
const msg = {
|
|
175
|
+
id,
|
|
176
|
+
jid,
|
|
177
|
+
content,
|
|
178
|
+
cron,
|
|
179
|
+
type: 'recurring',
|
|
180
|
+
executionCount: 0,
|
|
181
|
+
createdAt: Date.now(),
|
|
182
|
+
...options
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
scheduled.set(id, msg);
|
|
186
|
+
saveScheduled();
|
|
187
|
+
return id;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Cancelar un mensaje programado
|
|
192
|
+
* @param {string} id - ID del mensaje
|
|
193
|
+
* @returns {boolean} true si se canceló
|
|
194
|
+
*/
|
|
195
|
+
function cancelScheduledMessage(id) {
|
|
196
|
+
const msg = scheduled.get(id);
|
|
197
|
+
if (!msg) return false;
|
|
198
|
+
msg.cancelled = true;
|
|
199
|
+
msg.cancelledAt = Date.now();
|
|
200
|
+
saveScheduled();
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Eliminar un mensaje programado permanentemente
|
|
206
|
+
* @param {string} id - ID del mensaje
|
|
207
|
+
* @returns {boolean} true si se eliminó
|
|
208
|
+
*/
|
|
209
|
+
function deleteScheduledMessage(id) {
|
|
210
|
+
const deleted = scheduled.delete(id);
|
|
211
|
+
if (deleted) saveScheduled();
|
|
212
|
+
return deleted;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Obtener todos los mensajes programados
|
|
217
|
+
* @param {Object} filter - Filtros opcionales
|
|
218
|
+
* @returns {Object[]} Lista de mensajes
|
|
219
|
+
*/
|
|
220
|
+
function listScheduledMessages(filter = {}) {
|
|
221
|
+
let msgs = Array.from(scheduled.values());
|
|
222
|
+
|
|
223
|
+
if (filter.jid) {
|
|
224
|
+
msgs = msgs.filter(m => m.jid === filter.jid);
|
|
225
|
+
}
|
|
226
|
+
if (filter.type) {
|
|
227
|
+
msgs = msgs.filter(m => m.type === filter.type);
|
|
228
|
+
}
|
|
229
|
+
if (filter.cancelled === false) {
|
|
230
|
+
msgs = msgs.filter(m => !m.cancelled);
|
|
231
|
+
}
|
|
232
|
+
if (filter.pending) {
|
|
233
|
+
msgs = msgs.filter(m => m.type === 'once' && !m.sent && !m.cancelled);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return msgs.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Obtener un mensaje programado por ID
|
|
241
|
+
* @param {string} id - ID del mensaje
|
|
242
|
+
* @returns {Object|null}
|
|
243
|
+
*/
|
|
244
|
+
function getScheduledMessage(id) {
|
|
245
|
+
return scheduled.get(id) || null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Actualizar el contenido de un mensaje programado
|
|
250
|
+
* @param {string} id - ID del mensaje
|
|
251
|
+
* @param {Object} updates - Campos a actualizar
|
|
252
|
+
* @returns {boolean}
|
|
253
|
+
*/
|
|
254
|
+
function updateScheduledMessage(id, updates) {
|
|
255
|
+
const msg = scheduled.get(id);
|
|
256
|
+
if (!msg) return false;
|
|
257
|
+
Object.assign(msg, updates);
|
|
258
|
+
saveScheduled();
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Obtener estadísticas del scheduler
|
|
264
|
+
* @returns {Object}
|
|
265
|
+
*/
|
|
266
|
+
function getSchedulerStats() {
|
|
267
|
+
const msgs = Array.from(scheduled.values());
|
|
268
|
+
return {
|
|
269
|
+
total: msgs.length,
|
|
270
|
+
pending: msgs.filter(m => m.type === 'once' && !m.sent && !m.cancelled).length,
|
|
271
|
+
sent: msgs.filter(m => m.type === 'once' && m.sent).length,
|
|
272
|
+
recurring: msgs.filter(m => m.type === 'recurring' && !m.cancelled).length,
|
|
273
|
+
cancelled: msgs.filter(m => m.cancelled).length,
|
|
274
|
+
errors: msgs.filter(m => m.lastError).length
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export {
|
|
279
|
+
startScheduler,
|
|
280
|
+
stopScheduler,
|
|
281
|
+
scheduleMessage,
|
|
282
|
+
recurringMessage,
|
|
283
|
+
cancelScheduledMessage,
|
|
284
|
+
deleteScheduledMessage,
|
|
285
|
+
listScheduledMessages,
|
|
286
|
+
getScheduledMessage,
|
|
287
|
+
updateScheduledMessage,
|
|
288
|
+
getSchedulerStats
|
|
289
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const RECONNECT_CONFIG: {
|
|
2
|
+
MAX_RETRIES: number;
|
|
3
|
+
INITIAL_DELAY: number;
|
|
4
|
+
MAX_DELAY: number;
|
|
5
|
+
BACKOFF_FACTOR: number;
|
|
6
|
+
JITTER_FACTOR: number;
|
|
7
|
+
STABLE_CONNECTION_TIME: number;
|
|
8
|
+
RECOVERABLE_REASONS: number[];
|
|
9
|
+
FATAL_REASONS: number[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export declare class SmartReconnect {
|
|
13
|
+
constructor(logger?: any, config?: Partial<typeof RECONNECT_CONFIG>);
|
|
14
|
+
retryCount: number;
|
|
15
|
+
connectionStable: boolean;
|
|
16
|
+
calculateDelay(): number;
|
|
17
|
+
shouldReconnect(reason: string, statusCode?: number): { shouldReconnect: boolean; reason?: string; delay?: number };
|
|
18
|
+
handleDisconnect(reason: string, statusCode: number | undefined, reconnectCallback: () => Promise<void>): Promise<{ reconnecting: boolean; reason?: string; success?: boolean; error?: any }>;
|
|
19
|
+
onSuccessfulReconnect(): void;
|
|
20
|
+
cancelPendingReconnect(): void;
|
|
21
|
+
reset(): void;
|
|
22
|
+
getStats(): {
|
|
23
|
+
totalReconnects: number;
|
|
24
|
+
successfulReconnects: number;
|
|
25
|
+
failedReconnects: number;
|
|
26
|
+
lastSuccessfulConnect: string | null;
|
|
27
|
+
averageDowntime: number;
|
|
28
|
+
currentRetryCount: number;
|
|
29
|
+
isStable: boolean;
|
|
30
|
+
lastDisconnect: { reason: string; statusCode?: number; time: number } | null;
|
|
31
|
+
};
|
|
32
|
+
startHealthCheck(pingCallback: () => Promise<void>, interval?: number): void;
|
|
33
|
+
stopHealthCheck(): void;
|
|
34
|
+
cleanup(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export declare function createConnectionHandler(sock: any, logger?: any, options?: Partial<typeof RECONNECT_CONFIG>): {
|
|
38
|
+
reconnect: SmartReconnect;
|
|
39
|
+
handleConnectionUpdate(update: any, startSock: () => Promise<void>): Promise<any>;
|
|
40
|
+
getStatus(): { stats: ReturnType<SmartReconnect['getStats']>; isHealthy: boolean };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export declare function withRetry<T>(fn: () => Promise<T>, options?: {
|
|
44
|
+
maxRetries?: number;
|
|
45
|
+
delay?: number;
|
|
46
|
+
backoff?: number;
|
|
47
|
+
onRetry?: ((error: any, attempt: number, waitTime: number) => void) | null;
|
|
48
|
+
}): Promise<T>;
|