feat: implement automated job cleanup, update notification presets, and upgrade Baileys library

This commit is contained in:
2026-07-21 21:12:29 -03:00
parent aac53e13cc
commit 98f0ec3102
8 changed files with 388 additions and 35 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
DATABASE_CONNECTION = mongodb://horacio:frx8us2w@192.168.1.50:20215/turnosxpress DATABASE_CONNECTION = mongodb://horacio:frx8us2w@localhost:20215/turnosxpress
DATABASE_CONNECTION_ATLAS = mongodb+srv://horacio:frx8us2w@reservar.a2cgd.mongodb.net/reservar DATABASE_CONNECTION_ATLAS = mongodb+srv://horacio:frx8us2w@reservar.a2cgd.mongodb.net/reservar
DATABASE_CONNECTION_LOCAL = mongodb://horacio:frx8us2w@192.168.1.50:20215/turnosxpress DATABASE_CONNECTION_LOCAL = mongodb://horacio:frx8us2w@192.168.1.50:20215/turnosxpress
@@ -8,8 +8,17 @@ const POLL_INTERVAL_MS = 5000;
const BASE_RETRY_DELAY_MS = 5000; const BASE_RETRY_DELAY_MS = 5000;
const MAX_JITTER_MS = 3000; const MAX_JITTER_MS = 3000;
const THROTTLE_RETRY_DELAY_MS = 8000; const THROTTLE_RETRY_DELAY_MS = 8000;
const DEFAULT_CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
const SUPPORTED_APPOINTMENT_NOTIFICATION_TYPES = new Set(["creation", "reminder", "cancellation"]); const SUPPORTED_APPOINTMENT_NOTIFICATION_TYPES = new Set(["creation", "reminder", "cancellation"]);
function resolveCleanupIntervalMs(): number {
const raw = process.env.NOTIFICATION_JOB_CLEANUP_INTERVAL_MS;
if (!raw) return DEFAULT_CLEANUP_INTERVAL_MS;
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_CLEANUP_INTERVAL_MS;
}
function resolveAppointmentNotificationType(type: unknown): string { function resolveAppointmentNotificationType(type: unknown): string {
return typeof type === "string" && SUPPORTED_APPOINTMENT_NOTIFICATION_TYPES.has(type) ? type : "reminder"; return typeof type === "string" && SUPPORTED_APPOINTMENT_NOTIFICATION_TYPES.has(type) ? type : "reminder";
} }
@@ -28,6 +37,10 @@ export function calculateNextRetryAt(attempt: number, now: Date): Date {
return new Date(now.getTime() + delay + jitter); return new Date(now.getTime() + delay + jitter);
} }
export function buildStartOfDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
function getJobId(job: { id?: unknown; _id?: unknown; appointmentId?: unknown; channel?: unknown; get?: (path: string) => unknown }): string { function getJobId(job: { id?: unknown; _id?: unknown; appointmentId?: unknown; channel?: unknown; get?: (path: string) => unknown }): string {
const value = job.id || job._id || job.get?.("_id"); const value = job.id || job._id || job.get?.("_id");
if (value) return String(value); if (value) return String(value);
@@ -41,24 +54,53 @@ export class JobProcessor {
private jobModel: NotificationJobModel; private jobModel: NotificationJobModel;
private throttle: WhatsAppThrottleClass; private throttle: WhatsAppThrottleClass;
private polling = false; private polling = false;
private cleanupTimeout?: NodeJS.Timeout;
private cleanupIntervalMs: number;
constructor() { constructor() {
this.jobModel = new NotificationJobModel(); this.jobModel = new NotificationJobModel();
this.throttle = new WhatsAppThrottleClass(); this.throttle = new WhatsAppThrottleClass();
this.cleanupIntervalMs = resolveCleanupIntervalMs();
} }
public async startPolling(): Promise<void> { public async startPolling(): Promise<void> {
if (this.polling) return; if (this.polling) return;
this.polling = true; this.polling = true;
logIntent("JobProcessor: starting poll loop"); logIntent("JobProcessor: starting poll loop");
await this.cleanupOldJobs();
this.scheduleCleanup();
this.poll(); this.poll();
} }
public stopPolling(): void { public stopPolling(): void {
this.polling = false; this.polling = false;
if (this.cleanupTimeout) {
clearTimeout(this.cleanupTimeout);
this.cleanupTimeout = undefined;
}
logIntent("JobProcessor: stopping poll loop"); logIntent("JobProcessor: stopping poll loop");
} }
private scheduleCleanup(): void {
if (!this.polling || this.cleanupIntervalMs <= 0) return;
this.cleanupTimeout = setTimeout(async () => {
await this.cleanupOldJobs();
this.scheduleCleanup();
}, this.cleanupIntervalMs);
}
private async cleanupOldJobs(now = new Date()): Promise<void> {
const cutoff = buildStartOfDay(now);
try {
const deletedCount = await this.jobModel.deleteBeforeScheduledAt(cutoff);
logDone(`JobProcessor: cleaned ${deletedCount} old notification jobs before ${cutoff.toISOString()}`);
} catch (error) {
logError(`JobProcessor: cleanup error before ${cutoff.toISOString()}`, error);
}
}
private async poll(): Promise<void> { private async poll(): Promise<void> {
if (!this.polling) return; if (!this.polling) return;
@@ -102,4 +102,12 @@ export class NotificationJobModel {
this.schema this.schema
); );
} }
async deleteBeforeScheduledAt(cutoff: Date): Promise<number> {
const result = await this.notificationJobList.deleteMany({
scheduledAt: { $lt: cutoff },
}).exec();
return result.deletedCount || 0;
}
} }
@@ -1,5 +1,5 @@
import { dispatchEmail, dispatchWhatsApp } from "../ChannelDispatchers.js"; import { dispatchEmail, dispatchWhatsApp } from "../ChannelDispatchers.js";
import { buildPendingJobQuery, calculateNextRetryAt, JobProcessor } from "../JobProcessor.js"; import { buildPendingJobQuery, buildStartOfDay, calculateNextRetryAt, JobProcessor } from "../JobProcessor.js";
import { resolveNotificationContent } from "../NotificationContentResolver.js"; import { resolveNotificationContent } from "../NotificationContentResolver.js";
jest.mock("../ChannelDispatchers.js", () => ({ jest.mock("../ChannelDispatchers.js", () => ({
@@ -94,6 +94,28 @@ describe("JobProcessor", () => {
}); });
}); });
describe("buildStartOfDay", () => {
it("returns the start of the local day", () => {
const result = buildStartOfDay(new Date(2026, 6, 21, 15, 30, 45, 123));
expect(result).toEqual(new Date(2026, 6, 21, 0, 0, 0, 0));
});
});
describe("old job cleanup", () => {
it("deletes jobs scheduled before the start of the current day", async () => {
const deleteBeforeScheduledAt = jest.fn().mockResolvedValue(3);
const now = new Date(2026, 6, 21, 15, 30, 0, 0);
await (JobProcessor.prototype as any).cleanupOldJobs.call(
{ jobModel: { deleteBeforeScheduledAt } },
now
);
expect(deleteBeforeScheduledAt).toHaveBeenCalledWith(new Date(2026, 6, 21, 0, 0, 0, 0));
});
});
describe("WhatsApp dispatch", () => { describe("WhatsApp dispatch", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
+306 -28
View File
@@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@whiskeysockets/baileys": "^6.7.18", "@whiskeysockets/baileys": "6.17.16",
"express": "^5.1.0", "express": "^5.1.0",
"qrcode": "^1.5.4" "qrcode": "^1.5.4"
}, },
@@ -20,6 +20,12 @@
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }
}, },
"node_modules/@adiwajshing/keyed-db": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@adiwajshing/keyed-db/-/keyed-db-0.2.4.tgz",
"integrity": "sha512-yprSnAtj80/VKuDqRcFFLDYltoNV8tChNwFfIgcf6PGD4sjzWIBgs08pRuTqGH5mk5wgL6PBRSsMCZqtZwzFEw==",
"license": "MIT"
},
"node_modules/@cacheable/node-cache": { "node_modules/@cacheable/node-cache": {
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@cacheable/node-cache/-/node-cache-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@cacheable/node-cache/-/node-cache-1.5.5.tgz",
@@ -44,6 +50,12 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@eshaz/web-worker": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@eshaz/web-worker/-/web-worker-1.2.2.tgz",
"integrity": "sha512-WxXiHFmD9u/owrzempiDlBB1ZYqiLnm9s6aPc8AlFQalq2tKmqdmMr9GXOupDgzXtqnBipj8Un0gkIm7Sjf8mw==",
"license": "Apache-2.0"
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -445,6 +457,55 @@
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@thi.ng/bitstream": {
"version": "2.4.54",
"resolved": "https://registry.npmjs.org/@thi.ng/bitstream/-/bitstream-2.4.54.tgz",
"integrity": "sha512-uInkAJge5O0bWWEaYKrQpMccPbFg0z6eIA5NDCJXPm7l3rjlDje6RBHBXll3LiQz9Y051EdzlAEQRaB5hEifdg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/postspectacular"
},
{
"type": "patreon",
"url": "https://patreon.com/thing_umbrella"
},
{
"type": "liberapay",
"url": "https://liberapay.com/thi.ng"
}
],
"license": "Apache-2.0",
"dependencies": {
"@thi.ng/errors": "^2.6.16"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@thi.ng/errors": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/@thi.ng/errors/-/errors-2.6.16.tgz",
"integrity": "sha512-a7Lv/G0La5eTNUEIyLldpeYziyFSj3rOlWNeXFu8v+ZSb8w8EnQ/L0r0sKHux7Ru6RhUkAXrcaHU7xd1ZkWovA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/postspectacular"
},
{
"type": "patreon",
"url": "https://patreon.com/thing_umbrella"
},
{
"type": "liberapay",
"url": "https://liberapay.com/thi.ng"
}
],
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/@tokenizer/token": { "node_modules/@tokenizer/token": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
@@ -835,44 +896,97 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/@whiskeysockets/baileys": { "node_modules/@wasm-audio-decoders/common": {
"version": "6.7.18", "version": "9.0.7",
"resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-6.7.18.tgz", "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/common/-/common-9.0.7.tgz",
"integrity": "sha512-lstyxtAdOC7vuI9dWyxDg9DQlYSz8MhVsBkQUTX9EWt0GY0IJIa2Rnu4psGXa1OxgNvYwLaGSHiQBRil8mGb8g==", "integrity": "sha512-WRaUuWSKV7pkttBygml/a6dIEpatq2nnZGFIoPTc5yPLkxL6Wk4YaslPM98OPQvWacvNZ+Py9xROGDtrFBDzag==",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eshaz/web-worker": "1.2.2",
"simple-yenc": "^1.0.4"
}
},
"node_modules/@wasm-audio-decoders/flac": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@wasm-audio-decoders/flac/-/flac-0.2.10.tgz",
"integrity": "sha512-YfcyoD2rYRBa6ffawZKNi5qvV5HArJmNmuMVUPoutuZ2hhGi6WNSWIzgvbROGmPbFivLL764Am7xxJENWJDhjw==",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/common": "9.0.7",
"codec-parser": "2.5.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/@wasm-audio-decoders/ogg-vorbis": {
"version": "0.1.20",
"resolved": "https://registry.npmjs.org/@wasm-audio-decoders/ogg-vorbis/-/ogg-vorbis-0.1.20.tgz",
"integrity": "sha512-zaQPasU5usRjUDXtXOHYED5tfkR4QMXd+EH3Nrz1+4+M5pCsdD+s9YxJqb0oqnTyRu/KUujOmu5Z/m/NT47vwg==",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/common": "9.0.7",
"codec-parser": "2.5.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/@wasm-audio-decoders/opus-ml": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@wasm-audio-decoders/opus-ml/-/opus-ml-0.0.2.tgz",
"integrity": "sha512-58rWEqDGg+CKCyEeKm2KoxxSwTWtHh/NLTW9ObR4K8CGF6VwuuGudEI1CtniS/oSRmL1nJq/eh8MKARiluw4DQ==",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/common": "9.0.7"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/@whiskeysockets/baileys": {
"version": "6.17.16",
"resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-6.17.16.tgz",
"integrity": "sha512-cZoUaKpO4fsDUNiCtyZfbjkW0Bjl/IudzHLCvpqfqtq5TACQzNynYsYdKPJz1I8Cu/SSEvmewk0RorIs0zDWyw==",
"deprecated": "This version is affected by a zero-day vulnerability that allows spoofing of messages, please update to\n the latest versions (6.7.22^ or 7.0.0-rc12^)! For more information, check out the public advisory at\n https://github.com/WhiskeySockets/Baileys/security/advisories/GHSA-qvv5-jq5g-4cgg",
"license": "MIT",
"dependencies": {
"@adiwajshing/keyed-db": "^0.2.4",
"@cacheable/node-cache": "^1.4.0", "@cacheable/node-cache": "^1.4.0",
"@hapi/boom": "^9.1.3", "@hapi/boom": "^9.1.3",
"@whiskeysockets/eslint-config": "github:whiskeysockets/eslint-config", "@whiskeysockets/eslint-config": "github:whiskeysockets/eslint-config",
"async-mutex": "^0.5.0", "async-lock": "^1.4.1",
"audio-decode": "^2.1.3",
"axios": "^1.6.0", "axios": "^1.6.0",
"cache-manager": "^5.7.6",
"libphonenumber-js": "^1.10.20",
"libsignal": "github:WhiskeySockets/libsignal-node", "libsignal": "github:WhiskeySockets/libsignal-node",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"music-metadata": "^7.12.3", "music-metadata": "^7.12.3",
"pino": "^9.6", "pino": "^9.6",
"protobufjs": "^7.2.4", "protobufjs": "^7.2.4",
"uuid": "^10.0.0",
"ws": "^8.13.0" "ws": "^8.13.0"
}, },
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": { "peerDependencies": {
"audio-decode": "^2.1.3",
"jimp": "^0.16.1", "jimp": "^0.16.1",
"link-preview-js": "^3.0.0", "link-preview-js": "^3.0.0",
"qrcode-terminal": "^0.12.0",
"sharp": "^0.32.6" "sharp": "^0.32.6"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"audio-decode": {
"optional": true
},
"jimp": { "jimp": {
"optional": true "optional": true
}, },
"link-preview-js": { "link-preview-js": {
"optional": true "optional": true
}, },
"qrcode-terminal": {
"optional": true
},
"sharp": { "sharp": {
"optional": true "optional": true
} }
@@ -1006,14 +1120,11 @@
"license": "Python-2.0", "license": "Python-2.0",
"peer": true "peer": true
}, },
"node_modules/async-mutex": { "node_modules/async-lock": {
"version": "0.5.0", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
"integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==",
"license": "MIT", "license": "MIT"
"dependencies": {
"tslib": "^2.4.0"
}
}, },
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
@@ -1030,6 +1141,41 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/audio-buffer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/audio-buffer/-/audio-buffer-5.0.0.tgz",
"integrity": "sha512-gsDyj1wwUp8u7NBB+eW6yhLb9ICf+0eBmDX8NGaAS00w8/fLqFdxUlL5Ge/U8kB64DlQhdonxYC59dXy1J7H/w==",
"license": "MIT"
},
"node_modules/audio-decode": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/audio-decode/-/audio-decode-2.2.3.tgz",
"integrity": "sha512-Z0lHvMayR/Pad9+O9ddzaBJE0DrhZkQlStrC1RwcAHF3AhQAsdwKHeLGK8fYKyp2DDU6xHxzGb4CLMui12yVrg==",
"deprecated": "Renamed to @audio/decode — same API; this name remains a thin alias. npm i @audio/decode",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/flac": "^0.2.4",
"@wasm-audio-decoders/ogg-vorbis": "^0.1.15",
"audio-buffer": "^5.0.0",
"audio-type": "^2.2.1",
"mpg123-decoder": "^1.0.0",
"node-wav": "^0.0.2",
"ogg-opus-decoder": "^1.6.12",
"qoa-format": "^1.0.1"
}
},
"node_modules/audio-type": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/audio-type/-/audio-type-2.4.2.tgz",
"integrity": "sha512-E9Bl1FGGDS81H/mKvaoPq0iQ8uT3qTBe6XWjipOo5kYrhLMAbjD7hREAVle0YJFahZOVbztxTdkwi0VBDJutSA==",
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/audiojs"
}
},
"node_modules/axios": { "node_modules/axios": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
@@ -1141,6 +1287,21 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/cache-manager": {
"version": "5.7.6",
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.6.tgz",
"integrity": "sha512-wBxnBHjDxF1RXpHCBD6HGvKER003Ts7IIm0CHpggliHzN1RZditb7rXoduE1rplc2DEFYKxhLKgFuchXMJje9w==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash.clonedeep": "^4.5.0",
"lru-cache": "^10.2.2",
"promise-coalesce": "^1.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/cacheable": { "node_modules/cacheable": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz",
@@ -1227,6 +1388,12 @@
"wrap-ansi": "^6.2.0" "wrap-ansi": "^6.2.0"
} }
}, },
"node_modules/codec-parser": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/codec-parser/-/codec-parser-2.5.0.tgz",
"integrity": "sha512-Ru9t80fV8B0ZiixQl8xhMTLru+dzuis/KQld32/x5T/+3LwZb0/YvQdSKytX9JqCnRdiupvAvyYJINKrXieziQ==",
"license": "LGPL-3.0-or-later"
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1803,6 +1970,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"license": "MIT"
},
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -2460,6 +2633,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/libphonenumber-js": {
"version": "1.13.9",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.9.tgz",
"integrity": "sha512-VNS5vWMM7r0P66BYv+TQJATxExEgLxN+34hfHDVhDkUsGAE4cRg0shCNSLTXNKm7nIUscC7AfB51TjxEeF7msQ==",
"license": "MIT"
},
"node_modules/libsignal": { "node_modules/libsignal": {
"name": "@whiskeysockets/libsignal-node", "name": "@whiskeysockets/libsignal-node",
"version": "2.0.1", "version": "2.0.1",
@@ -2526,6 +2705,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -2539,6 +2724,12 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/make-error": { "node_modules/make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -2634,6 +2825,19 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/mpg123-decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/mpg123-decoder/-/mpg123-decoder-1.0.3.tgz",
"integrity": "sha512-+fjxnWigodWJm3+4pndi+KUg9TBojgn31DPk85zEsim7C6s0X5Ztc/hQYdytXkwuGXH+aB0/aEkG40Emukv6oQ==",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/common": "9.0.7"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2677,6 +2881,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/node-wav": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/node-wav/-/node-wav-0.0.2.tgz",
"integrity": "sha512-M6Rm/bbG6De/gKGxOpeOobx/dnGuP0dz40adqx38boqHhlWssBJZgLCPBNtb9NkrmnKYiV04xELq+R6PFOnoLA==",
"license": "MIT",
"engines": {
"node": ">=4.4.0"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -2689,6 +2902,22 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/ogg-opus-decoder": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/ogg-opus-decoder/-/ogg-opus-decoder-1.7.3.tgz",
"integrity": "sha512-w47tiZpkLgdkpa+34VzYD8mHUj8I9kfWVZa82mBbNwDvB1byfLXSSzW/HxA4fI3e9kVlICSpXGFwMLV1LPdjwg==",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/common": "9.0.7",
"@wasm-audio-decoders/opus-ml": "0.0.2",
"codec-parser": "2.5.0",
"opus-decoder": "0.7.11"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/on-exit-leak-free": { "node_modules/on-exit-leak-free": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
@@ -2737,6 +2966,19 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/opus-decoder": {
"version": "0.7.11",
"resolved": "https://registry.npmjs.org/opus-decoder/-/opus-decoder-0.7.11.tgz",
"integrity": "sha512-+e+Jz3vGQLxRTBHs8YJQPRPc1Tr+/aC6coV/DlZylriA29BdHQAYXhvNRKtjftof17OFng0+P4wsFIqQu3a48A==",
"license": "MIT",
"dependencies": {
"@wasm-audio-decoders/common": "9.0.7"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/p-limit": { "node_modules/p-limit": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -2929,6 +3171,15 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/promise-coalesce": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.5.0.tgz",
"integrity": "sha512-cTJ30U+ur1LD7pMPyQxiKIwxjtAjLsyU7ivRhVWZrX9BNIXtf78pc37vSMc8Vikx7DVzEKNk2SEJ5KWUpSG2ig==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=16"
}
},
"node_modules/protobufjs": { "node_modules/protobufjs": {
"version": "7.4.0", "version": "7.4.0",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
@@ -2982,6 +3233,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qoa-format": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/qoa-format/-/qoa-format-1.0.1.tgz",
"integrity": "sha512-dMB0Z6XQjdpz/Cw4Rf6RiBpQvUSPCfYlQMWvmuWlWkAT7nDQD29cVZ1SwDUB6DYJSitHENwbt90lqfI+7bvMcw==",
"license": "MIT",
"dependencies": {
"@thi.ng/bitstream": "^2.2.12"
}
},
"node_modules/qrcode": { "node_modules/qrcode": {
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
@@ -3370,6 +3630,16 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/simple-yenc": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/simple-yenc/-/simple-yenc-1.0.4.tgz",
"integrity": "sha512-5gvxpSd79e9a3V4QDYUqnqxeD4HGlhCakVpb6gMnDD7lexJggSBJRBO5h52y/iJrdXRilX9UCuDaIJhSWm5OWw==",
"license": "MIT",
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/eshaz"
}
},
"node_modules/sonic-boom": { "node_modules/sonic-boom": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
@@ -3578,12 +3848,6 @@
} }
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -3649,6 +3913,20 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+1 -1
View File
@@ -12,7 +12,7 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"@whiskeysockets/baileys": "^6.7.18", "@whiskeysockets/baileys": "6.17.16",
"express": "^5.1.0", "express": "^5.1.0",
"qrcode": "^1.5.4" "qrcode": "^1.5.4"
}, },
+1 -1
View File
@@ -65,7 +65,7 @@ async function startServer() {
} }
try { try {
const jid = number.endsWith("@s.whatsapp.net") ? number : `${number}@s.whatsapp.net`; const jid = number.endsWith("@s.whatsapp.net") ? number : `${number}@s.whatsapp.net`;
await sock.sendMessage(jid, { text: message, ephemeralExpiration: 0 }); await sock.sendMessage(jid, { text: message });
res.json({ success: true }); res.json({ success: true });
} catch (err) { } catch (err) {
console.error("Send error:", err); console.error("Send error:", err);
@@ -25,9 +25,12 @@ const CHANNELS: { key: NotificationChannel; label: string }[] = [
]; ];
const REMINDER_PRESETS: { minutes: number; label: string }[] = [ const REMINDER_PRESETS: { minutes: number; label: string }[] = [
{ minutes: 2, label: "2 minutos antes" }, { minutes: 60, label: "1 hora antes" },
{ minutes: 5, label: "5 minutos antes" }, { minutes: 180, label: "3 horas antes" },
{ minutes: 10, label: "10 minutos antes" } { minutes: 360, label: "6 horas antes" },
{ minutes: 720, label: "12 horas antes" },
{ minutes: 1440, label: "1 día antes" },
{ minutes: 2880, label: "2 días antes" }
]; ];
export default function NotificationsPage() { export default function NotificationsPage() {