Пример создания Whatsapp бота на Java

Полное руководство
Репозиторий с исходником бота
Опубликовано: 5 ноября 2020

Расскажем, как написать простого ватсап бота на Java, используя наш API WhatsApp.

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

Какой функционал у данного бота?

  1. При отправке команды "chatid" бот должен отправить сообщение с текущим ID чата;
  2. При отправке команды "file [format]" бот должен отправить заранее подготовленные файлы (pdf, jpg, doc, mp3);
  3. При отправке команды "ogg", бот должен отправить файл с расширением "ogg" (Голосовое сообщение);
  4. При отправке команды "geo", бот должен отправить гео-координаты (локацию);
  5. При отправке команды "group" бот должен создать группу с собой и пользователем;
  6. Реагировать приветственным сообщением с описанием команд на все входящие не "командные" сообщения (как аналог "меню");

Подготовка

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

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

Для того, чтобы наш бот реагировал на команды необходимо, чтобы бот мог принять и обработать входящие данные. Для этого требуется поднять собственный сервер - Webhook и указать его адрес в личном кабинете.

Немного о том, для чего нужен Webhook

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

Но, если мы укажем адрес Webhook сервера, то данная необходимость перестанет быть актуальной. Сервера сами будут присылать уведомления о входящих изменениях, как только они появятся. А задача Webhook сервера их принять и правильно обработать, реализовывая логику бота. Указывать можно как домен, так и ip адрес

Устанавливаем webhook в личном кабинете Устанавливаем webhook в личном кабинете

Поднимаем собственный сервер на Java с помощью фреймворка Spring Boot

Для упрощения создания заготовки нашего spring boot приложения будем использовать spring initializr: Выставляем необходимые настройки, выбираем нужную версию Java и жмём Generate

Далее необходимо скачать сгенерированный проект и открыть в вашей IDE. Я буду использовать Intellij IDEA. Создадим папку "controller", в которой будем хранить наш контроллер по обработке входящих данных на наш сервер и необходимые классы для работы бота.

Сhat-api присылает Json данные, которые нам необходимо будет распарсить и обработать.

Для того, чтобы узнать, что именно нам приходит в json, воспользуемся страницей тестирование в личном кабинете и перейдем во вкладку "Симуляция Weebhoka"

JSON body - Данные, которые будут приходить к нам на сервер (Их структура). Для того, чтобы удобно с ним работать из Java мы будем десериализовывать его в объект и обращаться с данными, как со свойствами объекта.

Для нашего удобства воспользуемся сервисом по автоматической генерации Json to Java class. Копируем JSON body. Жмем Preview и копируем автоматически сгенерированные классы Java в наш проект. Для этого создадим файл jsonserializables.java и вставляем код в файл.

После того, как наш класс, описывающий JSON тело создан, приступаем к реализации самого контроллера, который будет обрабатывать входящие данные. Для этого создадим новый файл "MessageController" и опишем в нём класс MessageController.

@RestController
@RequestMapping("webhook")
    public class MessageController {
        @PostMapping
        public String AnswerWebhook(@RequestBody RequestWebhook hook) throws IOException {
            return  "ok";
        }

Аннотация @RequestMapping("webhook") отвечает за адрес, по которому будет обрабатываться наш контроллер. Например: "localhost:8080/webhook"

Аннотация @PostMapping означает, что функция AnswerWebhook будет обрабатывать Post запросы.

В тоже время, параметр RequestWebhook в параметрах функции - это наш десериализованный JSON класс, десериализацию которого берет на себя Spring Boot и мы получаем сразу готовый объект и можем с ним работать внутри самой функции. В ней мы опишем логику работы бота.

Реализуем логику контроллера

@RestController
    @RequestMapping("webhook")
    public class MessageController {
        @PostMapping
        public String AnswerWebhook(@RequestBody RequestWebhook hook) throws IOException {
            for (var message : hook.getMessages()) {
                if (message.getFromMe())
                    continue;
                String option = message.getBody().split(" ")[0].toLowerCase();
                switch (option)
                {
                    case "chatid":
                        ApiWA.sendChatId(message.getChatId());
                        break;
                    case "file":
                        var texts = message.getBody().split(" ");
                        if (texts.length > 1)
                            ApiWA.sendFile(message.getChatId(), texts[1]);
                        break;
                    case "ogg":
                        ApiWA.sendOgg(message.getChatId());
                        break;
                    case "geo":
                        ApiWA.sendGeo(message.getChatId());
                        break;
                    case "group":
                        ApiWA.createGroup(message.getAuthor());
                        break;
                    default:
                        ApiWA.sendDefault(message.getChatId());
                        break;
                }
            }
            return  "ok";
        }

В цикле проходимся по всем пришедшим сообщениям и обрабатываем их в switch. Проверка в начале цикла нужна для того, чтобы бот не зациклился сам на себя. В случае, если сообщение пришло от самого себя - то пропускаем его и не обрабатываем.

Далее записываем в переменную option пришедшую команду. Для этого тело сообщения мы разбиваем методом split и приводим команду в нижний регистр, чтобы бот реагировал на команды независимо от регистра. В switch, в зависимости от пришедшей команды, мы вызываем необходимый метод из класса ApiWA, о реализации которого мы сейчас и поговорим.

Класс ApiWA

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

public class ApiWA {
        private static String APIURL = "https://eu115.chat-api.com/instance123456/";
        private static String TOKEN = "1hi0xw1fzaen1234";
    }

После чего нам потребуется реализовать метод, который будет отправлять POST запрос.

 public static CompletableFuture<Void> postJSON(URI uri,
                                            Map<String,String> map)
            throws IOException
    {
        ObjectMapper objectMapper = new ObjectMapper();
        String requestBody = objectMapper
                .writerWithDefaultPrettyPrinter()
                .writeValueAsString(map);

        HttpRequest request = HttpRequest.newBuilder(uri)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .build();

        return HttpClient.newHttpClient()
                .sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::statusCode)
                .thenAccept(System.out::println);
    }

Данный метод принимает в параметры ссылку URI, на которую необходимо сделать Post запрос, а также словарь, который сериализуется в Json строку и передается на сервер.

Для реализации каждого из методов смотрим подробную документацию и повторяем запросы.

Так выглядит метод по отправке chatid:

    public static void sendChatId(String chat_id) throws IOException {
        URI uri = URI.create(APIURL + "sendMessage?token=" + TOKEN);
        Map<String, String> map = new HashMap<String, String>();
        map.put("body", "Your ID: " + chat_id);
        map.put("phone", chat_id);
        ApiWA.postJSON(uri, map);
    }

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

Реализацию отправки файла через Whatsapp бота мы описали ниже, а остальной функционал: отправка геолокации, создании группы и прочие команды, вы можете посмотреть в исходниках нашего бота.

Другие методы
Аналогичным способом реализованы остальные методы. Исходный код можно скачать и посмотреть на github.

Отдельно поговорим о реализации метода по отправке файлов

 public static void sendFile(String chat_id, String file_format) throws IOException {
        Map<String, String> formats= new HashMap<String, String>();
        formats.put("doc", Base64Help.getDOC());
        formats.put("jpeg", Base64Help.getJPEG());
        formats.put("pdf", Base64Help.getPDFtring());
        formats.put("mp3", Base64Help.getMP3String());

        if (formats.containsKey(file_format))
        {
            Map<String, String> map = new HashMap<String, String>();
            map.put("phone", chat_id);
            map.put("body", formats.get(file_format));
            map.put("filename", "ThisIsFile");
            map.put("caption", "ThisIsCaption");
            URI uri = URI.create(APIURL + "sendFile?token=" + TOKEN);
            ApiWA.postJSON(uri, map);
        }
        else
        {
            Map<String, String> map = new HashMap<String, String>();
            map.put("phone", chat_id);
            map.put("body", "File not found");
            URI uri = URI.create(APIURL + "sendMessage?token=" + TOKEN);
            ApiWA.postJSON(uri, map);
        }
    }

Данный метод хранит в себе словарь, в котором ключи - формат файлов, а значения - строка в формате Base64. Base64 строкa используется для передачи файлов. Для генерации строки можно воспользоваться сервисом на нашем сайте. Если файл нужного формата отсутствует в словаре, то мы отправляем сообщение о том, что файл не найден.

Мы описали класс Base64Help и методы по получению строки с файлом нужного формата. Сама строка хранится в txt файлах на сервере, а из кода мы просто считываем её из файла. Это необходимо, потому что Java не позволяет хранить такие длинные строки прямо в коде. Вы же можете генерировать строку Base64 автоматически, либо пользуясь сервисами.

public class Base64Help {
        static public String getPDFString() throws IOException {
            return new String(Files.readAllBytes(Paths.get("src/main/resources/pdf.txt")));
        }

        static  public String getMP3String() throws IOException {
            return new String(Files.readAllBytes(Paths.get("src/main/resources/mp3.txt")));
        }

        static public String getJPEG() throws IOException {
            return new String(Files.readAllBytes(Paths.get("src/main/resources/jpeg.txt")));
        }

        static public String getDOC() throws IOException {
            return new String(Files.readAllBytes(Paths.get("src/main/resources/doc.txt")));
        }

    }

Итак, мы описали работу простого ватсап-бота и выложили исходник с готовым функционалом на github.

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

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

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

Whatsapp бот на Java

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