topsapp: adicionado opção de abrir suporte
Signed-off-by: Rodrigo B. de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
1337e1c2d0
commit
3d35fc24cc
9 files changed
+1600
-1
No files matched your search
@@ -0,0 +1,378 @@
|
||||
const logger = require('./logger');
|
||||
|
||||
class DateProcessor {
|
||||
constructor() {
|
||||
// Dicionário de dias da semana
|
||||
this.diasSemana = {
|
||||
'domingo': 0, 'dom': 0,
|
||||
'segunda': 1, 'segunda-feira': 1, 'seg': 1,
|
||||
'terça': 2, 'terca': 2, 'terça-feira': 2, 'terca-feira': 2, 'ter': 2,
|
||||
'quarta': 3, 'quarta-feira': 3, 'qua': 3,
|
||||
'quinta': 4, 'quinta-feira': 4, 'qui': 4,
|
||||
'sexta': 5, 'sexta-feira': 5, 'sex': 5,
|
||||
'sábado': 6, 'sabado': 6, 'sab': 6
|
||||
};
|
||||
|
||||
// Dicionário de meses
|
||||
this.meses = {
|
||||
'janeiro': 0, 'jan': 0,
|
||||
'fevereiro': 1, 'fev': 1,
|
||||
'março': 2, 'mar': 2, 'marco': 2,
|
||||
'abril': 3, 'abr': 3,
|
||||
'maio': 4, 'mai': 4,
|
||||
'junho': 5, 'jun': 5,
|
||||
'julho': 6, 'jul': 6,
|
||||
'agosto': 7, 'ago': 7,
|
||||
'setembro': 8, 'set': 8,
|
||||
'outubro': 9, 'out': 9,
|
||||
'novembro': 10, 'nov': 10,
|
||||
'dezembro': 11, 'dez': 11
|
||||
};
|
||||
|
||||
// Palavras-chave temporais
|
||||
this.palavrasTemporais = {
|
||||
'hoje': 0,
|
||||
'amanhã': 1, 'amanha': 1,
|
||||
'depois de amanhã': 2, 'depois de amanha': 2,
|
||||
'ontem': -1,
|
||||
'anteontem': -2
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar texto em linguagem natural para extrair data e hora
|
||||
* @param {string} texto - Texto em linguagem natural
|
||||
* @returns {Object} - { success: boolean, data: Date, originalText: string, parsed: Object }
|
||||
*/
|
||||
processarTexto(texto) {
|
||||
if (!texto || typeof texto !== 'string') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Texto inválido'
|
||||
};
|
||||
}
|
||||
|
||||
const textoNormalizado = texto.toLowerCase().trim();
|
||||
logger.debug('Processando texto de data/hora:', { texto: textoNormalizado });
|
||||
|
||||
try {
|
||||
// Tentar diferentes padrões de reconhecimento
|
||||
let resultado = null;
|
||||
|
||||
// 1. Padrões com dias da semana
|
||||
resultado = this.processarDiaSemana(textoNormalizado);
|
||||
if (resultado.success) return resultado;
|
||||
|
||||
// 2. Padrões com palavras temporais (hoje, amanhã, etc)
|
||||
resultado = this.processarPalavrasTemporal(textoNormalizado);
|
||||
if (resultado.success) return resultado;
|
||||
|
||||
// 3. Padrões de data específica (DD/MM, DD/MM/YYYY)
|
||||
resultado = this.processarDataEspecifica(textoNormalizado);
|
||||
if (resultado.success) return resultado;
|
||||
|
||||
// 4. Padrões relativos (em X dias, daqui a X horas)
|
||||
resultado = this.processarDataRelativa(textoNormalizado);
|
||||
if (resultado.success) return resultado;
|
||||
|
||||
// Se não conseguiu processar
|
||||
return {
|
||||
success: false,
|
||||
error: 'Não foi possível interpretar a data/hora',
|
||||
originalText: texto
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Erro ao processar data/hora:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
originalText: texto
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar padrões com dias da semana
|
||||
*/
|
||||
processarDiaSemana(texto) {
|
||||
// Padrões: "sexta às 14h", "na segunda 09:00", "sexta-feira 14:30"
|
||||
const padroes = [
|
||||
/(?:na\s+)?(\w+(?:-\w+)?)\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||
/(?:na\s+)?(\w+(?:-\w+)?)\s+(?:às?\s*)?(\d{1,2})(?:h)?(\d{0,2})?/i
|
||||
];
|
||||
|
||||
for (const padrao of padroes) {
|
||||
const match = texto.match(padrao);
|
||||
if (match) {
|
||||
const diaNome = match[1].toLowerCase();
|
||||
const hora = parseInt(match[2]);
|
||||
const minuto = match[3] ? parseInt(match[3]) : 0;
|
||||
|
||||
if (this.diasSemana.hasOwnProperty(diaNome)) {
|
||||
const diaSemanaTarget = this.diasSemana[diaNome];
|
||||
const data = this.obterProximoDiaSemana(diaSemanaTarget);
|
||||
data.setHours(hora, minuto, 0, 0);
|
||||
|
||||
if (this.isValidTime(hora, minuto)) {
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
originalText: texto,
|
||||
parsed: {
|
||||
type: 'dia_semana',
|
||||
dia: diaNome,
|
||||
hora,
|
||||
minuto
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar palavras temporais (hoje, amanhã, etc)
|
||||
*/
|
||||
processarPalavrasTemporal(texto) {
|
||||
// Padrões: "amanhã às 17h", "hoje 14:30", "depois de amanhã 09h"
|
||||
const padroes = [
|
||||
/(hoje|amanhã|amanha|depois de amanhã|depois de amanha)\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||
/(hoje|amanhã|amanha|depois de amanhã|depois de amanha)\s+(?:às?\s*)?(\d{1,2})(?:h)?(\d{0,2})?/i
|
||||
];
|
||||
|
||||
for (const padrao of padroes) {
|
||||
const match = texto.match(padrao);
|
||||
if (match) {
|
||||
const palavraTemporal = match[1].toLowerCase();
|
||||
const hora = parseInt(match[2]);
|
||||
const minuto = match[3] ? parseInt(match[3]) : 0;
|
||||
|
||||
if (this.palavrasTemporais.hasOwnProperty(palavraTemporal)) {
|
||||
const diasOffset = this.palavrasTemporais[palavraTemporal];
|
||||
const data = new Date();
|
||||
data.setDate(data.getDate() + diasOffset);
|
||||
data.setHours(hora, minuto, 0, 0);
|
||||
|
||||
if (this.isValidTime(hora, minuto)) {
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
originalText: texto,
|
||||
parsed: {
|
||||
type: 'palavra_temporal',
|
||||
palavra: palavraTemporal,
|
||||
offset: diasOffset,
|
||||
hora,
|
||||
minuto
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar datas específicas
|
||||
*/
|
||||
processarDataEspecifica(texto) {
|
||||
// Padrões: "15/08 às 14h", "15/08/2024 14:30", "15 de agosto 09h"
|
||||
const padroes = [
|
||||
/(\d{1,2})\/(\d{1,2})(?:\/(\d{2,4}))?\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||
/(\d{1,2})\s+de\s+(\w+)(?:\s+de\s+(\d{2,4}))?\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i
|
||||
];
|
||||
|
||||
for (const padrao of padroes) {
|
||||
const match = texto.match(padrao);
|
||||
if (match) {
|
||||
let dia, mes, ano, hora, minuto;
|
||||
|
||||
if (match[0].includes('/')) {
|
||||
// Formato DD/MM/YYYY
|
||||
dia = parseInt(match[1]);
|
||||
mes = parseInt(match[2]) - 1; // JavaScript usa 0-11 para meses
|
||||
ano = match[3] ? parseInt(match[3]) : new Date().getFullYear();
|
||||
hora = parseInt(match[4]);
|
||||
minuto = match[5] ? parseInt(match[5]) : 0;
|
||||
} else {
|
||||
// Formato "DD de MMMM"
|
||||
dia = parseInt(match[1]);
|
||||
const mesNome = match[2].toLowerCase();
|
||||
mes = this.meses[mesNome];
|
||||
ano = match[3] ? parseInt(match[3]) : new Date().getFullYear();
|
||||
hora = parseInt(match[4]);
|
||||
minuto = match[5] ? parseInt(match[5]) : 0;
|
||||
}
|
||||
|
||||
if (ano < 100) ano += 2000; // Converter anos de 2 dígitos
|
||||
|
||||
if (mes !== undefined && this.isValidDate(dia, mes, ano) && this.isValidTime(hora, minuto)) {
|
||||
const data = new Date(ano, mes, dia, hora, minuto, 0, 0);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
originalText: texto,
|
||||
parsed: {
|
||||
type: 'data_especifica',
|
||||
dia,
|
||||
mes: mes + 1, // Para display humano
|
||||
ano,
|
||||
hora,
|
||||
minuto
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar datas relativas
|
||||
*/
|
||||
processarDataRelativa(texto) {
|
||||
// Padrões: "em 3 dias às 14h", "daqui a 2 horas", "em 1 semana"
|
||||
const padroes = [
|
||||
/(?:em|daqui\s+a)\s+(\d+)\s+(dias?|semanas?|meses?)\s+(?:às?\s*)?(\d{1,2}):?(\d{0,2})(?:h)?/i,
|
||||
/(?:em|daqui\s+a)\s+(\d+)\s+(horas?|minutos?)/i
|
||||
];
|
||||
|
||||
for (const padrao of padroes) {
|
||||
const match = texto.match(padrao);
|
||||
if (match) {
|
||||
const quantidade = parseInt(match[1]);
|
||||
const unidade = match[2].toLowerCase();
|
||||
|
||||
const data = new Date();
|
||||
|
||||
if (unidade.includes('dia')) {
|
||||
data.setDate(data.getDate() + quantidade);
|
||||
if (match[3] && match[4] !== undefined) {
|
||||
const hora = parseInt(match[3]);
|
||||
const minuto = match[4] ? parseInt(match[4]) : 0;
|
||||
if (this.isValidTime(hora, minuto)) {
|
||||
data.setHours(hora, minuto, 0, 0);
|
||||
}
|
||||
}
|
||||
} else if (unidade.includes('semana')) {
|
||||
data.setDate(data.getDate() + (quantidade * 7));
|
||||
if (match[3] && match[4] !== undefined) {
|
||||
const hora = parseInt(match[3]);
|
||||
const minuto = match[4] ? parseInt(match[4]) : 0;
|
||||
if (this.isValidTime(hora, minuto)) {
|
||||
data.setHours(hora, minuto, 0, 0);
|
||||
}
|
||||
}
|
||||
} else if (unidade.includes('mes')) {
|
||||
data.setMonth(data.getMonth() + quantidade);
|
||||
if (match[3] && match[4] !== undefined) {
|
||||
const hora = parseInt(match[3]);
|
||||
const minuto = match[4] ? parseInt(match[4]) : 0;
|
||||
if (this.isValidTime(hora, minuto)) {
|
||||
data.setHours(hora, minuto, 0, 0);
|
||||
}
|
||||
}
|
||||
} else if (unidade.includes('hora')) {
|
||||
data.setHours(data.getHours() + quantidade);
|
||||
} else if (unidade.includes('minuto')) {
|
||||
data.setMinutes(data.getMinutes() + quantidade);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
originalText: texto,
|
||||
parsed: {
|
||||
type: 'data_relativa',
|
||||
quantidade,
|
||||
unidade
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter próximo dia da semana
|
||||
*/
|
||||
obterProximoDiaSemana(diaSemanaTarget) {
|
||||
const hoje = new Date();
|
||||
const diaAtual = hoje.getDay();
|
||||
|
||||
let diasParaAdicionar = diaSemanaTarget - diaAtual;
|
||||
if (diasParaAdicionar <= 0) {
|
||||
diasParaAdicionar += 7; // Próxima semana
|
||||
}
|
||||
|
||||
const proximaData = new Date(hoje);
|
||||
proximaData.setDate(hoje.getDate() + diasParaAdicionar);
|
||||
|
||||
return proximaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar se a data é válida
|
||||
*/
|
||||
isValidDate(dia, mes, ano) {
|
||||
if (dia < 1 || dia > 31) return false;
|
||||
if (mes < 0 || mes > 11) return false;
|
||||
if (ano < 1900 || ano > 2100) return false;
|
||||
|
||||
// Verificar dias válidos para o mês
|
||||
const diasNoMes = new Date(ano, mes + 1, 0).getDate();
|
||||
return dia <= diasNoMes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validar se a hora é válida
|
||||
*/
|
||||
isValidTime(hora, minuto) {
|
||||
return hora >= 0 && hora <= 23 && minuto >= 0 && minuto <= 59;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatar data para string legível
|
||||
*/
|
||||
formatarData(data) {
|
||||
if (!(data instanceof Date)) return 'Data inválida';
|
||||
|
||||
const opcoes = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'America/Sao_Paulo'
|
||||
};
|
||||
|
||||
return data.toLocaleDateString('pt-BR', opcoes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatar data para o formato do sistema (YYYY-MM-DD HH:mm:ss)
|
||||
*/
|
||||
formatarParaSistema(data) {
|
||||
if (!(data instanceof Date)) return null;
|
||||
|
||||
const ano = data.getFullYear();
|
||||
const mes = String(data.getMonth() + 1).padStart(2, '0');
|
||||
const dia = String(data.getDate()).padStart(2, '0');
|
||||
const hora = String(data.getHours()).padStart(2, '0');
|
||||
const minuto = String(data.getMinutes()).padStart(2, '0');
|
||||
const segundo = String(data.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${ano}-${mes}-${dia} ${hora}:${minuto}:${segundo}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DateProcessor;
|
||||
Reference in new issue
Block a user