initial commit
This commit is contained in:
commit
a28952edcf
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
API_KEY=
|
||||||
|
AUTHORIZED_NUMBER=502xxxxxxxx@c.us
|
||||||
|
CHROME_PATH=
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.env
|
||||||
|
bot.log
|
||||||
|
.wwebjs_auth
|
||||||
|
.wwebjs_cache
|
||||||
|
node_modules
|
||||||
|
temp
|
||||||
|
config.json
|
87
README.md
Normal file
87
README.md
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# BodegAI - Asistente Personal en WhatsApp
|
||||||
|
|
||||||
|
 <!-- Reemplaza con la ruta a tu logo si tienes uno -->
|
||||||
|
|
||||||
|
BodegAI es un asistente personal desarrollado como un bot para WhatsApp, diseñado para ayudarte a gestionar y organizar aspectos clave de tu vida diaria. Ideal para profesionales como programadores que buscan mantener un equilibrio entre el trabajo y el bienestar personal.
|
||||||
|
|
||||||
|
## Tabla de Contenidos
|
||||||
|
|
||||||
|
- [BodegAI - Asistente Personal en WhatsApp](#bodegai---asistente-personal-en-whatsapp)
|
||||||
|
- [Tabla de Contenidos](#tabla-de-contenidos)
|
||||||
|
- [Características](#características)
|
||||||
|
- [Requisitos](#requisitos)
|
||||||
|
- [Instalación](#instalación)
|
||||||
|
- [Uso](#uso)
|
||||||
|
- [Comandos Disponibles](#comandos-disponibles)
|
||||||
|
|
||||||
|
## Características
|
||||||
|
|
||||||
|
- **Notificaciones Personalizadas**: Recibe recordatorios para beber agua, limpiar tu espacio, cocinar y más.
|
||||||
|
- **Información del Clima**: Consulta el clima actual de cualquier ciudad directamente desde WhatsApp.
|
||||||
|
- **Gestión de Finanzas**: Mantén un seguimiento de tus ingresos y gastos.
|
||||||
|
- **Mantenimiento del Hogar**: Controla el sistema de agua y otros servicios esenciales.
|
||||||
|
- **Planificación de Compras**: Organiza tus visitas a la tienda y gestiona tu lista de despensa.
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
- **Node.js**: Versión 14 o superior.
|
||||||
|
- **npm**: Versión 6 o superior.
|
||||||
|
- **WhatsApp Web**: Una cuenta de WhatsApp para conectar el bot.
|
||||||
|
- **Moto**: Para desplazarte a la tienda en tu ubicación actual.
|
||||||
|
|
||||||
|
## Instalación
|
||||||
|
|
||||||
|
1. **Clona el repositorio:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.davidwebgt.com/davidwebgt/bodegAI.git
|
||||||
|
cd bodegAI
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Instala las dependencias:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configura las variables de entorno:**
|
||||||
|
|
||||||
|
Crea un archivo `.env` en la raíz del proyecto y añade las siguientes variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
CHROME_PATH=/ruta/a/tu/instalación/de/chrome
|
||||||
|
AUTHORIZED_NUMBER=+1234567890
|
||||||
|
```
|
||||||
|
|
||||||
|
- `CHROME_PATH`: Ruta al ejecutable de Chrome en tu sistema.
|
||||||
|
- `AUTHORIZED_NUMBER`: Número de teléfono autorizado para interactuar con el bot.
|
||||||
|
|
||||||
|
## Uso
|
||||||
|
|
||||||
|
1. **Inicia el bot:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Escanea el QR Code:**
|
||||||
|
|
||||||
|
Al iniciar, el bot generará un código QR en la terminal. Escanéalo con tu aplicación de WhatsApp para conectar el bot a tu cuenta.
|
||||||
|
|
||||||
|
3. **Interactúa con el Bot:**
|
||||||
|
|
||||||
|
Envía comandos desde tu WhatsApp para recibir respuestas y gestionar tus tareas.
|
||||||
|
|
||||||
|
## Comandos Disponibles
|
||||||
|
|
||||||
|
- **!ping**
|
||||||
|
|
||||||
|
- **Descripción**: Verifica que el bot está activo.
|
||||||
|
- **Uso**: Envía `!ping` y el bot responderá con `pong`.
|
||||||
|
|
||||||
|
- **!clima [ciudad]**
|
||||||
|
|
||||||
|
- **Descripción**: Obtiene la información del clima actual para la ciudad especificada. Si no se proporciona una ciudad, usa "Guatemala" por defecto.
|
||||||
|
- **Uso**:
|
||||||
|
- `!clima`
|
||||||
|
- `!clima Ciudad de México`
|
152
config.example.json
Normal file
152
config.example.json
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"perfil": {
|
||||||
|
"personal": {
|
||||||
|
"nombre": {
|
||||||
|
"completo": "",
|
||||||
|
"preferido": ""
|
||||||
|
},
|
||||||
|
"contacto": {
|
||||||
|
"telefono": {
|
||||||
|
"codigo_pais": "",
|
||||||
|
"numero": ""
|
||||||
|
},
|
||||||
|
"email": ""
|
||||||
|
},
|
||||||
|
"ubicacion": {
|
||||||
|
"tipo": "",
|
||||||
|
"direccion": "",
|
||||||
|
"referencia": "",
|
||||||
|
"movilidad": {
|
||||||
|
"tipo": "",
|
||||||
|
"uso": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profesional": {
|
||||||
|
"puesto": "",
|
||||||
|
"modalidad": {
|
||||||
|
"tipo": "",
|
||||||
|
"distribucion": {
|
||||||
|
"ciclo_presencial": 0,
|
||||||
|
"ciclo_remoto": 0,
|
||||||
|
"frecuencia": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compensacion": {
|
||||||
|
"salario_base": 0,
|
||||||
|
"fechas_pago": [
|
||||||
|
{
|
||||||
|
"dia": 0,
|
||||||
|
"descripcion": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"finanzas": {
|
||||||
|
"ingresos": {
|
||||||
|
"fijos": {
|
||||||
|
"salario": 0
|
||||||
|
},
|
||||||
|
"total_mensual": 0
|
||||||
|
},
|
||||||
|
"egresos": {
|
||||||
|
"fijos": {
|
||||||
|
"servicios": {
|
||||||
|
"internet": 0,
|
||||||
|
"telefono": 0
|
||||||
|
},
|
||||||
|
"obligaciones": {
|
||||||
|
"universidad": 0,
|
||||||
|
"prestamo": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variables": {
|
||||||
|
"servicios": {
|
||||||
|
"electricidad": 0,
|
||||||
|
"gas": 0
|
||||||
|
},
|
||||||
|
"necesidades": {
|
||||||
|
"alimentacion": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_fijos": 0,
|
||||||
|
"total_variables_estimado": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hogar": {
|
||||||
|
"servicios": {
|
||||||
|
"agua": {
|
||||||
|
"suministro": {
|
||||||
|
"tipo": "",
|
||||||
|
"sistema": "",
|
||||||
|
"operacion": ""
|
||||||
|
},
|
||||||
|
"mantenimiento": {
|
||||||
|
"actividades": [],
|
||||||
|
"frecuencia": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"administracion": {
|
||||||
|
"cocina": {
|
||||||
|
"abastecimiento": {
|
||||||
|
"frecuencia": 0,
|
||||||
|
"actividades": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compras": {
|
||||||
|
"frecuencia": "",
|
||||||
|
"categorias": [],
|
||||||
|
"logistica": {
|
||||||
|
"transporte": "",
|
||||||
|
"observaciones": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rutinas": {
|
||||||
|
"diarias": [
|
||||||
|
{
|
||||||
|
"actividad": "",
|
||||||
|
"detalles": {
|
||||||
|
"que": "",
|
||||||
|
"intervalo": "",
|
||||||
|
"horario": {
|
||||||
|
"inicio": "",
|
||||||
|
"fin": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"semanales": [
|
||||||
|
{
|
||||||
|
"actividad": "",
|
||||||
|
"detalles": {
|
||||||
|
"frecuencia": "",
|
||||||
|
"dias_preferidos": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"objetivos": {
|
||||||
|
"personales": {
|
||||||
|
"salud": {
|
||||||
|
"meta": "",
|
||||||
|
"acciones": []
|
||||||
|
},
|
||||||
|
"balance": {
|
||||||
|
"meta": "",
|
||||||
|
"acciones": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domesticos": {
|
||||||
|
"meta": "",
|
||||||
|
"acciones": []
|
||||||
|
},
|
||||||
|
"economicos": {
|
||||||
|
"meta": "",
|
||||||
|
"acciones": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
148
index.js
Normal file
148
index.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
const { Client, LocalAuth } = require('whatsapp-web.js');
|
||||||
|
const qrcode = require('qrcode-terminal');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
const weatherUtils = require('./utils/weather_utils');
|
||||||
|
const weatherService = require('./services/weather_service');
|
||||||
|
|
||||||
|
// Configuración inicial
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Constantes
|
||||||
|
const CHROME_PATH = process.env.CHROME_PATH;
|
||||||
|
const AUTHORIZED_NUMBER = process.env.AUTHORIZED_NUMBER;
|
||||||
|
|
||||||
|
// Validación inicial
|
||||||
|
if (!AUTHORIZED_NUMBER) {
|
||||||
|
console.error('❌ Debes establecer el número autorizado en el archivo .env');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuración del cliente
|
||||||
|
const clientConfig = {
|
||||||
|
authStrategy: new LocalAuth(),
|
||||||
|
puppeteer: {
|
||||||
|
headless: true,
|
||||||
|
executablePath: CHROME_PATH,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class WhatsAppBot {
|
||||||
|
constructor() {
|
||||||
|
this.client = new Client(clientConfig);
|
||||||
|
this.myId = null;
|
||||||
|
this.myNumber = null;
|
||||||
|
this.initializeEventHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeEventHandlers() {
|
||||||
|
this.client.on('qr', this.handleQR);
|
||||||
|
this.client.on('ready', this.handleReady.bind(this));
|
||||||
|
this.client.on('message_create', this.handleMessage.bind(this));
|
||||||
|
this.client.on('message', this.logMessage);
|
||||||
|
this.client.on('group_join', this.handleGroupJoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleQR(qr) {
|
||||||
|
qrcode.generate(qr, { small: true });
|
||||||
|
console.log('🔍 Escanea el QR code con tu WhatsApp.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleReady() {
|
||||||
|
console.log('✅ 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}`);
|
||||||
|
const chat = await this.client.getChatById(this.myId);
|
||||||
|
console.log('💭 Chat propio encontrado:', chat);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error al obtener información: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isChatAllowed(messageFrom) {
|
||||||
|
const isAllowed = messageFrom === AUTHORIZED_NUMBER;
|
||||||
|
if (isAllowed) console.log('🎯 Mensaje detectado desde el número autorizado');
|
||||||
|
return isAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleMessage(message) {
|
||||||
|
if (!this.myId || !this.isChatAllowed(message.from)) return;
|
||||||
|
|
||||||
|
console.log('📨 Mensaje creado:', {
|
||||||
|
from: message.from,
|
||||||
|
fromMe: message.fromMe,
|
||||||
|
body: message.body,
|
||||||
|
type: message.type,
|
||||||
|
timestamp: message.timestamp
|
||||||
|
});
|
||||||
|
|
||||||
|
const command = message.body.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (command === '!ping') {
|
||||||
|
await this.handlePingCommand(message);
|
||||||
|
} else if (command.startsWith('!clima')) {
|
||||||
|
await this.handleWeatherCommand(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handlePingCommand(message) {
|
||||||
|
try {
|
||||||
|
await this.client.sendMessage(message.from, 'pong');
|
||||||
|
console.log(`📤 Respondido 'pong' al número autorizado ${message.from}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error al enviar respuesta:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleWeatherCommand(message) {
|
||||||
|
const parts = message.body.split(' ');
|
||||||
|
const city = parts.length > 1 ? parts.slice(1).join(' ') : 'Guatemala';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { latitude, longitude } = await weatherUtils.getCoordinates(city);
|
||||||
|
const weather = await weatherService.getWeather(latitude, longitude);
|
||||||
|
const weatherDescription = weatherUtils.translateWeatherCode(weather.weathercode);
|
||||||
|
|
||||||
|
const weatherInfo = this.formatWeatherInfo(city, weather, weatherDescription);
|
||||||
|
await this.client.sendMessage(message.from, weatherInfo);
|
||||||
|
console.log(`📤 Información del clima de ${city} enviada a ${message.from}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error al obtener el clima:', error);
|
||||||
|
await this.client.sendMessage(message.from, `❌ Error al obtener el clima: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatWeatherInfo(city, weather, weatherDescription) {
|
||||||
|
return `
|
||||||
|
🌤️ *Clima Actual en ${city}*:
|
||||||
|
- 🌡️ Temperatura: ${weather.temperature}°C
|
||||||
|
- 💨 Viento: ${weather.windspeed} km/h
|
||||||
|
- 🧭 Dirección del viento: ${weather.winddirection}°
|
||||||
|
- ☁️ Condición: ${weatherDescription}
|
||||||
|
- ⏰ Hora: ${new Date(weather.time).toLocaleTimeString('es-ES')}
|
||||||
|
`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
logMessage(message) {
|
||||||
|
console.log('📩 Mensaje recibido:', {
|
||||||
|
from: message.from,
|
||||||
|
fromMe: message.fromMe,
|
||||||
|
body: message.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGroupJoin(notification) {
|
||||||
|
console.log('👥 Bot añadido a un nuevo grupo:', notification.id.remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.client.initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar el bot
|
||||||
|
const bot = new WhatsAppBot();
|
||||||
|
bot.start();
|
1756
package-lock.json
generated
Normal file
1756
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "whatsapp_bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"form-data": "^4.0.1",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
|
"whatsapp-web.js": "^1.26.0",
|
||||||
|
"winston": "^3.15.0"
|
||||||
|
}
|
||||||
|
}
|
31
services/weather_service.js
Normal file
31
services/weather_service.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const getWeather = async (latitude, longitude) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('https://api.open-meteo.com/v1/forecast', {
|
||||||
|
params: {
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
current_weather: true,
|
||||||
|
timezone: 'auto'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.data.current_weather) {
|
||||||
|
throw new Error('Datos de clima no disponibles.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const weather = response.data.current_weather;
|
||||||
|
return {
|
||||||
|
temperature: weather.temperature,
|
||||||
|
windspeed: weather.windspeed,
|
||||||
|
winddirection: weather.winddirection,
|
||||||
|
weathercode: weather.weathercode,
|
||||||
|
time: weather.time
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error al obtener el clima: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { getWeather };
|
65
utils/weather_utils.js
Normal file
65
utils/weather_utils.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const getCoordinates = async (city) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('https://nominatim.openstreetmap.org/search', {
|
||||||
|
params: {
|
||||||
|
q: city,
|
||||||
|
format: 'json',
|
||||||
|
limit: 1
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'WhatsApp-Bot/1.0 (josuedavidvl18@gmail.com)' // Reemplaza con tu email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.length === 0) {
|
||||||
|
throw new Error('Ciudad no encontrada.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lat, lon } = response.data[0];
|
||||||
|
return { latitude: lat, longitude: lon };
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error al obtener coordenadas: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateWeatherCode = (code) => {
|
||||||
|
const weatherCodes = {
|
||||||
|
0: 'Cielo despejado',
|
||||||
|
1: 'Parcialmente nublado',
|
||||||
|
2: 'Nublado',
|
||||||
|
3: 'Cubierto',
|
||||||
|
45: 'Niebla',
|
||||||
|
48: 'Depositos de escarcha',
|
||||||
|
51: 'Llovizna ligera',
|
||||||
|
53: 'Llovizna moderada',
|
||||||
|
55: 'Llovizna densa',
|
||||||
|
56: 'Llovizna helada ligera',
|
||||||
|
57: 'Llovizna helada densa',
|
||||||
|
61: 'Lluvia ligera',
|
||||||
|
63: 'Lluvia moderada',
|
||||||
|
65: 'Lluvia fuerte',
|
||||||
|
66: 'Lluvia helada ligera',
|
||||||
|
67: 'Lluvia helada fuerte',
|
||||||
|
71: 'Nevada ligera',
|
||||||
|
73: 'Nevada moderada',
|
||||||
|
75: 'Nevada fuerte',
|
||||||
|
77: 'Aguanieve',
|
||||||
|
80: 'Chubascos ligeros',
|
||||||
|
81: 'Chubascos moderados',
|
||||||
|
82: 'Chubascos intensos',
|
||||||
|
85: 'Aguanieve ligera',
|
||||||
|
86: 'Aguanieve fuerte',
|
||||||
|
95: 'Tormenta',
|
||||||
|
96: 'Tormenta con granizo ligero',
|
||||||
|
99: 'Tormenta con granizo fuerte'
|
||||||
|
};
|
||||||
|
|
||||||
|
return weatherCodes[code] || 'Desconocido';
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getCoordinates,
|
||||||
|
translateWeatherCode
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user