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

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

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

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

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

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

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

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

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

Теперь, чтобы сервер вызывал наш скрипт при новых сообщениях, нужно указать WebHook URL. WebHook URL – это ссылка, куда будут посылаться, методом POST, JSON–данные с информацией о входящих сообщениях или уведомлениях. Соответственно, чтобы бот работал нам требуется сервер – который эти данные будет принимать и обрабатывать. При написании этой статьи, мы развернули у себя сервер с помощью микро-фраемворка FLASK. Он позволяет удобно реагировать на входящие запросы и обрабатывать их.

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

Пишем инициализацию класса бота

Создадим файл “wabot.py” и в нем опишем класс для нашего бота. Нам потребуется импортировать данные библиотеки:

                        import json
import requests
import datetime
                    

Библиотека json отвечает за обработку формата Json. Requests – Нужен нам для того, чтобы обращаться к API сайта.

Далее создадим класс WABot

                        class WABot():
def __init__(self, json):
    self.json = json
    self.dict_messages = json['messages']
    self.APIUrl = 'https://eu41.chat-api.com/instance12345/'
    self.token = 'abcdefg'
                    

В нем мы опишем конструктор класса, который по умолчанию будет принимать json – тот самый, что будет содержать информацию о входящих сообщениях (Его будет принимать Webhook и передавать в наш класс). Для того, чтобы узнать, как будет выглядеть принимаемый json – можно зайти в удобный раздел тестирования, который мы предоставляем в личном кабинете. В нем можно протестировать запросы и Webhook.

Здесь мы присваиваем атрибутам класса значения. Dict_messages – Словарь, который содержит информацию из сообщения в json файле, который мы приняли

                        self.json = json
self.dict_messages = json['messages']
                    

Посмотреть структуру json можно в разделе тестирования в элементе «Проверка WebHook». Требуется запустить проверку и отправлять в ваш whatsapp чат сообщения. На экране будет выводиться json, посылаемый к webhook.

                        self.APIUrl = 'https://eu41.chat-api.com/instance12345/'
self.token = 'abcdefg'
                    

Вам требуется подставить свои данные APIUrl и token, их мы будем использовать для формирования запроса к API.

Пишем функционал бота

Отправка запросов

Для работы нам потребуется отправлять запросы к API. Напишем функцию, которая будет формировать запросы и отправлять их, исходя из наших параметров.

                        def send_requests(self, method, data):
url = f"{self.APIUrl}{method}?token={self.token}"
headers = {'Content-type': 'application/json'}
answer = requests.post(url, data=json.dumps(data), headers=headers)
return answer.json()
                    

send_requests - принимает два параметра: method и data.

  • method определяет, какой метод ChatAPI должен быть вызван.

  • data содержит необходимые для пересылки данные.

Подробнее о всех методах можно почитать в документации. Data – это словарь данных из которых мы сформируем json и передадим методом Post на сервер. (Т.к. для нашего функционала бота требуются лишь Post методы, только ими мы и будем пользоваться)

На данном этапе мы формируем строку запроса к API.

                        url = f"{self.APIUrl}{method}?token={self.token}"
                    

Далее обязательно нужно указать header Contet-Type и пометить его Application/Json, так как мы будем всегда передавать наши данные форматом json.

                        headers = {'Content-type': 'application/json'}
                    

Теперь формируем полноценный запрос с помощью requests.post и передаем на сервер api наши данные. Json.dump(data) – серриализует наш словарь data в формат json.

                        answer = requests.post(url, data=json.dumps(data), headers=headers)
return answer.json()
                    

Возвращает функции ответ сервера в формате json.

Отправка сообщений

send_message - принимает два параметра: chatId и text.

  • ChatID – Id чата, в который необходимо отправить сообщение

  • Text – Текст сообщения

Теперь напишем метод, который позволит отправлять сообщения в наш чат.

                        def send_message(self, chatID, text):
    data = {"chatID" : chatID,
            "body" : text}
answer = self.send_requests('sendMessage', data)
return answer
                    

Формируем словарь data, который содержит в себе тело “chatId” - Id, куда требует отправить сообщение и тело ‘body’ с необходимым нам текстом.

                    data = {"chatID" : chatID,
"body" : text}

Далее передаем наши данные в метод, который мы написали в предыдущем этапе

                    answer = self.send_requests('sendMessage', data)
return answer

Для того чтобы отправить сообщение в Chat Api используется метод «sendMessage», поэтому его мы и передаем в функции в качестве параметра вместе с нашим словарем data. И возвращаем ответ сервера.

Приветствие

Метод welcome будем вызывать по команде “hi” бота и при вводе несуществующей команды.

  • ChatID – Id чата, в который необходимо отправить сообщение

  • noWelcome – Булева переменная, определяющая какой текст будет отправлен в чат: приветствие или список команд. По умолчанию False.

                    def welcome(self,chatID, noWelcome = False):
        welcome_string = ''
        if (noWelcome == False):
            welcome_string = "WhatsApp Demo Bot Python\n"
        else:
            welcome_string = """Incorrect command
Commands:
1. chatid - show ID of the current chat
2. time - show server time
3. me - show your nickname
4. file [format] - get a file. Available formats: doc/gif/jpg/png/pdf/mp3/mp4
5. ptt - get a voice message
6. geo - get a location
7. group - create a group with the bot"""
return self.send_message(chatID, welcome_string)

Формируем нашу строку с сообщением исходя из переменной noWelcome и передаем в функцию send_message в качестве отправляемого текста.

Вывод chatID

def show_chat_id(self,chatID):
return self.send_message(chatID, f"Chat ID : {chatID}")

Вывод времени

def time(self, chatID):
t = datetime.datetime.now()
time = t.strftime('%d:%m:%Y')
return self.send_message(chatID, time)

Функция me

Выводит информацию о имени собеседника по команде ‘me’

def me(self, chatID, name):
return self.send_message(chatID, name)

Функция file

Отправляет файл с указанным форматом в диалог

  • ChatID – Id чата, в который необходимо отправить сообщение

  • format – формат файла, который необходимо отправить. Все отправляемые файлы хранятся на сервере.

def file(self, chatID, format):
availableFiles = {'doc' : 'document.doc',
                'gif' : 'gifka.gif',
                'jpg' : 'jpgfile.jpg',
                'png' : 'pngfile.png',
                'pdf' : 'presentation.pdf',
                'mp4' : 'video.mp4',
                'mp3' : 'mp3file.mp3'}
if format in availableFiles.keys():
    data = {
                'chatId' : chatID,
                'body': f'https://domain.com/Python/{availableFiles[format]}',
                'filename' : availableFiles[format],
                'caption' : f'Get your file {availableFiles[format]}'
    }
return self.send_requests('sendFile', data)

Здесь мы сформировали словарь, который содержит в качестве ключей необходимые нам форматы, а в качестве значений имена файлов, которые лежат на сервере и ждут отправки:

availableFiles = {'doc' : 'document.doc',
'gif' : 'gifka.gif',
'jpg' : 'jpgfile.jpg',
'png' : 'pngfile.png',
'pdf' : 'presentation.pdf',
'mp4' : 'video.mp4',
'mp3' : 'mp3file.mp3'}

Далее проверяем существует ли в нашем словаре формат, который передал пользователь

Если существует – то формируем запрос на отправку файла, где:

if format in availableFiles.keys():
data = {
        'chatId' : chatID,
        'body': f'https://domain.com/Python/{availableFiles[format]}',
        'filename' : availableFiles[format],
        'caption' : f'Get your file {availableFiles[format]}'
}
return self.send_requests('sendFile', data)
  • ChatID – Id чата, в который необходимо отправить сообщение

  • Body – прямая ссылка до файла, который необходимо отправить

  • Filename – имя файла

  • Caption – текст, который будет отправлен вместе с файлом

Формируем запрос send_requests с параметром “sendFile” и передаем в него наши данные.

Функция ptt

Отправляет голосовое сообщение в диалог

def ptt(self, chatID):
data = {
"audio" : 'https://domain.com/Python/ptt.ogg',
"chatId" : chatID }
return self.send_requests('sendAudio', data)

Формируем словарь наших данных, где:

  • ChatID – Id чата, в который необходимо отправить сообщение

  • audio – прямая ссылка на файл формата ogg

Отправляем запрос к api методом “sendAudio”

Функция geo

Отправляет гео-координаты

def geo(self, chatID):
data = {
    "lat" : '51.51916',
    "lng" : '-0.139214',
    "address" :'Ваш адрес',
    "chatId" : chatID
}
answer = self.send_requests('sendLocation', data)
return answer
  • ChatID – Id чата, в который необходимо отправить сообщение

  • lat – заранее заданные координаты

  • lng – заранее заданные координаты

  • address – ваш адрес или любая необходимая вам строка.

После формирования словаря, отправляем запрос к API методом “sendLocation”

Функция group

Создает группу, в которой будете вы и бот

def group(self, author):
phone = author.replace('@c.us', '')
data = {
    "groupName" : 'Group with the bot Python',
    "phones" : phone,
    'messageText' : 'It is your group. Enjoy'
}
answer = self.send_requests('group', data)
return answer

author – тело json, посылаемое webhook, содержит информацию о том, кто отправил сообщение.

Данное тело содержит в себе информацию о номере пользователя, но с дополнительными символами. Вызовем функцию replace и удалим их, оставив лишь номер телефона:

phone = author.replace('@c.us', '')

Сформируем данные:

data = {
"groupName" : 'Group with the bot Python',
"phones" : phone,
'messageText' : 'It is your group. Enjoy'}
  • groupName – имя конференции после её создания

  • phones – телефоны необходимых участников конференции, можно передавать массив из нескольких телефонов

  • messageText – Первое сообщение в конференции

Отправляем запрос методом ‘group’

Обрабатываем запросы пользователей

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

def processing(self):
if self.dict_messages != []:
    for message in self.dict_messages:
        text = message['body'].split()
        if not message['fromMe']:
            id  = message['chatId']
            if text[0].lower() == 'hi':
                return self.welcome(id)
            elif text[0].lower() == 'time':
                return self.time(id)
            elif text[0].lower() == 'chatid':
                return self.show_chat_id(id)
            elif text[0].lower() == 'me':
                return self.me(id, message['senderName'])
            elif text[0].lower() == 'file':
                return self.file(id, text[1])
            elif text[0].lower() == 'ptt':
                return self.ptt(id)
            elif text[0].lower() == 'geo':
                return self.geo(id)
            elif text[0].lower() == 'group':
                return self.group(message['author'])
            else:
                return self.welcome(id, True)
        else: return 'NoCommand'

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

Разберем её по порядку:

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

if self.dict_messages != []:

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

for message in self.dict_messages:
text = message['body'].split()

После вхождения в цикл мы объявляем переменную text – которая будет являться листом слов, содержащихся в нашем сообщении. Для этого мы обращаемся к словарю message по ключу [‘body’], чтобы получить текст входящего сообщения и просто вызываем функцию split(), которая позволит разбить текст на слова.

Далее мы делаем проверку, что входящее сообщение не от нас самих, посредством обращения к ключу ‘fromMe”, который содержит в себе True или False и проверяет от кого было сообщение.

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

Если же данной проверки не будет бот может уйти в бесконечную рекурсию.

Теперь мы получаем id чата из все того же самого словаря message по ключу [‘chatId’]. Обращаемся к первому элементу листа слов, приводим его в нижний регистр, чтобы бот мог реагировать на сообщения написанное КАПСОМ или ЗаБоРчИкОм и сравниваем его с необходимыми нам командами.

После сравнения просто вызываем функционал, который мы описали на предыдущих этапах с параметром id.

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

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

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

Сервер Flask

Для обработки запросов будем использовать сервер Flask. Создадим файл app.py, импортируем все необходимые библиотеки.

from flask import Flask, request, jsonify
from wabot import WABot
import json

app = Flask(__name__)

@app.route('/', methods=['POST'])
def home():
    if request.method == 'POST':
        bot = WABot(request.json)
        return bot.processing()

if(__name__) == '__main__':
    app.run()

Инициализируем переменную app, которая будет являться классом Flask.

app = Flask(__name__)

@app.route('/', methods=['POST'])
def home():
    if request.method == 'POST':
        bot = WABot(request.json)
        return bot.processing()

И пропишем для неё путь app.route(‘/’, methods = [‘POST’]). Данный декоратор означает, что наша функция home будет вызываться каждый раз, когда к нашему серверу flask будут обращаться посредством post запроса по главному пути.

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

requests.json – позволяет получить json файлы из тела запроса, который был передан к нашему серверу.

def home():
if request.method == 'POST':
    bot = WABot(request.json)
    return bot.processing()

И теперь просто вызываем у нашего объекта метод bot.processing(), который отвечает за обработку запросов.

Whatsapp бот на Python

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

Весь код будет доступен по ссылке на гитхаб: https://github.com/oxybes/Chat-Api_WhatsApp_Bot