Com isso node.js micro estrutura usando Bot Venom sob o capô, você pode facilmente criar um WhatsApp Chatbot 🤖 . Você só precisará editar seu fluxo de conversa em um único arquivo.
Começando
- Crie um novo repositório de este modelo
- Instalar no seu ambiente de desenvolvimento
- configurar porta(s), credenciais, etc
- Escreva o seu fluxo de conversa
- Começar
Instalar
Janela de encaixe
Requisitos: janela de encaixe
Construir e executar com Dockerfile
$ docker build -t wchatbot .
$ docker run --name wchatbot -p 3000:3000 -v /your_project_absolute_path/src:/wchatbot/src wchatbot
ou Construa e execute com o Docker Compose
$ docker-compose build
$ docker-compose up
Visite http://host local:3000 e brinque com seu chatbot!
Máquina virtual
Requisitos: nodejs (Mais recente maintenance LTS versão), yarn (ou npm), pm2, chrome/chromium
Use um nginx proxy reverso para expor publicamente o painel de controle http (exemplo de configuração).
$ yarn install
Inicie o chatbot e o painel de controle http
$ yarn start
$ yarn http-ctrl:start
Visite http://host local:3000 e brinque com seu chatbot!
Máquina local
Requisitos: nodejs (Mais recente maintenance LTS versão), yarn (ou npm), chrome/chromium
$ yarn install
Inicie o chatbot e o painel de controle http
$ yarn http-ctrl:dev:detach
$ yarn dev
Visite http://host local:3000 e brinque com seu chatbot!
Configuração
Editar ./src/config.js
arquivo
Basic
export const chatbotOptions = {
httpCtrl: {
port: 3000, // httpCtrl port (http://localhost:3000/)
username: "admin", // httpCtrl auth login
password: "chatbot",
},
};
Advançado
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
Janela de encaixe
Controles do chatbot
$ docker exec wchatbot yarn start
$ docker exec wchatbot yarn stop
$ docker exec wchatbot yarn restart
$ docker exec wchatbot yarn reload
Controles do Painel de Controle 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 do chatbot
$ yarn start
$ yarn stop
$ yarn restart
$ yarn reload
Controles do Painel de Controle HTTP
$ yarn http-ctrl:start
$ yarn http-ctrl:stop
$ yarn http-ctrl:restart
$ yarn http-ctrl:reload
Máquina local
Direto no seu sistema operacional sem Docker
chatbot
$ yarn dev
$ yarn dev:detach
Iniciar Painel de Controle HTTP
$ yarn http-ctrl:dev
$ yarn http-ctrl:dev:detach
Sessões
Sessões e tokens de autenticação são gravados ./tokens
pasta.
Histórico
Os logs são gravados ./logs
pasta.
Atenção: console.log
e http-ctrl-console.log
apenas escreva em ./logs
pasta com yarn dev:detach
e yarn http-ctrl:dev:detach
de outra forma gerenciado por pm2
.
Janela de encaixe
chatbot
$ docker exec wchatbot yarn log
Painel de controle HTTP
$ docker exec wchatbot yarn http-ctrl:log
Conversas
$ docker exec wchatbot yarn conversations
Máquina virtual
chatbot
$ yarn log
Painel de controle HTTP
$ yarn http-ctrl:log
Conversas
$ yarn conversations
Máquina local
chatbot
$ yarn log:dev
Painel de controle HTTP
$ yarn log:http-ctrl:dev
Conversas
$ yarn conversations
Fluxo da conversa
Editar ./src/conversations/conversation.js
arquivo.
O fluxo da conversa é uma matriz de objetos de resposta ordenados.
Uma resposta só é acionada se for parent
(pode ser um número inteiro ou uma matriz)
é igual ao id
da resposta anterior.
Para indicar que uma resposta é o fim da conversa, adicione a seguinte propriedade:
Propriedade | Tipo | Descrição |
---|---|---|
end |
boleano | O fim da conversa |
Você pode proteger para que apenas um número ou uma lista de números sejam atendidos com:
Propriedade | Tipo | Descrição |
---|---|---|
from |
Corda / Array | Atenda apenas este ou estes números |
Uma resposta precisa necessariamente das seguintes propriedades:
Tipos de respostas
Mande mensagem
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
message |
Corda | Responder mensagem de texto |
Exemplo
[
{
id: 1,
parent: 0,
pattern: /.*/, // Match with all text
message: "Hi I am a Chatbot!",
}
]
Enviar Botões
Atenção: No momento não está funcionando!.
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
message |
Corda | Responder mensagem de texto |
description |
Corda | Responder legenda do texto |
buttons |
Array | Objeto de botão, Olhe para o exemplo |
Exemplo
[
{
id: 1,
parent: 0,
pattern: /.*/,
message: "Hello!",
description: "Can I help with something?",
buttons: buttons([
"Website",
"LinkedIn",
"Github",
]),
}
]
Enviar Lista
Atenção: No momento não está funcionando!.
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
message |
Corda | Responder mensagem de texto |
description |
Corda | Responder legenda do texto |
button |
Corda | Texto do botão de lista |
list |
Array | Objeto de lista, Olhe para o exemplo |
Exemplo
[
{
id: 1,
parent: 0,
pattern: /other country/,
message: "Choice one country",
description: "Choice one option!",
button: "Countries list",
list: list([
"Argentina",
"Belize",
"Bolivia",
]),
},
]
Enviar Link
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
message |
Corda | Responder mensagem de texto |
link |
Corda | URL da visualização do link gerado |
Exemplo
[
{
id: 2,
parent: 1, // Relation with id: 1
pattern: /github/,
message: "Check my Github repositories!",
link: "https://github.com/jfadev",
}
]
Enviar imagem
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
image |
Caminho / Objeto | Caminho ou Objeto retornado por remoteImg() função |
Exemplo
[
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
image: remoteImg("https://remote-server.com/menu.jpg"),
// image: "./images/menu.jpg",
}
]
Enviar áudio
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
audio |
Caminho / Objeto | Caminho ou Objeto retornado por remoteAudio() função. |
Exemplo
[
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
audio: remoteAudio("https://remote-server.com/audio.mp3"),
// audio: "./audios/audio.mp3",
}
]
Encaminhar mensagem
Propriedade | Tipo | Descrição |
---|---|---|
id |
inteiro | Resposder id é usado para ligar com parent |
parent |
inteiro | Id do pai de resposta ou matriz de ids [2, 3] . Se não tem pai, é 0 por padrão |
pattern |
RegExp | Expressão regular para corresponder em minúsculas |
message |
Corda | Responder mensagem de texto |
forward |
Corda | Número para onde a mensagem é encaminhada |
Exemplo
[
{
id: 1,
parent: 0,
pattern: /forward/,
message: "Text to forward",
forward: "[email protected]", // forward this message to this number
}
]
ajudantes
Ajudante | Retornar | Descrição |
---|---|---|
buttons(buttonTexts) |
Array | Gerar botões |
remoteTxt(url) |
Corda | Retornar um arquivo TXT remoto |
remoteJson(url) |
JSON | Retornar um arquivo JSON remoto |
remoteImg(url) |
Objeto | Retornar um arquivo de imagem remoto |
remoteAudio(url) |
Objeto | Retornar um arquivo de áudio remoto |
list(listRows) |
Array | Gerar lista |
inp(id, parents) |
Corda | Retorna a string de entrada por id de resposta. Use antes de responder, afterReply e beforeForward |
med(id, parents) |
Mídia / nulo | Devolução de mídia ({amortecedor, extensão}) por id de resposta. Use antes de responder, afterReply e beforeForward |
ganchos
Propriedade | Tipo | Descrição |
---|---|---|
beforeReply(from, input, output, parents, media) |
Função | Injetar código personalizado antes de uma resposta |
afterReply(from, input, parents, media) |
Função | Injetar código personalizado após uma resposta |
beforeForward(from, forward, input, parents, media) |
Função | Injetar código personalizado antes de um encaminhamento |
rotações
Propriedade | Tipo | Descrição |
---|---|---|
goTo(from, input, output, parents, media) |
Função | Deve retornar o id da resposta para onde pular |
clearParents |
boleano | Limpar dados dos pais, usar com goTo() |
Painel de Controle Http
Com o painel de controle você pode fazer login, começar, pare ou reinicie o bot e monitore os logs.
Defina seu username
e password
para acessar seu painel de controle em arquivo ./src/config.js
export const chatbotOptions = {
httpCtrl: {
port: 3000, // httpCtrl port (http://localhost:3000/)
username: "admin",
password: "chatbot"
}
};
Use um proxy reverso nginx para expor publicamente o painel de controle http (exemplo de configuração).
Exemplos
Edite seu arquivo ./src/conversations/conversation.js
e crie seu fluxo de trabalho de conversa personalizado.
Exemplo 1
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,
},
];
Exemplo 2
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,
},
];
Exemplo 3
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,
},
];
Exemplo 4
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,
},
];
Exemplo 5
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,
},
];
Exemplo 6
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,
},
];
Exemplo 7
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,
},
];
Exemplo 8
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,
},
];
Exemplo 9
/**
* 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,
},
];
Exemplo 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,
},
];
Exemplo 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
},
];
Advançado
Múltiplos Fluxos de Conversa
Editar ./src/main.js
arquivo.
import { session } from "./core";
import info from "./conversations/info";
import delivery from "./conversations/delivery";
session("chatbotSession", info);
session("chatbotSession", delivery);
Múltiplas contas
Editar ./src/main.js
arquivo.
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
arquivo.
import { httpCtrl } from "./core";
httpCtrl("commercial_1", 3000);
httpCtrl("commercial_2", 3001);
httpCtrl("delivery", 3002);
Acesso ao cliente Venom
Editar ./src/main.js
arquivo.
import { session } from "./core";
import conversation from "./conversations/conversation";
// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);
Agendar trabalhos
Editar ./src/main.js
arquivo.
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");
}
);
teste
Testes de unidade escrevem com É
$ yarn test
Teste sua estrutura de matriz de fluxo de conversação com conversa.teste.js arquivo como exemplo.
$ yarn test src/conversations/conversation
Solução de problemas
Atenção: Não entre no whatsapp web com a mesma conta que o chatbot usa. Isso fará com que o chatbot não consiga ouvir as mensagens.
Atenção: Você precisa de uma conta whatsapp para o chatbot e outra conta para poder falar com ele.