Skip to content

Instantly share code, notes, and snippets.

@shaneapen
Last active November 19, 2023 06:03
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save shaneapen/3406477b9f946855d02e3f33ec121975 to your computer and use it in GitHub Desktop.
Save shaneapen/3406477b9f946855d02e3f33ec121975 to your computer and use it in GitHub Desktop.
WhatsApp Group Contacts Exporter

WhatsApp Group Contacts Exporter (WAXP) by codegena.com

  1. Go to web.whatsapp.com and select the group from which you want to export contacts.
  2. Copy-paste the code into your browser console.
  3. Type WAXP and hit enter to see the available methods.
  4. Execute WAXP.quickExport() to export unsaved phone numbers alone quickly or WAXP.start() to export contacts with names and status.

Side Note : This code snippet is shared only for educational purpose and the code is not maintained anymore. Pro users who requires contact exports on a daily basis can check WAXP chrome extension

WAXP = (function(){
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var SCROLL_INTERVAL = 600,
SCROLL_INCREMENT = 450,
AUTO_SCROLL = true,
NAME_PREFIX = '',
UNKNOWN_CONTACTS_ONLY = false,
MEMBERS_QUEUE = {},
TOTAL_MEMBERS;
var scrollInterval, observer, membersList, header;
console.log("%c WhatsApp Group Contacts Exporter ","font-size:24px;font-weight:bold;color:white;background:green;");
var start = function(){
membersList = document.querySelectorAll('span[title=You]')[0]?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode;
header = document.getElementsByTagName('header')[0];
if(!membersList){
document.querySelector("#main > header").firstChild.click();
membersList = document.querySelectorAll('span[title=You]')[0]?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode;
header = document.getElementsByTagName('header')[0];
}
observer = new MutationObserver(function (mutations, observer) {
scrapeData(); // fired when a mutation occurs
});
// the div to watch for mutations
observer.observe(membersList, {
childList: true,
subtree: true
});
TOTAL_MEMBERS = membersList.parentElement.parentElement.querySelector('span').innerText.match(/\d+/)[0]*1;
// click the `n more` button to show all members
document.querySelector("span[data-icon=down]")?.click()
//scroll to top before beginning
header.nextSibling.scrollTop = 100;
scrapeData();
if(AUTO_SCROLL) scrollInterval = setInterval(autoScroll, SCROLL_INTERVAL);
}
/**
* Function to autoscroll the div
*/
var autoScroll = function (){
if(!utils.scrollEndReached(header.nextSibling))
header.nextSibling.scrollTop += SCROLL_INCREMENT;
else
stop();
};
/**
* Stops the current scrape instance
*/
var stop = function(){
window.clearInterval(scrollInterval);
observer.disconnect();
console.log(`%c Extracted [${utils.queueLength()} / ${TOTAL_MEMBERS}] Members. Starting Download..`,`font-size:13px;color:white;background:green;border-radius:10px;`)
downloadAsCSV(['Name','Phone','Status']);
}
/**
* Function to scrape member data
*/
var scrapeData = function () {
var contact, status, name;
var memberCard = membersList.querySelectorAll(':scope > div');
for (let i = 0; i < memberCard.length; i++) {
status = memberCard[i].querySelectorAll('span[title]')[1] ? memberCard[i].querySelectorAll('span[title]')[1].title : "";
contact = scrapePhoneNum(memberCard[i]);
name = scrapeName(memberCard[i]);
if (contact.phone!='NIL' && !MEMBERS_QUEUE[contact.phone]) {
if (contact.isUnsaved) {
MEMBERS_QUEUE[contact.phone] = { 'Name': NAME_PREFIX + name,'Status': status };
continue;
} else if (!UNKNOWN_CONTACTS_ONLY) {
MEMBERS_QUEUE[contact.phone] = { 'Name': name, 'Status': status };
}
}else if(MEMBERS_QUEUE[contact.phone]){
MEMBERS_QUEUE[contact.phone].Status = status;
}
if(utils.queueLength() >= TOTAL_MEMBERS) {
stop();
break;
}
//console.log(`%c Extracted [${utils.queueLength()} / ${TOTAL_MEMBERS}] Members `,`font-size:13px;color:white;background:green;border-radius:10px;`)
}
}
/**
* Scrapes phone no from html node
* @param {object} el - HTML node
* @returns {string} - phone number without special chars
*/
var scrapePhoneNum = function(el){
var phone, isUnsaved = false;
if (el.querySelector('img') && el.querySelector('img').src.match(/u=[0-9]*/)) {
phone = el.querySelector('img').src.match(/u=[0-9]*/)[0].substring(2).replace(/[+\s]/g, '');
} else {
var temp = el.querySelector('span[title]').getAttribute('title').match(/(.?)*[0-9]{3}$/);
if(temp){
phone = temp[0].replace(/\D/g,'');
isUnsaved = true;
}else{
phone = 'NIL';
}
}
return { 'phone': phone, 'isUnsaved': isUnsaved };
}
/**
* Scrapes name from HTML node
* @param {object} el - HTML node
* @returns {string} - returns name..if no name is present phone number is returned
*/
var scrapeName = function (el){
var expectedName;
expectedName = el.firstChild.firstChild.childNodes[1].childNodes[1].childNodes[1].querySelector('span').innerText;
if(expectedName == ""){
return el.querySelector('span[title]').getAttribute('title'); //phone number
}
return expectedName;
}
/**
* A utility function to download the result as CSV file
* @References
* [1] - https://stackoverflow.com/questions/4617935/is-there-a-way-to-include-commas-in-csv-columns-without-breaking-the-formatting
*
*/
var downloadAsCSV = function (header) {
var groupName = document.querySelectorAll("#main > header span")[1].title;
var fileName = groupName.replace(/[^\d\w\s]/g,'') ? groupName.replace(/[^\d\w\s]/g,'') : 'WAXP-group-members';
var name = `${fileName}.csv`, data = `${header.join(',')}\n`;
if(utils.queueLength() > 0){
for (key in MEMBERS_QUEUE) {
// Wrapping each variable around double quotes to prevent commas in the string from adding new cols in CSV
// replacing any double quotes within the text to single quotes
if(header.includes('Status'))
data += `"${MEMBERS_QUEUE[key]['Name']}","${key}","${MEMBERS_QUEUE[key]['Status'].replace(/\"/g,"'")}"\n`;
else
data += `"${MEMBERS_QUEUE[key]['Name']}","${key}"\n`;
}
utils.createDownloadLink(data,name);
}else{
alert("Couldn't find any contacts with the given options");
}
}
/**
* Scrape contacts instantly from the group header.
* Saved Contacts cannot be exchanged for numbers with this method.
*/
var quickExport = function(){
var members = document.querySelectorAll("#main > header span")[2].title.replace(/ /g,'').split(',');
var groupName = document.querySelectorAll("#main > header span")[1].title;
var fileName = groupName.replace(/[^\d\w\s]/g,'') ? groupName.replace(/[^\d\w\s]/g,'') : 'WAXP-group-members';
fileName = `${fileName}.csv`;
members.pop(); //removing 'YOU' from array
MEMBERS_QUEUE = {};
for (i = 0; i < members.length; ++i) {
if (members[i].match(/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/)) {
MEMBERS_QUEUE[members[i]] = {
'Name': NAME_PREFIX + members[i]
};
continue;
} else if (!UNKNOWN_CONTACTS_ONLY) {
MEMBERS_QUEUE[members[i]] = {
'Name': members[i]
};
}
}
downloadAsCSV(['Name','Phone']);
}
/**
* Helper functions
* @References [1] https://stackoverflow.com/questions/53158796/get-scroll-position-with-reactjs/53158893#53158893
*/
var utils = (function(){
return {
scrollEndReached: function(el){
if((el.scrollHeight - (el.clientHeight + el.scrollTop)) == 0)
return true;
return false;
},
queueLength: function() {
var size = 0, key;
for (key in MEMBERS_QUEUE) {
if (MEMBERS_QUEUE.hasOwnProperty(key)) size++;
}
return size;
},
createDownloadLink: function (data,fileName) {
var a = document.createElement('a');
a.style.display = "none";
var url = window.URL.createObjectURL(new Blob([data], {
type: "data:attachment/text"
}));
a.setAttribute("href", url);
a.setAttribute("download", fileName);
document.body.append(a);
a.click();
window.URL.revokeObjectURL(url);
a.remove();
}
}
})();
// Defines the WAXP interface following module pattern
return {
start: function(){
MEMBERS_QUEUE = {}; //reset
try {
start();
} catch (error) {
//TO overcome below error..but not sure of any sideeffects
//TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
console.log(error, '\nRETRYING in 1 second')
setTimeout(start, 1000);
}
},
stop: function(){
stop()
},
options: {
// works for now...but consider refactoring it provided better approach exist
set NAME_PREFIX(val){ NAME_PREFIX = val },
set SCROLL_INTERVAL(val){ SCROLL_INTERVAL = val },
set SCROLL_INCREMENT(val){ SCROLL_INCREMENT = val },
set AUTO_SCROLL(val){ AUTO_SCROLL = val },
set UNKNOWN_CONTACTS_ONLY(val){ UNKNOWN_CONTACTS_ONLY = val },
// getter
get NAME_PREFIX(){ return NAME_PREFIX },
get SCROLL_INTERVAL(){ return SCROLL_INTERVAL },
get SCROLL_INCREMENT(){ return SCROLL_INCREMENT },
get AUTO_SCROLL(){ return AUTO_SCROLL },
get UNKNOWN_CONTACTS_ONLY(){ return UNKNOWN_CONTACTS_ONLY },
},
quickExport: function(){
quickExport();
},
debug: function(){
return {
size: utils.queueLength(),
q: MEMBERS_QUEUE
}
}
}
})();
@vishalparkar
Copy link

It worked for me ! Thanks a ton !

@nicoausnrw
Copy link

For all the have a error.
It is maybe because you don't have Whatsapp on english. But there is a easy fix. Just go to Row :19, :24 and translate the "You" in your language.

document.querySelectorAll('span[title=You]')[0]?

Hope this help somebody

@shaneapen
Copy link
Author

For all the have a error.
It is maybe because you don't have Whatsapp on english. But there is a easy fix. Just go to Row :19, :24 and translate the "You" in your language.

document.querySelectorAll('span[title=You]')[0]?

Hope this help somebody

Hey @nicoausnrw! Thanks for pointing out that!

@engrUmerMalik
Copy link

Thanks dear for sharing such a precious thing. It worked perfectly for me.

@shaneapen
Copy link
Author

@engrUmerMalik Glad, you found it useful!

@EDENNEW
Copy link

EDENNEW commented Jun 16, 2021

Couldn't find any contacts with the given options

@shaneapen
Copy link
Author

Couldn't find any contacts with the given options

@EDENNEW Did you open a group first? What's the error you are seeing in the console?

@Thenuwan
Copy link

Couldn't find any contacts with the given options

@EDENNEW Did you open a group first? What's the error you are seeing in the console?

yas,but i want js bro i want send bulk msg.

@RafaelRRRR
Copy link

Uncaught TypeError: Cannot read properties of undefined (reading 'title')
at quickExport (:186:74)
at Object.quickExport (:281:17)
at :1:6

HELP ME

@godfredaddai1
Copy link

It gives me this message "Couldn't find any contacts with the given options".

Any help please?

@NKcool
Copy link

NKcool commented Oct 31, 2022

Couldn't find any contacts with the given options
please help ?

@NKcool
Copy link

NKcool commented Oct 31, 2022

the error shows in alert box

@milandeepak
Copy link

TypeError: Cannot read properties of null (reading '0')
at start (:38:109)
at Object.start (:255:22)
at :1:6 '\nRETRYING in 1 second'

@Sudhir-26
Copy link

serviceworker.js:1 Uncaught (in promise) TypeError: Failed to execute 'put' on 'Cache': Partial response (status code 206) is unsupported
at serviceworker.js:1:47881
at Generator.next ()
at t (serviceworker.js:1:32220)
at c (serviceworker.js:1:32431)

@Sudhir-26
Copy link

Any help for this error

@rake213
Copy link

rake213 commented Nov 15, 2022

its work for me thanks

@iabarragan
Copy link

Great tool. All functions work smoothly except for one.

I purchased the paid Chrome extension and a bug with the Export All Groups (beta) function has given me a headache. It dies when two or more WhatsApp groups have the same name.

It seems it also dies when a group name from another WhatsApp account is duplicated. I mean, I was extracting contacts from more than 30 phones and when Phone-1-Group-X was extracted, it remanined in cache. So when I tried to extract all groups from Phone-2, including a group called Group-X, it died again no matter that group name was not duplicated within Phone-2. In other words, the problem seems to be with cached data.

In order to bypass the problem, you have to:

  1. Rename or delete the repeated groups
  2. Logout from WhatsApp web
  3. Clear your browser's cache
  4. Restart your browser
  5. Log in back to WhatsApp web

Fix that and let's go for a beer.

@shaneapen
Copy link
Author

Great tool. All functions work smoothly except for one.

I purchased the paid Chrome extension and a bug with the Export All Groups (beta) function has given me a headache. It dies when two or more WhatsApp groups have the same name.

It seems it also dies when a group name from another WhatsApp account is duplicated. I mean, I was extracting contacts from more than 30 phones and when Phone-1-Group-X was extracted, it remanined in cache. So when I tried to extract all groups from Phone-2, including a group called Group-X, it died again no matter that group name was not duplicated within Phone-2. In other words, the problem seems to be with cached data.

In order to bypass the problem, you have to:

  1. Rename or delete the repeated groups
  2. Logout from WhatsApp web
  3. Clear your browser's cache
  4. Restart your browser
  5. Log in back to WhatsApp web

Fix that and let's go for a beer.

@iabarragan Unfortunately, I'm not able to reproduce the issue by following the same steps. I can export duplicate named groups quite fine. Drop me an email on shaneapen@gmail.com and let's try to sort that out.

@moazzzzam
Copy link

Couldn't find any contacts with the given options

Error in an alert box.
Please Help

@sarcasticccccc
Copy link

I am getting an error @shaneapen i opened the group but i getting this error

Uncaught TypeError: Assignment to constant variable. at quickExport (<anonymous>:195:16) at Object.quickExport (<anonymous>:281:17) at <anonymous>:1:6

GET blob:https://web.whatsapp.com/5d135b96-d634-4c79-98ee-05c7e56b9e1d net::ERR_FILE_NOT_FOUND serviceworker.js:1

   ``Uncaught (in promise) TypeError: Failed to execute 'put' on 'Cache': Partial response (status code 206) is unsupported
at serviceworker.js:1:62491
at Generator.next (<anonymous>)
at t (serviceworker.js:1:32289)
at o (serviceworker.js:1:32500)``

@sarcasticccccc
Copy link

sarcasticccccc commented Dec 10, 2022

image
@shaneapen this is the error
i get again this
image

@blogissimmo
Copy link

Hi,
I've just tried to make it work on windows and I have the following error message:
image

TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
at start (:33:18)
at Object.start (:255:22)
at :1:6 '\nRETRYING in 1 second'

Anyone coule help me to fix it please?
many thanks in advance!

@lubianat
Copy link

I am getting the same error as sarcasticccccc here.

@singhrohit9525
Copy link

Getting this error while executing the code in mac. How to fix this??

TypeError: Cannot read properties of null (reading 'firstChild')
at start (:23:53)
at Object.start (:255:22)
at :1:6 '\nRETRYING in 1 second'

@jamesmurickan
Copy link

I am getting error while executing on Firefox on Ubuntu.

WAXP.start()
TypeError: membersList.parentElement.parentElement.querySelector(...).innerText.match(...) is null
start debugger eval code:38
start debugger eval code:255
debugger eval code:1

RETRYING in 1 second debugger eval code:259:30

@allskandal
Copy link

NOT WORK right now

@mzahidriaz
Copy link

WhatsApp Group Contacts Export
Check this one, I made this tool for my own convenience.

@imaginovationz
Copy link

not working

WAXP.quickExport();
VM179:186 Uncaught TypeError: Cannot read properties of undefined (reading 'title')
at quickExport (:186:74)
at Object.quickExport (:281:17)
at :1:6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment