How to Build a Chrome Extension to Spam on WhatsApp using Vanilla JavaScript

Akash Pawara
The Startup
Published in
10 min readJun 26, 2020

--

So in this article I’m gonna show you guys how to build a simple chrome extension to spam your foes on WhatsApp using vanilla JavaScript — that is, plain JavaScript without any additional frameworks like React, Angular, or Vue. Not only you will learn how to build a Chrome Extension, you will also get to learn more about Modern JavaScript and best practices one should follow.

If you have your own idea for an extension, however, and just want to know what to add to your existing project files to get it working in Chrome, you can skip down to the section on customizing your own manifest.json file.

In case If you are just interested in the bot (Why not?) you can simply checkout my GitHub repository. It has steps for you to install it manually.

Just keep in mind that you need to strictly go through this note before using this Bot.

Prerequisites

To start with this project all you need to know is some basic knowledge of html, CSS & JavaScript.

File Structure

We will be following the given file structure to build our bot. It’s a good practice to follow a good structured file system.

whastapp-spam-chrome-extension/
├── assets
│ ├── css
│ │ └── popup.css
│ ├── js
│ │ ├── background.js
│ │ ├── inject.js
│ │ └── popup.js
│ └── icons
│ └── icon.png
├── popup.html
└── manifest.json

Setting Up Your Design

To get started, We will create two files: popup.html, popup.css. I won’t be going in deep details about these files as our main objective is JavaScript and manifest.json file. Put these files in their given directory based on our file structure. Next, fill the HTML file with basic HTML document setup, and connect it to the CSS and JS files:

popup.html (HTML document setup)

<!doctype html><html> <head>  <title>Whatsapp Spammer</title>  <link rel="stylesheet" href="assets/css/popup.css"></head><body>  <div class="container">   <h1>Whatsapp Spammer</h1>   <textarea placeholder="message" id="message" required></textarea>   <input type="number" placeholder="count" id="count" required>   <button id="injectMessage">Send</button>   <div id="validation"></div>  </div><script type="text/javascript" src="assets/js/popup.js"></script></body></html>

popup.css (css stylesheet)

Link For CSS code :-https://github.com/akashpawara/whatsapp-spam-chrome-extension/blob/master/assets/css/popup.css

Once done your extension will somehow look like this.

WhatsApp Spammer Extension

Let’s Get Started

Customizing Your manifest.json File

Every extension needs to have a manifest.json. The above files won’t be enough to get your project working as a Chrome extension. For that, we need a manifest.json file that we’ll customize with some basic information about our extension.

For a full list of options, visit their official documentation.

Just copy/paste the following lines into a new file and save it as manifest.json in your folder according to our file structure:

{
“name”: “Getting Started Example”,
“version”: “1.0”,
“description”: “Build an Extension!”,
“manifest_version”: 2
}

Now, let’s update the sample file with a little more information about our extension. We’ll want to change only the first three values of this code: name, version, and description. Let’s fill in our name and a one-line description, and since this is our first version, let’s keep that value at 1.0. The manifest_version number should be kept the same.

{  "name": "WhatsApp Spammer",  "version": "1.0",  "manifest_version": 2,  "description": "Spamming Foes",}

Next, we’re going to add a few lines to tell Chrome what to do with this extension:-

Background Scripts

Official Documentation for Background Scripts.

Extensions are event based programs used to modify or enhance the Chrome browsing experience. Events are browser triggers, such as navigating to a new page, removing a bookmark, or closing a tab. Extensions monitor these events in their background script, then react with specified instructions.

We’ll be using background scripts to add an event listener to the onMessage event.

This will allow us to run code when the extension receives a particular message. We'll use this event to Inject our script to send repetitive messages.

"background": {"scripts": ["assets/js/background.js"],"persistent": false}

Why is persistent marked as false? As the documentation states:

The only occasion to keep a background script persistently active is if the extension uses chrome.webRequest API to block or modify network requests. The webRequest API is incompatible with non-persistent background pages.

Permissions

Official Documentation for Permissions.

To use most chrome APIs, your extension or app must declare its intent in the “permissions” field of the manifest.

For example, if you would like to use Chrome’s Storage API, you’ll have to request permission for storage. We will also use activeTab for our bot.

"permissions": ["activeTab","storage"]

Browser Action

Official Documentation for Browser Action.

Use browser actions to put icons in the main Google Chrome toolbar, to the right of the address bar. In addition to its icon , a browser action can have a tooltip , a badge, and a popup.

We will be using default_popup to initialize our html to act as popup once a user clicks on extension.

"browser_action": {"default_title": "WhatsApp Spammer","default_popup": "popup.html"}

Icons

Finally We will include our icon. Anything without an icon looks dull. Right?

"icons":{"128":"/assets/icons/icon.png"}

Customizing Your Background Script

The first thing we should do is set some default data using chrome’s storage api.

The two methods you need to know about for this tutorial are:

chrome.storage.sync.set({ key: value },()=> {
console.log('Value is set to ' + value);
});

chrome.storage.sync.get(['key'],(result)=> {
console.log('Value currently is ' + result.key);
});

The second parameter for each method is a callback function once the storage operation is complete, We'll be leveraging this in content script to initialize our message and count.

Let’s open up background.js and add an event onMessage for when an extension receives a message:

chrome.runtime.onMessage.addListener((message, sender)=> {if(!message.manageMischief) return;  else{chrome.tabs.executeScript({    file: 'assets/js/inject.js'  }); }});

In the above code, we are doing the following:

  1. add a onMessage Event to receive a message when a method sendMessage is called.
  2. Execute Content Script if the Message received is manageMischief.

Customizing Your popup.js File

In chrome apps/extensions, Content Security Policy does not allow inline JavaScript. So you have to put your JavaScript in a .js file and include it in your HTML.

Let’s Initialize a click Event Listener to perform our storage operation.

let button = document.getElementById("injectMessage");button.addEventListener("click", async ()=> {  let message = document.getElementById("message").value;  let count = document.getElementById("count").value;  chrome.storage.sync.set({"message": message});  chrome.storage.sync.set({"count": count});  chrome.runtime.sendMessage({"manageMischief": true});});

In the above code, we are doing the following:

  1. Adding click Event Listener to our button.
  2. Storing Message & Count input field values into local storage with key values as ”message"& ”count"respectively.
  3. Adding sendMessage method to chrome.runtime which listens & responds to events in app or extension life cycle.

If you try to send more than 200 messages, Then your browser is more likely to crash. To solve this we need to validate our input data.

So let’s add validation to our button event listener :-

We will be doing three validations such as ”empty fields", ”count greater than 200"and ”count less than 1"(Just in case). If you want you can remove ”count less than 1" validation from your code if it seems unlikely.

sleep = async (ms)=>{ return new Promise(resolve => setTimeout(resolve, ms)); }let validation = document.getElementById("validation");if(!message || !count){  validation.innerHTML="Some fields are Empty!";  validation.className = "show"; await sleep(2000);  validation.className = validation.className.replace("show", "");}else if(count>200){  validation.innerHTML="Count Limit 200";  validation.className = "show"; await sleep(3000);  validation.className = validation.className.replace("show", "");  count=200;}else if(count<1){  validation.innerHTML="Count Limit set to 1";  validation.className = "show"; await sleep(3000);  validation.className = validation.className.replace("show", "");  count=1;}

In the above code, we are doing the following:

  1. Initializing asynchronous sleep function to call it every time we need to use setTimeout method. Cause writing sleep(ms) instead of setTimeout(()=>{ //Your code// }), ms) seems right and yeah also saves you from callback hell and it performs in Sequential Manner instead of Parallel Manner.
  2. Adding validation text to your Toast message div using innerHTML Property.
  3. Adding class show to our Toast message div so that it overwrites the visibility property to visible. Later using sleep function we replace show class with empty string so that it’s visibility property goes back to hidden.
  4. Finally we specify the validation count i.e. 200 or 1.
setTimeout(function () { alert(1); }, 1000);
alert(2);

setTimeout is a Asynchronous Method. So it will first alert 2 and then after 1 sec it will alert 1. Which is what you don’t want. If you need multiple time intervals then you need to precisely match their timings as they perform in a parallel manner.

sleep = async (ms)=>{ return new Promise(resolve => setTimeout(resolve, ms)); }

Asynchronous sleep function returns a promise and waits for it to get resolved. Thus it is executed sequentially instead of parallel execution. This makes next execution wait until previous execution is finished. Thus we don’t have to match our setTimeout time every time.

chrome.storage.sync.set({"message": message});chrome.storage.sync.set({"count": count});

This methods used to store data on your local storage will keep the data and will increment the new data the next time you use it like a tube.

chrome.storage is not a big truck. It's a series of tubes. And if you don't understand, those tubes can be filled, and if they are filled when you put your message in, it gets in line, and it's going to be delayed by anyone that puts into that tube enormous amounts of material.

Thus, it’s a good practice to clear out your storage if it’s not needed anymore. To do so we will add a line of code at the beginning of our js file so every time the extension is called, it will first clear out the storage.

chrome.storage.sync.clear();

A bonus step just to display process of ”sending" in our button. If you want, you can skip down to the section on customizing your Content Script file.

Let’s write a Asynchronous sending function which will do our biding.

sending = async ()=>{  button.innerHTML="sending."; await sleep(500);  button.innerHTML="sending.."; await sleep(500);  button.innerHTML="sending..."; await sleep(500);  button.innerHTML="Mischief Managed!"; await sleep(1500);  button.innerHTML="send";}

This function will overwrite the innerHTML text of our button in specified time interval.

Customizing Your Content Script File

Official Documentation for Content Script.

Content scripts are files that run in the context of web pages. By using the standard Document Object Model (DOM), they are able to read details of the web pages the browser visits, make changes to them and pass information to their parent extension.

Essentially, any script you would like to actually run on a given page, needs to leverage this api. In our example, we'll be injecting our message on WhatsApp Web of the active tab and start sending them repetitively.

There are two ways you could inject your Content Script.

First you can include it in your manifest.json.

"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["assets/js/inject.js"]
}
]

Second instead of including in your manifest.json you could call it inside your background.js File using chrome.tabs.executeScript() Function.

chrome.tabs.executeScript({  file: 'assets/js/inject.js'});

So as I explained in our background.js section, We will be using the second method.

Now that we have a popup.html with a way to send messages, let's now implement the injection of this message inside WhatsApp web Page.

So Finally it’s time for our long-awaited part of this article. So let’s go ahead and open up the assets/js/inject.js file and add a Mouse Event.

eventFire = async(MyElement, ElementType)=>{  let MyEvent = document.createEvent("MouseEvents");  MyEvent.initMouseEvent(ElementType, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  MyElement.dispatchEvent(MyEvent);}

In the above code, we are doing the following:

  1. Initializing asynchronous eventFire function with two parameters. MyElement :- This defines the Element of web page on which we want to perform our action. ElementType:- The type of action that we want to perform such as ”click", ”mouseover", ”mousemove", etc.
  2. Adding a Mouse Event and using initMOuseEvent method on it to set the event before it is dispatched.
  3. Using dispatchEvent Method to dispatch the following Event task. Once dispatched, it doesn’t do anything anymore.

Now our Mouse Event is functional. Let’s add our inject function to perform our long-awaited task. Yes the time has come to inject our message!

inject = async()=>{  let resolveCount = new Promise(function(resolve, reject){    chrome.storage.sync.get({"count": true}, function(options){ resolve(options.count); })  });  let resolveMessage = new Promise(function(resolve, reject){    chrome.storage.sync.get({"message": true}, function(options){ resolve(options.message); })  });  let counter = await resolveCount; 

let message = await resolveMessage;

let messageBox = document.querySelectorAll("[contenteditable='true']")[1];
for (i = 0; i < counter; i++) { event = document.createEvent("UIEvents"); messageBox.innerHTML = message; event.initUIEvent("input", true, true, window, 1); messageBox.dispatchEvent(event); if(message && counter){ eventFire(document.querySelector('span[data-icon="send"]'), 'click'); } }}

In the above code, we are doing the following:

  1. We are creating two promises to resolve our chrome.storage.sync.get method. Then we await till it gets resolved and then we initialize them. Why we do this instead of simply returning it? Cause The chrome.storage API is asynchronous - it doesn’t return it directly, rather passing it as an argument to the callback function. The function call itself always returns undefined.
  2. Next we inspect the WhatsApp Web page and select the element with whom we want to play with. In our case ”contenteditable='true'" is the div property of message box.
  3. Now that we have our message & count from our popup.html and messageBox having element of WhatsApp Web message box, We create a loop, based on our count and create UIEvents.
  4. We change WhatsApp Web mesasge box div text using innerHTML with our message and add initUIEvent() method to our UIEvent Event. Next we use dispatchEvent Method to dispatch the following Event task. Once dispatched, it doesn’t do anything anymore.
  5. Now we inspect again and find send button element. In our case ”span[data-icon="send"]" is our element. Now we call eventFire Function and pass MyElement as ”span[data-icon="send"]" and ElementType as ”click" which perform click event on the specified element.

And that’s it! Your Extension will work like this:

Your very own WhatsApp Spam Bot :)

I hope this article at least got you interested in Chrome Extensions and gave you some knowledge about Modern JavaScript and the best practices you need to follow. We only scratched the surface. Feel free to use any of the code in my repository mentioned at top for any purpose.

--

--

Akash Pawara
The Startup

An engineer who deeps down into cosmology, addicted to K-pop(Lisa is kawaii), an outgoing person(traveling/trecking), cat? dog? (why not both? ❤)