Compare commits

...

3 Commits

11 changed files with 1280 additions and 44 deletions

View File

@ -1,3 +1,4 @@
API_KEY=
AUTHORIZED_NUMBER=502xxxxxxxx@c.us
CHROME_PATH=
CHROME_PATH=
GOOGLE_GEMINI_API_KEY=

View File

@ -2,6 +2,7 @@ const axios = require('axios');
const { MessageMedia } = require('whatsapp-web.js');
const recipesService = require('../services/recipes_service');
const recipeUtils = require('../utils/recipe_utils');
const { translateText } = require('../plugins/translator');
/**
* Selecciona una receta aleatoria de una lista.
@ -70,7 +71,12 @@ const handleRecipeCommand = async (client, message) => {
throw new Error('No se encontraron recetas para tu búsqueda.');
}
const recipeInfo = recipeUtils.formatRecipeInfo(recipe);
// Traducir instrucciones al español antes de formatear la información
const instructions = recipe.strInstructions
? await translateText(recipe.strInstructions, 'es')
: 'No hay instrucciones disponibles.';
const recipeInfo = await recipeUtils.formatRecipeInfo({ ...recipe, strInstructions: instructions });
// Descargar la imagen de la receta
const imageUrl = recipe.strMealThumb;
@ -92,4 +98,4 @@ const handleRecipeCommand = async (client, message) => {
module.exports = {
handleRecipeCommand
};
};

View File

@ -27,9 +27,26 @@
"modalidad": {
"tipo": "",
"distribucion": {
"ciclo_presencial": 0,
"ciclo_remoto": 0,
"frecuencia": ""
"dias_ciclo_1": 0,
"dias_ciclo_2": 0,
"horario": {
"inicio": "",
"fin": ""
},
"dias_oficina_ciclo_1": {
"lunes": "",
"martes": "",
"miercoles": "",
"jueves": "",
"viernes": ""
},
"dias_oficina_ciclo_2": {
"lunes": "",
"martes": "",
"miercoles": "",
"jueves": "",
"viernes": ""
}
}
},
"compensacion": {
@ -38,6 +55,10 @@
{
"dia": 0,
"descripcion": ""
},
{
"dia": "",
"descripcion": ""
}
]
}
@ -149,4 +170,4 @@
"acciones": []
}
}
}
}

View File

@ -5,6 +5,7 @@ const qrcode = require('qrcode-terminal');
const dotenv = require('dotenv');
const weatherUtils = require('./utils/weather_utils');
const weatherService = require('./services/weather_service');
const logger = require('./utils/logger');
const { handleRecipeCommand } = require('./commands/recipes_command'); // Importar el comando de recetas
const { handleWeatherCommand } = require('./commands/weather_command'); // Importar el comando de clima
@ -49,20 +50,20 @@ class WhatsAppBot {
handleQR(qr) {
qrcode.generate(qr, { small: true });
console.log('🔍 Escanea el QR code con tu WhatsApp.');
logger.info('🔍 Escanea el QR code con tu WhatsApp.');
}
async handleReady() {
console.log('✅ Cliente de WhatsApp está listo!');
logger.info('✅ Cliente de WhatsApp está listo!');
try {
this.myId = this.client.info.wid._serialized;
this.myNumber = this.myId.split('@')[0];
console.log(`🔑 Tu ID de WhatsApp es: ${this.myId}`);
console.log(`📱 Tu número de teléfono es: ${this.myNumber}`);
logger.info(`🔑 Tu ID de WhatsApp es: ${this.myId}`);
logger.info(`📱 Tu número de teléfono es: ${this.myNumber}`);
const chat = await this.client.getChatById(this.myId);
console.log('💭 Chat propio encontrado:', chat);
logger.info(`💭 Chat propio encontrado: ${JSON.stringify(chat, null, 2)}`);
} catch (error) {
console.error(`❌ Error al obtener información: ${error}`);
logger.error(`❌ Error al obtener información: ${error}`);
}
}

1026
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,8 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
@ -14,8 +15,14 @@
"dotenv": "^16.4.5",
"form-data": "^4.0.1",
"node-cron": "^3.0.3",
"pino": "^9.5.0",
"pino-pretty": "^11.3.0",
"qrcode-terminal": "^0.12.0",
"translate-google": "^1.5.0",
"whatsapp-web.js": "^1.26.0",
"winston": "^3.16.0"
},
"devDependencies": {
"nodemon": "^3.1.7"
}
}

45
plugins/translator.js Normal file
View File

@ -0,0 +1,45 @@
const axios = require('axios');
const dotenv = require('dotenv');
dotenv.config();
const apiKey = process.env.GOOGLE_GEMINI_API_KEY;
/**
* Traduce un texto utilizando la API de Google Gemini.
* @param {String} text - Texto a traducir.
* @param {String} targetLang - Idioma de destino, 'es' para español.
* @returns {Promise<String>} - Texto traducido.
*/
const translateText = async (text, targetLang = 'es') => {
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${apiKey}`;
// Añadimos el preprompt para que especifique la traducción al español
const prompt = `Por favor, traduce el siguiente texto al español:\n\n${text}`;
try {
const response = await axios.post(
url,
{
contents: [{ parts: [{ text: prompt }] }]
},
{
headers: { 'Content-Type': 'application/json' }
}
);
console.log('Respuesta de la API de traducción:', response.data);
console.log('Texto original:', text);
// Extrae el texto traducido de la respuesta
const translatedText = response.data.candidates[0].content.parts[0].text;
return translatedText;
} catch (error) {
console.error('Error al traducir el texto:', JSON.stringify(error.response?.data, null, 2));
return text; // Devuelve el texto original en caso de error
}
};
module.exports = {
translateText
};

View File

@ -1,25 +1,59 @@
const axios = require('axios');
/**
* Busca recetas por nombre utilizando la API de TheMealDB.
* Realiza una búsqueda de recetas usando combinaciones de palabras.
* @param {String} name - Nombre de la receta a buscar.
* @returns {Array} - Lista de recetas encontradas.
* @throws {Error} - Si no se encuentran recetas o hay un error en la solicitud.
*/
const searchMealByName = async (name) => {
const words = name.split(' ');
let recipes = [];
// Intenta la búsqueda completa primero
try {
const response = await axios.get('https://www.themealdb.com/api/json/v1/1/search.php', {
params: { s: name }
});
if (!response.data.meals) {
throw new Error('No se encontraron recetas para tu búsqueda.');
}
return response.data.meals; // Devuelve todas las recetas encontradas
recipes = await performSearch(name);
if (recipes.length > 0) return recipes;
} catch (error) {
throw new Error(`Error al buscar la receta: ${error.message}`);
console.log(`No se encontraron recetas para "${name}", probando combinaciones...`);
}
// Si no encuentra con el nombre completo, intenta con combinaciones
for (let i = 0; i < words.length; i++) {
const partialName = words.slice(i).join(' ');
try {
recipes = await performSearch(partialName);
if (recipes.length > 0) return recipes;
} catch (error) {
console.log(`No se encontraron recetas para "${partialName}"`);
}
}
// Si no encuentra nada, intenta con cada palabra individualmente
for (const word of words) {
try {
recipes = await performSearch(word);
if (recipes.length > 0) return recipes;
} catch (error) {
console.log(`No se encontraron recetas para "${word}"`);
}
}
// Si no se encontraron resultados, lanza un error
throw new Error('No se encontraron recetas para tu búsqueda.');
};
/**
* Realiza la búsqueda en la API de TheMealDB.
* @param {String} query - Consulta de búsqueda.
* @returns {Array} - Lista de recetas encontradas.
*/
const performSearch = async (query) => {
const response = await axios.get('https://www.themealdb.com/api/json/v1/1/search.php', { params: { s: query } });
// console.log('Request URL:', response.config.url);
// console.log('Response:', response.data);
return response.data.meals || [];
};
/**
@ -30,6 +64,7 @@ const searchMealByName = async (name) => {
const getRandomMeal = async () => {
try {
const response = await axios.get('https://www.themealdb.com/api/json/v1/1/random.php');
if (!response.data.meals) {
throw new Error('No se pudo obtener una receta aleatoria.');
}
@ -48,15 +83,11 @@ const getRandomMeal = async () => {
const filterByIngredient = async (ingredient) => {
try {
const response = await axios.get('https://www.themealdb.com/api/json/v1/1/filter.php', {
params: {
i: ingredient
}
params: { i: ingredient }
});
if (!response.data.meals) {
throw new Error('No se encontraron recetas con ese ingrediente.');
}
return response.data.meals;
} catch (error) {
throw new Error(`Error al filtrar por ingrediente: ${error.message}`);
@ -75,10 +106,7 @@ const healthyCategories = ['Salad', 'Chicken', 'Eggs', 'Fish', 'Seafood', 'Pasta
*/
const getRandomHealthyMeal = async () => {
try {
// Escoge una categoría saludable al azar
const randomCategory = healthyCategories[Math.floor(Math.random() * healthyCategories.length)];
// Filtra las recetas por la categoría saludable seleccionada
const response = await axios.get('https://www.themealdb.com/api/json/v1/1/filter.php', {
params: { c: randomCategory }
});
@ -87,7 +115,6 @@ const getRandomHealthyMeal = async () => {
throw new Error('No se encontraron recetas saludables.');
}
// Escoge una receta aleatoria dentro de las recetas saludables
const randomMeal = response.data.meals[Math.floor(Math.random() * response.data.meals.length)];
return await lookupMealById(randomMeal.idMeal);
} catch (error) {
@ -118,4 +145,4 @@ module.exports = {
filterByIngredient,
getRandomHealthyMeal,
lookupMealById
};
};

94
utils/json_utils.js Normal file
View File

@ -0,0 +1,94 @@
class JSONUtils {
constructor(data) {
if (typeof data !== 'object' || data === null) {
throw new Error('Data must be a valid JSON object');
}
this.data = data;
}
getValue(path) {
const keys = path.split('.');
let result = this.data;
try {
for (const key of keys) {
if (result[key] === undefined) {
throw new Error(`Property '${key}' does not exist`);
}
result = result[key];
}
return result;
} catch (error) {
console.error(error.message);
return null; // Devuelve null si hay un error
}
}
setValue(path, value) {
const keys = path.split('.');
let current = this.data;
try {
keys.forEach((key, index) => {
if (index === keys.length - 1) {
current[key] = value;
} else {
if (current[key] === undefined) {
current[key] = {}; // Crea objeto si no existe
}
current = current[key];
}
});
} catch (error) {
console.error(`Error setting value: ${error.message}`);
}
}
updateValue(path, value) {
const currentValue = this.getValue(path);
if (currentValue !== null) {
this.setValue(path, value);
} else {
console.error(`Cannot update: Property at '${path}' does not exist`);
}
}
deleteValue(path) {
const keys = path.split('.');
let current = this.data;
try {
keys.forEach((key, index) => {
if (index === keys.length - 1) {
delete current[key];
} else {
if (current[key] === undefined) {
throw new Error(`Property '${key}' does not exist`);
}
current = current[key];
}
});
} catch (error) {
console.error(`Error deleting value: ${error.message}`);
}
}
saveToFile() {
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
console.log('Datos guardados exitosamente en el archivo.');
} catch (error) {
console.error('Error al guardar los datos:', error.message);
}
}
getJSON() {
return this.data;
}
printJSON() {
console.log(JSON.stringify(this.data, null, 2));
}
}
module.exports = JSONUtils;

13
utils/logger.js Normal file
View File

@ -0,0 +1,13 @@
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true
}
}
});
module.exports = logger;

View File

@ -1,9 +1,16 @@
const { translateText } = require('../plugins/translator');
/**
* Formatea la información de una receta para enviarla como mensaje.
* @param {Object} recipe - Objeto de receta obtenido de TheMealDB.
* @returns {String} - Mensaje formateado con la información de la receta.
*/
const formatRecipeInfo = (recipe) => {
const formatRecipeInfo = async (recipe) => {
// Traducir instrucciones de la receta
const instructions = recipe.strInstructions
? await translateText(recipe.strInstructions, 'es')
: 'No hay instrucciones disponibles.';
return `
🍽 *${recipe.strMeal}*
@ -11,7 +18,7 @@ const formatRecipeInfo = (recipe) => {
🌍 *Área:* ${recipe.strArea || 'No disponible'}
📝 *Instrucciones:*
${recipe.strInstructions || 'No hay instrucciones disponibles.'}
${instructions}
🔗 *Fuente:*
${recipe.strSource || 'No disponible'}
@ -46,4 +53,4 @@ const getIngredientsList = (recipe) => {
module.exports = {
formatRecipeInfo
};
};