Saltar a contenido

Bot con Engine Personalizado

Bot de produccion usando un engine personalizado para almacenamiento.


Descripcion

Este ejemplo muestra como crear un bot que usa un engine personalizado (PostgreSQL, S3, etc.) en lugar del FileEngine por defecto.

La libreria ya incluye RedisEngine como export oficial, asi que puedes usarlo directamente sin implementar uno propio.


Ejemplo: Usando el RedisEngine incluido

Usar el engine

bot.ts
import { writeFileSync } from "fs";
import Redis from "ioredis";
import { WhatsApp, RedisEngine } from "@arcaelas/whatsapp";
import "dotenv/config";

async function main() {
  // Crear conexion Redis
  const client = new Redis(process.env.REDIS_URL || "redis://localhost:6379");

  // Usar el RedisEngine incluido
  const engine = new RedisEngine(client, "wa:mi-bot");

  // Crear instancia con engine Redis
  const wa = new WhatsApp({ engine });

  // Logging
  wa.event.on("open", () => console.log("[INFO] Conectado"));
  wa.event.on("close", () => console.log("[WARN] Desconectado"));
  wa.event.on("error", (e) => console.error("[ERROR]", e.message));

  // Mensajes
  wa.event.on("message:created", async (msg) => {
    if (msg.me || msg.type !== "text") return;

    const text = (await msg.content()).toString();
    const prefix = process.env.BOT_PREFIX || "!";

    if (!text.startsWith(prefix)) return;

    const [cmd] = text.slice(prefix.length).split(" ");

    switch (cmd.toLowerCase()) {
      case "ping":
        await wa.Message.text(msg.cid, "pong!");
        break;

      case "status":
        await wa.Message.text(
          msg.cid,
          `*Estado del Bot*\n\n` +
          `Engine: Redis\n` +
          `Uptime: ${Math.floor(process.uptime() / 60)} minutos`
        );
        break;

      case "ayuda":
        await wa.Message.text(
          msg.cid,
          `Comandos:\n` +
          `${prefix}ping - Test\n` +
          `${prefix}status - Estado del bot\n` +
          `${prefix}ayuda - Este mensaje`
        );
        break;
    }
  });

  // Conectar
  console.log("[INFO] Iniciando bot...");
  await wa.pair(async (data) => {
    if (Buffer.isBuffer(data)) {
      writeFileSync("/tmp/qr.png", data);
      console.log("[INFO] QR guardado en /tmp/qr.png");
    } else {
      console.log("[INFO] Codigo:", data);
    }
  });

  console.log("[INFO] Bot listo!");

  // Mantener proceso vivo
  process.on("SIGINT", () => {
    console.log("[INFO] Cerrando...");
    process.exit(0);
  });
}

main().catch((e) => {
  console.error("[FATAL]", e);
  process.exit(1);
});

Ejemplo de Engine Personalizado

Si necesitas un backend diferente, implementa la interfaz Engine. El contrato clave de set(key, null) es que debe eliminar la key Y todas las sub-keys con ese prefijo (cascade delete).

CustomEngine.ts
import type { Engine } from "@arcaelas/whatsapp";

class MyEngine implements Engine {
  async get(key: string): Promise<string | null> {
    // Retornar valor almacenado o null
  }

  async set(key: string, value: string | null): Promise<void> {
    if (value === null) {
      // IMPORTANTE: Debe hacer cascade delete.
      // Eliminar la key exacta Y todas las keys que empiecen con key + "/"
      // Ejemplo: set("chat/123", null) debe tambien eliminar
      //   chat/123/index, chat/123/messages, chat/123/message/*/*, etc.
    } else {
      // Almacenar el valor
    }
  }

  async list(prefix: string, offset = 0, limit = 50): Promise<string[]> {
    // Retornar keys que coincidan con el prefix, ordenadas por mas reciente
  }
}

Docker

Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

CMD ["node", "--import", "tsx", "bot.ts"]
docker-compose.yml
version: "3.8"

services:
  redis:
    image: redis:7-alpine
    restart: unless-stopped

  bot:
    build: .
    restart: unless-stopped
    depends_on:
      - redis
    environment:
      - REDIS_URL=redis://redis:6379
      - BOT_PREFIX=!
    volumes:
      - /tmp:/tmp  # Para el QR temporal

Health check

import { createServer } from "http";

// Agregar despues de inicializar el bot
const server = createServer((req, res) => {
  if (req.url === "/health") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({
      status: "ok",
      connected: wa.socket !== null,
      uptime: process.uptime(),
    }));
  } else {
    res.writeHead(404);
    res.end();
  }
});

server.listen(3000, () => {
  console.log("[INFO] Health check en http://localhost:3000/health");
});

Variables de entorno

.env
REDIS_URL=redis://localhost:6379
BOT_PREFIX=!