[email protected]
Twitter
LinkedIn
YouTube
GitHub
  • Servicios
  • Blog
  • Repositorios
  • GitHub
  • Currículum
  • Contacto
Producto se añadió a tu carrito

Carrito

Jfa Whatsapp chatbot

marzo 29, 2023Automatización, Back-End, Herramientasjfadev

Con este nodo.js micro marco usando Robot Venenoso bajo el capó, puedes crear fácilmente un Chatbot de WhatsApp 🤖 . Solo necesitarás editar tu flujo de conversación en un solo archivo.

Empezando

  1. Crear un nuevo repositorio a partir de esta plantilla
  2. Instalación en su entorno de desarrollo
  3. Configurar puerto(s), cartas credenciales, etc
  4. Escribe tu flujo de conversación
  5. Comenzar

Instalación

Docker

Requisitos: estibador

Cree y ejecute con Dockerfile

$ docker build -t wchatbot .
$ docker run --name wchatbot -p 3000:3000 -v /your_project_absolute_path/src:/wchatbot/src wchatbot

o Compile y ejecute con Docker Compose

$ docker-compose build
$ docker-compose up

Visita http://servidor local:3000 y juega con tu chatbot!

Máquina virtual

Requisitos: nodejs (El último maintenance LTS versión), yarn (o npm), pm2, chrome/chromium

Usa un nginx proxy inverso para exponer públicamente el panel de control http (ejemplo de configuración).

$ yarn install

Inicie el chatbot y el panel de control http

$ yarn start
$ yarn http-ctrl:start

Visita http://servidor local:3000 y juega con tu chatbot!

Máquina local

Requisitos: nodejs (El último maintenance LTS versión), yarn (o npm), chrome/chromium

$ yarn install

Inicie el chatbot y el panel de control http

$ yarn http-ctrl:dev:detach
$ yarn dev

Visita http://servidor local:3000 y juega con tu chatbot!

Configuración

Editar ./src/config.js archivo

Basic

export const chatbotOptions = {
  httpCtrl: {
    port: 3000, // httpCtrl port (http://localhost:3000/)
    username: "admin", // httpCtrl auth login
    password: "chatbot",
  },
};

Avanzado

export const venomOptions = {
  ...
  browserArgs: [
    "--no-sandbox", // Will be passed to browser. Use --no-sandbox with Docker
  ],
  puppeteerOptions: { // Will be passed to puppeteer.launch.
    args: ["--no-sandbox"] // Use --no-sandbox with Docker
  },
  ...
};

Commands

Docker

Controles de chatbots

$ docker exec wchatbot yarn start
$ docker exec wchatbot yarn stop
$ docker exec wchatbot yarn restart
$ docker exec wchatbot yarn reload

Controles del panel de control HTTP

$ docker exec wchatbot yarn http-ctrl:start
$ docker exec wchatbot yarn http-ctrl:stop
$ docker exec wchatbot yarn http-ctrl:restart
$ docker exec wchatbot yarn http-ctrl:reload

Máquina virtual

Controles de chatbots

$ yarn start
$ yarn stop
$ yarn restart
$ yarn reload

Controles del panel de control HTTP

$ yarn http-ctrl:start
$ yarn http-ctrl:stop
$ yarn http-ctrl:restart
$ yarn http-ctrl:reload

Máquina local

Directo en su sistema operativo sin Docker

bot conversacional

$ yarn dev
$ yarn dev:detach

Inicie el panel de control HTTP

$ yarn http-ctrl:dev
$ yarn http-ctrl:dev:detach

Sesiones

Las sesiones y los tokens de autenticación se escriben ./tokens carpeta.

Registros

Los registros se escriben ./logs carpeta. Atención: console.log y http-ctrl-console.log solo escribe ./logs carpeta con yarn dev:detach y yarn http-ctrl:dev:detach administrado de otra manera por pm2.

Docker

bot conversacional

$ docker exec wchatbot yarn log

Panel de control HTTP

$ docker exec wchatbot yarn http-ctrl:log

Conversaciones

$ docker exec wchatbot yarn conversations

Máquina virtual

bot conversacional

$ yarn log

Panel de control HTTP

$ yarn http-ctrl:log

Conversaciones

$ yarn conversations

Máquina local

bot conversacional

$ yarn log:dev

Panel de control HTTP

$ yarn log:http-ctrl:dev

Conversaciones

$ yarn conversations

Flujo de conversación

Editar ./src/conversations/conversation.js archivo.

El flujo de conversación es una matriz de objetos de respuesta ordenados.. Una respuesta solo se activa si su parent (puede ser un entero o una matriz) es igual a la id de la respuesta anterior.

Replies Relations

Para indicar que una respuesta es el final de la conversación, agregue la siguiente propiedad:

Propiedad Tipo Descripción
end booleano El final de la conversación

Puedes proteger para que solo se responda un número o una lista de números con:

Propiedad Tipo Descripción
from Cadena / Array Solo responde este o estos números

Una respuesta necesita necesariamente las siguientes propiedades:

Tipos de respuestas

enviar texto

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
message Cadena Responder mensaje de texto

Ejemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match with all text
    message: "Hi I am a Chatbot!",
  }
]

Enviar Botones

Atención: Actualmente no está funcionando.!.

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
message Cadena Responder mensaje de texto
description Cadena Subtítulo del texto de respuesta
buttons Array Objeto de botón, mira el ejemplo

Ejemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/,
    message: "Hello!",
    description: "Can I help with something?",
    buttons: buttons([
      "Website",
      "LinkedIn",
      "Github",
    ]),
  }
]

Enviar lista

Atención: Actualmente no está funcionando.!.

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
message Cadena Responder mensaje de texto
description Cadena Subtítulo del texto de respuesta
button Cadena Texto del botón de lista
list Array Objeto de lista, mira el ejemplo

Ejemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /other country/,
    message: "Choice one country",
    description: "Choice one option!",
    button: "Countries list",
    list: list([
      "Argentina",
      "Belize",
      "Bolivia",
    ]),
  },
]

Enviar enlace

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
message Cadena Responder mensaje de texto
link Cadena URL de la vista previa del enlace generado

Ejemplo

[
  {
    id: 2,
    parent: 1, // Relation with id: 1
    pattern: /github/,
    message: "Check my Github repositories!",
    link: "https://github.com/jfadev",
  }
]

Enviar imagen

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
image Camino / Objeto Ruta u objeto devuelto por remoteImg() funcion

Ejemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    image: remoteImg("https://remote-server.com/menu.jpg"),
    // image: "./images/menu.jpg",
  }
]

Enviar sonido

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
audio Camino / Objeto Ruta u objeto devuelto por remoteAudio() funcion.

Ejemplo

[
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    audio: remoteAudio("https://remote-server.com/audio.mp3"),
    // audio: "./audios/audio.mp3",
  }
]

Reenviar mensaje

Propiedad Tipo Descripción
id Entero Responder id se utiliza para enlazar con parent
parent Entero Id del padre de respuesta o matriz de ids [2, 3]. Si no tiene padre es 0 por defecto
pattern RegExp Expresión regular para hacer coincidir en minúsculas
message Cadena Responder mensaje de texto
forward Cadena Número donde se reenvía el mensaje

Ejemplo

[
    {
    id: 1,
    parent: 0,
    pattern: /forward/,
    message: "Text to forward",
    forward: "[email protected]", // forward this message to this number
  }
]

ayudantes

Ayudante Devolver Descripción
buttons(buttonTexts) Array Generar botones
remoteTxt(url) Cadena Devolver un archivo TXT remoto
remoteJson(url) JSON Devolver un archivo JSON remoto
remoteImg(url) Objeto Devolver un archivo de imagen remoto
remoteAudio(url) Objeto Devolver un archivo de audio remoto
list(listRows) Array Generar lista
inp(id, parents) Cadena Devolver cadena de entrada por ID de respuesta. Usar antes de Responder, afterReply y beforeForward
med(id, parents) Medios de comunicación / nulo Medios de devolución ({buffer, extensión}) por ID de respuesta. Usar antes de Responder, afterReply y beforeForward

Manos

Propiedad Tipo Descripción
beforeReply(from, input, output, parents, media) Función Inyectar código personalizado antes de una respuesta
afterReply(from, input, parents, media) Función Inyectar código personalizado después de una respuesta
beforeForward(from, forward, input, parents, media) Función Inyectar código personalizado antes de un reenvío

Bucles

Propiedad Tipo Descripción
goTo(from, input, output, parents, media) Función Debería devolver la identificación de respuesta a dónde saltar
clearParents booleano Borrar datos de padres, usar con ir a()

Panel de control HTTP

Con el panel de control puedes iniciar sesión, comenzar, detener o reiniciar el bot y monitorear los registros.

Establecer su username y password para acceder a su panel de control en archivo ./src/config.js

export const chatbotOptions = {
  httpCtrl: {
    port: 3000, // httpCtrl port (http://localhost:3000/)
    username: "admin",
    password: "chatbot"
  }
};

Use un proxy inverso nginx para exponer públicamente el panel de control http (ejemplo de configuración).

Http Control Panel

Ejemplos

Edita tu archivo ./src/conversations/conversation.js y crea tu flujo de trabajo de conversación personalizado.

Ejemplo 1

doc/examples/conversation1.js

import { buttons } from "../helpers";

/**
 * Chatbot conversation flow
 * Example 1
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /hello|hi|howdy|good day|good morning|hey|hi-ya|how are you|how goes it|howdy\-do/,
    message: "Hello! Thank you for contacting me, I am a Chatbot 🤖 , we will gladly assist you.",
    description: "Can I help with something?",
    buttons: buttons([
      "Website",
      "Linkedin",
      "Github",
      "Donate",
      "Leave a Message",
    ]),
  },
  {
    id: 2,
    parent: 1, // Relation with id: 1
    pattern: /website/,
    message: "Visit my website and learn more about me!",
    link: "https://jordifernandes.com/",
    end: true,
  },
  {
    id: 3,
    parent: 1, // Relation with id: 1
    pattern: /linkedin/,
    message: "Visit my LinkedIn profile!",
    link: "https://www.linkedin.com/in/jfadev",
    end: true,
  },
  {
    id: 4,
    parent: 1, // Relation with id: 1
    pattern: /github/,
    message: "Check my Github repositories!",
    link: "https://github.com/jfadev",
    end: true,
  },
  {
    id: 5,
    parent: 1, // Relation with id: 1
    pattern: /donate/,
    message: "A tip is always good!",
    link: "https://jordifernandes.com/donate/",
    end: true,
  },
  {
    id: 6,
    parent: 1, // Relation with id: 1
    pattern: /leave a message/,
    message: "Write your message, I will contact you as soon as possible!",
  },
  {
    id: 7,
    parent: 6, // Relation with id: 6
    pattern: /.*/, // Match with all text
    message: "Thank you very much, your message will be sent to Jordi! Sincerely the Chatbot 🤖 !",
    end: true,
  },
];

Ejemplo 2

doc/examples/conversation2.js

import { buttons, remoteTxt, remoteJson } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 2
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/,
    message: "Hello! I am a Delivery Chatbot.",
    description: "Choice one option!",
    buttons: buttons([
      "See today's menu?",
      "Order directly!",
      "Talk to a human!",
    ]),
  },
  {
    id: 2,
    parent: 1, // Relation with id: 1
    pattern: /menu/,
    message: remoteTxt(`${customEndpoint}/menu.txt`),
    // message: remoteJson(`${customEndpoint}/menu.json`)[0].message,
    end: true,
  },
  {
    id: 3,
    parent: 1, // Relation with id: 1
    pattern: /order/,
    message: "Make a order!",
    link: `${customEndpoint}/delivery-order.php`,
    end: true,
  },
  {
    id: 4,
    parent: 1, // Relation with id: 1
    pattern: /human/,
    message: "Please call the following WhatsApp number: +1 206 555 0100",
    end: true,
  },
];

Ejemplo 3

doc/examples/conversation3.js

import fetch from "sync-fetch";
import { remoteImg } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 3
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Hello! I am a Delivery Chatbot. Send a menu item number!",
  },
  {
    id: 2,
    parent: 0, // Same parent (send reply id=1 and id=2)
    pattern: /.*/, // Match all
    image: remoteImg(`${customEndpoint}/menu.jpg`),
  },
  {
    id: 3,
    parent: 1, // Relation with id: 1
    pattern: /\d+/, // Match any number
    message: "You are choice item number $input. How many units do you want?", // Inject input value ($input) in message
  },  
  {
    id: 4,
    parent: 2, // Relation with id: 2
    pattern: /\d+/, // Match any number
    message: "You are choice $input units. How many units do you want?",
    // Inject custom code or overwrite output 'message' property before reply
    beforeReply(from, input, output, parents) {
      // Example check external api and overwrite output 'message'
      const response = fetch(
        `${customEndpoint}/delivery-check-stock.php/?item=${input}&qty=${parents.pop()}`
      ).json();
      return response.stock === 0
        ? "Item number $input is not available in this moment!"
        : output;
    },
    end: true,
  },
];

Ejemplo 4

doc/examples/conversation4.js

import { remoteImg } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 4
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Image local and remote! Send [local] or [remote]",
  },
  {
    id: 2,
    parent: 1,
    pattern: /local/, 
    image: "./images/image1.jpg",
    end: true,
  },
  {
    id: 3,
    parent: 1,
    pattern: /remote/, 
    image: remoteImg(`${customEndpoint}/image1.jpg`),
    end: true,
  },
];

Ejemplo 5

doc/examples/conversation5.js

import { remoteImg } from "../helpers";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 5
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Audio local and remote! Send [local] or [remote]",
  },
  {
    id: 2,
    parent: 1,
    pattern: /local/, 
    audio: "./audios/audio1.mp3",
    end: true,
  },
  {
    id: 3,
    parent: 1,
    pattern: /remote/, 
    audio: remoteAudio(`${customEndpoint}/audio1.mp3`),
    end: true,
  },
];

Ejemplo 6

doc/examples/conversation6.js

import fetch from "sync-fetch";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 6
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "",
    // Inject custom code or overwrite output 'message' property before reply
    beforeReply(from, input, output, parents) {
      // Get reply from external api and overwrite output 'message'
      const response = fetch(`${customEndpoint}/ai-reply.php/?input=${input}`).json();
      return response.message;
    },
    end: true,
  },
];

Ejemplo 7

doc/examples/conversation7.js

import fetch from "sync-fetch";

const customEndpoint = "https://jordifernandes.com/examples/chatbot";

/**
 * Chatbot conversation flow
 * Example 7
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "Hello!",
    // Inject custom code after reply
    afterReply(from, input, parents) {
      // Send WhatApp number to external api
      const response = fetch(`${customEndpoint}/number-lead.php/`, {
        method: "POST",
        body: JSON.stringify({ number: from }),
        headers: { "Content-Type": "application/json" },
      }).json();
      console.log('response:', response);
    },
    end: true,
  },
];

Ejemplo 8

doc/examples/conversation8.js

import { buttons, inp } from "../helpers";

/**
 * Chatbot conversation flow
 * Example 8
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/,
    message: "Choice one option",
    description: "choice option:",
    buttons: buttons(["Option 1", "Option 2"]),
  },
  {
    id: 2,
    parent: 1,
    pattern: /.*/,
    message: "We have received your request. Thanks.\n\n",
    beforeReply(from, input, output, parents) {
      output += `Your option: ${inp(2, parents)}`;
      return output;
    },
    forward: "[email protected]", // default number or empty
    beforeForward(from, forward, input, parents) { // Overwrite forward number
      switch (inp(2, parents)) { // Access to replies inputs by id
        case "option 1":
          forward = "[email protected]";
          break;
        case "option 2":
          forward = "[email protected]";
          break;
        default:
          forward = "[email protected]";
          break;
      }
      return forward;
    },
    end: true,
  },
];

Ejemplo 9

doc/examples/conversation9.js

/**
 * Chatbot conversation flow
 * Example 9
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /.*/, // Match all
    message: "",
    // Inject custom code or overwrite output 'message' property before reply
    beforeReply(from, input, output, parents, media) {
      if (media) {
        console.log("media buffer", media.buffer);
        return `You send file with .${media.extension} extension!`;
      } else {
        return "Send a picture please!";
      }
    },
    end: true,
  },
];

Ejemplo 10

doc/examples/conversation10.js

import { promises as fs } from "fs";

/**
 * Chatbot conversation flow
 * Example 10
 */
 export default [
  {
    id: 1,
    parent: 0,
    pattern: /\b(?!photo\b)\w+/, // different to photo
    message: `Write "photo" for starting.`,
  },
  {
    id: 2,
    parent: [0, 1],
    pattern: /photo/,
    message: `Hi I'm a Chatbot, send a photo(s)`,
  },
  {
    id: 3,
    parent: 2,
    pattern: /\b(?!finalize\b)\w+/, // different to finalize
    message: "",
    async beforeReply(from, input, output, parents, media) {
      const uniqId =  (new Date()).getTime();
      // Download media
      if (media) {
        const dirName = "./downloads";
        const fileName = `${uniqId}.${media.extension}`;
        const filePath = `${dirName}/${fileName}`;
        await fs.mkdir(dirName, { recursive: true });
        await fs.writeFile(filePath, await media.buffer);
        return `Photo download successfully! Send another or write "finalize".`;
      } else {
        return `Try send again or write "finalize".`;
      }
    },
    goTo(from, input, output, parents, media) {
      return 3; // return to id = 3
    },
  },
  {
    id: 4,
    parent: 2,
    pattern: /finalize/,
    message: "Thank's you!",
    end: true,
  },
];

Ejemplo 11

doc/examples/conversation11.js

import { inp, med } from "../helpers";
import { promises as fs } from "fs";

const menu = "Menu:\n\n" +
  "1. Send Text\n" +
  "2. Send Image\n";

/**
 * Chatbot conversation flow
 * Example 11
 */
export default [
  {
    id: 1,
    parent: 0,
    pattern: /\/admin/,
    from: "[email protected]", // only respond to this number
    message: menu
  },
  {
    id: 2,
    parent: [1, 5],
    pattern: /.*/,
    message: "",
    async beforeReply(from, input, output, parents, media) {
      switch (input) {
        case "1":
          return `Write your text:`;
        case "2":
          return `Send your image:`;
      }
    },
  },
  {
    id: 3,
    parent: 2,
    pattern: /.*/,
    message: `Write "/save" to save or cancel with "/cancel".`,
  },
  {
    id: 4,
    parent: 3,
    pattern: /\/save/,
    message: "",
    async beforeReply(from, input, output, parents, media) {
      let txt = "";
      let img = null;
      let filePath = null;
      const type = inp(2, parents);
      if (type === "1") {
        txt = inp(3, parents);
      } else if (type === "2") {
        img = med(3, parents); // media from parent replies
      }
      if (img) {
        const uniqId = new Date().getTime();
        const dirName = ".";
        const fileName = `${uniqId}.${img.extension}`;
        filePath = `${dirName}/${fileName}`;
        await fs.writeFile(filePath, await img.buffer);
      } else {
        const uniqId = new Date().getTime();
        const dirName = ".";
        const fileName = `${uniqId}.txt`;
        await fs.writeFile(filePath, txt);
      }
      return `Ok, text or image saved. Thank you very much!`;
    },
    end: true,
  },
  {
    id: 5,
    parent: 3,
    pattern: /\/cancel/,
    message: menu,
    goTo(from, input, output, parents, media) {
      return 2;
    },
    clearParents: true, // reset parents
  },
];

Avanzado

Múltiples flujos de conversación

Editar ./src/main.js archivo.

import { session } from "./core";
import info from "./conversations/info";
import delivery from "./conversations/delivery";

session("chatbotSession", info);
session("chatbotSession", delivery);

Multiples cuentas

Editar ./src/main.js archivo.

import { session } from "./core";
import commercial from "./conversations/commercial";
import delivery from "./conversations/delivery";

session("commercial_1", commercial);
session("commercial_2", commercial);
session("delivery", delivery);

Editar ./src/httpCtrl.js archivo.

import { httpCtrl } from "./core";

httpCtrl("commercial_1", 3000);
httpCtrl("commercial_2", 3001);
httpCtrl("delivery", 3002);

Acceso al cliente de Venom

Editar ./src/main.js archivo.

import { session } from "./core";
import conversation from "./conversations/conversation";

// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);

Programar trabajos

Editar ./src/main.js archivo.

import schedule from "node-schedule"; // Add node-schedule in your project
import { session, log } from "./core";
import { jobsOptions } from "./config";
import conversation from "./conversations/conversation";

// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);

const job1 = schedule.scheduleJob(
  jobsOptions.job1.rule, // "*/15 * * * *"
  async () => {
    // custom logic example
    await chatbot.sendText("[email protected]", "test");
  }
);

Pruebas

Pruebas unitarias escribe con Es

$ yarn test

Pruebe su estructura de matriz de flujo de conversación con conversación.test.js archivo como ejemplo.

$ yarn test src/conversations/conversation  

Solución de problemas

Atención: No inicie sesión en whatsapp web con la misma cuenta que usa el chatbot. Esto hará que el chatbot no pueda escuchar los mensajes..

Atención: Necesitas una cuenta de whatsapp para el chatbot y otra cuenta para poder hablar con él.

Repositorio

🤖 Con este micro framework node.js usando Venom Bot debajo del capó, puedes crear fácilmente un Chatbot de WhatsApp. Solo necesitarás editar tu flujo de conversación en un solo archivo.
https://github.com/jfadev/jfa-whatsapp-chatbot
8 tenedores.
39 estrellas.
1 problemas abiertos.

commits recientes:
  • Actualizar veneno 5.0.1, jfadev
  • Actualizar veneno 5.0.0, jfadev
  • documento ficticio, jfadev
  • Agregar med de las propiedades y correcciones de clearParents, jfadev
  • Reparar, jfadev

: Bot, bot conversacional, Docker, Framework, JS, Node.js, Whatsapp

Servicios

  • Excel2chatGPT $10.00
  • Bot Tok $45.00
  • Corrección de errores en tu aplicación PHP Symfony $70.00 / hora
  • Corrección de errores Wordpressen su sitio de $70.00 / hora
  • Automatización de tareas usando Node.js $70.00 / hora

Blog

  • Cómo pagar con una tarjeta bancaria en Cryptomus
  • Guía completa para principiantes de Bot Tok: Comandos de terminal explicados
  • Mejor sitio para obtener vistas en TikTok
  • Jfa Whatsapp chatbot
  • TikTok Bot

Explorar

  • Gratis 10 Me gusta
  • Vistas gratuitas de 2K TikTok
  • Gratis 100 Favoritos de Tik Tok
  • Gratis 300 Acciones de TikTok
  • Comprar vistas de TikTok
  • Gratis 100 Me gusta en Instagram
Twitter
LinkedIn
YouTube
GitHub

© 2013-2025 Jordi Fernandes Alves (@jfadev)