Skip to content

Instantly share code, notes, and snippets.

@shaneapen

shaneapen/WAXP.js

Last active Sep 18, 2020
Embed
What would you like to do?
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.
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

This comment has been minimized.

Copy link

@vishalparkar vishalparkar commented Jul 11, 2020

It worked for me ! Thanks a ton !

@nicoausnrw

This comment has been minimized.

Copy link

@nicoausnrw nicoausnrw commented Jul 11, 2020

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

This comment has been minimized.

Copy link
Owner Author

@shaneapen shaneapen commented Jul 12, 2020

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.