Como criar um chatbot para o WhatsApp

Como criar um chatbot para o WhatsApp

Desenvolver um chatbot para WhatsApp não precisa ser um bicho de sete cabeças, mas você precisa ficar atento já que a WhatsApp Business API é de uso fechado e há apenas duas formas de ter acesso.

A primeira é por meio de solicitação direta ao Facebook, o que pode ser um pouco desafiador já que pouquíssimas requisições são aceitas pela empresa. Já a segunda é mais viável: contratar uma empresa provedora oficial de confiança do WhatsApp. Dentre as opções, destacamos a brasileira Zenvia.

A empresa é provedora oficial permite ao usuário criar uma conta gratuita para utilizar serviços como a API do WhatsApp.

Após a criação da conta, você deve seguir o seguinte fluxo:

 

Menu superior  > Produtos > Desenvolvedores > Sandbox > Criar novo > Escolha o canal "WhatsApp" > Próximo

 

Um QR Code aparecerá na tela com o celular e a partir dele será gerado um link que abrirá o seu aplicativo do WhatsApp, contendo o contato da Zenvia e uma mensagem previamente escrita. Basta enviá-la para configurar a SandBox e registrar seu número de telefone para o ambiente de testes:

 

Criando a aplicação Node.js

1 - Instale o Node no seu computador (de preferência a versão LTS);

1.1- opcional - Instale o yarn. O node possui um gerenciador de pacotes padrão chamado npm (Node Package Manager), porém existe um gerenciador diferente chamado yarn.

No restante deste artigo, haverão as opções de instalação para ambos gerenciadores de pacote como no passo a seguir.

2 - Inicie o projeto criando o package.json com o seguinte comando no terminal:

npm init

yarn init

 

3 - Instale o ts-node, ts-node-dev e o typescript como dependências de desenvolvimento:

npm i ts-node ts-node-dev typescript -D

yarn add ts-node ts-node-dev typescript -D

 

3.1 - Instale o arquivo de configuração do typescript:

npx typescript --init

 

4 - Abra o arquivo package.json e insira o script para iniciar o servidor assim como na área grifada em cinza:

{
  "name"
: "WhatsAppChatbot",
  "version": "1.0.0",
  "description": "This project aims to create an example of creating a chatbot for WhatsApp.",
  "main": "index.js",
  "scripts": {
    "dev": "ts-node-dev index.ts"
  },
  "author"
: "Henry Kimura",
  "license": "MIT",
  "devDependencies": {
    "ts-node"
: "^9.0.0",
    "ts-node-dev": "^1.0.0",
    "typescript": "^4.0.5"
  }
}

 

Criando chatbot e enviando mensagem ao celular cadastrado

1 - Instale a dependência request-promise para o Node:

npm i request-promise
yarn add request-promise

 

2 - Após ter configurado seu número de testes com a SandBox anteriormente, ele aparecerá na aba de contatos:

 

3 - Copie o código de exemplo abaixo e substitua SEU-TOKEN pelo seu token gerado pela plataforma Zenvia, substitua o REMETENTE e o DESTINATÁRIO também:

import post from 'request-promise'

post({
    uri
: 'https://api.zenvia.com/v2/channels/whatsapp/messages',
    headers
: {
      
 'X-API-TOKEN': 'SEU_TOKEN',
    },
    body
: {
        
from: 'REMETENTE',
        to
: 'DESTINATARIO',
        contents
: [
            {
                type
: 'text',
                text
: 'Lembre-se que sua consulta está marcada para às 15:00',
            },
        ],
    },
    json
: true,
})
    .
then((response) => {
        console.
log('Response:', response)
    })
    .
catch((error) => {
        console.
log('Error:', error)
    })

 

As informações solicitadas podem ser encontradas nesta tela:

 

4 - Crie um arquivo index.ts, cole o código copiado anteriormente e salve o arquivo.

5 - Abra o terminal na pasta do projeto e execute o script criado anteriormente no package.json:

npm run dev

yarn dev

 

6 - Se estiver tudo certo, você receberá a mensagem configurada no código:

Criando exemplo de recebimento de mensagem

Até o momento apenas criamos um exemplo para enviar uma mensagem definida. Agora vamos para um exemplo mais divertido onde o usuário manda uma mensagem e recebe uma resposta.

Primeiro vamos precisar configurar um webhook, que é uma forma de recebimento de informações quando um evento acontece. No nosso caso, a API da Zenvia irá enviar um objeto JSON com informações sobre uma mensagem recebida pela API.

Configurando webhook

1- Vamos começar instalando o express para lidar com o recebimento das requisições e o body-parser para lidar melhor com o manuseio de objetos:

npm i express
npm i @types/express -D
npm i body-parser

yarn add express
yarn add @types/express -D
yarn add body-parser

 

2- Renomeie o arquivo index.ts para sendMessage.ts e crie um novo index.ts com este código:

import express, { Request, Response } from 'express'
import bodyParser from 'body-parser'

// Inicializa o express e define uma porta
const app = express()
const PORT = 3000

// Indica para o express usar o JSON parsing do body-parser
app.
use(bodyParser.json())

app.post('/hook', (req: Request, res: Request) => {
    console.
log(req.body) // Exibe no console da aplicação o objeto da mensagem
    res.
status(200).end() // Responde quem solicitou nosso webhook com status 200
})

// Inicia o express na porta definida anteriormente
app.
listen(PORT, () => console.log(`Server running on port ${PORT}`))

 

3 - Precisamos tornar nosso webhook visível para que a API da Zenvia possa nos enviar as informações da mensagem recebida, para isto vamos utilizar o ngrok. Faça login no site, baixe a versão para Windows, extraia o zip, execute a aplicação (abrirá um terminal), autentique-se digitando ngrok authtoken SEU_TOKEN (seu token está na primeira tela do seu dashboard) e exponha a porta da aplicação (3000) digitando ngrok http 3000

 

Configurando a Zenvia para enviar dados ao nosso webhook

1- Após expor a porta da sua aplicação copie o link que aparecerá no terminal:

 

2 - Cole seu link no campo "Message Webhook URL", coloque /hook (rota criada anteriormente no nosso webhook) ao final do URL na aba "Subscription" e clique em "Salvar":

3 - Mande uma mensagem de teste pelo WhatsApp e verifique se seu webhook recebeu a informação:

O conteúdo da mensagem estará dentro do corpo da requisição, desta forma: req.body.message.contents[0].text

4 - Agora que estamos recebendo a mensagem, é preciso de algo para enviar uma resposta. Vamos criar um sistema bem simples onde o chatbot recebe um "Oi" e ele responde com um "Olá {nome do usuário}! Como posso ajudar?". Para começar, precisamos alterar o arquivo sendMessage.ts. Do jeito que ele está, este arquivo apenas envia uma mensagem específica ao nosso contato, vamos torná-lo em uma função dinâmica e exportável.

Vamos criar uma função exportável para mandar mensagem dinamicamente no sendMessage.ts, onde o parâmetro message da função, terá o conteúdo da mensagem que será enviada ao usuário:

import post from 'request-promise'

const sendMessage = (message) => {
    post({
        uri
: 'https://api.zenvia.com/v2/channels/whatsapp/messages',
        headers: {
            'X-API-TOKEN': 'SEU_TOKEN',
        },
        body
: {
            from: 'REMETENTE',
            to: 'DESTINATARIO',
            contents: [
                {

                    type: 'text',
                    text: message,
                },
            ],
        },
        json
: true,
    })
        .then((response) => {
            console.log('Response:', response)
        })
        .catch((error) => {
            console.log('Error:', error)
        })
}

export default sendMessage

 

Adicione esta linha de importação no index.ts:

import express from 'express'
import bodyParser from 'body-parser'
import sendMessage from './sendMessage'

//...

 

Por fim, vamos alterar a rota /hook no arquivo index.ts

 //...

app.post('/hook', (req: Request, res: Response) => {
    
const contact = req.body.message.visitor // Armazena em uma variável quem mandou a mensagem
    const message = req.body.message.contents[0].text // Armazena em uma variável a mensagem

    if (message.toLowerCase() === 'oi') { // Verifica se a mensagem enviada foi um "Oi"
        sendMessage(`Olá ${contact.firstName}! Como posso te ajudar?`)
    }
else { // Se não, mande a seguinte mensagem
        sendMessage('Me desculpe, não entendi.')
    }

    res.status(200).end() // Responde quem solicitou nosso webhook com status 200
})

//...

 

Nosso chatbot agora está funcionando, podendo receber uma mensagem e respondê-la

Com isso tudo apresentado você já consegue colher e enviar mensagens. A partir daqui a criatividade é o limite para a criação do seu chatbot.

Agora vamos criar um chatbot para ser o seu Assistente Pessoal que consiga guardar informações e lembrar o usuário destas informações em um horário específico, liste todas as informações guardadas e consiga removê-las também.

Exemplo de criação de um Assistente Pessoal

Agora a nossa aplicação vai ficar um pouco mais complexa e interessante, necessitando de um banco de dados para armazenar informações.

Para fins de aprendizado, vamos utilizar o SQLite pois é muito simples de ser configurado e não precisa ser instalado na sua máquina. Também utilizaremos um query builder chamado Knex.js, onde podemos contruir consultas em javascript e posteriormente podemos alterar o banco de dados que as consultas permanecerão as mesmas,

 

Configurando o ambiente

1 - Instale a dependência do SQLite

npm i sqlite3 

yarn add sqlite3

 

2 - Instale a dependência o Knex.js

npm install knex --save

yarn add knex

 

3 - Crie o arquivo de configuração knex com o nome knexfile.ts

import path from 'path' // lida com caminhos de arquivos

module.exports = {
    client
: 'sqlite3',
    connection
: {
        filename
: path.resolve(__dirname, 'src', 'database', 'database.sqlite'),
    },
    migrations: {
        directory
: path.resolve(__dirname, 'src', 'database', 'migrations'),
    },
    useNullAsDefault:
true,
}

 

4 - Crie uma pasta src, dentro dela cria outra chamada database e crie o arquivo connection.ts

import knex from 'knex'
import path from 'path'

const db = knex({
    client
: 'sqlite3',
    connection
: {
        filename
: path.resolve(__dirname, 'database.sqlite')
    },
    useNullAsDefault:
true,
})

export default db

 

5 - Dentro da pasta database, crie outra chamada migrations e crie o arquivo 00_create_appointments.ts

import Knex from 'knex'

async function up(knex: Knex) {
    
return knex.schema.createTable('appointments', (table) => {
        table.
increments('id').primary(),
            table.
string('description').notNullable(),
            table.
timestamp('datetime').notNullable()
    })
}

async function down(knex: Knex) {
    
return knex.schema.dropTable('appointments')
}

module.exports = { up, down }

 

5.1 - Em seguida, crie o banco de dados executando:

 npx knex --knexfile knexfile.ts migrate:latest 

 

Criando a interpretação de mensagens e o armazenamento de lembretes

1- Crie o arquivo PastaDoProjeto/src/functions/interpretsMessage.ts com a função que irá interpretar a mensagem recebida e direcionar para demais outras funções:

import sendMessage from './sendMessage'
import createAppointment from './createAppointment'

interface Contact { //formato do parâmetro contato
    name: string
    firstName
: string
    lastName
: string
}

const interpretsMessage = (contact: Contact, message: any) => {
    
switch (message.toLowerCase()) {
        
case 'ajuda': // Verifica se a mensagem enviada foi "ajuda"
            sendMessage(`
                
Olá ${contact.firstName}! Digite algum dos comandos para saber mais:\n \t*lembrete*
            `)
            
break
        case 'lembrete': // Verifica se a mensagem enviada foi "lembrete"
            sendMessage(
                
`Entendido. Mande uma mensagem com o seguinte formato:\n Me lembre de _____ dia __/__/____ às __:__`
            )

            break

        default: // Se não foi nenhuma das anteriores
            if (message.split(' ')[1] === 'lembre') { // Verifica se é para armazenar o compromisso
                const description = message.split('Me lembre de ')[1].split(' dia')[0] // Armazena a descrição em uma variável
                
const date = message.split('dia ')[1].split(' às')[0].split('/') // Armazena a data em uma variável
                const time = message.split('às ')[1] // Armazena o horário em uma variável
                
const when = `${date[2]}-${date[1]}-${date[0]} ${time}` // Junta a data e o horário em uma variável
                const appointment = { // Armazena a descrição e quando o deve ser enviado o lembrete do compromisso em um objeto
                    description,
                    when,
                }
                
createAppointment(appointment) // Chama a função de criar o compromisso no banco de dados (será criada logo a seguir)
            } else {
                
// Se não, mande a seguinte mensagem
                sendMessage(
                  
 'Me desculpe, não entendi. Digite *ajuda* para saber todos os comandos.'
                )
                
break
            }
    }
}

export default interpretsMessage

 

2 - No index.ts importe a função interpretsMessage, remova a importação da função sendMessage e altere a rota /hooks:

import express, { Request, Response } from 'express'
import bodyParser from 'body-parser'
import interpretsMessage from './src/functions/interpretsMessage'

// Inicializa o express e define uma porta
const app = express()
const PORT = 3000

// Indica para o express usar o JSON parsing do body-parser
app.use(bodyParser.json())

app.post('/hook', (req: Request, res: Response) => {
    
const contact = req.body.message.visitor // Armazena em uma variável quem mandou a mensagem
    const message = req.body.message.contents[0].text // Armazena em uma variável a mensagem

    interpretsMessage(contact, message)

    res.status(200).end() // Responde quem solicitou nosso webhook com status 200
})

// Inicia o express na porta definida anteriormente
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))

 

3 - Crie o arquivo PastaDoProjeto/src/functions/createAppointment.ts com a função de criação de um novo lembrete:

import db from '../database/connection' // Importação da conexão com o Banco de Dados

interface Appointment { // Formato do parâmetro appointment
    description: string
    when
: string
}

async function createAppointment(appointment: Appointment) {
    
const { description, when } = appointment // Coletando as informações do parâmetro

    await db('appointments').insert({ // Inserindo no banco de dados o compromisso
        description,
        when,
    })
}

export default createAppointment

Enviando o lembrete do compromisso

1- Vamos criar uma função que executa a cada 1 minuto e faz uma consulta ao banco de dados verificando se existe algum compromisso para aquele momento. Instale o node-cron e importe ele no index.ts para criarmos o laço

npm i node-cron
npm i @types/node-cron
-D

yarn add node-cron 
yarn add @types/node-cron
-D

 

import express, { Request, Response } from 'express'
import bodyParser from 'body-parser'
import interpretsMessage from './src/functions/interpretsMessage'
import cron from 'node-cron'

// Inicializa o express e define uma porta
const app = express()
const
PORT = 3000

// Indica para o express usar o JSON parsing do body-parser
app.use(bodyParser.json())

app.post('/hook', (req: Request, res: Response) => {
    
const contact = req.body.message.visitor // Armazena em uma variável quem mandou a mensagem
    const message = req.body.message.contents[0].text // Armazena em uma variável a mensagem

    interpretsMessage(contact, message)

    res.status(200).end() // Responde quem solicitou nosso webhook com status 200
})

cron.schedule('* * * * *', () => { // Laço que executa a cada 1 minuto
    checkAndSendReminder() // Função que será criada a seguir
})

// Inicia o express na porta definida anteriormente
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))

 

- Mova o arquivo sendMessage.ts para o diretório PastaDoProjeto/src/functions/

 

3 - Devemos criar a função que verifica no banco de dados se há um compromisso e mandar um lembrete. Crie o arquivo no seguinte caminho PastaDoProjeto/src/functions/checkAndSendAppointment.ts

import db from '../database/connection' // Importação da conexão com o Banco de Dados
import sendMessage from './sendMessage' // Importação da função de envio de mensagem

const fixZero = (datetime: string) => {
  
 //Função para corrigir 0 no horário
    if (datetime.split(':')[0].substr(-2, 1) === ' ') {
        
return `${datetime.substr(0, 11)}0${datetime.substr(11, 4)}`
    }
else {
        
return datetime
    }
}

export const checkAndSendReminder = async () => { // Função que verica a existência de compromisso e envia lembrete se houver
    const datetime = `${fixZero(new Date().toLocaleString('pt-BR'))}`.substr(0, 16) // Captura a data e o horário atual
    const appointments = await db('appointments').where('datetime', '=', datetime).select('*') // Busca no Banco de Dados se existe um compromisso agora

    if (appointments.length !== 0) { // Se houver algum compromisso envia uma mensagem de lembrete
        const message = `Lembre-se que você precisa: ${appointments.map(
            (appointment)
=> `\n*${appointment.description}*`
        )}`
        
sendMessage(message)
    }
}

 

4 - Vamos criar uma função que remove compromissos do banco de dados. Crie o arquivo  PastaDoProjeto/src/functions/removeAppointments.ts com o seguinte código:

import db from '../database/connection' // Importação da conexão do Banco de Dados
import sendMessage from './sendMessage' // Importação da função de envio de mensagem

async function removeAppointments(datetime: string, notificateUser: boolean) {
    
const removedAppointment = await db('appointments').where('datetime', datetime).del() // Tenta remover do banco de dados os compromissos na data e hora especificadas

    if (notificateUser === true && typeof removedAppointment === 'number') { // Caso seja para notificar o usuario
        if (removedAppointment === 1) {    // Verifica se foi removido apenas um compromisso
            
sendMessage('Lembrete apagado com sucesso.')
        }
        
if (removedAppointment > 1) {    // Verifica se foram removidos diversos compromissos
            
sendMessage('Lembretes apagados com sucesso.')
        }
    }
}

export default removeAppointments

 

5 - Para evitar que acumule informações desnecessárias, sempre que o usuário receber o lembrete, vamos apagar o compromisso no banco de dados. Para isto, vamos alterar o arquivo checkAndSendReminder.ts:

import db from '../database/connection' // Importação da conexão com o Banco de Dados
import sendMessage from './sendMessage' // Importação da função de envio de mensagem
import removeAppointments from './removeAppointments' // Importação da função de remoção de compromisso

const fixZero = (datetime: string) => {    //Função para corrigir 0 no horário
    if (datetime.split(':')[0].substr(-2, 1) === ' ') {
        
return `${datetime.substr(0, 11)}0${datetime.substr(11, 4)}`
    }
else {
        
return datetime
    }
}

export const checkAndSendReminder = async () => {    // Função que verica a existência de compromisso e envia lembrete se houver
    const datetime = `${fixZero(new Date().toLocaleString('pt-BR'))}`.substr(0, 16) // Captura a data e o horário atual
    const appointments = await db('appointments').where('datetime', '=', datetime).select('*') // Busca no Banco de Dados se existe um compromisso agora

    if (appointments.length !== 0) {        // Se houver algum compromisso envia uma mensagem de lembrete
        const message = `Lembre-se que você precisa: ${appointments.map(
            (appointment)
=> `\n*${appointment.description}*`
        )}`
        
sendMessage(message)
        removeAppointments(datetime,
false) // Remove o compromisso no banco de dados e não notifica o usuário
    }
}

 

6 - Vamos agora confirmar ao usuário quando um compromisso for agendado com sucesso. Altere o arquivo createAppointment.ts:

import db from '../database/connection'
import sendMessage from './sendMessage'

interface Appointment {
    description: string
    datetime: string
}

async function createAppointment(appointment: Appointment) {
    
const { description, datetime } = appointment

    const insertedAppointment = await db('appointments').insert({
        description,
        datetime,
    })
    
    
if (typeof insertedAppointment[0] === 'number') {
        
sendMessage('Compromisso agendado com sucesso.')
    }
}

export default createAppointment

 

7- Agora precisamos permitir que o usuário remova compromissos, para isto vamos alterar o arquivo interpretsMessage.ts

import sendMessage from './sendMessage'
import createAppointment from './createAppointment'
import removeAppointments from './removeAppointments'

interface Contact {
    name: string
    firstName: string
    lastName: string
}

const interpretsMessage = (contact: Contact, message: any) => {
    
switch (message.toLowerCase()) {
        
case 'ajuda':    // Verifica se a mensagem enviada foi "ajuda"
            sendMessage(`
                
Olá ${contact.firstName}! Digite algum dos comandos para saber mais:\n\t*lembrete*\n\t*esqueça*
            `)
            
break
        case 'lembrete':    // Verifica se a mensagem enviada foi "lembrete"
            sendMessage(
                
`Entendido. Mande uma mensagem com o seguinte formato:\n Me lembre de _____ dia __/__/____ às __:__`
            )
            
break
        case 'esqueça':    // Verifica se a mensagem enviada foi "esqueça"
            sendMessage(
                
`Entendido. Mande uma mensagem com o seguinte formato:\n Desmarque tudo no dia __/__/____ às __:__`
            )
            
break

        default: // Se não foi nenhuma das anteriores
            if (message.split(' ')[1] === 'lembre') { // Verifica se é para armazenar o compromisso
                const description = message.split('Me lembre de ')[1].split(' dia')[0] // Armazena a descrição em uma variável
                const date = message.split('dia ')[1].split(' às')[0].split('/') // Armazena a data em uma variável
                const time = message.split('às ')[1] // Armazena o horário em uma variável
                
const datetime = `${date[2]}-${date[1]}-${date[0]} ${time}` // Junta a data e o horário em uma variável
                const appointment = {
                    description,
                    datetime,
                }
                
createAppointment(appointment) // Chama a função de criar o compromisso no banco de dados
            }
else if (message.substr(0, 9) === 'desmarque') { // Verifica se é para remover o compromisso
                const date = message.split('dia ')[1].split(' às')[0].split('/')
                
const time = message.split('às ')[1]

                removeAppointments(`${date[2]}-${date[1]}-${date[0]} ${time}`, true)
            }
else {
              
 // Se não, mande a seguinte mensagem
                sendMessage('Digite *ajuda* para saber todos os comandos.')
                
break
            }
    }
}

export default interpretsMessage

 

8 - Teste e veja seu Assistente Pessoal funcionando:

 

9 - Vamos criar a função que liste todos os compromissos no arquivo indexAppointments.ts

import db from '../database/connection' // Importação da conexão do Banco de Dados
import sendMessage from './sendMessage' // Importação da função de envio de mensagem

const showDate = (datetime: string) => { // Altera a exibição da data e da hora
    const date_time = datetime.split(' ')
    
const date = date_time[0].split('-')

    return `${date[2]}/${date[1]}/${date[0]} às ${date_time[1]}`
}

async function indexAppointments() {
    
const indexedAppointment = await db('appointments').select('*') // Busca no banco de dados os compromissos na data e hora especificadas

    if (indexedAppointment.length !== 0) { // Verifica se encontrou dados
        const message = `*Seus compromissos são:*${indexedAppointment.map(
            (appointment)
=> `\n\t${showDate(appointment.datetime)} - ${appointment.description}`
        )}`
        
sendMessage(message) // Manda mensagem com os agendamentos
    } else {
        
sendMessage('Você não possui compromissos marcados.'
    }
}

export default indexAppointments

 

10 - Altere mais uma vez o arquivo interpretsMessage.ts para adicionar a função de agenda

import sendMessage from './sendMessage'
import createAppointment from './createAppointment'
import removeAppointments from './removeAppointments'
import indexAppointments from './indexAppointments'

//...

        case 'esqueça':    // Verifica se a mensagem enviada foi "esqueça"
            sendMessage(
              
 `Entendido. Mande uma mensagem com o seguinte formato:\n Desmarque tudo no dia __/__/____ às __:__`
            )
            
break
        case 'agenda': // Verifica se a mensagem enviada foi "agenda"
            indexAppointments()
            
break

        default:

//...

 

Agora seu Assistente Pessoal está pronto para guardar seus compromissos, te lembrar quando chegar o momento, listar todos os compromissos agendados e remover compromissos em uma determinada hora e data.

 

Agora ficou fácil tirar a sua ideia do papel. Aproveite e faça uso da API de WhatApp no ambiente de testes da Zenvia, o Sandbox.  A partir dele você consegue receber informações sobre as mensagens trocadas por meio de um webhook.

Com todas as informações necessárias em mãos, o que você tem são inumeras de possibilidades de automação, oferecendo benefícios como aumento da produtividade, personalização de produtos e atendimento 24/7.

 

Quer conhecer mais sobre o assunto? Acesse o perfil da Zenvia aqui na Prensa e confira a Zenvia Academy para embarcar de vez no universo dos bots!

Topo