Building the WhatsApp bot on Python. Complete Guide

Repository with a bot source

We will explain the way to write a simple WhatsApp bot on Python using the WhatsApp API.

The demo bot will be able to react to and reply to commands sent to WhatsApp as regular messages. At the moment our demo chatbot has the following functionality:

  • The output of the command list
  • The output of the current chat ID
  • The output of the actual server time of the bot running on
  • The output of your name
  • Sending files of different formats (PDF, jpg, doc, mp3, etc.)
  • Sending of prerecorded voice messages
  • Sending of geo-coordinates (locations)
  • Setting up a conference (group)

Attention: To make the bot fully functional, please, always keep your phone online. Your phone should not be used for the WhatsApp Web at the same time.

Getting Started

Authorization of Whatsapp via QR code

At first, let's link up the WhatsApp with our script at once so that we can check the way code works while we're writing one. To do so, we will switch to our account and get a QR code there. Then we open WhatsApp on your mobile phone, go to Settings → WhatsApp Web → Scan the QR code.

At this point, the WebHook URL must be provided for the server to trigger our script for incoming messages. The WebHook URL is a link to which JSON-data containing information about incoming messages or notifications will be sent using the POST method. So we need a server to make the bot run and this server will accept and process this information. While writing this article, we deployed the server using the FLASK microframework. The FLASK server allows us to conveniently respond to incoming requests and process them.

Get free access to the WhatsApp API

Initializing the bot class

Let's create a file “wabot.py” and describe the class for our bot in it. We will have to import the data from the library:

                        import json
import requests
import datetime
                    

The JSON library handles the JSON format. Requests – We need it to access the website API.

Then we'll create the WABot class.

                        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'
                    

We will describe the class constructor, the default one that will accept JSON, which will contain information about incoming messages (it will be received by WebHook and forwarded to the class). To see how the received JSON will look like, you can enter the easy-to-use Testing section, available in your cabinet. You can also test WebHook requests in it.

In this section, we assign values to class attributes. Dict_messages – Dictionary containing information from the message in the JSON file that we have received.

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

You can view the JSON structure in the testing section of the “Check WebHook” item. It is required to run the check and send messages to your WhatsApp chat. You will see the JSON sent to the WebHook on the display.

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

You have to submit your APIUrl and token data, and we will use it for generating a request to the API.

Getting the bot functionality

Sending requests

We need to send requests to the API to get things done. Let's make a function to generate requests and send them depending on the parameters we have.

                        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 - it accepts two parameters: method and data.

  • method determines the Chat API method be called.

  • data contains the data required for sending.

For more information on all methods, please refer to the documentation. Data – is a data dictionary based on which we will generate JSON and send it to the server by Post method (As only Post methods we need for our bot functionality, we are going to use them exclusively).

At this stage, we are generating a request string to the API.

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

Then we have to define header Content-Type and mark it as Application/JSON since we will always send our data in JSON format.

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

Now we generate a complete request with the requests.post and upload our data to the API server. Json.dump(data) – it serializes our data dictionary into JSON format.

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

It returns the server response functions in JSON format.

Sending messages

send_message - accepts two parameters: chatId and text.

  • ChatID – ID of the chat where the message should be sent

  • Text – Text of the message

So let's write a method allowing us to send messages to our chat room.

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

We are creating a data dictionary, containing the body “ChatID” — ID, to which the message and dataset 'body' with the text we need to be sent.

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

Next, we are going to send our data to the method we wrote in the previous step.

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

To send a message to Chat API the method “sendMessage” is used, so we send it to the function as a parameter as well as our data dictionary. And then we return the server response.

Welcome

The welcome method will be called by the command “hi” of the bot as well as by entering a command that does not exist.

  • ChatID – ID of the chat where the message should be sent

  • noWelcome – Boolean type variable defining the text to be sent to a chat: welcome or command list. This is the False by default.

                    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)

We generate our message string based on the noWelcome variable and then send it to the send_message function as the text to be sent.

ChatID Output

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

Time Output

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

“me” Function

Outputs information about your companion's name by the “me” command

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

“file” Function

Sends a file with the specified format to the chat room

  • ChatID – ID of the chat where the message should be sent

  • format – is the format of the file to be sent. All files to be sent are stored on the server-side.

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)

At this point, we have created a dictionary containing the formats we need like keys, and the file names as values, both of which are stored on the server-side and are waiting to be sent:

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

We then check if there exists a format that the user sends in our dictionary

If it does exist, we generate a request for sending a file containing:

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 of the chat where the message should be sent

  • Body – direct link to the file to be sent

  • Filename – the name of the file

  • Caption – the text to be sent out together with the file

Generate request with the send_requests and the “sendFile” as a parameter; submit our data to it.

“ptt” Function

Sends a voice message to the chat room

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

Generating our data dictionary in which:

  • ChatID – ID of the chat where the message should be sent

  • audio – a direct link to a file in ogg format

Send a request to API using “sendAudio” method

“geo” Function

Sending geo-coordinates.

def geo(self, chatID):
data = {
    "lat" : '51.51916',
    "lng" : '-0.139214',
    "address" :'Your address',
    "chatId" : chatID
}
answer = self.send_requests('sendLocation', data)
return answer
  • ChatID – ID of the chat where the message should be sent

  • lat – predefined coordinates

  • lng – predefined coordinates

  • address – this is your address or any string you need.

After the dictionary is generated, send a request to the API with the “sendLocation” method

“group” Function

Makes a group between you and the bot

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 – the JSON body sent by the WebHook with information on the message sender.

The body contains information about the user number, but with additional characters. Let's call the replace function and delete them, keeping only the phone number:

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

Let's generate the data:

data = {
"groupName" : 'Group with the bot Python',
"phones" : phone,
'messageText' : 'It is your group. Enjoy'}
  • groupName – the conference name when created

  • phones – all the necessary participants' phone numbers; you can also send an array of several phone numbers

  • messageText – The first message in the conference

We send a request with the 'group' method

User requests processing

We have described all the functionality of our demo bot, and now we need to arrange the bot's logic so it can react to commands and interact with the user as well. Let's describe one more function for this

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'

We will call this function for each time we receive data in our WebHook

Let's take it one by one:

Do you remember the attribute of our bot dict_messages we created at the beginning? It has dictionaries of messages that we have accepted. The test filters out data not containing any messages. WebHook can receive a request with no message.

if self.dict_messages != []:

We may receive several messages in a single request, and our bot must process them all. To do this, we are going through all the dictionaries which have a dict_messages list.

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

We declare a variable text — which will be a list of words contained in our message — after entering the loop. To do this, we turn to the message dictionary using the ['body'] key to get the text of the incoming message and just call the split() function, which allows us to split the text into words.

We then check if the incoming message is not coming from ourselves by referring to the 'fromMe' key which contains True or False values and validates who the message was from.

Prepared a manual on how to write a bot whatsapp on a python

If this test is not performed, the bot may become an infinite recursion.

We now get the chat id from the same message dictionary using the ['ChatID'] key. We address the first element of the word list, put it in the lower case so that the bot could react to messages written by UPPERCASE or MiXeD cAsE and then compare it with the commands we need.

Then we just call the functionality we described in the previous steps with the id parameter.

Congratulations, our bot is ready!) So now we can fully react to incoming messages.

You only need to substitute both your token from your account and the instance number.

Get API key

FLASK Server

We are going to use the FLASK server to process requests. We will make a file app.py, and import all the necessary libraries.

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()

Let's initialize the app variable, which is going to be a FLASK class.

app = Flask(__name__)

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

We'll write the path app.route('/', methods = ['POST']) for it. This decorator means that our home function will be called every time our FLASK server is accessed through a post request by the main path.

We are testing the fact that the server was accessed through the POST method. Now we create an instance of our bot and submit JSON data to it.

requests.json – lets you get JSON files from the request body sent to our server.

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

So now we just call the bot.processing() method that handles requests from our object.

The WhatsApp bot on Python

Now we need to upload our server and the bot to the hosting and name your domain as a WebHook. Whenever a message is received, the server will receive the data and process it.

You can access the complete source code by clicking on the link to GitHub: https://github.com/chatapi/whatsapp-python-bot