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

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

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

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

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

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

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

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

Теперь, чтобы сервер вызывал наш скрипт при новых сообщениях, нужно указать WebHook URL. Укажите там прямую ссылку на ваш скрипт, например, https://domain.com/PHP/whatsappbot.php. Нельзя указать просто IP-адрес сервера. Можно указать порт.

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

Теперь давайте создадим файл whatsappbot.php и создадим в нем класс: class whatsAppBot { }

Создадим в нем переменные, в которые поместим API Url и токен. Их можно узнать в личном кабинете.

{var $APIurl = 'https://api.chat-api.com/instanceYYYYY/';
                            var $token = 'abcdefgh12345678';}

Теперь объявим функцию __construct(), которая будет вызываться автоматически при каждом запуске скрипта. Сервер ChatAPI будет обращаться к боту при поступлении новых сообщений (об этом подробнее ниже), присылая данные о новом сообщении в JSON-формате. Сразу ловим эти данные в начале функции и помещаем в переменные.

{public function __construct(){ $json = file_get_contents('php://input');
                            $decoded = json_decode($json,true);}}

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

{ ob_start();
                        var_dump($decoded);
                        $input = ob_get_contents();
                        ob_end_clean();
                        file_put_contents('input_requests.log',$input.PHP_EOL,FILE_APPEND);}

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

{ if(isset($decoded['messages'])){
                        foreach($decoded['messages'] as $message){
                        $text = explode(' ',trim($message['body']));
                        if(!$message['fromMe']){
                        switch(mb_strtolower($text[0],'UTF-8')){
                        case 'hi':  {$this->welcome($message['chatId'],false); break;}
                            case 'chatid': {$this->showChatId($message['chatId']); break;}
                            case 'time':   {$this->time($message['chatId']); break;}
                            case 'me':     {$this->me($message['chatId'],$message['senderName']); break;}
                            case 'file':   {$this->file($message['chatId'],$text[1]); break;}
                            case 'ptt':     {$this->ptt($message['chatId']); break;}
                            case 'geo':    {$this->geo($message['chatId']); break;}
                            case 'group':  {$this->group($message['author']); break;}
                            default:        {$this->welcome($message['chatId'],true); break;}
                            }}}}}

Разбираемся:

  • if(isset($decoded['messages']))
    Уведомления типа "пользователь покинул чат" тоже присылаются сервером, но в них будет отсутствовать массив сообщений. Данная проверка предотвращает ошибку "Undefined index".

  • foreach($decoded['messages'] as $message)
    Сообщения находятся в массиве, и их может прийти несколько сообщений одновременно. Бот должен отреагировать на каждое из них.

  • $text = explode(' ',trim($message['body']));
    Разбиваем тело сообщения на отдельные слова. Первое слово - команда, остальные будут параметрами команды.

  • if(!$message['fromMe'])
    Эта проверка нужна, чтобы бот не ушел в рекурсию. Отметка "fromMe" означает, что сообщение было послано самим ботом. Поэтому выполнение продолжаем только для входящих сообщений.

  • switch(mb_strtolower($text[0],'UTF-8'))
    Блок switch, который определяет, что за команда содержится в первом слове. Команду приводим в строчной регистр, чтобы бот реагировал на нее независимо от того, капсом она написана, с заглавной буквы или зАбОрЧиКоМ.

  • case 'hi': {$this->welcome($message['chatId'],false)}
    Собственно, выполнение соответствующей команды в зависимости от первого слова. В вызываемую функцию передаем chatId из сообщения, чтобы отправка происходила в соответствующий чат. В принципе, все следующие строки одинаковые, но обратите внимание на:

    case 'file': {$this->file($message['chatId'],$text[1])}
    Здесь мы передаем еще один параметр, а именно второе слово сообщения, т.к. оно является параметром команды. Об этом ниже. Также обратите внимание на:

    case 'me': {$this->me($message['chatId'],$message['senderName'])}
    Здесь в качестве второго параметра стоит имя собеседника, взятое также из данных сообщения. А в default мы вызываем функцию, выводящую список команд, но с параметром true, что означает получение неправильной команды.

Мы закончили писать функцию __construct(). Теперь перейдем к функциям, вызываемым по командам из вышеупомянутого блока switch. В части функций вызывается функция sendMessage(), в другой части - sendRequest(). В скрипте эти функции помещены внизу, но расскажем о них сразу:

Функция sendRequest() осуществляет непосредственно запрос на сервер ChatAPI для отправки сообщений и различного медиа. Она принимает 2 параметра - $method и $data.

  • $method определяет, какой метод chatAPI должен быть вызван.
  • $data содержит необходимые для пересылки данные.
{ public function sendRequest($method,$data){
                        $url = $this->APIurl.$method.'?token='.$this->token;
                        if(is_array($data)){ $data = json_encode($data);}
                        $options = stream_context_create(['http' => [
                        'method'  => 'POST',
                        'header'  => 'Content-type: application/json',
                        'content' => $data
                        ]]);
                        $response = file_get_contents($url,false,$options);
                        file_put_contents('requests.log',$response.PHP_EOL,FILE_APPEND);}

Разберем подробнее: В $url мы формируем корректный URL-адрес, содержащий APIUrl, метод и токен. Затем проверяем входящие данные. Если это массив, преобразовываем его в JSON. Если нет - значит преобразование в JSON было уже осуществлено в вызвавшей функции. $options - задаем HTTP-заголовки. Затем через file_get_contents выполняем запрос на сформированный URL, передавая данные. Последняя строка необязательна, она просто записывает ответ сервера ChatAPI в файл для отладки и логгирования.

Функция sendMessage() - по сути просто оболочка отправки простых текстовых сообщений. Она формирует корректный массив данных и передает его в вышеупомянутую функцию sendRequest() с методом "message".

{ public function sendMessage($chatId, $text){
                        $data = array('chatId'=>$chatId,'body'=>$text);
                        $this->sendRequest('message',$data);}}

Теперь создадим управляющие функции из блока switch. Функции, посылающие простое текстовое сообщение, в большинстве своем просто вызывают sendMessage() с определенным текстом. Функции, посылающие различное медиа, формируют свои массивы данных и вызывают sendRequest() с другими методами.

Функция welcome() - вывод списка доступных команд

{ public function welcome($chatId, $noWelcome = false){
                        $welcomeString = ($noWelcome) ? "Incorrect command\n" : "WhatsApp Demo Bot PHP\n";
                        $this->sendMessage($chatId,
                        $welcomeString.
                        "Commands:\n".
                        "1. chatid - show ID of the current chat\n".
                        "2. time - show server time\n".
                        "3. me - show your nickname\n".
                        "4. file [format] - get a file. Available formats: doc/gif/jpg/png/pdf/mp3/mp4\n".
                        "5. ptt - get a voice message\n".
                        "6. geo - get a location\n".
                        "7. group - create a group with the bot");}}

Если параметр $noWelcome равен false, то первая строка сообщения будет приветствием, отображаемым по команде "hi". Если true - приветствие будет заменено на сообщение неверной команде.

Функция showChatId() - вывод ID текущего чата по команде "chatid".

{ public function showChatId($chatId){
                        $this->sendMessage($chatId,'ChatID: '.$chatId);}}

Функция time() - вывод текущего времени сервера по команде "time".

{ public function time($chatId){
                        $this->sendMessage($chatId,date('d.m.Y H:i:s'));}}

Функция me() - вывод имени собеседника по команде "me".

{
                        public function me($chatId,$name){
                        $this->sendMessage($chatId,$name);
                        }}

Функция file() - отправка файла по команде "file". Эта функция наиболее интересна, т.к. работает с параметром. В качестве параметра передается формат файла, который нужно отправить.

{ public function file($chatId,$format){
                        $availableFiles = array(
                        'doc' => 'document.doc',
                        'gif' => 'gifka.gif',
                        'jpg' => 'jpgfile.jpg',
                        'png' => 'pngfile.png',
                        'pdf' => 'presentation.pdf',
                        'mp4' => 'video.mp4',
                        'mp3' => 'mp3file.mp3'
                        );
                        if(isset($availableFiles[$format])){
                        $data = array(
                        'chatId'=>$chatId,
                        'body'=>'https://domain.com/PHP/'.$availableFiles[$format],
                        'filename'=>$availableFiles[$format],
                        'caption'=>'Get your file '.$availableFiles[$format]
                        );
                        $this->sendRequest('sendFile',$data);}}}

Разберем подробнее:

  • $availableFiles - это массив, в котором ключами выступают параметры функции, а значениями - имена файлов. Естественно, файлы с именами из массива должны присутствовать на сервере. В данном примере они лежат там же, где и скрипт бота, но вы можете поместить их в другую папку.
  • if(isset($availableFiles[$format])) - проверяем существование ключа массива с полученным параметром. Если он существует, то мы формируем массив данных, и передаем его в sendRequest() с методом "sendFile". В массиве данных должны быть следующие данные:
  • chatId - как обычно, ИД чата, в который посылается ответ.
  • body - прямая ссылка на файл на вашем сервере. Обратите внимание, что на сервере должен быть включен SSL!
  • filename - имя файла, можно указать любое
  • caption - сопровождающее этот файл сообщение.

Функция ptt() - отправка голосового сообщения по команде "ptt". Голосовое сообщение должно быть файлом формата .OGG на вашем сервере.

{ public function ptt($chatId){
                        $data = array(
                        'audio'=>'https://domain.com/PHP/ptt.ogg',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendAudio',$data);}}

Здесь, как и в предыдущей функции, формируем массив данных: chatId - ID чата audio - прямая ссылка на файл .ogg, опять же обязателен SSL И передаем его функции sendRequest с методом "sendAudio".

Функция geo() - отправка гео-координат по команде "geo"

{ public function geo($chatId){
                        $data = array(
                        'lat'=>51.51916,
                        'lng'=>-0.139214,
                        'address'=>'Ваш адрес',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendLocation',$data);
                        }}

Все то же самое, что и в предыдущих двух функциях. Массив должен содержать следующие данные: lat и lng - координаты; address - адрес, но можно написать любую строку; chatId - само собой разумеещееся.

Функция group() - создать конференцию, в которой будете вы и бот, по команде "group".

{ public function group($author){
                        $phone = str_replace('@c.us','',$author);
                        $data = array(
                        'groupName'=>'Group with the bot PHP',
                        'phones'=>array($phone),
                        'messageText'=>'It is your group. Enjoy'
                        );
                        $this->sendRequest('group',$data);}}

Здесь нам требуется указать номера телефонов пользователей, которые будут добавлены в конференцию. В первой строке извлечем номер телефона пользователя из его личного ID, который имеет вид [email protected] Затем формируем массив:

  • groupName - название конференции;
  • phones - массив номеров телефонов;
  • messageText - текст первого сообщения в группе;

Подготовили мануал о том, как написать простого whatsapp бота на php Обратите внимание, что это единственная функция, где НЕ НАДО передавать chatId. И передаем массив в sendRequest().

Теперь, когда мы закончили работать с функциями, после закрывающей класс скобки напишите строчку: new whatsAppBot();

Чтобы класс вызвался автоматически при обращении к скрипту.

Итоговый код будет выглядеть следующим образом

{
                        class whatsAppBot{
                        //specify instance URL and token
                        var $APIurl = 'https://api.chat-api.com/instanceYYYYY/';
                        var $token = '**************************';

                        public function __construct(){
                        //get the JSON body from the instance
                        $json = file_get_contents('php://input');
                        $decoded = json_decode($json,true);

                        //write parsed JSON-body to the file for debugging
                        ob_start();
                        var_dump($decoded);
                        $input = ob_get_contents();
                        ob_end_clean();
                        file_put_contents('input_requests.log',$input.PHP_EOL,FILE_APPEND);

                        if(isset($decoded['messages'])){
                        //check every new message
                        foreach($decoded['messages'] as $message){
                        //delete excess spaces and split the message on spaces. The first word in the message is a command, other words are parameters
                        $text = explode(' ',trim($message['body']));
                        //current message shouldn't be send from your bot, because it calls recursion
                        if(!$message['fromMe']){
                        //check what command contains the first word and call the function
                        switch(mb_strtolower($text[0],'UTF-8')){
                        case 'hi':  {$this->welcome($message['chatId'],false); break;}
                            case 'chatid': {$this->showChatId($message['chatId']); break;}
                            case 'time':   {$this->time($message['chatId']); break;}
                            case 'me':     {$this->me($message['chatId'],$message['senderName']); break;}
                            case 'file':   {$this->file($message['chatId'],$text[1]); break;}
                            case 'ptt':     {$this->ptt($message['chatId']); break;}
                            case 'geo':    {$this->geo($message['chatId']); break;}
                            case 'group':  {$this->group($message['author']); break;}
                            default:        {$this->welcome($message['chatId'],true); break;}
                            }}}}}

                        //this function calls function sendRequest to send a simple message
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        //@param $text [string] [required] - text of the message
                        public function welcome($chatId, $noWelcome = false){
                        $welcomeString = ($noWelcome) ? "Incorrect command\n" : "WhatsApp Demo Bot PHP\n";
                        $this->sendMessage($chatId,
                        $welcomeString.
                        "Commands:\n".
                        "1. chatid - show ID of the current chat\n".
                        "2. time - show server time\n".
                        "3. me - show your nickname\n".
                        "4. file [format] - get a file. Available formats: doc/gif/jpg/png/pdf/mp3/mp4\n".
                        "5. ptt - get a voice message\n".
                        "6. geo - get a location\n".
                        "7. group - create a group with the bot"
                        );
                        }

                        //sends Id of the current chat. it is called when the bot gets the command "chatId"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function showChatId($chatId){
                        $this->sendMessage($chatId,'ChatID: '.$chatId);
                        }
                        //sends current server time. it is called when the bot gets the command "time"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function time($chatId){
                        $this->sendMessage($chatId,date('d.m.Y H:i:s'));
                        }
                        //sends your nickname. it is called when the bot gets the command "me"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        //@param $name [string] [required] - the "senderName" property of the message
                        public function me($chatId,$name){
                        $this->sendMessage($chatId,$name);
                        }
                        //sends a file. it is called when the bot gets the command "file"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        //@param $format [string] [required] - file format, from the params in the message body (text[1], etc)
                        public function file($chatId,$format){
                        $availableFiles = array(
                        'doc' => 'document.doc',
                        'gif' => 'gifka.gif',
                        'jpg' => 'jpgfile.jpg',
                        'png' => 'pngfile.png',
                        'pdf' => 'presentation.pdf',
                        'mp4' => 'video.mp4',
                        'mp3' => 'mp3file.mp3'
                        );

                        if(isset($availableFiles[$format])){
                        $data = array(
                        'chatId'=>$chatId,
                        'body'=>'https://domain.com/PHP/'.$availableFiles[$format],
                        'filename'=>$availableFiles[$format],
                        'caption'=>'Get your file '.$availableFiles[$format]
                        );
                        $this->sendRequest('sendFile',$data);}}

                        //sends a voice message. it is called when the bot gets the command "ptt"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function ptt($chatId){
                        $data = array(
                        'audio'=>'https://domain.com/PHP/ptt.ogg',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendAudio',$data);}

                        //sends a location. it is called when the bot gets the command "geo"
                        //@param $chatId [string] [required] - the ID of chat where we send a message
                        public function geo($chatId){
                        $data = array(
                        'lat'=>51.51916,
                        'lng'=>-0.139214,
                        'address'=>'Ваш адрес',
                        'chatId'=>$chatId
                        );
                        $this->sendRequest('sendLocation',$data);}

                        //creates a group. it is called when the bot gets the command "group"
                        //@param chatId [string] [required] - the ID of chat where we send a message
                        //@param author [string] [required] - "author" property of the message
                        public function group($author){
                        $phone = str_replace('@c.us','',$author);
                        $data = array(
                        'groupName'=>'Group with the bot PHP',
                        'phones'=>array($phone),
                        'messageText'=>'It is your group. Enjoy'
                        );
                        $this->sendRequest('group',$data);}

                        public function sendMessage($chatId, $text){
                        $data = array('chatId'=>$chatId,'body'=>$text);
                        $this->sendRequest('message',$data);}

                        public function sendRequest($method,$data){
                        $url = $this->APIurl.$method.'?token='.$this->token;
                        if(is_array($data)){ $data = json_encode($data);}
                        $options = stream_context_create(['http' => [
                        'method'  => 'POST',
                        'header'  => 'Content-type: application/json',
                        'content' => $data]]);
                        $response = file_get_contents($url,false,$options);
                        file_put_contents('requests.log',$response.PHP_EOL,FILE_APPEND);}}
                        //execute the class when this file is requested by the instance
                        new whatsAppBot();}


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

Получить ключ API