feat: enhance relative time parsing and clean up reminder text handling
This commit is contained in:
@@ -13,60 +13,135 @@ function humanizeDate(d: Date) {
|
||||
return d.toLocaleString('es-ES', { timeZone: 'UTC', hour12: false });
|
||||
}
|
||||
|
||||
function formatRelativeEs(when: Date): string {
|
||||
const diffMs = when.getTime() - Date.now();
|
||||
const diffSec = Math.max(0, Math.round(diffMs / 1000));
|
||||
if (diffSec < 60) {
|
||||
const s = Math.max(1, diffSec);
|
||||
return `en ${s} segundo${s === 1 ? '' : 's'}`;
|
||||
}
|
||||
const diffMin = Math.round(diffSec / 60);
|
||||
if (diffMin < 60) {
|
||||
const m = Math.max(1, diffMin);
|
||||
return `en ${m} minuto${m === 1 ? '' : 's'}`;
|
||||
}
|
||||
const diffH = Math.round(diffMin / 60);
|
||||
if (diffH < 24) {
|
||||
const h = Math.max(1, diffH);
|
||||
return `en ${h} hora${h === 1 ? '' : 's'}`;
|
||||
}
|
||||
const d = Math.max(1, Math.round(diffH / 24));
|
||||
return `en ${d} día${d === 1 ? '' : 's'}`;
|
||||
}
|
||||
|
||||
function cleanSpaces(s: string): string {
|
||||
return s.replace(/\s{2,}/g, ' ').replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
|
||||
function parseRelativeDelay(text: string): { when: Date, reminderText: string } | null {
|
||||
const lower = text.toLowerCase();
|
||||
|
||||
// 0) Helpers de unidades
|
||||
const minUnit = 'm(?:in(?:uto(?:s)?)?)?';
|
||||
const hourUnit = 'h(?:ora(?:s)?)?';
|
||||
const dayUnit = 'd(?:[ií]a(?:s)?)?|d'; // dia, días, dias, día, d
|
||||
|
||||
// 1) "en menos de 1h" -> 59 minutos
|
||||
let m = lower.match(/en\s+menos\s+de\s+1\s*h(ora(s)?)?/i);
|
||||
let m = lower.match(new RegExp(`en\\s+menos\\s+de\\s+1\\s*${hourUnit}`, 'i'));
|
||||
if (m) {
|
||||
const minutes = 59;
|
||||
const when = new Date(Date.now() + minutes * 60 * 1000);
|
||||
const reminderText = text.replace(m[0], '').trim() || text;
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 2) "en menos de X min" -> (X-1) minutos
|
||||
m = lower.match(/en\s+menos\s+de\s+(\d+)\s*m(in(utos)?)?/i);
|
||||
m = lower.match(new RegExp(`en\\s+menos\\s+de\\s+(\\d+)\\s*${minUnit}`, 'i'));
|
||||
if (m) {
|
||||
const minutes = Math.max(1, parseInt(m[1], 10) - 1);
|
||||
const when = new Date(Date.now() + minutes * 60 * 1000);
|
||||
const reminderText = text.replace(m[0], '').trim() || text;
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 3) "en X minutos" / "en Xm"
|
||||
m = lower.match(/en\s+(\d+)\s*m(in(utos)?)?/i);
|
||||
m = lower.match(new RegExp(`en\\s+(\\d+)\\s*${minUnit}`, 'i'));
|
||||
if (m) {
|
||||
const minutes = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + minutes * 60 * 1000);
|
||||
const reminderText = text.replace(m[0], '').trim() || text;
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 4) "en X horas" / "en Xh"
|
||||
m = lower.match(/en\s+(\d+)\s*h(ora(s)?)?/i);
|
||||
m = lower.match(new RegExp(`en\\s+(\\d+)\\s*${hourUnit}`, 'i'));
|
||||
if (m) {
|
||||
const hours = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + hours * 60 * 60 * 1000);
|
||||
const reminderText = text.replace(m[0], '').trim() || text;
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 5) Post-fijo corto: "15m" o "45 min" al final
|
||||
m = lower.match(/(\d+)\s*m(in(utos)?)?$/i);
|
||||
// 5) "en X días" / "en Xd"
|
||||
m = lower.match(new RegExp(`en\\s+(\\d+)\\s*${dayUnit}`, 'i'));
|
||||
if (m) {
|
||||
const days = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 6) "dentro de X minutos"
|
||||
m = lower.match(new RegExp(`dentro\\s+de\\s+(\\d+)\\s*${minUnit}`, 'i'));
|
||||
if (m) {
|
||||
const minutes = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + minutes * 60 * 1000);
|
||||
const reminderText = text.slice(0, m.index).trim() || text;
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 6) Post-fijo corto: "1h" o "2 horas" al final
|
||||
m = lower.match(/(\d+)\s*h(ora(s)?)?$/i);
|
||||
// 7) "dentro de X horas"
|
||||
m = lower.match(new RegExp(`dentro\\s+de\\s+(\\d+)\\s*${hourUnit}`, 'i'));
|
||||
if (m) {
|
||||
const hours = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + hours * 60 * 60 * 1000);
|
||||
const reminderText = text.slice(0, m.index).trim() || text;
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 8) "dentro de X días"
|
||||
m = lower.match(new RegExp(`dentro\\s+de\\s+(\\d+)\\s*${dayUnit}`, 'i'));
|
||||
if (m) {
|
||||
const days = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
|
||||
const reminderText = cleanSpaces(text.replace(m[0], '')) || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 9) Post-fijo corto al final: "15m" o "45 min"
|
||||
m = lower.match(new RegExp(`(\\d+)\\s*${minUnit}$`, 'i'));
|
||||
if (m) {
|
||||
const minutes = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + minutes * 60 * 1000);
|
||||
const reminderText = cleanSpaces(text.slice(0, (m.index ?? text.length))).trim() || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 10) Post-fijo corto: "1h" o "2 horas" al final
|
||||
m = lower.match(new RegExp(`(\\d+)\\s*${hourUnit}$`, 'i'));
|
||||
if (m) {
|
||||
const hours = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + hours * 60 * 60 * 1000);
|
||||
const reminderText = cleanSpaces(text.slice(0, (m.index ?? text.length))).trim() || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
// 11) Post-fijo corto: "7d" o "7 dias" al final
|
||||
m = lower.match(new RegExp(`(\\d+)\\s*${dayUnit}$`, 'i'));
|
||||
if (m) {
|
||||
const days = Math.max(1, parseInt(m[1], 10));
|
||||
const when = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
|
||||
const reminderText = cleanSpaces(text.slice(0, (m.index ?? text.length))).trim() || text;
|
||||
return { when, reminderText };
|
||||
}
|
||||
|
||||
@@ -93,12 +168,12 @@ export const command: CommandMessage = {
|
||||
}
|
||||
|
||||
// 1) Soporte rápido para tiempos relativos: "en 10 minutos", "15m", "1h", "en menos de 1h"
|
||||
const rel = parseRelativeDelay(text);
|
||||
const relParsed = parseRelativeDelay(text);
|
||||
let when: Date | null = null;
|
||||
let reminderText = text;
|
||||
if (rel) {
|
||||
when = rel.when;
|
||||
reminderText = rel.reminderText;
|
||||
if (relParsed) {
|
||||
when = relParsed.when;
|
||||
reminderText = relParsed.reminderText;
|
||||
}
|
||||
|
||||
// 2) Si no hubo match relativo, usar parser natural (chrono) en español
|
||||
@@ -122,7 +197,14 @@ export const command: CommandMessage = {
|
||||
if (matched) {
|
||||
const idx = text.toLowerCase().indexOf(matched.toLowerCase());
|
||||
if (idx >= 0) {
|
||||
reminderText = (text.slice(0, idx) + text.slice(idx + matched.length)).trim() || text;
|
||||
// Intentar eliminar también un conector previo como "en" o "dentro de"
|
||||
let start = idx;
|
||||
const before = text.slice(0, idx);
|
||||
const lead = before.match(/(?:en|dentro\s+de)\s*$/i);
|
||||
if (lead) start = idx - lead[0].length;
|
||||
|
||||
let reminderTextCandidate = text.slice(0, start) + text.slice(idx + matched.length);
|
||||
reminderText = cleanSpaces(reminderTextCandidate) || text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,6 +232,7 @@ export const command: CommandMessage = {
|
||||
}
|
||||
|
||||
const whenHuman = humanizeDate(when);
|
||||
await message.reply(`✅ Recordatorio guardado para: ${whenHuman} UTC\nMensaje: ${reminderText}`);
|
||||
const relText = formatRelativeEs(when);
|
||||
await message.reply(`✅ Recordatorio guardado: ${relText} (${whenHuman} UTC)\nMensaje: ${reminderText}`);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user