How to Build a Chrome Extension to Spam on WhatsApp using Vanilla JavaScript
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.
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:
- add a
onMessage
Event to receive a message when a methodsendMessage
is called. - 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:
- Adding
click
Event Listener to our button. - Storing Message & Count input field values into local storage with key values as
”message"
&”count"
respectively. - Adding
sendMessage
method tochrome.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:
- Initializing
asynchronous
sleep function to call it every time we need to usesetTimeout
method. Cause writing sleep(ms) instead of setTimeout(()=>{ //Your code// }), ms) seems right and yeah also saves you fromcallback hell
and it performs in Sequential Manner instead of Parallel Manner. - Adding validation text to your Toast message div using
innerHTML
Property. - Adding class
show
to our Toast message div so that it overwrites the visibility property tovisible
. Later using sleep function we replace show class with empty string so that it’s visibility property goes back tohidden
. - 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:
- 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. - Adding a Mouse Event and using
initMOuseEvent
method on it to set the event before it is dispatched. - 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:
- 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 Thechrome.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 returnsundefined
. - 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. - 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 createUIEvents
. - We change WhatsApp Web mesasge box div text using
innerHTML
with our message and addinitUIEvent()
method to ourUIEvent
Event. Next we usedispatchEvent
Method to dispatch the following Event task. Once dispatched, it doesn’t do anything anymore. - Now we inspect again and find send button element. In our case
”span[data-icon="send"]"
is our element. Now we calleventFire
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:
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.