How to Add a Contact to Multiple Whatsapp Groups Using Python Code (Beginner’s Guide)

Problem description

Let’s say you’re a group admin for several hundred Whatsapp groups. Perhaps these are business customers who prefer to carry out some communications over Whatsapp. From time to time you need to add or remove people to some (or all) of these groups. For example, a new account manager from your company has been assigned to 100 of these groups and she needs to be added to all of them. Or, the old account manager has left and you need to remove him from a large number of groups.

Here arises some problems:

  1. While you can add multiple contacts to a single group at once, there is no simple way to add a single contact to multiple groups at once. Similarly, there is no way to remove a contact from multiple groups at once, or change the admin status of a contact from multiple groups at once.
  2. For all the above mentioned operations, the typical manual process requires searching for the group, clicking on the group info, scrolling through the existing group participants (or searching from existing contacts to add new participants), setting a change and then making a confirmation. Doing this half-to-one minute job quickly gets old when you need repeat it hundreds of times.

Overview of an automated solution using Python code

I wasn’t able to find any existing solution or article about this online, and I’ve run into this situation enough times now that I decided that a simple script should do the job instead. Here I’m providing a simple walk-through of my solution, with hopefully enough guidance that a motivated individual would be able to set this up for themselves even without prior programming knowledge.

From a high level, the way I approached this was to automate the manual actions on Whatsapp Web (the desktop browser version of the app). The automation is carried out using the popular browser automation tool Selenium WebDriver. Selenium works in several programming languages, and I used Python in the Jupyter Notebook environment. Much of the initial challenge here for a newcomer would be to get the environment set up. Please follow this Install Jupyter Notebook guide. You would need to do some searching online if you run into any quirks, but if you manage to set it up, you should be up and running with an instance of Jupyter Notebook in your browser. You would also need to install the Selenium library.

Let’s see a demo first. For the demo setup, I created a bunch of groups TestGroup01, TestGroup02 … up to TestGroup10. The script logged on to my Business Whatsapp account, and proceeded to add my Personal Whatsapp account to all those groups. Without further ado, let’s see what this actually looks like in action:

Demo of automatically adding a contact to multiple chat groups.

This a looped screen recording of the demo at true 1x speed (meaning it has not been sped up) for the processing on TestGroup01 and TestGroup02, with about 4 secs run time/group. Since making the recording I have removed some unnecessary wait time in the code to shave an extra second off each group.

How to instruct a browser

If you were sitting next to someone and trying to instruct them, you would probably point to the screen and say, “Click here, then here, then type this”. If you were instead instructing someone over the phone, you would need to be more descriptive and say, “Click on the search box on the left…click on Group Info”. To instruct a machine, we need to be very specific, and understand how the browser sees web pages.

Every web page, including the Whatsapp Web page, is represented in the browser by a tree structure known as the DOM model. Each atomic part is known as an element, and it’s our job to pinpoint specific elements of interest on the page. Elements can have IDs which make them unique and easy to reference. We can simply tell the browser to “find the element with ID=main”. However, not all elements have IDs, and for those that don’t, we need to instruct the browser to traverse the DOM using rules and “landmarks” that we identify ourselves, with the goal of uniquely identifying the elements we want to interact with. For this, the programmer would inspect the elements of the page and try to reverse-engineer the logic that the original web page creator used to structure and name the elements. Then, the programmer would provide a more (necessarily) long-winded instruction to the browser, making use of whatever landmarks they can identify: “first find the element with x attribute, then go up 2 levels in the DOM tree, and then find the button that has y attribute under it.” There are multiple ways that the same element can be referenced, and some ways are more robust than others.

These long-winded instructions are represented in our code using XPath syntax. This process of inspecting the DOM, identifying valid structural rules and writing correct XPath is the most laborious part of writing the automation.

The Code

In this section I walk through the main parts of the code. These pieces of code would be inserted in order into a new Jupyter Notebook file.

This first section imports all the required third party modules, and then makes a call to launch Whatsapp Web in a new Chrome browser window.

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.action_chains import ActionChains
from datetime import datetime
import time
chrome_options = Options()
driver = webdriver.Chrome(options=chrome_options)
driver.get('https://web.whatsapp.com/')
#wait 60 secs to allow for the user to manually scan the Whatsapp Web QR code to log on
el_side = WebDriverWait(driver, 60).until(EC.element_to_be_clickable((By.ID, "side")))
#locate the search box
el_search = el_side.find_element(By.XPATH, "//div[contains(@title, 'Search')]")
print("Logged in and located search box:", el_search)

The below code should be inserted into the first cell of the Jupyter Notebook file like so:

Jupyter Notebook cell with code inserted

Merely inserting the code doesn’t run it. To run it, we need to press the Run button, which runs the code in the selected cell. After running the first cell, you’ll notice a new browser being launched and directed to the Whatsapp Web URL. You’ll also notice the browser show a special info-bar with the message “Chrome is being controlled by automated test software” — good! We’re well on our way.

Infobar indicating that Chrome is being controlled by automated test software.

At this point the script pauses for up to 60 seconds to allow us to take our device out and log into Whatsapp Web with the account we want.

Next, we need to provide a list of chat group names that we need to process. This is achieved by creating a list such as the one shown below. This can be inserted into its own cell and run, to load it into the code memory.

list_chat_groups_test = [
'TestGroup01',
'TestGroup02',
'TestGroup03',
'TestGroup04',
'TestGroup05',
'TestGroup06',
'TestGroup07',
'TestGroup08',
'TestGroup09',
'TestGroup10'
]

Finally, we insert and run the code below that defines the functions that perform the automation, and also the code that makes the actual function calls.

#define a helper function
def click_modal_button(button_text):
modal_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@data-animate-modal-body='true']//div[@role='button']//div[text() = '%s']" % (button_text))))
modal_button.click()
#define a function that adds contact_to_add to group_name
def add_contact_to_group(group_name, contact_to_add):
#find chat with the correct title
el_target_chat = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//span[@title='%s']" % (group_name))))
el_target_chat.click()
#wait for it to load by detecting that the header changed with the new title
el_header_title = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@id='main']//header//span[@title='%s']" % (group_name))))
#click on the menu button
el_menu_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@id='main']//div[@data-testid='conversation-menu-button']")))
el_menu_button.click()
#click on the Group Info button
el_group_info = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@id='app']//li//div[@aria-label='Group info']")))
el_group_info.click()
#click on the Add Participant button
el_add_participant = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@data-testid='drawer-right']//div[text() = 'Add participant']")))
el_add_participant.click()
#click on the Search
el_modal_popup = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@data-animate-modal-body='true']")))
el_modal_popup.find_element(By.XPATH, "//div[contains(@title, 'Search')]").send_keys(contact_to_add)
#click on the Contact
el_contact_to_add = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@data-animate-modal-body='true']//span[@title='%s']" % (contact_to_add))))
el_contact_to_add.click()
#check whether already added
if len(el_modal_popup.find_elements(By.XPATH, "//div[text() = 'Already added to group']")) > 0:
print(contact_to_add + ' was already an existing participant of ' + group_name)
el_modal_popup.find_element(By.XPATH, "//header//button[@aria-label='Close']").click()
else:
#click on the Green Check Mark
el_green_check = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.XPATH, "//div[@data-animate-modal-body='true']//div[@role='button']//span[@data-testid='checkmark-medium']")))
el_green_check.click()
#click on the Add Participant
click_modal_button('Add participant')
print(contact_to_add + ' added to ' + group_name)
start_time = datetime.now()
#loop through a provided list of chat groups to perform an action
for chat_name in list_chat_groups_test:
el_search.clear()
el_search.send_keys(chat_name)
try:
print('Attempting to add to', chat_name, ":")
add_contact_to_group(chat_name, 'Jimmy')
except Exception as exception:
print("Exception: {}".format(type(exception).__name__))
print("Exception message: {}".format(exception))
end_time = datetime.now()
print('Duration: {}'.format(end_time - start_time))

Note that on line 51 we provide the list name list_chat_groups_test which we created earlier. You can create multiple lists beforehand and substitute as required. Also note that on line 57 we provide the name of the contact to be added. Thus ‘Jimmy’ would be replaced by the appropriate contact name.

The completed Jupyter Notebook file can be downloaded in its entirety at this github repo. Along with the “Add Contact to Group” function that we went over above, 3 other functions are also provided. Demos are shown below:

Remove Contact from Group

Demo of automatically removing a contact from multiple chat groups.

Set Contact as Admin

Demo of automatically setting a contact as a group admin.

Dismiss Contact as Admin

Demo of automatically dismissing a contact as a group admin.

Ending Remarks

In this article I went over some scenarios where one might need to repeat some mundane Whatsapp group administrative tasks, and how a little code can be used to automate the execution. I provided an overview of how we could use Selenium as a tool, introduced the DOM tree model for web pages, and described some of the mechanics of automation. I presented the code solution in a few sections, along with how one would insert the code into a Jupyter Notebook environment. Finally, a link to the full code was provided.

Even if you don’t intend to implement this, I hope this article provided some insight into the art and science of browser automation, and maybe even inspire you to think of or commission a small project based on it.

If you did come here looking for some code for Whatsapp Web automation, please let me know how it goes, or feel free to leave a comment or send any pull requests to the repo.

Other Notes and Disclaimers

  1. This script can be easily modified to execute perhaps a more typical problem — messaging. I have not tackled the mass messaging problem because there are several relatively more bearable manual workarounds. There are also powerful unified messaging tools and platforms that make use of the official Whatsapp Business API for greater functionality.
  2. The code currently handles batch operations for a single contact for multiple groups. It can be modified to handle operations for multiple contacts for multiple groups. I haven’t done it because it adds to the complexity and the workaround would be simply to run the code again and substitute another contact in the function calls.
  3. If Meta updates the structure of the Whatsapp Web significantly in the future rendering some of the current XPaths invalid, the code would need to be updated.
  4. If you do plan to use this code, please run it with test groups first, and then on some small pilot groups, before unleashing it on your full list 🙂
  5. While this script will work under the right conditions, there are likely several cases that will cause it to fail or work unexpectedly (e.g. duplicate chat group names or contact names). As of the date of publishing I have used it a few times on lists of several hundred groups and have fixed some bugs along the way, but there is no guarantee that this is fully production ready. This code is provided ‘as is’ without any warranty. Please use at your own risk.
  6. Having said that, the common failure would be for it to just stop and give an error message, without doing anything to your Whatsapp groups. In the worst case, if you’re not careful in updating the list values (e.g. make some changes to the list, but forget to run that part of the code to update it in code memory), you might perform the wrong operations on the wrong contacts. Side anecdote: back in 2010, before it was common to get work email on your phone, I bought an app that sycned my work email to my BlackBerry Torch 9800. It was cool and fun until the app somehow forwarded a bunch of my work meeting invites to my entire contact list. The following day I received a stark warning from the in-house counsel to be careful not to compromise the confidentiality of our work. So, just another friendly warning here when playing around with messaging tools.

Leave a Reply

Your email address will not be published. Required fields are marked *