Создание Whatsapp бота на Node.js. Полное руководство

Репозиторий с исходником бота
hand

С сожалением сообщаем вам, что эта статья потеряла свою актуальность. Возможно описываемая услуга перестала предоставляться или изменились условия работы API.

Сейчас Chat API предлагает самый доступный и автоматизированный WhatsApp Business API на рынке с Многопользовательским Чатом, Визуальным конструктором ботов, готовыми интеграциями приложений и другими полезными функциями.

Это надежный способ познакомить более чем 2-миллиардную аудиторию мессенджера с вашим бизнесом или продуктом: привлечение клиентов в WhatsApp стало проще.
Спасибо, что посетили наш сайт. Успехов во всех делах!

Введение

Сейчас мы расскажем, как создать WhatsApp бота на Node JS, используя наш шлюз API WhatsApp

Бот будет реагировать на команды, поступающие ему в виде обычных сообщений в WhatsApp и отвечать на них. В нашем примере мы постараемся охватить самый частоиспользуемый и необходимый в разработке функционал:

  • Реагирование и ответ на команду
  • Вывод ID текущего чата (в ЛС или в беседе)
  • Вывод имени того, кто общается с ботом
  • Отправка файлов различных форматов (pdf, jpg, doc, mp3 и др.)
  • Отправка голосовых сообщений (файлов *.ogg)
  • Отправка геопозиции (координаты локации)
  • Создание конференции (группы) с ботом

Обратите внимание: чтобы бот работал, телефон должен быть всегда подключен к интернету и не должен использоваться для Whatsapp Web

Подготовительная работа

Авторизация Whatsapp через QR код

В самом начале, сразу свяжем whatsapp с нашим скриптом, чтобы по мере написания кода - проверять его работу. Для этого переходим в личный кабинет и получаем там QR-код. Далее открываем WhatsApp на мобильном телефоне, заходим в Настройки -> WhatsApp Web -> Сканируем QR-код.

Возможности сервиса Chat-Api позволяют установить вебхук, который будет передавать информацию о новых сообщениях (и не только) веб-серверу. Чтобы сервер вызывал наш скрипт при новых сообщениях, нужно указать WebHook URL. WebHook URL – это ссылка, куда будут посылаться, методом POST, JSON–данные с информацией о входящих сообщениях или уведомлениях. Соответственно, чтобы бот работал нам требуется сервер – который эти данные будет принимать и обрабатывать. И мы дарим его вам!

UPD 2021-11-22. Дополнили код в репозитории деплоем бота в докер и описанием докер файлов. Нашим клиентам доступен Docker-хостинг бесплатно. Подробную инструкцию по заливке бота и получению Webhook вы найдете в конце гайда.

Получить бесплатный доступ к WhatsApp API

Разворачиваем бота у себя

Мы рекомендуем начать с клонирования нашего Git репозитория с последующим изменением под себя.

Итак, создаём папку WhatsAppBot, клонируем гитом (или просто скачиваем) в неё файлы из репозитория. Первым делом мы установим необходимые зависимости для нашего бота командой npm install – набор библиотек, с помощью которых он будет работать.

Далее заходим в конфиг (файл config.js) и указываем в кавычках свой url адрес для запросов и токен (взять их можно в личном кабинете). Получается так:

                        module.exports = {
    // URL адрес для обращений к API
    apiUrl: "https://eu14.chat-api.com/instance12345/",
    // Токен для работы с API из личного кабинета
    token: "xb1s668fphzmr9m"
}
                    

Сохраняем файл и пишем в терминале node index.js – этой командой мы запустим веб-сервер для обработки запросов. Если Вы всё сделали верно – бот будет работать.

Разбор кода и написание Whatsapp бота с нуля

Если Вы решили написать бота с нуля – сейчас расскажем о том, как это сделать.

Пропускаем описание установки Node.JS и NPM (предполагая, что Вы с этим уже знакомы, раз читаете этот гайд).

Итак, создаём папку WhatsAppBot, заходим в неё и открываем терминал. Инициализируем проект командой npm init, заполняем данные или просто несколько раз нажимаем Enter до появления надписи Is this OK? (yes), подтверждаем создание ещё одним нажатием Enter.

Далее создаём наш главный файл – index.js. Он будет содержать всю основную логику работы бота. Создаём ещё и config.js – туда мы вынесем основные параметры, которые могут быть динамическими (чтобы для их исправления не залезать в основной файл).

Откроем config.js и внесём туда два параметра – apiUrl и token:

                        module.exports = {
    apiUrl: "",
    token: ""
}
                    

Не забудьте первую строку – module.exports – именно она позволит работать с этими данными из другого файла. Можем сразу заполнить данные в конфиге нашими данными из личного кабинета. Сохраняем и закрываем.

Мы будем использовать следующие зависимости (requirements):

Открываем index.js и начнём создание бота с объявления зависимостей:

                        const config = require("./config.js");
const token = config.token, apiUrl = config.apiUrl;
const app = require('express')();
const bodyParser = require('body-parser');
const fetch = require('node-fetch');
                    

Подробнее: node-fetch позволит совершать запросы к API, config подгрузит наши данные с другого файла, в переменные token и apiUrl сразу поместим данные из конфига (чтобы проще было к ним обращаться). Модуль Express нужен для разворачивания веб-сервера, а body-parser позволит удобно извлекать поток входящих запросов.

Далее объясним нашему парсеру, что работать мы будем с JSON данными:

                        app.use(bodyParser.json());
                    

Кстати, для того, чтобы узнать, как будет выглядеть принимаемый json – можно зайти в удобный раздел тестирования, который мы предоставляем в личном кабинете. В нем можно протестировать запросы и Webhook.

На всякий случай повесим полный обработчик ошибок, которые может выбросить нам prequest в процессе запросов:

                        process.on('unhandledRejection', err => {
    console.log(err)
});
                    

Ну а теперь начнём писать основной код.

Для проверки припаркованного к машине домена будем обрабатывать основную страницу (своеобразный index.html) следующим блоком:

                        app.get('/', function (req, res) {
    res.send("It's working");
}); 
                    

Проще говоря – это позволит проверить работоспособность нашего сайта – после запуска проекта при переходе на yoursite.ru - если всё сделано правильно - Вы увидите надпись “It's working" Теперь напишем функцию для общения с нашим API.

                        async function apiChatApi(method, params){
    const options = {};
    options['method'] = "POST";
    options['body'] = JSON.stringify(params);
    options['headers'] = { 'Content-Type': 'application/json' };
    
    const url = `${apiUrl}/${method}?token=${token}`; 
    
    const apiResponse = await fetch(url, options);
    const jsonResponse = await apiResponse.json();
    return jsonResponse;
}
                    

Разберём подробнее: создаём асинхронную функцию apiChatApi, которая будет принимать в себя два параметра: метод, к которому мы хотим обратиться, и объект параметров, с которым мы к этому методу обращаемся. Ну, грубо говоря, мы хотим отправить сообщение – обращаемся с методом message и передаём в объекте текст сообщения и получателя.

Внутри функции создаём объект options, который сразу пополняем двумя ключами: json и method. В первом мы передаём параметры, необходимые для API, а во втором указываем метод, с котором обращаемся и в котором хотим получить ответ.

Далее мы объявляем константу – наш url адрес для обращения к API. Он будет содержать в себе, собственно, сам url (из конфига), метод и токен, передаваемый GET запросом.

После этого направляем запрос и ответ записываем в apiResponse, который и возвращаем (в простейшем боте, кстати говоря, возврат ответа от функции, в принципе, и не потребуется – разве что для отлова ошибок)

Функция для общения с API готова. Самое время начать написание логики бота.

Выбираем, как будет называться наша страница для обработки. В моём случае пусть это будет webhook (т.е. вебхук будет отправлять запросы по адресу http://yoursite.ru/webhook). Пишем обработчик этого url:

                        app.post('/webhook', async function (req, res) {

});
                    

Внутри обработчика запишем в переменную data всё то, что получим:

                        app.post('/webhook', async function (req, res) {
    const data = req.body;

});
                    

И прогоним его через цикл for для парсинга всех сообщений:

                        app.post('/webhook', async function (req, res) {
    const data = req.body;
    for (var i in data.messages) {

    }
});
                    

Теперь запишем информацию о полученном сообщении в переменные, а также сразу отсечём информацию об исходящих сообщениях:

                        app.post('/webhook', async function (req, res) {
    const data = req.body;
    for (var i in data.messages) {
        const author = data.messages[i].author;
        const body = data.messages[i].body;
        const chatId = data.messages[i].chatId;
        const senderName = data.messages[i].senderName;

        if(data.messages[i].fromMe)return;
    }
});
                    

Итак, в author у нас теперь информация про автора сообщения, body содержит в себе текст, chatId – Id текущего чата, а senderName принял в себя имя человека, который с нами общается.

Не забудем добавить код для запуска веб-сервера в конце файла:

                        app.listen(80, function () {
    console.log('Listening on port 80..');
});
                    

А теперь можем проверить работоспособность бота, написав следующий код в цикле for после объявленных переменных:

                        console.log(senderName, author, chatId, body);
                    

Запустим бота командой node index.js и напишем бот сообщение: Test. Если всё верно, то в консоли мы увидим: Евгений [email protected] [email protected] Test

Если всё работает – двигаемся дальше. Уберём (или закомментируем) отладочную строку с console.log и подумаем, как можно обрабатывать команды.

Вариантов, на самом деле, несколько, но мы рекомендуем конструкцию if else if в связке с регулярными выражениями: это позволит, во-первых, создавать сложные команды с аргументами, во-вторых, не возиться с повторением переменных (как это было бы в случае с switch – case), а в-третьих, очень легко проверять неверно введённую команду (замыкающим else) и выдавать соответствующее сообщение.

Далее мы будем рассматривать вложенный код в for, не запутайтесь 😉

Итак, начнём с написания конструкции команд:

                        if(/help/.test(body)) {
    // Этот участок сработает, когда юзер введёт help
    } else if(/chatId/.test(body)) {
    // Этот участок сработает, когда юзер введёт chatId
    } else if(/file (pdf|jpg|doc|mp3)/.test(body)) {
    // Этот участок сработает, когда юзер введёт file pdf, file jpg, etc
    } else if(/ptt/.test(body)) {            
    // Этот участок сработает, когда юзер введёт ptt
    } else if(/geo/.test(body)) {
    // Этот участок сработает, когда юзер введёт geo
    } else if(/group/.test(body)) {
    // Этот участок сработает, когда юзер введёт group
}
                    

Ну а теперь, собственно, напишем обработчики команд. Начнём с help – тут всё проще простого:

                        const text = `${senderName}, это демо-бот для https://chat-api.com/.
Команды:
1. chatId - отобразить ID текущего чата
2. file [pdf/jpg/doc/mp3] - получить файл
3. ptt - получить голосовое сообщение
4. geo - получить локацию
5. group - создать группу с Вами и ботом`;
await apiChatApi('message', {chatId: chatId, body: text});
                    

Тут всё понятно: в переменную text мы запишем заранее заготовленный текст, разместим внутри него переменную senderName, содержащую в себе имя пользователя, который написал боту.

Ну и последняя строка – это вызов нашей функции для работы с API, в которую мы передаём метод – ‘message’ и объект с параметрами - {chatId: chatId, body: text}.

Можем запустить наш проект командой node index.js и написать боту help. Получим:

Подготовили мануал о том, как написать вацап бота на ноде

Кстати, мы советуем вам отправлять текст со всеми командами на любую непрописанную команду боту, например если человек написал нашему боту «Привет!», хотя такую команду мы не закладывали. Тогда, это стартовое сообщение всё время будет на виду у пользователя.

Теперь напишем обработчик для команды chatId. В принципе, тут тоже всё просто:

                        await apiChatApi('message', {chatId: chatId, body: chatId});
                    

Мы обращаемся к API, к методу message и просим отправить в чат chatId текст chatId.

ватсап апи

Всё просто, согласитесь? Двигаемся дальше.

На пути у нас самая сложная часть кода – функционал команды file. Для начала внимательно посмотрим на строку:

                        /file (pdf|jpg|doc|mp3)/.test(body)
                    

Я вкратце опишу её логику: мы проверяем, равняется ли body значению file + одно из значений из скобок. Например, равняется ли body значению file pdf, или значению file jpg, и так далее.

Если равняется – запускаем наш обработчик, который, первым делом с помощью функции match вычленит тип отправляемого файла в переменную fileType:

                        const fileType = body.match(/file (pdf|jpg|doc|mp3)/)[1];
                    

Т.е. в fileType у нас сейчас значение “pdf”/”jpg”/”doc”/”mp3”. Теперь заведём объект с отправляемыми данными:

                        const files = {
     doc: "https://domain.ru/tra.docx",
     jpg: "https://domain.ru/tra.jpg",
     mp3: "https://domain.ru/tra.mp3",
     pdf: "https://domain.ru/tra.pdf"
};
                    

Соответственно, он позволит нам получать url адрес файла, обращаясь к нему по индексу ключа, например:

                        files["doc"] // => "https://domain.ru/tra.docx"
files["mp3"] // => "https://domain.ru/tra.mp3"
                    

Соответственно, files[fileType] выдаст url нужного нам файла.

UPD 2021-11-22. Дополнили код бота в репозитории возможностью отправлять файлы с относительными путями.

Осталось дело за малым – сформировать объект параметров, которые нам нужно передать в API:

                        var dataFile = {
     phone: author,
     body: files[fileType],
     filename: `Файл *.${fileType}`            
};
                    

В phone передаём автора сообщения, в body ссылку на файл (см. API), а в filename помещаем видимое название файла (я для примера помещаю слово «Файл» и его расширение).

Напишем небольшую конструкцию, которая будет добавлять в объект параметров ключ “caption” (нижняя строка текста под картинкой) только в случае, если запрашивается картинка:

                        if (fileType == "jpg") dataFile['caption'] = "Текст под фото.";
                    

И, собственно, передаём всё это в нашу функцию, указывая, что хотим вызвать метод sendFile:

                        await apiChatApi('sendFile', dataFile);
                    

Теперь реализуем обработчик для команды ptt – голосовые сообщения.

                        await apiChatApi('sendAudio', {audio: "https://domain.ru//tra.ogg", chatId: chatId});
                    

Мы обращаемся к нашей функции, передавая метод sendAudio и ключ audio с прямой ссылкой на файл в объекте параметров.

Отправка аудио бот Whatsapp

В этой статье ссылки на файлы статичны (обращаются к хостингу), а в репозитории предоставлен вариант кода с относительными путями. При этом чаще всего мы советуем передавать файлы в формате base64. Для этого можно использовать наш мини-сервис кодирования в личном кабинете.

Обработчик для команды geo тоже очень прост:

                        await apiChatApi('sendLocation', {lat: 51.178843, lng: -1.826210, address: 'Стоунхендж', chatId: chatId});
                    

Вместо audio мы передаём ключи lat и lng – широту и долготу необходимого места, соответственно. В ключе “address” передаём его название. Сами координаты можно взять, например, в Гугл Картах.

Осталась команда group, создающая группу бота с человеком. Её обработчик почти не отличается от нескольких предыдущих:

                        let arrayPhones = [ author.replace("@c.us","") ];

await apiChatApi('group', {groupName: 'Группа с ботом на Node.JS', phones: arrayPhones, messageText: 'Добро пожаловать в новую группу!'});
                    

Мы создаём массив arrayPhones, в который сразу вкладываем автора, убирая в его строке @c.us, оставляя только номер. Можем вложить несколько номеров сразу.

И выполняем запрос к функции методом group, передавая ключи groupName – название беседы, массив пользователей в phones и приветственный текст в messageText.

Поздравляю, наш бот готов!) На этом разработка обработчиков завершена. Теперь мы можем полноценно реагировать на входящие сообщения.

Вам необходимо только подставить в коде свой токен из личного кабинета и номер инстанса.

Получить токен API

Docker-хостинг в подарок!

Мы продолжаем расширять и улучшать возможности сервиса, чтобы упростить жизнь разработчикам. Наша новинка – бесплатный Docker хостинг для клиентов Chat API.

Мы знаем, как часто возникают сложности с разворачиванием Webhook для своих проектов, особенно если нет времени на создание своего сервера. Поэтому мы открываем возможность бесплатно размещать свои приложения на нашем Docker хостинге.

Работает все просто и мы пройдемся по каждому пункту: 1) Проверьте, есть ли у Вас Docker?; 2) Установите Dockerize (бесплатное решение для наших клиентов); 3) Протестируйте и разверните свое приложение на нашем хостинге.

Установите Docker. Это можно сделать на официально сайте. Вы можете проверить, если он уже установлен у вас командой docker help

Далее, установите Dockerize:

                        npm install -g @solohin/dockerize
                    

Получите в вашем личном кабинете бесплатный Docker-инстанс. Это легко и быстро, достаточно лишь связать его с вашим основным whatsapp-инстансом! Вы сразу получите свой Docker-токен, который нужно будет использовать для авторизации:

                        dockerize login {your_token}
                    

Как найти токен докер-инстанса


Создадим наше приложение! Для этого введите эту строку с названием вашего приложения:

                        dockerize create app test-chat-api-bot
                    

Теперь в рабочей директории нам нужно создать Dockerfile, который будет содержать инструкции для сборки образа, на основе которого будет запущен контейнер. В него мы запишем:

                        FROM node:14

WORKDIR /usr/src/app
COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 80

CMD ["node", "index.js"]
                    

Создадим файл .dockerignore и запишем туда несколько локальных файлов, которые будут игнорироваться (npm-debug.log и .idea)

Пришло время протестировать Ваше приложение локально командой: dockerize test

Это запустит Ваше приложение на 8080 порту. Откройте localhost:8080, чтобы проверить. Наконец-то давайте его выгрузим:

                        dockerize deploy
                    

Ваше приложение загружено и доступно на test-chat-api-bot.dockerize.xyz (поменяйте на название вашего приложения в формате {name}.dockerize.xyz)

Выглядит совсем просто, не правда ли?


Подробный гайд о разработке бота на node js

Whatsapp бот на Node.JS

Теперь, когда мы залили нашего бота на Dockerize, не забудьте указать в качестве webhook полученный домен. При каждом входящем сообщении на сервер будут приходить и обрабатываться данные.

Весь код будет доступен по ссылке на гитхаб: https://github.com/chatapi/whatsapp-nodejs-bot-ru