Skip to content

Instantly share code, notes, and snippets.

@mzahidriaz
Last active April 26, 2024 08:11
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mzahidriaz/4c5404fe24e3c6a00d7bd82b3ca328e7 to your computer and use it in GitHub Desktop.
Save mzahidriaz/4c5404fe24e3c6a00d7bd82b3ca328e7 to your computer and use it in GitHub Desktop.
WhatsApp Group Contacts Export: This will download the members of group with their phone number, whatsapp name and if contact is stored on phone

WhatsApp Group Contacts Export (Personal Project)

This will download the members of group with their phone number, whatsapp name and if contact is stored on phone, it will export the name as well.

Go to web.whatsapp.com. Copy-paste the code into your browser console.

(async () => {
  const contactFinder = new ContactFinder("YOUR GROUP SEARCH STRING OR GROUP TITLE");
  const members = await contactFinder.getGroupMembers(); // This will return a JS Map Object
  console.log(members);

  // OR 
  await contactFinder.downloadMembersAsCSV(); // This will download the contacts as CSV
})();


This code uses data stored in indexDB by WhatsApp and search through indexDb collections to collect the required data

Side Note : This code snippet is shared only for educational purpose and the code might not be maintained anymore. Developer is not responsible for any Privacy/Security voilations. Also no data is being sent to anywhere, everything is stored and fetched from local browser tab.

Sample Image

class ContactFinder {
#db;
#chatToFind;
#dbName = "model-storage";
#chatsCol = "chat";
#contactCol = "contact";
#groupCol = "participant";
constructor(chatGroupName) {
this.#chatToFind = chatGroupName;
}
async openConnection() {
if (!this.#db) {
const dbName = this.#dbName;
this.#db = await new Promise((resolve, reject) => {
let request = indexedDB.open(dbName);
request.onerror = (event) => {
reject(event);
};
request.onsuccess = (event) => {
resolve(event.target.result);
};
});
}
return this.#db;
}
async #promisifyCol(collection, query, count) {
const db = await this.openConnection();
return new Promise((resolve, reject) => {
const request = db.transaction(collection).objectStore(collection).getAll(query, count);
request.onerror = (event) => {
reject(event);
};
request.onsuccess = (event) => {
resolve(event.target.result);
};
});
}
async #getChats() {
const allChats = await this.#promisifyCol(this.#chatsCol);
const chatToFind = this.#chatToFind;
return allChats.filter((chat) => {
return chat.name && chat.name.includes(chatToFind);
});
}
async #getGroups() {
const chats = (await this.#getChats()).map((chat) => chat.id);
const allGroups = await this.#promisifyCol(this.#groupCol);
return allGroups.filter((group) => {
return group.groupId && chats.includes(group.groupId);
});
}
async #getGroupParticipants() {
const groups = await this.#getGroups();
const map = new Map();
groups.forEach((group) => {
group.participants.forEach((par) => {
const num = par.replace("@c.us", "");
map.set(num, num);
});
});
return map;
}
async #getContacts() {
return this.#promisifyCol(this.#contactCol);
}
async getGroupMembers() {
const members = await this.#getGroupParticipants();
const contacts = await this.#getContacts();
contacts.forEach((contact) => {
var num;
if (contact.phoneNumber) {
num = contact.phoneNumber.split("@")[0];
} else if (contact.id) {
num = contact.id.split("@")[0];
}
if (num && members.get(num)) {
members.set(num, {
phoneNum: num,
name: contact.name,
pushname: contact.pushname,
});
}
});
return members;
}
async downloadMembersAsCSV() {
const members = await this.getGroupMembers();
let csvContent = "data:text/csv;charset=utf-8,";
for (const [key, value] of members.entries()) {
const values = [value.phoneNum];
if (value.name) values.push(value.name);
if (value.pushname) values.push(value.pushname);
let row = values.join(",");
csvContent += row + "\r\n";
}
var link = document.createElement("a");
link.setAttribute("href", encodeURI(csvContent));
link.setAttribute("download", "my_data.csv");
document.body.appendChild(link); // Required for FF
link.click();
}
}
@LavieNroseee
Copy link

Is it possible to get the date when he joined the group? I want to make a filter between dates to identify new contacts.

@mzahidriaz
Copy link
Author

mzahidriaz commented Mar 6, 2023 via email

@rlandaburu
Copy link

rlandaburu commented Apr 24, 2023

I have this error: let row = values.join(","); is not a function

@timofeen
Copy link

Sorry, I don't understand what do I have to do with WhatsAppGroupContactExport.js file. Or just by pasting the first code it must work?

@herduin
Copy link

herduin commented Aug 15, 2023

I created this variant for a bookmarket to ease use, this is the bookmarket Get Contacts:

And this is the code:

// https://www.jspacker.org/

var _contacts = [];
class ContactFinder {
  #db;
  #chatToFind;
  #dbName = "model-storage";
  #chatsCol = "chat";
  #contactCol = "contact";
  #groupCol = "participant";

  constructor(chatGroupName) {
    this.#chatToFind = chatGroupName;
  }

  async openConnection() {
    if (!this.#db) {
      const dbName = this.#dbName;
      this.#db = await new Promise((resolve, reject) => {
        let request = indexedDB.open(dbName);
        request.onerror = (event) => {
          reject(event);
        };
        request.onsuccess = (event) => {
          resolve(event.target.result);
        };
      });
    }
    return this.#db;
  }

  async #promisifyCol(collection, query, count) {
    const db = await this.openConnection();
    return new Promise((resolve, reject) => {
      const request = db.transaction(collection).objectStore(collection).getAll(query, count);

      request.onerror = (event) => {
        reject(event);
      };
      request.onsuccess = (event) => {
        resolve(event.target.result);
      };
    });
  }

  async #getChats() {
    const allChats = await this.#promisifyCol(this.#chatsCol);
    const chatToFind = this.#chatToFind;
    return allChats.filter((chat) => {
      return chat.name && chat.name.includes(chatToFind);
    });
  }

  async #getGroups() {
    const chats = (await this.#getChats()).map((chat) => chat.id);
    const allGroups = await this.#promisifyCol(this.#groupCol);

    return allGroups.filter((group) => {
      return group.groupId && chats.includes(group.groupId);
    });
  }

  async #getGroupParticipants() {
    const groups = await this.#getGroups();
    const map = new Map();

    groups.forEach((group) => {
      group.participants.forEach((par) => {
        const num = par.replace("@c.us", "");
        map.set(num, num);
      });
    });

    return map;
  }

  async #getContacts() {
    return this.#promisifyCol(this.#contactCol);
  }

  async getGroupMembers() {
    const members = await this.#getGroupParticipants();
    const contacts = await this.#getContacts();

    contacts.forEach((contact) => {
      var num;
      if (contact.phoneNumber) {
        num = contact.phoneNumber.split("@")[0];
      } else if (contact.id) {
        num = contact.id.split("@")[0];
      }
      if (num && members.get(num)) {
        var m = {
          phoneNum: num,
          name: contact.name,
          pushname: contact.pushname,
        };
        members.set(num, m);
        _contacts.push(m);
      }
    });
    return members;
  }


  async downloadMembersAsCSV() {
    const members = await this.getGroupMembers();
    let csvContent = "data:text/csv;charset=utf-8,";
        csvContent += '"Phone","Name","Push Name"\r\n';

    for (const [key, value] of members.entries()) {
      const values = [
        value.phoneNum || "",
        value.name || "",
        value.pushname || "",
      ];
      const row = values.map((value) => `"${value}"`).join(",");
      csvContent += row + "\r\n";
    }
    console.log(csvContent);

    const encodedUri = encodeURI(csvContent);
    var link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", "WhatsAppContacts.csv");
    document.body.appendChild(link); // Required for FF
    link.click();
  }



}

(async () => {
  const contactFinder = new ContactFinder(window.prompt("Nombre del grupo:"));
  const members = await contactFinder.getGroupMembers(); // This will return a JS Map Object
  await contactFinder.downloadMembersAsCSV(); // This will download the contacts as CSV
  console.log(_contacts);
})();

@HarpurLiam
Copy link

Very Nice @herduin

@durga18584
Copy link

@herduin Thanks for the idea.
I have created an extension using this tool
https://sandbox.self.li/bookmarklet-to-extension/
It's working fine.

@reinaldomml
Copy link

Thank you @herduin

@otema666
Copy link

Congrats! @herduin

@chandimasw
Copy link

thank you @herduin. Works Great.

@ftonato
Copy link

ftonato commented Dec 27, 2023

Very Nice @herduin

@benh1979
Copy link

I created this variant for a bookmarket to ease use, this is the bookmarket Get Contacts:

And this is the code:

// https://www.jspacker.org/

var _contacts = [];
class ContactFinder {
  #db;
  #chatToFind;
  #dbName = "model-storage";
  #chatsCol = "chat";
  #contactCol = "contact";
  #groupCol = "participant";

  constructor(chatGroupName) {
    this.#chatToFind = chatGroupName;
  }

  async openConnection() {
    if (!this.#db) {
      const dbName = this.#dbName;
      this.#db = await new Promise((resolve, reject) => {
        let request = indexedDB.open(dbName);
        request.onerror = (event) => {
          reject(event);
        };
        request.onsuccess = (event) => {
          resolve(event.target.result);
        };
      });
    }
    return this.#db;
  }

  async #promisifyCol(collection, query, count) {
    const db = await this.openConnection();
    return new Promise((resolve, reject) => {
      const request = db.transaction(collection).objectStore(collection).getAll(query, count);

      request.onerror = (event) => {
        reject(event);
      };
      request.onsuccess = (event) => {
        resolve(event.target.result);
      };
    });
  }

  async #getChats() {
    const allChats = await this.#promisifyCol(this.#chatsCol);
    const chatToFind = this.#chatToFind;
    return allChats.filter((chat) => {
      return chat.name && chat.name.includes(chatToFind);
    });
  }

  async #getGroups() {
    const chats = (await this.#getChats()).map((chat) => chat.id);
    const allGroups = await this.#promisifyCol(this.#groupCol);

    return allGroups.filter((group) => {
      return group.groupId && chats.includes(group.groupId);
    });
  }

  async #getGroupParticipants() {
    const groups = await this.#getGroups();
    const map = new Map();

    groups.forEach((group) => {
      group.participants.forEach((par) => {
        const num = par.replace("@c.us", "");
        map.set(num, num);
      });
    });

    return map;
  }

  async #getContacts() {
    return this.#promisifyCol(this.#contactCol);
  }

  async getGroupMembers() {
    const members = await this.#getGroupParticipants();
    const contacts = await this.#getContacts();

    contacts.forEach((contact) => {
      var num;
      if (contact.phoneNumber) {
        num = contact.phoneNumber.split("@")[0];
      } else if (contact.id) {
        num = contact.id.split("@")[0];
      }
      if (num && members.get(num)) {
        var m = {
          phoneNum: num,
          name: contact.name,
          pushname: contact.pushname,
        };
        members.set(num, m);
        _contacts.push(m);
      }
    });
    return members;
  }


  async downloadMembersAsCSV() {
    const members = await this.getGroupMembers();
    let csvContent = "data:text/csv;charset=utf-8,";
        csvContent += '"Phone","Name","Push Name"\r\n';

    for (const [key, value] of members.entries()) {
      const values = [
        value.phoneNum || "",
        value.name || "",
        value.pushname || "",
      ];
      const row = values.map((value) => `"${value}"`).join(",");
      csvContent += row + "\r\n";
    }
    console.log(csvContent);

    const encodedUri = encodeURI(csvContent);
    var link = document.createElement("a");
    link.setAttribute("href", encodedUri);
    link.setAttribute("download", "WhatsAppContacts.csv");
    document.body.appendChild(link); // Required for FF
    link.click();
  }



}

(async () => {
  const contactFinder = new ContactFinder(window.prompt("Nombre del grupo:"));
  const members = await contactFinder.getGroupMembers(); // This will return a JS Map Object
  await contactFinder.downloadMembersAsCSV(); // This will download the contacts as CSV
  console.log(_contacts);
})();

@herduin good stuff.. This worked for me but only 520 out of 850 wa group contacts were successfully exported.. is there a fix?

@mzahidriaz
Copy link
Author

@benh1979 the original script which I shared is downloading all the contacts, I tried with 1025 members and it's working fine

@mzahidriaz
Copy link
Author

@rlandaburu this issue has been fixed.
Thank you for reporting.

@Eraldo
Copy link

Eraldo commented Mar 4, 2024

Is there an easy way to iterate over all groups? 😅

@ChaT0n
Copy link

ChaT0n commented Apr 9, 2024

Hello, can u put a small video or text explaining how to use it ? cause i tried but didn't succeed
I've tried using devtools in chrome and putting the code in the console, a file was downloaded but with 0 data
Thanks

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